feat: updated questions obrona magister

This commit is contained in:
Krzysztof kuhy Rudnicki 2026-02-16 14:06:46 +01:00
parent b6b20191a3
commit 4aeeafface
37 changed files with 4027 additions and 155 deletions

File diff suppressed because it is too large Load Diff

View 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}/")

View 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)

View 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!")

View 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)

View 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'
'(V1 = 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 (V1=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}/")

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
pytania/img/cpm_example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB