feat: updated questions obrona magister
519
pytania/generate_automata_diagrams.py
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate diagrams for PYTANIE 1: Automata and language classes.
|
||||||
|
1. FA recognition example — DFA for strings ending in "ab"
|
||||||
|
2. PDA recognition example — PDA for aⁿbⁿ
|
||||||
|
3. LBA recognition example — LBA for aⁿbⁿcⁿ
|
||||||
|
4. TM recognition example — TM for 0ⁿ1ⁿ
|
||||||
|
|
||||||
|
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as mpatches
|
||||||
|
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch, Circle
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
DPI = 300
|
||||||
|
BG = 'white'
|
||||||
|
LN = 'black'
|
||||||
|
FS = 8
|
||||||
|
FS_TITLE = 11
|
||||||
|
FS_SMALL = 6.5
|
||||||
|
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
GRAY1 = '#E8E8E8'
|
||||||
|
GRAY2 = '#D0D0D0'
|
||||||
|
GRAY3 = '#B8B8B8'
|
||||||
|
GRAY4 = '#F5F5F5'
|
||||||
|
GRAY5 = '#C0C0C0'
|
||||||
|
LIGHT_GREEN = '#D5E8D4'
|
||||||
|
LIGHT_RED = '#F8D7DA'
|
||||||
|
LIGHT_BLUE = '#D6EAF8'
|
||||||
|
LIGHT_YELLOW = '#FFF9C4'
|
||||||
|
|
||||||
|
|
||||||
|
def draw_state_circle(ax, x, y, r, label, accepting=False, initial=False,
|
||||||
|
fillcolor='white', fontsize=FS):
|
||||||
|
"""Draw an automaton state circle."""
|
||||||
|
circle = plt.Circle((x, y), r, fill=True, facecolor=fillcolor,
|
||||||
|
edgecolor=LN, linewidth=1.5, zorder=3)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
if accepting:
|
||||||
|
inner = plt.Circle((x, y), r * 0.82, fill=False,
|
||||||
|
edgecolor=LN, linewidth=1.2, zorder=3)
|
||||||
|
ax.add_patch(inner)
|
||||||
|
if initial:
|
||||||
|
ax.annotate("", xy=(x - r, y), xytext=(x - r - 0.4, y),
|
||||||
|
arrowprops=dict(arrowstyle='->', color=LN, lw=1.5),
|
||||||
|
zorder=4)
|
||||||
|
ax.text(x, y, label, ha='center', va='center', fontsize=fontsize,
|
||||||
|
fontweight='bold', zorder=5)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_curved_arrow(ax, x1, y1, x2, y2, label, r=0.25,
|
||||||
|
connectionstyle="arc3,rad=0.3", fontsize=FS_SMALL,
|
||||||
|
label_offset=(0, 0)):
|
||||||
|
"""Draw a curved arrow between points with label."""
|
||||||
|
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
|
||||||
|
arrowprops=dict(arrowstyle='->', color=LN, lw=1.2,
|
||||||
|
connectionstyle=connectionstyle),
|
||||||
|
zorder=2)
|
||||||
|
mx = (x1 + x2) / 2 + label_offset[0]
|
||||||
|
my = (y1 + y2) / 2 + label_offset[1]
|
||||||
|
ax.text(mx, my, label, ha='center', va='center', fontsize=fontsize,
|
||||||
|
fontstyle='italic', zorder=5,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor='white',
|
||||||
|
edgecolor='none', alpha=0.9))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_self_loop(ax, x, y, r, label, direction='top', fontsize=FS_SMALL):
|
||||||
|
"""Draw a self-loop on a state."""
|
||||||
|
if direction == 'top':
|
||||||
|
loop = mpatches.FancyArrowPatch(
|
||||||
|
(x - 0.12, y + r), (x + 0.12, y + r),
|
||||||
|
connectionstyle="arc3,rad=-1.8",
|
||||||
|
arrowstyle='->', mutation_scale=12, lw=1.2, color=LN, zorder=2)
|
||||||
|
ax.add_patch(loop)
|
||||||
|
ax.text(x, y + r + 0.35, label, ha='center', va='center',
|
||||||
|
fontsize=fontsize, fontstyle='italic', zorder=5)
|
||||||
|
elif direction == 'bottom':
|
||||||
|
loop = mpatches.FancyArrowPatch(
|
||||||
|
(x - 0.12, y - r), (x + 0.12, y - r),
|
||||||
|
connectionstyle="arc3,rad=1.8",
|
||||||
|
arrowstyle='->', mutation_scale=12, lw=1.2, color=LN, zorder=2)
|
||||||
|
ax.add_patch(loop)
|
||||||
|
ax.text(x, y - r - 0.35, label, ha='center', va='center',
|
||||||
|
fontsize=fontsize, fontstyle='italic', zorder=5)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 1. FA Recognition Example — DFA for strings ending in "ab"
|
||||||
|
# ============================================================
|
||||||
|
def draw_fa_recognition():
|
||||||
|
"""FA state diagram + step-by-step trace for 'baab'."""
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(11.69, 4),
|
||||||
|
gridspec_kw={'width_ratios': [1, 1.3]})
|
||||||
|
|
||||||
|
# --- Left: State diagram ---
|
||||||
|
ax = axes[0]
|
||||||
|
ax.set_xlim(-1, 5.5)
|
||||||
|
ax.set_ylim(-1.5, 2.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('DFA — diagram stanów\nL = {słowa nad {a,b} kończące się na "ab"}',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
R = 0.35
|
||||||
|
# States positions
|
||||||
|
states = {'q₀': (0.8, 0.5), 'q₁': (2.8, 0.5), 'q₂': (4.8, 0.5)}
|
||||||
|
|
||||||
|
draw_state_circle(ax, *states['q₀'], R, 'q₀', initial=True)
|
||||||
|
draw_state_circle(ax, *states['q₁'], R, 'q₁')
|
||||||
|
draw_state_circle(ax, *states['q₂'], R, 'q₂', accepting=True,
|
||||||
|
fillcolor=LIGHT_GREEN)
|
||||||
|
|
||||||
|
# Transitions
|
||||||
|
# q₀ --a--> q₁
|
||||||
|
draw_curved_arrow(ax, states['q₀'][0] + R, states['q₀'][1] + 0.05,
|
||||||
|
states['q₁'][0] - R, states['q₁'][1] + 0.05,
|
||||||
|
'a', connectionstyle="arc3,rad=0.15",
|
||||||
|
label_offset=(0, 0.25))
|
||||||
|
# q₁ --b--> q₂
|
||||||
|
draw_curved_arrow(ax, states['q₁'][0] + R, states['q₁'][1] + 0.05,
|
||||||
|
states['q₂'][0] - R, states['q₂'][1] + 0.05,
|
||||||
|
'b', connectionstyle="arc3,rad=0.15",
|
||||||
|
label_offset=(0, 0.25))
|
||||||
|
# q₂ --a--> q₁
|
||||||
|
draw_curved_arrow(ax, states['q₂'][0] - R, states['q₂'][1] - 0.05,
|
||||||
|
states['q₁'][0] + R, states['q₁'][1] - 0.05,
|
||||||
|
'a', connectionstyle="arc3,rad=0.15",
|
||||||
|
label_offset=(0, -0.3))
|
||||||
|
# q₂ --b--> q₀
|
||||||
|
draw_curved_arrow(ax, states['q₂'][0] - 0.2, states['q₂'][1] - R,
|
||||||
|
states['q₀'][0] + 0.2, states['q₀'][1] - R,
|
||||||
|
'b', connectionstyle="arc3,rad=0.4",
|
||||||
|
label_offset=(0, -0.4))
|
||||||
|
# q₀ --b--> q₀ (self-loop)
|
||||||
|
draw_self_loop(ax, *states['q₀'], R, 'b', direction='top')
|
||||||
|
# q₁ --a--> q₁ (self-loop)
|
||||||
|
draw_self_loop(ax, *states['q₁'], R, 'a', direction='top')
|
||||||
|
|
||||||
|
# Legend
|
||||||
|
ax.text(0.3, -1.0, '→ = start ◎ = akceptujący',
|
||||||
|
fontsize=FS_SMALL, ha='left', va='center',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
# --- Right: Step-by-step trace ---
|
||||||
|
ax2 = axes[1]
|
||||||
|
ax2.axis('off')
|
||||||
|
ax2.set_title('Ślad wykonania — wejście: "baab"',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
trace_data = [
|
||||||
|
['Krok', 'Czytam', 'Stan przed', 'Przejście', 'Stan po'],
|
||||||
|
['—', '—', 'q₀ (start)', '—', 'q₀'],
|
||||||
|
['1', 'b', 'q₀', 'δ(q₀, b) = q₀', 'q₀'],
|
||||||
|
['2', 'a', 'q₀', 'δ(q₀, a) = q₁', 'q₁'],
|
||||||
|
['3', 'a', 'q₁', 'δ(q₁, a) = q₁', 'q₁'],
|
||||||
|
['4', 'b', 'q₁', 'δ(q₁, b) = q₂', 'q₂ ✓'],
|
||||||
|
]
|
||||||
|
|
||||||
|
colors = [GRAY2] + ['white'] * 4 + [LIGHT_GREEN]
|
||||||
|
table = ax2.table(cellText=trace_data, cellLoc='center',
|
||||||
|
loc='center', bbox=[0.05, 0.15, 0.9, 0.75])
|
||||||
|
table.auto_set_font_size(False)
|
||||||
|
table.set_fontsize(FS)
|
||||||
|
for (row, col), cell in table.get_celld().items():
|
||||||
|
cell.set_edgecolor(GRAY3)
|
||||||
|
if row == 0:
|
||||||
|
cell.set_facecolor(GRAY2)
|
||||||
|
cell.set_text_props(fontweight='bold')
|
||||||
|
else:
|
||||||
|
cell.set_facecolor(colors[row])
|
||||||
|
cell.set_height(0.12)
|
||||||
|
|
||||||
|
ax2.text(0.5, 0.05, 'Wynik: q₂ ∈ F → "baab" AKCEPTOWANE ✓',
|
||||||
|
ha='center', va='center', fontsize=FS + 1, fontweight='bold',
|
||||||
|
transform=ax2.transAxes,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN,
|
||||||
|
edgecolor=LN))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'fa_recognition_example.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ fa_recognition_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 2. PDA Recognition Example — PDA for aⁿbⁿ
|
||||||
|
# ============================================================
|
||||||
|
def draw_pda_recognition():
|
||||||
|
"""PDA state diagram + step-by-step trace with stack visualization for 'aabb'."""
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(11.69, 5.5),
|
||||||
|
gridspec_kw={'width_ratios': [1, 1.4]})
|
||||||
|
|
||||||
|
# --- Left: State diagram ---
|
||||||
|
ax = axes[0]
|
||||||
|
ax.set_xlim(-1, 5.5)
|
||||||
|
ax.set_ylim(-2, 3)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('PDA — diagram stanów\nL = {aⁿbⁿ | n ≥ 1}',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
R = 0.38
|
||||||
|
states = {'q₀': (0.8, 0.5), 'q₁': (2.8, 0.5), 'q₂': (4.8, 0.5)}
|
||||||
|
|
||||||
|
draw_state_circle(ax, *states['q₀'], R, 'q₀', initial=True)
|
||||||
|
draw_state_circle(ax, *states['q₁'], R, 'q₁')
|
||||||
|
draw_state_circle(ax, *states['q₂'], R, 'q₂', accepting=True,
|
||||||
|
fillcolor=LIGHT_GREEN)
|
||||||
|
|
||||||
|
# q₀ --b,A/ε--> q₁
|
||||||
|
draw_curved_arrow(ax, states['q₀'][0] + R, states['q₀'][1],
|
||||||
|
states['q₁'][0] - R, states['q₁'][1],
|
||||||
|
'b, A → ε\n(pop A)', connectionstyle="arc3,rad=0.0",
|
||||||
|
label_offset=(0, 0.4))
|
||||||
|
# q₁ --ε,Z₀/Z₀--> q₂
|
||||||
|
draw_curved_arrow(ax, states['q₁'][0] + R, states['q₁'][1],
|
||||||
|
states['q₂'][0] - R, states['q₂'][1],
|
||||||
|
'ε, Z₀ → Z₀\n(akceptuj)', connectionstyle="arc3,rad=0.0",
|
||||||
|
label_offset=(0, 0.45))
|
||||||
|
# q₀ self-loop: a, Z₀/AZ₀ and a, A/AA
|
||||||
|
draw_self_loop(ax, *states['q₀'], R, 'a, Z₀ → AZ₀\na, A → AA\n(push A)',
|
||||||
|
direction='top')
|
||||||
|
# q₁ self-loop: b, A/ε
|
||||||
|
draw_self_loop(ax, *states['q₁'], R, 'b, A → ε\n(pop A)', direction='top')
|
||||||
|
|
||||||
|
# Key explanation
|
||||||
|
ax.text(0.3, -1.3, 'Notacja: symbol_wejścia, szczyt_stosu → nowy_szczyt\n'
|
||||||
|
'ε = brak symbolu (przejście spontaniczne lub pusty stos)',
|
||||||
|
fontsize=FS_SMALL, ha='left', va='center',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
# --- Right: Step trace with stack ---
|
||||||
|
ax2 = axes[1]
|
||||||
|
ax2.axis('off')
|
||||||
|
ax2.set_title('Ślad wykonania z wizualizacją stosu — wejście: "aabb"',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
trace_data = [
|
||||||
|
['Krok', 'Czytam', 'Stan', 'Stos (szczyt→)', 'Operacja'],
|
||||||
|
['start', '—', 'q₀', '[Z₀]', '—'],
|
||||||
|
['1', 'a', 'q₀', '[A, Z₀]', 'push A'],
|
||||||
|
['2', 'a', 'q₀', '[A, A, Z₀]', 'push A'],
|
||||||
|
['3', 'b', 'q₁', '[A, Z₀]', 'pop A'],
|
||||||
|
['4', 'b', 'q₁', '[Z₀]', 'pop A'],
|
||||||
|
['5', 'ε', 'q₂', '[Z₀]', 'akceptuj!'],
|
||||||
|
]
|
||||||
|
|
||||||
|
colors = [GRAY2, 'white', LIGHT_BLUE, LIGHT_BLUE, LIGHT_YELLOW, LIGHT_YELLOW, LIGHT_GREEN]
|
||||||
|
table = ax2.table(cellText=trace_data, cellLoc='center',
|
||||||
|
loc='center', bbox=[0.02, 0.08, 0.96, 0.82])
|
||||||
|
table.auto_set_font_size(False)
|
||||||
|
table.set_fontsize(FS)
|
||||||
|
for (row, col), cell in table.get_celld().items():
|
||||||
|
cell.set_edgecolor(GRAY3)
|
||||||
|
if row == 0:
|
||||||
|
cell.set_facecolor(GRAY2)
|
||||||
|
cell.set_text_props(fontweight='bold')
|
||||||
|
else:
|
||||||
|
cell.set_facecolor(colors[row])
|
||||||
|
cell.set_height(0.11)
|
||||||
|
|
||||||
|
ax2.text(0.5, 0.0, 'Wynik: q₂ ∈ F, stos=[Z₀] → "aabb" AKCEPTOWANE ✓\n'
|
||||||
|
'Intuicja: 2× push A (za "aa") + 2× pop A (za "bb") = stos pusty = OK',
|
||||||
|
ha='center', va='center', fontsize=FS, fontweight='bold',
|
||||||
|
transform=ax2.transAxes,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN,
|
||||||
|
edgecolor=LN))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'pda_recognition_example.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ pda_recognition_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 3. LBA Recognition Example — LBA for aⁿbⁿcⁿ
|
||||||
|
# ============================================================
|
||||||
|
def draw_lba_recognition():
|
||||||
|
"""LBA tape visualization showing marking rounds for 'aabbcc'."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(11.69, 6.5))
|
||||||
|
ax.set_xlim(-0.5, 12)
|
||||||
|
ax.set_ylim(-1, 10.5)
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('LBA — rozpoznawanie aⁿbⁿcⁿ (n=2)\n'
|
||||||
|
'Strategia: w każdej rundzie zaznacz jedno a, b, c',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
CELL_W = 0.9
|
||||||
|
CELL_H = 0.7
|
||||||
|
TAPE_X0 = 1.5
|
||||||
|
HEAD_COLOR = '#FFD700'
|
||||||
|
|
||||||
|
def draw_tape(y, cells, head_pos, label, step_label=""):
|
||||||
|
"""Draw a tape row with cells, head position highlighted."""
|
||||||
|
ax.text(0.2, y + CELL_H/2, label, ha='right', va='center',
|
||||||
|
fontsize=FS, fontweight='bold')
|
||||||
|
for i, (sym, color) in enumerate(cells):
|
||||||
|
x = TAPE_X0 + i * CELL_W
|
||||||
|
fc = HEAD_COLOR if i == head_pos else color
|
||||||
|
rect = mpatches.FancyBboxPatch((x, y), CELL_W, CELL_H,
|
||||||
|
boxstyle="round,pad=0.03",
|
||||||
|
lw=1.2, edgecolor=LN, facecolor=fc)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + CELL_W/2, y + CELL_H/2, sym,
|
||||||
|
ha='center', va='center', fontsize=FS + 2,
|
||||||
|
fontweight='bold' if sym in ('X', 'Y', 'Z') else 'normal',
|
||||||
|
family='monospace')
|
||||||
|
if head_pos is not None:
|
||||||
|
hx = TAPE_X0 + head_pos * CELL_W + CELL_W/2
|
||||||
|
ax.annotate('▼', xy=(hx, y + CELL_H), xytext=(hx, y + CELL_H + 0.25),
|
||||||
|
ha='center', va='bottom', fontsize=8, color='black')
|
||||||
|
if step_label:
|
||||||
|
sx = TAPE_X0 + 6 * CELL_W + 0.5
|
||||||
|
ax.text(sx, y + CELL_H/2, step_label, ha='left', va='center',
|
||||||
|
fontsize=FS_SMALL,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
W = 'white'
|
||||||
|
MK = GRAY1 # marked cell color
|
||||||
|
|
||||||
|
# Row 1: Initial tape
|
||||||
|
y = 9.0
|
||||||
|
draw_tape(y, [('a', W), ('a', W), ('b', W), ('b', W), ('c', W), ('c', W)],
|
||||||
|
0, 'Początek', 'taśma = [a, a, b, b, c, c], głowica na 0')
|
||||||
|
|
||||||
|
# Row 2: After marking first 'a'
|
||||||
|
y = 7.8
|
||||||
|
draw_tape(y, [('X', MK), ('a', W), ('b', W), ('b', W), ('c', W), ('c', W)],
|
||||||
|
1, 'R1, krok 1', 'zaznacz a→X, szukaj b')
|
||||||
|
|
||||||
|
# Row 3: After marking first 'b'
|
||||||
|
y = 6.6
|
||||||
|
draw_tape(y, [('X', MK), ('a', W), ('Y', MK), ('b', W), ('c', W), ('c', W)],
|
||||||
|
3, 'R1, krok 2', 'zaznacz b→Y, szukaj c')
|
||||||
|
|
||||||
|
# Row 4: After marking first 'c'
|
||||||
|
y = 5.4
|
||||||
|
draw_tape(y, [('X', MK), ('a', W), ('Y', MK), ('b', W), ('Z', MK), ('c', W)],
|
||||||
|
0, 'R1, krok 3', 'zaznacz c→Z, wróć na początek')
|
||||||
|
|
||||||
|
# Runda 2 header
|
||||||
|
y = 4.5
|
||||||
|
ax.text(TAPE_X0 + 3 * CELL_W, y + 0.3, '═══ RUNDA 2 ═══',
|
||||||
|
ha='center', va='center', fontsize=FS, fontweight='bold',
|
||||||
|
color=LN)
|
||||||
|
|
||||||
|
# Row 5: After marking second 'a'
|
||||||
|
y = 3.6
|
||||||
|
draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('b', W), ('Z', MK), ('c', W)],
|
||||||
|
2, 'R2, krok 1', 'pomiń X, zaznacz a→X, szukaj b')
|
||||||
|
|
||||||
|
# Row 6: After marking second 'b'
|
||||||
|
y = 2.4
|
||||||
|
draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('Z', MK), ('c', W)],
|
||||||
|
4, 'R2, krok 2', 'pomiń Y, zaznacz b→Y, szukaj c')
|
||||||
|
|
||||||
|
# Row 7: After marking second 'c'
|
||||||
|
y = 1.2
|
||||||
|
draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('Z', MK), ('Z', MK)],
|
||||||
|
None, 'R2, krok 3', 'zaznacz c→Z, wróć na początek')
|
||||||
|
|
||||||
|
# Result
|
||||||
|
y = 0.0
|
||||||
|
ax.text(TAPE_X0 + 3 * CELL_W, y + 0.3,
|
||||||
|
'Wszystko zaznaczone → q_acc → "aabbcc" AKCEPTOWANE ✓',
|
||||||
|
ha='center', va='center', fontsize=FS + 1, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN,
|
||||||
|
edgecolor=LN))
|
||||||
|
|
||||||
|
# Key
|
||||||
|
ax.text(TAPE_X0 + 6 * CELL_W + 0.5, y + 0.3,
|
||||||
|
'Ograniczenie LBA:\ngłowica ≤ 6 komórek\n(= |w| = |"aabbcc"|)',
|
||||||
|
ha='left', va='center', fontsize=FS_SMALL,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_YELLOW,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'lba_recognition_example.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ lba_recognition_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 4. TM Recognition Example — TM for 0ⁿ1ⁿ
|
||||||
|
# ============================================================
|
||||||
|
def draw_tm_recognition():
|
||||||
|
"""TM tape visualization for 0ⁿ1ⁿ with infinite tape shown."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(11.69, 6.5))
|
||||||
|
ax.set_xlim(-0.5, 13)
|
||||||
|
ax.set_ylim(-1, 10.5)
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('TM — rozpoznawanie 0ⁿ1ⁿ (n=2)\n'
|
||||||
|
'Strategia: zaznacz jedno 0 i jedno 1 w każdej rundzie',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
CELL_W = 0.9
|
||||||
|
CELL_H = 0.7
|
||||||
|
TAPE_X0 = 1.5
|
||||||
|
HEAD_COLOR = '#FFD700'
|
||||||
|
|
||||||
|
def draw_tape(y, cells, head_pos, label, step_label=""):
|
||||||
|
ax.text(0.2, y + CELL_H/2, label, ha='right', va='center',
|
||||||
|
fontsize=FS, fontweight='bold')
|
||||||
|
for i, (sym, color) in enumerate(cells):
|
||||||
|
x = TAPE_X0 + i * CELL_W
|
||||||
|
fc = HEAD_COLOR if i == head_pos else color
|
||||||
|
lw = 1.2
|
||||||
|
ls = '-'
|
||||||
|
if sym == '⊔':
|
||||||
|
ls = '--'
|
||||||
|
rect = mpatches.FancyBboxPatch((x, y), CELL_W, CELL_H,
|
||||||
|
boxstyle="round,pad=0.03",
|
||||||
|
lw=lw, edgecolor=LN, facecolor=fc,
|
||||||
|
linestyle=ls)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + CELL_W/2, y + CELL_H/2, sym,
|
||||||
|
ha='center', va='center', fontsize=FS + 2,
|
||||||
|
fontweight='bold' if sym in ('X', 'Y') else 'normal',
|
||||||
|
family='monospace',
|
||||||
|
color=GRAY3 if sym == '⊔' else LN)
|
||||||
|
# ∞ arrow
|
||||||
|
last_x = TAPE_X0 + len(cells) * CELL_W
|
||||||
|
ax.annotate('→ ∞', xy=(last_x + 0.3, y + CELL_H/2),
|
||||||
|
fontsize=FS, ha='left', va='center', color=GRAY3)
|
||||||
|
if head_pos is not None:
|
||||||
|
hx = TAPE_X0 + head_pos * CELL_W + CELL_W/2
|
||||||
|
ax.annotate('▼', xy=(hx, y + CELL_H), xytext=(hx, y + CELL_H + 0.25),
|
||||||
|
ha='center', va='bottom', fontsize=8, color='black')
|
||||||
|
if step_label:
|
||||||
|
sx = TAPE_X0 + 8 * CELL_W + 0.8
|
||||||
|
ax.text(sx, y + CELL_H/2, step_label, ha='left', va='center',
|
||||||
|
fontsize=FS_SMALL,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
W = 'white'
|
||||||
|
MK = GRAY1
|
||||||
|
BL = '#F0F0F0' # blank cell
|
||||||
|
|
||||||
|
# Row 1: Initial
|
||||||
|
y = 9.0
|
||||||
|
draw_tape(y, [('0', W), ('0', W), ('1', W), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)],
|
||||||
|
0, 'Początek', 'taśma = [0,0,1,1,⊔,⊔,...∞]')
|
||||||
|
|
||||||
|
# Row 2: Mark first 0
|
||||||
|
y = 7.8
|
||||||
|
draw_tape(y, [('X', MK), ('0', W), ('1', W), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)],
|
||||||
|
1, 'R1, krok 1', 'zaznacz 0→X, idź w prawo')
|
||||||
|
|
||||||
|
# Row 3: Skip to first 1, mark it
|
||||||
|
y = 6.6
|
||||||
|
draw_tape(y, [('X', MK), ('0', W), ('Y', MK), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)],
|
||||||
|
0, 'R1, krok 2', 'zaznacz 1→Y, wróć na początek')
|
||||||
|
|
||||||
|
# Runda 2 header
|
||||||
|
y = 5.8
|
||||||
|
ax.text(TAPE_X0 + 3.5 * CELL_W, y + 0.3, '═══ RUNDA 2 ═══',
|
||||||
|
ha='center', va='center', fontsize=FS, fontweight='bold')
|
||||||
|
|
||||||
|
# Row 4: Mark second 0
|
||||||
|
y = 4.8
|
||||||
|
draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)],
|
||||||
|
2, 'R2, krok 1', 'pomiń X, zaznacz 0→X')
|
||||||
|
|
||||||
|
# Row 5: Mark second 1
|
||||||
|
y = 3.6
|
||||||
|
draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('⊔', BL), ('⊔', BL), ('⊔', BL)],
|
||||||
|
0, 'R2, krok 2', 'pomiń Y, zaznacz 1→Y, wróć')
|
||||||
|
|
||||||
|
# Row 6: Check — all marked
|
||||||
|
y = 2.4
|
||||||
|
draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('⊔', BL), ('⊔', BL), ('⊔', BL)],
|
||||||
|
None, 'Sprawdzenie', 'brak niezaznaczonych → q_acc')
|
||||||
|
|
||||||
|
# Result + TM vs LBA comparison
|
||||||
|
y = 0.8
|
||||||
|
ax.text(TAPE_X0 + 3.5 * CELL_W, y + 0.3,
|
||||||
|
'"0011" AKCEPTOWANE ✓',
|
||||||
|
ha='center', va='center', fontsize=FS + 1, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN,
|
||||||
|
edgecolor=LN))
|
||||||
|
|
||||||
|
y = -0.3
|
||||||
|
ax.text(TAPE_X0 + 3.5 * CELL_W, y + 0.3,
|
||||||
|
'Różnica TM vs LBA: taśma TM jest nieskończona (⊔ → ∞)\n'
|
||||||
|
'LBA: głowica ograniczona do |w| komórek\n'
|
||||||
|
'TM: głowica może wyjść POZA wejście i pisać na pustych ⊔',
|
||||||
|
ha='center', va='center', fontsize=FS_SMALL,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_YELLOW,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'tm_recognition_example.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ tm_recognition_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Main
|
||||||
|
# ============================================================
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Generating automata diagrams for PYTANIE 1...")
|
||||||
|
draw_fa_recognition()
|
||||||
|
draw_pda_recognition()
|
||||||
|
draw_lba_recognition()
|
||||||
|
draw_tm_recognition()
|
||||||
|
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")
|
||||||
711
pytania/generate_normalization_diagrams.py
Normal file
@ -0,0 +1,711 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate B&W normalization step diagrams for PYTANIE 3.
|
||||||
|
Each diagram shows database tables at a specific normalization stage.
|
||||||
|
Designed for A4 laser printer output (300 DPI, black & white).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as mpatches
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# Common settings
|
||||||
|
DPI = 300
|
||||||
|
FONT_SIZE = 8
|
||||||
|
HEADER_COLOR = '#D0D0D0'
|
||||||
|
CELL_COLOR = '#FFFFFF'
|
||||||
|
HIGHLIGHT_COLOR = '#F0D0D0' # light red-ish gray for violations
|
||||||
|
FIXED_COLOR = '#D0F0D0' # light green-ish gray for fixed
|
||||||
|
FD_ARROW_COLOR = '#444444'
|
||||||
|
|
||||||
|
|
||||||
|
def draw_table(ax, x, y, title, headers, rows, col_widths=None,
|
||||||
|
highlight_cols=None, highlight_rows=None, highlight_cells=None,
|
||||||
|
strikethrough_cells=None, title_fontsize=9):
|
||||||
|
"""Draw a single table on the axes at position (x, y).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ax: matplotlib axes
|
||||||
|
x, y: top-left position
|
||||||
|
title: table title string
|
||||||
|
headers: list of column header strings
|
||||||
|
rows: list of lists (row data)
|
||||||
|
col_widths: list of column widths (in inches-ish units)
|
||||||
|
highlight_cols: set of column indices to highlight
|
||||||
|
highlight_rows: set of row indices to highlight
|
||||||
|
highlight_cells: set of (row, col) to highlight
|
||||||
|
strikethrough_cells: set of (row, col) to draw strikethrough
|
||||||
|
title_fontsize: font size for table title
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(width, height) of the drawn table
|
||||||
|
"""
|
||||||
|
n_cols = len(headers)
|
||||||
|
n_rows = len(rows)
|
||||||
|
|
||||||
|
if col_widths is None:
|
||||||
|
# Auto-calculate based on content
|
||||||
|
col_widths = []
|
||||||
|
for c in range(n_cols):
|
||||||
|
max_len = len(headers[c])
|
||||||
|
for r in rows:
|
||||||
|
if c < len(r):
|
||||||
|
max_len = max(max_len, len(str(r[c])))
|
||||||
|
col_widths.append(max(max_len * 0.08 + 0.1, 0.5))
|
||||||
|
|
||||||
|
row_height = 0.22
|
||||||
|
total_width = sum(col_widths)
|
||||||
|
total_height = (n_rows + 1) * row_height # +1 for header
|
||||||
|
|
||||||
|
# Title
|
||||||
|
ax.text(x + total_width / 2, y + 0.18, title,
|
||||||
|
fontsize=title_fontsize, fontweight='bold',
|
||||||
|
ha='center', va='bottom', family='monospace')
|
||||||
|
|
||||||
|
y_start = y
|
||||||
|
|
||||||
|
# Draw header row
|
||||||
|
cx = x
|
||||||
|
for c, (hdr, w) in enumerate(zip(headers, col_widths)):
|
||||||
|
color = HEADER_COLOR
|
||||||
|
rect = mpatches.FancyBboxPatch(
|
||||||
|
(cx, y_start), w, -row_height,
|
||||||
|
boxstyle="square,pad=0", facecolor=color,
|
||||||
|
edgecolor='black', linewidth=0.5)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(cx + w / 2, y_start - row_height / 2, hdr,
|
||||||
|
fontsize=FONT_SIZE, fontweight='bold',
|
||||||
|
ha='center', va='center', family='monospace')
|
||||||
|
cx += w
|
||||||
|
|
||||||
|
# Draw data rows
|
||||||
|
for r_idx, row in enumerate(rows):
|
||||||
|
cy = y_start - (r_idx + 1) * row_height
|
||||||
|
cx = x
|
||||||
|
for c_idx, (val, w) in enumerate(zip(row, col_widths)):
|
||||||
|
color = CELL_COLOR
|
||||||
|
if highlight_cols and c_idx in highlight_cols:
|
||||||
|
color = HIGHLIGHT_COLOR
|
||||||
|
if highlight_rows and r_idx in highlight_rows:
|
||||||
|
color = HIGHLIGHT_COLOR
|
||||||
|
if highlight_cells and (r_idx, c_idx) in highlight_cells:
|
||||||
|
color = HIGHLIGHT_COLOR
|
||||||
|
|
||||||
|
rect = mpatches.FancyBboxPatch(
|
||||||
|
(cx, cy), w, -row_height,
|
||||||
|
boxstyle="square,pad=0", facecolor=color,
|
||||||
|
edgecolor='black', linewidth=0.5)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
|
||||||
|
text_color = 'black'
|
||||||
|
ax.text(cx + w / 2, cy - row_height / 2, str(val),
|
||||||
|
fontsize=FONT_SIZE, ha='center', va='center',
|
||||||
|
family='monospace', color=text_color)
|
||||||
|
|
||||||
|
if strikethrough_cells and (r_idx, c_idx) in strikethrough_cells:
|
||||||
|
ax.plot([cx + 0.03, cx + w - 0.03],
|
||||||
|
[cy - row_height / 2, cy - row_height / 2],
|
||||||
|
color='black', linewidth=1.0)
|
||||||
|
|
||||||
|
cx += w
|
||||||
|
|
||||||
|
return total_width, total_height + 0.25 # extra for title
|
||||||
|
|
||||||
|
|
||||||
|
def create_figure(width_inches=11.69, height_inches=8.27):
|
||||||
|
"""Create A4 landscape figure."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(width_inches, height_inches), dpi=DPI)
|
||||||
|
ax.set_xlim(0, width_inches)
|
||||||
|
ax.set_ylim(0, height_inches)
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
return fig, ax
|
||||||
|
|
||||||
|
|
||||||
|
def add_arrow(ax, x1, y1, x2, y2, label='', color='black'):
|
||||||
|
"""Draw an arrow with optional label."""
|
||||||
|
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
|
||||||
|
arrowprops=dict(arrowstyle='->', color=color, lw=1.5))
|
||||||
|
if label:
|
||||||
|
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
|
||||||
|
ax.text(mx, my + 0.12, label, fontsize=7, ha='center', va='bottom',
|
||||||
|
family='monospace', color=color)
|
||||||
|
|
||||||
|
|
||||||
|
def add_label(ax, x, y, text, fontsize=8, color='black', ha='left', style='normal'):
|
||||||
|
"""Add a text label."""
|
||||||
|
ax.text(x, y, text, fontsize=fontsize, ha=ha, va='center',
|
||||||
|
family='monospace', color=color, style=style)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 1: 0NF Table
|
||||||
|
# ============================================================
|
||||||
|
def draw_0nf():
|
||||||
|
fig, ax = create_figure(11.69, 5.5)
|
||||||
|
|
||||||
|
headers = ['StID', 'Imie', 'Telefony', 'KursID', 'NazwaKursu',
|
||||||
|
'Prowadzacy', 'WydzialID', 'NazwaWydzialu']
|
||||||
|
rows = [
|
||||||
|
['1', 'Anna', '111-222, 333-444', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'],
|
||||||
|
['1', 'Anna', '111-222, 333-444', 'K20', 'Algorytmy', 'Nowak', 'W4', 'EiTI'],
|
||||||
|
['2', 'Jan', '555-666', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'],
|
||||||
|
['3', 'Ewa', '777-888', 'K30', 'Optyka', 'Wisniewski', 'W2', 'Fizyka'],
|
||||||
|
]
|
||||||
|
col_widths = [0.5, 0.55, 1.55, 0.65, 1.1, 1.05, 0.85, 1.2]
|
||||||
|
|
||||||
|
# Highlight the non-atomic column
|
||||||
|
draw_table(ax, 0.8, 4.5, '0NF: Rejestr (forma nienormalna)',
|
||||||
|
headers, rows, col_widths,
|
||||||
|
highlight_cols={2}, # Telefony column
|
||||||
|
title_fontsize=11)
|
||||||
|
|
||||||
|
# Annotations
|
||||||
|
add_label(ax, 0.8, 1.9,
|
||||||
|
'PROBLEM: Kolumna "Telefony" zawiera LISTY wartosci (nieatomowe).',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.8, 1.55,
|
||||||
|
'Redundancja: "Anna", "W4", "EiTI", "Bazy danych" powtorzone wielokrotnie.',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.8, 1.2,
|
||||||
|
'Zaleznosci funkcyjne: StID -> Imie, WydzialID | WydzialID -> NazwaWydzialu',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.8, 0.9,
|
||||||
|
' KursID -> NazwaKursu | (StID,KursID) -> Prowadzacy | Prowadzacy -> KursID',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_0nf_table.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_0nf_table.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 2: 1NF — atomic values
|
||||||
|
# ============================================================
|
||||||
|
def draw_1nf():
|
||||||
|
fig, ax = create_figure(11.69, 6.0)
|
||||||
|
|
||||||
|
# Main table after removing Telefony
|
||||||
|
headers1 = ['StID*', 'Imie', 'KursID*', 'NazwaKursu',
|
||||||
|
'Prowadzacy', 'WydzialID', 'NazwaWydzialu']
|
||||||
|
rows1 = [
|
||||||
|
['1', 'Anna', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'],
|
||||||
|
['1', 'Anna', 'K20', 'Algorytmy', 'Nowak', 'W4', 'EiTI'],
|
||||||
|
['2', 'Jan', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'],
|
||||||
|
['3', 'Ewa', 'K30', 'Optyka', 'Wisniewski', 'W2', 'Fizyka'],
|
||||||
|
]
|
||||||
|
cw1 = [0.55, 0.55, 0.7, 1.1, 1.05, 0.85, 1.2]
|
||||||
|
|
||||||
|
draw_table(ax, 0.5, 5.2, '1NF: Rejestr (klucz: StID, KursID)',
|
||||||
|
headers1, rows1, cw1, title_fontsize=10)
|
||||||
|
|
||||||
|
# Telefony table
|
||||||
|
headers2 = ['StID*', 'Telefon*']
|
||||||
|
rows2 = [
|
||||||
|
['1', '111-222'],
|
||||||
|
['1', '333-444'],
|
||||||
|
['2', '555-666'],
|
||||||
|
['3', '777-888'],
|
||||||
|
]
|
||||||
|
cw2 = [0.55, 0.85]
|
||||||
|
|
||||||
|
draw_table(ax, 7.5, 5.2, 'Telefony (klucz: StID, Telefon)',
|
||||||
|
headers2, rows2, cw2, title_fontsize=10)
|
||||||
|
|
||||||
|
# Arrow
|
||||||
|
add_arrow(ax, 6.6, 4.3, 7.4, 4.3, 'wydzielono', '#333333')
|
||||||
|
|
||||||
|
# Annotations
|
||||||
|
add_label(ax, 0.5, 2.6,
|
||||||
|
'KROK: Nieatomowa kolumna "Telefony" wydzielona do osobnej tabeli.',
|
||||||
|
fontsize=9)
|
||||||
|
add_label(ax, 0.5, 2.25,
|
||||||
|
'Kazda komorka zawiera JEDNA wartosc. Klucz glowny wyznaczony.',
|
||||||
|
fontsize=9)
|
||||||
|
add_label(ax, 0.5, 1.85,
|
||||||
|
'PROBLEM 2NF: NazwaKursu zalezy TYLKO od KursID (czesc klucza).',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.5, 1.5,
|
||||||
|
' Imie, WydzialID, NazwaWydzialu zaleza TYLKO od StID (czesc klucza).',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.5, 1.15,
|
||||||
|
' --> Czesciowe zaleznosci od klucza zlozonego = NARUSZENIE 2NF.',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_1nf_tables.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_1nf_tables.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 3: 2NF — no partial dependencies
|
||||||
|
# ============================================================
|
||||||
|
def draw_2nf():
|
||||||
|
fig, ax = create_figure(11.69, 6.5)
|
||||||
|
|
||||||
|
# Studenci
|
||||||
|
h1 = ['StID*', 'Imie', 'WydzialID', 'NazwaWydzialu']
|
||||||
|
r1 = [['1', 'Anna', 'W4', 'EiTI'],
|
||||||
|
['2', 'Jan', 'W4', 'EiTI'],
|
||||||
|
['3', 'Ewa', 'W2', 'Fizyka']]
|
||||||
|
cw1 = [0.55, 0.55, 0.85, 1.2]
|
||||||
|
draw_table(ax, 0.3, 5.8, 'Studenci (kl: StID)',
|
||||||
|
h1, r1, cw1, highlight_cols={2, 3}, title_fontsize=9)
|
||||||
|
|
||||||
|
# Kursy
|
||||||
|
h2 = ['KursID*', 'NazwaKursu']
|
||||||
|
r2 = [['K10', 'Bazy danych'], ['K20', 'Algorytmy'], ['K30', 'Optyka']]
|
||||||
|
cw2 = [0.7, 1.1]
|
||||||
|
draw_table(ax, 4.0, 5.8, 'Kursy (kl: KursID)',
|
||||||
|
h2, r2, cw2, title_fontsize=9)
|
||||||
|
|
||||||
|
# Zapisy
|
||||||
|
h3 = ['StID*', 'KursID*', 'Prowadzacy']
|
||||||
|
r3 = [['1', 'K10', 'Kowalski'], ['1', 'K20', 'Nowak'],
|
||||||
|
['2', 'K10', 'Kowalski'], ['3', 'K30', 'Wisniewski']]
|
||||||
|
cw3 = [0.55, 0.7, 1.05]
|
||||||
|
draw_table(ax, 6.8, 5.8, 'Zapisy (kl: StID, KursID)',
|
||||||
|
h3, r3, cw3, title_fontsize=9)
|
||||||
|
|
||||||
|
# Telefony
|
||||||
|
h4 = ['StID*', 'Telefon*']
|
||||||
|
r4 = [['1', '111-222'], ['1', '333-444'], ['2', '555-666'], ['3', '777-888']]
|
||||||
|
cw4 = [0.55, 0.85]
|
||||||
|
draw_table(ax, 9.5, 5.8, 'Telefony',
|
||||||
|
h4, r4, cw4, title_fontsize=9)
|
||||||
|
|
||||||
|
# Annotations
|
||||||
|
add_label(ax, 0.3, 3.3,
|
||||||
|
'KROK: Rozbito czesc. zaleznosci — atrybuty zalezne od czesci klucza wydzielone.',
|
||||||
|
fontsize=9)
|
||||||
|
add_label(ax, 0.3, 2.95,
|
||||||
|
' StID -> Imie, WydzialID, NazwaWydzialu ==> tabela Studenci',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.3, 2.65,
|
||||||
|
' KursID -> NazwaKursu ==> tabela Kursy',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.3, 2.3,
|
||||||
|
'PROBLEM 3NF w "Studenci": StID -> WydzialID -> NazwaWydzialu',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.3, 1.95,
|
||||||
|
' NazwaWydzialu zalezy od WydzialID (nie-klucz), nie bezposrednio od StID.',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.3, 1.6,
|
||||||
|
' --> Zaleznosc PRZECHODNIA = NARUSZENIE 3NF.',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_2nf_tables.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_2nf_tables.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 4: 3NF — no transitive dependencies
|
||||||
|
# ============================================================
|
||||||
|
def draw_3nf():
|
||||||
|
fig, ax = create_figure(11.69, 6.5)
|
||||||
|
|
||||||
|
# Studenci (fixed)
|
||||||
|
h1 = ['StID*', 'Imie', 'WydzialID']
|
||||||
|
r1 = [['1', 'Anna', 'W4'], ['2', 'Jan', 'W4'], ['3', 'Ewa', 'W2']]
|
||||||
|
cw1 = [0.55, 0.55, 0.85]
|
||||||
|
draw_table(ax, 0.3, 5.8, 'Studenci (kl: StID)',
|
||||||
|
h1, r1, cw1, title_fontsize=9)
|
||||||
|
|
||||||
|
# Wydzialy (new!)
|
||||||
|
h2 = ['WydzialID*', 'NazwaWydzialu']
|
||||||
|
r2 = [['W4', 'EiTI'], ['W2', 'Fizyka']]
|
||||||
|
cw2 = [0.85, 1.2]
|
||||||
|
draw_table(ax, 2.6, 5.8, 'Wydzialy (kl: WydzialID)',
|
||||||
|
h2, r2, cw2, title_fontsize=9)
|
||||||
|
|
||||||
|
# Kursy
|
||||||
|
h3 = ['KursID*', 'NazwaKursu']
|
||||||
|
r3 = [['K10', 'Bazy danych'], ['K20', 'Algorytmy'], ['K30', 'Optyka']]
|
||||||
|
cw3 = [0.7, 1.1]
|
||||||
|
draw_table(ax, 5.2, 5.8, 'Kursy (kl: KursID)',
|
||||||
|
h3, r3, cw3, title_fontsize=9)
|
||||||
|
|
||||||
|
# Zapisy (highlight BCNF violation)
|
||||||
|
h4 = ['StID*', 'KursID*', 'Prowadzacy']
|
||||||
|
r4 = [['1', 'K10', 'Kowalski'], ['1', 'K20', 'Nowak'],
|
||||||
|
['2', 'K10', 'Kowalski'], ['3', 'K30', 'Wisniewski']]
|
||||||
|
cw4 = [0.55, 0.7, 1.05]
|
||||||
|
draw_table(ax, 7.8, 5.8, 'Zapisy (kl: StID, KursID)',
|
||||||
|
h4, r4, cw4, highlight_cols={1, 2}, title_fontsize=9)
|
||||||
|
|
||||||
|
# Annotations
|
||||||
|
add_label(ax, 0.3, 3.3,
|
||||||
|
'KROK: Rozdzielono Studenci -> Studenci + Wydzialy (usun. zal. przechodnia).',
|
||||||
|
fontsize=9)
|
||||||
|
add_label(ax, 0.3, 2.95,
|
||||||
|
' StID -> WydzialID -> NazwaWydzialu rozbito: NazwaWydzialu w osobnej tabeli.',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.3, 2.55,
|
||||||
|
'PROBLEM BCNF w "Zapisy": FD: Prowadzacy -> KursID (1 prowadzacy = 1 kurs)',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.3, 2.2,
|
||||||
|
' Prowadzacy NIE jest nadkluczem tabeli Zapisy -> NARUSZENIE BCNF.',
|
||||||
|
fontsize=9, color='black')
|
||||||
|
add_label(ax, 0.3, 1.85,
|
||||||
|
' 3NF OK, bo KursID jest atrybutem pierwszym (prime) -> wyjatek 3NF.',
|
||||||
|
fontsize=9, color='#333333')
|
||||||
|
add_label(ax, 0.3, 1.5,
|
||||||
|
' BCNF nie ma takiego wyjatku -> kazda nietrywialna FD wymaga nadklucza po lewej.',
|
||||||
|
fontsize=9, color='#333333')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_3nf_tables.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_3nf_tables.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 5: BCNF — every determinant is a superkey
|
||||||
|
# ============================================================
|
||||||
|
def draw_bcnf():
|
||||||
|
fig, ax = create_figure(11.69, 7.5)
|
||||||
|
|
||||||
|
# Studenci
|
||||||
|
h1 = ['StID*', 'Imie', 'WydzialID']
|
||||||
|
r1 = [['1', 'Anna', 'W4'], ['2', 'Jan', 'W4'], ['3', 'Ewa', 'W2']]
|
||||||
|
cw1 = [0.55, 0.55, 0.85]
|
||||||
|
draw_table(ax, 0.3, 6.8, 'Studenci', h1, r1, cw1, title_fontsize=9)
|
||||||
|
|
||||||
|
# Wydzialy
|
||||||
|
h2 = ['WydzialID*', 'NazwaWydz.']
|
||||||
|
r2 = [['W4', 'EiTI'], ['W2', 'Fizyka']]
|
||||||
|
cw2 = [0.85, 1.0]
|
||||||
|
draw_table(ax, 2.5, 6.8, 'Wydzialy', h2, r2, cw2, title_fontsize=9)
|
||||||
|
|
||||||
|
# Kursy
|
||||||
|
h3 = ['KursID*', 'NazwaKursu']
|
||||||
|
r3 = [['K10', 'Bazy danych'], ['K20', 'Algorytmy'], ['K30', 'Optyka']]
|
||||||
|
cw3 = [0.7, 1.1]
|
||||||
|
draw_table(ax, 4.8, 6.8, 'Kursy', h3, r3, cw3, title_fontsize=9)
|
||||||
|
|
||||||
|
# ProwadzacyKurs (NEW - from BCNF decomposition)
|
||||||
|
h4 = ['Prowadzacy*', 'KursID']
|
||||||
|
r4 = [['Kowalski', 'K10'], ['Nowak', 'K20'], ['Wisniewski', 'K30']]
|
||||||
|
cw4 = [1.05, 0.7]
|
||||||
|
draw_table(ax, 7.2, 6.8, 'ProwadzacyKurs (kl: Prow.)',
|
||||||
|
h4, r4, cw4, title_fontsize=9)
|
||||||
|
|
||||||
|
# StudentProwadzacy (NEW)
|
||||||
|
h5 = ['StID*', 'Prowadzacy*']
|
||||||
|
r5 = [['1', 'Kowalski'], ['1', 'Nowak'], ['2', 'Kowalski'], ['3', 'Wisniewski']]
|
||||||
|
cw5 = [0.55, 1.05]
|
||||||
|
draw_table(ax, 9.5, 6.8, 'StudentProw. (kl: oba)',
|
||||||
|
h5, r5, cw5, title_fontsize=9)
|
||||||
|
|
||||||
|
# Telefony
|
||||||
|
h6 = ['StID*', 'Telefon*']
|
||||||
|
r6 = [['1', '111-222'], ['1', '333-444'], ['2', '555-666'], ['3', '777-888']]
|
||||||
|
cw6 = [0.55, 0.85]
|
||||||
|
draw_table(ax, 0.3, 4.6, 'Telefony', h6, r6, cw6, title_fontsize=9)
|
||||||
|
|
||||||
|
# Annotations
|
||||||
|
add_label(ax, 0.3, 2.9,
|
||||||
|
'KROK: Zapisy(StID, KursID, Prowadzacy) rozbite na:',
|
||||||
|
fontsize=9)
|
||||||
|
add_label(ax, 0.3, 2.55,
|
||||||
|
' ProwadzacyKurs(Prowadzacy, KursID) — FD: Prowadzacy -> KursID, klucz: Prowadzacy',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.3, 2.25,
|
||||||
|
' StudentProwadzacy(StID, Prowadzacy) — ktory student u ktorego prowadzacego',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.3, 1.85,
|
||||||
|
'Teraz KAZDA nietrywialna FD ma nadklucz po lewej stronie -> BCNF spelnione.',
|
||||||
|
fontsize=9)
|
||||||
|
add_label(ax, 0.3, 1.45,
|
||||||
|
'Rekonstrukcja: StudentProw. JOIN ProwadzacyKurs ON Prowadzacy -> odtworzenie Zapisy.',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_bcnf_tables.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_bcnf_tables.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 6: 4NF example — multi-valued dependencies
|
||||||
|
# ============================================================
|
||||||
|
def draw_4nf():
|
||||||
|
fig, ax = create_figure(11.69, 7.5)
|
||||||
|
|
||||||
|
# Before: table with MVD violation
|
||||||
|
h_before = ['StID*', 'Hobby*', 'Umiejetnosc*']
|
||||||
|
r_before = [
|
||||||
|
['1', 'Szachy', 'Python'],
|
||||||
|
['1', 'Szachy', 'SQL'],
|
||||||
|
['1', 'Bieganie', 'Python'],
|
||||||
|
['1', 'Bieganie', 'SQL'],
|
||||||
|
['2', 'Plywanie', 'Java'],
|
||||||
|
]
|
||||||
|
cw_before = [0.55, 0.9, 1.0]
|
||||||
|
draw_table(ax, 0.5, 6.8,
|
||||||
|
'PRZED: StudentAktywnosci (klucz: StID, Hobby, Umiejetnosc)',
|
||||||
|
h_before, r_before, cw_before,
|
||||||
|
highlight_cols={1, 2}, title_fontsize=10)
|
||||||
|
|
||||||
|
# Arrows
|
||||||
|
add_label(ax, 3.5, 6.3, 'StID ->> Hobby', fontsize=9, color='black')
|
||||||
|
add_label(ax, 3.5, 6.0, 'StID ->> Umiejetnosc', fontsize=9, color='black')
|
||||||
|
add_label(ax, 3.5, 5.6, 'NIEZALEZNE MVD w jednej tabeli', fontsize=9, color='black')
|
||||||
|
add_label(ax, 3.5, 5.2, '= iloczyn kartezjanski = NARUSZENIE 4NF', fontsize=9, color='black')
|
||||||
|
|
||||||
|
# After: two tables
|
||||||
|
add_arrow(ax, 3.0, 4.2, 3.0, 3.7, '', '#333333')
|
||||||
|
add_label(ax, 3.2, 3.95, 'dekompozycja', fontsize=8, color='#333333')
|
||||||
|
|
||||||
|
h_hobby = ['StID*', 'Hobby*']
|
||||||
|
r_hobby = [['1', 'Szachy'], ['1', 'Bieganie'], ['2', 'Plywanie']]
|
||||||
|
cw_hobby = [0.55, 0.9]
|
||||||
|
draw_table(ax, 0.5, 3.5,
|
||||||
|
'PO: StudentHobby',
|
||||||
|
h_hobby, r_hobby, cw_hobby, title_fontsize=10)
|
||||||
|
|
||||||
|
h_skill = ['StID*', 'Umiejetnosc*']
|
||||||
|
r_skill = [['1', 'Python'], ['1', 'SQL'], ['2', 'Java']]
|
||||||
|
cw_skill = [0.55, 1.0]
|
||||||
|
draw_table(ax, 3.5, 3.5,
|
||||||
|
'PO: StudentUmiejetnosc',
|
||||||
|
h_skill, r_skill, cw_skill, title_fontsize=10)
|
||||||
|
|
||||||
|
# Summary on the right side
|
||||||
|
add_label(ax, 6.5, 6.5, '4NF: BCNF + brak nietrywialnych MVD', fontsize=10)
|
||||||
|
add_label(ax, 6.5, 6.1,
|
||||||
|
'MVD X ->> Y: jeden X = ZBIOR Y-ow,', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 6.5, 5.8,
|
||||||
|
'niezaleznie od reszty kolumn.', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 6.5, 5.35,
|
||||||
|
'Naruszenie: Student 1 ma 2 hobby i 2 umiejetnosci', fontsize=8)
|
||||||
|
add_label(ax, 6.5, 5.05,
|
||||||
|
' -> 2 x 2 = 4 wiersze (iloczyn kartezjanski!)', fontsize=8)
|
||||||
|
add_label(ax, 6.5, 4.65,
|
||||||
|
'Naprawa: rozdziel niezalezne MVD do osobnych tabel.', fontsize=8)
|
||||||
|
add_label(ax, 6.5, 4.25,
|
||||||
|
'Po dekompozycji: 3 + 3 = 6 wierszy zamiast 5 z ilocz.', fontsize=8,
|
||||||
|
color='#333333')
|
||||||
|
add_label(ax, 6.5, 3.85,
|
||||||
|
' (ale BEZ sztucznych kombinacji!)', fontsize=8, color='#333333')
|
||||||
|
|
||||||
|
# Key insight box
|
||||||
|
rect = mpatches.FancyBboxPatch(
|
||||||
|
(6.3, 2.5), 5.0, 1.0,
|
||||||
|
boxstyle="round,pad=0.1", facecolor='#F0F0F0',
|
||||||
|
edgecolor='black', linewidth=1.0)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
add_label(ax, 6.5, 3.2,
|
||||||
|
'ROZNICA 4NF vs BCNF:', fontsize=9)
|
||||||
|
add_label(ax, 6.5, 2.85,
|
||||||
|
'BCNF dotyczy FD (X -> Y, jedna wartosc)', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 6.5, 2.55,
|
||||||
|
'4NF dotyczy MVD (X ->> Y, zbior wartosci)', fontsize=8, color='#333333')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_4nf_example.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_4nf_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 7: 5NF example — join dependencies
|
||||||
|
# ============================================================
|
||||||
|
def draw_5nf():
|
||||||
|
fig, ax = create_figure(11.69, 8.5)
|
||||||
|
|
||||||
|
# Before: ternary table
|
||||||
|
h_before = ['Dostawca*', 'Czesc*', 'Projekt*']
|
||||||
|
r_before = [
|
||||||
|
['Alfa', 'Sruba', 'Most'],
|
||||||
|
['Alfa', 'Sruba', 'Wiezowiec'],
|
||||||
|
['Alfa', 'Nakretka', 'Most'],
|
||||||
|
['Beta', 'Sruba', 'Wiezowiec'],
|
||||||
|
['Beta', 'Nakretka', 'Wiezowiec'],
|
||||||
|
]
|
||||||
|
cw_before = [0.9, 0.9, 1.0]
|
||||||
|
draw_table(ax, 0.5, 7.8,
|
||||||
|
'PRZED: Dostawy (klucz: Dostawca, Czesc, Projekt)',
|
||||||
|
h_before, r_before, cw_before, title_fontsize=10)
|
||||||
|
|
||||||
|
add_label(ax, 3.8, 7.3, 'Tabela w 4NF (brak nietrywialnych MVD),', fontsize=8)
|
||||||
|
add_label(ax, 3.8, 7.0, 'ale NIE w 5NF jesli zachodzi regula cykliczna:', fontsize=8)
|
||||||
|
add_label(ax, 3.8, 6.55,
|
||||||
|
'Jesli Dostawca dostarcza Czesc', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 3.8, 6.25,
|
||||||
|
' I Dostawca dostarcza do Projektu', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 3.8, 5.95,
|
||||||
|
' I Czesc jest uzywana w Projekcie', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 3.8, 5.65,
|
||||||
|
' ==> Dostawca dostarcza te Czesc do tego Projektu.', fontsize=8,
|
||||||
|
color='black')
|
||||||
|
|
||||||
|
# Arrow down
|
||||||
|
add_arrow(ax, 1.8, 5.1, 1.8, 4.6, 'dekompozycja 5NF', '#333333')
|
||||||
|
|
||||||
|
# After: three binary tables
|
||||||
|
h1 = ['Dostawca*', 'Czesc*']
|
||||||
|
r1 = [['Alfa', 'Sruba'], ['Alfa', 'Nakretka'], ['Beta', 'Sruba'], ['Beta', 'Nakretka']]
|
||||||
|
cw1 = [0.9, 0.9]
|
||||||
|
draw_table(ax, 0.3, 4.3, 'DostawcaCzesc',
|
||||||
|
h1, r1, cw1, title_fontsize=9)
|
||||||
|
|
||||||
|
h2 = ['Dostawca*', 'Projekt*']
|
||||||
|
r2 = [['Alfa', 'Most'], ['Alfa', 'Wiezowiec'], ['Beta', 'Wiezowiec']]
|
||||||
|
cw2 = [0.9, 1.0]
|
||||||
|
draw_table(ax, 3.0, 4.3, 'DostawcaProjekt',
|
||||||
|
h2, r2, cw2, title_fontsize=9)
|
||||||
|
|
||||||
|
h3 = ['Czesc*', 'Projekt*']
|
||||||
|
r3 = [['Sruba', 'Most'], ['Sruba', 'Wiezowiec'],
|
||||||
|
['Nakretka', 'Most'], ['Nakretka', 'Wiezowiec']]
|
||||||
|
cw3 = [0.9, 1.0]
|
||||||
|
draw_table(ax, 5.7, 4.3, 'CzescProjekt',
|
||||||
|
h3, r3, cw3, title_fontsize=9)
|
||||||
|
|
||||||
|
# Join reconstruction note
|
||||||
|
rect = mpatches.FancyBboxPatch(
|
||||||
|
(8.3, 3.5), 3.0, 4.0,
|
||||||
|
boxstyle="round,pad=0.1", facecolor='#F0F0F0',
|
||||||
|
edgecolor='black', linewidth=1.0)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
|
||||||
|
add_label(ax, 8.5, 7.2, '5NF (PJNF):', fontsize=10)
|
||||||
|
add_label(ax, 8.5, 6.8, 'Project-Join NF', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 8.5, 6.35, 'Kazda zaleznosc', fontsize=8)
|
||||||
|
add_label(ax, 8.5, 6.05, 'zlaczenia (JD)', fontsize=8)
|
||||||
|
add_label(ax, 8.5, 5.75, 'implikowana przez', fontsize=8)
|
||||||
|
add_label(ax, 8.5, 5.45, 'klucze kandydujace.', fontsize=8)
|
||||||
|
add_label(ax, 8.5, 4.9, 'Rekonstrukcja:', fontsize=9)
|
||||||
|
add_label(ax, 8.5, 4.55, 'DC JOIN DP JOIN CP', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 8.5, 4.2, '= oryginalna tabela', fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 8.5, 3.75, '(bezstratnie!)', fontsize=8, color='#333333')
|
||||||
|
|
||||||
|
# Verification example at the bottom
|
||||||
|
add_label(ax, 0.3, 2.0,
|
||||||
|
'Weryfikacja: Alfa dostarcza Nakretke? Alfa -> Wiezowiec? Nakretka -> Wiezowiec?',
|
||||||
|
fontsize=8)
|
||||||
|
add_label(ax, 0.3, 1.65,
|
||||||
|
' TAK, TAK, TAK --> wg reguly cyklicznej: Alfa dostarcza Nakretke do Wiezowca.',
|
||||||
|
fontsize=8, color='#333333')
|
||||||
|
add_label(ax, 0.3, 1.25,
|
||||||
|
'Ale: Alfa dostarcza Nakretke? TAK. Alfa -> Most? TAK. Nakretka -> Most? TAK.',
|
||||||
|
fontsize=8)
|
||||||
|
add_label(ax, 0.3, 0.9,
|
||||||
|
' --> Alfa dostarcza Nakretke do Mostu. (Tego wiersza NIE MA w oryginale -- BLAD!)',
|
||||||
|
fontsize=8, color='black')
|
||||||
|
add_label(ax, 0.3, 0.5,
|
||||||
|
' Dekompozycja 5NF jest poprawna TYLKO jesli regula cykliczna rzeczywiscie zachodzi!',
|
||||||
|
fontsize=8, color='black')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_5nf_example.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_5nf_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DIAGRAM 8: Full normalization summary flowchart
|
||||||
|
# ============================================================
|
||||||
|
def draw_summary_flow():
|
||||||
|
fig, ax = create_figure(11.69, 6.0)
|
||||||
|
|
||||||
|
# Boxes for each NF
|
||||||
|
box_y = 4.5
|
||||||
|
box_h = 1.8
|
||||||
|
box_w = 1.4
|
||||||
|
gap = 0.25
|
||||||
|
|
||||||
|
nf_data = [
|
||||||
|
('0NF', 'Nienormalna', 'Listy w\nkomorkach,\nbrak klucza'),
|
||||||
|
('1NF', 'Atomowosc', 'Kazda komorka\n= 1 wartosc,\njest klucz'),
|
||||||
|
('2NF', 'Pelny klucz', 'Brak czesciowej\nzaleznosci od\nklucza zlozonego'),
|
||||||
|
('3NF', 'Tylko klucz', 'Brak zaleznosci\nprzechodniej\nA->B->C'),
|
||||||
|
('BCNF', 'Nadklucz', 'Lewa strona\nkazdej FD\n= nadklucz'),
|
||||||
|
('4NF', 'Brak MVD', 'Brak nietryw.\nwielowart.\nzaleznosci'),
|
||||||
|
('5NF', 'Brak JD', 'Kazda zal.\nzlaczenia\nimpl. kluczem'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (name, subtitle, desc) in enumerate(nf_data):
|
||||||
|
x = 0.3 + i * (box_w + gap)
|
||||||
|
|
||||||
|
# Main box
|
||||||
|
rect = mpatches.FancyBboxPatch(
|
||||||
|
(x, box_y - box_h), box_w, box_h,
|
||||||
|
boxstyle="round,pad=0.05",
|
||||||
|
facecolor='#F5F5F5' if i == 0 else '#FFFFFF',
|
||||||
|
edgecolor='black', linewidth=1.2)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
|
||||||
|
# NF name
|
||||||
|
ax.text(x + box_w / 2, box_y - 0.15, name,
|
||||||
|
fontsize=12, fontweight='bold', ha='center', va='top',
|
||||||
|
family='monospace')
|
||||||
|
|
||||||
|
# Subtitle
|
||||||
|
ax.text(x + box_w / 2, box_y - 0.45, subtitle,
|
||||||
|
fontsize=7, ha='center', va='top',
|
||||||
|
family='monospace', color='#333333')
|
||||||
|
|
||||||
|
# Description
|
||||||
|
ax.text(x + box_w / 2, box_y - 0.75, desc,
|
||||||
|
fontsize=6.5, ha='center', va='top',
|
||||||
|
family='monospace', color='#555555',
|
||||||
|
linespacing=1.3)
|
||||||
|
|
||||||
|
# Arrow to next
|
||||||
|
if i < len(nf_data) - 1:
|
||||||
|
ax.annotate('', xy=(x + box_w + 0.02, box_y - box_h / 2),
|
||||||
|
xytext=(x + box_w + gap - 0.02, box_y - box_h / 2),
|
||||||
|
arrowprops=dict(arrowstyle='<-', color='black', lw=1.5))
|
||||||
|
|
||||||
|
# Bottom: mnemonic
|
||||||
|
ax.text(5.85, 2.2,
|
||||||
|
'\"Klucz, caly klucz i tylko klucz -- tak mi dopomoz Codd\"',
|
||||||
|
fontsize=11, ha='center', va='center', family='monospace',
|
||||||
|
style='italic')
|
||||||
|
ax.text(5.85, 1.8,
|
||||||
|
'1NF: klucz istnieje | 2NF: caly klucz | 3NF: tylko klucz',
|
||||||
|
fontsize=9, ha='center', va='center', family='monospace',
|
||||||
|
color='#333333')
|
||||||
|
ax.text(5.85, 1.4,
|
||||||
|
'BCNF: kazdy determinant = nadklucz | 4NF: +brak MVD | 5NF: +brak JD',
|
||||||
|
fontsize=9, ha='center', va='center', family='monospace',
|
||||||
|
color='#333333')
|
||||||
|
|
||||||
|
# Hierarchy
|
||||||
|
ax.text(5.85, 0.8,
|
||||||
|
'5NF (zawiera sie w) 4NF (zaw.) BCNF (zaw.) 3NF (zaw.) 2NF (zaw.) 1NF',
|
||||||
|
fontsize=8, ha='center', va='center', family='monospace',
|
||||||
|
color='#555555')
|
||||||
|
|
||||||
|
fig.savefig(os.path.join(OUTPUT_DIR, 'nf_summary_flow.png'),
|
||||||
|
bbox_inches='tight', facecolor='white', pad_inches=0.2)
|
||||||
|
plt.close(fig)
|
||||||
|
print("Generated: nf_summary_flow.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Main
|
||||||
|
# ============================================================
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Generating normalization diagrams...")
|
||||||
|
draw_0nf()
|
||||||
|
draw_1nf()
|
||||||
|
draw_2nf()
|
||||||
|
draw_3nf()
|
||||||
|
draw_bcnf()
|
||||||
|
draw_4nf()
|
||||||
|
draw_5nf()
|
||||||
|
draw_summary_flow()
|
||||||
|
print("\nDone! All diagrams saved to:", OUTPUT_DIR)
|
||||||
717
pytania/generate_q9_q12_diagrams.py
Normal file
@ -0,0 +1,717 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate diagrams for:
|
||||||
|
PYTANIE 9: Processes & Threads (IPC mechanisms, deadlock, producer-consumer)
|
||||||
|
PYTANIE 12: Network optimization models (Ford-Fulkerson, Hungarian, CPM, Kruskal, TSP, Min-cost flow)
|
||||||
|
|
||||||
|
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
DPI = 300
|
||||||
|
BG = 'white'
|
||||||
|
LN = 'black'
|
||||||
|
FS = 8
|
||||||
|
FS_TITLE = 11
|
||||||
|
FS_SMALL = 6.5
|
||||||
|
FS_EDGE = 9
|
||||||
|
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
GRAY1 = '#E8E8E8'
|
||||||
|
GRAY2 = '#D0D0D0'
|
||||||
|
GRAY3 = '#B8B8B8'
|
||||||
|
GRAY4 = '#F5F5F5'
|
||||||
|
GRAY5 = '#C0C0C0'
|
||||||
|
LIGHT_GREEN = '#D5E8D4'
|
||||||
|
LIGHT_RED = '#F8D7DA'
|
||||||
|
LIGHT_BLUE = '#D6EAF8'
|
||||||
|
LIGHT_YELLOW = '#FFF9C4'
|
||||||
|
LIGHT_ORANGE = '#FFE0B2'
|
||||||
|
|
||||||
|
|
||||||
|
def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS,
|
||||||
|
fontweight='normal', ha='center', va='center', rounded=True, edgecolor=LN):
|
||||||
|
if rounded:
|
||||||
|
rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05",
|
||||||
|
lw=lw, edgecolor=edgecolor, facecolor=fill)
|
||||||
|
else:
|
||||||
|
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=edgecolor, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize,
|
||||||
|
fontweight=fontweight, wrap=True)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN):
|
||||||
|
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
|
||||||
|
arrowprops=dict(arrowstyle=style, color=color, lw=lw))
|
||||||
|
|
||||||
|
|
||||||
|
def save_fig(fig, name):
|
||||||
|
path = os.path.join(OUTPUT_DIR, name)
|
||||||
|
fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG, pad_inches=0.15)
|
||||||
|
plt.close(fig)
|
||||||
|
print(f" Saved: {path}")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PYTANIE 9 DIAGRAMS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def gen_ipc_mechanisms():
|
||||||
|
"""IPC mechanisms comparison diagram."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(8, 5))
|
||||||
|
ax.set_xlim(0, 10)
|
||||||
|
ax.set_ylim(0, 7)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Mechanizmy IPC — porównanie', fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
mechanisms = [
|
||||||
|
('Pipe', '→ jednokierunkowy\n→ bufor w jądrze\n→ spokrewnione procesy',
|
||||||
|
'ls | grep txt', GRAY1),
|
||||||
|
('Shared\nMemory', '→ wspólna ramka RAM\n→ zero kopiowania\n→ wymaga synchronizacji',
|
||||||
|
'mmap() / shm_open()', LIGHT_GREEN),
|
||||||
|
('Message\nQueue', '→ strukturalne wiad.\n→ asynchroniczna\n→ filtrowanie typów',
|
||||||
|
'msgsnd() / msgrcv()', LIGHT_BLUE),
|
||||||
|
('Socket', '→ dwukierunkowy\n→ lokalny lub sieciowy\n→ TCP/UDP',
|
||||||
|
'connect() / accept()', LIGHT_YELLOW),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (name, desc, example, color) in enumerate(mechanisms):
|
||||||
|
x = 0.3
|
||||||
|
y = 5.5 - i * 1.5
|
||||||
|
# Box for mechanism name
|
||||||
|
draw_box(ax, x, y, 1.5, 1.0, name, fill=color, fontsize=9, fontweight='bold')
|
||||||
|
# Description
|
||||||
|
ax.text(x + 2.0, y + 0.5, desc, fontsize=FS, va='center', ha='left',
|
||||||
|
family='monospace')
|
||||||
|
# Example
|
||||||
|
draw_box(ax, 6.5, y + 0.15, 3.0, 0.7, example, fill=GRAY4, fontsize=FS_SMALL)
|
||||||
|
|
||||||
|
# Draw process boxes for pipe illustration at top
|
||||||
|
y_top = 6.3
|
||||||
|
ax.text(5.0, y_top, 'Proces A ──bufor jądra──▶ Proces B',
|
||||||
|
fontsize=FS, ha='center', va='center', family='monospace',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY1, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
# Legend
|
||||||
|
ax.text(0.3, 0.3, 'Szybkość: Shared Memory > Pipe ≈ MsgQueue > Socket (sieciowy)',
|
||||||
|
fontsize=FS, va='center', style='italic')
|
||||||
|
|
||||||
|
save_fig(fig, 'ipc_mechanisms.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_deadlock_illustration():
|
||||||
|
"""Deadlock circular wait diagram."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
|
||||||
|
ax.set_xlim(0, 8)
|
||||||
|
ax.set_ylim(0, 6.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Zakleszczenie (Deadlock) — cykliczne oczekiwanie', fontsize=FS_TITLE,
|
||||||
|
fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
# Thread boxes
|
||||||
|
draw_box(ax, 0.5, 3.5, 2.0, 1.2, 'Wątek A\n(trzyma Mutex 1)', fill=LIGHT_BLUE,
|
||||||
|
fontsize=9, fontweight='bold')
|
||||||
|
draw_box(ax, 5.5, 3.5, 2.0, 1.2, 'Wątek B\n(trzyma Mutex 2)', fill=LIGHT_ORANGE,
|
||||||
|
fontsize=9, fontweight='bold')
|
||||||
|
|
||||||
|
# Resource boxes
|
||||||
|
draw_box(ax, 0.5, 0.8, 2.0, 1.0, 'Mutex 1\nzablokowany', fill=GRAY2,
|
||||||
|
fontsize=8, fontweight='bold')
|
||||||
|
draw_box(ax, 5.5, 0.8, 2.0, 1.0, 'Mutex 2\nzablokowany', fill=GRAY2,
|
||||||
|
fontsize=8, fontweight='bold')
|
||||||
|
|
||||||
|
# Arrows: "holds" (down)
|
||||||
|
draw_arrow(ax, 1.5, 3.5, 1.5, 1.8, lw=2.0, color='#333333')
|
||||||
|
ax.text(0.3, 2.65, 'trzyma', fontsize=FS, ha='center', rotation=90, color='#333333')
|
||||||
|
|
||||||
|
draw_arrow(ax, 6.5, 3.5, 6.5, 1.8, lw=2.0, color='#333333')
|
||||||
|
ax.text(7.7, 2.65, 'trzyma', fontsize=FS, ha='center', rotation=90, color='#333333')
|
||||||
|
|
||||||
|
# Arrows: "waits for" (across, with red)
|
||||||
|
draw_arrow(ax, 2.5, 4.3, 5.5, 4.3, lw=2.5, color='#C62828')
|
||||||
|
ax.text(4.0, 4.6, 'czeka na Mutex 2', fontsize=FS, ha='center', color='#C62828',
|
||||||
|
fontweight='bold')
|
||||||
|
|
||||||
|
draw_arrow(ax, 5.5, 3.7, 2.5, 3.7, lw=2.5, color='#C62828')
|
||||||
|
ax.text(4.0, 3.2, 'czeka na Mutex 1', fontsize=FS, ha='center', color='#C62828',
|
||||||
|
fontweight='bold')
|
||||||
|
|
||||||
|
# Coffman conditions
|
||||||
|
conditions = [
|
||||||
|
'1. Mutual Exclusion — zasoby wyłączne',
|
||||||
|
'2. Hold and Wait — trzymaj + czekaj',
|
||||||
|
'3. No Preemption — nie można zabrać siłą',
|
||||||
|
'4. Circular Wait — cykl oczekiwania ← złam ten!'
|
||||||
|
]
|
||||||
|
for i, cond in enumerate(conditions):
|
||||||
|
color_c = '#C62828' if i == 3 else LN
|
||||||
|
fw = 'bold' if i == 3 else 'normal'
|
||||||
|
ax.text(0.5, 0.5 - i * 0.25 + 0.2, cond, fontsize=FS_SMALL, color=color_c,
|
||||||
|
fontweight=fw, va='center')
|
||||||
|
|
||||||
|
save_fig(fig, 'deadlock_illustration.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_producer_consumer():
|
||||||
|
"""Producer-consumer with bounded buffer diagram."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(8, 4.5))
|
||||||
|
ax.set_xlim(0, 10)
|
||||||
|
ax.set_ylim(0, 5.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Producent-Konsument z buforem cyklicznym (N=4)', fontsize=FS_TITLE,
|
||||||
|
fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
# Producer
|
||||||
|
draw_box(ax, 0.3, 2.0, 1.8, 1.5, 'Producent\n\nwstaw(elem)\nV(full)\nV(mutex)',
|
||||||
|
fill=LIGHT_GREEN, fontsize=FS, fontweight='bold')
|
||||||
|
|
||||||
|
# Buffer slots
|
||||||
|
buf_x = 3.0
|
||||||
|
buf_y = 2.5
|
||||||
|
slot_w = 1.0
|
||||||
|
slot_h = 0.8
|
||||||
|
items = ['A', 'B', '', '']
|
||||||
|
fills = [LIGHT_BLUE, LIGHT_BLUE, 'white', 'white']
|
||||||
|
for i, (item, fc) in enumerate(zip(items, fills)):
|
||||||
|
x = buf_x + i * slot_w
|
||||||
|
draw_box(ax, x, buf_y, slot_w, slot_h, item, fill=fc, fontsize=10,
|
||||||
|
fontweight='bold', rounded=False)
|
||||||
|
|
||||||
|
ax.text(buf_x + 2.0, buf_y + slot_h + 0.3, 'Bufor (N=4)',
|
||||||
|
fontsize=9, ha='center', fontweight='bold')
|
||||||
|
ax.text(buf_x + 2.0, buf_y - 0.3, 'full=2, empty=2',
|
||||||
|
fontsize=FS, ha='center', family='monospace')
|
||||||
|
|
||||||
|
# Consumer
|
||||||
|
draw_box(ax, 7.8, 2.0, 1.8, 1.5, 'Konsument\n\npobierz()\nV(empty)\nV(mutex)',
|
||||||
|
fill=LIGHT_YELLOW, fontsize=FS, fontweight='bold')
|
||||||
|
|
||||||
|
# Arrows
|
||||||
|
draw_arrow(ax, 2.1, 2.75, 3.0, 2.9, lw=1.5)
|
||||||
|
draw_arrow(ax, 7.0, 2.9, 7.8, 2.75, lw=1.5)
|
||||||
|
|
||||||
|
# Semaphores
|
||||||
|
sems = [
|
||||||
|
('mutex = 1', 'wyłączny dostęp do bufora', GRAY2),
|
||||||
|
('empty = 2', 'wolne sloty (P = czekaj, V = +1)', LIGHT_GREEN),
|
||||||
|
('full = 2', 'pełne sloty (P = czekaj, V = +1)', LIGHT_BLUE),
|
||||||
|
]
|
||||||
|
for i, (name, desc, color) in enumerate(sems):
|
||||||
|
y = 1.2 - i * 0.45
|
||||||
|
draw_box(ax, 3.0, y, 1.5, 0.35, name, fill=color, fontsize=FS_SMALL,
|
||||||
|
fontweight='bold')
|
||||||
|
ax.text(4.7, y + 0.17, desc, fontsize=FS_SMALL, va='center')
|
||||||
|
|
||||||
|
# Warning
|
||||||
|
ax.text(0.3, 4.8, 'KOLEJNOŚĆ: P(empty/full) PRZED P(mutex)! Odwrotnie = DEADLOCK',
|
||||||
|
fontsize=FS, fontweight='bold', color='#C62828',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.2', facecolor=LIGHT_RED, edgecolor='#C62828'))
|
||||||
|
|
||||||
|
save_fig(fig, 'producer_consumer.png')
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PYTANIE 12 DIAGRAMS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def draw_network_node(ax, name, pos, color='white', fontsize=10, r=0.3):
|
||||||
|
"""Draw a network node (circle)."""
|
||||||
|
x, y = pos
|
||||||
|
circle = plt.Circle((x, y), r, fill=True, facecolor=color,
|
||||||
|
edgecolor=LN, linewidth=1.5, zorder=5)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
ax.text(x, y, name, ha='center', va='center', fontsize=fontsize,
|
||||||
|
fontweight='bold', zorder=6)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_network_edge(ax, pos1, pos2, label='', color=LN, lw=1.5, offset=0.0,
|
||||||
|
directed=True, r=0.33, label_bg='white'):
|
||||||
|
"""Draw a directed edge with label."""
|
||||||
|
x1, y1 = pos1
|
||||||
|
x2, y2 = pos2
|
||||||
|
dx, dy = x2 - x1, y2 - y1
|
||||||
|
length = np.sqrt(dx**2 + dy**2)
|
||||||
|
if length == 0:
|
||||||
|
return
|
||||||
|
sx = x1 + r * dx / length
|
||||||
|
sy = y1 + r * dy / length
|
||||||
|
ex = x2 - r * dx / length
|
||||||
|
ey = y2 - r * dy / length
|
||||||
|
|
||||||
|
if directed:
|
||||||
|
ax.annotate("", xy=(ex, ey), xytext=(sx, sy),
|
||||||
|
arrowprops=dict(arrowstyle='->', color=color, lw=lw))
|
||||||
|
else:
|
||||||
|
ax.plot([sx, ex], [sy, ey], color=color, linewidth=lw, zorder=2)
|
||||||
|
|
||||||
|
if label:
|
||||||
|
mx = (x1 + x2) / 2
|
||||||
|
my = (y1 + y2) / 2
|
||||||
|
perp_x = -dy / length * (0.2 + offset)
|
||||||
|
perp_y = dx / length * (0.2 + offset)
|
||||||
|
ax.text(mx + perp_x, my + perp_y, str(label), ha='center', va='center',
|
||||||
|
fontsize=FS_EDGE, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=label_bg,
|
||||||
|
edgecolor=GRAY3, alpha=0.95), zorder=4)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_ford_fulkerson():
|
||||||
|
"""Ford-Fulkerson max flow step-by-step."""
|
||||||
|
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
|
||||||
|
fig.suptitle('Ford-Fulkerson — Maksymalny przepływ (krok po kroku)',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold')
|
||||||
|
|
||||||
|
pos = {'s': (0.5, 1.5), 'A': (2.5, 2.5), 'B': (2.5, 0.5), 't': (4.5, 1.5)}
|
||||||
|
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
'title': 'Krok 0: Sieć wejściowa\n(przepustowości)',
|
||||||
|
'edges': [('s','A','10'), ('s','B','8'), ('A','t','6'),
|
||||||
|
('B','t','10'), ('B','A','2')],
|
||||||
|
'flows': {},
|
||||||
|
'path': [],
|
||||||
|
'note': 'Szukamy ścieżki s→...→t'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 1: Ścieżka s→A→t\nPrzepływ: +6 (min(10,6))',
|
||||||
|
'edges': [('s','A','4/10'), ('s','B','0/8'), ('A','t','6/6'),
|
||||||
|
('B','t','0/10'), ('B','A','0/2')],
|
||||||
|
'flows': {},
|
||||||
|
'path': [('s','A'), ('A','t')],
|
||||||
|
'note': 'Łączny przepływ: 6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 2: Ścieżka s→B→t\nPrzepływ: +8 (min(8,10))',
|
||||||
|
'edges': [('s','A','4/10'), ('s','B','8/8'), ('A','t','6/6'),
|
||||||
|
('B','t','8/10'), ('B','A','0/2')],
|
||||||
|
'flows': {},
|
||||||
|
'path': [('s','B'), ('B','t')],
|
||||||
|
'note': 'Łączny przepływ: 14'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 3: Brak ścieżki powiększającej\nMAX FLOW = 14',
|
||||||
|
'edges': [('s','A','4/10'), ('s','B','8/8'), ('A','t','6/6'),
|
||||||
|
('B','t','8/10'), ('B','A','0/2')],
|
||||||
|
'flows': {},
|
||||||
|
'path': [],
|
||||||
|
'note': 'Min-cut: {s,A,B}|{t}\nA→t(6)+B→t(10)=16? Nie!\ns→B(8)+A→t(6)=14 ✓'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for idx, (ax, step) in enumerate(zip(axes.flat, steps)):
|
||||||
|
ax.set_xlim(-0.3, 5.3)
|
||||||
|
ax.set_ylim(-0.3, 3.3)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title(step['title'], fontsize=FS, fontweight='bold', pad=5)
|
||||||
|
|
||||||
|
path_set = set(step['path'])
|
||||||
|
|
||||||
|
for e in step['edges']:
|
||||||
|
u, v, label = e
|
||||||
|
is_path = (u, v) in path_set
|
||||||
|
c = '#C62828' if is_path else LN
|
||||||
|
w = 2.5 if is_path else 1.5
|
||||||
|
draw_network_edge(ax, pos[u], pos[v], label=label, color=c, lw=w)
|
||||||
|
|
||||||
|
for name, p in pos.items():
|
||||||
|
if name == 's':
|
||||||
|
c = LIGHT_GREEN
|
||||||
|
elif name == 't':
|
||||||
|
c = LIGHT_RED
|
||||||
|
else:
|
||||||
|
c = 'white'
|
||||||
|
draw_network_node(ax, name, p, color=c)
|
||||||
|
|
||||||
|
ax.text(2.5, -0.15, step['note'], fontsize=FS_SMALL, ha='center',
|
||||||
|
va='center', style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
fig.tight_layout(rect=[0, 0, 1, 0.93])
|
||||||
|
save_fig(fig, 'ford_fulkerson_example.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hungarian():
|
||||||
|
"""Hungarian algorithm step-by-step."""
|
||||||
|
fig, axes = plt.subplots(2, 2, figsize=(9, 7))
|
||||||
|
fig.suptitle('Algorytm węgierski — Problem przydziału (krok po kroku)',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold')
|
||||||
|
|
||||||
|
matrices = [
|
||||||
|
{
|
||||||
|
'title': 'Macierz kosztów (wejściowa)',
|
||||||
|
'data': [[8, 4, 7], [5, 2, 3], [9, 4, 8]],
|
||||||
|
'highlight': [],
|
||||||
|
'note': 'Minimalizuj łączny koszt przydziału',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 1: Redukcja wierszy\n(odejmij min z wiersza)',
|
||||||
|
'data': [[4, 0, 3], [3, 0, 1], [5, 0, 4]],
|
||||||
|
'highlight': [(0,1), (1,1), (2,1)],
|
||||||
|
'note': 'min: A=4, B=2, C=4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 2: Redukcja kolumn\n(odejmij min z kolumny)',
|
||||||
|
'data': [[1, 0, 2], [0, 0, 0], [2, 0, 3]],
|
||||||
|
'highlight': [(1,0), (0,1), (1,1), (2,1), (1,2)],
|
||||||
|
'note': 'min: Z1=3, Z2=0, Z3=1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 3: Optymalne przypisanie\nA→Z2(4), B→Z1(5), C=?',
|
||||||
|
'data': [[0, 0, 1], [0, 1, 0], [1, 0, 2]],
|
||||||
|
'highlight': [(0,1), (1,0), (2,1)],
|
||||||
|
'note': 'Optymalne: A→Z1(8) + B→Z3(3) + C→Z2(4) = 15',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
rows = ['A', 'B', 'C']
|
||||||
|
cols = ['Z1', 'Z2', 'Z3']
|
||||||
|
|
||||||
|
for ax, m in zip(axes.flat, matrices):
|
||||||
|
ax.set_xlim(-0.5, 4.5)
|
||||||
|
ax.set_ylim(-1, 4.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title(m['title'], fontsize=FS, fontweight='bold', pad=5)
|
||||||
|
|
||||||
|
# Column headers
|
||||||
|
for j, col in enumerate(cols):
|
||||||
|
ax.text(j + 1.5, 3.8, col, ha='center', va='center', fontsize=9,
|
||||||
|
fontweight='bold')
|
||||||
|
|
||||||
|
# Row headers and data
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
y = 2.8 - i
|
||||||
|
ax.text(0.3, y, row, ha='center', va='center', fontsize=9,
|
||||||
|
fontweight='bold')
|
||||||
|
for j in range(3):
|
||||||
|
val = m['data'][i][j]
|
||||||
|
is_zero = val == 0
|
||||||
|
is_hl = (i, j) in m['highlight']
|
||||||
|
fc = LIGHT_GREEN if is_hl else ('white' if not is_zero else LIGHT_YELLOW)
|
||||||
|
rect = FancyBboxPatch((j + 1.0, y - 0.35), 1.0, 0.7,
|
||||||
|
boxstyle="round,pad=0.05", lw=1.2,
|
||||||
|
edgecolor=LN if not is_hl else '#1B5E20',
|
||||||
|
facecolor=fc)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(j + 1.5, y, str(val), ha='center', va='center',
|
||||||
|
fontsize=10, fontweight='bold' if is_hl else 'normal')
|
||||||
|
|
||||||
|
ax.text(2.0, -0.6, m['note'], fontsize=FS_SMALL, ha='center', va='center',
|
||||||
|
style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
fig.tight_layout(rect=[0, 0, 1, 0.93])
|
||||||
|
save_fig(fig, 'hungarian_example.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_cpm():
|
||||||
|
"""CPM critical path diagram."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
|
||||||
|
ax.set_xlim(-0.5, 12)
|
||||||
|
ax.set_ylim(-0.5, 5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('CPM — Ścieżka krytyczna projektu IT', fontsize=FS_TITLE,
|
||||||
|
fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
# Task positions: (x, y)
|
||||||
|
tasks = {
|
||||||
|
'START': (0.5, 2.5),
|
||||||
|
'A\n3 tyg': (2.5, 2.5),
|
||||||
|
'B\n4 tyg': (5.0, 3.8),
|
||||||
|
'C\n5 tyg': (5.0, 1.2),
|
||||||
|
'D\n6 tyg': (7.5, 3.8),
|
||||||
|
'E\n2 tyg': (9.5, 2.5),
|
||||||
|
'F\n1 tyg': (11.5, 2.5),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Critical path: START→A→B→D→E→F
|
||||||
|
critical = {'START', 'A\n3 tyg', 'B\n4 tyg', 'D\n6 tyg', 'E\n2 tyg', 'F\n1 tyg'}
|
||||||
|
critical_edges = {
|
||||||
|
('START', 'A\n3 tyg'), ('A\n3 tyg', 'B\n4 tyg'),
|
||||||
|
('B\n4 tyg', 'D\n6 tyg'), ('D\n6 tyg', 'E\n2 tyg'),
|
||||||
|
('E\n2 tyg', 'F\n1 tyg'),
|
||||||
|
}
|
||||||
|
|
||||||
|
edges = [
|
||||||
|
('START', 'A\n3 tyg'),
|
||||||
|
('A\n3 tyg', 'B\n4 tyg'),
|
||||||
|
('A\n3 tyg', 'C\n5 tyg'),
|
||||||
|
('B\n4 tyg', 'D\n6 tyg'),
|
||||||
|
('C\n5 tyg', 'E\n2 tyg'),
|
||||||
|
('D\n6 tyg', 'E\n2 tyg'),
|
||||||
|
('E\n2 tyg', 'F\n1 tyg'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Draw edges
|
||||||
|
for u, v in edges:
|
||||||
|
is_crit = (u, v) in critical_edges
|
||||||
|
c = '#C62828' if is_crit else GRAY3
|
||||||
|
w = 2.5 if is_crit else 1.2
|
||||||
|
draw_network_edge(ax, tasks[u], tasks[v], color=c, lw=w, r=0.5)
|
||||||
|
|
||||||
|
# Draw nodes
|
||||||
|
for name, p in tasks.items():
|
||||||
|
is_crit = name in critical
|
||||||
|
c = LIGHT_RED if is_crit else LIGHT_BLUE
|
||||||
|
r = 0.45
|
||||||
|
circle = plt.Circle(p, r, fill=True, facecolor=c,
|
||||||
|
edgecolor='#C62828' if is_crit else LN,
|
||||||
|
linewidth=2.0 if is_crit else 1.2, zorder=5)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
ax.text(p[0], p[1], name, ha='center', va='center',
|
||||||
|
fontsize=7 if '\n' in name else 8,
|
||||||
|
fontweight='bold', zorder=6)
|
||||||
|
|
||||||
|
# ES/EF labels
|
||||||
|
es_ef = [
|
||||||
|
('A\n3 tyg', 'ES=0, EF=3'),
|
||||||
|
('B\n4 tyg', 'ES=3, EF=7'),
|
||||||
|
('C\n5 tyg', 'ES=3, EF=8\nzapas=5'),
|
||||||
|
('D\n6 tyg', 'ES=7, EF=13'),
|
||||||
|
('E\n2 tyg', 'ES=13, EF=15'),
|
||||||
|
('F\n1 tyg', 'ES=15, EF=16'),
|
||||||
|
]
|
||||||
|
for name, label in es_ef:
|
||||||
|
x, y = tasks[name]
|
||||||
|
offset_y = 0.7 if y > 2.5 else -0.7
|
||||||
|
ax.text(x, y + offset_y, label, ha='center', va='center', fontsize=FS_SMALL,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor='white',
|
||||||
|
edgecolor=GRAY3, alpha=0.95))
|
||||||
|
|
||||||
|
# Legend
|
||||||
|
ax.text(0.5, -0.2, 'Ścieżka krytyczna: A→B→D→E→F (16 tyg)',
|
||||||
|
fontsize=9, fontweight='bold', color='#C62828')
|
||||||
|
ax.text(0.5, -0.6, 'C ma 5 tyg zapasu — może się opóźnić bez wpływu na projekt',
|
||||||
|
fontsize=FS, style='italic')
|
||||||
|
|
||||||
|
save_fig(fig, 'cpm_example.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_kruskal():
|
||||||
|
"""Kruskal MST construction step-by-step."""
|
||||||
|
fig, axes = plt.subplots(2, 2, figsize=(9, 8))
|
||||||
|
fig.suptitle('Kruskal — budowa MST krok po kroku', fontsize=FS_TITLE, fontweight='bold')
|
||||||
|
|
||||||
|
pos = {'A': (0.5, 2.5), 'B': (3.0, 2.5), 'C': (3.0, 0.5), 'D': (0.5, 0.5)}
|
||||||
|
|
||||||
|
all_edges = [
|
||||||
|
('C', 'D', 1), ('A', 'C', 2), ('A', 'B', 4),
|
||||||
|
('B', 'C', 6), ('B', 'D', 7), ('A', 'D', 8)
|
||||||
|
]
|
||||||
|
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
'title': 'Graf wejściowy\n(6 krawędzi)',
|
||||||
|
'mst': [],
|
||||||
|
'consider': None,
|
||||||
|
'note': 'Posortowane: CD(1), AC(2), AB(4), BC(6), BD(7), AD(8)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 1: Dodaj C-D (waga 1)\nNajlżejsza krawędź',
|
||||||
|
'mst': [('C', 'D', 1)],
|
||||||
|
'consider': ('C', 'D'),
|
||||||
|
'note': 'MST = {C-D}, koszt = 1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 2: Dodaj A-C (waga 2)\nA nie w {C,D}',
|
||||||
|
'mst': [('C', 'D', 1), ('A', 'C', 2)],
|
||||||
|
'consider': ('A', 'C'),
|
||||||
|
'note': 'MST = {C-D, A-C}, koszt = 3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 3: Dodaj A-B (waga 4)\nB nie w {A,C,D} → KONIEC',
|
||||||
|
'mst': [('C', 'D', 1), ('A', 'C', 2), ('A', 'B', 4)],
|
||||||
|
'consider': ('A', 'B'),
|
||||||
|
'note': 'MST = {C-D, A-C, A-B}, koszt = 7 ✓'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for ax, step in zip(axes.flat, steps):
|
||||||
|
ax.set_xlim(-0.5, 4.0)
|
||||||
|
ax.set_ylim(-0.5, 3.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title(step['title'], fontsize=FS, fontweight='bold', pad=5)
|
||||||
|
|
||||||
|
mst_set = set((u, v) for u, v, _ in step['mst'])
|
||||||
|
|
||||||
|
for u, v, w in all_edges:
|
||||||
|
in_mst = (u, v) in mst_set or (v, u) in mst_set
|
||||||
|
is_cur = step['consider'] and ((u, v) == step['consider'] or (v, u) == step['consider'])
|
||||||
|
if is_cur:
|
||||||
|
c, lw = '#C62828', 3.0
|
||||||
|
elif in_mst:
|
||||||
|
c, lw = '#1B5E20', 2.5
|
||||||
|
else:
|
||||||
|
c, lw = GRAY3, 1.0
|
||||||
|
draw_network_edge(ax, pos[u], pos[v], label=str(w), color=c, lw=lw,
|
||||||
|
directed=False, label_bg=LIGHT_GREEN if in_mst else 'white')
|
||||||
|
|
||||||
|
for name, p in pos.items():
|
||||||
|
# Check if in current MST component
|
||||||
|
in_mst = any(name in (u, v) for u, v, _ in step['mst'])
|
||||||
|
c = LIGHT_GREEN if in_mst else 'white'
|
||||||
|
draw_network_node(ax, name, p, color=c, r=0.3)
|
||||||
|
|
||||||
|
ax.text(1.75, -0.3, step['note'], fontsize=FS_SMALL, ha='center',
|
||||||
|
va='center', style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
fig.tight_layout(rect=[0, 0, 1, 0.93])
|
||||||
|
save_fig(fig, 'kruskal_example.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_tsp():
|
||||||
|
"""TSP nearest neighbor heuristic."""
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(10, 4.5))
|
||||||
|
fig.suptitle('TSP — heurystyka Nearest Neighbor (5 miast)',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold')
|
||||||
|
|
||||||
|
pos = {
|
||||||
|
'A': (0.5, 3.0), 'B': (2.0, 4.0), 'C': (4.0, 3.5),
|
||||||
|
'D': (3.5, 1.0), 'E': (1.5, 1.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
dist = {
|
||||||
|
('A','B'): 20, ('A','C'): 42, ('A','D'): 35, ('A','E'): 12,
|
||||||
|
('B','C'): 30, ('B','D'): 34, ('B','E'): 10,
|
||||||
|
('C','D'): 12, ('C','E'): 40,
|
||||||
|
('D','E'): 25,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Left: full graph with all distances
|
||||||
|
ax = axes[0]
|
||||||
|
ax.set_xlim(-0.5, 5.0)
|
||||||
|
ax.set_ylim(0, 5.0)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Graf pełny (odległości)', fontsize=FS, fontweight='bold')
|
||||||
|
|
||||||
|
for (u, v), d in dist.items():
|
||||||
|
draw_network_edge(ax, pos[u], pos[v], label=str(d), color=GRAY3, lw=0.8,
|
||||||
|
directed=False, r=0.3)
|
||||||
|
|
||||||
|
for name, p in pos.items():
|
||||||
|
draw_network_node(ax, name, p, color=LIGHT_BLUE, r=0.3)
|
||||||
|
|
||||||
|
# Right: NN solution
|
||||||
|
ax = axes[1]
|
||||||
|
ax.set_xlim(-0.5, 5.0)
|
||||||
|
ax.set_ylim(0, 5.0)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Nearest Neighbor (start A)\nTrasa: A→E→B→C→D→A = 99', fontsize=FS,
|
||||||
|
fontweight='bold')
|
||||||
|
|
||||||
|
nn_path = [('A','E',12), ('E','B',10), ('B','C',30), ('C','D',12), ('D','A',35)]
|
||||||
|
colors = ['#C62828', '#1B5E20', '#1565C0', '#E65100', '#4A148C']
|
||||||
|
|
||||||
|
for i, (u, v, d) in enumerate(nn_path):
|
||||||
|
draw_network_edge(ax, pos[u], pos[v], label=f'{d}',
|
||||||
|
color=colors[i], lw=2.0, directed=True, r=0.3)
|
||||||
|
# Step number
|
||||||
|
mx = (pos[u][0] + pos[v][0]) / 2
|
||||||
|
my = (pos[u][1] + pos[v][1]) / 2
|
||||||
|
dx = pos[v][0] - pos[u][0]
|
||||||
|
dy = pos[v][1] - pos[u][1]
|
||||||
|
length = np.sqrt(dx**2 + dy**2)
|
||||||
|
ox = dy / length * 0.45
|
||||||
|
oy = -dx / length * 0.45
|
||||||
|
ax.text(mx + ox, my + oy, f'#{i+1}', fontsize=FS_SMALL, ha='center',
|
||||||
|
color=colors[i], fontweight='bold')
|
||||||
|
|
||||||
|
for name, p in pos.items():
|
||||||
|
c = LIGHT_GREEN if name == 'A' else LIGHT_BLUE
|
||||||
|
draw_network_node(ax, name, p, color=c, r=0.3)
|
||||||
|
|
||||||
|
fig.tight_layout(rect=[0, 0, 1, 0.9])
|
||||||
|
save_fig(fig, 'tsp_nearest_neighbor.png')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_min_cost_flow():
|
||||||
|
"""Min-cost flow example."""
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
|
||||||
|
fig.suptitle('Minimalny koszt przepływu — transport 10 ton',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold')
|
||||||
|
|
||||||
|
pos = {'s': (0.5, 1.5), 'A': (2.5, 2.5), 'B': (2.5, 0.5), 't': (4.5, 1.5)}
|
||||||
|
|
||||||
|
# Left: network with capacities and costs
|
||||||
|
ax = axes[0]
|
||||||
|
ax.set_xlim(-0.3, 5.3)
|
||||||
|
ax.set_ylim(-0.3, 3.3)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Sieć (przepustowość, koszt/t)', fontsize=FS, fontweight='bold')
|
||||||
|
|
||||||
|
edges_info = [
|
||||||
|
('s', 'A', '(8, 2zł)'), ('s', 'B', '(5, 4zł)'),
|
||||||
|
('A', 't', '(6, 3zł)'), ('B', 't', '(5, 1zł)'),
|
||||||
|
]
|
||||||
|
for u, v, label in edges_info:
|
||||||
|
draw_network_edge(ax, pos[u], pos[v], label=label, r=0.33)
|
||||||
|
|
||||||
|
for name, p in pos.items():
|
||||||
|
c = LIGHT_GREEN if name == 's' else (LIGHT_RED if name == 't' else 'white')
|
||||||
|
draw_network_node(ax, name, p, color=c)
|
||||||
|
|
||||||
|
# Right: optimal flow
|
||||||
|
ax = axes[1]
|
||||||
|
ax.set_xlim(-0.3, 5.3)
|
||||||
|
ax.set_ylim(-0.3, 3.3)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Optymalny przepływ (koszt = 50 zł)', fontsize=FS, fontweight='bold')
|
||||||
|
|
||||||
|
opt_edges = [
|
||||||
|
('s', 'A', '5/8', '#1B5E20'), ('s', 'B', '5/5', '#C62828'),
|
||||||
|
('A', 't', '5/6', '#1B5E20'), ('B', 't', '5/5', '#C62828'),
|
||||||
|
]
|
||||||
|
for u, v, label, color in opt_edges:
|
||||||
|
draw_network_edge(ax, pos[u], pos[v], label=label, color=color, lw=2.0, r=0.33)
|
||||||
|
|
||||||
|
for name, p in pos.items():
|
||||||
|
c = LIGHT_GREEN if name == 's' else (LIGHT_RED if name == 't' else 'white')
|
||||||
|
draw_network_node(ax, name, p, color=c)
|
||||||
|
|
||||||
|
ax.text(2.5, -0.15, '5t×(2+3)=25zł + 5t×(4+1)=25zł = 50zł',
|
||||||
|
fontsize=FS, ha='center', style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
fig.tight_layout(rect=[0, 0, 1, 0.9])
|
||||||
|
save_fig(fig, 'min_cost_flow_example.png')
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Generating PYTANIE 9 diagrams...")
|
||||||
|
gen_ipc_mechanisms()
|
||||||
|
gen_deadlock_illustration()
|
||||||
|
gen_producer_consumer()
|
||||||
|
|
||||||
|
print("\nGenerating PYTANIE 12 diagrams...")
|
||||||
|
gen_ford_fulkerson()
|
||||||
|
gen_hungarian()
|
||||||
|
gen_cpm()
|
||||||
|
gen_kruskal()
|
||||||
|
gen_tsp()
|
||||||
|
gen_min_cost_flow()
|
||||||
|
|
||||||
|
print("\nAll diagrams generated successfully!")
|
||||||
778
pytania/generate_scheduling_diagrams.py
Normal file
@ -0,0 +1,778 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate diagrams for PYTANIE 17: Szeregowanie zadań (Scheduling).
|
||||||
|
|
||||||
|
Diagrams:
|
||||||
|
1. Graham notation α|β|γ visual mnemonic map
|
||||||
|
2. Johnson's algorithm Gantt chart (F2||Cmax example)
|
||||||
|
3. SPT vs LPT comparison Gantt (1||ΣCⱼ)
|
||||||
|
4. Flow shop vs Job shop visual comparison
|
||||||
|
5. Scheduling complexity landscape
|
||||||
|
|
||||||
|
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as mpatches
|
||||||
|
from matplotlib.patches import FancyBboxPatch
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
DPI = 300
|
||||||
|
BG = 'white'
|
||||||
|
LN = 'black'
|
||||||
|
FS = 8
|
||||||
|
FS_TITLE = 11
|
||||||
|
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
GRAY1 = '#E8E8E8'
|
||||||
|
GRAY2 = '#D0D0D0'
|
||||||
|
GRAY3 = '#B8B8B8'
|
||||||
|
GRAY4 = '#F5F5F5'
|
||||||
|
GRAY5 = '#C0C0C0'
|
||||||
|
|
||||||
|
|
||||||
|
def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS,
|
||||||
|
fontweight='normal', ha='center', va='center', rounded=True):
|
||||||
|
if rounded:
|
||||||
|
rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05",
|
||||||
|
lw=lw, edgecolor=LN, facecolor=fill)
|
||||||
|
else:
|
||||||
|
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize,
|
||||||
|
fontweight=fontweight, wrap=True)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN):
|
||||||
|
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
|
||||||
|
arrowprops=dict(arrowstyle=style, color=color, lw=lw))
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 1. GRAHAM NOTATION α|β|γ — MNEMONIC MAP
|
||||||
|
# ============================================================
|
||||||
|
def draw_graham_notation():
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(8.27, 10))
|
||||||
|
ax.set_xlim(0, 10)
|
||||||
|
ax.set_ylim(0, 14)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Notacja Grahama α | β | γ — Mapa mnemoniczna',
|
||||||
|
fontsize=FS_TITLE + 1, fontweight='bold', pad=12)
|
||||||
|
|
||||||
|
# === TOP: The three fields ===
|
||||||
|
# Big formula bar
|
||||||
|
bar_y = 12.5
|
||||||
|
bar_h = 1.0
|
||||||
|
# α box
|
||||||
|
rect = FancyBboxPatch((0.5, bar_y), 2.5, bar_h, boxstyle="round,pad=0.08",
|
||||||
|
lw=2, edgecolor=LN, facecolor=GRAY1)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(1.75, bar_y + bar_h/2, 'α', fontsize=20, fontweight='bold',
|
||||||
|
ha='center', va='center')
|
||||||
|
ax.text(1.75, bar_y - 0.25, 'MASZYNY', fontsize=8, fontweight='bold',
|
||||||
|
ha='center', va='top', color='#444444')
|
||||||
|
|
||||||
|
# separator |
|
||||||
|
ax.text(3.3, bar_y + bar_h/2, '|', fontsize=24, fontweight='bold',
|
||||||
|
ha='center', va='center')
|
||||||
|
|
||||||
|
# β box
|
||||||
|
rect = FancyBboxPatch((3.7, bar_y), 2.5, bar_h, boxstyle="round,pad=0.08",
|
||||||
|
lw=2, edgecolor=LN, facecolor=GRAY2)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(4.95, bar_y + bar_h/2, 'β', fontsize=20, fontweight='bold',
|
||||||
|
ha='center', va='center')
|
||||||
|
ax.text(4.95, bar_y - 0.25, 'OGRANICZENIA', fontsize=8, fontweight='bold',
|
||||||
|
ha='center', va='top', color='#444444')
|
||||||
|
|
||||||
|
# separator |
|
||||||
|
ax.text(6.5, bar_y + bar_h/2, '|', fontsize=24, fontweight='bold',
|
||||||
|
ha='center', va='center')
|
||||||
|
|
||||||
|
# γ box
|
||||||
|
rect = FancyBboxPatch((6.9, bar_y), 2.5, bar_h, boxstyle="round,pad=0.08",
|
||||||
|
lw=2, edgecolor=LN, facecolor=GRAY3)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(8.15, bar_y + bar_h/2, 'γ', fontsize=20, fontweight='bold',
|
||||||
|
ha='center', va='center')
|
||||||
|
ax.text(8.15, bar_y - 0.25, 'CEL', fontsize=8, fontweight='bold',
|
||||||
|
ha='center', va='top', color='#444444')
|
||||||
|
|
||||||
|
# === SECTION α: MACHINES ===
|
||||||
|
sec_y = 11.5
|
||||||
|
ax.text(0.3, sec_y, 'α — „1 Prawdziwy Quasi-Rycerz Forsuje Jaskinię Orków"',
|
||||||
|
fontsize=8, fontweight='bold', va='top',
|
||||||
|
style='italic', color='#333333')
|
||||||
|
|
||||||
|
alpha_items = [
|
||||||
|
('1', 'jedna maszyna', '●', GRAY4),
|
||||||
|
('Pm', 'identyczne Parallel', '●●●', GRAY1),
|
||||||
|
('Qm', 'Quasi-uniform\n(różne prędkości)', '●●◐', GRAY4),
|
||||||
|
('Rm', 'Random unrelated\n(czasy per para)', '●◆▲', GRAY1),
|
||||||
|
('Fm', 'Flow shop\n(ta sama kolejność)', '→→→', GRAY2),
|
||||||
|
('Jm', 'Job shop\n(indyw. trasy)', '↗↙↘', GRAY4),
|
||||||
|
('Om', 'Open shop\n(dowolna kolej.)', '?→?', GRAY1),
|
||||||
|
]
|
||||||
|
|
||||||
|
col_w = 1.28
|
||||||
|
box_h_a = 1.1
|
||||||
|
start_x = 0.3
|
||||||
|
start_y = 9.6
|
||||||
|
|
||||||
|
for i, (symbol, desc, icon, fill) in enumerate(alpha_items):
|
||||||
|
x = start_x + i * col_w
|
||||||
|
y = start_y
|
||||||
|
rect = FancyBboxPatch((x, y), col_w - 0.1, box_h_a,
|
||||||
|
boxstyle="round,pad=0.04",
|
||||||
|
lw=1, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + (col_w - 0.1)/2, y + box_h_a - 0.15, symbol,
|
||||||
|
ha='center', va='top', fontsize=9, fontweight='bold')
|
||||||
|
ax.text(x + (col_w - 0.1)/2, y + box_h_a/2 - 0.1, desc,
|
||||||
|
ha='center', va='center', fontsize=5.5)
|
||||||
|
ax.text(x + (col_w - 0.1)/2, y + 0.12, icon,
|
||||||
|
ha='center', va='bottom', fontsize=7)
|
||||||
|
|
||||||
|
# Complexity arrow under α
|
||||||
|
arr_y = 9.35
|
||||||
|
ax.annotate("", xy=(9.0, arr_y), xytext=(0.5, arr_y),
|
||||||
|
arrowprops=dict(arrowstyle='->', color='#666666', lw=1.5))
|
||||||
|
ax.text(4.8, arr_y - 0.18, 'rosnąca złożoność →',
|
||||||
|
ha='center', fontsize=6, color='#666666')
|
||||||
|
|
||||||
|
# === SECTION β: CONSTRAINTS ===
|
||||||
|
sec_y2 = 8.9
|
||||||
|
ax.text(0.3, sec_y2, 'β — „Robak Daje Deadline: Przerwy Poprzedzają Pojedyncze Setup\'y"',
|
||||||
|
fontsize=8, fontweight='bold', va='top',
|
||||||
|
style='italic', color='#333333')
|
||||||
|
|
||||||
|
beta_items = [
|
||||||
|
('rⱼ', 'release\ndates', 'Robak\ndostępne\nod czasu rⱼ', GRAY1),
|
||||||
|
('dⱼ', 'due\ndates', 'Daje\ntermin soft\n(kara za spóźn.)', GRAY4),
|
||||||
|
('d̄ⱼ', 'dead-\nlines', 'Deadline\ntermin hard\n(musi dotrzymać)', GRAY1),
|
||||||
|
('pmtn', 'preemp-\ntion', 'Przerwy\nmożna\nprzerwać', GRAY2),
|
||||||
|
('prec', 'prece-\ndencje', 'Poprzedzają\nA->B (DAG)', GRAY4),
|
||||||
|
('pⱼ=1', 'unit\ntime', 'Pojedyncze\nwszystkie = 1', GRAY1),
|
||||||
|
('sⱼₖ', 'setup\ntimes', "Setup'y\nprzezbrojenie\nmiędzy j->k", GRAY4),
|
||||||
|
]
|
||||||
|
|
||||||
|
start_y2 = 7.0
|
||||||
|
box_h_b = 1.4
|
||||||
|
|
||||||
|
for i, (symbol, label, desc, fill) in enumerate(beta_items):
|
||||||
|
x = start_x + i * col_w
|
||||||
|
y = start_y2
|
||||||
|
rect = FancyBboxPatch((x, y), col_w - 0.1, box_h_b,
|
||||||
|
boxstyle="round,pad=0.04",
|
||||||
|
lw=1, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + (col_w - 0.1)/2, y + box_h_b - 0.12, symbol,
|
||||||
|
ha='center', va='top', fontsize=9, fontweight='bold')
|
||||||
|
ax.text(x + (col_w - 0.1)/2, y + box_h_b/2 - 0.05, desc,
|
||||||
|
ha='center', va='center', fontsize=5)
|
||||||
|
|
||||||
|
# === SECTION γ: CRITERIA ===
|
||||||
|
sec_y3 = 6.5
|
||||||
|
ax.text(0.3, sec_y3, 'γ — „Ciężki Sum Ważony Lata, Tardiness Uderza"',
|
||||||
|
fontsize=8, fontweight='bold', va='top',
|
||||||
|
style='italic', color='#333333')
|
||||||
|
|
||||||
|
gamma_items = [
|
||||||
|
('Cmax', 'makespan\nmax(Cⱼ)', 'Jak długo\ntrwa WSZYSTKO?', GRAY2),
|
||||||
|
('ΣCⱼ', 'suma\nukończeń', 'Średni czas\noczekiwania?', GRAY4),
|
||||||
|
('ΣwⱼCⱼ', 'ważona\nsuma', 'Priorytety\nzadań?', GRAY1),
|
||||||
|
('Lmax', 'max\nopóźnienie', 'Najgorsze\nspóźnienie?', GRAY2),
|
||||||
|
('ΣTⱼ', 'suma\nspóźnień', 'Łączne\nspóźnienia?', GRAY4),
|
||||||
|
('ΣUⱼ', 'liczba\nspóźnionych', 'Ile spóźnionych\nzadań?', GRAY1),
|
||||||
|
]
|
||||||
|
|
||||||
|
start_y3 = 4.5
|
||||||
|
box_h_g = 1.4
|
||||||
|
col_w_g = 1.5
|
||||||
|
|
||||||
|
for i, (symbol, label, question, fill) in enumerate(gamma_items):
|
||||||
|
x = start_x + i * col_w_g
|
||||||
|
y = start_y3
|
||||||
|
rect = FancyBboxPatch((x, y), col_w_g - 0.1, box_h_g,
|
||||||
|
boxstyle="round,pad=0.04",
|
||||||
|
lw=1, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + (col_w_g - 0.1)/2, y + box_h_g - 0.1, symbol,
|
||||||
|
ha='center', va='top', fontsize=9, fontweight='bold')
|
||||||
|
ax.text(x + (col_w_g - 0.1)/2, y + box_h_g/2 - 0.05, label,
|
||||||
|
ha='center', va='center', fontsize=6)
|
||||||
|
ax.text(x + (col_w_g - 0.1)/2, y + 0.15, f'„{question}"',
|
||||||
|
ha='center', va='bottom', fontsize=5, style='italic')
|
||||||
|
|
||||||
|
# === BOTTOM: Example + Optimal methods ===
|
||||||
|
ex_y = 3.5
|
||||||
|
ax.text(0.3, ex_y, 'Przykłady zapisu i optymalne metody:',
|
||||||
|
fontsize=8, fontweight='bold', va='top')
|
||||||
|
|
||||||
|
examples = [
|
||||||
|
('1 || ΣCⱼ', 'SPT (najkrótsze\nnajpierw)', 'O(n log n)', GRAY1),
|
||||||
|
('1 || Lmax', 'EDD (najwcześniejszy\ntermin)', 'O(n log n)', GRAY4),
|
||||||
|
('F2 || Cmax', 'Algorytm\nJohnsona', 'O(n log n)', GRAY2),
|
||||||
|
('Pm || Cmax', 'LPT heurystyka\n(NP-trudny!)', 'NP-hard', GRAY3),
|
||||||
|
('Jm || Cmax', 'Branch & Bound\n(NP-trudny!)', 'NP-hard', GRAY5),
|
||||||
|
]
|
||||||
|
|
||||||
|
ex_start_y = 1.8
|
||||||
|
ex_box_w = 1.72
|
||||||
|
ex_box_h = 1.4
|
||||||
|
|
||||||
|
for i, (notation, method, complexity, fill) in enumerate(examples):
|
||||||
|
x = start_x + i * (ex_box_w + 0.1)
|
||||||
|
y = ex_start_y
|
||||||
|
rect = FancyBboxPatch((x, y), ex_box_w, ex_box_h,
|
||||||
|
boxstyle="round,pad=0.04",
|
||||||
|
lw=1.2, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + ex_box_w/2, y + ex_box_h - 0.12, notation,
|
||||||
|
ha='center', va='top', fontsize=8, fontweight='bold',
|
||||||
|
fontfamily='monospace')
|
||||||
|
ax.text(x + ex_box_w/2, y + ex_box_h/2 - 0.05, method,
|
||||||
|
ha='center', va='center', fontsize=6)
|
||||||
|
ax.text(x + ex_box_w/2, y + 0.12, complexity,
|
||||||
|
ha='center', va='bottom', fontsize=6.5, fontweight='bold',
|
||||||
|
color='#555555')
|
||||||
|
|
||||||
|
# Footer mnemonic summary
|
||||||
|
ax.text(5.0, 0.8, '„α|β|γ = Maszyny | Ograniczenia | Cel"',
|
||||||
|
ha='center', fontsize=9, fontweight='bold', style='italic',
|
||||||
|
color='#333333',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=1))
|
||||||
|
|
||||||
|
ax.text(5.0, 0.2, 'α: ILE maszyn i JAKIE? β: JAKIE ograniczenia zadań? γ: CO minimalizujemy?',
|
||||||
|
ha='center', fontsize=7, color='#555555')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_graham_notation.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ scheduling_graham_notation.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 2. JOHNSON'S ALGORITHM GANTT CHART
|
||||||
|
# ============================================================
|
||||||
|
def draw_johnson_gantt():
|
||||||
|
fig, axes = plt.subplots(2, 1, figsize=(8.27, 7), gridspec_kw={'height_ratios': [1, 1.8]})
|
||||||
|
|
||||||
|
# --- Top: The decision process ---
|
||||||
|
ax = axes[0]
|
||||||
|
ax.set_xlim(0, 10)
|
||||||
|
ax.set_ylim(0, 5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Algorytm Johnsona (F2 || Cmax) — Decyzja + Diagram Gantta',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
# Task table
|
||||||
|
tasks = ['J1', 'J2', 'J3', 'J4', 'J5']
|
||||||
|
a_times = [4, 2, 6, 1, 3]
|
||||||
|
b_times = [5, 3, 2, 7, 4]
|
||||||
|
min_vals = [min(a, b) for a, b in zip(a_times, b_times)]
|
||||||
|
min_on = ['M1' if a <= b else 'M2' for a, b in zip(a_times, b_times)]
|
||||||
|
assign = ['POCZątek' if m == 'M1' else 'KONIEC' for m in min_on]
|
||||||
|
|
||||||
|
# Draw table
|
||||||
|
col_w_t = 1.3
|
||||||
|
row_h = 0.55
|
||||||
|
headers = ['Zadanie', 'aⱼ (M1)', 'bⱼ (M2)', 'min', 'min na', 'Przydziel']
|
||||||
|
table_x = 0.8
|
||||||
|
table_y = 3.8
|
||||||
|
|
||||||
|
for j, hdr in enumerate(headers):
|
||||||
|
x = table_x + j * col_w_t
|
||||||
|
rect = mpatches.Rectangle((x, table_y), col_w_t, row_h,
|
||||||
|
lw=1, edgecolor=LN, facecolor=GRAY2)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(x + col_w_t/2, table_y + row_h/2, hdr,
|
||||||
|
ha='center', va='center', fontsize=6.5, fontweight='bold')
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
row_data = [tasks[i], str(a_times[i]), str(b_times[i]),
|
||||||
|
str(min_vals[i]), min_on[i], assign[i]]
|
||||||
|
for j, val in enumerate(row_data):
|
||||||
|
x = table_x + j * col_w_t
|
||||||
|
y = table_y - (i + 1) * row_h
|
||||||
|
fill_c = GRAY1 if min_on[i] == 'M1' else GRAY4
|
||||||
|
if j == 3: # min column - highlight
|
||||||
|
fill_c = GRAY3
|
||||||
|
rect = mpatches.Rectangle((x, y), col_w_t, row_h,
|
||||||
|
lw=0.8, edgecolor=LN, facecolor=fill_c)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
fw = 'bold' if j >= 3 else 'normal'
|
||||||
|
ax.text(x + col_w_t/2, y + row_h/2, val,
|
||||||
|
ha='center', va='center', fontsize=6.5, fontweight=fw)
|
||||||
|
|
||||||
|
# Sorting result
|
||||||
|
result_y = 0.7
|
||||||
|
ax.text(5.0, result_y + 0.4, 'Sortuj → POCZĄTEK ↑aⱼ: J4(1), J2(2), J5(3), J1(4) | KONIEC ↓bⱼ: J3(2)',
|
||||||
|
ha='center', fontsize=7, color='#333333')
|
||||||
|
ax.text(5.0, result_y, 'Optymalna kolejność: J4 → J2 → J5 → J1 → J3',
|
||||||
|
ha='center', fontsize=9, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY1, edgecolor=LN, lw=1.2))
|
||||||
|
|
||||||
|
# --- Bottom: Gantt chart ---
|
||||||
|
ax2 = axes[1]
|
||||||
|
ax2.set_xlim(-1, 24)
|
||||||
|
ax2.set_ylim(-1, 4)
|
||||||
|
ax2.axis('off')
|
||||||
|
|
||||||
|
# Machines labels
|
||||||
|
m1_y = 2.5
|
||||||
|
m2_y = 0.8
|
||||||
|
bar_h = 0.9
|
||||||
|
|
||||||
|
ax2.text(-0.8, m1_y + bar_h/2, 'M1', ha='center', va='center',
|
||||||
|
fontsize=11, fontweight='bold')
|
||||||
|
ax2.text(-0.8, m2_y + bar_h/2, 'M2', ha='center', va='center',
|
||||||
|
fontsize=11, fontweight='bold')
|
||||||
|
|
||||||
|
# Schedule: J4 → J2 → J5 → J1 → J3
|
||||||
|
order = ['J4', 'J2', 'J5', 'J1', 'J3']
|
||||||
|
a_ord = [1, 2, 3, 4, 6] # M1 times in order
|
||||||
|
b_ord = [7, 3, 4, 5, 2] # M2 times in order
|
||||||
|
fills = [GRAY1, GRAY2, GRAY4, GRAY3, GRAY5]
|
||||||
|
hatches = ['', '///', '', '\\\\\\', 'xxx']
|
||||||
|
|
||||||
|
# M1 schedule
|
||||||
|
m1_starts = []
|
||||||
|
t = 0
|
||||||
|
for a in a_ord:
|
||||||
|
m1_starts.append(t)
|
||||||
|
t += a
|
||||||
|
m1_ends = [s + a for s, a in zip(m1_starts, a_ord)]
|
||||||
|
|
||||||
|
# M2 schedule (must wait for M1 finish AND previous M2 finish)
|
||||||
|
m2_starts = []
|
||||||
|
m2_ends = []
|
||||||
|
prev_m2_end = 0
|
||||||
|
for i, b in enumerate(b_ord):
|
||||||
|
start = max(m1_ends[i], prev_m2_end)
|
||||||
|
m2_starts.append(start)
|
||||||
|
m2_ends.append(start + b)
|
||||||
|
prev_m2_end = start + b
|
||||||
|
|
||||||
|
# Draw M1 bars
|
||||||
|
for i in range(5):
|
||||||
|
rect = mpatches.Rectangle((m1_starts[i], m1_y), a_ord[i], bar_h,
|
||||||
|
lw=1.2, edgecolor=LN, facecolor=fills[i],
|
||||||
|
hatch=hatches[i])
|
||||||
|
ax2.add_patch(rect)
|
||||||
|
ax2.text(m1_starts[i] + a_ord[i]/2, m1_y + bar_h/2,
|
||||||
|
f'{order[i]}\n({a_ord[i]})',
|
||||||
|
ha='center', va='center', fontsize=7, fontweight='bold')
|
||||||
|
|
||||||
|
# Draw M2 bars
|
||||||
|
for i in range(5):
|
||||||
|
rect = mpatches.Rectangle((m2_starts[i], m2_y), b_ord[i], bar_h,
|
||||||
|
lw=1.2, edgecolor=LN, facecolor=fills[i],
|
||||||
|
hatch=hatches[i])
|
||||||
|
ax2.add_patch(rect)
|
||||||
|
ax2.text(m2_starts[i] + b_ord[i]/2, m2_y + bar_h/2,
|
||||||
|
f'{order[i]}\n({b_ord[i]})',
|
||||||
|
ha='center', va='center', fontsize=7, fontweight='bold')
|
||||||
|
|
||||||
|
# Draw idle regions on M2
|
||||||
|
idle_starts = [0]
|
||||||
|
idle_ends = [m2_starts[0]]
|
||||||
|
for i in range(1, 5):
|
||||||
|
if m2_starts[i] > m2_ends[i-1]:
|
||||||
|
idle_starts.append(m2_ends[i-1])
|
||||||
|
idle_ends.append(m2_starts[i])
|
||||||
|
|
||||||
|
for s, e in zip(idle_starts, idle_ends):
|
||||||
|
if e > s:
|
||||||
|
rect = mpatches.Rectangle((s, m2_y), e - s, bar_h,
|
||||||
|
lw=0.5, edgecolor='#AAAAAA',
|
||||||
|
facecolor='white', linestyle='--')
|
||||||
|
ax2.add_patch(rect)
|
||||||
|
ax2.text(s + (e-s)/2, m2_y + bar_h/2, 'idle',
|
||||||
|
ha='center', va='center', fontsize=5, color='#999999')
|
||||||
|
|
||||||
|
# Time axis
|
||||||
|
ax_y = m2_y - 0.15
|
||||||
|
ax2.plot([0, 23], [ax_y, ax_y], color=LN, lw=0.8)
|
||||||
|
for t in range(0, 24, 2):
|
||||||
|
ax2.plot([t, t], [ax_y - 0.08, ax_y + 0.08], color=LN, lw=0.8)
|
||||||
|
ax2.text(t, ax_y - 0.25, str(t), ha='center', va='top', fontsize=6)
|
||||||
|
ax2.text(11.5, ax_y - 0.55, 'czas', ha='center', fontsize=7)
|
||||||
|
|
||||||
|
# Cmax annotation
|
||||||
|
ax2.annotate(f'Cmax = {m2_ends[-1]}', xy=(m2_ends[-1], m2_y + bar_h),
|
||||||
|
xytext=(m2_ends[-1] + 0.5, m2_y + bar_h + 0.6),
|
||||||
|
fontsize=10, fontweight='bold', color='#333333',
|
||||||
|
arrowprops=dict(arrowstyle='->', color='#333333', lw=1.5))
|
||||||
|
|
||||||
|
# Mnemonic at bottom
|
||||||
|
ax2.text(11, -0.7,
|
||||||
|
'„Krótki na M1 → START (szybko karmi M2) Krótki na M2 → KONIEC (szybko kończy)"',
|
||||||
|
ha='center', fontsize=7.5, fontweight='bold', style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=0.8))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_johnson_gantt.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ scheduling_johnson_gantt.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 3. SPT vs LPT COMPARISON (1 || ΣCⱼ)
|
||||||
|
# ============================================================
|
||||||
|
def draw_spt_comparison():
|
||||||
|
fig, axes = plt.subplots(2, 1, figsize=(8.27, 5.5))
|
||||||
|
|
||||||
|
tasks_orig = [('J1', 5), ('J2', 3), ('J3', 8), ('J4', 2), ('J5', 6)]
|
||||||
|
|
||||||
|
spt_order = sorted(tasks_orig, key=lambda x: x[1])
|
||||||
|
lpt_order = sorted(tasks_orig, key=lambda x: -x[1])
|
||||||
|
|
||||||
|
fills_map = {'J1': GRAY1, 'J2': GRAY2, 'J3': GRAY3, 'J4': GRAY4, 'J5': GRAY5}
|
||||||
|
hatch_map = {'J1': '', 'J2': '///', 'J3': 'xxx', 'J4': '', 'J5': '\\\\\\'}
|
||||||
|
|
||||||
|
for idx, (ax, order_list, title, is_optimal) in enumerate([
|
||||||
|
(axes[0], spt_order, 'SPT (Shortest Processing Time) — OPTYMALNE', True),
|
||||||
|
(axes[1], lpt_order, 'LPT (Longest Processing Time) — gorsze!', False)
|
||||||
|
]):
|
||||||
|
ax.set_xlim(-2, 26)
|
||||||
|
ax.set_ylim(-0.5, 2.5)
|
||||||
|
ax.axis('off')
|
||||||
|
color = '#222222' if is_optimal else '#666666'
|
||||||
|
marker = '✓' if is_optimal else '✗'
|
||||||
|
ax.set_title(f'{marker} {title}', fontsize=9, fontweight='bold',
|
||||||
|
loc='left', color=color, pad=5)
|
||||||
|
|
||||||
|
bar_y = 1.0
|
||||||
|
bar_h = 0.8
|
||||||
|
t = 0
|
||||||
|
completions = []
|
||||||
|
|
||||||
|
for name, duration in order_list:
|
||||||
|
rect = mpatches.Rectangle((t, bar_y), duration, bar_h,
|
||||||
|
lw=1.2, edgecolor=LN,
|
||||||
|
facecolor=fills_map[name],
|
||||||
|
hatch=hatch_map[name])
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(t + duration/2, bar_y + bar_h/2, f'{name}\n({duration})',
|
||||||
|
ha='center', va='center', fontsize=7, fontweight='bold')
|
||||||
|
t += duration
|
||||||
|
completions.append(t)
|
||||||
|
|
||||||
|
# Completion time marker
|
||||||
|
ax.plot([t, t], [bar_y - 0.15, bar_y], color=LN, lw=0.8)
|
||||||
|
ax.text(t, bar_y - 0.25, f'C={t}', ha='center', va='top',
|
||||||
|
fontsize=6, color='#555555')
|
||||||
|
|
||||||
|
total = sum(completions)
|
||||||
|
# Time axis
|
||||||
|
ax.plot([0, 25], [bar_y - 0.05, bar_y - 0.05], color=LN, lw=0.5)
|
||||||
|
|
||||||
|
# Sum annotation
|
||||||
|
comp_str = ' + '.join(str(c) for c in completions)
|
||||||
|
ax.text(25, bar_y + bar_h/2,
|
||||||
|
f'ΣCⱼ = {comp_str}\n = {total}',
|
||||||
|
ha='left', va='center', fontsize=7,
|
||||||
|
fontweight='bold' if is_optimal else 'normal',
|
||||||
|
color=color,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.2',
|
||||||
|
facecolor=GRAY1 if is_optimal else 'white',
|
||||||
|
edgecolor=color, lw=1))
|
||||||
|
|
||||||
|
# Bottom annotation
|
||||||
|
fig.text(0.5, 0.02,
|
||||||
|
'„Short People To the front" — krótkie najpierw, jak niskie osoby w zdjęciu klasowym',
|
||||||
|
ha='center', fontsize=8, fontweight='bold', style='italic',
|
||||||
|
color='#444444')
|
||||||
|
|
||||||
|
plt.tight_layout(rect=[0, 0.05, 1, 1])
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_spt_comparison.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ scheduling_spt_comparison.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 4. FLOW SHOP vs JOB SHOP
|
||||||
|
# ============================================================
|
||||||
|
def draw_flow_vs_job():
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(8.27, 4.5))
|
||||||
|
|
||||||
|
# --- LEFT: Flow Shop ---
|
||||||
|
ax = axes[0]
|
||||||
|
ax.set_xlim(0, 6)
|
||||||
|
ax.set_ylim(0, 6)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Flow Shop (Fm)', fontsize=10, fontweight='bold', pad=8)
|
||||||
|
|
||||||
|
# Machines in a row
|
||||||
|
machines_x = [1, 3, 5]
|
||||||
|
machines_y = 3
|
||||||
|
mach_r = 0.4
|
||||||
|
|
||||||
|
for i, mx in enumerate(machines_x):
|
||||||
|
circle = plt.Circle((mx, machines_y), mach_r,
|
||||||
|
facecolor=GRAY2, edgecolor=LN, lw=1.5)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
ax.text(mx, machines_y, f'M{i+1}', ha='center', va='center',
|
||||||
|
fontsize=9, fontweight='bold')
|
||||||
|
|
||||||
|
# Arrows between machines
|
||||||
|
for i in range(len(machines_x) - 1):
|
||||||
|
draw_arrow(ax, machines_x[i] + mach_r + 0.05, machines_y,
|
||||||
|
machines_x[i+1] - mach_r - 0.05, machines_y, lw=2)
|
||||||
|
|
||||||
|
# Jobs all flowing the same way
|
||||||
|
jobs_flow = ['J1', 'J2', 'J3']
|
||||||
|
for j, (job, y_off) in enumerate(zip(jobs_flow, [0.8, 0, -0.8])):
|
||||||
|
ax.text(0.2, machines_y + y_off, job, ha='center', va='center',
|
||||||
|
fontsize=7, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY1, edgecolor=LN))
|
||||||
|
# Dashed flow line
|
||||||
|
ax.annotate("", xy=(5.5, machines_y + y_off * 0.3),
|
||||||
|
xytext=(0.5, machines_y + y_off),
|
||||||
|
arrowprops=dict(arrowstyle='->', color='#888888', lw=0.8,
|
||||||
|
linestyle='dashed'))
|
||||||
|
|
||||||
|
ax.text(3, 1.2, 'Wszystkie zadania:\nM1 → M2 → M3',
|
||||||
|
ha='center', va='center', fontsize=8,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
ax.text(3, 0.4, 'Jak taśma montażowa',
|
||||||
|
ha='center', fontsize=7, style='italic', color='#666666')
|
||||||
|
|
||||||
|
# --- RIGHT: Job Shop ---
|
||||||
|
ax = axes[1]
|
||||||
|
ax.set_xlim(0, 6)
|
||||||
|
ax.set_ylim(0, 6)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Job Shop (Jm)', fontsize=10, fontweight='bold', pad=8)
|
||||||
|
|
||||||
|
# Machines scattered
|
||||||
|
m_positions = [(1.5, 4.2), (4.5, 4.2), (3, 2.5)]
|
||||||
|
|
||||||
|
for i, (mx, my) in enumerate(m_positions):
|
||||||
|
circle = plt.Circle((mx, my), mach_r,
|
||||||
|
facecolor=GRAY2, edgecolor=LN, lw=1.5)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
ax.text(mx, my, f'M{i+1}', ha='center', va='center',
|
||||||
|
fontsize=9, fontweight='bold')
|
||||||
|
|
||||||
|
# J1: M1 → M2 → M3 (solid)
|
||||||
|
route1 = [(1.5, 4.2), (4.5, 4.2), (3, 2.5)]
|
||||||
|
for i in range(len(route1) - 1):
|
||||||
|
x1, y1 = route1[i]
|
||||||
|
x2, y2 = route1[i+1]
|
||||||
|
dx = x2 - x1
|
||||||
|
dy = y2 - y1
|
||||||
|
d = (dx**2 + dy**2)**0.5
|
||||||
|
draw_arrow(ax, x1 + mach_r * dx/d + 0.05, y1 + mach_r * dy/d,
|
||||||
|
x2 - mach_r * dx/d - 0.05, y2 - mach_r * dy/d,
|
||||||
|
lw=1.5)
|
||||||
|
ax.text(0.3, 4.8, 'J1: M1→M2→M3', fontsize=7, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY1, edgecolor=LN))
|
||||||
|
|
||||||
|
# J2: M2 → M3 → M1 (dashed)
|
||||||
|
route2 = [(4.5, 4.2), (3, 2.5), (1.5, 4.2)]
|
||||||
|
for i in range(len(route2) - 1):
|
||||||
|
x1, y1 = route2[i]
|
||||||
|
x2, y2 = route2[i+1]
|
||||||
|
dx = x2 - x1
|
||||||
|
dy = y2 - y1
|
||||||
|
d = (dx**2 + dy**2)**0.5
|
||||||
|
off = 0.15 # offset to avoid overlap
|
||||||
|
ax.annotate("", xy=(x2 - mach_r * dx/d - 0.05, y2 - mach_r * dy/d + off),
|
||||||
|
xytext=(x1 + mach_r * dx/d + 0.05, y1 + mach_r * dy/d + off),
|
||||||
|
arrowprops=dict(arrowstyle='->', color='#555555', lw=1.5,
|
||||||
|
linestyle='dashed'))
|
||||||
|
ax.text(3.8, 5.2, 'J2: M2→M3→M1', fontsize=7, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY4, edgecolor=LN))
|
||||||
|
|
||||||
|
ax.text(3, 1.2, 'Każde zadanie:\nwłasna trasa!',
|
||||||
|
ha='center', va='center', fontsize=8,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
ax.text(3, 0.4, 'NP-trudny już dla 3 maszyn',
|
||||||
|
ha='center', fontsize=7, style='italic', color='#666666')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_flow_vs_job.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ scheduling_flow_vs_job.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 5. SCHEDULING COMPLEXITY LANDSCAPE
|
||||||
|
# ============================================================
|
||||||
|
def draw_complexity_map():
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(8.27, 5))
|
||||||
|
ax.set_xlim(0, 10)
|
||||||
|
ax.set_ylim(0, 7)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Złożoność problemów szeregowania — od łatwych do NP-trudnych',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
# Gradient arrow at the top
|
||||||
|
ax.annotate("", xy=(9.5, 6.2), xytext=(0.5, 6.2),
|
||||||
|
arrowprops=dict(arrowstyle='->', color=LN, lw=2))
|
||||||
|
ax.text(5, 6.5, 'Rosnąca złożoność', ha='center', fontsize=9,
|
||||||
|
fontweight='bold')
|
||||||
|
|
||||||
|
# Easy (polynomial) region
|
||||||
|
easy_rect = FancyBboxPatch((0.3, 2.8), 4.0, 3.0,
|
||||||
|
boxstyle="round,pad=0.15",
|
||||||
|
lw=1.5, edgecolor='#666666',
|
||||||
|
facecolor=GRAY4, linestyle='-')
|
||||||
|
ax.add_patch(easy_rect)
|
||||||
|
ax.text(2.3, 5.5, 'WIELOMIANOWE O(n log n)', ha='center',
|
||||||
|
fontsize=9, fontweight='bold', color='#444444')
|
||||||
|
|
||||||
|
easy_problems = [
|
||||||
|
('1 || ΣCⱼ', 'SPT', GRAY1, 4.8),
|
||||||
|
('1 || Lmax', 'EDD', GRAY2, 4.0),
|
||||||
|
('F2 || Cmax', 'Johnson', GRAY1, 3.2),
|
||||||
|
]
|
||||||
|
for prob, method, fill, y in easy_problems:
|
||||||
|
rect = FancyBboxPatch((0.6, y), 3.5, 0.6,
|
||||||
|
boxstyle="round,pad=0.05",
|
||||||
|
lw=1, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(1.2, y + 0.3, prob, ha='center', va='center',
|
||||||
|
fontsize=8, fontweight='bold', fontfamily='monospace')
|
||||||
|
ax.text(3.0, y + 0.3, f'→ {method}', ha='center', va='center',
|
||||||
|
fontsize=8)
|
||||||
|
|
||||||
|
# Hard (NP) region
|
||||||
|
hard_rect = FancyBboxPatch((5.3, 2.8), 4.3, 3.0,
|
||||||
|
boxstyle="round,pad=0.15",
|
||||||
|
lw=1.5, edgecolor='#444444',
|
||||||
|
facecolor=GRAY3, linestyle='-')
|
||||||
|
ax.add_patch(hard_rect)
|
||||||
|
ax.text(7.45, 5.5, 'NP-TRUDNE', ha='center',
|
||||||
|
fontsize=9, fontweight='bold', color='#333333')
|
||||||
|
|
||||||
|
hard_problems = [
|
||||||
|
('Pm || Cmax\n(m≥2)', 'LPT heuryst.', GRAY2, 4.5),
|
||||||
|
('1 || ΣTⱼ', 'branch&bound', GRAY4, 3.7),
|
||||||
|
('Jm || Cmax\n(m≥3)', 'metaheuryst.', GRAY5, 2.9),
|
||||||
|
]
|
||||||
|
for prob, method, fill, y in hard_problems:
|
||||||
|
rect = FancyBboxPatch((5.6, y), 3.7, 0.7,
|
||||||
|
boxstyle="round,pad=0.05",
|
||||||
|
lw=1, edgecolor=LN, facecolor=fill)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(6.5, y + 0.35, prob, ha='center', va='center',
|
||||||
|
fontsize=7, fontweight='bold', fontfamily='monospace')
|
||||||
|
ax.text(8.2, y + 0.35, f'→ {method}', ha='center', va='center',
|
||||||
|
fontsize=7)
|
||||||
|
|
||||||
|
# Arrow connecting
|
||||||
|
draw_arrow(ax, 4.4, 4.0, 5.2, 4.0, lw=2, color='#888888')
|
||||||
|
ax.text(4.8, 4.25, '+1\nmaszyna', ha='center', fontsize=6, color='#888888')
|
||||||
|
|
||||||
|
# Bottom: key insight
|
||||||
|
ax.text(5.0, 1.8, '„Dodanie jednej maszyny lub jednego ograniczenia\n'
|
||||||
|
'może zmienić problem z łatwego na NP-trudny!"',
|
||||||
|
ha='center', fontsize=8, fontweight='bold', style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=1))
|
||||||
|
|
||||||
|
# Bottom examples
|
||||||
|
ax.text(5.0, 0.8,
|
||||||
|
'1 maszyna → łatwe (sortuj) | ≥2 maszyny równoległe → NP-trudne\n'
|
||||||
|
'Flow shop 2 maszyny → Johnson O(n log n) | Flow shop 3 maszyny → NP-trudne',
|
||||||
|
ha='center', fontsize=7, color='#555555')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_complexity_map.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ scheduling_complexity_map.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 6. EDD EXAMPLE (1 || Lmax)
|
||||||
|
# ============================================================
|
||||||
|
def draw_edd_example():
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(8.27, 4))
|
||||||
|
ax.set_xlim(-2, 28)
|
||||||
|
ax.set_ylim(-2, 4)
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('EDD (Earliest Due Date) — 1 || Lmax — Przykład',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=8)
|
||||||
|
|
||||||
|
# Tasks: name, processing time, due date
|
||||||
|
tasks = [('J1', 4, 10), ('J2', 2, 6), ('J3', 6, 15), ('J4', 3, 8), ('J5', 5, 18)]
|
||||||
|
# EDD: sort by due date
|
||||||
|
edd_order = sorted(tasks, key=lambda x: x[2])
|
||||||
|
|
||||||
|
bar_y = 1.5
|
||||||
|
bar_h = 0.8
|
||||||
|
t = 0
|
||||||
|
fills_edd = [GRAY1, GRAY2, GRAY4, GRAY3, GRAY5]
|
||||||
|
|
||||||
|
lateness_vals = []
|
||||||
|
for i, (name, p, d) in enumerate(edd_order):
|
||||||
|
rect = mpatches.Rectangle((t, bar_y), p, bar_h,
|
||||||
|
lw=1.2, edgecolor=LN, facecolor=fills_edd[i])
|
||||||
|
ax.add_patch(rect)
|
||||||
|
ax.text(t + p/2, bar_y + bar_h/2, f'{name}\np={p}, d={d}',
|
||||||
|
ha='center', va='center', fontsize=6.5, fontweight='bold')
|
||||||
|
t += p
|
||||||
|
L = t - d
|
||||||
|
lateness_vals.append(L)
|
||||||
|
|
||||||
|
# Due date marker
|
||||||
|
ax.plot([d, d], [bar_y - 0.4, bar_y - 0.1], color='#888888',
|
||||||
|
lw=0.8, linestyle='--')
|
||||||
|
ax.text(d, bar_y - 0.5, f'd={d}', ha='center', va='top',
|
||||||
|
fontsize=5.5, color='#888888')
|
||||||
|
|
||||||
|
# Completion + lateness
|
||||||
|
ax.plot([t, t], [bar_y + bar_h, bar_y + bar_h + 0.15], color=LN, lw=0.8)
|
||||||
|
ax.text(t, bar_y + bar_h + 0.2, f'C={t}\nL={L}',
|
||||||
|
ha='center', va='bottom', fontsize=5.5)
|
||||||
|
|
||||||
|
# Time axis
|
||||||
|
ax.plot([0, 22], [bar_y - 0.05, bar_y - 0.05], color=LN, lw=0.5)
|
||||||
|
|
||||||
|
Lmax = max(lateness_vals)
|
||||||
|
ax.text(22, bar_y + bar_h/2, f'Lmax = {Lmax}',
|
||||||
|
ha='left', va='center', fontsize=10, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY1, edgecolor=LN))
|
||||||
|
|
||||||
|
# Bottom mnemonic
|
||||||
|
ax.text(10, -1.3, '„Early Due Date Does it first" — najpilniejszy deadline idzie pierwszy',
|
||||||
|
ha='center', fontsize=8, fontweight='bold', style='italic',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=0.8))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_edd_example.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ scheduling_edd_example.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN
|
||||||
|
# ============================================================
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Generating scheduling diagrams for PYTANIE 17...")
|
||||||
|
draw_graham_notation()
|
||||||
|
draw_johnson_gantt()
|
||||||
|
draw_spt_comparison()
|
||||||
|
draw_flow_vs_job()
|
||||||
|
draw_complexity_map()
|
||||||
|
draw_edd_example()
|
||||||
|
print("Done! All diagrams saved to:", OUTPUT_DIR)
|
||||||
446
pytania/generate_shortest_path_diagrams.py
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate diagrams for PYTANIE 2: Shortest path algorithms.
|
||||||
|
1. Graph structure — the shared example graph (A,B,C,D)
|
||||||
|
2. Dijkstra traversal — step-by-step on that graph
|
||||||
|
3. Bellman-Ford traversal — step-by-step
|
||||||
|
4. A* traversal — step-by-step with heuristics
|
||||||
|
|
||||||
|
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as mpatches
|
||||||
|
from matplotlib.patches import FancyBboxPatch
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
DPI = 300
|
||||||
|
BG = 'white'
|
||||||
|
LN = 'black'
|
||||||
|
FS = 8
|
||||||
|
FS_TITLE = 11
|
||||||
|
FS_SMALL = 6.5
|
||||||
|
FS_EDGE = 9
|
||||||
|
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
GRAY1 = '#E8E8E8'
|
||||||
|
GRAY2 = '#D0D0D0'
|
||||||
|
GRAY3 = '#B8B8B8'
|
||||||
|
GRAY4 = '#F5F5F5'
|
||||||
|
GRAY5 = '#C0C0C0'
|
||||||
|
LIGHT_GREEN = '#D5E8D4'
|
||||||
|
LIGHT_RED = '#F8D7DA'
|
||||||
|
LIGHT_BLUE = '#D6EAF8'
|
||||||
|
LIGHT_YELLOW = '#FFF9C4'
|
||||||
|
LIGHT_ORANGE = '#FFE0B2'
|
||||||
|
LIGHT_PURPLE = '#E8D5F5'
|
||||||
|
|
||||||
|
# --- Shared graph layout ---
|
||||||
|
# Graph: A--2--B--3--D, A--4--C--5--D, D--1--C (directed: C->D has weight 5)
|
||||||
|
NODE_POS = {'A': (1, 2), 'B': (3.5, 3.2), 'C': (1, 0), 'D': (3.5, 0.8)}
|
||||||
|
EDGES = [('A', 'B', 2), ('A', 'C', 4), ('B', 'D', 3), ('C', 'D', 5)]
|
||||||
|
|
||||||
|
|
||||||
|
def draw_graph_node(ax, name, pos, color='white', current=False, visited=False,
|
||||||
|
dist_label=None, fontsize=12):
|
||||||
|
"""Draw a graph node (circle with label)."""
|
||||||
|
x, y = pos
|
||||||
|
r = 0.35
|
||||||
|
lw = 2.5 if current else 1.5
|
||||||
|
ec = '#D32F2F' if current else LN
|
||||||
|
fc = LIGHT_GREEN if visited else color
|
||||||
|
if current:
|
||||||
|
fc = LIGHT_YELLOW
|
||||||
|
|
||||||
|
circle = plt.Circle((x, y), r, fill=True, facecolor=fc,
|
||||||
|
edgecolor=ec, linewidth=lw, zorder=5)
|
||||||
|
ax.add_patch(circle)
|
||||||
|
ax.text(x, y, name, ha='center', va='center', fontsize=fontsize,
|
||||||
|
fontweight='bold', zorder=6)
|
||||||
|
|
||||||
|
if dist_label is not None:
|
||||||
|
ax.text(x, y - 0.55, f'd={dist_label}', ha='center', va='center',
|
||||||
|
fontsize=FS, zorder=6,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor='white',
|
||||||
|
edgecolor=GRAY3, alpha=0.95))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False):
|
||||||
|
"""Draw an edge between two nodes with weight label."""
|
||||||
|
x1, y1 = pos1
|
||||||
|
x2, y2 = pos2
|
||||||
|
|
||||||
|
# Shorten line to not overlap node circles
|
||||||
|
dx, dy = x2 - x1, y2 - y1
|
||||||
|
length = np.sqrt(dx**2 + dy**2)
|
||||||
|
r = 0.38
|
||||||
|
sx = x1 + r * dx / length
|
||||||
|
sy = y1 + r * dy / length
|
||||||
|
ex = x2 - r * dx / length
|
||||||
|
ey = y2 - r * dy / length
|
||||||
|
|
||||||
|
color = '#D32F2F' if relaxed else ('#1565C0' if highlighted else GRAY3)
|
||||||
|
lw = 2.5 if (highlighted or relaxed) else 1.5
|
||||||
|
ls = '-'
|
||||||
|
|
||||||
|
ax.plot([sx, ex], [sy, ey], color=color, linewidth=lw, linestyle=ls, zorder=2)
|
||||||
|
|
||||||
|
# Weight label
|
||||||
|
mx = (x1 + x2) / 2
|
||||||
|
my = (y1 + y2) / 2
|
||||||
|
# Offset perpendicular to edge
|
||||||
|
perp_x = -dy / length * 0.2
|
||||||
|
perp_y = dx / length * 0.2
|
||||||
|
|
||||||
|
ax.text(mx + perp_x, my + perp_y, str(weight), ha='center', va='center',
|
||||||
|
fontsize=FS_EDGE, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.15', facecolor='white',
|
||||||
|
edgecolor=GRAY3 if not highlighted else color, alpha=0.95),
|
||||||
|
zorder=4)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_full_graph(ax, title="", dist=None, current=None, visited=None,
|
||||||
|
highlighted_edges=None, relaxed_edges=None):
|
||||||
|
"""Draw the complete graph with optional highlighting."""
|
||||||
|
if visited is None:
|
||||||
|
visited = set()
|
||||||
|
if highlighted_edges is None:
|
||||||
|
highlighted_edges = set()
|
||||||
|
if relaxed_edges is None:
|
||||||
|
relaxed_edges = set()
|
||||||
|
if dist is None:
|
||||||
|
dist = {}
|
||||||
|
|
||||||
|
ax.set_xlim(-0.2, 4.7)
|
||||||
|
ax.set_ylim(-0.8, 4.2)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
if title:
|
||||||
|
ax.set_title(title, fontsize=FS, fontweight='bold', pad=5)
|
||||||
|
|
||||||
|
# Draw edges
|
||||||
|
for u, v, w in EDGES:
|
||||||
|
hl = (u, v) in highlighted_edges or (v, u) in highlighted_edges
|
||||||
|
rl = (u, v) in relaxed_edges or (v, u) in relaxed_edges
|
||||||
|
draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w,
|
||||||
|
highlighted=hl, relaxed=rl)
|
||||||
|
|
||||||
|
# Draw nodes
|
||||||
|
for name, pos in NODE_POS.items():
|
||||||
|
is_current = (name == current)
|
||||||
|
is_visited = (name in visited)
|
||||||
|
d_label = dist.get(name, None)
|
||||||
|
draw_graph_node(ax, name, pos, current=is_current, visited=is_visited,
|
||||||
|
dist_label=d_label)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 1. Graph structure diagram
|
||||||
|
# ============================================================
|
||||||
|
def draw_graph_structure():
|
||||||
|
"""The shared example graph used across all three algorithms."""
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(5, 4))
|
||||||
|
ax.set_xlim(-0.5, 5.0)
|
||||||
|
ax.set_ylim(-1.2, 4.5)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.axis('off')
|
||||||
|
ax.set_title('Przykładowy graf — wspólny dla wszystkich algorytmów\n'
|
||||||
|
'Wierzchołki: {A, B, C, D}, Start = A',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', pad=10)
|
||||||
|
|
||||||
|
# Draw edges
|
||||||
|
for u, v, w in EDGES:
|
||||||
|
draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w)
|
||||||
|
|
||||||
|
# Draw nodes
|
||||||
|
for name, pos in NODE_POS.items():
|
||||||
|
draw_graph_node(ax, name, pos)
|
||||||
|
|
||||||
|
# Start arrow
|
||||||
|
ax.annotate("START", xy=(NODE_POS['A'][0] - 0.35, NODE_POS['A'][1]),
|
||||||
|
xytext=(NODE_POS['A'][0] - 1.2, NODE_POS['A'][1]),
|
||||||
|
fontsize=FS, fontweight='bold', color='#D32F2F',
|
||||||
|
arrowprops=dict(arrowstyle='->', color='#D32F2F', lw=2),
|
||||||
|
va='center')
|
||||||
|
|
||||||
|
# Edge list
|
||||||
|
ax.text(2.3, -0.8, 'Krawędzie: A→B(2), A→C(4), B→D(3), C→D(5)\n'
|
||||||
|
'|V|=4, |E|=4, wagi ≥ 0',
|
||||||
|
ha='center', va='center', fontsize=FS,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'graph_example_structure.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ graph_example_structure.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 2. Dijkstra traversal
|
||||||
|
# ============================================================
|
||||||
|
def draw_dijkstra_traversal():
|
||||||
|
"""Step-by-step Dijkstra on the shared graph."""
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
'title': 'Krok 0: Inicjalizacja\nd = {A:0, B:∞, C:∞, D:∞}',
|
||||||
|
'dist': {'A': '0', 'B': '∞', 'C': '∞', 'D': '∞'},
|
||||||
|
'current': 'A', 'visited': set(),
|
||||||
|
'highlighted': set(), 'relaxed': set(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 1: Przetwarzam A (d=0)\nRelaksacja: A→B: 0+2=2<∞ ✓ A→C: 0+4=4<∞ ✓',
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '∞'},
|
||||||
|
'current': 'A', 'visited': {'A'},
|
||||||
|
'highlighted': set(), 'relaxed': {('A', 'B'), ('A', 'C')},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 2: Przetwarzam B (d=2) — minimum\nRelaksacja: B→D: 2+3=5<∞ ✓',
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'current': 'B', 'visited': {'A', 'B'},
|
||||||
|
'highlighted': set(), 'relaxed': {('B', 'D')},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 3: Przetwarzam C (d=4)\nRelaksacja: C→D: 4+5=9 > 5 ✗ (nie poprawia)',
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'current': 'C', 'visited': {'A', 'B', 'C'},
|
||||||
|
'highlighted': {('C', 'D')}, 'relaxed': set(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 4: WYNIK — wszystkie przetworzone\nd = {A:0, B:2, C:4, D:5}',
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'current': None, 'visited': {'A', 'B', 'C', 'D'},
|
||||||
|
'highlighted': {('A', 'B'), ('B', 'D'), ('A', 'C')}, 'relaxed': set(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 5, figsize=(14, 3.5))
|
||||||
|
fig.suptitle('Dijkstra — przejście grafu krok po kroku (zachłannie: zawsze bierz min d)',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', y=1.02)
|
||||||
|
|
||||||
|
for i, (ax, step) in enumerate(zip(axes, steps)):
|
||||||
|
draw_full_graph(ax, title=step['title'],
|
||||||
|
dist=step['dist'], current=step['current'],
|
||||||
|
visited=step['visited'],
|
||||||
|
highlighted_edges=step['highlighted'],
|
||||||
|
relaxed_edges=step['relaxed'])
|
||||||
|
|
||||||
|
# Legend
|
||||||
|
fig.text(0.5, -0.04,
|
||||||
|
'[zolty] = aktualnie przetwarzany [zielony] = odwiedzony (zamkniety) '
|
||||||
|
'czerwona krawedz = relaksacja OK szara krawedz = nie poprawia',
|
||||||
|
ha='center', fontsize=FS,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3))
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'dijkstra_traversal.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ dijkstra_traversal.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 3. Bellman-Ford traversal
|
||||||
|
# ============================================================
|
||||||
|
def draw_bellman_ford_traversal():
|
||||||
|
"""Step-by-step Bellman-Ford on the shared graph."""
|
||||||
|
fig = plt.figure(figsize=(14, 7))
|
||||||
|
fig.suptitle('Bellman-Ford — przejście grafu krok po kroku\n'
|
||||||
|
'(V−1 = 3 iteracje, w każdej relaksuj WSZYSTKIE krawędzie)',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', y=0.98)
|
||||||
|
|
||||||
|
# Data for each iteration
|
||||||
|
iterations = [
|
||||||
|
{
|
||||||
|
'title': 'Inicjalizacja',
|
||||||
|
'edges_detail': '—',
|
||||||
|
'dist': {'A': '0', 'B': '∞', 'C': '∞', 'D': '∞'},
|
||||||
|
'relaxed': set(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Iteracja 1 (V−1=3)',
|
||||||
|
'edges_detail': (
|
||||||
|
'A→B: 0+2=2<∞ ✓\n'
|
||||||
|
'A→C: 0+4=4<∞ ✓\n'
|
||||||
|
'B→D: 2+3=5<∞ ✓\n'
|
||||||
|
'C→D: 4+5=9>5 ✗'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'relaxed': {('A', 'B'), ('A', 'C'), ('B', 'D')},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Iteracja 2',
|
||||||
|
'edges_detail': (
|
||||||
|
'A→B: 0+2=2=2 ✗\n'
|
||||||
|
'A→C: 0+4=4=4 ✗\n'
|
||||||
|
'B→D: 2+3=5=5 ✗\n'
|
||||||
|
'C→D: 4+5=9>5 ✗'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'relaxed': set(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Iteracja 3',
|
||||||
|
'edges_detail': (
|
||||||
|
'Brak zmian → stabilne!\n'
|
||||||
|
'(wczesne zakończenie\n'
|
||||||
|
' optymalizacja)'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'relaxed': set(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, it in enumerate(iterations):
|
||||||
|
# Graph subplot
|
||||||
|
ax_g = fig.add_subplot(2, 4, i + 1)
|
||||||
|
draw_full_graph(ax_g, title=it['title'],
|
||||||
|
dist=it['dist'], current=None,
|
||||||
|
visited=set() if i == 0 else {'A', 'B', 'C', 'D'},
|
||||||
|
relaxed_edges=it['relaxed'])
|
||||||
|
|
||||||
|
# Detail subplot below
|
||||||
|
ax_d = fig.add_subplot(2, 4, i + 5)
|
||||||
|
ax_d.axis('off')
|
||||||
|
ax_d.text(0.5, 0.5, it['edges_detail'], ha='center', va='center',
|
||||||
|
fontsize=FS, family='monospace',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=GRAY4,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
# Negative cycle check note
|
||||||
|
fig.text(0.5, 0.01,
|
||||||
|
'Po 3 iteracjach: sprawdź raz jeszcze — nic się nie zmienia → BRAK cyklu ujemnego → wynik poprawny',
|
||||||
|
ha='center', fontsize=FS, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_GREEN, edgecolor=LN))
|
||||||
|
|
||||||
|
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'bellman_ford_traversal.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ bellman_ford_traversal.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 4. A* traversal
|
||||||
|
# ============================================================
|
||||||
|
def draw_astar_traversal():
|
||||||
|
"""Step-by-step A* on the shared graph with heuristics."""
|
||||||
|
# Heuristic values (straight-line distance to D)
|
||||||
|
h_vals = {'A': 4, 'B': 2, 'C': 3, 'D': 0}
|
||||||
|
|
||||||
|
fig = plt.figure(figsize=(14, 7.5))
|
||||||
|
fig.suptitle('A* — przejście grafu krok po kroku (cel = D)\n'
|
||||||
|
'f(n) = g(n) + h(n), heurystyka h = oszacowana odległość do D',
|
||||||
|
fontsize=FS_TITLE, fontweight='bold', y=0.99)
|
||||||
|
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
'title': 'Krok 0: Inicjalizacja\nh(A)=4, h(B)=2, h(C)=3, h(D)=0',
|
||||||
|
'detail': (
|
||||||
|
'g(A)=0, f(A)=0+4=4\n'
|
||||||
|
'pq = [(4, A)]\n'
|
||||||
|
'h = oszacowanie\n'
|
||||||
|
' odl. do celu D'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0'},
|
||||||
|
'f_vals': {'A': 'f=4'},
|
||||||
|
'current': 'A', 'visited': set(), 'relaxed': set(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 1: pop A (f=4)\nA→B: g=2, f=2+2=4\nA→C: g=4, f=4+3=7',
|
||||||
|
'detail': (
|
||||||
|
'Relaksacja:\n'
|
||||||
|
' A→B: g=0+2=2\n'
|
||||||
|
' f=2+h(B)=2+2=4\n'
|
||||||
|
' A→C: g=0+4=4\n'
|
||||||
|
' f=4+h(C)=4+3=7\n'
|
||||||
|
'pq = [(4,B), (7,C)]'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4'},
|
||||||
|
'current': 'A', 'visited': {'A'},
|
||||||
|
'relaxed': {('A', 'B'), ('A', 'C')},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 2: pop B (f=4) — min!\nB→D: g=5, f=5+0=5',
|
||||||
|
'detail': (
|
||||||
|
'B ma f=4 < C(f=7)\n'
|
||||||
|
'→ A* kieruje się\n'
|
||||||
|
' W STRONĘ celu!\n'
|
||||||
|
'Relaksacja:\n'
|
||||||
|
' B→D: g=2+3=5\n'
|
||||||
|
' f=5+h(D)=5+0=5\n'
|
||||||
|
'pq = [(5,D), (7,C)]'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'},
|
||||||
|
'current': 'B', 'visited': {'A', 'B'},
|
||||||
|
'relaxed': {('B', 'D')},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Krok 3: pop D (f=5)\nu == goal → STOP!',
|
||||||
|
'detail': (
|
||||||
|
'D to CEL → KONIEC!\n'
|
||||||
|
'Nie przetwarzamy C\n'
|
||||||
|
' (f(C)=7 > f(D)=5)\n\n'
|
||||||
|
'Ścieżka: A→B→D\n'
|
||||||
|
'Koszt: 5\n\n'
|
||||||
|
'Dijkstra odwi-\n'
|
||||||
|
'edziłby też C!'
|
||||||
|
),
|
||||||
|
'dist': {'A': '0', 'B': '2', 'D': '5'},
|
||||||
|
'current': 'D', 'visited': {'A', 'B', 'D'},
|
||||||
|
'relaxed': set(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, step in enumerate(steps):
|
||||||
|
# Graph
|
||||||
|
ax_g = fig.add_subplot(2, 4, i + 1)
|
||||||
|
draw_full_graph(ax_g, title=step['title'],
|
||||||
|
dist=step['dist'], current=step['current'],
|
||||||
|
visited=step['visited'],
|
||||||
|
relaxed_edges=step['relaxed'])
|
||||||
|
|
||||||
|
# Add h values as small labels
|
||||||
|
for name, pos in NODE_POS.items():
|
||||||
|
ax_g.text(pos[0] + 0.35, pos[1] + 0.35, f'h={h_vals[name]}',
|
||||||
|
ha='center', va='center', fontsize=5.5, color='#1565C0',
|
||||||
|
fontweight='bold', zorder=7,
|
||||||
|
bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_BLUE,
|
||||||
|
edgecolor='#1565C0', alpha=0.9, lw=0.5))
|
||||||
|
|
||||||
|
# Detail
|
||||||
|
ax_d = fig.add_subplot(2, 4, i + 5)
|
||||||
|
ax_d.axis('off')
|
||||||
|
ax_d.text(0.5, 0.5, step['detail'], ha='center', va='center',
|
||||||
|
fontsize=FS, family='monospace',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.4', facecolor=GRAY4,
|
||||||
|
edgecolor=GRAY3))
|
||||||
|
|
||||||
|
# Comparison note
|
||||||
|
fig.text(0.5, 0.01,
|
||||||
|
'A* odwiedził 3 wierzchołki (A, B, D) — POMINĄŁ C!\n'
|
||||||
|
'Dijkstra odwiedziłby wszystkie 4. Heurystyka h kieruje przeszukiwanie w stronę celu.',
|
||||||
|
ha='center', fontsize=FS, fontweight='bold',
|
||||||
|
bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_BLUE, edgecolor='#1565C0'))
|
||||||
|
|
||||||
|
plt.tight_layout(rect=[0, 0.06, 1, 0.95])
|
||||||
|
plt.savefig(os.path.join(OUTPUT_DIR, 'astar_traversal.png'),
|
||||||
|
dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||||||
|
plt.close()
|
||||||
|
print(" ✓ astar_traversal.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Main
|
||||||
|
# ============================================================
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Generating shortest path diagrams for PYTANIE 2...")
|
||||||
|
draw_graph_structure()
|
||||||
|
draw_dijkstra_traversal()
|
||||||
|
draw_bellman_ford_traversal()
|
||||||
|
draw_astar_traversal()
|
||||||
|
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")
|
||||||
BIN
pytania/img/astar_traversal.png
Normal file
|
After Width: | Height: | Size: 442 KiB |
BIN
pytania/img/bellman_ford_traversal.png
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
pytania/img/cpm_example.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
pytania/img/deadlock_illustration.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
pytania/img/dijkstra_traversal.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
pytania/img/fa_recognition_example.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
pytania/img/ford_fulkerson_example.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
pytania/img/graph_example_structure.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
pytania/img/hungarian_example.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
pytania/img/ipc_mechanisms.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
pytania/img/kruskal_example.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
pytania/img/lba_recognition_example.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
pytania/img/min_cost_flow_example.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
pytania/img/nf_0nf_table.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
pytania/img/nf_1nf_tables.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
pytania/img/nf_2nf_tables.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
pytania/img/nf_3nf_tables.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
pytania/img/nf_4nf_example.png
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
pytania/img/nf_5nf_example.png
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
pytania/img/nf_bcnf_tables.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
pytania/img/nf_summary_flow.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
pytania/img/pda_recognition_example.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
pytania/img/producer_consumer.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
pytania/img/scheduling_complexity_map.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
pytania/img/scheduling_edd_example.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
pytania/img/scheduling_flow_vs_job.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
pytania/img/scheduling_graham_notation.png
Normal file
|
After Width: | Height: | Size: 414 KiB |
BIN
pytania/img/scheduling_johnson_gantt.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
pytania/img/scheduling_spt_comparison.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
pytania/img/tm_recognition_example.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
pytania/img/tsp_nearest_neighbor.png
Normal file
|
After Width: | Height: | Size: 191 KiB |