feat: added scripts for generating images for praca magisterska obrona

This commit is contained in:
Krzysztof kuhy Rudnicki 2026-02-22 13:08:14 +01:00
parent 86bc592791
commit 592a50f1d2
174 changed files with 24217 additions and 0 deletions

View File

@ -0,0 +1,268 @@
## PYTANIE 2: Algorytmy najkrótszej ścieżki (AISDI)
**Omówić i porównać algorytmy: Dijkstry, Bellmana-Forda, A\*.**
---
### Tło pojęciowe — słowniczek
**Graf** — struktura danych składająca się z **wierzchołków** (vertices/nodes) połączonych **krawędziami** (edges). Np. mapa miast: miasta = wierzchołki, drogi = krawędzie.
**Wierzchołek (vertex, node)** — punkt w grafie. Oznaczany jako v, u, n, m itp. **V** = zbiór wszystkich wierzchołków; |V| = ich liczba.
**Krawędź (edge)** — połączenie między dwoma wierzchołkami. **E** = zbiór krawędzi; |E| = ich liczba. Krawędź może być skierowana (A→B ≠ B→A) lub nieskierowana (A↔B).
**Waga (weight)** — liczba przypisana do krawędzi, oznaczająca „koszt" przejścia. Np. odległość w km, czas podróży, opłata za przejazd. Graf z wagami = graf ważony.
**Koszt (cost)** — ogólne pojęcie „ceny" przejścia ścieżką. Koszt ścieżki = suma wag krawędzi na tej ścieżce. Cel algorytmów: znaleźć ścieżkę o **minimalnym koszcie**.
**SSSP (Single-Source Shortest Path)** — problem: mając JEDEN wierzchołek startowy (źródło), znajdź najkrótsze ścieżki do WSZYSTKICH pozostałych wierzchołków. Dijkstra i Bellman-Ford rozwiązują SSSP. **Single-Pair** — prostszy problem: znajdź najkrótszą ścieżkę z A do B (jednej konkretnej pary). A\* rozwiązuje Single-Pair.
**d[v]** — tablica odległości. **d** = tablica (array), **v** = wierzchołek. d[v] przechowuje aktualnie najlepsze znane oszacowanie odległości od źródła do wierzchołka v. Na początku d[start] = 0, d[wszystko inne] = ∞. Algorytm stopniowo poprawia te wartości.
**Zachłanny (greedy)** — strategia algorytmiczna: w każdym kroku wybierz opcję, która TERAZ wygląda najlepiej (lokalnie optymalna), bez cofania się. Dijkstra jest zachłanny: zawsze bierze wierzchołek o najmniejszym d[v] i nigdy go nie rewiduje.
**Relaksacja krawędzi (edge relaxation)** — kluczowa operacja. Sprawdza: „czy droga do v przez u jest krótsza niż dotychczas znana?" Jeśli d[u] + waga(u,v) < d[v], to zaktualizuj d[v]. Nazwa od rozluźniania" górne ograniczenie na odległość się rozluźnia" (maleje) w stronę prawdziwej wartości.
**Tablica (array)** — najprostsza struktura danych: ciągły blok pamięci. W Dijkstrze z tablicą: szukanie minimum d[v] wymaga przejrzenia WSZYSTKICH wierzchołków → O(V) na szukanie × V razy = **O(V²)**.
Przykład — graf z 4 wierzchołkami (A, B, C, D), start = A:
![Graf przykładowy — 4 wierzchołki z wagami](img/graph_example_structure.png)
d = [ A:0, B:∞, C:∞, D:∞ ] ← tablica na starcie
odwiedzone = {}
Krok 1: przeszukaj CAŁĄ tablicę d → min = A (0)
d = [ A:0, B:2, C:4, D:∞ ] odw = {A}
↑ ↑
A→B=2 A→C=4 (relaksacja sąsiadów A)
Krok 2: przeszukaj CAŁĄ tablicę d (poza odw.) → min = B (2)
d = [ A:0, B:2, C:4, D:5 ] odw = {A,B}
B→D=2+3=5 (relaksacja)
Krok 3: przeszukaj tablicę → min = C (4)
d = [ A:0, B:2, C:4, D:5 ] odw = {A,B,C}
C→D=4+5=9 > 5, nie zmieniaj
Krok 4: min = D (5). Koniec! d = [A:0, B:2, C:4, D:5]
Każdy krok = przejrzyj V elementów → 4 kroki × 4 elementy = 16 operacji = O(V²)
**Kopiec (heap)** — drzewiasta struktura danych, w której element minimalny jest zawsze na szczycie. Wyciąganie minimum: O(log n). W Dijkstrze z kopcem: szukanie min d[v] to O(log V) zamiast O(V) → **O((V+E) log V)**.
Przykład — ten sam graf, ale z kopcem (min-heap):
Kopiec na starcie: (0,A) ← min zawsze na szczycie
(reszta to ∞)
Krok 1: pop (0,A) — O(log 4)=O(2), relaksuj sąsiadów:
push (2,B), push (4,C)
Kopiec: (2,B)
/ \
(4,C) ...
Krok 2: pop (2,B) — O(log 4), relaksuj:
push (5,D)
Kopiec: (4,C)
/
(5,D)
Krok 3: pop (4,C) — O(log 4). C→D: 9 > 5, nie zmieniaj.
Krok 4: pop (5,D) — O(log 4). Koniec!
Każdy pop = O(log V), każdy push = O(log V)
V popów + E pushów = O((V+E) log V)
**Kopiec Fibonacciego** — zaawansowany kopiec, w którym operacja „zmniejsz klucz" (decrease-key) działa w zamortyzowanym O(1) zamiast O(log V). Dijkstra robi decrease-key dla każdej krawędzi → z kopcem Fib: **O(V log V + E)** — E operacji po O(1) + V wyciągnięć po O(log V).
Przykład — kluczowa różnica: decrease-key:
Zwykły kopiec — gdy znajdziesz krótszą drogę do D:
d[D] zmienia się z 9 na 5
Trzeba „naprawić" kopiec: przesuwaj D w górę → O(log V)
Kopiec Fibonacciego — ta sama sytuacja:
d[D] zmienia się z 9 na 5
Po prostu odetnij D od rodzica i wstaw do listy korzeni → O(1)!
(naprawienie struktury odłożone na później — „zamortyzowane")
Różnica ma znaczenie przy GĘSTYCH grafach (E >> V):
- Zwykły kopiec: E × O(log V) = O(E log V) na decrease-key
- Kopiec Fib: E × O(1) = O(E) na decrease-key
Razem: O(V log V) [pop] + O(E) [decrease-key] = O(V log V + E)
**Złożoność — dlaczego takie wartości:**
- **O(V²)** z tablicą: V razy szukaj minimum (O(V) każdy) = V × V.
- **O((V+E) log V)** z kopcem: V wyciągnięć min (O(log V)) + E relaksacji z decrease-key (O(log V)).
- **O(V log V + E)** z kopcem Fib: V wyciągnięć min (O(log V)) + E decrease-key (O(1) zamortyzowane).
**Programowanie dynamiczne (DP)** — technika rozwiązywania problemów przez rozbicie na mniejsze podproblemy i zapamiętywanie wyników (żeby nie liczyć tego samego dwa razy). Bellman-Ford jest DP: podproblem = „najkrótsza ścieżka do v używająca ≤ k krawędzi"; rozwiązuje dla k = 1, 2, ..., V1.
**Cykl** — ścieżka w grafie, która wraca do punktu wyjścia (A → B → C → A). **Cykl ujemny** — cykl, w którym suma wag < 0. Problem: za każdym obejściem cyklu odległość" maleje można iść w nieskończoność najkrótsza ścieżka nie istnieje (= −∞).
**Dlaczego O(V·E) w Bellman-Ford:** Algorytm wykonuje |V|1 iteracji (bo najdłuższa najkrótsza ścieżka bez cykli ma co najwyżej V1 krawędzi). W każdej iteracji relaksuje WSZYSTKIE |E| krawędzi. Razem: (V1) × E ≈ O(V·E).
**Heurystyczny** — wykorzystujący przybliżone oszacowanie (heurystykę) zamiast dokładnych obliczeń. A\* jest heurystyczny: używa funkcji h(n) do zgadywania „ile jeszcze do celu".
**f(n), g(n), h(n) — co oznacza n i każda funkcja:**
- **n** = aktualnie rozpatrywany wierzchołek.
- **g(n)** = dotychczasowy koszt dotarcia od startu do n (znany, dokładny).
- **h(n)** = heurystyka: OSZACOWANIE kosztu od n do celu (przybliżone, „zgadywane"). Np. odległość w linii prostej do celu.
- **f(n) = g(n) + h(n)** = oszacowanie CAŁKOWITEGO kosztu ścieżki przez n. A\* zawsze rozwija wierzchołek o najniższym f(n).
**Dopuszczalna (admissible)** — heurystyka h jest dopuszczalna, jeśli NIGDY nie przeszacowuje: h(n) ≤ prawdziwy koszt od n do celu. Gwarantuje, że A\* znajdzie optymalną ścieżkę. Np. odległość w linii prostej jest dopuszczalna (nie da się dojechać krócej niż prosto).
**Rzeczywisty koszt** — prawdziwa najkrótsza odległość (nie oszacowanie). Np. faktyczna najkrótsza droga od n do celu, uwzględniając wszystkie krawędzie.
**n → cel** — od wierzchołka n do wierzchołka docelowego (cel = destination = target).
**Spójna (consistent / monotone)** — silniejszy warunek na heurystykę: h(n) ≤ w(n,m) + h(m) dla każdej krawędzi n→m. Tu **w(n,m)** = waga krawędzi z n do m, a **m** = sąsiad n. Spójność oznacza: oszacowanie z n nie jest „dużo lepsze" niż to co uzyskasz idąc jeden krok do m. Spójna ⇒ dopuszczalna (ale nie odwrotnie).
**Dlaczego O(V) w najlepszym przypadku A\*:** Jeśli heurystyka jest idealna (h(n) = prawdziwy koszt), A* idzie prosto do celu, nie eksplorując zbędnych wierzchołków — odwiedza tylko te na optymalnej ścieżce ≈ O(V) jeśli ścieżka krótka. **Najgorszy przypadek** = h(n) = 0 dla wszystkich n → A* degeneruje się do Dijkstry.
### Pseudokod (Python)
**Dijkstra** (graph = słownik sąsiedztwa, np. `{'A': [('B',2), ('C',4)]}`):
def dijkstra(graph, source):
dist = {v: float('inf') for v in graph}
dist[source] = 0
visited = set()
for _ in range(len(graph)):
current = None # szukaj nieodwiedzonego wierzchołka o min dist — O(V)
for v in graph:
if v not in visited and (current is None or dist[v] < dist[current]):
current = v
if dist[current] == float('inf'):
break # reszta nieosiągalna
visited.add(current) # zamknij — NIE wracamy (zachłanność)
for neighbor, weight in graph[current]: # relaksacja sąsiadów
if dist[current] + weight < dist[neighbor]:
dist[neighbor] = dist[current] + weight
return dist # O(V²) z tablicą
![Przejście grafu algorytmem Dijkstry — krok po kroku](img/dijkstra_traversal.png)
**Bellman-Ford** (vertices = lista wierzchołków, edges = lista krotek (src, dst, weight)):
def bellman_ford(vertices, edges, source):
dist = {v: float('inf') for v in vertices}
dist[source] = 0
for _ in range(len(vertices) - 1): # V1 iteracji (najdłuższa ścieżka = V1 krawędzi)
for src, dst, weight in edges: # relaksuj WSZYSTKIE krawędzie
if dist[src] + weight < dist[dst]:
dist[dst] = dist[src] + weight
for src, dst, weight in edges: # V-ta iteracja: wykrywanie cyklu ujemnego
if dist[src] + weight < dist[dst]:
return None # cykl ujemny!
return dist # O(V·E)
Przykład — graf z ujemnymi wagami (Dijkstra daje ZŁY wynik, B-F poprawny):
Graf: S→A(2), A→C(3), S→B(5), B→A(4)
Dijkstra:
1. S(0): dist[A]=2, dist[B]=5
2. A(2) zamknięty: dist[C]=5
3. B(5): B→A = 54 = 1 < 2, ALE A już zamknięty POMIJA!
Wynik: A=2, C=5 ← BŁĄD (prawidłowe: A=1, C=4)
Bellman-Ford — relaksuje WSZYSTKIE krawędzie, V1 = 3 razy:
Start: dist = [S:0, A:∞, B:∞, C:∞]
Iteracja 1:
S→A: 0+2=2 < ∞ → A=2
A→C: 2+3=5 < ∞ → C=5
S→B: 0+5=5 < ∞ → B=5
B→A: 54=1 < 2 A=1 ujemna waga poprawia!
Iteracja 2:
A→C: 1+3=4 < 5 C=4 propagacja poprawionego A
Iteracja 3: brak zmian → stabilne.
Wynik: [S:0, A:1, B:5, C:4] ← POPRAWNE
Wykrywanie cyklu ujemnego — dodaj krawędź C→B(3):
Cykl B→A→C→B = 4 + 3 + (3) = 4 < 0.
Po V1 iteracjach dist nadal maleje → V-ta iteracja:
dist[src] + weight < dist[dst] return None
![Bellman-Ford — ujemne wagi vs Dijkstra](img/bellman_ford_negative_weights.png)
![Bellman-Ford — wykrywanie cyklu ujemnego](img/bellman_ford_negative_cycle.png)
![Przejście grafu algorytmem Bellmana-Forda — krok po kroku](img/bellman_ford_traversal.png)
**A\*** (graph jak Dijkstra; heuristic = h(v) → oszacowanie odl. do celu):
def a_star(graph, source, goal, heuristic):
cost_so_far = {source: 0} # g(n) — faktyczny koszt dotarcia
priority = {source: heuristic(source)} # f(n) = g(n) + h(n)
came_from = {} # do odtworzenia ścieżki
visited = set()
while priority:
current = min(priority, key=priority.get) # wierzchołek o min f(n)
del priority[current]
if current == goal:
break # dotarliśmy — A* kończy (Dijkstra przetworzyłby wszystko)
visited.add(current)
for neighbor, weight in graph[current]:
if neighbor in visited:
continue
new_cost = cost_so_far[current] + weight
if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]:
cost_so_far[neighbor] = new_cost
priority[neighbor] = new_cost + heuristic(neighbor)
came_from[neighbor] = current
return came_from, cost_so_far.get(goal) # ścieżka + koszt
![Przejście grafu algorytmem A* — krok po kroku](img/astar_traversal.png)
---
### Dijkstra — zachłanny, SSSP
**Ograniczenie:** wagi ≥ 0.
**Idea:** Relaksacja krawędzi; zawsze przetwarzaj wierzchołek o najmniejszym d[v].
**Złożoność:** O(V²) z tablicą, O((V+E) log V) z kopcem, O(V log V + E) z kopcem Fibonacciego.
**Dlaczego nie ujemne wagi?** Raz oznaczony wierzchołek nie jest rewidowany — ujemna krawędź może go poprawić.
### Bellman-Ford — programowanie dynamiczne, SSSP
**Zaleta:** obsługuje ujemne wagi + **wykrywa cykle ujemne**.
**Idea:** |V|1 iteracji relaksacji WSZYSTKICH krawędzi. Jeśli w iteracji V nadal można poprawić → cykl ujemny.
**Złożoność:** O(V·E) — zawsze.
### A\* — heurystyczny, Single-Pair
**Rozszerzenie Dijkstry:** f(n) = g(n) + h(n), gdzie h(n) to heurystyka.
**Wymóg:** h dopuszczalna (admissible): h(n) ≤ rzeczywisty koszt n→cel. Jeśli h spójna (consistent): h(n) ≤ w(n,m) + h(m) — optymalne.
**Złożoność:** zależy od h; najlepszy przypadek O(V), najgorszy jak Dijkstra.
### Porównanie
| Cecha | Dijkstra | Bellman-Ford | A\* |
| -------------- | ------------- | ---------------- | ------------ |
| Typ | Zachłanny | Prog. dynamiczne | Heurystyczny |
| Problem | SSSP | SSSP | Single-pair |
| Ujemne wagi | NIE | TAK | NIE |
| Wykrywa cykle- | NIE | TAK | NIE |
| Złożoność | O((V+E)log V) | O(VE) | Zależy od h |
### Etymologia
**Dijkstra** — Edsger W. Dijkstra (Holandia, 1959); pionier informatyki (Turing Award 1972). **Bellman-Ford** — Richard Bellman (twórca programowania dynamicznego) + Lester Ford Jr. (1956). **A\*** — Hart, Nilsson, Raphael (Stanford, 1968); „A\*" = ulepszona wersja algorytmu „A". **Zachłanny (Greedy)** — algorytm „chciwie" bierze lokalnie najlepszą opcję. **SSSP** — Single-Source Shortest Path. **Programowanie dynamiczne** — Bellman wybrał „dynamic" by brzmiało imponująco dla polityków (nie miało związku z dynamiką!). **Heurystyka** — grec. „heuriskein" = znajdować (to samo co „Eureka!" Archimedesa). **Relaksacja** — „rozluźnianie" górnego ograniczenia na odległość d[v].
### Jak zapamiętać
- **Dijkstra = chciwy**, bierze minimum — ale „nie patrzy wstecz" (stąd problem z ujemnymi wagami)
- **Bellman-Ford = brute force x (V1)** — relaksuj wszystko, V1 razy, bo najdłuższa ścieżka ma V1 krawędzi
- **A\* = Dijkstra + „GPS"** — heurystyka mówi w którą stronę jest cel

View File

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""Approach 1: STRICT FILTERING ONLY.
- Only include cards with answers > 100 characters
- No changes to extraction logic.
"""
from __future__ import annotations
from pathlib import Path
import re
def clean_text(text) -> str:
"""Clean text."""
if not text:
return ""
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<i>\1</i>", text)
text = text.replace("\t", " ")
text = text.replace('"', "&quot;")
text = re.sub(r" +", " ", text)
return text.strip()
def extract_cards(filepath) -> list[dict[str, str]]:
"""Extract cards."""
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
cards = []
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
num = match.group(1) if match else "00"
subj_match = re.search(r"Przedmiot:\s*(\w+)", content)
subject = subj_match.group(1) if subj_match else "Ogólne"
base_tags = f"egzamin pyt{num} {subject}"
# Main question
q_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
if q_match:
main_q = re.sub(r"\s+", " ", q_match.group(1).strip())
# Simple extraction - headers as answer
answer_match = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## |\Z)", content, re.DOTALL
)
if answer_match:
headers = re.findall(
r"^### (?:\d+\.\s*)?(.+)$", answer_match.group(1), re.MULTILINE
)
if headers:
answer = (
"<ul>"
+ "".join([f"<li>{clean_text(h)}</li>" for h in headers[:6]])
+ "</ul>"
)
cards.append(
{"front": clean_text(main_q), "back": answer, "tags": base_tags}
)
# Detail cards - simple extraction
sections = re.findall(
r"^### (?:\d+\.\s*)?([^\n]+)\n((?:(?!^### ).)*)",
content,
re.MULTILINE | re.DOTALL,
)
for header, body in sections:
header = header.strip()
body = body.strip()
if len(body) < 50:
continue
# Get first paragraph
paras = [
p.strip()
for p in body.split("\n\n")
if p.strip() and not p.startswith("```")
]
if paras:
answer = clean_text(paras[0][:400])
cards.append(
{
"front": f"Wyjaśnij: {clean_text(header)}",
"back": answer,
"tags": base_tags,
}
)
return cards
def main() -> None:
"""Main."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
output_file = Path("/home/kuchy/praca_magisterska/pytania/anki_1_strict_filter.txt")
all_cards = []
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
all_cards.extend(extract_cards(md_file))
# APPROACH 1: Strict filtering - only cards with answer > 100 chars
filtered_cards = [c for c in all_cards if len(c["back"]) > 100]
# Remove duplicates
seen = set()
unique = []
for c in filtered_cards:
if c["front"][:80] not in seen:
seen.add(c["front"][:80])
unique.append(c)
with Path(output_file).open("w", encoding="utf-8") as f:
f.write(
"#separator:Tab\n#html:true\n#notetype:Basic\n#deck:Egzamin_1_StrictFilter\n\n"
)
for c in unique:
f.write(f"{c['front']}\t{c['back']}\t{c['tags']}\n")
print(f"✅ Approach 1 (Strict Filter): {len(unique)} cards -> {output_file.name}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Approach 2: BETTER EXTRACTION ONLY.
- Improved algorithm to get more complete content
- No minimum length filtering.
"""
from __future__ import annotations
from pathlib import Path
import re
def clean_text(text) -> str:
"""Clean text."""
if not text:
return ""
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<i>\1</i>", text)
text = text.replace("\t", " ")
text = text.replace('"', "&quot;")
text = re.sub(r" +", " ", text)
return text.strip()
def extract_structured_content(body) -> str | None:
"""Better extraction - look for multiple content types."""
parts = []
# 1. Look for definitions
def_match = re.search(r"#### Definicja[^\n]*\n([^\n#]+)", body)
if def_match:
parts.append(f"<b>Definicja:</b> {def_match.group(1).strip()}")
# 2. Look for bullet points with bold terms
bullets = re.findall(r"[-•]\s*\*\*([^*]+)\*\*[:\s-]*([^\n]*)", body)
for term, desc in bullets[:5]:
if desc.strip():
parts.append(f"• <b>{term}</b>: {desc.strip()}")
else:
parts.append(f"• <b>{term}</b>")
# 3. Look for key-value patterns
if not parts:
kvs = re.findall(r"\*\*([^*]+)\*\*\s*[-:]\s*([^\n*]+)", body)
for k, v in kvs[:4]:
parts.append(f"<b>{k}</b>: {v.strip()}")
# 4. Get paragraphs as fallback
if not parts:
paras = [
p.strip()
for p in body.split("\n\n")
if p.strip()
and not p.startswith("```")
and not p.startswith("|")
and len(p.strip()) > 30
]
for p in paras[:2]:
parts.append(p[:300])
return "<br>".join([clean_text(p) for p in parts]) if parts else None
def extract_cards(filepath) -> list[dict[str, str]]:
"""Extract cards."""
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
cards = []
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
num = match.group(1) if match else "00"
subj_match = re.search(r"Przedmiot:\s*(\w+)", content)
subject = subj_match.group(1) if subj_match else "Ogólne"
base_tags = f"egzamin pyt{num} {subject}"
# Main question with better extraction
q_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
if q_match:
main_q = re.sub(r"\s+", " ", q_match.group(1).strip())
answer_match = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## |\Z)", content, re.DOTALL
)
if answer_match:
answer = extract_structured_content(answer_match.group(1))
if answer:
cards.append(
{"front": clean_text(main_q), "back": answer, "tags": base_tags}
)
# Detail cards with better extraction
sections = re.findall(
r"^### (?:\d+\.\s*)?([^\n]+)\n((?:(?!^### ).)*)",
content,
re.MULTILINE | re.DOTALL,
)
for header, body in sections:
header = header.strip()
if "Przykład" in header or '"' in header or len(body) < 50:
continue
answer = extract_structured_content(body)
if answer:
cards.append(
{
"front": f"Wyjaśnij: {clean_text(header)}",
"back": answer,
"tags": base_tags,
}
)
return cards
def main() -> None:
"""Main."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
output_file = Path(
"/home/kuchy/praca_magisterska/pytania/anki_2_better_extract.txt"
)
all_cards = []
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
all_cards.extend(extract_cards(md_file))
# No filtering - just dedupe
seen = set()
unique = []
for c in all_cards:
if c["front"][:80] not in seen:
seen.add(c["front"][:80])
unique.append(c)
with Path(output_file).open("w", encoding="utf-8") as f:
f.write(
"#separator:Tab\n#html:true\n#notetype:Basic\n#deck:Egzamin_2_BetterExtract\n\n"
)
for c in unique:
f.write(f"{c['front']}\t{c['back']}\t{c['tags']}\n")
print(
f"✅ Approach 2 (Better Extraction): {len(unique)} cards -> {output_file.name}"
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,426 @@
#!/usr/bin/env python3
"""Anki Generator - Modular approach with 3 combinable strategies.
Usage:
python anki_generator.py [options]
Options:
--filter Apply strict filtering (answers > 100 chars)
--extract Use improved extraction algorithm
--main-only Only generate main exam questions (45 comprehensive cards)
Combinations:
python anki_generator.py # Basic extraction, no filter
python anki_generator.py --filter # Approach 1: Strict filter only
python anki_generator.py --extract # Approach 2: Better extraction only
python anki_generator.py --main-only # Approach 3: Main questions only
python anki_generator.py --filter --extract # Approach 4: Filter + Better extraction
python anki_generator.py --filter --main-only # Approach 5: Filter + Main only
python anki_generator.py --extract --main-only # Approach 6: Better extraction + Main only
python anki_generator.py --filter --extract --main-only # Approach 7: All three
"""
from __future__ import annotations
import argparse
from pathlib import Path
import re
# =============================================================================
# SHARED UTILITIES
# =============================================================================
def clean_text(text) -> str:
"""Clean and format text for Anki."""
if not text:
return ""
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<i>\1</i>", text)
text = text.replace("\t", " ")
text = text.replace('"', "&quot;")
text = re.sub(r" +", " ", text)
return text.strip()
def get_file_metadata(filepath) -> tuple[str, str, str]:
"""Extract question number and subject from filename."""
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
num = match.group(1) if match else "00"
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
subj_match = re.search(r"Przedmiot:\s*(\w+)", content)
subject = subj_match.group(1) if subj_match else "Ogólne"
return num, subject, content
def get_main_question(content) -> str | None:
"""Extract the main exam question."""
q_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
if q_match:
return re.sub(r"\s+", " ", q_match.group(1).strip())
return None
# =============================================================================
# APPROACH 1: STRICT FILTERING
# =============================================================================
def apply_strict_filter(cards, min_length=100) -> list[dict[str, str]]:
"""Filter cards to only include those with answers > min_length characters."""
return [c for c in cards if len(c["back"]) > min_length]
# =============================================================================
# APPROACH 2: BETTER EXTRACTION
# =============================================================================
def extract_structured_content(body) -> str | None:
"""Improved extraction - multiple content types with better formatting."""
parts = []
# 1. Definitions
def_match = re.search(r"#### Definicja[^\n]*\n([^\n#]+)", body)
if def_match:
parts.append(f"<b>Definicja:</b> {def_match.group(1).strip()}")
# 2. Bullet points with bold terms
bullets = re.findall(r"[-•]\s*\*\*([^*]+)\*\*[:\s-]*([^\n]*)", body)
for term, desc in bullets[:5]:
if desc.strip():
parts.append(f"• <b>{term}</b>: {desc.strip()}")
else:
parts.append(f"• <b>{term}</b>")
# 3. Key-value patterns
if len(parts) < 2:
kvs = re.findall(r"\*\*([^*\n]+)\*\*\s*[--:]\s*([^\n*]{10,150})", body)
for k, v in kvs[:4]:
entry = f"<b>{k.strip()}</b>: {v.strip()}"
if entry not in parts:
parts.append(entry)
# 4. Paragraphs as fallback
if not parts:
paras = [
p.strip()
for p in body.split("\n\n")
if p.strip()
and not p.startswith("```")
and not p.startswith("|")
and len(p.strip()) > 30
]
for p in paras[:2]:
parts.append(p[:300])
return "<br>".join([clean_text(p) for p in parts]) if parts else None
def extract_cards_better(filepath) -> list[dict[str, str]]:
"""Extract cards with improved algorithm."""
num, subject, content = get_file_metadata(filepath)
base_tags = f"egzamin pyt{num} {subject}"
cards = []
# Main question
main_q = get_main_question(content)
if main_q:
answer_match = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## [^<5E>]|\Z)", content, re.DOTALL
)
if answer_match:
answer = extract_structured_content(answer_match.group(1))
if answer:
cards.append(
{
"front": clean_text(main_q),
"back": answer,
"tags": f"{base_tags} main",
}
)
# Detail sections
sections = re.findall(
r"^### (?:\d+\.\s*)?([^\n]+)\n((?:(?!^### ).)*)",
content,
re.MULTILINE | re.DOTALL,
)
for header, body in sections:
header = header.strip()
if (
"Przykład" in header
or '"' in header
or "Mnemonic" in header
or len(body) < 50
):
continue
answer = extract_structured_content(body)
if answer:
cards.append(
{
"front": f"Wyjaśnij: {clean_text(header)}",
"back": answer,
"tags": f"{base_tags} detail",
}
)
return cards
def extract_cards_basic(filepath) -> list[dict[str, str]]:
"""Basic extraction - simpler algorithm."""
num, subject, content = get_file_metadata(filepath)
base_tags = f"egzamin pyt{num} {subject}"
cards = []
# Main question - just headers
main_q = get_main_question(content)
if main_q:
answer_match = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## |\Z)", content, re.DOTALL
)
if answer_match:
headers = re.findall(
r"^### (?:\d+\.\s*)?(.+)$", answer_match.group(1), re.MULTILINE
)
if headers:
answer = (
"<ul>"
+ "".join([f"<li>{clean_text(h)}</li>" for h in headers[:6]])
+ "</ul>"
)
cards.append(
{
"front": clean_text(main_q),
"back": answer,
"tags": f"{base_tags} main",
}
)
# Detail sections - first paragraph only
sections = re.findall(
r"^### (?:\d+\.\s*)?([^\n]+)\n((?:(?!^### ).)*)",
content,
re.MULTILINE | re.DOTALL,
)
for header, body in sections:
header = header.strip()
body = body.strip()
if len(body) < 50 or "Przykład" in header:
continue
paras = [
p.strip()
for p in body.split("\n\n")
if p.strip() and not p.startswith("```")
]
if paras:
answer = clean_text(paras[0][:400])
cards.append(
{
"front": f"Wyjaśnij: {clean_text(header)}",
"back": answer,
"tags": f"{base_tags} detail",
}
)
return cards
# =============================================================================
# APPROACH 3: MAIN QUESTIONS ONLY
# =============================================================================
def extract_main_only(filepath) -> list[dict[str, str]]:
"""Extract only the main exam question with comprehensive answer."""
num, subject, content = get_file_metadata(filepath)
base_tags = f"egzamin pyt{num} {subject} main"
main_q = get_main_question(content)
if not main_q:
return []
# Build comprehensive answer from multiple sections
answer_parts = []
# Get main answer section
answer_match = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## [^<5E>]|\Z)", content, re.DOTALL
)
if answer_match:
section = answer_match.group(1)
# Get all ### headers with their first substantive content
headers = re.findall(
r"^### (?:\d+\.\s*)?([^\n]+)\n((?:(?!^### ).)*)",
section,
re.MULTILINE | re.DOTALL,
)
for header, body in headers[:5]:
header = header.strip()
if "Przykład" in header or "Mnemonic" in header or '"' in header:
continue
# Get key point from this section
key_point = None
# Try to get a definition or first bullet
def_match = re.search(
r"Rozpoznawana klasa języków\s*\n\s*\*\*([^*]+)\*\*", body
)
if def_match:
key_point = def_match.group(1).strip()
if not key_point:
bullets = re.findall(r"[-•]\s*\*\*([^*]+)\*\*[:\s-]*([^\n]*)", body)
if bullets:
term, desc = bullets[0]
key_point = f"{term}: {desc.strip()}" if desc.strip() else term
if not key_point:
para_match = re.search(r"\n\n([^#\n\-•|`][^\n]{20,150})", body)
if para_match:
key_point = para_match.group(1).strip()
if key_point:
answer_parts.append(f"<b>{header}</b>: {key_point}")
if answer_parts:
answer = "<br><br>".join([clean_text(p) for p in answer_parts])
return [{"front": clean_text(main_q), "back": answer, "tags": base_tags}]
return []
# =============================================================================
# MAIN GENERATOR
# =============================================================================
def generate_anki(use_filter=False, use_better_extract=False, main_only=False) -> Path:
"""Generate Anki deck with specified approaches."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
# Determine output filename based on options
suffix_parts = []
if use_filter:
suffix_parts.append("filter")
if use_better_extract:
suffix_parts.append("extract")
if main_only:
suffix_parts.append("main")
suffix = "_".join(suffix_parts) if suffix_parts else "basic"
output_file = Path(f"/home/kuchy/praca_magisterska/pytania/anki_{suffix}.txt")
deck_name = f"Egzamin_{suffix.replace('_', '+')}"
all_cards = []
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
if main_only:
# Approach 3: Only main questions
cards = extract_main_only(md_file)
elif use_better_extract:
# Approach 2: Better extraction
cards = extract_cards_better(md_file)
else:
# Basic extraction
cards = extract_cards_basic(md_file)
all_cards.extend(cards)
# Approach 1: Apply filtering if requested
if use_filter:
all_cards = apply_strict_filter(all_cards, min_length=100)
# Remove duplicates
seen = set()
unique = []
for c in all_cards:
key = c["front"][:80]
if key not in seen:
seen.add(key)
unique.append(c)
# Write output
with Path(output_file).open("w", encoding="utf-8") as f:
f.write(f"#separator:Tab\n#html:true\n#notetype:Basic\n#deck:{deck_name}\n\n")
for c in unique:
f.write(f"{c['front']}\t{c['back']}\t{c['tags']}\n")
# Statistics
lengths = [len(c["back"]) for c in unique]
short = sum(1 for l in lengths if l < 50)
medium = sum(1 for l in lengths if 50 <= l < 150)
good = sum(1 for l in lengths if l >= 150)
print(f"✅ Generated: {output_file.name}")
print(f" Cards: {len(unique)}")
print(f" Quality: {short} short / {medium} medium / {good} good")
print()
return output_file
def main() -> None:
"""Main."""
parser = argparse.ArgumentParser(
description="Generate Anki flashcards with modular approaches"
)
parser.add_argument(
"--filter",
action="store_true",
help="Approach 1: Strict filtering (>100 chars)",
)
parser.add_argument(
"--extract", action="store_true", help="Approach 2: Better extraction algorithm"
)
parser.add_argument(
"--main-only", action="store_true", help="Approach 3: Main exam questions only"
)
parser.add_argument(
"--all-combinations", action="store_true", help="Generate all 7 combinations"
)
args = parser.parse_args()
if args.all_combinations:
# Generate all 7 combinations
print("=" * 60)
print("Generating all 7 combinations...")
print("=" * 60 + "\n")
combinations = [
(True, False, False), # 1: Filter only
(False, True, False), # 2: Extract only
(False, False, True), # 3: Main only
(True, True, False), # 4: Filter + Extract
(True, False, True), # 5: Filter + Main
(False, True, True), # 6: Extract + Main
(True, True, True), # 7: All three
]
for i, (f, e, m) in enumerate(combinations, 1):
print(f"--- Combination {i} (filter={f}, extract={e}, main={m}) ---")
generate_anki(use_filter=f, use_better_extract=e, main_only=m)
else:
generate_anki(
use_filter=args.filter,
use_better_extract=args.extract,
main_only=args.main_only,
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,767 @@
#!/usr/bin/env python3
"""Generate diagrams for PYTANIE 15: Agent upostaciowiony w robotyce.
Diagrams:
1. See-Think-Act cycle of an embodied agent
2. 3T Architecture (Planner / Sequencer / Controller)
3. Behavior Tree example (robot pick-and-place)
4. BDI model flow
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
"""
from __future__ import annotations
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import matplotlib.pyplot as plt
DPI = 300
BG = "white"
LN = "black"
FS = 8
FS_TITLE = 11
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
GRAY5 = "#C0C0C0"
def draw_box(
ax,
x,
y,
w,
h,
text,
fill="white",
lw=1.2,
fontsize=FS,
fontweight="normal",
ha="center",
va="center",
rounded=True,
) -> None:
"""Draw box."""
if rounded:
rect = FancyBboxPatch(
(x, y), w, h, boxstyle="round,pad=0.05", lw=lw, edgecolor=LN, facecolor=fill
)
else:
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
ax.add_patch(rect)
ax.text(
x + w / 2,
y + h / 2,
text,
ha=ha,
va=va,
fontsize=fontsize,
fontweight=fontweight,
wrap=True,
)
def draw_arrow(
ax, x1, y1, x2, y2, lw=1.2, style="->", color=LN, label="", label_offset=0.12
) -> None:
"""Draw arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={"arrowstyle": style, "color": color, "lw": lw},
)
if label:
mx, my = (x1 + x2) / 2, (y1 + y2) / 2 + label_offset
ax.text(mx, my, label, ha="center", va="bottom", fontsize=6.5, color=color)
def draw_dashed_arrow(
ax, x1, y1, x2, y2, lw=1.0, color=LN, label="", label_offset=0.12
) -> None:
"""Draw dashed arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={
"arrowstyle": "->",
"color": color,
"lw": lw,
"linestyle": "dashed",
},
)
if label:
mx, my = (x1 + x2) / 2, (y1 + y2) / 2 + label_offset
ax.text(mx, my, label, ha="center", va="bottom", fontsize=6.5, color=color)
# ─── DIAGRAM 1: See-Think-Act Cycle ──────────────────────────────
def draw_see_think_act() -> None:
"""Draw see think act."""
fig, ax = plt.subplots(1, 1, figsize=(7, 4.5), facecolor=BG)
ax.set_xlim(0, 7)
ax.set_ylim(0, 4.5)
ax.axis("off")
ax.set_title(
"Cykl agenta upostaciowionego: Percepcja → Deliberacja → Akcja",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Environment box (large background)
env_rect = FancyBboxPatch(
(0.2, 0.2),
6.6,
1.0,
boxstyle="round,pad=0.08",
lw=1.5,
edgecolor=LN,
facecolor=GRAY1,
linestyle="--",
)
ax.add_patch(env_rect)
ax.text(
3.5,
0.7,
"ŚRODOWISKO FIZYCZNE\n(przeszkody, obiekty, ludzie)",
ha="center",
va="center",
fontsize=FS,
fontstyle="italic",
)
# Agent body (large rounded box)
agent_rect = FancyBboxPatch(
(0.5, 1.5),
6.0,
2.6,
boxstyle="round,pad=0.1",
lw=2.0,
edgecolor=LN,
facecolor=GRAY4,
)
ax.add_patch(agent_rect)
ax.text(
3.5,
3.85,
"AGENT UPOSTACIOWIONY (robot)",
ha="center",
va="center",
fontsize=9,
fontweight="bold",
)
# Three main phases
bw = 1.4
bh = 0.7
by = 2.2
# SEE
draw_box(
ax,
0.8,
by,
bw,
bh,
"SEE\n(Percepcja)",
fill=GRAY2,
fontsize=8,
fontweight="bold",
)
ax.text(
1.5,
by - 0.2,
"kamery, LIDAR\nczujniki dotyku",
ha="center",
va="top",
fontsize=6,
fontstyle="italic",
)
# THINK
draw_box(
ax,
2.8,
by,
bw,
bh,
"THINK\n(Deliberacja)",
fill=GRAY3,
fontsize=8,
fontweight="bold",
)
ax.text(
3.5,
by - 0.2,
"planowanie trasy\nmodel BDI",
ha="center",
va="top",
fontsize=6,
fontstyle="italic",
)
# ACT
draw_box(
ax, 4.8, by, bw, bh, "ACT\n(Akcja)", fill=GRAY2, fontsize=8, fontweight="bold"
)
ax.text(
5.5,
by - 0.2,
"silniki, chwytaki\nkomendy PWM",
ha="center",
va="top",
fontsize=6,
fontstyle="italic",
)
# Arrows between phases
draw_arrow(
ax, 0.8 + bw, by + bh / 2, 2.8, by + bh / 2, lw=1.5, label="dane sensoryczne"
)
draw_arrow(
ax, 2.8 + bw, by + bh / 2, 4.8, by + bh / 2, lw=1.5, label="komendy sterujące"
)
# Arrows to/from environment
draw_arrow(ax, 1.5, 1.2, 1.5, by, lw=1.3, label="odczyt", label_offset=0.08)
draw_arrow(ax, 5.5, by, 5.5, 1.2, lw=1.3, label="działanie", label_offset=0.08)
# Feedback loop arrow (from ACT back to environment back to SEE)
ax.annotate(
"",
xy=(1.5, 1.15),
xytext=(5.5, 1.15),
arrowprops={
"arrowstyle": "->",
"color": "#555555",
"lw": 1.0,
"linestyle": "dashed",
"connectionstyle": "arc3,rad=-0.15",
},
)
ax.text(
3.5,
0.35,
"← sprzężenie zwrotne (efekt akcji zmienia środowisko) →",
ha="center",
va="center",
fontsize=6,
color="#555555",
)
fig.tight_layout()
path = str(Path(OUTPUT_DIR) / "agent_see_think_act.png")
fig.savefig(path, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f"{path}")
# ─── DIAGRAM 2: 3T Architecture ─────────────────────────────────
def draw_3t_architecture() -> None:
"""Draw 3t architecture."""
fig, ax = plt.subplots(1, 1, figsize=(7, 5.5), facecolor=BG)
ax.set_xlim(0, 7)
ax.set_ylim(0, 5.5)
ax.axis("off")
ax.set_title(
"Architektura 3T sterownika robota (3-Layer Architecture)",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
layers = [
{
"y": 4.0,
"name": "WARSTWA 3: PLANNER\n(Deliberacja)",
"time": "sekundy \u2013 minuty",
"fill": GRAY1,
"example": 'Cel: "Jed\u017a do kuchni po kubek"\nPlanowanie trasy A \u2192 B \u2192 C',
"tech": "Planowanie symboliczne\nA*, graf zada\u0144",
},
{
"y": 2.6,
"name": "WARSTWA 2: SEQUENCER\n(Wykonawca)",
"time": "100 ms \u2013 sekundy",
"fill": GRAY2,
"example": "Sekwencja: Jed\u017a do drzwi \u2192\nOtw\u00f3rz \u2192 Jed\u017a do blatu \u2192 Chwy\u0107",
"tech": "FSM, Behavior Trees\nkoordynacja zachowa\u0144",
},
{
"y": 1.2,
"name": "WARSTWA 1: CONTROLLER\n(Reaktywny)",
"time": "milisekundy",
"fill": GRAY3,
"example": "PID: utrzymaj pr\u0119dko\u015b\u0107 0.5 m/s\nUnikaj kolizji (emergency stop)",
"tech": "PID, regulacja\nbezpo\u015brednie I/O",
},
]
bw = 4.0
bh = 0.85
for _i, layer in enumerate(layers):
y = layer["y"]
# Main layer box
draw_box(
ax,
0.3,
y,
bw,
bh,
layer["name"],
fill=layer["fill"],
fontsize=8,
fontweight="bold",
)
# Time label (left)
ax.text(
0.15,
y + bh / 2,
layer["time"],
ha="right",
va="center",
fontsize=6,
fontstyle="italic",
rotation=0,
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": "white",
"edgecolor": LN,
"lw": 0.5,
},
)
# Example (right side)
draw_box(ax, 4.5, y, 2.3, bh, layer["example"], fill="white", fontsize=6.5)
# Technology used
# ax.text(4.5 + 1.15, y - 0.12, layer["tech"], ha='center', va='top',
# fontsize=5.5, fontstyle='italic', color='#555555')
# Arrows between layers (downward = commands, upward = status)
for i in range(len(layers) - 1):
y_top = layers[i]["y"]
y_bot = layers[i + 1]["y"] + 0.85
# Command arrow (down)
draw_arrow(
ax, 1.8, y_top, 1.8, y_bot, lw=1.3, label="polecenia ↓", label_offset=0.02
)
# Status arrow (up)
draw_arrow(
ax,
2.8,
y_bot,
2.8,
y_top,
lw=1.0,
style="->",
color="#666666",
label="↑ status",
label_offset=0.02,
)
# Environment at bottom
env_rect = FancyBboxPatch(
(0.3, 0.3),
bw,
0.6,
boxstyle="round,pad=0.05",
lw=1.5,
edgecolor=LN,
facecolor=GRAY4,
linestyle="--",
)
ax.add_patch(env_rect)
ax.text(
0.3 + bw / 2,
0.6,
"SPRZĘT: silniki, czujniki, efektory",
ha="center",
va="center",
fontsize=7,
fontstyle="italic",
)
# Arrow from controller to hardware
draw_arrow(ax, 2.3, 1.2, 2.3, 0.9, lw=1.3)
# Abstraction label on the right
ax.annotate(
"",
xy=(6.9, 4.8),
xytext=(6.9, 0.5),
arrowprops={"arrowstyle": "<->", "color": "#888888", "lw": 1.0},
)
ax.text(
6.95,
2.65,
"abstrakcja",
ha="left",
va="center",
fontsize=7,
rotation=90,
color="#888888",
)
fig.tight_layout()
path = str(Path(OUTPUT_DIR) / "agent_3t_architecture.png")
fig.savefig(path, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f"{path}")
# ─── DIAGRAM 3: Behavior Tree Example ────────────────────────────
def draw_behavior_tree() -> None:
"""Draw behavior tree."""
fig, ax = plt.subplots(1, 1, figsize=(7.5, 4.5), facecolor=BG)
ax.set_xlim(0, 7.5)
ax.set_ylim(0, 4.5)
ax.axis("off")
ax.set_title(
"Behavior Tree: robot przenoszący obiekt (pick-and-place)",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Node positions: (x, y, text, shape_type)
# shape_type: 'seq' = sequence (→), 'sel' = selector (?), 'act' = action, 'cond' = condition
def draw_bt_node(ax, x, y, text, ntype="act", w=1.0, h=0.45) -> tuple[float, float]:
"""Draw a behavior tree node."""
if ntype == "seq":
# Sequence = box with →
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.06",
lw=1.5,
edgecolor=LN,
facecolor=GRAY2,
)
ax.add_patch(rect)
ax.text(
x,
y,
f"{text}",
ha="center",
va="center",
fontsize=7,
fontweight="bold",
)
elif ntype == "sel":
# Selector = box with ?
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.06",
lw=1.5,
edgecolor=LN,
facecolor=GRAY3,
)
ax.add_patch(rect)
ax.text(
x,
y,
f"? {text}",
ha="center",
va="center",
fontsize=7,
fontweight="bold",
)
elif ntype == "cond":
# Condition = diamond-ish / oval with dashed border
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.06",
lw=1.0,
edgecolor=LN,
facecolor="white",
linestyle="--",
)
ax.add_patch(rect)
ax.text(
x, y, text, ha="center", va="center", fontsize=6.5, fontstyle="italic"
)
else: # action
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.06",
lw=1.0,
edgecolor=LN,
facecolor=GRAY1,
)
ax.add_patch(rect)
ax.text(x, y, text, ha="center", va="center", fontsize=6.5)
return x, y
# Root: Sequence "Przenieś obiekt"
root = draw_bt_node(ax, 3.75, 3.8, "Przenieś obiekt", "seq", w=1.6)
# Level 2 children
find = draw_bt_node(ax, 1.2, 2.8, "Znajdź obiekt", "sel", w=1.3)
nav = draw_bt_node(ax, 3.75, 2.8, "Jedź do obiektu", "act", w=1.3)
pick = draw_bt_node(ax, 6.3, 2.8, "Chwyć i dostarcz", "seq", w=1.4)
# Arrows from root
draw_arrow(ax, root[0], root[1] - 0.225, find[0], find[1] + 0.225, lw=1.0)
draw_arrow(ax, root[0], root[1] - 0.225, nav[0], nav[1] + 0.225, lw=1.0)
draw_arrow(ax, root[0], root[1] - 0.225, pick[0], pick[1] + 0.225, lw=1.0)
# Level 3: children of "Znajdź obiekt" (selector)
vis = draw_bt_node(ax, 0.55, 1.7, "Widzę\nobiekt?", "cond", w=0.85, h=0.5)
scan = draw_bt_node(ax, 1.85, 1.7, "Skanuj\notoczenie", "act", w=0.85, h=0.5)
draw_arrow(ax, find[0], find[1] - 0.225, vis[0], vis[1] + 0.25, lw=0.8)
draw_arrow(ax, find[0], find[1] - 0.225, scan[0], scan[1] + 0.25, lw=0.8)
# Level 3: children of "Chwyć i dostarcz" (sequence)
grasp = draw_bt_node(ax, 5.4, 1.7, "Chwyć\nobject", "act", w=0.85, h=0.5)
deliver = draw_bt_node(ax, 6.5, 1.7, "Jedź do\ncelu", "act", w=0.85, h=0.5)
release = draw_bt_node(ax, 7.2, 1.7, "Puść", "act", w=0.55, h=0.5)
draw_arrow(ax, pick[0], pick[1] - 0.225, grasp[0], grasp[1] + 0.25, lw=0.8)
draw_arrow(ax, pick[0], pick[1] - 0.225, deliver[0], deliver[1] + 0.25, lw=0.8)
draw_arrow(ax, pick[0], pick[1] - 0.225, release[0], release[1] + 0.25, lw=0.8)
# Legend
leg_y = 0.5
draw_bt_node(ax, 0.8, leg_y, "→ Sequence", "seq", w=1.1, h=0.35)
draw_bt_node(ax, 2.3, leg_y, "? Selector", "sel", w=1.0, h=0.35)
draw_bt_node(ax, 3.6, leg_y, "Akcja", "act", w=0.8, h=0.35)
draw_bt_node(ax, 4.8, leg_y, "Warunek", "cond", w=0.8, h=0.35)
ax.text(
0.3, leg_y, "Legenda:", ha="left", va="center", fontsize=6.5, fontweight="bold"
)
# Execution note
ax.text(
3.75,
0.05,
"Wykonanie: od lewej do prawej. Sequence (→) = wszystkie po kolei. "
"Selector (?) = pierwszy sukces.",
ha="center",
va="center",
fontsize=6,
fontstyle="italic",
color="#555555",
)
fig.tight_layout()
path = str(Path(OUTPUT_DIR) / "agent_behavior_tree.png")
fig.savefig(path, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f"{path}")
# ─── DIAGRAM 4: BDI Model ────────────────────────────────────────
def draw_bdi_model() -> None:
"""Draw bdi model."""
fig, ax = plt.subplots(1, 1, figsize=(7, 4), facecolor=BG)
ax.set_xlim(0, 7)
ax.set_ylim(0, 4)
ax.axis("off")
ax.set_title(
"Model BDI agenta (Beliefs-Desires-Intentions)",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
bw = 1.6
bh = 1.4
# BELIEFS box
draw_box(ax, 0.3, 1.3, bw, bh, "", fill=GRAY1, fontsize=8, fontweight="bold")
ax.text(
0.3 + bw / 2,
1.3 + bh - 0.15,
"BELIEFS",
ha="center",
va="top",
fontsize=9,
fontweight="bold",
)
ax.text(
0.3 + bw / 2,
1.3 + bh / 2 - 0.1,
"(wiedza o \u015bwiecie)\n\n\u2022 mapa pokoju\n\u2022 pozycja robota\n\u2022 drzwi zamkni\u0119te\n\u2022 bateria: 45%",
ha="center",
va="center",
fontsize=6.5,
)
# DESIRES box
draw_box(ax, 2.7, 1.3, bw, bh, "", fill=GRAY2, fontsize=8, fontweight="bold")
ax.text(
2.7 + bw / 2,
1.3 + bh - 0.15,
"DESIRES",
ha="center",
va="top",
fontsize=9,
fontweight="bold",
)
ax.text(
2.7 + bw / 2,
1.3 + bh / 2 - 0.1,
"(cele agenta)\n\n• dostarczyć paczkę\n do pokoju 5\n• naładować baterię\n• unikać kolizji",
ha="center",
va="center",
fontsize=6.5,
)
# INTENTIONS box
draw_box(ax, 5.1, 1.3, bw, bh, "", fill=GRAY3, fontsize=8, fontweight="bold")
ax.text(
5.1 + bw / 2,
1.3 + bh - 0.15,
"INTENTIONS",
ha="center",
va="top",
fontsize=9,
fontweight="bold",
)
ax.text(
5.1 + bw / 2,
1.3 + bh / 2 - 0.1,
"(aktualny plan)\n\n→ jedź do drzwi\n bocznych\n→ otwórz drzwi\n→ wjedź do pokoju 5",
ha="center",
va="center",
fontsize=6.5,
)
# Arrows
draw_arrow(
ax,
0.3 + bw,
1.3 + bh / 2 + 0.15,
2.7,
1.3 + bh / 2 + 0.15,
lw=1.3,
label="informuje",
label_offset=0.08,
)
draw_arrow(
ax,
2.7 + bw,
1.3 + bh / 2 + 0.15,
5.1,
1.3 + bh / 2 + 0.15,
lw=1.3,
label="filtruje → wybiera",
label_offset=0.08,
)
# Feedback from intentions back to beliefs (update)
ax.annotate(
"",
xy=(0.3 + bw / 2, 1.3),
xytext=(5.1 + bw / 2, 1.3),
arrowprops={
"arrowstyle": "->",
"color": "#666666",
"lw": 1.0,
"linestyle": "dashed",
"connectionstyle": "arc3,rad=0.3",
},
)
ax.text(
3.5,
0.75,
"aktualizacja wiedzy po wykonaniu akcji",
ha="center",
va="center",
fontsize=6,
fontstyle="italic",
color="#666666",
)
# Sensor input arrow
draw_arrow(
ax,
0.3 + bw / 2,
3.5,
0.3 + bw / 2,
1.3 + bh,
lw=1.3,
label="percepcja (sensory)",
label_offset=0.05,
)
ax.text(
0.3 + bw / 2,
3.55,
"ŚRODOWISKO",
ha="center",
va="bottom",
fontsize=7,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.2",
"facecolor": GRAY4,
"edgecolor": LN,
"lw": 0.8,
},
)
# Action output arrow
draw_arrow(
ax,
5.1 + bw / 2,
1.3 + bh,
5.1 + bw / 2,
3.5,
lw=1.3,
label="akcja (efektory)",
label_offset=0.05,
)
ax.text(
5.1 + bw / 2,
3.55,
"EFEKTORY",
ha="center",
va="bottom",
fontsize=7,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.2",
"facecolor": GRAY4,
"edgecolor": LN,
"lw": 0.8,
},
)
fig.tight_layout()
path = str(Path(OUTPUT_DIR) / "agent_bdi_model.png")
fig.savefig(path, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f"{path}")
# ─── MAIN ────────────────────────────────────────────────────────
if __name__ == "__main__":
print("Generating PYTANIE 15 diagrams...")
draw_see_think_act()
draw_3t_architecture()
draw_behavior_tree()
draw_bdi_model()
print("Done! All diagrams saved to", OUTPUT_DIR)

View File

@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""Generate Anki flashcards from exam questions in odpowiedzi/ folder.
Creates a tab-separated file compatible with Anki import.
"""
from __future__ import annotations
from pathlib import Path
import re
def extract_question_and_answer(filepath) -> list[dict[str, str]]:
"""Extract main question and key answer points from a markdown file."""
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
cards = []
# Extract file number for tagging
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
if match:
num = match.group(1)
topic = match.group(2).replace("-", "_")
else:
num = "00"
topic = "unknown"
# Extract main title (usually contains the question)
title_match = re.search(r"^# (.+)$", content, re.MULTILINE)
title = title_match.group(1) if title_match else "Unknown"
# Extract the main question from ## Pytanie section
question_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
if question_match:
main_question = question_match.group(1).strip()
main_question = re.sub(r"\s+", " ", main_question)
else:
main_question = title
# Extract subject/przedmiot
subject_match = re.search(r"Przedmiot:\s*(\w+)", content)
subject = subject_match.group(1) if subject_match else "Ogólne"
# Create main question card - extract key sections for answer
answer_parts = []
# Look for main answer section
main_answer = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## |\n---\s*\n## |\Z)",
content,
re.DOTALL,
)
if main_answer:
answer_text = main_answer.group(1)
# Extract key points, definitions, headers
headers = re.findall(r"### (.+)", answer_text)
for h in headers[:5]: # Limit to first 5 headers
answer_parts.append(f"{h}")
# Also extract key definitions if present
definitions = re.findall(r"\*\*([^*]+)\*\*\s*[--:]\s*([^*\n]+)", content)
for term, definition in definitions[:3]:
if len(definition) > 20 and len(definition) < 200:
answer_parts.append(f"{term}: {definition.strip()}")
# If we found answer parts, create main card
if answer_parts:
answer_html = "<br>".join(answer_parts[:8]) # Limit answer length
cards.append(
{
"question": main_question,
"answer": answer_html,
"tags": f"egzamin_magisterski pytanie_{num} {subject} {topic}",
}
)
# Extract sub-questions and key concepts as additional cards
# Look for ### headers with explanations
subsections = re.findall(
r"### (\d+\.\s+)?(.+?)\n\n(.+?)(?=\n### |\n## |\n---|\Z)", content, re.DOTALL
)
for _, header, body in subsections:
if len(header) < 5 or header.startswith("Przykład"):
continue
# Extract first substantive paragraph or key points
body_clean = body.strip()
# Skip very short or code-only sections
if len(body_clean) < 50:
continue
# Extract bullet points or first paragraph
bullets = re.findall(r"[-•]\s*\*\*(.+?)\*\*[:\s]*([^\n]+)?", body_clean)
if bullets:
answer_text = "<br>".join(
[
f"{b[0]}: {b[1].strip()}" if b[1] else f"{b[0]}"
for b in bullets[:5]
]
)
else:
# Get first meaningful paragraph
paragraphs = [
p.strip()
for p in body_clean.split("\n\n")
if p.strip() and not p.startswith("```") and not p.startswith("|")
]
if paragraphs:
first_para = paragraphs[0]
# Clean markdown
first_para = re.sub(r"\*\*(.+?)\*\*", r"\1", first_para)
first_para = re.sub(r"\*(.+?)\*", r"\1", first_para)
answer_text = first_para[:400]
else:
continue
# Create sub-concept card
sub_question = f"Co to jest {header}?" if not header.endswith("?") else header
if (
"Charakterystyka" in header
or "Definicja" in header
or "Właściwości" in header
):
# These are answer-type headers, reframe
parent_topic = title.replace("Pytanie", "").strip(": 0123456789")
sub_question = f"{header} - {parent_topic}"
cards.append(
{
"question": sub_question,
"answer": answer_text,
"tags": f"egzamin_magisterski pytanie_{num} {subject} {topic} szczegoly",
}
)
# Extract key formulas/definitions as separate cards
formulas = re.findall(
r"\*\*([A-Za-z\s]+(?:formuła|wzór|twierdzenie|definicja|lemat))\*\*[:\s]*\n?(.+?)(?=\n\n|\n\*\*|\Z)",
content,
re.IGNORECASE | re.DOTALL,
)
for formula_name, formula_content in formulas:
if len(formula_content) > 20:
cards.append(
{
"question": f"Podaj {formula_name.strip()}",
"answer": formula_content.strip()[:300],
"tags": f"egzamin_magisterski pytanie_{num} {subject} formuly",
}
)
return cards
def clean_for_anki(text) -> str:
"""Clean text for Anki import - escape special characters."""
# Replace tabs with spaces
text = text.replace("\t", " ")
# Convert markdown formatting to HTML
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"\*(.+?)\*", r"<i>\1</i>", text)
# Handle newlines - convert to <br> for Anki
text = text.replace("\n", "<br>")
# Remove multiple <br>
text = re.sub(r"(<br>)+", "<br>", text)
# Remove leading/trailing <br>
text = re.sub(r"^<br>|<br>$", "", text)
# Escape quotes in a way that works with tab-separated
text = text.replace('"', "&quot;")
return text.strip()
def main() -> None:
"""Main."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
output_file = Path(
"/home/kuchy/praca_magisterska/pytania/anki_egzamin_magisterski.txt"
)
all_cards = []
# Process each file
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
print(f"Processing: {md_file.name}")
try:
cards = extract_question_and_answer(md_file)
all_cards.extend(cards)
print(f" -> Extracted {len(cards)} cards")
except Exception as e:
print(f" -> Error: {e}")
# Write Anki file with headers
with Path(output_file).open("w", encoding="utf-8") as f:
# Anki file headers
f.write("#separator:tab\n")
f.write("#html:true\n")
f.write("#columns:Front\tBack\tTags\n")
f.write("#deck:Egzamin Magisterski ISY\n")
f.write("#notetype:Basic\n")
f.write("\n")
for card in all_cards:
front = clean_for_anki(card["question"])
back = clean_for_anki(card["answer"])
tags = card["tags"]
f.write(f"{front}\t{back}\t{tags}\n")
print(f"\n✅ Created {len(all_cards)} flashcards")
print(f"📁 Output: {output_file}")
print("\nTo import into Anki:")
print("1. Open Anki → File → Import")
print("2. Select the .txt file")
print("3. Verify 'Allow HTML' is checked")
print("4. Click Import")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""Generate comprehensive Anki flashcards from exam questions.
Creates tab-separated file for Anki import with proper HTML formatting.
"""
from __future__ import annotations
from pathlib import Path
import re
def clean_text(text) -> str:
"""Clean and format text for Anki."""
if not text:
return ""
# Convert markdown formatting to HTML
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<i>\1</i>", text)
# Handle special characters
text = text.replace("\t", " ")
text = text.replace('"', "&quot;")
# Clean up whitespace but preserve intentional line breaks
text = re.sub(r" +", " ", text)
return text.strip()
def format_list(items, numbered=False) -> str:
"""Format a list of items as HTML."""
if not items:
return ""
tag = "ol" if numbered else "ul"
html = f"<{tag}>"
for item in items:
cleaned = clean_text(item)
if cleaned:
html += f"<li>{cleaned}</li>"
html += f"</{tag}>"
return html
def extract_from_file(filepath) -> list[dict[str, str]]:
"""Extract flashcard data from a markdown file."""
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
cards = []
# Get file metadata
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
num = match.group(1) if match else "00"
match.group(2).replace("-", "_") if match else "unknown"
# Extract subject
subj_match = re.search(r"Przedmiot:\s*(\w+)", content)
subject = subj_match.group(1) if subj_match else "Ogólne"
# Base tags
base_tags = f"egzamin_magisterski pyt{num} {subject}"
# =====================================================
# CARD TYPE 1: Main Exam Question
# =====================================================
q_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
if q_match:
main_q = re.sub(r"\s+", " ", q_match.group(1).strip())
# Extract key topics from main answer
answer_match = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## [<5B>🎯]|\n---\s*\n## |\Z)",
content,
re.DOTALL,
)
if answer_match:
answer_section = answer_match.group(1)
# Get main headers
headers = re.findall(
r"^### (?:\d+\.\s*)?(.+)$", answer_section, re.MULTILINE
)
headers = [h.strip() for h in headers if len(h.strip()) > 3][:6]
if headers:
answer_html = "<b>Kluczowe zagadnienia:</b>" + format_list(headers)
cards.append(
{
"front": clean_text(main_q),
"back": answer_html,
"tags": f"{base_tags} pytanie_glowne",
}
)
# =====================================================
# CARD TYPE 2: Subsection Cards (detailed concepts)
# =====================================================
# Find all ### sections
sections = re.findall(
r"^### (?:\d+\.\s*)?(.+?)\n((?:(?!^###).)+)", content, re.MULTILINE | re.DOTALL
)
for header, body in sections:
header = header.strip()
body = body.strip()
# Skip very short sections or example sections
if len(body) < 50 or header.lower().startswith("przykład"):
continue
# Extract key information from body
answer_parts = []
# Look for #### sub-headers
subheaders = re.findall(r"^#### (.+)$", body, re.MULTILINE)
if subheaders:
answer_parts.extend(subheaders[:4])
# Look for bullet points with bold terms
bullets = re.findall(r"[-•]\s*\*\*([^*]+)\*\*[:\s-]*([^\n]+)?", body)
for term, desc in bullets[:5]:
if desc:
answer_parts.append(f"<b>{term}</b>: {desc.strip()}")
else:
answer_parts.append(f"<b>{term}</b>")
# If no structured content, get first paragraph
if not answer_parts:
paras = [
p.strip()
for p in body.split("\n\n")
if p.strip()
and not p.strip().startswith("```")
and not p.strip().startswith("|")
]
if paras:
first = paras[0]
# Limit length
if len(first) > 300:
first = first[:300] + "..."
answer_parts.append(first)
if answer_parts:
# Determine card type
if "Definicja" in header or "Co to" in header:
q = f"Co to jest: {header.replace('Definicja', '').strip()}?"
elif "Charakterystyka" in header:
q = f"Scharakteryzuj: {header.replace('Charakterystyka', '').strip()}"
elif header.endswith("?"):
q = header
else:
q = f"Omów: {header}"
# Format answer
if len(answer_parts) > 1:
answer_html = format_list(answer_parts)
else:
answer_html = clean_text(answer_parts[0])
cards.append(
{
"front": clean_text(q),
"back": answer_html,
"tags": f"{base_tags} szczegoly",
}
)
# =====================================================
# CARD TYPE 3: Algorithms/Formulas
# =====================================================
algo_patterns = [
r"#### Złożoność(?:\s+czasowa)?\s*\n(.+?)(?=\n####|\n###|\Z)",
r"Złożoność:\s*\*\*([^*]+)\*\*",
]
for pattern in algo_patterns:
matches = re.findall(pattern, content, re.DOTALL)
for match in matches[:2]:
if len(match) > 10:
# Find context - which algorithm?
algo_context = re.search(
r"### (\d+\.\s*)?(.+?)(?=\n)", content[: content.find(match)]
)
if algo_context:
algo_name = algo_context.group(2).strip()
cards.append(
{
"front": f"Jaka jest złożoność algorytmu/metody: {algo_name}?",
"back": clean_text(match.strip()[:200]),
"tags": f"{base_tags} zlozonosc",
}
)
break
# =====================================================
# CARD TYPE 4: Comparisons (when file contains comparisons)
# =====================================================
compare_match = re.search(
r"## .*(Porównanie|Zestawienie|vs).*\n(.+?)(?=\n## |\Z)",
content,
re.DOTALL | re.IGNORECASE,
)
if compare_match:
compare_section = compare_match.group(2)
# Extract comparison items
items = re.findall(r"\|\s*\*\*([^|*]+)\*\*\s*\|([^|]+)\|", compare_section)
if items:
comparison_html = "<table><tr><th>Aspekt</th><th>Wartość</th></tr>"
for aspect, value in items[:6]:
comparison_html += f"<tr><td>{clean_text(aspect)}</td><td>{clean_text(value)}</td></tr>"
comparison_html += "</table>"
# Get comparison title
title_match = re.search(
r"## .*(Porównanie|Zestawienie).*?(\w+.*?(?:vs|i|oraz).*?\w+)",
compare_match.group(0),
re.IGNORECASE,
)
if title_match:
cards.append(
{
"front": f"Porównaj kluczowe różnice w temacie: pytanie {num}",
"back": comparison_html,
"tags": f"{base_tags} porownanie",
}
)
# =====================================================
# CARD TYPE 5: Q&A from practice questions section
# =====================================================
qa_section = re.search(r"## 🎓 Pytania.*?\n(.+?)(?=\n## |\Z)", content, re.DOTALL)
if qa_section:
qa_content = qa_section.group(1)
# Find Q&A pairs
qas = re.findall(
r'### Q\d+:?\s*["\']?(.+?)["\']?\s*\n.*?Odpowiedź:\s*\n?(.+?)(?=\n### |\Z)',
qa_content,
re.DOTALL,
)
for q, a in qas[:3]:
q = re.sub(r"\s+", " ", q.strip())
a = a.strip()
if len(a) > 30:
# Limit answer length
a_lines = a.split("\n")
a_short = "\n".join(a_lines[:5])
if len(a_short) > 400:
a_short = a_short[:400] + "..."
cards.append(
{
"front": clean_text(q),
"back": clean_text(a_short).replace("\n", "<br>"),
"tags": f"{base_tags} egzamin_praktyka",
}
)
return cards
def main() -> None:
"""Main."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
output_file = Path(
"/home/kuchy/praca_magisterska/pytania/anki_egzamin_magisterski.txt"
)
all_cards = []
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
print(f"Processing: {md_file.name}", end=" ")
try:
cards = extract_from_file(md_file)
all_cards.extend(cards)
print(f"{len(cards)} cards")
except Exception as e:
print(f"→ ERROR: {e}")
# Remove potential duplicates (same front)
seen = set()
unique_cards = []
for card in all_cards:
if card["front"] not in seen:
seen.add(card["front"])
unique_cards.append(card)
# Write output file
with Path(output_file).open("w", encoding="utf-8") as f:
# Anki headers
f.write("#separator:tab\n")
f.write("#html:true\n")
f.write("#tags column:3\n")
f.write("#deck:Egzamin Magisterski ISY\n")
f.write("#notetype:Basic\n")
f.write("\n")
for card in unique_cards:
# Ensure no tabs in content (would break parsing)
front = card["front"].replace("\t", " ")
back = card["back"].replace("\t", " ")
tags = card["tags"]
f.write(f"{front}\t{back}\t{tags}\n")
print(f"\n{'=' * 50}")
print(f"✅ Generated {len(unique_cards)} unique flashcards")
print(f"📁 Saved to: {output_file}")
print(f"{'=' * 50}")
print("\n📋 IMPORT INSTRUCTIONS:")
print("" * 40)
print("Anki Desktop:")
print(" 1. File → Import")
print(" 2. Select: anki_egzamin_magisterski.txt")
print(" 3. Verify: Fields separated by Tab")
print(" 4. Check: Allow HTML in fields")
print(" 5. Click Import")
print()
print("AnkiWeb / AnkiDroid:")
print(" 1. First import on Anki Desktop")
print(" 2. Click Sync to upload to AnkiWeb")
print(" 3. Sync on mobile to download")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Generate Anki flashcards from exam questions in odpowiedzi/ folder.
Creates a tab-separated file compatible with Anki import.
"""
from __future__ import annotations
from pathlib import Path
import re
import traceback
def extract_main_question(content, filename) -> str:
"""Extract the main exam question from the file."""
# Extract the main question from ## Pytanie section
question_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
if question_match:
main_question = question_match.group(1).strip()
return re.sub(r"\s+", " ", main_question)
# Fallback to title
title_match = re.search(r"^# (.+)$", content, re.MULTILINE)
return title_match.group(1) if title_match else filename
def extract_subject(content) -> str:
"""Extract the subject code."""
subject_match = re.search(r"Przedmiot:\s*(\w+)", content)
return subject_match.group(1) if subject_match else "Ogólne"
def extract_key_points(content) -> list[str]:
"""Extract key points from the main answer section."""
points = []
# Look for main answer section
main_answer = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## [^<5E>]|\n---\s*\n## |\Z)",
content,
re.DOTALL,
)
if not main_answer:
return points
answer_text = main_answer.group(1)
# Extract ### headers as key points
headers = re.findall(r"^### (.+)$", answer_text, re.MULTILINE)
for h in headers[:6]:
# Clean header
h = re.sub(r"\d+\.\s*", "", h).strip()
if h and len(h) > 3:
points.append(h)
return points
def extract_definitions(content) -> list[tuple[str, str]]:
"""Extract key definitions from the content."""
definitions = []
# Pattern for **Term** - definition or **Term**: definition
pattern = r"\*\*([^*\n]+)\*\*\s*[--:]\s*([^*\n]{20,150})"
matches = re.findall(pattern, content)
for term, definition in matches:
term = term.strip()
definition = definition.strip()
# Filter out non-definition patterns
if (
term
and definition
and not term.startswith("Przykład")
and not term.startswith("Uwaga")
):
definitions.append((term, definition))
return definitions[:5]
def clean_html(text) -> str:
"""Convert markdown to HTML and clean for Anki."""
if not text:
return ""
# Replace markdown bold/italic with HTML
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"\*(.+?)\*", r"<i>\1</i>", text)
# Clean up special characters
text = text.replace("\t", " ")
text = text.replace('"', "&quot;")
# Handle newlines - convert to <br>
text = text.replace("\n", " ")
text = re.sub(r"\s+", " ", text)
return text.strip()
def process_file(filepath) -> list[dict[str, str]]:
"""Process a single file and return flashcards."""
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
cards = []
# Extract metadata
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
if match:
num = match.group(1)
match.group(2).replace("-", "_")
else:
num = "00"
subject = extract_subject(content)
main_question = extract_main_question(content, filename)
# Base tags for this question
base_tags = f"egzamin_magisterski pytanie_{num} {subject}"
# Card 1: Main question with key points
key_points = extract_key_points(content)
if key_points:
answer = (
"<ul>"
+ "".join([f"<li>{clean_html(p)}</li>" for p in key_points])
+ "</ul>"
)
cards.append(
{"front": clean_html(main_question), "back": answer, "tags": base_tags}
)
# Card 2+: Key definitions as individual cards
definitions = extract_definitions(content)
for term, definition in definitions:
q = f"Definicja: {term}"
a = clean_html(definition)
cards.append({"front": q, "back": a, "tags": f"{base_tags} definicje"})
return cards
def main() -> None:
"""Main."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
output_file = Path(
"/home/kuchy/praca_magisterska/pytania/anki_egzamin_magisterski.txt"
)
all_cards = []
# Process each file
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
print(f"Processing: {md_file.name}")
try:
cards = process_file(md_file)
all_cards.extend(cards)
print(f" -> {len(cards)} cards")
except Exception as e:
print(f" -> Error: {e}")
traceback.print_exc()
# Write Anki-compatible file
with Path(output_file).open("w", encoding="utf-8") as f:
# File headers for Anki
f.write("#separator:tab\n")
f.write("#html:true\n")
f.write("#tags column:3\n")
f.write("#deck:Egzamin Magisterski ISY\n")
f.write("#notetype:Basic\n")
f.write("\n")
for card in all_cards:
front = card["front"]
back = card["back"]
tags = card["tags"]
# Ensure no tabs in content
front = front.replace("\t", " ")
back = back.replace("\t", " ")
f.write(f"{front}\t{back}\t{tags}\n")
print(f"\n✅ Created {len(all_cards)} flashcards")
print(f"📁 Output: {output_file}")
print("\n=== Import Instructions ===")
print("1. Open Anki desktop → File → Import")
print("2. Select: anki_egzamin_magisterski.txt")
print("3. Set 'Fields separated by: Tab'")
print("4. Check 'Allow HTML in fields'")
print("5. Map: Field 1 → Front, Field 2 → Back, Field 3 → Tags")
print("6. Click Import")
print("\nFor AnkiWeb/AnkiDroid: Sync after importing on desktop")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""Generate Anki flashcards with ACTUAL substantive answers, not just headers."""
from __future__ import annotations
from pathlib import Path
import re
def clean_text(text) -> str:
"""Clean text for Anki."""
if not text:
return ""
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<i>\1</i>", text)
text = text.replace("\t", " ")
text = text.replace('"', "&quot;")
text = re.sub(r" +", " ", text)
return text.strip()
def extract_real_answer(content, section_name) -> str | None:
"""Extract actual content from a section, not just headers."""
# Find the section
pattern = rf"### (?:\d+\.\s*)?{re.escape(section_name)}\s*\n((?:(?!^### ).)+)"
match = re.search(pattern, content, re.MULTILINE | re.DOTALL)
if not match:
return None
body = match.group(1).strip()
# Extract meaningful content
lines = []
# Get subheaders with their first line of content
subheader_pattern = r"#### ([^\n]+)\n([^\n#]+)"
for sub_header, first_line in re.findall(subheader_pattern, body):
lines.append(f"<b>{sub_header.strip()}</b>: {first_line.strip()}")
# Get bullet points
bullet_pattern = r"[-•]\s*\*\*([^*]+)\*\*[:\s-]*([^\n]*)"
for term, desc in re.findall(bullet_pattern, body):
if desc.strip():
lines.append(f"• <b>{term.strip()}</b>: {desc.strip()}")
else:
lines.append(f"• <b>{term.strip()}</b>")
# If no structured content, get paragraphs
if not lines:
paras = [
p.strip()
for p in body.split("\n\n")
if p.strip() and not p.startswith("```") and not p.startswith("|")
]
for p in paras[:2]:
if len(p) > 20 and len(p) < 400:
lines.append(p)
return "<br>".join(lines[:6]) if lines else None
def extract_cards(filepath) -> list[dict[str, str]]:
"""Extract flashcards from a file."""
with Path(filepath).open(encoding="utf-8") as f:
content = f.read()
cards = []
filename = Path(filepath).name
match = re.match(r"(\d+)-(.+)\.md", filename)
num = match.group(1) if match else "00"
subj_match = re.search(r"Przedmiot:\s*(\w+)", content)
subject = subj_match.group(1) if subj_match else "Ogólne"
base_tags = f"egzamin_magisterski pyt{num} {subject}"
# Get main question
q_match = re.search(
r'## Pytanie\s*\n\s*\*\*["\']?(.+?)["\']?\*\*', content, re.DOTALL
)
main_question = re.sub(r"\s+", " ", q_match.group(1).strip()) if q_match else None
# ===============================================
# MAIN CARD: Question with REAL answer summary
# ===============================================
if main_question:
# Build a real answer from the main sections
answer_parts = []
# For automata question - extract key facts about each automaton
if "automat" in main_question.lower() or "maszyn" in main_question.lower():
# FA
fa_match = re.search(
r"Automat Skończony.*?Rozpoznawana klasa języków\s*\n\s*\*\*([^*]+)\*\*",
content,
re.DOTALL,
)
if fa_match:
answer_parts.append(
f"<b>Automat Skończony (FA)</b>: {fa_match.group(1).strip()}"
)
# PDA
pda_match = re.search(
r"Automat ze Stosem.*?Rozpoznawana klasa języków\s*\n\s*\*\*([^*]+)\*\*",
content,
re.DOTALL,
)
if pda_match:
answer_parts.append(
f"<b>Automat ze Stosem (PDA)</b>: {pda_match.group(1).strip()}"
)
# TM
tm_match = re.search(
r"Maszyna Turinga.*?Rozpoznawana klasa języków\s*\n\s*\*\*([^*]+)\*\*",
content,
re.DOTALL,
)
if tm_match:
answer_parts.append(
f"<b>Maszyna Turinga (TM)</b>: {tm_match.group(1).strip()}"
)
# Generic extraction if specific didn't work
if not answer_parts:
# Look for key definitions/summaries
key_patterns = [
r"#### Definicja\s*\n([^\n#]+)",
r"#### Charakterystyka\s*\n([^\n#]+)",
r"\*\*Definicja[:\s]*\*\*\s*([^\n]+)",
]
for pattern in key_patterns:
for match in re.findall(pattern, content)[:3]:
if len(match) > 20:
answer_parts.append(match.strip())
# Still nothing? Get first substantive paragraph from main answer
if not answer_parts:
main_answer = re.search(
r"## 📚 Odpowiedź główna\s*\n(.+?)(?=\n## |\Z)", content, re.DOTALL
)
if main_answer:
# Skip headers, get actual content
text = main_answer.group(1)
paras = re.findall(r"\n\n([^#\n][^\n]{50,300})", text)
answer_parts = paras[:3]
if answer_parts:
answer = "<br><br>".join([clean_text(p) for p in answer_parts])
cards.append(
{
"front": clean_text(main_question),
"back": answer,
"tags": f"{base_tags} pytanie_glowne",
}
)
# ===============================================
# CONCEPT CARDS: Specific topics with real content
# ===============================================
# Find all ### sections and extract their actual content
sections = re.findall(
r"^### (?:\d+\.\s*)?([^\n]+)\n((?:(?!^### ).)*)",
content,
re.MULTILINE | re.DOTALL,
)
for header, body in sections:
header = header.strip()
body = body.strip()
# Skip short sections, mnemonics, examples
if (
len(body) < 80
or "Przykład" in header
or "Mnemonic" in header
or '"' in header
):
continue
# Extract real content
answer_lines = []
# Get definition if present
def_match = re.search(r"#### Definicja[^\n]*\n([^\n#]+(?:\n[^\n#]+)?)", body)
if def_match:
answer_lines.append(def_match.group(1).strip())
# Get characterization
char_match = re.search(r"#### Charakterystyka\s*\n((?:[-•][^\n]+\n?)+)", body)
if char_match:
bullets = re.findall(
r"[-•]\s*\*\*([^*]+)\*\*[:\s]*([^\n]*)", char_match.group(1)
)
for term, desc in bullets[:4]:
answer_lines.append(
f"• <b>{term}</b>: {desc.strip()}" if desc else f"• <b>{term}</b>"
)
# Get bullet points if no structured content yet
if not answer_lines:
bullets = re.findall(r"[-•]\s*\*\*([^*]+)\*\*[:\s]*([^\n]*)", body)
for term, desc in bullets[:5]:
answer_lines.append(
f"• <b>{term}</b>: {desc.strip()}" if desc else f"• <b>{term}</b>"
)
# Get first paragraph if still nothing
if not answer_lines:
first_para = re.search(r"^([^#\n\-•|`][^\n]{30,250})", body, re.MULTILINE)
if first_para:
answer_lines.append(first_para.group(1))
if answer_lines:
question = f"Wyjaśnij: {header}" if not header.endswith("?") else header
answer = "<br>".join([clean_text(l) for l in answer_lines])
cards.append(
{
"front": clean_text(question),
"back": answer,
"tags": f"{base_tags} szczegoly",
}
)
# ===============================================
# Q&A CARDS: From practice questions section
# ===============================================
qa_matches = re.findall(
r'### Q\d+:\s*["\']?([^"\'?\n]+)\?*["\']?\s*\n.*?Odpowiedź:\s*\n(.+?)(?=\n### |\n## |\Z)',
content,
re.DOTALL,
)
for question, answer in qa_matches[:5]:
question = question.strip()
answer = answer.strip()
# Clean up answer - get first meaningful part
answer_lines = answer.split("\n")
clean_answer = []
for line in answer_lines[:6]:
line = line.strip()
if line and not line.startswith("```") and not line.startswith("|"):
clean_answer.append(line)
if clean_answer:
cards.append(
{
"front": clean_text(question + "?"),
"back": "<br>".join([clean_text(l) for l in clean_answer]),
"tags": f"{base_tags} qa",
}
)
return cards
def main() -> None:
"""Main."""
odpowiedzi_dir = Path("/home/kuchy/praca_magisterska/pytania/odpowiedzi")
output_file = Path(
"/home/kuchy/praca_magisterska/pytania/anki_egzamin_magisterski.txt"
)
all_cards = []
for md_file in sorted(odpowiedzi_dir.glob("*.md")):
print(f"Processing: {md_file.name}", end=" ")
try:
cards = extract_cards(md_file)
all_cards.extend(cards)
print(f"{len(cards)} cards")
except Exception as e:
print(f"→ ERROR: {e}")
# Remove duplicates
seen = set()
unique_cards = []
for card in all_cards:
key = card["front"][:100]
if key not in seen:
seen.add(key)
unique_cards.append(card)
# Write file
with Path(output_file).open("w", encoding="utf-8") as f:
f.write("#separator:Tab\n")
f.write("#html:true\n")
f.write("#notetype:Basic\n")
f.write("#deck:Egzamin Magisterski ISY\n")
f.write("#columns:Front\tBack\tTags\n")
f.write("#tags column:3\n")
f.write("\n")
for card in unique_cards:
front = card["front"].replace("\t", " ")
back = card["back"].replace("\t", " ")
tags = card["tags"]
f.write(f"{front}\t{back}\t{tags}\n")
print(f"\n✅ Generated {len(unique_cards)} flashcards")
print(f"📁 Output: {output_file}")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,887 @@
#!/usr/bin/env python3
"""Generate diagrams for PYTANIE 1: Automata and language classes.
1. FA recognition example DFA for strings ending in "ab"
2. PDA recognition example PDA for aⁿbⁿ
3. LBA recognition example LBA for aⁿbⁿcⁿ
4. TM recognition example TM for 0ⁿ1ⁿ.
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
DPI = 300
BG = "white"
LN = "black"
FS = 8
FS_TITLE = 11
FS_SMALL = 6.5
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
GRAY5 = "#C0C0C0"
LIGHT_GREEN = "#D5E8D4"
LIGHT_RED = "#F8D7DA"
LIGHT_BLUE = "#D6EAF8"
LIGHT_YELLOW = "#FFF9C4"
def draw_state_circle(
ax, x, y, r, label, accepting=False, initial=False, fillcolor="white", fontsize=FS
) -> None:
"""Draw an automaton state circle."""
circle = plt.Circle(
(x, y), r, fill=True, facecolor=fillcolor, edgecolor=LN, linewidth=1.5, zorder=3
)
ax.add_patch(circle)
if accepting:
inner = plt.Circle(
(x, y), r * 0.82, fill=False, edgecolor=LN, linewidth=1.2, zorder=3
)
ax.add_patch(inner)
if initial:
ax.annotate(
"",
xy=(x - r, y),
xytext=(x - r - 0.4, y),
arrowprops={"arrowstyle": "->", "color": LN, "lw": 1.5},
zorder=4,
)
ax.text(
x,
y,
label,
ha="center",
va="center",
fontsize=fontsize,
fontweight="bold",
zorder=5,
)
def draw_curved_arrow(
ax,
x1,
y1,
x2,
y2,
label,
_r=0.25,
connectionstyle="arc3,rad=0.3",
fontsize=FS_SMALL,
label_offset=(0, 0),
) -> None:
"""Draw a curved arrow between points with label."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={
"arrowstyle": "->",
"color": LN,
"lw": 1.2,
"connectionstyle": connectionstyle,
},
zorder=2,
)
mx = (x1 + x2) / 2 + label_offset[0]
my = (y1 + y2) / 2 + label_offset[1]
ax.text(
mx,
my,
label,
ha="center",
va="center",
fontsize=fontsize,
fontstyle="italic",
zorder=5,
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": "white",
"edgecolor": "none",
"alpha": 0.9,
},
)
def draw_self_loop(ax, x, y, r, label, direction="top", fontsize=FS_SMALL) -> None:
"""Draw a self-loop on a state."""
if direction == "top":
loop = mpatches.FancyArrowPatch(
(x - 0.12, y + r),
(x + 0.12, y + r),
connectionstyle="arc3,rad=-1.8",
arrowstyle="->",
mutation_scale=12,
lw=1.2,
color=LN,
zorder=2,
)
ax.add_patch(loop)
ax.text(
x,
y + r + 0.35,
label,
ha="center",
va="center",
fontsize=fontsize,
fontstyle="italic",
zorder=5,
)
elif direction == "bottom":
loop = mpatches.FancyArrowPatch(
(x - 0.12, y - r),
(x + 0.12, y - r),
connectionstyle="arc3,rad=1.8",
arrowstyle="->",
mutation_scale=12,
lw=1.2,
color=LN,
zorder=2,
)
ax.add_patch(loop)
ax.text(
x,
y - r - 0.35,
label,
ha="center",
va="center",
fontsize=fontsize,
fontstyle="italic",
zorder=5,
)
# ============================================================
# 1. FA Recognition Example — DFA for strings ending in "ab"
# ============================================================
def draw_fa_recognition() -> None:
"""FA state diagram + step-by-step trace for 'baab'."""
_fig, axes = plt.subplots(
1, 2, figsize=(11.69, 4), gridspec_kw={"width_ratios": [1, 1.3]}
)
# --- Left: State diagram ---
ax = axes[0]
ax.set_xlim(-1, 5.5)
ax.set_ylim(-1.5, 2.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
'DFA — diagram stanów\nL = {słowa nad {a,b} kończące się na "ab"}',
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
R = 0.35
# States positions
states = {"q₀": (0.8, 0.5), "q₁": (2.8, 0.5), "q₂": (4.8, 0.5)}
draw_state_circle(ax, *states["q₀"], R, "q₀", initial=True)
draw_state_circle(ax, *states["q₁"], R, "q₁")
draw_state_circle(ax, *states["q₂"], R, "q₂", accepting=True, fillcolor=LIGHT_GREEN)
# Transitions
# q₀ --a--> q₁
draw_curved_arrow(
ax,
states["q₀"][0] + R,
states["q₀"][1] + 0.05,
states["q₁"][0] - R,
states["q₁"][1] + 0.05,
"a",
connectionstyle="arc3,rad=0.15",
label_offset=(0, 0.25),
)
# q₁ --b--> q₂
draw_curved_arrow(
ax,
states["q₁"][0] + R,
states["q₁"][1] + 0.05,
states["q₂"][0] - R,
states["q₂"][1] + 0.05,
"b",
connectionstyle="arc3,rad=0.15",
label_offset=(0, 0.25),
)
# q₂ --a--> q₁
draw_curved_arrow(
ax,
states["q₂"][0] - R,
states["q₂"][1] - 0.05,
states["q₁"][0] + R,
states["q₁"][1] - 0.05,
"a",
connectionstyle="arc3,rad=0.15",
label_offset=(0, -0.3),
)
# q₂ --b--> q₀
draw_curved_arrow(
ax,
states["q₂"][0] - 0.2,
states["q₂"][1] - R,
states["q₀"][0] + 0.2,
states["q₀"][1] - R,
"b",
connectionstyle="arc3,rad=0.4",
label_offset=(0, -0.4),
)
# q₀ --b--> q₀ (self-loop)
draw_self_loop(ax, *states["q₀"], R, "b", direction="top")
# q₁ --a--> q₁ (self-loop)
draw_self_loop(ax, *states["q₁"], R, "a", direction="top")
# Legend
ax.text(
0.3,
-1.0,
"→ = start ◎ = akceptujący",
fontsize=FS_SMALL,
ha="left",
va="center",
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
# --- Right: Step-by-step trace ---
ax2 = axes[1]
ax2.axis("off")
ax2.set_title(
'Ślad wykonania — wejście: "baab"', fontsize=FS_TITLE, fontweight="bold", pad=10
)
trace_data = [
["Krok", "Czytam", "Stan przed", "Przejście", "Stan po"],
["", "", "q₀ (start)", "", "q₀"],
["1", "b", "q₀", "δ(q₀, b) = q₀", "q₀"],
["2", "a", "q₀", "δ(q₀, a) = q₁", "q₁"],
["3", "a", "q₁", "δ(q₁, a) = q₁", "q₁"],
["4", "b", "q₁", "δ(q₁, b) = q₂", "q₂ ✓"],
]
colors = [GRAY2] + ["white"] * 4 + [LIGHT_GREEN]
table = ax2.table(
cellText=trace_data,
cellLoc="center",
loc="center",
bbox=[0.05, 0.15, 0.9, 0.75],
)
table.auto_set_font_size(False)
table.set_fontsize(FS)
for (row, _col), cell in table.get_celld().items():
cell.set_edgecolor(GRAY3)
if row == 0:
cell.set_facecolor(GRAY2)
cell.set_text_props(fontweight="bold")
else:
cell.set_facecolor(colors[row])
cell.set_height(0.12)
ax2.text(
0.5,
0.05,
'Wynik: q₂ ∈ F → "baab" AKCEPTOWANE ✓',
ha="center",
va="center",
fontsize=FS + 1,
fontweight="bold",
transform=ax2.transAxes,
bbox={"boxstyle": "round,pad=0.4", "facecolor": LIGHT_GREEN, "edgecolor": LN},
)
plt.tight_layout()
plt.savefig(
str(Path(OUTPUT_DIR) / "fa_recognition_example.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ fa_recognition_example.png")
# ============================================================
# 2. PDA Recognition Example — PDA for aⁿbⁿ
# ============================================================
def draw_pda_recognition() -> None:
"""PDA state diagram + step-by-step trace with stack visualization for 'aabb'."""
_fig, axes = plt.subplots(
1, 2, figsize=(11.69, 5.5), gridspec_kw={"width_ratios": [1, 1.4]}
)
# --- Left: State diagram ---
ax = axes[0]
ax.set_xlim(-1, 5.5)
ax.set_ylim(-2, 3)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"PDA — diagram stanów\nL = {aⁿbⁿ | n ≥ 1}",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
R = 0.38
states = {"q₀": (0.8, 0.5), "q₁": (2.8, 0.5), "q₂": (4.8, 0.5)}
draw_state_circle(ax, *states["q₀"], R, "q₀", initial=True)
draw_state_circle(ax, *states["q₁"], R, "q₁")
draw_state_circle(ax, *states["q₂"], R, "q₂", accepting=True, fillcolor=LIGHT_GREEN)
# q₀ --b,A/ε--> q₁
draw_curved_arrow(
ax,
states["q₀"][0] + R,
states["q₀"][1],
states["q₁"][0] - R,
states["q₁"][1],
"b, A → ε\n(pop A)",
connectionstyle="arc3,rad=0.0",
label_offset=(0, 0.4),
)
# q₁ --ε,Z₀/Z₀--> q₂
draw_curved_arrow(
ax,
states["q₁"][0] + R,
states["q₁"][1],
states["q₂"][0] - R,
states["q₂"][1],
"ε, Z₀ → Z₀\n(akceptuj)",
connectionstyle="arc3,rad=0.0",
label_offset=(0, 0.45),
)
# q₀ self-loop: a, Z₀/AZ₀ and a, A/AA
draw_self_loop(
ax, *states["q₀"], R, "a, Z₀ → AZ₀\na, A → AA\n(push A)", direction="top"
)
# q₁ self-loop: b, A/ε
draw_self_loop(ax, *states["q₁"], R, "b, A → ε\n(pop A)", direction="top")
# Key explanation
ax.text(
0.3,
-1.3,
"Notacja: symbol_wejścia, szczyt_stosu → nowy_szczyt\n"
"ε = brak symbolu (przejście spontaniczne lub pusty stos)",
fontsize=FS_SMALL,
ha="left",
va="center",
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
# --- Right: Step trace with stack ---
ax2 = axes[1]
ax2.axis("off")
ax2.set_title(
'Ślad wykonania z wizualizacją stosu — wejście: "aabb"',
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
trace_data = [
["Krok", "Czytam", "Stan", "Stos (szczyt→)", "Operacja"],
["start", "", "q₀", "[Z₀]", ""],
["1", "a", "q₀", "[A, Z₀]", "push A"],
["2", "a", "q₀", "[A, A, Z₀]", "push A"],
["3", "b", "q₁", "[A, Z₀]", "pop A"],
["4", "b", "q₁", "[Z₀]", "pop A"],
["5", "ε", "q₂", "[Z₀]", "akceptuj!"],
]
colors = [
GRAY2,
"white",
LIGHT_BLUE,
LIGHT_BLUE,
LIGHT_YELLOW,
LIGHT_YELLOW,
LIGHT_GREEN,
]
table = ax2.table(
cellText=trace_data,
cellLoc="center",
loc="center",
bbox=[0.02, 0.08, 0.96, 0.82],
)
table.auto_set_font_size(False)
table.set_fontsize(FS)
for (row, _col), cell in table.get_celld().items():
cell.set_edgecolor(GRAY3)
if row == 0:
cell.set_facecolor(GRAY2)
cell.set_text_props(fontweight="bold")
else:
cell.set_facecolor(colors[row])
cell.set_height(0.11)
ax2.text(
0.5,
0.0,
'Wynik: q₂ ∈ F, stos=[Z₀] → "aabb" AKCEPTOWANE ✓\n'
'Intuicja: 2x push A (za "aa") + 2x pop A (za "bb") = stos pusty = OK',
ha="center",
va="center",
fontsize=FS,
fontweight="bold",
transform=ax2.transAxes,
bbox={"boxstyle": "round,pad=0.4", "facecolor": LIGHT_GREEN, "edgecolor": LN},
)
plt.tight_layout()
plt.savefig(
str(Path(OUTPUT_DIR) / "pda_recognition_example.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ pda_recognition_example.png")
# ============================================================
# 3. LBA Recognition Example — LBA for aⁿbⁿcⁿ
# ============================================================
def draw_lba_recognition() -> None:
"""LBA tape visualization showing marking rounds for 'aabbcc'."""
_fig, ax = plt.subplots(1, 1, figsize=(11.69, 6.5))
ax.set_xlim(-0.5, 12)
ax.set_ylim(-1, 10.5)
ax.axis("off")
ax.set_title(
"LBA — rozpoznawanie aⁿbⁿcⁿ (n=2)\n"
"Strategia: w każdej rundzie zaznacz jedno a, b, c",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
CELL_W = 0.9
CELL_H = 0.7
TAPE_X0 = 1.5
HEAD_COLOR = "#FFD700"
def draw_tape(y, cells, head_pos, label, step_label="") -> None:
"""Draw a tape row with cells, head position highlighted."""
ax.text(
0.2,
y + CELL_H / 2,
label,
ha="right",
va="center",
fontsize=FS,
fontweight="bold",
)
for i, (sym, color) in enumerate(cells):
x = TAPE_X0 + i * CELL_W
fc = HEAD_COLOR if i == head_pos else color
rect = mpatches.FancyBboxPatch(
(x, y),
CELL_W,
CELL_H,
boxstyle="round,pad=0.03",
lw=1.2,
edgecolor=LN,
facecolor=fc,
)
ax.add_patch(rect)
ax.text(
x + CELL_W / 2,
y + CELL_H / 2,
sym,
ha="center",
va="center",
fontsize=FS + 2,
fontweight="bold" if sym in ("X", "Y", "Z") else "normal",
family="monospace",
)
if head_pos is not None:
hx = TAPE_X0 + head_pos * CELL_W + CELL_W / 2
ax.annotate(
"",
xy=(hx, y + CELL_H),
xytext=(hx, y + CELL_H + 0.25),
ha="center",
va="bottom",
fontsize=8,
color="black",
)
if step_label:
sx = TAPE_X0 + 6 * CELL_W + 0.5
ax.text(
sx,
y + CELL_H / 2,
step_label,
ha="left",
va="center",
fontsize=FS_SMALL,
bbox={
"boxstyle": "round,pad=0.2",
"facecolor": GRAY4,
"edgecolor": GRAY3,
},
)
W = "white"
MK = GRAY1 # marked cell color
# Row 1: Initial tape
y = 9.0
draw_tape(
y,
[("a", W), ("a", W), ("b", W), ("b", W), ("c", W), ("c", W)],
0,
"Początek",
"taśma = [a, a, b, b, c, c], głowica na 0",
)
# Row 2: After marking first 'a'
y = 7.8
draw_tape(
y,
[("X", MK), ("a", W), ("b", W), ("b", W), ("c", W), ("c", W)],
1,
"R1, krok 1",
"zaznacz a→X, szukaj b",
)
# Row 3: After marking first 'b'
y = 6.6
draw_tape(
y,
[("X", MK), ("a", W), ("Y", MK), ("b", W), ("c", W), ("c", W)],
3,
"R1, krok 2",
"zaznacz b→Y, szukaj c",
)
# Row 4: After marking first 'c'
y = 5.4
draw_tape(
y,
[("X", MK), ("a", W), ("Y", MK), ("b", W), ("Z", MK), ("c", W)],
0,
"R1, krok 3",
"zaznacz c→Z, wróć na początek",
)
# Runda 2 header
y = 4.5
ax.text(
TAPE_X0 + 3 * CELL_W,
y + 0.3,
"═══ RUNDA 2 ═══",
ha="center",
va="center",
fontsize=FS,
fontweight="bold",
color=LN,
)
# Row 5: After marking second 'a'
y = 3.6
draw_tape(
y,
[("X", MK), ("X", MK), ("Y", MK), ("b", W), ("Z", MK), ("c", W)],
2,
"R2, krok 1",
"pomiń X, zaznacz a→X, szukaj b",
)
# Row 6: After marking second 'b'
y = 2.4
draw_tape(
y,
[("X", MK), ("X", MK), ("Y", MK), ("Y", MK), ("Z", MK), ("c", W)],
4,
"R2, krok 2",
"pomiń Y, zaznacz b→Y, szukaj c",
)
# Row 7: After marking second 'c'
y = 1.2
draw_tape(
y,
[("X", MK), ("X", MK), ("Y", MK), ("Y", MK), ("Z", MK), ("Z", MK)],
None,
"R2, krok 3",
"zaznacz c→Z, wróć na początek",
)
# Result
y = 0.0
ax.text(
TAPE_X0 + 3 * CELL_W,
y + 0.3,
'Wszystko zaznaczone → q_acc → "aabbcc" AKCEPTOWANE ✓',
ha="center",
va="center",
fontsize=FS + 1,
fontweight="bold",
bbox={"boxstyle": "round,pad=0.4", "facecolor": LIGHT_GREEN, "edgecolor": LN},
)
# Key
ax.text(
TAPE_X0 + 6 * CELL_W + 0.5,
y + 0.3,
'Ograniczenie LBA:\ngłowica ≤ 6 komórek\n(= |w| = |"aabbcc"|)',
ha="left",
va="center",
fontsize=FS_SMALL,
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": LIGHT_YELLOW,
"edgecolor": GRAY3,
},
)
plt.tight_layout()
plt.savefig(
str(Path(OUTPUT_DIR) / "lba_recognition_example.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ lba_recognition_example.png")
# ============================================================
# 4. TM Recognition Example — TM for 0ⁿ1ⁿ
# ============================================================
def draw_tm_recognition() -> None:
"""TM tape visualization for 0ⁿ1ⁿ with infinite tape shown."""
_fig, ax = plt.subplots(1, 1, figsize=(11.69, 6.5))
ax.set_xlim(-0.5, 13)
ax.set_ylim(-1, 10.5)
ax.axis("off")
ax.set_title(
"TM — rozpoznawanie 0ⁿ1ⁿ (n=2)\n"
"Strategia: zaznacz jedno 0 i jedno 1 w każdej rundzie",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
CELL_W = 0.9
CELL_H = 0.7
TAPE_X0 = 1.5
HEAD_COLOR = "#FFD700"
def draw_tape(y, cells, head_pos, label, step_label="") -> None:
"""Draw tape."""
ax.text(
0.2,
y + CELL_H / 2,
label,
ha="right",
va="center",
fontsize=FS,
fontweight="bold",
)
for i, (sym, color) in enumerate(cells):
x = TAPE_X0 + i * CELL_W
fc = HEAD_COLOR if i == head_pos else color
lw = 1.2
ls = "-"
if sym == "":
ls = "--"
rect = mpatches.FancyBboxPatch(
(x, y),
CELL_W,
CELL_H,
boxstyle="round,pad=0.03",
lw=lw,
edgecolor=LN,
facecolor=fc,
linestyle=ls,
)
ax.add_patch(rect)
ax.text(
x + CELL_W / 2,
y + CELL_H / 2,
sym,
ha="center",
va="center",
fontsize=FS + 2,
fontweight="bold" if sym in ("X", "Y") else "normal",
family="monospace",
color=GRAY3 if sym == "" else LN,
)
# ∞ arrow
last_x = TAPE_X0 + len(cells) * CELL_W
ax.annotate(
"→ ∞",
xy=(last_x + 0.3, y + CELL_H / 2),
fontsize=FS,
ha="left",
va="center",
color=GRAY3,
)
if head_pos is not None:
hx = TAPE_X0 + head_pos * CELL_W + CELL_W / 2
ax.annotate(
"",
xy=(hx, y + CELL_H),
xytext=(hx, y + CELL_H + 0.25),
ha="center",
va="bottom",
fontsize=8,
color="black",
)
if step_label:
sx = TAPE_X0 + 8 * CELL_W + 0.8
ax.text(
sx,
y + CELL_H / 2,
step_label,
ha="left",
va="center",
fontsize=FS_SMALL,
bbox={
"boxstyle": "round,pad=0.2",
"facecolor": GRAY4,
"edgecolor": GRAY3,
},
)
W = "white"
MK = GRAY1
BL = "#F0F0F0" # blank cell
# Row 1: Initial
y = 9.0
draw_tape(
y,
[("0", W), ("0", W), ("1", W), ("1", W), ("", BL), ("", BL), ("", BL)],
0,
"Początek",
"taśma = [0,0,1,1,⊔,⊔,...∞]",
)
# Row 2: Mark first 0
y = 7.8
draw_tape(
y,
[("X", MK), ("0", W), ("1", W), ("1", W), ("", BL), ("", BL), ("", BL)],
1,
"R1, krok 1",
"zaznacz 0→X, idź w prawo",
)
# Row 3: Skip to first 1, mark it
y = 6.6
draw_tape(
y,
[("X", MK), ("0", W), ("Y", MK), ("1", W), ("", BL), ("", BL), ("", BL)],
0,
"R1, krok 2",
"zaznacz 1→Y, wróć na początek",
)
# Runda 2 header
y = 5.8
ax.text(
TAPE_X0 + 3.5 * CELL_W,
y + 0.3,
"═══ RUNDA 2 ═══",
ha="center",
va="center",
fontsize=FS,
fontweight="bold",
)
# Row 4: Mark second 0
y = 4.8
draw_tape(
y,
[("X", MK), ("X", MK), ("Y", MK), ("1", W), ("", BL), ("", BL), ("", BL)],
2,
"R2, krok 1",
"pomiń X, zaznacz 0→X",
)
# Row 5: Mark second 1
y = 3.6
draw_tape(
y,
[("X", MK), ("X", MK), ("Y", MK), ("Y", MK), ("", BL), ("", BL), ("", BL)],
0,
"R2, krok 2",
"pomiń Y, zaznacz 1→Y, wróć",
)
# Row 6: Check — all marked
y = 2.4
draw_tape(
y,
[("X", MK), ("X", MK), ("Y", MK), ("Y", MK), ("", BL), ("", BL), ("", BL)],
None,
"Sprawdzenie",
"brak niezaznaczonych → q_acc",
)
# Result + TM vs LBA comparison
y = 0.8
ax.text(
TAPE_X0 + 3.5 * CELL_W,
y + 0.3,
'"0011" AKCEPTOWANE ✓',
ha="center",
va="center",
fontsize=FS + 1,
fontweight="bold",
bbox={"boxstyle": "round,pad=0.4", "facecolor": LIGHT_GREEN, "edgecolor": LN},
)
y = -0.3
ax.text(
TAPE_X0 + 3.5 * CELL_W,
y + 0.3,
"Różnica TM vs LBA: taśma TM jest nieskończona (⊔ → ∞)\n"
"LBA: głowica ograniczona do |w| komórek\n"
"TM: głowica może wyjść POZA wejście i pisać na pustych ⊔",
ha="center",
va="center",
fontsize=FS_SMALL,
bbox={
"boxstyle": "round,pad=0.4",
"facecolor": LIGHT_YELLOW,
"edgecolor": GRAY3,
},
)
plt.tight_layout()
plt.savefig(
str(Path(OUTPUT_DIR) / "tm_recognition_example.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ tm_recognition_example.png")
# ============================================================
# Main
# ============================================================
if __name__ == "__main__":
print("Generating automata diagrams for PYTANIE 1...")
draw_fa_recognition()
draw_pda_recognition()
draw_lba_recognition()
draw_tm_recognition()
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")

View File

@ -0,0 +1,598 @@
#!/usr/bin/env python3
"""Generate Bellman-Ford negative-weights & negative-cycle diagram for PYTANIE 2.
Two-part figure:
Part 1: Graph with negative edge, Dijkstra WRONG vs Bellman-Ford CORRECT
Part 2: Negative cycle detection (add CB(-3))
A4-compatible, monochrome-friendly, 300 DPI.
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
DPI = 300
BG = "white"
LN = "black"
FS = 8
FS_TITLE = 10
FS_SMALL = 6.5
FS_EDGE = 9
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
LIGHT_GREEN = "#D5E8D4"
LIGHT_RED = "#F8D7DA"
LIGHT_YELLOW = "#FFF9C4"
# --- Graph layout for negative-weight example ---
# S→A(2), A→C(3), S→B(5), B→A(-4)
NEG_POS = {"S": (0.8, 2), "A": (3.3, 3.2), "B": (3.3, 0.8), "C": (5.8, 2)}
NEG_EDGES = [("S", "A", 2), ("A", "C", 3), ("S", "B", 5), ("B", "A", -4)]
def draw_node(
ax,
name,
pos,
color="white",
current=False,
visited=False,
dist_label=None,
fontsize=12,
error=False,
) -> None:
"""Draw node."""
x, y = pos
r = 0.35
lw = 2.5 if current else 1.5
ec = "#D32F2F" if current else ("#D32F2F" if error else LN)
fc = LIGHT_YELLOW if current else (LIGHT_GREEN if visited else color)
if error:
fc = LIGHT_RED
circle = plt.Circle(
(x, y), r, fill=True, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=5
)
ax.add_patch(circle)
ax.text(
x,
y,
name,
ha="center",
va="center",
fontsize=fontsize,
fontweight="bold",
zorder=6,
)
if dist_label is not None:
bbox_ec = "#D32F2F" if error else GRAY3
bbox_fc = LIGHT_RED if error else "white"
ax.text(
x,
y - 0.55,
f"d={dist_label}",
ha="center",
va="center",
fontsize=FS,
zorder=6,
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": bbox_fc,
"edgecolor": bbox_ec,
"alpha": 0.95,
},
)
def draw_edge(
ax,
pos1,
pos2,
weight,
highlighted=False,
relaxed=False,
negative=False,
cycle_edge=False,
offset=0.0,
) -> None:
"""Draw edge."""
x1, y1 = pos1
x2, y2 = pos2
dx, dy = x2 - x1, y2 - y1
length = np.sqrt(dx**2 + dy**2)
r = 0.38
sx = x1 + r * dx / length
sy = y1 + r * dy / length
ex = x2 - r * dx / length
ey = y2 - r * dy / length
# Offset perpendicular for parallel edges
if offset != 0:
perp_x = -dy / length * offset
perp_y = dx / length * offset
sx += perp_x
sy += perp_y
ex += perp_x
ey += perp_y
if cycle_edge:
color = "#D32F2F"
lw = 2.5
ls = "--"
elif negative or relaxed:
color = "#D32F2F"
lw = 2.5
ls = "-"
elif highlighted:
color = "#1565C0"
lw = 2.0
ls = "-"
else:
color = GRAY3
lw = 1.5
ls = "-"
# Arrow
ax.annotate(
"",
xy=(ex, ey),
xytext=(sx, sy),
arrowprops={
"arrowstyle": "->",
"color": color,
"lw": lw,
"linestyle": ls,
"shrinkA": 0,
"shrinkB": 0,
},
zorder=2,
)
# Weight label
mx = (sx + ex) / 2
my = (sy + ey) / 2
perp_x = -dy / length * 0.22
perp_y = dx / length * 0.22
if offset != 0:
perp_x *= 0.5
perp_y *= 0.5
weight_str = str(weight)
edge_fc = LIGHT_RED if negative or cycle_edge else "white"
edge_ec = "#D32F2F" if negative or cycle_edge else GRAY3
ax.text(
mx + perp_x,
my + perp_y,
weight_str,
ha="center",
va="center",
fontsize=FS_EDGE,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": edge_fc,
"edgecolor": edge_ec,
"alpha": 0.95,
},
zorder=4,
)
def draw_neg_graph(
ax,
edges,
title="",
dist=None,
current=None,
visited=None,
relaxed_edges=None,
error_nodes=None,
extra_edges=None,
node_positions=None,
) -> None:
"""Draw neg graph."""
if visited is None:
visited = set()
if relaxed_edges is None:
relaxed_edges = set()
if dist is None:
dist = {}
if error_nodes is None:
error_nodes = set()
if node_positions is None:
node_positions = NEG_POS
ax.set_xlim(-0.5, 7.0)
ax.set_ylim(-0.8, 4.5)
ax.set_aspect("equal")
ax.axis("off")
if title:
ax.set_title(title, fontsize=FS, fontweight="bold", pad=5)
all_edges = list(edges)
if extra_edges:
all_edges += extra_edges
for u, v, w in all_edges:
rl = (u, v) in relaxed_edges
neg = w < 0
cycle = extra_edges and (u, v, w) in extra_edges
# If B→A and A→B both exist, offset them
off = 0.0
draw_edge(
ax,
node_positions[u],
node_positions[v],
w,
relaxed=rl,
negative=neg,
cycle_edge=cycle,
offset=off,
)
for name, pos in node_positions.items():
is_current = name == current
is_visited = name in visited
d_label = dist.get(name)
is_error = name in error_nodes
draw_node(
ax,
name,
pos,
current=is_current,
visited=is_visited,
dist_label=d_label,
error=is_error,
)
def generate_bf_negative_weights() -> None:
"""Two-row figure.
Row 1: Graph structure + Dijkstra WRONG + Bellman-Ford CORRECT
Row 2: B-F iterations 1-3 step by step.
"""
fig = plt.figure(figsize=(14, 10))
fig.suptitle(
"Bellman-Ford — ujemne wagi vs Dijkstra\n"
"Graf: S→A(2), A→C(3), S→B(5), B→A(-4). Start = S",
fontsize=FS_TITLE + 1,
fontweight="bold",
y=0.99,
)
# ---- Row 1: Graph + Dijkstra wrong + BF correct ----
# Panel 1: The graph structure
ax1 = fig.add_subplot(2, 3, 1)
draw_neg_graph(
ax1,
NEG_EDGES,
title="Graf z ujemną wagą\n(B→A = -4, zaznaczona na czerwono)",
dist={"S": "0", "A": "?", "B": "?", "C": "?"},
)
# START label
ax1.annotate(
"START",
xy=(NEG_POS["S"][0] - 0.35, NEG_POS["S"][1]),
xytext=(NEG_POS["S"][0] - 1.2, NEG_POS["S"][1]),
fontsize=FS,
fontweight="bold",
color="#D32F2F",
arrowprops={"arrowstyle": "->", "color": "#D32F2F", "lw": 2},
va="center",
)
# Panel 2: Dijkstra — WRONG
ax2 = fig.add_subplot(2, 3, 2)
draw_neg_graph(
ax2,
NEG_EDGES,
title="Dijkstra — BŁĘDNY wynik\nA zamknięty z d=2, nie poprawia przy B→A",
dist={"S": "0", "A": "2", "B": "5", "C": "5"},
visited={"S", "A", "B", "C"},
error_nodes={"A", "C"},
)
# Add "WRONG" annotations
ax2.text(
NEG_POS["A"][0] + 0.6,
NEG_POS["A"][1] + 0.3,
"✗ powinno 1",
fontsize=FS_SMALL,
color="#D32F2F",
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.1",
"facecolor": LIGHT_RED,
"edgecolor": "#D32F2F",
"alpha": 0.9,
"lw": 0.5,
},
)
ax2.text(
NEG_POS["C"][0] + 0.05,
NEG_POS["C"][1] + 0.55,
"✗ powinno 4",
fontsize=FS_SMALL,
color="#D32F2F",
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.1",
"facecolor": LIGHT_RED,
"edgecolor": "#D32F2F",
"alpha": 0.9,
"lw": 0.5,
},
)
# Panel 3: Bellman-Ford — CORRECT
ax3 = fig.add_subplot(2, 3, 3)
draw_neg_graph(
ax3,
NEG_EDGES,
title="Bellman-Ford — POPRAWNY wynik\nUjemna waga B→A poprawnie propagowana",
dist={"S": "0", "A": "1", "B": "5", "C": "4"},
visited={"S", "A", "B", "C"},
relaxed_edges={("B", "A")},
)
ax3.text(
NEG_POS["A"][0] + 0.6,
NEG_POS["A"][1] + 0.3,
"✓ poprawne!",
fontsize=FS_SMALL,
color="#006400",
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.1",
"facecolor": LIGHT_GREEN,
"edgecolor": "#006400",
"alpha": 0.9,
"lw": 0.5,
},
)
ax3.text(
NEG_POS["C"][0] + 0.05,
NEG_POS["C"][1] + 0.55,
"✓ poprawne!",
fontsize=FS_SMALL,
color="#006400",
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.1",
"facecolor": LIGHT_GREEN,
"edgecolor": "#006400",
"alpha": 0.9,
"lw": 0.5,
},
)
# ---- Row 2: B-F iterations step by step ----
iterations = [
{
"title": "B-F Iteracja 1\nRelaksuj WSZYSTKIE krawędzie",
"dist": {"S": "0", "A": "1", "B": "5", "C": "5"},
"relaxed": {("S", "A"), ("A", "C"), ("S", "B"), ("B", "A")},
"detail": (
"S→A: 0+2=2<∞ → A=2\n"
"A→C: 2+3=5<∞ → C=5\n"
"S→B: 0+5=5<∞ → B=5\n"
"B→A: 5-4=1<2 → A=1 ✓"
),
},
{
"title": "B-F Iteracja 2\nPropagacja poprawionego A",
"dist": {"S": "0", "A": "1", "B": "5", "C": "4"},
"relaxed": {("A", "C")},
"detail": (
"S→A: 0+2=2>1 ✗\nA→C: 1+3=4<5 → C=4 ✓\nS→B: 0+5=5=5 ✗\nB→A: 5-4=1=1 ✗"
),
},
{
"title": "B-F Iteracja 3\nBrak zmian → stabilne!",
"dist": {"S": "0", "A": "1", "B": "5", "C": "4"},
"relaxed": set(),
"detail": (
"Wszystkie krawędzie:\n"
"brak poprawy ✗\n"
"→ wynik stabilny\n"
"→ BRAK cyklu ujemnego"
),
},
]
for i, it in enumerate(iterations):
ax = fig.add_subplot(2, 3, i + 4)
draw_neg_graph(
ax,
NEG_EDGES,
title=it["title"],
dist=it["dist"],
visited={"S", "A", "B", "C"},
relaxed_edges=it["relaxed"],
)
# Detail text below graph
ax.text(
3.2,
-0.5,
it["detail"],
ha="center",
va="top",
fontsize=FS_SMALL,
family="monospace",
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
# Bottom note
fig.text(
0.5,
0.01,
"Dijkstra zamyka wierzchołki na stałe (zachłanność) → ujemna waga B→A(-4) nie może poprawić zamkniętego A.\n"
"Bellman-Ford relaksuje WSZYSTKIE krawędzie w każdej iteracji → ujemne wagi propagują się poprawnie.",
ha="center",
fontsize=FS,
fontweight="bold",
bbox={"boxstyle": "round,pad=0.3", "facecolor": LIGHT_YELLOW, "edgecolor": LN},
)
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
plt.savefig(
str(Path(OUTPUT_DIR) / "bellman_ford_negative_weights.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ bellman_ford_negative_weights.png")
def generate_bf_negative_cycle() -> None:
"""Figure showing negative cycle detection.
Graph: SA(2), AC(3), SB(5), BA(-4), CB(-3) [added edge]
Cycle: BACB = -4+3+(-3) = -4 < 0.
"""
fig = plt.figure(figsize=(14, 5.5))
fig.suptitle(
"Bellman-Ford — wykrywanie cyklu ujemnego\n"
"Dodano krawędź C→B(-3). Cykl: B→A→C→B = -4+3+(-3) = -4 < 0",
fontsize=FS_TITLE + 1,
fontweight="bold",
y=0.99,
)
# Panel 1: Graph with cycle highlighted
ax1 = fig.add_subplot(1, 3, 1)
draw_neg_graph(
ax1,
NEG_EDGES,
title="Graf z cyklem ujemnym\nDodana krawędź C→B(-3) — przerywana",
dist={"S": "0", "A": "?", "B": "?", "C": "?"},
extra_edges=[("C", "B", -3)],
)
# Mark cycle
ax1.annotate(
"CYKL\n-4+3+(-3)=-4<0",
xy=(3.3, 2.0),
fontsize=FS,
fontweight="bold",
color="#D32F2F",
ha="center",
va="center",
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": LIGHT_RED,
"edgecolor": "#D32F2F",
"alpha": 0.9,
},
)
# Panel 2: After V-1 iterations — still changing
ax2 = fig.add_subplot(1, 3, 2)
draw_neg_graph(
ax2,
NEG_EDGES,
title="Po V-1=3 iteracjach\ndist wciąż maleje (niestabilne!)",
dist={"S": "0", "A": "-7", "B": "-4", "C": "-4"},
visited={"S", "A", "B", "C"},
error_nodes={"A", "B", "C"},
extra_edges=[("C", "B", -3)],
)
ax2.text(
3.2,
-0.4,
"Każde okrążenie cyklu\nzmniejsza dist o 4.\nDist → -∞ (brak minimum!)",
ha="center",
va="top",
fontsize=FS_SMALL,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": LIGHT_RED,
"edgecolor": "#D32F2F",
},
)
# Panel 3: V-th iteration detects
ax3 = fig.add_subplot(1, 3, 3)
ax3.axis("off")
ax3.set_xlim(0, 10)
ax3.set_ylim(0, 10)
detection_text = (
"V-ta iteracja (sprawdzenie):\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"for (src, dst, w) in edges:\n"
" if dist[src]+w < dist[dst]:\n"
" return None # CYKL!\n\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"Sprawdzamy np. krawędź B→A:\n"
" dist[B] + (-4) = -4 + (-4) = -8\n"
" -8 < dist[A] = -7\n"
" → NADAL SIĘ POPRAWIA!\n"
" → CYKL UJEMNY WYKRYTY!\n\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"Wynik: return None\n"
"(najkrótsza ścieżka nie istnieje)"
)
ax3.text(
5,
5,
detection_text,
ha="center",
va="center",
fontsize=FS + 0.5,
family="monospace",
bbox={
"boxstyle": "round,pad=0.6",
"facecolor": LIGHT_RED,
"edgecolor": "#D32F2F",
"lw": 2,
},
)
ax3.set_title(
"Wykrywanie — V-ta iteracja\nJeśli cokolwiek się poprawia → cykl ujemny!",
fontsize=FS,
fontweight="bold",
pad=5,
)
# Bottom note
fig.text(
0.5,
0.01,
"Bez cyklu ujemnego: po V-1 iteracjach dist jest stabilne. "
"Z cyklem ujemnym: dist maleje w nieskończoność → V-ta iteracja to wykrywa.",
ha="center",
fontsize=FS,
fontweight="bold",
bbox={"boxstyle": "round,pad=0.3", "facecolor": LIGHT_YELLOW, "edgecolor": LN},
)
plt.tight_layout(rect=[0, 0.06, 1, 0.94])
plt.savefig(
str(Path(OUTPUT_DIR) / "bellman_ford_negative_cycle.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ bellman_ford_negative_cycle.png")
if __name__ == "__main__":
print("Generating Bellman-Ford negative weight diagrams...")
generate_bf_negative_weights()
generate_bf_negative_cycle()
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,952 @@
#!/usr/bin/env python3
"""Generate pattern cataloguing diagrams for PYTANIE 14 (AIS).
1. Pattern Template Structure the standard fields every pattern has
2. Catalog Classification Map catalogs arranged by scope & domain
3. Pattern Language Network how patterns reference each other.
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import matplotlib.pyplot as plt
import numpy as np
DPI = 300
BG = "white"
LN = "black"
FS = 9
FS_TITLE = 13
FS_SMALL = 7.5
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
GRAY5 = "#C0C0C0"
def draw_box(
ax,
x,
y,
w,
h,
text,
fill="white",
lw=1.2,
fontsize=FS,
fontweight="normal",
ha="center",
va="center",
rounded=True,
) -> None:
"""Draw box."""
if rounded:
rect = FancyBboxPatch(
(x, y), w, h, boxstyle="round,pad=0.08", lw=lw, edgecolor=LN, facecolor=fill
)
else:
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
ax.add_patch(rect)
ax.text(
x + w / 2,
y + h / 2,
text,
ha=ha,
va=va,
fontsize=fontsize,
fontweight=fontweight,
wrap=True,
)
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style="->", color=LN) -> None:
"""Draw arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={"arrowstyle": style, "color": color, "lw": lw},
)
# ============================================================
# 1. Pattern Template Structure (NaPSiRoKo mnemonic)
# ============================================================
def generate_pattern_template() -> None:
"""Generate pattern template."""
fig, ax = plt.subplots(figsize=(8.27, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG)
ax.set_title(
"Szablon opisu wzorca \u2014 \u201eNaPSiRoKo\u201d",
fontsize=FS_TITLE,
fontweight="bold",
pad=15,
)
# Main card outline
card_x, card_y, card_w, card_h = 1.5, 0.5, 7, 7
card = FancyBboxPatch(
(card_x, card_y),
card_w,
card_h,
boxstyle="round,pad=0.15",
lw=2.5,
edgecolor=LN,
facecolor=GRAY4,
)
ax.add_patch(card)
# Title of card
ax.text(
card_x + card_w / 2,
card_y + card_h - 0.35,
"KARTA WZORCA",
ha="center",
va="center",
fontsize=FS_TITLE,
fontweight="bold",
)
# Fields as horizontal bands
fields = [
("Na", "NAZWA", "Layered, Observer, Microservices", GRAY1),
(
"P",
"PROBLEM / KONTEKST",
"Kiedy stosować? Jaki problem rozwiązuje?",
"white",
),
(
"Si",
"SIŁY (forces)",
"Konkurencyjne wymagania do pogodzenia\n(np. testowalność vs wydajność)",
GRAY1,
),
("Ro", "ROZWIĄZANIE", "Struktura, diagram, zachowanie", "white"),
("Ko", "KONSEKWENCJE", "Tradeoffs: co zyskujemy, co tracimy", GRAY1),
]
band_x = card_x + 0.3
band_w = card_w - 0.6
band_h = 1.05
start_y = card_y + card_h - 1.1
for i, (abbr, title, desc, fill) in enumerate(fields):
by = start_y - i * (band_h + 0.15)
# Abbreviation circle on the left
circle = plt.Circle(
(band_x + 0.35, by + band_h / 2),
0.28,
lw=1.5,
edgecolor=LN,
facecolor=GRAY2,
)
ax.add_patch(circle)
ax.text(
band_x + 0.35,
by + band_h / 2,
abbr,
ha="center",
va="center",
fontsize=10,
fontweight="bold",
)
# Field box
fx = band_x + 0.8
fw = band_w - 0.8
rect = FancyBboxPatch(
(fx, by),
fw,
band_h,
boxstyle="round,pad=0.06",
lw=1,
edgecolor=LN,
facecolor=fill,
)
ax.add_patch(rect)
ax.text(
fx + 0.15,
by + band_h - 0.25,
title,
ha="left",
va="center",
fontsize=FS,
fontweight="bold",
)
ax.text(
fx + 0.15,
by + 0.25,
desc,
ha="left",
va="center",
fontsize=FS_SMALL,
fontstyle="italic",
color="#444444",
)
# Arrow connecting fields
if i < len(fields) - 1:
draw_arrow(ax, band_x + 0.35, by - 0.02, band_x + 0.35, by - 0.13, lw=1.0)
# Extra fields note at bottom
ax.text(
card_x + card_w / 2,
card_y + 0.25,
"+ Powiązane wzorce • Znane zastosowania • Warianty",
ha="center",
va="center",
fontsize=FS_SMALL,
fontstyle="italic",
)
# Mnemonic reminder on the right
ax.text(
9.8,
4,
"Mnemonik:\nNaPSiRoKo",
ha="center",
va="center",
fontsize=10,
fontweight="bold",
rotation=90,
color="#666666",
)
fig.tight_layout()
out = str(Path(OUTPUT_DIR) / "q14_pattern_template.png")
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f" Saved: {out}")
# ============================================================
# 2. Catalog Classification Map
# ============================================================
def generate_catalog_map() -> None:
"""Generate catalog map."""
fig, ax = plt.subplots(figsize=(8.27, 7))
ax.set_xlim(0, 12)
ax.set_ylim(0, 9)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG)
ax.set_title(
"Mapa katalog\u00f3w wzorc\u00f3w \u2014 \u201ePawe\u0142 Gra\u0142 Efektownie Pod Chmurami\u201d",
fontsize=FS_TITLE,
fontweight="bold",
pad=15,
)
# Y-axis: Scale (architectural → design → idiom)
ax.text(
0.3,
7.8,
"SKALA",
fontsize=10,
fontweight="bold",
ha="center",
va="center",
rotation=90,
)
ax.annotate(
"",
xy=(0.3, 2.0),
xytext=(0.3, 7.5),
arrowprops={"arrowstyle": "->", "lw": 1.5, "color": LN},
)
scale_labels = [
(7.0, "Architektoniczny\n(cały system)"),
(5.0, "Projektowy\n(klasa/obiekt)"),
(3.0, "Idiomatyczny\n(linia kodu)"),
]
for sy, label in scale_labels:
ax.text(
1.0,
sy,
label,
fontsize=FS_SMALL,
ha="left",
va="center",
fontstyle="italic",
)
ax.plot([0.15, 0.45], [sy, sy], color=GRAY3, lw=0.8, ls="--")
# X-axis: Domain
ax.text(
6.5,
1.2,
"DOMENA ZASTOSOWANIA",
fontsize=10,
fontweight="bold",
ha="center",
va="center",
)
ax.annotate(
"",
xy=(11.5, 1.5),
xytext=(2.0, 1.5),
arrowprops={"arrowstyle": "->", "lw": 1.5, "color": LN},
)
# Catalog boxes positioned by scale x domain
catalogs = [
# (x, y, w, h, name, subtitle, fill, mnemonic_letter)
(
2.5,
6.2,
2.5,
1.4,
"POSA",
"1996 • Buschmann\nLayers, Broker,\nPipes & Filters, MVC",
GRAY1,
"P",
),
(
2.5,
4.2,
2.5,
1.4,
"GoF",
"1994 • Gamma et al.\n23 wzorce:\n5 kreac. / 7 strukt. / 11 behaw.",
GRAY2,
"G",
),
(
5.5,
6.2,
2.5,
1.4,
"EIP",
"2003 • Hohpe & Woolf\nMessage Channel,\nRouter, Aggregator",
GRAY1,
"E",
),
(
5.5,
4.2,
2.5,
1.4,
"PoEAA",
"2002 • M. Fowler\nRepository, Unit of Work,\nDomain Model",
"white",
"P",
),
(
8.5,
6.2,
2.8,
1.4,
"Cloud\nPatterns",
"~2015 • Azure/AWS\nCircuit Breaker,\nSaga, Sidecar",
GRAY1,
"C",
),
]
for cx, cy, cw, ch, name, sub, fill, ml in catalogs:
rect = FancyBboxPatch(
(cx, cy),
cw,
ch,
boxstyle="round,pad=0.1",
lw=1.5,
edgecolor=LN,
facecolor=fill,
)
ax.add_patch(rect)
ax.text(
cx + cw / 2,
cy + ch - 0.3,
name,
ha="center",
va="center",
fontsize=10,
fontweight="bold",
)
ax.text(
cx + cw / 2,
cy + 0.4,
sub,
ha="center",
va="center",
fontsize=FS_SMALL,
linespacing=1.3,
)
# Mnemonic letter in corner
circle = plt.Circle(
(cx + 0.25, cy + ch - 0.25), 0.2, lw=1, edgecolor=LN, facecolor=GRAY5
)
ax.add_patch(circle)
ax.text(
cx + 0.25,
cy + ch - 0.25,
ml,
ha="center",
va="center",
fontsize=8,
fontweight="bold",
)
# Mnemonic bar at bottom
mnem_y = 2.2
ax.text(
6.0,
mnem_y,
"PGEP+C → Paweł Grał Efektownie Pod Chmurami",
ha="center",
va="center",
fontsize=10,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": GRAY4,
"edgecolor": LN,
"lw": 1.5,
},
)
# Domain labels along x-axis
domains = [
(3.75, 1.7, "Architektura"),
(6.75, 1.7, "Integracja / Enterprise"),
(9.9, 1.7, "Chmura"),
]
for dx, dy, dlabel in domains:
ax.text(
dx,
dy,
dlabel,
ha="center",
va="center",
fontsize=FS_SMALL,
fontstyle="italic",
)
fig.tight_layout()
out = str(Path(OUTPUT_DIR) / "q14_catalog_map.png")
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f" Saved: {out}")
# ============================================================
# 3. Three Pillars of Cataloguing
# ============================================================
def generate_three_pillars() -> None:
"""Generate three pillars."""
fig, ax = plt.subplots(figsize=(8.27, 5.5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG)
ax.set_title(
"Jak są katalogowane wzorce? — Trzy filary",
fontsize=FS_TITLE,
fontweight="bold",
pad=15,
)
# Roof / banner
roof_pts = np.array([[1, 5.5], [6, 6.8], [11, 5.5]])
roof = plt.Polygon(roof_pts, closed=True, lw=2, edgecolor=LN, facecolor=GRAY4)
ax.add_patch(roof)
ax.text(
6,
6.0,
"KATALOGOWANIE WZORCÓW",
ha="center",
va="center",
fontsize=11,
fontweight="bold",
)
# Three pillars
pillars = [
(
1.3,
"1. SZABLON\nOPISU",
"Każdy wzorzec ma\nte same pola:\nNazwa → Problem\n→ Siły → Rozwiązanie\n→ Konsekwencje",
"Analogia:\nformatka\nencyklopedii",
),
(
4.8,
"2. KLASYFIKACJA\nWIELOOSIOWA",
"Osie podziału:\n• Skala (arch/proj/idiom)\n• Domena problemu\n• Atrybut jakościowy\n• Domena zastosowania",
"Analogia:\nkategorie\nw bibliotece",
),
(
8.3,
"3. JĘZYK\nWZORCÓW",
"Wzorce referują się\nwzajemnie tworząc\nsieć/graf:\nA → wymaga → B\nB → wariant → C",
"Analogia:\n\u201ezobacz te\u017c\u201d\nw encyklopedii",
),
]
for px, title, desc, analogy in pillars:
pw, ph = 2.8, 5.0
py = 0.5
# Pillar rectangle
rect = FancyBboxPatch(
(px, py),
pw,
ph,
boxstyle="round,pad=0.1",
lw=1.8,
edgecolor=LN,
facecolor="white",
)
ax.add_patch(rect)
# Title
ax.text(
px + pw / 2,
py + ph - 0.55,
title,
ha="center",
va="center",
fontsize=9,
fontweight="bold",
)
# Horizontal line under title
ax.plot(
[px + 0.2, px + pw - 0.2], [py + ph - 1.0, py + ph - 1.0], color=LN, lw=0.8
)
# Description
ax.text(
px + pw / 2,
py + ph / 2 - 0.3,
desc,
ha="center",
va="center",
fontsize=FS_SMALL,
linespacing=1.4,
)
# Analogy box at bottom
analogy_rect = FancyBboxPatch(
(px + 0.2, py + 0.15),
pw - 0.4,
1.0,
boxstyle="round,pad=0.06",
lw=0.8,
edgecolor=GRAY3,
facecolor=GRAY1,
)
ax.add_patch(analogy_rect)
ax.text(
px + pw / 2,
py + 0.65,
analogy,
ha="center",
va="center",
fontsize=FS_SMALL,
fontstyle="italic",
color="#555555",
)
fig.tight_layout()
out = str(Path(OUTPUT_DIR) / "q14_three_pillars.png")
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f" Saved: {out}")
# ============================================================
# 4. Filled-in Observer Pattern Card
# ============================================================
def generate_observer_card_filled() -> None:
"""Generate observer card filled."""
fig, ax = plt.subplots(figsize=(8.27, 8.5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG)
ax.set_title(
"Wypełniona karta wzorca — Observer (GoF)",
fontsize=FS_TITLE,
fontweight="bold",
pad=15,
)
# Main card outline
card_x, card_y, card_w, card_h = 0.8, 0.3, 8.4, 9.2
card = FancyBboxPatch(
(card_x, card_y),
card_w,
card_h,
boxstyle="round,pad=0.15",
lw=2.5,
edgecolor=LN,
facecolor=GRAY4,
)
ax.add_patch(card)
# Fields with actual Observer content
fields = [
("Na", "NAZWA", "Observer", GRAY2, True),
(
"P",
"PROBLEM",
"Obiekt (Subject) zmienia stan → wielu zależnych\n"
"obiektów musi zareagować, ale Subject nie\n"
"powinien znać ich konkretnych typów.",
GRAY1,
False,
),
(
"Si",
"SIŁY",
"• loose coupling (nie znać obserwatorów z nazwy)\n"
" vs koszt powiadomień (N obserwatorów = N wywołań)\n"
"• otwartość na rozszerzenia vs złożoność debugowania",
"white",
False,
),
(
"Ro",
"ROZWIĄZANIE",
"Subject przechowuje listę Observer.\n"
"Metody: attach(o), detach(o), notify().\n"
"notify() iteruje po liście i woła update()\n"
"na każdym obserwatorze.",
GRAY1,
False,
),
(
"Ko",
"KONSEKWENCJE",
"(+) Luźne wiązanie — Subject ↔ Observer\n"
"(+) Nowi obserwatorzy bez zmian w Subject\n"
"(-) Kaskada powiadomień może być kosztowna\n"
"(-) Memory leaks jeśli nie detach()",
"white",
False,
),
]
band_x = card_x + 0.3
band_w = card_w - 0.6
start_y = card_y + card_h - 0.65
for i, (abbr, title, content, fill, is_title_field) in enumerate(fields):
if is_title_field:
band_h = 0.7
elif i == 1:
band_h = 1.3
elif i == 2:
band_h = 1.4
elif i == 3:
band_h = 1.5
else:
band_h = 1.5
by = start_y - sum(
(0.7 if j == 0 else 1.3 if j == 1 else 1.4 if j == 2 else 1.5) + 0.15
for j in range(i)
)
# Abbreviation circle
circle = plt.Circle(
(band_x + 0.35, by + band_h / 2),
0.28,
lw=1.5,
edgecolor=LN,
facecolor=GRAY3,
)
ax.add_patch(circle)
ax.text(
band_x + 0.35,
by + band_h / 2,
abbr,
ha="center",
va="center",
fontsize=10,
fontweight="bold",
)
# Field box
fx = band_x + 0.8
fw = band_w - 0.8
rect = FancyBboxPatch(
(fx, by),
fw,
band_h,
boxstyle="round,pad=0.06",
lw=1,
edgecolor=LN,
facecolor=fill,
)
ax.add_patch(rect)
if is_title_field:
ax.text(
fx + fw / 2,
by + band_h / 2,
f"{title}: {content}",
ha="center",
va="center",
fontsize=12,
fontweight="bold",
)
else:
ax.text(
fx + 0.15,
by + band_h - 0.2,
title,
ha="left",
va="center",
fontsize=FS,
fontweight="bold",
)
ax.text(
fx + 0.15,
by + band_h / 2 - 0.15,
content,
ha="left",
va="center",
fontsize=FS_SMALL,
family="monospace",
linespacing=1.3,
)
# Arrow
if i < len(fields) - 1:
draw_arrow(ax, band_x + 0.35, by - 0.02, band_x + 0.35, by - 0.13, lw=1.0)
# Extra info at bottom
extra_y = 0.55
extras = [
"Powiązane: Mediator (centralizuje), Pub/Sub (rozproszony), MVC (View = Observer)",
"Znane użycia: Java Swing listeners, C# event/delegate, React useState, DOM addEventListener",
]
for j, txt in enumerate(extras):
ax.text(
card_x + card_w / 2,
extra_y + (1 - j) * 0.25,
txt,
ha="center",
va="center",
fontsize=FS_SMALL,
fontstyle="italic",
color="#444444",
)
fig.tight_layout()
out = str(Path(OUTPUT_DIR) / "q14_observer_card_filled.png")
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f" Saved: {out}")
# ============================================================
# 5. Pattern Language Navigation Graph
# ============================================================
def generate_pattern_language_navigation() -> None:
"""Generate pattern language navigation."""
fig, ax = plt.subplots(figsize=(8.27, 9))
ax.set_xlim(0, 12)
ax.set_ylim(0, 12)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG)
ax.set_title(
'Język wzorców — nawigacja „problem → wzorzec → nowy problem"',
fontsize=FS_TITLE,
fontweight="bold",
pad=15,
)
# Node positions: (x, y, label, is_pattern, fill)
# Left column: problems; Right column: patterns
nodes = [
# Problems (left, rounded rect, white)
(1.5, 10.5, "Monolith\nnie skaluje się", False, "white"),
(1.5, 8.2, "Jak routować\nżądania do\nserwisów?", False, "white"),
(1.5, 5.9, "Co gdy serwis\nnie odpowiada?", False, "white"),
(1.5, 3.6, "Jak zachować\nspójność\ntransakcji?", False, "white"),
(1.5, 1.3, "Jak odnaleźć\nadres serwisu?", False, "white"),
# Patterns (right, filled rect, gray)
(7.0, 9.3, "Microservices", True, GRAY2),
(7.0, 7.0, "API Gateway", True, GRAY2),
(7.0, 4.7, "Circuit Breaker", True, GRAY2),
(7.0, 2.4, "Saga", True, GRAY2),
(10.0, 5.9, "Service\nDiscovery", True, GRAY1),
]
# Draw nodes
node_w_prob = 2.8
node_h_prob = 1.3
node_w_pat = 2.5
node_h_pat = 1.0
for nx, ny, label, is_pattern, fill in nodes:
if is_pattern:
w, h = node_w_pat, node_h_pat
rect = FancyBboxPatch(
(nx - w / 2, ny - h / 2),
w,
h,
boxstyle="round,pad=0.1",
lw=2,
edgecolor=LN,
facecolor=fill,
)
ax.add_patch(rect)
ax.text(
nx, ny, label, ha="center", va="center", fontsize=10, fontweight="bold"
)
else:
w, h = node_w_prob, node_h_prob
rect = FancyBboxPatch(
(nx - w / 2, ny - h / 2),
w,
h,
boxstyle="round,pad=0.1",
lw=1.2,
edgecolor=LN,
facecolor=fill,
linestyle="--",
)
ax.add_patch(rect)
ax.text(
nx,
ny,
label,
ha="center",
va="center",
fontsize=FS_SMALL,
fontstyle="italic",
)
# Arrows: problem → pattern (solid), pattern → problem (dashed label)
arrows = [
# (x1, y1, x2, y2, label, style)
(2.9, 10.5, 5.75, 9.5, "rozwiązuje →", "->", 1.5),
(7.0, 8.8, 2.9, 8.5, "← rodzi problem", "->", 1.0),
(2.9, 8.0, 5.75, 7.2, "rozwiązuje →", "->", 1.5),
(7.0, 6.5, 2.9, 6.2, "← rodzi problem", "->", 1.0),
(2.9, 5.7, 5.75, 5.0, "rozwiązuje →", "->", 1.5),
(7.0, 4.2, 2.9, 3.9, "← rodzi problem", "->", 1.0),
(2.9, 3.3, 5.75, 2.6, "rozwiązuje →", "->", 1.5),
# Microservices → Service Discovery
(8.25, 9.0, 9.5, 6.5, "wymaga →", "->", 1.0),
# Problem → Service Discovery
(2.9, 1.3, 8.75, 5.6, "rozwiązuje →", "->", 1.2),
]
for x1, y1, x2, y2, label, style, lw in arrows:
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={
"arrowstyle": style,
"color": LN,
"lw": lw,
"connectionstyle": "arc3,rad=0.05",
},
)
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
ax.text(
mx,
my + 0.2,
label,
ha="center",
va="center",
fontsize=6.5,
fontstyle="italic",
color="#555555",
bbox={
"boxstyle": "round,pad=0.1",
"facecolor": "white",
"edgecolor": "none",
"alpha": 0.8,
},
)
# Legend
legend_y = 0.3
# Problem node
r1 = FancyBboxPatch(
(1.0, legend_y - 0.2),
1.5,
0.4,
boxstyle="round,pad=0.05",
lw=1,
edgecolor=LN,
facecolor="white",
linestyle="--",
)
ax.add_patch(r1)
ax.text(1.75, legend_y, "Problem", ha="center", va="center", fontsize=7)
# Pattern node
r2 = FancyBboxPatch(
(3.5, legend_y - 0.2),
1.5,
0.4,
boxstyle="round,pad=0.05",
lw=1.5,
edgecolor=LN,
facecolor=GRAY2,
)
ax.add_patch(r2)
ax.text(
4.25,
legend_y,
"Wzorzec",
ha="center",
va="center",
fontsize=7,
fontweight="bold",
)
ax.text(
6.5,
legend_y,
"Nawigacja: Problem → Wzorzec → Nowy Problem → Wzorzec → ...",
ha="left",
va="center",
fontsize=7,
fontstyle="italic",
)
fig.tight_layout()
out = str(Path(OUTPUT_DIR) / "q14_pattern_language_navigation.png")
fig.savefig(out, dpi=DPI, bbox_inches="tight", facecolor=BG)
plt.close(fig)
print(f" Saved: {out}")
# ============================================================
# Main
# ============================================================
if __name__ == "__main__":
print("Generating PYTANIE 14 diagrams...")
generate_pattern_template()
generate_catalog_map()
generate_three_pillars()
generate_observer_card_filled()
generate_pattern_language_navigation()
print("Done!")

View File

@ -0,0 +1,708 @@
#!/usr/bin/env python3
"""Generate 4 process modeling diagrams (BPMN, UML Activity, EPC, Flowchart).
all representing the same process: "Obsluga reklamacji" (Complaint Handling).
Output: A4-compatible, black & white, laser-printer-friendly PNG files.
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, Polygon
from matplotlib.path import Path as MplPath
import matplotlib.pyplot as plt
# --- Common settings ---
DPI = 300
BG_COLOR = "white"
LINE_COLOR = "black"
FONT_SIZE = 9
TITLE_SIZE = 14
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
def draw_arrow(ax, x1, y1, x2, y2) -> None:
"""Draw arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={"arrowstyle": "->", "color": LINE_COLOR, "lw": 1.3},
)
def draw_line(ax, x1, y1, x2, y2) -> None:
"""Draw line."""
ax.plot([x1, x2], [y1, y2], color=LINE_COLOR, lw=1.3, solid_capstyle="round")
def draw_rounded_rect(
ax, x, y, w, h, text, fill="white", lw=1.5, fontsize=FONT_SIZE
) -> None:
"""Draw rounded rect."""
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.3",
linewidth=lw,
edgecolor=LINE_COLOR,
facecolor=fill,
)
ax.add_patch(rect)
ax.text(x, y, text, ha="center", va="center", fontsize=fontsize)
def draw_diamond(ax, x, y, size, text="", fill="white", fontsize=8) -> None:
"""Draw diamond."""
s = size
diamond = Polygon(
[(x, y + s), (x + s, y), (x, y - s), (x - s, y)],
closed=True,
linewidth=1.5,
edgecolor=LINE_COLOR,
facecolor=fill,
)
ax.add_patch(diamond)
if text:
ax.text(
x, y, text, ha="center", va="center", fontsize=fontsize, fontweight="bold"
)
# =========================================================================
# 1. BPMN 2.0 Diagram
# =========================================================================
def generate_bpmn() -> None:
"""Generate bpmn."""
fig, ax = plt.subplots(figsize=(11, 7.5))
ax.set_xlim(0, 110)
ax.set_ylim(0, 75)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG_COLOR)
ax.set_title(
"BPMN 2.0 \u2014 Obs\u0142uga reklamacji",
fontsize=TITLE_SIZE,
fontweight="bold",
pad=12,
)
# --- Pool outline ---
pool_x, pool_y, pool_w, pool_h = 3, 3, 104, 68
ax.add_patch(
plt.Rectangle(
(pool_x, pool_y),
pool_w,
pool_h,
lw=2,
edgecolor=LINE_COLOR,
facecolor="white",
)
)
# Pool label
label_strip = pool_x + 4
ax.plot(
[label_strip, label_strip], [pool_y, pool_y + pool_h], color=LINE_COLOR, lw=1.5
)
ax.text(
pool_x + 2,
pool_y + pool_h / 2,
"FIRMA",
fontsize=11,
fontweight="bold",
rotation=90,
ha="center",
va="center",
)
# --- Lanes ---
lane_top = pool_y + pool_h
lane_mid1 = pool_y + pool_h * 2 / 3
lane_mid2 = pool_y + pool_h * 1 / 3
ax.plot(
[label_strip, pool_x + pool_w], [lane_mid1, lane_mid1], color=LINE_COLOR, lw=1
)
ax.plot(
[label_strip, pool_x + pool_w], [lane_mid2, lane_mid2], color=LINE_COLOR, lw=1
)
y_bok = (lane_top + lane_mid1) / 2
y_jak = (lane_mid1 + lane_mid2) / 2
y_mag = (lane_mid2 + pool_y) / 2
ax.text(
label_strip + 2.5,
y_bok,
"BOK",
fontsize=8,
ha="center",
va="center",
rotation=90,
fontstyle="italic",
)
ax.text(
label_strip + 2.5,
y_jak,
"Jako\u015b\u0107",
fontsize=8,
ha="center",
va="center",
rotation=90,
fontstyle="italic",
)
ax.text(
label_strip + 2.5,
y_mag,
"Magazyn",
fontsize=8,
ha="center",
va="center",
rotation=90,
fontstyle="italic",
)
content_left = label_strip + 5
# --- Elements ---
# Start event
sx = content_left + 4
ax.add_patch(
plt.Circle((sx, y_bok), 2, lw=2, edgecolor=LINE_COLOR, facecolor="white")
)
ax.text(sx, y_bok - 3.5, "Reklamacja\nwp\u0142ywa", fontsize=6, ha="center")
# Task 1: Przyjmij zg\u0142oszenie (BOK)
t1x = sx + 14
draw_rounded_rect(ax, t1x, y_bok, 14, 6, "Przyjmij\nzg\u0142oszenie")
draw_arrow(ax, sx + 2, y_bok, t1x - 7, y_bok)
# Task 2: Zweryfikuj zasadno\u015b\u0107 (Jako\u015b\u0107) \u2014 elbow routing
t2x = t1x + 18
draw_rounded_rect(ax, t2x, y_jak, 14, 6, "Zweryfikuj\nzasadno\u015b\u0107")
elbow_x = t1x + 10
draw_line(ax, t1x + 7, y_bok, elbow_x, y_bok)
draw_line(ax, elbow_x, y_bok, elbow_x, y_jak)
draw_arrow(ax, elbow_x, y_jak, t2x - 7, y_jak)
# XOR Gateway (split)
gx = t2x + 14
draw_diamond(ax, gx, y_jak, 3.5, "X")
draw_arrow(ax, t2x + 7, y_jak, gx - 3.5, y_jak)
# YES: down to Magazyn
t3x = gx + 14
draw_rounded_rect(ax, t3x, y_mag, 14, 6, "Przygotuj\nwymian\u0119/zwrot")
draw_line(ax, gx, y_jak - 3.5, gx, y_mag)
draw_arrow(ax, gx, y_mag, t3x - 7, y_mag)
ax.text(gx + 1.5, y_jak - 6, "Tak", fontsize=7, ha="left")
# NO: right in Jako\u015b\u0107
t4x = gx + 14
draw_rounded_rect(ax, t4x, y_jak, 14, 6, "Odrzu\u0107\nreklamacj\u0119")
draw_arrow(ax, gx + 3.5, y_jak, t4x - 7, y_jak)
ax.text(gx + 4, y_jak + 2, "Nie", fontsize=7, ha="left")
# XOR merge (in BOK)
mx = t4x + 14
draw_diamond(ax, mx, y_bok, 3.5, "X")
# From Odrzu\u0107 up to merge
draw_line(ax, t4x + 7, y_jak, mx, y_jak)
draw_arrow(ax, mx, y_jak, mx, y_bok - 3.5)
# From Przygotuj wymian\u0119 up to merge (offset to avoid overlap)
draw_line(ax, t3x + 7, y_mag, mx - 4, y_mag)
draw_line(ax, mx - 4, y_mag, mx - 4, y_bok)
draw_arrow(ax, mx - 4, y_bok, mx - 3.5, y_bok)
# Task 5: Powiadom klienta (BOK)
t5x = mx + 13
draw_rounded_rect(ax, t5x, y_bok, 14, 6, "Powiadom\nklienta")
draw_arrow(ax, mx + 3.5, y_bok, t5x - 7, y_bok)
# End event
ex = t5x + 12
ax.add_patch(
plt.Circle((ex, y_bok), 2, lw=3, edgecolor=LINE_COLOR, facecolor="white")
)
draw_arrow(ax, t5x + 7, y_bok, ex - 2, y_bok)
ax.text(ex, y_bok - 3.5, "Koniec", fontsize=6, ha="center")
# --- Legend ---
ly = 1
ax.text(12, ly, "Legenda:", fontsize=7, fontweight="bold", va="center")
ax.add_patch(plt.Circle((22, ly), 1, lw=2, edgecolor=LINE_COLOR, facecolor="white"))
ax.text(24, ly, "Start", fontsize=6, va="center")
ax.add_patch(plt.Circle((30, ly), 1, lw=3, edgecolor=LINE_COLOR, facecolor="white"))
ax.text(32, ly, "Koniec", fontsize=6, va="center")
draw_diamond(ax, 40, ly, 1.5, "X", fontsize=5)
ax.text(43, ly, "Bramka XOR", fontsize=6, va="center")
draw_rounded_rect(ax, 58, ly, 7, 2.5, "Zadanie", fontsize=6)
ax.text(65, ly, "Sequence Flow \u2192", fontsize=6, va="center")
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "bpmn_reklamacja.png"),
dpi=DPI,
facecolor="white",
bbox_inches="tight",
)
plt.close(fig)
print(" OK BPMN saved")
# =========================================================================
# 2. UML Activity Diagram
# =========================================================================
def generate_uml_activity() -> None:
"""Generate uml activity."""
fig, ax = plt.subplots(figsize=(8.27, 10))
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG_COLOR)
ax.set_title(
"UML Activity Diagram \u2014 Obs\u0142uga reklamacji",
fontsize=TITLE_SIZE,
fontweight="bold",
pad=12,
)
cx = 50
y = 93
step = 11
# Initial node
ax.add_patch(plt.Circle((cx, y), 1.8, facecolor="black", edgecolor="black"))
# Action 1
y -= step
draw_rounded_rect(ax, cx, y, 28, 6, "Przyjmij zg\u0142oszenie reklamacji")
draw_arrow(ax, cx, y + step - 1.8, cx, y + 3)
# Action 2
y -= step
draw_rounded_rect(ax, cx, y, 28, 6, "Zweryfikuj zasadno\u015b\u0107")
draw_arrow(ax, cx, y + step - 3, cx, y + 3)
# Decision
y -= step
draw_diamond(ax, cx, y, 4)
draw_arrow(ax, cx, y + step - 3, cx, y + 4)
ax.text(cx + 6, y + 5, "[zasadna?]", fontsize=8, fontstyle="italic")
dec_y = y
branch_y = dec_y - step
# Left [tak]
left_x = cx - 24
draw_rounded_rect(ax, left_x, branch_y, 22, 6, "Przygotuj\nwymian\u0119/zwrot")
draw_line(ax, cx - 4, dec_y, left_x, dec_y)
draw_arrow(ax, left_x, dec_y, left_x, branch_y + 3)
ax.text(left_x + 2, dec_y + 1.5, "[tak]", fontsize=8, fontstyle="italic")
# Right [nie]
right_x = cx + 24
draw_rounded_rect(ax, right_x, branch_y, 22, 6, "Odrzu\u0107\nreklamacj\u0119")
draw_line(ax, cx + 4, dec_y, right_x, dec_y)
draw_arrow(ax, right_x, dec_y, right_x, branch_y + 3)
ax.text(right_x - 12, dec_y + 1.5, "[nie]", fontsize=8, fontstyle="italic")
# Merge
merge_y = branch_y - step
draw_diamond(ax, cx, merge_y, 4)
draw_line(ax, left_x, branch_y - 3, left_x, merge_y)
draw_line(ax, left_x, merge_y, cx - 4, merge_y)
draw_line(ax, right_x, branch_y - 3, right_x, merge_y)
draw_line(ax, right_x, merge_y, cx + 4, merge_y)
# Action: Powiadom
y = merge_y - step
draw_rounded_rect(ax, cx, y, 28, 6, "Powiadom klienta")
draw_arrow(ax, cx, merge_y - 4, cx, y + 3)
# Final node
ey = y - step
ax.add_patch(plt.Circle((cx, ey), 2.5, lw=2, facecolor="white", edgecolor="black"))
ax.add_patch(plt.Circle((cx, ey), 1.5, facecolor="black", edgecolor="black"))
draw_arrow(ax, cx, y - 3, cx, ey + 2.5)
# Legend
ly = 5
ax.add_patch(plt.Circle((12, ly), 1.2, facecolor="black", edgecolor="black"))
ax.text(15, ly, "= Pocz\u0105tek", fontsize=7, va="center")
ax.add_patch(plt.Circle((32, ly), 1.3, lw=2, facecolor="white", edgecolor="black"))
ax.add_patch(plt.Circle((32, ly), 0.8, facecolor="black", edgecolor="black"))
ax.text(35, ly, "= Koniec", fontsize=7, va="center")
draw_diamond(ax, 50, ly, 1.5)
ax.text(53, ly, "= Decyzja/Merge", fontsize=7, va="center")
draw_rounded_rect(ax, 78, ly, 9, 3, "Akcja", fontsize=7)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "uml_activity_reklamacja.png"),
dpi=DPI,
facecolor="white",
bbox_inches="tight",
)
plt.close(fig)
print(" OK UML Activity saved")
# =========================================================================
# 3. EPC (Event-driven Process Chain)
# =========================================================================
def generate_epc() -> None:
"""Generate epc."""
fig, ax = plt.subplots(figsize=(8.27, 11))
ax.set_xlim(0, 100)
ax.set_ylim(0, 120)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG_COLOR)
ax.set_title(
"EPC (Event-driven Process Chain) \u2014 Obs\u0142uga reklamacji",
fontsize=TITLE_SIZE,
fontweight="bold",
pad=12,
)
cx = 50
y = 114
step = 9.5
def draw_epc_event(x, y, text) -> None:
"""Draw epc event."""
w, h = 26, 5.5
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.5",
lw=1.5,
edgecolor=LINE_COLOR,
facecolor="#D8D8D8",
)
ax.add_patch(rect)
ax.text(x, y, text, ha="center", va="center", fontsize=8)
def draw_epc_function(x, y, text) -> None:
"""Draw epc function."""
w, h = 26, 5.5
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=0.3",
lw=2,
edgecolor=LINE_COLOR,
facecolor="white",
)
ax.add_patch(rect)
ax.text(x, y, text, ha="center", va="center", fontsize=8, fontweight="bold")
def draw_epc_connector(x, y, text) -> None:
"""Draw epc connector."""
c = plt.Circle((x, y), 2.8, lw=1.5, edgecolor=LINE_COLOR, facecolor="white")
ax.add_patch(c)
ax.text(x, y, text, ha="center", va="center", fontsize=9, fontweight="bold")
# E1
draw_epc_event(cx, y, "Reklamacja wp\u0142yn\u0119\u0142a")
# F1
y -= step
draw_epc_function(cx, y, "Przyjmij zg\u0142oszenie")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# E2
y -= step
draw_epc_event(cx, y, "Zg\u0142oszenie przyj\u0119te")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# F2
y -= step
draw_epc_function(cx, y, "Zweryfikuj zasadno\u015b\u0107")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# E3
y -= step
draw_epc_event(cx, y, "Zasadno\u015b\u0107 oceniona")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# XOR split
y -= step
draw_epc_connector(cx, y, "XOR")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
split_y = y
left_x = cx - 28
right_x = cx + 28
# Left branch
by = split_y - step
draw_epc_event(left_x, by, "Reklamacja zasadna")
draw_line(ax, cx - 2.8, split_y, left_x, split_y)
draw_arrow(ax, left_x, split_y, left_x, by + 2.8)
by2 = by - step
draw_epc_function(left_x, by2, "Przygotuj wymian\u0119/zwrot")
draw_arrow(ax, left_x, by - 2.8, left_x, by2 + 2.8)
by3 = by2 - step
draw_epc_event(left_x, by3, "Wymiana przygotowana")
draw_arrow(ax, left_x, by2 - 2.8, left_x, by3 + 2.8)
# Right branch
draw_epc_event(right_x, by, "Reklamacja niezasadna")
draw_line(ax, cx + 2.8, split_y, right_x, split_y)
draw_arrow(ax, right_x, split_y, right_x, by + 2.8)
draw_epc_function(right_x, by2, "Odrzu\u0107 reklamacj\u0119")
draw_arrow(ax, right_x, by - 2.8, right_x, by2 + 2.8)
draw_epc_event(right_x, by3, "Reklamacja odrzucona")
draw_arrow(ax, right_x, by2 - 2.8, right_x, by3 + 2.8)
# XOR merge
merge_y = by3 - step
draw_epc_connector(cx, merge_y, "XOR")
draw_line(ax, left_x, by3 - 2.8, left_x, merge_y)
draw_line(ax, left_x, merge_y, cx - 2.8, merge_y)
draw_line(ax, right_x, by3 - 2.8, right_x, merge_y)
draw_line(ax, right_x, merge_y, cx + 2.8, merge_y)
# F: Powiadom
y = merge_y - step
draw_epc_function(cx, y, "Powiadom klienta")
draw_arrow(ax, cx, merge_y - 2.8, cx, y + 2.8)
# E: Klient powiadomiony
y -= step
draw_epc_event(cx, y, "Klient powiadomiony")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# Legend
ly = 3
draw_epc_event(16, ly, "Zdarzenie")
draw_epc_function(46, ly, "Funkcja")
draw_epc_connector(68, ly, "XOR")
ax.text(72, ly, "= \u0141\u0105cznik logiczny", fontsize=7, va="center")
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "epc_reklamacja.png"),
dpi=DPI,
facecolor="white",
bbox_inches="tight",
)
plt.close(fig)
print(" OK EPC saved")
# =========================================================================
# 4. Classic Flowchart
# =========================================================================
def generate_flowchart() -> None:
"""Generate flowchart."""
fig, ax = plt.subplots(figsize=(8.27, 11))
ax.set_xlim(0, 100)
ax.set_ylim(0, 110)
ax.set_aspect("equal")
ax.axis("off")
fig.patch.set_facecolor(BG_COLOR)
ax.set_title(
"Schemat blokowy (Flowchart) \u2014 Obs\u0142uga reklamacji",
fontsize=TITLE_SIZE,
fontweight="bold",
pad=12,
)
cx = 50
y = 103
step = 11
def draw_terminal(x, y, text) -> None:
"""Draw terminal."""
w, h = 20, 5.5
rect = FancyBboxPatch(
(x - w / 2, y - h / 2),
w,
h,
boxstyle="round,pad=1.0",
lw=2,
edgecolor=LINE_COLOR,
facecolor="#E0E0E0",
)
ax.add_patch(rect)
ax.text(
x, y, text, ha="center", va="center", fontsize=FONT_SIZE, fontweight="bold"
)
def draw_process_box(x, y, text) -> None:
"""Draw process box."""
w, h = 26, 6
rect = plt.Rectangle(
(x - w / 2, y - h / 2),
w,
h,
lw=1.5,
edgecolor=LINE_COLOR,
facecolor="white",
)
ax.add_patch(rect)
ax.text(x, y, text, ha="center", va="center", fontsize=FONT_SIZE)
def draw_io_shape(x, y, text) -> None:
"""Draw io shape."""
w, h = 26, 5.5
skew = 3
verts = [
(x - w / 2 + skew, y + h / 2),
(x + w / 2 + skew, y + h / 2),
(x + w / 2 - skew, y - h / 2),
(x - w / 2 - skew, y - h / 2),
(x - w / 2 + skew, y + h / 2),
]
codes = [
MplPath.MOVETO,
MplPath.LINETO,
MplPath.LINETO,
MplPath.LINETO,
MplPath.CLOSEPOLY,
]
patch = mpatches.PathPatch(
MplPath(verts, codes), facecolor="white", edgecolor=LINE_COLOR, lw=1.5
)
ax.add_patch(patch)
ax.text(x, y, text, ha="center", va="center", fontsize=FONT_SIZE)
# Start
draw_terminal(cx, y, "START")
# I/O
y -= step
draw_io_shape(cx, y, "Reklamacja od klienta")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# Process
y -= step
draw_process_box(cx, y, "Przyjmij zg\u0142oszenie")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 3)
# Process
y -= step
draw_process_box(cx, y, "Zweryfikuj zasadno\u015b\u0107")
draw_arrow(ax, cx, y + step - 3, cx, y + 3)
# Decision
y -= step
draw_diamond(ax, cx, y, 4.5, "Zasadna?")
draw_arrow(ax, cx, y + step - 3, cx, y + 4.5)
dec_y = y
# Left: Tak
left_x = cx - 26
draw_process_box(left_x, dec_y, "Przygotuj wymian\u0119/zwrot")
draw_line(ax, cx - 4.5, dec_y, left_x + 13, dec_y)
ax.text(cx - 7, dec_y + 2, "Tak", fontsize=8, ha="center", fontweight="bold")
# Right: Nie
right_x = cx + 26
draw_process_box(right_x, dec_y, "Odrzu\u0107 reklamacj\u0119")
draw_line(ax, cx + 4.5, dec_y, right_x - 13, dec_y)
ax.text(cx + 7, dec_y + 2, "Nie", fontsize=8, ha="center", fontweight="bold")
# Merge
merge_y = dec_y - step
draw_line(ax, left_x, dec_y - 3, left_x, merge_y)
draw_line(ax, right_x, dec_y - 3, right_x, merge_y)
draw_line(ax, left_x, merge_y, right_x, merge_y)
ax.plot(cx, merge_y, "ko", markersize=4)
# Process: Powiadom
y = merge_y - step + 3
draw_process_box(cx, y, "Powiadom klienta")
draw_arrow(ax, cx, merge_y, cx, y + 3)
# I/O
y -= step
draw_io_shape(cx, y, "Odpowied\u017a do klienta")
draw_arrow(ax, cx, y + step - 3, cx, y + 2.8)
# End
y -= step
draw_terminal(cx, y, "KONIEC")
draw_arrow(ax, cx, y + step - 2.8, cx, y + 2.8)
# Legend
ly = 4
ax.text(5, ly, "Legenda:", fontsize=7, fontweight="bold", va="center")
draw_terminal(18, ly, "")
ax.text(18, ly, "Start/\nKoniec", fontsize=5.5, ha="center", va="center")
w, h = 9, 3
ax.add_patch(
plt.Rectangle(
(32 - w / 2, ly - h / 2),
w,
h,
lw=1.5,
edgecolor=LINE_COLOR,
facecolor="white",
)
)
ax.text(32, ly, "Proces", fontsize=6, ha="center", va="center")
draw_diamond(ax, 46, ly, 2)
ax.text(49.5, ly, "= Decyzja", fontsize=6, va="center")
skew = 1.5
w2, h2 = 9, 3
verts = [
(62 - w2 / 2 + skew, ly + h2 / 2),
(62 + w2 / 2 + skew, ly + h2 / 2),
(62 + w2 / 2 - skew, ly - h2 / 2),
(62 - w2 / 2 - skew, ly - h2 / 2),
(62 - w2 / 2 + skew, ly + h2 / 2),
]
codes = [
MplPath.MOVETO,
MplPath.LINETO,
MplPath.LINETO,
MplPath.LINETO,
MplPath.CLOSEPOLY,
]
ax.add_patch(
mpatches.PathPatch(
MplPath(verts, codes), facecolor="white", edgecolor=LINE_COLOR, lw=1.2
)
)
ax.text(62, ly, "We/Wy", fontsize=6, ha="center", va="center")
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "flowchart_reklamacja.png"),
dpi=DPI,
facecolor="white",
bbox_inches="tight",
)
plt.close(fig)
print(" OK Flowchart saved")
# =========================================================================
if __name__ == "__main__":
print(f"Generating diagrams to {OUTPUT_DIR}/...")
generate_bpmn()
generate_uml_activity()
generate_epc()
generate_flowchart()
print(f"\nAll 4 diagrams saved to {OUTPUT_DIR}/")
for f in sorted([p.name for p in Path(OUTPUT_DIR).iterdir()]):
if f.endswith(".png"):
size_kb = Path(str(Path(OUTPUT_DIR).stat().st_size / f)) / 1024
print(f" {f} ({size_kb:.0f} KB)")

View File

@ -0,0 +1,747 @@
#!/usr/bin/env python3
"""Generate Pub/Sub diagrams for PYTANIE 19.
Subscription types (4 separate images):
1. Topic-based
2. Content-based
3. Type-based
4. Hierarchical (wildcards)
Delivery guarantees (3 separate images):
5. At-most-once
6. At-least-once
7. Exactly-once.
All: A4-width, B&W, 300 DPI, laser-printer-friendly.
One diagram per image no cramming.
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import matplotlib.pyplot as plt
DPI = 300
BG = "white"
LN = "black"
FS = 9
FS_TITLE = 13
FIG_W = 8.27 # A4 width in inches
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
GRAY5 = "#C0C0C0"
def draw_box(
ax,
x,
y,
w,
h,
text,
fill="white",
lw=1.2,
fontsize=FS,
fontweight="normal",
ha="center",
va="center",
rounded=True,
) -> None:
"""Draw box."""
if rounded:
rect = FancyBboxPatch(
(x, y), w, h, boxstyle="round,pad=0.05", lw=lw, edgecolor=LN, facecolor=fill
)
else:
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
ax.add_patch(rect)
ax.text(
x + w / 2,
y + h / 2,
text,
ha=ha,
va=va,
fontsize=fontsize,
fontweight=fontweight,
wrap=True,
)
def draw_arrow(
ax,
x1,
y1,
x2,
y2,
lw=1.2,
style="->",
color=LN,
label="",
label_offset=0.15,
label_fs=8,
) -> None:
"""Draw arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={"arrowstyle": style, "color": color, "lw": lw},
)
if label:
mx, my = (x1 + x2) / 2, (y1 + y2) / 2 + label_offset
ax.text(mx, my, label, ha="center", va="bottom", fontsize=label_fs, color=color)
def draw_dashed_arrow(
ax, x1, y1, x2, y2, lw=1.0, color=LN, label="", label_offset=0.15, label_fs=8
) -> None:
"""Draw dashed arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={
"arrowstyle": "->",
"color": color,
"lw": lw,
"linestyle": "dashed",
},
)
if label:
mx, my = (x1 + x2) / 2, (y1 + y2) / 2 + label_offset
ax.text(mx, my, label, ha="center", va="bottom", fontsize=label_fs, color=color)
def draw_cross(ax, x, y, size=0.15, lw=2.5, color="black") -> None:
"""Draw cross."""
ax.plot([x - size, x + size], [y - size, y + size], color=color, lw=lw)
ax.plot([x - size, x + size], [y + size, y - size], color=color, lw=lw)
def draw_check(ax, x, y, size=0.15, lw=2.5, color="black") -> None:
"""Draw check."""
ax.plot([x - size, x - size * 0.2], [y, y - size * 0.7], color=color, lw=lw)
ax.plot(
[x - size * 0.2, x + size], [y - size * 0.7, y + size * 0.5], color=color, lw=lw
)
def save(fig, name) -> None:
"""Save."""
plt.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / name), dpi=DPI, bbox_inches="tight", facecolor=BG
)
plt.close(fig)
print(f"{name}")
# ============================================================
# 1. Topic-based subscription
# ============================================================
def draw_sub_topic() -> None:
"""Draw sub topic."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 4.0))
ax.set_xlim(0, 12)
ax.set_ylim(0, 5.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Subskrypcja topic-based — routing po nazwie tematu",
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
# Publisher + messages
draw_box(
ax, 0.2, 3.2, 2.4, 1.1, "Publisher", fill=GRAY1, fontsize=10, fontweight="bold"
)
draw_box(ax, 0.3, 1.8, 2.2, 0.8, 'topic: "orders"', fill=GRAY4, fontsize=8)
draw_box(ax, 0.3, 0.7, 2.2, 0.8, 'topic: "payments"', fill=GRAY4, fontsize=8)
# Broker
draw_box(
ax,
4.2,
1.5,
2.8,
2.2,
"BROKER\n\ntopic routing",
fill=GRAY2,
fontsize=10,
fontweight="bold",
)
# Subscribers
draw_box(
ax,
8.5,
3.8,
3.0,
1.0,
'Subscriber A\nsubskrybuje: "orders"',
fill=GRAY1,
fontsize=8.5,
)
draw_box(
ax,
8.5,
2.2,
3.0,
1.0,
'Subscriber B\nsubskrybuje: "payments"',
fill=GRAY1,
fontsize=8.5,
)
draw_box(
ax,
8.5,
0.6,
3.0,
1.0,
'Subscriber C\nsubskrybuje: "orders"',
fill=GRAY1,
fontsize=8.5,
)
# Arrows: publisher → broker
draw_arrow(ax, 2.6, 2.2, 4.2, 2.8, label_fs=8)
draw_arrow(ax, 2.6, 1.1, 4.2, 2.2, label_fs=8)
# Arrows: broker → subscribers
draw_arrow(ax, 7.0, 3.4, 8.5, 4.2, label='"orders"', label_fs=8)
draw_arrow(ax, 7.0, 2.6, 8.5, 2.7, label='"payments"', label_fs=8)
draw_arrow(ax, 7.0, 2.2, 8.5, 1.2, label='"orders"', label_fs=8)
# Explanation
ax.text(
6.0,
0.1,
"Subscriber deklaruje nazwę tematu. Broker kieruje wiadomości\n"
"do WSZYSTKICH subscriberów danego tematu. Najprostszy model.",
ha="center",
va="bottom",
fontsize=8.5,
style="italic",
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
save(fig, "pubsub_sub_topic.png")
# ============================================================
# 2. Content-based subscription
# ============================================================
def draw_sub_content() -> None:
"""Draw sub content."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 4.5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Subskrypcja content-based — filtrowanie po treści wiadomości",
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
# Publisher + message
draw_box(
ax, 0.2, 3.5, 2.4, 1.1, "Publisher", fill=GRAY1, fontsize=10, fontweight="bold"
)
draw_box(
ax,
0.2,
1.8,
2.4,
1.2,
'price: 150\ntype: "book"\ncategory: "IT"',
fill=GRAY4,
fontsize=8.5,
)
# Broker
draw_box(
ax,
4.0,
2.0,
3.0,
2.5,
"BROKER\n\newaluuje filtry\nkażdego subscribera",
fill=GRAY2,
fontsize=9,
fontweight="bold",
)
# Subscribers with filters
draw_box(
ax, 8.5, 4.2, 3.2, 1.0, "Sub A\nfiltr: price > 100", fill=GRAY1, fontsize=9
)
draw_box(
ax, 8.5, 2.6, 3.2, 1.0, 'Sub B\nfiltr: type = "food"', fill=GRAY1, fontsize=9
)
draw_box(ax, 8.5, 1.0, 3.2, 1.0, "Sub C\nfiltr: price < 50", fill=GRAY1, fontsize=9)
# Arrows
draw_arrow(ax, 2.6, 2.4, 4.0, 3.0)
draw_arrow(ax, 7.0, 4.0, 8.5, 4.6, label="150 > 100 ✓ dostarczono", label_fs=8)
draw_dashed_arrow(
ax, 7.0, 3.2, 8.5, 3.1, label='"book""food" ✗ odrzucono', label_fs=8
)
draw_dashed_arrow(
ax, 7.0, 2.5, 8.5, 1.6, label="150 < 50 ✗ odrzucono", label_fs=8
)
ax.text(
6.0,
0.2,
"Broker analizuje TREŚĆ wiadomości i ewaluuje predykaty.\n"
"Bardziej elastyczny niż topic-based, ale wolniejszy (koszt ewaluacji).",
ha="center",
va="bottom",
fontsize=8.5,
style="italic",
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
save(fig, "pubsub_sub_content.png")
# ============================================================
# 3. Type-based subscription
# ============================================================
def draw_sub_type() -> None:
"""Draw sub type."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 5.0))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Subskrypcja type-based — routing po typie (klasie) obiektu",
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
# Publisher
draw_box(
ax, 0.2, 4.2, 2.4, 1.1, "Publisher", fill=GRAY1, fontsize=10, fontweight="bold"
)
# Messages
draw_box(ax, 0.1, 2.8, 2.6, 0.9, "new OrderEvent()", fill=GRAY4, fontsize=9)
draw_box(ax, 0.1, 1.5, 2.6, 0.9, "new PaymentEvent()", fill=GRAY4, fontsize=9)
# Broker
draw_box(
ax,
4.0,
2.3,
3.0,
2.4,
"BROKER\n\nrouting po\ntypie klasy",
fill=GRAY2,
fontsize=10,
fontweight="bold",
)
# Subscribers
draw_box(ax, 8.5, 4.8, 3.2, 1.0, "Sub A\n→ OrderEvent", fill=GRAY1, fontsize=9)
draw_box(ax, 8.5, 3.2, 3.2, 1.0, "Sub B\n→ PaymentEvent", fill=GRAY1, fontsize=9)
draw_box(ax, 8.5, 1.6, 3.2, 1.0, "Sub C\n→ Event (base)", fill=GRAY1, fontsize=9)
# Arrows
draw_arrow(ax, 2.7, 3.2, 4.0, 3.8)
draw_arrow(ax, 2.7, 2.0, 4.0, 3.0)
draw_arrow(ax, 7.0, 4.3, 8.5, 5.2, label="OrderEvent", label_fs=8)
draw_arrow(ax, 7.0, 3.5, 8.5, 3.7, label="PaymentEvent", label_fs=8)
draw_arrow(ax, 7.0, 3.0, 8.5, 2.2, label="oba (dziedziczenie!)", label_fs=8)
# Class hierarchy inset
hx, hy = 0.5, 0.0
draw_box(
ax,
hx + 2.0,
hy + 0.2,
1.8,
0.6,
"Event",
fill=GRAY3,
fontsize=8,
fontweight="bold",
)
draw_box(ax, hx + 0.0, hy + 0.2, 1.8, 0.6, "OrderEvent", fill=GRAY4, fontsize=7.5)
draw_box(ax, hx + 4.0, hy + 0.2, 2.0, 0.6, "PaymentEvent", fill=GRAY4, fontsize=7.5)
draw_arrow(
ax,
hx + 2.9,
hy + 0.2,
hx + 0.9,
hy + 0.2,
lw=1.0,
style="->",
label="extends",
label_offset=-0.3,
label_fs=7,
)
draw_arrow(
ax,
hx + 2.9,
hy + 0.2,
hx + 5.0,
hy + 0.2,
lw=1.0,
style="->",
label="extends",
label_offset=-0.3,
label_fs=7,
)
ax.text(
9.5,
0.5,
"Sub C subskrybuje bazowy Event\n→ otrzymuje WSZYSTKIE podtypy",
ha="center",
va="center",
fontsize=8.5,
style="italic",
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
save(fig, "pubsub_sub_type.png")
# ============================================================
# 4. Hierarchical / Wildcards subscription
# ============================================================
def draw_sub_hierarchical() -> None:
"""Draw sub hierarchical."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 5.5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Subskrypcja hierarchiczna (wildcards) — wzorce tematów",
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
# Topic tree
draw_box(
ax, 4.5, 5.8, 2.4, 0.8, "sensors/", fill=GRAY2, fontsize=10, fontweight="bold"
)
draw_box(ax, 1.5, 4.2, 2.4, 0.8, "temperature/", fill=GRAY3, fontsize=9)
draw_box(ax, 7.5, 4.2, 2.4, 0.8, "humidity/", fill=GRAY3, fontsize=9)
draw_box(ax, 0.2, 2.8, 1.8, 0.7, "room1", fill=GRAY4, fontsize=8.5)
draw_box(ax, 2.4, 2.8, 1.8, 0.7, "room2", fill=GRAY4, fontsize=8.5)
draw_box(ax, 6.8, 2.8, 1.8, 0.7, "room1", fill=GRAY4, fontsize=8.5)
draw_box(ax, 9.0, 2.8, 1.8, 0.7, "room2", fill=GRAY4, fontsize=8.5)
# Tree edges
draw_arrow(ax, 5.7, 5.8, 2.7, 5.0, lw=1.0)
draw_arrow(ax, 5.7, 5.8, 8.7, 5.0, lw=1.0)
draw_arrow(ax, 2.2, 4.2, 1.1, 3.5, lw=1.0)
draw_arrow(ax, 3.2, 4.2, 3.3, 3.5, lw=1.0)
draw_arrow(ax, 8.2, 4.2, 7.7, 3.5, lw=1.0)
draw_arrow(ax, 9.2, 4.2, 9.9, 3.5, lw=1.0)
# Full paths
ax.text(
1.1,
2.4,
"sensors/temperature/room1",
fontsize=7,
ha="center",
fontfamily="monospace",
style="italic",
)
ax.text(
3.3,
2.4,
"sensors/temperature/room2",
fontsize=7,
ha="center",
fontfamily="monospace",
style="italic",
)
# Wildcard examples
ax.text(
0.3, 1.5, "Wzorce subskrypcji (MQTT-style):", fontsize=10, fontweight="bold"
)
patterns = [
('"sensors/temperature/room1"', "→ TYLKO room1", "(dokładne dopasowanie)"),
('"sensors/temperature/*"', "→ room1, room2", "( * = jeden poziom)"),
('"sensors/#"', "→ WSZYSTKO", "( # = dowolna głębokość)"),
]
for i, (pat, result, note) in enumerate(patterns):
yy = 0.9 - i * 0.55
ax.text(0.5, yy, pat, fontsize=9, fontweight="bold", fontfamily="monospace")
ax.text(7.0, yy, result, fontsize=9, fontweight="bold")
ax.text(9.5, yy, note, fontsize=8, style="italic")
save(fig, "pubsub_sub_hierarchical.png")
# ============================================================
# 5. At-most-once (QoS 0)
# ============================================================
def draw_qos_at_most_once() -> None:
"""Draw qos at most once."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 4.5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
'QoS: At-most-once — „wyślij i zapomnij" (0 lub 1 dostarczenie)',
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
# Actors
px, bx, sx = 1.0, 4.8, 8.5
pw, bw, sw = 2.0, 2.2, 2.0
bh = 0.8
draw_box(
ax, px, 5.0, pw, bh, "Publisher", fill=GRAY1, fontsize=10, fontweight="bold"
)
draw_box(ax, bx, 5.0, bw, bh, "Broker", fill=GRAY2, fontsize=10, fontweight="bold")
draw_box(
ax, sx, 5.0, sw, bh, "Subscriber", fill=GRAY1, fontsize=10, fontweight="bold"
)
# Timelines
for xc in [px + pw / 2, bx + bw / 2, sx + sw / 2]:
ax.plot([xc, xc], [5.0, 1.2], color=GRAY3, lw=1, linestyle=":")
# Scenario A: success
y = 4.3
ax.text(0.2, y + 0.15, "Scenariusz A:", fontsize=8.5, fontweight="bold")
draw_arrow(ax, px + pw / 2, y, bx + bw / 2, y, label="MSG", label_fs=9)
draw_arrow(ax, bx + bw / 2, y - 0.6, sx + sw / 2, y - 0.6, label="MSG", label_fs=9)
draw_check(ax, sx + sw / 2 + 0.4, y - 0.6, size=0.18)
ax.text(sx + sw / 2 + 0.7, y - 0.6, "OK", fontsize=9, fontweight="bold")
# Scenario B: lost
y = 2.6
ax.text(0.2, y + 0.15, "Scenariusz B:", fontsize=8.5, fontweight="bold")
draw_arrow(ax, px + pw / 2, y, bx + bw / 2, y, label="MSG", label_fs=9)
draw_dashed_arrow(ax, bx + bw / 2, y - 0.6, 7.5, y - 0.6)
draw_cross(ax, 7.8, y - 0.6, size=0.2)
ax.text(8.2, y - 0.55, "UTRACONA", fontsize=9, fontweight="bold")
ax.text(8.2, y - 1.0, "(brak retransmisji)", fontsize=8, style="italic")
# Summary
ax.text(
6.0,
0.5,
"Brak ACK, brak retransmisji. Najszybszy. Use case: logi, metryki, telemetria.",
ha="center",
va="center",
fontsize=9,
bbox={"boxstyle": "round,pad=0.4", "facecolor": GRAY4, "edgecolor": GRAY3},
)
save(fig, "pubsub_qos_at_most_once.png")
# ============================================================
# 6. At-least-once (QoS 1)
# ============================================================
def draw_qos_at_least_once() -> None:
"""Draw qos at least once."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 5.0))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
'QoS: At-least-once — „powtarzaj aż potwierdzą" (≥1 dostarczenie)',
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
bx, bw = 3.5, 2.2
sx, sw = 8.0, 2.2
bh = 0.8
draw_box(ax, bx, 5.5, bw, bh, "Broker", fill=GRAY2, fontsize=10, fontweight="bold")
draw_box(
ax, sx, 5.5, sw, bh, "Subscriber", fill=GRAY1, fontsize=10, fontweight="bold"
)
# Timelines
for xc in [bx + bw / 2, sx + sw / 2]:
ax.plot([xc, xc], [5.5, 0.8], color=GRAY3, lw=1, linestyle=":")
# Step 1: send MSG
y1 = 4.8
draw_arrow(ax, bx + bw / 2, y1, sx + sw / 2, y1, label="MSG #1", label_fs=9)
draw_check(ax, sx + sw + 0.2, y1, size=0.15)
ax.text(sx + sw + 0.5, y1, "odebrano", fontsize=8)
# Step 2: ACK lost
y2 = 3.9
draw_dashed_arrow(ax, sx + sw / 2, y2, bx + bw + 1.2, y2)
ax.text((bx + bw / 2 + sx + sw / 2) / 2, y2 + 0.18, "ACK", fontsize=9)
draw_cross(ax, bx + bw + 0.8, y2, size=0.18)
ax.text(bx + 0.3, y2 - 0.35, "ACK utracony!", fontsize=8.5, style="italic")
# Step 3: timeout → retry
y3 = 2.9
ax.text(
bx + bw / 2, y3 + 0.45, "timeout...", fontsize=8.5, style="italic", ha="center"
)
draw_arrow(ax, bx + bw / 2, y3, sx + sw / 2, y3, label="MSG #1 (retry)", label_fs=9)
draw_check(ax, sx + sw + 0.2, y3, size=0.15)
ax.text(sx + sw + 0.5, y3, "odebrano\n(ponownie!)", fontsize=8)
# Step 4: ACK ok
y4 = 2.0
draw_arrow(ax, sx + sw / 2, y4, bx + bw / 2, y4, label="ACK", label_fs=9)
draw_check(ax, bx + bw / 2 - 0.5, y4, size=0.18)
# Duplicate bracket
ax.annotate(
"",
xy=(sx + sw + 1.3, y1),
xytext=(sx + sw + 1.3, y3),
arrowprops={"arrowstyle": "<->", "color": "black", "lw": 1.2},
)
ax.text(
sx + sw + 1.6,
(y1 + y3) / 2,
"DUPLIKAT!\nSubscriber\notrzymał 2x",
fontsize=9,
ha="left",
va="center",
fontweight="bold",
bbox={"boxstyle": "round,pad=0.25", "facecolor": GRAY4, "edgecolor": GRAY3},
)
# Summary
ax.text(
6.0,
0.5,
"Broker czeka na ACK, retransmituje po timeout. Mogą być duplikaty!\n"
"Use case: zamówienia, płatności (subscriber musi być idempotentny).",
ha="center",
va="center",
fontsize=9,
bbox={"boxstyle": "round,pad=0.4", "facecolor": GRAY4, "edgecolor": GRAY3},
)
save(fig, "pubsub_qos_at_least_once.png")
# ============================================================
# 7. Exactly-once (QoS 2)
# ============================================================
def draw_qos_exactly_once() -> None:
"""Draw qos exactly once."""
fig, ax = plt.subplots(1, 1, figsize=(FIG_W, 5.5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"QoS: Exactly-once — 4-krokowy handshake (dokładnie 1 dostarczenie)",
fontsize=FS_TITLE,
fontweight="bold",
pad=12,
)
bx, bw = 2.5, 2.2
sx, sw = 7.5, 2.2
bh = 0.8
draw_box(ax, bx, 6.0, bw, bh, "Broker", fill=GRAY2, fontsize=10, fontweight="bold")
draw_box(
ax, sx, 6.0, sw, bh, "Subscriber", fill=GRAY1, fontsize=10, fontweight="bold"
)
# Timelines
for xc in [bx + bw / 2, sx + sw / 2]:
ax.plot([xc, xc], [6.0, 1.0], color=GRAY3, lw=1, linestyle=":")
# 4-step handshake
steps = [
(5.2, "right", "PUBLISH (msg_id=42)", "Broker wysyła wiadomość"),
(
4.2,
"left",
"PUBREC (otrzymałem id=42)",
"Sub potwierdza odbiór, zapisuje id",
),
(3.2, "right", "PUBREL (możesz przetworzyć)", "Broker zwalnia wiadomość"),
(2.2, "left", "PUBCOMP (zakończone)", "Sub potwierdza przetworzenie"),
]
for i, (y, direction, label, desc) in enumerate(steps):
# Step number
ax.text(
bx + bw / 2 - 0.7,
y,
f"{i + 1}",
fontsize=9,
fontweight="bold",
ha="center",
va="center",
bbox={"boxstyle": "circle,pad=0.18", "facecolor": GRAY3, "edgecolor": LN},
)
if direction == "right":
draw_arrow(ax, bx + bw / 2, y, sx + sw / 2, y, label=label, label_fs=9)
else:
draw_arrow(ax, sx + sw / 2, y, bx + bw / 2, y, label=label, label_fs=9)
# Side description
ax.text(
sx + sw + 0.3, y, desc, fontsize=8, ha="left", va="center", style="italic"
)
# Summary
ax.text(
6.0,
0.6,
"Deduplikacja po msg_id. Sub nie przetwarza przed PUBREL.\n"
"Najkosztowniejszy (4 pakiety). Use case: transakcje finansowe, krytyczne zdarzenia.",
ha="center",
va="center",
fontsize=9,
bbox={"boxstyle": "round,pad=0.4", "facecolor": GRAY4, "edgecolor": GRAY3},
)
save(fig, "pubsub_qos_exactly_once.png")
# ============================================================
# Main
# ============================================================
if __name__ == "__main__":
print("Generating Pub/Sub diagrams (7 separate images)...")
draw_sub_topic()
draw_sub_content()
draw_sub_type()
draw_sub_hierarchical()
draw_qos_at_most_once()
draw_qos_at_least_once()
draw_qos_exactly_once()
print("Done!")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,942 @@
#!/usr/bin/env python3
"""Generate diagrams for PYTANIE 16: Języki programowania robotów.
A4-compatible, B&W, 300 DPI, laser-printer-friendly.
Diagrams:
1. T-R-M-S abstraction pyramid
2. Vendor languages comparison chart
3. Robot movement types (PTP, LIN, CIRC)
4. Online vs Offline programming flowchart
5. ROS architecture (pub/sub nodes)
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import matplotlib.pyplot as plt
import numpy as np
DPI = 300
BG = "white"
LN = "black"
FS = 8
FS_TITLE = 11
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
GRAY5 = "#C0C0C0"
WHITE = "white"
def draw_box(
ax,
x,
y,
w,
h,
text,
fill="white",
lw=1.2,
fontsize=FS,
fontweight="normal",
ha="center",
va="center",
rounded=True,
) -> None:
"""Draw box."""
if rounded:
rect = FancyBboxPatch(
(x, y), w, h, boxstyle="round,pad=0.05", lw=lw, edgecolor=LN, facecolor=fill
)
else:
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
ax.add_patch(rect)
ax.text(
x + w / 2,
y + h / 2,
text,
ha=ha,
va=va,
fontsize=fontsize,
fontweight=fontweight,
wrap=True,
)
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style="->", color=LN) -> None:
"""Draw arrow."""
ax.annotate(
"",
xy=(x2, y2),
xytext=(x1, y1),
arrowprops={"arrowstyle": style, "color": color, "lw": lw},
)
# ============================================================
# 1. T-R-M-S Abstraction Pyramid
# ============================================================
def draw_trms_pyramid() -> None:
"""Draw trms pyramid."""
fig, ax = plt.subplots(1, 1, figsize=(8.27, 5.5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Poziomy abstrakcji języków programowania robotów (T-R-M-S)",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Pyramid layers (bottom to top)
layers = [
# (y, left_x, right_x, label, sublabel, fill, examples, timing)
(
0.5,
1.0,
9.0,
"SERVO-LEVEL",
"Sterowanie silnikami",
GRAY3,
"C/C++, FPGA, VHDL\nPID, PWM",
"~1 ms",
),
(
2.0,
1.8,
8.2,
"MOTION-LEVEL",
"Planowanie trajektorii",
GRAY2,
"MoveIt, OMPL\nIK, collision avoidance",
"~20 ms",
),
(
3.5,
2.6,
7.4,
"ROBOT-LEVEL",
"Komendy ruchu",
GRAY1,
"RAPID, KRL, Karel\nPDL2, URScript, ROS",
"~100 ms",
),
(
5.0,
3.4,
6.6,
"TASK-LEVEL",
"Opis celu",
GRAY4,
"PDDL, BT, STRIPS\nplanowanie AI",
"~sekundy",
),
]
h = 1.3
for y, lx, rx, label, sublabel, fill, examples, timing in layers:
rx - lx
# Draw trapezoid
trap = plt.Polygon(
[(lx, y), (rx, y), (rx - 0.4, y + h), (lx + 0.4, y + h)],
closed=True,
facecolor=fill,
edgecolor=LN,
lw=1.5,
)
ax.add_patch(trap)
# Label
ax.text(
(lx + rx) / 2,
y + h * 0.65,
label,
ha="center",
va="center",
fontsize=9,
fontweight="bold",
)
ax.text(
(lx + rx) / 2,
y + h * 0.35,
sublabel,
ha="center",
va="center",
fontsize=7,
style="italic",
)
# Examples - right side
ax.text(
rx + 0.2,
y + h * 0.5,
examples,
ha="left",
va="center",
fontsize=6.5,
color="#333333",
)
# Timing - left side
ax.text(
lx - 0.2,
y + h * 0.5,
timing,
ha="right",
va="center",
fontsize=7,
fontweight="bold",
color="#333333",
)
# Arrow on left
ax.annotate(
"",
xy=(0.5, 6.2),
xytext=(0.5, 0.8),
arrowprops={"arrowstyle": "->", "color": "black", "lw": 2},
)
ax.text(
0.5,
3.5,
"Abstrakcja\nrośnie",
ha="center",
va="center",
fontsize=7,
rotation=90,
fontweight="bold",
)
# Arrow on right side for timing
ax.annotate(
"",
xy=(9.7, 0.8),
xytext=(9.7, 6.2),
arrowprops={"arrowstyle": "->", "color": "black", "lw": 2},
)
ax.text(
9.7,
3.5,
"Szybkość\nreakcji",
ha="center",
va="center",
fontsize=7,
rotation=270,
fontweight="bold",
)
# Mnemonic at bottom
ax.text(
5.0,
0.0,
'Mnemonik: „Tomek Robi Mechaniczne Serwa" (T→R→M→S, od góry do dołu)',
ha="center",
va="center",
fontsize=7,
style="italic",
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": GRAY4,
"edgecolor": LN,
"lw": 0.8,
},
)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "robot_trms_pyramid.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close(fig)
print(" ✓ robot_trms_pyramid.png")
# ============================================================
# 2. Vendor Languages Comparison
# ============================================================
def draw_vendor_comparison() -> None:
"""Draw vendor comparison."""
fig, ax = plt.subplots(1, 1, figsize=(8.27, 5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 7.5)
ax.axis("off")
ax.set_title(
"Języki producentów robotów — porównanie",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Table headers
headers = [
"Cecha",
"RAPID\n(ABB)",
"KRL\n(KUKA)",
"Karel\n(FANUC)",
"PDL2\n(Comau)",
"URScript\n(UR)",
]
col_widths = [1.8, 1.6, 1.6, 1.6, 1.6, 1.6]
col_x = [0.1]
for w in col_widths[:-1]:
col_x.append(col_x[-1] + w)
row_h = 0.7
header_y = 6.3
rows = [
[
"Składnia",
"typ własny\nstrukturalna",
"Pascal-like\nstrukturalna",
"Pascal-like\nstrukturalna",
"proceduralna\nC-like",
"Python-like\nskryptowy",
],
[
"Ruch liniowy",
"MoveL",
"LIN",
"MOVE TO\nw/LINEAR",
"MOVE\nLINEAR TO",
"movel()",
],
["Ruch joint", "MoveJ", "PTP", "MOVE TO", "MOVE TO", "movej()"],
[
"Ruch kołowy",
"MoveC",
"CIRC",
"(brak\nwbudow.)",
"MOVE\nCIRCULAR",
"movec()",
],
[
"I/O",
"SetDO/\nWaitDI",
"OUT/IN",
"DOUT/DIN",
"OUT/IN",
"set_digital\n_out()",
],
[
"Zmienne",
"num, robtarget\nstring, bool",
"INT, REAL\nPOS, E6POS",
"INTEGER\nPOSITION",
"INTEGER\nPOSITION",
"int, float\npose",
],
[
"Symulator",
"RobotStudio",
"KUKA.Sim",
"ROBOGUIDE",
"RoboSim",
"URSim\n(darmowy)",
],
]
# Draw header row
for j, (hdr, w) in enumerate(zip(headers, col_widths, strict=False)):
x = col_x[j]
fill = GRAY2 if j == 0 else GRAY1
draw_box(
ax,
x,
header_y,
w - 0.05,
row_h,
hdr,
fill=fill,
fontsize=7,
fontweight="bold",
rounded=False,
)
# Draw data rows
for i, row in enumerate(rows):
y = header_y - (i + 1) * row_h
for j, (cell, w) in enumerate(zip(row, col_widths, strict=False)):
x = col_x[j]
fill = GRAY4 if j == 0 else (WHITE if i % 2 == 0 else GRAY4)
fw = "bold" if j == 0 else "normal"
draw_box(
ax,
x,
y,
w - 0.05,
row_h - 0.02,
cell,
fill=fill,
fontsize=6,
fontweight=fw,
rounded=False,
)
# Note
ax.text(
5.0,
0.5,
"Vendor lock-in: program w RAPID ≠ działa na KUKA. "
"ROS/ROS 2 jako warstwa unifikująca.",
ha="center",
va="center",
fontsize=7,
style="italic",
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": GRAY4,
"edgecolor": LN,
"lw": 0.8,
},
)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "robot_vendor_comparison.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close(fig)
print(" ✓ robot_vendor_comparison.png")
# ============================================================
# 3. Robot Movement Types (PTP, LIN, CIRC)
# ============================================================
def draw_movement_types() -> None:
"""Draw movement types."""
fig, axes = plt.subplots(1, 3, figsize=(8.27, 3.2))
fig.suptitle(
"Typy ruchu robota: PTP, LIN, CIRC",
fontsize=FS_TITLE,
fontweight="bold",
y=0.98,
)
# --- PTP (Point-to-Point) ---
ax = axes[0]
ax.set_xlim(-0.5, 4.5)
ax.set_ylim(-0.5, 4.5)
ax.set_aspect("equal")
ax.set_title("PTP (Point-to-Point)\nMoveJ / PTP", fontsize=8, fontweight="bold")
ax.grid(True, alpha=0.3)
# Start and end
start = (0.5, 0.5)
end = (3.5, 3.5)
ax.plot(*start, "ko", ms=10, zorder=5)
ax.plot(*end, "ks", ms=10, zorder=5)
ax.text(start[0] - 0.3, start[1] - 0.3, "Start", fontsize=7, ha="center")
ax.text(end[0] + 0.3, end[1] + 0.3, "Cel", fontsize=7, ha="center")
# Curved path (joint space = not necessarily straight in Cartesian)
t = np.linspace(0, 1, 50)
x_ptp = start[0] + (end[0] - start[0]) * t + 0.8 * np.sin(np.pi * t)
y_ptp = start[1] + (end[1] - start[1]) * t - 0.3 * np.sin(np.pi * t)
ax.plot(x_ptp, y_ptp, "k-", lw=2)
ax.annotate(
"",
xy=(x_ptp[-1], y_ptp[-1]),
xytext=(x_ptp[-3], y_ptp[-3]),
arrowprops={"arrowstyle": "->", "color": "black", "lw": 2},
)
ax.text(
2.8,
1.2,
"Ścieżka\nw kartezjańskiej\nnieokreślona!",
fontsize=6,
ha="center",
style="italic",
bbox={"boxstyle": "round", "facecolor": GRAY4, "edgecolor": GRAY5},
)
ax.text(
2.0,
-0.3,
"Najszybszy, ale\nścieżka nieprzewidywalna",
fontsize=6,
ha="center",
style="italic",
)
ax.set_xlabel("")
ax.set_ylabel("")
ax.tick_params(labelsize=6)
# --- LIN (Linear) ---
ax = axes[1]
ax.set_xlim(-0.5, 4.5)
ax.set_ylim(-0.5, 4.5)
ax.set_aspect("equal")
ax.set_title("LIN (Linear)\nMoveL / LIN", fontsize=8, fontweight="bold")
ax.grid(True, alpha=0.3)
start = (0.5, 1.0)
end = (3.5, 3.5)
ax.plot(*start, "ko", ms=10, zorder=5)
ax.plot(*end, "ks", ms=10, zorder=5)
ax.text(start[0] - 0.3, start[1] - 0.3, "Start", fontsize=7, ha="center")
ax.text(end[0] + 0.3, end[1] + 0.3, "Cel", fontsize=7, ha="center")
# Straight line
ax.plot([start[0], end[0]], [start[1], end[1]], "k-", lw=2)
ax.annotate(
"",
xy=end,
xytext=(
start[0] + 0.9 * (end[0] - start[0]),
start[1] + 0.9 * (end[1] - start[1]),
),
arrowprops={"arrowstyle": "->", "color": "black", "lw": 2},
)
# Show intermediate points
for frac in [0.25, 0.5, 0.75]:
px = start[0] + frac * (end[0] - start[0])
py = start[1] + frac * (end[1] - start[1])
ax.plot(px, py, "k.", ms=6)
ax.text(
2.0,
-0.3,
"Prosta linia TCP\nIK w każdym punkcie",
fontsize=6,
ha="center",
style="italic",
)
ax.tick_params(labelsize=6)
# --- CIRC (Circular) ---
ax = axes[2]
ax.set_xlim(-0.5, 4.5)
ax.set_ylim(-0.5, 4.5)
ax.set_aspect("equal")
ax.set_title("CIRC (Circular)\nMoveC / CIRC", fontsize=8, fontweight="bold")
ax.grid(True, alpha=0.3)
# Arc through 3 points
center = (2.0, 1.5)
r = 2.0
theta_start = np.radians(20)
theta_end = np.radians(160)
theta = np.linspace(theta_start, theta_end, 50)
x_circ = center[0] + r * np.cos(theta)
y_circ = center[1] + r * np.sin(theta)
ax.plot(x_circ, y_circ, "k-", lw=2)
ax.annotate(
"",
xy=(x_circ[-1], y_circ[-1]),
xytext=(x_circ[-3], y_circ[-3]),
arrowprops={"arrowstyle": "->", "color": "black", "lw": 2},
)
# Start, auxiliary, end points
ax.plot(x_circ[0], y_circ[0], "ko", ms=10, zorder=5)
ax.plot(x_circ[24], y_circ[24], "k^", ms=8, 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[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)
# Center
ax.plot(*center, "k+", ms=8, mew=1.5)
ax.text(center[0], center[1] - 0.3, "środek", fontsize=6, ha="center")
ax.text(
2.0,
-0.3,
"Łuk wyznaczony\nprzez 3 punkty",
fontsize=6,
ha="center",
style="italic",
)
ax.tick_params(labelsize=6)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "robot_movement_types.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close(fig)
print(" ✓ robot_movement_types.png")
# ============================================================
# 4. Online vs Offline Programming
# ============================================================
def draw_online_offline() -> None:
"""Draw online offline."""
fig, ax = plt.subplots(1, 1, figsize=(8.27, 4.5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 6.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Programowanie robotów: Online (teach-in) vs Offline",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# === ONLINE side (left) ===
# Title
draw_box(
ax,
0.3,
5.2,
4.2,
0.8,
"ONLINE\n(teach-in / pendant)",
fill=GRAY2,
fontsize=9,
fontweight="bold",
)
steps_online = [
(4.2, "Operator przy robocie\nz teach pendantem"),
(3.2, 'Prowadzi ramię\n„za rękę" do punktów'),
(2.2, "Robot zapamiętuje\npozycje (record)"),
(1.2, "Odtwarzanie\nzapisanej ścieżki"),
]
for y, txt in steps_online:
draw_box(ax, 0.5, y, 3.8, 0.8, txt, fill=WHITE, fontsize=7)
for i in range(len(steps_online) - 1):
draw_arrow(ax, 2.4, steps_online[i][0], 2.4, steps_online[i + 1][0] + 0.8)
# Pros/cons
ax.text(
2.4,
0.6,
"✓ Proste, intuicyjne\n✗ Wymaga zatrzymania produkcji\n✗ Niska precyzja",
ha="center",
va="center",
fontsize=6.5,
bbox={"boxstyle": "round", "facecolor": GRAY4, "edgecolor": GRAY5, "lw": 0.8},
)
# Divider
ax.plot([4.9, 4.9], [0.3, 6.2], "k--", lw=1, alpha=0.5)
# === OFFLINE side (right) ===
draw_box(
ax,
5.3,
5.2,
4.2,
0.8,
"OFFLINE\n(symulacja / CAD/CAM)",
fill=GRAY2,
fontsize=9,
fontweight="bold",
)
steps_offline = [
(4.2, "Model 3D robota +\nśrodowisko w symulatorze"),
(3.2, "Programowanie ścieżek\nw środowisku wirtualnym"),
(2.2, "Weryfikacja kolizji\ni optymalizacja"),
(1.2, "Transfer na\nrzeczywistego robota"),
]
for y, txt in steps_offline:
draw_box(ax, 5.5, y, 3.8, 0.8, txt, fill=WHITE, fontsize=7)
for i in range(len(steps_offline) - 1):
draw_arrow(ax, 7.4, steps_offline[i][0], 7.4, steps_offline[i + 1][0] + 0.8)
ax.text(
7.4,
0.6,
"✓ Bez zatrzymania produkcji\n✓ Wysoka precyzja, symulacja\n✗ Wymaga kalibracji",
ha="center",
va="center",
fontsize=6.5,
bbox={"boxstyle": "round", "facecolor": GRAY4, "edgecolor": GRAY5, "lw": 0.8},
)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "robot_online_offline.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close(fig)
print(" ✓ robot_online_offline.png")
# ============================================================
# 5. ROS Architecture (pub/sub)
# ============================================================
def draw_ros_architecture() -> None:
"""Draw ros architecture."""
fig, ax = plt.subplots(1, 1, figsize=(8.27, 4.5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 6.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"ROS — architektura publish/subscribe",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Nodes
nodes = [
(1.0, 4.5, "Czujnik\n(LiDAR)", GRAY1),
(1.0, 2.5, "Kamera\n(RGB-D)", GRAY1),
(4.0, 4.5, "Lokalizacja\n(SLAM)", GRAY4),
(4.0, 2.5, "Percepcja\n(detekcja)", GRAY4),
(7.0, 3.5, "Planowanie\nruchu (MoveIt)", GRAY2),
(7.0, 1.0, "Sterownik\nsilników", GRAY3),
]
for x, y, txt, fill in nodes:
draw_box(ax, x, y, 2.2, 1.0, txt, fill=fill, fontsize=7, fontweight="bold")
# Topics (arrows with labels)
topics = [
# (from_x, from_y, to_x, to_y, label)
(3.2, 5.0, 4.0, 5.0, "/scan"),
(3.2, 3.0, 4.0, 3.0, "/image"),
(6.2, 5.0, 7.0, 4.3, "/pose"),
(6.2, 3.0, 7.0, 3.8, "/objects"),
(8.0, 3.5, 8.0, 2.0, "/cmd_vel"),
]
for x1, y1, x2, y2, label in topics:
draw_arrow(ax, x1, y1, x2, y2, lw=1.5)
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
ax.text(
mx,
my + 0.2,
label,
ha="center",
va="bottom",
fontsize=6,
fontweight="bold",
style="italic",
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": WHITE,
"edgecolor": GRAY5,
"lw": 0.5,
},
)
# ROS Master / roscore
draw_box(
ax,
3.5,
0.3,
3.0,
0.8,
"ROS Master (roscore)\nRejestr węzłów i tematów",
fill=GRAY2,
fontsize=7,
fontweight="bold",
)
# Dashed lines to master
for x, y, _, _ in nodes[:4]:
ax.plot([x + 1.1, 5.0], [y, 1.1], "k:", lw=0.5, alpha=0.4)
# Legend
ax.text(
0.3,
0.8,
"Węzeł (Node) = proces\n"
"Temat (Topic) = kanał pub/sub\n"
"Wiadomość = typowany komunikat",
ha="left",
va="center",
fontsize=6,
bbox={"boxstyle": "round", "facecolor": GRAY4, "edgecolor": LN, "lw": 0.8},
)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "robot_ros_architecture.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close(fig)
print(" ✓ robot_ros_architecture.png")
# ============================================================
# 6. RAPID program structure example
# ============================================================
def draw_rapid_structure() -> None:
"""Draw rapid structure."""
fig, ax = plt.subplots(1, 1, figsize=(8.27, 5.5))
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.axis("off")
ax.set_title(
"Struktura programu RAPID (ABB) — przykład pick & place",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Program structure blocks
# Simplified: just draw code blocks
code_sections = [
(
"Deklaracje danych (stałe, zmienne)",
GRAY4,
[
"CONST robtarget pHome := [[500,0,600],[1,0,0,0],...];",
"CONST robtarget pPick := [[400,200,100],[1,0,0,0],...];",
"CONST robtarget pPlace := [[400,-200,100],[1,0,0,0],...];",
"VAR num nCycles := 0;",
"PERS tooldata tGripper := [...];",
],
),
(
"Procedura główna: main()",
GRAY1,
[
"PROC main()",
" MoveJ pHome, v1000, z50, tGripper;",
" WHILE TRUE DO",
" PickPart;",
" PlacePart;",
" Incr nCycles;",
" ENDWHILE",
"ENDPROC",
],
),
(
"Podprocedura: PickPart()",
GRAY1,
[
"PROC PickPart()",
" MoveL Offs(pPick,0,0,50), v500, z10, tGripper;",
" MoveL pPick, v100, fine, tGripper;",
" SetDO doGripper, 1; ! zamknij chwytak",
" WaitTime 0.5;",
" MoveL Offs(pPick,0,0,50), v500, z10, tGripper;",
"ENDPROC",
],
),
]
y_cur = 7.2
for title, fill, lines in code_sections:
0.25 * len(lines) + 0.5
# Title bar
draw_box(
ax,
0.5,
y_cur - 0.35,
9.0,
0.35,
title,
fill=fill,
fontsize=7,
fontweight="bold",
rounded=False,
)
y_cur -= 0.35
# Code lines
for _i, line in enumerate(lines):
y_cur -= 0.25
ax.text(
0.7,
y_cur + 0.12,
line,
fontsize=5.5,
fontfamily="monospace",
va="center",
)
# Border around code
code_h = 0.25 * len(lines)
rect = mpatches.Rectangle(
(0.5, y_cur - 0.05),
9.0,
code_h + 0.15,
lw=0.8,
edgecolor=GRAY5,
facecolor=WHITE,
zorder=-1,
)
ax.add_patch(rect)
y_cur -= 0.3
# Annotations on right
annotations = [
(
6.5,
"robtarget = pozycja\nkartezjańska + orientacja\n+ konfiguracja ramienia",
),
(
4.5,
"v500 = prędkość 500 mm/s\nz10 = strefa zbliżenia 10mm\nfine = dokładne dojście",
),
(2.5, "SetDO = Digital Output\nSterowanie I/O\n(chwytak, zawory)"),
]
for yy, txt in annotations:
ax.text(
9.8,
yy,
txt,
fontsize=5.5,
ha="left",
va="center",
bbox={
"boxstyle": "round,pad=0.2",
"facecolor": GRAY4,
"edgecolor": GRAY5,
"lw": 0.5,
},
)
fig.tight_layout()
fig.savefig(
str(Path(OUTPUT_DIR) / "robot_rapid_example.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close(fig)
print(" ✓ robot_rapid_example.png")
# ============================================================
# Main
# ============================================================
if __name__ == "__main__":
print("Generating PYTANIE 16 diagrams...")
draw_trms_pyramid()
draw_vendor_comparison()
draw_movement_types()
draw_online_offline()
draw_ros_architecture()
draw_rapid_structure()
print("Done! All diagrams saved to", OUTPUT_DIR)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,593 @@
#!/usr/bin/env python3
"""Generate diagrams for PYTANIE 2: Shortest path algorithms.
1. Graph structure the shared example graph (A,B,C,D)
2. Dijkstra traversal step-by-step on that graph
3. Bellman-Ford traversal step-by-step
4. A* traversal step-by-step with heuristics.
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
"""
import matplotlib as mpl
mpl.use("Agg")
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
DPI = 300
BG = "white"
LN = "black"
FS = 8
FS_TITLE = 11
FS_SMALL = 6.5
FS_EDGE = 9
OUTPUT_DIR = str(Path(__file__).resolve().parent / "img")
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
GRAY1 = "#E8E8E8"
GRAY2 = "#D0D0D0"
GRAY3 = "#B8B8B8"
GRAY4 = "#F5F5F5"
GRAY5 = "#C0C0C0"
LIGHT_GREEN = "#D5E8D4"
LIGHT_RED = "#F8D7DA"
LIGHT_BLUE = "#D6EAF8"
LIGHT_YELLOW = "#FFF9C4"
LIGHT_ORANGE = "#FFE0B2"
LIGHT_PURPLE = "#E8D5F5"
# --- Shared graph layout ---
# Graph: A--2--B--3--D, A--4--C--5--D, D--1--C (directed: C->D has weight 5)
NODE_POS = {"A": (1, 2), "B": (3.5, 3.2), "C": (1, 0), "D": (3.5, 0.8)}
EDGES = [("A", "B", 2), ("A", "C", 4), ("B", "D", 3), ("C", "D", 5)]
def draw_graph_node(
ax,
name,
pos,
color="white",
current=False,
visited=False,
dist_label=None,
fontsize=12,
) -> None:
"""Draw a graph node (circle with label)."""
x, y = pos
r = 0.35
lw = 2.5 if current else 1.5
ec = "#D32F2F" if current else LN
fc = LIGHT_GREEN if visited else color
if current:
fc = LIGHT_YELLOW
circle = plt.Circle(
(x, y), r, fill=True, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=5
)
ax.add_patch(circle)
ax.text(
x,
y,
name,
ha="center",
va="center",
fontsize=fontsize,
fontweight="bold",
zorder=6,
)
if dist_label is not None:
ax.text(
x,
y - 0.55,
f"d={dist_label}",
ha="center",
va="center",
fontsize=FS,
zorder=6,
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": "white",
"edgecolor": GRAY3,
"alpha": 0.95,
},
)
def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False) -> None:
"""Draw an edge between two nodes with weight label."""
x1, y1 = pos1
x2, y2 = pos2
# Shorten line to not overlap node circles
dx, dy = x2 - x1, y2 - y1
length = np.sqrt(dx**2 + dy**2)
r = 0.38
sx = x1 + r * dx / length
sy = y1 + r * dy / length
ex = x2 - r * dx / length
ey = y2 - r * dy / length
color = "#D32F2F" if relaxed else ("#1565C0" if highlighted else GRAY3)
lw = 2.5 if (highlighted or relaxed) else 1.5
ls = "-"
ax.plot([sx, ex], [sy, ey], color=color, linewidth=lw, linestyle=ls, zorder=2)
# Weight label
mx = (x1 + x2) / 2
my = (y1 + y2) / 2
# Offset perpendicular to edge
perp_x = -dy / length * 0.2
perp_y = dx / length * 0.2
ax.text(
mx + perp_x,
my + perp_y,
str(weight),
ha="center",
va="center",
fontsize=FS_EDGE,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.15",
"facecolor": "white",
"edgecolor": GRAY3 if not highlighted else color,
"alpha": 0.95,
},
zorder=4,
)
def draw_full_graph(
ax,
title="",
dist=None,
current=None,
visited=None,
highlighted_edges=None,
relaxed_edges=None,
) -> None:
"""Draw the complete graph with optional highlighting."""
if visited is None:
visited = set()
if highlighted_edges is None:
highlighted_edges = set()
if relaxed_edges is None:
relaxed_edges = set()
if dist is None:
dist = {}
ax.set_xlim(-0.2, 4.7)
ax.set_ylim(-0.8, 4.2)
ax.set_aspect("equal")
ax.axis("off")
if title:
ax.set_title(title, fontsize=FS, fontweight="bold", pad=5)
# Draw edges
for u, v, w in EDGES:
hl = (u, v) in highlighted_edges or (v, u) in highlighted_edges
rl = (u, v) in relaxed_edges or (v, u) in relaxed_edges
draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w, highlighted=hl, relaxed=rl)
# Draw nodes
for name, pos in NODE_POS.items():
is_current = name == current
is_visited = name in visited
d_label = dist.get(name)
draw_graph_node(
ax, name, pos, current=is_current, visited=is_visited, dist_label=d_label
)
# ============================================================
# 1. Graph structure diagram
# ============================================================
def draw_graph_structure() -> None:
"""The shared example graph used across all three algorithms."""
_fig, ax = plt.subplots(1, 1, figsize=(5, 4))
ax.set_xlim(-0.5, 5.0)
ax.set_ylim(-1.2, 4.5)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(
"Przykładowy graf — wspólny dla wszystkich algorytmów\n"
"Wierzchołki: {A, B, C, D}, Start = A",
fontsize=FS_TITLE,
fontweight="bold",
pad=10,
)
# Draw edges
for u, v, w in EDGES:
draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w)
# Draw nodes
for name, pos in NODE_POS.items():
draw_graph_node(ax, name, pos)
# Start arrow
ax.annotate(
"START",
xy=(NODE_POS["A"][0] - 0.35, NODE_POS["A"][1]),
xytext=(NODE_POS["A"][0] - 1.2, NODE_POS["A"][1]),
fontsize=FS,
fontweight="bold",
color="#D32F2F",
arrowprops={"arrowstyle": "->", "color": "#D32F2F", "lw": 2},
va="center",
)
# Edge list
ax.text(
2.3,
-0.8,
"Krawędzie: A→B(2), A→C(4), B→D(3), C→D(5)\n|V|=4, |E|=4, wagi ≥ 0",
ha="center",
va="center",
fontsize=FS,
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
plt.tight_layout()
plt.savefig(
str(Path(OUTPUT_DIR) / "graph_example_structure.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ graph_example_structure.png")
# ============================================================
# 2. Dijkstra traversal
# ============================================================
def draw_dijkstra_traversal() -> None:
"""Step-by-step Dijkstra on the shared graph."""
steps = [
{
"title": "Krok 0: Inicjalizacja\nd = {A:0, B:∞, C:∞, D:∞}",
"dist": {"A": "0", "B": "", "C": "", "D": ""},
"current": "A",
"visited": set(),
"highlighted": set(),
"relaxed": set(),
},
{
"title": "Krok 1: Przetwarzam A (d=0)\nRelaksacja: A→B: 0+2=2<∞ ✓ A→C: 0+4=4<∞ ✓",
"dist": {"A": "0", "B": "2", "C": "4", "D": ""},
"current": "A",
"visited": {"A"},
"highlighted": set(),
"relaxed": {("A", "B"), ("A", "C")},
},
{
"title": "Krok 2: Przetwarzam B (d=2) — minimum\nRelaksacja: B→D: 2+3=5<∞ ✓",
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"current": "B",
"visited": {"A", "B"},
"highlighted": set(),
"relaxed": {("B", "D")},
},
{
"title": "Krok 3: Przetwarzam C (d=4)\nRelaksacja: C→D: 4+5=9 > 5 ✗ (nie poprawia)",
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"current": "C",
"visited": {"A", "B", "C"},
"highlighted": {("C", "D")},
"relaxed": set(),
},
{
"title": "Krok 4: WYNIK — wszystkie przetworzone\nd = {A:0, B:2, C:4, D:5}",
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"current": None,
"visited": {"A", "B", "C", "D"},
"highlighted": {("A", "B"), ("B", "D"), ("A", "C")},
"relaxed": set(),
},
]
fig, axes = plt.subplots(1, 5, figsize=(14, 3.5))
fig.suptitle(
"Dijkstra — przejście grafu krok po kroku (zachłannie: zawsze bierz min d)",
fontsize=FS_TITLE,
fontweight="bold",
y=1.02,
)
for _i, (ax, step) in enumerate(zip(axes, steps, strict=False)):
draw_full_graph(
ax,
title=step["title"],
dist=step["dist"],
current=step["current"],
visited=step["visited"],
highlighted_edges=step["highlighted"],
relaxed_edges=step["relaxed"],
)
# Legend
fig.text(
0.5,
-0.04,
"[zolty] = aktualnie przetwarzany [zielony] = odwiedzony (zamkniety) "
"czerwona krawedz = relaksacja OK szara krawedz = nie poprawia",
ha="center",
fontsize=FS,
bbox={"boxstyle": "round,pad=0.3", "facecolor": GRAY4, "edgecolor": GRAY3},
)
plt.tight_layout()
plt.savefig(
str(Path(OUTPUT_DIR) / "dijkstra_traversal.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ dijkstra_traversal.png")
# ============================================================
# 3. Bellman-Ford traversal
# ============================================================
def draw_bellman_ford_traversal() -> None:
"""Step-by-step Bellman-Ford on the shared graph."""
fig = plt.figure(figsize=(14, 7))
fig.suptitle(
"Bellman-Ford — przejście grafu krok po kroku\n"
"(V-1 = 3 iteracje, w każdej relaksuj WSZYSTKIE krawędzie)",
fontsize=FS_TITLE,
fontweight="bold",
y=0.98,
)
# Data for each iteration
iterations = [
{
"title": "Inicjalizacja",
"edges_detail": "",
"dist": {"A": "0", "B": "", "C": "", "D": ""},
"relaxed": set(),
},
{
"title": "Iteracja 1 (V-1=3)",
"edges_detail": (
"A→B: 0+2=2<∞ ✓\nA→C: 0+4=4<∞ ✓\nB→D: 2+3=5<∞ ✓\nC→D: 4+5=9>5 ✗"
),
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"relaxed": {("A", "B"), ("A", "C"), ("B", "D")},
},
{
"title": "Iteracja 2",
"edges_detail": (
"A→B: 0+2=2=2 ✗\nA→C: 0+4=4=4 ✗\nB→D: 2+3=5=5 ✗\nC→D: 4+5=9>5 ✗"
),
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"relaxed": set(),
},
{
"title": "Iteracja 3",
"edges_detail": (
"Brak zmian → stabilne!\n(wczesne zakończenie\n optymalizacja)"
),
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"relaxed": set(),
},
]
for i, it in enumerate(iterations):
# Graph subplot
ax_g = fig.add_subplot(2, 4, i + 1)
draw_full_graph(
ax_g,
title=it["title"],
dist=it["dist"],
current=None,
visited=set() if i == 0 else {"A", "B", "C", "D"},
relaxed_edges=it["relaxed"],
)
# Detail subplot below
ax_d = fig.add_subplot(2, 4, i + 5)
ax_d.axis("off")
ax_d.text(
0.5,
0.5,
it["edges_detail"],
ha="center",
va="center",
fontsize=FS,
family="monospace",
bbox={"boxstyle": "round,pad=0.4", "facecolor": GRAY4, "edgecolor": GRAY3},
)
# Negative cycle check note
fig.text(
0.5,
0.01,
"Po 3 iteracjach: sprawdź raz jeszcze — nic się nie zmienia → BRAK cyklu ujemnego → wynik poprawny",
ha="center",
fontsize=FS,
fontweight="bold",
bbox={"boxstyle": "round,pad=0.3", "facecolor": LIGHT_GREEN, "edgecolor": LN},
)
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
plt.savefig(
str(Path(OUTPUT_DIR) / "bellman_ford_traversal.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ bellman_ford_traversal.png")
# ============================================================
# 4. A* traversal
# ============================================================
def draw_astar_traversal() -> None:
"""Step-by-step A* on the shared graph with heuristics."""
# Heuristic values (straight-line distance to D)
h_vals = {"A": 4, "B": 2, "C": 3, "D": 0}
fig = plt.figure(figsize=(14, 7.5))
fig.suptitle(
"A* — przejście grafu krok po kroku (cel = D)\n"
"f(n) = g(n) + h(n), heurystyka h = oszacowana odległość do D",
fontsize=FS_TITLE,
fontweight="bold",
y=0.99,
)
steps = [
{
"title": "Krok 0: Inicjalizacja\nh(A)=4, h(B)=2, h(C)=3, h(D)=0",
"detail": (
"g(A)=0, f(A)=0+4=4\npq = [(4, A)]\nh = oszacowanie\n odl. do celu D"
),
"dist": {"A": "0"},
"f_vals": {"A": "f=4"},
"current": "A",
"visited": set(),
"relaxed": set(),
},
{
"title": "Krok 1: pop A (f=4)\nA→B: g=2, f=2+2=4\nA→C: g=4, f=4+3=7",
"detail": (
"Relaksacja:\n"
" A→B: g=0+2=2\n"
" f=2+h(B)=2+2=4\n"
" A→C: g=0+4=4\n"
" f=4+h(C)=4+3=7\n"
"pq = [(4,B), (7,C)]"
),
"dist": {"A": "0", "B": "2", "C": "4"},
"current": "A",
"visited": {"A"},
"relaxed": {("A", "B"), ("A", "C")},
},
{
"title": "Krok 2: pop B (f=4) — min!\nB→D: g=5, f=5+0=5",
"detail": (
"B ma f=4 < C(f=7)\n"
"→ A* kieruje się\n"
" W STRONĘ celu!\n"
"Relaksacja:\n"
" B→D: g=2+3=5\n"
" f=5+h(D)=5+0=5\n"
"pq = [(5,D), (7,C)]"
),
"dist": {"A": "0", "B": "2", "C": "4", "D": "5"},
"current": "B",
"visited": {"A", "B"},
"relaxed": {("B", "D")},
},
{
"title": "Krok 3: pop D (f=5)\nu == goal → STOP!",
"detail": (
"D to CEL → KONIEC!\n"
"Nie przetwarzamy C\n"
" (f(C)=7 > f(D)=5)\n\n"
"Ścieżka: A→B→D\n"
"Koszt: 5\n\n"
"Dijkstra odwi-\n"
"edziłby też C!"
),
"dist": {"A": "0", "B": "2", "D": "5"},
"current": "D",
"visited": {"A", "B", "D"},
"relaxed": set(),
},
]
for i, step in enumerate(steps):
# Graph
ax_g = fig.add_subplot(2, 4, i + 1)
draw_full_graph(
ax_g,
title=step["title"],
dist=step["dist"],
current=step["current"],
visited=step["visited"],
relaxed_edges=step["relaxed"],
)
# Add h values as small labels
for name, pos in NODE_POS.items():
ax_g.text(
pos[0] + 0.35,
pos[1] + 0.35,
f"h={h_vals[name]}",
ha="center",
va="center",
fontsize=5.5,
color="#1565C0",
fontweight="bold",
zorder=7,
bbox={
"boxstyle": "round,pad=0.1",
"facecolor": LIGHT_BLUE,
"edgecolor": "#1565C0",
"alpha": 0.9,
"lw": 0.5,
},
)
# Detail
ax_d = fig.add_subplot(2, 4, i + 5)
ax_d.axis("off")
ax_d.text(
0.5,
0.5,
step["detail"],
ha="center",
va="center",
fontsize=FS,
family="monospace",
bbox={"boxstyle": "round,pad=0.4", "facecolor": GRAY4, "edgecolor": GRAY3},
)
# Comparison note
fig.text(
0.5,
0.01,
"A* odwiedził 3 wierzchołki (A, B, D) — POMINĄŁ C!\n"
"Dijkstra odwiedziłby wszystkie 4. Heurystyka h kieruje przeszukiwanie w stronę celu.",
ha="center",
fontsize=FS,
fontweight="bold",
bbox={
"boxstyle": "round,pad=0.3",
"facecolor": LIGHT_BLUE,
"edgecolor": "#1565C0",
},
)
plt.tight_layout(rect=[0, 0.06, 1, 0.95])
plt.savefig(
str(Path(OUTPUT_DIR) / "astar_traversal.png"),
dpi=DPI,
bbox_inches="tight",
facecolor=BG,
)
plt.close()
print(" ✓ astar_traversal.png")
# ============================================================
# Main
# ============================================================
if __name__ == "__main__":
print("Generating shortest path diagrams for PYTANIE 2...")
draw_graph_structure()
draw_dijkstra_traversal()
draw_bellman_ford_traversal()
draw_astar_traversal()
print(f"\nAll diagrams saved to {OUTPUT_DIR}/")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,276 @@
#!/usr/bin/env bash
#
# print_questions.sh — Convert and print exam questions from questions/ folder
#
# Usage:
# ./print_questions.sh [OPTIONS] [QUESTION_NUMBERS...]
#
# Examples:
# ./print_questions.sh # Generate PDF of ALL questions
# ./print_questions.sh 1 # Generate PDF of question 1
# ./print_questions.sh 1 3 7 # Generate PDF of questions 1, 3, 7
# ./print_questions.sh 1-5 # Generate PDF of questions 1 through 5
# ./print_questions.sh 1 3-5 8 # Mix of individual and ranges
# ./print_questions.sh --print 1 3 # Generate PDF AND print questions 1, 3
# ./print_questions.sh --print # Generate PDF AND print ALL questions
# ./print_questions.sh --list # List available questions
#
# Options:
# --print, -p Send to printer after generating PDF
# --printer NAME Printer name (default: Brother_HL-1110_series)
# --list, -l List available questions and exit
# --output, -o FILE Output PDF path (default: auto-generated in /tmp)
# --keep, -k Keep intermediate files (for debugging)
# --help, -h Show this help
#
set -euo pipefail
# ── Configuration ──────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
QUESTIONS_DIR="${SCRIPT_DIR}/questions"
PRINTER="Brother_HL-1110_series"
DO_PRINT=false
LIST_ONLY=false
KEEP_TMP=false
OUTPUT_PDF=""
QUESTIONS=()
# ── Parse arguments ───────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--print|-p)
DO_PRINT=true
shift
;;
--printer)
PRINTER="$2"
shift 2
;;
--list|-l)
LIST_ONLY=true
shift
;;
--output|-o)
OUTPUT_PDF="$2"
shift 2
;;
--keep|-k)
KEEP_TMP=true
shift
;;
--help|-h)
head -28 "$0" | tail -26
exit 0
;;
*)
# Parse question numbers and ranges (e.g., "1", "3-5", "13/27")
if [[ "$1" =~ ^[0-9]+(\/[0-9]+)?(-[0-9]+(\/[0-9]+)?)?$ ]]; then
if [[ "$1" == *-* ]]; then
# Range: extract start-end
range_start="${1%%-*}"
range_end="${1##*-}"
for ((i=range_start; i<=range_end; i++)); do
QUESTIONS+=("$i")
done
else
QUESTIONS+=("$1")
fi
else
echo "Error: Unknown argument '$1'. Use --help for usage." >&2
exit 1
fi
shift
;;
esac
done
# ── Verify questions directory ────────────────────────────────────────────────
if [[ ! -d "$QUESTIONS_DIR" ]]; then
echo "Error: Questions directory not found: $QUESTIONS_DIR" >&2
echo "Run split_questions.py first to generate per-question files." >&2
exit 1
fi
# ── Helper: find question file by number ──────────────────────────────────────
# Matches question number against filenames like pytanie_01.md, pytanie_13_27.md
find_question_file() {
local num="$1"
local padded
padded=$(printf "%02d" "$num")
# Try exact match: pytanie_NN.md
local exact="${QUESTIONS_DIR}/pytanie_${padded}.md"
if [[ -f "$exact" ]]; then
echo "$exact"
return 0
fi
# Try dual-numbered: pytanie_NN_MM.md (match either side)
for f in "${QUESTIONS_DIR}"/pytanie_*_*.md; do
[[ -f "$f" ]] || continue
local base
base=$(basename "$f" .md)
# Extract numbers from pytanie_NN_MM
local nums="${base#pytanie_}"
local left="${nums%%_*}"
local right="${nums##*_}"
if [[ "$padded" == "$left" || "$padded" == "$right" ]]; then
echo "$f"
return 0
fi
done
return 1
}
# ── List questions ────────────────────────────────────────────────────────────
list_questions() {
echo "Available questions in ${QUESTIONS_DIR}/"
echo ""
for f in "${QUESTIONS_DIR}"/pytanie_*.md; do
[[ -f "$f" ]] || continue
# Read first line to get the header
local header
header=$(head -1 "$f")
local num title
num=$(echo "$header" | sed 's/^## PYTANIE \([0-9/]*\):.*/\1/')
title=$(echo "$header" | sed 's/^## PYTANIE [0-9/]*: //')
printf " %6s %s\n" "$num" "$title"
done
echo ""
echo "Total: $(ls -1 "${QUESTIONS_DIR}"/pytanie_*.md 2>/dev/null | wc -l) questions"
}
if $LIST_ONLY; then
list_questions
exit 0
fi
# ── Assemble selected questions ───────────────────────────────────────────────
assemble_questions() {
local selected=("$@")
if [[ ${#selected[@]} -eq 0 ]]; then
# All questions — concatenate all files in order
local first=true
for f in "${QUESTIONS_DIR}"/pytanie_*.md; do
[[ -f "$f" ]] || continue
if ! $first; then
echo ""
echo "\\newpage"
echo ""
fi
first=false
cat "$f"
done
return
fi
# Selected questions
local first=true
local found_any=false
for sel in "${selected[@]}"; do
local qfile
if qfile=$(find_question_file "$sel"); then
found_any=true
if ! $first; then
echo ""
echo "\\newpage"
echo ""
fi
first=false
cat "$qfile"
else
echo "Warning: Question $sel not found, skipping." >&2
fi
done
if ! $found_any; then
return 1
fi
}
# ── Generate output filename ─────────────────────────────────────────────────
if [[ -z "$OUTPUT_PDF" ]]; then
if [[ ${#QUESTIONS[@]} -eq 0 ]]; then
OUTPUT_PDF="/tmp/obrona_all_questions.pdf"
elif [[ ${#QUESTIONS[@]} -eq 1 ]]; then
OUTPUT_PDF="/tmp/obrona_q${QUESTIONS[0]}.pdf"
else
joined=$(IFS=_; echo "${QUESTIONS[*]}")
joined="${joined//\//-}" # Replace / with - for safe filenames
OUTPUT_PDF="/tmp/obrona_q${joined}.pdf"
fi
fi
# ── Create temporary markdown ────────────────────────────────────────────────
TMP_DIR=$(mktemp -d)
TMP_MD="${TMP_DIR}/questions.md"
if ! assemble_questions "${QUESTIONS[@]}" > "$TMP_MD"; then
echo "Error: No matching questions found for: ${QUESTIONS[*]}" >&2
echo "Use --list to see available questions." >&2
rm -rf "$TMP_DIR"
exit 1
fi
# Count extracted questions
extracted=$(grep -c "^## PYTANIE" "$TMP_MD" || echo "0")
if [[ "$extracted" -eq 0 ]]; then
echo "Error: No matching questions found for: ${QUESTIONS[*]}" >&2
echo "Use --list to see available questions." >&2
rm -rf "$TMP_DIR"
exit 1
fi
# ── Convert to PDF via pandoc + xelatex ───────────────────────────────────────
echo "Converting $extracted question(s) to PDF..."
pandoc "$TMP_MD" \
-o "$OUTPUT_PDF" \
--pdf-engine=xelatex \
--resource-path="${SCRIPT_DIR}" \
-V geometry:a4paper \
-V geometry:margin=1.8cm \
-V fontsize=12pt \
-V mainfont="DejaVu Sans" \
-V sansfont="DejaVu Sans" \
-V monofont="DejaVu Sans Mono" \
-V linestretch=1.15 \
-V colorlinks=false \
--highlight-style=monochrome \
-V header-includes='\usepackage{fancyhdr}\pagestyle{fancy}\fancyhead[L]{\small Obrona magisterska}\fancyhead[R]{\small\thepage}\fancyfoot{}' \
-V header-includes='\usepackage{enumitem}\setlist{nosep,leftmargin=*}' \
-V header-includes='\renewcommand{\arraystretch}{1.3}' \
-V header-includes='\usepackage{booktabs}' \
2>/dev/null
echo "PDF generated: $OUTPUT_PDF"
echo " Pages: $(pdfinfo "$OUTPUT_PDF" 2>/dev/null | grep Pages | awk '{print $2}' || echo "?")"
# ── Print ─────────────────────────────────────────────────────────────────────
if $DO_PRINT; then
if ! lpstat -p "$PRINTER" &>/dev/null; then
echo "Error: Printer '$PRINTER' not found." >&2
echo "Available printers:" >&2
lpstat -p 2>/dev/null | awk '{print " " $2}' >&2
rm -rf "$TMP_DIR"
exit 1
fi
echo "Printing to $PRINTER..."
lp -d "$PRINTER" \
-o media=A4 \
-o sides=one-sided \
-o fit-to-page \
"$OUTPUT_PDF"
echo "Print job submitted."
fi
# ── Cleanup ───────────────────────────────────────────────────────────────────
if $KEEP_TMP; then
echo "Temporary files kept in: $TMP_DIR"
else
rm -rf "$TMP_DIR"
fi

View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Split OBRONA_MAGISTERSKA_ODPOWIEDZI.md into per-question files.
Each file: pytanie_NN.md (or pytanie_NN_MM.md for dual-numbered like 13/27).
Placed in pytania/questions/ folder.
"""
from pathlib import Path
import re
SCRIPT_DIR = str(Path(__file__).resolve().parent)
SOURCE = str(Path(SCRIPT_DIR) / "OBRONA_MAGISTERSKA_ODPOWIEDZI.md")
OUT_DIR = str(Path(SCRIPT_DIR) / "questions")
Path(OUT_DIR).mkdir(parents=True, exist_ok=True)
with Path(SOURCE).open(encoding="utf-8") as f:
lines = f.readlines()
# Find all question boundaries
question_starts = []
for i, line in enumerate(lines):
m = re.match(r"^## PYTANIE (\d+(/\d+)?):(.*)$", line)
if m:
raw_num = m.group(1) # e.g. "13/27" or "1"
title = m.group(3).strip()
question_starts.append((i, raw_num, title))
print(f"Found {len(question_starts)} questions")
for idx, (start_line, raw_num, title) in enumerate(question_starts):
# End = next question start or EOF
if idx + 1 < len(question_starts):
end_line = question_starts[idx + 1][0]
else:
end_line = len(lines)
# Extract content, strip trailing \newpage and blank lines
content_lines = lines[start_line:end_line]
# Remove trailing \newpage and blank lines
while content_lines and content_lines[-1].strip() in ("", "\\newpage"):
content_lines.pop()
# Add final newline
content_lines.append("\n")
# Build filename: pytanie_01.md, pytanie_13_27.md
safe_num = raw_num.replace("/", "_")
# Zero-pad single numbers for sorting
parts = safe_num.split("_")
padded = "_".join(p.zfill(2) for p in parts)
filename = f"pytanie_{padded}.md"
filepath = str(Path(OUT_DIR) / filename)
with Path(filepath).open("w", encoding="utf-8") as f:
f.writelines(content_lines)
line_count = len(content_lines)
print(f" {filename:30s} ({line_count:4d} lines) PYTANIE {raw_num}: {title}")
print(f"\nAll files written to: {OUT_DIR}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Some files were not shown because too many files have changed in this diff Show More