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