codexjacobi-simultaneous-component-update.py
jacobi-simultaneous-component-update.py
"""Draw the information flow of two original three-variable update sweeps.
Tested with Python 3.14, Matplotlib 3.10.7 and NumPy 2.3.5.
Run from _media; emits this script's basename.png into the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch
BLUE = '#2563a6'
ORANGE = '#ba4e17'
INK = '#1e293b'
def box(ax, x, y, w, h, text, color):
ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle='round,pad=0.012',
facecolor='white', edgecolor=color, linewidth=1.7,
zorder=3))
ax.text(x+w/2, y+h/2, text, ha='center', va='center', fontsize=13,
color=INK, zorder=4)
def arrow(ax, start, end, color, bend=0):
ax.add_patch(FancyArrowPatch(start, end, arrowstyle='-|>', mutation_scale=14,
color=color, linewidth=1.8,
connectionstyle=f'arc3,rad={bend}', zorder=2))
def main():
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 12})
fig, axes = plt.subplots(1, 2, figsize=(11, 5.5), dpi=120, facecolor='white')
fig.subplots_adjust(left=.025, right=.975, top=.745, bottom=.15, wspace=.12)
fig.suptitle('Same equations, different information flow', fontsize=20,
color=INK, y=.97)
fig.text(.5, .865, r'$4x_1-x_2=2,\quad -x_1+4x_2-x_3=4,\quad -x_2+4x_3=10$'
'\nStarting vector: (0, 0, 0)', ha='center', va='center', fontsize=14, color=INK)
for ax in axes:
ax.set(xlim=(0, 1), ylim=(0, 1))
ax.axis('off')
left, right = axes
left.text(.5, 1.03, 'Jacobi: all inputs from the old vector', ha='center',
fontsize=14, weight='bold', color=BLUE)
ys = [.74, .44, .14]
for i, y in enumerate(ys, 1):
box(left, .025, y, .23, .14, rf'$x_{i}^{{(0)}}=0$', BLUE)
formulas = [r'$x_1^{(1)}=(2+0)/4=1/2$',
r'$x_2^{(1)}=(4+0+0)/4=1$',
r'$x_3^{(1)}=(10+0)/4=5/2$']
for y, label in zip(ys, formulas):
box(left, .47, y, .49, .14, label, ORANGE)
for source, target in [(1, 0), (0, 1), (2, 1), (1, 2)]:
arrow(left, (.265, ys[source]+.07), (.457, ys[target]+.07), BLUE)
left.text(.12, .975, 'Frozen inputs', ha='center', color=BLUE, fontsize=11)
left.text(.715, .975, 'New outputs', ha='center', color=ORANGE, fontsize=11)
left.text(.5, -.05, 'Replace the old vector only after the whole sweep.',
ha='center', fontsize=11, color=INK)
right.text(.5, 1.03, 'Gauss-Seidel: use each new value immediately', ha='center',
fontsize=14, weight='bold', color=ORANGE)
gs_formulas = [r'$x_1^{(1)}=(2+0)/4=1/2$',
r'$x_2^{(1)}=(4+1/2+0)/4=9/8$',
r'$x_3^{(1)}=(10+9/8)/4=89/32$']
for y, label in zip(ys, gs_formulas):
box(right, .095, y, .81, .14, label, ORANGE)
for i, label in [(0, r'new $x_1^{(1)}=1/2$'), (1, r'new $x_2^{(1)}=9/8$')]:
arrow(right, (.26, ys[i]-.017), (.26, ys[i+1]+.157), ORANGE)
right.text(.31, (ys[i]+ys[i+1])/2+.07, label, color=ORANGE,
va='center', fontsize=12)
right.text(.5, .965, r'Old $x_2^{(0)}=0$ and $x_3^{(0)}=0$ where still needed',
ha='center', fontsize=11, color=BLUE)
right.text(.5, -.05, 'Later rows read the newly updated coordinates.',
ha='center', fontsize=11, color=INK)
fig.text(.5, .035, 'Blue: old data Orange: new data Coordinate order: 1, 2, 3',
ha='center', color=INK, fontsize=12)
fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'), facecolor='white')
plt.close(fig)
if __name__ == '__main__':
main()