Skip to content

Commit b6492ee

Browse files
committed
feat: some utils for testing only, drawing and some migration
1 parent f14fa49 commit b6492ee

5 files changed

Lines changed: 126 additions & 15 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from graphviz import Digraph
2+
from .fsa import FSA, Transition
3+
4+
import matplotlib.pyplot as plt
5+
import networkx as nx
6+
7+
# plt is not thread safe, but graphviz is harder to install for cross-env compiles
8+
# this is sequentially safe anyway
9+
# notice that if relative path, it is relative to where you execute the code,
10+
# and invalid fsa always look weird as nx+plt cannot find a equilibrium
11+
def draw_fsa(fsa:FSA, show=False, save=True, filename="./fsa.png"):
12+
# 1. Initialize Graph and collect all possible states
13+
G = nx.MultiDiGraph()
14+
15+
# Start with declared states, but use a set to allow for "orphans"
16+
all_states = set(fsa.states)
17+
18+
# Always include initial and accept states in the set (even if missing from fsa.states)
19+
if fsa.initial_state:
20+
all_states.add(fsa.initial_state)
21+
all_states.update(fsa.accept_states)
22+
23+
# 2. Add Transitions and discover states used in transitions
24+
for t in fsa.transitions:
25+
all_states.add(t.from_state)
26+
all_states.add(t.to_state)
27+
G.add_edge(t.from_state, t.to_state, label=t.symbol)
28+
29+
# Ensure all discovered states are in the graph
30+
G.add_nodes_from(all_states)
31+
32+
# 3. Layout and Figure
33+
# Kamada-Kawai is more stable than Spring for state machines
34+
pos = nx.kamada_kawai_layout(G)
35+
plt.figure(figsize=(10, 7))
36+
37+
# 4. Draw Nodes with specific styling
38+
for node in G.nodes():
39+
# Default Style
40+
color = 'skyblue'
41+
linewidth = 1.0
42+
edgecolor = 'black'
43+
label_suffix = ""
44+
45+
# Initial State Styling (Green fill)
46+
if node == fsa.initial_state:
47+
color = '#90ee90' # Light green
48+
label_suffix = "\n(start)"
49+
50+
# Accept State Styling (Double-circle effect/Thick border)
51+
if node in fsa.accept_states:
52+
linewidth = 4.0
53+
edgecolor = '#ff4500' # Orange-red border for visibility
54+
55+
nx.draw_networkx_nodes(
56+
G, pos,
57+
nodelist=[node],
58+
node_color=color,
59+
edgecolors=edgecolor,
60+
linewidths=linewidth,
61+
node_size=2500
62+
)
63+
64+
# 5. Draw Edges (Arcing handles multi-edges/non-determinism)
65+
nx.draw_networkx_edges(
66+
G, pos,
67+
arrowstyle='->',
68+
arrowsize=25,
69+
connectionstyle='arc3,rad=0.15',
70+
edge_color='gray'
71+
)
72+
73+
# 6. Edge and Node Labels
74+
# Use bracket notation for the 'label' key in the data dict
75+
edge_labels = {(u, v): d['label'] for u, v, k, d in G.edges(data=True, keys=True)}
76+
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=10)
77+
78+
node_labels = {node: f"{node}{'(start)' if node == fsa.initial_state else ''}" for node in G.nodes()}
79+
nx.draw_networkx_labels(G, pos, labels=node_labels, font_size=11, font_weight='bold')
80+
81+
plt.title(f"FSA:\n(Accept states marked with thick borders)")
82+
plt.axis('off')
83+
84+
# 7. Save and Clean Up
85+
if save:
86+
plt.savefig(filename, bbox_inches='tight')
87+
88+
if show:
89+
plt.show()
90+
91+
plt.close()
92+
print(f"Process complete. Memory flushed.")
93+
94+
def make_fsa(states, alphabet, transitions, initial, accept):
95+
return FSA(
96+
states=states,
97+
alphabet=alphabet,
98+
transitions=[
99+
Transition(**t) for t in transitions
100+
],
101+
initial_state=initial,
102+
accept_states=accept,
103+
)
104+
105+
if __name__ == "__main__":
106+
fsa = make_fsa(
107+
states=["q0", "q1"],
108+
alphabet=["a"],
109+
transitions=[
110+
{"from_state": "q0", "to_state": "q0", "symbol": "a"},
111+
{"from_state": "q0", "to_state": "q1", "symbol": "a"}, # Non-deterministic
112+
],
113+
initial="q0",
114+
accept=["q1"],
115+
)
116+
draw_fsa(fsa, False, True, "./valid.png")
117+
fsa = make_fsa(
118+
states=["q0"],
119+
alphabet=["a"],
120+
transitions=[],
121+
initial="q0",
122+
accept=["q1"], # q1 is not in states
123+
)
124+
draw_fsa(fsa, False, True, "./invalid.png")
125+

invalid.png

14.7 KB
Loading

tests/validation/test_fsa_validation.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import unittest
22

33
from evaluation_function.validation.validation import *
4-
from evaluation_function.schemas.fsa import FSA, Transition
5-
from .utils import make_fsa
4+
from evaluation_function.schemas.utils import make_fsa
65

76

87
class TestFSAValidation(unittest.TestCase):

tests/validation/utils.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

valid.png

25.1 KB
Loading

0 commit comments

Comments
 (0)