mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-07-04 13:43:02 +02:00
refactor(praca/generate_images): fix ruff violations in diagram generators batch 1
This commit is contained in:
parent
47c7679222
commit
6e1648f264
@ -1,13 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Bellman-Ford negative-weights & negative-cycle diagram for PYTANIE 2.
|
||||
"""Generate Bellman-Ford negative-weights & negative-cycle diagram.
|
||||
|
||||
Two-part figure:
|
||||
Diagram for PYTANIE 2. Two-part figure:
|
||||
Part 1: Graph with negative edge, Dijkstra WRONG vs Bellman-Ford CORRECT
|
||||
Part 2: Negative cycle detection (add C→B(-3))
|
||||
Part 2: Negative cycle detection (add C->B(-3))
|
||||
|
||||
A4-compatible, monochrome-friendly, 300 DPI.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.use("Agg")
|
||||
@ -16,6 +21,11 @@ from pathlib import Path
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DPI = 300
|
||||
BG = "white"
|
||||
LN = "black"
|
||||
@ -34,34 +44,53 @@ LIGHT_GREEN = "#D5E8D4"
|
||||
LIGHT_RED = "#F8D7DA"
|
||||
LIGHT_YELLOW = "#FFF9C4"
|
||||
|
||||
# --- Graph layout for negative-weight example ---
|
||||
# S→A(2), A→C(3), S→B(5), B→A(-4)
|
||||
NEG_POS = {"S": (0.8, 2), "A": (3.3, 3.2), "B": (3.3, 0.8), "C": (5.8, 2)}
|
||||
NEG_EDGES = [("S", "A", 2), ("A", "C", 3), ("S", "B", 5), ("B", "A", -4)]
|
||||
# Graph layout for negative-weight example:
|
||||
# S->A(2), A->C(3), S->B(5), B->A(-4)
|
||||
NEG_POS: dict[str, tuple[float, float]] = {
|
||||
"S": (0.8, 2),
|
||||
"A": (3.3, 3.2),
|
||||
"B": (3.3, 0.8),
|
||||
"C": (5.8, 2),
|
||||
}
|
||||
NEG_EDGES: list[tuple[str, str, int]] = [
|
||||
("S", "A", 2),
|
||||
("A", "C", 3),
|
||||
("S", "B", 5),
|
||||
("B", "A", -4),
|
||||
]
|
||||
|
||||
|
||||
def draw_node(
|
||||
ax,
|
||||
name,
|
||||
pos,
|
||||
color="white",
|
||||
current=False,
|
||||
visited=False,
|
||||
dist_label=None,
|
||||
fontsize=12,
|
||||
error=False,
|
||||
ax: Axes,
|
||||
name: str,
|
||||
pos: tuple[float, float],
|
||||
*,
|
||||
color: str = "white",
|
||||
current: bool = False,
|
||||
visited: bool = False,
|
||||
dist_label: str | None = None,
|
||||
fontsize: int = 12,
|
||||
error: bool = False,
|
||||
) -> None:
|
||||
"""Draw node."""
|
||||
"""Draw a graph node with optional distance label."""
|
||||
x, y = pos
|
||||
r = 0.35
|
||||
lw = 2.5 if current else 1.5
|
||||
ec = "#D32F2F" if current else ("#D32F2F" if error else LN)
|
||||
fc = LIGHT_YELLOW if current else (LIGHT_GREEN if visited else color)
|
||||
fc = LIGHT_YELLOW if current else (
|
||||
LIGHT_GREEN if visited else color
|
||||
)
|
||||
if error:
|
||||
fc = LIGHT_RED
|
||||
|
||||
circle = plt.Circle(
|
||||
(x, y), r, fill=True, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=5
|
||||
(x, y),
|
||||
r,
|
||||
fill=True,
|
||||
facecolor=fc,
|
||||
edgecolor=ec,
|
||||
linewidth=lw,
|
||||
zorder=5,
|
||||
)
|
||||
ax.add_patch(circle)
|
||||
ax.text(
|
||||
@ -95,18 +124,36 @@ def draw_node(
|
||||
)
|
||||
|
||||
|
||||
def _choose_edge_style(
|
||||
*,
|
||||
negative: bool,
|
||||
relaxed: bool,
|
||||
highlighted: bool,
|
||||
cycle_edge: bool,
|
||||
) -> tuple[str, float, str]:
|
||||
"""Return (color, lw, linestyle) for an edge."""
|
||||
if cycle_edge:
|
||||
return "#D32F2F", 2.5, "--"
|
||||
if negative or relaxed:
|
||||
return "#D32F2F", 2.5, "-"
|
||||
if highlighted:
|
||||
return "#1565C0", 2.0, "-"
|
||||
return GRAY3, 1.5, "-"
|
||||
|
||||
|
||||
def draw_edge(
|
||||
ax,
|
||||
pos1,
|
||||
pos2,
|
||||
weight,
|
||||
highlighted=False,
|
||||
relaxed=False,
|
||||
negative=False,
|
||||
cycle_edge=False,
|
||||
offset=0.0,
|
||||
ax: Axes,
|
||||
pos1: tuple[float, float],
|
||||
pos2: tuple[float, float],
|
||||
weight: int,
|
||||
*,
|
||||
highlighted: bool = False,
|
||||
relaxed: bool = False,
|
||||
negative: bool = False,
|
||||
cycle_edge: bool = False,
|
||||
offset: float = 0.0,
|
||||
) -> None:
|
||||
"""Draw edge."""
|
||||
"""Draw a directed edge between two nodes with a weight label."""
|
||||
x1, y1 = pos1
|
||||
x2, y2 = pos2
|
||||
|
||||
@ -127,22 +174,12 @@ def draw_edge(
|
||||
ex += perp_x
|
||||
ey += perp_y
|
||||
|
||||
if cycle_edge:
|
||||
color = "#D32F2F"
|
||||
lw = 2.5
|
||||
ls = "--"
|
||||
elif negative or relaxed:
|
||||
color = "#D32F2F"
|
||||
lw = 2.5
|
||||
ls = "-"
|
||||
elif highlighted:
|
||||
color = "#1565C0"
|
||||
lw = 2.0
|
||||
ls = "-"
|
||||
else:
|
||||
color = GRAY3
|
||||
lw = 1.5
|
||||
ls = "-"
|
||||
color, lw, ls = _choose_edge_style(
|
||||
negative=negative,
|
||||
relaxed=relaxed,
|
||||
highlighted=highlighted,
|
||||
cycle_edge=cycle_edge,
|
||||
)
|
||||
|
||||
# Arrow
|
||||
ax.annotate(
|
||||
@ -191,18 +228,19 @@ def draw_edge(
|
||||
|
||||
|
||||
def draw_neg_graph(
|
||||
ax,
|
||||
edges,
|
||||
title="",
|
||||
dist=None,
|
||||
current=None,
|
||||
visited=None,
|
||||
relaxed_edges=None,
|
||||
error_nodes=None,
|
||||
extra_edges=None,
|
||||
node_positions=None,
|
||||
ax: Axes,
|
||||
edges: list[tuple[str, str, int]],
|
||||
*,
|
||||
title: str = "",
|
||||
dist: dict[str, str] | None = None,
|
||||
current: str | None = None,
|
||||
visited: set[str] | None = None,
|
||||
relaxed_edges: set[tuple[str, str]] | None = None,
|
||||
error_nodes: set[str] | None = None,
|
||||
extra_edges: list[tuple[str, str, int]] | None = None,
|
||||
node_positions: dict[str, tuple[float, float]] | None = None,
|
||||
) -> None:
|
||||
"""Draw neg graph."""
|
||||
"""Draw the negative-weight graph with annotations."""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
if relaxed_edges is None:
|
||||
@ -228,9 +266,7 @@ def draw_neg_graph(
|
||||
for u, v, w in all_edges:
|
||||
rl = (u, v) in relaxed_edges
|
||||
neg = w < 0
|
||||
cycle = extra_edges and (u, v, w) in extra_edges
|
||||
# If B→A and A→B both exist, offset them
|
||||
off = 0.0
|
||||
cycle = bool(extra_edges and (u, v, w) in extra_edges)
|
||||
draw_edge(
|
||||
ax,
|
||||
node_positions[u],
|
||||
@ -239,7 +275,7 @@ def draw_neg_graph(
|
||||
relaxed=rl,
|
||||
negative=neg,
|
||||
cycle_edge=cycle,
|
||||
offset=off,
|
||||
offset=0.0,
|
||||
)
|
||||
|
||||
for name, pos in node_positions.items():
|
||||
@ -258,32 +294,62 @@ def draw_neg_graph(
|
||||
)
|
||||
|
||||
|
||||
def _add_annotation_box(
|
||||
ax: Axes,
|
||||
x: float,
|
||||
y: float,
|
||||
text: str,
|
||||
*,
|
||||
color: str,
|
||||
bg_color: str,
|
||||
) -> None:
|
||||
"""Add a small annotation box near a node."""
|
||||
ax.text(
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
fontsize=FS_SMALL,
|
||||
color=color,
|
||||
fontweight="bold",
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.1",
|
||||
"facecolor": bg_color,
|
||||
"edgecolor": color,
|
||||
"alpha": 0.9,
|
||||
"lw": 0.5,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def generate_bf_negative_weights() -> None:
|
||||
"""Two-row figure.
|
||||
"""Generate two-row figure.
|
||||
|
||||
Row 1: Graph structure + Dijkstra WRONG + Bellman-Ford CORRECT
|
||||
Row 2: B-F iterations 1-3 step by step.
|
||||
"""
|
||||
fig = plt.figure(figsize=(14, 10))
|
||||
fig.suptitle(
|
||||
"Bellman-Ford — ujemne wagi vs Dijkstra\n"
|
||||
"Graf: S→A(2), A→C(3), S→B(5), B→A(-4). Start = S",
|
||||
"Bellman-Ford \u2014 ujemne wagi vs Dijkstra\n"
|
||||
"Graf: S\u2192A(2), A\u2192C(3),"
|
||||
" S\u2192B(5), B\u2192A(-4). Start = S",
|
||||
fontsize=FS_TITLE + 1,
|
||||
fontweight="bold",
|
||||
y=0.99,
|
||||
)
|
||||
|
||||
# ---- Row 1: Graph + Dijkstra wrong + BF correct ----
|
||||
# Row 1: Graph + Dijkstra wrong + BF correct
|
||||
|
||||
# Panel 1: The graph structure
|
||||
ax1 = fig.add_subplot(2, 3, 1)
|
||||
draw_neg_graph(
|
||||
ax1,
|
||||
NEG_EDGES,
|
||||
title="Graf z ujemną wagą\n(B→A = -4, zaznaczona na czerwono)",
|
||||
title=(
|
||||
"Graf z ujemną wagą\n"
|
||||
"(B→A = -4, zaznaczona na czerwono)"
|
||||
),
|
||||
dist={"S": "0", "A": "?", "B": "?", "C": "?"},
|
||||
)
|
||||
# START label
|
||||
ax1.annotate(
|
||||
"START",
|
||||
xy=(NEG_POS["S"][0] - 0.35, NEG_POS["S"][1]),
|
||||
@ -291,7 +357,11 @@ def generate_bf_negative_weights() -> None:
|
||||
fontsize=FS,
|
||||
fontweight="bold",
|
||||
color="#D32F2F",
|
||||
arrowprops={"arrowstyle": "->", "color": "#D32F2F", "lw": 2},
|
||||
arrowprops={
|
||||
"arrowstyle": "->",
|
||||
"color": "#D32F2F",
|
||||
"lw": 2,
|
||||
},
|
||||
va="center",
|
||||
)
|
||||
|
||||
@ -300,41 +370,29 @@ def generate_bf_negative_weights() -> None:
|
||||
draw_neg_graph(
|
||||
ax2,
|
||||
NEG_EDGES,
|
||||
title="Dijkstra — BŁĘDNY wynik\nA zamknięty z d=2, nie poprawia przy B→A",
|
||||
title=(
|
||||
"Dijkstra \u2014 BŁĘDNY wynik\n"
|
||||
"A zamknięty z d=2, nie poprawia przy B→A"
|
||||
),
|
||||
dist={"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||
visited={"S", "A", "B", "C"},
|
||||
error_nodes={"A", "C"},
|
||||
)
|
||||
# Add "WRONG" annotations
|
||||
ax2.text(
|
||||
_add_annotation_box(
|
||||
ax2,
|
||||
NEG_POS["A"][0] + 0.6,
|
||||
NEG_POS["A"][1] + 0.3,
|
||||
"✗ powinno 1",
|
||||
fontsize=FS_SMALL,
|
||||
color="#D32F2F",
|
||||
fontweight="bold",
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.1",
|
||||
"facecolor": LIGHT_RED,
|
||||
"edgecolor": "#D32F2F",
|
||||
"alpha": 0.9,
|
||||
"lw": 0.5,
|
||||
},
|
||||
bg_color=LIGHT_RED,
|
||||
)
|
||||
ax2.text(
|
||||
_add_annotation_box(
|
||||
ax2,
|
||||
NEG_POS["C"][0] + 0.05,
|
||||
NEG_POS["C"][1] + 0.55,
|
||||
"✗ powinno 4",
|
||||
fontsize=FS_SMALL,
|
||||
color="#D32F2F",
|
||||
fontweight="bold",
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.1",
|
||||
"facecolor": LIGHT_RED,
|
||||
"edgecolor": "#D32F2F",
|
||||
"alpha": 0.9,
|
||||
"lw": 0.5,
|
||||
},
|
||||
bg_color=LIGHT_RED,
|
||||
)
|
||||
|
||||
# Panel 3: Bellman-Ford — CORRECT
|
||||
@ -342,48 +400,45 @@ def generate_bf_negative_weights() -> None:
|
||||
draw_neg_graph(
|
||||
ax3,
|
||||
NEG_EDGES,
|
||||
title="Bellman-Ford — POPRAWNY wynik\nUjemna waga B→A poprawnie propagowana",
|
||||
title=(
|
||||
"Bellman-Ford \u2014 POPRAWNY wynik\n"
|
||||
"Ujemna waga B→A poprawnie propagowana"
|
||||
),
|
||||
dist={"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||
visited={"S", "A", "B", "C"},
|
||||
relaxed_edges={("B", "A")},
|
||||
)
|
||||
ax3.text(
|
||||
_add_annotation_box(
|
||||
ax3,
|
||||
NEG_POS["A"][0] + 0.6,
|
||||
NEG_POS["A"][1] + 0.3,
|
||||
"✓ poprawne!",
|
||||
fontsize=FS_SMALL,
|
||||
color="#006400",
|
||||
fontweight="bold",
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.1",
|
||||
"facecolor": LIGHT_GREEN,
|
||||
"edgecolor": "#006400",
|
||||
"alpha": 0.9,
|
||||
"lw": 0.5,
|
||||
},
|
||||
bg_color=LIGHT_GREEN,
|
||||
)
|
||||
ax3.text(
|
||||
_add_annotation_box(
|
||||
ax3,
|
||||
NEG_POS["C"][0] + 0.05,
|
||||
NEG_POS["C"][1] + 0.55,
|
||||
"✓ poprawne!",
|
||||
fontsize=FS_SMALL,
|
||||
color="#006400",
|
||||
fontweight="bold",
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.1",
|
||||
"facecolor": LIGHT_GREEN,
|
||||
"edgecolor": "#006400",
|
||||
"alpha": 0.9,
|
||||
"lw": 0.5,
|
||||
},
|
||||
bg_color=LIGHT_GREEN,
|
||||
)
|
||||
|
||||
# ---- Row 2: B-F iterations step by step ----
|
||||
# Row 2: B-F iterations step by step
|
||||
iterations = [
|
||||
{
|
||||
"title": "B-F Iteracja 1\nRelaksuj WSZYSTKIE krawędzie",
|
||||
"dist": {"S": "0", "A": "1", "B": "5", "C": "5"},
|
||||
"relaxed": {("S", "A"), ("A", "C"), ("S", "B"), ("B", "A")},
|
||||
"title": (
|
||||
"B-F Iteracja 1\n"
|
||||
"Relaksuj WSZYSTKIE krawędzie"
|
||||
),
|
||||
"dist": {
|
||||
"S": "0", "A": "1", "B": "5", "C": "5",
|
||||
},
|
||||
"relaxed": {
|
||||
("S", "A"), ("A", "C"),
|
||||
("S", "B"), ("B", "A"),
|
||||
},
|
||||
"detail": (
|
||||
"S→A: 0+2=2<∞ → A=2\n"
|
||||
"A→C: 2+3=5<∞ → C=5\n"
|
||||
@ -392,16 +447,29 @@ def generate_bf_negative_weights() -> None:
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "B-F Iteracja 2\nPropagacja poprawionego A",
|
||||
"dist": {"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||
"title": (
|
||||
"B-F Iteracja 2\n"
|
||||
"Propagacja poprawionego A"
|
||||
),
|
||||
"dist": {
|
||||
"S": "0", "A": "1", "B": "5", "C": "4",
|
||||
},
|
||||
"relaxed": {("A", "C")},
|
||||
"detail": (
|
||||
"S→A: 0+2=2>1 ✗\nA→C: 1+3=4<5 → C=4 ✓\nS→B: 0+5=5=5 ✗\nB→A: 5-4=1=1 ✗"
|
||||
"S→A: 0+2=2>1 ✗\n"
|
||||
"A→C: 1+3=4<5 → C=4 ✓\n"
|
||||
"S→B: 0+5=5=5 ✗\n"
|
||||
"B→A: 5-4=1=1 ✗"
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "B-F Iteracja 3\nBrak zmian → stabilne!",
|
||||
"dist": {"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||
"title": (
|
||||
"B-F Iteracja 3\n"
|
||||
"Brak zmian → stabilne!"
|
||||
),
|
||||
"dist": {
|
||||
"S": "0", "A": "1", "B": "5", "C": "4",
|
||||
},
|
||||
"relaxed": set(),
|
||||
"detail": (
|
||||
"Wszystkie krawędzie:\n"
|
||||
@ -422,7 +490,6 @@ def generate_bf_negative_weights() -> None:
|
||||
visited={"S", "A", "B", "C"},
|
||||
relaxed_edges=it["relaxed"],
|
||||
)
|
||||
# Detail text below graph
|
||||
ax.text(
|
||||
3.2,
|
||||
-0.5,
|
||||
@ -431,19 +498,31 @@ def generate_bf_negative_weights() -> None:
|
||||
va="top",
|
||||
fontsize=FS_SMALL,
|
||||
family="monospace",
|
||||
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.3",
|
||||
"facecolor": GRAY4,
|
||||
"edgecolor": GRAY3,
|
||||
},
|
||||
)
|
||||
|
||||
# Bottom note
|
||||
fig.text(
|
||||
0.5,
|
||||
0.01,
|
||||
"Dijkstra zamyka wierzchołki na stałe (zachłanność) → ujemna waga B→A(-4) nie może poprawić zamkniętego A.\n"
|
||||
"Bellman-Ford relaksuje WSZYSTKIE krawędzie w każdej iteracji → ujemne wagi propagują się poprawnie.",
|
||||
"Dijkstra zamyka wierzchołki na stałe"
|
||||
" (zachłanność) → ujemna waga B→A(-4)"
|
||||
" nie może poprawić zamkniętego A.\n"
|
||||
"Bellman-Ford relaksuje WSZYSTKIE krawędzie"
|
||||
" w każdej iteracji → ujemne wagi"
|
||||
" propagują się poprawnie.",
|
||||
ha="center",
|
||||
fontsize=FS,
|
||||
fontweight="bold",
|
||||
bbox={"boxstyle": "round,pad=0.3", "facecolor": LIGHT_YELLOW, "edgecolor": LN},
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.3",
|
||||
"facecolor": LIGHT_YELLOW,
|
||||
"edgecolor": LN,
|
||||
},
|
||||
)
|
||||
|
||||
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
|
||||
@ -454,19 +533,20 @@ def generate_bf_negative_weights() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close()
|
||||
print(" ✓ bellman_ford_negative_weights.png")
|
||||
_logger.info(" ✓ bellman_ford_negative_weights.png")
|
||||
|
||||
|
||||
def generate_bf_negative_cycle() -> None:
|
||||
"""Figure showing negative cycle detection.
|
||||
"""Generate figure showing negative cycle detection.
|
||||
|
||||
Graph: S→A(2), A→C(3), S→B(5), B→A(-4), C→B(-3) [added edge]
|
||||
Cycle: B→A→C→B = -4+3+(-3) = -4 < 0.
|
||||
Graph: S->A(2), A->C(3), S->B(5), B->A(-4), C->B(-3)
|
||||
Cycle: B->A->C->B = -4+3+(-3) = -4 < 0.
|
||||
"""
|
||||
fig = plt.figure(figsize=(14, 5.5))
|
||||
fig.suptitle(
|
||||
"Bellman-Ford — wykrywanie cyklu ujemnego\n"
|
||||
"Dodano krawędź C→B(-3). Cykl: B→A→C→B = -4+3+(-3) = -4 < 0",
|
||||
"Bellman-Ford \u2014 wykrywanie cyklu ujemnego\n"
|
||||
"Dodano krawędź C→B(-3)."
|
||||
" Cykl: B→A→C→B = -4+3+(-3) = -4 < 0",
|
||||
fontsize=FS_TITLE + 1,
|
||||
fontweight="bold",
|
||||
y=0.99,
|
||||
@ -477,11 +557,13 @@ def generate_bf_negative_cycle() -> None:
|
||||
draw_neg_graph(
|
||||
ax1,
|
||||
NEG_EDGES,
|
||||
title="Graf z cyklem ujemnym\nDodana krawędź C→B(-3) — przerywana",
|
||||
title=(
|
||||
"Graf z cyklem ujemnym\n"
|
||||
"Dodana krawędź C→B(-3) \u2014 przerywana"
|
||||
),
|
||||
dist={"S": "0", "A": "?", "B": "?", "C": "?"},
|
||||
extra_edges=[("C", "B", -3)],
|
||||
)
|
||||
# Mark cycle
|
||||
ax1.annotate(
|
||||
"CYKL\n-4+3+(-3)=-4<0",
|
||||
xy=(3.3, 2.0),
|
||||
@ -503,7 +585,10 @@ def generate_bf_negative_cycle() -> None:
|
||||
draw_neg_graph(
|
||||
ax2,
|
||||
NEG_EDGES,
|
||||
title="Po V-1=3 iteracjach\ndist wciąż maleje (niestabilne!)",
|
||||
title=(
|
||||
"Po V-1=3 iteracjach\n"
|
||||
"dist wciąż maleje (niestabilne!)"
|
||||
),
|
||||
dist={"S": "0", "A": "-7", "B": "-4", "C": "-4"},
|
||||
visited={"S", "A", "B", "C"},
|
||||
error_nodes={"A", "B", "C"},
|
||||
@ -512,7 +597,9 @@ def generate_bf_negative_cycle() -> None:
|
||||
ax2.text(
|
||||
3.2,
|
||||
-0.4,
|
||||
"Każde okrążenie cyklu\nzmniejsza dist o 4.\nDist → -∞ (brak minimum!)",
|
||||
"Każde okrążenie cyklu\n"
|
||||
"zmniejsza dist o 4.\n"
|
||||
"Dist → -∞ (brak minimum!)",
|
||||
ha="center",
|
||||
va="top",
|
||||
fontsize=FS_SMALL,
|
||||
@ -562,7 +649,8 @@ def generate_bf_negative_cycle() -> None:
|
||||
},
|
||||
)
|
||||
ax3.set_title(
|
||||
"Wykrywanie — V-ta iteracja\nJeśli cokolwiek się poprawia → cykl ujemny!",
|
||||
"Wykrywanie \u2014 V-ta iteracja\n"
|
||||
"Jeśli cokolwiek się poprawia → cykl ujemny!",
|
||||
fontsize=FS,
|
||||
fontweight="bold",
|
||||
pad=5,
|
||||
@ -572,12 +660,19 @@ def generate_bf_negative_cycle() -> None:
|
||||
fig.text(
|
||||
0.5,
|
||||
0.01,
|
||||
"Bez cyklu ujemnego: po V-1 iteracjach dist jest stabilne. "
|
||||
"Z cyklem ujemnym: dist maleje w nieskończoność → V-ta iteracja to wykrywa.",
|
||||
"Bez cyklu ujemnego: po V-1 iteracjach"
|
||||
" dist jest stabilne. "
|
||||
"Z cyklem ujemnym: dist maleje"
|
||||
" w nieskończoność"
|
||||
" → V-ta iteracja to wykrywa.",
|
||||
ha="center",
|
||||
fontsize=FS,
|
||||
fontweight="bold",
|
||||
bbox={"boxstyle": "round,pad=0.3", "facecolor": LIGHT_YELLOW, "edgecolor": LN},
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.3",
|
||||
"facecolor": LIGHT_YELLOW,
|
||||
"edgecolor": LN,
|
||||
},
|
||||
)
|
||||
|
||||
plt.tight_layout(rect=[0, 0.06, 1, 0.94])
|
||||
@ -588,11 +683,14 @@ def generate_bf_negative_cycle() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close()
|
||||
print(" ✓ bellman_ford_negative_cycle.png")
|
||||
_logger.info(" ✓ bellman_ford_negative_cycle.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Generating Bellman-Ford negative weight diagrams...")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
_logger.info(
|
||||
"Generating Bellman-Ford negative weight diagrams..."
|
||||
)
|
||||
generate_bf_negative_weights()
|
||||
generate_bf_negative_cycle()
|
||||
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")
|
||||
_logger.info("All diagrams saved to %s/", OUTPUT_DIR)
|
||||
|
||||
@ -582,7 +582,7 @@ def draw_3nf() -> None:
|
||||
"""Draw 3nf."""
|
||||
fig, ax = create_figure(11.69, 6.5)
|
||||
|
||||
# Studenci (fixed)
|
||||
# Student table after removing transitive dependency
|
||||
h1 = ["StID*", "Imie", "WydzialID"]
|
||||
r1 = [["1", "Anna", "W4"], ["2", "Jan", "W4"], ["3", "Ewa", "W2"]]
|
||||
cw1 = [0.55, 0.55, 0.85]
|
||||
@ -633,7 +633,8 @@ def draw_3nf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
2.95,
|
||||
" StID -> WydzialID -> NazwaWydzialu rozbito: NazwaWydzialu w osobnej tabeli.",
|
||||
" StID -> WydzialID -> NazwaWydzialu"
|
||||
" rozbito: NazwaWydzialu w osobnej tabeli.",
|
||||
fontsize=8,
|
||||
color="#333333",
|
||||
)
|
||||
@ -665,7 +666,8 @@ def draw_3nf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
1.5,
|
||||
" BCNF nie ma takiego wyjatku -> kazda nietrywialna FD wymaga nadklucza po lewej.",
|
||||
" BCNF nie ma takiego wyjatku"
|
||||
" -> kazda nietrywialna FD wymaga nadklucza po lewej.",
|
||||
fontsize=9,
|
||||
color="#333333",
|
||||
)
|
||||
@ -677,7 +679,7 @@ def draw_3nf() -> None:
|
||||
pad_inches=0.2,
|
||||
)
|
||||
plt.close(fig)
|
||||
print("Generated: nf_3nf_tables.png")
|
||||
logger.info("Generated: nf_3nf_tables.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -713,7 +715,7 @@ def draw_bcnf() -> None:
|
||||
ax, 7.2, 6.8, "ProwadzacyKurs (kl: Prow.)", h4, r4, cw4, title_fontsize=9
|
||||
)
|
||||
|
||||
# StudentProwadzacy (NEW)
|
||||
# New student-advisor junction table
|
||||
h5 = ["StID*", "Prowadzacy*"]
|
||||
r5 = [["1", "Kowalski"], ["1", "Nowak"], ["2", "Kowalski"], ["3", "Wisniewski"]]
|
||||
cw5 = [0.55, 1.05]
|
||||
@ -733,7 +735,8 @@ def draw_bcnf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
2.55,
|
||||
" ProwadzacyKurs(Prowadzacy, KursID) — FD: Prowadzacy -> KursID, klucz: Prowadzacy",
|
||||
" ProwadzacyKurs(Prowadzacy, KursID)"
|
||||
" — FD: Prowadzacy -> KursID, klucz: Prowadzacy",
|
||||
fontsize=8,
|
||||
color="#333333",
|
||||
)
|
||||
@ -756,7 +759,8 @@ def draw_bcnf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
1.45,
|
||||
"Rekonstrukcja: StudentProw. JOIN ProwadzacyKurs ON Prowadzacy -> odtworzenie Zapisy.",
|
||||
"Rekonstrukcja: StudentProw. JOIN ProwadzacyKurs"
|
||||
" ON Prowadzacy -> odtworzenie Zapisy.",
|
||||
fontsize=8,
|
||||
color="#333333",
|
||||
)
|
||||
@ -768,7 +772,7 @@ def draw_bcnf() -> None:
|
||||
pad_inches=0.2,
|
||||
)
|
||||
plt.close(fig)
|
||||
print("Generated: nf_bcnf_tables.png")
|
||||
logger.info("Generated: nf_bcnf_tables.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -903,7 +907,7 @@ def draw_4nf() -> None:
|
||||
pad_inches=0.2,
|
||||
)
|
||||
plt.close(fig)
|
||||
print("Generated: nf_4nf_example.png")
|
||||
logger.info("Generated: nf_4nf_example.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -1013,14 +1017,16 @@ def draw_5nf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
2.0,
|
||||
"Weryfikacja: Alfa dostarcza Nakretke? Alfa -> Wiezowiec? Nakretka -> Wiezowiec?",
|
||||
"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.",
|
||||
" TAK, TAK, TAK --> wg reguly cyklicznej:"
|
||||
" Alfa dostarcza Nakretke do Wiezowca.",
|
||||
fontsize=8,
|
||||
color="#333333",
|
||||
)
|
||||
@ -1035,7 +1041,8 @@ def draw_5nf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
0.9,
|
||||
" --> Alfa dostarcza Nakretke do Mostu. (Tego wiersza NIE MA w oryginale -- BLAD!)",
|
||||
" --> Alfa dostarcza Nakretke do Mostu."
|
||||
" (Tego wiersza NIE MA w oryginale -- BLAD!)",
|
||||
fontsize=8,
|
||||
color="black",
|
||||
)
|
||||
@ -1043,7 +1050,8 @@ def draw_5nf() -> None:
|
||||
ax,
|
||||
0.3,
|
||||
0.5,
|
||||
" Dekompozycja 5NF jest poprawna TYLKO jesli regula cykliczna rzeczywiscie zachodzi!",
|
||||
" Dekompozycja 5NF jest poprawna TYLKO"
|
||||
" jesli regula cykliczna rzeczywiscie zachodzi!",
|
||||
fontsize=8,
|
||||
color="black",
|
||||
)
|
||||
@ -1055,7 +1063,7 @@ def draw_5nf() -> None:
|
||||
pad_inches=0.2,
|
||||
)
|
||||
plt.close(fig)
|
||||
print("Generated: nf_5nf_example.png")
|
||||
logger.info("Generated: nf_5nf_example.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -1142,7 +1150,7 @@ def draw_summary_flow() -> None:
|
||||
arrowprops={"arrowstyle": "<-", "color": "black", "lw": 1.5},
|
||||
)
|
||||
|
||||
# Bottom: mnemonic
|
||||
# Mnemonic quote at the bottom
|
||||
ax.text(
|
||||
5.85,
|
||||
2.2,
|
||||
@ -1178,7 +1186,8 @@ def draw_summary_flow() -> None:
|
||||
ax.text(
|
||||
5.85,
|
||||
0.8,
|
||||
"5NF (zawiera sie w) 4NF (zaw.) BCNF (zaw.) 3NF (zaw.) 2NF (zaw.) 1NF",
|
||||
"5NF (zawiera sie w) 4NF (zaw.) BCNF"
|
||||
" (zaw.) 3NF (zaw.) 2NF (zaw.) 1NF",
|
||||
fontsize=8,
|
||||
ha="center",
|
||||
va="center",
|
||||
@ -1193,14 +1202,15 @@ def draw_summary_flow() -> None:
|
||||
pad_inches=0.2,
|
||||
)
|
||||
plt.close(fig)
|
||||
print("Generated: nf_summary_flow.png")
|
||||
logger.info("Generated: nf_summary_flow.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
if __name__ == "__main__":
|
||||
print("Generating normalization diagrams...")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger.info("Generating normalization diagrams...")
|
||||
draw_0nf()
|
||||
draw_1nf()
|
||||
draw_2nf()
|
||||
@ -1209,4 +1219,4 @@ if __name__ == "__main__":
|
||||
draw_4nf()
|
||||
draw_5nf()
|
||||
draw_summary_flow()
|
||||
print("\nDone! All diagrams saved to:", OUTPUT_DIR)
|
||||
logger.info("Done! All diagrams saved to %s", OUTPUT_DIR)
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.use("Agg")
|
||||
@ -18,6 +23,11 @@ from matplotlib.patches import FancyBboxPatch
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DPI = 300
|
||||
BG = "white"
|
||||
LN = "black"
|
||||
@ -33,29 +43,40 @@ GRAY3 = "#B8B8B8"
|
||||
GRAY4 = "#F5F5F5"
|
||||
GRAY5 = "#C0C0C0"
|
||||
|
||||
_BAND_HEIGHTS = [0.7, 1.3, 1.4, 1.5, 1.5]
|
||||
|
||||
|
||||
def draw_box(
|
||||
ax,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
text,
|
||||
fill="white",
|
||||
lw=1.2,
|
||||
fontsize=FS,
|
||||
fontweight="normal",
|
||||
ha="center",
|
||||
va="center",
|
||||
rounded=True,
|
||||
ax: Axes,
|
||||
x: float,
|
||||
y: float,
|
||||
w: float,
|
||||
h: float,
|
||||
text: str,
|
||||
*,
|
||||
fill: str = "white",
|
||||
lw: float = 1.2,
|
||||
fontsize: float = FS,
|
||||
fontweight: str = "normal",
|
||||
ha: str = "center",
|
||||
va: str = "center",
|
||||
rounded: bool = True,
|
||||
) -> None:
|
||||
"""Draw box."""
|
||||
"""Draw a labeled box on the axes."""
|
||||
if rounded:
|
||||
rect = FancyBboxPatch(
|
||||
(x, y), w, h, boxstyle="round,pad=0.08", lw=lw, edgecolor=LN, facecolor=fill
|
||||
(x, y),
|
||||
w,
|
||||
h,
|
||||
boxstyle="round,pad=0.08",
|
||||
lw=lw,
|
||||
edgecolor=LN,
|
||||
facecolor=fill,
|
||||
)
|
||||
else:
|
||||
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
|
||||
rect = mpatches.Rectangle(
|
||||
(x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill
|
||||
)
|
||||
ax.add_patch(rect)
|
||||
ax.text(
|
||||
x + w / 2,
|
||||
@ -69,8 +90,18 @@ def draw_box(
|
||||
)
|
||||
|
||||
|
||||
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style="->", color=LN) -> None:
|
||||
"""Draw arrow."""
|
||||
def draw_arrow(
|
||||
ax: Axes,
|
||||
x1: float,
|
||||
y1: float,
|
||||
x2: float,
|
||||
y2: float,
|
||||
*,
|
||||
lw: float = 1.2,
|
||||
style: str = "->",
|
||||
color: str = LN,
|
||||
) -> None:
|
||||
"""Draw an arrow between two points."""
|
||||
ax.annotate(
|
||||
"",
|
||||
xy=(x2, y2),
|
||||
@ -83,7 +114,7 @@ def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style="->", color=LN) -> None:
|
||||
# 1. Pattern Template Structure (NaPSiRoKo mnemonic)
|
||||
# ============================================================
|
||||
def generate_pattern_template() -> None:
|
||||
"""Generate pattern template."""
|
||||
"""Generate pattern template diagram with NaPSiRoKo mnemonic."""
|
||||
fig, ax = plt.subplots(figsize=(8.27, 6))
|
||||
ax.set_xlim(0, 10)
|
||||
ax.set_ylim(0, 8)
|
||||
@ -133,11 +164,17 @@ def generate_pattern_template() -> None:
|
||||
(
|
||||
"Si",
|
||||
"SIŁY (forces)",
|
||||
"Konkurencyjne wymagania do pogodzenia\n(np. testowalność vs wydajność)",
|
||||
"Konkurencyjne wymagania do pogodzenia\n"
|
||||
"(np. testowalność vs wydajność)",
|
||||
GRAY1,
|
||||
),
|
||||
("Ro", "ROZWIĄZANIE", "Struktura, diagram, zachowanie", "white"),
|
||||
("Ko", "KONSEKWENCJE", "Tradeoffs: co zyskujemy, co tracimy", GRAY1),
|
||||
(
|
||||
"Ko",
|
||||
"KONSEKWENCJE",
|
||||
"Tradeoffs: co zyskujemy, co tracimy",
|
||||
GRAY1,
|
||||
),
|
||||
]
|
||||
|
||||
band_x = card_x + 0.3
|
||||
@ -202,7 +239,14 @@ def generate_pattern_template() -> None:
|
||||
|
||||
# Arrow connecting fields
|
||||
if i < len(fields) - 1:
|
||||
draw_arrow(ax, band_x + 0.35, by - 0.02, band_x + 0.35, by - 0.13, lw=1.0)
|
||||
draw_arrow(
|
||||
ax,
|
||||
band_x + 0.35,
|
||||
by - 0.02,
|
||||
band_x + 0.35,
|
||||
by - 0.13,
|
||||
lw=1.0,
|
||||
)
|
||||
|
||||
# Extra fields note at bottom
|
||||
ax.text(
|
||||
@ -232,14 +276,14 @@ def generate_pattern_template() -> None:
|
||||
out = str(Path(OUTPUT_DIR) / "q14_pattern_template.png")
|
||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||
plt.close(fig)
|
||||
print(f" Saved: {out}")
|
||||
_logger.info(" Saved: %s", out)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 2. Catalog Classification Map
|
||||
# ============================================================
|
||||
def generate_catalog_map() -> None:
|
||||
"""Generate catalog map."""
|
||||
"""Generate catalog classification map diagram."""
|
||||
fig, ax = plt.subplots(figsize=(8.27, 7))
|
||||
ax.set_xlim(0, 12)
|
||||
ax.set_ylim(0, 9)
|
||||
@ -247,13 +291,15 @@ def generate_catalog_map() -> None:
|
||||
ax.axis("off")
|
||||
fig.patch.set_facecolor(BG)
|
||||
ax.set_title(
|
||||
"Mapa katalog\u00f3w wzorc\u00f3w \u2014 \u201ePawe\u0142 Gra\u0142 Efektownie Pod Chmurami\u201d",
|
||||
"Mapa katalog\u00f3w wzorc\u00f3w \u2014"
|
||||
" \u201ePawe\u0142 Gra\u0142 Efektownie"
|
||||
" Pod Chmurami\u201d",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
pad=15,
|
||||
)
|
||||
|
||||
# Y-axis: Scale (architectural → design → idiom)
|
||||
# Y-axis: Scale (architectural -> design -> idiom)
|
||||
ax.text(
|
||||
0.3,
|
||||
7.8,
|
||||
@ -286,7 +332,9 @@ def generate_catalog_map() -> None:
|
||||
va="center",
|
||||
fontstyle="italic",
|
||||
)
|
||||
ax.plot([0.15, 0.45], [sy, sy], color=GRAY3, lw=0.8, ls="--")
|
||||
ax.plot(
|
||||
[0.15, 0.45], [sy, sy], color=GRAY3, lw=0.8, ls="--"
|
||||
)
|
||||
|
||||
# X-axis: Domain
|
||||
ax.text(
|
||||
@ -305,16 +353,16 @@ def generate_catalog_map() -> None:
|
||||
arrowprops={"arrowstyle": "->", "lw": 1.5, "color": LN},
|
||||
)
|
||||
|
||||
# Catalog boxes positioned by scale x domain
|
||||
# Catalog boxes positioned by scale and domain
|
||||
catalogs = [
|
||||
# (x, y, w, h, name, subtitle, fill, mnemonic_letter)
|
||||
(
|
||||
2.5,
|
||||
6.2,
|
||||
2.5,
|
||||
1.4,
|
||||
"POSA",
|
||||
"1996 • Buschmann\nLayers, Broker,\nPipes & Filters, MVC",
|
||||
"1996 • Buschmann\nLayers, Broker,\n"
|
||||
"Pipes & Filters, MVC",
|
||||
GRAY1,
|
||||
"P",
|
||||
),
|
||||
@ -324,7 +372,8 @@ def generate_catalog_map() -> None:
|
||||
2.5,
|
||||
1.4,
|
||||
"GoF",
|
||||
"1994 • Gamma et al.\n23 wzorce:\n5 kreac. / 7 strukt. / 11 behaw.",
|
||||
"1994 • Gamma et al.\n23 wzorce:\n"
|
||||
"5 kreac. / 7 strukt. / 11 behaw.",
|
||||
GRAY2,
|
||||
"G",
|
||||
),
|
||||
@ -334,7 +383,8 @@ def generate_catalog_map() -> None:
|
||||
2.5,
|
||||
1.4,
|
||||
"EIP",
|
||||
"2003 • Hohpe & Woolf\nMessage Channel,\nRouter, Aggregator",
|
||||
"2003 • Hohpe & Woolf\nMessage Channel,\n"
|
||||
"Router, Aggregator",
|
||||
GRAY1,
|
||||
"E",
|
||||
),
|
||||
@ -344,7 +394,8 @@ def generate_catalog_map() -> None:
|
||||
2.5,
|
||||
1.4,
|
||||
"PoEAA",
|
||||
"2002 • M. Fowler\nRepository, Unit of Work,\nDomain Model",
|
||||
"2002 • M. Fowler\nRepository,"
|
||||
" Unit of Work,\nDomain Model",
|
||||
"white",
|
||||
"P",
|
||||
),
|
||||
@ -354,7 +405,8 @@ def generate_catalog_map() -> None:
|
||||
2.8,
|
||||
1.4,
|
||||
"Cloud\nPatterns",
|
||||
"~2015 • Azure/AWS\nCircuit Breaker,\nSaga, Sidecar",
|
||||
"~2015 • Azure/AWS\nCircuit Breaker,\n"
|
||||
"Saga, Sidecar",
|
||||
GRAY1,
|
||||
"C",
|
||||
),
|
||||
@ -392,7 +444,11 @@ def generate_catalog_map() -> None:
|
||||
|
||||
# Mnemonic letter in corner
|
||||
circle = plt.Circle(
|
||||
(cx + 0.25, cy + ch - 0.25), 0.2, lw=1, edgecolor=LN, facecolor=GRAY5
|
||||
(cx + 0.25, cy + ch - 0.25),
|
||||
0.2,
|
||||
lw=1,
|
||||
edgecolor=LN,
|
||||
facecolor=GRAY5,
|
||||
)
|
||||
ax.add_patch(circle)
|
||||
ax.text(
|
||||
@ -444,14 +500,14 @@ def generate_catalog_map() -> None:
|
||||
out = str(Path(OUTPUT_DIR) / "q14_catalog_map.png")
|
||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||
plt.close(fig)
|
||||
print(f" Saved: {out}")
|
||||
_logger.info(" Saved: %s", out)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 3. Three Pillars of Cataloguing
|
||||
# ============================================================
|
||||
def generate_three_pillars() -> None:
|
||||
"""Generate three pillars."""
|
||||
"""Generate three pillars of cataloguing diagram."""
|
||||
fig, ax = plt.subplots(figsize=(8.27, 5.5))
|
||||
ax.set_xlim(0, 12)
|
||||
ax.set_ylim(0, 7)
|
||||
@ -467,7 +523,13 @@ def generate_three_pillars() -> None:
|
||||
|
||||
# Roof / banner
|
||||
roof_pts = np.array([[1, 5.5], [6, 6.8], [11, 5.5]])
|
||||
roof = plt.Polygon(roof_pts, closed=True, lw=2, edgecolor=LN, facecolor=GRAY4)
|
||||
roof = plt.Polygon(
|
||||
roof_pts,
|
||||
closed=True,
|
||||
lw=2,
|
||||
edgecolor=LN,
|
||||
facecolor=GRAY4,
|
||||
)
|
||||
ax.add_patch(roof)
|
||||
ax.text(
|
||||
6,
|
||||
@ -484,20 +546,29 @@ def generate_three_pillars() -> None:
|
||||
(
|
||||
1.3,
|
||||
"1. SZABLON\nOPISU",
|
||||
"Każdy wzorzec ma\nte same pola:\nNazwa → Problem\n→ Siły → Rozwiązanie\n→ Konsekwencje",
|
||||
"Każdy wzorzec ma\nte same pola:\n"
|
||||
"Nazwa → Problem\n→ Siły → Rozwiązanie\n"
|
||||
"→ Konsekwencje",
|
||||
"Analogia:\nformatka\nencyklopedii",
|
||||
),
|
||||
(
|
||||
4.8,
|
||||
"2. KLASYFIKACJA\nWIELOOSIOWA",
|
||||
"Osie podziału:\n• Skala (arch/proj/idiom)\n• Domena problemu\n• Atrybut jakościowy\n• Domena zastosowania",
|
||||
"Osie podziału:\n"
|
||||
"• Skala (arch/proj/idiom)\n"
|
||||
"• Domena problemu\n"
|
||||
"• Atrybut jakościowy\n"
|
||||
"• Domena zastosowania",
|
||||
"Analogia:\nkategorie\nw bibliotece",
|
||||
),
|
||||
(
|
||||
8.3,
|
||||
"3. JĘZYK\nWZORCÓW",
|
||||
"Wzorce referują się\nwzajemnie tworząc\nsieć/graf:\nA → wymaga → B\nB → wariant → C",
|
||||
"Analogia:\n\u201ezobacz te\u017c\u201d\nw encyklopedii",
|
||||
"Wzorce referują się\nwzajemnie tworząc\n"
|
||||
"sieć/graf:\nA → wymaga → B\n"
|
||||
"B → wariant → C",
|
||||
"Analogia:\n\u201ezobacz te\u017c\u201d\n"
|
||||
"w encyklopedii",
|
||||
),
|
||||
]
|
||||
|
||||
@ -530,7 +601,10 @@ def generate_three_pillars() -> None:
|
||||
|
||||
# Horizontal line under title
|
||||
ax.plot(
|
||||
[px + 0.2, px + pw - 0.2], [py + ph - 1.0, py + ph - 1.0], color=LN, lw=0.8
|
||||
[px + 0.2, px + pw - 0.2],
|
||||
[py + ph - 1.0, py + ph - 1.0],
|
||||
color=LN,
|
||||
lw=0.8,
|
||||
)
|
||||
|
||||
# Description
|
||||
@ -570,14 +644,19 @@ def generate_three_pillars() -> None:
|
||||
out = str(Path(OUTPUT_DIR) / "q14_three_pillars.png")
|
||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||
plt.close(fig)
|
||||
print(f" Saved: {out}")
|
||||
_logger.info(" Saved: %s", out)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 4. Filled-in Observer Pattern Card
|
||||
# ============================================================
|
||||
def _get_observer_band_height(index: int) -> float:
|
||||
"""Return band height for the given field index."""
|
||||
return _BAND_HEIGHTS[index]
|
||||
|
||||
|
||||
def generate_observer_card_filled() -> None:
|
||||
"""Generate observer card filled."""
|
||||
"""Generate filled-in Observer pattern card diagram."""
|
||||
fig, ax = plt.subplots(figsize=(8.27, 8.5))
|
||||
ax.set_xlim(0, 10)
|
||||
ax.set_ylim(0, 10)
|
||||
@ -610,7 +689,8 @@ def generate_observer_card_filled() -> None:
|
||||
(
|
||||
"P",
|
||||
"PROBLEM",
|
||||
"Obiekt (Subject) zmienia stan → wielu zależnych\n"
|
||||
"Obiekt (Subject) zmienia stan → wielu"
|
||||
" zależnych\n"
|
||||
"obiektów musi zareagować, ale Subject nie\n"
|
||||
"powinien znać ich konkretnych typów.",
|
||||
GRAY1,
|
||||
@ -619,9 +699,12 @@ def generate_observer_card_filled() -> None:
|
||||
(
|
||||
"Si",
|
||||
"SIŁY",
|
||||
"• loose coupling (nie znać obserwatorów z nazwy)\n"
|
||||
" vs koszt powiadomień (N obserwatorów = N wywołań)\n"
|
||||
"• otwartość na rozszerzenia vs złożoność debugowania",
|
||||
"• loose coupling (nie znać obserwatorów"
|
||||
" z nazwy)\n"
|
||||
" vs koszt powiadomień"
|
||||
" (N obserwatorów = N wywołań)\n"
|
||||
"• otwartość na rozszerzenia"
|
||||
" vs złożoność debugowania",
|
||||
"white",
|
||||
False,
|
||||
),
|
||||
@ -651,21 +734,13 @@ def generate_observer_card_filled() -> None:
|
||||
band_w = card_w - 0.6
|
||||
start_y = card_y + card_h - 0.65
|
||||
|
||||
for i, (abbr, title, content, fill, is_title_field) in enumerate(fields):
|
||||
if is_title_field:
|
||||
band_h = 0.7
|
||||
elif i == 1:
|
||||
band_h = 1.3
|
||||
elif i == 2:
|
||||
band_h = 1.4
|
||||
elif i == 3:
|
||||
band_h = 1.5
|
||||
else:
|
||||
band_h = 1.5
|
||||
for i, (abbr, title, content, fill, is_title_field) in enumerate(
|
||||
fields
|
||||
):
|
||||
band_h = _get_observer_band_height(i)
|
||||
|
||||
by = start_y - sum(
|
||||
(0.7 if j == 0 else 1.3 if j == 1 else 1.4 if j == 2 else 1.5) + 0.15
|
||||
for j in range(i)
|
||||
_get_observer_band_height(j) + 0.15 for j in range(i)
|
||||
)
|
||||
|
||||
# Abbreviation circle
|
||||
@ -734,13 +809,24 @@ def generate_observer_card_filled() -> None:
|
||||
|
||||
# Arrow
|
||||
if i < len(fields) - 1:
|
||||
draw_arrow(ax, band_x + 0.35, by - 0.02, band_x + 0.35, by - 0.13, lw=1.0)
|
||||
draw_arrow(
|
||||
ax,
|
||||
band_x + 0.35,
|
||||
by - 0.02,
|
||||
band_x + 0.35,
|
||||
by - 0.13,
|
||||
lw=1.0,
|
||||
)
|
||||
|
||||
# Extra info at bottom
|
||||
extra_y = 0.55
|
||||
extras = [
|
||||
"Powiązane: Mediator (centralizuje), Pub/Sub (rozproszony), MVC (View = Observer)",
|
||||
"Znane użycia: Java Swing listeners, C# event/delegate, React useState, DOM addEventListener",
|
||||
"Powiązane: Mediator (centralizuje),"
|
||||
" Pub/Sub (rozproszony),"
|
||||
" MVC (View = Observer)",
|
||||
"Znane użycia: Java Swing listeners,"
|
||||
" C# event/delegate,"
|
||||
" React useState, DOM addEventListener",
|
||||
]
|
||||
for j, txt in enumerate(extras):
|
||||
ax.text(
|
||||
@ -758,14 +844,14 @@ def generate_observer_card_filled() -> None:
|
||||
out = str(Path(OUTPUT_DIR) / "q14_observer_card_filled.png")
|
||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||
plt.close(fig)
|
||||
print(f" Saved: {out}")
|
||||
_logger.info(" Saved: %s", out)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 5. Pattern Language Navigation Graph
|
||||
# ============================================================
|
||||
def generate_pattern_language_navigation() -> None:
|
||||
"""Generate pattern language navigation."""
|
||||
"""Generate pattern language navigation graph diagram."""
|
||||
fig, ax = plt.subplots(figsize=(8.27, 9))
|
||||
ax.set_xlim(0, 12)
|
||||
ax.set_ylim(0, 12)
|
||||
@ -773,22 +859,37 @@ def generate_pattern_language_navigation() -> None:
|
||||
ax.axis("off")
|
||||
fig.patch.set_facecolor(BG)
|
||||
ax.set_title(
|
||||
'Język wzorców — nawigacja „problem → wzorzec → nowy problem"',
|
||||
"Język wzorców \u2014 nawigacja"
|
||||
" \u201eproblem \u2192 wzorzec"
|
||||
" \u2192 nowy problem\u201d",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
pad=15,
|
||||
)
|
||||
|
||||
# Node positions: (x, y, label, is_pattern, fill)
|
||||
# Left column: problems; Right column: patterns
|
||||
nodes = [
|
||||
# Problems (left, rounded rect, white)
|
||||
(1.5, 10.5, "Monolith\nnie skaluje się", False, "white"),
|
||||
(1.5, 8.2, "Jak routować\nżądania do\nserwisów?", False, "white"),
|
||||
(1.5, 5.9, "Co gdy serwis\nnie odpowiada?", False, "white"),
|
||||
(1.5, 3.6, "Jak zachować\nspójność\ntransakcji?", False, "white"),
|
||||
(1.5, 1.3, "Jak odnaleźć\nadres serwisu?", False, "white"),
|
||||
# Patterns (right, filled rect, gray)
|
||||
(
|
||||
1.5, 8.2,
|
||||
"Jak routować\nżądania do\nserwisów?",
|
||||
False, "white",
|
||||
),
|
||||
(
|
||||
1.5, 5.9,
|
||||
"Co gdy serwis\nnie odpowiada?",
|
||||
False, "white",
|
||||
),
|
||||
(
|
||||
1.5, 3.6,
|
||||
"Jak zachować\nspójność\ntransakcji?",
|
||||
False, "white",
|
||||
),
|
||||
(
|
||||
1.5, 1.3,
|
||||
"Jak odnaleźć\nadres serwisu?",
|
||||
False, "white",
|
||||
),
|
||||
(7.0, 9.3, "Microservices", True, GRAY2),
|
||||
(7.0, 7.0, "API Gateway", True, GRAY2),
|
||||
(7.0, 4.7, "Circuit Breaker", True, GRAY2),
|
||||
@ -816,7 +917,13 @@ def generate_pattern_language_navigation() -> None:
|
||||
)
|
||||
ax.add_patch(rect)
|
||||
ax.text(
|
||||
nx, ny, label, ha="center", va="center", fontsize=10, fontweight="bold"
|
||||
nx,
|
||||
ny,
|
||||
label,
|
||||
ha="center",
|
||||
va="center",
|
||||
fontsize=10,
|
||||
fontweight="bold",
|
||||
)
|
||||
else:
|
||||
w, h = node_w_prob, node_h_prob
|
||||
@ -841,9 +948,8 @@ def generate_pattern_language_navigation() -> None:
|
||||
fontstyle="italic",
|
||||
)
|
||||
|
||||
# Arrows: problem → pattern (solid), pattern → problem (dashed label)
|
||||
# Arrows: problem -> pattern, pattern -> problem
|
||||
arrows = [
|
||||
# (x1, y1, x2, y2, label, style)
|
||||
(2.9, 10.5, 5.75, 9.5, "rozwiązuje →", "->", 1.5),
|
||||
(7.0, 8.8, 2.9, 8.5, "← rodzi problem", "->", 1.0),
|
||||
(2.9, 8.0, 5.75, 7.2, "rozwiązuje →", "->", 1.5),
|
||||
@ -851,9 +957,7 @@ def generate_pattern_language_navigation() -> None:
|
||||
(2.9, 5.7, 5.75, 5.0, "rozwiązuje →", "->", 1.5),
|
||||
(7.0, 4.2, 2.9, 3.9, "← rodzi problem", "->", 1.0),
|
||||
(2.9, 3.3, 5.75, 2.6, "rozwiązuje →", "->", 1.5),
|
||||
# Microservices → Service Discovery
|
||||
(8.25, 9.0, 9.5, 6.5, "wymaga →", "->", 1.0),
|
||||
# Problem → Service Discovery
|
||||
(2.9, 1.3, 8.75, 5.6, "rozwiązuje →", "->", 1.2),
|
||||
]
|
||||
|
||||
@ -889,7 +993,6 @@ def generate_pattern_language_navigation() -> None:
|
||||
|
||||
# Legend
|
||||
legend_y = 0.3
|
||||
# Problem node
|
||||
r1 = FancyBboxPatch(
|
||||
(1.0, legend_y - 0.2),
|
||||
1.5,
|
||||
@ -901,8 +1004,10 @@ def generate_pattern_language_navigation() -> None:
|
||||
linestyle="--",
|
||||
)
|
||||
ax.add_patch(r1)
|
||||
ax.text(1.75, legend_y, "Problem", ha="center", va="center", fontsize=7)
|
||||
# Pattern node
|
||||
ax.text(
|
||||
1.75, legend_y, "Problem",
|
||||
ha="center", va="center", fontsize=7,
|
||||
)
|
||||
r2 = FancyBboxPatch(
|
||||
(3.5, legend_y - 0.2),
|
||||
1.5,
|
||||
@ -925,7 +1030,8 @@ def generate_pattern_language_navigation() -> None:
|
||||
ax.text(
|
||||
6.5,
|
||||
legend_y,
|
||||
"Nawigacja: Problem → Wzorzec → Nowy Problem → Wzorzec → ...",
|
||||
"Nawigacja: Problem \u2192 Wzorzec"
|
||||
" \u2192 Nowy Problem \u2192 Wzorzec \u2192 ...",
|
||||
ha="left",
|
||||
va="center",
|
||||
fontsize=7,
|
||||
@ -933,20 +1039,23 @@ def generate_pattern_language_navigation() -> None:
|
||||
)
|
||||
|
||||
fig.tight_layout()
|
||||
out = str(Path(OUTPUT_DIR) / "q14_pattern_language_navigation.png")
|
||||
out = str(
|
||||
Path(OUTPUT_DIR) / "q14_pattern_language_navigation.png"
|
||||
)
|
||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||
plt.close(fig)
|
||||
print(f" Saved: {out}")
|
||||
_logger.info(" Saved: %s", out)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
if __name__ == "__main__":
|
||||
print("Generating PYTANIE 14 diagrams...")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
_logger.info("Generating PYTANIE 14 diagrams...")
|
||||
generate_pattern_template()
|
||||
generate_catalog_map()
|
||||
generate_three_pillars()
|
||||
generate_observer_card_filled()
|
||||
generate_pattern_language_navigation()
|
||||
print("Done!")
|
||||
_logger.info("Done!")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,11 @@ Diagrams:
|
||||
5. ROS architecture (pub/sub nodes)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.use("Agg")
|
||||
@ -21,6 +26,11 @@ from matplotlib.patches import FancyBboxPatch
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DPI = 300
|
||||
BG = "white"
|
||||
LN = "black"
|
||||
@ -38,21 +48,38 @@ WHITE = "white"
|
||||
|
||||
|
||||
def draw_box(
|
||||
ax,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
text,
|
||||
fill="white",
|
||||
lw=1.2,
|
||||
fontsize=FS,
|
||||
fontweight="normal",
|
||||
ha="center",
|
||||
va="center",
|
||||
rounded=True,
|
||||
ax: Axes,
|
||||
x: float,
|
||||
y: float,
|
||||
w: float,
|
||||
h: float,
|
||||
text: str,
|
||||
*,
|
||||
fill: str = "white",
|
||||
lw: float = 1.2,
|
||||
fontsize: float = FS,
|
||||
fontweight: str = "normal",
|
||||
ha: str = "center",
|
||||
va: str = "center",
|
||||
rounded: bool = True,
|
||||
) -> None:
|
||||
"""Draw box."""
|
||||
"""Draw a labeled box on the axes.
|
||||
|
||||
Args:
|
||||
ax: Matplotlib axes to draw on.
|
||||
x: Left edge x-coordinate.
|
||||
y: Bottom edge y-coordinate.
|
||||
w: Box width.
|
||||
h: Box height.
|
||||
text: Label text inside the box.
|
||||
fill: Fill color.
|
||||
lw: Line width.
|
||||
fontsize: Font size for label.
|
||||
fontweight: Font weight for label.
|
||||
ha: Horizontal alignment.
|
||||
va: Vertical alignment.
|
||||
rounded: Whether to use rounded corners.
|
||||
"""
|
||||
if rounded:
|
||||
rect = FancyBboxPatch(
|
||||
(x, y), w, h, boxstyle="round,pad=0.05", lw=lw, edgecolor=LN, facecolor=fill
|
||||
@ -72,8 +99,29 @@ def draw_box(
|
||||
)
|
||||
|
||||
|
||||
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style="->", color=LN) -> None:
|
||||
"""Draw arrow."""
|
||||
def draw_arrow(
|
||||
ax: Axes,
|
||||
x1: float,
|
||||
y1: float,
|
||||
x2: float,
|
||||
y2: float,
|
||||
*,
|
||||
lw: float = 1.2,
|
||||
style: str = "->",
|
||||
color: str = LN,
|
||||
) -> None:
|
||||
"""Draw an arrow annotation.
|
||||
|
||||
Args:
|
||||
ax: Matplotlib axes to draw on.
|
||||
x1: Start x-coordinate.
|
||||
y1: Start y-coordinate.
|
||||
x2: End x-coordinate.
|
||||
y2: End y-coordinate.
|
||||
lw: Line width.
|
||||
style: Arrow style.
|
||||
color: Arrow color.
|
||||
"""
|
||||
ax.annotate(
|
||||
"",
|
||||
xy=(x2, y2),
|
||||
@ -101,7 +149,7 @@ def draw_trms_pyramid() -> None:
|
||||
|
||||
# Pyramid layers (bottom to top)
|
||||
layers = [
|
||||
# (y, left_x, right_x, label, sublabel, fill, examples, timing)
|
||||
# Fields: y left_x right_x label sublabel fill examples timing
|
||||
(
|
||||
0.5,
|
||||
1.0,
|
||||
@ -261,7 +309,7 @@ def draw_trms_pyramid() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close(fig)
|
||||
print(" ✓ robot_trms_pyramid.png")
|
||||
_logger.info("Generated robot_trms_pyramid.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -411,31 +459,24 @@ def draw_vendor_comparison() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close(fig)
|
||||
print(" ✓ robot_vendor_comparison.png")
|
||||
_logger.info("Generated robot_vendor_comparison.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 3. Robot Movement Types (PTP, LIN, CIRC)
|
||||
# ============================================================
|
||||
def draw_movement_types() -> None:
|
||||
"""Draw movement types."""
|
||||
fig, axes = plt.subplots(1, 3, figsize=(8.27, 3.2))
|
||||
fig.suptitle(
|
||||
"Typy ruchu robota: PTP, LIN, CIRC",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
y=0.98,
|
||||
)
|
||||
|
||||
# --- PTP (Point-to-Point) ---
|
||||
ax = axes[0]
|
||||
def _draw_ptp_subplot(ax: Axes) -> None:
|
||||
"""Draw the PTP (Point-to-Point) subplot."""
|
||||
ax.set_xlim(-0.5, 4.5)
|
||||
ax.set_ylim(-0.5, 4.5)
|
||||
ax.set_aspect("equal")
|
||||
ax.set_title("PTP (Point-to-Point)\nMoveJ / PTP", fontsize=8, fontweight="bold")
|
||||
ax.grid(True, alpha=0.3)
|
||||
ax.set_title(
|
||||
"PTP (Point-to-Point)\nMoveJ / PTP",
|
||||
fontsize=8,
|
||||
fontweight="bold",
|
||||
)
|
||||
ax.grid(visible=True, alpha=0.3)
|
||||
|
||||
# Start and end
|
||||
start = (0.5, 0.5)
|
||||
end = (3.5, 3.5)
|
||||
ax.plot(*start, "ko", ms=10, zorder=5)
|
||||
@ -476,13 +517,18 @@ def draw_movement_types() -> None:
|
||||
ax.set_ylabel("")
|
||||
ax.tick_params(labelsize=6)
|
||||
|
||||
# --- LIN (Linear) ---
|
||||
ax = axes[1]
|
||||
|
||||
def _draw_lin_subplot(ax: Axes) -> None:
|
||||
"""Draw the LIN (Linear) subplot."""
|
||||
ax.set_xlim(-0.5, 4.5)
|
||||
ax.set_ylim(-0.5, 4.5)
|
||||
ax.set_aspect("equal")
|
||||
ax.set_title("LIN (Linear)\nMoveL / LIN", fontsize=8, fontweight="bold")
|
||||
ax.grid(True, alpha=0.3)
|
||||
ax.set_title(
|
||||
"LIN (Linear)\nMoveL / LIN",
|
||||
fontsize=8,
|
||||
fontweight="bold",
|
||||
)
|
||||
ax.grid(visible=True, alpha=0.3)
|
||||
|
||||
start = (0.5, 1.0)
|
||||
end = (3.5, 3.5)
|
||||
@ -519,22 +565,27 @@ def draw_movement_types() -> None:
|
||||
)
|
||||
ax.tick_params(labelsize=6)
|
||||
|
||||
# --- CIRC (Circular) ---
|
||||
ax = axes[2]
|
||||
|
||||
def _draw_circ_subplot(ax: Axes) -> None:
|
||||
"""Draw the CIRC (Circular) subplot."""
|
||||
ax.set_xlim(-0.5, 4.5)
|
||||
ax.set_ylim(-0.5, 4.5)
|
||||
ax.set_aspect("equal")
|
||||
ax.set_title("CIRC (Circular)\nMoveC / CIRC", fontsize=8, fontweight="bold")
|
||||
ax.grid(True, alpha=0.3)
|
||||
ax.set_title(
|
||||
"CIRC (Circular)\nMoveC / CIRC",
|
||||
fontsize=8,
|
||||
fontweight="bold",
|
||||
)
|
||||
ax.grid(visible=True, alpha=0.3)
|
||||
|
||||
# Arc through 3 points
|
||||
center = (2.0, 1.5)
|
||||
r = 2.0
|
||||
radius = 2.0
|
||||
theta_start = np.radians(20)
|
||||
theta_end = np.radians(160)
|
||||
theta = np.linspace(theta_start, theta_end, 50)
|
||||
x_circ = center[0] + r * np.cos(theta)
|
||||
y_circ = center[1] + r * np.sin(theta)
|
||||
x_circ = center[0] + radius * np.cos(theta)
|
||||
y_circ = center[1] + radius * np.sin(theta)
|
||||
|
||||
ax.plot(x_circ, y_circ, "k-", lw=2)
|
||||
ax.annotate(
|
||||
@ -550,7 +601,11 @@ def draw_movement_types() -> None:
|
||||
ax.plot(x_circ[-1], y_circ[-1], "ks", ms=10, zorder=5)
|
||||
ax.text(x_circ[0] + 0.3, y_circ[0] - 0.3, "Start", fontsize=7)
|
||||
ax.text(
|
||||
x_circ[24] + 0.05, y_circ[24] + 0.25, "Pkt\npomocniczy", fontsize=6, ha="center"
|
||||
x_circ[24] + 0.05,
|
||||
y_circ[24] + 0.25,
|
||||
"Pkt\npomocniczy",
|
||||
fontsize=6,
|
||||
ha="center",
|
||||
)
|
||||
ax.text(x_circ[-1] - 0.5, y_circ[-1] - 0.3, "Cel", fontsize=7)
|
||||
|
||||
@ -568,6 +623,21 @@ def draw_movement_types() -> None:
|
||||
)
|
||||
ax.tick_params(labelsize=6)
|
||||
|
||||
|
||||
def draw_movement_types() -> None:
|
||||
"""Draw movement types."""
|
||||
fig, axes = plt.subplots(1, 3, figsize=(8.27, 3.2))
|
||||
fig.suptitle(
|
||||
"Typy ruchu robota: PTP, LIN, CIRC",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
y=0.98,
|
||||
)
|
||||
|
||||
_draw_ptp_subplot(axes[0])
|
||||
_draw_lin_subplot(axes[1])
|
||||
_draw_circ_subplot(axes[2])
|
||||
|
||||
fig.tight_layout()
|
||||
fig.savefig(
|
||||
str(Path(OUTPUT_DIR) / "robot_movement_types.png"),
|
||||
@ -576,7 +646,7 @@ def draw_movement_types() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close(fig)
|
||||
print(" ✓ robot_movement_types.png")
|
||||
_logger.info("Generated robot_movement_types.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -664,7 +734,9 @@ def draw_online_offline() -> None:
|
||||
ax.text(
|
||||
7.4,
|
||||
0.6,
|
||||
"✓ Bez zatrzymania produkcji\n✓ Wysoka precyzja, symulacja\n✗ Wymaga kalibracji",
|
||||
"✓ Bez zatrzymania produkcji\n"
|
||||
"✓ Wysoka precyzja, symulacja\n"
|
||||
"✗ Wymaga kalibracji",
|
||||
ha="center",
|
||||
va="center",
|
||||
fontsize=6.5,
|
||||
@ -679,7 +751,7 @@ def draw_online_offline() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close(fig)
|
||||
print(" ✓ robot_online_offline.png")
|
||||
_logger.info("Generated robot_online_offline.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -714,7 +786,7 @@ def draw_ros_architecture() -> None:
|
||||
|
||||
# Topics (arrows with labels)
|
||||
topics = [
|
||||
# (from_x, from_y, to_x, to_y, label)
|
||||
# Fields: from_x from_y to_x to_y label
|
||||
(3.2, 5.0, 4.0, 5.0, "/scan"),
|
||||
(3.2, 3.0, 4.0, 3.0, "/image"),
|
||||
(6.2, 5.0, 7.0, 4.3, "/pose"),
|
||||
@ -780,7 +852,7 @@ def draw_ros_architecture() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close(fig)
|
||||
print(" ✓ robot_ros_architecture.png")
|
||||
_logger.info("Generated robot_ros_architecture.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
@ -896,7 +968,9 @@ def draw_rapid_structure() -> None:
|
||||
),
|
||||
(
|
||||
4.5,
|
||||
"v500 = prędkość 500 mm/s\nz10 = strefa zbliżenia 10mm\nfine = dokładne dojście",
|
||||
"v500 = prędkość 500 mm/s\n"
|
||||
"z10 = strefa zbliżenia 10mm\n"
|
||||
"fine = dokładne dojście",
|
||||
),
|
||||
(2.5, "SetDO = Digital Output\nSterowanie I/O\n(chwytak, zawory)"),
|
||||
]
|
||||
@ -925,18 +999,19 @@ def draw_rapid_structure() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close(fig)
|
||||
print(" ✓ robot_rapid_example.png")
|
||||
_logger.info("Generated robot_rapid_example.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
if __name__ == "__main__":
|
||||
print("Generating PYTANIE 16 diagrams...")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
_logger.info("Generating PYTANIE 16 diagrams...")
|
||||
draw_trms_pyramid()
|
||||
draw_vendor_comparison()
|
||||
draw_movement_types()
|
||||
draw_online_offline()
|
||||
draw_ros_architecture()
|
||||
draw_rapid_structure()
|
||||
print("Done! All diagrams saved to", OUTPUT_DIR)
|
||||
_logger.info("Done! All diagrams saved to %s", OUTPUT_DIR)
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
#!/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.
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl.use("Agg")
|
||||
@ -17,47 +22,65 @@ from pathlib import Path
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DPI = 300
|
||||
BG = "white"
|
||||
LN = "black"
|
||||
FS = 8
|
||||
FS_TITLE = 11
|
||||
FS_SMALL = 6.5
|
||||
FS_EDGE = 9
|
||||
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
|
||||
Path(OUTPUT_DIR).mkdir(parents=True, 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)]
|
||||
NODE_POS: dict[str, tuple[float, float]] = {
|
||||
"A": (1, 2),
|
||||
"B": (3.5, 3.2),
|
||||
"C": (1, 0),
|
||||
"D": (3.5, 0.8),
|
||||
}
|
||||
EDGES: list[tuple[str, str, int]] = [
|
||||
("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,
|
||||
ax: Axes,
|
||||
name: str,
|
||||
pos: tuple[float, float],
|
||||
*,
|
||||
color: str = "white",
|
||||
current: bool = False,
|
||||
visited: bool = False,
|
||||
dist_label: str | None = None,
|
||||
fontsize: int = 12,
|
||||
) -> None:
|
||||
"""Draw a graph node (circle with label)."""
|
||||
"""Draw a graph node (circle with label).
|
||||
|
||||
Args:
|
||||
ax: Matplotlib axes to draw on.
|
||||
name: Node label text.
|
||||
pos: (x, y) position of the node center.
|
||||
color: Fill color when not current/visited.
|
||||
current: Whether this node is being processed.
|
||||
visited: Whether this node has been visited.
|
||||
dist_label: Optional distance label below node.
|
||||
fontsize: Font size for the node label.
|
||||
"""
|
||||
x, y = pos
|
||||
r = 0.35
|
||||
radius = 0.35
|
||||
lw = 2.5 if current else 1.5
|
||||
ec = "#D32F2F" if current else LN
|
||||
fc = LIGHT_GREEN if visited else color
|
||||
@ -65,7 +88,13 @@ def draw_graph_node(
|
||||
fc = LIGHT_YELLOW
|
||||
|
||||
circle = plt.Circle(
|
||||
(x, y), r, fill=True, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=5
|
||||
(x, y),
|
||||
radius,
|
||||
fill=True,
|
||||
facecolor=fc,
|
||||
edgecolor=ec,
|
||||
linewidth=lw,
|
||||
zorder=5,
|
||||
)
|
||||
ax.add_patch(circle)
|
||||
ax.text(
|
||||
@ -97,25 +126,52 @@ def draw_graph_node(
|
||||
)
|
||||
|
||||
|
||||
def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) -> None:
|
||||
"""Draw an edge between two nodes with weight label."""
|
||||
def draw_graph_edge(
|
||||
ax: Axes,
|
||||
pos1: tuple[float, float],
|
||||
pos2: tuple[float, float],
|
||||
weight: int,
|
||||
*,
|
||||
highlighted: bool = False,
|
||||
relaxed: bool = False,
|
||||
) -> None:
|
||||
"""Draw an edge between two nodes with weight label.
|
||||
|
||||
Args:
|
||||
ax: Matplotlib axes to draw on.
|
||||
pos1: Start node position.
|
||||
pos2: End node position.
|
||||
weight: Edge weight value.
|
||||
highlighted: Whether edge is highlighted.
|
||||
relaxed: Whether edge was just relaxed.
|
||||
"""
|
||||
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
|
||||
node_radius = 0.38
|
||||
sx = x1 + node_radius * dx / length
|
||||
sy = y1 + node_radius * dy / length
|
||||
ex = x2 - node_radius * dx / length
|
||||
ey = y2 - node_radius * dy / length
|
||||
|
||||
color = "#D32F2F" if relaxed else ("#1565C0" if highlighted else GRAY3)
|
||||
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)
|
||||
ax.plot(
|
||||
[sx, ex],
|
||||
[sy, ey],
|
||||
color=color,
|
||||
linewidth=lw,
|
||||
linestyle="-",
|
||||
zorder=2,
|
||||
)
|
||||
|
||||
# Weight label
|
||||
mx = (x1 + x2) / 2
|
||||
@ -135,7 +191,9 @@ def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) ->
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.15",
|
||||
"facecolor": "white",
|
||||
"edgecolor": GRAY3 if not highlighted else color,
|
||||
"edgecolor": (
|
||||
GRAY3 if not highlighted else color
|
||||
),
|
||||
"alpha": 0.95,
|
||||
},
|
||||
zorder=4,
|
||||
@ -143,15 +201,26 @@ def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) ->
|
||||
|
||||
|
||||
def draw_full_graph(
|
||||
ax,
|
||||
title="",
|
||||
dist=None,
|
||||
current=None,
|
||||
visited=None,
|
||||
highlighted_edges=None,
|
||||
relaxed_edges=None,
|
||||
ax: Axes,
|
||||
*,
|
||||
title: str = "",
|
||||
dist: dict[str, str] | None = None,
|
||||
current: str | None = None,
|
||||
visited: set[str] | None = None,
|
||||
highlighted_edges: set[tuple[str, str]] | None = None,
|
||||
relaxed_edges: set[tuple[str, str]] | None = None,
|
||||
) -> None:
|
||||
"""Draw the complete graph with optional highlighting."""
|
||||
"""Draw the complete graph with optional highlighting.
|
||||
|
||||
Args:
|
||||
ax: Matplotlib axes to draw on.
|
||||
title: Subplot title.
|
||||
dist: Distance labels per node.
|
||||
current: Currently processed node name.
|
||||
visited: Set of visited node names.
|
||||
highlighted_edges: Edges to highlight.
|
||||
relaxed_edges: Edges that were just relaxed.
|
||||
"""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
if highlighted_edges is None:
|
||||
@ -170,17 +239,35 @@ def draw_full_graph(
|
||||
|
||||
# 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)
|
||||
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)
|
||||
for node_name, pos in NODE_POS.items():
|
||||
is_current = node_name == current
|
||||
is_visited = node_name in visited
|
||||
d_label = dist.get(node_name)
|
||||
draw_graph_node(
|
||||
ax, name, pos, current=is_current, visited=is_visited, dist_label=d_label
|
||||
ax,
|
||||
node_name,
|
||||
pos,
|
||||
current=is_current,
|
||||
visited=is_visited,
|
||||
dist_label=d_label,
|
||||
)
|
||||
|
||||
|
||||
@ -188,7 +275,7 @@ def draw_full_graph(
|
||||
# 1. Graph structure diagram
|
||||
# ============================================================
|
||||
def draw_graph_structure() -> None:
|
||||
"""The shared example graph used across all three algorithms."""
|
||||
"""Draw the shared example graph used across all algorithms."""
|
||||
_fig, ax = plt.subplots(1, 1, figsize=(5, 4))
|
||||
ax.set_xlim(-0.5, 5.0)
|
||||
ax.set_ylim(-1.2, 4.5)
|
||||
@ -207,8 +294,8 @@ def draw_graph_structure() -> None:
|
||||
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)
|
||||
for node_name, pos in NODE_POS.items():
|
||||
draw_graph_node(ax, node_name, pos)
|
||||
|
||||
# Start arrow
|
||||
ax.annotate(
|
||||
@ -241,14 +328,14 @@ def draw_graph_structure() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close()
|
||||
print(" ✓ graph_example_structure.png")
|
||||
_logger.info("graph_example_structure.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 2. Dijkstra traversal
|
||||
# ============================================================
|
||||
def draw_dijkstra_traversal() -> None:
|
||||
"""Step-by-step Dijkstra on the shared graph."""
|
||||
"""Draw step-by-step Dijkstra on the shared graph."""
|
||||
steps = [
|
||||
{
|
||||
"title": "Krok 0: Inicjalizacja\nd = {A:0, B:∞, C:∞, D:∞}",
|
||||
@ -259,7 +346,11 @@ def draw_dijkstra_traversal() -> None:
|
||||
"relaxed": set(),
|
||||
},
|
||||
{
|
||||
"title": "Krok 1: Przetwarzam A (d=0)\nRelaksacja: A→B: 0+2=2<∞ ✓ A→C: 0+4=4<∞ ✓",
|
||||
"title": (
|
||||
"Krok 1: Przetwarzam A (d=0)\n"
|
||||
"Relaksacja: A→B: 0+2=2<∞ ✓"
|
||||
" A→C: 0+4=4<∞ ✓"
|
||||
),
|
||||
"dist": {"A": "0", "B": "2", "C": "4", "D": "∞"},
|
||||
"current": "A",
|
||||
"visited": {"A"},
|
||||
@ -267,7 +358,11 @@ def draw_dijkstra_traversal() -> None:
|
||||
"relaxed": {("A", "B"), ("A", "C")},
|
||||
},
|
||||
{
|
||||
"title": "Krok 2: Przetwarzam B (d=2) — minimum\nRelaksacja: B→D: 2+3=5<∞ ✓",
|
||||
"title": (
|
||||
"Krok 2: Przetwarzam B (d=2)"
|
||||
" — minimum\n"
|
||||
"Relaksacja: B→D: 2+3=5<∞ ✓"
|
||||
),
|
||||
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
|
||||
"current": "B",
|
||||
"visited": {"A", "B"},
|
||||
@ -275,7 +370,11 @@ def draw_dijkstra_traversal() -> None:
|
||||
"relaxed": {("B", "D")},
|
||||
},
|
||||
{
|
||||
"title": "Krok 3: Przetwarzam C (d=4)\nRelaksacja: C→D: 4+5=9 > 5 ✗ (nie poprawia)",
|
||||
"title": (
|
||||
"Krok 3: Przetwarzam C (d=4)\n"
|
||||
"Relaksacja: C→D: 4+5=9 > 5"
|
||||
" ✗ (nie poprawia)"
|
||||
),
|
||||
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
|
||||
"current": "C",
|
||||
"visited": {"A", "B", "C"},
|
||||
@ -283,7 +382,11 @@ def draw_dijkstra_traversal() -> None:
|
||||
"relaxed": set(),
|
||||
},
|
||||
{
|
||||
"title": "Krok 4: WYNIK — wszystkie przetworzone\nd = {A:0, B:2, C:4, D:5}",
|
||||
"title": (
|
||||
"Krok 4: WYNIK"
|
||||
" — wszystkie przetworzone\n"
|
||||
"d = {A:0, B:2, C:4, D:5}"
|
||||
),
|
||||
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
|
||||
"current": None,
|
||||
"visited": {"A", "B", "C", "D"},
|
||||
@ -294,7 +397,8 @@ def draw_dijkstra_traversal() -> None:
|
||||
|
||||
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)",
|
||||
"Dijkstra — przejście grafu krok po kroku"
|
||||
" (zachłannie: zawsze bierz min d)",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
y=1.02,
|
||||
@ -315,11 +419,17 @@ def draw_dijkstra_traversal() -> None:
|
||||
fig.text(
|
||||
0.5,
|
||||
-0.04,
|
||||
"[zolty] = aktualnie przetwarzany [zielony] = odwiedzony (zamkniety) "
|
||||
"czerwona krawedz = relaksacja OK szara krawedz = nie poprawia",
|
||||
"[zolty] = aktualnie przetwarzany"
|
||||
" [zielony] = odwiedzony (zamkniety)"
|
||||
" czerwona krawedz = relaksacja OK"
|
||||
" szara krawedz = nie poprawia",
|
||||
ha="center",
|
||||
fontsize=FS,
|
||||
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.3",
|
||||
"facecolor": GRAY4,
|
||||
"edgecolor": GRAY3,
|
||||
},
|
||||
)
|
||||
|
||||
plt.tight_layout()
|
||||
@ -330,18 +440,19 @@ def draw_dijkstra_traversal() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close()
|
||||
print(" ✓ dijkstra_traversal.png")
|
||||
_logger.info("dijkstra_traversal.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 3. Bellman-Ford traversal
|
||||
# ============================================================
|
||||
def draw_bellman_ford_traversal() -> None:
|
||||
"""Step-by-step Bellman-Ford on the shared graph."""
|
||||
"""Draw step-by-step Bellman-Ford on the shared graph."""
|
||||
fig = plt.figure(figsize=(14, 7))
|
||||
fig.suptitle(
|
||||
"Bellman-Ford — przejście grafu krok po kroku\n"
|
||||
"(V-1 = 3 iteracje, w każdej relaksuj WSZYSTKIE krawędzie)",
|
||||
"(V-1 = 3 iteracje, w każdej relaksuj"
|
||||
" WSZYSTKIE krawędzie)",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
y=0.98,
|
||||
@ -408,14 +519,23 @@ def draw_bellman_ford_traversal() -> None:
|
||||
)
|
||||
|
||||
# Negative cycle check note
|
||||
neg_cycle_msg = (
|
||||
"Po 3 iteracjach: sprawdz raz jeszcze"
|
||||
" — nic sie nie zmienia"
|
||||
" → BRAK cyklu ujemnego → wynik poprawny"
|
||||
)
|
||||
fig.text(
|
||||
0.5,
|
||||
0.01,
|
||||
"Po 3 iteracjach: sprawdź raz jeszcze — nic się nie zmienia → BRAK cyklu ujemnego → wynik poprawny",
|
||||
neg_cycle_msg,
|
||||
ha="center",
|
||||
fontsize=FS,
|
||||
fontweight="bold",
|
||||
bbox={"boxstyle": "round,pad=0.3", "facecolor": LIGHT_GREEN, "edgecolor": LN},
|
||||
bbox={
|
||||
"boxstyle": "round,pad=0.3",
|
||||
"facecolor": LIGHT_GREEN,
|
||||
"edgecolor": LN,
|
||||
},
|
||||
)
|
||||
|
||||
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
|
||||
@ -426,21 +546,22 @@ def draw_bellman_ford_traversal() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close()
|
||||
print(" ✓ bellman_ford_traversal.png")
|
||||
_logger.info("bellman_ford_traversal.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 4. A* traversal
|
||||
# ============================================================
|
||||
def draw_astar_traversal() -> None:
|
||||
"""Step-by-step A* on the shared graph with heuristics."""
|
||||
"""Draw 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",
|
||||
"f(n) = g(n) + h(n), heurystyka h"
|
||||
" = oszacowana odległość do D",
|
||||
fontsize=FS_TITLE,
|
||||
fontweight="bold",
|
||||
y=0.99,
|
||||
@ -520,11 +641,11 @@ def draw_astar_traversal() -> None:
|
||||
)
|
||||
|
||||
# Add h values as small labels
|
||||
for name, pos in NODE_POS.items():
|
||||
for node_name, pos in NODE_POS.items():
|
||||
ax_g.text(
|
||||
pos[0] + 0.35,
|
||||
pos[1] + 0.35,
|
||||
f"h={h_vals[name]}",
|
||||
f"h={h_vals[node_name]}",
|
||||
ha="center",
|
||||
va="center",
|
||||
fontsize=5.5,
|
||||
@ -558,8 +679,11 @@ def draw_astar_traversal() -> None:
|
||||
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.",
|
||||
"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",
|
||||
@ -578,16 +702,17 @@ def draw_astar_traversal() -> None:
|
||||
facecolor=BG,
|
||||
)
|
||||
plt.close()
|
||||
print(" ✓ astar_traversal.png")
|
||||
_logger.info("astar_traversal.png")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
if __name__ == "__main__":
|
||||
print("Generating shortest path diagrams for PYTANIE 2...")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
_logger.info("Generating shortest path diagrams...")
|
||||
draw_graph_structure()
|
||||
draw_dijkstra_traversal()
|
||||
draw_bellman_ford_traversal()
|
||||
draw_astar_traversal()
|
||||
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")
|
||||
_logger.info("All diagrams saved to %s/", OUTPUT_DIR)
|
||||
|
||||
@ -5,6 +5,9 @@ Each file: pytanie_NN.md (or pytanie_NN_MM.md for dual-numbered like 13/27).
|
||||
Placed in pytania/questions/ folder.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
@ -13,6 +16,9 @@ SOURCE = str(Path(SCRIPT_DIR) / "OBRONA_MAGISTERSKA_ODPOWIEDZI.md")
|
||||
OUT_DIR = str(Path(SCRIPT_DIR) / "questions")
|
||||
Path(OUT_DIR).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
with Path(SOURCE).open(encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
@ -25,7 +31,7 @@ for i, line in enumerate(lines):
|
||||
title = m.group(3).strip()
|
||||
question_starts.append((i, raw_num, title))
|
||||
|
||||
print(f"Found {len(question_starts)} questions")
|
||||
_logger.info("Found %d questions", len(question_starts))
|
||||
|
||||
for idx, (start_line, raw_num, title) in enumerate(question_starts):
|
||||
# End = next question start or EOF
|
||||
@ -54,6 +60,12 @@ for idx, (start_line, raw_num, title) in enumerate(question_starts):
|
||||
f.writelines(content_lines)
|
||||
|
||||
line_count = len(content_lines)
|
||||
print(f" {filename:30s} ({line_count:4d} lines) PYTANIE {raw_num}: {title}")
|
||||
_logger.info(
|
||||
" %-30s (%4d lines) PYTANIE %s: %s",
|
||||
filename,
|
||||
line_count,
|
||||
raw_num,
|
||||
title,
|
||||
)
|
||||
|
||||
print(f"\nAll files written to: {OUT_DIR}")
|
||||
_logger.info("All files written to: %s", OUT_DIR)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user