Path: blob/master/src/sage/combinat/finite_state_machine.py
8817 views
# -*- coding: utf-8 -*-1"""2Finite State Machines, Automata, Transducers34This module adds support for finite state machines, automata and5transducers. See class :class:`FiniteStateMachine` and the examples6below for details creating one.78Examples9========101112A simple finite state machine13-----------------------------1415We can easily create a finite state machine by1617::1819sage: fsm = FiniteStateMachine()20sage: fsm21Finite state machine with 0 states2223By default this is the empty finite state machine, so not very24interesting. Let's create some states and transitions::2526sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition27sage: day = FSMState('day')28sage: night = FSMState('night')29sage: sunrise = FSMTransition(night, day)30sage: sunset = FSMTransition(day, night)3132And now let's add those states and transitions to our finite state machine::3334sage: fsm.add_transition(sunrise)35Transition from 'night' to 'day': -|-36sage: fsm.add_transition(sunset)37Transition from 'day' to 'night': -|-3839Note that the states are added automatically, since they are present40in the transitions. We could add the states manually by4142::4344sage: fsm.add_state(day)45'day'46sage: fsm.add_state(night)47'night'4849Anyhow, we got the following finite state machine::5051sage: fsm52Finite state machine with 2 states5354We can also visualize it as a graph by5556::5758sage: fsm.graph()59Digraph on 2 vertices6061Alternatively, we could have created the finite state machine above62simply by6364::6566sage: FiniteStateMachine([('night', 'day'), ('day', 'night')])67Finite state machine with 2 states6869or by7071::7273sage: fsm = FiniteStateMachine()74sage: day = fsm.add_state('day')75sage: night = fsm.add_state('night')76sage: sunrise = fsm.add_transition(night, day)77sage: sunset = fsm.add_transition(day, night)78sage: fsm79Finite state machine with 2 states8081A simple Automaton (recognizing NAFs)82---------------------------------------8384We want to build an automaton which recognizes non-adjacent forms85(NAFs), i.e., sequences which have no adjacent non-zeros.86We use `0`, `1`, and `-1` as digits::8788sage: NAF = Automaton(89....: {'A': [('A', 0), ('B', 1), ('B', -1)], 'B': [('A', 0)]})90sage: NAF.state('A').is_initial = True91sage: NAF.state('A').is_final = True92sage: NAF.state('B').is_final = True93sage: NAF94Automaton with 2 states9596Of course, we could have specified the initial and final states97directly in the definition of ``NAF`` by ``initial_states=['A']`` and98``final_states=['A', 'B']``.99100So let's test the automaton with some input::101102sage: NAF([0])[0]103True104sage: NAF([0, 1])[0]105True106sage: NAF([1, -1])[0]107False108sage: NAF([0, -1, 0, 1])[0]109True110sage: NAF([0, -1, -1, -1, 0])[0]111False112sage: NAF([-1, 0, 0, 1, 1])[0]113False114115Alternatively, we could call that by116117::118119sage: NAF.process([-1, 0, 0, 1, 1])[0]120False121122A simple transducer (binary inverter)123-------------------------------------124125Let's build a simple transducer, which rewrites a binary word by126iverting each bit::127128sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},129....: initial_states=['A'], final_states=['A'])130131We can look at the states and transitions::132133sage: inverter.states()134['A']135sage: for t in inverter.transitions():136....: print t137Transition from 'A' to 'A': 0|1138Transition from 'A' to 'A': 1|0139140Now we apply a word to it and see what the transducer does::141142sage: inverter([0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1])143(True, 'A', [1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0])144145``True`` means, that we landed in a final state, that state is labeled146``'A'``, and we also got an output.147148149A transducer which performs division by `3` in binary150-----------------------------------------------------151152Now we build a transducer, which divides a binary number by 3.153The labels of the states are the remainder of the division.154The transition function is155156::157158sage: def f(state_from, read):159....: if state_from + read <= 1:160....: state_to = 2*state_from + read161....: write = 0162....: else:163....: state_to = 2*state_from + read - 3164....: write = 1165....: return (state_to, write)166167We get the transducer with168169::170171sage: D = Transducer(f, initial_states=[0], final_states=[0],172....: input_alphabet=[0, 1])173174Now we want to divide 13 by 3::175176sage: D([1, 1, 0, 1])177(False, 1, [0, 1, 0, 0])178179So we have 13 : 3 = 4 and the reminder is 1. ``False`` means 13 is not180divisible by 3.181182183Using the hook-functions184------------------------185186Let's use the previous example "divison by `3`" to demonstrate the187optional state and transition parameters ``hook``.188189First, we define, what those functions should do. In our case, this is190just saying in which state we are and which transition we take191192::193194sage: def state_hook(state, process):195....: print "We are now in State %s." % (state.label(),)196sage: from sage.combinat.finite_state_machine import FSMWordSymbol197sage: def transition_hook(transition, process):198....: print ("Currently we go from %s to %s, "199....: "reading %s and writing %s." % (200....: transition.from_state, transition.to_state,201....: FSMWordSymbol(transition.word_in),202....: FSMWordSymbol(transition.word_out)))203204Now, let's add these hook-functions to the existing transducer::205206sage: for s in D.iter_states():207....: s.hook = state_hook208sage: for t in D.iter_transitions():209....: t.hook = transition_hook210211Rerunning the process again now gives the following output::212213sage: D.process([1, 1, 0, 1])214We are now in State 0.215Currently we go from 0 to 1, reading 1 and writing 0.216We are now in State 1.217Currently we go from 1 to 0, reading 1 and writing 1.218We are now in State 0.219Currently we go from 0 to 0, reading 0 and writing 0.220We are now in State 0.221Currently we go from 0 to 1, reading 1 and writing 0.222We are now in State 1.223(False, 1, [0, 1, 0, 0])224225The example above just explains the basic idea of using226hook-functions. In the following, we will use those hooks more seriously.227228229Detecting sequences with same number of `0` and `1`230---------------------------------------------------231232Suppose we have a binary input and want to accept all sequences with233the same number of `0` and `1`. This cannot be done with a finite234automaton. Anyhow, we can make usage of the hook functions to extend235our finite automaton by a counter::236237sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition238sage: C = Automaton()239sage: def update_counter(state, process):240....: l = process.read_letter()241....: process.fsm.counter += 1 if l == 1 else -1242....: if process.fsm.counter > 0:243....: next_state = 'positive'244....: elif process.fsm.counter < 0:245....: next_state = 'negative'246....: else:247....: next_state = 'zero'248....: return FSMTransition(state, process.fsm.state(next_state),249....: l, process.fsm.counter)250sage: C.add_state(FSMState('zero', hook=update_counter,251....: is_initial=True, is_final=True))252'zero'253sage: C.add_state(FSMState('positive', hook=update_counter))254'positive'255sage: C.add_state(FSMState('negative', hook=update_counter))256'negative'257258Now, let's input some sequence::259260sage: C.counter = 0; C([1, 1, 1, 1, 0, 0])261(False, 'positive', [1, 2, 3, 4, 3, 2])262263The result is False, since there are four `1` but only two `0`. We264land in the state ``positive`` and we can also see the values of the265counter in each step.266267Let's try some other examples::268269sage: C.counter = 0; C([1, 1, 0, 0])270(True, 'zero', [1, 2, 1, 0])271sage: C.counter = 0; C([0, 1, 0, 0])272(False, 'negative', [-1, 0, -1, -2])273274275AUTHORS:276277- Daniel Krenn (2012-03-27): initial version278- Clemens Heuberger (2012-04-05): initial version279- Sara Kropf (2012-04-17): initial version280- Clemens Heuberger (2013-08-21): release candidate for Sage patch281- Daniel Krenn (2013-08-21): release candidate for Sage patch282- Sara Kropf (2013-08-21): release candidate for Sage patch283- Clemens Heuberger (2013-09-02): documentation improved284- Daniel Krenn (2013-09-13): comments from trac worked in285- Clemens Heuberger (2013-11-03): output (labels) of determinisation,286product, composition, etc. changed (for consistency),287representation of state changed, documentation improved288- Daniel Krenn (2013-11-04): whitespaces in documentation corrected289- Clemens Heuberger (2013-11-04): full_group_by added290- Daniel Krenn (2013-11-04): next release candidate for Sage patch291- Sara Kropf (2013-11-08): fix for adjacency matrix292- Clemens Heuberger (2013-11-11): fix for prepone_output293- Daniel Krenn (2013-11-11): comments from trac 15078 included:294docstring of FiniteStateMachine rewritten, Automaton and Transducer295inherited from FiniteStateMachine296- Daniel Krenn (2013-11-25): documentation improved according to297comments from trac 15078298299ACKNOWLEDGEMENT:300301- Daniel Krenn, Clemens Heuberger and Sara Kropf are supported by the302Austrian Science Fund (FWF): P 24644-N26.303304"""305306#*****************************************************************************307# Copyright (C) 2012, 2013 Daniel Krenn <[email protected]>308# 2012, 2013 Clemens Heuberger <[email protected]>309# 2012, 2013 Sara Kropf <[email protected]>310#311# Distributed under the terms of the GNU General Public License (GPL)312# as published by the Free Software Foundation; either version 2 of313# the License, or (at your option) any later version.314# http://www.gnu.org/licenses/315#*****************************************************************************316317from sage.structure.sage_object import SageObject318from sage.graphs.digraph import DiGraph319from sage.matrix.constructor import matrix320from sage.rings.integer_ring import ZZ321from sage.calculus.var import var322from sage.misc.latex import latex323from sage.functions.trig import cos, sin, atan2324from sage.symbolic.constants import pi325326from copy import copy327from copy import deepcopy328329import itertools330from collections import defaultdict331332333def full_group_by(l, key=lambda x: x):334"""335Group iterable ``l`` by values of ``key``.336337INPUT:338339- iterable ``l``340- key function ``key``341342OUTPUT:343344A list of pairs ``(k, elements)`` such that ``key(e)=k`` for all345``e`` in ``elements``.346347This is similar to ``itertools.groupby`` except that lists are348returned instead of iterables and no prior sorting is required.349350We do not require351352- that the keys are sortable (in contrast to the353approach via ``sorted`` and ``itertools.groupby``) and354- that the keys are hashable (in contrast to the355implementation proposed in `<http://stackoverflow.com/a/15250161>`_).356357However, it is required358359- that distinct keys have distinct ``str``-representations.360361The implementation is inspired by362`<http://stackoverflow.com/a/15250161>`_, but non-hashable keys are363allowed.364365EXAMPLES::366367sage: from sage.combinat.finite_state_machine import full_group_by368sage: t = [2/x, 1/x, 2/x]369sage: r = full_group_by([0,1,2], key=lambda i:t[i])370sage: sorted(r, key=lambda p:p[1])371[(2/x, [0, 2]), (1/x, [1])]372sage: from itertools import groupby373sage: for k, elements in groupby(sorted([0,1,2],374....: key=lambda i:t[i]),375....: key=lambda i:t[i]):376....: print k, list(elements)3772/x [0]3781/x [1]3792/x [2]380381Note that the behavior is different from ``itertools.groupby``382because neither `1/x<2/x` nor `2/x<1/x` does hold.383384Here, the result ``r`` has been sorted in order to guarantee a385consistent order for the doctest suite.386"""387elements = defaultdict(list)388original_keys = {}389for item in l:390k = key(item)391s = str(k)392if s in original_keys:393if original_keys[s]!=k:394raise ValueError("Two distinct elements with representation "395"%s " % s)396else:397original_keys[s]=k398elements[s].append(item)399return [(original_keys[s], values ) for (s, values) in elements.items()]400401#*****************************************************************************402403FSMEmptyWordSymbol = '-'404405def FSMLetterSymbol(letter):406"""407Returns a string associated to the input letter.408409INPUT:410411- ``letter`` -- the input letter or ``None`` (representing the412empty word).413414OUTPUT:415416If ``letter`` is ``None`` the symbol for the empty word417``FSMEmptyWordSymbol`` is returned, otherwise the string418associated to the letter.419420EXAMPLES::421422sage: from sage.combinat.finite_state_machine import FSMLetterSymbol423sage: FSMLetterSymbol(0)424'0'425sage: FSMLetterSymbol(None)426'-'427"""428return FSMEmptyWordSymbol if letter is None else repr(letter)429430431def FSMWordSymbol(word):432"""433Returns a string of ``word``. It may returns the symbol of the434empty word ``FSMEmptyWordSymbol``.435436INPUT:437438- ``word`` -- the input word.439440OUTPUT:441442A string of ``word``.443444EXAMPLES::445446sage: from sage.combinat.finite_state_machine import FSMWordSymbol447sage: FSMWordSymbol([0, 1, 1])448'0,1,1'449"""450if not isinstance(word, list):451return FSMLetterSymbol(word)452if len(word) == 0:453return FSMEmptyWordSymbol454s = ''455for letter in word:456s += (',' if len(s) > 0 else '') + FSMLetterSymbol(letter)457return s458459460#*****************************************************************************461462463def is_FSMState(S):464"""465Tests whether or not ``S`` inherits from :class:`FSMState`.466467TESTS::468469sage: from sage.combinat.finite_state_machine import is_FSMState, FSMState470sage: is_FSMState(FSMState('A'))471True472"""473return isinstance(S, FSMState)474475476class FSMState(SageObject):477"""478Class for a state of a finite state machine.479480INPUT:481482- ``label`` -- the label of the state.483484- ``word_out`` -- (default: ``None``) a word that is written when485the state is reached.486487- ``is_initial`` -- (default: ``False``)488489- ``is_final`` -- (default: ``False``)490491- ``hook`` -- (default: ``None``) A function which is called when492the state is reached during processing input.493494OUTPUT:495496Returns a state of a finite state machine.497498EXAMPLES::499500sage: from sage.combinat.finite_state_machine import FSMState501sage: A = FSMState('state 1', word_out=0, is_initial=True)502sage: A503'state 1'504sage: A.label()505'state 1'506sage: B = FSMState('state 2')507sage: A == B508False509510"""511def __init__(self, label, word_out=None,512is_initial=False, is_final=False,513hook=None):514"""515See :class:`FSMState` for more information.516517EXAMPLES::518519sage: from sage.combinat.finite_state_machine import FSMState520sage: FSMState('final', is_final=True)521'final'522"""523if label is None or label == "":524raise ValueError, "You have to specify a label for the state."525self._label_ = label526527if isinstance(word_out, list):528self.word_out = word_out529elif word_out is not None:530self.word_out = [word_out]531else:532self.word_out = []533534self.is_initial = is_initial535self.is_final = is_final536if hook is not None:537if hasattr(hook, '__call__'):538self.hook = hook539else:540raise TypeError, 'Wrong argument for hook.'541542543def label(self):544"""545Returns the label of the state.546547INPUT:548549Nothing.550551OUTPUT:552553The label of the state.554555EXAMPLES::556557sage: from sage.combinat.finite_state_machine import FSMState558sage: A = FSMState('state')559sage: A.label()560'state'561"""562return self._label_563564565def __copy__(self):566"""567Returns a (shallow) copy of the state.568569INPUT:570571Nothing.572573OUTPUT:574575A new state.576577EXAMPLES::578579sage: from sage.combinat.finite_state_machine import FSMState580sage: A = FSMState('A')581sage: copy(A)582'A'583"""584new = FSMState(self.label(), self.word_out,585self.is_initial, self.is_final)586if hasattr(self, 'hook'):587new.hook = self.hook588return new589590591copy = __copy__592593594def __deepcopy__(self, memo):595"""596Returns a deep copy of the state.597598INPUT:599600- ``memo`` -- a dictionary storing already processed elements.601602OUTPUT:603604A new state.605606EXAMPLES::607608sage: from sage.combinat.finite_state_machine import FSMState609sage: A = FSMState('A')610sage: deepcopy(A)611'A'612"""613try:614label = self._deepcopy_relabel_615except AttributeError:616label = deepcopy(self.label(), memo)617new = FSMState(label, deepcopy(self.word_out, memo),618self.is_initial, self.is_final)619if hasattr(self, 'hook'):620new.hook = deepcopy(self.hook, memo)621return new622623624def deepcopy(self, memo=None):625"""626Returns a deep copy of the state.627628INPUT:629630- ``memo`` -- (default: ``None``) a dictionary storing already631processed elements.632633OUTPUT:634635A new state.636637EXAMPLES::638639sage: from sage.combinat.finite_state_machine import FSMState640sage: A = FSMState('A')641sage: deepcopy(A)642'A'643"""644return deepcopy(self, memo)645646647def relabeled(self, label, memo=None):648"""649Returns a deep copy of the state with a new label.650651INPUT:652653- ``label`` -- the label of new state.654655- ``memo`` -- (default: ``None``) a dictionary storing already656processed elements.657658OUTPUT:659660A new state.661662EXAMPLES::663664sage: from sage.combinat.finite_state_machine import FSMState665sage: A = FSMState('A')666sage: A.relabeled('B')667'B'668669"""670self._deepcopy_relabel_ = label671new = deepcopy(self, memo)672del self._deepcopy_relabel_673return new674675676def __hash__(self):677"""678Returns a hash value for the object.679680INPUT:681682Nothing.683684OUTPUT:685686The hash of this state.687688TESTS::689690sage: from sage.combinat.finite_state_machine import FSMState691sage: A = FSMState('A')692sage: hash(A) #random693-269909568694"""695return hash(self.label())696697698def _repr_(self):699"""700Returns the string "label".701702INPUT:703704Nothing.705706OUTPUT:707708A string.709710TESTS:711712sage: from sage.combinat.finite_state_machine import FSMState713sage: FSMState('A')._repr_()714"'A'"715"""716return repr(self.label())717718719def __eq__(left, right):720"""721Returns True if two states are the same, i.e., if they have722the same labels.723724Note that the hooks and whether the states are initial or725final are not checked.726727INPUT:728729- ``left`` -- a state.730731- ``right`` -- a state.732733OUTPUT:734735True or False.736737EXAMPLES::738739sage: from sage.combinat.finite_state_machine import FSMState740sage: A = FSMState('A')741sage: B = FSMState('A', is_initial=True)742sage: A == B743True744"""745if not is_FSMState(right):746return False747return left.label() == right.label()748749750def __ne__(left, right):751"""752Tests for inequality, complement of __eq__.753754INPUT:755756- ``left`` -- a state.757758- ``right`` -- a state.759760OUTPUT:761762True or False.763764EXAMPLES::765766sage: from sage.combinat.finite_state_machine import FSMState767sage: A = FSMState('A', is_initial=True)768sage: B = FSMState('A', is_final=True)769sage: A != B770False771"""772return (not (left == right))773774775def __nonzero__(self):776"""777Returns True.778779INPUT:780781Nothing.782783OUTPUT:784785True or False.786787TESTS::788789sage: from sage.combinat.finite_state_machine import FSMState790sage: FSMState('A').__nonzero__()791True792"""793return True # A state cannot be zero (see __init__)794795796#*****************************************************************************797798799def is_FSMTransition(T):800"""801Tests whether or not ``T`` inherits from :class:`FSMTransition`.802803TESTS::804805sage: from sage.combinat.finite_state_machine import is_FSMTransition, FSMTransition806sage: is_FSMTransition(FSMTransition('A', 'B'))807True808"""809return isinstance(T, FSMTransition)810811812class FSMTransition(SageObject):813"""814Class for a transition of a finite state machine.815816INPUT:817818- ``from_state`` -- state from which transition starts.819820- ``to_state`` -- state in which transition ends.821822- ``word_in`` -- the input word of the transitions (when the823finite state machine is used as automaton)824825- ``word_out`` -- the output word of the transitions (when the826finite state machine is used as transducer)827828OUTPUT:829830A transition of a finite state machine.831832EXAMPLES::833834sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition835sage: A = FSMState('A')836sage: B = FSMState('B')837sage: S = FSMTransition(A, B, 0, 1)838sage: T = FSMTransition('A', 'B', 0, 1)839sage: T == S840True841sage: U = FSMTransition('A', 'B', 0)842sage: U == T843False844845"""846def __init__(self, from_state, to_state,847word_in=None, word_out=None,848hook=None):849"""850See :class:`FSMTransition` for more information.851852EXAMPLES::853854sage: from sage.combinat.finite_state_machine import FSMTransition855sage: FSMTransition('A', 'B', 0, 1)856Transition from 'A' to 'B': 0|1857"""858if is_FSMState(from_state):859self.from_state = from_state860else:861self.from_state = FSMState(from_state)862if is_FSMState(to_state):863self.to_state = to_state864else:865self.to_state = FSMState(to_state)866867if isinstance(word_in, list):868self.word_in = word_in869elif word_in is not None:870self.word_in = [word_in]871else:872self.word_in = []873874if isinstance(word_out, list):875self.word_out = word_out876elif word_out is not None:877self.word_out = [word_out]878else:879self.word_out = []880881if hook is not None:882if hasattr(hook, '__call__'):883self.hook = hook884else:885raise TypeError, 'Wrong argument for hook.'886887888def __copy__(self):889"""890Returns a (shallow) copy of the transition.891892INPUT:893894Nothing.895896OUTPUT:897898A new transition.899900EXAMPLES::901902sage: from sage.combinat.finite_state_machine import FSMTransition903sage: t = FSMTransition('A', 'B', 0)904sage: copy(t)905Transition from 'A' to 'B': 0|-906"""907new = FSMTransition(self.from_state, self.to_state,908self.word_in, self.word_out)909if hasattr(self, 'hook'):910new.hook = self.hook911return new912913914copy = __copy__915916def __deepcopy__(self, memo):917"""918Returns a deep copy of the transition.919920INPUT:921922- ``memo`` -- a dictionary storing already processed elements.923924OUTPUT:925926A new transition.927928EXAMPLES::929930sage: from sage.combinat.finite_state_machine import FSMTransition931sage: t = FSMTransition('A', 'B', 0)932sage: deepcopy(t)933Transition from 'A' to 'B': 0|-934"""935new = FSMTransition(deepcopy(self.from_state, memo),936deepcopy(self.to_state, memo),937deepcopy(self.word_in, memo),938deepcopy(self.word_out, memo))939if hasattr(self, 'hook'):940new.hook = deepcopy(self.hook, memo)941return new942943944def deepcopy(self, memo=None):945"""946Returns a deep copy of the transition.947948INPUT:949950- ``memo`` -- (default: ``None``) a dictionary storing already951processed elements.952953OUTPUT:954955A new transition.956957EXAMPLES::958959sage: from sage.combinat.finite_state_machine import FSMTransition960sage: t = FSMTransition('A', 'B', 0)961sage: deepcopy(t)962Transition from 'A' to 'B': 0|-963"""964return deepcopy(self, memo)965966967def __hash__(self):968"""969Since transitions are mutable, they should not be hashable, so970we return a type error.971972INPUT:973974Nothing.975976OUTPUT:977978The hash of this transition.979980EXAMPLES::981982sage: from sage.combinat.finite_state_machine import FSMTransition983sage: hash(FSMTransition('A', 'B'))984Traceback (most recent call last):985...986TypeError: Transitions are mutable, and thus not hashable.987988"""989raise TypeError, "Transitions are mutable, and thus not hashable."990991992def _repr_(self):993"""994Represents a transitions as from state to state and input, output.995996INPUT:997998Nothing.9991000OUTPUT:10011002A string.10031004EXAMPLES::10051006sage: from sage.combinat.finite_state_machine import FSMTransition1007sage: FSMTransition('A', 'B', 0, 0)._repr_()1008"Transition from 'A' to 'B': 0|0"10091010"""1011return "Transition from %s to %s: %s" % (repr(self.from_state),1012repr(self.to_state),1013self._in_out_label_())101410151016def _in_out_label_(self):1017"""1018Returns the input and output of a transition as1019"word_in|word_out".10201021INPUT:10221023Nothing.10241025OUTPUT:10261027A string of the input and output labels.10281029EXAMPLES::10301031sage: from sage.combinat.finite_state_machine import FSMTransition1032sage: FSMTransition('A', 'B', 0, 1)._in_out_label_()1033'0|1'1034"""1035return "%s|%s" % (FSMWordSymbol(self.word_in),1036FSMWordSymbol(self.word_out))103710381039def __eq__(left, right):1040"""1041Returns True if the two transitions are the same, i.e., if the1042both go from the same states to the same states and read and1043write the same words.10441045Note that the hooks are not checked.10461047INPUT:10481049- ``left`` -- a transition.10501051- ``right`` -- a transition.10521053OUTPUT:10541055True or False.10561057EXAMPLES::10581059sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition1060sage: A = FSMState('A', is_initial=True)1061sage: t1 = FSMTransition('A', 'B', 0, 1)1062sage: t2 = FSMTransition(A, 'B', 0, 1)1063sage: t1 == t21064True1065"""1066if not is_FSMTransition(right):1067raise TypeError, 'Only instances of FSMTransition ' \1068'can be compared.'1069return left.from_state == right.from_state \1070and left.to_state == right.to_state \1071and left.word_in == right.word_in \1072and left.word_out == right.word_out107310741075def __ne__(left, right):1076"""10771078INPUT:10791080- ``left`` -- a transition.10811082- ``right`` -- a transition.10831084OUTPUT:10851086True or False.1087Tests for inequality, complement of __eq__.10881089EXAMPLES::10901091sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition1092sage: A = FSMState('A', is_initial=True)1093sage: t1 = FSMTransition('A', 'B', 0, 1)1094sage: t2 = FSMTransition(A, 'B', 0, 1)1095sage: t1 != t21096False1097"""1098return (not (left == right))109911001101def __nonzero__(self):1102"""1103Returns True.11041105INPUT:11061107Nothing.11081109OUTPUT:11101111True or False.11121113EXAMPLES::11141115sage: from sage.combinat.finite_state_machine import FSMTransition1116sage: FSMTransition('A', 'B', 0).__nonzero__()1117True1118"""1119return True # A transition cannot be zero (see __init__)112011211122#*****************************************************************************112311241125def is_FiniteStateMachine(FSM):1126"""1127Tests whether or not ``FSM`` inherits from :class:`FiniteStateMachine`.11281129TESTS::11301131sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine1132sage: is_FiniteStateMachine(FiniteStateMachine())1133True1134sage: is_FiniteStateMachine(Automaton())1135True1136sage: is_FiniteStateMachine(Transducer())1137True1138"""1139return isinstance(FSM, FiniteStateMachine)114011411142class FiniteStateMachine(SageObject):1143"""1144Class for a finite state machine.11451146A finite state machine is a finite set of states connected by1147transitions.11481149INPUT:11501151- ``data`` -- can be any of the following:11521153#. a dictionary of dictionaries (of transitions),11541155#. a dictionary of lists (of states or transitions),11561157#. a list (of transitions),11581159#. a function (transition function),11601161#. an other instance of a finite state machine.11621163- ``initial_states`` and ``final_states`` -- the initial and1164final states of this machine11651166- ``input_alphabet`` and ``output_alphabet`` -- the input and1167output alphabets of this machine11681169- ``determine_alphabets`` -- If True, then the function1170``determine_alphabets()`` is called after ``data`` was read and1171processed, if ``False``, then not. If it is ``None``, then it is1172decided during the construction of the finite state machine1173whether ``determine_alphabets()`` should be called.11741175- ``store_states_dict`` -- If ``True``, then additionally the states1176are stored in an interal dictionary for speed up.11771178OUTPUT:11791180A finite state machine.11811182The object creation of :class:`Automaton` and :class:`Transducer`1183is the same as the one described here (i.e. just replace the word1184``FiniteStateMachine`` by ``Automaton`` or ``Transducer``).11851186Each transition of an automaton has an input label. Automata can,1187for example, be determinised (see1188:meth:`Automaton.determinisation`) and minimized (see1189:meth:`Automaton.minimization`). Each transition of a transducer1190has an input and an output label. Transducers can, for example, be1191simplified (see :meth:`Transducer.simplification`).11921193EXAMPLES::11941195sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition11961197See documentation for more examples.11981199We illustrate the different input formats:12001201#. The input-data can be a dictionary of dictionaries, where12021203- the keys of the outer dictionary are state-labels (from-states of1204transitions),1205- the keys of the inner dictionaries are state-labels (to-states of1206transitions),1207- the values of the inner dictionaries specify the transition1208more precisely.12091210The easiest is to use a tuple consisting of an input and an1211output word::12121213sage: FiniteStateMachine({'a':{'b':(0, 1), 'c':(1, 1)}})1214Finite state machine with 3 states12151216Instead of the tuple anything iterable (e.g. a list) can be1217used as well.12181219If you want to use the arguments of ``FSMTransition``1220directly, you can use a dictionary::12211222sage: FiniteStateMachine({'a':{'b':{'word_in':0, 'word_out':1},1223....: 'c':{'word_in':1, 'word_out':1}}})1224Finite state machine with 3 states12251226In the case you already have instances of ``FSMTransition``, it is1227possible to use them directly::12281229sage: FiniteStateMachine({'a':{'b':FSMTransition('a', 'b', 0, 1),1230....: 'c':FSMTransition('a', 'c', 1, 1)}})1231Finite state machine with 3 states12321233#. The input-data can be a dictionary of lists, where the keys1234are states or label of states.12351236The list-elements can be states::12371238sage: a = FSMState('a')1239sage: b = FSMState('b')1240sage: c = FSMState('c')1241sage: FiniteStateMachine({a:[b, c]})1242Finite state machine with 3 states12431244Or the list-elements can simply be labels of states::12451246sage: FiniteStateMachine({'a':['b', 'c']})1247Finite state machine with 3 states12481249The list-elements can also be transitions::12501251sage: FiniteStateMachine({'a':[FSMTransition('a', 'b', 0, 1),1252....: FSMTransition('a', 'c', 1, 1)]})1253Finite state machine with 3 states12541255Or they can be tuples of a label, an input word and an output1256word specifying a transition::12571258sage: FiniteStateMachine({'a':[('b', 0, 1), ('c', 1, 1)]})1259Finite state machine with 3 states12601261#. The input-data can be a list, where its elements specify1262transitions::12631264sage: FiniteStateMachine([FSMTransition('a', 'b', 0, 1),1265....: FSMTransition('a', 'c', 1, 1)])1266Finite state machine with 3 states12671268It is possible to skip ``FSMTransition`` in the example above::12691270sage: FiniteStateMachine([('a', 'b', 0, 1), ('a', 'c', 1, 1)])1271Finite state machine with 3 states12721273The parameters of the transition are given in tuples. Anyhow,1274anything iterable (e.g. a list) is possible.12751276You can also name the parameters of the transition. For this1277purpose you take a dictionary::12781279sage: FiniteStateMachine([{'from_state':'a', 'to_state':'b',1280....: 'word_in':0, 'word_out':1},1281....: {'from_state':'a', 'to_state':'c',1282....: 'word_in':1, 'word_out':1}])1283Finite state machine with 3 states12841285Other arguments, which :class:`FSMTransition` accepts, can be1286added, too.12871288#. The input-data can also be function acting as transition1289function:12901291This function has two input arguments:12921293#. a label of a state (from which the transition starts),12941295#. a letter of the (input-)alphabet (as input-label of the transition).12961297It returns a tuple with the following entries:12981299#. a label of a state (to which state the transition goes),13001301#. a letter of or a word over the (output-)alphabet (as1302output-label of the transition).13031304It may also output a list of such tuples if several1305transitions from the from-state and the input letter exist1306(this means that the finite state machine is1307non-deterministic).13081309If the transition does not exist, the function should raise a1310``LookupError`` or return an empty list.13111312When constructing a finite state machine in this way, some1313inital states and an input alphabet have to be specified.13141315::13161317sage: def f(state_from, read):1318....: if int(state_from) + read <= 2:1319....: state_to = 2*int(state_from)+read1320....: write = 01321....: else:1322....: state_to = 2*int(state_from) + read - 51323....: write = 11324....: return (str(state_to), write)1325sage: F = FiniteStateMachine(f, input_alphabet=[0, 1],1326....: initial_states=['0'],1327....: final_states=['0'])1328sage: F([1, 0, 1])1329(True, '0', [0, 0, 1])13301331#. The input-data can be an other instance of a finite state machine::13321333sage: FiniteStateMachine(FiniteStateMachine([]))1334Traceback (most recent call last):1335...1336NotImplementedError133713381339TESTS::13401341sage: a = FSMState('S_a', 'a')1342sage: b = FSMState('S_b', 'b')1343sage: c = FSMState('S_c', 'c')1344sage: d = FSMState('S_d', 'd')1345sage: FiniteStateMachine({a:[b, c], b:[b, c, d],1346....: c:[a, b], d:[a, c]})1347Finite state machine with 4 states13481349We have several constructions which lead to the same finite1350state machine::13511352sage: A = FSMState('A')1353sage: B = FSMState('B')1354sage: C = FSMState('C')1355sage: FSM1 = FiniteStateMachine(1356....: {A:{B:{'word_in':0, 'word_out':1},1357....: C:{'word_in':1, 'word_out':1}}})1358sage: FSM2 = FiniteStateMachine({A:{B:(0, 1), C:(1, 1)}})1359sage: FSM3 = FiniteStateMachine(1360....: {A:{B:FSMTransition(A, B, 0, 1),1361....: C:FSMTransition(A, C, 1, 1)}})1362sage: FSM4 = FiniteStateMachine({A:[(B, 0, 1), (C, 1, 1)]})1363sage: FSM5 = FiniteStateMachine(1364....: {A:[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]})1365sage: FSM6 = FiniteStateMachine(1366....: [{'from_state':A, 'to_state':B, 'word_in':0, 'word_out':1},1367....: {'from_state':A, 'to_state':C, 'word_in':1, 'word_out':1}])1368sage: FSM7 = FiniteStateMachine([(A, B, 0, 1), (A, C, 1, 1)])1369sage: FSM8 = FiniteStateMachine(1370....: [FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)])13711372sage: FSM1 == FSM2 == FSM3 == FSM4 == FSM5 == FSM6 == FSM7 == FSM81373True13741375It is possible to skip ``FSMTransition`` in the example above.13761377Some more tests for different input-data::13781379sage: FiniteStateMachine({'a':{'a':[0, 0], 'b':[1, 1]},1380....: 'b':{'b':[1, 0]}})1381Finite state machine with 2 states13821383sage: a = FSMState('S_a', 'a')1384sage: b = FSMState('S_b', 'b')1385sage: c = FSMState('S_c', 'c')1386sage: d = FSMState('S_d', 'd')1387sage: t1 = FSMTransition(a, b)1388sage: t2 = FSMTransition(b, c)1389sage: t3 = FSMTransition(b, d)1390sage: t4 = FSMTransition(c, d)1391sage: FiniteStateMachine([t1, t2, t3, t4])1392Finite state machine with 4 states1393"""13941395#*************************************************************************1396# init1397#*************************************************************************139813991400def __init__(self,1401data=None,1402initial_states=None, final_states=None,1403input_alphabet=None, output_alphabet=None,1404determine_alphabets=None,1405store_states_dict=True):1406"""1407See :class:`FiniteStateMachine` for more information.14081409TEST::14101411sage: FiniteStateMachine()1412Finite state machine with 0 states1413"""1414self._states_ = [] # List of states in the finite state1415# machine. Each state stores a list of1416# outgoing transitions.1417if store_states_dict:1418self._states_dict_ = {}14191420if initial_states is not None:1421if not hasattr(initial_states, '__iter__'):1422raise TypeError, 'Initial states must be iterable ' \1423'(e.g. a list of states).'1424for s in initial_states:1425state = self.add_state(s)1426state.is_initial = True14271428if final_states is not None:1429if not hasattr(final_states, '__iter__'):1430raise TypeError, 'Final states must be iterable ' \1431'(e.g. a list of states).'1432for s in final_states:1433state = self.add_state(s)1434state.is_final = True14351436self.input_alphabet = input_alphabet1437self.output_alphabet = output_alphabet14381439if data is None:1440pass1441elif is_FiniteStateMachine(data):1442raise NotImplementedError1443elif hasattr(data, 'iteritems'):1444# data is a dict (or something similar),1445# format: key = from_state, value = iterator of transitions1446for (sf, iter_transitions) in data.iteritems():1447self.add_state(sf)1448if hasattr(iter_transitions, 'iteritems'):1449for (st, transition) in iter_transitions.iteritems():1450self.add_state(st)1451if is_FSMTransition(transition):1452self.add_transition(transition)1453elif hasattr(transition, 'iteritems'):1454self.add_transition(sf, st, **transition)1455elif hasattr(transition, '__iter__'):1456self.add_transition(sf, st, *transition)1457else:1458self.add_transition(sf, st, transition)1459elif hasattr(iter_transitions, '__iter__'):1460for transition in iter_transitions:1461if hasattr(transition, '__iter__'):1462L = [sf]1463L.extend(transition)1464elif is_FSMTransition(transition):1465L = transition1466else:1467L = [sf, transition]1468self.add_transition(L)1469else:1470raise TypeError, 'Wrong input data for transition.'1471if determine_alphabets is None and input_alphabet is None \1472and output_alphabet is None:1473determine_alphabets = True1474elif hasattr(data, '__iter__'):1475# data is a something that is iterable,1476# items are transitions1477for transition in data:1478if is_FSMTransition(transition):1479self.add_transition(transition)1480elif hasattr(transition, 'iteritems'):1481self.add_transition(transition)1482elif hasattr(transition, '__iter__'):1483self.add_transition(transition)1484else:1485raise TypeError, 'Wrong input data for transition.'1486if determine_alphabets is None and input_alphabet is None \1487and output_alphabet is None:1488determine_alphabets = True1489elif hasattr(data, '__call__'):1490self.add_from_transition_function(data)1491else:1492raise TypeError, 'Cannot decide what to do with data.'14931494if determine_alphabets:1495self.determine_alphabets()149614971498#*************************************************************************1499# copy and hash1500#*************************************************************************150115021503def __copy__(self):1504"""1505Returns a (shallow) copy of the finite state machine.15061507INPUT:15081509Nothing.15101511OUTPUT:15121513A new finite state machine.15141515TESTS::15161517sage: copy(FiniteStateMachine())1518Traceback (most recent call last):1519...1520NotImplementedError1521"""1522raise NotImplementedError152315241525copy = __copy__15261527def empty_copy(self, memo=None):1528"""1529Returns an empty deep copy of the finite state machine, i.e.,1530input_alphabet, output_alphabet are preserved, but states and1531transitions are not.15321533INPUT:15341535- ``memo`` -- a dictionary storing already processed elements.15361537OUTPUT:15381539A new finite state machine.15401541EXAMPLES::15421543sage: F = FiniteStateMachine([('A', 'A', 0, 2), ('A', 'A', 1, 3)],1544....: input_alphabet=[0,1],1545....: output_alphabet=[2,3])1546sage: FE = F.empty_copy(); FE1547Finite state machine with 0 states1548sage: FE.input_alphabet1549[0, 1]1550sage: FE.output_alphabet1551[2, 3]1552"""1553new = self.__class__()1554new.input_alphabet = deepcopy(self.input_alphabet, memo)1555new.output_alphabet = deepcopy(self.output_alphabet, memo)1556return new15571558def __deepcopy__(self, memo):1559"""1560Returns a deep copy of the finite state machine.15611562INPUT:15631564- ``memo`` -- a dictionary storing already processed elements.15651566OUTPUT:15671568A new finite state machine.15691570EXAMPLES::15711572sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)])1573sage: deepcopy(F)1574Finite state machine with 1 states1575"""1576relabel = hasattr(self, '_deepcopy_relabel_')1577new = self.empty_copy(memo=memo)1578relabel_iter = itertools.count(0)1579for state in self.iter_states():1580if relabel:1581state._deepcopy_relabel_ = relabel_iter.next()1582s = deepcopy(state, memo)1583if relabel:1584del state._deepcopy_relabel_1585new.add_state(s)1586for transition in self.iter_transitions():1587new.add_transition(deepcopy(transition, memo))1588return new158915901591def deepcopy(self, memo=None):1592"""1593Returns a deep copy of the finite state machine.15941595INPUT:15961597- ``memo`` -- (default: ``None``) a dictionary storing already1598processed elements.15991600OUTPUT:16011602A new finite state machine.16031604EXAMPLES::16051606sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)])1607sage: deepcopy(F)1608Finite state machine with 1 states1609"""1610return deepcopy(self, memo)161116121613def relabeled(self, memo=None):1614"""1615Returns a deep copy of the finite state machine, but the1616states are relabeled by integers starting with 0.16171618INPUT:16191620- ``memo`` -- (default: ``None``) a dictionary storing already1621processed elements.16221623OUTPUT:16241625A new finite state machine.16261627EXAMPLES::16281629sage: FSM1 = FiniteStateMachine([('A', 'B'), ('B', 'C'), ('C', 'A')])1630sage: FSM1.states()1631['A', 'B', 'C']1632sage: FSM2 = FSM1.relabeled()1633sage: FSM2.states()1634[0, 1, 2]1635"""1636self._deepcopy_relabel_ = True1637new = deepcopy(self, memo)1638del self._deepcopy_relabel_1639return new164016411642def __hash__(self):1643"""1644Since finite state machines are mutable, they should not be1645hashable, so we return a type error.16461647INPUT:16481649Nothing.16501651OUTPUT:16521653The hash of this finite state machine.16541655EXAMPLES::16561657sage: hash(FiniteStateMachine())1658Traceback (most recent call last):1659...1660TypeError: Finite state machines are mutable, and thus not hashable.1661"""1662if getattr(self, "_immutable", False):1663return hash((tuple(self.states()), tuple(self.transitions())))1664raise TypeError, "Finite state machines are mutable, " \1665"and thus not hashable."166616671668#*************************************************************************1669# operators1670#*************************************************************************167116721673def __add__(self, other):1674"""1675Returns the disjoint union of the finite state machines self and other.16761677INPUT:16781679- ``other`` -- a finite state machine.16801681OUTPUT:16821683A new finite state machine.16841685TESTS::16861687sage: FiniteStateMachine() + FiniteStateMachine([('A', 'B')])1688Traceback (most recent call last):1689...1690NotImplementedError1691"""1692if is_FiniteStateMachine(other):1693return self.disjoint_union(other)169416951696def __iadd__(self, other):1697"""1698TESTS::16991700sage: F = FiniteStateMachine()1701sage: F += FiniteStateMachine()1702Traceback (most recent call last):1703...1704NotImplementedError1705"""1706raise NotImplementedError170717081709def __mul__(self, other):1710"""1711TESTS::17121713sage: FiniteStateMachine() * FiniteStateMachine([('A', 'B')])1714Traceback (most recent call last):1715...1716NotImplementedError1717"""1718if is_FiniteStateMachine(other):1719return self.intersection(other)172017211722def __imul__(self, other):1723"""1724TESTS::17251726sage: F = FiniteStateMachine()1727sage: F *= FiniteStateMachine()1728Traceback (most recent call last):1729...1730NotImplementedError1731"""1732raise NotImplementedError173317341735def __call__(self, *args, **kwargs):1736"""1737Calls either method :meth:`.composition` or :meth:`.process`.17381739EXAMPLES::17401741sage: from sage.combinat.finite_state_machine import FSMState1742sage: A = FSMState('A', is_initial=True, is_final=True)1743sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]})1744sage: binary_inverter([0, 1, 0, 0, 1, 1])1745(True, 'A', [1, 0, 1, 1, 0, 0])17461747::17481749sage: F = Transducer([('A', 'B', 1, 0), ('B', 'B', 1, 1),1750....: ('B', 'B', 0, 0)],1751....: initial_states=['A'], final_states=['B'])1752sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),1753....: (2, 2, 0, 1), (2, 1, 1, 1)],1754....: initial_states=[1], final_states=[1])1755sage: H = G(F)1756sage: H.states()1757[('A', 1), ('B', 1), ('B', 2)]1758"""1759if len(args) == 0:1760raise TypeError, "Called with too few arguments."1761if is_FiniteStateMachine(args[0]):1762return self.composition(*args, **kwargs)1763if hasattr(args[0], '__iter__'):1764return self.process(*args, **kwargs)1765raise TypeError, "Do not know what to do with that arguments."176617671768#*************************************************************************1769# tests1770#*************************************************************************177117721773def __nonzero__(self):1774"""1775Returns True if the finite state machine consists of at least1776one state.17771778INPUT:17791780Nothing.17811782OUTPUT:17831784True or False.17851786TESTS::17871788sage: FiniteStateMachine().__nonzero__()1789False1790"""1791return len(self._states_) > 0179217931794def __eq__(left, right):1795"""1796Returns True if the two finite state machines are equal, i.e.,1797if they have the same states and the same transitions.17981799INPUT:18001801- ``left`` -- a finite state machine.18021803- ``right`` -- a finite state machine.18041805OUTPUT:18061807True or False.18081809EXAMPLES::18101811sage: F = FiniteStateMachine([('A', 'B', 1)])1812sage: F == FiniteStateMachine()1813False1814"""1815if not is_FiniteStateMachine(right):1816raise TypeError, 'Only instances of FiniteStateMachine ' \1817'can be compared.'1818if len(left._states_) != len(right._states_):1819return False1820for state in left.iter_states():1821if state not in right._states_:1822return False1823left_transitions = state.transitions1824right_transitions = right.state(state).transitions1825if len(left_transitions) != len(right_transitions):1826return False1827for t in left_transitions:1828if t not in right_transitions:1829return False1830return True183118321833def __ne__(left, right):1834"""1835Tests for inequality, complement of :meth:`.__eq__`.18361837INPUT:18381839- ``left`` -- a finite state machine.18401841- ``right`` -- a finite state machine.18421843OUTPUT:18441845True or False.18461847EXAMPLES::18481849sage: E = FiniteStateMachine([('A', 'B', 0)])1850sage: F = Automaton([('A', 'B', 0)])1851sage: G = Transducer([('A', 'B', 0, 1)])1852sage: E == F1853True1854sage: E == G1855False1856"""1857return (not (left == right))185818591860def __contains__(self, item):1861"""1862Returns true, if the finite state machine contains the1863state or transition item. Note that only the labels of the1864states and the input and output words are tested.18651866INPUT:18671868- ``item`` -- a state or a transition.18691870OUTPUT:18711872True or False.18731874EXAMPLES::18751876sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition1877sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)])1878sage: FSMState('A', is_initial=True) in F1879True1880sage: 'A' in F1881False1882sage: FSMTransition('A', 'B', 0) in F1883True1884"""1885if is_FSMState(item):1886return self.has_state(item)1887if is_FSMTransition(item):1888return self.has_transition(item)1889return False189018911892#*************************************************************************1893# representations / LaTeX1894#*************************************************************************189518961897def _repr_(self):1898"""1899Represents the finite state machine as "Finite state machine1900with n states" where n is the number of states.19011902INPUT:19031904Nothing.19051906OUTPUT:19071908A string.19091910EXAMPLES::19111912sage: FiniteStateMachine()._repr_()1913'Finite state machine with 0 states'1914"""1915return "Finite state machine with %s states" % len(self._states_)191619171918def _latex_(self):1919r"""1920Returns a LaTeX code for the graph of the finite state machine.19211922INPUT:19231924Nothing.19251926OUTPUT:19271928A string.19291930EXAMPLES::19311932sage: F = FiniteStateMachine([('A', 'B', 1, 2)])1933sage: F._latex_()1934'\\begin{tikzpicture}[auto]\n\\node[state] (v0) at (3.000000,0.000000) {\\text{\\texttt{A}}}\n;\\node[state] (v1) at (-3.000000,0.000000) {\\text{\\texttt{B}}}\n;\\path[->] (v0) edge node {$ $} (v1);\n\\end{tikzpicture}'1935"""1936result = "\\begin{tikzpicture}[auto]\n"1937j = 0;1938for vertex in self.states():1939if not hasattr(vertex, "coordinates"):1940vertex.coordinates = (3*cos(2*pi*j/len(self.states())),19413*sin(2*pi*j/len(self.states())))1942options = ""1943if vertex in self.final_states():1944options += ",accepting"1945if hasattr(vertex, "format_label"):1946label = vertex.format_label()1947elif hasattr(self, "format_state_label"):1948label = self.format_state_label(vertex)1949else:1950label = latex(vertex.label())1951result += "\\node[state%s] (v%d) at (%f,%f) {%s}\n;" % (1952options, j, vertex.coordinates[0],1953vertex.coordinates[1], label)1954vertex._number_ = j1955j += 11956adjacent = {}1957for source in self.states():1958for target in self.states():1959transitions = filter(lambda transition: \1960transition.to_state == target,1961source.transitions)1962adjacent[source, target] = transitions19631964for ((source, target), transitions) in adjacent.iteritems():1965if len(transitions) > 0:1966labels = []1967for transition in transitions:1968if hasattr(transition, "format_label"):1969labels.append(transition.format_label())1970continue1971elif hasattr(self, "format_transition_label"):1972format_transition_label = self.format_transition_label1973else:1974format_transition_label = latex1975labels.append(self._latex_transition_label_(1976transition, format_transition_label))1977label = ", ".join(labels)1978if source != target:1979if len(adjacent[target, source]) > 0:1980angle = atan2(1981target.coordinates[1] - source.coordinates[1],1982target.coordinates[0]-source.coordinates[0])*180/pi1983angle_source = ".%.2f" % ((angle+5).n(),)1984angle_target = ".%.2f" % ((angle+175).n(),)1985else:1986angle_source = ""1987angle_target = ""1988result += "\\path[->] (v%d%s) edge node {$%s$} (v%d%s);\n" % (1989source._number_, angle_source, label,1990target._number_, angle_target)1991else:1992result += "\\path[->] (v%d) edge[loop above] node {$%s$} ();\n" % (1993source._number_, label)19941995result += "\\end{tikzpicture}"1996return result199719981999def _latex_transition_label_(self, transition, format_function=latex):2000r"""2001Returns the proper transition label.20022003INPUT:20042005- ``transition`` - a transition20062007- ``format_function'' - a function formatting the labels20082009OUTPUT:20102011A string.20122013TESTS::20142015sage: F = FiniteStateMachine([('A', 'B', 0, 1)])2016sage: t = F.transitions()[0]2017sage: F._latex_transition_label_(t)2018' '2019"""2020return ' '202120222023#*************************************************************************2024# other2025#*************************************************************************202620272028def _matrix_(self, R=None):2029"""2030Returns the adjacency matrix of the finite state machine.2031See :meth:`.adjacency_matrix` for more information.20322033EXAMPLES::20342035sage: B = FiniteStateMachine({0: {0: (0, 0), 'a': (1, 0)},2036....: 'a': {2: (0, 0), 3: (1, 0)},2037....: 2:{0:(1, 1), 4:(0, 0)},2038....: 3:{'a':(0, 1), 2:(1, 1)},2039....: 4:{4:(1, 1), 3:(0, 1)}},2040....: initial_states=[0])2041sage: B._matrix_()2042[1 1 0 0 0]2043[0 0 1 1 0]2044[x 0 0 0 1]2045[0 x x 0 0]2046[0 0 0 x x]2047"""2048return self.adjacency_matrix()204920502051def adjacency_matrix(self, input=None,2052entry=(lambda transition:var('x')**transition.word_out[0])):2053"""2054Returns the adjacency matrix of the underlying graph.20552056INPUT:20572058- ``input`` -- Only transitions with input label ``input`` are2059respected.20602061- ``entry`` -- The function ``entry`` takes a transition and2062the return value is written in the matrix as the entry2063``(transition.from_state, transition.to_state)``.20642065OUTPUT:20662067A matrix.20682069If any label of a state is not an integer, the finite state2070machine is relabeled at the beginning. If there are more than2071one transitions between two states, then the different return2072values of ``entry`` are added up.20732074The default value of entry takes the variable ``x`` to the2075power of the output word of the transition.20762077EXAMPLES::20782079sage: B = FiniteStateMachine({0:{0:(0, 0), 'a':(1, 0)},2080....: 'a':{2:(0, 0), 3:(1, 0)},2081....: 2:{0:(1, 1), 4:(0, 0)},2082....: 3:{'a':(0, 1), 2:(1, 1)},2083....: 4:{4:(1, 1), 3:(0, 1)}},2084....: initial_states=[0])2085sage: B.adjacency_matrix()2086[1 1 0 0 0]2087[0 0 1 1 0]2088[x 0 0 0 1]2089[0 x x 0 0]2090[0 0 0 x x]2091sage: B.adjacency_matrix(entry=(lambda transition: 1))2092[1 1 0 0 0]2093[0 0 1 1 0]2094[1 0 0 0 1]2095[0 1 1 0 0]2096[0 0 0 1 1]2097sage: B.adjacency_matrix(1, entry=(lambda transition:2098....: exp(I*transition.word_out[0]*var('t'))))2099[ 0 1 0 0 0]2100[ 0 0 0 1 0]2101[e^(I*t) 0 0 0 0]2102[ 0 0 e^(I*t) 0 0]2103[ 0 0 0 0 e^(I*t)]21042105"""2106relabeledFSM = self2107l = len(relabeledFSM.states())2108for state in self.states():2109if state.label() not in ZZ or state.label() >= l or state.label() < 0:2110relabeledFSM = self.relabeled()2111break2112dictionary = {}2113for transition in relabeledFSM.iter_transitions():2114if input is None or transition.word_in == [input]:2115if (transition.from_state.label(), transition.to_state.label()) in dictionary:2116dictionary[(transition.from_state.label(), transition.to_state.label())] += entry(transition)2117else:2118dictionary[(transition.from_state.label(), transition.to_state.label())] = entry(transition)2119return matrix(len(relabeledFSM.states()), dictionary)212021212122def determine_alphabets(self, reset=True):2123"""2124Determines the input and output alphabet according to the2125transitions in self.21262127INPUT:21282129- ``reset`` -- If reset is True, then the existing input2130alphabet is erased, otherwise new letters are appended to2131the existing alphabet.21322133OUTPUT:21342135Nothing.21362137After this operation the input alphabet and the output2138alphabet of self are a list of letters.21392140EXAMPLES::21412142sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1),2143....: (2, 2, 1, 1), (2, 2, 0, 0)],2144....: determine_alphabets=False)2145sage: (T.input_alphabet, T.output_alphabet)2146(None, None)2147sage: T.determine_alphabets()2148sage: (T.input_alphabet, T.output_alphabet)2149([0, 1, 2], [0, 1])2150"""2151if reset:2152ain = set()2153aout = set()2154else:2155ain = set(self.input_alphabet)2156aout = set(self.output_alphabet)21572158for t in self.iter_transitions():2159for letter in t.word_in:2160ain.add(letter)2161for letter in t.word_out:2162aout.add(letter)2163self.input_alphabet = list(ain)2164self.output_alphabet = list(aout)216521662167#*************************************************************************2168# get states and transitions2169#*************************************************************************217021712172def states(self):2173"""2174Returns the states of the finite state machine.21752176INPUT:21772178Nothing.21792180OUTPUT:21812182The states of the finite state machine as list.21832184EXAMPLES::21852186sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])2187sage: FSM.states()2188['1', '2']21892190"""2191return copy(self._states_)219221932194def iter_states(self):2195"""2196Returns an iterator of the states.21972198INPUT:21992200Nothing.22012202OUTPUT:22032204An iterator of the states of the finite state machine.22052206EXAMPLES::22072208sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])2209sage: [s.label() for s in FSM.iter_states()]2210['1', '2']2211"""2212return iter(self._states_)221322142215def transitions(self, from_state=None):2216"""2217Returns a list of all transitions.22182219INPUT:22202221- ``from_state`` -- (default: ``None``) If ``from_state`` is2222given, then a list of transitions starting there is given.22232224OUTPUT:22252226A list of all transitions.22272228EXAMPLES::22292230sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])2231sage: FSM.transitions()2232[Transition from '1' to '2': 1|-,2233Transition from '2' to '2': 0|-]2234"""2235return list(self.iter_transitions(from_state))223622372238def iter_transitions(self, from_state=None):2239"""2240Returns an iterator of all transitions.22412242INPUT:22432244- ``from_state`` -- (default: ``None``) If ``from_state`` is2245given, then a list of transitions starting there is given.22462247OUTPUT:22482249An iterator of all transitions.22502251EXAMPLES::22522253sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])2254sage: [(t.from_state.label(), t.to_state.label())2255....: for t in FSM.iter_transitions('1')]2256[('1', '2')]2257sage: [(t.from_state.label(), t.to_state.label())2258....: for t in FSM.iter_transitions('2')]2259[('2', '2')]2260sage: [(t.from_state.label(), t.to_state.label())2261....: for t in FSM.iter_transitions()]2262[('1', '2'), ('2', '2')]2263"""2264if from_state is None:2265return self._iter_transitions_all_()2266else:2267return iter(self.state(from_state).transitions)226822692270def _iter_transitions_all_(self):2271"""2272Returns an iterator over all transitions.22732274INPUT:22752276Nothing.22772278OUTPUT:22792280An iterator over all transitions.22812282EXAMPLES::22832284sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])2285sage: [(t.from_state.label(), t.to_state.label())2286....: for t in FSM._iter_transitions_all_()]2287[('1', '2'), ('2', '2')]2288"""2289for state in self.iter_states():2290for t in state.transitions:2291yield t229222932294def initial_states(self):2295"""2296Returns a list of all initial states.22972298INPUT:22992300Nothing.23012302OUTPUT:23032304A list of all initial states.23052306EXAMPLES::23072308sage: from sage.combinat.finite_state_machine import FSMState2309sage: A = FSMState('A', is_initial=True)2310sage: B = FSMState('B')2311sage: F = FiniteStateMachine([(A, B, 1, 0)])2312sage: F.initial_states()2313['A']2314"""2315return list(self.iter_initial_states())231623172318def iter_initial_states(self):2319"""2320Returns an iterator of the initial states.23212322INPUT:23232324Nothing.23252326OUTPUT:23272328An iterator over all initial states.23292330EXAMPLES::23312332sage: from sage.combinat.finite_state_machine import FSMState2333sage: A = FSMState('A', is_initial=True)2334sage: B = FSMState('B')2335sage: F = FiniteStateMachine([(A, B, 1, 0)])2336sage: [s.label() for s in F.iter_initial_states()]2337['A']2338"""2339return itertools.ifilter(lambda s:s.is_initial, self.iter_states())234023412342def final_states(self):2343"""2344Returns a list of all final states.23452346INPUT:23472348Nothing.23492350OUTPUT:23512352A list of all final states.23532354EXAMPLES::23552356sage: from sage.combinat.finite_state_machine import FSMState2357sage: A = FSMState('A', is_final=True)2358sage: B = FSMState('B', is_initial=True)2359sage: C = FSMState('C', is_final=True)2360sage: F = FiniteStateMachine([(A, B), (A, C)])2361sage: F.final_states()2362['A', 'C']2363"""2364return list(self.iter_final_states())236523662367def iter_final_states(self):2368"""2369Returns an iterator of the final states.23702371INPUT:23722373Nothing.23742375OUTPUT:23762377An iterator over all initial states.23782379EXAMPLES::23802381sage: from sage.combinat.finite_state_machine import FSMState2382sage: A = FSMState('A', is_final=True)2383sage: B = FSMState('B', is_initial=True)2384sage: C = FSMState('C', is_final=True)2385sage: F = FiniteStateMachine([(A, B), (A, C)])2386sage: [s.label() for s in F.iter_final_states()]2387['A', 'C']2388"""2389return itertools.ifilter(lambda s:s.is_final, self.iter_states())239023912392def state(self, state):2393"""2394Returns the state of the finite state machine.23952396INPUT:23972398- ``state`` -- If ``state`` is not an instance of2399:class:`FSMState`, then it is assumed that it is the label2400of a state.24012402OUTPUT:24032404Returns the state of the finite state machine corresponding to2405``state``.24062407If no state is found, then a ``LookupError`` is thrown.24082409EXAMPLES::24102411sage: from sage.combinat.finite_state_machine import FSMState2412sage: A = FSMState('A')2413sage: FSM = FiniteStateMachine([(A, 'B'), ('C', A)])2414sage: FSM.state('A') == A2415True2416sage: FSM.state('xyz')2417Traceback (most recent call last):2418...2419LookupError: No state with label xyz found.2420"""2421def what(s, switch):2422if switch:2423return s.label()2424else:2425return s2426switch = is_FSMState(state)24272428try:2429return self._states_dict_[what(state, switch)]2430except AttributeError:2431for s in self.iter_states():2432if what(s, not switch) == state:2433return s2434except KeyError:2435pass2436raise LookupError, \2437"No state with label %s found." % (what(state, switch),)243824392440def transition(self, transition):2441"""2442Returns the transition of the finite state machine.24432444INPUT:24452446- ``transition`` -- If ``transition`` is not an instance of2447:class:`FSMTransition`, then it is assumed that it is a2448tuple ``(from_state, to_state, word_in, word_out)``.24492450OUTPUT:24512452Returns the transition of the finite state machine2453corresponding to ``transition``.24542455If no transition is found, then a ``LookupError`` is thrown.24562457EXAMPLES::24582459sage: from sage.combinat.finite_state_machine import FSMTransition2460sage: t = FSMTransition('A', 'B', 0)2461sage: F = FiniteStateMachine([t])2462sage: F.transition(('A', 'B', 0))2463Transition from 'A' to 'B': 0|-2464sage: id(t) == id(F.transition(('A', 'B', 0)))2465True2466"""2467if not is_FSMTransition(transition):2468transition = FSMTransition(*transition)2469for s in self.iter_transitions(transition.from_state):2470if s == transition:2471return s2472raise LookupError, "No transition found."247324742475#*************************************************************************2476# properties (state and transitions)2477#*************************************************************************247824792480def has_state(self, state):2481"""2482Returns whether ``state`` is one of the states of the finite2483state machine.24842485INPUT:24862487- ``state`` can be a :class:`FSMState` or a label of a state.24882489OUTPUT:24902491True or False.24922493EXAMPLES::24942495sage: FiniteStateMachine().has_state('A')2496False2497"""2498try:2499self.state(state)2500return True2501except LookupError:2502return False250325042505def has_transition(self, transition):2506"""2507Returns whether ``transition`` is one of the transitions of2508the finite state machine.25092510INPUT:25112512- ``transition`` has to be a :class:`FSMTransition`.25132514OUTPUT:25152516True or False.25172518EXAMPLES::25192520sage: from sage.combinat.finite_state_machine import FSMTransition2521sage: t = FSMTransition('A', 'A', 0, 1)2522sage: FiniteStateMachine().has_transition(t)2523False2524sage: FiniteStateMachine().has_transition(('A', 'A', 0, 1))2525Traceback (most recent call last):2526...2527TypeError: Transition is not an instance of FSMTransition.2528"""2529if is_FSMTransition(transition):2530return transition in self.iter_transitions()2531raise TypeError, "Transition is not an instance of FSMTransition."253225332534def has_initial_state(self, state):2535"""2536Returns whether ``state`` is one of the initial states of the2537finite state machine.25382539INPUT:25402541- ``state`` can be a :class:`FSMState` or a label.25422543OUTPUT:25442545True or False.25462547EXAMPLES::25482549sage: F = FiniteStateMachine([('A', 'A')], initial_states=['A'])2550sage: F.has_initial_state('A')2551True2552"""2553try:2554return self.state(state).is_initial2555except LookupError:2556return False255725582559def has_initial_states(self):2560"""2561Returns whether the finite state machine has an initial state.25622563INPUT:25642565Nothing.25662567OUTPUT:25682569True or False.25702571EXAMPLES::25722573sage: FiniteStateMachine().has_initial_states()2574False2575"""2576return len(self.initial_states()) > 0257725782579def has_final_state(self, state):2580"""2581Returns whether ``state`` is one of the final states of the2582finite state machine.25832584INPUT:25852586- ``state`` can be a :class:`FSMState` or a label.25872588OUTPUT:25892590True or False.25912592EXAMPLES::25932594sage: FiniteStateMachine(final_states=['A']).has_final_state('A')2595True2596"""2597try:2598return self.state(state).is_final2599except LookupError:2600return False260126022603def has_final_states(self):2604"""2605Returns whether the finite state machine has a final state.26062607INPUT:26082609Nothing.26102611OUTPUT:26122613True or False.26142615EXAMPLES::26162617sage: FiniteStateMachine().has_final_states()2618False2619"""2620return len(self.final_states()) > 0262126222623#*************************************************************************2624# properties2625#*************************************************************************262626272628def is_deterministic(self):2629"""2630Returns whether the finite finite state machine is deterministic.26312632INPUT:26332634Nothing.26352636OUTPUT:26372638True or False.26392640A finite state machine is considered to be deterministic if2641each transition has input label of length one and for each2642pair `(q,a)` where `q` is a state and `a` is an element of the2643input alphabet, there is at most one transition from `q` with2644input label `a`.26452646TESTS::26472648sage: fsm = FiniteStateMachine()2649sage: fsm.add_transition(('A', 'B', 0, []))2650Transition from 'A' to 'B': 0|-2651sage: fsm.is_deterministic()2652True2653sage: fsm.add_transition(('A', 'C', 0, []))2654Transition from 'A' to 'C': 0|-2655sage: fsm.is_deterministic()2656False2657sage: fsm.add_transition(('A', 'B', [0,1], []))2658Transition from 'A' to 'B': 0,1|-2659sage: fsm.is_deterministic()2660False2661"""2662for state in self.states():2663for transition in state.transitions:2664if len(transition.word_in) != 1:2665return False26662667transition_classes_by_word_in = full_group_by(2668state.transitions,2669key=lambda t:t.word_in)26702671for key,transition_class in transition_classes_by_word_in:2672if len(transition_class) > 1:2673return False2674return True267526762677def is_connected(self):2678"""2679TESTS::26802681sage: FiniteStateMachine().is_connected()2682Traceback (most recent call last):2683...2684NotImplementedError2685"""2686raise NotImplementedError268726882689#*************************************************************************2690# let the finite state machine work2691#*************************************************************************269226932694def process(self, *args, **kwargs):2695"""2696Returns whether the finite state machine accepts the input, the state2697where the computation stops and which output is generated.26982699INPUT:27002701- ``input_tape`` -- The input tape can be a list with entries from2702the input alphabet.27032704- ``initial_state`` -- (default: ``None``) The state in which2705to start. If this parameter is ``None`` and there is only2706one initial state in the machine, then this state is taken.27072708OUTPUT:27092710A triple, where27112712- the first entry is true if the input string is accepted,27132714- the second gives the reached state after processing the2715input tape, and27162717- the third gives a list of the output labels used during2718processing (in the case the finite state machine runs as2719transducer).27202721Note that in the case the finite state machine is not2722deterministic, one possible path is gone. This means, that in2723this case the output can be wrong. Use2724:meth:`.determinisation` to get a deterministic finite state2725machine and try again.27262727EXAMPLES::27282729sage: from sage.combinat.finite_state_machine import FSMState2730sage: A = FSMState('A', is_initial = True, is_final = True)2731sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]})2732sage: binary_inverter.process([0, 1, 0, 0, 1, 1])2733(True, 'A', [1, 0, 1, 1, 0, 0])27342735Alternatively, we can invoke this function by::27362737sage: binary_inverter([0, 1, 0, 0, 1, 1])2738(True, 'A', [1, 0, 1, 1, 0, 0])27392740::27412742sage: NAF_ = FSMState('_', is_initial = True, is_final = True)2743sage: NAF1 = FSMState('1', is_final = True)2744sage: NAF = Automaton(2745....: {NAF_: [(NAF_, 0), (NAF1, 1)], NAF1: [(NAF_, 0)]})2746sage: [NAF.process(w)[0] for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1],2747....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]]2748[True, True, False, True, False, False]27492750"""2751it = self.iter_process(*args, **kwargs)2752for _ in it:2753pass2754return (it.accept_input, it.current_state, it.output_tape)275527562757def iter_process(self, input_tape=None, initial_state=None):2758"""2759See `process` for more informations.27602761EXAMPLES::27622763sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},2764....: initial_states=['A'], final_states=['A'])2765sage: it = inverter.iter_process(input_tape=[0, 1, 1])2766sage: for _ in it:2767....: pass2768sage: it.output_tape2769[1, 0, 0]2770"""2771return FSMProcessIterator(self, input_tape, initial_state)277227732774#*************************************************************************2775# change finite state machine (add/remove state/transitions)2776#*************************************************************************277727782779def add_state(self, state):2780"""2781Adds a state to the finite state machine and returns the new2782state. If the state already exists, that existing state is2783returned.27842785INPUT:27862787- ``state`` is either an instance of FSMState or, otherwise, a2788label of a state.27892790OUTPUT:27912792The new or existing state.27932794EXAMPLES::27952796sage: from sage.combinat.finite_state_machine import FSMState2797sage: F = FiniteStateMachine()2798sage: A = FSMState('A', is_initial=True)2799sage: F.add_state(A)2800'A'2801"""2802try:2803return self.state(state)2804except LookupError:2805pass2806# at this point we know that we have a new state2807if is_FSMState(state):2808s = state2809else:2810s = FSMState(state)2811s.transitions = list()2812self._states_.append(s)2813try:2814self._states_dict_[s.label()] = s2815except AttributeError:2816pass2817return s281828192820def add_states(self, states):2821"""2822Adds several states. See add_state for more information.28232824INPUT:28252826- ``states`` -- a list of states or iterator over states.28272828OUTPUT:28292830Nothing.28312832EXAMPLES::28332834sage: F = FiniteStateMachine()2835sage: F.add_states(['A', 'B'])2836sage: F.states()2837['A', 'B']2838"""2839for state in states:2840self.add_state(state)284128422843def add_transition(self, *args, **kwargs):2844"""2845Adds a transition to the finite state machine and returns the2846new transition. If the transition already exists, that2847existing transition is returned.28482849INPUT:28502851The following forms are all accepted:28522853::28542855sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition2856sage: A = FSMState('A')2857sage: B = FSMState('B')28582859sage: FSM = FiniteStateMachine()2860sage: FSM.add_transition(FSMTransition(A, B, 0, 1))2861Transition from 'A' to 'B': 0|128622863sage: FSM = FiniteStateMachine()2864sage: FSM.add_transition(A, B, 0, 1)2865Transition from 'A' to 'B': 0|128662867sage: FSM = FiniteStateMachine()2868sage: FSM.add_transition(A, B, word_in=0, word_out=1)2869Transition from 'A' to 'B': 0|128702871sage: FSM = FiniteStateMachine()2872sage: FSM.add_transition('A', 'B', {'word_in': 0, 'word_out': 1})2873Transition from 'A' to 'B': {'word_in': 0, 'word_out': 1}|-28742875sage: FSM = FiniteStateMachine()2876sage: FSM.add_transition(from_state=A, to_state=B,2877....: word_in=0, word_out=1)2878Transition from 'A' to 'B': 0|128792880sage: FSM = FiniteStateMachine()2881sage: FSM.add_transition({'from_state': A, 'to_state': B,2882....: 'word_in': 0, 'word_out': 1})2883Transition from 'A' to 'B': 0|128842885sage: FSM = FiniteStateMachine()2886sage: FSM.add_transition((A, B, 0, 1))2887Transition from 'A' to 'B': 0|128882889sage: FSM = FiniteStateMachine()2890sage: FSM.add_transition([A, B, 0, 1])2891Transition from 'A' to 'B': 0|128922893If the states ``A`` and ``B`` are not instances of FSMState, then2894it is assumed that they are labels of states.28952896OUTPUT:28972898The new or existing transition.2899"""2900if len(args) + len(kwargs) == 0:2901return2902if len(args) + len(kwargs) == 1:2903if len(args) == 1:2904d = args[0]2905if is_FSMTransition(d):2906return self._add_fsm_transition_(d)2907else:2908d = kwargs.itervalues().next()2909if hasattr(d, 'iteritems'):2910args = []2911kwargs = d2912elif hasattr(d, '__iter__'):2913args = d2914kwargs = {}2915else:2916raise TypeError, "Cannot decide what to do with input."29172918data = dict(zip(2919('from_state', 'to_state', 'word_in', 'word_out', 'hook'),2920args))2921data.update(kwargs)29222923data['from_state'] = self.add_state(data['from_state'])2924data['to_state'] = self.add_state(data['to_state'])29252926return self._add_fsm_transition_(FSMTransition(**data))292729282929def _add_fsm_transition_(self, t):2930"""2931Adds a transition.29322933INPUT:29342935- ``t`` -- an instance of ``FSMTransition``.29362937OUTPUT:29382939The new transition.29402941TESTS::29422943sage: from sage.combinat.finite_state_machine import FSMTransition2944sage: F = FiniteStateMachine()2945sage: F._add_fsm_transition_(FSMTransition('A', 'B'))2946Transition from 'A' to 'B': -|-2947"""2948try:2949return self.transition(t)2950except LookupError:2951pass2952from_state = self.add_state(t.from_state)2953self.add_state(t.to_state)2954from_state.transitions.append(t)2955return t295629572958def add_from_transition_function(self, function, initial_states=None,2959explore_existing_states=True):2960"""2961Constructs a finite state machine from a transition function.29622963INPUT:29642965- ``function`` may return a tuple (new_state, output_word) or a2966list of such tuples.29672968- ``initial_states`` -- If no initial states are given, the2969already existing initial states of self are taken.29702971- If ``explore_existing_states`` is True (default), then2972already existing states in self (e.g. already given final2973states) will also be processed if they are reachable from2974the initial states.29752976OUTPUT:29772978Nothing.29792980EXAMPLES::29812982sage: F = FiniteStateMachine(initial_states=['A'],2983....: input_alphabet=[0, 1])2984sage: def f(state, input):2985....: return [('A', input), ('B', 1-input)]2986sage: F.add_from_transition_function(f)2987sage: F.transitions()2988[Transition from 'A' to 'A': 0|0,2989Transition from 'A' to 'B': 0|1,2990Transition from 'A' to 'A': 1|1,2991Transition from 'A' to 'B': 1|0,2992Transition from 'B' to 'A': 0|0,2993Transition from 'B' to 'B': 0|1,2994Transition from 'B' to 'A': 1|1,2995Transition from 'B' to 'B': 1|0]29962997Initial states can also be given as a parameter::29982999sage: F = FiniteStateMachine(input_alphabet=[0,1])3000sage: def f(state, input):3001....: return [('A', input), ('B', 1-input)]3002sage: F.add_from_transition_function(f,initial_states=['A'])3003sage: F.initial_states()3004['A']30053006Already existing states in the finite state machine (the final3007states in the example below) are also explored::30083009sage: F = FiniteStateMachine(initial_states=[0],3010....: final_states=[1],3011....: input_alphabet=[0])3012sage: def transition_function(state, letter):3013....: return(1-state, [])3014sage: F.add_from_transition_function(transition_function)3015sage: F.transitions()3016[Transition from 0 to 1: 0|-,3017Transition from 1 to 0: 0|-]30183019If ``explore_existing_states=False``, however, this behavior3020is turned off, i.e., already existing states are not3021explored::30223023sage: F = FiniteStateMachine(initial_states=[0],3024....: final_states=[1],3025....: input_alphabet=[0])3026sage: def transition_function(state, letter):3027....: return(1-state, [])3028sage: F.add_from_transition_function(transition_function,3029....: explore_existing_states=False)3030sage: F.transitions()3031[Transition from 0 to 1: 0|-]30323033TEST::30343035sage: F = FiniteStateMachine(initial_states=['A'])3036sage: def f(state, input):3037....: return [('A', input), ('B', 1-input)]3038sage: F.add_from_transition_function(f)3039Traceback (most recent call last):3040...3041ValueError: No input alphabet is given.3042Try calling determine_alphabets().3043"""3044if self.input_alphabet is None:3045raise ValueError, ("No input alphabet is given. "3046"Try calling determine_alphabets().")30473048if initial_states is None:3049not_done = self.initial_states()3050elif hasattr(initial_states, '__iter__'):3051not_done = []3052for s in initial_states:3053state = self.add_state(s)3054state.is_initial = True3055not_done.append(state)3056else:3057raise TypeError, 'Initial states must be iterable ' \3058'(e.g. a list of states).'3059if len(not_done) == 0:3060raise ValueError, "No state is initial."3061if explore_existing_states:3062ignore_done = self.states()3063for s in not_done:3064try:3065ignore_done.remove(s)3066except ValueError:3067pass3068else:3069ignore_done = []3070while len(not_done) > 0:3071s = not_done.pop(0)3072for letter in self.input_alphabet:3073try:3074return_value = function(s.label(), letter)3075except LookupError:3076continue3077if not hasattr(return_value, "pop"):3078return_value = [return_value]3079try:3080for (st_label, word) in return_value:3081if not self.has_state(st_label):3082not_done.append(self.add_state(st_label))3083elif len(ignore_done) > 0:3084u = self.state(st_label)3085if u in ignore_done:3086not_done.append(u)3087ignore_done.remove(u)3088self.add_transition(s, st_label,3089word_in=letter, word_out=word)3090except TypeError:3091raise ValueError("The callback function for add_from_transition is expected to return a pair (new_state, output_label) or a list of such pairs. For the state %s and the input letter %s, it however returned %s, which is not acceptable." % (s.label(), letter, return_value))309230933094def add_transitions_from_function(self, function, labels_as_input=True):3095"""3096Adds a transition if ``function(state, state)`` says that there is one.30973098INPUT:30993100- ``function`` -- a transition function. Given two states3101``from_state`` and ``to_state`` (or their labels, if3102``label_as_input`` is true), this function shall return a3103tuple ``(word_in, word_out)`` to add a transition from3104``from_state`` to ``to_state`` with input and output labels3105``word_in`` and ``word_out``, respectively. If no such3106addition is to be added, the transition function shall3107return ``None``.31083109- ``label_as_input`` -- (default: ``True``)31103111OUTPUT:31123113Nothing.31143115EXAMPLES::31163117sage: F = FiniteStateMachine()3118sage: F.add_states(['A', 'B', 'C'])3119sage: def f(state1, state2):3120....: if state1 == 'C':3121....: return None3122....: return (0, 1)3123sage: F.add_transitions_from_function(f)3124sage: len(F.transitions())312563126"""3127for s_from in self.iter_states():3128for s_to in self.iter_states():3129if labels_as_input:3130t = function(s_from.label(), s_to.label())3131else:3132t = function(s_from, s_to)3133if hasattr(t, '__getitem__'):3134label_in = t[0]3135try:3136label_out = t[1]3137except LookupError:3138label_out = None3139self.add_transition(s_from, s_to, label_in, label_out)314031413142def delete_transition(self, t):3143"""3144Deletes a transition by removing it from the list of transitions of3145the state, where the transition starts.31463147INPUT:31483149- ``t`` -- a transition.31503151OUTPUT:31523153Nothing.31543155EXAMPLES::31563157sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)])3158sage: F.delete_transition(('A', 'B', 0))3159sage: F.transitions()3160[Transition from 'B' to 'A': 1|-]3161"""3162transition = self.transition(t)3163transition.from_state.transitions.remove(transition)316431653166def delete_state(self, s):3167"""3168Deletes a state and all transitions coming or going to this state.31693170INPUT:31713172- ``s`` -- s has to be a label of a state or :class:`FSMState`.31733174OUTPUT:31753176Nothing.31773178EXAMPLES::31793180sage: from sage.combinat.finite_state_machine import FSMTransition3181sage: t1 = FSMTransition('A', 'B', 0)3182sage: t2 = FSMTransition('B', 'B', 1)3183sage: F = FiniteStateMachine([t1, t2])3184sage: F.delete_state('A')3185sage: F. transitions()3186[Transition from 'B' to 'B': 1|-]3187"""3188state = self.state(s)3189for transition in self.transitions():3190if transition.to_state == state:3191self.delete_transition(transition)3192self._states_.remove(state)319331943195def remove_epsilon_transitions(self):3196"""3197TESTS::31983199sage: FiniteStateMachine().remove_epsilon_transitions()3200Traceback (most recent call last):3201...3202NotImplementedError3203"""3204raise NotImplementedError320532063207def accessible_components(self):3208"""3209Returns a new finite state machine with the accessible states3210of self and all transitions between those states.32113212INPUT:32133214Nothing.32153216OUTPUT:32173218A finite state machine with the accessible states of self and3219all transitions between those states.32203221A state is accessible if there is a directed path from an3222initial state to the state. If self has no initial states then3223a copy of the finite state machine self is returned.32243225EXAMPLES::32263227sage: F = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 0), (1, 0, 1)],3228....: initial_states=[0])3229sage: F.accessible_components()3230Automaton with 2 states32313232::32333234sage: F = Automaton([(0, 0, 1), (0, 0, 1), (1, 1, 0), (1, 0, 1)],3235....: initial_states=[0])3236sage: F.accessible_components()3237Automaton with 1 states3238"""3239if len(self.initial_states()) == 0:3240return deepcopy(self)32413242memo = {}3243def accessible(sf, read):3244trans = filter(lambda x: x.word_in[0] == read,3245self.transitions(sf))3246return map(lambda x: (deepcopy(x.to_state, memo), x.word_out),3247trans)32483249new_initial_states=map(lambda x: deepcopy(x, memo),3250self.initial_states())3251result = self.empty_copy()3252result.add_from_transition_function(accessible,3253initial_states=new_initial_states)3254for final_state in self.iter_final_states():3255try:3256new_final_state=result.state(final_state.label)3257new_final_state.is_final=True3258except LookupError:3259pass3260return result326132623263# *************************************************************************3264# creating new finite state machines3265# *************************************************************************326632673268def disjoint_union(self, other):3269"""3270TESTS::32713272sage: F = FiniteStateMachine([('A', 'A')])3273sage: FiniteStateMachine().disjoint_union(F)3274Traceback (most recent call last):3275...3276NotImplementedError3277"""3278raise NotImplementedError327932803281def concatenation(self, other):3282"""3283TESTS::32843285sage: F = FiniteStateMachine([('A', 'A')])3286sage: FiniteStateMachine().concatenation(F)3287Traceback (most recent call last):3288...3289NotImplementedError3290"""3291raise NotImplementedError329232933294def Kleene_closure(self):3295"""3296TESTS::32973298sage: FiniteStateMachine().Kleene_closure()3299Traceback (most recent call last):3300...3301NotImplementedError3302"""3303raise NotImplementedError330433053306def intersection(self, other):3307"""3308TESTS::33093310sage: F = FiniteStateMachine([('A', 'A')])3311sage: FiniteStateMachine().intersection(F)3312Traceback (most recent call last):3313...3314NotImplementedError3315"""3316raise NotImplementedError331733183319def product_FiniteStateMachine(self, other, function,3320new_input_alphabet=None,3321only_accessible_components=True):3322"""3323Returns a new finite state machine whose states are3324pairs of states of the original finite state machines.33253326INPUT:33273328- ``other`` -- a finite state machine.33293330- ``function`` has to accept two transitions from `A` to `B`3331and `C` to `D` and returns a pair ``(word_in, word_out)``3332which is the label of the transition `(A, C)` to `(B,3333D)`. If there is no transition from `(A, C)` to `(B, D)`,3334then ``function`` should raise a ``LookupError``.33353336- ``new_input_alphabet`` (optional)-- the new input alphabet3337as a list.33383339- ``only_accessible_components`` -- If true (default), then3340the result is piped through ``accessible_components``. If no3341``new_input_alphabet`` is given, it is determined by3342``determine_alphabets``.33433344OUTPUT:33453346A finite state machine whose states are pairs of states of the3347original finite state machines.33483349The labels of the transitions are defined by ``function``.33503351EXAMPLES::33523353sage: F = Automaton([('A', 'B', 1), ('A', 'A', 0), ('B', 'A', 2)],3354....: initial_states=['A'], final_states=['B'],3355....: determine_alphabets=True)3356sage: G = Automaton([(1, 1, 1)], initial_states=[1], final_states=[1])3357sage: def addition(transition1, transition2):3358....: return (transition1.word_in[0] + transition2.word_in[0],3359....: None)3360sage: H = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False)3361sage: H.transitions()3362[Transition from ('A', 1) to ('B', 1): 2|-,3363Transition from ('A', 1) to ('A', 1): 1|-,3364Transition from ('B', 1) to ('A', 1): 3|-]3365sage: H1 = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False)3366sage: H1.states()[0].label()[0] is F.states()[0]3367True3368sage: H1.states()[0].label()[1] is G.states()[0]3369True33703371::33723373sage: F = Automaton([(0,1,1/4), (0,0,3/4), (1,1,3/4), (1,0,1/4)],3374....: initial_states=[0] )3375sage: G = Automaton([(0,0,1), (1,1,3/4), (1,0,1/4)],3376....: initial_states=[0] )3377sage: H = F.product_FiniteStateMachine(3378....: G, lambda t1,t2: (t1.word_in[0]*t2.word_in[0], None))3379sage: H.states()3380[(0, 0), (1, 0)]33813382::33833384sage: F = Automaton([(0,1,1/4), (0,0,3/4), (1,1,3/4), (1,0,1/4)],3385....: initial_states=[0] )3386sage: G = Automaton([(0,0,1), (1,1,3/4), (1,0,1/4)],3387....: initial_states=[0] )3388sage: H = F.product_FiniteStateMachine(G,3389....: lambda t1,t2: (t1.word_in[0]*t2.word_in[0], None),3390....: only_accessible_components=False)3391sage: H.states()3392[(0, 0), (1, 0), (0, 1), (1, 1)]3393"""3394result = self.empty_copy()3395if new_input_alphabet is not None:3396result.input_alphabet = new_input_alphabet3397else:3398result.input_alphabet = None33993400for transition1 in self.transitions():3401for transition2 in other.transitions():3402try:3403word = function(transition1, transition2)3404except LookupError:3405continue3406result.add_transition((transition1.from_state,3407transition2.from_state),3408(transition1.to_state,3409transition2.to_state),3410word[0], word[1])3411for state in result.states():3412if all(map(lambda s: s.is_initial, state.label())):3413state.is_initial = True3414if all(map(lambda s: s.is_final, state.label())):3415state.is_final = True34163417if only_accessible_components:3418if new_input_alphabet is None:3419result.determine_alphabets()3420return result.accessible_components()3421else:3422return result342334243425def cartesian_product(self, other, only_accessible_components=True):3426"""3427Returns a new finite state machine, which is the cartesian3428product of self and other.34293430INPUT:34313432- ``other`` -- a finite state machine.34333434- ``only_accessible_components``34353436OUTPUT:34373438A new finite state machine, which is the cartesian product of3439self and other.34403441The set of states of the new automaton is the cartesian3442product of the set of states of both given automata. There is3443a transition `((A, B), (C, D), a)` in the new automaton if3444there are transitions `(A, C, a)` and `(B, C, a)` in the old3445automata.34463447EXAMPLES::34483449sage: aut1 = Automaton([('1', '2', 1), ('2', '2', 1), ('2', '2', 0)],3450....: initial_states=['1'], final_states=['2'],3451....: determine_alphabets=True)3452sage: aut2 = Automaton([('A', 'A', 1), ('A', 'B', 1),3453....: ('B', 'B', 0), ('B', 'A', 0)],3454....: initial_states=['A'], final_states=['B'],3455....: determine_alphabets=True)3456sage: res = aut1.cartesian_product(aut2)3457sage: res.transitions()3458[Transition from ('1', 'A') to ('2', 'A'): 1|-,3459Transition from ('1', 'A') to ('2', 'B'): 1|-,3460Transition from ('2', 'A') to ('2', 'A'): 1|-,3461Transition from ('2', 'A') to ('2', 'B'): 1|-,3462Transition from ('2', 'B') to ('2', 'B'): 0|-,3463Transition from ('2', 'B') to ('2', 'A'): 0|-]3464"""3465def function(transition1, transition2):3466if transition1.word_in == transition2.word_in \3467and transition1.word_out == transition2.word_out:3468return (transition1.word_in, transition1.word_out)3469else:3470raise LookupError34713472return self.product_FiniteStateMachine(3473other, function,3474only_accessible_components = only_accessible_components)347534763477def composition(self, other, algorithm=None,3478only_accessible_components=True):3479"""3480Returns a new transducer which is the composition of self and3481other.34823483INPUT:34843485- ``other`` -- a transducer34863487- ``algorithm`` -- can be one of the following34883489- ``direct`` -- The composition is calculated directly.34903491There can be arbitrarily many initial and final states,3492but the input and output labels must have length 1.349334943495WARNING: The output of other is fed into self.34963497- ``explorative`` -- An explorative algorithm is used.34983499At least the following restrictions apply, but are not3500checked:3501- both self and other have exactly one initial state3502- all input labels of transitions have length exactly 135033504The input alphabet of self has to be specified.35053506This is a very limited implementation of composition.3507WARNING: The output of ``other`` is fed into ``self``.35083509If algorithm is ``None``, then the algorithm is chosen3510automatically (at the moment always ``direct``).35113512OUTPUT:35133514A new transducer.35153516The labels of the new finite state machine are pairs3517of states of the original finite state machines.35183519EXAMPLES::35203521sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)],3522....: initial_states=['A', 'B'], final_states=['B'],3523....: determine_alphabets=True)3524sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1),3525....: (2, 2, 1, 1), (2, 2, 0, 0)],3526....: initial_states=[1], final_states=[2],3527....: determine_alphabets=True)3528sage: Hd = F.composition(G, algorithm='direct')3529sage: Hd.initial_states()3530[(1, 'B'), (1, 'A')]3531sage: Hd.transitions()3532[Transition from (1, 'B') to (1, 'A'): 1|1,3533Transition from (1, 'A') to (2, 'B'): 0|0,3534Transition from (2, 'B') to (2, 'A'): 0|1,3535Transition from (2, 'A') to (2, 'B'): 1|0]35363537::35383539sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1),3540....: ('B', 'B', 0, 0)],3541....: initial_states=['A'], final_states=['B'])3542sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),3543....: (2, 2, 0, 1), (2, 1, 1, 1)],3544....: initial_states=[1], final_states=[1])3545sage: He = G.composition(F, algorithm='explorative')3546sage: He.transitions()3547[Transition from ('A', 1) to ('B', 2): 1|0,1,3548Transition from ('B', 2) to ('B', 2): 0|1,3549Transition from ('B', 2) to ('B', 1): 1|1,3550Transition from ('B', 1) to ('B', 1): 0|0,3551Transition from ('B', 1) to ('B', 2): 1|0]35523553Be aware that after composition, different transitions may3554share the same output label (same python object)::35553556sage: F = Transducer([ ('A','B',0,0), ('B','A',0,0)],3557....: initial_states=['A'],3558....: final_states=['A'])3559sage: F.transitions()[0].word_out is F.transitions()[1].word_out3560False3561sage: G = Transducer([('C','C',0,1)],)3562....: initial_states=['C'],3563....: final_states=['C'])3564sage: H = G.composition(F)3565sage: H.transitions()[0].word_out is H.transitions()[1].word_out3566True35673568TESTS:35693570Due to the limitations of the two algorithms the following3571(examples from above, but different algorithm used) does not3572give a full answer or does not work35733574In the following, ``algorithm='explorative'`` is inadequate,3575as ``F`` has more than one initial state::35763577sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)],3578....: initial_states=['A', 'B'], final_states=['B'],3579....: determine_alphabets=True)3580sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1),3581....: (2, 2, 1, 1), (2, 2, 0, 0)],3582....: initial_states=[1], final_states=[2],3583....: determine_alphabets=True)3584sage: He = F.composition(G, algorithm='explorative')3585sage: He.initial_states()3586[(1, 'A')]3587sage: He.transitions()3588[Transition from (1, 'A') to (2, 'B'): 0|0,3589Transition from (2, 'B') to (2, 'A'): 0|1,3590Transition from (2, 'A') to (2, 'B'): 1|0]35913592In the following example, ``algorithm='direct'`` is inappropriate3593as there are edges with output labels of length greater than 1::35943595sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1),3596....: ('B', 'B', 0, 0)],3597....: initial_states=['A'], final_states=['B'])3598sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),3599....: (2, 2, 0, 1), (2, 1, 1, 1)],3600....: initial_states=[1], final_states=[1])3601sage: Hd = G.composition(F, algorithm='direct')3602"""3603if algorithm == None:3604algorithm = 'direct'3605if algorithm == 'direct':3606return self._composition_direct_(other, only_accessible_components)3607elif algorithm == 'explorative':3608return self._composition_explorative_(other)3609else:3610raise ValueError, "Unknown algorithm %s." % (algorithm,)361136123613def _composition_direct_(self, other, only_accessible_components=True):3614"""3615See :meth:`.composition` for details.36163617TESTS::36183619sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)],3620....: initial_states=['A', 'B'], final_states=['B'],3621....: determine_alphabets=True)3622sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1),3623....: (2, 2, 1, 1), (2, 2, 0, 0)],3624....: initial_states=[1], final_states=[2],3625....: determine_alphabets=True)3626sage: Hd = F._composition_direct_(G)3627sage: Hd.initial_states()3628[(1, 'B'), (1, 'A')]3629sage: Hd.transitions()3630[Transition from (1, 'B') to (1, 'A'): 1|1,3631Transition from (1, 'A') to (2, 'B'): 0|0,3632Transition from (2, 'B') to (2, 'A'): 0|1,3633Transition from (2, 'A') to (2, 'B'): 1|0]36343635"""3636def function(transition1, transition2):3637if transition1.word_out == transition2.word_in:3638return (transition1.word_in, transition2.word_out)3639else:3640raise LookupError36413642return other.product_FiniteStateMachine(3643self, function,3644only_accessible_components=only_accessible_components)364536463647def _composition_explorative_(self, other):3648"""3649See :meth:`.composition` for details.36503651TESTS::36523653sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1),3654....: ('B', 'B', 0, 0)],3655....: initial_states=['A'], final_states=['B'])3656sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),3657....: (2, 2, 0, 1), (2, 1, 1, 1)],3658....: initial_states=[1], final_states=[1])3659sage: He = G._composition_explorative_(F)3660sage: He.transitions()3661[Transition from ('A', 1) to ('B', 2): 1|0,1,3662Transition from ('B', 2) to ('B', 2): 0|1,3663Transition from ('B', 2) to ('B', 1): 1|1,3664Transition from ('B', 1) to ('B', 1): 0|0,3665Transition from ('B', 1) to ('B', 2): 1|0]36663667TODO:36683669The explorative algorithm should be re-implemented using the3670process iterators of both finite state machines.3671"""3672def composition_transition(state, input):3673(state1, state2) = state3674transition1 = None3675for transition in other.iter_transitions(state1):3676if transition.word_in == [input]:3677transition1 = transition3678break3679if transition1 is None:3680raise LookupError3681new_state1 = transition1.to_state3682new_state2 = state23683output = []3684for o in transition1.word_out:3685transition2 = None3686for transition in self.iter_transitions(new_state2):3687if transition.word_in == [o]:3688transition2 = transition3689break3690if transition2 is None:3691raise LookupError3692new_state2 = transition2.to_state3693output += transition2.word_out3694return ((new_state1, new_state2), output)36953696F = other.empty_copy()3697new_initial_states = [(other.initial_states()[0], self.initial_states()[0])]3698F.add_from_transition_function(composition_transition,3699initial_states=new_initial_states)37003701for state in F.states():3702if all(map(lambda s: s.is_final, state.label())):3703state.is_final = True37043705return F370637073708def input_projection(self):3709"""3710Returns an automaton where the output of each transition of3711self is deleted.37123713INPUT:37143715Nothing37163717OUTPUT:37183719An automaton.37203721EXAMPLES::37223723sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1),3724....: ('B', 'B', 1, 0)])3725sage: G = F.input_projection()3726sage: G.transitions()3727[Transition from 'A' to 'B': 0|-,3728Transition from 'A' to 'A': 1|-,3729Transition from 'B' to 'B': 1|-]3730"""3731return self.projection(what='input')373237333734def output_projection(self):3735"""3736Returns a automaton where the input of each transition of self3737is deleted and the new input is the original output.37383739INPUT:37403741Nothing37423743OUTPUT:37443745An automaton.37463747EXAMPLES::37483749sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1),3750....: ('B', 'B', 1, 0)])3751sage: G = F.output_projection()3752sage: G.transitions()3753[Transition from 'A' to 'B': 1|-,3754Transition from 'A' to 'A': 1|-,3755Transition from 'B' to 'B': 0|-]3756"""3757return self.projection(what='output')375837593760def projection(self, what='input'):3761"""3762Returns an Automaton which transition labels are the projection3763of the transition labels of the input.37643765INPUT:37663767- ``what`` -- (default: ``input``) either ``input`` or ``output``.37683769OUTPUT:37703771An automaton.37723773EXAMPLES::37743775sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1),3776....: ('B', 'B', 1, 0)])3777sage: G = F.projection(what='output')3778sage: G.transitions()3779[Transition from 'A' to 'B': 1|-,3780Transition from 'A' to 'A': 1|-,3781Transition from 'B' to 'B': 0|-]3782"""3783new = Automaton()37843785if what == 'input':3786new.input_alphabet = copy(self.input_alphabet)3787elif what == 'output':3788new.input_alphabet = copy(self.output_alphabet)3789else:3790raise NotImplementedError37913792state_mapping = {}3793for state in self.iter_states():3794state_mapping[state] = new.add_state(deepcopy(state))3795for transition in self.iter_transitions():3796if what == 'input':3797new_word_in = transition.word_in3798elif what == 'output':3799new_word_in = transition.word_out3800else:3801raise NotImplementedError3802new.add_transition((state_mapping[transition.from_state],3803state_mapping[transition.to_state],3804new_word_in, None))3805return new380638073808def transposition(self):3809"""3810Returns a new finite state machine, where all transitions of the3811input finite state machine are reversed.38123813INPUT:38143815Nothing.38163817OUTPUT:38183819A new finite state machine.38203821EXAMPLES::38223823sage: aut = Automaton([('A', 'A', 0), ('A', 'A', 1), ('A', 'B', 0)],3824....: initial_states=['A'], final_states=['B'])3825sage: aut.transposition().transitions('B')3826[Transition from 'B' to 'A': 0|-]38273828::38293830sage: aut = Automaton([('1', '1', 1), ('1', '2', 0), ('2', '2', 0)],3831....: initial_states=['1'], final_states=['1', '2'])3832sage: aut.transposition().initial_states()3833['1', '2']3834"""3835transposition = self.empty_copy()38363837for state in self.states():3838transposition.add_state(deepcopy(state))38393840for transition in self.transitions():3841transposition.add_transition(3842transition.to_state.label(), transition.from_state.label(),3843transition.word_in, transition.word_out)38443845for initial in self.initial_states():3846state = transposition.state(initial.label())3847if not initial.is_final:3848state.is_final = True3849state.is_initial = False38503851for final in self.final_states():3852state = transposition.state(final.label())3853if not final.is_initial:3854state.is_final = False3855state.is_initial = True38563857return transposition385838593860def split_transitions(self):3861"""3862Returns a new transducer, where all transitions in self with input3863labels consisting of more than one letter3864are replaced by a path of the corresponding length.38653866INPUT:38673868Nothing.38693870OUTPUT:38713872A new transducer.38733874EXAMPLES::38753876sage: A = Transducer([('A', 'B', [1, 2, 3], 0)],3877....: initial_states=['A'], final_states=['B'])3878sage: A.split_transitions().states()3879[('A', ()), ('B', ()),3880('A', (1,)), ('A', (1, 2))]3881"""3882new = self.empty_copy()3883for state in self.states():3884new.add_state(FSMState((state, ()), is_initial=state.is_initial,3885is_final=state.is_final))3886for transition in self.transitions():3887for j in range(len(transition.word_in)-1):3888new.add_transition((3889(transition.from_state, tuple(transition.word_in[:j])),3890(transition.from_state, tuple(transition.word_in[:j+1])),3891transition.word_in[j],3892[]))3893new.add_transition((3894(transition.from_state, tuple(transition.word_in[:-1])),3895(transition.to_state, ()),3896transition.word_in[-1:],3897transition.word_out))3898return new389939003901# *************************************************************************3902# simplifications3903# *************************************************************************390439053906def prepone_output(self):3907"""3908Apply the following to each state `s` (except initial and3909final states) of the finite state machine as often as3910possible:39113912If the letter a is prefix of the output label of all3913transitions from `s`, then remove it from all these labels and3914append it to all output labels of all transitions leading to3915`s`.39163917We assume that the states have no output labels.39183919INPUT:39203921Nothing.39223923OUTPUT:39243925Nothing.39263927EXAMPLES::39283929sage: A = Transducer([('A', 'B', 1, 1), ('B', 'B', 0, 0), ('B', 'C', 1, 0)],3930....: initial_states=['A'], final_states=['C'])3931sage: A.prepone_output()3932sage: A.transitions()3933[Transition from 'A' to 'B': 1|1,0,3934Transition from 'B' to 'B': 0|0,3935Transition from 'B' to 'C': 1|-]39363937::39383939sage: B = Transducer([('A', 'B', 0, 1), ('B', 'C', 1, [1, 1]), ('B', 'C', 0, 1)],3940....: initial_states=['A'], final_states=['C'])3941sage: B.prepone_output()3942sage: B.transitions()3943[Transition from 'A' to 'B': 0|1,1,3944Transition from 'B' to 'C': 1|1,3945Transition from 'B' to 'C': 0|-]39463947If initial states are not labeled as such, unexpected results may be obtained::39483949sage: C = Transducer([(0,1,0,0)])3950sage: C.prepone_output()3951prepone_output: All transitions leaving state 0 have an3952output label with prefix 0. However, there is no inbound3953transition and it is not an initial state. This routine3954(possibly called by simplification) therefore erased this3955prefix from all outbound transitions.3956sage: C.transitions()3957[Transition from 0 to 1: 0|-]39583959"""3960def find_common_output(state):3961if len(filter(lambda transition: len(transition.word_out) == 0, self.transitions(state))) > 0:3962return ()3963first_letters = set(map(lambda transition: transition.word_out[0], self.transitions(state)))3964if len(first_letters) == 1:3965return (first_letters.pop(),)3966return ()39673968changed = 13969iteration = 03970while changed > 0:3971changed = 03972iteration += 13973for state in self.states():3974if state.is_initial or state.is_final:3975continue3976assert len(state.word_out) == 0, \3977"prepone_output assumes that all states have empty output word, but state %s has output word %s" % \3978(state, state.word_out)3979common_output = find_common_output(state)3980if len(common_output) > 0:3981changed += 13982for transition in self.transitions(state):3983assert transition.word_out[0] == common_output[0]3984transition.word_out = transition.word_out[1:]3985found_inbound_transition = False3986for transition in self.transitions():3987if transition.to_state == state:3988transition.word_out = transition.word_out + [common_output[0]]3989found_inbound_transition = True3990if not found_inbound_transition:3991print "prepone_output: All transitions leaving state %s have an output label with prefix %s. "\3992"However, there is no inbound transition and it is not an initial state. "\3993"This routine (possibly called by simplification) therefore erased this prefix from all "\3994"outbound transitions." % (state, common_output[0])3995399639973998def equivalence_classes(self):3999"""4000Returns a list of equivalence classes of states.40014002INPUT:40034004Nothing.40054006OUTPUT:40074008A list of equivalence classes of states.40094010Two states `a` and `b` are equivalent, if and only if for each4011input label word_in the following holds:40124013For paths `p_a` from `a` to `a'` with input label ``word_in``4014and output label ``word_out_a`` and `p_b` from `b` to `b'`4015with input label ``word_in`` and output label ``word_out_b``,4016we have ``word_out_a=word_out_b``, `a'` and `b'` have the same4017output label and are both final or both non-final.40184019The function :meth:`.equivalence_classes` returns a list of4020the equivalence classes to this equivalence relation.40214022This is one step of Moore's minimization algorithm.40234024.. SEEALSO::40254026:meth:`.minimization`40274028EXAMPLES::40294030sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0),4031....: ("B", "C", 0, 0), ("B", "C", 1, 1),4032....: ("C", "D", 0, 1), ("C", "D", 1, 0),4033....: ("D", "A", 0, 0), ("D", "A", 1, 1)])4034sage: fsm.equivalence_classes()4035[['A', 'C'], ['B', 'D']]4036"""40374038# Two states a and b are said to be 0-equivalent, if their output4039# labels agree and if they are both final or non-final.4040#4041# For some j >= 1, two states a and b are said to be j-equivalent, if4042# they are j-1 equivalent and if for each element letter letter_in of4043# the input alphabet and transitions t_a from a with input label4044# letter_in, output label word_out_a to a' and t_b from b with input4045# label letter_in, output label word_out_b to b', we have4046# word_out_a=word_out_b and a' and b' are j-1 equivalent.40474048# If for some j the relations j-1 equivalent and j-equivalent4049# coincide, then they are equal to the equivalence relation described4050# in the docstring.40514052# classes_current holds the equivalence classes of j-equivalence,4053# classes_previous holds the equivalence classes of j-1 equivalence.40544055if not self.is_deterministic():4056raise NotImplementedError, "Minimization via Moore's Algorithm is only implemented for deterministic finite state machines"40574058# initialize with 0-equivalence4059classes_previous = []4060key_0 = lambda state: (state.is_final, state.word_out)4061states_grouped = full_group_by(self.states(), key=key_0)4062classes_current = [equivalence_class for4063(key,equivalence_class) in states_grouped]40644065while len(classes_current) != len(classes_previous):4066class_of = {}4067classes_previous = classes_current4068classes_current = []40694070for k in range(len(classes_previous)):4071for state in classes_previous[k]:4072class_of[state] = k40734074key_current = lambda state: sorted(4075[(transition.word_in,4076transition.word_out,4077class_of[transition.to_state])4078for transition in state.transitions])40794080for class_previous in classes_previous:4081states_grouped = full_group_by(class_previous, key=key_current)4082classes_current.extend([equivalence_class for4083(key,equivalence_class) in states_grouped])40844085return classes_current408640874088def quotient(self, classes):4089"""4090Constructs the quotient with respect to the equivalence4091classes.40924093INPUT:40944095- ``classes`` is a list of equivalence classes of states.40964097OUTPUT:40984099A finite state machine.41004101Assume that `c` is a class and `s`, `s'` are states in `c`. If4102there is a transition from `s` to some `t` with input label4103``word_in`` and output label ``word_out``, then there has to4104be a transition from `s'` to some `t'` with input label4105``word_in`` and output label ``word_out`` such that `s'` and4106`t'` are states of the same class `c'`. Then there is a4107transition from `c` to `c'` in the quotient with input label4108``word_in`` and output label ``word_out``.41094110Non-initial states may be merged with initial states, the4111resulting state is an initial state.41124113All states in a class must have the same ``is_final`` and4114``word_out`` values.41154116EXAMPLES::41174118sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0),4119....: ("B", "C", 0, 0), ("B", "C", 1, 1),4120....: ("C", "D", 0, 1), ("C", "D", 1, 0),4121....: ("D", "A", 0, 0), ("D", "A", 1, 1)])4122sage: fsmq = fsm.quotient([[fsm.state("A"), fsm.state("C")],4123....: [fsm.state("B"), fsm.state("D")]])4124sage: fsmq.transitions()4125[Transition from ('A', 'C')4126to ('B', 'D'): 0|1,4127Transition from ('A', 'C')4128to ('B', 'D'): 1|0,4129Transition from ('B', 'D')4130to ('A', 'C'): 0|0,4131Transition from ('B', 'D')4132to ('A', 'C'): 1|1]4133sage: fsmq.relabeled().transitions()4134[Transition from 0 to 1: 0|1,4135Transition from 0 to 1: 1|0,4136Transition from 1 to 0: 0|0,4137Transition from 1 to 0: 1|1]4138sage: fsmq1 = fsm.quotient(fsm.equivalence_classes())4139sage: fsmq1 == fsmq4140True4141sage: fsm.quotient([[fsm.state("A"), fsm.state("B"), fsm.state("C"), fsm.state("D")]])4142Traceback (most recent call last):4143...4144ValueError: There is a transition Transition from 'B' to 'C': 0|0 in the original transducer, but no corresponding transition in the new transducer.4145"""4146new = self.empty_copy()4147state_mapping = {}41484149# Create new states and build state_mapping4150for c in classes:4151new_state = new.add_state(tuple(c))4152for state in c:4153state_mapping[state] = new_state41544155# Copy data from old transducer4156for c in classes:4157new_state = state_mapping[c[0]]4158# copy all data from first class member4159new_state.is_initial = c[0].is_initial4160new_state.is_final = c[0].is_final4161new_state.word_out = c[0].word_out4162for transition in self.iter_transitions(c[0]):4163new.add_transition(4164from_state=new_state,4165to_state = state_mapping[transition.to_state],4166word_in = transition.word_in,4167word_out = transition.word_out)41684169# check that all class members have the same information (modulo classes)4170for state in c:4171new_state.is_initial = new_state.is_initial or state.is_initial4172assert new_state.is_final == state.is_final, "Class %s mixes final and non-final states" % (c,)4173assert new_state.word_out == state.word_out, "Class %s mixes different word_out" % (c,)4174assert len(self.transitions(state)) == len(new.transitions(new_state)), \4175"Class %s has %d outgoing transitions, but class member %s has %d outgoing transitions" % \4176(c, len(new.transitions(new_state)), state, len(self.transitions(state)))4177for transition in self.transitions(state):4178try:4179new.transition((new_state, state_mapping[transition.to_state], transition.word_in, transition.word_out))4180except LookupError:4181raise ValueError, "There is a transition %s in the original transducer, but no corresponding transition in the new transducer." % transition4182return new418341844185# *************************************************************************4186# other4187# *************************************************************************418841894190def graph(self, edge_labels='words_in_out'):4191"""4192Returns the graph of the finite state machine with labeled4193vertices and labeled edges.41944195INPUT:41964197- ``edge_label``: (default: ``'words_in_out'``) can be4198- ``'words_in_out'`` (labels will be strings ``'i|o'``)4199- a function with which takes as input a transition4200and outputs (returns) the label42014202OUTPUT:42034204A graph.42054206EXAMPLES::42074208sage: from sage.combinat.finite_state_machine import FSMState4209sage: A = FSMState('A')4210sage: T = Transducer()4211sage: T.graph()4212Digraph on 0 vertices4213sage: T.add_state(A)4214'A'4215sage: T.graph()4216Digraph on 1 vertex4217sage: T.add_transition(('A', 'A', 0, 1))4218Transition from 'A' to 'A': 0|14219sage: T.graph()4220Looped digraph on 1 vertex4221"""4222if edge_labels == 'words_in_out':4223label_fct = lambda t:t._in_out_label_()4224elif hasattr(edge_labels, '__call__'):4225label_fct = edge_labels4226else:4227raise TypeError, 'Wrong argument for edge_labels.'42284229graph_data = []4230isolated_vertices = []4231for state in self.iter_states():4232transitions = state.transitions4233if len(transitions) == 0:4234isolated_vertices.append(state.label())4235for t in transitions:4236graph_data.append((t.from_state.label(), t.to_state.label(),4237label_fct(t)))42384239G = DiGraph(graph_data)4240G.add_vertices(isolated_vertices)4241return G424242434244digraph = graph424542464247def plot(self):4248"""4249Plots a graph of the finite state machine with labeled4250vertices and labeled edges.42514252INPUT:42534254Nothing.42554256OUTPUT:42574258A plot of the graph of the finite state machine.42594260TESTS::42614262sage: FiniteStateMachine([('A', 'A', 0)]).plot()4263"""4264return self.graph(edge_labels='words_in_out').plot()426542664267def predecessors(self, state, valid_input=None):4268"""4269Lists all predecessors of a state.42704271INPUT:42724273- ``state`` -- the state from which the predecessors should be4274listed.42754276- ``valid_input`` -- If ``valid_input`` is a list, then we4277only consider transitions whose input labels are contained4278in ``valid_input``. ``state`` has to be a :class:`FSMState`4279(not a label of a state). If input labels of length larger4280than `1` are used, then ``valid_input`` has to be a list of4281lists.42824283OUTPUT:42844285A list of states.42864287EXAMPLES::42884289sage: A = Transducer([('I', 'A', 'a', 'b'), ('I', 'B', 'b', 'c'),4290....: ('I', 'C', 'c', 'a'), ('A', 'F', 'b', 'a'),4291....: ('B', 'F', ['c', 'b'], 'b'), ('C', 'F', 'a', 'c')],4292....: initial_states=['I'], final_states=['F'])4293sage: A.predecessors(A.state('A'))4294['A', 'I']4295sage: A.predecessors(A.state('F'), valid_input=['b', 'a'])4296['F', 'C', 'A', 'I']4297sage: A.predecessors(A.state('F'), valid_input=[['c', 'b'], 'a'])4298['F', 'C', 'B']4299"""4300if valid_input != None:4301valid_list = list()4302for input in valid_input:4303input_list = input4304if not isinstance(input_list, list):4305input_list = [input]4306valid_list.append(input_list)4307valid_input = valid_list43084309unhandeled_direct_predecessors = {s:[] for s in self.states() }4310for t in self.transitions():4311if valid_input is None or t.word_in in valid_input:4312unhandeled_direct_predecessors[t.to_state].append(t.from_state)4313done = []4314open = [state]4315while len(open) > 0:4316s = open.pop()4317candidates = unhandeled_direct_predecessors[s]4318if candidates is not None:4319open.extend(candidates)4320unhandeled_direct_predecessors[s] = None4321done.append(s)4322return(done)432343244325#*****************************************************************************432643274328def is_Automaton(FSM):4329"""4330Tests whether or not ``FSM`` inherits from :class:`Automaton`.43314332TESTS::43334334sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine, is_Automaton4335sage: is_Automaton(FiniteStateMachine())4336False4337sage: is_Automaton(Automaton())4338True4339sage: is_FiniteStateMachine(Automaton())4340True4341"""4342return isinstance(FSM, Automaton)434343444345class Automaton(FiniteStateMachine):4346"""4347This creates an automaton, which is a finite state machine, whose4348transitions have input labels.43494350An automaton has additional features like creating a deterministic4351and a minimized automaton.43524353See class :class:`FiniteStateMachine` for more information.43544355EXAMPLES:43564357We can create an automaton recognizing even numbers (given in4358binary and read from left to right) in the following way::43594360sage: A = Automaton([('P', 'Q', 0), ('P', 'P', 1),4361....: ('Q', 'P', 1), ('Q', 'Q', 0)],4362....: initial_states=['P'], final_states=['Q'])4363sage: A4364Automaton with 2 states4365sage: A([0])[0]4366True4367sage: A([1,1,0])[0]4368True4369sage: A([1,0,1])[0]4370False43714372Note that the full output of the commands above gives more4373information and looks like this::43744375sage: A([1,0,1])4376(False, 'P', [])43774378TESTS::43794380sage: Automaton()4381Automaton with 0 states4382"""43834384def _repr_(self):4385"""4386Represents the finite state machine as "Automaton with n4387states" where n is the number of states.43884389INPUT:43904391Nothing.43924393OUTPUT:43944395A string.43964397EXAMPLES::43984399sage: Automaton()._repr_()4400'Automaton with 0 states'4401"""4402return "Automaton with %s states" % len(self._states_)44034404def _latex_transition_label_(self, transition, format_function=latex):4405r"""4406Returns the proper transition label.44074408INPUT:44094410- ``transition`` - a transition44114412- ``format_function'' - a function formatting the labels44134414OUTPUT:44154416A string.44174418EXAMPLES::44194420sage: F = Automaton([('A', 'B', 1)])4421sage: F._latex_()4422'\\begin{tikzpicture}[auto]\n\\node[state] (v0) at (3.000000,0.000000) {\\text{\\texttt{A}}}\n;\\node[state] (v1) at (-3.000000,0.000000) {\\text{\\texttt{B}}}\n;\\path[->] (v0) edge node {$\\left[1\\right]$} (v1);\n\\end{tikzpicture}'44234424TESTS::44254426sage: F = Automaton([('A', 'B', 0, 1)])4427sage: t = F.transitions()[0]4428sage: F._latex_transition_label_(t)4429\left[0\right]4430"""4431return format_function(transition.word_in)44324433def determinisation(self):4434"""4435Returns a deterministic automaton which accepts the same input4436words as the original one.44374438INPUT:44394440Nothing.44414442OUTPUT:44434444A new automaton, which is deterministic.44454446The labels of the states of the new automaton are frozensets of4447states of ``self``.44484449The input alphabet must be specified. It is restricted to nice4450cases: input words have to have length at most `1`.44514452EXAMPLES::44534454sage: aut = Automaton([('A', 'A', 0), ('A', 'B', 1), ('B', 'B', 1)],4455....: initial_states=['A'], final_states=['B'])4456sage: aut.determinisation().transitions()4457[Transition from frozenset(['A'])4458to frozenset(['A']): 0|-,4459Transition from frozenset(['A'])4460to frozenset(['B']): 1|-,4461Transition from frozenset(['B'])4462to frozenset([]): 0|-,4463Transition from frozenset(['B'])4464to frozenset(['B']): 1|-,4465Transition from frozenset([])4466to frozenset([]): 0|-,4467Transition from frozenset([])4468to frozenset([]): 1|-]44694470::44714472sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1),4473....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)],4474....: initial_states=['A'], final_states=['C'])4475sage: A.determinisation().states()4476[frozenset(['A']), frozenset(['A', 'B']),4477frozenset(['A', 'C']), frozenset(['A', 'C', 'B'])]44784479TESTS:44804481This is from #15078, comment 13.44824483::44844485sage: D = {'A': [('A', 'a'), ('B', 'a'), ('A', 'b')],4486....: 'C': [], 'B': [('C', 'b')]}4487sage: auto = Automaton(D, initial_states=['A'], final_states=['C'])4488sage: auto.is_deterministic()4489False4490sage: auto.process(list('aaab'))4491(False, 'A', [])4492sage: auto.states()4493['A', 'C', 'B']4494sage: auto.determinisation()4495Automaton with 3 states4496"""4497for transition in self.transitions():4498assert len(transition.word_in) <= 1, "%s has input label of length > 1, which we cannot handle" % (transition,)44994500epsilon_successors = {}4501direct_epsilon_successors = {}4502for state in self.states():4503direct_epsilon_successors[state] = set(map(lambda t:t.to_state,4504filter(lambda transition: len(transition.word_in) == 0,4505self.transitions(state)4506)4507)4508)4509epsilon_successors[state] = set([state])45104511old_count_epsilon_successors = 04512count_epsilon_successors = len(epsilon_successors)45134514while old_count_epsilon_successors < count_epsilon_successors:4515old_count_epsilon_successors = count_epsilon_successors4516count_epsilon_successors = 04517for state in self.states():4518for direct_successor in direct_epsilon_successors[state]:4519epsilon_successors[state] = epsilon_successors[state].union(epsilon_successors[direct_successor])4520count_epsilon_successors += len(epsilon_successors[state])452145224523def set_transition(states, letter):4524result = set()4525for state in states:4526for transition in self.transitions(state):4527if transition.word_in == [letter]:4528result.add(transition.to_state)4529result = result.union(*map(lambda s:epsilon_successors[s], result))4530return (frozenset(result), [])45314532result = self.empty_copy()4533new_initial_states = [frozenset([state for state in self.initial_states()])]4534result.add_from_transition_function(set_transition,4535initial_states=new_initial_states)45364537for state in result.states():4538if any(map(lambda s: s.is_final, state.label())):4539state.is_final = True454045414542return result454345444545def minimization(self, algorithm=None):4546"""4547Returns the minimization of the input automaton as a new automaton.45484549INPUT:45504551- ``algorithm`` -- Either Moore's algorithm is used (default4552or ``algorithm='Moore'``), or Brzozowski's algorithm when4553``algorithm='Brzozowski'``.45544555OUTPUT:45564557A new automaton.45584559The resulting automaton is deterministic and has a minimal4560number of states.45614562EXAMPLES::45634564sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1),4565....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)],4566....: initial_states=['A'], final_states=['C'])4567sage: B = A.minimization(algorithm='Brzozowski')4568sage: B.transitions(B.states()[1])4569[Transition from frozenset([frozenset(['A', 'C', 'B']),4570frozenset(['C', 'B']), frozenset(['A', 'C'])]) to4571frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),4572frozenset(['A', 'C']), frozenset(['C'])]): 0|-,4573Transition from frozenset([frozenset(['A', 'C', 'B']),4574frozenset(['C', 'B']), frozenset(['A', 'C'])]) to4575frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),4576frozenset(['A', 'C'])]): 1|-]4577sage: len(B.states())457834579sage: C = A.minimization(algorithm='Brzozowski')4580sage: C.transitions(C.states()[1])4581[Transition from frozenset([frozenset(['A', 'C', 'B']),4582frozenset(['C', 'B']), frozenset(['A', 'C'])]) to4583frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),4584frozenset(['A', 'C']), frozenset(['C'])]): 0|-,4585Transition from frozenset([frozenset(['A', 'C', 'B']),4586frozenset(['C', 'B']), frozenset(['A', 'C'])]) to4587frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),4588frozenset(['A', 'C'])]): 1|-]4589sage: len(C.states())4590345914592::45934594sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'),4595....: ('3', '2', 'a'), ('2', '1', 'b'),4596....: ('3', '4', 'a'), ('4', '3', 'b')],4597....: initial_states=['1'], final_states=['1'])4598sage: min = aut.minimization(algorithm='Brzozowski')4599sage: [len(min.states()), len(aut.states())]4600[3, 4]4601sage: min = aut.minimization(algorithm='Moore')4602Traceback (most recent call last):4603...4604NotImplementedError: Minimization via Moore's Algorithm is only4605implemented for deterministic finite state machines4606"""4607if algorithm is None or algorithm == "Moore":4608return self._minimization_Moore_()4609elif algorithm == "Brzozowski":4610return self._minimization_Brzozowski_()4611else:4612raise NotImplementedError, "Algorithm '%s' is not implemented. Choose 'Moore' or 'Brzozowski'" % algorithm461346144615def _minimization_Brzozowski_(self):4616"""4617Returns a minimized automaton by using Brzozowski's algorithm.46184619See also :meth:`.minimization`.46204621TESTS::46224623sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1),4624....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)],4625....: initial_states=['A'], final_states=['C'])4626sage: B = A._minimization_Brzozowski_()4627sage: len(B.states())462834629"""4630return self.transposition().determinisation().transposition().determinisation()463146324633def _minimization_Moore_(self):4634"""4635Returns a minimized automaton by using Brzozowski's algorithm.46364637See also :meth:`.minimization`.46384639TESTS::46404641sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'),4642....: ('3', '2', 'a'), ('2', '1', 'b'),4643....: ('3', '4', 'a'), ('4', '3', 'b')],4644....: initial_states=['1'], final_states=['1'])4645sage: min = aut._minimization_Moore_()4646Traceback (most recent call last):4647...4648NotImplementedError: Minimization via Moore's Algorithm is only4649implemented for deterministic finite state machines4650"""4651return self.quotient(self.equivalence_classes())465246534654#*****************************************************************************465546564657def is_Transducer(FSM):4658"""4659Tests whether or not ``FSM`` inherits from :class:`Transducer`.46604661TESTS::46624663sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine, is_Transducer4664sage: is_Transducer(FiniteStateMachine())4665False4666sage: is_Transducer(Transducer())4667True4668sage: is_FiniteStateMachine(Transducer())4669True4670"""4671return isinstance(FSM, Transducer)467246734674class Transducer(FiniteStateMachine):4675"""4676This creates a transducer, which is a finite state machine, whose4677transitions have input and output labels.46784679An transducer has additional features like creating a simplified4680transducer.46814682See class :class:`FiniteStateMachine` for more information.46834684EXAMPLES:46854686We can create a transducer performing the addition of 1 (for4687numbers given in binary and read from right to left) in the4688following way::46894690sage: T = Transducer([('C', 'C', 1, 0), ('C', 'N', 0, 1),4691....: ('N', 'N', 0, 0), ('N', 'N', 1, 1)],4692....: initial_states=['C'], final_states=['N'])4693sage: T4694Transducer with 2 states4695sage: T([0])4696(True, 'N', [1])4697sage: T([1,1,0])4698(True, 'N', [0, 0, 1])4699sage: ZZ(T(15.digits(base=2)+[0])[2], base=2)47001647014702Note that we have padded the binary input sequence by a `0` so4703that the transducer can reach its final state.47044705TESTS::47064707sage: Transducer()4708Transducer with 0 states4709"""47104711def _repr_(self):4712"""4713Represents the transducer as "Transducer with n states" where4714n is the number of states.47154716INPUT:47174718Nothing.47194720OUTPUT:47214722A string.47234724EXAMPLES::47254726sage: Transducer()._repr_()4727'Transducer with 0 states'4728"""4729return "Transducer with %s states" % len(self._states_)47304731def _latex_transition_label_(self, transition, format_function=latex):4732r"""4733Returns the proper transition label.47344735INPUT:47364737- ``transition`` - a transition47384739- ``format_function'' - a function formatting the labels47404741OUTPUT:47424743A string.47444745sage: F = Transducer([('A', 'B', 1, 2)])4746sage: F._latex_()4747'\\begin{tikzpicture}[auto]\n\\node[state] (v0) at (3.000000,0.000000) {\\text{\\texttt{A}}}\n;\\node[state] (v1) at (-3.000000,0.000000) {\\text{\\texttt{B}}}\n;\\path[->] (v0) edge node {$\\left[1\\right] \\mid \\left[2\\right]$} (v1);\n\\end{tikzpicture}'47484749TESTS::47504751sage: F = Transducer([('A', 'B', 0, 1)])4752sage: t = F.transitions()[0]4753sage: F._latex_transition_label_(t)4754\left[0\right] \mid \left[1\right]4755"""4756return (format_function(transition.word_in) + "\\mid"4757+ format_function(transition.word_out))475847594760def simplification(self):4761"""4762Returns a simplified transducer.47634764INPUT:47654766Nothing.47674768OUTPUT:47694770A new transducer.47714772This function simplifies a transducer by Moore's algorithm,4773first moving common output labels of transitions leaving a4774state to output labels of transitions entering the state4775(cf. :meth:`.prepone_output`).47764777The resulting transducer implements the same function as the4778original transducer.47794780EXAMPLES::47814782sage: fsm = Transducer([("A", "B", 0, 1), ("A", "B", 1, 0),4783....: ("B", "C", 0, 0), ("B", "C", 1, 1),4784....: ("C", "D", 0, 1), ("C", "D", 1, 0),4785....: ("D", "A", 0, 0), ("D", "A", 1, 1)])4786sage: fsms = fsm.simplification()4787sage: fsms4788Transducer with 2 states4789sage: fsms.transitions()4790[Transition from ('A', 'C')4791to ('B', 'D'): 0|1,4792Transition from ('A', 'C')4793to ('B', 'D'): 1|0,4794Transition from ('B', 'D')4795to ('A', 'C'): 0|0,4796Transition from ('B', 'D')4797to ('A', 'C'): 1|1]4798sage: fsms.relabeled().transitions()4799[Transition from 0 to 1: 0|1,4800Transition from 0 to 1: 1|0,4801Transition from 1 to 0: 0|0,4802Transition from 1 to 0: 1|1]4803"""4804fsm = deepcopy(self)4805fsm.prepone_output()4806return fsm.quotient(fsm.equivalence_classes())480748084809#*****************************************************************************481048114812def is_FSMProcessIterator(PI):4813"""4814Tests whether or not ``PI`` inherits from :class:`FSMProcessIterator`.48154816TESTS::48174818sage: from sage.combinat.finite_state_machine import is_FSMProcessIterator, FSMProcessIterator4819sage: is_FSMProcessIterator(FSMProcessIterator(FiniteStateMachine()))4820Traceback (most recent call last):4821...4822ValueError: No state is initial.4823"""4824return isinstance(PI, FSMProcessIterator)482548264827class FSMProcessIterator:4828"""4829This class is for processing an input string on a finite state4830machine.48314832An instance of this class is generated when4833:meth:`FiniteStateMachine.process` or4834:meth:`FiniteStateMachine.iter_process` of the finite state4835machine is invoked. It behaves like an iterator which, in each4836step, takes one letter of the input and runs (one step on) the4837finite state machine with this input. More precisely, in each4838step, the process iterator takes an outgoing transition of the4839current state, whose input label equals the input letter of the4840tape. The output label of the transition, if present, is written4841on the output tape.48424843INPUT:48444845- ``fsm`` -- The finite state machine on which the input should be4846processed.48474848- ``input_tape`` -- The input tape. It can be anything that is4849iterable.48504851- ``initial_state`` -- The initial state in which the machine4852starts. If this is ``None``, the unique inital state of the4853finite state machine is takes. If there are several, an error is4854reported.48554856The process (iteration) stops if there are no more input letters4857on the tape. In this case a StopIteration exception is thrown. As4858result the following attributes are available:48594860- ``accept_input`` -- Is True if the reached state is a final state.48614862- ``current_state`` -- The current/reached state in the process.48634864- ``output_tape`` -- The written output.48654866Current values of those attribtes (except ``accept_input``) are4867(also) available during the iteration.48684869OUTPUT:48704871An iterator.48724873EXAMPLES:48744875The following transducer reads binary words and outputs a word,4876where blocks of ones are replaced by just a single one. Further4877only words that end with a zero are accepted.48784879::48804881sage: T = Transducer({'A': [('A', 0, 0), ('B', 1, None)],4882....: 'B': [('B', 1, None), ('A', 0, [1, 0])]},4883....: initial_states=['A'], final_states=['A'])4884sage: input = [1, 1, 0, 0, 1, 0, 1, 1, 1, 0]4885sage: T.process(input)4886(True, 'A', [1, 0, 0, 1, 0, 1, 0])48874888The function :meth:`FiniteStateMachine.process` created a new4889``FSMProcessIterator``. We can do that manually, too, and get full4890access to the iteration process::48914892sage: from sage.combinat.finite_state_machine import FSMProcessIterator4893sage: it = FSMProcessIterator(T, input_tape=input)4894sage: for _ in it:4895....: print (it.current_state, it.output_tape)4896('B', [])4897('B', [])4898('A', [1, 0])4899('A', [1, 0, 0])4900('B', [1, 0, 0])4901('A', [1, 0, 0, 1, 0])4902('B', [1, 0, 0, 1, 0])4903('B', [1, 0, 0, 1, 0])4904('B', [1, 0, 0, 1, 0])4905('A', [1, 0, 0, 1, 0, 1, 0])4906sage: it.accept_input4907True4908"""4909def __init__(self, fsm, input_tape=None, initial_state=None):4910"""4911See :class:`FSMProcessIterator` for more information.49124913EXAMPLES::49144915sage: from sage.combinat.finite_state_machine import FSMProcessIterator4916sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},4917....: initial_states=['A'], final_states=['A'])4918sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])4919sage: for _ in it:4920....: pass4921sage: it.output_tape4922[1, 0]4923"""4924self.fsm = fsm4925if initial_state is None:4926fsm_initial_states = self.fsm.initial_states()4927try:4928self.current_state = fsm_initial_states[0]4929except IndexError:4930raise ValueError, "No state is initial."4931if len(fsm_initial_states) > 1:4932raise ValueError, "Several initial states."4933else:4934self.current_state = initial_state4935self.output_tape = []4936if input_tape is None:4937self._input_tape_iter_ = iter([])4938else:4939if hasattr(input_tape, '__iter__'):4940self._input_tape_iter_ = iter(input_tape)4941else:4942raise ValueError, "Given input tape is not iterable."49434944def __iter__(self):4945"""4946Returns ``self``.49474948TESTS::49494950sage: from sage.combinat.finite_state_machine import FSMProcessIterator4951sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},4952....: initial_states=['A'], final_states=['A'])4953sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])4954sage: id(it) == id(iter(it))4955True4956"""4957return self49584959def next(self):4960"""4961Makes one step in processing the input tape.49624963INPUT:49644965Nothing.49664967OUTPUT:49684969It returns the taken transition. A ``StopIteration`` exception is4970thrown when there is nothing more to read.49714972EXAMPLES::49734974sage: from sage.combinat.finite_state_machine import FSMProcessIterator4975sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},4976....: initial_states=['A'], final_states=['A'])4977sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])4978sage: it.next()4979Transition from 'A' to 'A': 0|14980sage: it.next()4981Transition from 'A' to 'A': 1|04982sage: it.next()4983Traceback (most recent call last):4984...4985StopIteration4986"""4987if hasattr(self, 'accept_input'):4988raise StopIteration4989try:4990# process current state4991transition = None4992try:4993transition = self.current_state.hook(4994self.current_state, self)4995except AttributeError:4996pass4997self.write_word(self.current_state.word_out)49984999# get next5000if not isinstance(transition, FSMTransition):5001next_word = []5002found = False50035004try:5005while not found:5006next_word.append(self.read_letter())5007try:5008transition = self.get_next_transition(5009next_word)5010found = True5011except ValueError:5012pass5013except StopIteration:5014# this means input tape is finished5015if len(next_word) > 0:5016self.accept_input = False5017raise StopIteration50185019# process transition5020try:5021transition.hook(transition, self)5022except AttributeError:5023pass5024self.write_word(transition.word_out)50255026# go to next state5027self.current_state = transition.to_state50285029except StopIteration:5030# this means, either input tape is finished or5031# someone has thrown StopIteration manually (in one5032# of the hooks)5033if not self.current_state.is_final:5034self.accept_input = False5035if not hasattr(self, 'accept_input'):5036self.accept_input = True5037raise StopIteration50385039# return5040return transition50415042def read_letter(self):5043"""5044Reads a letter from the input tape.50455046INPUT:50475048Nothing.50495050OUTPUT:50515052A letter.50535054Exception ``StopIteration`` is thrown if tape has reached5055the end.50565057EXAMPLES::50585059sage: from sage.combinat.finite_state_machine import FSMProcessIterator5060sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},5061....: initial_states=['A'], final_states=['A'])5062sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])5063sage: it.read_letter()506405065"""5066return self._input_tape_iter_.next()50675068def write_letter(self, letter):5069"""5070Writes a letter on the output tape.50715072INPUT:50735074- ``letter`` -- the letter to be written.50755076OUTPUT:50775078Nothing.50795080EXAMPLES::50815082sage: from sage.combinat.finite_state_machine import FSMProcessIterator5083sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},5084....: initial_states=['A'], final_states=['A'])5085sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])5086sage: it.write_letter(42)5087sage: it.output_tape5088[42]5089"""5090self.output_tape.append(letter)50915092def write_word(self, word):5093"""5094Writes a word on the output tape.50955096INPUT:50975098- ``word`` -- the word to be written.50995100OUTPUT:51015102Nothing.51035104EXAMPLES::51055106sage: from sage.combinat.finite_state_machine import FSMProcessIterator5107sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},5108....: initial_states=['A'], final_states=['A'])5109sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])5110sage: it.write_word([4, 2])5111sage: it.output_tape5112[4, 2]5113"""5114for letter in word:5115self.write_letter(letter)51165117def get_next_transition(self, word_in):5118"""5119Returns the next transition according to ``word_in``. It is5120assumed that we are in state ``self.current_state``.51215122INPUT:51235124- ``word_in`` -- the input word.51255126OUTPUT:51275128The next transition according to ``word_in``. It is assumed5129that we are in state ``self.current_state``.51305131EXAMPLES::51325133sage: from sage.combinat.finite_state_machine import FSMProcessIterator5134sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},5135....: initial_states=['A'], final_states=['A'])5136sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])5137sage: it.get_next_transition([0])5138Transition from 'A' to 'A': 0|15139"""5140for transition in self.current_state.transitions:5141if transition.word_in == word_in:5142return transition5143raise ValueError514451455146#*****************************************************************************514751485149def setup_latex_preamble():5150"""5151This function adds the package ``tikz`` with support for automata5152to the preamble of Latex so that the finite state machines can be5153drawn nicely.51545155INPUT:51565157Nothing.51585159OUTPUT:51605161Nothing.51625163TESTS::51645165sage: from sage.combinat.finite_state_machine import setup_latex_preamble5166sage: setup_latex_preamble()5167"""5168latex.add_package_to_preamble_if_available('tikz')5169latex.add_to_preamble('\\usetikzlibrary{automata}')517051715172#*****************************************************************************517351745175