@@ -55,16 +55,18 @@ class FST:
5555 """A class representing finite state transducers.
5656
5757 Attributes:
58- Q (list): a list of states;
59- Sigma (list): a list of symbols of the input alphabet;
60- Gamma (list): a list of symbols of the output alphabet;
61- qe (str): name of the unique initial state;
62- E (list): a list of transitions;
58+ name (str): a descriptive name for this FST.
59+ Q (list): a list of states.
60+ Sigma (list): a list of symbols of the input alphabet.
61+ Gamma (list): a list of symbols of the output alphabet.
62+ qe (str): name of the unique initial state.
63+ E (list): a list of transitions.
6364 stout (dict): a collection of state outputs.
6465 """
6566
6667 def __init__ (self , Sigma = None , Gamma = None ):
6768 """Initializes the FST object."""
69+ self .name = "unnamed-FST"
6870 self .Q = None
6971 self .Sigma = Sigma
7072 self .Gamma = Gamma
@@ -73,7 +75,11 @@ def __init__(self, Sigma=None, Gamma=None):
7375 self .stout = None
7476
7577 def __str__ (self ):
78+ return self .name
79+
80+ def __repr__ (self ):
7681 return f'''FST(
82+ name='{ self .name } ',
7783 (Sigma, Gamma)=({ self .Sigma } , { self .Gamma } ),
7884 Q={ self .Q } ,
7985 qe='{ self .qe } ',
@@ -183,13 +189,13 @@ def copy_fst(self):
183189 T (FST): a copy of the current FST.
184190 """
185191 T = FST ()
192+ T .name = deepcopy (self .name )
186193 T .Q = deepcopy (self .Q )
187194 T .Sigma = deepcopy (self .Sigma )
188195 T .Gamma = deepcopy (self .Gamma )
189196 T .qe = deepcopy (self .qe )
190197 T .E = deepcopy (self .E )
191198 T .stout = deepcopy (self .stout )
192-
193199 return T
194200
195201 def fresh_state (self , name_prefix ):
@@ -260,6 +266,7 @@ def new_rejector(Sigma, Gamma):
260266 """
261267 # initialize the new FST
262268 F = FST (Sigma , Gamma )
269+ F .name = "rejector-FST"
263270 F .Q , F .E , F .qe , F .stout = ["q" ], [], "q" , {}
264271
265272 return F
@@ -281,6 +288,7 @@ def new_acceptor(Sigma, Gamma):
281288 """
282289 # initialize the new FST
283290 F = FST (Sigma , Gamma )
291+ F .name = "acceptor-FST"
284292 F .Q , F .E , F .qe , F .stout = ["q" ], [], "q" , {"q" : "" }
285293
286294 # add transitions that allow writing any character to input or output
@@ -309,6 +317,7 @@ def trim_inaccessible(F):
309317 """
310318 # initialize the new FST
311319 G = FST (deepcopy (F .Sigma ), deepcopy (F .Gamma ))
320+ G .name = f"trim_inaccessible({ F } )"
312321 Q_set , E_set , G .qe , G .stout = set (), set (), F .qe , {}
313322
314323 # perform a breadth-first traversal of the original FST from the initial state
@@ -350,6 +359,7 @@ def trim_useless(F):
350359 """
351360 # initialize the new FST
352361 G = FST (deepcopy (F .Sigma ), deepcopy (F .Gamma ))
362+ G .name = f"trim_useless({ F } )"
353363 Q_set , E_set , G .qe , G .stout = set (), set (), F .qe , {}
354364
355365 # perform a breadth-first traversal of the original FST from the accepting states
@@ -394,7 +404,10 @@ def trim(F):
394404 Returns:
395405 FST: the trimmed FST.
396406 """
397- return trim_useless (trim_inaccessible (F ))
407+ G = trim_useless (trim_inaccessible (F ))
408+ G .name = f"trim({ F } )"
409+
410+ return G
398411
399412def is_empty (F ):
400413 """Checks that the FST accepts no string pairs.
@@ -427,6 +440,7 @@ def expand_inputs(F):
427440 """
428441 # initialize the new FST
429442 G = FST (deepcopy (F .Sigma ), deepcopy (F .Gamma ))
443+ G .name = f"expand_inputs({ F } )"
430444 Q_set , E_set , G .qe , G .stout = set (), set (), FST .encode_state (F .qe , "" ), {}
431445
432446 # Construct the new set of states and transitions.
@@ -470,6 +484,7 @@ def expand_final(F):
470484 """
471485 # initialize the new FST
472486 G = FST (deepcopy (F .Sigma ), deepcopy (F .Gamma ))
487+ G .name = f"expand_final({ F } )"
473488 G .Q , G .E , G .qe , G .stout = deepcopy (F .Q ), deepcopy (F .E ), F .qe , {}
474489
475490 # account for nonempty final outputs by turning them into transitions to new accepting states,
@@ -501,10 +516,12 @@ def invert(F):
501516 FST: the inverted FST.
502517 """
503518 # we need final outputs to be empty because the FST class does not support final inputs
519+ F_name = F .name
504520 F = expand_final (F )
505521
506522 # initialize the new FST
507523 G = FST (deepcopy (F .Sigma ), deepcopy (F .Gamma ))
524+ G .name = f"{ F_name } ⁻¹"
508525 G .Q , G .E , G .qe , G .stout = deepcopy (F .Q ), [], F .qe , deepcopy (F .stout )
509526
510527 # copy over the transitions with swapped input an output strings
@@ -533,10 +550,12 @@ def concatenate(F, G):
533550 """
534551 # we need final outputs of the first machine to be empty
535552 # so that we do not miss output upon traversal to the next machine
553+ F_name = F .name
536554 F = expand_final (F )
537555
538556 # initialize the new FST
539557 H = FST (list (set (F .Sigma ) | set (G .Sigma )), list (set (F .Gamma ) | set (G .Gamma )))
558+ H .name = f"({ F_name } · { G } )"
540559 H .Q , H .E , H .qe , H .stout = [], [], FST .encode_state ("LEFT" , F .qe ), {}
541560
542561 # copy over the states from both `F` and `G`
@@ -583,10 +602,12 @@ def kleene_closure(F):
583602 """
584603 # we need final outputs to be empty
585604 # so that we do not miss output upon traversal back to the initial state
605+ F_name = F .name
586606 F = expand_final (F )
587607
588608 # initialize the new FST
589609 G = FST (deepcopy (F .Sigma ), deepcopy (F .Gamma ))
610+ G .name = f"{ F_name } *"
590611 G .Q , G .E , G .qe , G .stout = deepcopy (F .Q ), deepcopy (F .E ), F .qe , deepcopy (F .stout )
591612
592613 # for every final state,
@@ -616,6 +637,7 @@ def union(F, G):
616637 """
617638 # initialize the new FST
618639 H = FST (list (set (F .Sigma ) | set (G .Sigma )), list (set (F .Gamma ) | set (G .Gamma )))
640+ H .name = f"({ F } ∪ { G } )"
619641 H .Q , H .E , H .qe , H .stout = [], [], FST .encode_state ("LEFT" , F .qe ), {}
620642
621643 # create an epsilon transition to nondeterministically choose between running `F` and `G`
@@ -662,10 +684,12 @@ def intersect(F, G):
662684 # expanding final outputs beforehand makes this construction far easier,
663685 # but it also means that determinism is not an invariant,
664686 # whereas it would be in a more robust implementation
687+ F_name , G_name = F .name , G .name
665688 F , G = expand_final (F ), expand_final (G )
666689
667690 # initialize the new FST
668691 H = FST (list (set (F .Sigma ) | set (G .Sigma )), list (set (F .Gamma ) | set (G .Gamma )))
692+ H .name = f"({ F_name } ∩ { G_name } )"
669693 Q_set , E_set , H .stout = set (), set (), {}
670694 H .qe = FST .encode_state (F .qe , G .qe , ("" , "" ), ("" , "" ))
671695
@@ -733,10 +757,12 @@ def compose(F, G):
733757 # expanding final outputs beforehand makes this construction far easier,
734758 # but it also means that determinism is not an invariant,
735759 # whereas it would be in a more robust implementation
760+ F_name , G_name = F .name , G .name
736761 F , G = expand_final (F ), expand_final (G )
737762
738763 # initialize the new FST
739764 H = FST (deepcopy (G .Sigma ), deepcopy (F .Gamma ))
765+ H .name = f"({ F_name } ∘ { G_name } )"
740766 Q_set , E_set , H .stout = set (), set (), {}
741767 H .qe = FST .encode_state (F .qe , G .qe , "" )
742768
0 commit comments