mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-07-04 15:43:11 +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
|
#!/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 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.
|
A4-compatible, monochrome-friendly, 300 DPI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import matplotlib as mpl
|
import matplotlib as mpl
|
||||||
|
|
||||||
mpl.use("Agg")
|
mpl.use("Agg")
|
||||||
@ -16,6 +21,11 @@ from pathlib import Path
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from matplotlib.axes import Axes
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DPI = 300
|
DPI = 300
|
||||||
BG = "white"
|
BG = "white"
|
||||||
LN = "black"
|
LN = "black"
|
||||||
@ -34,34 +44,53 @@ LIGHT_GREEN = "#D5E8D4"
|
|||||||
LIGHT_RED = "#F8D7DA"
|
LIGHT_RED = "#F8D7DA"
|
||||||
LIGHT_YELLOW = "#FFF9C4"
|
LIGHT_YELLOW = "#FFF9C4"
|
||||||
|
|
||||||
# --- Graph layout for negative-weight example ---
|
# Graph layout for negative-weight example:
|
||||||
# S→A(2), A→C(3), S→B(5), B→A(-4)
|
# 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_POS: dict[str, tuple[float, float]] = {
|
||||||
NEG_EDGES = [("S", "A", 2), ("A", "C", 3), ("S", "B", 5), ("B", "A", -4)]
|
"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(
|
def draw_node(
|
||||||
ax,
|
ax: Axes,
|
||||||
name,
|
name: str,
|
||||||
pos,
|
pos: tuple[float, float],
|
||||||
color="white",
|
*,
|
||||||
current=False,
|
color: str = "white",
|
||||||
visited=False,
|
current: bool = False,
|
||||||
dist_label=None,
|
visited: bool = False,
|
||||||
fontsize=12,
|
dist_label: str | None = None,
|
||||||
error=False,
|
fontsize: int = 12,
|
||||||
|
error: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw node."""
|
"""Draw a graph node with optional distance label."""
|
||||||
x, y = pos
|
x, y = pos
|
||||||
r = 0.35
|
r = 0.35
|
||||||
lw = 2.5 if current else 1.5
|
lw = 2.5 if current else 1.5
|
||||||
ec = "#D32F2F" if current else ("#D32F2F" if error else LN)
|
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:
|
if error:
|
||||||
fc = LIGHT_RED
|
fc = LIGHT_RED
|
||||||
|
|
||||||
circle = plt.Circle(
|
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.add_patch(circle)
|
||||||
ax.text(
|
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(
|
def draw_edge(
|
||||||
ax,
|
ax: Axes,
|
||||||
pos1,
|
pos1: tuple[float, float],
|
||||||
pos2,
|
pos2: tuple[float, float],
|
||||||
weight,
|
weight: int,
|
||||||
highlighted=False,
|
*,
|
||||||
relaxed=False,
|
highlighted: bool = False,
|
||||||
negative=False,
|
relaxed: bool = False,
|
||||||
cycle_edge=False,
|
negative: bool = False,
|
||||||
offset=0.0,
|
cycle_edge: bool = False,
|
||||||
|
offset: float = 0.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw edge."""
|
"""Draw a directed edge between two nodes with a weight label."""
|
||||||
x1, y1 = pos1
|
x1, y1 = pos1
|
||||||
x2, y2 = pos2
|
x2, y2 = pos2
|
||||||
|
|
||||||
@ -127,22 +174,12 @@ def draw_edge(
|
|||||||
ex += perp_x
|
ex += perp_x
|
||||||
ey += perp_y
|
ey += perp_y
|
||||||
|
|
||||||
if cycle_edge:
|
color, lw, ls = _choose_edge_style(
|
||||||
color = "#D32F2F"
|
negative=negative,
|
||||||
lw = 2.5
|
relaxed=relaxed,
|
||||||
ls = "--"
|
highlighted=highlighted,
|
||||||
elif negative or relaxed:
|
cycle_edge=cycle_edge,
|
||||||
color = "#D32F2F"
|
)
|
||||||
lw = 2.5
|
|
||||||
ls = "-"
|
|
||||||
elif highlighted:
|
|
||||||
color = "#1565C0"
|
|
||||||
lw = 2.0
|
|
||||||
ls = "-"
|
|
||||||
else:
|
|
||||||
color = GRAY3
|
|
||||||
lw = 1.5
|
|
||||||
ls = "-"
|
|
||||||
|
|
||||||
# Arrow
|
# Arrow
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
@ -191,18 +228,19 @@ def draw_edge(
|
|||||||
|
|
||||||
|
|
||||||
def draw_neg_graph(
|
def draw_neg_graph(
|
||||||
ax,
|
ax: Axes,
|
||||||
edges,
|
edges: list[tuple[str, str, int]],
|
||||||
title="",
|
*,
|
||||||
dist=None,
|
title: str = "",
|
||||||
current=None,
|
dist: dict[str, str] | None = None,
|
||||||
visited=None,
|
current: str | None = None,
|
||||||
relaxed_edges=None,
|
visited: set[str] | None = None,
|
||||||
error_nodes=None,
|
relaxed_edges: set[tuple[str, str]] | None = None,
|
||||||
extra_edges=None,
|
error_nodes: set[str] | None = None,
|
||||||
node_positions=None,
|
extra_edges: list[tuple[str, str, int]] | None = None,
|
||||||
|
node_positions: dict[str, tuple[float, float]] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw neg graph."""
|
"""Draw the negative-weight graph with annotations."""
|
||||||
if visited is None:
|
if visited is None:
|
||||||
visited = set()
|
visited = set()
|
||||||
if relaxed_edges is None:
|
if relaxed_edges is None:
|
||||||
@ -228,9 +266,7 @@ def draw_neg_graph(
|
|||||||
for u, v, w in all_edges:
|
for u, v, w in all_edges:
|
||||||
rl = (u, v) in relaxed_edges
|
rl = (u, v) in relaxed_edges
|
||||||
neg = w < 0
|
neg = w < 0
|
||||||
cycle = extra_edges and (u, v, w) in extra_edges
|
cycle = bool(extra_edges and (u, v, w) in extra_edges)
|
||||||
# If B→A and A→B both exist, offset them
|
|
||||||
off = 0.0
|
|
||||||
draw_edge(
|
draw_edge(
|
||||||
ax,
|
ax,
|
||||||
node_positions[u],
|
node_positions[u],
|
||||||
@ -239,7 +275,7 @@ def draw_neg_graph(
|
|||||||
relaxed=rl,
|
relaxed=rl,
|
||||||
negative=neg,
|
negative=neg,
|
||||||
cycle_edge=cycle,
|
cycle_edge=cycle,
|
||||||
offset=off,
|
offset=0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
for name, pos in node_positions.items():
|
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:
|
def generate_bf_negative_weights() -> None:
|
||||||
"""Two-row figure.
|
"""Generate two-row figure.
|
||||||
|
|
||||||
Row 1: Graph structure + Dijkstra WRONG + Bellman-Ford CORRECT
|
Row 1: Graph structure + Dijkstra WRONG + Bellman-Ford CORRECT
|
||||||
Row 2: B-F iterations 1-3 step by step.
|
Row 2: B-F iterations 1-3 step by step.
|
||||||
"""
|
"""
|
||||||
fig = plt.figure(figsize=(14, 10))
|
fig = plt.figure(figsize=(14, 10))
|
||||||
fig.suptitle(
|
fig.suptitle(
|
||||||
"Bellman-Ford — ujemne wagi vs Dijkstra\n"
|
"Bellman-Ford \u2014 ujemne wagi vs Dijkstra\n"
|
||||||
"Graf: S→A(2), A→C(3), S→B(5), B→A(-4). Start = S",
|
"Graf: S\u2192A(2), A\u2192C(3),"
|
||||||
|
" S\u2192B(5), B\u2192A(-4). Start = S",
|
||||||
fontsize=FS_TITLE + 1,
|
fontsize=FS_TITLE + 1,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
y=0.99,
|
y=0.99,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- Row 1: Graph + Dijkstra wrong + BF correct ----
|
# Row 1: Graph + Dijkstra wrong + BF correct
|
||||||
|
|
||||||
# Panel 1: The graph structure
|
# Panel 1: The graph structure
|
||||||
ax1 = fig.add_subplot(2, 3, 1)
|
ax1 = fig.add_subplot(2, 3, 1)
|
||||||
draw_neg_graph(
|
draw_neg_graph(
|
||||||
ax1,
|
ax1,
|
||||||
NEG_EDGES,
|
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": "?"},
|
dist={"S": "0", "A": "?", "B": "?", "C": "?"},
|
||||||
)
|
)
|
||||||
# START label
|
|
||||||
ax1.annotate(
|
ax1.annotate(
|
||||||
"START",
|
"START",
|
||||||
xy=(NEG_POS["S"][0] - 0.35, NEG_POS["S"][1]),
|
xy=(NEG_POS["S"][0] - 0.35, NEG_POS["S"][1]),
|
||||||
@ -291,7 +357,11 @@ def generate_bf_negative_weights() -> None:
|
|||||||
fontsize=FS,
|
fontsize=FS,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
color="#D32F2F",
|
color="#D32F2F",
|
||||||
arrowprops={"arrowstyle": "->", "color": "#D32F2F", "lw": 2},
|
arrowprops={
|
||||||
|
"arrowstyle": "->",
|
||||||
|
"color": "#D32F2F",
|
||||||
|
"lw": 2,
|
||||||
|
},
|
||||||
va="center",
|
va="center",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -300,41 +370,29 @@ def generate_bf_negative_weights() -> None:
|
|||||||
draw_neg_graph(
|
draw_neg_graph(
|
||||||
ax2,
|
ax2,
|
||||||
NEG_EDGES,
|
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"},
|
dist={"S": "0", "A": "2", "B": "5", "C": "5"},
|
||||||
visited={"S", "A", "B", "C"},
|
visited={"S", "A", "B", "C"},
|
||||||
error_nodes={"A", "C"},
|
error_nodes={"A", "C"},
|
||||||
)
|
)
|
||||||
# Add "WRONG" annotations
|
_add_annotation_box(
|
||||||
ax2.text(
|
ax2,
|
||||||
NEG_POS["A"][0] + 0.6,
|
NEG_POS["A"][0] + 0.6,
|
||||||
NEG_POS["A"][1] + 0.3,
|
NEG_POS["A"][1] + 0.3,
|
||||||
"✗ powinno 1",
|
"✗ powinno 1",
|
||||||
fontsize=FS_SMALL,
|
|
||||||
color="#D32F2F",
|
color="#D32F2F",
|
||||||
fontweight="bold",
|
bg_color=LIGHT_RED,
|
||||||
bbox={
|
|
||||||
"boxstyle": "round,pad=0.1",
|
|
||||||
"facecolor": LIGHT_RED,
|
|
||||||
"edgecolor": "#D32F2F",
|
|
||||||
"alpha": 0.9,
|
|
||||||
"lw": 0.5,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
ax2.text(
|
_add_annotation_box(
|
||||||
|
ax2,
|
||||||
NEG_POS["C"][0] + 0.05,
|
NEG_POS["C"][0] + 0.05,
|
||||||
NEG_POS["C"][1] + 0.55,
|
NEG_POS["C"][1] + 0.55,
|
||||||
"✗ powinno 4",
|
"✗ powinno 4",
|
||||||
fontsize=FS_SMALL,
|
|
||||||
color="#D32F2F",
|
color="#D32F2F",
|
||||||
fontweight="bold",
|
bg_color=LIGHT_RED,
|
||||||
bbox={
|
|
||||||
"boxstyle": "round,pad=0.1",
|
|
||||||
"facecolor": LIGHT_RED,
|
|
||||||
"edgecolor": "#D32F2F",
|
|
||||||
"alpha": 0.9,
|
|
||||||
"lw": 0.5,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Panel 3: Bellman-Ford — CORRECT
|
# Panel 3: Bellman-Ford — CORRECT
|
||||||
@ -342,48 +400,45 @@ def generate_bf_negative_weights() -> None:
|
|||||||
draw_neg_graph(
|
draw_neg_graph(
|
||||||
ax3,
|
ax3,
|
||||||
NEG_EDGES,
|
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"},
|
dist={"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||||
visited={"S", "A", "B", "C"},
|
visited={"S", "A", "B", "C"},
|
||||||
relaxed_edges={("B", "A")},
|
relaxed_edges={("B", "A")},
|
||||||
)
|
)
|
||||||
ax3.text(
|
_add_annotation_box(
|
||||||
|
ax3,
|
||||||
NEG_POS["A"][0] + 0.6,
|
NEG_POS["A"][0] + 0.6,
|
||||||
NEG_POS["A"][1] + 0.3,
|
NEG_POS["A"][1] + 0.3,
|
||||||
"✓ poprawne!",
|
"✓ poprawne!",
|
||||||
fontsize=FS_SMALL,
|
|
||||||
color="#006400",
|
color="#006400",
|
||||||
fontweight="bold",
|
bg_color=LIGHT_GREEN,
|
||||||
bbox={
|
|
||||||
"boxstyle": "round,pad=0.1",
|
|
||||||
"facecolor": LIGHT_GREEN,
|
|
||||||
"edgecolor": "#006400",
|
|
||||||
"alpha": 0.9,
|
|
||||||
"lw": 0.5,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
ax3.text(
|
_add_annotation_box(
|
||||||
|
ax3,
|
||||||
NEG_POS["C"][0] + 0.05,
|
NEG_POS["C"][0] + 0.05,
|
||||||
NEG_POS["C"][1] + 0.55,
|
NEG_POS["C"][1] + 0.55,
|
||||||
"✓ poprawne!",
|
"✓ poprawne!",
|
||||||
fontsize=FS_SMALL,
|
|
||||||
color="#006400",
|
color="#006400",
|
||||||
fontweight="bold",
|
bg_color=LIGHT_GREEN,
|
||||||
bbox={
|
|
||||||
"boxstyle": "round,pad=0.1",
|
|
||||||
"facecolor": LIGHT_GREEN,
|
|
||||||
"edgecolor": "#006400",
|
|
||||||
"alpha": 0.9,
|
|
||||||
"lw": 0.5,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- Row 2: B-F iterations step by step ----
|
# Row 2: B-F iterations step by step
|
||||||
iterations = [
|
iterations = [
|
||||||
{
|
{
|
||||||
"title": "B-F Iteracja 1\nRelaksuj WSZYSTKIE krawędzie",
|
"title": (
|
||||||
"dist": {"S": "0", "A": "1", "B": "5", "C": "5"},
|
"B-F Iteracja 1\n"
|
||||||
"relaxed": {("S", "A"), ("A", "C"), ("S", "B"), ("B", "A")},
|
"Relaksuj WSZYSTKIE krawędzie"
|
||||||
|
),
|
||||||
|
"dist": {
|
||||||
|
"S": "0", "A": "1", "B": "5", "C": "5",
|
||||||
|
},
|
||||||
|
"relaxed": {
|
||||||
|
("S", "A"), ("A", "C"),
|
||||||
|
("S", "B"), ("B", "A"),
|
||||||
|
},
|
||||||
"detail": (
|
"detail": (
|
||||||
"S→A: 0+2=2<∞ → A=2\n"
|
"S→A: 0+2=2<∞ → A=2\n"
|
||||||
"A→C: 2+3=5<∞ → C=5\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",
|
"title": (
|
||||||
"dist": {"S": "0", "A": "1", "B": "5", "C": "4"},
|
"B-F Iteracja 2\n"
|
||||||
|
"Propagacja poprawionego A"
|
||||||
|
),
|
||||||
|
"dist": {
|
||||||
|
"S": "0", "A": "1", "B": "5", "C": "4",
|
||||||
|
},
|
||||||
"relaxed": {("A", "C")},
|
"relaxed": {("A", "C")},
|
||||||
"detail": (
|
"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!",
|
"title": (
|
||||||
"dist": {"S": "0", "A": "1", "B": "5", "C": "4"},
|
"B-F Iteracja 3\n"
|
||||||
|
"Brak zmian → stabilne!"
|
||||||
|
),
|
||||||
|
"dist": {
|
||||||
|
"S": "0", "A": "1", "B": "5", "C": "4",
|
||||||
|
},
|
||||||
"relaxed": set(),
|
"relaxed": set(),
|
||||||
"detail": (
|
"detail": (
|
||||||
"Wszystkie krawędzie:\n"
|
"Wszystkie krawędzie:\n"
|
||||||
@ -422,7 +490,6 @@ def generate_bf_negative_weights() -> None:
|
|||||||
visited={"S", "A", "B", "C"},
|
visited={"S", "A", "B", "C"},
|
||||||
relaxed_edges=it["relaxed"],
|
relaxed_edges=it["relaxed"],
|
||||||
)
|
)
|
||||||
# Detail text below graph
|
|
||||||
ax.text(
|
ax.text(
|
||||||
3.2,
|
3.2,
|
||||||
-0.5,
|
-0.5,
|
||||||
@ -431,19 +498,31 @@ def generate_bf_negative_weights() -> None:
|
|||||||
va="top",
|
va="top",
|
||||||
fontsize=FS_SMALL,
|
fontsize=FS_SMALL,
|
||||||
family="monospace",
|
family="monospace",
|
||||||
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
|
bbox={
|
||||||
|
"boxstyle": "round,pad=0.3",
|
||||||
|
"facecolor": GRAY4,
|
||||||
|
"edgecolor": GRAY3,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bottom note
|
# Bottom note
|
||||||
fig.text(
|
fig.text(
|
||||||
0.5,
|
0.5,
|
||||||
0.01,
|
0.01,
|
||||||
"Dijkstra zamyka wierzchołki na stałe (zachłanność) → ujemna waga B→A(-4) nie może poprawić zamkniętego A.\n"
|
"Dijkstra zamyka wierzchołki na stałe"
|
||||||
"Bellman-Ford relaksuje WSZYSTKIE krawędzie w każdej iteracji → ujemne wagi propagują się poprawnie.",
|
" (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",
|
ha="center",
|
||||||
fontsize=FS,
|
fontsize=FS,
|
||||||
fontweight="bold",
|
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])
|
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
|
||||||
@ -454,19 +533,20 @@ def generate_bf_negative_weights() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close()
|
plt.close()
|
||||||
print(" ✓ bellman_ford_negative_weights.png")
|
_logger.info(" ✓ bellman_ford_negative_weights.png")
|
||||||
|
|
||||||
|
|
||||||
def generate_bf_negative_cycle() -> None:
|
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]
|
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.
|
Cycle: B->A->C->B = -4+3+(-3) = -4 < 0.
|
||||||
"""
|
"""
|
||||||
fig = plt.figure(figsize=(14, 5.5))
|
fig = plt.figure(figsize=(14, 5.5))
|
||||||
fig.suptitle(
|
fig.suptitle(
|
||||||
"Bellman-Ford — wykrywanie cyklu ujemnego\n"
|
"Bellman-Ford \u2014 wykrywanie cyklu ujemnego\n"
|
||||||
"Dodano krawędź C→B(-3). Cykl: B→A→C→B = -4+3+(-3) = -4 < 0",
|
"Dodano krawędź C→B(-3)."
|
||||||
|
" Cykl: B→A→C→B = -4+3+(-3) = -4 < 0",
|
||||||
fontsize=FS_TITLE + 1,
|
fontsize=FS_TITLE + 1,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
y=0.99,
|
y=0.99,
|
||||||
@ -477,11 +557,13 @@ def generate_bf_negative_cycle() -> None:
|
|||||||
draw_neg_graph(
|
draw_neg_graph(
|
||||||
ax1,
|
ax1,
|
||||||
NEG_EDGES,
|
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": "?"},
|
dist={"S": "0", "A": "?", "B": "?", "C": "?"},
|
||||||
extra_edges=[("C", "B", -3)],
|
extra_edges=[("C", "B", -3)],
|
||||||
)
|
)
|
||||||
# Mark cycle
|
|
||||||
ax1.annotate(
|
ax1.annotate(
|
||||||
"CYKL\n-4+3+(-3)=-4<0",
|
"CYKL\n-4+3+(-3)=-4<0",
|
||||||
xy=(3.3, 2.0),
|
xy=(3.3, 2.0),
|
||||||
@ -503,7 +585,10 @@ def generate_bf_negative_cycle() -> None:
|
|||||||
draw_neg_graph(
|
draw_neg_graph(
|
||||||
ax2,
|
ax2,
|
||||||
NEG_EDGES,
|
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"},
|
dist={"S": "0", "A": "-7", "B": "-4", "C": "-4"},
|
||||||
visited={"S", "A", "B", "C"},
|
visited={"S", "A", "B", "C"},
|
||||||
error_nodes={"A", "B", "C"},
|
error_nodes={"A", "B", "C"},
|
||||||
@ -512,7 +597,9 @@ def generate_bf_negative_cycle() -> None:
|
|||||||
ax2.text(
|
ax2.text(
|
||||||
3.2,
|
3.2,
|
||||||
-0.4,
|
-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",
|
ha="center",
|
||||||
va="top",
|
va="top",
|
||||||
fontsize=FS_SMALL,
|
fontsize=FS_SMALL,
|
||||||
@ -562,7 +649,8 @@ def generate_bf_negative_cycle() -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
ax3.set_title(
|
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,
|
fontsize=FS,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
pad=5,
|
pad=5,
|
||||||
@ -572,12 +660,19 @@ def generate_bf_negative_cycle() -> None:
|
|||||||
fig.text(
|
fig.text(
|
||||||
0.5,
|
0.5,
|
||||||
0.01,
|
0.01,
|
||||||
"Bez cyklu ujemnego: po V-1 iteracjach dist jest stabilne. "
|
"Bez cyklu ujemnego: po V-1 iteracjach"
|
||||||
"Z cyklem ujemnym: dist maleje w nieskończoność → V-ta iteracja to wykrywa.",
|
" dist jest stabilne. "
|
||||||
|
"Z cyklem ujemnym: dist maleje"
|
||||||
|
" w nieskończoność"
|
||||||
|
" → V-ta iteracja to wykrywa.",
|
||||||
ha="center",
|
ha="center",
|
||||||
fontsize=FS,
|
fontsize=FS,
|
||||||
fontweight="bold",
|
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])
|
plt.tight_layout(rect=[0, 0.06, 1, 0.94])
|
||||||
@ -588,11 +683,14 @@ def generate_bf_negative_cycle() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close()
|
plt.close()
|
||||||
print(" ✓ bellman_ford_negative_cycle.png")
|
_logger.info(" ✓ bellman_ford_negative_cycle.png")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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_weights()
|
||||||
generate_bf_negative_cycle()
|
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."""
|
"""Draw 3nf."""
|
||||||
fig, ax = create_figure(11.69, 6.5)
|
fig, ax = create_figure(11.69, 6.5)
|
||||||
|
|
||||||
# Studenci (fixed)
|
# Student table after removing transitive dependency
|
||||||
h1 = ["StID*", "Imie", "WydzialID"]
|
h1 = ["StID*", "Imie", "WydzialID"]
|
||||||
r1 = [["1", "Anna", "W4"], ["2", "Jan", "W4"], ["3", "Ewa", "W2"]]
|
r1 = [["1", "Anna", "W4"], ["2", "Jan", "W4"], ["3", "Ewa", "W2"]]
|
||||||
cw1 = [0.55, 0.55, 0.85]
|
cw1 = [0.55, 0.55, 0.85]
|
||||||
@ -633,7 +633,8 @@ def draw_3nf() -> None:
|
|||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
2.95,
|
2.95,
|
||||||
" StID -> WydzialID -> NazwaWydzialu rozbito: NazwaWydzialu w osobnej tabeli.",
|
" StID -> WydzialID -> NazwaWydzialu"
|
||||||
|
" rozbito: NazwaWydzialu w osobnej tabeli.",
|
||||||
fontsize=8,
|
fontsize=8,
|
||||||
color="#333333",
|
color="#333333",
|
||||||
)
|
)
|
||||||
@ -665,7 +666,8 @@ def draw_3nf() -> None:
|
|||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
1.5,
|
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,
|
fontsize=9,
|
||||||
color="#333333",
|
color="#333333",
|
||||||
)
|
)
|
||||||
@ -677,7 +679,7 @@ def draw_3nf() -> None:
|
|||||||
pad_inches=0.2,
|
pad_inches=0.2,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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
|
ax, 7.2, 6.8, "ProwadzacyKurs (kl: Prow.)", h4, r4, cw4, title_fontsize=9
|
||||||
)
|
)
|
||||||
|
|
||||||
# StudentProwadzacy (NEW)
|
# New student-advisor junction table
|
||||||
h5 = ["StID*", "Prowadzacy*"]
|
h5 = ["StID*", "Prowadzacy*"]
|
||||||
r5 = [["1", "Kowalski"], ["1", "Nowak"], ["2", "Kowalski"], ["3", "Wisniewski"]]
|
r5 = [["1", "Kowalski"], ["1", "Nowak"], ["2", "Kowalski"], ["3", "Wisniewski"]]
|
||||||
cw5 = [0.55, 1.05]
|
cw5 = [0.55, 1.05]
|
||||||
@ -733,7 +735,8 @@ def draw_bcnf() -> None:
|
|||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
2.55,
|
2.55,
|
||||||
" ProwadzacyKurs(Prowadzacy, KursID) — FD: Prowadzacy -> KursID, klucz: Prowadzacy",
|
" ProwadzacyKurs(Prowadzacy, KursID)"
|
||||||
|
" — FD: Prowadzacy -> KursID, klucz: Prowadzacy",
|
||||||
fontsize=8,
|
fontsize=8,
|
||||||
color="#333333",
|
color="#333333",
|
||||||
)
|
)
|
||||||
@ -756,7 +759,8 @@ def draw_bcnf() -> None:
|
|||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
1.45,
|
1.45,
|
||||||
"Rekonstrukcja: StudentProw. JOIN ProwadzacyKurs ON Prowadzacy -> odtworzenie Zapisy.",
|
"Rekonstrukcja: StudentProw. JOIN ProwadzacyKurs"
|
||||||
|
" ON Prowadzacy -> odtworzenie Zapisy.",
|
||||||
fontsize=8,
|
fontsize=8,
|
||||||
color="#333333",
|
color="#333333",
|
||||||
)
|
)
|
||||||
@ -768,7 +772,7 @@ def draw_bcnf() -> None:
|
|||||||
pad_inches=0.2,
|
pad_inches=0.2,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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,
|
pad_inches=0.2,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
2.0,
|
2.0,
|
||||||
"Weryfikacja: Alfa dostarcza Nakretke? Alfa -> Wiezowiec? Nakretka -> Wiezowiec?",
|
"Weryfikacja: Alfa dostarcza Nakretke?"
|
||||||
|
" Alfa -> Wiezowiec? Nakretka -> Wiezowiec?",
|
||||||
fontsize=8,
|
fontsize=8,
|
||||||
)
|
)
|
||||||
add_label(
|
add_label(
|
||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
1.65,
|
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,
|
fontsize=8,
|
||||||
color="#333333",
|
color="#333333",
|
||||||
)
|
)
|
||||||
@ -1035,7 +1041,8 @@ def draw_5nf() -> None:
|
|||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
0.9,
|
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,
|
fontsize=8,
|
||||||
color="black",
|
color="black",
|
||||||
)
|
)
|
||||||
@ -1043,7 +1050,8 @@ def draw_5nf() -> None:
|
|||||||
ax,
|
ax,
|
||||||
0.3,
|
0.3,
|
||||||
0.5,
|
0.5,
|
||||||
" Dekompozycja 5NF jest poprawna TYLKO jesli regula cykliczna rzeczywiscie zachodzi!",
|
" Dekompozycja 5NF jest poprawna TYLKO"
|
||||||
|
" jesli regula cykliczna rzeczywiscie zachodzi!",
|
||||||
fontsize=8,
|
fontsize=8,
|
||||||
color="black",
|
color="black",
|
||||||
)
|
)
|
||||||
@ -1055,7 +1063,7 @@ def draw_5nf() -> None:
|
|||||||
pad_inches=0.2,
|
pad_inches=0.2,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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},
|
arrowprops={"arrowstyle": "<-", "color": "black", "lw": 1.5},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bottom: mnemonic
|
# Mnemonic quote at the bottom
|
||||||
ax.text(
|
ax.text(
|
||||||
5.85,
|
5.85,
|
||||||
2.2,
|
2.2,
|
||||||
@ -1178,7 +1186,8 @@ def draw_summary_flow() -> None:
|
|||||||
ax.text(
|
ax.text(
|
||||||
5.85,
|
5.85,
|
||||||
0.8,
|
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,
|
fontsize=8,
|
||||||
ha="center",
|
ha="center",
|
||||||
va="center",
|
va="center",
|
||||||
@ -1193,14 +1202,15 @@ def draw_summary_flow() -> None:
|
|||||||
pad_inches=0.2,
|
pad_inches=0.2,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print("Generated: nf_summary_flow.png")
|
logger.info("Generated: nf_summary_flow.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Main
|
# Main
|
||||||
# ============================================================
|
# ============================================================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("Generating normalization diagrams...")
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger.info("Generating normalization diagrams...")
|
||||||
draw_0nf()
|
draw_0nf()
|
||||||
draw_1nf()
|
draw_1nf()
|
||||||
draw_2nf()
|
draw_2nf()
|
||||||
@ -1209,4 +1219,4 @@ if __name__ == "__main__":
|
|||||||
draw_4nf()
|
draw_4nf()
|
||||||
draw_5nf()
|
draw_5nf()
|
||||||
draw_summary_flow()
|
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.
|
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
|
import matplotlib as mpl
|
||||||
|
|
||||||
mpl.use("Agg")
|
mpl.use("Agg")
|
||||||
@ -18,6 +23,11 @@ from matplotlib.patches import FancyBboxPatch
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from matplotlib.axes import Axes
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DPI = 300
|
DPI = 300
|
||||||
BG = "white"
|
BG = "white"
|
||||||
LN = "black"
|
LN = "black"
|
||||||
@ -33,29 +43,40 @@ GRAY3 = "#B8B8B8"
|
|||||||
GRAY4 = "#F5F5F5"
|
GRAY4 = "#F5F5F5"
|
||||||
GRAY5 = "#C0C0C0"
|
GRAY5 = "#C0C0C0"
|
||||||
|
|
||||||
|
_BAND_HEIGHTS = [0.7, 1.3, 1.4, 1.5, 1.5]
|
||||||
|
|
||||||
|
|
||||||
def draw_box(
|
def draw_box(
|
||||||
ax,
|
ax: Axes,
|
||||||
x,
|
x: float,
|
||||||
y,
|
y: float,
|
||||||
w,
|
w: float,
|
||||||
h,
|
h: float,
|
||||||
text,
|
text: str,
|
||||||
fill="white",
|
*,
|
||||||
lw=1.2,
|
fill: str = "white",
|
||||||
fontsize=FS,
|
lw: float = 1.2,
|
||||||
fontweight="normal",
|
fontsize: float = FS,
|
||||||
ha="center",
|
fontweight: str = "normal",
|
||||||
va="center",
|
ha: str = "center",
|
||||||
rounded=True,
|
va: str = "center",
|
||||||
|
rounded: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw box."""
|
"""Draw a labeled box on the axes."""
|
||||||
if rounded:
|
if rounded:
|
||||||
rect = FancyBboxPatch(
|
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:
|
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.add_patch(rect)
|
||||||
ax.text(
|
ax.text(
|
||||||
x + w / 2,
|
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:
|
def draw_arrow(
|
||||||
"""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(
|
ax.annotate(
|
||||||
"",
|
"",
|
||||||
xy=(x2, y2),
|
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)
|
# 1. Pattern Template Structure (NaPSiRoKo mnemonic)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def generate_pattern_template() -> None:
|
def generate_pattern_template() -> None:
|
||||||
"""Generate pattern template."""
|
"""Generate pattern template diagram with NaPSiRoKo mnemonic."""
|
||||||
fig, ax = plt.subplots(figsize=(8.27, 6))
|
fig, ax = plt.subplots(figsize=(8.27, 6))
|
||||||
ax.set_xlim(0, 10)
|
ax.set_xlim(0, 10)
|
||||||
ax.set_ylim(0, 8)
|
ax.set_ylim(0, 8)
|
||||||
@ -133,11 +164,17 @@ def generate_pattern_template() -> None:
|
|||||||
(
|
(
|
||||||
"Si",
|
"Si",
|
||||||
"SIŁY (forces)",
|
"SIŁY (forces)",
|
||||||
"Konkurencyjne wymagania do pogodzenia\n(np. testowalność vs wydajność)",
|
"Konkurencyjne wymagania do pogodzenia\n"
|
||||||
|
"(np. testowalność vs wydajność)",
|
||||||
GRAY1,
|
GRAY1,
|
||||||
),
|
),
|
||||||
("Ro", "ROZWIĄZANIE", "Struktura, diagram, zachowanie", "white"),
|
("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
|
band_x = card_x + 0.3
|
||||||
@ -202,7 +239,14 @@ def generate_pattern_template() -> None:
|
|||||||
|
|
||||||
# Arrow connecting fields
|
# Arrow connecting fields
|
||||||
if i < len(fields) - 1:
|
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
|
# Extra fields note at bottom
|
||||||
ax.text(
|
ax.text(
|
||||||
@ -232,14 +276,14 @@ def generate_pattern_template() -> None:
|
|||||||
out = str(Path(OUTPUT_DIR) / "q14_pattern_template.png")
|
out = str(Path(OUTPUT_DIR) / "q14_pattern_template.png")
|
||||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(f" Saved: {out}")
|
_logger.info(" Saved: %s", out)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 2. Catalog Classification Map
|
# 2. Catalog Classification Map
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def generate_catalog_map() -> None:
|
def generate_catalog_map() -> None:
|
||||||
"""Generate catalog map."""
|
"""Generate catalog classification map diagram."""
|
||||||
fig, ax = plt.subplots(figsize=(8.27, 7))
|
fig, ax = plt.subplots(figsize=(8.27, 7))
|
||||||
ax.set_xlim(0, 12)
|
ax.set_xlim(0, 12)
|
||||||
ax.set_ylim(0, 9)
|
ax.set_ylim(0, 9)
|
||||||
@ -247,13 +291,15 @@ def generate_catalog_map() -> None:
|
|||||||
ax.axis("off")
|
ax.axis("off")
|
||||||
fig.patch.set_facecolor(BG)
|
fig.patch.set_facecolor(BG)
|
||||||
ax.set_title(
|
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,
|
fontsize=FS_TITLE,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
pad=15,
|
pad=15,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Y-axis: Scale (architectural → design → idiom)
|
# Y-axis: Scale (architectural -> design -> idiom)
|
||||||
ax.text(
|
ax.text(
|
||||||
0.3,
|
0.3,
|
||||||
7.8,
|
7.8,
|
||||||
@ -286,7 +332,9 @@ def generate_catalog_map() -> None:
|
|||||||
va="center",
|
va="center",
|
||||||
fontstyle="italic",
|
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
|
# X-axis: Domain
|
||||||
ax.text(
|
ax.text(
|
||||||
@ -305,16 +353,16 @@ def generate_catalog_map() -> None:
|
|||||||
arrowprops={"arrowstyle": "->", "lw": 1.5, "color": LN},
|
arrowprops={"arrowstyle": "->", "lw": 1.5, "color": LN},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Catalog boxes positioned by scale x domain
|
# Catalog boxes positioned by scale and domain
|
||||||
catalogs = [
|
catalogs = [
|
||||||
# (x, y, w, h, name, subtitle, fill, mnemonic_letter)
|
|
||||||
(
|
(
|
||||||
2.5,
|
2.5,
|
||||||
6.2,
|
6.2,
|
||||||
2.5,
|
2.5,
|
||||||
1.4,
|
1.4,
|
||||||
"POSA",
|
"POSA",
|
||||||
"1996 • Buschmann\nLayers, Broker,\nPipes & Filters, MVC",
|
"1996 • Buschmann\nLayers, Broker,\n"
|
||||||
|
"Pipes & Filters, MVC",
|
||||||
GRAY1,
|
GRAY1,
|
||||||
"P",
|
"P",
|
||||||
),
|
),
|
||||||
@ -324,7 +372,8 @@ def generate_catalog_map() -> None:
|
|||||||
2.5,
|
2.5,
|
||||||
1.4,
|
1.4,
|
||||||
"GoF",
|
"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,
|
GRAY2,
|
||||||
"G",
|
"G",
|
||||||
),
|
),
|
||||||
@ -334,7 +383,8 @@ def generate_catalog_map() -> None:
|
|||||||
2.5,
|
2.5,
|
||||||
1.4,
|
1.4,
|
||||||
"EIP",
|
"EIP",
|
||||||
"2003 • Hohpe & Woolf\nMessage Channel,\nRouter, Aggregator",
|
"2003 • Hohpe & Woolf\nMessage Channel,\n"
|
||||||
|
"Router, Aggregator",
|
||||||
GRAY1,
|
GRAY1,
|
||||||
"E",
|
"E",
|
||||||
),
|
),
|
||||||
@ -344,7 +394,8 @@ def generate_catalog_map() -> None:
|
|||||||
2.5,
|
2.5,
|
||||||
1.4,
|
1.4,
|
||||||
"PoEAA",
|
"PoEAA",
|
||||||
"2002 • M. Fowler\nRepository, Unit of Work,\nDomain Model",
|
"2002 • M. Fowler\nRepository,"
|
||||||
|
" Unit of Work,\nDomain Model",
|
||||||
"white",
|
"white",
|
||||||
"P",
|
"P",
|
||||||
),
|
),
|
||||||
@ -354,7 +405,8 @@ def generate_catalog_map() -> None:
|
|||||||
2.8,
|
2.8,
|
||||||
1.4,
|
1.4,
|
||||||
"Cloud\nPatterns",
|
"Cloud\nPatterns",
|
||||||
"~2015 • Azure/AWS\nCircuit Breaker,\nSaga, Sidecar",
|
"~2015 • Azure/AWS\nCircuit Breaker,\n"
|
||||||
|
"Saga, Sidecar",
|
||||||
GRAY1,
|
GRAY1,
|
||||||
"C",
|
"C",
|
||||||
),
|
),
|
||||||
@ -392,7 +444,11 @@ def generate_catalog_map() -> None:
|
|||||||
|
|
||||||
# Mnemonic letter in corner
|
# Mnemonic letter in corner
|
||||||
circle = plt.Circle(
|
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.add_patch(circle)
|
||||||
ax.text(
|
ax.text(
|
||||||
@ -444,14 +500,14 @@ def generate_catalog_map() -> None:
|
|||||||
out = str(Path(OUTPUT_DIR) / "q14_catalog_map.png")
|
out = str(Path(OUTPUT_DIR) / "q14_catalog_map.png")
|
||||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(f" Saved: {out}")
|
_logger.info(" Saved: %s", out)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 3. Three Pillars of Cataloguing
|
# 3. Three Pillars of Cataloguing
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def generate_three_pillars() -> None:
|
def generate_three_pillars() -> None:
|
||||||
"""Generate three pillars."""
|
"""Generate three pillars of cataloguing diagram."""
|
||||||
fig, ax = plt.subplots(figsize=(8.27, 5.5))
|
fig, ax = plt.subplots(figsize=(8.27, 5.5))
|
||||||
ax.set_xlim(0, 12)
|
ax.set_xlim(0, 12)
|
||||||
ax.set_ylim(0, 7)
|
ax.set_ylim(0, 7)
|
||||||
@ -467,7 +523,13 @@ def generate_three_pillars() -> None:
|
|||||||
|
|
||||||
# Roof / banner
|
# Roof / banner
|
||||||
roof_pts = np.array([[1, 5.5], [6, 6.8], [11, 5.5]])
|
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.add_patch(roof)
|
||||||
ax.text(
|
ax.text(
|
||||||
6,
|
6,
|
||||||
@ -484,20 +546,29 @@ def generate_three_pillars() -> None:
|
|||||||
(
|
(
|
||||||
1.3,
|
1.3,
|
||||||
"1. SZABLON\nOPISU",
|
"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",
|
"Analogia:\nformatka\nencyklopedii",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
4.8,
|
4.8,
|
||||||
"2. KLASYFIKACJA\nWIELOOSIOWA",
|
"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",
|
"Analogia:\nkategorie\nw bibliotece",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
8.3,
|
8.3,
|
||||||
"3. JĘZYK\nWZORCÓW",
|
"3. JĘZYK\nWZORCÓW",
|
||||||
"Wzorce referują się\nwzajemnie tworząc\nsieć/graf:\nA → wymaga → B\nB → wariant → C",
|
"Wzorce referują się\nwzajemnie tworząc\n"
|
||||||
"Analogia:\n\u201ezobacz te\u017c\u201d\nw encyklopedii",
|
"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
|
# Horizontal line under title
|
||||||
ax.plot(
|
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
|
# Description
|
||||||
@ -570,14 +644,19 @@ def generate_three_pillars() -> None:
|
|||||||
out = str(Path(OUTPUT_DIR) / "q14_three_pillars.png")
|
out = str(Path(OUTPUT_DIR) / "q14_three_pillars.png")
|
||||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(f" Saved: {out}")
|
_logger.info(" Saved: %s", out)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 4. Filled-in Observer Pattern Card
|
# 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:
|
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))
|
fig, ax = plt.subplots(figsize=(8.27, 8.5))
|
||||||
ax.set_xlim(0, 10)
|
ax.set_xlim(0, 10)
|
||||||
ax.set_ylim(0, 10)
|
ax.set_ylim(0, 10)
|
||||||
@ -610,7 +689,8 @@ def generate_observer_card_filled() -> None:
|
|||||||
(
|
(
|
||||||
"P",
|
"P",
|
||||||
"PROBLEM",
|
"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"
|
"obiektów musi zareagować, ale Subject nie\n"
|
||||||
"powinien znać ich konkretnych typów.",
|
"powinien znać ich konkretnych typów.",
|
||||||
GRAY1,
|
GRAY1,
|
||||||
@ -619,9 +699,12 @@ def generate_observer_card_filled() -> None:
|
|||||||
(
|
(
|
||||||
"Si",
|
"Si",
|
||||||
"SIŁY",
|
"SIŁY",
|
||||||
"• loose coupling (nie znać obserwatorów z nazwy)\n"
|
"• loose coupling (nie znać obserwatorów"
|
||||||
" vs koszt powiadomień (N obserwatorów = N wywołań)\n"
|
" z nazwy)\n"
|
||||||
"• otwartość na rozszerzenia vs złożoność debugowania",
|
" vs koszt powiadomień"
|
||||||
|
" (N obserwatorów = N wywołań)\n"
|
||||||
|
"• otwartość na rozszerzenia"
|
||||||
|
" vs złożoność debugowania",
|
||||||
"white",
|
"white",
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
@ -651,21 +734,13 @@ def generate_observer_card_filled() -> None:
|
|||||||
band_w = card_w - 0.6
|
band_w = card_w - 0.6
|
||||||
start_y = card_y + card_h - 0.65
|
start_y = card_y + card_h - 0.65
|
||||||
|
|
||||||
for i, (abbr, title, content, fill, is_title_field) in enumerate(fields):
|
for i, (abbr, title, content, fill, is_title_field) in enumerate(
|
||||||
if is_title_field:
|
fields
|
||||||
band_h = 0.7
|
):
|
||||||
elif i == 1:
|
band_h = _get_observer_band_height(i)
|
||||||
band_h = 1.3
|
|
||||||
elif i == 2:
|
|
||||||
band_h = 1.4
|
|
||||||
elif i == 3:
|
|
||||||
band_h = 1.5
|
|
||||||
else:
|
|
||||||
band_h = 1.5
|
|
||||||
|
|
||||||
by = start_y - sum(
|
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
|
_get_observer_band_height(j) + 0.15 for j in range(i)
|
||||||
for j in range(i)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Abbreviation circle
|
# Abbreviation circle
|
||||||
@ -734,13 +809,24 @@ def generate_observer_card_filled() -> None:
|
|||||||
|
|
||||||
# Arrow
|
# Arrow
|
||||||
if i < len(fields) - 1:
|
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 info at bottom
|
||||||
extra_y = 0.55
|
extra_y = 0.55
|
||||||
extras = [
|
extras = [
|
||||||
"Powiązane: Mediator (centralizuje), Pub/Sub (rozproszony), MVC (View = Observer)",
|
"Powiązane: Mediator (centralizuje),"
|
||||||
"Znane użycia: Java Swing listeners, C# event/delegate, React useState, DOM addEventListener",
|
" 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):
|
for j, txt in enumerate(extras):
|
||||||
ax.text(
|
ax.text(
|
||||||
@ -758,14 +844,14 @@ def generate_observer_card_filled() -> None:
|
|||||||
out = str(Path(OUTPUT_DIR) / "q14_observer_card_filled.png")
|
out = str(Path(OUTPUT_DIR) / "q14_observer_card_filled.png")
|
||||||
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(f" Saved: {out}")
|
_logger.info(" Saved: %s", out)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 5. Pattern Language Navigation Graph
|
# 5. Pattern Language Navigation Graph
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def generate_pattern_language_navigation() -> None:
|
def generate_pattern_language_navigation() -> None:
|
||||||
"""Generate pattern language navigation."""
|
"""Generate pattern language navigation graph diagram."""
|
||||||
fig, ax = plt.subplots(figsize=(8.27, 9))
|
fig, ax = plt.subplots(figsize=(8.27, 9))
|
||||||
ax.set_xlim(0, 12)
|
ax.set_xlim(0, 12)
|
||||||
ax.set_ylim(0, 12)
|
ax.set_ylim(0, 12)
|
||||||
@ -773,22 +859,37 @@ def generate_pattern_language_navigation() -> None:
|
|||||||
ax.axis("off")
|
ax.axis("off")
|
||||||
fig.patch.set_facecolor(BG)
|
fig.patch.set_facecolor(BG)
|
||||||
ax.set_title(
|
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,
|
fontsize=FS_TITLE,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
pad=15,
|
pad=15,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Node positions: (x, y, label, is_pattern, fill)
|
# Node positions: (x, y, label, is_pattern, fill)
|
||||||
# Left column: problems; Right column: patterns
|
|
||||||
nodes = [
|
nodes = [
|
||||||
# Problems (left, rounded rect, white)
|
|
||||||
(1.5, 10.5, "Monolith\nnie skaluje się", False, "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, 8.2,
|
||||||
(1.5, 3.6, "Jak zachować\nspójność\ntransakcji?", False, "white"),
|
"Jak routować\nżądania do\nserwisów?",
|
||||||
(1.5, 1.3, "Jak odnaleźć\nadres serwisu?", False, "white"),
|
False, "white",
|
||||||
# Patterns (right, filled rect, gray)
|
),
|
||||||
|
(
|
||||||
|
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, 9.3, "Microservices", True, GRAY2),
|
||||||
(7.0, 7.0, "API Gateway", True, GRAY2),
|
(7.0, 7.0, "API Gateway", True, GRAY2),
|
||||||
(7.0, 4.7, "Circuit Breaker", 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.add_patch(rect)
|
||||||
ax.text(
|
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:
|
else:
|
||||||
w, h = node_w_prob, node_h_prob
|
w, h = node_w_prob, node_h_prob
|
||||||
@ -841,9 +948,8 @@ def generate_pattern_language_navigation() -> None:
|
|||||||
fontstyle="italic",
|
fontstyle="italic",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Arrows: problem → pattern (solid), pattern → problem (dashed label)
|
# Arrows: problem -> pattern, pattern -> problem
|
||||||
arrows = [
|
arrows = [
|
||||||
# (x1, y1, x2, y2, label, style)
|
|
||||||
(2.9, 10.5, 5.75, 9.5, "rozwiązuje →", "->", 1.5),
|
(2.9, 10.5, 5.75, 9.5, "rozwiązuje →", "->", 1.5),
|
||||||
(7.0, 8.8, 2.9, 8.5, "← rodzi problem", "->", 1.0),
|
(7.0, 8.8, 2.9, 8.5, "← rodzi problem", "->", 1.0),
|
||||||
(2.9, 8.0, 5.75, 7.2, "rozwiązuje →", "->", 1.5),
|
(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),
|
(2.9, 5.7, 5.75, 5.0, "rozwiązuje →", "->", 1.5),
|
||||||
(7.0, 4.2, 2.9, 3.9, "← rodzi problem", "->", 1.0),
|
(7.0, 4.2, 2.9, 3.9, "← rodzi problem", "->", 1.0),
|
||||||
(2.9, 3.3, 5.75, 2.6, "rozwiązuje →", "->", 1.5),
|
(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),
|
(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),
|
(2.9, 1.3, 8.75, 5.6, "rozwiązuje →", "->", 1.2),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -889,7 +993,6 @@ def generate_pattern_language_navigation() -> None:
|
|||||||
|
|
||||||
# Legend
|
# Legend
|
||||||
legend_y = 0.3
|
legend_y = 0.3
|
||||||
# Problem node
|
|
||||||
r1 = FancyBboxPatch(
|
r1 = FancyBboxPatch(
|
||||||
(1.0, legend_y - 0.2),
|
(1.0, legend_y - 0.2),
|
||||||
1.5,
|
1.5,
|
||||||
@ -901,8 +1004,10 @@ def generate_pattern_language_navigation() -> None:
|
|||||||
linestyle="--",
|
linestyle="--",
|
||||||
)
|
)
|
||||||
ax.add_patch(r1)
|
ax.add_patch(r1)
|
||||||
ax.text(1.75, legend_y, "Problem", ha="center", va="center", fontsize=7)
|
ax.text(
|
||||||
# Pattern node
|
1.75, legend_y, "Problem",
|
||||||
|
ha="center", va="center", fontsize=7,
|
||||||
|
)
|
||||||
r2 = FancyBboxPatch(
|
r2 = FancyBboxPatch(
|
||||||
(3.5, legend_y - 0.2),
|
(3.5, legend_y - 0.2),
|
||||||
1.5,
|
1.5,
|
||||||
@ -925,7 +1030,8 @@ def generate_pattern_language_navigation() -> None:
|
|||||||
ax.text(
|
ax.text(
|
||||||
6.5,
|
6.5,
|
||||||
legend_y,
|
legend_y,
|
||||||
"Nawigacja: Problem → Wzorzec → Nowy Problem → Wzorzec → ...",
|
"Nawigacja: Problem \u2192 Wzorzec"
|
||||||
|
" \u2192 Nowy Problem \u2192 Wzorzec \u2192 ...",
|
||||||
ha="left",
|
ha="left",
|
||||||
va="center",
|
va="center",
|
||||||
fontsize=7,
|
fontsize=7,
|
||||||
@ -933,20 +1039,23 @@ def generate_pattern_language_navigation() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
fig.tight_layout()
|
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)
|
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(f" Saved: {out}")
|
_logger.info(" Saved: %s", out)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Main
|
# Main
|
||||||
# ============================================================
|
# ============================================================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("Generating PYTANIE 14 diagrams...")
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
_logger.info("Generating PYTANIE 14 diagrams...")
|
||||||
generate_pattern_template()
|
generate_pattern_template()
|
||||||
generate_catalog_map()
|
generate_catalog_map()
|
||||||
generate_three_pillars()
|
generate_three_pillars()
|
||||||
generate_observer_card_filled()
|
generate_observer_card_filled()
|
||||||
generate_pattern_language_navigation()
|
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)
|
5. ROS architecture (pub/sub nodes)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import matplotlib as mpl
|
import matplotlib as mpl
|
||||||
|
|
||||||
mpl.use("Agg")
|
mpl.use("Agg")
|
||||||
@ -21,6 +26,11 @@ from matplotlib.patches import FancyBboxPatch
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from matplotlib.axes import Axes
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DPI = 300
|
DPI = 300
|
||||||
BG = "white"
|
BG = "white"
|
||||||
LN = "black"
|
LN = "black"
|
||||||
@ -38,21 +48,38 @@ WHITE = "white"
|
|||||||
|
|
||||||
|
|
||||||
def draw_box(
|
def draw_box(
|
||||||
ax,
|
ax: Axes,
|
||||||
x,
|
x: float,
|
||||||
y,
|
y: float,
|
||||||
w,
|
w: float,
|
||||||
h,
|
h: float,
|
||||||
text,
|
text: str,
|
||||||
fill="white",
|
*,
|
||||||
lw=1.2,
|
fill: str = "white",
|
||||||
fontsize=FS,
|
lw: float = 1.2,
|
||||||
fontweight="normal",
|
fontsize: float = FS,
|
||||||
ha="center",
|
fontweight: str = "normal",
|
||||||
va="center",
|
ha: str = "center",
|
||||||
rounded=True,
|
va: str = "center",
|
||||||
|
rounded: bool = True,
|
||||||
) -> None:
|
) -> 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:
|
if rounded:
|
||||||
rect = FancyBboxPatch(
|
rect = FancyBboxPatch(
|
||||||
(x, y), w, h, boxstyle="round,pad=0.05", lw=lw, edgecolor=LN, facecolor=fill
|
(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:
|
def draw_arrow(
|
||||||
"""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(
|
ax.annotate(
|
||||||
"",
|
"",
|
||||||
xy=(x2, y2),
|
xy=(x2, y2),
|
||||||
@ -101,7 +149,7 @@ def draw_trms_pyramid() -> None:
|
|||||||
|
|
||||||
# Pyramid layers (bottom to top)
|
# Pyramid layers (bottom to top)
|
||||||
layers = [
|
layers = [
|
||||||
# (y, left_x, right_x, label, sublabel, fill, examples, timing)
|
# Fields: y left_x right_x label sublabel fill examples timing
|
||||||
(
|
(
|
||||||
0.5,
|
0.5,
|
||||||
1.0,
|
1.0,
|
||||||
@ -261,7 +309,7 @@ def draw_trms_pyramid() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(" ✓ robot_vendor_comparison.png")
|
_logger.info("Generated robot_vendor_comparison.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 3. Robot Movement Types (PTP, LIN, CIRC)
|
# 3. Robot Movement Types (PTP, LIN, CIRC)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def draw_movement_types() -> None:
|
def _draw_ptp_subplot(ax: Axes) -> None:
|
||||||
"""Draw movement types."""
|
"""Draw the PTP (Point-to-Point) subplot."""
|
||||||
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]
|
|
||||||
ax.set_xlim(-0.5, 4.5)
|
ax.set_xlim(-0.5, 4.5)
|
||||||
ax.set_ylim(-0.5, 4.5)
|
ax.set_ylim(-0.5, 4.5)
|
||||||
ax.set_aspect("equal")
|
ax.set_aspect("equal")
|
||||||
ax.set_title("PTP (Point-to-Point)\nMoveJ / PTP", fontsize=8, fontweight="bold")
|
ax.set_title(
|
||||||
ax.grid(True, alpha=0.3)
|
"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)
|
start = (0.5, 0.5)
|
||||||
end = (3.5, 3.5)
|
end = (3.5, 3.5)
|
||||||
ax.plot(*start, "ko", ms=10, zorder=5)
|
ax.plot(*start, "ko", ms=10, zorder=5)
|
||||||
@ -476,13 +517,18 @@ def draw_movement_types() -> None:
|
|||||||
ax.set_ylabel("")
|
ax.set_ylabel("")
|
||||||
ax.tick_params(labelsize=6)
|
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_xlim(-0.5, 4.5)
|
||||||
ax.set_ylim(-0.5, 4.5)
|
ax.set_ylim(-0.5, 4.5)
|
||||||
ax.set_aspect("equal")
|
ax.set_aspect("equal")
|
||||||
ax.set_title("LIN (Linear)\nMoveL / LIN", fontsize=8, fontweight="bold")
|
ax.set_title(
|
||||||
ax.grid(True, alpha=0.3)
|
"LIN (Linear)\nMoveL / LIN",
|
||||||
|
fontsize=8,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
ax.grid(visible=True, alpha=0.3)
|
||||||
|
|
||||||
start = (0.5, 1.0)
|
start = (0.5, 1.0)
|
||||||
end = (3.5, 3.5)
|
end = (3.5, 3.5)
|
||||||
@ -519,22 +565,27 @@ def draw_movement_types() -> None:
|
|||||||
)
|
)
|
||||||
ax.tick_params(labelsize=6)
|
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_xlim(-0.5, 4.5)
|
||||||
ax.set_ylim(-0.5, 4.5)
|
ax.set_ylim(-0.5, 4.5)
|
||||||
ax.set_aspect("equal")
|
ax.set_aspect("equal")
|
||||||
ax.set_title("CIRC (Circular)\nMoveC / CIRC", fontsize=8, fontweight="bold")
|
ax.set_title(
|
||||||
ax.grid(True, alpha=0.3)
|
"CIRC (Circular)\nMoveC / CIRC",
|
||||||
|
fontsize=8,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
ax.grid(visible=True, alpha=0.3)
|
||||||
|
|
||||||
# Arc through 3 points
|
# Arc through 3 points
|
||||||
center = (2.0, 1.5)
|
center = (2.0, 1.5)
|
||||||
r = 2.0
|
radius = 2.0
|
||||||
theta_start = np.radians(20)
|
theta_start = np.radians(20)
|
||||||
theta_end = np.radians(160)
|
theta_end = np.radians(160)
|
||||||
theta = np.linspace(theta_start, theta_end, 50)
|
theta = np.linspace(theta_start, theta_end, 50)
|
||||||
x_circ = center[0] + r * np.cos(theta)
|
x_circ = center[0] + radius * np.cos(theta)
|
||||||
y_circ = center[1] + r * np.sin(theta)
|
y_circ = center[1] + radius * np.sin(theta)
|
||||||
|
|
||||||
ax.plot(x_circ, y_circ, "k-", lw=2)
|
ax.plot(x_circ, y_circ, "k-", lw=2)
|
||||||
ax.annotate(
|
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.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[0] + 0.3, y_circ[0] - 0.3, "Start", fontsize=7)
|
||||||
ax.text(
|
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)
|
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)
|
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.tight_layout()
|
||||||
fig.savefig(
|
fig.savefig(
|
||||||
str(Path(OUTPUT_DIR) / "robot_movement_types.png"),
|
str(Path(OUTPUT_DIR) / "robot_movement_types.png"),
|
||||||
@ -576,7 +646,7 @@ def draw_movement_types() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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(
|
ax.text(
|
||||||
7.4,
|
7.4,
|
||||||
0.6,
|
0.6,
|
||||||
"✓ Bez zatrzymania produkcji\n✓ Wysoka precyzja, symulacja\n✗ Wymaga kalibracji",
|
"✓ Bez zatrzymania produkcji\n"
|
||||||
|
"✓ Wysoka precyzja, symulacja\n"
|
||||||
|
"✗ Wymaga kalibracji",
|
||||||
ha="center",
|
ha="center",
|
||||||
va="center",
|
va="center",
|
||||||
fontsize=6.5,
|
fontsize=6.5,
|
||||||
@ -679,7 +751,7 @@ def draw_online_offline() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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 (arrows with labels)
|
||||||
topics = [
|
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, 5.0, 4.0, 5.0, "/scan"),
|
||||||
(3.2, 3.0, 4.0, 3.0, "/image"),
|
(3.2, 3.0, 4.0, 3.0, "/image"),
|
||||||
(6.2, 5.0, 7.0, 4.3, "/pose"),
|
(6.2, 5.0, 7.0, 4.3, "/pose"),
|
||||||
@ -780,7 +852,7 @@ def draw_ros_architecture() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
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,
|
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)"),
|
(2.5, "SetDO = Digital Output\nSterowanie I/O\n(chwytak, zawory)"),
|
||||||
]
|
]
|
||||||
@ -925,18 +999,19 @@ def draw_rapid_structure() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
print(" ✓ robot_rapid_example.png")
|
_logger.info("Generated robot_rapid_example.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Main
|
# Main
|
||||||
# ============================================================
|
# ============================================================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("Generating PYTANIE 16 diagrams...")
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
_logger.info("Generating PYTANIE 16 diagrams...")
|
||||||
draw_trms_pyramid()
|
draw_trms_pyramid()
|
||||||
draw_vendor_comparison()
|
draw_vendor_comparison()
|
||||||
draw_movement_types()
|
draw_movement_types()
|
||||||
draw_online_offline()
|
draw_online_offline()
|
||||||
draw_ros_architecture()
|
draw_ros_architecture()
|
||||||
draw_rapid_structure()
|
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
|
#!/usr/bin/env python3
|
||||||
"""Generate diagrams for PYTANIE 2: Shortest path algorithms.
|
"""Generate diagrams for PYTANIE 2: Shortest path algorithms.
|
||||||
|
|
||||||
1. Graph structure — the shared example graph (A,B,C,D)
|
1. Graph structure -- the shared example graph (A,B,C,D)
|
||||||
2. Dijkstra traversal — step-by-step on that graph
|
2. Dijkstra traversal -- step-by-step on that graph
|
||||||
3. Bellman-Ford traversal — step-by-step
|
3. Bellman-Ford traversal -- step-by-step
|
||||||
4. A* traversal — step-by-step with heuristics.
|
4. A* traversal -- step-by-step with heuristics.
|
||||||
|
|
||||||
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
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
|
import matplotlib as mpl
|
||||||
|
|
||||||
mpl.use("Agg")
|
mpl.use("Agg")
|
||||||
@ -17,47 +22,65 @@ from pathlib import Path
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from matplotlib.axes import Axes
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DPI = 300
|
DPI = 300
|
||||||
BG = "white"
|
BG = "white"
|
||||||
LN = "black"
|
LN = "black"
|
||||||
FS = 8
|
FS = 8
|
||||||
FS_TITLE = 11
|
FS_TITLE = 11
|
||||||
FS_SMALL = 6.5
|
|
||||||
FS_EDGE = 9
|
FS_EDGE = 9
|
||||||
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
|
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
|
||||||
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
|
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
GRAY1 = "#E8E8E8"
|
|
||||||
GRAY2 = "#D0D0D0"
|
|
||||||
GRAY3 = "#B8B8B8"
|
GRAY3 = "#B8B8B8"
|
||||||
GRAY4 = "#F5F5F5"
|
GRAY4 = "#F5F5F5"
|
||||||
GRAY5 = "#C0C0C0"
|
|
||||||
LIGHT_GREEN = "#D5E8D4"
|
LIGHT_GREEN = "#D5E8D4"
|
||||||
LIGHT_RED = "#F8D7DA"
|
|
||||||
LIGHT_BLUE = "#D6EAF8"
|
LIGHT_BLUE = "#D6EAF8"
|
||||||
LIGHT_YELLOW = "#FFF9C4"
|
LIGHT_YELLOW = "#FFF9C4"
|
||||||
LIGHT_ORANGE = "#FFE0B2"
|
|
||||||
LIGHT_PURPLE = "#E8D5F5"
|
|
||||||
|
|
||||||
# --- Shared graph layout ---
|
NODE_POS: dict[str, tuple[float, float]] = {
|
||||||
# Graph: A--2--B--3--D, A--4--C--5--D, D--1--C (directed: C->D has weight 5)
|
"A": (1, 2),
|
||||||
NODE_POS = {"A": (1, 2), "B": (3.5, 3.2), "C": (1, 0), "D": (3.5, 0.8)}
|
"B": (3.5, 3.2),
|
||||||
EDGES = [("A", "B", 2), ("A", "C", 4), ("B", "D", 3), ("C", "D", 5)]
|
"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(
|
def draw_graph_node(
|
||||||
ax,
|
ax: Axes,
|
||||||
name,
|
name: str,
|
||||||
pos,
|
pos: tuple[float, float],
|
||||||
color="white",
|
*,
|
||||||
current=False,
|
color: str = "white",
|
||||||
visited=False,
|
current: bool = False,
|
||||||
dist_label=None,
|
visited: bool = False,
|
||||||
fontsize=12,
|
dist_label: str | None = None,
|
||||||
|
fontsize: int = 12,
|
||||||
) -> None:
|
) -> 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
|
x, y = pos
|
||||||
r = 0.35
|
radius = 0.35
|
||||||
lw = 2.5 if current else 1.5
|
lw = 2.5 if current else 1.5
|
||||||
ec = "#D32F2F" if current else LN
|
ec = "#D32F2F" if current else LN
|
||||||
fc = LIGHT_GREEN if visited else color
|
fc = LIGHT_GREEN if visited else color
|
||||||
@ -65,7 +88,13 @@ def draw_graph_node(
|
|||||||
fc = LIGHT_YELLOW
|
fc = LIGHT_YELLOW
|
||||||
|
|
||||||
circle = plt.Circle(
|
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.add_patch(circle)
|
||||||
ax.text(
|
ax.text(
|
||||||
@ -97,25 +126,52 @@ def draw_graph_node(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) -> None:
|
def draw_graph_edge(
|
||||||
"""Draw an edge between two nodes with weight label."""
|
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
|
x1, y1 = pos1
|
||||||
x2, y2 = pos2
|
x2, y2 = pos2
|
||||||
|
|
||||||
# Shorten line to not overlap node circles
|
# Shorten line to not overlap node circles
|
||||||
dx, dy = x2 - x1, y2 - y1
|
dx, dy = x2 - x1, y2 - y1
|
||||||
length = np.sqrt(dx**2 + dy**2)
|
length = np.sqrt(dx**2 + dy**2)
|
||||||
r = 0.38
|
node_radius = 0.38
|
||||||
sx = x1 + r * dx / length
|
sx = x1 + node_radius * dx / length
|
||||||
sy = y1 + r * dy / length
|
sy = y1 + node_radius * dy / length
|
||||||
ex = x2 - r * dx / length
|
ex = x2 - node_radius * dx / length
|
||||||
ey = y2 - r * dy / 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
|
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
|
# Weight label
|
||||||
mx = (x1 + x2) / 2
|
mx = (x1 + x2) / 2
|
||||||
@ -135,7 +191,9 @@ def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) ->
|
|||||||
bbox={
|
bbox={
|
||||||
"boxstyle": "round,pad=0.15",
|
"boxstyle": "round,pad=0.15",
|
||||||
"facecolor": "white",
|
"facecolor": "white",
|
||||||
"edgecolor": GRAY3 if not highlighted else color,
|
"edgecolor": (
|
||||||
|
GRAY3 if not highlighted else color
|
||||||
|
),
|
||||||
"alpha": 0.95,
|
"alpha": 0.95,
|
||||||
},
|
},
|
||||||
zorder=4,
|
zorder=4,
|
||||||
@ -143,15 +201,26 @@ def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) ->
|
|||||||
|
|
||||||
|
|
||||||
def draw_full_graph(
|
def draw_full_graph(
|
||||||
ax,
|
ax: Axes,
|
||||||
title="",
|
*,
|
||||||
dist=None,
|
title: str = "",
|
||||||
current=None,
|
dist: dict[str, str] | None = None,
|
||||||
visited=None,
|
current: str | None = None,
|
||||||
highlighted_edges=None,
|
visited: set[str] | None = None,
|
||||||
relaxed_edges=None,
|
highlighted_edges: set[tuple[str, str]] | None = None,
|
||||||
|
relaxed_edges: set[tuple[str, str]] | None = 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:
|
if visited is None:
|
||||||
visited = set()
|
visited = set()
|
||||||
if highlighted_edges is None:
|
if highlighted_edges is None:
|
||||||
@ -170,17 +239,35 @@ def draw_full_graph(
|
|||||||
|
|
||||||
# Draw edges
|
# Draw edges
|
||||||
for u, v, w in EDGES:
|
for u, v, w in EDGES:
|
||||||
hl = (u, v) in highlighted_edges or (v, u) in highlighted_edges
|
hl = (
|
||||||
rl = (u, v) in relaxed_edges or (v, u) in relaxed_edges
|
(u, v) in highlighted_edges
|
||||||
draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w, highlighted=hl, relaxed=rl)
|
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
|
# Draw nodes
|
||||||
for name, pos in NODE_POS.items():
|
for node_name, pos in NODE_POS.items():
|
||||||
is_current = name == current
|
is_current = node_name == current
|
||||||
is_visited = name in visited
|
is_visited = node_name in visited
|
||||||
d_label = dist.get(name)
|
d_label = dist.get(node_name)
|
||||||
draw_graph_node(
|
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
|
# 1. Graph structure diagram
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def draw_graph_structure() -> None:
|
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))
|
_fig, ax = plt.subplots(1, 1, figsize=(5, 4))
|
||||||
ax.set_xlim(-0.5, 5.0)
|
ax.set_xlim(-0.5, 5.0)
|
||||||
ax.set_ylim(-1.2, 4.5)
|
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_graph_edge(ax, NODE_POS[u], NODE_POS[v], w)
|
||||||
|
|
||||||
# Draw nodes
|
# Draw nodes
|
||||||
for name, pos in NODE_POS.items():
|
for node_name, pos in NODE_POS.items():
|
||||||
draw_graph_node(ax, name, pos)
|
draw_graph_node(ax, node_name, pos)
|
||||||
|
|
||||||
# Start arrow
|
# Start arrow
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
@ -241,14 +328,14 @@ def draw_graph_structure() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close()
|
plt.close()
|
||||||
print(" ✓ graph_example_structure.png")
|
_logger.info("graph_example_structure.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 2. Dijkstra traversal
|
# 2. Dijkstra traversal
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def draw_dijkstra_traversal() -> None:
|
def draw_dijkstra_traversal() -> None:
|
||||||
"""Step-by-step Dijkstra on the shared graph."""
|
"""Draw step-by-step Dijkstra on the shared graph."""
|
||||||
steps = [
|
steps = [
|
||||||
{
|
{
|
||||||
"title": "Krok 0: Inicjalizacja\nd = {A:0, B:∞, C:∞, D:∞}",
|
"title": "Krok 0: Inicjalizacja\nd = {A:0, B:∞, C:∞, D:∞}",
|
||||||
@ -259,7 +346,11 @@ def draw_dijkstra_traversal() -> None:
|
|||||||
"relaxed": set(),
|
"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": "∞"},
|
"dist": {"A": "0", "B": "2", "C": "4", "D": "∞"},
|
||||||
"current": "A",
|
"current": "A",
|
||||||
"visited": {"A"},
|
"visited": {"A"},
|
||||||
@ -267,7 +358,11 @@ def draw_dijkstra_traversal() -> None:
|
|||||||
"relaxed": {("A", "B"), ("A", "C")},
|
"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"},
|
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
|
||||||
"current": "B",
|
"current": "B",
|
||||||
"visited": {"A", "B"},
|
"visited": {"A", "B"},
|
||||||
@ -275,7 +370,11 @@ def draw_dijkstra_traversal() -> None:
|
|||||||
"relaxed": {("B", "D")},
|
"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"},
|
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
|
||||||
"current": "C",
|
"current": "C",
|
||||||
"visited": {"A", "B", "C"},
|
"visited": {"A", "B", "C"},
|
||||||
@ -283,7 +382,11 @@ def draw_dijkstra_traversal() -> None:
|
|||||||
"relaxed": set(),
|
"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"},
|
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
|
||||||
"current": None,
|
"current": None,
|
||||||
"visited": {"A", "B", "C", "D"},
|
"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, axes = plt.subplots(1, 5, figsize=(14, 3.5))
|
||||||
fig.suptitle(
|
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,
|
fontsize=FS_TITLE,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
y=1.02,
|
y=1.02,
|
||||||
@ -315,11 +419,17 @@ def draw_dijkstra_traversal() -> None:
|
|||||||
fig.text(
|
fig.text(
|
||||||
0.5,
|
0.5,
|
||||||
-0.04,
|
-0.04,
|
||||||
"[zolty] = aktualnie przetwarzany [zielony] = odwiedzony (zamkniety) "
|
"[zolty] = aktualnie przetwarzany"
|
||||||
"czerwona krawedz = relaksacja OK szara krawedz = nie poprawia",
|
" [zielony] = odwiedzony (zamkniety)"
|
||||||
|
" czerwona krawedz = relaksacja OK"
|
||||||
|
" szara krawedz = nie poprawia",
|
||||||
ha="center",
|
ha="center",
|
||||||
fontsize=FS,
|
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()
|
plt.tight_layout()
|
||||||
@ -330,18 +440,19 @@ def draw_dijkstra_traversal() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close()
|
plt.close()
|
||||||
print(" ✓ dijkstra_traversal.png")
|
_logger.info("dijkstra_traversal.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 3. Bellman-Ford traversal
|
# 3. Bellman-Ford traversal
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def draw_bellman_ford_traversal() -> None:
|
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 = plt.figure(figsize=(14, 7))
|
||||||
fig.suptitle(
|
fig.suptitle(
|
||||||
"Bellman-Ford — przejście grafu krok po kroku\n"
|
"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,
|
fontsize=FS_TITLE,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
y=0.98,
|
y=0.98,
|
||||||
@ -408,14 +519,23 @@ def draw_bellman_ford_traversal() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Negative cycle check note
|
# Negative cycle check note
|
||||||
|
neg_cycle_msg = (
|
||||||
|
"Po 3 iteracjach: sprawdz raz jeszcze"
|
||||||
|
" — nic sie nie zmienia"
|
||||||
|
" → BRAK cyklu ujemnego → wynik poprawny"
|
||||||
|
)
|
||||||
fig.text(
|
fig.text(
|
||||||
0.5,
|
0.5,
|
||||||
0.01,
|
0.01,
|
||||||
"Po 3 iteracjach: sprawdź raz jeszcze — nic się nie zmienia → BRAK cyklu ujemnego → wynik poprawny",
|
neg_cycle_msg,
|
||||||
ha="center",
|
ha="center",
|
||||||
fontsize=FS,
|
fontsize=FS,
|
||||||
fontweight="bold",
|
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])
|
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
|
||||||
@ -426,21 +546,22 @@ def draw_bellman_ford_traversal() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close()
|
plt.close()
|
||||||
print(" ✓ bellman_ford_traversal.png")
|
_logger.info("bellman_ford_traversal.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 4. A* traversal
|
# 4. A* traversal
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def draw_astar_traversal() -> None:
|
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)
|
# Heuristic values (straight-line distance to D)
|
||||||
h_vals = {"A": 4, "B": 2, "C": 3, "D": 0}
|
h_vals = {"A": 4, "B": 2, "C": 3, "D": 0}
|
||||||
|
|
||||||
fig = plt.figure(figsize=(14, 7.5))
|
fig = plt.figure(figsize=(14, 7.5))
|
||||||
fig.suptitle(
|
fig.suptitle(
|
||||||
"A* — przejście grafu krok po kroku (cel = D)\n"
|
"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,
|
fontsize=FS_TITLE,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
y=0.99,
|
y=0.99,
|
||||||
@ -520,11 +641,11 @@ def draw_astar_traversal() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Add h values as small labels
|
# Add h values as small labels
|
||||||
for name, pos in NODE_POS.items():
|
for node_name, pos in NODE_POS.items():
|
||||||
ax_g.text(
|
ax_g.text(
|
||||||
pos[0] + 0.35,
|
pos[0] + 0.35,
|
||||||
pos[1] + 0.35,
|
pos[1] + 0.35,
|
||||||
f"h={h_vals[name]}",
|
f"h={h_vals[node_name]}",
|
||||||
ha="center",
|
ha="center",
|
||||||
va="center",
|
va="center",
|
||||||
fontsize=5.5,
|
fontsize=5.5,
|
||||||
@ -558,8 +679,11 @@ def draw_astar_traversal() -> None:
|
|||||||
fig.text(
|
fig.text(
|
||||||
0.5,
|
0.5,
|
||||||
0.01,
|
0.01,
|
||||||
"A* odwiedził 3 wierzchołki (A, B, D) — POMINĄŁ C!\n"
|
"A* odwiedził 3 wierzchołki (A, B, D)"
|
||||||
"Dijkstra odwiedziłby wszystkie 4. Heurystyka h kieruje przeszukiwanie w stronę celu.",
|
" — POMINĄŁ C!\n"
|
||||||
|
"Dijkstra odwiedziłby wszystkie 4."
|
||||||
|
" Heurystyka h kieruje przeszukiwanie"
|
||||||
|
" w stronę celu.",
|
||||||
ha="center",
|
ha="center",
|
||||||
fontsize=FS,
|
fontsize=FS,
|
||||||
fontweight="bold",
|
fontweight="bold",
|
||||||
@ -578,16 +702,17 @@ def draw_astar_traversal() -> None:
|
|||||||
facecolor=BG,
|
facecolor=BG,
|
||||||
)
|
)
|
||||||
plt.close()
|
plt.close()
|
||||||
print(" ✓ astar_traversal.png")
|
_logger.info("astar_traversal.png")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Main
|
# Main
|
||||||
# ============================================================
|
# ============================================================
|
||||||
if __name__ == "__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_graph_structure()
|
||||||
draw_dijkstra_traversal()
|
draw_dijkstra_traversal()
|
||||||
draw_bellman_ford_traversal()
|
draw_bellman_ford_traversal()
|
||||||
draw_astar_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.
|
Placed in pytania/questions/ folder.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -13,6 +16,9 @@ SOURCE = str(Path(SCRIPT_DIR) / "OBRONA_MAGISTERSKA_ODPOWIEDZI.md")
|
|||||||
OUT_DIR = str(Path(SCRIPT_DIR) / "questions")
|
OUT_DIR = str(Path(SCRIPT_DIR) / "questions")
|
||||||
Path(OUT_DIR).mkdir(parents=True, exist_ok=True)
|
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:
|
with Path(SOURCE).open(encoding="utf-8") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
@ -25,7 +31,7 @@ for i, line in enumerate(lines):
|
|||||||
title = m.group(3).strip()
|
title = m.group(3).strip()
|
||||||
question_starts.append((i, raw_num, title))
|
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):
|
for idx, (start_line, raw_num, title) in enumerate(question_starts):
|
||||||
# End = next question start or EOF
|
# 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)
|
f.writelines(content_lines)
|
||||||
|
|
||||||
line_count = len(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