praca_magisterska/pytania/generate_q26_jacobi_diagrams.py

637 lines
25 KiB
Python

#!/usr/bin/env python3
"""
Generate diagrams explaining WHY parallel Jacobi needs ghost cells
and neighbor communication.
Output: A4-compatible, black & white, laser-printer-friendly PNG files.
Diagrams:
1. q26_jacobi_stencil.png — 1D Jacobi stencil: x_new[i] depends on x[i-1] and x[i+1]
2. q26_domain_decomposition.png — domain split across processes, showing the boundary problem
3. q26_ghost_cell_exchange.png — ghost cell exchange with Send/Recv
4. q26_deadlock_vs_solution.png — deadlock scenario vs Sendrecv solution
"""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
import numpy as np
import os
# --- Common settings ---
DPI = 300
BG_COLOR = 'white'
LINE_COLOR = 'black'
FONT_SIZE = 10
TITLE_SIZE = 13
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
os.makedirs(OUTPUT_DIR, exist_ok=True)
LIGHT_GRAY = '#D0D0D0'
MED_GRAY = '#A0A0A0'
DARK_GRAY = '#606060'
VERY_LIGHT = '#F0F0F0'
HIGHLIGHT = '#404040'
def draw_cell(ax, x, y, w, h, text, fill='white', edgecolor='black',
lw=1.5, fontsize=FONT_SIZE, fontweight='normal', textcolor='black'):
"""Draw a rectangular cell with text."""
rect = plt.Rectangle((x, y), w, h, lw=lw, edgecolor=edgecolor,
facecolor=fill, zorder=2)
ax.add_patch(rect)
ax.text(x + w/2, y + h/2, text, ha='center', va='center',
fontsize=fontsize, fontweight=fontweight, color=textcolor, zorder=3)
def draw_arrow(ax, x1, y1, x2, y2, color='black', lw=1.5, style='->', zorder=4):
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle=style, color=color, lw=lw),
zorder=zorder)
def draw_curvy_arrow(ax, x1, y1, x2, y2, color='black', lw=1.5,
connectionstyle="arc3,rad=0.3"):
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle='->', color=color, lw=lw,
connectionstyle=connectionstyle),
zorder=4)
# =========================================================================
# 1. JACOBI STENCIL — why x_new[i] needs neighbors
# =========================================================================
def generate_jacobi_stencil():
fig, ax = plt.subplots(figsize=(10, 5.5))
ax.set_xlim(-0.5, 10)
ax.set_ylim(-2.5, 4.5)
ax.set_aspect('equal')
ax.axis('off')
fig.patch.set_facecolor(BG_COLOR)
ax.set_title("Szablon Jacobiego 1D — dlaczego potrzebujesz sąsiadów",
fontsize=TITLE_SIZE, fontweight='bold', pad=15)
# Draw the 1D array: x[0], x[1], ..., x[7]
cw = 1.0 # cell width
ch = 0.8
y_old = 2.5 # y for old array
y_new = 0.0 # y for new array
n = 8
# Label rows
ax.text(-0.4, y_old + ch/2, "Stara\niteracja:", ha='right', va='center',
fontsize=FONT_SIZE, fontweight='bold')
ax.text(-0.4, y_new + ch/2, "Nowa\niteracja:", ha='right', va='center',
fontsize=FONT_SIZE, fontweight='bold')
for i in range(n):
x = i * cw
# Old values row
fill_old = LIGHT_GRAY if i in (2, 4) else 'white'
if i == 3:
fill_old = MED_GRAY
draw_cell(ax, x, y_old, cw, ch, f"x[{i}]", fill=fill_old)
# New values row
fill_new = 'white'
if i == 3:
fill_new = DARK_GRAY
draw_cell(ax, x, y_new, cw, ch, f"x'[{i}]", fill=fill_new,
textcolor='white', fontweight='bold')
else:
draw_cell(ax, x, y_new, cw, ch, f"x'[{i}]", fill=fill_new)
# Arrows from x[2] and x[4] to x'[3]
# x[2] → x'[3]
draw_arrow(ax, 2*cw + cw/2, y_old, 3*cw + cw/2, y_new + ch, lw=2.5)
# x[4] → x'[3]
draw_arrow(ax, 4*cw + cw/2, y_old, 3*cw + cw/2, y_new + ch, lw=2.5)
# Formula
ax.text(4.0, -1.2,
r"$x'[i] = \frac{1}{2}\,(x[i\!-\!1] + x[i\!+\!1])$",
ha='center', va='center', fontsize=14,
bbox=dict(boxstyle='round,pad=0.4', facecolor=VERY_LIGHT,
edgecolor='black', lw=1.5))
# Concrete example
ax.text(4.0, -2.0,
r"$x'[3] = \frac{1}{2}\,(x[2] + x[4])$"
" — aby obliczyć nową wartość x'[3],\n"
"potrzebujemy wartości lewego i prawego sąsiada.",
ha='center', va='center', fontsize=9)
fig.tight_layout()
path = os.path.join(OUTPUT_DIR, 'q26_jacobi_stencil.png')
fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG_COLOR)
plt.close(fig)
print(f"{path}")
# =========================================================================
# 2. DOMAIN DECOMPOSITION — the boundary problem
# =========================================================================
def generate_domain_decomposition():
fig, axes = plt.subplots(2, 1, figsize=(11, 7.5),
gridspec_kw={'height_ratios': [1, 1.5]})
fig.patch.set_facecolor(BG_COLOR)
fig.suptitle("Dekompozycja domeny — dlaczego proces potrzebuje danych od sąsiada",
fontsize=TITLE_SIZE, fontweight='bold', y=0.98)
# ---------- TOP: Full domain (single process) ----------
ax = axes[0]
ax.set_xlim(-1, 13)
ax.set_ylim(-1, 3)
ax.set_aspect('equal')
ax.axis('off')
ax.text(-0.8, 1.4, "Jeden proces\n(cała domena):", ha='right', va='center',
fontsize=9, fontweight='bold')
cw, ch = 1.0, 0.8
for i in range(12):
draw_cell(ax, i, 1, cw, ch, f"x[{i}]", fontsize=8)
ax.text(6, 0.2, "← Jeden proces ma WSZYSTKIE wartości → brak problemu",
ha='center', fontsize=9, style='italic')
# ---------- BOTTOM: Split across 3 processes ----------
ax = axes[1]
ax.set_xlim(-2.5, 14)
ax.set_ylim(-3.5, 4.5)
ax.set_aspect('equal')
ax.axis('off')
ax.text(6, 4.2, "Trzy procesy (domena podzielona):", ha='center',
fontsize=11, fontweight='bold')
# Process colors
p_colors = [VERY_LIGHT, LIGHT_GRAY, '#E8E8E8']
p_labels = ['Proces 0', 'Proces 1', 'Proces 2']
p_ranges = [(0, 4), (4, 8), (8, 12)]
y_proc = 2.5
for pi, (start, end) in enumerate(p_ranges):
# Background box for process
bx = start * cw - 0.15
bw = (end - start) * cw + 0.3
bg = plt.Rectangle((bx, y_proc - 0.3), bw, ch + 0.6,
lw=2, edgecolor='black', facecolor=p_colors[pi],
linestyle='--', zorder=1)
ax.add_patch(bg)
ax.text(bx + bw/2, y_proc + ch + 0.55, p_labels[pi],
ha='center', va='center', fontsize=9, fontweight='bold')
for i in range(start, end):
fill = 'white'
fw = 'normal'
# Highlight boundary cells
if i == end - 1 and pi < 2: # right boundary
fill = MED_GRAY
fw = 'bold'
if i == start and pi > 0: # left boundary
fill = MED_GRAY
fw = 'bold'
draw_cell(ax, i * cw, y_proc, cw, ch, f"x[{i}]",
fill=fill, fontsize=8, fontweight=fw)
# Show the PROBLEM: Process 0 computing x'[3]
# x'[3] needs x[2] (has it) + x[4] (does NOT have it!)
# Highlight the dependency
y_explain = 0.3
# Box showing calculation
ax.text(2, y_explain + 0.6,
"Proces 0 oblicza x'[3]:",
ha='center', va='center', fontsize=10, fontweight='bold')
ax.text(2, y_explain - 0.1,
r"$x'[3] = \frac{1}{2}\,(x[2] + x[4])$",
ha='center', va='center', fontsize=12,
bbox=dict(boxstyle='round,pad=0.3', facecolor='white',
edgecolor='black', lw=1.5))
# Arrow from x[2] — "ma"
draw_arrow(ax, 2.5, y_proc, 1.5, y_explain + 1.1, color='black', lw=1.5)
ax.text(1.3, y_proc - 0.6, "MA ✓", ha='center', fontsize=9, fontweight='bold')
# Arrow from x[4] — "NIE MA!"
draw_arrow(ax, 4.5, y_proc, 3.0, y_explain + 1.1, color='black', lw=1.5)
ax.text(4.5, y_proc - 0.6, "NIE MA ✗", ha='center', fontsize=9,
fontweight='bold', color='black')
# Explanation below
ax.text(6, -1.2,
"Problem: x[4] należy do Procesu 1, ale Proces 0 potrzebuje go\n"
"do obliczenia x'[3]. → Proces 0 musi POPROSIĆ Proces 1 o wartość x[4].\n"
"T\u0119 dodatkow\u0105 kopi\u0119 nazywamy \u201Ekom\u00F3rk\u0105-duchem\u201D (ghost cell).",
ha='center', va='center', fontsize=9,
bbox=dict(boxstyle='round,pad=0.5', facecolor=VERY_LIGHT,
edgecolor='black', lw=1))
# Similarly for Process 1 needing x[3] from Process 0
ax.text(6, -2.7,
"Analogicznie: Proces 1 obliczając x'[4] potrzebuje x[3] od Procesu 0.\n"
"→ Każda para sąsiadów musi wymienić po jednej wartości granicznej (w obie strony).",
ha='center', va='center', fontsize=9,
bbox=dict(boxstyle='round,pad=0.5', facecolor=VERY_LIGHT,
edgecolor='black', lw=1))
fig.tight_layout(rect=[0, 0, 1, 0.95])
path = os.path.join(OUTPUT_DIR, 'q26_domain_decomposition.png')
fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG_COLOR)
plt.close(fig)
print(f"{path}")
# =========================================================================
# 3. GHOST CELL EXCHANGE — the communication pattern
# =========================================================================
def generate_ghost_cell_exchange():
fig, ax = plt.subplots(figsize=(12, 7))
ax.set_xlim(-2.5, 15)
ax.set_ylim(-4.5, 5.5)
ax.set_aspect('equal')
ax.axis('off')
fig.patch.set_facecolor(BG_COLOR)
ax.set_title("Wymiana komórek-duchów (ghost cell exchange) między procesami",
fontsize=TITLE_SIZE, fontweight='bold', pad=15)
cw, ch = 1.0, 0.8
# === BEFORE exchange ===
y_before = 3.0
ax.text(-2.0, y_before + ch/2, "PRZED\nwymianą:", ha='right', va='center',
fontsize=9, fontweight='bold')
# Process 0: x[0..3]
for i in range(4):
fill = MED_GRAY if i == 3 else 'white'
draw_cell(ax, i * cw, y_before, cw, ch, f"x[{i}]", fill=fill, fontsize=8)
# Empty ghost cell slot
draw_cell(ax, 4 * cw, y_before, cw, ch, "?",
fill=VERY_LIGHT, edgecolor=MED_GRAY,
fontsize=10, fontweight='bold')
ax.text(4.5, y_before + ch + 0.2, "ghost", ha='center', fontsize=7,
style='italic', color=DARK_GRAY)
# Gap
ax.text(5.8, y_before + ch/2, "", ha='center', fontsize=16, color=MED_GRAY)
# Process 1: x[4..7]
# Empty ghost cell slot
draw_cell(ax, 6 * cw, y_before, cw, ch, "?",
fill=VERY_LIGHT, edgecolor=MED_GRAY,
fontsize=10, fontweight='bold')
ax.text(6.5, y_before + ch + 0.2, "ghost", ha='center', fontsize=7,
style='italic', color=DARK_GRAY)
for i in range(4, 8):
fill = MED_GRAY if i == 4 else 'white'
draw_cell(ax, (i + 3) * cw, y_before, cw, ch, f"x[{i}]", fill=fill, fontsize=8)
# Process labels
ax.text(2, y_before - 0.5, "Proces 0", ha='center', fontsize=9, fontweight='bold')
ax.text(9, y_before - 0.5, "Proces 1", ha='center', fontsize=9, fontweight='bold')
# === ARROWS: the exchange ===
y_mid = 1.3
# x[3] → ghost of Process 1
draw_curvy_arrow(ax, 3.5, y_before, 6.5, y_before,
connectionstyle="arc3,rad=-0.6", lw=2)
ax.text(5, y_before + 1.8, "Send x[3]", ha='center', fontsize=9,
fontweight='bold',
bbox=dict(boxstyle='round,pad=0.2', facecolor='white',
edgecolor='black'))
# x[4] → ghost of Process 0
draw_curvy_arrow(ax, 7.5, y_before, 4.5, y_before,
connectionstyle="arc3,rad=-0.6", lw=2)
ax.text(5, y_before - 1.5, "Send x[4]", ha='center', fontsize=9,
fontweight='bold',
bbox=dict(boxstyle='round,pad=0.2', facecolor='white',
edgecolor='black'))
# === AFTER exchange ===
y_after = -1.5
ax.text(-2.0, y_after + ch/2, "PO\nwymianie:", ha='right', va='center',
fontsize=9, fontweight='bold')
# Process 0: x[0..3] + ghost x[4]
for i in range(4):
fill = MED_GRAY if i == 3 else 'white'
draw_cell(ax, i * cw, y_after, cw, ch, f"x[{i}]", fill=fill, fontsize=8)
draw_cell(ax, 4 * cw, y_after, cw, ch, "x[4]",
fill=LIGHT_GRAY, edgecolor='black',
fontsize=8, fontweight='bold')
ax.text(4.5, y_after + ch + 0.2, "ghost ✓", ha='center', fontsize=7,
style='italic', fontweight='bold')
ax.text(5.8, y_after + ch/2, "", ha='center', fontsize=16, color=MED_GRAY)
# Process 1: ghost x[3] + x[4..7]
draw_cell(ax, 6 * cw, y_after, cw, ch, "x[3]",
fill=LIGHT_GRAY, edgecolor='black',
fontsize=8, fontweight='bold')
ax.text(6.5, y_after + ch + 0.2, "ghost ✓", ha='center', fontsize=7,
style='italic', fontweight='bold')
for i in range(4, 8):
fill = MED_GRAY if i == 4 else 'white'
draw_cell(ax, (i + 3) * cw, y_after, cw, ch, f"x[{i}]", fill=fill, fontsize=8)
ax.text(2, y_after - 0.5, "Proces 0", ha='center', fontsize=9, fontweight='bold')
ax.text(9, y_after - 0.5, "Proces 1", ha='center', fontsize=9, fontweight='bold')
# Explanation
ax.text(5.5, -3.5,
"Teraz Proces 0 ma kopię x[4] (ghost cell) i może obliczyć x'[3] = ½(x[2] + x[4]).\n"
"Proces 1 ma kopię x[3] (ghost cell) i może obliczyć x'[4] = ½(x[3] + x[5]).\n"
"→ Ta wymiana musi się powtarzać W KAŻDEJ iteracji Jacobiego.",
ha='center', va='center', fontsize=9,
bbox=dict(boxstyle='round,pad=0.5', facecolor=VERY_LIGHT,
edgecolor='black', lw=1))
fig.tight_layout()
path = os.path.join(OUTPUT_DIR, 'q26_ghost_cell_exchange.png')
fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG_COLOR)
plt.close(fig)
print(f"{path}")
# =========================================================================
# 4. DEADLOCK vs SOLUTION — side by side
# =========================================================================
def generate_deadlock_vs_solution():
fig, axes = plt.subplots(1, 2, figsize=(14, 6.5))
fig.patch.set_facecolor(BG_COLOR)
fig.suptitle("Zakleszczenie (deadlock) vs rozwiązanie MPI_Sendrecv",
fontsize=TITLE_SIZE, fontweight='bold', y=0.98)
# ---- LEFT: Deadlock ----
ax = axes[0]
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 11)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title("DEADLOCK — oba procesy w Ssend", fontsize=11, fontweight='bold')
# Timeline
# Proc 0 column at x=2, Proc 1 column at x=8
px0, px1 = 2.5, 8.5
y_top = 9.5
y_bot = 1.0
# Process headers
draw_cell(ax, px0 - 1.2, y_top, 2.4, 0.8, "Proces 0",
fill=LIGHT_GRAY, fontweight='bold', fontsize=10)
draw_cell(ax, px1 - 1.2, y_top, 2.4, 0.8, "Proces 1",
fill=LIGHT_GRAY, fontweight='bold', fontsize=10)
# Timeline lines
ax.plot([px0, px0], [y_top, y_bot], color='black', lw=2, zorder=1)
ax.plot([px1, px1], [y_top, y_bot], color='black', lw=2, zorder=1)
# Time arrow on the left
ax.annotate("", xy=(0, y_bot), xytext=(0, y_top - 0.5),
arrowprops=dict(arrowstyle='->', color=MED_GRAY, lw=1.5))
ax.text(0, 5.5, "czas", ha='center', va='center', fontsize=8,
rotation=90, color=DARK_GRAY)
# Step 1: Both call Ssend
y1 = 8.0
draw_cell(ax, px0 - 1.3, y1 - 0.3, 2.6, 0.6, "Ssend(→P1)",
fill='white', fontsize=9, fontweight='bold')
draw_cell(ax, px1 - 1.3, y1 - 0.3, 2.6, 0.6, "Ssend(→P0)",
fill='white', fontsize=9, fontweight='bold')
# Arrows showing they wait for each other
draw_curvy_arrow(ax, px0 + 1.3, y1, px1 - 1.3, y1,
connectionstyle="arc3,rad=0.15", lw=1.5)
ax.text(5.5, y1 + 0.5, "czeka na\nRecv P1", ha='center', fontsize=7,
style='italic')
draw_curvy_arrow(ax, px1 - 1.3, y1 - 0.1, px0 + 1.3, y1 - 0.1,
connectionstyle="arc3,rad=0.15", lw=1.5)
ax.text(5.5, y1 - 0.8, "czeka na\nRecv P0", ha='center', fontsize=7,
style='italic')
# Blocked indicators
y2 = 5.5
for px in [px0, px1]:
ax.plot([px - 0.5, px + 0.5], [y2 - 0.3, y2 + 0.3],
color='black', lw=3, zorder=5)
ax.plot([px - 0.5, px + 0.5], [y2 + 0.3, y2 - 0.3],
color='black', lw=3, zorder=5)
ax.text(5.5, y2, "DEADLOCK!", ha='center', fontsize=14,
fontweight='bold', color='black',
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_GRAY,
edgecolor='black', lw=2))
# Recv never reached
y3 = 3.5
draw_cell(ax, px0 - 1.3, y3 - 0.3, 2.6, 0.6, "Recv(←P1)",
fill=VERY_LIGHT, edgecolor=MED_GRAY, fontsize=9)
draw_cell(ax, px1 - 1.3, y3 - 0.3, 2.6, 0.6, "Recv(←P0)",
fill=VERY_LIGHT, edgecolor=MED_GRAY, fontsize=9)
ax.text(5.5, y3, "nigdy nie\nosiągnięte", ha='center', fontsize=8,
style='italic', color=DARK_GRAY)
# ---- RIGHT: Solution with MPI_Sendrecv ----
ax = axes[1]
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 11)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title("ROZWIĄZANIE — MPI_Sendrecv", fontsize=11, fontweight='bold')
# Process headers
draw_cell(ax, px0 - 1.2, y_top, 2.4, 0.8, "Proces 0",
fill=LIGHT_GRAY, fontweight='bold', fontsize=10)
draw_cell(ax, px1 - 1.2, y_top, 2.4, 0.8, "Proces 1",
fill=LIGHT_GRAY, fontweight='bold', fontsize=10)
# Timeline lines
ax.plot([px0, px0], [y_top, y_bot], color='black', lw=2, zorder=1)
ax.plot([px1, px1], [y_top, y_bot], color='black', lw=2, zorder=1)
# Time arrow
ax.annotate("", xy=(0, y_bot), xytext=(0, y_top - 0.5),
arrowprops=dict(arrowstyle='->', color=MED_GRAY, lw=1.5))
ax.text(0, 5.5, "czas", ha='center', va='center', fontsize=8,
rotation=90, color=DARK_GRAY)
# Step 1: Both call Sendrecv
y1 = 7.5
draw_cell(ax, px0 - 1.5, y1 - 0.4, 3.0, 0.8, "Sendrecv\n(→P1, ←P1)",
fill='white', fontsize=9, fontweight='bold')
draw_cell(ax, px1 - 1.5, y1 - 0.4, 3.0, 0.8, "Sendrecv\n(→P0, ←P0)",
fill='white', fontsize=9, fontweight='bold')
# Bidirectional arrows — simultaneous exchange
draw_curvy_arrow(ax, px0 + 1.5, y1 + 0.15, px1 - 1.5, y1 + 0.15,
connectionstyle="arc3,rad=0.12", lw=2)
draw_curvy_arrow(ax, px1 - 1.5, y1 - 0.15, px0 + 1.5, y1 - 0.15,
connectionstyle="arc3,rad=0.12", lw=2)
ax.text(5.5, y1 + 0.9, "x[3] →", ha='center', fontsize=8)
ax.text(5.5, y1 - 0.85, "← x[4]", ha='center', fontsize=8)
# Step 2: Both complete
y2 = 5.0
ax.text(5.5, y2 + 0.3, "Wymiana zakończona ✓", ha='center', fontsize=10,
fontweight='bold',
bbox=dict(boxstyle='round,pad=0.3', facecolor=VERY_LIGHT,
edgecolor='black', lw=1.5))
# Step 3: Compute
y3 = 3.5
draw_cell(ax, px0 - 1.5, y3 - 0.4, 3.0, 0.8, "Oblicz x'[0..3]",
fill='white', fontsize=9)
draw_cell(ax, px1 - 1.5, y3 - 0.4, 3.0, 0.8, "Oblicz x'[4..7]",
fill='white', fontsize=9)
# Step 4: Next iteration
y4 = 2.0
ax.text(5.5, y4, "→ Następna iteracja", ha='center', fontsize=9,
style='italic', color=DARK_GRAY)
fig.tight_layout(rect=[0, 0, 1, 0.93])
path = os.path.join(OUTPUT_DIR, 'q26_deadlock_vs_solution.png')
fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG_COLOR)
plt.close(fig)
print(f"{path}")
# =========================================================================
# 5. FULL JACOBI ITERATION — sequential pseudocode showing the dependency
# =========================================================================
def generate_jacobi_full_picture():
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_xlim(-1, 16)
ax.set_ylim(-5, 8)
ax.set_aspect('equal')
ax.axis('off')
fig.patch.set_facecolor(BG_COLOR)
ax.set_title("Równoległy Jacobi — pełny obraz jednej iteracji",
fontsize=TITLE_SIZE, fontweight='bold', pad=15)
cw, ch = 1.0, 0.7
# === ROW 1: Old values (iteration k) ===
y1 = 6.0
ax.text(-0.8, y1 + ch/2, "Iteracja k\n(stare):", ha='right', va='center',
fontsize=9, fontweight='bold')
n = 12
p_colors_cells = [VERY_LIGHT]*4 + [LIGHT_GRAY]*4 + ['#E8E8E8']*4
for i in range(n):
draw_cell(ax, i * cw, y1, cw, ch, f"x[{i}]", fill=p_colors_cells[i],
fontsize=7)
# Process boundaries
for bx in [4, 8]:
ax.plot([bx, bx], [y1 - 0.3, y1 + ch + 0.3], color='black',
lw=2.5, linestyle='--')
ax.text(2, y1 + ch + 0.5, "Proces 0", ha='center', fontsize=9, fontweight='bold')
ax.text(6, y1 + ch + 0.5, "Proces 1", ha='center', fontsize=9, fontweight='bold')
ax.text(10, y1 + ch + 0.5, "Proces 2", ha='center', fontsize=9, fontweight='bold')
# === STEP labels on the left ===
# Step 1: Compute interior
y2 = 3.5
ax.text(6, y2 + 1.5, "Krok 1: Oblicz punkty wewnętrzne (bez ghost cells)",
ha='center', fontsize=10, fontweight='bold')
ax.text(-0.8, y2 + ch/2, "Iteracja k+1\n(nowe):", ha='right', va='center',
fontsize=9, fontweight='bold')
# Show computation arrows only for interior points
for i in range(n):
is_boundary = (i in [0, 3, 4, 7, 8, 11])
if is_boundary:
draw_cell(ax, i * cw, y2, cw, ch, "?",
fill=MED_GRAY, fontsize=9, fontweight='bold')
else:
fill = p_colors_cells[i]
draw_cell(ax, i * cw, y2, cw, ch, f"x'[{i}]", fill=fill, fontsize=7)
# Show dependency arrows from old row
draw_arrow(ax, (i-1)*cw + cw/2, y1, i*cw + cw/2, y2 + ch,
color=DARK_GRAY, lw=0.8)
draw_arrow(ax, (i+1)*cw + cw/2, y1, i*cw + cw/2, y2 + ch,
color=DARK_GRAY, lw=0.8)
for bx in [4, 8]:
ax.plot([bx, bx], [y2 - 0.3, y2 + ch + 0.3], color='black',
lw=2.5, linestyle='--')
# Label boundary cells
ax.annotate("granica\n(potrzebuje\nghost cell)", xy=(3.5, y2), xytext=(3.5, y2 - 1.5),
fontsize=7, ha='center', va='top',
arrowprops=dict(arrowstyle='->', color='black', lw=1))
ax.annotate("granica\n(potrzebuje\nghost cell)", xy=(4.5, y2), xytext=(5.5, y2 - 1.5),
fontsize=7, ha='center', va='top',
arrowprops=dict(arrowstyle='->', color='black', lw=1))
# === Step 2: Exchange ghost cells ===
y3 = -0.5
ax.text(6, y3 + 2.0, "Krok 2: Wymień ghost cells → Krok 3: Oblicz punkty graniczne",
ha='center', fontsize=10, fontweight='bold')
for i in range(n):
fill = p_colors_cells[i]
draw_cell(ax, i * cw, y3, cw, ch, f"x'[{i}]", fill=fill, fontsize=7)
for bx in [4, 8]:
ax.plot([bx, bx], [y3 - 0.3, y3 + ch + 0.3], color='black',
lw=2.5, linestyle='--')
# Show exchange arrows at boundaries
draw_curvy_arrow(ax, 3.5, y3 + ch + 0.05, 4.5, y3 + ch + 0.05,
connectionstyle="arc3,rad=-0.5", lw=1.5)
draw_curvy_arrow(ax, 4.5, y3 - 0.05, 3.5, y3 - 0.05,
connectionstyle="arc3,rad=-0.5", lw=1.5)
draw_curvy_arrow(ax, 7.5, y3 + ch + 0.05, 8.5, y3 + ch + 0.05,
connectionstyle="arc3,rad=-0.5", lw=1.5)
draw_curvy_arrow(ax, 8.5, y3 - 0.05, 7.5, y3 - 0.05,
connectionstyle="arc3,rad=-0.5", lw=1.5)
# Summary
ax.text(6, y3 - 1.5,
"Powtarzaj Krok 1→2→3 aż do zbieżności (|x' - x| < ε)",
ha='center', fontsize=10, style='italic',
bbox=dict(boxstyle='round,pad=0.4', facecolor=VERY_LIGHT,
edgecolor='black', lw=1))
# Pseudocode box
pseudo = (
"for iter = 1 to max_iter:\n"
" oblicz x'[wewnętrzne] // nie wymaga ghost cells\n"
" wymień ghost cells z sąsiadami // Send + Recv\n"
" oblicz x'[graniczne] // teraz ghost cells dostępne\n"
" if |x' - x| < ε: break\n"
" x ← x'"
)
ax.text(6, -3.5, pseudo, ha='center', va='center', fontsize=8,
family='monospace',
bbox=dict(boxstyle='round,pad=0.5', facecolor='white',
edgecolor='black', lw=1.5))
fig.tight_layout()
path = os.path.join(OUTPUT_DIR, 'q26_jacobi_full_iteration.png')
fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG_COLOR)
plt.close(fig)
print(f"{path}")
# =========================================================================
# MAIN
# =========================================================================
if __name__ == '__main__':
print("Generating Q26 Jacobi diagrams...")
generate_jacobi_stencil()
generate_domain_decomposition()
generate_ghost_cell_exchange()
generate_deadlock_vs_solution()
generate_jacobi_full_picture()
print("Done!")