diff --git a/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md b/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md index 8bfcc7f..a2ef190 100644 --- a/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md +++ b/pytania/OBRONA_MAGISTERSKA_ODPOWIEDZI.md @@ -92,25 +92,227 @@ ### FA — Typ 3: Języki regularne -M = (Q, Σ, δ, q₀, F). **Pamięć:** brak — tylko stan. **DFA ≡ NFA.** +**Definicja formalna:** M = (Q, Σ, δ, q₀, F) + +| Symbol | Nazwa | Znaczenie | Przykład | +|--------|-------|-----------|----------| +| **Q** | Zbiór stanów (States) | Skończony zbiór wszystkich stanów automatu — „pozycji" w schemacie | Q = {q₀, q₁, q₂} — trzy stany | +| **Σ** | Alfabet wejściowy (Sigma) | Skończony zbiór symboli, które automat czyta | Σ = {a, b} — alfabet dwuliterowy | +| **δ** | Funkcja przejścia (Delta) | Reguła: „w stanie q, czytając symbol a, przejdź do stanu q'" — δ: Q × Σ → Q (DFA) | δ(q₀, a) = q₁ — z q₀ po 'a' idź do q₁ | +| **q₀** | Stan początkowy | Stan, w którym automat zaczyna pracę | q₀ — zawsze startujemy tu | +| **F** | Stany akceptujące (Final) | Podzbiór Q — jeśli automat skończy w stanie z F, słowo jest AKCEPTOWANE | F = {q₂} — tylko q₂ akceptuje | + +**Pamięć:** brak — cała informacja to aktualny stan. **DFA ≡ NFA.** Równoważne wyrażeniom regularnym (regex). Przykłady: identyfikatory, podzielność. Nie: aⁿbⁿ, nawiasy, palindromy. +**Przykład krok po kroku — FA rozpoznaje język L = {słowa nad {a,b} kończące się na "ab"}:** + +Automat DFA: Q = {q₀, q₁, q₂}, Σ = {a, b}, F = {q₂} + + Tabela przejść: + ┌───────┬───────┬───────┐ + │ Stan │ a │ b │ + ├───────┼───────┼───────┤ + │ q₀ │ q₁ │ q₀ │ (q₀ = „nie widziałem 'a' na końcu") + │ q₁ │ q₁ │ q₂ │ (q₁ = „ostatni symbol to 'a'") + │ q₂* │ q₁ │ q₀ │ (q₂ = „ostatnie dwa to 'ab'" ← AKCEPTUJ) + └───────┴───────┴───────┘ + + Wejście: w = "baab" + + Krok 1: stan = q₀, czytam 'b' → δ(q₀, b) = q₀ stan: q₀ + Krok 2: stan = q₀, czytam 'a' → δ(q₀, a) = q₁ stan: q₁ + Krok 3: stan = q₁, czytam 'a' → δ(q₁, a) = q₁ stan: q₁ + Krok 4: stan = q₁, czytam 'b' → δ(q₁, b) = q₂ stan: q₂ ✓ + + Końcowy stan q₂ ∈ F → słowo "baab" AKCEPTOWANE ✓ + + Wejście: w = "ba" + + Krok 1: stan = q₀, czytam 'b' → δ(q₀, b) = q₀ stan: q₀ + Krok 2: stan = q₀, czytam 'a' → δ(q₀, a) = q₁ stan: q₁ + + Końcowy stan q₁ ∉ F → słowo "ba" ODRZUCONE ✗ + +![Diagram FA rozpoznającego język kończący się na "ab"](img/fa_recognition_example.png) + ### PDA — Typ 2: Języki bezkontekstowe -M = (Q, Σ, Γ, δ, q₀, Z₀, F). **Pamięć:** stos LIFO. **DPDA ⊂ NPDA!** +**Definicja formalna:** M = (Q, Σ, Γ, δ, q₀, Z₀, F) + +| Symbol | Nazwa | Znaczenie | Przykład | +|--------|-------|-----------|----------| +| **Q** | Zbiór stanów | Jak w FA — skończony zbiór stanów | Q = {q₀, q₁, q₂} | +| **Σ** | Alfabet wejściowy | Symbole czytane z wejścia | Σ = {a, b} | +| **Γ** | Alfabet stosowy (Gamma) | Symbole, które mogą być na stosie — INNE niż alfabet wejściowy! | Γ = {Z₀, A} — Z₀ = dno stosu, A = znacznik | +| **δ** | Funkcja przejścia | Teraz zależy od TRZECH rzeczy: stan + symbol wejściowy + szczyt stosu → nowy stan + operacja na stosie | δ(q₀, a, Z₀) = (q₀, AZ₀) — „push A" | +| **q₀** | Stan początkowy | Jak w FA | q₀ | +| **Z₀** | Symbol dna stosu | Początkowy symbol na stosie — pozwala wykryć, że stos jest „pusty" | Z₀ — stos zaczyna z jednym Z₀ | +| **F** | Stany akceptujące | Jak w FA (alternatywnie: akceptacja przez pusty stos) | F = {q₂} | + +**Pamięć:** stos LIFO — ostatni włożony, pierwszy wyjęty. **DPDA ⊂ NPDA!** Przykłady: aⁿbⁿ, nawiasy, wwᴿ. Nie: aⁿbⁿcⁿ, ww. +**Przykład krok po kroku — PDA rozpoznaje język L = {aⁿbⁿ | n ≥ 1}:** + +Automat PDA: Q = {q₀, q₁, q₂}, Σ = {a, b}, Γ = {Z₀, A}, F = {q₂} + + Reguły przejścia: + δ(q₀, a, Z₀) = (q₀, AZ₀) ← czytam 'a', stos pusty → push A + δ(q₀, a, A) = (q₀, AA) ← czytam 'a', na stosie A → push jeszcze A + δ(q₀, b, A) = (q₁, ε) ← czytam 'b', na stosie A → pop A, przejdź do q₁ + δ(q₁, b, A) = (q₁, ε) ← czytam 'b', na stosie A → pop A + δ(q₁, ε, Z₀) = (q₂, Z₀) ← brak wejścia, stos = Z₀ → akceptuj! + + Wejście: w = "aabb" (a²b², n=2) + + Krok 1: stan=q₀, czytam 'a', stos=[Z₀] + δ(q₀, a, Z₀) = (q₀, AZ₀) → push A + stan=q₀, stos=[A, Z₀] + + Krok 2: stan=q₀, czytam 'a', stos=[A, Z₀] + δ(q₀, a, A) = (q₀, AA) → push A + stan=q₀, stos=[A, A, Z₀] + + Krok 3: stan=q₀, czytam 'b', stos=[A, A, Z₀] + δ(q₀, b, A) = (q₁, ε) → pop A + stan=q₁, stos=[A, Z₀] + + Krok 4: stan=q₁, czytam 'b', stos=[A, Z₀] + δ(q₁, b, A) = (q₁, ε) → pop A + stan=q₁, stos=[Z₀] + + Krok 5: stan=q₁, brak wejścia, stos=[Z₀] + δ(q₁, ε, Z₀) = (q₂, Z₀) → akceptuj! + stan=q₂ ∈ F → "aabb" AKCEPTOWANE ✓ + + Intuicja: Push A za każde 'a', pop A za każde 'b'. + Jeśli stos pusty po przeczytaniu → równo a i b → akceptuj! + +![Diagram PDA rozpoznającego język aⁿbⁿ](img/pda_recognition_example.png) + ### LBA — Typ 1: Języki kontekstowe -TM z taśmą ograniczoną do |w|. **DLBA =? NLBA** — problem otwarty! +**Definicja formalna:** M = (Q, Σ, Γ, δ, q₀, q_acc, q_rej) — jak TM, ale z ograniczeniem taśmy + +| Symbol | Nazwa | Znaczenie | Przykład | +|--------|-------|-----------|----------| +| **Q** | Zbiór stanów | Skończony zbiór stanów — może być większy niż w FA/PDA | Q = {q₀, q₁, q₂, q₃, q₄, q_acc, q_rej} | +| **Σ** | Alfabet wejściowy | Symbole na taśmie na początku (BEZ markera pustego) | Σ = {a, b, c} | +| **Γ** | Alfabet taśmowy (Gamma) | Wszystkie symbole dozwolone na taśmie, Σ ⊂ Γ, zawiera marker pusty ⊔ | Γ = {a, b, c, X, Y, Z, ⊔} — X,Y,Z = zaznaczone | +| **δ** | Funkcja przejścia | δ(stan, symbol_na_taśmie) = (nowy_stan, symbol_do_zapisania, kierunek_głowicy) | δ(q₀, a) = (q₁, X, R) — zaznacz 'a' jako X, idź w prawo | +| **q₀** | Stan początkowy | Głowica na pierwszym symbolu taśmy | q₀ | +| **q_acc** | Stan akceptujący | Akceptacja — automat się ZATRZYMUJE | q_acc | +| **q_rej** | Stan odrzucający | Odrzucenie — automat się ZATRZYMUJE | q_rej | + +**Ograniczenie LBA:** Głowica NIE może wyjść poza |w| komórek (długość wejścia). To jedyna różnica od TM! +**DLBA =? NLBA** — problem otwarty! Przykłady: aⁿbⁿcⁿ, ww. +**Przykład krok po kroku — LBA rozpoznaje język L = {aⁿbⁿcⁿ | n ≥ 1}:** + +Strategia: wielokrotnie przejdź taśmę, za każdym razem zaznacz jedno 'a', jedno 'b', jedno 'c'. + + Wejście: w = "aabbcc" (n=2), taśma = [a, a, b, b, c, c] + + === Runda 1: zaznacz po jednym z każdego === + Krok 1: stan=q₀, głowica→'a', zaznacz a→X, idź w prawo + taśma = [X, a, b, b, c, c] głowica na 'a' + + Krok 2: stan=q₁, przeskocz pozostałe 'a' (idź prawo) + głowica na 'b' + + Krok 3: zaznacz b→Y, idź w prawo + taśma = [X, a, Y, b, c, c] głowica na 'b' + + Krok 4: stan=q₂, przeskocz pozostałe 'b' (idź prawo) + głowica na 'c' + + Krok 5: zaznacz c→Z, wróć na początek + taśma = [X, a, Y, b, Z, c] + + === Runda 2: zaznacz kolejne === + Krok 6: od początku, znajdź pierwsze niezaznaczone 'a' → zaznacz X + taśma = [X, X, Y, b, Z, c] + + Krok 7: znajdź pierwsze niezaznaczone 'b' → zaznacz Y + taśma = [X, X, Y, Y, Z, c] + + Krok 8: znajdź pierwsze niezaznaczone 'c' → zaznacz Z + taśma = [X, X, Y, Y, Z, Z] + + === Sprawdzenie: nie ma niezaznaczonych symboli → AKCEPTUJ ✓ === + + Gdyby "aabcc" (2a, 1b, 2c): po zaznaczeniu a,b,c w rundzie 1 + w rundzie 2 zaznaczamy drugie 'a', ale NIE MA drugiego 'b' → ODRZUĆ ✗ + +![Diagram LBA rozpoznającego język aⁿbⁿcⁿ](img/lba_recognition_example.png) + ### TM — Typ 0: Rekurencyjnie przeliczalne -M = (Q, Σ, Γ, δ, q₀, q_acc, q_rej). **Pamięć:** taśma ∞ R/W. **DTM ≡ NTM** (moc). +**Definicja formalna:** M = (Q, Σ, Γ, δ, q₀, q_acc, q_rej) + +| Symbol | Nazwa | Znaczenie | Przykład | +|--------|-------|-----------|----------| +| **Q** | Zbiór stanów | Skończony zbiór stanów — jak LBA | Q = {q₀, q₁, q₂, q₃, q_acc, q_rej} | +| **Σ** | Alfabet wejściowy | Symbole wejścia (nie zawiera ⊔) | Σ = {0, 1} | +| **Γ** | Alfabet taśmowy | Σ ⊂ Γ, zawiera marker pusty ⊔ (blank) | Γ = {0, 1, X, ⊔} | +| **δ** | Funkcja przejścia | Identyczna forma jak LBA: δ(q, a) = (q', b, L/R) | δ(q₀, 0) = (q₁, X, R) | +| **q₀** | Stan początkowy | Głowica na pierwszym symbolu | q₀ | +| **q_acc** | Stan akceptujący | Wejście do q_acc → słowo AKCEPTOWANE, stop | q_acc | +| **q_rej** | Stan odrzucający | Wejście do q_rej → słowo ODRZUCONE, stop | q_rej | + +**Kluczowa różnica od LBA:** Taśma jest **nieskończona** w prawo — głowica może wyjść poza wejście i pisać na pustych komórkach. To daje NIEOGRANICZONĄ pamięć roboczą. + +**Pamięć:** taśma ∞ R/W. **DTM ≡ NTM** (równoważne pod względem mocy). Teza Churcha-Turinga: TM modeluje każde obliczenie. Nie: komplement problemu stopu. +**Przykład krok po kroku — TM rozpoznaje język L = {0ⁿ1ⁿ | n ≥ 1} (jak aⁿbⁿ, ale z 0 i 1):** + +Strategia: zaznacz jedno '0' i jedno '1' w każdej rundzie, powtarzaj aż do wyczerpania. + + Wejście: w = "0011", taśma = [0, 0, 1, 1, ⊔, ⊔, ⊔, ...] + ↑ nieskończona! + + === Runda 1 === + Krok 1: stan=q₀, głowica→'0', zaznacz 0→X, idź w prawo + taśma = [X, 0, 1, 1, ⊔, ...] stan=q₁ + + Krok 2: stan=q₁, przeskocz '0' w prawo, znajdź '1' + głowica na pierwszym '1' + + Krok 3: zaznacz 1→Y, wróć na początek (idź w lewo do X) + taśma = [X, 0, Y, 1, ⊔, ...] stan=q₃ + + === Runda 2 === + Krok 4: znajdź pierwsze niezaznaczone '0' → zaznacz X + taśma = [X, X, Y, 1, ⊔, ...] + + Krok 5: znajdź pierwsze niezaznaczone '1' → zaznacz Y + taśma = [X, X, Y, Y, ⊔, ...] + + === Sprawdzenie === + Krok 6: od początku szukam niezaznaczonych — nie ma → q_acc + "0011" AKCEPTOWANE ✓ + + TM może też WYJŚĆ poza wejście (czego LBA nie może): + Np. "011" — po zaznaczeniu 0↔1, zostaje '1' bez pary → q_rej ✗ + +![Diagram TM rozpoznającego język 0ⁿ1ⁿ](img/tm_recognition_example.png) + +### Porównanie definicji formalnych + +| Element | FA | PDA | LBA | TM | +|---------|----|----|-----|-----| +| **Stany Q** | ✓ mało | ✓ mało | ✓ więcej | ✓ więcej | +| **Alfabet Σ** | ✓ | ✓ | ✓ | ✓ | +| **Alfabet stosu/taśmy Γ** | ✗ brak | ✓ stosowy | ✓ taśmowy | ✓ taśmowy | +| **Przejście δ** | q × Σ → q | q × (Σ∪ε) × Γ → q × Γ* | q × Γ → q × Γ × {L,R} | q × Γ → q × Γ × {L,R} | +| **Dodatkowa pamięć** | brak | stos (LIFO) | taśma ogr. do \|w\| | taśma ∞ | +| **Akceptacja** | stan ∈ F | stan ∈ F lub pusty stos | stan = q_acc | stan = q_acc | +| **Może się zapętlić?** | NIE (skończone) | TAK (ε-przejścia) | TAK | TAK (problem stopu!) | + ### Tabela porównawcza | Cecha | FA | PDA | LBA | TM | @@ -166,11 +368,7 @@ Teza Churcha-Turinga: TM modeluje każde obliczenie. Nie: komplement problemu st Przykład — graf z 4 wierzchołkami (A, B, C, D), start = A: - Graf: A --2-- B --3-- D - | | - 4 1 - | | - C ------5-------+ +![Graf przykładowy — 4 wierzchołki z wagami](img/graph_example_structure.png) d = [ A:0, B:∞, C:∞, D:∞ ] ← tablica na starcie odwiedzone = {} @@ -272,51 +470,197 @@ Przykład — kluczowa różnica: decrease-key: **Dijkstra:** - import heapq - def dijkstra(graph, start): - d = {v: float('inf') for v in graph} - d[start] = 0 - pq = [(0, start)] # (odległość, wierzchołek) - while pq: - dist_u, u = heapq.heappop(pq) - if dist_u > d[u]: continue # nieaktualny wpis - for v, w in graph[u]: # v=sąsiad, w=waga - if d[u] + w < d[v]: # relaksacja - d[v] = d[u] + w - heapq.heappush(pq, (d[v], v)) - return d + def dijkstra(graph, start): # graph = słownik, klucz = wierzchołek, + # wartość = lista par (sąsiad, waga) + # np. graph = {'A': [('B',2), ('C',4)], + # 'B': [('D',3)], ...} + # start = wierzchołek startowy, np. 'A' + + d = {v: float('inf') for v in graph} # d = słownik odległości (distance) + # Klucz: wierzchołek v + # Wartość: najkrótsza DOTYCHCZAS ZNANA + # odległość od start do v + # Na początku: ∞ (nieskończoność) dla + # wszystkich — bo jeszcze niczego + # nie odkryliśmy + # float('inf') = Python-owa nieskończoność, + # każda liczba jest od niej mniejsza + + d[start] = 0 # odległość od startu do samego siebie = 0 + + visited = set() # visited = zbiór ZAMKNIĘTYCH wierzchołków + # (już przetworzonych, nie wracamy do nich) + # set() = pusty zbiór Pythona — O(1) lookup + + for _ in range(len(graph)): # powtórz V razy (raz na każdy wierzchołek) + # W każdej iteracji wybieramy JEDEN + # wierzchołek o min d[v] i go przetwarzamy + + # --- Szukanie minimum w tablicy d --- → O(V) na każde szukanie + u = None # u = wierzchołek o najmniejszej odległości + # (jeszcze nie odwiedzony) + for v in graph: # przejrzyj WSZYSTKIE wierzchołki + if v not in visited: # pomiń już odwiedzone + if u is None or d[v] < d[u]: # jeśli v ma mniejszą odległość + u = v # zapamiętaj go jako kandydata + # Po tej pętli: u = wierzchołek z min d, spośród nieodwiedzonych + # To jest O(V) — przeszukujemy całą tablicę! + + if d[u] == float('inf'): # jeśli minimum to ∞, reszta jest + break # nieosiągalna — koniec + + visited.add(u) # oznacz u jako odwiedzony (zamknięty) + # NIE WRACAMY do u — to jest ZACHŁANNOŚĆ + # Dijkstry (i dlatego ujemne wagi psują!) + + for v, w in graph[u]: # iteruj po sąsiadach wierzchołka u + # v = sąsiad (vertex), w = waga krawędzi u→v + # np. graph['A'] = [('B',2), ('C',4)] + # → v='B', w=2, potem v='C', w=4 + + if d[u] + w < d[v]: # RELAKSACJA: czy droga do v PRZEZ u + # jest krótsza niż dotychczas znana? + # d[u] = koszt dotarcia do u + # w = koszt krawędzi u→v + # d[u]+w = koszt drogi start→u→v + # d[v] = dotychczasowy najlepszy koszt do v + + d[v] = d[u] + w # TAK, jest krótsza → zaktualizuj! + # (tablica d pełni tu rolę kolejki + # priorytetowej — po prostu szukamy + # minimum w niej w każdej iteracji) + + return d # zwróć słownik najkrótszych odległości + # np. {'A': 0, 'B': 2, 'C': 4, 'D': 5} + # Złożoność: O(V²) — V szukań min × O(V) każde + +![Przejście grafu algorytmem Dijkstry — krok po kroku](img/dijkstra_traversal.png) **Bellman-Ford:** - def bellman_ford(vertices, edges, start): - d = {v: float('inf') for v in vertices} - d[start] = 0 - for _ in range(len(vertices) - 1): # V-1 iteracji - for u, v, w in edges: # każda krawędź - if d[u] + w < d[v]: # relaksacja - d[v] = d[u] + w - # Wykrywanie cyklu ujemnego: - for u, v, w in edges: - if d[u] + w < d[v]: - return None # cykl ujemny! - return d + def bellman_ford(vertices, edges, start): # vertices = lista wierzchołków, np. ['A','B','C','D'] + # edges = lista krawędzi, każda to (skąd, dokąd, waga) + # np. [('A','B',2), ('A','C',4), ('B','D',3), ...] + # start = wierzchołek startowy + # UWAGA: format inny niż w Dijkstrze! + # Dijkstra: graf jako słownik sąsiedztwa + # B-F: explicite lista krawędzi + + d = {v: float('inf') for v in vertices} # d = słownik odległości — identycznie + # jak w Dijkstrze. Klucz = wierzchołek, + # wartość = najkrótsza znana odległość. + # Na starcie: ∞ dla wszystkich. + + d[start] = 0 # odległość do siebie = 0 + + for _ in range(len(vertices) - 1): # powtórz V−1 razy (V = liczba wierzchołków) + # DLACZEGO V−1? Bo najdłuższa najkrótsza + # ścieżka (bez cykli) ma co najwyżej V−1 + # krawędzi. Po k iteracjach mamy poprawne + # odległości dla ścieżek o ≤ k krawędziach. + # _ = zmienna, której nie używamy (konwencja) + + for u, v, w in edges: # w KAŻDEJ iteracji przejrzyj WSZYSTKIE krawędzie + # u = początek krawędzi, v = koniec, w = waga + # To jest brute-force — stąd O(V·E) + + if d[u] + w < d[v]: # RELAKSACJA — identyczna jak w Dijkstrze: + # czy droga start→u→v jest krótsza niż d[v]? + + d[v] = d[u] + w # TAK → zaktualizuj + + # --- Wykrywanie cyklu ujemnego --- + for u, v, w in edges: # dodatkowe (V-te) przejście po krawędziach + # Jeśli NADAL da się poprawić odległość, + # to znaczy, że istnieje cykl ujemny! + # (po V−1 iteracjach powinno być stabilne) + + if d[u] + w < d[v]: # nadal można polepszyć? → cykl ujemny! + return None # zwróć None = sygnał "cykl ujemny wykryty" + + return d # zwróć słownik odległości (jak Dijkstra) + +![Przejście grafu algorytmem Bellmana-Forda — krok po kroku](img/bellman_ford_traversal.png) **A*:** - def a_star(graph, start, goal, h): - d = {start: 0} - pq = [(h(start), start)] # f = g + h - came_from = {} - while pq: - f_u, u = heapq.heappop(pq) - if u == goal: break # znaleziono! - for v, w in graph[u]: - g_new = d[u] + w - if v not in d or g_new < d[v]: - d[v] = g_new - heapq.heappush(pq, (g_new + h(v), v)) - came_from[v] = u - return came_from, d.get(goal) + def a_star(graph, start, goal, h): # graph = słownik sąsiedztwa (jak Dijkstra) + # start = wierzchołek startowy + # goal = wierzchołek DOCELOWY (cel) + # → to jedyna różnica od Dijkstry: + # szukamy ścieżki do JEDNEGO celu + # h = FUNKCJA heurystyczna: h(v) zwraca + # oszacowanie odległości od v do goal + # np. h = lambda v: odl_euklidesowa(v, goal) + + d = {start: 0} # d = słownik g(n) = faktyczny koszt + # dotarcia od start do n + # Tu trzymamy TYLKO odkryte wierzchołki + # (nie inicjalizujemy ∞ dla reszty) + + f = {start: h(start)} # f = słownik f(n) = g(n) + h(n) + # f to szacunkowy ŁĄCZNY koszt ścieżki: + # dotychczasowy koszt g + heurystyka h + # Sortujemy po f (nie po g!) — to kieruje + # przeszukiwanie W STRONĘ CELU + # Na starcie: f(start) = 0 + h(start) + + came_from = {} # came_from = słownik "skąd przyszliśmy" + # Klucz: wierzchołek v + # Wartość: wierzchołek, z którego dotarliśmy do v + # Służy do ODTWORZENIA ścieżki po znalezieniu celu + # np. came_from = {'B':'A', 'D':'B'} + # → ścieżka: A → B → D + + visited = set() # visited = zbiór zamkniętych wierzchołków + # (już przetworzonych) + + while f: # dopóki są odkryte, nieprzetworzone wierzchołki + # (f zawiera tylko te, do których dotarliśmy) + + # --- Szukanie minimum f w tablicy --- → O(V) + u = min(f, key=f.get) # u = wierzchołek o najniższym f(n) + # min() przeszukuje WSZYSTKIE klucze w f + # key=f.get → porównuj po wartościach f[v] + # Równoważne: for v in f: if f[v] < f[best]... + del f[u] # usuń u z open set (przetwarzamy go teraz) + + if u == goal: break # ZNALEZIONO CEL! → przerwij + # Kluczowa optymalizacja A*: + # Dijkstra przetwarza WSZYSTKIE wierzchołki, + # A* KOŃCZY gdy dotrze do celu + + visited.add(u) # oznacz u jako przetworzony + + for v, w in graph[u]: # iteruj po sąsiadach u + # v = sąsiad, w = waga krawędzi u→v + + if v in visited: # jeśli v już przetworzony → pomiń + continue + + g_new = d[u] + w # g_new = potencjalny nowy koszt dotarcia do v + # (koszt do u + krawędź u→v) + + if v not in d or g_new < d[v]: # jeśli v jeszcze nie odkryty + # LUB znaleźliśmy krótszą drogę + + d[v] = g_new # zaktualizuj g(v) = faktyczny koszt do v + + f[v] = g_new + h(v) # zaktualizuj f(v) = g(v) + h(v) + # f kieruje przeszukiwanie: + # niskie f = „obiecujący" wierzchołek + # (blisko celu wg heurystyki) + + came_from[v] = u # zapamiętaj: do v dotarliśmy z u + # (do odtworzenia ścieżki) + + return came_from, d.get(goal) # came_from = mapa do odtworzenia ścieżki + # d.get(goal) = koszt najkrótszej ścieżki + # do celu (None jeśli nieosiągalny) + # Złożoność: O(V²) z tablicą, ale w praktyce + # dużo szybciej dzięki heurystyce + +![Przejście grafu algorytmem A* — krok po kroku](img/astar_traversal.png) --- @@ -383,16 +727,11 @@ Przykład — kluczowa różnica: decrease-key: **Redundancja** — powtarzanie tych samych danych w wielu miejscach. Nie chodzi o kopie zapasowe — chodzi o niepotrzebne duplikowanie informacji w tabeli. -Przykład — tabela StudentKursy (ZŁEJ konstrukcji, BEZ normalizacji): +Przykład — tabela „Rejestr" (ZŁEJ konstrukcji, BEZ normalizacji) — ta sama tabela będzie normalizowana krok po kroku od 0NF do 5NF w dalszej części: - | StudentID | Imię | WydziałID | NazwaWydziału | KursID | NazwaKursu | - |-----------|-------|-----------|---------------|--------|------------| - | 1 | Anna | W4 | EiTI | K10 | Bazy danych| - | 1 | Anna | W4 | EiTI | K20 | Algorytmy | - | 2 | Jan | W4 | EiTI | K10 | Bazy danych| - | 3 | Ewa | W2 | Fizyka | K30 | Optyka | +![Tabela 0NF — forma nienormalna z listami w komórkach](img/nf_0nf_table.png) -Problem: „Anna", „W4", „EiTI", „Bazy danych" powtórzone wielokrotnie! +Problem: „Anna", „W4", „EiTI", „Bazy danych" powtórzone wielokrotnie! Kolumna „Telefony" zawiera listy (nieatomowe wartości). **Anomalia** — niepożądany efekt uboczny operacji na redundantnych danych. Trzy typy: @@ -410,116 +749,181 @@ Problem: „Anna", „W4", „EiTI", „Bazy danych" powtórzone wielokrotnie! **Zależność przechodnia (transitive dependency):** A → B i B → C, więc A → C „przez pośrednika B". Np. StudentID → WydziałID → NazwaWydziału. StudentID nie określa bezpośrednio NazwaWydziału — robi to pośrednio przez WydziałID. Problem: NazwaWydziału „zależy od czegoś, co nie jest kluczem" → redundancja. **Nietrywialna FD:** X → A, gdzie A nie jest częścią X. Np. StudentID → Imię (nietrywialna: Imię ≠ StudentID). Ale {StudentID, Imię} → StudentID jest TRYWIALNA (StudentID jest częścią lewej strony — oczywiste). W BCNF sprawdzamy tylko nietrywialne FD. - +P **Wielowartościowa zależność (MVD — Multi-Valued Dependency):** X →→ Y oznacza: dla jednej wartości X istnieje ZBIÓR wartości Y, niezależny od reszty. Np. Student →→ Hobby i Student →→ Kurs: hobby Ani nie zależą od jej kursów i odwrotnie, ale ich połączenie tworzy iloczyn kartezjański (niepotrzebne powtórzenia). **Dekompozycja** — rozbicie jednej dużej tabeli na kilka mniejszych, połączonych kluczami obcymi. Cel: każda tabela przechowuje JEDEN fakt. **Normalizacja eliminuje redundancję** właśnie przez dekompozycję — informacja zamiast być powtarzana w wielu wierszach, przechowywana jest RAZ w osobnej tabeli i łączona przez JOIN. -Przykład dekompozycji powyższej tabeli: - - Studenci: Wydziały: Kursy: - | StID | Imię | WydziałID | | WydziałID | Nazwa | | KursID | Nazwa | - |------|------|-----------| |-----------|-------| |--------|------------| - | 1 | Anna | W4 | | W4 | EiTI | | K10 | Bazy danych| - | 2 | Jan | W4 | | W2 |Fizyka | | K20 | Algorytmy | - | 3 | Ewa | W2 | | K30 | Optyka | - - Zapisy (StudentID, KursID): - | StID | KursID | - |------|--------| - | 1 | K10 | - | 1 | K20 | - | 2 | K10 | - | 3 | K30 | - - Teraz: „EiTI" zapisane RAZ (w Wydziały). Zmiana nazwy = 1 wiersz. - **Atomowe wartości (1NF)** — każda komórka zawiera JEDNĄ niepodzielną wartość. NIE listy, NIE zbiory, NIE tabele w komórce. - ŹLE (łamie 1NF): DOBRZE (1NF): - | Student | Kursy | | Student | Kurs | - |---------|-----------------| |---------|-------------| - | Anna | BD, Algorytmy | | Anna | BD | - | Jan | BD | | Anna | Algorytmy | - | Jan | BD | +**Zależność złączenia (JD — Join Dependency):** Tabela R spełnia JD *{R₁, R₂, ..., Rₙ} jeśli R = R₁ ⨝ R₂ ⨝ ... ⨝ Rₙ (bezstratna dekompozycja na n projekcji). Każda MVD implikuje JD, ale JD jest OGÓLNIEJSZA — obejmuje dekompozycje na 3+ tabele, których MVD nie wyraża. 5NF eliminuje takie „ukryte" redundancje. -### Przykłady postaci normalnych +### Pełny przykład: od 0NF do 5NF krok po kroku -**Przykład naruszenia 2NF** — klucz złożony (StudentID, KursID): +Używamy JEDNEJ tabeli „Rejestr" i normalizujemy ją przez WSZYSTKIE postacie normalne. W każdym kroku pokazujemy: (1) jaki problem istnieje, (2) jaką regułę łamie, (3) jak go naprawić dekompozycją. - | StudentID | KursID | NazwaKursu | Ocena | - |-----------|--------|-------------|-------| - | 1 | K10 | Bazy danych | 5 | - | 2 | K10 | Bazy danych | 4 | - - NazwaKursu zależy TYLKO od KursID, nie od pełnego klucza (StudentID, KursID). - To „częściowa zależność" → łamie 2NF. - Naprawa: wydziel Kursy(KursID, NazwaKursu) osobno. - -**Przykład naruszenia 3NF** — klucz StudentID: - - | StudentID | Imię | WydziałID | NazwaWydziału | - |-----------|-------|-----------|---------------| - | 1 | Anna | W4 | EiTI | - | 2 | Jan | W4 | EiTI | ← „EiTI" powtórzone! - - StudentID → WydziałID → NazwaWydziału (zależność przechodnia!). - NazwaWydziału zależy od WydziałID, a nie bezpośrednio od klucza. - Naprawa: wydziel Wydziały(WydziałID, NazwaWydziału). - -**Przykład naruszenia BCNF:** - - Przedmioty prowadzone — klucz: (Student, Przedmiot) - | Student | Przedmiot | Prowadzący | - |---------|-----------|------------| - | Anna | BD | Kowalski | - | Jan | BD | Kowalski | - | Anna | Algo | Nowak | - - FD: Prowadzący → Przedmiot (jeden prowadzący = jeden przedmiot). - Prowadzący NIE jest nadkluczem → łamie BCNF. - (Spełnia 3NF, bo Przedmiot jest atrybutem pierwszym — wyjątek 3NF.) - Naprawa: StudentProwadzący(Student, Prowadzący) + ProwadzącyPrzedmiot(Prowadzący, Przedmiot). - -**Przykład naruszenia 4NF:** - - | Student | Hobby | Kurs | - |---------|----------|-----------| - | Anna | Szachy | BD | - | Anna | Szachy | Algorytmy | - | Anna | Bieganie | BD | - | Anna | Bieganie | Algorytmy | - - Student →→ Hobby i Student →→ Kurs (niezależne wielowartościowe zależności). - Hobby i Kursy nie mają ze sobą nic wspólnego, ale tworzą iloczyn kartezjański. - Naprawa: StudentHobby(Student, Hobby) + StudentKurs(Student, Kurs). +**Zależności funkcyjne w tabeli Rejestr:** +- StID → Imię, WydziałID (jeden student = jedno imię, jeden wydział) +- WydziałID → NazwaWydziału (jeden wydział = jedna nazwa) +- KursID → NazwaKursu (jeden kurs = jedna nazwa) +- (StID, KursID) → Prowadzący (student na kursie ma jednego prowadzącego) +- Prowadzący → KursID (jeden prowadzący uczy dokładnie jednego kursu) +- StID →→ Telefon (wielowartościowa — student ma ZBIÓR telefonów) --- -### Redundancja — powtarzanie danych +### KROK 1: 0NF → 1NF (atomowość wartości) -Prowadzi do trzech **anomalii**: -1. **Wstawiania** — nie można dodać danych bez zbędnych powiązań -2. **Usuwania** — usunięcie rekordu kasuje niezwiązane informacje -3. **Modyfikacji** — zmiana jednej informacji wymaga wielu aktualizacji +**Problem:** Kolumna „Telefony" zawiera listy wartości (np. „111-222, 333-444") — to NIE jest atomowe. -### Normalizacja — eliminacja redundancji przez dekompozycję +**Reguła 1NF:** Każda komórka = JEDNA niepodzielna wartość + istnieje klucz główny. -**Zależność funkcyjna:** X → Y — wartość X jednoznacznie określa Y. +**Naprawa:** Wydziel wielowartościowy atrybut do osobnej tabeli. -### Postacie normalne (5NF ⊂ 4NF ⊂ BCNF ⊂ 3NF ⊂ 2NF ⊂ 1NF) +![Tabele po normalizacji do 1NF](img/nf_1nf_tables.png) -**1NF:** Atomowe wartości, brak list/tablic w komórkach, istnieje klucz główny. +**Wynik:** Dwie tabele — Rejestr1NF (klucz: StID, KursID) i Telefony (klucz: StID, Telefon). Wszystkie komórki atomowe ✓. -**2NF:** 1NF + każdy atrybut wtórny zależy od CAŁEGO klucza (dotyczy kluczy złożonych). -- Naruszenie: NazwaKursu zależy tylko od KursID, nie od (StudentID, KursID). +--- -**3NF:** 2NF + brak zależności przechodnich (atrybut wtórny nie zależy od innego wtórnego). -- Naruszenie: StudentID → WydziałID → NazwaWydziału. +### KROK 2: 1NF → 2NF (pełna zależność od klucza) -**BCNF:** Dla każdej nietrywialnej FD X→A, X jest nadkluczem. Silniejsza niż 3NF. +**Problem:** Klucz złożony (StID, KursID), ale wiele atrybutów zależy tylko od CZĘŚCI klucza: +- StID → Imię, WydziałID, NazwaWydziału — zależy TYLKO od StID (częściowa zależność!) +- KursID → NazwaKursu — zależy TYLKO od KursID (częściowa zależność!) +- (StID, KursID) → Prowadzący — zależy od PEŁNEGO klucza ✓ -**4NF:** BCNF + brak wielowartościowych zależności nietrywialnych. +**Reguła 2NF:** Każdy atrybut wtórny (non-prime) zależy od CAŁEGO klucza, nie od jego części. + +**Naprawa:** Wydziel atrybuty częściowo zależne do osobnych tabel. + +![Tabele po normalizacji do 2NF](img/nf_2nf_tables.png) + +**Wynik:** Studenci (StID → Imię, WydziałID, NazwaWydziału), Kursy (KursID → NazwaKursu), Zapisy (StID, KursID → Prowadzący), Telefony. + +**Dlaczego to ważne:** Bez 2NF: dodanie nowego kursu wymaga podania studenta. Z 2NF: dodajesz kurs do tabeli Kursy niezależnie. + +--- + +### KROK 3: 2NF → 3NF (brak zależności przechodnich) + +**Problem w tabeli Studenci:** StID → WydziałID → NazwaWydziału — zależność PRZECHODNIA! +- NazwaWydziału nie zależy bezpośrednio od klucza (StID) — zależy od WydziałID, który sam zależy od StID. +- WydziałID NIE jest kluczem tabeli Studenci → NazwaWydziału zależy od nie-klucza → redundancja „EiTI" powtórzone. + +**Reguła 3NF:** 2NF + żaden atrybut wtórny nie zależy od innego atrybutu wtórnego (brak zależności przechodnich). Formalnie: dla każdej nietrywialnej FD X → A, albo X jest nadkluczem, ALBO A jest atrybutem pierwszym (prime). + +**Naprawa:** Wydziel WydziałID → NazwaWydziału do osobnej tabeli Wydziały. + +![Tabele po normalizacji do 3NF](img/nf_3nf_tables.png) + +**Wynik:** Studenci (StID → Imię, WydziałID), Wydziały (WydziałID → NazwaWydziału), Kursy, Zapisy, Telefony. + +**Dlaczego to ważne:** Bez 3NF: zmiana nazwy wydziału wymaga aktualizacji WIELU wierszy w Studenci. Z 3NF: zmiana nazwy = 1 wiersz w tabeli Wydziały. + +--- + +### KROK 4: 3NF → BCNF (każdy determinant = nadklucz) + +**Problem w tabeli Zapisy(StID, KursID, Prowadzący):** +- Klucz: (StID, KursID) +- FD: Prowadzący → KursID (jeden prowadzący uczy jednego kursu) +- Prowadzący NIE jest nadkluczem tabeli Zapisy → **NARUSZENIE BCNF!** +- ALE: KursID JEST atrybutem pierwszym (prime — część klucza) → wyjątek 3NF → **3NF jest spełnione!** + +**Reguła 3NF vs BCNF — kluczowa różnica:** +- **3NF:** Dla FD X → A: X jest nadkluczem **LUB** A jest prime. (Wyjątek dla atrybutów pierwszych!) +- **BCNF:** Dla FD X → A: X **MUSI** być nadkluczem. Kropka. (Bez wyjątków!) +- Dlatego BCNF jest SILNIEJSZA — eliminuje anomalie, które 3NF dopuszcza. + +**Kiedy 3NF ≠ BCNF?** Dokładnie wtedy, gdy istnieje nietrywialna FD, w której lewa strona nie jest nadkluczem, ale prawa strona jest atrybutem pierwszym. To się zdarza tylko przy kluczach złożonych z nakładającymi się zależnościami. + +**Naprawa:** Rozbij Zapisy na dwie tabele wg FD Prowadzący → KursID. + +![Tabele po normalizacji do BCNF](img/nf_bcnf_tables.png) + +**Wynik:** ProwadzącyKurs (Prowadzący → KursID), StudentProwadzący (StID, Prowadzący). + +**Rekonstrukcja:** StudentProwadzący ⨝ ProwadzącyKurs ON Prowadzący = oryginalne Zapisy (bezstratnie!). + +**Uwaga:** Dekompozycja BCNF może utracić pewne FD (tu: (StID, KursID) → Prowadzący nie jest wymuszona przez żadną z dwóch tabel z osobna). To cena za eliminację anomalii — czasem 3NF jest „wystarczająco dobra" w praktyce. + +--- + +### KROK 5: BCNF → 4NF (brak wielowartościowych zależności) + +**Nowy scenariusz:** Chcemy przechowywać hobby i umiejętności studentów. Tworzymy tabelę: +StudentAktywności(StID, Hobby, Umiejętność) — klucz: (StID, Hobby, Umiejętność). + +**Problem:** Student 1 (Anna) ma 2 hobby (Szachy, Bieganie) i 2 umiejętności (Python, SQL). Te zbiory są NIEZALEŻNE od siebie, ale w jednej tabeli tworzą **iloczyn kartezjański** → 2 × 2 = 4 wiersze! + +**MVD:** StID →→ Hobby i StID →→ Umiejętność — dwie niezależne wielowartościowe zależności. + +**Reguła 4NF:** BCNF + dla każdej nietrywialnej MVD X →→ Y, X jest nadkluczem. Niezależne zbiory wartości nie mogą być w jednej tabeli. + +**Naprawa:** Rozdziel do: StudentHobby(StID, Hobby) + StudentUmiejętność(StID, Umiejętność). + +![Przykład naruszenia i naprawy 4NF](img/nf_4nf_example.png) + +**Dlaczego to ważne:** Bez 4NF: dodanie nowego hobby Anny wymaga dodania TYLE wierszy, ile ma umiejętności (i odwrotnie). Z 4NF: dodanie hobby = 1 wiersz. + +**Jak rozpoznać naruszenie 4NF:** +1. Czy tabela ma klucz złożony z 3+ kolumn? (StID, Hobby, Umiejętność) +2. Czy istnieją dwa niezależne zbiory wartości dla tego samego klucza? (Hobby niezależne od Umiejętności) +3. Czy widać „iloczyn kartezjański" w danych? (każde hobby × każda umiejętność) +→ Jeśli TAK na wszystkie → naruszenie 4NF. + +--- + +### KROK 6: 4NF → 5NF (brak zależności złączenia) + +**Nowy scenariusz:** Tabela Dostawy(Dostawca, Część, Projekt) — rejestr kto dostarcza co do którego projektu. Klucz: (Dostawca, Część, Projekt) — cała krotka. + +**Problem:** Tabela jest w 4NF (brak nietrywialnych MVD), ale może zawierać ukrytą redundancję wynikającą z **zależności złączenia** (JD — Join Dependency). + +**Reguła cykliczna:** Jeśli zachodzi ograniczenie biznesowe: +- Dostawca dostarcza Część, I +- Dostawca dostarcza do Projektu, I +- Część jest używana w Projekcie +- → TO Dostawca dostarcza tę Część do tego Projektu + +...to tabela Dostawy jest redundantna — można ją bezstratnie rozłożyć na TRZY tabele binarne. + +**Reguła 5NF (PJNF — Project-Join Normal Form):** Każda zależność złączenia jest implikowana przez klucze kandydujące. Innymi słowy: tabela NIE DA SIĘ dalej rozłożyć bezstratnie (bez utraty informacji) na mniejsze tabele. + +**Naprawa:** Dekomponuj na trzy tabele: DostawcaCzęść, DostawcaProjekt, CzęśćProjekt. + +![Przykład naruszenia i naprawy 5NF](img/nf_5nf_example.png) + +**Rekonstrukcja:** DostawcaCzęść ⨝ DostawcaProjekt ⨝ CzęśćProjekt = oryginalna tabela Dostawy. + +**UWAGA:** Dekompozycja 5NF jest poprawna TYLKO jeśli reguła cykliczna rzeczywiście zachodzi w domenie biznesowej! Jeśli nie zachodzi, JOIN wygeneruje fałszywe krotki (spurious tuples). + +**Kiedy 5NF ma znaczenie praktyczne?** +- Rzadko w typowych aplikacjach (większość zatrzymuje się na 3NF/BCNF) +- Głównie w złożonych relacjach ternary/n-ary z ograniczeniami cyklicznymi +- Np. systemy logistyczne, harmonogramowanie, konfiguracje produktów + +**Jak rozpoznać naruszenie 5NF:** +1. Tabela ma klucz = cała krotka (brak atrybutów nie-kluczowych) +2. Tabela jest w 4NF (brak MVD) +3. ALE da się ją rozłożyć na 3+ mniejszych tabel i bezstratnie złożyć JOINem +4. To rozkładalność wynika z ograniczenia biznesowego (reguły cyklicznej), nie z MVD + +--- + +### Podsumowanie normalizacji 0NF → 5NF + +![Schemat postaci normalnych — przejście od 0NF do 5NF](img/nf_summary_flow.png) + +| Postać | Co eliminuje | Kluczowa reguła | Typ zależności | +|--------|-------------|-----------------|----------------| +| **1NF** | Nieatomowe wartości | Każda komórka = 1 wartość, jest klucz | — | +| **2NF** | Częściowe zależności | Atrybut wtórny zależy od CAŁEGO klucza | FD częściowa | +| **3NF** | Zależności przechodnie | Atrybut wtórny nie zależy od nie-klucza (z wyjątkiem prime) | FD przechodnia | +| **BCNF** | Determinanty nie-nadkluczowe | Lewa strona KAŻDEJ FD = nadklucz (bez wyjątków) | FD nietrywialna | +| **4NF** | Wielowartościowe zależności | Lewa strona każdej MVD = nadklucz | MVD | +| **5NF** | Zależności złączenia | Każda JD implikowana przez klucze | JD | ### Denormalizacja @@ -527,13 +931,18 @@ Prowadzi do trzech **anomalii**: ### Etymologia -**Redundancja** — łac. „redundantia" = nadmiar/przelewanie się. **Normalizacja** — Edgar F. Codd (IBM, 1970, „A Relational Model of Data"); 1NF–3NF w oryginalnej pracy. **BCNF** — Raymond Boyce + Codd (1974). **Anomalia** — grec. „anomalia" = nieregularność. **„Klucz, cały klucz i tylko klucz"** — parafraza przysięgi sądowej; przypisywana Coddowi. **Zależność funkcyjna** — jak funkcja mat.: X jednoznacznie wyznacza Y. +**Redundancja** — łac. „redundantia" = nadmiar/przelewanie się. **Normalizacja** — Edgar F. Codd (IBM, 1970, „A Relational Model of Data"); 1NF–3NF w oryginalnej pracy. **BCNF** — Raymond Boyce + Codd (1974). **4NF** — Ronald Fagin (1977). **5NF (PJNF)** — Ronald Fagin (1979); PJNF = Project-Join Normal Form. **Anomalia** — grec. „anomalia" = nieregularność. **„Klucz, cały klucz i tylko klucz"** — parafraza przysięgi sądowej; przypisywana Coddowi. **Zależność funkcyjna** — jak funkcja mat.: X jednoznacznie wyznacza Y. **MVD** — Multi-Valued Dependency; Fagin udowodnił, że 4NF eliminuje redundancje z MVD. **JD** — Join Dependency; Fagin udowodnił, że 5NF jest „ostateczną" postacią normalną dla relacyjnych baz danych. ### Jak zapamiętać - **„Klucz, cały klucz i tylko klucz — tak mi dopomóż Codd"** — 1NF (klucz), 2NF (cały klucz), 3NF (tylko klucz) - **3 anomalie:** Wstawianie, Usuwanie, Modyfikacja — „WUM" +- **3NF vs BCNF:** 3NF pozwala determinantowi nie-nadkluczowemu JEŚLI zależny jest prime; BCNF nie pozwala w ogóle - **BCNF:** jak 3NF, ale lewa strona FD zawsze nadklucz (bez wyjątku dla atrybutów pierwszych) +- **4NF:** Czy widzisz iloczyn kartezjański? Niezależne zbiory w jednej tabeli? → rozdziel! +- **5NF:** Czy tabela „rozpadalna" na 3+ części bezstratnie? Reguła cykliczna? → dekomponuj! +- **Hierarchia typów zależności:** FD (jedna wartość) → MVD (zbiór wartości) → JD (złączenie n tabel) +- **Praktyka:** 90% systemów normalizuje do 3NF/BCNF. 4NF/5NF = egzotyka, ale egzamin wymaga \newpage @@ -1646,17 +2055,309 @@ Przełączanie kontekstu (benchmarki): | Awaria | Nie zabija innych | Może zabić cały proces| | Zastosowanie | Izolacja, bezpieczeństwo | Wydajność, współdzielenie | -### Komunikacja międzyprocesowa (IPC) -Pipe, Named Pipe (FIFO), Message Queue, Shared Memory, Sockets, Signals, Memory-mapped files. +### Problemy komunikacji -### Synchronizacja — problemy -- **Wyścig (race condition)** — wynik zależy od kolejności operacji -- **Sekcja krytyczna** — fragment kodu wymagający wyłącznego dostępu -- **Zakleszczenie (deadlock)** — wzajemne oczekiwanie (warunki Coffmana: mutual exclusion, hold & wait, no preemption, circular wait) -- **Zagłodzenie (starvation)** — wątek nigdy nie dostaje zasobu +Procesy mają **izolowane** przestrzenie adresowe — nie mogą bezpośrednio czytać/pisać wzajemnej pamięci. Komunikacja wymaga pośrednictwa jądra OS (IPC). Wątki mają odwrotny problem: współdzielą pamięć, więc komunikacja jest trywialna, ale wymaga synchronizacji. -### Mechanizmy synchronizacji -Mutex, Semaphore, Monitor, Condition Variable, Spinlock, Read-Write Lock, Barrier. +![Mechanizmy IPC — porównanie wizualny](img/ipc_mechanisms.png) + +**Problem 1 — Overhead kopiowania (procesy).** Większość mechanizmów IPC wymaga kopiowania danych: proces A → jądro → proces B (2 kopie!). Przy dużych danych (np. klatki wideo 4K = 24 MB) to kosztowne. + +**Problem 2 — Synchronizacja dostępu (wątki).** Wątki komunikują się przez wspólny heap, ale muszą pilnować, by nie pisać jednocześnie w to samo miejsce (race condition). + +**Problem 3 — Blokowanie.** IPC może być synchroniczne (blokujące — nadawca czeka aż odbiorca przeczyta) lub asynchroniczne (nieblokujące — nadawca idzie dalej). Wybór wpływa na wydajność i złożoność kodu. + +#### Mechanizmy IPC z przykładami + +**Pipe (potok anonimowy)** — jednokierunkowy strumień bajtów w pamięci jądra. Tylko między procesem-rodzicem a potomkiem (fork). Klasyczny przykład Unix: + + $ ls -la | grep ".txt" | wc -l + + Jak to działa wewnętrznie: + ┌────────┐ write() ┌─────────────┐ read() ┌────────┐ + │ ls │──────────→│ bufor jądra │──────────→│ grep │ + │ stdout │ fd[1] │ (4 KB) │ fd[0] │ stdin │ + └────────┘ └─────────────┘ └────────┘ + Proces A pisze do fd[1], Proces B czyta z fd[0]. + Jądro buforuje dane. Gdy bufor pełny → write() blokuje. + + Kod C: + int fd[2]; + pipe(fd); // tworzy potok: fd[0]=read, fd[1]=write + if (fork() == 0) { // potomek + close(fd[1]); // zamknij pisanie + read(fd[0], buf, n); // czytaj od rodzica + } else { // rodzic + close(fd[0]); // zamknij czytanie + write(fd[1], "hello", 5); // pisz do potomka + } + +**Named Pipe (FIFO)** — jak pipe, ale ma nazwę w systemie plików. Niespokrewnione procesy mogą go używać: + + $ mkfifo /tmp/moj_potok # stwórz FIFO + $ echo "dane" > /tmp/moj_potok & # proces A pisze + $ cat /tmp/moj_potok # proces B czyta → "dane" + +**Shared Memory (pamięć współdzielona)** — najszybszy IPC. OS mapuje ten sam region pamięci fizycznej do obu procesów. Zero kopiowania — oba procesy czytają/piszą bezpośrednio. ALE: wymaga synchronizacji (semafor/mutex). + + ┌───────────┐ ┌───────────┐ + │ Proces A │ │ Proces B │ + │ │ │ │ + │ strona 7 ─┼──→ RAM ←─┼─ strona 3 │ ← ta sama ramka fizyczna! + │ │ ramka 42 │ │ + └───────────┘ └───────────┘ + Bez kopiowania — A pisze, B widzi od razu. + + Kod C (POSIX): + int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0666); + ftruncate(fd, 4096); + char *ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + sprintf(ptr, "dane z procesu A"); // Proces A pisze + // Proces B: shm_open + mmap → czyta ptr → "dane z procesu A" + +**Message Queue (kolejka wiadomości)** — strukturalne wiadomości w jądrze. Asynchroniczna: nadawca wrzuca, odbiorca pobiera z kolejki kiedy chce. Typ wiadomości pozwala filtrować. + + Proces A: msgsnd(qid, &msg, size, 0) // wyślij wiadomość + Proces B: msgrcv(qid, &msg, size, typ, 0) // odbierz (filtruj typ) + + Przewaga nad pipe: wiele nadawców/odbiorców, filtrowanie typów, + wiadomość ma granice (pipe to surowy strumień bajtów). + +**Socket** — dwukierunkowa komunikacja, działa lokalnie (Unix domain) i przez sieć (TCP/UDP). Najbardziej uniwersalny mechanizm IPC. + + ┌──────────┐ TCP/IP ┌──────────┐ + │ Klient │←────────→│ Serwer │ sieciowy (różne maszyny) + └──────────┘ └──────────┘ + + ┌──────────┐ Unix ┌──────────┐ + │ Proces A │←────────→│ Proces B │ lokalny (ten sam host) + └──────────┘ socket └──────────┘ /tmp/app.sock + +**Signal (sygnał)** — asynchroniczne powiadomienie od jądra/procesu. Przesyła TYLKO numer (nie dane). Użycie: obsługa Ctrl+C (SIGINT), zabijanie procesów (SIGKILL), powiadomienie o zdarzeniu. + + kill(pid, SIGUSR1); // wyślij sygnał SIGUSR1 do procesu + $ kill -9 1234 // wyślij SIGKILL (nie do przechwycenia) + signal(SIGINT, handler); // zarejestruj handler dla Ctrl+C + +#### Porównanie mechanizmów IPC + + Mechanizm Kierunek Szybkość Zastosowanie + ────────────────────────────────────────────────────────── + Pipe jednokier. średnia ls | grep + Named Pipe jednokier. średnia demon → klient + Shared Memory dwukier. najszybsza video, bazy danych + Message Queue dwukier. średnia wieloproducentowe + Socket dwukier. wolna (sieć) klient-serwer + Signal jednokier. natychmiast. powiadomienia + +--- + +### Problemy synchronizacji + +Gdy wątki (lub procesy z shared memory) współdzielą dane, pojawiają się 4 fundamentalne problemy: + +![Ilustracja zakleszczenia — cykl oczekiwania](img/deadlock_illustration.png) + +#### Problem 1 — Wyścig (Race Condition) + +Wynik programu zależy od losowej kolejności operacji wątków. Źródło: operacja „czytaj-modyfikuj-zapisz" nie jest atomowa. + + Przykład: konto bankowe, saldo = 1000 zł + Wątek A: wpłata 500 zł Wątek B: wypłata 200 zł + + BEZ synchronizacji (błąd!): + ───────────────────────────────────────────────────────── + Czas Wątek A Wątek B + ───────────────────────────────────────────────────────── + t1 czytaj saldo → 1000 + t2 czytaj saldo → 1000 + t3 saldo = 1000 + 500 = 1500 + t4 saldo = 1000 - 200 = 800 + t5 zapisz saldo ← 1500 + t6 zapisz saldo ← 800 + ───────────────────────────────────────────────────────── + Wynik: 800 zł (powinno być 1300!) ← wypłata nadpisała wpłatę! + + Poprawka — mutex: + lock(mutex); + saldo = saldo + kwota; // sekcja krytyczna + unlock(mutex); + + Z mutex: A blokuje → czyta 1000 → pisze 1500 → B blokuje → czyta 1500 → pisze 1300. ✓ + +#### Problem 2 — Zakleszczenie (Deadlock) + +Dwa lub więcej wątków czekają na siebie nawzajem — żaden nie może kontynuować. System „zamiera". + + Klasyczny scenariusz: 2 wątki, 2 mutexy + ───────────────────────────────────────────────────────── + Wątek A: Wątek B: + lock(mutex1) ✓ ←trzyma lock(mutex2) ✓ ←trzyma + lock(mutex2) ⏳ ←czeka! lock(mutex1) ⏳ ←czeka! + ───────────────────────────────────────────────────────── + A czeka na mutex2 (B go trzyma), B czeka na mutex1 (A go trzyma). + → DEADLOCK — żaden nie odpuści! + + Diagram cyklu: + ┌──────────┐ czeka na ┌──────────┐ + │ Wątek A │───────────→│ Mutex 2 │ + │ trzyma │ │ trzyma │ + │ Mutex 1 │←───────────│ Wątek B │ + └──────────┘ czeka na └──────────┘ + +**Warunki Coffmana** — 4 warunki konieczne deadlocka (WSZYSTKIE muszą zachodzić): + + 1. Mutual Exclusion — zasób wyłączny (tylko 1 wątek) + 2. Hold and Wait — trzymaj zasób, czekaj na kolejny + 3. No Preemption — nie można zabrać zasobu siłą + 4. Circular Wait — cykliczne oczekiwanie (A→B→...→A) + + Strategie zapobiegania (złam jeden warunek): + ──────────────────────────────────────────────────── + Warunek Jak złamać Przykład + ──────────────────────────────────────────────────── + Mutual Exclusion Zrób zasób współdzielony Read-write lock + Hold and Wait Bierz WSZYSTKIE naraz lock(m1, m2) atomowo + No Preemption Pozwól na timeout/trylock pthread_mutex_trylock() + Circular Wait Porządek liniowy zamków Zawsze m1 przed m2 + ──────────────────────────────────────────────────── + + Najczęstsza strategia: PORZĄDEK LINIOWY (Circular Wait). + Zasada: numeruj mutexy, zawsze blokuj w rosnącej kolejności. + Jeśli mutex1 < mutex2 → ZAWSZE lock(mutex1) przed lock(mutex2). + +#### Problem 3 — Zagłodzenie (Starvation) + +Wątek nigdy nie dostaje zasobu, bo inni ciągle go wyprzedzają. Nie jest deadlockiem (inni się wykonują). Przykład: 10 wątków, wątek niskopriorytetowy nigdy nie dostaje CPU bo wysoko priorytetowe ciągle dominują. + + Rozwiązanie: aging (starzenie) — priorytet rośnie z czasem oczekiwania. + Po 100 ms bez CPU: priorytet +1, po 200 ms: +2, itd. + W końcu nawet najniższy wątek dostanie CPU. + +#### Problem 4 — Inwersja priorytetów (Priority Inversion) + +Wątek wysokopriorytetowy (H) czeka na mutex trzymany przez niskopriorytetowy (L), a średniopriorytetowy (M) blokuje L. Efekt: H czeka na M (mimo wyższego priorytetu!). + + Priorytet: H > M > L + ───────────────────────────────────────────────── + Czas L M H + ───────────────────────────────────────────────── + t1 lock(mutex) + t2 (gotowy, wypycha L!) + t3 pracuje... (czeka na mutex!) + t4 pracuje... (CZEKA — bo M blokuje L) + t5 gotowy + t6 unlock(mutex) (wreszcie!) + ───────────────────────────────────────────────── + H czekał, dopóki M nie skończył, mimo że H > M! + + Rozwiązanie: Priority Inheritance Protocol. + L dziedziczy priorytet H (tymczasowo L=H), więc M nie może wypchać L. + Mars Pathfinder (1997) — klasyczny bug priority inversion w kosmosie! + +--- + +### Klasyczne problemy synchronizacji + +![Producent-konsument z buforem cyklicznym](img/producer_consumer.png) + +#### Producent-Konsument (Bounded Buffer) + +n producentów wrzuca elementy do bufora o ograniczonej pojemności, m konsumentów pobiera. Bufor pełny → producent czeka. Bufor pusty → konsument czeka. + + Rozwiązanie z semaforami: + ───────────────────────────────────────────────── + semaphore mutex = 1; // wyłączny dostęp do bufora + semaphore empty = N; // ile wolnych slotów (początkowo N) + semaphore full = 0; // ile pełnych slotów (początkowo 0) + + Producent: Konsument: + P(empty) // czekaj na P(full) // czekaj na + // wolny slot // pełny slot + P(mutex) // wejdź do P(mutex) // wejdź do + // sek. kryt. // sek. kryt. + wstaw(elem) elem = pobierz() + V(mutex) // wyjdź V(mutex) // wyjdź + V(full) // +1 pełny V(empty) // +1 wolny + ───────────────────────────────────────────────── + + Bufor (N=4): + ┌────┬────┬────┬────┐ + │ A │ B │ │ │ ← full=2, empty=2 + └────┴────┴────┴────┘ + ↑ ↑ + konsument producent + + BŁĄD jeśli zamienimy kolejność P(empty) i P(mutex) w producencie: + Producent: P(mutex) → P(empty) ← bufor pełny → czeka z mutexem! + Konsument: P(full) → P(mutex) ← mutex zajęty → DEADLOCK! + +#### Czytelnicy-Pisarze (Readers-Writers) + +Wielu czytelników może czytać jednocześnie. Pisarz wymaga wyłącznego dostępu (ani czytelnicy, ani inni pisarze). + + Rozwiązanie (first readers-writers): + ───────────────────────────────────────────────── + int readers = 0; + mutex rw_mutex; // pisarz LUB pierwszy/ostatni czytelnik + mutex count_mutex; // ochrona zmiennej readers + + Czytelnik: Pisarz: + lock(count_mutex) lock(rw_mutex) + readers++ // PISZ (wyłączny) + if (readers == 1) unlock(rw_mutex) + lock(rw_mutex) // 1. czytelnik blokuje pisarzy + unlock(count_mutex) + // CZYTAJ (wielu jednocześnie!) + lock(count_mutex) + readers-- + if (readers == 0) + unlock(rw_mutex) // ostatni odblokowuje pisarzy + unlock(count_mutex) + ───────────────────────────────────────────────── + + Problem: pisarze mogą głodować (readers=0 nigdy nie zachodzi). + Rozwiązanie: fairness — kolejka FIFO czytelników i pisarzy. + +#### Ucztujący filozofowie (Dining Philosophers) + +5 filozofów siedzi przy okrągłym stole, między każdą parą 1 widelec. Filozofowie myślą lub jedzą. Jedzenie wymaga 2 widelców (lewego i prawego). Naiwne rozwiązanie: każdy bierze lewy → prawy → deadlock (wszyscy trzymają lewy, czekają na prawy). + + Rozwiązanie — złamanie cyklu: + Filozofowie 0-3: bierz lewy, potem prawy. + Filozof 4: bierz PRAWY, potem lewy. ← łamie circular wait! + + Alternatywa: semafor(4) — max 4 filozofów próbuje jeść naraz + → jeden widelec zawsze wolny → brak deadlocka. + +--- + +### Mechanizmy synchronizacji — porównanie + + Mechanizm Opis Kiedy używać + ────────────────────────────────────────────────────────────────── + Mutex Zamek: 1 wątek w sekcji Sekcja krytyczna + Semafor(n) Licznik: max n wątków Ograniczone zasoby + Monitor Obiekt z wbudowanym mutex Java synchronized + Cond. Variable wait()/signal() na warunek Producent-konsument + Spinlock Aktywne czekanie (busy-wait) Bardzo krótkie sekcje + RW Lock Wielu czytelników LUB 1 pisarz Bazy danych, cache + Barrier Czekaj aż wszyscy dotrą Obliczenia równoległe + + Mutex vs Semafor: + ┌────────────────────────────────────────────────────────────┐ + │ Mutex = klucz do łazienki (1 osoba) │ + │ Semafor(3) = parking na 3 miejsca (3 samochody naraz) │ + │ Semafor(1) = mutex (szczególny przypadek) │ + └────────────────────────────────────────────────────────────┘ + + Mutex vs Spinlock: + ┌────────────────────────────────────────────────────────────┐ + │ Mutex: wątek ZASYPIA gdy czeka → OS go obudzi (koszt ~μs)│ + │ Spinlock: wątek KRĘCI się w pętli → marnuje CPU │ + │ Spinlock lepszy gdy sekcja < 1 μs (koszt uśpienia > spin)│ + │ Mutex lepszy gdy sekcja > 1 μs (nie marnuje CPU) │ + └────────────────────────────────────────────────────────────┘ ### Etymologia diff --git a/pytania/generate_automata_diagrams.py b/pytania/generate_automata_diagrams.py new file mode 100644 index 0000000..ea55a28 --- /dev/null +++ b/pytania/generate_automata_diagrams.py @@ -0,0 +1,519 @@ +#!/usr/bin/env python3 +""" +Generate diagrams for PYTANIE 1: Automata and language classes. + 1. FA recognition example — DFA for strings ending in "ab" + 2. PDA recognition example — PDA for aⁿbⁿ + 3. LBA recognition example — LBA for aⁿbⁿcⁿ + 4. TM recognition example — TM for 0ⁿ1ⁿ + +All: A4-compatible, B&W, 300 DPI, laser-printer-friendly. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch, FancyArrowPatch, Circle +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +FS_SMALL = 6.5 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' +LIGHT_GREEN = '#D5E8D4' +LIGHT_RED = '#F8D7DA' +LIGHT_BLUE = '#D6EAF8' +LIGHT_YELLOW = '#FFF9C4' + + +def draw_state_circle(ax, x, y, r, label, accepting=False, initial=False, + fillcolor='white', fontsize=FS): + """Draw an automaton state circle.""" + circle = plt.Circle((x, y), r, fill=True, facecolor=fillcolor, + edgecolor=LN, linewidth=1.5, zorder=3) + ax.add_patch(circle) + if accepting: + inner = plt.Circle((x, y), r * 0.82, fill=False, + edgecolor=LN, linewidth=1.2, zorder=3) + ax.add_patch(inner) + if initial: + ax.annotate("", xy=(x - r, y), xytext=(x - r - 0.4, y), + arrowprops=dict(arrowstyle='->', color=LN, lw=1.5), + zorder=4) + ax.text(x, y, label, ha='center', va='center', fontsize=fontsize, + fontweight='bold', zorder=5) + + +def draw_curved_arrow(ax, x1, y1, x2, y2, label, r=0.25, + connectionstyle="arc3,rad=0.3", fontsize=FS_SMALL, + label_offset=(0, 0)): + """Draw a curved arrow between points with label.""" + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle='->', color=LN, lw=1.2, + connectionstyle=connectionstyle), + zorder=2) + mx = (x1 + x2) / 2 + label_offset[0] + my = (y1 + y2) / 2 + label_offset[1] + ax.text(mx, my, label, ha='center', va='center', fontsize=fontsize, + fontstyle='italic', zorder=5, + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', + edgecolor='none', alpha=0.9)) + + +def draw_self_loop(ax, x, y, r, label, direction='top', fontsize=FS_SMALL): + """Draw a self-loop on a state.""" + if direction == 'top': + loop = mpatches.FancyArrowPatch( + (x - 0.12, y + r), (x + 0.12, y + r), + connectionstyle="arc3,rad=-1.8", + arrowstyle='->', mutation_scale=12, lw=1.2, color=LN, zorder=2) + ax.add_patch(loop) + ax.text(x, y + r + 0.35, label, ha='center', va='center', + fontsize=fontsize, fontstyle='italic', zorder=5) + elif direction == 'bottom': + loop = mpatches.FancyArrowPatch( + (x - 0.12, y - r), (x + 0.12, y - r), + connectionstyle="arc3,rad=1.8", + arrowstyle='->', mutation_scale=12, lw=1.2, color=LN, zorder=2) + ax.add_patch(loop) + ax.text(x, y - r - 0.35, label, ha='center', va='center', + fontsize=fontsize, fontstyle='italic', zorder=5) + + +# ============================================================ +# 1. FA Recognition Example — DFA for strings ending in "ab" +# ============================================================ +def draw_fa_recognition(): + """FA state diagram + step-by-step trace for 'baab'.""" + fig, axes = plt.subplots(1, 2, figsize=(11.69, 4), + gridspec_kw={'width_ratios': [1, 1.3]}) + + # --- Left: State diagram --- + ax = axes[0] + ax.set_xlim(-1, 5.5) + ax.set_ylim(-1.5, 2.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('DFA — diagram stanów\nL = {słowa nad {a,b} kończące się na "ab"}', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + R = 0.35 + # States positions + states = {'q₀': (0.8, 0.5), 'q₁': (2.8, 0.5), 'q₂': (4.8, 0.5)} + + draw_state_circle(ax, *states['q₀'], R, 'q₀', initial=True) + draw_state_circle(ax, *states['q₁'], R, 'q₁') + draw_state_circle(ax, *states['q₂'], R, 'q₂', accepting=True, + fillcolor=LIGHT_GREEN) + + # Transitions + # q₀ --a--> q₁ + draw_curved_arrow(ax, states['q₀'][0] + R, states['q₀'][1] + 0.05, + states['q₁'][0] - R, states['q₁'][1] + 0.05, + 'a', connectionstyle="arc3,rad=0.15", + label_offset=(0, 0.25)) + # q₁ --b--> q₂ + draw_curved_arrow(ax, states['q₁'][0] + R, states['q₁'][1] + 0.05, + states['q₂'][0] - R, states['q₂'][1] + 0.05, + 'b', connectionstyle="arc3,rad=0.15", + label_offset=(0, 0.25)) + # q₂ --a--> q₁ + draw_curved_arrow(ax, states['q₂'][0] - R, states['q₂'][1] - 0.05, + states['q₁'][0] + R, states['q₁'][1] - 0.05, + 'a', connectionstyle="arc3,rad=0.15", + label_offset=(0, -0.3)) + # q₂ --b--> q₀ + draw_curved_arrow(ax, states['q₂'][0] - 0.2, states['q₂'][1] - R, + states['q₀'][0] + 0.2, states['q₀'][1] - R, + 'b', connectionstyle="arc3,rad=0.4", + label_offset=(0, -0.4)) + # q₀ --b--> q₀ (self-loop) + draw_self_loop(ax, *states['q₀'], R, 'b', direction='top') + # q₁ --a--> q₁ (self-loop) + draw_self_loop(ax, *states['q₁'], R, 'a', direction='top') + + # Legend + ax.text(0.3, -1.0, '→ = start ◎ = akceptujący', + fontsize=FS_SMALL, ha='left', va='center', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + # --- Right: Step-by-step trace --- + ax2 = axes[1] + ax2.axis('off') + ax2.set_title('Ślad wykonania — wejście: "baab"', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + trace_data = [ + ['Krok', 'Czytam', 'Stan przed', 'Przejście', 'Stan po'], + ['—', '—', 'q₀ (start)', '—', 'q₀'], + ['1', 'b', 'q₀', 'δ(q₀, b) = q₀', 'q₀'], + ['2', 'a', 'q₀', 'δ(q₀, a) = q₁', 'q₁'], + ['3', 'a', 'q₁', 'δ(q₁, a) = q₁', 'q₁'], + ['4', 'b', 'q₁', 'δ(q₁, b) = q₂', 'q₂ ✓'], + ] + + colors = [GRAY2] + ['white'] * 4 + [LIGHT_GREEN] + table = ax2.table(cellText=trace_data, cellLoc='center', + loc='center', bbox=[0.05, 0.15, 0.9, 0.75]) + table.auto_set_font_size(False) + table.set_fontsize(FS) + for (row, col), cell in table.get_celld().items(): + cell.set_edgecolor(GRAY3) + if row == 0: + cell.set_facecolor(GRAY2) + cell.set_text_props(fontweight='bold') + else: + cell.set_facecolor(colors[row]) + cell.set_height(0.12) + + ax2.text(0.5, 0.05, 'Wynik: q₂ ∈ F → "baab" AKCEPTOWANE ✓', + ha='center', va='center', fontsize=FS + 1, fontweight='bold', + transform=ax2.transAxes, + bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN, + edgecolor=LN)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'fa_recognition_example.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ fa_recognition_example.png") + + +# ============================================================ +# 2. PDA Recognition Example — PDA for aⁿbⁿ +# ============================================================ +def draw_pda_recognition(): + """PDA state diagram + step-by-step trace with stack visualization for 'aabb'.""" + fig, axes = plt.subplots(1, 2, figsize=(11.69, 5.5), + gridspec_kw={'width_ratios': [1, 1.4]}) + + # --- Left: State diagram --- + ax = axes[0] + ax.set_xlim(-1, 5.5) + ax.set_ylim(-2, 3) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('PDA — diagram stanów\nL = {aⁿbⁿ | n ≥ 1}', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + R = 0.38 + states = {'q₀': (0.8, 0.5), 'q₁': (2.8, 0.5), 'q₂': (4.8, 0.5)} + + draw_state_circle(ax, *states['q₀'], R, 'q₀', initial=True) + draw_state_circle(ax, *states['q₁'], R, 'q₁') + draw_state_circle(ax, *states['q₂'], R, 'q₂', accepting=True, + fillcolor=LIGHT_GREEN) + + # q₀ --b,A/ε--> q₁ + draw_curved_arrow(ax, states['q₀'][0] + R, states['q₀'][1], + states['q₁'][0] - R, states['q₁'][1], + 'b, A → ε\n(pop A)', connectionstyle="arc3,rad=0.0", + label_offset=(0, 0.4)) + # q₁ --ε,Z₀/Z₀--> q₂ + draw_curved_arrow(ax, states['q₁'][0] + R, states['q₁'][1], + states['q₂'][0] - R, states['q₂'][1], + 'ε, Z₀ → Z₀\n(akceptuj)', connectionstyle="arc3,rad=0.0", + label_offset=(0, 0.45)) + # q₀ self-loop: a, Z₀/AZ₀ and a, A/AA + draw_self_loop(ax, *states['q₀'], R, 'a, Z₀ → AZ₀\na, A → AA\n(push A)', + direction='top') + # q₁ self-loop: b, A/ε + draw_self_loop(ax, *states['q₁'], R, 'b, A → ε\n(pop A)', direction='top') + + # Key explanation + ax.text(0.3, -1.3, 'Notacja: symbol_wejścia, szczyt_stosu → nowy_szczyt\n' + 'ε = brak symbolu (przejście spontaniczne lub pusty stos)', + fontsize=FS_SMALL, ha='left', va='center', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + # --- Right: Step trace with stack --- + ax2 = axes[1] + ax2.axis('off') + ax2.set_title('Ślad wykonania z wizualizacją stosu — wejście: "aabb"', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + trace_data = [ + ['Krok', 'Czytam', 'Stan', 'Stos (szczyt→)', 'Operacja'], + ['start', '—', 'q₀', '[Z₀]', '—'], + ['1', 'a', 'q₀', '[A, Z₀]', 'push A'], + ['2', 'a', 'q₀', '[A, A, Z₀]', 'push A'], + ['3', 'b', 'q₁', '[A, Z₀]', 'pop A'], + ['4', 'b', 'q₁', '[Z₀]', 'pop A'], + ['5', 'ε', 'q₂', '[Z₀]', 'akceptuj!'], + ] + + colors = [GRAY2, 'white', LIGHT_BLUE, LIGHT_BLUE, LIGHT_YELLOW, LIGHT_YELLOW, LIGHT_GREEN] + table = ax2.table(cellText=trace_data, cellLoc='center', + loc='center', bbox=[0.02, 0.08, 0.96, 0.82]) + table.auto_set_font_size(False) + table.set_fontsize(FS) + for (row, col), cell in table.get_celld().items(): + cell.set_edgecolor(GRAY3) + if row == 0: + cell.set_facecolor(GRAY2) + cell.set_text_props(fontweight='bold') + else: + cell.set_facecolor(colors[row]) + cell.set_height(0.11) + + ax2.text(0.5, 0.0, 'Wynik: q₂ ∈ F, stos=[Z₀] → "aabb" AKCEPTOWANE ✓\n' + 'Intuicja: 2× push A (za "aa") + 2× pop A (za "bb") = stos pusty = OK', + ha='center', va='center', fontsize=FS, fontweight='bold', + transform=ax2.transAxes, + bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN, + edgecolor=LN)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'pda_recognition_example.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ pda_recognition_example.png") + + +# ============================================================ +# 3. LBA Recognition Example — LBA for aⁿbⁿcⁿ +# ============================================================ +def draw_lba_recognition(): + """LBA tape visualization showing marking rounds for 'aabbcc'.""" + fig, ax = plt.subplots(1, 1, figsize=(11.69, 6.5)) + ax.set_xlim(-0.5, 12) + ax.set_ylim(-1, 10.5) + ax.axis('off') + ax.set_title('LBA — rozpoznawanie aⁿbⁿcⁿ (n=2)\n' + 'Strategia: w każdej rundzie zaznacz jedno a, b, c', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + CELL_W = 0.9 + CELL_H = 0.7 + TAPE_X0 = 1.5 + HEAD_COLOR = '#FFD700' + + def draw_tape(y, cells, head_pos, label, step_label=""): + """Draw a tape row with cells, head position highlighted.""" + ax.text(0.2, y + CELL_H/2, label, ha='right', va='center', + fontsize=FS, fontweight='bold') + for i, (sym, color) in enumerate(cells): + x = TAPE_X0 + i * CELL_W + fc = HEAD_COLOR if i == head_pos else color + rect = mpatches.FancyBboxPatch((x, y), CELL_W, CELL_H, + boxstyle="round,pad=0.03", + lw=1.2, edgecolor=LN, facecolor=fc) + ax.add_patch(rect) + ax.text(x + CELL_W/2, y + CELL_H/2, sym, + ha='center', va='center', fontsize=FS + 2, + fontweight='bold' if sym in ('X', 'Y', 'Z') else 'normal', + family='monospace') + if head_pos is not None: + hx = TAPE_X0 + head_pos * CELL_W + CELL_W/2 + ax.annotate('▼', xy=(hx, y + CELL_H), xytext=(hx, y + CELL_H + 0.25), + ha='center', va='bottom', fontsize=8, color='black') + if step_label: + sx = TAPE_X0 + 6 * CELL_W + 0.5 + ax.text(sx, y + CELL_H/2, step_label, ha='left', va='center', + fontsize=FS_SMALL, + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, + edgecolor=GRAY3)) + + W = 'white' + MK = GRAY1 # marked cell color + + # Row 1: Initial tape + y = 9.0 + draw_tape(y, [('a', W), ('a', W), ('b', W), ('b', W), ('c', W), ('c', W)], + 0, 'Początek', 'taśma = [a, a, b, b, c, c], głowica na 0') + + # Row 2: After marking first 'a' + y = 7.8 + draw_tape(y, [('X', MK), ('a', W), ('b', W), ('b', W), ('c', W), ('c', W)], + 1, 'R1, krok 1', 'zaznacz a→X, szukaj b') + + # Row 3: After marking first 'b' + y = 6.6 + draw_tape(y, [('X', MK), ('a', W), ('Y', MK), ('b', W), ('c', W), ('c', W)], + 3, 'R1, krok 2', 'zaznacz b→Y, szukaj c') + + # Row 4: After marking first 'c' + y = 5.4 + draw_tape(y, [('X', MK), ('a', W), ('Y', MK), ('b', W), ('Z', MK), ('c', W)], + 0, 'R1, krok 3', 'zaznacz c→Z, wróć na początek') + + # Runda 2 header + y = 4.5 + ax.text(TAPE_X0 + 3 * CELL_W, y + 0.3, '═══ RUNDA 2 ═══', + ha='center', va='center', fontsize=FS, fontweight='bold', + color=LN) + + # Row 5: After marking second 'a' + y = 3.6 + draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('b', W), ('Z', MK), ('c', W)], + 2, 'R2, krok 1', 'pomiń X, zaznacz a→X, szukaj b') + + # Row 6: After marking second 'b' + y = 2.4 + draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('Z', MK), ('c', W)], + 4, 'R2, krok 2', 'pomiń Y, zaznacz b→Y, szukaj c') + + # Row 7: After marking second 'c' + y = 1.2 + draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('Z', MK), ('Z', MK)], + None, 'R2, krok 3', 'zaznacz c→Z, wróć na początek') + + # Result + y = 0.0 + ax.text(TAPE_X0 + 3 * CELL_W, y + 0.3, + 'Wszystko zaznaczone → q_acc → "aabbcc" AKCEPTOWANE ✓', + ha='center', va='center', fontsize=FS + 1, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN, + edgecolor=LN)) + + # Key + ax.text(TAPE_X0 + 6 * CELL_W + 0.5, y + 0.3, + 'Ograniczenie LBA:\ngłowica ≤ 6 komórek\n(= |w| = |"aabbcc"|)', + ha='left', va='center', fontsize=FS_SMALL, + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_YELLOW, + edgecolor=GRAY3)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'lba_recognition_example.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ lba_recognition_example.png") + + +# ============================================================ +# 4. TM Recognition Example — TM for 0ⁿ1ⁿ +# ============================================================ +def draw_tm_recognition(): + """TM tape visualization for 0ⁿ1ⁿ with infinite tape shown.""" + fig, ax = plt.subplots(1, 1, figsize=(11.69, 6.5)) + ax.set_xlim(-0.5, 13) + ax.set_ylim(-1, 10.5) + ax.axis('off') + ax.set_title('TM — rozpoznawanie 0ⁿ1ⁿ (n=2)\n' + 'Strategia: zaznacz jedno 0 i jedno 1 w każdej rundzie', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + CELL_W = 0.9 + CELL_H = 0.7 + TAPE_X0 = 1.5 + HEAD_COLOR = '#FFD700' + + def draw_tape(y, cells, head_pos, label, step_label=""): + ax.text(0.2, y + CELL_H/2, label, ha='right', va='center', + fontsize=FS, fontweight='bold') + for i, (sym, color) in enumerate(cells): + x = TAPE_X0 + i * CELL_W + fc = HEAD_COLOR if i == head_pos else color + lw = 1.2 + ls = '-' + if sym == '⊔': + ls = '--' + rect = mpatches.FancyBboxPatch((x, y), CELL_W, CELL_H, + boxstyle="round,pad=0.03", + lw=lw, edgecolor=LN, facecolor=fc, + linestyle=ls) + ax.add_patch(rect) + ax.text(x + CELL_W/2, y + CELL_H/2, sym, + ha='center', va='center', fontsize=FS + 2, + fontweight='bold' if sym in ('X', 'Y') else 'normal', + family='monospace', + color=GRAY3 if sym == '⊔' else LN) + # ∞ arrow + last_x = TAPE_X0 + len(cells) * CELL_W + ax.annotate('→ ∞', xy=(last_x + 0.3, y + CELL_H/2), + fontsize=FS, ha='left', va='center', color=GRAY3) + if head_pos is not None: + hx = TAPE_X0 + head_pos * CELL_W + CELL_W/2 + ax.annotate('▼', xy=(hx, y + CELL_H), xytext=(hx, y + CELL_H + 0.25), + ha='center', va='bottom', fontsize=8, color='black') + if step_label: + sx = TAPE_X0 + 8 * CELL_W + 0.8 + ax.text(sx, y + CELL_H/2, step_label, ha='left', va='center', + fontsize=FS_SMALL, + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY4, + edgecolor=GRAY3)) + + W = 'white' + MK = GRAY1 + BL = '#F0F0F0' # blank cell + + # Row 1: Initial + y = 9.0 + draw_tape(y, [('0', W), ('0', W), ('1', W), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)], + 0, 'Początek', 'taśma = [0,0,1,1,⊔,⊔,...∞]') + + # Row 2: Mark first 0 + y = 7.8 + draw_tape(y, [('X', MK), ('0', W), ('1', W), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)], + 1, 'R1, krok 1', 'zaznacz 0→X, idź w prawo') + + # Row 3: Skip to first 1, mark it + y = 6.6 + draw_tape(y, [('X', MK), ('0', W), ('Y', MK), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)], + 0, 'R1, krok 2', 'zaznacz 1→Y, wróć na początek') + + # Runda 2 header + y = 5.8 + ax.text(TAPE_X0 + 3.5 * CELL_W, y + 0.3, '═══ RUNDA 2 ═══', + ha='center', va='center', fontsize=FS, fontweight='bold') + + # Row 4: Mark second 0 + y = 4.8 + draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('1', W), ('⊔', BL), ('⊔', BL), ('⊔', BL)], + 2, 'R2, krok 1', 'pomiń X, zaznacz 0→X') + + # Row 5: Mark second 1 + y = 3.6 + draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('⊔', BL), ('⊔', BL), ('⊔', BL)], + 0, 'R2, krok 2', 'pomiń Y, zaznacz 1→Y, wróć') + + # Row 6: Check — all marked + y = 2.4 + draw_tape(y, [('X', MK), ('X', MK), ('Y', MK), ('Y', MK), ('⊔', BL), ('⊔', BL), ('⊔', BL)], + None, 'Sprawdzenie', 'brak niezaznaczonych → q_acc') + + # Result + TM vs LBA comparison + y = 0.8 + ax.text(TAPE_X0 + 3.5 * CELL_W, y + 0.3, + '"0011" AKCEPTOWANE ✓', + ha='center', va='center', fontsize=FS + 1, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_GREEN, + edgecolor=LN)) + + y = -0.3 + ax.text(TAPE_X0 + 3.5 * CELL_W, y + 0.3, + 'Różnica TM vs LBA: taśma TM jest nieskończona (⊔ → ∞)\n' + 'LBA: głowica ograniczona do |w| komórek\n' + 'TM: głowica może wyjść POZA wejście i pisać na pustych ⊔', + ha='center', va='center', fontsize=FS_SMALL, + bbox=dict(boxstyle='round,pad=0.4', facecolor=LIGHT_YELLOW, + edgecolor=GRAY3)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'tm_recognition_example.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ tm_recognition_example.png") + + +# ============================================================ +# Main +# ============================================================ +if __name__ == '__main__': + print("Generating automata diagrams for PYTANIE 1...") + draw_fa_recognition() + draw_pda_recognition() + draw_lba_recognition() + draw_tm_recognition() + print(f"\nAll diagrams saved to {OUTPUT_DIR}/") diff --git a/pytania/generate_normalization_diagrams.py b/pytania/generate_normalization_diagrams.py new file mode 100644 index 0000000..c2d1d24 --- /dev/null +++ b/pytania/generate_normalization_diagrams.py @@ -0,0 +1,711 @@ +#!/usr/bin/env python3 +""" +Generate B&W normalization step diagrams for PYTANIE 3. +Each diagram shows database tables at a specific normalization stage. +Designed for A4 laser printer output (300 DPI, black & white). +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +import numpy as np +import os + +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +# Common settings +DPI = 300 +FONT_SIZE = 8 +HEADER_COLOR = '#D0D0D0' +CELL_COLOR = '#FFFFFF' +HIGHLIGHT_COLOR = '#F0D0D0' # light red-ish gray for violations +FIXED_COLOR = '#D0F0D0' # light green-ish gray for fixed +FD_ARROW_COLOR = '#444444' + + +def draw_table(ax, x, y, title, headers, rows, col_widths=None, + highlight_cols=None, highlight_rows=None, highlight_cells=None, + strikethrough_cells=None, title_fontsize=9): + """Draw a single table on the axes at position (x, y). + + Args: + ax: matplotlib axes + x, y: top-left position + title: table title string + headers: list of column header strings + rows: list of lists (row data) + col_widths: list of column widths (in inches-ish units) + highlight_cols: set of column indices to highlight + highlight_rows: set of row indices to highlight + highlight_cells: set of (row, col) to highlight + strikethrough_cells: set of (row, col) to draw strikethrough + title_fontsize: font size for table title + + Returns: + (width, height) of the drawn table + """ + n_cols = len(headers) + n_rows = len(rows) + + if col_widths is None: + # Auto-calculate based on content + col_widths = [] + for c in range(n_cols): + max_len = len(headers[c]) + for r in rows: + if c < len(r): + max_len = max(max_len, len(str(r[c]))) + col_widths.append(max(max_len * 0.08 + 0.1, 0.5)) + + row_height = 0.22 + total_width = sum(col_widths) + total_height = (n_rows + 1) * row_height # +1 for header + + # Title + ax.text(x + total_width / 2, y + 0.18, title, + fontsize=title_fontsize, fontweight='bold', + ha='center', va='bottom', family='monospace') + + y_start = y + + # Draw header row + cx = x + for c, (hdr, w) in enumerate(zip(headers, col_widths)): + color = HEADER_COLOR + rect = mpatches.FancyBboxPatch( + (cx, y_start), w, -row_height, + boxstyle="square,pad=0", facecolor=color, + edgecolor='black', linewidth=0.5) + ax.add_patch(rect) + ax.text(cx + w / 2, y_start - row_height / 2, hdr, + fontsize=FONT_SIZE, fontweight='bold', + ha='center', va='center', family='monospace') + cx += w + + # Draw data rows + for r_idx, row in enumerate(rows): + cy = y_start - (r_idx + 1) * row_height + cx = x + for c_idx, (val, w) in enumerate(zip(row, col_widths)): + color = CELL_COLOR + if highlight_cols and c_idx in highlight_cols: + color = HIGHLIGHT_COLOR + if highlight_rows and r_idx in highlight_rows: + color = HIGHLIGHT_COLOR + if highlight_cells and (r_idx, c_idx) in highlight_cells: + color = HIGHLIGHT_COLOR + + rect = mpatches.FancyBboxPatch( + (cx, cy), w, -row_height, + boxstyle="square,pad=0", facecolor=color, + edgecolor='black', linewidth=0.5) + ax.add_patch(rect) + + text_color = 'black' + ax.text(cx + w / 2, cy - row_height / 2, str(val), + fontsize=FONT_SIZE, ha='center', va='center', + family='monospace', color=text_color) + + if strikethrough_cells and (r_idx, c_idx) in strikethrough_cells: + ax.plot([cx + 0.03, cx + w - 0.03], + [cy - row_height / 2, cy - row_height / 2], + color='black', linewidth=1.0) + + cx += w + + return total_width, total_height + 0.25 # extra for title + + +def create_figure(width_inches=11.69, height_inches=8.27): + """Create A4 landscape figure.""" + fig, ax = plt.subplots(1, 1, figsize=(width_inches, height_inches), dpi=DPI) + ax.set_xlim(0, width_inches) + ax.set_ylim(0, height_inches) + ax.axis('off') + ax.set_aspect('equal') + return fig, ax + + +def add_arrow(ax, x1, y1, x2, y2, label='', color='black'): + """Draw an arrow with optional label.""" + ax.annotate('', xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle='->', color=color, lw=1.5)) + if label: + mx, my = (x1 + x2) / 2, (y1 + y2) / 2 + ax.text(mx, my + 0.12, label, fontsize=7, ha='center', va='bottom', + family='monospace', color=color) + + +def add_label(ax, x, y, text, fontsize=8, color='black', ha='left', style='normal'): + """Add a text label.""" + ax.text(x, y, text, fontsize=fontsize, ha=ha, va='center', + family='monospace', color=color, style=style) + + +# ============================================================ +# DIAGRAM 1: 0NF Table +# ============================================================ +def draw_0nf(): + fig, ax = create_figure(11.69, 5.5) + + headers = ['StID', 'Imie', 'Telefony', 'KursID', 'NazwaKursu', + 'Prowadzacy', 'WydzialID', 'NazwaWydzialu'] + rows = [ + ['1', 'Anna', '111-222, 333-444', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'], + ['1', 'Anna', '111-222, 333-444', 'K20', 'Algorytmy', 'Nowak', 'W4', 'EiTI'], + ['2', 'Jan', '555-666', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'], + ['3', 'Ewa', '777-888', 'K30', 'Optyka', 'Wisniewski', 'W2', 'Fizyka'], + ] + col_widths = [0.5, 0.55, 1.55, 0.65, 1.1, 1.05, 0.85, 1.2] + + # Highlight the non-atomic column + draw_table(ax, 0.8, 4.5, '0NF: Rejestr (forma nienormalna)', + headers, rows, col_widths, + highlight_cols={2}, # Telefony column + title_fontsize=11) + + # Annotations + add_label(ax, 0.8, 1.9, + 'PROBLEM: Kolumna "Telefony" zawiera LISTY wartosci (nieatomowe).', + fontsize=9, color='black') + add_label(ax, 0.8, 1.55, + 'Redundancja: "Anna", "W4", "EiTI", "Bazy danych" powtorzone wielokrotnie.', + fontsize=9, color='black') + add_label(ax, 0.8, 1.2, + 'Zaleznosci funkcyjne: StID -> Imie, WydzialID | WydzialID -> NazwaWydzialu', + fontsize=8, color='#333333') + add_label(ax, 0.8, 0.9, + ' KursID -> NazwaKursu | (StID,KursID) -> Prowadzacy | Prowadzacy -> KursID', + fontsize=8, color='#333333') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_0nf_table.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_0nf_table.png") + + +# ============================================================ +# DIAGRAM 2: 1NF — atomic values +# ============================================================ +def draw_1nf(): + fig, ax = create_figure(11.69, 6.0) + + # Main table after removing Telefony + headers1 = ['StID*', 'Imie', 'KursID*', 'NazwaKursu', + 'Prowadzacy', 'WydzialID', 'NazwaWydzialu'] + rows1 = [ + ['1', 'Anna', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'], + ['1', 'Anna', 'K20', 'Algorytmy', 'Nowak', 'W4', 'EiTI'], + ['2', 'Jan', 'K10', 'Bazy danych', 'Kowalski', 'W4', 'EiTI'], + ['3', 'Ewa', 'K30', 'Optyka', 'Wisniewski', 'W2', 'Fizyka'], + ] + cw1 = [0.55, 0.55, 0.7, 1.1, 1.05, 0.85, 1.2] + + draw_table(ax, 0.5, 5.2, '1NF: Rejestr (klucz: StID, KursID)', + headers1, rows1, cw1, title_fontsize=10) + + # Telefony table + headers2 = ['StID*', 'Telefon*'] + rows2 = [ + ['1', '111-222'], + ['1', '333-444'], + ['2', '555-666'], + ['3', '777-888'], + ] + cw2 = [0.55, 0.85] + + draw_table(ax, 7.5, 5.2, 'Telefony (klucz: StID, Telefon)', + headers2, rows2, cw2, title_fontsize=10) + + # Arrow + add_arrow(ax, 6.6, 4.3, 7.4, 4.3, 'wydzielono', '#333333') + + # Annotations + add_label(ax, 0.5, 2.6, + 'KROK: Nieatomowa kolumna "Telefony" wydzielona do osobnej tabeli.', + fontsize=9) + add_label(ax, 0.5, 2.25, + 'Kazda komorka zawiera JEDNA wartosc. Klucz glowny wyznaczony.', + fontsize=9) + add_label(ax, 0.5, 1.85, + 'PROBLEM 2NF: NazwaKursu zalezy TYLKO od KursID (czesc klucza).', + fontsize=9, color='black') + add_label(ax, 0.5, 1.5, + ' Imie, WydzialID, NazwaWydzialu zaleza TYLKO od StID (czesc klucza).', + fontsize=9, color='black') + add_label(ax, 0.5, 1.15, + ' --> Czesciowe zaleznosci od klucza zlozonego = NARUSZENIE 2NF.', + fontsize=9, color='black') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_1nf_tables.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_1nf_tables.png") + + +# ============================================================ +# DIAGRAM 3: 2NF — no partial dependencies +# ============================================================ +def draw_2nf(): + fig, ax = create_figure(11.69, 6.5) + + # Studenci + h1 = ['StID*', 'Imie', 'WydzialID', 'NazwaWydzialu'] + r1 = [['1', 'Anna', 'W4', 'EiTI'], + ['2', 'Jan', 'W4', 'EiTI'], + ['3', 'Ewa', 'W2', 'Fizyka']] + cw1 = [0.55, 0.55, 0.85, 1.2] + draw_table(ax, 0.3, 5.8, 'Studenci (kl: StID)', + h1, r1, cw1, highlight_cols={2, 3}, title_fontsize=9) + + # Kursy + h2 = ['KursID*', 'NazwaKursu'] + r2 = [['K10', 'Bazy danych'], ['K20', 'Algorytmy'], ['K30', 'Optyka']] + cw2 = [0.7, 1.1] + draw_table(ax, 4.0, 5.8, 'Kursy (kl: KursID)', + h2, r2, cw2, title_fontsize=9) + + # Zapisy + h3 = ['StID*', 'KursID*', 'Prowadzacy'] + r3 = [['1', 'K10', 'Kowalski'], ['1', 'K20', 'Nowak'], + ['2', 'K10', 'Kowalski'], ['3', 'K30', 'Wisniewski']] + cw3 = [0.55, 0.7, 1.05] + draw_table(ax, 6.8, 5.8, 'Zapisy (kl: StID, KursID)', + h3, r3, cw3, title_fontsize=9) + + # Telefony + h4 = ['StID*', 'Telefon*'] + r4 = [['1', '111-222'], ['1', '333-444'], ['2', '555-666'], ['3', '777-888']] + cw4 = [0.55, 0.85] + draw_table(ax, 9.5, 5.8, 'Telefony', + h4, r4, cw4, title_fontsize=9) + + # Annotations + add_label(ax, 0.3, 3.3, + 'KROK: Rozbito czesc. zaleznosci — atrybuty zalezne od czesci klucza wydzielone.', + fontsize=9) + add_label(ax, 0.3, 2.95, + ' StID -> Imie, WydzialID, NazwaWydzialu ==> tabela Studenci', + fontsize=8, color='#333333') + add_label(ax, 0.3, 2.65, + ' KursID -> NazwaKursu ==> tabela Kursy', + fontsize=8, color='#333333') + add_label(ax, 0.3, 2.3, + 'PROBLEM 3NF w "Studenci": StID -> WydzialID -> NazwaWydzialu', + fontsize=9, color='black') + add_label(ax, 0.3, 1.95, + ' NazwaWydzialu zalezy od WydzialID (nie-klucz), nie bezposrednio od StID.', + fontsize=9, color='black') + add_label(ax, 0.3, 1.6, + ' --> Zaleznosc PRZECHODNIA = NARUSZENIE 3NF.', + fontsize=9, color='black') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_2nf_tables.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_2nf_tables.png") + + +# ============================================================ +# DIAGRAM 4: 3NF — no transitive dependencies +# ============================================================ +def draw_3nf(): + fig, ax = create_figure(11.69, 6.5) + + # Studenci (fixed) + h1 = ['StID*', 'Imie', 'WydzialID'] + r1 = [['1', 'Anna', 'W4'], ['2', 'Jan', 'W4'], ['3', 'Ewa', 'W2']] + cw1 = [0.55, 0.55, 0.85] + draw_table(ax, 0.3, 5.8, 'Studenci (kl: StID)', + h1, r1, cw1, title_fontsize=9) + + # Wydzialy (new!) + h2 = ['WydzialID*', 'NazwaWydzialu'] + r2 = [['W4', 'EiTI'], ['W2', 'Fizyka']] + cw2 = [0.85, 1.2] + draw_table(ax, 2.6, 5.8, 'Wydzialy (kl: WydzialID)', + h2, r2, cw2, title_fontsize=9) + + # Kursy + h3 = ['KursID*', 'NazwaKursu'] + r3 = [['K10', 'Bazy danych'], ['K20', 'Algorytmy'], ['K30', 'Optyka']] + cw3 = [0.7, 1.1] + draw_table(ax, 5.2, 5.8, 'Kursy (kl: KursID)', + h3, r3, cw3, title_fontsize=9) + + # Zapisy (highlight BCNF violation) + h4 = ['StID*', 'KursID*', 'Prowadzacy'] + r4 = [['1', 'K10', 'Kowalski'], ['1', 'K20', 'Nowak'], + ['2', 'K10', 'Kowalski'], ['3', 'K30', 'Wisniewski']] + cw4 = [0.55, 0.7, 1.05] + draw_table(ax, 7.8, 5.8, 'Zapisy (kl: StID, KursID)', + h4, r4, cw4, highlight_cols={1, 2}, title_fontsize=9) + + # Annotations + add_label(ax, 0.3, 3.3, + 'KROK: Rozdzielono Studenci -> Studenci + Wydzialy (usun. zal. przechodnia).', + fontsize=9) + add_label(ax, 0.3, 2.95, + ' StID -> WydzialID -> NazwaWydzialu rozbito: NazwaWydzialu w osobnej tabeli.', + fontsize=8, color='#333333') + add_label(ax, 0.3, 2.55, + 'PROBLEM BCNF w "Zapisy": FD: Prowadzacy -> KursID (1 prowadzacy = 1 kurs)', + fontsize=9, color='black') + add_label(ax, 0.3, 2.2, + ' Prowadzacy NIE jest nadkluczem tabeli Zapisy -> NARUSZENIE BCNF.', + fontsize=9, color='black') + add_label(ax, 0.3, 1.85, + ' 3NF OK, bo KursID jest atrybutem pierwszym (prime) -> wyjatek 3NF.', + fontsize=9, color='#333333') + add_label(ax, 0.3, 1.5, + ' BCNF nie ma takiego wyjatku -> kazda nietrywialna FD wymaga nadklucza po lewej.', + fontsize=9, color='#333333') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_3nf_tables.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_3nf_tables.png") + + +# ============================================================ +# DIAGRAM 5: BCNF — every determinant is a superkey +# ============================================================ +def draw_bcnf(): + fig, ax = create_figure(11.69, 7.5) + + # Studenci + h1 = ['StID*', 'Imie', 'WydzialID'] + r1 = [['1', 'Anna', 'W4'], ['2', 'Jan', 'W4'], ['3', 'Ewa', 'W2']] + cw1 = [0.55, 0.55, 0.85] + draw_table(ax, 0.3, 6.8, 'Studenci', h1, r1, cw1, title_fontsize=9) + + # Wydzialy + h2 = ['WydzialID*', 'NazwaWydz.'] + r2 = [['W4', 'EiTI'], ['W2', 'Fizyka']] + cw2 = [0.85, 1.0] + draw_table(ax, 2.5, 6.8, 'Wydzialy', h2, r2, cw2, title_fontsize=9) + + # Kursy + h3 = ['KursID*', 'NazwaKursu'] + r3 = [['K10', 'Bazy danych'], ['K20', 'Algorytmy'], ['K30', 'Optyka']] + cw3 = [0.7, 1.1] + draw_table(ax, 4.8, 6.8, 'Kursy', h3, r3, cw3, title_fontsize=9) + + # ProwadzacyKurs (NEW - from BCNF decomposition) + h4 = ['Prowadzacy*', 'KursID'] + r4 = [['Kowalski', 'K10'], ['Nowak', 'K20'], ['Wisniewski', 'K30']] + cw4 = [1.05, 0.7] + draw_table(ax, 7.2, 6.8, 'ProwadzacyKurs (kl: Prow.)', + h4, r4, cw4, title_fontsize=9) + + # StudentProwadzacy (NEW) + h5 = ['StID*', 'Prowadzacy*'] + r5 = [['1', 'Kowalski'], ['1', 'Nowak'], ['2', 'Kowalski'], ['3', 'Wisniewski']] + cw5 = [0.55, 1.05] + draw_table(ax, 9.5, 6.8, 'StudentProw. (kl: oba)', + h5, r5, cw5, title_fontsize=9) + + # Telefony + h6 = ['StID*', 'Telefon*'] + r6 = [['1', '111-222'], ['1', '333-444'], ['2', '555-666'], ['3', '777-888']] + cw6 = [0.55, 0.85] + draw_table(ax, 0.3, 4.6, 'Telefony', h6, r6, cw6, title_fontsize=9) + + # Annotations + add_label(ax, 0.3, 2.9, + 'KROK: Zapisy(StID, KursID, Prowadzacy) rozbite na:', + fontsize=9) + add_label(ax, 0.3, 2.55, + ' ProwadzacyKurs(Prowadzacy, KursID) — FD: Prowadzacy -> KursID, klucz: Prowadzacy', + fontsize=8, color='#333333') + add_label(ax, 0.3, 2.25, + ' StudentProwadzacy(StID, Prowadzacy) — ktory student u ktorego prowadzacego', + fontsize=8, color='#333333') + add_label(ax, 0.3, 1.85, + 'Teraz KAZDA nietrywialna FD ma nadklucz po lewej stronie -> BCNF spelnione.', + fontsize=9) + add_label(ax, 0.3, 1.45, + 'Rekonstrukcja: StudentProw. JOIN ProwadzacyKurs ON Prowadzacy -> odtworzenie Zapisy.', + fontsize=8, color='#333333') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_bcnf_tables.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_bcnf_tables.png") + + +# ============================================================ +# DIAGRAM 6: 4NF example — multi-valued dependencies +# ============================================================ +def draw_4nf(): + fig, ax = create_figure(11.69, 7.5) + + # Before: table with MVD violation + h_before = ['StID*', 'Hobby*', 'Umiejetnosc*'] + r_before = [ + ['1', 'Szachy', 'Python'], + ['1', 'Szachy', 'SQL'], + ['1', 'Bieganie', 'Python'], + ['1', 'Bieganie', 'SQL'], + ['2', 'Plywanie', 'Java'], + ] + cw_before = [0.55, 0.9, 1.0] + draw_table(ax, 0.5, 6.8, + 'PRZED: StudentAktywnosci (klucz: StID, Hobby, Umiejetnosc)', + h_before, r_before, cw_before, + highlight_cols={1, 2}, title_fontsize=10) + + # Arrows + add_label(ax, 3.5, 6.3, 'StID ->> Hobby', fontsize=9, color='black') + add_label(ax, 3.5, 6.0, 'StID ->> Umiejetnosc', fontsize=9, color='black') + add_label(ax, 3.5, 5.6, 'NIEZALEZNE MVD w jednej tabeli', fontsize=9, color='black') + add_label(ax, 3.5, 5.2, '= iloczyn kartezjanski = NARUSZENIE 4NF', fontsize=9, color='black') + + # After: two tables + add_arrow(ax, 3.0, 4.2, 3.0, 3.7, '', '#333333') + add_label(ax, 3.2, 3.95, 'dekompozycja', fontsize=8, color='#333333') + + h_hobby = ['StID*', 'Hobby*'] + r_hobby = [['1', 'Szachy'], ['1', 'Bieganie'], ['2', 'Plywanie']] + cw_hobby = [0.55, 0.9] + draw_table(ax, 0.5, 3.5, + 'PO: StudentHobby', + h_hobby, r_hobby, cw_hobby, title_fontsize=10) + + h_skill = ['StID*', 'Umiejetnosc*'] + r_skill = [['1', 'Python'], ['1', 'SQL'], ['2', 'Java']] + cw_skill = [0.55, 1.0] + draw_table(ax, 3.5, 3.5, + 'PO: StudentUmiejetnosc', + h_skill, r_skill, cw_skill, title_fontsize=10) + + # Summary on the right side + add_label(ax, 6.5, 6.5, '4NF: BCNF + brak nietrywialnych MVD', fontsize=10) + add_label(ax, 6.5, 6.1, + 'MVD X ->> Y: jeden X = ZBIOR Y-ow,', fontsize=8, color='#333333') + add_label(ax, 6.5, 5.8, + 'niezaleznie od reszty kolumn.', fontsize=8, color='#333333') + add_label(ax, 6.5, 5.35, + 'Naruszenie: Student 1 ma 2 hobby i 2 umiejetnosci', fontsize=8) + add_label(ax, 6.5, 5.05, + ' -> 2 x 2 = 4 wiersze (iloczyn kartezjanski!)', fontsize=8) + add_label(ax, 6.5, 4.65, + 'Naprawa: rozdziel niezalezne MVD do osobnych tabel.', fontsize=8) + add_label(ax, 6.5, 4.25, + 'Po dekompozycji: 3 + 3 = 6 wierszy zamiast 5 z ilocz.', fontsize=8, + color='#333333') + add_label(ax, 6.5, 3.85, + ' (ale BEZ sztucznych kombinacji!)', fontsize=8, color='#333333') + + # Key insight box + rect = mpatches.FancyBboxPatch( + (6.3, 2.5), 5.0, 1.0, + boxstyle="round,pad=0.1", facecolor='#F0F0F0', + edgecolor='black', linewidth=1.0) + ax.add_patch(rect) + add_label(ax, 6.5, 3.2, + 'ROZNICA 4NF vs BCNF:', fontsize=9) + add_label(ax, 6.5, 2.85, + 'BCNF dotyczy FD (X -> Y, jedna wartosc)', fontsize=8, color='#333333') + add_label(ax, 6.5, 2.55, + '4NF dotyczy MVD (X ->> Y, zbior wartosci)', fontsize=8, color='#333333') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_4nf_example.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_4nf_example.png") + + +# ============================================================ +# DIAGRAM 7: 5NF example — join dependencies +# ============================================================ +def draw_5nf(): + fig, ax = create_figure(11.69, 8.5) + + # Before: ternary table + h_before = ['Dostawca*', 'Czesc*', 'Projekt*'] + r_before = [ + ['Alfa', 'Sruba', 'Most'], + ['Alfa', 'Sruba', 'Wiezowiec'], + ['Alfa', 'Nakretka', 'Most'], + ['Beta', 'Sruba', 'Wiezowiec'], + ['Beta', 'Nakretka', 'Wiezowiec'], + ] + cw_before = [0.9, 0.9, 1.0] + draw_table(ax, 0.5, 7.8, + 'PRZED: Dostawy (klucz: Dostawca, Czesc, Projekt)', + h_before, r_before, cw_before, title_fontsize=10) + + add_label(ax, 3.8, 7.3, 'Tabela w 4NF (brak nietrywialnych MVD),', fontsize=8) + add_label(ax, 3.8, 7.0, 'ale NIE w 5NF jesli zachodzi regula cykliczna:', fontsize=8) + add_label(ax, 3.8, 6.55, + 'Jesli Dostawca dostarcza Czesc', fontsize=8, color='#333333') + add_label(ax, 3.8, 6.25, + ' I Dostawca dostarcza do Projektu', fontsize=8, color='#333333') + add_label(ax, 3.8, 5.95, + ' I Czesc jest uzywana w Projekcie', fontsize=8, color='#333333') + add_label(ax, 3.8, 5.65, + ' ==> Dostawca dostarcza te Czesc do tego Projektu.', fontsize=8, + color='black') + + # Arrow down + add_arrow(ax, 1.8, 5.1, 1.8, 4.6, 'dekompozycja 5NF', '#333333') + + # After: three binary tables + h1 = ['Dostawca*', 'Czesc*'] + r1 = [['Alfa', 'Sruba'], ['Alfa', 'Nakretka'], ['Beta', 'Sruba'], ['Beta', 'Nakretka']] + cw1 = [0.9, 0.9] + draw_table(ax, 0.3, 4.3, 'DostawcaCzesc', + h1, r1, cw1, title_fontsize=9) + + h2 = ['Dostawca*', 'Projekt*'] + r2 = [['Alfa', 'Most'], ['Alfa', 'Wiezowiec'], ['Beta', 'Wiezowiec']] + cw2 = [0.9, 1.0] + draw_table(ax, 3.0, 4.3, 'DostawcaProjekt', + h2, r2, cw2, title_fontsize=9) + + h3 = ['Czesc*', 'Projekt*'] + r3 = [['Sruba', 'Most'], ['Sruba', 'Wiezowiec'], + ['Nakretka', 'Most'], ['Nakretka', 'Wiezowiec']] + cw3 = [0.9, 1.0] + draw_table(ax, 5.7, 4.3, 'CzescProjekt', + h3, r3, cw3, title_fontsize=9) + + # Join reconstruction note + rect = mpatches.FancyBboxPatch( + (8.3, 3.5), 3.0, 4.0, + boxstyle="round,pad=0.1", facecolor='#F0F0F0', + edgecolor='black', linewidth=1.0) + ax.add_patch(rect) + + add_label(ax, 8.5, 7.2, '5NF (PJNF):', fontsize=10) + add_label(ax, 8.5, 6.8, 'Project-Join NF', fontsize=8, color='#333333') + add_label(ax, 8.5, 6.35, 'Kazda zaleznosc', fontsize=8) + add_label(ax, 8.5, 6.05, 'zlaczenia (JD)', fontsize=8) + add_label(ax, 8.5, 5.75, 'implikowana przez', fontsize=8) + add_label(ax, 8.5, 5.45, 'klucze kandydujace.', fontsize=8) + add_label(ax, 8.5, 4.9, 'Rekonstrukcja:', fontsize=9) + add_label(ax, 8.5, 4.55, 'DC JOIN DP JOIN CP', fontsize=8, color='#333333') + add_label(ax, 8.5, 4.2, '= oryginalna tabela', fontsize=8, color='#333333') + add_label(ax, 8.5, 3.75, '(bezstratnie!)', fontsize=8, color='#333333') + + # Verification example at the bottom + add_label(ax, 0.3, 2.0, + 'Weryfikacja: Alfa dostarcza Nakretke? Alfa -> Wiezowiec? Nakretka -> Wiezowiec?', + fontsize=8) + add_label(ax, 0.3, 1.65, + ' TAK, TAK, TAK --> wg reguly cyklicznej: Alfa dostarcza Nakretke do Wiezowca.', + fontsize=8, color='#333333') + add_label(ax, 0.3, 1.25, + 'Ale: Alfa dostarcza Nakretke? TAK. Alfa -> Most? TAK. Nakretka -> Most? TAK.', + fontsize=8) + add_label(ax, 0.3, 0.9, + ' --> Alfa dostarcza Nakretke do Mostu. (Tego wiersza NIE MA w oryginale -- BLAD!)', + fontsize=8, color='black') + add_label(ax, 0.3, 0.5, + ' Dekompozycja 5NF jest poprawna TYLKO jesli regula cykliczna rzeczywiscie zachodzi!', + fontsize=8, color='black') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_5nf_example.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_5nf_example.png") + + +# ============================================================ +# DIAGRAM 8: Full normalization summary flowchart +# ============================================================ +def draw_summary_flow(): + fig, ax = create_figure(11.69, 6.0) + + # Boxes for each NF + box_y = 4.5 + box_h = 1.8 + box_w = 1.4 + gap = 0.25 + + nf_data = [ + ('0NF', 'Nienormalna', 'Listy w\nkomorkach,\nbrak klucza'), + ('1NF', 'Atomowosc', 'Kazda komorka\n= 1 wartosc,\njest klucz'), + ('2NF', 'Pelny klucz', 'Brak czesciowej\nzaleznosci od\nklucza zlozonego'), + ('3NF', 'Tylko klucz', 'Brak zaleznosci\nprzechodniej\nA->B->C'), + ('BCNF', 'Nadklucz', 'Lewa strona\nkazdej FD\n= nadklucz'), + ('4NF', 'Brak MVD', 'Brak nietryw.\nwielowart.\nzaleznosci'), + ('5NF', 'Brak JD', 'Kazda zal.\nzlaczenia\nimpl. kluczem'), + ] + + for i, (name, subtitle, desc) in enumerate(nf_data): + x = 0.3 + i * (box_w + gap) + + # Main box + rect = mpatches.FancyBboxPatch( + (x, box_y - box_h), box_w, box_h, + boxstyle="round,pad=0.05", + facecolor='#F5F5F5' if i == 0 else '#FFFFFF', + edgecolor='black', linewidth=1.2) + ax.add_patch(rect) + + # NF name + ax.text(x + box_w / 2, box_y - 0.15, name, + fontsize=12, fontweight='bold', ha='center', va='top', + family='monospace') + + # Subtitle + ax.text(x + box_w / 2, box_y - 0.45, subtitle, + fontsize=7, ha='center', va='top', + family='monospace', color='#333333') + + # Description + ax.text(x + box_w / 2, box_y - 0.75, desc, + fontsize=6.5, ha='center', va='top', + family='monospace', color='#555555', + linespacing=1.3) + + # Arrow to next + if i < len(nf_data) - 1: + ax.annotate('', xy=(x + box_w + 0.02, box_y - box_h / 2), + xytext=(x + box_w + gap - 0.02, box_y - box_h / 2), + arrowprops=dict(arrowstyle='<-', color='black', lw=1.5)) + + # Bottom: mnemonic + ax.text(5.85, 2.2, + '\"Klucz, caly klucz i tylko klucz -- tak mi dopomoz Codd\"', + fontsize=11, ha='center', va='center', family='monospace', + style='italic') + ax.text(5.85, 1.8, + '1NF: klucz istnieje | 2NF: caly klucz | 3NF: tylko klucz', + fontsize=9, ha='center', va='center', family='monospace', + color='#333333') + ax.text(5.85, 1.4, + 'BCNF: kazdy determinant = nadklucz | 4NF: +brak MVD | 5NF: +brak JD', + fontsize=9, ha='center', va='center', family='monospace', + color='#333333') + + # Hierarchy + ax.text(5.85, 0.8, + '5NF (zawiera sie w) 4NF (zaw.) BCNF (zaw.) 3NF (zaw.) 2NF (zaw.) 1NF', + fontsize=8, ha='center', va='center', family='monospace', + color='#555555') + + fig.savefig(os.path.join(OUTPUT_DIR, 'nf_summary_flow.png'), + bbox_inches='tight', facecolor='white', pad_inches=0.2) + plt.close(fig) + print("Generated: nf_summary_flow.png") + + +# ============================================================ +# Main +# ============================================================ +if __name__ == '__main__': + print("Generating normalization diagrams...") + draw_0nf() + draw_1nf() + draw_2nf() + draw_3nf() + draw_bcnf() + draw_4nf() + draw_5nf() + draw_summary_flow() + print("\nDone! All diagrams saved to:", OUTPUT_DIR) diff --git a/pytania/generate_q9_q12_diagrams.py b/pytania/generate_q9_q12_diagrams.py new file mode 100644 index 0000000..aeb593a --- /dev/null +++ b/pytania/generate_q9_q12_diagrams.py @@ -0,0 +1,717 @@ +#!/usr/bin/env python3 +""" +Generate diagrams for: + PYTANIE 9: Processes & Threads (IPC mechanisms, deadlock, producer-consumer) + PYTANIE 12: Network optimization models (Ford-Fulkerson, Hungarian, CPM, Kruskal, TSP, Min-cost flow) + +All: A4-compatible, B&W, 300 DPI, laser-printer-friendly. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch, FancyArrowPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +FS_SMALL = 6.5 +FS_EDGE = 9 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' +LIGHT_GREEN = '#D5E8D4' +LIGHT_RED = '#F8D7DA' +LIGHT_BLUE = '#D6EAF8' +LIGHT_YELLOW = '#FFF9C4' +LIGHT_ORANGE = '#FFE0B2' + + +def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, + fontweight='normal', ha='center', va='center', rounded=True, edgecolor=LN): + if rounded: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05", + lw=lw, edgecolor=edgecolor, facecolor=fill) + else: + rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=edgecolor, facecolor=fill) + ax.add_patch(rect) + ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize, + fontweight=fontweight, wrap=True) + + +def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=color, lw=lw)) + + +def save_fig(fig, name): + path = os.path.join(OUTPUT_DIR, name) + fig.savefig(path, dpi=DPI, bbox_inches='tight', facecolor=BG, pad_inches=0.15) + plt.close(fig) + print(f" Saved: {path}") + + +# ============================================================ +# PYTANIE 9 DIAGRAMS +# ============================================================ + +def gen_ipc_mechanisms(): + """IPC mechanisms comparison diagram.""" + fig, ax = plt.subplots(1, 1, figsize=(8, 5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 7) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Mechanizmy IPC — porównanie', fontsize=FS_TITLE, fontweight='bold', pad=10) + + mechanisms = [ + ('Pipe', '→ jednokierunkowy\n→ bufor w jądrze\n→ spokrewnione procesy', + 'ls | grep txt', GRAY1), + ('Shared\nMemory', '→ wspólna ramka RAM\n→ zero kopiowania\n→ wymaga synchronizacji', + 'mmap() / shm_open()', LIGHT_GREEN), + ('Message\nQueue', '→ strukturalne wiad.\n→ asynchroniczna\n→ filtrowanie typów', + 'msgsnd() / msgrcv()', LIGHT_BLUE), + ('Socket', '→ dwukierunkowy\n→ lokalny lub sieciowy\n→ TCP/UDP', + 'connect() / accept()', LIGHT_YELLOW), + ] + + for i, (name, desc, example, color) in enumerate(mechanisms): + x = 0.3 + y = 5.5 - i * 1.5 + # Box for mechanism name + draw_box(ax, x, y, 1.5, 1.0, name, fill=color, fontsize=9, fontweight='bold') + # Description + ax.text(x + 2.0, y + 0.5, desc, fontsize=FS, va='center', ha='left', + family='monospace') + # Example + draw_box(ax, 6.5, y + 0.15, 3.0, 0.7, example, fill=GRAY4, fontsize=FS_SMALL) + + # Draw process boxes for pipe illustration at top + y_top = 6.3 + ax.text(5.0, y_top, 'Proces A ──bufor jądra──▶ Proces B', + fontsize=FS, ha='center', va='center', family='monospace', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY1, edgecolor=GRAY3)) + + # Legend + ax.text(0.3, 0.3, 'Szybkość: Shared Memory > Pipe ≈ MsgQueue > Socket (sieciowy)', + fontsize=FS, va='center', style='italic') + + save_fig(fig, 'ipc_mechanisms.png') + + +def gen_deadlock_illustration(): + """Deadlock circular wait diagram.""" + fig, ax = plt.subplots(1, 1, figsize=(6, 5)) + ax.set_xlim(0, 8) + ax.set_ylim(0, 6.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Zakleszczenie (Deadlock) — cykliczne oczekiwanie', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + # Thread boxes + draw_box(ax, 0.5, 3.5, 2.0, 1.2, 'Wątek A\n(trzyma Mutex 1)', fill=LIGHT_BLUE, + fontsize=9, fontweight='bold') + draw_box(ax, 5.5, 3.5, 2.0, 1.2, 'Wątek B\n(trzyma Mutex 2)', fill=LIGHT_ORANGE, + fontsize=9, fontweight='bold') + + # Resource boxes + draw_box(ax, 0.5, 0.8, 2.0, 1.0, 'Mutex 1\nzablokowany', fill=GRAY2, + fontsize=8, fontweight='bold') + draw_box(ax, 5.5, 0.8, 2.0, 1.0, 'Mutex 2\nzablokowany', fill=GRAY2, + fontsize=8, fontweight='bold') + + # Arrows: "holds" (down) + draw_arrow(ax, 1.5, 3.5, 1.5, 1.8, lw=2.0, color='#333333') + ax.text(0.3, 2.65, 'trzyma', fontsize=FS, ha='center', rotation=90, color='#333333') + + draw_arrow(ax, 6.5, 3.5, 6.5, 1.8, lw=2.0, color='#333333') + ax.text(7.7, 2.65, 'trzyma', fontsize=FS, ha='center', rotation=90, color='#333333') + + # Arrows: "waits for" (across, with red) + draw_arrow(ax, 2.5, 4.3, 5.5, 4.3, lw=2.5, color='#C62828') + ax.text(4.0, 4.6, 'czeka na Mutex 2', fontsize=FS, ha='center', color='#C62828', + fontweight='bold') + + draw_arrow(ax, 5.5, 3.7, 2.5, 3.7, lw=2.5, color='#C62828') + ax.text(4.0, 3.2, 'czeka na Mutex 1', fontsize=FS, ha='center', color='#C62828', + fontweight='bold') + + # Coffman conditions + conditions = [ + '1. Mutual Exclusion — zasoby wyłączne', + '2. Hold and Wait — trzymaj + czekaj', + '3. No Preemption — nie można zabrać siłą', + '4. Circular Wait — cykl oczekiwania ← złam ten!' + ] + for i, cond in enumerate(conditions): + color_c = '#C62828' if i == 3 else LN + fw = 'bold' if i == 3 else 'normal' + ax.text(0.5, 0.5 - i * 0.25 + 0.2, cond, fontsize=FS_SMALL, color=color_c, + fontweight=fw, va='center') + + save_fig(fig, 'deadlock_illustration.png') + + +def gen_producer_consumer(): + """Producer-consumer with bounded buffer diagram.""" + fig, ax = plt.subplots(1, 1, figsize=(8, 4.5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 5.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Producent-Konsument z buforem cyklicznym (N=4)', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + # Producer + draw_box(ax, 0.3, 2.0, 1.8, 1.5, 'Producent\n\nwstaw(elem)\nV(full)\nV(mutex)', + fill=LIGHT_GREEN, fontsize=FS, fontweight='bold') + + # Buffer slots + buf_x = 3.0 + buf_y = 2.5 + slot_w = 1.0 + slot_h = 0.8 + items = ['A', 'B', '', ''] + fills = [LIGHT_BLUE, LIGHT_BLUE, 'white', 'white'] + for i, (item, fc) in enumerate(zip(items, fills)): + x = buf_x + i * slot_w + draw_box(ax, x, buf_y, slot_w, slot_h, item, fill=fc, fontsize=10, + fontweight='bold', rounded=False) + + ax.text(buf_x + 2.0, buf_y + slot_h + 0.3, 'Bufor (N=4)', + fontsize=9, ha='center', fontweight='bold') + ax.text(buf_x + 2.0, buf_y - 0.3, 'full=2, empty=2', + fontsize=FS, ha='center', family='monospace') + + # Consumer + draw_box(ax, 7.8, 2.0, 1.8, 1.5, 'Konsument\n\npobierz()\nV(empty)\nV(mutex)', + fill=LIGHT_YELLOW, fontsize=FS, fontweight='bold') + + # Arrows + draw_arrow(ax, 2.1, 2.75, 3.0, 2.9, lw=1.5) + draw_arrow(ax, 7.0, 2.9, 7.8, 2.75, lw=1.5) + + # Semaphores + sems = [ + ('mutex = 1', 'wyłączny dostęp do bufora', GRAY2), + ('empty = 2', 'wolne sloty (P = czekaj, V = +1)', LIGHT_GREEN), + ('full = 2', 'pełne sloty (P = czekaj, V = +1)', LIGHT_BLUE), + ] + for i, (name, desc, color) in enumerate(sems): + y = 1.2 - i * 0.45 + draw_box(ax, 3.0, y, 1.5, 0.35, name, fill=color, fontsize=FS_SMALL, + fontweight='bold') + ax.text(4.7, y + 0.17, desc, fontsize=FS_SMALL, va='center') + + # Warning + ax.text(0.3, 4.8, 'KOLEJNOŚĆ: P(empty/full) PRZED P(mutex)! Odwrotnie = DEADLOCK', + fontsize=FS, fontweight='bold', color='#C62828', + bbox=dict(boxstyle='round,pad=0.2', facecolor=LIGHT_RED, edgecolor='#C62828')) + + save_fig(fig, 'producer_consumer.png') + + +# ============================================================ +# PYTANIE 12 DIAGRAMS +# ============================================================ + +def draw_network_node(ax, name, pos, color='white', fontsize=10, r=0.3): + """Draw a network node (circle).""" + x, y = pos + circle = plt.Circle((x, y), r, fill=True, facecolor=color, + edgecolor=LN, linewidth=1.5, zorder=5) + ax.add_patch(circle) + ax.text(x, y, name, ha='center', va='center', fontsize=fontsize, + fontweight='bold', zorder=6) + + +def draw_network_edge(ax, pos1, pos2, label='', color=LN, lw=1.5, offset=0.0, + directed=True, r=0.33, label_bg='white'): + """Draw a directed edge with label.""" + x1, y1 = pos1 + x2, y2 = pos2 + dx, dy = x2 - x1, y2 - y1 + length = np.sqrt(dx**2 + dy**2) + if length == 0: + return + sx = x1 + r * dx / length + sy = y1 + r * dy / length + ex = x2 - r * dx / length + ey = y2 - r * dy / length + + if directed: + ax.annotate("", xy=(ex, ey), xytext=(sx, sy), + arrowprops=dict(arrowstyle='->', color=color, lw=lw)) + else: + ax.plot([sx, ex], [sy, ey], color=color, linewidth=lw, zorder=2) + + if label: + mx = (x1 + x2) / 2 + my = (y1 + y2) / 2 + perp_x = -dy / length * (0.2 + offset) + perp_y = dx / length * (0.2 + offset) + ax.text(mx + perp_x, my + perp_y, str(label), ha='center', va='center', + fontsize=FS_EDGE, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=label_bg, + edgecolor=GRAY3, alpha=0.95), zorder=4) + + +def gen_ford_fulkerson(): + """Ford-Fulkerson max flow step-by-step.""" + fig, axes = plt.subplots(2, 2, figsize=(10, 8)) + fig.suptitle('Ford-Fulkerson — Maksymalny przepływ (krok po kroku)', + fontsize=FS_TITLE, fontweight='bold') + + pos = {'s': (0.5, 1.5), 'A': (2.5, 2.5), 'B': (2.5, 0.5), 't': (4.5, 1.5)} + + steps = [ + { + 'title': 'Krok 0: Sieć wejściowa\n(przepustowości)', + 'edges': [('s','A','10'), ('s','B','8'), ('A','t','6'), + ('B','t','10'), ('B','A','2')], + 'flows': {}, + 'path': [], + 'note': 'Szukamy ścieżki s→...→t' + }, + { + 'title': 'Krok 1: Ścieżka s→A→t\nPrzepływ: +6 (min(10,6))', + 'edges': [('s','A','4/10'), ('s','B','0/8'), ('A','t','6/6'), + ('B','t','0/10'), ('B','A','0/2')], + 'flows': {}, + 'path': [('s','A'), ('A','t')], + 'note': 'Łączny przepływ: 6' + }, + { + 'title': 'Krok 2: Ścieżka s→B→t\nPrzepływ: +8 (min(8,10))', + 'edges': [('s','A','4/10'), ('s','B','8/8'), ('A','t','6/6'), + ('B','t','8/10'), ('B','A','0/2')], + 'flows': {}, + 'path': [('s','B'), ('B','t')], + 'note': 'Łączny przepływ: 14' + }, + { + 'title': 'Krok 3: Brak ścieżki powiększającej\nMAX FLOW = 14', + 'edges': [('s','A','4/10'), ('s','B','8/8'), ('A','t','6/6'), + ('B','t','8/10'), ('B','A','0/2')], + 'flows': {}, + 'path': [], + 'note': 'Min-cut: {s,A,B}|{t}\nA→t(6)+B→t(10)=16? Nie!\ns→B(8)+A→t(6)=14 ✓' + }, + ] + + for idx, (ax, step) in enumerate(zip(axes.flat, steps)): + ax.set_xlim(-0.3, 5.3) + ax.set_ylim(-0.3, 3.3) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title(step['title'], fontsize=FS, fontweight='bold', pad=5) + + path_set = set(step['path']) + + for e in step['edges']: + u, v, label = e + is_path = (u, v) in path_set + c = '#C62828' if is_path else LN + w = 2.5 if is_path else 1.5 + draw_network_edge(ax, pos[u], pos[v], label=label, color=c, lw=w) + + for name, p in pos.items(): + if name == 's': + c = LIGHT_GREEN + elif name == 't': + c = LIGHT_RED + else: + c = 'white' + draw_network_node(ax, name, p, color=c) + + ax.text(2.5, -0.15, step['note'], fontsize=FS_SMALL, ha='center', + va='center', style='italic', + bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3)) + + fig.tight_layout(rect=[0, 0, 1, 0.93]) + save_fig(fig, 'ford_fulkerson_example.png') + + +def gen_hungarian(): + """Hungarian algorithm step-by-step.""" + fig, axes = plt.subplots(2, 2, figsize=(9, 7)) + fig.suptitle('Algorytm węgierski — Problem przydziału (krok po kroku)', + fontsize=FS_TITLE, fontweight='bold') + + matrices = [ + { + 'title': 'Macierz kosztów (wejściowa)', + 'data': [[8, 4, 7], [5, 2, 3], [9, 4, 8]], + 'highlight': [], + 'note': 'Minimalizuj łączny koszt przydziału', + }, + { + 'title': 'Krok 1: Redukcja wierszy\n(odejmij min z wiersza)', + 'data': [[4, 0, 3], [3, 0, 1], [5, 0, 4]], + 'highlight': [(0,1), (1,1), (2,1)], + 'note': 'min: A=4, B=2, C=4', + }, + { + 'title': 'Krok 2: Redukcja kolumn\n(odejmij min z kolumny)', + 'data': [[1, 0, 2], [0, 0, 0], [2, 0, 3]], + 'highlight': [(1,0), (0,1), (1,1), (2,1), (1,2)], + 'note': 'min: Z1=3, Z2=0, Z3=1', + }, + { + 'title': 'Krok 3: Optymalne przypisanie\nA→Z2(4), B→Z1(5), C=?', + 'data': [[0, 0, 1], [0, 1, 0], [1, 0, 2]], + 'highlight': [(0,1), (1,0), (2,1)], + 'note': 'Optymalne: A→Z1(8) + B→Z3(3) + C→Z2(4) = 15', + }, + ] + + rows = ['A', 'B', 'C'] + cols = ['Z1', 'Z2', 'Z3'] + + for ax, m in zip(axes.flat, matrices): + ax.set_xlim(-0.5, 4.5) + ax.set_ylim(-1, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title(m['title'], fontsize=FS, fontweight='bold', pad=5) + + # Column headers + for j, col in enumerate(cols): + ax.text(j + 1.5, 3.8, col, ha='center', va='center', fontsize=9, + fontweight='bold') + + # Row headers and data + for i, row in enumerate(rows): + y = 2.8 - i + ax.text(0.3, y, row, ha='center', va='center', fontsize=9, + fontweight='bold') + for j in range(3): + val = m['data'][i][j] + is_zero = val == 0 + is_hl = (i, j) in m['highlight'] + fc = LIGHT_GREEN if is_hl else ('white' if not is_zero else LIGHT_YELLOW) + rect = FancyBboxPatch((j + 1.0, y - 0.35), 1.0, 0.7, + boxstyle="round,pad=0.05", lw=1.2, + edgecolor=LN if not is_hl else '#1B5E20', + facecolor=fc) + ax.add_patch(rect) + ax.text(j + 1.5, y, str(val), ha='center', va='center', + fontsize=10, fontweight='bold' if is_hl else 'normal') + + ax.text(2.0, -0.6, m['note'], fontsize=FS_SMALL, ha='center', va='center', + style='italic', + bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3)) + + fig.tight_layout(rect=[0, 0, 1, 0.93]) + save_fig(fig, 'hungarian_example.png') + + +def gen_cpm(): + """CPM critical path diagram.""" + fig, ax = plt.subplots(1, 1, figsize=(10, 5)) + ax.set_xlim(-0.5, 12) + ax.set_ylim(-0.5, 5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('CPM — Ścieżka krytyczna projektu IT', fontsize=FS_TITLE, + fontweight='bold', pad=10) + + # Task positions: (x, y) + tasks = { + 'START': (0.5, 2.5), + 'A\n3 tyg': (2.5, 2.5), + 'B\n4 tyg': (5.0, 3.8), + 'C\n5 tyg': (5.0, 1.2), + 'D\n6 tyg': (7.5, 3.8), + 'E\n2 tyg': (9.5, 2.5), + 'F\n1 tyg': (11.5, 2.5), + } + + # Critical path: START→A→B→D→E→F + critical = {'START', 'A\n3 tyg', 'B\n4 tyg', 'D\n6 tyg', 'E\n2 tyg', 'F\n1 tyg'} + critical_edges = { + ('START', 'A\n3 tyg'), ('A\n3 tyg', 'B\n4 tyg'), + ('B\n4 tyg', 'D\n6 tyg'), ('D\n6 tyg', 'E\n2 tyg'), + ('E\n2 tyg', 'F\n1 tyg'), + } + + edges = [ + ('START', 'A\n3 tyg'), + ('A\n3 tyg', 'B\n4 tyg'), + ('A\n3 tyg', 'C\n5 tyg'), + ('B\n4 tyg', 'D\n6 tyg'), + ('C\n5 tyg', 'E\n2 tyg'), + ('D\n6 tyg', 'E\n2 tyg'), + ('E\n2 tyg', 'F\n1 tyg'), + ] + + # Draw edges + for u, v in edges: + is_crit = (u, v) in critical_edges + c = '#C62828' if is_crit else GRAY3 + w = 2.5 if is_crit else 1.2 + draw_network_edge(ax, tasks[u], tasks[v], color=c, lw=w, r=0.5) + + # Draw nodes + for name, p in tasks.items(): + is_crit = name in critical + c = LIGHT_RED if is_crit else LIGHT_BLUE + r = 0.45 + circle = plt.Circle(p, r, fill=True, facecolor=c, + edgecolor='#C62828' if is_crit else LN, + linewidth=2.0 if is_crit else 1.2, zorder=5) + ax.add_patch(circle) + ax.text(p[0], p[1], name, ha='center', va='center', + fontsize=7 if '\n' in name else 8, + fontweight='bold', zorder=6) + + # ES/EF labels + es_ef = [ + ('A\n3 tyg', 'ES=0, EF=3'), + ('B\n4 tyg', 'ES=3, EF=7'), + ('C\n5 tyg', 'ES=3, EF=8\nzapas=5'), + ('D\n6 tyg', 'ES=7, EF=13'), + ('E\n2 tyg', 'ES=13, EF=15'), + ('F\n1 tyg', 'ES=15, EF=16'), + ] + for name, label in es_ef: + x, y = tasks[name] + offset_y = 0.7 if y > 2.5 else -0.7 + ax.text(x, y + offset_y, label, ha='center', va='center', fontsize=FS_SMALL, + bbox=dict(boxstyle='round,pad=0.1', facecolor='white', + edgecolor=GRAY3, alpha=0.95)) + + # Legend + ax.text(0.5, -0.2, 'Ścieżka krytyczna: A→B→D→E→F (16 tyg)', + fontsize=9, fontweight='bold', color='#C62828') + ax.text(0.5, -0.6, 'C ma 5 tyg zapasu — może się opóźnić bez wpływu na projekt', + fontsize=FS, style='italic') + + save_fig(fig, 'cpm_example.png') + + +def gen_kruskal(): + """Kruskal MST construction step-by-step.""" + fig, axes = plt.subplots(2, 2, figsize=(9, 8)) + fig.suptitle('Kruskal — budowa MST krok po kroku', fontsize=FS_TITLE, fontweight='bold') + + pos = {'A': (0.5, 2.5), 'B': (3.0, 2.5), 'C': (3.0, 0.5), 'D': (0.5, 0.5)} + + all_edges = [ + ('C', 'D', 1), ('A', 'C', 2), ('A', 'B', 4), + ('B', 'C', 6), ('B', 'D', 7), ('A', 'D', 8) + ] + + steps = [ + { + 'title': 'Graf wejściowy\n(6 krawędzi)', + 'mst': [], + 'consider': None, + 'note': 'Posortowane: CD(1), AC(2), AB(4), BC(6), BD(7), AD(8)' + }, + { + 'title': 'Krok 1: Dodaj C-D (waga 1)\nNajlżejsza krawędź', + 'mst': [('C', 'D', 1)], + 'consider': ('C', 'D'), + 'note': 'MST = {C-D}, koszt = 1' + }, + { + 'title': 'Krok 2: Dodaj A-C (waga 2)\nA nie w {C,D}', + 'mst': [('C', 'D', 1), ('A', 'C', 2)], + 'consider': ('A', 'C'), + 'note': 'MST = {C-D, A-C}, koszt = 3' + }, + { + 'title': 'Krok 3: Dodaj A-B (waga 4)\nB nie w {A,C,D} → KONIEC', + 'mst': [('C', 'D', 1), ('A', 'C', 2), ('A', 'B', 4)], + 'consider': ('A', 'B'), + 'note': 'MST = {C-D, A-C, A-B}, koszt = 7 ✓' + }, + ] + + for ax, step in zip(axes.flat, steps): + ax.set_xlim(-0.5, 4.0) + ax.set_ylim(-0.5, 3.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title(step['title'], fontsize=FS, fontweight='bold', pad=5) + + mst_set = set((u, v) for u, v, _ in step['mst']) + + for u, v, w in all_edges: + in_mst = (u, v) in mst_set or (v, u) in mst_set + is_cur = step['consider'] and ((u, v) == step['consider'] or (v, u) == step['consider']) + if is_cur: + c, lw = '#C62828', 3.0 + elif in_mst: + c, lw = '#1B5E20', 2.5 + else: + c, lw = GRAY3, 1.0 + draw_network_edge(ax, pos[u], pos[v], label=str(w), color=c, lw=lw, + directed=False, label_bg=LIGHT_GREEN if in_mst else 'white') + + for name, p in pos.items(): + # Check if in current MST component + in_mst = any(name in (u, v) for u, v, _ in step['mst']) + c = LIGHT_GREEN if in_mst else 'white' + draw_network_node(ax, name, p, color=c, r=0.3) + + ax.text(1.75, -0.3, step['note'], fontsize=FS_SMALL, ha='center', + va='center', style='italic', + bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3)) + + fig.tight_layout(rect=[0, 0, 1, 0.93]) + save_fig(fig, 'kruskal_example.png') + + +def gen_tsp(): + """TSP nearest neighbor heuristic.""" + fig, axes = plt.subplots(1, 2, figsize=(10, 4.5)) + fig.suptitle('TSP — heurystyka Nearest Neighbor (5 miast)', + fontsize=FS_TITLE, fontweight='bold') + + pos = { + 'A': (0.5, 3.0), 'B': (2.0, 4.0), 'C': (4.0, 3.5), + 'D': (3.5, 1.0), 'E': (1.5, 1.5) + } + + dist = { + ('A','B'): 20, ('A','C'): 42, ('A','D'): 35, ('A','E'): 12, + ('B','C'): 30, ('B','D'): 34, ('B','E'): 10, + ('C','D'): 12, ('C','E'): 40, + ('D','E'): 25, + } + + # Left: full graph with all distances + ax = axes[0] + ax.set_xlim(-0.5, 5.0) + ax.set_ylim(0, 5.0) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Graf pełny (odległości)', fontsize=FS, fontweight='bold') + + for (u, v), d in dist.items(): + draw_network_edge(ax, pos[u], pos[v], label=str(d), color=GRAY3, lw=0.8, + directed=False, r=0.3) + + for name, p in pos.items(): + draw_network_node(ax, name, p, color=LIGHT_BLUE, r=0.3) + + # Right: NN solution + ax = axes[1] + ax.set_xlim(-0.5, 5.0) + ax.set_ylim(0, 5.0) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Nearest Neighbor (start A)\nTrasa: A→E→B→C→D→A = 99', fontsize=FS, + fontweight='bold') + + nn_path = [('A','E',12), ('E','B',10), ('B','C',30), ('C','D',12), ('D','A',35)] + colors = ['#C62828', '#1B5E20', '#1565C0', '#E65100', '#4A148C'] + + for i, (u, v, d) in enumerate(nn_path): + draw_network_edge(ax, pos[u], pos[v], label=f'{d}', + color=colors[i], lw=2.0, directed=True, r=0.3) + # Step number + mx = (pos[u][0] + pos[v][0]) / 2 + my = (pos[u][1] + pos[v][1]) / 2 + dx = pos[v][0] - pos[u][0] + dy = pos[v][1] - pos[u][1] + length = np.sqrt(dx**2 + dy**2) + ox = dy / length * 0.45 + oy = -dx / length * 0.45 + ax.text(mx + ox, my + oy, f'#{i+1}', fontsize=FS_SMALL, ha='center', + color=colors[i], fontweight='bold') + + for name, p in pos.items(): + c = LIGHT_GREEN if name == 'A' else LIGHT_BLUE + draw_network_node(ax, name, p, color=c, r=0.3) + + fig.tight_layout(rect=[0, 0, 1, 0.9]) + save_fig(fig, 'tsp_nearest_neighbor.png') + + +def gen_min_cost_flow(): + """Min-cost flow example.""" + fig, axes = plt.subplots(1, 2, figsize=(10, 4)) + fig.suptitle('Minimalny koszt przepływu — transport 10 ton', + fontsize=FS_TITLE, fontweight='bold') + + pos = {'s': (0.5, 1.5), 'A': (2.5, 2.5), 'B': (2.5, 0.5), 't': (4.5, 1.5)} + + # Left: network with capacities and costs + ax = axes[0] + ax.set_xlim(-0.3, 5.3) + ax.set_ylim(-0.3, 3.3) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Sieć (przepustowość, koszt/t)', fontsize=FS, fontweight='bold') + + edges_info = [ + ('s', 'A', '(8, 2zł)'), ('s', 'B', '(5, 4zł)'), + ('A', 't', '(6, 3zł)'), ('B', 't', '(5, 1zł)'), + ] + for u, v, label in edges_info: + draw_network_edge(ax, pos[u], pos[v], label=label, r=0.33) + + for name, p in pos.items(): + c = LIGHT_GREEN if name == 's' else (LIGHT_RED if name == 't' else 'white') + draw_network_node(ax, name, p, color=c) + + # Right: optimal flow + ax = axes[1] + ax.set_xlim(-0.3, 5.3) + ax.set_ylim(-0.3, 3.3) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Optymalny przepływ (koszt = 50 zł)', fontsize=FS, fontweight='bold') + + opt_edges = [ + ('s', 'A', '5/8', '#1B5E20'), ('s', 'B', '5/5', '#C62828'), + ('A', 't', '5/6', '#1B5E20'), ('B', 't', '5/5', '#C62828'), + ] + for u, v, label, color in opt_edges: + draw_network_edge(ax, pos[u], pos[v], label=label, color=color, lw=2.0, r=0.33) + + for name, p in pos.items(): + c = LIGHT_GREEN if name == 's' else (LIGHT_RED if name == 't' else 'white') + draw_network_node(ax, name, p, color=c) + + ax.text(2.5, -0.15, '5t×(2+3)=25zł + 5t×(4+1)=25zł = 50zł', + fontsize=FS, ha='center', style='italic', + bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY4, edgecolor=GRAY3)) + + fig.tight_layout(rect=[0, 0, 1, 0.9]) + save_fig(fig, 'min_cost_flow_example.png') + + +# ============================================================ +# MAIN +# ============================================================ + +if __name__ == '__main__': + print("Generating PYTANIE 9 diagrams...") + gen_ipc_mechanisms() + gen_deadlock_illustration() + gen_producer_consumer() + + print("\nGenerating PYTANIE 12 diagrams...") + gen_ford_fulkerson() + gen_hungarian() + gen_cpm() + gen_kruskal() + gen_tsp() + gen_min_cost_flow() + + print("\nAll diagrams generated successfully!") diff --git a/pytania/generate_scheduling_diagrams.py b/pytania/generate_scheduling_diagrams.py new file mode 100644 index 0000000..90a56b7 --- /dev/null +++ b/pytania/generate_scheduling_diagrams.py @@ -0,0 +1,778 @@ +#!/usr/bin/env python3 +""" +Generate diagrams for PYTANIE 17: Szeregowanie zadań (Scheduling). + +Diagrams: + 1. Graham notation α|β|γ visual mnemonic map + 2. Johnson's algorithm Gantt chart (F2||Cmax example) + 3. SPT vs LPT comparison Gantt (1||ΣCⱼ) + 4. Flow shop vs Job shop visual comparison + 5. Scheduling complexity landscape + +All: A4-compatible, B&W, 300 DPI, laser-printer-friendly. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' + + +def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS, + fontweight='normal', ha='center', va='center', rounded=True): + if rounded: + rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05", + lw=lw, edgecolor=LN, facecolor=fill) + else: + rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize, + fontweight=fontweight, wrap=True) + + +def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN): + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle=style, color=color, lw=lw)) + + +# ============================================================ +# 1. GRAHAM NOTATION α|β|γ — MNEMONIC MAP +# ============================================================ +def draw_graham_notation(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 10)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 14) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Notacja Grahama α | β | γ — Mapa mnemoniczna', + fontsize=FS_TITLE + 1, fontweight='bold', pad=12) + + # === TOP: The three fields === + # Big formula bar + bar_y = 12.5 + bar_h = 1.0 + # α box + rect = FancyBboxPatch((0.5, bar_y), 2.5, bar_h, boxstyle="round,pad=0.08", + lw=2, edgecolor=LN, facecolor=GRAY1) + ax.add_patch(rect) + ax.text(1.75, bar_y + bar_h/2, 'α', fontsize=20, fontweight='bold', + ha='center', va='center') + ax.text(1.75, bar_y - 0.25, 'MASZYNY', fontsize=8, fontweight='bold', + ha='center', va='top', color='#444444') + + # separator | + ax.text(3.3, bar_y + bar_h/2, '|', fontsize=24, fontweight='bold', + ha='center', va='center') + + # β box + rect = FancyBboxPatch((3.7, bar_y), 2.5, bar_h, boxstyle="round,pad=0.08", + lw=2, edgecolor=LN, facecolor=GRAY2) + ax.add_patch(rect) + ax.text(4.95, bar_y + bar_h/2, 'β', fontsize=20, fontweight='bold', + ha='center', va='center') + ax.text(4.95, bar_y - 0.25, 'OGRANICZENIA', fontsize=8, fontweight='bold', + ha='center', va='top', color='#444444') + + # separator | + ax.text(6.5, bar_y + bar_h/2, '|', fontsize=24, fontweight='bold', + ha='center', va='center') + + # γ box + rect = FancyBboxPatch((6.9, bar_y), 2.5, bar_h, boxstyle="round,pad=0.08", + lw=2, edgecolor=LN, facecolor=GRAY3) + ax.add_patch(rect) + ax.text(8.15, bar_y + bar_h/2, 'γ', fontsize=20, fontweight='bold', + ha='center', va='center') + ax.text(8.15, bar_y - 0.25, 'CEL', fontsize=8, fontweight='bold', + ha='center', va='top', color='#444444') + + # === SECTION α: MACHINES === + sec_y = 11.5 + ax.text(0.3, sec_y, 'α — „1 Prawdziwy Quasi-Rycerz Forsuje Jaskinię Orków"', + fontsize=8, fontweight='bold', va='top', + style='italic', color='#333333') + + alpha_items = [ + ('1', 'jedna maszyna', '●', GRAY4), + ('Pm', 'identyczne Parallel', '●●●', GRAY1), + ('Qm', 'Quasi-uniform\n(różne prędkości)', '●●◐', GRAY4), + ('Rm', 'Random unrelated\n(czasy per para)', '●◆▲', GRAY1), + ('Fm', 'Flow shop\n(ta sama kolejność)', '→→→', GRAY2), + ('Jm', 'Job shop\n(indyw. trasy)', '↗↙↘', GRAY4), + ('Om', 'Open shop\n(dowolna kolej.)', '?→?', GRAY1), + ] + + col_w = 1.28 + box_h_a = 1.1 + start_x = 0.3 + start_y = 9.6 + + for i, (symbol, desc, icon, fill) in enumerate(alpha_items): + x = start_x + i * col_w + y = start_y + rect = FancyBboxPatch((x, y), col_w - 0.1, box_h_a, + boxstyle="round,pad=0.04", + lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + (col_w - 0.1)/2, y + box_h_a - 0.15, symbol, + ha='center', va='top', fontsize=9, fontweight='bold') + ax.text(x + (col_w - 0.1)/2, y + box_h_a/2 - 0.1, desc, + ha='center', va='center', fontsize=5.5) + ax.text(x + (col_w - 0.1)/2, y + 0.12, icon, + ha='center', va='bottom', fontsize=7) + + # Complexity arrow under α + arr_y = 9.35 + ax.annotate("", xy=(9.0, arr_y), xytext=(0.5, arr_y), + arrowprops=dict(arrowstyle='->', color='#666666', lw=1.5)) + ax.text(4.8, arr_y - 0.18, 'rosnąca złożoność →', + ha='center', fontsize=6, color='#666666') + + # === SECTION β: CONSTRAINTS === + sec_y2 = 8.9 + ax.text(0.3, sec_y2, 'β — „Robak Daje Deadline: Przerwy Poprzedzają Pojedyncze Setup\'y"', + fontsize=8, fontweight='bold', va='top', + style='italic', color='#333333') + + beta_items = [ + ('rⱼ', 'release\ndates', 'Robak\ndostępne\nod czasu rⱼ', GRAY1), + ('dⱼ', 'due\ndates', 'Daje\ntermin soft\n(kara za spóźn.)', GRAY4), + ('d̄ⱼ', 'dead-\nlines', 'Deadline\ntermin hard\n(musi dotrzymać)', GRAY1), + ('pmtn', 'preemp-\ntion', 'Przerwy\nmożna\nprzerwać', GRAY2), + ('prec', 'prece-\ndencje', 'Poprzedzają\nA->B (DAG)', GRAY4), + ('pⱼ=1', 'unit\ntime', 'Pojedyncze\nwszystkie = 1', GRAY1), + ('sⱼₖ', 'setup\ntimes', "Setup'y\nprzezbrojenie\nmiędzy j->k", GRAY4), + ] + + start_y2 = 7.0 + box_h_b = 1.4 + + for i, (symbol, label, desc, fill) in enumerate(beta_items): + x = start_x + i * col_w + y = start_y2 + rect = FancyBboxPatch((x, y), col_w - 0.1, box_h_b, + boxstyle="round,pad=0.04", + lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + (col_w - 0.1)/2, y + box_h_b - 0.12, symbol, + ha='center', va='top', fontsize=9, fontweight='bold') + ax.text(x + (col_w - 0.1)/2, y + box_h_b/2 - 0.05, desc, + ha='center', va='center', fontsize=5) + + # === SECTION γ: CRITERIA === + sec_y3 = 6.5 + ax.text(0.3, sec_y3, 'γ — „Ciężki Sum Ważony Lata, Tardiness Uderza"', + fontsize=8, fontweight='bold', va='top', + style='italic', color='#333333') + + gamma_items = [ + ('Cmax', 'makespan\nmax(Cⱼ)', 'Jak długo\ntrwa WSZYSTKO?', GRAY2), + ('ΣCⱼ', 'suma\nukończeń', 'Średni czas\noczekiwania?', GRAY4), + ('ΣwⱼCⱼ', 'ważona\nsuma', 'Priorytety\nzadań?', GRAY1), + ('Lmax', 'max\nopóźnienie', 'Najgorsze\nspóźnienie?', GRAY2), + ('ΣTⱼ', 'suma\nspóźnień', 'Łączne\nspóźnienia?', GRAY4), + ('ΣUⱼ', 'liczba\nspóźnionych', 'Ile spóźnionych\nzadań?', GRAY1), + ] + + start_y3 = 4.5 + box_h_g = 1.4 + col_w_g = 1.5 + + for i, (symbol, label, question, fill) in enumerate(gamma_items): + x = start_x + i * col_w_g + y = start_y3 + rect = FancyBboxPatch((x, y), col_w_g - 0.1, box_h_g, + boxstyle="round,pad=0.04", + lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + (col_w_g - 0.1)/2, y + box_h_g - 0.1, symbol, + ha='center', va='top', fontsize=9, fontweight='bold') + ax.text(x + (col_w_g - 0.1)/2, y + box_h_g/2 - 0.05, label, + ha='center', va='center', fontsize=6) + ax.text(x + (col_w_g - 0.1)/2, y + 0.15, f'„{question}"', + ha='center', va='bottom', fontsize=5, style='italic') + + # === BOTTOM: Example + Optimal methods === + ex_y = 3.5 + ax.text(0.3, ex_y, 'Przykłady zapisu i optymalne metody:', + fontsize=8, fontweight='bold', va='top') + + examples = [ + ('1 || ΣCⱼ', 'SPT (najkrótsze\nnajpierw)', 'O(n log n)', GRAY1), + ('1 || Lmax', 'EDD (najwcześniejszy\ntermin)', 'O(n log n)', GRAY4), + ('F2 || Cmax', 'Algorytm\nJohnsona', 'O(n log n)', GRAY2), + ('Pm || Cmax', 'LPT heurystyka\n(NP-trudny!)', 'NP-hard', GRAY3), + ('Jm || Cmax', 'Branch & Bound\n(NP-trudny!)', 'NP-hard', GRAY5), + ] + + ex_start_y = 1.8 + ex_box_w = 1.72 + ex_box_h = 1.4 + + for i, (notation, method, complexity, fill) in enumerate(examples): + x = start_x + i * (ex_box_w + 0.1) + y = ex_start_y + rect = FancyBboxPatch((x, y), ex_box_w, ex_box_h, + boxstyle="round,pad=0.04", + lw=1.2, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(x + ex_box_w/2, y + ex_box_h - 0.12, notation, + ha='center', va='top', fontsize=8, fontweight='bold', + fontfamily='monospace') + ax.text(x + ex_box_w/2, y + ex_box_h/2 - 0.05, method, + ha='center', va='center', fontsize=6) + ax.text(x + ex_box_w/2, y + 0.12, complexity, + ha='center', va='bottom', fontsize=6.5, fontweight='bold', + color='#555555') + + # Footer mnemonic summary + ax.text(5.0, 0.8, '„α|β|γ = Maszyny | Ograniczenia | Cel"', + ha='center', fontsize=9, fontweight='bold', style='italic', + color='#333333', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=1)) + + ax.text(5.0, 0.2, 'α: ILE maszyn i JAKIE? β: JAKIE ograniczenia zadań? γ: CO minimalizujemy?', + ha='center', fontsize=7, color='#555555') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_graham_notation.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ scheduling_graham_notation.png") + + +# ============================================================ +# 2. JOHNSON'S ALGORITHM GANTT CHART +# ============================================================ +def draw_johnson_gantt(): + fig, axes = plt.subplots(2, 1, figsize=(8.27, 7), gridspec_kw={'height_ratios': [1, 1.8]}) + + # --- Top: The decision process --- + ax = axes[0] + ax.set_xlim(0, 10) + ax.set_ylim(0, 5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Algorytm Johnsona (F2 || Cmax) — Decyzja + Diagram Gantta', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Task table + tasks = ['J1', 'J2', 'J3', 'J4', 'J5'] + a_times = [4, 2, 6, 1, 3] + b_times = [5, 3, 2, 7, 4] + min_vals = [min(a, b) for a, b in zip(a_times, b_times)] + min_on = ['M1' if a <= b else 'M2' for a, b in zip(a_times, b_times)] + assign = ['POCZątek' if m == 'M1' else 'KONIEC' for m in min_on] + + # Draw table + col_w_t = 1.3 + row_h = 0.55 + headers = ['Zadanie', 'aⱼ (M1)', 'bⱼ (M2)', 'min', 'min na', 'Przydziel'] + table_x = 0.8 + table_y = 3.8 + + for j, hdr in enumerate(headers): + x = table_x + j * col_w_t + rect = mpatches.Rectangle((x, table_y), col_w_t, row_h, + lw=1, edgecolor=LN, facecolor=GRAY2) + ax.add_patch(rect) + ax.text(x + col_w_t/2, table_y + row_h/2, hdr, + ha='center', va='center', fontsize=6.5, fontweight='bold') + + for i in range(5): + row_data = [tasks[i], str(a_times[i]), str(b_times[i]), + str(min_vals[i]), min_on[i], assign[i]] + for j, val in enumerate(row_data): + x = table_x + j * col_w_t + y = table_y - (i + 1) * row_h + fill_c = GRAY1 if min_on[i] == 'M1' else GRAY4 + if j == 3: # min column - highlight + fill_c = GRAY3 + rect = mpatches.Rectangle((x, y), col_w_t, row_h, + lw=0.8, edgecolor=LN, facecolor=fill_c) + ax.add_patch(rect) + fw = 'bold' if j >= 3 else 'normal' + ax.text(x + col_w_t/2, y + row_h/2, val, + ha='center', va='center', fontsize=6.5, fontweight=fw) + + # Sorting result + result_y = 0.7 + ax.text(5.0, result_y + 0.4, 'Sortuj → POCZĄTEK ↑aⱼ: J4(1), J2(2), J5(3), J1(4) | KONIEC ↓bⱼ: J3(2)', + ha='center', fontsize=7, color='#333333') + ax.text(5.0, result_y, 'Optymalna kolejność: J4 → J2 → J5 → J1 → J3', + ha='center', fontsize=9, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY1, edgecolor=LN, lw=1.2)) + + # --- Bottom: Gantt chart --- + ax2 = axes[1] + ax2.set_xlim(-1, 24) + ax2.set_ylim(-1, 4) + ax2.axis('off') + + # Machines labels + m1_y = 2.5 + m2_y = 0.8 + bar_h = 0.9 + + ax2.text(-0.8, m1_y + bar_h/2, 'M1', ha='center', va='center', + fontsize=11, fontweight='bold') + ax2.text(-0.8, m2_y + bar_h/2, 'M2', ha='center', va='center', + fontsize=11, fontweight='bold') + + # Schedule: J4 → J2 → J5 → J1 → J3 + order = ['J4', 'J2', 'J5', 'J1', 'J3'] + a_ord = [1, 2, 3, 4, 6] # M1 times in order + b_ord = [7, 3, 4, 5, 2] # M2 times in order + fills = [GRAY1, GRAY2, GRAY4, GRAY3, GRAY5] + hatches = ['', '///', '', '\\\\\\', 'xxx'] + + # M1 schedule + m1_starts = [] + t = 0 + for a in a_ord: + m1_starts.append(t) + t += a + m1_ends = [s + a for s, a in zip(m1_starts, a_ord)] + + # M2 schedule (must wait for M1 finish AND previous M2 finish) + m2_starts = [] + m2_ends = [] + prev_m2_end = 0 + for i, b in enumerate(b_ord): + start = max(m1_ends[i], prev_m2_end) + m2_starts.append(start) + m2_ends.append(start + b) + prev_m2_end = start + b + + # Draw M1 bars + for i in range(5): + rect = mpatches.Rectangle((m1_starts[i], m1_y), a_ord[i], bar_h, + lw=1.2, edgecolor=LN, facecolor=fills[i], + hatch=hatches[i]) + ax2.add_patch(rect) + ax2.text(m1_starts[i] + a_ord[i]/2, m1_y + bar_h/2, + f'{order[i]}\n({a_ord[i]})', + ha='center', va='center', fontsize=7, fontweight='bold') + + # Draw M2 bars + for i in range(5): + rect = mpatches.Rectangle((m2_starts[i], m2_y), b_ord[i], bar_h, + lw=1.2, edgecolor=LN, facecolor=fills[i], + hatch=hatches[i]) + ax2.add_patch(rect) + ax2.text(m2_starts[i] + b_ord[i]/2, m2_y + bar_h/2, + f'{order[i]}\n({b_ord[i]})', + ha='center', va='center', fontsize=7, fontweight='bold') + + # Draw idle regions on M2 + idle_starts = [0] + idle_ends = [m2_starts[0]] + for i in range(1, 5): + if m2_starts[i] > m2_ends[i-1]: + idle_starts.append(m2_ends[i-1]) + idle_ends.append(m2_starts[i]) + + for s, e in zip(idle_starts, idle_ends): + if e > s: + rect = mpatches.Rectangle((s, m2_y), e - s, bar_h, + lw=0.5, edgecolor='#AAAAAA', + facecolor='white', linestyle='--') + ax2.add_patch(rect) + ax2.text(s + (e-s)/2, m2_y + bar_h/2, 'idle', + ha='center', va='center', fontsize=5, color='#999999') + + # Time axis + ax_y = m2_y - 0.15 + ax2.plot([0, 23], [ax_y, ax_y], color=LN, lw=0.8) + for t in range(0, 24, 2): + ax2.plot([t, t], [ax_y - 0.08, ax_y + 0.08], color=LN, lw=0.8) + ax2.text(t, ax_y - 0.25, str(t), ha='center', va='top', fontsize=6) + ax2.text(11.5, ax_y - 0.55, 'czas', ha='center', fontsize=7) + + # Cmax annotation + ax2.annotate(f'Cmax = {m2_ends[-1]}', xy=(m2_ends[-1], m2_y + bar_h), + xytext=(m2_ends[-1] + 0.5, m2_y + bar_h + 0.6), + fontsize=10, fontweight='bold', color='#333333', + arrowprops=dict(arrowstyle='->', color='#333333', lw=1.5)) + + # Mnemonic at bottom + ax2.text(11, -0.7, + '„Krótki na M1 → START (szybko karmi M2) Krótki na M2 → KONIEC (szybko kończy)"', + ha='center', fontsize=7.5, fontweight='bold', style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=0.8)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_johnson_gantt.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ scheduling_johnson_gantt.png") + + +# ============================================================ +# 3. SPT vs LPT COMPARISON (1 || ΣCⱼ) +# ============================================================ +def draw_spt_comparison(): + fig, axes = plt.subplots(2, 1, figsize=(8.27, 5.5)) + + tasks_orig = [('J1', 5), ('J2', 3), ('J3', 8), ('J4', 2), ('J5', 6)] + + spt_order = sorted(tasks_orig, key=lambda x: x[1]) + lpt_order = sorted(tasks_orig, key=lambda x: -x[1]) + + fills_map = {'J1': GRAY1, 'J2': GRAY2, 'J3': GRAY3, 'J4': GRAY4, 'J5': GRAY5} + hatch_map = {'J1': '', 'J2': '///', 'J3': 'xxx', 'J4': '', 'J5': '\\\\\\'} + + for idx, (ax, order_list, title, is_optimal) in enumerate([ + (axes[0], spt_order, 'SPT (Shortest Processing Time) — OPTYMALNE', True), + (axes[1], lpt_order, 'LPT (Longest Processing Time) — gorsze!', False) + ]): + ax.set_xlim(-2, 26) + ax.set_ylim(-0.5, 2.5) + ax.axis('off') + color = '#222222' if is_optimal else '#666666' + marker = '✓' if is_optimal else '✗' + ax.set_title(f'{marker} {title}', fontsize=9, fontweight='bold', + loc='left', color=color, pad=5) + + bar_y = 1.0 + bar_h = 0.8 + t = 0 + completions = [] + + for name, duration in order_list: + rect = mpatches.Rectangle((t, bar_y), duration, bar_h, + lw=1.2, edgecolor=LN, + facecolor=fills_map[name], + hatch=hatch_map[name]) + ax.add_patch(rect) + ax.text(t + duration/2, bar_y + bar_h/2, f'{name}\n({duration})', + ha='center', va='center', fontsize=7, fontweight='bold') + t += duration + completions.append(t) + + # Completion time marker + ax.plot([t, t], [bar_y - 0.15, bar_y], color=LN, lw=0.8) + ax.text(t, bar_y - 0.25, f'C={t}', ha='center', va='top', + fontsize=6, color='#555555') + + total = sum(completions) + # Time axis + ax.plot([0, 25], [bar_y - 0.05, bar_y - 0.05], color=LN, lw=0.5) + + # Sum annotation + comp_str = ' + '.join(str(c) for c in completions) + ax.text(25, bar_y + bar_h/2, + f'ΣCⱼ = {comp_str}\n = {total}', + ha='left', va='center', fontsize=7, + fontweight='bold' if is_optimal else 'normal', + color=color, + bbox=dict(boxstyle='round,pad=0.2', + facecolor=GRAY1 if is_optimal else 'white', + edgecolor=color, lw=1)) + + # Bottom annotation + fig.text(0.5, 0.02, + '„Short People To the front" — krótkie najpierw, jak niskie osoby w zdjęciu klasowym', + ha='center', fontsize=8, fontweight='bold', style='italic', + color='#444444') + + plt.tight_layout(rect=[0, 0.05, 1, 1]) + plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_spt_comparison.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ scheduling_spt_comparison.png") + + +# ============================================================ +# 4. FLOW SHOP vs JOB SHOP +# ============================================================ +def draw_flow_vs_job(): + fig, axes = plt.subplots(1, 2, figsize=(8.27, 4.5)) + + # --- LEFT: Flow Shop --- + ax = axes[0] + ax.set_xlim(0, 6) + ax.set_ylim(0, 6) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Flow Shop (Fm)', fontsize=10, fontweight='bold', pad=8) + + # Machines in a row + machines_x = [1, 3, 5] + machines_y = 3 + mach_r = 0.4 + + for i, mx in enumerate(machines_x): + circle = plt.Circle((mx, machines_y), mach_r, + facecolor=GRAY2, edgecolor=LN, lw=1.5) + ax.add_patch(circle) + ax.text(mx, machines_y, f'M{i+1}', ha='center', va='center', + fontsize=9, fontweight='bold') + + # Arrows between machines + for i in range(len(machines_x) - 1): + draw_arrow(ax, machines_x[i] + mach_r + 0.05, machines_y, + machines_x[i+1] - mach_r - 0.05, machines_y, lw=2) + + # Jobs all flowing the same way + jobs_flow = ['J1', 'J2', 'J3'] + for j, (job, y_off) in enumerate(zip(jobs_flow, [0.8, 0, -0.8])): + ax.text(0.2, machines_y + y_off, job, ha='center', va='center', + fontsize=7, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY1, edgecolor=LN)) + # Dashed flow line + ax.annotate("", xy=(5.5, machines_y + y_off * 0.3), + xytext=(0.5, machines_y + y_off), + arrowprops=dict(arrowstyle='->', color='#888888', lw=0.8, + linestyle='dashed')) + + ax.text(3, 1.2, 'Wszystkie zadania:\nM1 → M2 → M3', + ha='center', va='center', fontsize=8, + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.text(3, 0.4, 'Jak taśma montażowa', + ha='center', fontsize=7, style='italic', color='#666666') + + # --- RIGHT: Job Shop --- + ax = axes[1] + ax.set_xlim(0, 6) + ax.set_ylim(0, 6) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Job Shop (Jm)', fontsize=10, fontweight='bold', pad=8) + + # Machines scattered + m_positions = [(1.5, 4.2), (4.5, 4.2), (3, 2.5)] + + for i, (mx, my) in enumerate(m_positions): + circle = plt.Circle((mx, my), mach_r, + facecolor=GRAY2, edgecolor=LN, lw=1.5) + ax.add_patch(circle) + ax.text(mx, my, f'M{i+1}', ha='center', va='center', + fontsize=9, fontweight='bold') + + # J1: M1 → M2 → M3 (solid) + route1 = [(1.5, 4.2), (4.5, 4.2), (3, 2.5)] + for i in range(len(route1) - 1): + x1, y1 = route1[i] + x2, y2 = route1[i+1] + dx = x2 - x1 + dy = y2 - y1 + d = (dx**2 + dy**2)**0.5 + draw_arrow(ax, x1 + mach_r * dx/d + 0.05, y1 + mach_r * dy/d, + x2 - mach_r * dx/d - 0.05, y2 - mach_r * dy/d, + lw=1.5) + ax.text(0.3, 4.8, 'J1: M1→M2→M3', fontsize=7, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY1, edgecolor=LN)) + + # J2: M2 → M3 → M1 (dashed) + route2 = [(4.5, 4.2), (3, 2.5), (1.5, 4.2)] + for i in range(len(route2) - 1): + x1, y1 = route2[i] + x2, y2 = route2[i+1] + dx = x2 - x1 + dy = y2 - y1 + d = (dx**2 + dy**2)**0.5 + off = 0.15 # offset to avoid overlap + ax.annotate("", xy=(x2 - mach_r * dx/d - 0.05, y2 - mach_r * dy/d + off), + xytext=(x1 + mach_r * dx/d + 0.05, y1 + mach_r * dy/d + off), + arrowprops=dict(arrowstyle='->', color='#555555', lw=1.5, + linestyle='dashed')) + ax.text(3.8, 5.2, 'J2: M2→M3→M1', fontsize=7, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.1', facecolor=GRAY4, edgecolor=LN)) + + ax.text(3, 1.2, 'Każde zadanie:\nwłasna trasa!', + ha='center', va='center', fontsize=8, + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + ax.text(3, 0.4, 'NP-trudny już dla 3 maszyn', + ha='center', fontsize=7, style='italic', color='#666666') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_flow_vs_job.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ scheduling_flow_vs_job.png") + + +# ============================================================ +# 5. SCHEDULING COMPLEXITY LANDSCAPE +# ============================================================ +def draw_complexity_map(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 5)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 7) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Złożoność problemów szeregowania — od łatwych do NP-trudnych', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Gradient arrow at the top + ax.annotate("", xy=(9.5, 6.2), xytext=(0.5, 6.2), + arrowprops=dict(arrowstyle='->', color=LN, lw=2)) + ax.text(5, 6.5, 'Rosnąca złożoność', ha='center', fontsize=9, + fontweight='bold') + + # Easy (polynomial) region + easy_rect = FancyBboxPatch((0.3, 2.8), 4.0, 3.0, + boxstyle="round,pad=0.15", + lw=1.5, edgecolor='#666666', + facecolor=GRAY4, linestyle='-') + ax.add_patch(easy_rect) + ax.text(2.3, 5.5, 'WIELOMIANOWE O(n log n)', ha='center', + fontsize=9, fontweight='bold', color='#444444') + + easy_problems = [ + ('1 || ΣCⱼ', 'SPT', GRAY1, 4.8), + ('1 || Lmax', 'EDD', GRAY2, 4.0), + ('F2 || Cmax', 'Johnson', GRAY1, 3.2), + ] + for prob, method, fill, y in easy_problems: + rect = FancyBboxPatch((0.6, y), 3.5, 0.6, + boxstyle="round,pad=0.05", + lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(1.2, y + 0.3, prob, ha='center', va='center', + fontsize=8, fontweight='bold', fontfamily='monospace') + ax.text(3.0, y + 0.3, f'→ {method}', ha='center', va='center', + fontsize=8) + + # Hard (NP) region + hard_rect = FancyBboxPatch((5.3, 2.8), 4.3, 3.0, + boxstyle="round,pad=0.15", + lw=1.5, edgecolor='#444444', + facecolor=GRAY3, linestyle='-') + ax.add_patch(hard_rect) + ax.text(7.45, 5.5, 'NP-TRUDNE', ha='center', + fontsize=9, fontweight='bold', color='#333333') + + hard_problems = [ + ('Pm || Cmax\n(m≥2)', 'LPT heuryst.', GRAY2, 4.5), + ('1 || ΣTⱼ', 'branch&bound', GRAY4, 3.7), + ('Jm || Cmax\n(m≥3)', 'metaheuryst.', GRAY5, 2.9), + ] + for prob, method, fill, y in hard_problems: + rect = FancyBboxPatch((5.6, y), 3.7, 0.7, + boxstyle="round,pad=0.05", + lw=1, edgecolor=LN, facecolor=fill) + ax.add_patch(rect) + ax.text(6.5, y + 0.35, prob, ha='center', va='center', + fontsize=7, fontweight='bold', fontfamily='monospace') + ax.text(8.2, y + 0.35, f'→ {method}', ha='center', va='center', + fontsize=7) + + # Arrow connecting + draw_arrow(ax, 4.4, 4.0, 5.2, 4.0, lw=2, color='#888888') + ax.text(4.8, 4.25, '+1\nmaszyna', ha='center', fontsize=6, color='#888888') + + # Bottom: key insight + ax.text(5.0, 1.8, '„Dodanie jednej maszyny lub jednego ograniczenia\n' + 'może zmienić problem z łatwego na NP-trudny!"', + ha='center', fontsize=8, fontweight='bold', style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=1)) + + # Bottom examples + ax.text(5.0, 0.8, + '1 maszyna → łatwe (sortuj) | ≥2 maszyny równoległe → NP-trudne\n' + 'Flow shop 2 maszyny → Johnson O(n log n) | Flow shop 3 maszyny → NP-trudne', + ha='center', fontsize=7, color='#555555') + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_complexity_map.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ scheduling_complexity_map.png") + + +# ============================================================ +# 6. EDD EXAMPLE (1 || Lmax) +# ============================================================ +def draw_edd_example(): + fig, ax = plt.subplots(1, 1, figsize=(8.27, 4)) + ax.set_xlim(-2, 28) + ax.set_ylim(-2, 4) + ax.axis('off') + ax.set_title('EDD (Earliest Due Date) — 1 || Lmax — Przykład', + fontsize=FS_TITLE, fontweight='bold', pad=8) + + # Tasks: name, processing time, due date + tasks = [('J1', 4, 10), ('J2', 2, 6), ('J3', 6, 15), ('J4', 3, 8), ('J5', 5, 18)] + # EDD: sort by due date + edd_order = sorted(tasks, key=lambda x: x[2]) + + bar_y = 1.5 + bar_h = 0.8 + t = 0 + fills_edd = [GRAY1, GRAY2, GRAY4, GRAY3, GRAY5] + + lateness_vals = [] + for i, (name, p, d) in enumerate(edd_order): + rect = mpatches.Rectangle((t, bar_y), p, bar_h, + lw=1.2, edgecolor=LN, facecolor=fills_edd[i]) + ax.add_patch(rect) + ax.text(t + p/2, bar_y + bar_h/2, f'{name}\np={p}, d={d}', + ha='center', va='center', fontsize=6.5, fontweight='bold') + t += p + L = t - d + lateness_vals.append(L) + + # Due date marker + ax.plot([d, d], [bar_y - 0.4, bar_y - 0.1], color='#888888', + lw=0.8, linestyle='--') + ax.text(d, bar_y - 0.5, f'd={d}', ha='center', va='top', + fontsize=5.5, color='#888888') + + # Completion + lateness + ax.plot([t, t], [bar_y + bar_h, bar_y + bar_h + 0.15], color=LN, lw=0.8) + ax.text(t, bar_y + bar_h + 0.2, f'C={t}\nL={L}', + ha='center', va='bottom', fontsize=5.5) + + # Time axis + ax.plot([0, 22], [bar_y - 0.05, bar_y - 0.05], color=LN, lw=0.5) + + Lmax = max(lateness_vals) + ax.text(22, bar_y + bar_h/2, f'Lmax = {Lmax}', + ha='left', va='center', fontsize=10, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.2', facecolor=GRAY1, edgecolor=LN)) + + # Bottom mnemonic + ax.text(10, -1.3, '„Early Due Date Does it first" — najpilniejszy deadline idzie pierwszy', + ha='center', fontsize=8, fontweight='bold', style='italic', + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=0.8)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'scheduling_edd_example.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ scheduling_edd_example.png") + + +# ============================================================ +# MAIN +# ============================================================ +if __name__ == '__main__': + print("Generating scheduling diagrams for PYTANIE 17...") + draw_graham_notation() + draw_johnson_gantt() + draw_spt_comparison() + draw_flow_vs_job() + draw_complexity_map() + draw_edd_example() + print("Done! All diagrams saved to:", OUTPUT_DIR) diff --git a/pytania/generate_shortest_path_diagrams.py b/pytania/generate_shortest_path_diagrams.py new file mode 100644 index 0000000..113b301 --- /dev/null +++ b/pytania/generate_shortest_path_diagrams.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python3 +""" +Generate diagrams for PYTANIE 2: Shortest path algorithms. + 1. Graph structure — the shared example graph (A,B,C,D) + 2. Dijkstra traversal — step-by-step on that graph + 3. Bellman-Ford traversal — step-by-step + 4. A* traversal — step-by-step with heuristics + +All: A4-compatible, B&W, 300 DPI, laser-printer-friendly. +""" + +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from matplotlib.patches import FancyBboxPatch +import numpy as np +import os + +DPI = 300 +BG = 'white' +LN = 'black' +FS = 8 +FS_TITLE = 11 +FS_SMALL = 6.5 +FS_EDGE = 9 +OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +os.makedirs(OUTPUT_DIR, exist_ok=True) + +GRAY1 = '#E8E8E8' +GRAY2 = '#D0D0D0' +GRAY3 = '#B8B8B8' +GRAY4 = '#F5F5F5' +GRAY5 = '#C0C0C0' +LIGHT_GREEN = '#D5E8D4' +LIGHT_RED = '#F8D7DA' +LIGHT_BLUE = '#D6EAF8' +LIGHT_YELLOW = '#FFF9C4' +LIGHT_ORANGE = '#FFE0B2' +LIGHT_PURPLE = '#E8D5F5' + +# --- Shared graph layout --- +# Graph: A--2--B--3--D, A--4--C--5--D, D--1--C (directed: C->D has weight 5) +NODE_POS = {'A': (1, 2), 'B': (3.5, 3.2), 'C': (1, 0), 'D': (3.5, 0.8)} +EDGES = [('A', 'B', 2), ('A', 'C', 4), ('B', 'D', 3), ('C', 'D', 5)] + + +def draw_graph_node(ax, name, pos, color='white', current=False, visited=False, + dist_label=None, fontsize=12): + """Draw a graph node (circle with label).""" + x, y = pos + r = 0.35 + lw = 2.5 if current else 1.5 + ec = '#D32F2F' if current else LN + fc = LIGHT_GREEN if visited else color + if current: + fc = LIGHT_YELLOW + + circle = plt.Circle((x, y), r, fill=True, facecolor=fc, + edgecolor=ec, linewidth=lw, zorder=5) + ax.add_patch(circle) + ax.text(x, y, name, ha='center', va='center', fontsize=fontsize, + fontweight='bold', zorder=6) + + if dist_label is not None: + ax.text(x, y - 0.55, f'd={dist_label}', ha='center', va='center', + fontsize=FS, zorder=6, + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', + edgecolor=GRAY3, alpha=0.95)) + + +def draw_graph_edge(ax, pos1, pos2, weight, highlighted=False, relaxed=False): + """Draw an edge between two nodes with weight label.""" + x1, y1 = pos1 + x2, y2 = pos2 + + # Shorten line to not overlap node circles + dx, dy = x2 - x1, y2 - y1 + length = np.sqrt(dx**2 + dy**2) + r = 0.38 + sx = x1 + r * dx / length + sy = y1 + r * dy / length + ex = x2 - r * dx / length + ey = y2 - r * dy / length + + color = '#D32F2F' if relaxed else ('#1565C0' if highlighted else GRAY3) + lw = 2.5 if (highlighted or relaxed) else 1.5 + ls = '-' + + ax.plot([sx, ex], [sy, ey], color=color, linewidth=lw, linestyle=ls, zorder=2) + + # Weight label + mx = (x1 + x2) / 2 + my = (y1 + y2) / 2 + # Offset perpendicular to edge + perp_x = -dy / length * 0.2 + perp_y = dx / length * 0.2 + + ax.text(mx + perp_x, my + perp_y, str(weight), ha='center', va='center', + fontsize=FS_EDGE, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', + edgecolor=GRAY3 if not highlighted else color, alpha=0.95), + zorder=4) + + +def draw_full_graph(ax, title="", dist=None, current=None, visited=None, + highlighted_edges=None, relaxed_edges=None): + """Draw the complete graph with optional highlighting.""" + if visited is None: + visited = set() + if highlighted_edges is None: + highlighted_edges = set() + if relaxed_edges is None: + relaxed_edges = set() + if dist is None: + dist = {} + + ax.set_xlim(-0.2, 4.7) + ax.set_ylim(-0.8, 4.2) + ax.set_aspect('equal') + ax.axis('off') + if title: + ax.set_title(title, fontsize=FS, fontweight='bold', pad=5) + + # Draw edges + for u, v, w in EDGES: + hl = (u, v) in highlighted_edges or (v, u) in highlighted_edges + rl = (u, v) in relaxed_edges or (v, u) in relaxed_edges + draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w, + highlighted=hl, relaxed=rl) + + # Draw nodes + for name, pos in NODE_POS.items(): + is_current = (name == current) + is_visited = (name in visited) + d_label = dist.get(name, None) + draw_graph_node(ax, name, pos, current=is_current, visited=is_visited, + dist_label=d_label) + + +# ============================================================ +# 1. Graph structure diagram +# ============================================================ +def draw_graph_structure(): + """The shared example graph used across all three algorithms.""" + fig, ax = plt.subplots(1, 1, figsize=(5, 4)) + ax.set_xlim(-0.5, 5.0) + ax.set_ylim(-1.2, 4.5) + ax.set_aspect('equal') + ax.axis('off') + ax.set_title('Przykładowy graf — wspólny dla wszystkich algorytmów\n' + 'Wierzchołki: {A, B, C, D}, Start = A', + fontsize=FS_TITLE, fontweight='bold', pad=10) + + # Draw edges + for u, v, w in EDGES: + draw_graph_edge(ax, NODE_POS[u], NODE_POS[v], w) + + # Draw nodes + for name, pos in NODE_POS.items(): + draw_graph_node(ax, name, pos) + + # Start arrow + ax.annotate("START", xy=(NODE_POS['A'][0] - 0.35, NODE_POS['A'][1]), + xytext=(NODE_POS['A'][0] - 1.2, NODE_POS['A'][1]), + fontsize=FS, fontweight='bold', color='#D32F2F', + arrowprops=dict(arrowstyle='->', color='#D32F2F', lw=2), + va='center') + + # Edge list + ax.text(2.3, -0.8, 'Krawędzie: A→B(2), A→C(4), B→D(3), C→D(5)\n' + '|V|=4, |E|=4, wagi ≥ 0', + ha='center', va='center', fontsize=FS, + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'graph_example_structure.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ graph_example_structure.png") + + +# ============================================================ +# 2. Dijkstra traversal +# ============================================================ +def draw_dijkstra_traversal(): + """Step-by-step Dijkstra on the shared graph.""" + steps = [ + { + 'title': 'Krok 0: Inicjalizacja\nd = {A:0, B:∞, C:∞, D:∞}', + 'dist': {'A': '0', 'B': '∞', 'C': '∞', 'D': '∞'}, + 'current': 'A', 'visited': set(), + 'highlighted': set(), 'relaxed': set(), + }, + { + 'title': 'Krok 1: Przetwarzam A (d=0)\nRelaksacja: A→B: 0+2=2<∞ ✓ A→C: 0+4=4<∞ ✓', + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '∞'}, + 'current': 'A', 'visited': {'A'}, + 'highlighted': set(), 'relaxed': {('A', 'B'), ('A', 'C')}, + }, + { + 'title': 'Krok 2: Przetwarzam B (d=2) — minimum\nRelaksacja: B→D: 2+3=5<∞ ✓', + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'current': 'B', 'visited': {'A', 'B'}, + 'highlighted': set(), 'relaxed': {('B', 'D')}, + }, + { + 'title': 'Krok 3: Przetwarzam C (d=4)\nRelaksacja: C→D: 4+5=9 > 5 ✗ (nie poprawia)', + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'current': 'C', 'visited': {'A', 'B', 'C'}, + 'highlighted': {('C', 'D')}, 'relaxed': set(), + }, + { + 'title': 'Krok 4: WYNIK — wszystkie przetworzone\nd = {A:0, B:2, C:4, D:5}', + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'current': None, 'visited': {'A', 'B', 'C', 'D'}, + 'highlighted': {('A', 'B'), ('B', 'D'), ('A', 'C')}, 'relaxed': set(), + }, + ] + + fig, axes = plt.subplots(1, 5, figsize=(14, 3.5)) + fig.suptitle('Dijkstra — przejście grafu krok po kroku (zachłannie: zawsze bierz min d)', + fontsize=FS_TITLE, fontweight='bold', y=1.02) + + for i, (ax, step) in enumerate(zip(axes, steps)): + draw_full_graph(ax, title=step['title'], + dist=step['dist'], current=step['current'], + visited=step['visited'], + highlighted_edges=step['highlighted'], + relaxed_edges=step['relaxed']) + + # Legend + fig.text(0.5, -0.04, + '[zolty] = aktualnie przetwarzany [zielony] = odwiedzony (zamkniety) ' + 'czerwona krawedz = relaksacja OK szara krawedz = nie poprawia', + ha='center', fontsize=FS, + bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3)) + + plt.tight_layout() + plt.savefig(os.path.join(OUTPUT_DIR, 'dijkstra_traversal.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ dijkstra_traversal.png") + + +# ============================================================ +# 3. Bellman-Ford traversal +# ============================================================ +def draw_bellman_ford_traversal(): + """Step-by-step Bellman-Ford on the shared graph.""" + fig = plt.figure(figsize=(14, 7)) + fig.suptitle('Bellman-Ford — przejście grafu krok po kroku\n' + '(V−1 = 3 iteracje, w każdej relaksuj WSZYSTKIE krawędzie)', + fontsize=FS_TITLE, fontweight='bold', y=0.98) + + # Data for each iteration + iterations = [ + { + 'title': 'Inicjalizacja', + 'edges_detail': '—', + 'dist': {'A': '0', 'B': '∞', 'C': '∞', 'D': '∞'}, + 'relaxed': set(), + }, + { + 'title': 'Iteracja 1 (V−1=3)', + 'edges_detail': ( + 'A→B: 0+2=2<∞ ✓\n' + 'A→C: 0+4=4<∞ ✓\n' + 'B→D: 2+3=5<∞ ✓\n' + 'C→D: 4+5=9>5 ✗' + ), + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'relaxed': {('A', 'B'), ('A', 'C'), ('B', 'D')}, + }, + { + 'title': 'Iteracja 2', + 'edges_detail': ( + 'A→B: 0+2=2=2 ✗\n' + 'A→C: 0+4=4=4 ✗\n' + 'B→D: 2+3=5=5 ✗\n' + 'C→D: 4+5=9>5 ✗' + ), + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'relaxed': set(), + }, + { + 'title': 'Iteracja 3', + 'edges_detail': ( + 'Brak zmian → stabilne!\n' + '(wczesne zakończenie\n' + ' optymalizacja)' + ), + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'relaxed': set(), + }, + ] + + for i, it in enumerate(iterations): + # Graph subplot + ax_g = fig.add_subplot(2, 4, i + 1) + draw_full_graph(ax_g, title=it['title'], + dist=it['dist'], current=None, + visited=set() if i == 0 else {'A', 'B', 'C', 'D'}, + relaxed_edges=it['relaxed']) + + # Detail subplot below + ax_d = fig.add_subplot(2, 4, i + 5) + ax_d.axis('off') + ax_d.text(0.5, 0.5, it['edges_detail'], ha='center', va='center', + fontsize=FS, family='monospace', + bbox=dict(boxstyle='round,pad=0.4', facecolor=GRAY4, + edgecolor=GRAY3)) + + # Negative cycle check note + fig.text(0.5, 0.01, + 'Po 3 iteracjach: sprawdź raz jeszcze — nic się nie zmienia → BRAK cyklu ujemnego → wynik poprawny', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_GREEN, edgecolor=LN)) + + plt.tight_layout(rect=[0, 0.05, 1, 0.95]) + plt.savefig(os.path.join(OUTPUT_DIR, 'bellman_ford_traversal.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ bellman_ford_traversal.png") + + +# ============================================================ +# 4. A* traversal +# ============================================================ +def draw_astar_traversal(): + """Step-by-step A* on the shared graph with heuristics.""" + # Heuristic values (straight-line distance to D) + h_vals = {'A': 4, 'B': 2, 'C': 3, 'D': 0} + + fig = plt.figure(figsize=(14, 7.5)) + fig.suptitle('A* — przejście grafu krok po kroku (cel = D)\n' + 'f(n) = g(n) + h(n), heurystyka h = oszacowana odległość do D', + fontsize=FS_TITLE, fontweight='bold', y=0.99) + + steps = [ + { + 'title': 'Krok 0: Inicjalizacja\nh(A)=4, h(B)=2, h(C)=3, h(D)=0', + 'detail': ( + 'g(A)=0, f(A)=0+4=4\n' + 'pq = [(4, A)]\n' + 'h = oszacowanie\n' + ' odl. do celu D' + ), + 'dist': {'A': '0'}, + 'f_vals': {'A': 'f=4'}, + 'current': 'A', 'visited': set(), 'relaxed': set(), + }, + { + 'title': 'Krok 1: pop A (f=4)\nA→B: g=2, f=2+2=4\nA→C: g=4, f=4+3=7', + 'detail': ( + 'Relaksacja:\n' + ' A→B: g=0+2=2\n' + ' f=2+h(B)=2+2=4\n' + ' A→C: g=0+4=4\n' + ' f=4+h(C)=4+3=7\n' + 'pq = [(4,B), (7,C)]' + ), + 'dist': {'A': '0', 'B': '2', 'C': '4'}, + 'current': 'A', 'visited': {'A'}, + 'relaxed': {('A', 'B'), ('A', 'C')}, + }, + { + 'title': 'Krok 2: pop B (f=4) — min!\nB→D: g=5, f=5+0=5', + 'detail': ( + 'B ma f=4 < C(f=7)\n' + '→ A* kieruje się\n' + ' W STRONĘ celu!\n' + 'Relaksacja:\n' + ' B→D: g=2+3=5\n' + ' f=5+h(D)=5+0=5\n' + 'pq = [(5,D), (7,C)]' + ), + 'dist': {'A': '0', 'B': '2', 'C': '4', 'D': '5'}, + 'current': 'B', 'visited': {'A', 'B'}, + 'relaxed': {('B', 'D')}, + }, + { + 'title': 'Krok 3: pop D (f=5)\nu == goal → STOP!', + 'detail': ( + 'D to CEL → KONIEC!\n' + 'Nie przetwarzamy C\n' + ' (f(C)=7 > f(D)=5)\n\n' + 'Ścieżka: A→B→D\n' + 'Koszt: 5\n\n' + 'Dijkstra odwi-\n' + 'edziłby też C!' + ), + 'dist': {'A': '0', 'B': '2', 'D': '5'}, + 'current': 'D', 'visited': {'A', 'B', 'D'}, + 'relaxed': set(), + }, + ] + + for i, step in enumerate(steps): + # Graph + ax_g = fig.add_subplot(2, 4, i + 1) + draw_full_graph(ax_g, title=step['title'], + dist=step['dist'], current=step['current'], + visited=step['visited'], + relaxed_edges=step['relaxed']) + + # Add h values as small labels + for name, pos in NODE_POS.items(): + ax_g.text(pos[0] + 0.35, pos[1] + 0.35, f'h={h_vals[name]}', + ha='center', va='center', fontsize=5.5, color='#1565C0', + fontweight='bold', zorder=7, + bbox=dict(boxstyle='round,pad=0.1', facecolor=LIGHT_BLUE, + edgecolor='#1565C0', alpha=0.9, lw=0.5)) + + # Detail + ax_d = fig.add_subplot(2, 4, i + 5) + ax_d.axis('off') + ax_d.text(0.5, 0.5, step['detail'], ha='center', va='center', + fontsize=FS, family='monospace', + bbox=dict(boxstyle='round,pad=0.4', facecolor=GRAY4, + edgecolor=GRAY3)) + + # Comparison note + fig.text(0.5, 0.01, + 'A* odwiedził 3 wierzchołki (A, B, D) — POMINĄŁ C!\n' + 'Dijkstra odwiedziłby wszystkie 4. Heurystyka h kieruje przeszukiwanie w stronę celu.', + ha='center', fontsize=FS, fontweight='bold', + bbox=dict(boxstyle='round,pad=0.3', facecolor=LIGHT_BLUE, edgecolor='#1565C0')) + + plt.tight_layout(rect=[0, 0.06, 1, 0.95]) + plt.savefig(os.path.join(OUTPUT_DIR, 'astar_traversal.png'), + dpi=DPI, bbox_inches='tight', facecolor=BG) + plt.close() + print(" ✓ astar_traversal.png") + + +# ============================================================ +# Main +# ============================================================ +if __name__ == '__main__': + print("Generating shortest path diagrams for PYTANIE 2...") + draw_graph_structure() + draw_dijkstra_traversal() + draw_bellman_ford_traversal() + draw_astar_traversal() + print(f"\nAll diagrams saved to {OUTPUT_DIR}/") diff --git a/pytania/img/astar_traversal.png b/pytania/img/astar_traversal.png new file mode 100644 index 0000000..5e0b4c1 Binary files /dev/null and b/pytania/img/astar_traversal.png differ diff --git a/pytania/img/bellman_ford_traversal.png b/pytania/img/bellman_ford_traversal.png new file mode 100644 index 0000000..c15fefe Binary files /dev/null and b/pytania/img/bellman_ford_traversal.png differ diff --git a/pytania/img/cpm_example.png b/pytania/img/cpm_example.png new file mode 100644 index 0000000..fe91a2b Binary files /dev/null and b/pytania/img/cpm_example.png differ diff --git a/pytania/img/deadlock_illustration.png b/pytania/img/deadlock_illustration.png new file mode 100644 index 0000000..e94ce84 Binary files /dev/null and b/pytania/img/deadlock_illustration.png differ diff --git a/pytania/img/dijkstra_traversal.png b/pytania/img/dijkstra_traversal.png new file mode 100644 index 0000000..2540c6b Binary files /dev/null and b/pytania/img/dijkstra_traversal.png differ diff --git a/pytania/img/fa_recognition_example.png b/pytania/img/fa_recognition_example.png new file mode 100644 index 0000000..51d1721 Binary files /dev/null and b/pytania/img/fa_recognition_example.png differ diff --git a/pytania/img/ford_fulkerson_example.png b/pytania/img/ford_fulkerson_example.png new file mode 100644 index 0000000..7c1d56f Binary files /dev/null and b/pytania/img/ford_fulkerson_example.png differ diff --git a/pytania/img/graph_example_structure.png b/pytania/img/graph_example_structure.png new file mode 100644 index 0000000..2995f28 Binary files /dev/null and b/pytania/img/graph_example_structure.png differ diff --git a/pytania/img/hungarian_example.png b/pytania/img/hungarian_example.png new file mode 100644 index 0000000..4547185 Binary files /dev/null and b/pytania/img/hungarian_example.png differ diff --git a/pytania/img/ipc_mechanisms.png b/pytania/img/ipc_mechanisms.png new file mode 100644 index 0000000..6a723be Binary files /dev/null and b/pytania/img/ipc_mechanisms.png differ diff --git a/pytania/img/kruskal_example.png b/pytania/img/kruskal_example.png new file mode 100644 index 0000000..4918862 Binary files /dev/null and b/pytania/img/kruskal_example.png differ diff --git a/pytania/img/lba_recognition_example.png b/pytania/img/lba_recognition_example.png new file mode 100644 index 0000000..3f11a14 Binary files /dev/null and b/pytania/img/lba_recognition_example.png differ diff --git a/pytania/img/min_cost_flow_example.png b/pytania/img/min_cost_flow_example.png new file mode 100644 index 0000000..e85d255 Binary files /dev/null and b/pytania/img/min_cost_flow_example.png differ diff --git a/pytania/img/nf_0nf_table.png b/pytania/img/nf_0nf_table.png new file mode 100644 index 0000000..ac8bbd1 Binary files /dev/null and b/pytania/img/nf_0nf_table.png differ diff --git a/pytania/img/nf_1nf_tables.png b/pytania/img/nf_1nf_tables.png new file mode 100644 index 0000000..e6eab68 Binary files /dev/null and b/pytania/img/nf_1nf_tables.png differ diff --git a/pytania/img/nf_2nf_tables.png b/pytania/img/nf_2nf_tables.png new file mode 100644 index 0000000..751aa6f Binary files /dev/null and b/pytania/img/nf_2nf_tables.png differ diff --git a/pytania/img/nf_3nf_tables.png b/pytania/img/nf_3nf_tables.png new file mode 100644 index 0000000..0ff6858 Binary files /dev/null and b/pytania/img/nf_3nf_tables.png differ diff --git a/pytania/img/nf_4nf_example.png b/pytania/img/nf_4nf_example.png new file mode 100644 index 0000000..98fae7f Binary files /dev/null and b/pytania/img/nf_4nf_example.png differ diff --git a/pytania/img/nf_5nf_example.png b/pytania/img/nf_5nf_example.png new file mode 100644 index 0000000..d8f218a Binary files /dev/null and b/pytania/img/nf_5nf_example.png differ diff --git a/pytania/img/nf_bcnf_tables.png b/pytania/img/nf_bcnf_tables.png new file mode 100644 index 0000000..49d9714 Binary files /dev/null and b/pytania/img/nf_bcnf_tables.png differ diff --git a/pytania/img/nf_summary_flow.png b/pytania/img/nf_summary_flow.png new file mode 100644 index 0000000..e36a997 Binary files /dev/null and b/pytania/img/nf_summary_flow.png differ diff --git a/pytania/img/pda_recognition_example.png b/pytania/img/pda_recognition_example.png new file mode 100644 index 0000000..8c7da4a Binary files /dev/null and b/pytania/img/pda_recognition_example.png differ diff --git a/pytania/img/producer_consumer.png b/pytania/img/producer_consumer.png new file mode 100644 index 0000000..5190202 Binary files /dev/null and b/pytania/img/producer_consumer.png differ diff --git a/pytania/img/scheduling_complexity_map.png b/pytania/img/scheduling_complexity_map.png new file mode 100644 index 0000000..5d0a360 Binary files /dev/null and b/pytania/img/scheduling_complexity_map.png differ diff --git a/pytania/img/scheduling_edd_example.png b/pytania/img/scheduling_edd_example.png new file mode 100644 index 0000000..cf7b8a7 Binary files /dev/null and b/pytania/img/scheduling_edd_example.png differ diff --git a/pytania/img/scheduling_flow_vs_job.png b/pytania/img/scheduling_flow_vs_job.png new file mode 100644 index 0000000..0e54c4b Binary files /dev/null and b/pytania/img/scheduling_flow_vs_job.png differ diff --git a/pytania/img/scheduling_graham_notation.png b/pytania/img/scheduling_graham_notation.png new file mode 100644 index 0000000..b8efdae Binary files /dev/null and b/pytania/img/scheduling_graham_notation.png differ diff --git a/pytania/img/scheduling_johnson_gantt.png b/pytania/img/scheduling_johnson_gantt.png new file mode 100644 index 0000000..4ddebdc Binary files /dev/null and b/pytania/img/scheduling_johnson_gantt.png differ diff --git a/pytania/img/scheduling_spt_comparison.png b/pytania/img/scheduling_spt_comparison.png new file mode 100644 index 0000000..c34b1e6 Binary files /dev/null and b/pytania/img/scheduling_spt_comparison.png differ diff --git a/pytania/img/tm_recognition_example.png b/pytania/img/tm_recognition_example.png new file mode 100644 index 0000000..a225fe4 Binary files /dev/null and b/pytania/img/tm_recognition_example.png differ diff --git a/pytania/img/tsp_nearest_neighbor.png b/pytania/img/tsp_nearest_neighbor.png new file mode 100644 index 0000000..540eeac Binary files /dev/null and b/pytania/img/tsp_nearest_neighbor.png differ