codexgauss-seidel-two-variable-trajectory-contraction.py
gauss-seidel-two-variable-trajectory-contraction.py
"""Draw original exact-system trajectory and contraction; PNG goes to the CWD.
Tested with Python 3.14, matplotlib 3.10.7, and numpy 2.3.5.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
a = np.array([[4.0, 1.0], [1.0, 3.0]])
b = np.array([1.0, 2.0])
solution = np.linalg.solve(a, b)
gs, jacobi = [np.zeros(2)], [np.zeros(2)]
row_path = [gs[0].copy()]
for _ in range(9):
x_new = (1 - gs[-1][1]) / 4
row_path.append(np.array([x_new, gs[-1][1]]))
y_new = (2 - x_new) / 3
gs.append(np.array([x_new, y_new]))
row_path.append(gs[-1].copy())
jacobi.append(np.array([(1 - jacobi[-1][1]) / 4, (2 - jacobi[-1][0]) / 3]))
gs, jacobi, row_path = np.array(gs), np.array(jacobi), np.array(row_path)
k = np.arange(len(gs))
gs_error = np.linalg.norm(gs - solution, axis=1)
jacobi_error = np.linalg.norm(jacobi - solution, axis=1)
# An independent formula checks the plotted direct row updates.
np.testing.assert_allclose(gs_error[1:], 7 * np.sqrt(10) / (11 * 12.0 ** k[1:]),
rtol=3e-6, atol=1e-16)
blue, orange, gray = "#146b88", "#b05012", "#6b7280"
fig, (ax, err) = plt.subplots(1, 2, figsize=(11, 4.8), dpi=110, facecolor="white")
fig.suptitle("Fresh updates select a faster error mode", fontsize=18, y=0.98)
ax.set_title("Rows move one coordinate at a time", fontsize=13, pad=11)
x = np.linspace(-0.04, 0.30, 250)
ax.plot(x, 1 - 4*x, color=gray, alpha=0.55, linewidth=1.2, label=r"$4x+y=1$")
ax.plot(x, (2 - x)/3, color=gray, linestyle="--", alpha=0.8, linewidth=1.2,
label=r"$x+3y=2$")
ax.plot(row_path[:7, 0], row_path[:7, 1], color=blue, linewidth=2, alpha=0.85)
for start, end in zip(row_path[:4], row_path[1:5]):
ax.annotate("", xy=end, xytext=start,
arrowprops={"arrowstyle": "->", "color": blue, "lw": 1.7})
ax.scatter(gs[:4, 0], gs[:4, 1], color=blue, s=30, zorder=4, label="Sweep endpoints")
ax.scatter(*solution, marker="*", color=orange, s=120, zorder=5, label="Exact solution")
ax.annotate(r"$k=0$", gs[0], xytext=(5, 8), textcoords="offset points")
ax.annotate(r"$k=1:\ (1/4,7/12)$", gs[1], xytext=(-5, -25),
textcoords="offset points", ha="right", color=blue)
ax.annotate(r"$k=2$", gs[2], xytext=(25, 5), textcoords="offset points", color=blue,
arrowprops={"arrowstyle": "-", "color": blue})
ax.annotate(r"$(1/11,7/11)$", solution, xytext=(-48, 22), textcoords="offset points",
color=orange, arrowprops={"arrowstyle": "-", "color": orange})
ax.set(xlim=(-0.025, 0.30), ylim=(-0.04, 0.76), xlabel=r"$x$", ylabel=r"$y$")
ax.legend(loc="lower left", fontsize=9, framealpha=1)
ax.grid(alpha=0.15)
err.set_title("Computed error norms and exact decay", fontsize=13, pad=11)
err.semilogy(k, gs_error, "o", color=blue, markersize=5, label="Gauss–Seidel iterates")
err.semilogy(k[1:], 7*np.sqrt(10)/(11*12.0**k[1:]), "-", color=blue,
linewidth=1.6, label=r"$7\sqrt{10}/(11\cdot12^k),\ k\geq1$")
err.semilogy(k, jacobi_error, "s--", color=orange, markersize=4, linewidth=1.3,
label="Jacobi iterates (same start)")
err.text(0.04, 0.10, "Gauss–Seidel: factor 1/12 after sweep 1\n"
"Jacobi: factor 1/12 every two sweeps", transform=err.transAxes,
fontsize=10, bbox={"facecolor": "white", "edgecolor": "#d1d5db", "pad": 7})
err.set(xlabel=r"Completed sweeps $k$", ylabel=r"Forward error $\|z^{(k)}-z_*\|_2$",
xticks=np.arange(0, 10), ylim=(1e-10, 2))
err.legend(loc="upper right", fontsize=9)
err.grid(alpha=0.2, which="major")
fig.subplots_adjust(left=0.075, right=0.975, bottom=0.17, top=0.84, wspace=0.30)
fig.text(0.5, 0.04, "Original constructed system: A = [[4, 1], [1, 3]], b = [1, 2] • exact-arithmetic formulas",
ha="center", fontsize=10, color="#374151")
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()