Ticket #15078: trac_15078_fsm_automata_transducers.3.patch

File trac_15078_fsm_automata_transducers.3.patch, 154.0 KB (added by dkrenn, 6 years ago)
  • doc/en/reference/combinat/index.rst

    # HG changeset patch
    # User Daniel Krenn <math+sage@danielkrenn.at>
    # Date 1381318649 -7200
    # Node ID 5ee85d6f3a2b95ef79da31b79c201e72b84b3f3a
    # Parent  f0ee3538887fe739601babb54e177ec5e1133b7a
    Trac 15078: finite state machines, automata, transducers
    
    diff --git a/doc/en/reference/combinat/index.rst b/doc/en/reference/combinat/index.rst
    a b  
    8585   sage/combinat/misc
    8686   sage/combinat/combinatorial_map
    8787
     88   sage/combinat/finite_state_machine
    8889
    8990.. include:: ../footer.txt
  • sage/combinat/all.py

    diff --git a/sage/combinat/all.py b/sage/combinat/all.py
    a b  
    158158# Gelfand-Tsetlin patterns
    159159from gelfand_tsetlin_patterns import GelfandTsetlinPattern, GelfandTsetlinPatterns
    160160
     161# Finite State Machines (Automaton, Transducer)
     162from finite_state_machine import Automaton, Transducer, FiniteStateMachine
  • new file sage/combinat/finite_state_machine.py

    diff --git a/sage/combinat/finite_state_machine.py b/sage/combinat/finite_state_machine.py
    new file mode 100644
    - +  
     1# -*- coding: utf-8 -*-
     2"""
     3Finite State Machines, Automata, Transducers
     4
     5This module adds support for finite state machines, automata and
     6transducers. See class :class:`FiniteStateMachine` and the examples
     7below for details creating one.
     8
     9Examples
     10========
     11
     12
     13A simple finite state machine
     14-----------------------------
     15
     16We can easily create a finite state machine by
     17
     18::
     19
     20    sage: fsm = FiniteStateMachine()
     21    sage: fsm
     22    finite state machine with 0 states
     23
     24By default this is the empty finite state machine, so not very
     25interesting. Let's create some states and transitions::
     26
     27    sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     28    sage: day = FSMState('day')
     29    sage: night = FSMState('night')
     30    sage: sunrise = FSMTransition(night, day)
     31    sage: sunset = FSMTransition(day, night)
     32
     33And now let's add those states and transitions to our finite state machine::
     34
     35    sage: fsm.add_transition(sunrise)
     36    Transition from State 'night' to State 'day': -|-
     37    sage: fsm.add_transition(sunset)
     38    Transition from State 'day' to State 'night': -|-
     39
     40Note that the states are added automatically, since they are present
     41in the transitions. We could add the states manually by
     42
     43::
     44
     45    sage: fsm.add_state(day)
     46    State 'day'
     47    sage: fsm.add_state(night)
     48    State 'night'
     49
     50Anyhow, we got the following finite state machine::
     51
     52    sage: fsm
     53    finite state machine with 2 states
     54
     55We can also visualize it as a graph by
     56
     57::
     58
     59    sage: fsm.graph()
     60    Digraph on 2 vertices
     61
     62Alternatively, we could have created the finite state machine above
     63simply by
     64
     65::
     66
     67    sage: FiniteStateMachine([('night', 'day'), ('day', 'night')])
     68    finite state machine with 2 states
     69
     70or by
     71
     72::
     73
     74    sage: fsm = FiniteStateMachine()
     75    sage: day = fsm.add_state('day')
     76    sage: night = fsm.add_state('night')
     77    sage: sunrise = fsm.add_transition(night, day)
     78    sage: sunset = fsm.add_transition(day, night)
     79    sage: fsm
     80    finite state machine with 2 states
     81
     82A simple Automaton (recognizing NAFs)
     83---------------------------------------
     84
     85We want to build an automaton which recognizes non-adjacent forms
     86(NAFs), i.e., sequences which have no adjacent non-zeros.
     87We use `0`, `1`, and `-1` as digits::
     88
     89    sage: NAF = Automaton(
     90    ....:     {'A': [('A', 0), ('B', 1), ('B', -1)], 'B': [('A', 0)]})
     91    sage: NAF.state('A').is_initial = True
     92    sage: NAF.state('A').is_final = True
     93    sage: NAF.state('B').is_final = True
     94    sage: NAF
     95    finite state machine with 2 states
     96
     97Of course, we could have specified the initial and final states
     98directly in the definition of ``NAF`` by ``initial_states=['A']`` and
     99``final_states=['A', 'B']``.
     100
     101So let's test the automaton with some input::
     102
     103    sage: NAF([0])[0]
     104    True
     105    sage: NAF([0, 1])[0]
     106    True
     107    sage: NAF([1, -1])[0]
     108    False
     109    sage: NAF([0, -1, 0, 1])[0]
     110    True
     111    sage: NAF([0, -1, -1, -1, 0])[0]
     112    False
     113    sage: NAF([-1, 0, 0, 1, 1])[0]
     114    False
     115
     116Alternatively, we could call that by
     117
     118::
     119
     120    sage: NAF.process([-1, 0, 0, 1, 1])[0]
     121    False
     122
     123A simple transducer (binary inverter)
     124-------------------------------------
     125
     126Let's build a simple transducer, which rewrites a binary word by
     127iverting each bit::
     128
     129    sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     130    ....:     initial_states=['A'], final_states=['A'])
     131
     132We can look at the states and transitions::
     133
     134    sage: inverter.states()
     135    [State 'A']
     136    sage: for t in inverter.transitions():
     137    ....:     print t
     138    Transition from State 'A' to State 'A': 0|1
     139    Transition from State 'A' to State 'A': 1|0
     140
     141Now we apply a word to it and see what the transducer does::
     142
     143    sage: inverter([0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1])
     144    (True, State 'A', [1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0])
     145
     146``True`` means, that we landed in a final state, that state is labeled
     147``'A'``, and we also got an output.
     148
     149
     150A transducer which performs division by `3` in binary
     151-----------------------------------------------------
     152
     153Now we build a transducer, which divides a binary number by 3.
     154The labels of the states are the remainder of the division.
     155The transition function is
     156
     157::
     158
     159    sage: def f(state_from, read):
     160    ....:     if state_from + read <= 1:
     161    ....:         state_to = 2*state_from + read
     162    ....:         write = 0
     163    ....:     else:
     164    ....:         state_to = 2*state_from + read - 3
     165    ....:         write = 1
     166    ....:     return (state_to, write)
     167
     168We get the transducer with
     169
     170::
     171
     172    sage: D = Transducer(f, initial_states=[0], final_states=[0],
     173    ....:                input_alphabet=[0, 1])
     174
     175Now we want to divide 13 by 3::
     176
     177    sage: D([1, 1, 0, 1])
     178    (False, State 1, [0, 1, 0, 0])
     179
     180So we have 13 : 3 = 4 and the reminder is 1. ``False`` means 13 is not
     181divisible by 3.
     182
     183
     184Using the hook-functions
     185------------------------
     186
     187Let's use the previous example "divison by `3`" to demonstrate the
     188optional state and transition parameters ``hook``.
     189
     190First, we define, what those functions should do. In our case, this is
     191just saying in which state we are and which transition we take
     192
     193::
     194
     195    sage: def state_hook(state, process):
     196    ....:     print "We are now in State %s." % (state.label(),)
     197    sage: from sage.combinat.finite_state_machine import FSMWordSymbol
     198    sage: def transition_hook(transition, process):
     199    ....:     print ("Currently we go from %s to %s, "
     200    ....:            "reading %s and writing %s." % (
     201    ....:                transition.from_state, transition.to_state,
     202    ....:                FSMWordSymbol(transition.word_in),
     203    ....:                FSMWordSymbol(transition.word_out)))
     204
     205Now, let's add these hook-functions to the existing transducer::
     206
     207    sage: for s in D.iter_states():
     208    ....:     s.hook = state_hook
     209    sage: for t in D.iter_transitions():
     210    ....:     t.hook = transition_hook
     211
     212Rerunning the process again now gives the following output::
     213
     214    sage: D.process([1, 1, 0, 1])
     215    We are now in State 0.
     216    Currently we go from State 0 to State 1, reading 1 and writing 0.
     217    We are now in State 1.
     218    Currently we go from State 1 to State 0, reading 1 and writing 1.
     219    We are now in State 0.
     220    Currently we go from State 0 to State 0, reading 0 and writing 0.
     221    We are now in State 0.
     222    Currently we go from State 0 to State 1, reading 1 and writing 0.
     223    We are now in State 1.
     224    (False, State 1, [0, 1, 0, 0])
     225
     226The example above just explains the basic idea of using
     227hook-functions. In the following, we will use those hooks more seriously.
     228
     229
     230Detecting sequences with same number of `0` and `1`
     231---------------------------------------------------
     232
     233Suppose we have a binary input and want to accept all sequences with
     234the same number of `0` and `1`. This cannot be done with a finite
     235automaton. Anyhow, we can make usage of the hook functions to extend
     236our finite automaton by a counter::
     237
     238    sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     239    sage: C = Automaton()
     240    sage: def update_counter(state, process):
     241    ....:     l = process.read_letter()
     242    ....:     process.fsm.counter += 1 if l == 1 else -1
     243    ....:     if process.fsm.counter > 0:
     244    ....:         next_state = 'positive'
     245    ....:     elif process.fsm.counter < 0:
     246    ....:         next_state = 'negative'
     247    ....:     else:
     248    ....:         next_state = 'zero'
     249    ....:     return FSMTransition(state, process.fsm.state(next_state),
     250    ....:                          l, process.fsm.counter)
     251    sage: C.add_state(FSMState('zero', hook=update_counter,
     252    ....:             is_initial=True, is_final=True))
     253    State 'zero'
     254    sage: C.add_state(FSMState('positive', hook=update_counter))
     255    State 'positive'
     256    sage: C.add_state(FSMState('negative', hook=update_counter))
     257    State 'negative'
     258
     259Now, let's input some sequence::
     260
     261    sage: C.counter = 0; C([1, 1, 1, 1, 0, 0])
     262    (False, State 'positive', [1, 2, 3, 4, 3, 2])
     263
     264The result is False, since there are four `1` but only two `0`. We
     265land in the state ``positive`` and we can also see the values of the
     266counter in each step.
     267
     268Let's try some other examples::
     269
     270    sage: C.counter = 0; C([1, 1, 0, 0])
     271    (True, State 'zero', [1, 2, 1, 0])
     272    sage: C.counter = 0; C([0, 1, 0, 0])
     273    (False, State 'negative', [-1, 0, -1, -2])
     274
     275
     276AUTHORS:
     277
     278- Daniel Krenn (2012-03-27): initial version
     279- Clemens Heuberger (2012-04-05): initial version
     280- Sara Kropf (2012-04-17): initial version
     281- Clemens Heuberger (2013-08-21): release candidate for Sage patch
     282- Daniel Krenn (2013-08-21): release candidate for Sage patch
     283- Sara Kropf (2013-08-21): release candidate for Sage patch
     284
     285
     286ACKNOWLEDGEMENT:
     287
     288- Daniel Krenn, Clemens Heuberger and Sara Kropf are supported by the
     289  Austrian Science Fund (FWF): P 24644-N26.
     290
     291"""
     292
     293#*****************************************************************************
     294#  Copyright (C) 2012, 2013 Daniel Krenn <math+sage@danielkrenn.at>
     295#                2012, 2013 Clemens Heuberger <clemens.heuberger@aau.at>
     296#                2012, 2013 Sara Kropf <sara.kropf@aau.at>
     297#
     298#  Distributed under the terms of the GNU General Public License (GPL)
     299#  as published by the Free Software Foundation; either version 2 of
     300#  the License, or (at your option) any later version.
     301#                http://www.gnu.org/licenses/
     302#*****************************************************************************
     303
     304from sage.structure.sage_object import SageObject
     305from sage.graphs.digraph import DiGraph
     306from sage.matrix.constructor import matrix
     307from sage.rings.integer_ring import ZZ
     308from sage.calculus.var import var
     309from sage.misc.latex import latex
     310from sage.functions.trig import cos, sin, atan2
     311from sage.symbolic.constants import pi
     312
     313from copy import copy
     314from copy import deepcopy
     315
     316import itertools
     317
     318#*****************************************************************************
     319
     320FSMEmptyWordSymbol = '-'
     321
     322def FSMLetterSymbol(letter):
     323    """
     324    Returns a string associated to the input letter.
     325
     326    INPUT:
     327
     328    - ``letter`` -- the input letter or ``None`` (representing the
     329      empty word).
     330
     331    OUTPUT:
     332   
     333    If ``letter`` is ``None`` the symbol for the empty word
     334    ``FSMEmptyWordSymbol`` is returned, otherwise the string
     335    associated to the letter.
     336
     337    EXAMPLES::
     338
     339        sage: from sage.combinat.finite_state_machine import FSMLetterSymbol
     340        sage: FSMLetterSymbol(0)
     341        '0'
     342        sage: FSMLetterSymbol(None)
     343        '-'
     344    """
     345    return FSMEmptyWordSymbol if letter is None else repr(letter)
     346
     347
     348def FSMWordSymbol(word):
     349    """
     350    Returns a string of ``word``. It may returns the symbol of the
     351    empty word ``FSMEmptyWordSymbol``.
     352
     353    INPUT:
     354
     355    - ``word`` -- the input word.
     356
     357    OUTPUT:
     358
     359    A string of ``word``.
     360
     361    EXAMPLES::
     362
     363        sage: from sage.combinat.finite_state_machine import FSMWordSymbol
     364        sage: FSMWordSymbol([0, 1, 1])
     365        '0,1,1'
     366    """
     367    if not isinstance(word, list):
     368        return FSMLetterSymbol(word)
     369    if len(word) == 0:
     370        return FSMEmptyWordSymbol
     371    s = ''
     372    for letter in word:
     373        s += (',' if len(s) > 0 else '') + FSMLetterSymbol(letter)
     374    return s
     375
     376
     377#*****************************************************************************
     378
     379
     380def Automaton(*args, **kwargs):
     381    """
     382    A FiniteStateMachine with ``mode='automaton'``
     383    See class :class:`FiniteStateMachine` for more information.
     384
     385    TESTS::
     386
     387        sage: Automaton()
     388        finite state machine with 0 states
     389    """
     390    return FiniteStateMachine(mode='automaton', *args, **kwargs)
     391
     392
     393def Transducer(*args, **kwargs):
     394    """
     395    A FiniteStateMachine with ``mode='transducer'``
     396    See class :class:`FiniteStateMachine` for more information.
     397
     398    TESTS::
     399
     400        sage: Transducer()
     401        finite state machine with 0 states
     402    """
     403    return FiniteStateMachine(mode='transducer', *args, **kwargs)
     404
     405
     406#*****************************************************************************
     407
     408
     409def is_FSMState(S):
     410    """
     411    Tests whether or not ``S`` inherits from :class:`FSMState`.
     412
     413    TESTS::
     414
     415        sage: from sage.combinat.finite_state_machine import is_FSMState, FSMState
     416        sage: is_FSMState(FSMState('A'))
     417        True
     418    """
     419    return isinstance(S, FSMState)
     420
     421
     422class FSMState(SageObject):
     423    """
     424    Class for a state of a finite state machine.
     425
     426    INPUT:
     427
     428    - ``label`` -- the label of the state.
     429
     430    - ``word_out`` -- (default: ``None``) a word that is written when
     431      the state is reached.
     432
     433    - ``is_initial`` -- (default: ``False``)
     434
     435    - ``is_final`` -- (default: ``False``)
     436
     437    - ``hook`` -- (default: ``None``) A function which is called when
     438      the state is reached during processing input.
     439
     440    OUTPUT:
     441
     442    Returns a state of a finite state machine.
     443
     444    EXAMPLES::
     445
     446        sage: from sage.combinat.finite_state_machine import FSMState
     447        sage: A = FSMState('state 1', word_out=0, is_initial=True)
     448        sage: A
     449        State 'state 1'
     450        sage: A.label()
     451        'state 1'
     452        sage: B = FSMState('state 2')
     453        sage: A == B
     454        False
     455
     456    """
     457    def __init__(self, label, word_out=None,
     458                 is_initial=False, is_final=False,
     459                 hook=None):
     460        """
     461        See :class:`FSMState` for more information.
     462
     463        EXAMPLES::
     464
     465            sage: from sage.combinat.finite_state_machine import FSMState
     466            sage: FSMState('final', is_final=True)
     467            State 'final'
     468        """
     469        if label is None or label == "":
     470            raise ValueError, "You have to specify a label for the state."
     471        self._label_ = label
     472
     473        if isinstance(word_out, list):
     474            self.word_out = word_out
     475        elif word_out is not None:
     476            self.word_out = [word_out]
     477        else:
     478            self.word_out = []
     479
     480        self.is_initial = is_initial
     481        self.is_final = is_final
     482        if hook is not None:
     483            if hasattr(hook, '__call__'):
     484                self.hook = hook
     485            else:
     486                raise TypeError, 'Wrong argument for hook.'
     487
     488
     489    def label(self):
     490        """
     491        Returns the label of the state.
     492
     493        INPUT:
     494
     495        Nothing.
     496
     497        OUTPUT:
     498
     499        The label of the state.
     500
     501        EXAMPLES::
     502
     503            sage: from sage.combinat.finite_state_machine import FSMState
     504            sage: A = FSMState('state')
     505            sage: A.label()
     506            'state'
     507        """
     508        return self._label_
     509
     510
     511    def __copy__(self):
     512        """
     513        Returns a (shallow) copy of the state.
     514
     515        INPUT:
     516
     517        Nothing.
     518       
     519        OUTPUT:
     520
     521        A new state.
     522
     523        EXAMPLES::
     524
     525            sage: from sage.combinat.finite_state_machine import FSMState
     526            sage: A = FSMState('A')
     527            sage: copy(A)
     528            State 'A'
     529        """
     530        new = FSMState(self.label(), self.word_out,
     531                       self.is_initial, self.is_final)
     532        if hasattr(self, 'hook'):
     533            new.hook = self.hook
     534        return new
     535
     536
     537    copy = __copy__
     538
     539
     540    def __deepcopy__(self, memo):
     541        """
     542        Returns a deep copy of the state.
     543
     544        INPUT:
     545
     546        - ``memo`` -- a dictionary storing already processed elements.
     547
     548        OUTPUT:
     549       
     550        A new state.
     551
     552        EXAMPLES::
     553
     554            sage: from sage.combinat.finite_state_machine import FSMState
     555            sage: A = FSMState('A')
     556            sage: deepcopy(A)
     557            State 'A'
     558        """
     559        try:
     560            label = self._deepcopy_relabel_
     561        except AttributeError:
     562            label = self.label()
     563        new = FSMState(deepcopy(label, memo), deepcopy(self.word_out, memo),
     564                       self.is_initial, self.is_final)
     565        if hasattr(self, 'hook'):
     566            new.hook = deepcopy(self.hook, memo)
     567        return new
     568
     569
     570    def deepcopy(self, memo=None):
     571        """
     572        Returns a deep copy of the state.
     573
     574        INPUT:
     575
     576        - ``memo`` -- (default: ``None``) a dictionary storing already
     577          processed elements.
     578
     579        OUTPUT:
     580       
     581        A new state.
     582
     583        EXAMPLES::
     584
     585            sage: from sage.combinat.finite_state_machine import FSMState
     586            sage: A = FSMState('A')
     587            sage: deepcopy(A)
     588            State 'A'
     589        """
     590        return deepcopy(self, memo)
     591
     592
     593    def relabeled(self, label, memo=None):
     594        """
     595        Returns a deep copy of the state with a new label.
     596
     597        INPUT:
     598
     599        - ``label`` -- the label of new state.
     600
     601        - ``memo`` -- (default: ``None``) a dictionary storing already
     602          processed elements.
     603
     604        OUTPUT:
     605       
     606        A new state.
     607
     608        EXAMPLES::
     609
     610            sage: from sage.combinat.finite_state_machine import FSMState
     611            sage: A = FSMState('A')
     612            sage: A.relabeled('B')
     613            State 'B'
     614
     615        """
     616        self._deepcopy_relabel_ = label
     617        new = deepcopy(self, memo)
     618        del self._deepcopy_relabel_
     619        return new
     620
     621
     622    def __hash__(self):
     623        """
     624        Returns a hash value for the object.
     625
     626        INPUT:
     627
     628        Nothing.
     629
     630        OUTPUT:
     631
     632        The hash of this state.
     633
     634        TESTS::
     635
     636            sage: from sage.combinat.finite_state_machine import FSMState
     637            sage: A = FSMState('A')
     638            sage: hash(A) #random
     639            -269909568
     640        """
     641        return hash(self.label())
     642
     643
     644    def _repr_(self):
     645        """
     646        Returns the string "State label".
     647
     648        INPUT:
     649
     650        Nothing.
     651
     652        OUTPUT:
     653       
     654        A string.
     655
     656        TESTS:
     657
     658            sage: from sage.combinat.finite_state_machine import FSMState
     659            sage: FSMState('A')._repr_()
     660            "State 'A'"
     661        """
     662        return "State %s" % (repr(self.label()))
     663
     664
     665    def __eq__(left, right):
     666        """
     667        Returns True if two states are the same, i.e., if they have
     668        the same labels.
     669
     670        Note that the hooks and whether the states are initial or
     671        final are not checked.
     672
     673        INPUT:
     674
     675        - ``left`` -- a state.
     676
     677        - ``right`` -- a state.
     678
     679        OUTPUT:
     680
     681        True or False.
     682
     683        EXAMPLES::
     684
     685            sage: from sage.combinat.finite_state_machine import FSMState
     686            sage: A = FSMState('A')
     687            sage: B = FSMState('A', is_initial=True)
     688            sage: A == B
     689            True
     690        """
     691        if not is_FSMState(right):
     692            return False
     693        return left.label() == right.label()
     694
     695
     696    def __ne__(left, right):
     697        """
     698        Tests for inequality, complement of __eq__.
     699
     700        INPUT:
     701
     702        - ``left`` -- a state.
     703
     704        - ``right`` -- a state.
     705
     706        OUTPUT:
     707
     708        True or False.
     709
     710        EXAMPLES::
     711
     712            sage: from sage.combinat.finite_state_machine import FSMState
     713            sage: A = FSMState('A', is_initial=True)
     714            sage: B = FSMState('A', is_final=True)
     715            sage: A != B
     716            False
     717        """
     718        return (not (left == right))
     719
     720
     721    def __nonzero__(self):
     722        """
     723        Returns True.
     724
     725        INPUT:
     726
     727        Nothing.
     728
     729        OUTPUT:
     730
     731        True or False.
     732
     733        TESTS::
     734
     735            sage: from sage.combinat.finite_state_machine import FSMState
     736            sage: FSMState('A').__nonzero__()
     737            True
     738        """
     739        return True  # A state cannot be zero (see __init__)
     740
     741
     742#*****************************************************************************
     743
     744
     745def is_FSMTransition(T):
     746    """
     747    Tests whether or not ``T`` inherits from :class:`FSMTransition`.
     748
     749    TESTS::
     750
     751        sage: from sage.combinat.finite_state_machine import is_FSMTransition, FSMTransition
     752        sage: is_FSMTransition(FSMTransition('A', 'B'))
     753        True
     754    """
     755    return isinstance(T, FSMTransition)
     756
     757
     758class FSMTransition(SageObject):
     759    """
     760    Class for a transition of a finite state machine.
     761
     762    INPUT:
     763
     764    - ``from_state`` -- state from which transition starts.
     765
     766    - ``to_state`` -- state in which transition ends.
     767
     768    - ``word_in`` -- the input word of the transitions (when the
     769      finite state machine is used as automaton)
     770
     771    - ``word_out`` -- the output word of the transitions (when the
     772      finite state machine is used as transducer)
     773
     774    OUTPUT:
     775
     776    A transition of a finite state machine.
     777
     778    EXAMPLES::
     779
     780        sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     781        sage: A = FSMState('A')
     782        sage: B = FSMState('B')
     783        sage: S = FSMTransition(A, B, 0, 1)
     784        sage: T = FSMTransition('A', 'B', 0, 1)
     785        sage: T == S
     786        True
     787        sage: U = FSMTransition('A', 'B', 0)
     788        sage: U == T
     789        False
     790
     791    """
     792    def __init__(self, from_state, to_state,
     793                 word_in=None, word_out=None,
     794                 hook=None):
     795        """
     796        See :class:`FSMTransition` for more information.
     797
     798        EXAMPLES::
     799
     800            sage: from sage.combinat.finite_state_machine import FSMTransition
     801            sage: FSMTransition('A', 'B', 0, 1)
     802            Transition from State 'A' to State 'B': 0|1
     803        """
     804        if is_FSMState(from_state):
     805            self.from_state = from_state
     806        else:
     807            self.from_state = FSMState(from_state)
     808        if is_FSMState(to_state):
     809            self.to_state = to_state
     810        else:
     811            self.to_state = FSMState(to_state)
     812
     813        if isinstance(word_in, list):
     814            self.word_in = word_in
     815        elif word_in is not None:
     816            self.word_in = [word_in]
     817        else:
     818            self.word_in = []
     819
     820        if isinstance(word_out, list):
     821            self.word_out = word_out
     822        elif word_out is not None:
     823            self.word_out = [word_out]
     824        else:
     825            self.word_out = []
     826
     827        if hook is not None:
     828            if hasattr(hook, '__call__'):
     829                self.hook = hook
     830            else:
     831                raise TypeError, 'Wrong argument for hook.'
     832
     833
     834    def __copy__(self):
     835        """
     836        Returns a (shallow) copy of the transition.
     837
     838        INPUT:
     839
     840        Nothing.
     841
     842        OUTPUT:
     843
     844        A new transition.
     845
     846        EXAMPLES::
     847
     848            sage: from sage.combinat.finite_state_machine import FSMTransition
     849            sage: t = FSMTransition('A', 'B', 0)
     850            sage: copy(t)
     851            Transition from State 'A' to State 'B': 0|-
     852        """
     853        new = FSMTransition(self.from_state, self.to_state,
     854                            self.word_in, self.word_out)
     855        if hasattr(self, 'hook'):
     856            new.hook = self.hook
     857        return new
     858
     859
     860    copy = __copy__
     861
     862    def __deepcopy__(self, memo):
     863        """
     864        Returns a deep copy of the transition.
     865
     866        INPUT:
     867
     868        - ``memo`` -- a dictionary storing already processed elements.
     869
     870        OUTPUT:
     871       
     872        A new transition.
     873
     874        EXAMPLES::
     875
     876            sage: from sage.combinat.finite_state_machine import FSMTransition
     877            sage: t = FSMTransition('A', 'B', 0)
     878            sage: deepcopy(t)
     879            Transition from State 'A' to State 'B': 0|-
     880        """
     881        new = FSMTransition(deepcopy(self.from_state, memo),
     882                            deepcopy(self.to_state, memo),
     883                            deepcopy(self.word_in, memo),
     884                            deepcopy(self.word_out, memo))
     885        if hasattr(self, 'hook'):
     886            new.hook = deepcopy(self.hook, memo)
     887        return new
     888
     889
     890    def deepcopy(self, memo=None):
     891        """
     892        Returns a deep copy of the transition.
     893
     894        INPUT:
     895
     896        - ``memo`` -- (default: ``None``) a dictionary storing already
     897          processed elements.
     898
     899        OUTPUT:
     900       
     901        A new transition.
     902
     903        EXAMPLES::
     904
     905            sage: from sage.combinat.finite_state_machine import FSMTransition
     906            sage: t = FSMTransition('A', 'B', 0)
     907            sage: deepcopy(t)
     908            Transition from State 'A' to State 'B': 0|-
     909        """
     910        return deepcopy(self, memo)
     911
     912
     913    def __hash__(self):
     914        """
     915        Since transitions are mutable, they should not be hashable, so
     916        we return a type error.
     917
     918        INPUT:
     919
     920        Nothing.
     921
     922        OUTPUT:
     923
     924        The hash of this transition.
     925
     926        EXAMPLES::
     927
     928            sage: from sage.combinat.finite_state_machine import FSMTransition
     929            sage: hash(FSMTransition('A', 'B'))
     930            Traceback (most recent call last):
     931            ...
     932            TypeError: Transitions are mutable, and thus not hashable.
     933
     934        """
     935        raise TypeError, "Transitions are mutable, and thus not hashable."
     936
     937
     938    def _repr_(self):
     939        """
     940        Represents a transitions as from state to state and input, output.
     941
     942        INPUT:
     943
     944        Nothing.
     945
     946        OUTPUT:
     947
     948        A string.
     949
     950        EXAMPLES::
     951
     952            sage: from sage.combinat.finite_state_machine import FSMTransition
     953            sage: FSMTransition('A', 'B', 0, 0)._repr_()
     954            "Transition from State 'A' to State 'B': 0|0"
     955
     956        """
     957        return "Transition from %s to %s: %s" % (repr(self.from_state),
     958                                                 repr(self.to_state),
     959                                                 self._in_out_label_())
     960
     961
     962    def _in_out_label_(self):
     963        """
     964        Returns the input and output of a transition as
     965        "word_in|word_out".
     966
     967        INPUT:
     968
     969        Nothing.
     970
     971        OUTPUT:
     972
     973        A string of the input and output labels.
     974
     975        EXAMPLES::
     976
     977            sage: from sage.combinat.finite_state_machine import FSMTransition
     978            sage: FSMTransition('A', 'B', 0, 1)._in_out_label_()
     979            '0|1'
     980        """
     981        return "%s|%s" % (FSMWordSymbol(self.word_in),
     982                          FSMWordSymbol(self.word_out))
     983
     984
     985    def __eq__(left, right):
     986        """
     987        Returns True if the two transitions are the same, i.e., if the
     988        both go from the same states to the same states and read and
     989        write the same words.
     990
     991        Note that the hooks are not checked.
     992
     993        INPUT:
     994
     995        - ``left`` -- a transition.
     996
     997        - ``right`` -- a transition.
     998
     999        OUTPUT:
     1000
     1001        True or False.
     1002
     1003        EXAMPLES::
     1004
     1005            sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     1006            sage: A = FSMState('A', is_initial=True)
     1007            sage: t1 = FSMTransition('A', 'B', 0, 1)
     1008            sage: t2 = FSMTransition(A, 'B', 0, 1)
     1009            sage: t1 == t2
     1010            True
     1011        """
     1012        if not is_FSMTransition(right):
     1013            raise TypeError, 'Only instances of FSMTransition ' \
     1014                'can be compared.'
     1015        return left.from_state == right.from_state \
     1016            and left.to_state == right.to_state \
     1017            and left.word_in == right.word_in \
     1018            and left.word_out == right.word_out
     1019
     1020
     1021    def __ne__(left, right):
     1022        """
     1023
     1024        INPUT:
     1025
     1026        - ``left`` -- a transition.
     1027
     1028        - ``right`` -- a transition.
     1029
     1030        OUTPUT:
     1031
     1032        True or False.
     1033        Tests for inequality, complement of __eq__.
     1034
     1035        EXAMPLES::
     1036
     1037            sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     1038            sage: A = FSMState('A', is_initial=True)
     1039            sage: t1 = FSMTransition('A', 'B', 0, 1)
     1040            sage: t2 = FSMTransition(A, 'B', 0, 1)
     1041            sage: t1 != t2
     1042            False
     1043        """
     1044        return (not (left == right))
     1045
     1046
     1047    def __nonzero__(self):
     1048        """
     1049        Returns True.
     1050
     1051        INPUT:
     1052
     1053        Nothing.
     1054
     1055        OUTPUT:
     1056
     1057        True or False.
     1058
     1059        EXAMPLES::
     1060
     1061            sage: from sage.combinat.finite_state_machine import FSMTransition
     1062            sage: FSMTransition('A', 'B', 0).__nonzero__()
     1063            True
     1064        """
     1065        return True  # A transition cannot be zero (see __init__)
     1066
     1067
     1068#*****************************************************************************
     1069
     1070
     1071def is_FiniteStateMachine(FSM):
     1072    """
     1073    Tests whether or not ``FSM`` inherits from :class:`FiniteStateMachine`.
     1074
     1075    TESTS::
     1076
     1077        sage: from sage.combinat.finite_state_machine import is_FiniteStateMachine
     1078        sage: is_FiniteStateMachine(FiniteStateMachine())
     1079        True
     1080        sage: is_FiniteStateMachine(Automaton())
     1081        True
     1082        sage: is_FiniteStateMachine(Transducer())
     1083        True
     1084    """
     1085    return isinstance(FSM, FiniteStateMachine)
     1086
     1087
     1088class FiniteStateMachine(SageObject):
     1089    """
     1090    Class for a finite state machine.
     1091
     1092    A finite state machine is a finite set of states connected by transistions.
     1093
     1094    INPUT:
     1095
     1096    - ``data`` -- can be any of the following:
     1097
     1098      - ``{A:{B:{word_in=0, word_out=1}, C:{word_in=1, word_out=1}, ...}``
     1099      - ``{A:{B:(0, 1), C:(1, 1), ...}``
     1100      - ``{A:{B:FSMTransition(A, B, 0, 1), C:FSMTransition(A, C, 1, 1), ...}``
     1101      - ``{A:[(B, 0, 1), (C, 1, 1)], ...}``
     1102      - ``{A:[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)], ...}``
     1103      - ``[{from_state:A, to_state:B, word_in:0, word_out:1}, \
     1104            from_state:A, to_state:C, word_in:1, word_out:1}, ...]``
     1105      - ``[(A, B, 0, 1), (A, C, 1, 1), ...]``
     1106      - ``[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1), ...]``
     1107
     1108      If ``A``, ``B`` and ``C`` are not instances of :class:`FSMState`,
     1109      they are seen as labels of states. Note that the labels of a finite
     1110      state machine have to be unique.
     1111
     1112      Every tuple can be replaced by a list and vice versa. In fact,
     1113      everything that is iterable can be used.
     1114
     1115      Other arguments, which :class:`FSMTransition` accepts, can be added,
     1116      too. Do this after the argument ``word_out``.
     1117
     1118      Further the following things are accepted for ``data``:
     1119
     1120      - a function (acting as transition function): The input of that
     1121        function is the label of a state (the from-state) and a letter
     1122        of the alphabet. It should output a tuple consisting of a
     1123        label of a state (the to-state) and a word (the word-out). It
     1124        may also output a list of such tuples if several transitions
     1125        from the from-state and the input letter exist (this means
     1126        that the finite state machine is non-deterministic). If the
     1127        transition does not exist, the function should raise a
     1128        LookUpError or return an empty list.  Some ``initial_states``
     1129        and an ``input_alphabet`` have to be specified.
     1130
     1131      - an other instance of a finite state machine
     1132
     1133    - ``mode`` -- can be any of the following:
     1134
     1135      #.  None
     1136      #.  'automaton'
     1137      #.  'transducer'
     1138
     1139    - ``initial_states`` and ``final_states`` -- the initial and
     1140      finial states if this machine
     1141
     1142    - ``input_alphabet`` and ``output_alphabet`` -- the input and
     1143      output alphabets of this machine
     1144
     1145    - ``determine_alphabets`` -- If True, then the function
     1146      ``determine_alphabets()`` is called after ``data`` was read and
     1147      processed, if False, then not. If it is None, then it is decided
     1148      during the construction of the finite state machine whether
     1149      ``determine_alphabets()`` should be called.
     1150
     1151    - ``store_states_dict`` -- If True, then additionally the states
     1152      are stored in an interal dictionary for speed up.
     1153
     1154    OUTPUT:
     1155
     1156    A finite state machine.
     1157
     1158    EXAMPLES::
     1159
     1160        sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     1161
     1162    See documentation for more examples.
     1163
     1164    We illustrate the different input formats:
     1165
     1166    #.  A dictionary of lists, where keys are states and list-elements are
     1167        states::
     1168
     1169            sage: a = FSMState('S_a', 'a')
     1170            sage: b = FSMState('S_b', 'b')
     1171            sage: c = FSMState('S_c', 'c')
     1172            sage: d = FSMState('S_d', 'd')
     1173            sage: FiniteStateMachine({a:[b, c], b:[b, c, d],
     1174            ....:                     c:[a, b], d:[a, c]})
     1175            finite state machine with 4 states
     1176
     1177    #.  A list of transitions::
     1178
     1179            sage: a = FSMState('S_a', 'a')
     1180            sage: b = FSMState('S_b', 'b')
     1181            sage: c = FSMState('S_c', 'c')
     1182            sage: d = FSMState('S_d', 'd')
     1183            sage: t1 = FSMTransition(a, b)
     1184            sage: t2 = FSMTransition(b, c)
     1185            sage: t3 = FSMTransition(b, d)
     1186            sage: t4 = FSMTransition(c, d)
     1187            sage: FiniteStateMachine([t1, t2, t3, t4])
     1188            finite state machine with 4 states
     1189
     1190        It is possible to skip ``FSMTransition`` in the example above.
     1191
     1192    #.  A function:
     1193
     1194        In this case some inital states and an input alphabet have to
     1195        be specified. ::
     1196
     1197            sage: def f(state_from, read):
     1198            ....:     if int(state_from) + read <= 2:
     1199            ....:         state_to = 2*int(state_from)+read
     1200            ....:         write = 0
     1201            ....:     else:
     1202            ....:         state_to = 2*int(state_from) + read - 5
     1203            ....:         write = 1
     1204            ....:     return (str(state_to), write)
     1205            sage: F = FiniteStateMachine(f, input_alphabet=[0, 1],
     1206            ....:                        initial_states=['0'],
     1207            ....:                        final_states=['0'])
     1208            sage: F([1, 0, 1])
     1209            (True, State '0', [0, 0, 1])
     1210
     1211        ::
     1212
     1213            sage: A = FSMState('A')
     1214            sage: B = FSMState('B')
     1215            sage: C = FSMState('C')
     1216            sage: FSM1 = FiniteStateMachine(
     1217            ....:  {A:{B:{'word_in':0, 'word_out':1},
     1218            ....:   C:{'word_in':1, 'word_out':1}}})
     1219            sage: FSM2 = FiniteStateMachine({A:{B:(0, 1), C:(1, 1)}})
     1220            sage: FSM3 = FiniteStateMachine(
     1221            ....:  {A:{B:FSMTransition(A, B, 0, 1),
     1222            ....:      C:FSMTransition(A, C, 1, 1)}})
     1223            sage: FSM4 = FiniteStateMachine({A:[(B, 0, 1), (C, 1, 1)]})
     1224            sage: FSM5 = FiniteStateMachine(
     1225            ....:  {A:[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]})
     1226            sage: FSM6 = FiniteStateMachine(
     1227            ....:  [{'from_state':A, 'to_state':B, 'word_in':0, 'word_out':1},
     1228            ....:   {'from_state':A, 'to_state':C, 'word_in':1, 'word_out':1}])
     1229            sage: FSM7 = FiniteStateMachine([(A, B, 0, 1), (A, C, 1, 1)])
     1230            sage: FSM8 = FiniteStateMachine(
     1231            ....:  [FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)])
     1232
     1233            sage: FSM1 == FSM2 == FSM3 == FSM4 == FSM5 == FSM6 == FSM7 == FSM8
     1234            True
     1235
     1236        It is possible to skip ``FSMTransition`` in the example above.
     1237
     1238    #.  A dictionary of dictionaries, where
     1239
     1240        - the keys of the outer dictionary are state-labels (from-state of
     1241          transition),
     1242        - the keys of the inner dictionaries are state-labels (to-state of
     1243          transition),
     1244        - the values of the inner dictionaries are lists, where the first
     1245          entry is an input label and the second entry is an output label.
     1246
     1247        ::
     1248
     1249            sage: FiniteStateMachine({'a':{'a':[0, 0], 'b':[1, 1]},
     1250            ....:                     'b':{'b':[1, 0]}})
     1251            finite state machine with 2 states
     1252    """
     1253
     1254    #*************************************************************************
     1255    # init
     1256    #*************************************************************************
     1257
     1258
     1259    def __init__(self,
     1260                 data=None,
     1261                 mode=None,
     1262                 initial_states=None, final_states=None,
     1263                 input_alphabet=None, output_alphabet=None,
     1264                 determine_alphabets=None,
     1265                 store_states_dict=True):
     1266        """
     1267        See :class:`FiniteStateMachine` for more information.
     1268
     1269        TEST::
     1270
     1271            sage: FiniteStateMachine()
     1272            finite state machine with 0 states
     1273        """
     1274        self._states_ = []  # List of states in the finite state
     1275                            # machine.  Each state stores a list of
     1276                            # outgoing transitions.
     1277        if store_states_dict:
     1278            self._states_dict_ = {}
     1279
     1280        if initial_states is not None:
     1281            if not hasattr(initial_states, '__iter__'):
     1282                raise TypeError, 'Initial states must be iterable ' \
     1283                    '(e.g. a list of states).'
     1284            for s in initial_states:
     1285                state = self.add_state(s)
     1286                state.is_initial = True
     1287
     1288        if final_states is not None:
     1289            if not hasattr(final_states, '__iter__'):
     1290                raise TypeError, 'Final states must be iterable ' \
     1291                    '(e.g. a list of states).'
     1292            for s in final_states:
     1293                state = self.add_state(s)
     1294                state.is_final = True
     1295
     1296        self.mode = mode
     1297        self.input_alphabet = input_alphabet
     1298        self.output_alphabet = output_alphabet
     1299
     1300        if data is None:
     1301            pass
     1302        elif is_FiniteStateMachine(data):
     1303            raise NotImplementedError
     1304        elif hasattr(data, 'iteritems'):
     1305            # data is a dict (or something similar),
     1306            # format: key = from_state, value = iterator of transitions
     1307            for (sf, iter_transitions) in data.iteritems():
     1308                self.add_state(sf)
     1309                if hasattr(iter_transitions, 'iteritems'):
     1310                    for (st, transition) in iter_transitions.iteritems():
     1311                        self.add_state(st)
     1312                        if is_FSMTransition(transition):
     1313                            self.add_transition(transition)
     1314                        elif hasattr(transition, 'iteritems'):
     1315                            self.add_transition(sf, st, **transition)
     1316                        elif hasattr(transition, '__iter__'):
     1317                            self.add_transition(sf, st, *transition)
     1318                        else:
     1319                            self.add_transition(sf, st, transition)
     1320                elif hasattr(iter_transitions, '__iter__'):
     1321                    for transition in iter_transitions:
     1322                        if hasattr(transition, '__iter__'):
     1323                            L = [sf]
     1324                            L.extend(transition)
     1325                        elif is_FSMTransition(transition):
     1326                            L = transition
     1327                        else:
     1328                            L = [sf, transition]
     1329                        self.add_transition(L)
     1330                else:
     1331                    raise TypeError, 'Wrong input data for transition.'
     1332            if determine_alphabets is None and input_alphabet is None \
     1333                    and output_alphabet is None:
     1334                determine_alphabets = True
     1335        elif hasattr(data, '__iter__'):
     1336            # data is a something that is iterable,
     1337            # items are transitions
     1338            for transition in data:
     1339                if is_FSMTransition(transition):
     1340                    self.add_transition(transition)
     1341                elif hasattr(transition, 'iteritems'):
     1342                    self.add_transition(transition)
     1343                elif hasattr(transition, '__iter__'):
     1344                    self.add_transition(transition)
     1345                else:
     1346                    raise TypeError, 'Wrong input data for transition.'
     1347            if determine_alphabets is None and input_alphabet is None \
     1348                    and output_alphabet is None:
     1349                determine_alphabets = True
     1350        elif hasattr(data, '__call__'):
     1351            self.add_from_transition_function(data,
     1352                                              ignore_existing_states=True)
     1353        else:
     1354            raise TypeError, 'Cannot decide what to do with data.'
     1355
     1356        if determine_alphabets:
     1357            self.determine_alphabets()
     1358
     1359
     1360    #*************************************************************************
     1361    # copy and hash
     1362    #*************************************************************************
     1363
     1364
     1365    def __copy__(self):
     1366        """
     1367        Returns a (shallow) copy of the finite state machine.
     1368
     1369        INPUT:
     1370
     1371        Nothing.
     1372
     1373        OUTPUT:
     1374       
     1375        A new finite state machine.
     1376
     1377        TESTS::
     1378
     1379            sage: copy(FiniteStateMachine())
     1380            Traceback (most recent call last):
     1381            ...
     1382            NotImplementedError
     1383        """
     1384        raise NotImplementedError
     1385
     1386
     1387    copy = __copy__
     1388
     1389
     1390    def __deepcopy__(self, memo):
     1391        """
     1392        Returns a deep copy of the finite state machine.
     1393
     1394        INPUT:
     1395
     1396        - ``memo`` -- a dictionary storing already processed elements.
     1397
     1398        OUTPUT:
     1399       
     1400        A new finite state machine.
     1401
     1402        EXAMPLES::
     1403
     1404            sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)])
     1405            sage: deepcopy(F)
     1406            finite state machine with 1 states
     1407        """
     1408        relabel = hasattr(self, '_deepcopy_relabel_')
     1409        new = FiniteStateMachine()
     1410        relabel_iter = itertools.count(0)
     1411        for state in self.iter_states():
     1412            if relabel:
     1413                state._deepcopy_relabel_ = relabel_iter.next()
     1414            s = deepcopy(state, memo)
     1415            if relabel:
     1416                del state._deepcopy_relabel_
     1417            new.add_state(s)
     1418        for transition in self.iter_transitions():
     1419            new.add_transition(deepcopy(transition, memo))
     1420        new.mode = deepcopy(self.mode, memo)
     1421        new.input_alphabet = deepcopy(self.input_alphabet, memo)
     1422        new.output_alphabet = deepcopy(self.output_alphabet, memo)
     1423        return new
     1424
     1425
     1426    def deepcopy(self, memo=None):
     1427        """
     1428        Returns a deep copy of the finite state machine.
     1429
     1430        INPUT:
     1431
     1432        - ``memo`` -- (default: ``None``) a dictionary storing already
     1433          processed elements.
     1434
     1435        OUTPUT:
     1436       
     1437        A new finite state machine.
     1438
     1439        EXAMPLES::
     1440
     1441            sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)])
     1442            sage: deepcopy(F)
     1443            finite state machine with 1 states
     1444        """
     1445        return deepcopy(self, memo)
     1446
     1447
     1448    def relabeled(self, memo=None):
     1449        """
     1450        Returns a deep copy of the finite state machine, but the
     1451        states are relabeled by integers starting with 0.
     1452
     1453        INPUT:
     1454
     1455        - ``memo`` -- (default: ``None``) a dictionary storing already
     1456          processed elements.
     1457
     1458        OUTPUT:
     1459       
     1460        A new finite state machine.
     1461
     1462        EXAMPLES::
     1463
     1464            sage: FSM1 = FiniteStateMachine([('A', 'B'), ('B', 'C'), ('C', 'A')])
     1465            sage: FSM1.states()
     1466            [State 'A', State 'B', State 'C']
     1467            sage: FSM2 = FSM1.relabeled()
     1468            sage: FSM2.states()
     1469            [State 0, State 1, State 2]
     1470        """
     1471        self._deepcopy_relabel_ = True
     1472        new = deepcopy(self, memo)
     1473        del self._deepcopy_relabel_
     1474        return new
     1475
     1476
     1477    def __hash__(self):
     1478        """
     1479        Since finite state machines are mutable, they should not be
     1480        hashable, so we return a type error.
     1481
     1482        INPUT:
     1483
     1484        Nothing.
     1485
     1486        OUTPUT:
     1487
     1488        The hash of this finite state machine.
     1489
     1490        EXAMPLES::
     1491
     1492            sage: hash(FiniteStateMachine())
     1493            Traceback (most recent call last):
     1494            ...
     1495            TypeError: Finite state machines are mutable, and thus not hashable.
     1496        """
     1497        if getattr(self, "_immutable", False):
     1498            return hash((tuple(self.states()), tuple(self.transitions())))
     1499        raise TypeError, "Finite state machines are mutable, " \
     1500            "and thus not hashable."
     1501
     1502
     1503    #*************************************************************************
     1504    # operators
     1505    #*************************************************************************
     1506
     1507
     1508    def __add__(self, other):
     1509        """
     1510        Returns the disjoint union of the finite state machines self and other.
     1511
     1512        INPUT:
     1513       
     1514        - ``other`` -- a finite state machine.
     1515
     1516        OUTPUT:
     1517       
     1518        A new finite state machine.
     1519
     1520        TESTS::
     1521
     1522            sage: FiniteStateMachine() + FiniteStateMachine([('A', 'B')])
     1523            Traceback (most recent call last):
     1524            ...
     1525            NotImplementedError
     1526        """
     1527        if is_FiniteStateMachine(other):
     1528            return self.disjoint_union(other)
     1529
     1530
     1531    def __iadd__(self, other):
     1532        """
     1533        TESTS::
     1534
     1535            sage: F = FiniteStateMachine()
     1536            sage: F += FiniteStateMachine()
     1537            Traceback (most recent call last):
     1538            ...
     1539            NotImplementedError
     1540        """
     1541        raise NotImplementedError
     1542
     1543
     1544    def __mul__(self, other):
     1545        """
     1546        TESTS::
     1547
     1548            sage: FiniteStateMachine() * FiniteStateMachine([('A', 'B')])
     1549            Traceback (most recent call last):
     1550            ...
     1551            NotImplementedError
     1552        """
     1553        if is_FiniteStateMachine(other):
     1554            return self.intersection(other)
     1555
     1556
     1557    def __imul__(self, other):
     1558        """
     1559        TESTS::
     1560
     1561            sage: F = FiniteStateMachine()
     1562            sage: F *= FiniteStateMachine()
     1563            Traceback (most recent call last):
     1564            ...
     1565            NotImplementedError
     1566        """
     1567        raise NotImplementedError
     1568
     1569
     1570    def __call__(self, *args, **kwargs):
     1571        """
     1572        Calls either method :meth:`.composition` or :meth:`.process`.
     1573
     1574        EXAMPLES::
     1575
     1576            sage: from sage.combinat.finite_state_machine import FSMState
     1577            sage: A = FSMState('A', is_initial=True, is_final=True)
     1578            sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]})
     1579            sage: binary_inverter([0, 1, 0, 0, 1, 1])
     1580            (True, State 'A', [1, 0, 1, 1, 0, 0])
     1581
     1582        ::
     1583
     1584            sage: F = Transducer([('A', 'B', 1, 0), ('B', 'B', 1, 1),
     1585            ....:                 ('B', 'B', 0, 0)],
     1586            ....:                initial_states=['A'], final_states=['B'])
     1587            sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),
     1588            ....:                 (2, 2, 0, 1), (2, 1, 1, 1)],
     1589            ....:                initial_states=[1], final_states=[1])
     1590            sage: H = G(F)
     1591            sage: H.states()
     1592            [State ('A', 1), State ('B', 1), State ('B', 2)]
     1593        """
     1594        if len(args) == 0:
     1595            raise TypeError, "Called with too few arguments."
     1596        if is_FiniteStateMachine(args[0]):
     1597            return self.composition(*args, **kwargs)
     1598        if hasattr(args[0], '__iter__'):
     1599            return self.process(*args, **kwargs)
     1600        raise TypeError, "Do not know what to do with that arguments."
     1601
     1602
     1603    #*************************************************************************
     1604    # tests
     1605    #*************************************************************************
     1606
     1607
     1608    def __nonzero__(self):
     1609        """
     1610        Returns True if the finite state machine consists of at least
     1611        one state.
     1612
     1613        INPUT:
     1614
     1615        Nothing.
     1616
     1617        OUTPUT:
     1618
     1619        True or False.
     1620
     1621        TESTS::
     1622
     1623            sage: FiniteStateMachine().__nonzero__()
     1624            False
     1625        """
     1626        return len(self._states_) > 0
     1627
     1628
     1629    def __eq__(left, right):
     1630        """
     1631        Returns True if the two finite state machines are equal, i.e.,
     1632        if they have the same states and the same transitions.
     1633
     1634        INPUT:
     1635
     1636        - ``left`` -- a finite state machine.
     1637
     1638        - ``right`` -- a finite state machine.
     1639
     1640        OUTPUT:
     1641
     1642        True or False.
     1643
     1644        EXAMPLES::
     1645
     1646            sage: F = FiniteStateMachine([('A', 'B', 1)])
     1647            sage: F == FiniteStateMachine()
     1648            False
     1649        """
     1650        if not is_FiniteStateMachine(right):
     1651            raise TypeError, 'Only instances of FiniteStateMachine ' \
     1652                'can be compared.'
     1653        if len(left._states_) != len(right._states_):
     1654            return False
     1655        for state in left.iter_states():
     1656            if state not in right._states_:
     1657                return False
     1658            left_transitions = state.transitions
     1659            right_transitions = right.state(state).transitions
     1660            if len(left_transitions) != len(right_transitions):
     1661                return False
     1662            for t in left_transitions:
     1663                if t not in right_transitions:
     1664                    return False
     1665        return True
     1666
     1667
     1668    def __ne__(left, right):
     1669        """
     1670        Tests for inequality, complement of :meth:`.__eq__`.
     1671
     1672        INPUT:
     1673
     1674        - ``left`` -- a finite state machine.
     1675
     1676        - ``right`` -- a finite state machine.
     1677
     1678        OUTPUT:
     1679
     1680        True or False.
     1681
     1682        EXAMPLES::
     1683
     1684            sage: E = FiniteStateMachine([('A', 'B', 0)])
     1685            sage: F = Automaton([('A', 'B', 0)])
     1686            sage: G = Transducer([('A', 'B', 0, 1)])
     1687            sage: E == F
     1688            True
     1689            sage: E == G
     1690            False
     1691        """
     1692        return (not (left == right))
     1693
     1694
     1695    def __contains__(self, item):
     1696        """
     1697        Returns true, if the finite state machine contains the
     1698        state or transition item. Note that only the labels of the
     1699        states and the input and output words are tested.
     1700
     1701        INPUT:
     1702
     1703        - ``item`` -- a state or a transition.
     1704
     1705        OUTPUT:
     1706
     1707        True or False.
     1708
     1709        EXAMPLES::
     1710
     1711            sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     1712            sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)])
     1713            sage: FSMState('A', is_initial=True) in F
     1714            True
     1715            sage: 'A' in F
     1716            False
     1717            sage: FSMTransition('A', 'B', 0) in F
     1718            True
     1719        """
     1720        if is_FSMState(item):
     1721            return self.has_state(item)
     1722        if is_FSMTransition(item):
     1723            return self.has_transition(item)
     1724        return False
     1725
     1726
     1727    #*************************************************************************
     1728    # representations / LaTeX
     1729    #*************************************************************************
     1730
     1731
     1732    def _repr_(self):
     1733        """
     1734        Represents the finite state machine as "finite state machine
     1735        with n states" where n is the number of states.
     1736
     1737        INPUT:
     1738
     1739        Nothing.
     1740
     1741        OUTPUT:
     1742
     1743        A string.
     1744
     1745        EXAMPLES::
     1746
     1747            sage: FiniteStateMachine()._repr_()
     1748            'finite state machine with 0 states'
     1749        """
     1750        return "finite state machine with %s states" % len(self._states_)
     1751
     1752
     1753    def _latex_(self):
     1754        r"""
     1755        Returns a LaTeX code for the graph of the finite state machine.
     1756
     1757        INPUT:
     1758
     1759        Nothing.
     1760
     1761        OUTPUT:
     1762
     1763        A string.
     1764
     1765        EXAMPLES::
     1766
     1767            sage: F = FiniteStateMachine([('A', 'B', 1)])
     1768            sage: F._latex_()
     1769            '\\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[\\right]$} (v1);\n\\end{tikzpicture}'
     1770        """
     1771        result = "\\begin{tikzpicture}[auto]\n"
     1772        j = 0;
     1773        for vertex in self.states():
     1774            if not hasattr(vertex, "coordinates"):
     1775                vertex.coordinates = (3*cos(2*pi*j/len(self.states())),
     1776                                      3*sin(2*pi*j/len(self.states())))
     1777            options = ""
     1778            if vertex in self.final_states():
     1779                options += ",accepting"
     1780            if hasattr(vertex, "format_label"):
     1781                label = vertex.format_label()
     1782            elif hasattr(self, "format_state_label"):
     1783                label = self.format_state_label(vertex)
     1784            else:
     1785                label = latex(vertex.label())
     1786            result += "\\node[state%s] (v%d) at (%f,%f) {%s}\n;" % (
     1787                options, j, vertex.coordinates[0],
     1788                vertex.coordinates[1], label)
     1789            vertex._number_ = j
     1790            j += 1
     1791        adjacent = {}
     1792        for source in self.states():
     1793            for target in self.states():
     1794                transitions = filter(lambda transition: \
     1795                                         transition.to_state == target,
     1796                                     source.transitions)
     1797                adjacent[source, target] = transitions
     1798
     1799        for ((source, target), transitions) in adjacent.iteritems():
     1800            if len(transitions) > 0:
     1801                labels = []
     1802                for transition in transitions:
     1803                    if hasattr(transition, "format_label"):
     1804                        labels.append(transition.format_label())
     1805                        continue
     1806                    elif hasattr(self, "format_transition_label"):
     1807                        format_transition_label = self.format_transition_label
     1808                    else:
     1809                        format_transition_label = latex
     1810                    if self.mode == 'automaton':
     1811                        labels.append(format_transition_label(
     1812                                transition.word_in))
     1813                    else:
     1814                        labels.append(format_transition_label(
     1815                            transition.word_in) + "\\mid" + \
     1816                                format_transition_label(transition.word_out))
     1817                label = ", ".join(labels)
     1818                if source != target:
     1819                    if len(adjacent[target, source]) > 0:
     1820                        angle = atan2(
     1821                            target.coordinates[1] - source.coordinates[1],
     1822                            target.coordinates[0]-source.coordinates[0])*180/pi
     1823                        angle_source = ".%.2f" % ((angle+5).n(),)
     1824                        angle_target = ".%.2f" % ((angle+175).n(),)
     1825                    else:
     1826                        angle_source = ""
     1827                        angle_target = ""
     1828                    result += "\\path[->] (v%d%s) edge node {$%s$} (v%d%s);\n" % (
     1829                        source._number_, angle_source, label,
     1830                        target._number_, angle_target)
     1831                else:
     1832                    result += "\\path[->] (v%d) edge[loop above] node {$%s$} ();\n" % (
     1833                        source._number_, label)
     1834
     1835        result += "\\end{tikzpicture}"
     1836        return result
     1837
     1838
     1839    #*************************************************************************
     1840    # other
     1841    #*************************************************************************
     1842
     1843
     1844    def _matrix_(self, R=None):
     1845        """
     1846        Returns the adjacency matrix of the finite state machine.
     1847        See :meth:`.adjacency_matrix` for more information.
     1848
     1849        EXAMPLES::
     1850
     1851            sage: B = FiniteStateMachine({0: {0: (0, 0), 'a': (1, 0)},
     1852            ....:                         'a': {2: (0, 0), 3: (1, 0)},
     1853            ....:                         2:{0:(1, 1), 4:(0, 0)},
     1854            ....:                         3:{'a':(0, 1), 2:(1, 1)},
     1855            ....:                         4:{4:(1, 1), 3:(0, 1)}},
     1856            ....:                        initial_states=[0])
     1857            sage: B._matrix_()
     1858            [1 1 0 0 0]
     1859            [0 0 1 1 0]
     1860            [x 0 0 0 1]
     1861            [0 x x 0 0]
     1862            [0 0 0 x x]
     1863        """
     1864        return self.adjacency_matrix()
     1865
     1866
     1867    def adjacency_matrix(self, input=None,
     1868                         entry=(lambda transition:var('x')**transition.word_out[0])):
     1869        """
     1870        Returns the adjacency matrix of the underlying graph.
     1871
     1872        INPUT:
     1873
     1874        - ``input`` -- Only transitions with input label ``input`` are
     1875          respected.
     1876
     1877        - ``entry`` -- The function ``entry`` takes a transition and
     1878          the return value is written in the matrix as the entry
     1879          ``(transition.from_state, transition.to_state)``.
     1880         
     1881        OUTPUT:
     1882
     1883        A matrix.
     1884
     1885        If any label of a state is not an integer, the finite state
     1886        machine is relabeled at the beginning.  If there are more than
     1887        one transitions between two states, then the different return
     1888        values of ``entry`` are added up.
     1889
     1890        The default value of entry takes the variable ``x`` to the
     1891        power of the output word of the transition.
     1892
     1893        EXAMPLES::
     1894
     1895            sage: B = FiniteStateMachine({0:{0:(0, 0), 'a':(1, 0)},
     1896            ....:                         'a':{2:(0, 0), 3:(1, 0)},
     1897            ....:                         2:{0:(1, 1), 4:(0, 0)},
     1898            ....:                         3:{'a':(0, 1), 2:(1, 1)},
     1899            ....:                         4:{4:(1, 1), 3:(0, 1)}},
     1900            ....:                        initial_states=[0])
     1901            sage: B.adjacency_matrix()
     1902            [1 1 0 0 0]
     1903            [0 0 1 1 0]
     1904            [x 0 0 0 1]
     1905            [0 x x 0 0]
     1906            [0 0 0 x x]
     1907            sage: B.adjacency_matrix(entry=(lambda transition: 1))
     1908            [1 1 0 0 0]
     1909            [0 0 1 1 0]
     1910            [1 0 0 0 1]
     1911            [0 1 1 0 0]
     1912            [0 0 0 1 1]
     1913            sage: B.adjacency_matrix(1, entry=(lambda transition:
     1914            ....:     exp(I*transition.word_out[0]*var('t'))))
     1915            [      0       1       0       0       0]
     1916            [      0       0       0       1       0]
     1917            [e^(I*t)       0       0       0       0]
     1918            [      0       0 e^(I*t)       0       0]
     1919            [      0       0       0       0 e^(I*t)]
     1920
     1921        """
     1922        relabeledFSM = self
     1923        l = len(relabeledFSM.states())
     1924        for state in self.states():
     1925            if state.label() not in ZZ or state.label() >= l:
     1926                relabeledFSM = self.relabeled()
     1927                break
     1928        dictionary = {}
     1929        for transition in relabeledFSM.iter_transitions():
     1930            if input is None or transition.word_in == [input]:
     1931                if (transition.from_state.label(), transition.to_state.label()) in dictionary:
     1932                    dictionary[(transition.from_state.label(), transition.to_state.label())] += entry(transition)
     1933                else:
     1934                    dictionary[(transition.from_state.label(), transition.to_state.label())] = entry(transition)
     1935        return matrix(len(relabeledFSM.states()), dictionary)
     1936
     1937
     1938    def determine_alphabets(self, reset=True):
     1939        """
     1940        Determines the input and output alphabet according to the
     1941        transitions in self.
     1942
     1943        INPUT:
     1944
     1945        - ``reset`` -- If reset is True, then the existing input
     1946          alphabet is erased, otherwise new letters are appended to
     1947          the existing alphabet.
     1948
     1949        OUTPUT:
     1950
     1951        Nothing.
     1952
     1953        After this operation the input alphabet and the output
     1954        alphabet of self are a list of letters.
     1955
     1956        EXAMPLES::
     1957
     1958            sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1),
     1959            ....:                 (2, 2, 1, 1), (2, 2, 0, 0)],
     1960            ....:                determine_alphabets=False)
     1961            sage: (T.input_alphabet, T.output_alphabet)
     1962            (None, None)
     1963            sage: T.determine_alphabets()
     1964            sage: (T.input_alphabet, T.output_alphabet)
     1965            ([0, 1, 2], [0, 1])
     1966       """
     1967        if reset:
     1968            ain = set()
     1969            aout = set()
     1970        else:
     1971            ain = set(self.input_alphabet)
     1972            aout = set(self.output_alphabet)
     1973
     1974        for t in self.iter_transitions():
     1975            for letter in t.word_in:
     1976                ain.add(letter)
     1977            for letter in t.word_out:
     1978                aout.add(letter)
     1979        self.input_alphabet = list(ain)
     1980        self.output_alphabet = list(aout)
     1981
     1982
     1983    #*************************************************************************
     1984    # get states and transitions
     1985    #*************************************************************************
     1986
     1987
     1988    def states(self):
     1989        """
     1990        Returns the states of the finite state machine.
     1991
     1992        INPUT:
     1993
     1994        Nothing.
     1995
     1996        OUTPUT:
     1997
     1998        The states of the finite state machine as list.
     1999
     2000        EXAMPLES::
     2001
     2002            sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])
     2003            sage: FSM.states()
     2004            [State '1', State '2']
     2005
     2006        """
     2007        return copy(self._states_)
     2008
     2009
     2010    def iter_states(self):
     2011        """
     2012        Returns an iterator of the states.
     2013
     2014        INPUT:
     2015
     2016        Nothing.
     2017
     2018        OUTPUT:
     2019
     2020        An iterator of the states of the finite state machine.
     2021
     2022        EXAMPLES::
     2023
     2024            sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])
     2025            sage: [s.label() for s in FSM.iter_states()]
     2026            ['1', '2']
     2027        """
     2028        return iter(self._states_)
     2029
     2030
     2031    def transitions(self, from_state=None):
     2032        """
     2033        Returns a list of all transitions.
     2034
     2035        INPUT:
     2036       
     2037        - ``from_state`` -- (default: ``None``) If ``from_state`` is
     2038          given, then a list of transitions starting there is given.
     2039
     2040        OUTPUT:
     2041
     2042        A list of all transitions.
     2043
     2044        EXAMPLES::
     2045
     2046            sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])
     2047            sage: FSM.transitions()
     2048            [Transition from State '1' to State '2': 1|-,
     2049             Transition from State '2' to State '2': 0|-]
     2050        """
     2051        return list(self.iter_transitions(from_state))
     2052
     2053
     2054    def iter_transitions(self, from_state=None):
     2055        """
     2056        Returns an iterator of all transitions.
     2057
     2058        INPUT:
     2059       
     2060        - ``from_state`` -- (default: ``None``) If ``from_state`` is
     2061          given, then a list of transitions starting there is given.
     2062
     2063        OUTPUT:
     2064
     2065        An iterator of all transitions.
     2066
     2067        EXAMPLES::
     2068
     2069            sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])
     2070            sage: [(t.from_state.label(), t.to_state.label())
     2071            ....:     for t in FSM.iter_transitions('1')]
     2072            [('1', '2')]
     2073            sage: [(t.from_state.label(), t.to_state.label())
     2074            ....:     for t in FSM.iter_transitions('2')]
     2075            [('2', '2')]
     2076            sage: [(t.from_state.label(), t.to_state.label())
     2077            ....:     for t in FSM.iter_transitions()]
     2078            [('1', '2'), ('2', '2')]
     2079        """
     2080        if from_state is None:
     2081            return self._iter_transitions_all_()
     2082        else:
     2083            return iter(self.state(from_state).transitions)
     2084
     2085
     2086    def _iter_transitions_all_(self):
     2087        """
     2088        Returns an iterator over all transitions.
     2089
     2090        INPUT:
     2091
     2092        Nothing.
     2093
     2094        OUTPUT:
     2095
     2096        An iterator over all transitions.
     2097
     2098        EXAMPLES::
     2099
     2100            sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)])
     2101            sage: [(t.from_state.label(), t.to_state.label())
     2102            ....:     for t in FSM._iter_transitions_all_()]
     2103            [('1', '2'), ('2', '2')]
     2104        """
     2105        for state in self.iter_states():
     2106            for t in state.transitions:
     2107                yield t
     2108
     2109
     2110    def initial_states(self):
     2111        """
     2112        Returns a list of all initial states.
     2113
     2114        INPUT:
     2115
     2116        Nothing.
     2117
     2118        OUTPUT:
     2119
     2120        A list of all initial states.
     2121
     2122        EXAMPLES::
     2123
     2124            sage: from sage.combinat.finite_state_machine import FSMState
     2125            sage: A = FSMState('A', is_initial=True)
     2126            sage: B = FSMState('B')
     2127            sage: F = FiniteStateMachine([(A, B, 1, 0)])
     2128            sage: F.initial_states()
     2129            [State 'A']
     2130        """
     2131        return list(self.iter_initial_states())
     2132
     2133
     2134    def iter_initial_states(self):
     2135        """
     2136        Returns an iterator of the initial states.
     2137
     2138        INPUT:
     2139
     2140        Nothing.
     2141
     2142        OUTPUT:
     2143
     2144        An iterator over all initial states.
     2145
     2146        EXAMPLES::
     2147
     2148            sage: from sage.combinat.finite_state_machine import FSMState
     2149            sage: A = FSMState('A', is_initial=True)
     2150            sage: B = FSMState('B')
     2151            sage: F = FiniteStateMachine([(A, B, 1, 0)])
     2152            sage: [s.label() for s in F.iter_initial_states()]
     2153            ['A']
     2154        """
     2155        return itertools.ifilter(lambda s:s.is_initial, self.iter_states())
     2156
     2157
     2158    def final_states(self):
     2159        """
     2160        Returns a list of all final states.
     2161
     2162        INPUT:
     2163
     2164        Nothing.
     2165
     2166        OUTPUT:
     2167
     2168        A list of all final states.
     2169
     2170        EXAMPLES::
     2171
     2172            sage: from sage.combinat.finite_state_machine import FSMState
     2173            sage: A = FSMState('A', is_final=True)
     2174            sage: B = FSMState('B', is_initial=True)
     2175            sage: C = FSMState('C', is_final=True)
     2176            sage: F = FiniteStateMachine([(A, B), (A, C)])
     2177            sage: F.final_states()
     2178            [State 'A', State 'C']
     2179        """
     2180        return list(self.iter_final_states())
     2181
     2182
     2183    def iter_final_states(self):
     2184        """
     2185        Returns an iterator of the final states.
     2186
     2187        INPUT:
     2188
     2189        Nothing.
     2190
     2191        OUTPUT:
     2192
     2193        An iterator over all initial states.
     2194
     2195        EXAMPLES::
     2196
     2197            sage: from sage.combinat.finite_state_machine import FSMState
     2198            sage: A = FSMState('A', is_final=True)
     2199            sage: B = FSMState('B', is_initial=True)
     2200            sage: C = FSMState('C', is_final=True)
     2201            sage: F = FiniteStateMachine([(A, B), (A, C)])
     2202            sage: [s.label() for s in F.iter_final_states()]
     2203            ['A', 'C']
     2204        """
     2205        return itertools.ifilter(lambda s:s.is_final, self.iter_states())
     2206
     2207
     2208    def state(self, state):
     2209        """
     2210        Returns the state of the finite state machine.
     2211
     2212        INPUT:
     2213
     2214        - ``state`` -- If ``state`` is not an instance of
     2215          :class:`FSMState`, then it is assumed that it is the label
     2216          of a state.
     2217
     2218        OUTPUT:
     2219
     2220        Returns the state of the finite state machine corresponding to
     2221        ``state``.
     2222
     2223        If no state is found, then a ``LookupError`` is thrown.
     2224
     2225        EXAMPLES::
     2226
     2227            sage: from sage.combinat.finite_state_machine import FSMState
     2228            sage: A = FSMState('A')
     2229            sage: FSM = FiniteStateMachine([(A, 'B'), ('C', A)])
     2230            sage: FSM.state('A') == A
     2231            True
     2232            sage: FSM.state('xyz')
     2233            Traceback (most recent call last):
     2234            ...
     2235            LookupError: No state with label xyz found.
     2236        """
     2237        def what(s, switch):
     2238            if switch:
     2239                return s.label()
     2240            else:
     2241                return s
     2242        switch = is_FSMState(state)
     2243
     2244        try:
     2245            return self._states_dict_[what(state, switch)]
     2246        except AttributeError:
     2247            for s in self.iter_states():
     2248                if what(s, not switch) == state:
     2249                    return s
     2250        except KeyError:
     2251            pass
     2252        raise LookupError, \
     2253            "No state with label %s found." % (what(state, switch),)
     2254
     2255
     2256    def transition(self, transition):
     2257        """
     2258        Returns the transition of the finite state machine.
     2259
     2260        INPUT:
     2261
     2262        - ``transition`` -- If ``transition`` is not an instance of
     2263          :class:`FSMTransition`, then it is assumed that it is a
     2264          tuple ``(from_state, to_state, word_in, word_out)``.
     2265
     2266        OUTPUT:
     2267
     2268        Returns the transition of the finite state machine
     2269        corresponding to ``transition``.
     2270
     2271        If no transition is found, then a ``LookupError`` is thrown.
     2272
     2273        EXAMPLES::
     2274
     2275            sage: from sage.combinat.finite_state_machine import FSMTransition
     2276            sage: t = FSMTransition('A', 'B', 0)
     2277            sage: F = FiniteStateMachine([t])
     2278            sage: F.transition(('A', 'B', 0))
     2279            Transition from State 'A' to State 'B': 0|-
     2280            sage: id(t) == id(F.transition(('A', 'B', 0)))
     2281            True
     2282       """
     2283        if not is_FSMTransition(transition):
     2284            transition = FSMTransition(*transition)
     2285        for s in self.iter_transitions(transition.from_state):
     2286            if s == transition:
     2287                return s
     2288        raise LookupError, "No transition found."
     2289
     2290
     2291    #*************************************************************************
     2292    # properties (state and transitions)
     2293    #*************************************************************************
     2294
     2295
     2296    def has_state(self, state):
     2297        """
     2298        Returns whether ``state`` is one of the states of the finite
     2299        state machine.
     2300
     2301        INPUT:
     2302
     2303        - ``state`` can be a :class:`FSMState` or a label of a state.
     2304
     2305        OUTPUT:
     2306
     2307        True or False.
     2308
     2309        EXAMPLES::
     2310
     2311            sage: FiniteStateMachine().has_state('A')
     2312            False
     2313        """
     2314        try:
     2315            self.state(state)
     2316            return True
     2317        except LookupError:
     2318            return False
     2319
     2320
     2321    def has_transition(self, transition):
     2322        """
     2323        Returns whether ``transition`` is one of the transitions of
     2324        the finite state machine.
     2325
     2326        INPUT:
     2327
     2328        - ``transition`` has to be a :class:`FSMTransition`.
     2329
     2330        OUTPUT:
     2331
     2332        True or False.
     2333
     2334        EXAMPLES::
     2335
     2336            sage: from sage.combinat.finite_state_machine import FSMTransition
     2337            sage: t = FSMTransition('A', 'A', 0, 1)
     2338            sage: FiniteStateMachine().has_transition(t)
     2339            False
     2340            sage: FiniteStateMachine().has_transition(('A', 'A', 0, 1))
     2341            Traceback (most recent call last):
     2342            ...
     2343            TypeError: Transition is not an instance of FSMTransition.
     2344        """
     2345        if is_FSMTransition(transition):
     2346            return transition in self.iter_transitions()
     2347        raise TypeError, "Transition is not an instance of FSMTransition."
     2348
     2349
     2350    def has_initial_state(self, state):
     2351        """
     2352        Returns whether ``state`` is one of the initial states of the
     2353        finite state machine.
     2354
     2355        INPUT:
     2356
     2357        - ``state`` can be a :class:`FSMState` or a label.
     2358
     2359        OUTPUT:
     2360
     2361        True or False.
     2362
     2363        EXAMPLES::
     2364
     2365            sage: F = FiniteStateMachine([('A', 'A')], initial_states=['A'])
     2366            sage: F.has_initial_state('A')
     2367            True
     2368        """
     2369        try:
     2370            return self.state(state).is_initial
     2371        except LookupError:
     2372            return False
     2373
     2374
     2375    def has_initial_states(self):
     2376        """
     2377        Returns whether the finite state machine has an initial state.
     2378
     2379        INPUT:
     2380
     2381        Nothing.
     2382
     2383        OUTPUT:
     2384
     2385        True or False.
     2386
     2387        EXAMPLES::
     2388
     2389            sage: FiniteStateMachine().has_initial_states()
     2390            False
     2391        """
     2392        return len(self.initial_states()) > 0
     2393
     2394
     2395    def has_final_state(self, state):
     2396        """
     2397        Returns whether ``state`` is one of the final states of the
     2398        finite state machine.
     2399
     2400        INPUT:
     2401
     2402        - ``state`` can be a :class:`FSMState` or a label.
     2403
     2404        OUTPUT:
     2405
     2406        True or False.
     2407
     2408        EXAMPLES::
     2409
     2410            sage: FiniteStateMachine(final_states=['A']).has_final_state('A')
     2411            True
     2412        """
     2413        try:
     2414            return self.state(state).is_final
     2415        except LookupError:
     2416            return False
     2417
     2418
     2419    def has_final_states(self):
     2420        """
     2421        Returns whether the finite state machine has a final state.
     2422
     2423        INPUT:
     2424
     2425        Nothing.
     2426
     2427        OUTPUT:
     2428
     2429        True or False.
     2430
     2431        EXAMPLES::
     2432
     2433            sage: FiniteStateMachine().has_final_states()
     2434            False
     2435        """
     2436        return len(self.final_states()) > 0
     2437
     2438
     2439    #*************************************************************************
     2440    # properties
     2441    #*************************************************************************
     2442
     2443
     2444    def is_deterministic(self):
     2445        """
     2446        Returns whether the finite finite state machine is deterministic.
     2447
     2448        INPUT:
     2449
     2450        Nothing.
     2451
     2452        OUTPUT:
     2453
     2454        True or False.
     2455
     2456        A finite state machine is considered to be deterministic if
     2457        each transition has input label of length one and for each
     2458        pair `(q,a)` where `q` is a state and `a` is an element of the
     2459        input alphabet, there is at most one transition from `q` with
     2460        input label `a`.
     2461
     2462        TESTS::
     2463
     2464            sage: fsm = FiniteStateMachine()
     2465            sage: fsm.add_transition( ('A','B',0,[]))
     2466            Transition from State 'A' to State 'B': 0|-
     2467            sage: fsm.is_deterministic()
     2468            True
     2469            sage: fsm.add_transition( ('A','C',0,[]))
     2470            Transition from State 'A' to State 'C': 0|-
     2471            sage: fsm.is_deterministic()
     2472            False
     2473            sage: fsm.add_transition( ('A','B',[0,1],[]))
     2474            Transition from State 'A' to State 'B': 0,1|-
     2475            sage: fsm.is_deterministic()
     2476            False
     2477        """
     2478        for state in self.states():
     2479            for transition in state.transitions:
     2480                if len(transition.word_in) != 1:
     2481                    return False
     2482
     2483            transition_classes_by_word_in = itertools.groupby(
     2484                sorted(state.transitions, key=lambda t: t.word_in),
     2485                key=lambda t:t.word_in)
     2486
     2487            for key,transition_class in transition_classes_by_word_in:
     2488                if len(list(transition_class)) > 1:
     2489                    return False
     2490        return True
     2491
     2492
     2493    def is_connected(self):
     2494        """
     2495        TESTS::
     2496
     2497            sage: FiniteStateMachine().is_connected()
     2498            Traceback (most recent call last):
     2499            ...
     2500            NotImplementedError
     2501        """
     2502        raise NotImplementedError
     2503
     2504
     2505    #*************************************************************************
     2506    # let the finite state machine work
     2507    #*************************************************************************
     2508
     2509
     2510    def process(self, *args, **kwargs):
     2511        """
     2512        Returns whether the finite state machine accepts the input, the state
     2513        where the computation stops and which output is generated.
     2514
     2515        INPUT:
     2516
     2517        - ``input_tape`` -- The input tape can be a list with entries from
     2518          the input alphabet.
     2519       
     2520        - ``initial_state`` -- (default: ``None``) The state in which
     2521          to start. If this parameter is ``None`` and there is only
     2522          one initial state in the machine, then this state is taken.
     2523
     2524        OUTPUT:
     2525
     2526        A triple, where
     2527
     2528        - the first entry is true if the input string is accepted,
     2529
     2530        - the second gives the reached state after processing the
     2531          input tape, and
     2532
     2533        - the third gives a list of the output labels used during
     2534          processing (in the case the finite state machine runs as
     2535          transducer).
     2536
     2537        Note that in the case the finite state machine is not
     2538        deterministic, one possible path is gone. This means, that in
     2539        this case the output can be wrong. Use
     2540        :meth:`.determinisation` to get a deterministic finite state
     2541        machine and try again.
     2542
     2543        EXAMPLES::
     2544
     2545            sage: from sage.combinat.finite_state_machine import FSMState
     2546            sage: A = FSMState('A', is_initial = True, is_final = True)
     2547            sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]})
     2548            sage: binary_inverter.process([0, 1, 0, 0, 1, 1])
     2549            (True, State 'A', [1, 0, 1, 1, 0, 0])
     2550
     2551        Alternatively, we can invoke this function by::
     2552
     2553            sage: binary_inverter([0, 1, 0, 0, 1, 1])
     2554            (True, State 'A', [1, 0, 1, 1, 0, 0])
     2555
     2556        ::
     2557
     2558            sage: NAF_ = FSMState('_', is_initial = True, is_final = True)
     2559            sage: NAF1 = FSMState('1', is_final = True)
     2560            sage: NAF = Automaton(
     2561            ....:     {NAF_: [(NAF_, 0), (NAF1, 1)], NAF1: [(NAF_, 0)]})
     2562            sage: [NAF.process(w)[0] for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1],
     2563            ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]]
     2564            [True, True, False, True, False, False]
     2565
     2566        """
     2567        it = self.iter_process(*args, **kwargs)
     2568        for _ in it:
     2569            pass
     2570        return (it.accept_input, it.current_state, it.output_tape)
     2571
     2572
     2573    def iter_process(self, input_tape=None, initial_state=None):
     2574        """
     2575        See `process` for more informations.
     2576
     2577        EXAMPLES::
     2578
     2579            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     2580            ....:     initial_states=['A'], final_states=['A'])
     2581            sage: it = inverter.iter_process(input_tape=[0, 1, 1])
     2582            sage: for _ in it:
     2583            ....:     pass
     2584            sage: it.output_tape
     2585            [1, 0, 0]
     2586        """
     2587        return FSMProcessIterator(self, input_tape, initial_state)
     2588
     2589
     2590    #*************************************************************************
     2591    # change finite state machine (add/remove state/transitions)
     2592    #*************************************************************************
     2593
     2594
     2595    def add_state(self, state):
     2596        """
     2597        Adds a state to the finite state machine and returns the new
     2598        state. If the state already exists, that existing state is
     2599        returned.
     2600
     2601        INPUT:
     2602
     2603        - ``state`` is either an instance of FSMState or, otherwise, a
     2604          label of a state.
     2605
     2606        OUTPUT:
     2607
     2608        The new or existing state.
     2609
     2610        EXAMPLES::
     2611
     2612            sage: from sage.combinat.finite_state_machine import FSMState
     2613            sage: F = FiniteStateMachine()
     2614            sage: A = FSMState('A', is_initial=True)
     2615            sage: F.add_state(A)
     2616            State 'A'
     2617        """
     2618        try:
     2619            return self.state(state)
     2620        except LookupError:
     2621            pass
     2622        # at this point we know that we have a new state
     2623        if is_FSMState(state):
     2624            s = state
     2625        else:
     2626            s = FSMState(state)
     2627        s.transitions = list()
     2628        self._states_.append(s)
     2629        try:
     2630            self._states_dict_[s.label()] = s
     2631        except AttributeError:
     2632            pass
     2633        return s
     2634
     2635
     2636    def add_states(self, states):
     2637        """
     2638        Adds several states. See add_state for more information.
     2639
     2640        INPUT:
     2641
     2642        - ``states`` -- a list of states or iterator over states.
     2643
     2644        OUTPUT:
     2645
     2646        Nothing.
     2647
     2648        EXAMPLES::
     2649
     2650            sage: F = FiniteStateMachine()
     2651            sage: F.add_states(['A', 'B'])
     2652            sage: F.states()
     2653            [State 'A', State 'B']
     2654        """
     2655        for state in states:
     2656            self.add_state(state)
     2657
     2658
     2659    def add_transition(self, *args, **kwargs):
     2660        """
     2661        Adds a transition to the finite state machine and returns the
     2662        new transition. If the transition already exists, that
     2663        existing transition is returned.
     2664
     2665        INPUT:
     2666
     2667        The following forms are all accepted:
     2668
     2669        ::
     2670
     2671            sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
     2672            sage: A = FSMState('A')
     2673            sage: B = FSMState('B')
     2674
     2675            sage: FSM = FiniteStateMachine()
     2676            sage: FSM.add_transition(FSMTransition(A, B, 0, 1))
     2677            Transition from State 'A' to State 'B': 0|1
     2678
     2679            sage: FSM = FiniteStateMachine()
     2680            sage: FSM.add_transition(A, B, 0, 1)
     2681            Transition from State 'A' to State 'B': 0|1
     2682
     2683            sage: FSM = FiniteStateMachine()
     2684            sage: FSM.add_transition(A, B, word_in=0, word_out=1)
     2685            Transition from State 'A' to State 'B': 0|1
     2686
     2687            sage: FSM = FiniteStateMachine()
     2688            sage: FSM.add_transition('A', 'B', {'word_in': 0, 'word_out': 1})
     2689            Transition from State 'A' to State 'B': {'word_in': 0, 'word_out': 1}|-
     2690
     2691            sage: FSM = FiniteStateMachine()
     2692            sage: FSM.add_transition(from_state=A, to_state=B,
     2693            ....:                    word_in=0, word_out=1)
     2694            Transition from State 'A' to State 'B': 0|1
     2695
     2696            sage: FSM = FiniteStateMachine()
     2697            sage: FSM.add_transition({'from_state': A, 'to_state': B,
     2698            ....:                    'word_in': 0, 'word_out': 1})
     2699            Transition from State 'A' to State 'B': 0|1
     2700
     2701            sage: FSM = FiniteStateMachine()
     2702            sage: FSM.add_transition((A, B, 0, 1))
     2703            Transition from State 'A' to State 'B': 0|1
     2704
     2705            sage: FSM = FiniteStateMachine()
     2706            sage: FSM.add_transition([A, B, 0, 1])
     2707            Transition from State 'A' to State 'B': 0|1
     2708
     2709        If the states ``A`` and ``B`` are not instances of FSMState, then
     2710        it is assumed that they are labels of states.
     2711
     2712        OUTPUT:
     2713
     2714        The new or existing transition.
     2715        """
     2716        if len(args) + len(kwargs) == 0:
     2717            return
     2718        if len(args) + len(kwargs) == 1:
     2719            if len(args) == 1:
     2720                d = args[0]
     2721                if is_FSMTransition(d):
     2722                    return self._add_fsm_transition_(d)
     2723            else:
     2724                d = kwargs.itervalues().next()
     2725            if hasattr(d, 'iteritems'):
     2726                args = []
     2727                kwargs = d
     2728            elif hasattr(d, '__iter__'):
     2729                args = d
     2730                kwargs = {}
     2731            else:
     2732                raise TypeError, "Cannot decide what to do with input."
     2733
     2734        data = dict(zip(
     2735                ('from_state', 'to_state', 'word_in', 'word_out', 'hook'),
     2736                args))
     2737        data.update(kwargs)
     2738
     2739        data['from_state'] = self.add_state(data['from_state'])
     2740        data['to_state'] = self.add_state(data['to_state'])
     2741
     2742        return self._add_fsm_transition_(FSMTransition(**data))
     2743
     2744
     2745    def _add_fsm_transition_(self, t):
     2746        """
     2747        Adds a transition.
     2748       
     2749        INPUT:
     2750
     2751        - ``t`` -- an instance of ``FSMTransition``.
     2752
     2753        OUTPUT:
     2754
     2755        The new transition.
     2756
     2757        TESTS::
     2758
     2759            sage: from sage.combinat.finite_state_machine import FSMTransition
     2760            sage: F = FiniteStateMachine()
     2761            sage: F._add_fsm_transition_(FSMTransition('A', 'B'))
     2762            Transition from State 'A' to State 'B': -|-
     2763        """
     2764        try:
     2765            return self.transition(t)
     2766        except LookupError:
     2767            pass
     2768        from_state = self.add_state(t.from_state)
     2769        self.add_state(t.to_state)
     2770        from_state.transitions.append(t)
     2771        return t
     2772
     2773
     2774    def add_from_transition_function(self, function, initial_states=None,
     2775                                     ignore_existing_states=False):
     2776        """
     2777        Constructs a finite state machine from a transition function.
     2778
     2779        INPUT:
     2780       
     2781        - ``function`` may return a tuple (new_state, output_word) or a
     2782          list of such tuples.
     2783
     2784        - ``initial_states`` -- If no initial states are given, the
     2785          already existing initial states of self are taken.
     2786
     2787        - If ``ignore_existing_states`` is True, then already existing
     2788          states in self (e.g. already given final states) won't be
     2789          processed.
     2790
     2791        OUTPUT:
     2792
     2793        Nothing.
     2794
     2795        EXAMPLES::
     2796
     2797            sage: F = FiniteStateMachine(initial_states=['A'],
     2798            ....:                        input_alphabet=[0, 1])
     2799            sage: def f(state, input):
     2800            ....:     return [('A', input), ('B', 1-input)]
     2801            sage: F.add_from_transition_function(f)
     2802            sage: F.transitions()
     2803            [Transition from State 'A' to State 'A': 0|0,
     2804            Transition from State 'A' to State 'B': 0|1,
     2805            Transition from State 'A' to State 'A': 1|1,
     2806            Transition from State 'A' to State 'B': 1|0,
     2807            Transition from State 'B' to State 'A': 0|0,
     2808            Transition from State 'B' to State 'B': 0|1,
     2809            Transition from State 'B' to State 'A': 1|1,
     2810            Transition from State 'B' to State 'B': 1|0]
     2811
     2812        TEST::
     2813
     2814            sage: F = FiniteStateMachine(initial_states=['A'])
     2815            sage: def f(state, input):
     2816            ....:     return [('A', input), ('B', 1-input)]
     2817            sage: F.add_from_transition_function(f)
     2818            Traceback (most recent call last):
     2819            ...
     2820            ValueError: No input alphabet is given.
     2821            Try calling determine_alphabets().
     2822        """
     2823        if self.input_alphabet is None:
     2824            raise ValueError, ("No input alphabet is given. "
     2825                               "Try calling determine_alphabets().")
     2826
     2827        if initial_states is None:
     2828            not_done = self.initial_states()
     2829        else:
     2830            not_done = copy(initial_states)
     2831        if len(not_done) == 0:
     2832            raise ValueError, "No state is initial."
     2833        if ignore_existing_states:
     2834            ignore_done = self.states()
     2835            for s in not_done:
     2836                try:
     2837                    ignore_done.remove(s)
     2838                except ValueError:
     2839                    pass
     2840        else:
     2841            ignore_done = []
     2842        while len(not_done) > 0:
     2843            s = not_done.pop(0)
     2844            for letter in self.input_alphabet:
     2845                try:
     2846                    return_value = function(s.label(), letter)
     2847                except LookupError:
     2848                    continue
     2849                if not hasattr(return_value, "pop"):
     2850                    return_value = [return_value]
     2851                try:
     2852                    for (st_label, word) in return_value:
     2853                        if not self.has_state(st_label):
     2854                            not_done.append(self.add_state(st_label))
     2855                        elif len(ignore_done) > 0:
     2856                            u = self.state(st_label)
     2857                            if u in ignore_done:
     2858                                not_done.append(u)
     2859                                ignore_done.remove(u)
     2860                        self.add_transition(s, st_label,
     2861                                            word_in=letter, word_out=word)
     2862                except TypeError:
     2863                    raise 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))
     2864
     2865
     2866    def add_transitions_from_function(self, function, labels_as_input=True):
     2867        """
     2868        Adds a transition if ``function(state, state)`` says that there is one.
     2869
     2870        INPUT:
     2871
     2872        - ``function`` -- a transition function.
     2873
     2874        - ``label_as_input`` -- (default: ``True``)
     2875
     2876        OUTPUT:
     2877
     2878        Nothing.
     2879
     2880        EXAMPLES::
     2881
     2882            sage: F = FiniteStateMachine()
     2883            sage: F.add_states(['A', 'B', 'C'])
     2884            sage: def f(state1, state2):
     2885            ....:     if state1 == 'C':
     2886            ....:         return None
     2887            ....:     return [0, 1]
     2888            sage: F.add_transitions_from_function(f)
     2889            sage: len(F.transitions())
     2890            6
     2891        """
     2892        for s_from in self.iter_states():
     2893            for s_to in self.iter_states():
     2894                if labels_as_input:
     2895                    t = function(s_from.label(), s_to.label())
     2896                else:
     2897                    t = function(s_from, s_to)
     2898                if hasattr(t, '__getitem__'):
     2899                    label_in = t[0]
     2900                    try:
     2901                        label_out = t[1]
     2902                    except LookupError:
     2903                        label_out = None
     2904                    self.add_transition(s_from, s_to, label_in, label_out)
     2905
     2906
     2907    def delete_transition(self, t):
     2908        """
     2909        Deletes a transition by removing it from the list of transitions of
     2910        the state, where the transition starts.
     2911
     2912        INPUT:
     2913
     2914        - ``t`` -- a transition.
     2915
     2916        OUTPUT:
     2917
     2918        Nothing.
     2919
     2920        EXAMPLES::
     2921
     2922            sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)])
     2923            sage: F.delete_transition(('A', 'B', 0))
     2924            sage: F.transitions()
     2925            [Transition from State 'B' to State 'A': 1|-]
     2926        """
     2927        transition = self.transition(t)
     2928        transition.from_state.transitions.remove(transition)
     2929
     2930
     2931    def delete_state(self, s):
     2932        """
     2933        Deletes a state and all transitions coming or going to this state.
     2934       
     2935        INPUT:
     2936
     2937        - ``s`` -- s has to be a label of a state or :class:`FSMState`.
     2938
     2939        OUTPUT:
     2940
     2941        Nothing.
     2942
     2943        EXAMPLES::
     2944
     2945            sage: from sage.combinat.finite_state_machine import FSMTransition
     2946            sage: t1 = FSMTransition('A', 'B', 0)
     2947            sage: t2 = FSMTransition('B', 'B', 1)
     2948            sage: F = FiniteStateMachine([t1, t2])
     2949            sage: F.delete_state('A')
     2950            sage: F. transitions()
     2951            [Transition from State 'B' to State 'B': 1|-]
     2952        """
     2953        state = self.state(s)
     2954        for transition in self.transitions():
     2955            if transition.to_state == state:
     2956                self.delete_transition(transition)
     2957        self._states_.remove(state)
     2958
     2959
     2960    def remove_epsilon_transitions(self):
     2961        """
     2962        TESTS::
     2963
     2964            sage: FiniteStateMachine().remove_epsilon_transitions()
     2965            Traceback (most recent call last):
     2966            ...
     2967            NotImplementedError
     2968        """
     2969        raise NotImplementedError
     2970
     2971
     2972    def accessible_components(self):
     2973        """
     2974        Returns a new finite state machine with the accessible states
     2975        of self and all transitions between those states.
     2976
     2977        INPUT:
     2978
     2979        Nothing.
     2980
     2981        OUTPUT:
     2982
     2983        A finite state machine with the accessible states of self and
     2984        all transitions between those states.
     2985       
     2986        A state is accessible if there is a directed path from an
     2987        initial state to the state. If self has no initial states then
     2988        a copy of the finite state machine self is returned.
     2989
     2990        EXAMPLES::
     2991
     2992            sage: F = Automaton([(0, 0, 1), (0, 0, 1), (1, 1, 0), (1, 0, 1)],
     2993            ....:               initial_states=[0])
     2994            sage: F.accessible_components()
     2995            finite state machine with 1 states
     2996        """
     2997        if len(self.initial_states()) == 0:
     2998            return deepcopy(self)
     2999
     3000        memo = {}
     3001        def accessible(sf, read):
     3002            trans = filter(lambda x: x.word_in[0] == read,
     3003                           self.transitions(sf))
     3004            return map(lambda x: (deepcopy(x.to_state, memo), x.word_out),
     3005                       trans)
     3006
     3007        return FiniteStateMachine(
     3008            accessible,
     3009            initial_states=map(lambda x: deepcopy(x, memo),
     3010                               self.initial_states()),
     3011            final_states=map(lambda x: deepcopy(x, memo),
     3012                             self.final_states()),
     3013            input_alphabet=deepcopy(self.input_alphabet, memo))
     3014
     3015
     3016    # *************************************************************************
     3017    # creating new finite state machines
     3018    # *************************************************************************
     3019
     3020
     3021    def disjoint_union(self, other):
     3022        """
     3023        TESTS::
     3024
     3025            sage: F = FiniteStateMachine([('A', 'A')])
     3026            sage: FiniteStateMachine().disjoint_union(F)
     3027            Traceback (most recent call last):
     3028            ...
     3029            NotImplementedError
     3030        """
     3031        raise NotImplementedError
     3032
     3033
     3034    def concatenation(self, other):
     3035        """
     3036        TESTS::
     3037
     3038            sage: F = FiniteStateMachine([('A', 'A')])
     3039            sage: FiniteStateMachine().concatenation(F)
     3040            Traceback (most recent call last):
     3041            ...
     3042            NotImplementedError
     3043        """
     3044        raise NotImplementedError
     3045
     3046
     3047    def Kleene_closure(self):
     3048        """
     3049        TESTS::
     3050
     3051            sage: FiniteStateMachine().Kleene_closure()
     3052            Traceback (most recent call last):
     3053            ...
     3054            NotImplementedError
     3055        """
     3056        raise NotImplementedError
     3057
     3058
     3059    def intersection(self, other):
     3060        """
     3061        TESTS::
     3062
     3063            sage: F = FiniteStateMachine([('A', 'A')])
     3064            sage: FiniteStateMachine().intersection(F)
     3065            Traceback (most recent call last):
     3066            ...
     3067            NotImplementedError
     3068        """
     3069        raise NotImplementedError
     3070
     3071
     3072    def product_FiniteStateMachine(self, other, function,
     3073                                   new_input_alphabet=None,
     3074                                   only_accessible_components=True):
     3075        """
     3076        Returns a new finite state machine whose states are
     3077        pairs of states of the original finite state machines.
     3078
     3079        INPUT:
     3080
     3081        - ``other`` -- a finite state machine.
     3082
     3083        - ``function`` has to accept two transitions from `A` to `B`
     3084          and `C` to `D` and returns a pair ``(word_in, word_out)``
     3085          which is the label of the transition `(A, C)` to `(B,
     3086          D)`. If there is no transition from `(A, C)` to `(B, D)`,
     3087          then ``function`` should raise a ``LookupError``.
     3088
     3089        - ``new_input_alphabet`` -- the new input alphabet as a list.
     3090
     3091        - ``only_accessible_components``
     3092
     3093        OUTPUT:
     3094
     3095        A finite state machine whose states are pairs of states of the
     3096        original finite state machines.
     3097
     3098        The labels of the transitions are defined by ``function``.     
     3099
     3100        EXAMPLES::
     3101
     3102            sage: F = Automaton([('A', 'B', 1), ('A', 'A', 0), ('B', 'A', 2)],
     3103            ....:               initial_states=['A'], final_states=['B'],
     3104            ....:               determine_alphabets=True)
     3105            sage: G = Automaton([(1, 1, 1)], initial_states=[1], final_states=[1])
     3106            sage: def addition(transition1, transition2):
     3107            ....:     return (transition1.word_in[0] + transition2.word_in[0],
     3108            ....:             None)
     3109            sage: H = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3])
     3110            sage: H.transitions()
     3111            [Transition from State ('A', 1) to State ('A', 1): 1|-,
     3112             Transition from State ('A', 1) to State ('B', 1): 2|-,
     3113             Transition from State ('B', 1) to State ('A', 1): 3|-]
     3114        """
     3115        if new_input_alphabet is None:
     3116            new_input_alphabet = self.input_alphabet
     3117
     3118        result = FiniteStateMachine(input_alphabet=new_input_alphabet)
     3119
     3120        for transition1 in self.transitions():
     3121            for transition2 in other.transitions():
     3122                try:
     3123                    word = function(transition1, transition2)
     3124                except LookupError:
     3125                    continue
     3126                result.add_transition((transition1.from_state.label(),
     3127                                       transition2.from_state.label()),
     3128                                      (transition1.to_state.label(),
     3129                                       transition2.to_state.label()),
     3130                                      word[0], word[1])
     3131
     3132        for state1 in self.initial_states():
     3133            for state2 in other.initial_states():
     3134                result.state((state1.label(), state2.label())).is_initial = True
     3135
     3136        for state1 in self.final_states():
     3137            for state2 in other.final_states():
     3138                result.state((state1.label(), state2.label())).is_final = True
     3139
     3140        if only_accessible_components:
     3141            return result.accessible_components()
     3142        else:
     3143            return result
     3144
     3145
     3146    def cartesian_product(self, other, only_accessible_components=True):
     3147        """
     3148        Returns a new finite state machine, which is the cartesian
     3149        product of self and other.
     3150
     3151        INPUT:
     3152
     3153        - ``other`` -- a finite state machine.
     3154
     3155        - ``only_accessible_components``
     3156
     3157        OUTPUT:
     3158
     3159        A new finite state machine, which is the cartesian product of
     3160        self and other.
     3161
     3162        The set of states of the new automaton is the cartesian
     3163        product of the set of states of both given automata. There is
     3164        a transition `((A, B), (C, D), a)` in the new automaton if
     3165        there are transitions `(A, C, a)` and `(B, C, a)` in the old
     3166        automata.
     3167
     3168        EXAMPLES::
     3169
     3170            sage: aut1 = Automaton([('1', '2', 1), ('2', '2', 1), ('2', '2', 0)],
     3171            ....:                initial_states=['1'], final_states=['2'],
     3172            ....:                determine_alphabets=True)
     3173            sage: aut2 = Automaton([('A', 'A', 1), ('A', 'B', 1),
     3174            ....:                 ('B', 'B', 0), ('B', 'A', 0)],
     3175            ....:                initial_states=['A'], final_states=['B'],
     3176            ....:                determine_alphabets=True)
     3177            sage: res = aut1.cartesian_product(aut2)
     3178            sage: res.transitions()
     3179            [Transition from State ('1', 'A') to State ('2', 'A'): 1|-,
     3180             Transition from State ('1', 'A') to State ('2', 'B'): 1|-,
     3181             Transition from State ('2', 'B') to State ('2', 'B'): 0|-,
     3182             Transition from State ('2', 'B') to State ('2', 'A'): 0|-,
     3183             Transition from State ('2', 'A') to State ('2', 'A'): 1|-,
     3184             Transition from State ('2', 'A') to State ('2', 'B'): 1|-]
     3185        """
     3186        def function(transition1, transition2):
     3187            if transition1.word_in == transition2.word_in \
     3188                    and transition1.word_out == transition2.word_out:
     3189                return (transition1.word_in, transition1.word_out)
     3190            else:
     3191                raise LookupError
     3192
     3193        return self.product_FiniteStateMachine(
     3194            other, function,
     3195            only_accessible_components = only_accessible_components)
     3196
     3197
     3198    def composition(self, other, algorithm=None,
     3199                    only_accessible_components=True):
     3200        """
     3201        Returns a new transducer which is the composition of self and
     3202        other.
     3203
     3204        INPUT:
     3205
     3206        - ``algorithm`` -- can be one of the following
     3207
     3208          - ``direct`` -- The composition is calculated directly.
     3209
     3210            There can be arbitrarily many initial and final states,
     3211            but the input and output labels must have length 1.
     3212
     3213            WARNING: The output of other is fed into self.
     3214
     3215          - ``explorative`` -- An explorative algorithm is used.
     3216
     3217            At least the following restrictions apply, but are not
     3218            checked:
     3219            - both self and other have exactly one initial state
     3220            - all input labels of transitions have length exactly 1
     3221
     3222            The input alphabet of self has to be specified.
     3223
     3224            This is a very limited implementation of composition.
     3225            WARNING: The output of ``other`` is fed into ``self``.
     3226
     3227          If algorithm is ``None``, then the algorithm is chosen
     3228          automatically (at the moment always ``direct``).
     3229
     3230        OUTPUT:
     3231
     3232        A new transducer.
     3233
     3234        EXAMPLES::
     3235
     3236            sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)],
     3237            ....:                initial_states=['A', 'B'], final_states=['B'],
     3238            ....:                determine_alphabets=True)
     3239            sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1),
     3240            ....:                 (2, 2, 1, 1), (2, 2, 0, 0)],
     3241            ....:                initial_states=[1], final_states=[2],
     3242            ....:                determine_alphabets=True)
     3243            sage: Hd = F.composition(G, algorithm='direct')
     3244            sage: Hd.initial_states()
     3245            [State (1, 'B'), State (1, 'A')]
     3246            sage: Hd.transitions()
     3247            [Transition from State (1, 'B') to State (1, 'A'): 1|1,
     3248             Transition from State (1, 'A') to State (2, 'B'): 0|0,
     3249             Transition from State (2, 'B') to State (2, 'A'): 0|1,
     3250             Transition from State (2, 'A') to State (2, 'B'): 1|0]
     3251
     3252        ::
     3253
     3254            sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1),
     3255            ....:                 ('B', 'B', 0, 0)],
     3256            ....:                initial_states=['A'], final_states=['B'])
     3257            sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),
     3258            ....:                 (2, 2, 0, 1), (2, 1, 1, 1)],
     3259            ....:                initial_states=[1], final_states=[1])
     3260            sage: He = G.composition(F, algorithm='explorative')
     3261            sage: He.transitions()
     3262            [Transition from State ('A', 1) to State (None, None): 0|-,
     3263             Transition from State ('A', 1) to State ('B', 2): 1|0,1,
     3264             Transition from State (None, None) to State (None, None): 0|-,
     3265             Transition from State (None, None) to State (None, None): 1|-,
     3266             Transition from State ('B', 2) to State ('B', 2): 0|1,
     3267             Transition from State ('B', 2) to State ('B', 1): 1|1,
     3268             Transition from State ('B', 1) to State ('B', 1): 0|0,
     3269             Transition from State ('B', 1) to State ('B', 2): 1|0]
     3270
     3271        TESTS:
     3272
     3273        Due to the limitations of the two algorithms the following
     3274        (examples from above, but different algorithm used) does not
     3275        give a full answer or does not work
     3276
     3277        ::
     3278
     3279            sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)],
     3280            ....:                initial_states=['A', 'B'], final_states=['B'],
     3281            ....:                determine_alphabets=True)
     3282            sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1),
     3283            ....:                 (2, 2, 1, 1), (2, 2, 0, 0)],
     3284            ....:                initial_states=[1], final_states=[2],
     3285            ....:                determine_alphabets=True)
     3286            sage: He = F.composition(G, algorithm='explorative')
     3287            sage: He.initial_states()
     3288            [State (1, 'A')]
     3289            sage: He.transitions()
     3290            [Transition from State (1, 'A') to State (2, 'B'): 0|0,
     3291             Transition from State (1, 'A') to State (None, None): 1|-,
     3292             Transition from State (2, 'B') to State (2, 'A'): 0|1,
     3293             Transition from State (2, 'B') to State (None, None): 1|-,
     3294             Transition from State (None, None) to State (None, None): 0|-,
     3295             Transition from State (None, None) to State (None, None): 1|-,
     3296             Transition from State (2, 'A') to State (None, None): 0|-,
     3297             Transition from State (2, 'A') to State (2, 'B'): 1|0]
     3298
     3299        ::
     3300
     3301            sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1),
     3302            ....:                 ('B', 'B', 0, 0)],
     3303            ....:                initial_states=['A'], final_states=['B'])
     3304            sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),
     3305            ....:                 (2, 2, 0, 1), (2, 1, 1, 1)],
     3306            ....:                initial_states=[1], final_states=[1])
     3307            sage: Hd = G.composition(F, algorithm='direct')
     3308            Traceback (most recent call last):
     3309            ...
     3310            LookupError: No state with label ('A', 1) found.
     3311        """
     3312        if algorithm == None:
     3313            algorithm = 'direct'
     3314        if algorithm == 'direct':
     3315            return self._composition_direct_(other, only_accessible_components)
     3316        elif algorithm == 'explorative':
     3317            return self._composition_explorative_(other)
     3318        else:
     3319            raise ValueError, "Unknown algorithm %s." % (algorithm,)
     3320
     3321
     3322    def _composition_direct_(self, other, only_accessible_components=True):
     3323        """
     3324        See :meth:`.composition` for details.
     3325
     3326        TESTS::
     3327
     3328            sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)],
     3329            ....:                initial_states=['A', 'B'], final_states=['B'],
     3330            ....:                determine_alphabets=True)
     3331            sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1),
     3332            ....:                 (2, 2, 1, 1), (2, 2, 0, 0)],
     3333            ....:                initial_states=[1], final_states=[2],
     3334            ....:                determine_alphabets=True)
     3335            sage: Hd = F._composition_direct_(G)
     3336            sage: Hd.initial_states()
     3337            [State (1, 'B'), State (1, 'A')]
     3338            sage: Hd.transitions()
     3339            [Transition from State (1, 'B') to State (1, 'A'): 1|1,
     3340             Transition from State (1, 'A') to State (2, 'B'): 0|0,
     3341             Transition from State (2, 'B') to State (2, 'A'): 0|1,
     3342             Transition from State (2, 'A') to State (2, 'B'): 1|0]
     3343
     3344        """
     3345        def function(transition1, transition2):
     3346            if transition1.word_out == transition2.word_in:
     3347                return (transition1.word_in, transition2.word_out)
     3348            else:
     3349                raise LookupError
     3350
     3351        return other.product_FiniteStateMachine(
     3352            self, function,
     3353            only_accessible_components=only_accessible_components)
     3354
     3355
     3356    def _composition_explorative_(self, other):
     3357        """
     3358        See :meth:`.composition` for details.
     3359
     3360        TESTS::
     3361
     3362            sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1),
     3363            ....:                 ('B', 'B', 0, 0)],
     3364            ....:                initial_states=['A'], final_states=['B'])
     3365            sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0),
     3366            ....:                 (2, 2, 0, 1), (2, 1, 1, 1)],
     3367            ....:                initial_states=[1], final_states=[1])
     3368            sage: He = G._composition_explorative_(F)
     3369            sage: He.transitions()
     3370            [Transition from State ('A', 1) to State (None, None): 0|-,
     3371             Transition from State ('A', 1) to State ('B', 2): 1|0,1,
     3372             Transition from State (None, None) to State (None, None): 0|-,
     3373             Transition from State (None, None) to State (None, None): 1|-,
     3374             Transition from State ('B', 2) to State ('B', 2): 0|1,
     3375             Transition from State ('B', 2) to State ('B', 1): 1|1,
     3376             Transition from State ('B', 1) to State ('B', 1): 0|0,
     3377             Transition from State ('B', 1) to State ('B', 2): 1|0]
     3378
     3379        TODO:
     3380
     3381        The explorative algorithm should be re-implemented using the
     3382        process iterators of both finite state machines.
     3383        """
     3384        def composition_transition(state, input):
     3385            (state1, state2) = state
     3386            if state1 is None and state2 is None:
     3387                return ((None, None), [])
     3388            transition1 = None
     3389            for transition in other.iter_transitions(state1):
     3390                if transition.word_in == [input]:
     3391                    transition1 = transition
     3392                    break
     3393            if transition1 is None:
     3394                return((None, None), [])
     3395            new_state1 = transition1.to_state.label()
     3396            new_state2 = state2
     3397            output = []
     3398            for o in transition1.word_out:
     3399                transition2 = None
     3400                for transition in self.iter_transitions(new_state2):
     3401                    if transition.word_in == [o]:
     3402                        transition2 = transition
     3403                        break
     3404                if transition2 is None:
     3405                    return((None, None), [])
     3406                new_state2 = transition2.to_state.label()
     3407                output += transition2.word_out
     3408            return ((new_state1, new_state2), output)
     3409
     3410        F = FiniteStateMachine(input_alphabet=other.input_alphabet,
     3411                               data=composition_transition,
     3412                               initial_states=[(other.initial_states()[0].label(), self.initial_states()[0].label())])
     3413
     3414        for state1 in other.final_states():
     3415            for state2 in self.final_states():
     3416                F.state((state1.label(), state2.label())).is_final = True
     3417        return F
     3418
     3419
     3420    def input_projection(self):
     3421        """
     3422        Returns an automaton where the output of each transition of
     3423        self is deleted.
     3424       
     3425        INPUT:
     3426
     3427        Nothing
     3428
     3429        OUTPUT:
     3430
     3431        An automaton.
     3432
     3433        EXAMPLES::
     3434
     3435            sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1),
     3436            ....:                         ('B', 'B', 1, 0)])
     3437            sage: G = F.input_projection()
     3438            sage: G.transitions()
     3439            [Transition from State 'A' to State 'B': 0|-,
     3440             Transition from State 'A' to State 'A': 1|-,
     3441             Transition from State 'B' to State 'B': 1|-]
     3442        """
     3443        return self.projection(what='input')
     3444
     3445
     3446    def output_projection(self):
     3447        """
     3448        Returns a automaton where the input of each transition of self
     3449        is deleted and the new input is the original output.
     3450       
     3451        INPUT:
     3452
     3453        Nothing
     3454
     3455        OUTPUT:
     3456
     3457        An automaton.
     3458
     3459        EXAMPLES::
     3460
     3461            sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1),
     3462            ....:                         ('B', 'B', 1, 0)])
     3463            sage: G = F.output_projection()
     3464            sage: G.transitions()
     3465            [Transition from State 'A' to State 'B': 1|-,
     3466             Transition from State 'A' to State 'A': 1|-,
     3467             Transition from State 'B' to State 'B': 0|-]
     3468        """
     3469        return self.projection(what='output')
     3470
     3471
     3472    def projection(self, what='input'):
     3473        """
     3474        Returns an Automaton which transition labels are the projection
     3475        of the transition labels of the input.
     3476       
     3477        INPUT:
     3478
     3479        - ``what`` -- (default: ``input``) either ``input`` or ``output``.
     3480
     3481        OUTPUT:
     3482
     3483        An automaton.
     3484
     3485        EXAMPLES::
     3486
     3487            sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1),
     3488            ....:                         ('B', 'B', 1, 0)])
     3489            sage: G = F.projection(what='output')
     3490            sage: G.transitions()
     3491            [Transition from State 'A' to State 'B': 1|-,
     3492             Transition from State 'A' to State 'A': 1|-,
     3493             Transition from State 'B' to State 'B': 0|-]
     3494        """
     3495        new = Automaton()
     3496
     3497        state_mapping = {}
     3498        for state in self.iter_states():
     3499            state_mapping[state] = new.add_state(deepcopy(state))
     3500        for transition in self.iter_transitions():
     3501            if what == 'input':
     3502                new_word_in = transition.word_in
     3503            elif what == 'output':
     3504                new_word_in = transition.word_out
     3505            else:
     3506                raise NotImplementedError
     3507            new.add_transition((state_mapping[transition.from_state],
     3508                                state_mapping[transition.to_state],
     3509                                new_word_in, None))
     3510        if what == 'input':
     3511            new.input_alphabet = self.input_alphabet
     3512        elif what == 'output':
     3513            new.input_alphabet = self.output_alphabet
     3514        else:
     3515            raise NotImplementedError
     3516        return new
     3517
     3518
     3519    def determinisation(self):
     3520        """
     3521        Returns a deterministic automaton which accepts the same input
     3522        words as the original one.
     3523
     3524        INPUT:
     3525
     3526        Nothing.
     3527
     3528        OUTPUT:
     3529
     3530        A new automaton, which is deterministic.
     3531
     3532        The input alphabet must be specified. It is restricted to nice
     3533        cases: only ``mode=automaton`` is considered, input words have to
     3534        have length at most `1`.
     3535
     3536        EXAMPLES::
     3537
     3538            sage: aut = Automaton([('A', 'A', 0), ('A', 'B', 1), ('B', 'B', 1)],
     3539            ....:                 initial_states=['A'], final_states=['B'])
     3540            sage: aut.determinisation().transitions()
     3541            [Transition from State frozenset(['A'])
     3542                          to State frozenset(['A']): 0|-,
     3543            Transition from State frozenset(['A'])
     3544                          to State frozenset(['B']): 1|-,
     3545             Transition from State frozenset(['B'])
     3546                          to State frozenset([]): 0|-,
     3547             Transition from State frozenset(['B'])
     3548                          to State frozenset(['B']): 1|-,
     3549             Transition from State frozenset([])
     3550                          to State frozenset([]): 0|-,
     3551             Transition from State frozenset([])
     3552                          to State frozenset([]): 1|-]
     3553
     3554        ::
     3555
     3556            sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1),
     3557            ....:                ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)],
     3558            ....:               initial_states=['A'], final_states=['C'])
     3559            sage: A.determinisation().states()
     3560            [State frozenset(['A']), State frozenset(['A', 'B']),
     3561            State frozenset(['A', 'C']), State frozenset(['A', 'C', 'B'])]
     3562
     3563        TESTS:
     3564
     3565        This is from #15078, comment 13.
     3566
     3567        ::
     3568
     3569            sage: D = {'A': [('A', 'a'), ('B', 'a'), ('A', 'b')],
     3570            ....:      'C': [], 'B': [('C', 'b')]}
     3571            sage: auto = Automaton(D, initial_states=['A'], final_states=['C'])
     3572            sage: auto.is_deterministic()
     3573            False
     3574            sage: auto.process(list('aaab'))
     3575            (False, State 'A', [])
     3576            sage: auto.states()
     3577            [State 'A', State 'C', State 'B']
     3578            sage: auto.determinisation()
     3579            finite state machine with 3 states
     3580        """
     3581        assert self.mode == 'automaton'
     3582        for transition in self.transitions():
     3583            assert len(transition.word_in) <= 1, "%s has input label of length > 1, which we cannot handle" % (transition,)
     3584
     3585        epsilon_successors = {}
     3586        direct_epsilon_successors = {}
     3587        for state in self.states():
     3588            direct_epsilon_successors[state.label()] = set(map(lambda t:t.to_state.label(), filter(lambda transition: len(transition.word_in) == 0, self.transitions(state))))
     3589            epsilon_successors[state.label()] = set([state.label()])
     3590
     3591        old_count_epsilon_successors = 0
     3592        count_epsilon_successors = len(epsilon_successors)
     3593
     3594        while old_count_epsilon_successors < count_epsilon_successors:
     3595            old_count_epsilon_successors = count_epsilon_successors
     3596            count_epsilon_successors = 0
     3597            for state in self.states():
     3598                for direct_successor in direct_epsilon_successors[state.label()]:
     3599                    epsilon_successors[state.label()] = epsilon_successors[state.label()].union(epsilon_successors[direct_successor])
     3600                count_epsilon_successors += len(epsilon_successors[state.label()])
     3601
     3602
     3603        def set_transition(states, letter):
     3604            result = set()
     3605            for state in states:
     3606                for transition in self.transitions(state):
     3607                    if transition.word_in == [letter]:
     3608                        result.add(transition.to_state.label())
     3609            result = result.union(*map(lambda s:epsilon_successors[s], result))
     3610            return (frozenset(result), [])
     3611
     3612        result = Automaton(input_alphabet=self.input_alphabet,
     3613                           initial_states=[ frozenset([state.label() for state in self.initial_states()])],
     3614                          data=set_transition )
     3615
     3616        for state in result.states():
     3617            if state.label().intersection(map(lambda s: s.label(), self.final_states())):
     3618                state.is_final = True
     3619
     3620        return result
     3621
     3622
     3623    def minimization(self, algorithm=None):
     3624        """
     3625        Returns the minimization of the input automaton as a new automaton.
     3626
     3627        INPUT:
     3628
     3629        - ``algorithm`` -- Either Moore's algorithm is used (default
     3630          or ``algorithm='Moore'``), or Brzozowski's algorithm when
     3631          ``algorithm='Brzozowski'``.
     3632
     3633        OUTPUT:
     3634
     3635        A new automaton.
     3636
     3637        The resulting automaton is deterministic and has a minimal
     3638        number of states.  Only ``mode=automaton`` is accepted. Use
     3639        :meth:`.simplification` for transducers.
     3640
     3641        EXAMPLES::
     3642
     3643            sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1),
     3644            ....:                ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)],
     3645            ....:               initial_states=['A'], final_states=['C'])
     3646            sage: B = A.minimization(algorithm='Brzozowski')
     3647            sage: B.transitions(B.states()[1])
     3648            [Transition from State frozenset([frozenset(['A', 'C', 'B']),
     3649            frozenset(['C', 'B']), frozenset(['A', 'C'])]) to State
     3650            frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),
     3651            frozenset(['A', 'C']), frozenset(['C'])]): 0|-,
     3652            Transition from State frozenset([frozenset(['A', 'C', 'B']),
     3653            frozenset(['C', 'B']), frozenset(['A', 'C'])]) to State
     3654            frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),
     3655            frozenset(['A', 'C'])]): 1|-]
     3656            sage: len(B.states())
     3657            3
     3658            sage: C = A.minimization(algorithm='Brzozowski')
     3659            sage: C.transitions(C.states()[1])
     3660            [Transition from State frozenset([frozenset(['A', 'C', 'B']),
     3661            frozenset(['C', 'B']), frozenset(['A', 'C'])]) to State
     3662            frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),
     3663            frozenset(['A', 'C']), frozenset(['C'])]): 0|-,
     3664            Transition from State frozenset([frozenset(['A', 'C', 'B']),
     3665            frozenset(['C', 'B']), frozenset(['A', 'C'])]) to State
     3666            frozenset([frozenset(['A', 'C', 'B']), frozenset(['C', 'B']),
     3667            frozenset(['A', 'C'])]): 1|-]
     3668            sage: len(C.states())
     3669            3
     3670
     3671        ::
     3672
     3673            sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'),
     3674            ....:                  ('3', '2', 'a'), ('2', '1', 'b'),
     3675            ....:                  ('3', '4', 'a'), ('4', '3', 'b')],
     3676            ....:                  initial_states=['1'], final_states=['1'])
     3677            sage: min = aut.minimization(algorithm='Brzozowski')
     3678            sage: [len(min.states()), len(aut.states())]
     3679            [3, 4]
     3680            sage: min = aut.minimization(algorithm='Moore')
     3681            Traceback (most recent call last):
     3682            ...
     3683            NotImplementedError: Minimization via Moore's Algorithm is only
     3684            implemented for deterministic finite state machines
     3685        """
     3686        if self.mode is None:
     3687            raise NotImplementedError, "The mode attribute must be set."
     3688        if self.mode == 'transducer':
     3689            raise NotImplementedError, "Minimization for Transducer is not implemented. Try the simplification method."
     3690        if not self.mode == 'automaton':
     3691            raise NotImplementedError
     3692
     3693        if algorithm is None or algorithm == "Moore":
     3694            return self._minimization_Moore_()
     3695        elif algorithm == "Brzozowski":
     3696            return self._minimization_Brzozowski_()
     3697        else:
     3698            raise NotImplementedError, "Algorithm '%s' is not implemented. Choose 'Moore' or 'Brzozowski'" % algorithm
     3699
     3700
     3701    def _minimization_Brzozowski_(self):
     3702        """
     3703        Returns a minimized automaton by using Brzozowski's algorithm.
     3704
     3705        See also :meth:`.minimization`.
     3706
     3707        TESTS::
     3708
     3709            sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1),
     3710            ....:                ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)],
     3711            ....:               initial_states=['A'], final_states=['C'])
     3712            sage: B = A._minimization_Brzozowski_()
     3713            sage: len(B.states())
     3714            3
     3715        """
     3716        return self.transposition().determinisation().transposition().determinisation()
     3717
     3718
     3719    def _minimization_Moore_(self):
     3720        """
     3721        Returns a minimized automaton by using Brzozowski's algorithm.
     3722
     3723        See also :meth:`.minimization`.
     3724
     3725        TESTS::
     3726
     3727            sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'),
     3728            ....:                  ('3', '2', 'a'), ('2', '1', 'b'),
     3729            ....:                  ('3', '4', 'a'), ('4', '3', 'b')],
     3730            ....:                  initial_states=['1'], final_states=['1'])
     3731            sage: min = aut._minimization_Moore_()
     3732            Traceback (most recent call last):
     3733            ...
     3734            NotImplementedError: Minimization via Moore's Algorithm is only
     3735            implemented for deterministic finite state machines
     3736        """
     3737        return self.quotient(self.equivalence_classes())
     3738
     3739
     3740    def transposition(self):
     3741        """
     3742        Returns a new finite state machine, where all transitions of the
     3743        input finite state machine are reversed.
     3744
     3745        INPUT:
     3746
     3747        Nothing.
     3748
     3749        OUTPUT:
     3750
     3751        A new finite state machine.
     3752
     3753        EXAMPLES::
     3754
     3755            sage: aut = Automaton([('A', 'A', 0), ('A', 'A', 1), ('A', 'B', 0)],
     3756            ....:                 initial_states=['A'], final_states=['B'])
     3757            sage: aut.transposition().transitions('B')
     3758            [Transition from State 'B' to State 'A': 0|-]
     3759
     3760        ::
     3761
     3762            sage: aut = Automaton([('1', '1', 1), ('1', '2', 0), ('2', '2', 0)],
     3763            ....:                 initial_states=['1'], final_states=['1', '2'])
     3764            sage: aut.transposition().initial_states()
     3765            [State '1', State '2']
     3766        """
     3767        transposition = FiniteStateMachine(input_alphabet=self.input_alphabet,
     3768                                           mode=self.mode)
     3769        for state in self.states():
     3770            transposition.add_state(deepcopy(state))
     3771
     3772        for transition in self.transitions():
     3773            transposition.add_transition(
     3774                transition.to_state.label(), transition.from_state.label(),
     3775                transition.word_in, transition.word_out)
     3776
     3777        for initial in self.initial_states():
     3778            state = transposition.state(initial.label())
     3779            if not initial.is_final:
     3780                state.is_final = True
     3781                state.is_initial = False
     3782
     3783        for final in self.final_states():
     3784            state = transposition.state(final.label())
     3785            if not final.is_initial:
     3786                state.is_final = False
     3787                state.is_initial = True
     3788
     3789        return transposition
     3790
     3791
     3792    def split_transitions(self):
     3793        """
     3794        Returns a new transducer, where all transitions in self with input
     3795        labels consisting of more than one letter
     3796        are replaced by a path of the corresponding length.
     3797
     3798        INPUT:
     3799
     3800        Nothing.
     3801
     3802        OUTPUT:
     3803
     3804        A new transducer.
     3805       
     3806        EXAMPLES::
     3807
     3808            sage: A = Transducer([('A', 'B', [1, 2, 3], 0)],
     3809            ....:                initial_states=['A'], final_states=['B'])
     3810            sage: A.split_transitions().states()
     3811            [State (State 'A', ()), State (State 'B', ()),
     3812             State (State 'A', (1,)), State (State 'A', (1, 2))]
     3813        """
     3814        new = FiniteStateMachine(mode=self.mode,
     3815                                 input_alphabet=self.input_alphabet,
     3816                                 output_alphabet=self.output_alphabet)
     3817        for state in self.states():
     3818            new.add_state(FSMState((state, ()), is_initial=state.is_initial,
     3819                                   is_final=state.is_final))
     3820        for transition in self.transitions():
     3821            for j in range(len(transition.word_in)-1):
     3822                new.add_transition( (
     3823                        (transition.from_state, tuple(transition.word_in[:j])),
     3824                        (transition.from_state, tuple(transition.word_in[:j+1])),
     3825                        transition.word_in[j],
     3826                        []))
     3827            new.add_transition((
     3828                    (transition.from_state, tuple(transition.word_in[:-1])),
     3829                    (transition.to_state, ()),
     3830                    transition.word_in[-1:],
     3831                    transition.word_out))
     3832        return new
     3833
     3834
     3835    # *************************************************************************
     3836    # simplifications
     3837    # *************************************************************************
     3838
     3839
     3840    def prepone_output(self):
     3841        """
     3842        Apply the following to each state `s` (except initial and
     3843        final states) of the finite state machine as often as
     3844        possible:
     3845
     3846        If the letter a is prefix of the output label of all
     3847        transitions from `s`, then remove it from all these labels and
     3848        append it to all output labels of all transitions leading to
     3849        `s`.
     3850
     3851        We assume that the states have no output labels.
     3852
     3853        INPUT:
     3854
     3855        Nothing.
     3856
     3857        OUTPUT:
     3858
     3859        Nothing.
     3860
     3861        EXAMPLES::
     3862
     3863            sage: A = Transducer([('A', 'B', 1, 1), ('B', 'B', 0, 0), ('B', 'C', 1, 0)],
     3864            ....:                initial_states=['A'], final_states=['C'])
     3865            sage: A.prepone_output()
     3866            sage: A.transitions()
     3867            [Transition from State 'A' to State 'B': 1|1,0,
     3868             Transition from State 'B' to State 'B': 0|0,
     3869             Transition from State 'B' to State 'C': 1|-]
     3870
     3871        ::
     3872
     3873            sage: B = Transducer([('A', 'B', 0, 1), ('B', 'C', 1, [1, 1]), ('B', 'C', 0, 1)],
     3874            ....:                initial_states=['A'], final_states=['C'])
     3875            sage: B.prepone_output()
     3876            sage: B.transitions()
     3877            [Transition from State 'A' to State 'B': 0|1,1,
     3878             Transition from State 'B' to State 'C': 1|1,
     3879             Transition from State 'B' to State 'C': 0|-]
     3880        """
     3881        def find_common_output(state):
     3882            if len(filter(lambda transition: len(transition.word_out) == 0, self.transitions(state))) > 0:
     3883                return ()
     3884            first_letters = set(map(lambda transition: transition.word_out[0], self.transitions(state)))
     3885            if len(first_letters) == 1:
     3886                return (first_letters.pop(),)
     3887            return ()
     3888
     3889        changed = 1
     3890        iteration = 0
     3891        while changed > 0:
     3892            changed = 0
     3893            iteration += 1
     3894            for state in self.states():
     3895                if state.is_initial or state.is_final:
     3896                    continue
     3897                assert len(state.word_out) == 0, \
     3898                    "prepone_output assumes that all states have empty output word, but state %s has output word %s" % \
     3899                    (state, state.word_out)
     3900                common_output = find_common_output(state)
     3901                if len(common_output) > 0:
     3902                    changed += 1
     3903                    for transition in self.transitions(state):
     3904                        assert transition.word_out[0] == common_output[0]
     3905                        transition.word_out = transition.word_out[1:]
     3906                    for transition in self.transitions():
     3907                        if transition.to_state == state:
     3908                            transition.word_out.append(common_output[0])
     3909
     3910
     3911    def equivalence_classes(self):
     3912        """
     3913        Returns a list of equivalence classes of states.
     3914
     3915        INPUT:
     3916
     3917        Nothing.
     3918
     3919        OUTPUT:
     3920
     3921        A list of equivalence classes of states.
     3922
     3923        Two states `a` and `b` are equivalent, if and only if for each
     3924        input label word_in the following holds:
     3925
     3926        For paths `p_a` from `a` to `a'` with input label ``word_in``
     3927        and output label ``word_out_a`` and `p_b` from `b` to `b'`
     3928        with input label ``word_in`` and output label ``word_out_b``,
     3929        we have ``word_out_a=word_out_b``, `a'` and `b'` have the same
     3930        output label and are both final or both non-final.
     3931
     3932        The function :meth:`.equivalence_classes` returns a list of
     3933        the equivalence classes to this equivalence relation.
     3934
     3935        This is one step of Moore's minimization algorithm.
     3936
     3937        .. SEEALSO::
     3938
     3939            :meth:`.minimization`
     3940
     3941        EXAMPLES::
     3942
     3943            sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0),
     3944            ....:                           ("B", "C", 0, 0), ("B", "C", 1, 1),
     3945            ....:                           ("C", "D", 0, 1), ("C", "D", 1, 0),
     3946            ....:                           ("D", "A", 0, 0), ("D", "A", 1, 1)])
     3947            sage: fsm.equivalence_classes()
     3948            [[State 'B', State 'D'], [State 'A', State 'C']]
     3949        """
     3950
     3951        # Two states a and b are said to be 0-equivalent, if their output
     3952        # labels agree and if they are both final or non-final.
     3953        #
     3954        # For some j >= 1, two states a and b are said to be j-equivalent, if
     3955        # they are j-1 equivalent and if for each element letter letter_in of
     3956        # the input alphabet and transitions t_a from a with input label
     3957        # letter_in, output label word_out_a to a' and t_b from b with input
     3958        # label letter_in, output label word_out_b to b', we have
     3959        # word_out_a=word_out_b and a' and b' are j-1 equivalent.
     3960
     3961        # If for some j the relations j-1 equivalent and j-equivalent
     3962        # coincide, then they are equal to the equivalence relation described
     3963        # in the docstring.
     3964
     3965        # classes_current holds the equivalence classes of j-equivalence,
     3966        # classes_previous holds the equivalence classes of j-1 equivalence.
     3967
     3968        if not self.is_deterministic():
     3969            raise NotImplementedError, "Minimization via Moore's Algorithm is only implemented for deterministic finite state machines"
     3970
     3971        # initialize with 0-equivalence
     3972        classes_previous = []
     3973        key_0 = lambda state: (state.is_final, state.word_out)
     3974        states_sorted = sorted( self.states(), key=key_0)
     3975        states_grouped = itertools.groupby( states_sorted, key=key_0)
     3976        classes_current = [list(equivalence_class) for
     3977                           (key,equivalence_class) in states_grouped]
     3978
     3979        while len(classes_current) != len(classes_previous):
     3980            class_of = {}
     3981            classes_previous = classes_current
     3982            classes_current = []
     3983
     3984            for k in range(len(classes_previous)):
     3985                for state in classes_previous[k]:
     3986                    class_of[state] = k
     3987
     3988            key_current = lambda state: sorted(
     3989                [(transition.word_in,
     3990                  transition.word_out,
     3991                  class_of[transition.to_state])
     3992                 for transition in state.transitions])
     3993
     3994            for class_previous in classes_previous:
     3995                states_sorted = sorted(class_previous, key=key_current)
     3996                states_grouped = itertools.groupby(states_sorted, key=key_current)
     3997                classes_current.extend([list(equivalence_class) for
     3998                                       (key,equivalence_class) in states_grouped])
     3999
     4000        return classes_current
     4001
     4002
     4003    def quotient(self, classes):
     4004        """
     4005        Constructs the quotient with respect to the equivalence
     4006        classes.
     4007
     4008        INPUT:
     4009
     4010        - ``classes`` is a list of equivalence classes of states.
     4011
     4012        OUTPUT:
     4013       
     4014        A finite state machine.
     4015
     4016        Assume that `c` is a class and `s`, `s'` are states in `c`. If
     4017        there is a transition from `s` to some `t` with input label
     4018        ``word_in`` and output label ``word_out``, then there has to
     4019        be a transition from `s'` to some `t'` with input label
     4020        ``word_in`` and output label ``word_out`` such that `s'` and
     4021        `t'` are states of the same class `c'`. Then there is a
     4022        transition from `c` to `c'` in the quotient with input label
     4023        ``word_in`` and output label ``word_out``.
     4024
     4025        Non-initial states may be merged with initial states, the
     4026        resulting state is an initial state.
     4027
     4028        All states in a class must have the same ``is_final`` and
     4029        ``word_out`` values.
     4030
     4031        EXAMPLES::
     4032
     4033            sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0),
     4034            ....:                           ("B", "C", 0, 0), ("B", "C", 1, 1),
     4035            ....:                           ("C", "D", 0, 1), ("C", "D", 1, 0),
     4036            ....:                           ("D", "A", 0, 0), ("D", "A", 1, 1)])
     4037            sage: fsmq = fsm.quotient([[fsm.state("A"), fsm.state("C")],
     4038            ....:                      [fsm.state("B"), fsm.state("D")]])
     4039            sage: fsmq.transitions()
     4040            [Transition from State (State 'A', State 'C')
     4041                          to State (State 'B', State 'D'): 0|1,
     4042             Transition from State (State 'A', State 'C')
     4043                          to State (State 'B', State 'D'): 1|0,
     4044             Transition from State (State 'B', State 'D')
     4045                          to State (State 'A', State 'C'): 0|0,
     4046             Transition from State (State 'B', State 'D')
     4047                          to State (State 'A', State 'C'): 1|1]
     4048            sage: fsmq.relabeled().transitions()
     4049            [Transition from State 0 to State 1: 0|1,
     4050             Transition from State 0 to State 1: 1|0,
     4051             Transition from State 1 to State 0: 0|0,
     4052             Transition from State 1 to State 0: 1|1]
     4053            sage: fsmq1 = fsm.quotient(fsm.equivalence_classes())
     4054            sage: fsmq1 == fsmq
     4055            True
     4056            sage: fsm.quotient([[fsm.state("A"), fsm.state("B"), fsm.state("C"), fsm.state("D")]])
     4057            Traceback (most recent call last):
     4058                ...
     4059            ValueError: There is a transition Transition from State 'B' to State 'C': 0|0 in the original transducer, but no corresponding transition in the new transducer.
     4060        """
     4061        new = FiniteStateMachine()
     4062        new.mode = self.mode
     4063        new.input_alphabet = self.input_alphabet
     4064        new.output_alphabet = self.output_alphabet
     4065        state_mapping = {}
     4066
     4067        # Create new states and build state_mapping
     4068        for c in classes:
     4069            new_state = new.add_state(tuple(c))
     4070            for state in c:
     4071                state_mapping[state] = new_state
     4072
     4073        # Copy data from old transducer
     4074        for c in classes:
     4075            new_state = state_mapping[c[0]]
     4076            # copy all data from first class member
     4077            new_state.is_initial = c[0].is_initial
     4078            new_state.is_final = c[0].is_final
     4079            new_state.word_out = c[0].word_out
     4080            for transition in self.iter_transitions(c[0]):
     4081                new.add_transition(
     4082                    from_state=new_state,
     4083                    to_state = state_mapping[transition.to_state],
     4084                    word_in  = transition.word_in,
     4085                    word_out = transition.word_out)
     4086
     4087            # check that all class members have the same information (modulo classes)
     4088            for state in c:
     4089                new_state.is_initial = new_state.is_initial or state.is_initial
     4090                assert new_state.is_final == state.is_final, "Class %s mixes final and non-final states" % (c,)
     4091                assert new_state.word_out == state.word_out, "Class %s mixes different word_out" % (c,)
     4092                assert len(self.transitions(state)) == len(new.transitions(new_state)), \
     4093                    "Class %s has %d outgoing transitions, but class member %s has %d outgoing transitions" %  \
     4094                    (c, len(new.transitions(new_state)), state, len(self.transitions(state)))
     4095                for transition in self.transitions(state):
     4096                    try:
     4097                        new.transition((new_state, state_mapping[transition.to_state], transition.word_in, transition.word_out))
     4098                    except LookupError:
     4099                        raise ValueError, "There is a transition %s in the original transducer, but no corresponding transition in the new transducer." % transition
     4100        return new
     4101
     4102    def simplification(self):
     4103        """
     4104        Returns a simplified transducer.
     4105
     4106        INPUT:
     4107
     4108        Nothing.
     4109
     4110        OUTPUT:
     4111
     4112        A new transducer.
     4113
     4114        This function simplifies a transducer by Moore's algorithm,
     4115        first moving common output labels of transitions leaving a
     4116        state to output labels of transitions entering the state
     4117        (cf. :meth:`.prepone_output`).
     4118
     4119        The resulting transducer implements the same function as the
     4120        original transducer.
     4121
     4122        EXAMPLES::
     4123
     4124            sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0),
     4125            ....:                           ("B", "C", 0, 0), ("B", "C", 1, 1),
     4126            ....:                           ("C", "D", 0, 1), ("C", "D", 1, 0),
     4127            ....:                           ("D", "A", 0, 0), ("D", "A", 1, 1)])
     4128            sage: fsm.simplification()
     4129            Traceback (most recent call last):
     4130            ...
     4131            NotImplementedError: Simplification is only implemented for Transducers. For Automata, use minimization instead
     4132            sage: fsm = Transducer([("A", "B", 0, 1), ("A", "B", 1, 0),
     4133            ....:                           ("B", "C", 0, 0), ("B", "C", 1, 1),
     4134            ....:                           ("C", "D", 0, 1), ("C", "D", 1, 0),
     4135            ....:                           ("D", "A", 0, 0), ("D", "A", 1, 1)])
     4136            sage: fsms = fsm.simplification()
     4137            sage: fsms
     4138            finite state machine with 2 states
     4139            sage: fsms.transitions()
     4140            [Transition from State (State 'B', State 'D')
     4141                          to State (State 'A', State 'C'): 0|0,
     4142             Transition from State (State 'B', State 'D')
     4143                          to State (State 'A', State 'C'): 1|1,
     4144             Transition from State (State 'A', State 'C')
     4145                          to State (State 'B', State 'D'): 0|1,
     4146             Transition from State (State 'A', State 'C')
     4147                          to State (State 'B', State 'D'): 1|0]
     4148            sage: fsms.relabeled().transitions()
     4149            [Transition from State 0 to State 1: 0|0,
     4150             Transition from State 0 to State 1: 1|1,
     4151             Transition from State 1 to State 0: 0|1,
     4152             Transition from State 1 to State 0: 1|0]
     4153        """
     4154        if self.mode != 'transducer':
     4155            raise NotImplementedError, "Simplification is only implemented for Transducers. For Automata, use minimization instead"
     4156        fsm = deepcopy(self)
     4157        fsm.prepone_output()
     4158        return fsm.quotient(fsm.equivalence_classes())
     4159
     4160
     4161    # *************************************************************************
     4162    # other
     4163    # *************************************************************************
     4164
     4165
     4166    def graph(self, edge_labels='words_in_out'):
     4167        """
     4168        Returns the graph of the finite state machine with labeled
     4169        vertices and labeled edges.
     4170
     4171        INPUT:
     4172
     4173        - ``edge_label``: (default: ``'words_in_out'``) can be
     4174             - ``'words_in_out'`` (labels will be strings ``'i|o'``)
     4175             - a function with which takes as input a transition
     4176               and outputs (returns) the label
     4177
     4178        OUTPUT:
     4179
     4180        A graph.
     4181
     4182        EXAMPLES::
     4183
     4184            sage: from sage.combinat.finite_state_machine import FSMState
     4185            sage: A = FSMState('A')
     4186            sage: T = Transducer()
     4187            sage: T.graph()
     4188            Digraph on 0 vertices
     4189            sage: T.add_state(A)
     4190            State 'A'
     4191            sage: T.graph()
     4192            Digraph on 1 vertex
     4193            sage: T.add_transition(('A', 'A', 0, 1))
     4194            Transition from State 'A' to State 'A': 0|1
     4195            sage: T.graph()
     4196            Looped digraph on 1 vertex
     4197        """
     4198        if edge_labels == 'words_in_out':
     4199            label_fct = lambda t:t._in_out_label_()
     4200        elif hasattr(edge_labels, '__call__'):
     4201            label_fct = edge_labels
     4202        else:
     4203            raise TypeError, 'Wrong argument for edge_labels.'
     4204
     4205        graph_data = []
     4206        isolated_vertices = []
     4207        for state in self.iter_states():
     4208            transitions = state.transitions
     4209            if len(transitions) == 0:
     4210                isolated_vertices.append(state.label())
     4211            for t in transitions:
     4212                graph_data.append((t.from_state.label(), t.to_state.label(),
     4213                                   label_fct(t)))
     4214
     4215        G = DiGraph(graph_data)
     4216        G.add_vertices(isolated_vertices)
     4217        return G
     4218
     4219
     4220    digraph = graph
     4221
     4222
     4223    def plot(self):
     4224        """
     4225        Plots a graph of the finite state machine with labeled
     4226        vertices and labeled edges.
     4227
     4228        INPUT:
     4229
     4230        Nothing.
     4231
     4232        OUTPUT:
     4233
     4234        A plot of the graph of the finite state machine.
     4235
     4236        TESTS::
     4237
     4238            sage: FiniteStateMachine([('A', 'A', 0)]).plot()
     4239        """
     4240        return self.graph(edge_labels='words_in_out').plot()
     4241
     4242
     4243    def predecessors(self, state, valid_input=None):
     4244        """
     4245        Lists all predecessors of a state.
     4246
     4247        INPUT:
     4248
     4249        - ``state`` -- the state from which the predecessors should be
     4250          listed.
     4251
     4252        - ``valid_input`` -- If ``valid_input`` is a list, then we
     4253          only consider transitions whose input labels are contained
     4254          in ``valid_input``. ``state`` has to be a :class:`FSMState`
     4255          (not a label of a state). If input labels of length larger
     4256          than `1` are used, then ``valid_input`` has to be a list of
     4257          lists.
     4258
     4259        OUTPUT:
     4260
     4261        A list of states.
     4262
     4263        EXAMPLES::
     4264
     4265            sage: A = Transducer([('I', 'A', 'a', 'b'), ('I', 'B', 'b', 'c'),
     4266            ....:                 ('I', 'C', 'c', 'a'), ('A', 'F', 'b', 'a'),
     4267            ....:                 ('B', 'F', ['c', 'b'], 'b'), ('C', 'F', 'a', 'c')],
     4268            ....:                initial_states=['I'], final_states=['F'])
     4269            sage: A.predecessors(A.state('A'))
     4270            [State 'A', State 'I']
     4271            sage: A.predecessors(A.state('F'), valid_input=['b', 'a'])
     4272            [State 'F', State 'C', State 'A', State 'I']
     4273            sage: A.predecessors(A.state('F'), valid_input=[['c', 'b'], 'a'])
     4274            [State 'F', State 'C', State 'B']
     4275        """
     4276        if valid_input != None:
     4277            valid_list = list()
     4278            for input in valid_input:
     4279                input_list = input
     4280                if not isinstance(input_list, list):
     4281                    input_list = [input]
     4282                valid_list.append(input_list)
     4283            valid_input = valid_list
     4284
     4285        unhandeled_direct_predecessors = {s:[] for s in self.states() }
     4286        for t in self.transitions():
     4287            if valid_input is None or t.word_in in valid_input:
     4288                unhandeled_direct_predecessors[t.to_state].append(t.from_state)
     4289        done = []
     4290        open = [state]
     4291        while len(open) > 0:
     4292            s = open.pop()
     4293            candidates = unhandeled_direct_predecessors[s]
     4294            if candidates is not None:
     4295                open.extend(candidates)
     4296                unhandeled_direct_predecessors[s] = None
     4297                done.append(s)
     4298        return(done)
     4299
     4300
     4301#*****************************************************************************
     4302
     4303
     4304def is_FSMProcessIterator(PI):
     4305    """
     4306    Tests whether or not ``PI`` inherits from :class:`FSMProcessIterator`.
     4307
     4308    TESTS::
     4309
     4310        sage: from sage.combinat.finite_state_machine import is_FSMProcessIterator, FSMProcessIterator
     4311        sage: is_FSMProcessIterator(FSMProcessIterator(FiniteStateMachine()))
     4312        Traceback (most recent call last):
     4313        ...
     4314        ValueError: No state is initial.
     4315    """
     4316    return isinstance(PI, FSMProcessIterator)
     4317
     4318
     4319class FSMProcessIterator:
     4320    """
     4321    This class is for processing an input string on a finite state
     4322    machine.
     4323
     4324    An instance of this class is generated when
     4325    :meth:`FiniteStateMachine.process` or
     4326    :meth:`FiniteStateMachine.iter_process` of the finite state
     4327    machine is invoked. It behaves like an iterator which, in each
     4328    step, takes one letter of the input and runs (one step on) the
     4329    finite state machine with this input. More precisely, in each
     4330    step, the process iterator takes an outgoing transition of the
     4331    current state, whose input label equals the input letter of the
     4332    tape. The output label of the transition, if present, is written
     4333    on the output tape.
     4334
     4335    INPUT:
     4336
     4337    - ``fsm`` -- The finite state machine on which the input should be
     4338      processed.
     4339
     4340    - ``input_tape`` -- The input tape. It can be anything that is
     4341      iterable.
     4342
     4343    - ``initial_state`` -- The initial state in which the machine
     4344      starts. If this is ``None``, the unique inital state of the
     4345      finite state machine is takes. If there are several, an error is
     4346      reported.
     4347
     4348    The process (iteration) stops if there are no more input letters
     4349    on the tape. In this case a StopIteration exception is thrown. As
     4350    result the following attributes are available:
     4351
     4352    - ``accept_input`` -- Is True if the reached state is a final state.
     4353
     4354    - ``current_state`` -- The current/reached state in the process.
     4355
     4356    - ``output_tape`` -- The written output.
     4357
     4358    Current values of those attribtes (except ``accept_input``) are
     4359    (also) available during the iteration.
     4360
     4361    OUTPUT:
     4362
     4363    An iterator.
     4364
     4365    EXAMPLES:
     4366
     4367    The following transducer reads binary words and outputs a word,
     4368    where blocks of ones are replaced by just a single one. Further
     4369    only words that end with a zero are accepted.
     4370
     4371    ::
     4372
     4373        sage: T = Transducer({'A': [('A', 0, 0), ('B', 1, None)],
     4374        ....:                 'B': [('B', 1, None), ('A', 0, [1, 0])]},
     4375        ....:     initial_states=['A'], final_states=['A'])
     4376        sage: input = [1, 1, 0, 0, 1, 0, 1, 1, 1, 0]
     4377        sage: T.process(input)
     4378        (True, State 'A', [1, 0, 0, 1, 0, 1, 0])
     4379
     4380    The function :meth:`FiniteStateMachine.process` created a new
     4381    ``FSMProcessIterator``. We can do that manually, too, and get full
     4382    access to the iteration process::
     4383
     4384        sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4385        sage: it = FSMProcessIterator(T, input_tape=input)
     4386        sage: for _ in it:
     4387        ....:     print (it.current_state, it.output_tape)
     4388        (State 'B', [])
     4389        (State 'B', [])
     4390        (State 'A', [1, 0])
     4391        (State 'A', [1, 0, 0])
     4392        (State 'B', [1, 0, 0])
     4393        (State 'A', [1, 0, 0, 1, 0])
     4394        (State 'B', [1, 0, 0, 1, 0])
     4395        (State 'B', [1, 0, 0, 1, 0])
     4396        (State 'B', [1, 0, 0, 1, 0])
     4397        (State 'A', [1, 0, 0, 1, 0, 1, 0])
     4398        sage: it.accept_input
     4399        True
     4400    """
     4401    def __init__(self, fsm, input_tape=None, initial_state=None):
     4402        """
     4403        See :class:`FSMProcessIterator` for more information.
     4404
     4405        EXAMPLES::
     4406
     4407            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4408            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4409            ....:     initial_states=['A'], final_states=['A'])
     4410            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4411            sage: for _ in it:
     4412            ....:     pass
     4413            sage: it.output_tape
     4414            [1, 0]
     4415        """
     4416        self.fsm = fsm
     4417        if initial_state is None:
     4418            fsm_initial_states = self.fsm.initial_states()
     4419            try:
     4420                self.current_state = fsm_initial_states[0]
     4421            except IndexError:
     4422                raise ValueError, "No state is initial."
     4423            if len(fsm_initial_states) > 1:
     4424                raise ValueError, "Several initial states."
     4425        else:
     4426            self.current_state = initial_state
     4427        self.output_tape = []
     4428        if input_tape is None:
     4429            self._input_tape_iter_ = iter([])
     4430        else:
     4431            if hasattr(input_tape, '__iter__'):
     4432                self._input_tape_iter_ = iter(input_tape)
     4433            else:
     4434                raise ValueError, "Given input tape is not iterable."
     4435
     4436    def __iter__(self):
     4437        """
     4438        Returns ``self``.
     4439
     4440        TESTS::
     4441
     4442            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4443            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4444            ....:     initial_states=['A'], final_states=['A'])
     4445            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4446            sage: id(it) == id(iter(it))
     4447            True
     4448        """
     4449        return self
     4450
     4451    def next(self):
     4452        """
     4453        Makes one step in processing the input tape.
     4454
     4455        INPUT:
     4456       
     4457        Nothing.
     4458
     4459        OUTPUT:
     4460
     4461        It returns the taken transition. A ``StopIteration`` exception is
     4462        thrown when there is nothing more to read.
     4463
     4464        EXAMPLES::
     4465
     4466            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4467            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4468            ....:     initial_states=['A'], final_states=['A'])
     4469            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4470            sage: it.next()
     4471            Transition from State 'A' to State 'A': 0|1
     4472            sage: it.next()
     4473            Transition from State 'A' to State 'A': 1|0
     4474            sage: it.next()
     4475            Traceback (most recent call last):
     4476            ...
     4477            StopIteration
     4478        """
     4479        if hasattr(self, 'accept_input'):
     4480            raise StopIteration
     4481        try:
     4482            # process current state
     4483            transition = None
     4484            try:
     4485                transition = self.current_state.hook(
     4486                    self.current_state, self)
     4487            except AttributeError:
     4488                pass
     4489            self.write_word(self.current_state.word_out)
     4490
     4491            # get next
     4492            if not isinstance(transition, FSMTransition):
     4493                next_word = []
     4494                found = False
     4495
     4496                try:
     4497                    while not found:
     4498                        next_word.append(self.read_letter())
     4499                        try:
     4500                            transition = self.get_next_transition(
     4501                                next_word)
     4502                            found = True
     4503                        except ValueError:
     4504                            pass
     4505                except StopIteration:
     4506                    # this means input tape is finished
     4507                    if len(next_word) > 0:
     4508                        self.accept_input = False
     4509                    raise StopIteration
     4510
     4511            # process transition
     4512            try:
     4513                transition.hook(transition, self)
     4514            except AttributeError:
     4515                pass
     4516            self.write_word(transition.word_out)
     4517
     4518            # go to next state
     4519            self.current_state = transition.to_state
     4520
     4521        except StopIteration:
     4522            # this means, either input tape is finished or
     4523            # someone has thrown StopIteration manually (in one
     4524            # of the hooks)
     4525            if not self.current_state.is_final:
     4526                self.accept_input = False
     4527            if not hasattr(self, 'accept_input'):
     4528                self.accept_input = True
     4529            raise StopIteration
     4530
     4531        # return
     4532        return transition
     4533
     4534    def read_letter(self):
     4535        """
     4536        Reads a letter from the input tape.
     4537
     4538        INPUT:
     4539
     4540        Nothing.
     4541       
     4542        OUTPUT:
     4543
     4544        A letter.
     4545
     4546        Exception ``StopIteration`` is thrown if tape has reached
     4547        the end.
     4548
     4549        EXAMPLES::
     4550
     4551            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4552            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4553            ....:     initial_states=['A'], final_states=['A'])
     4554            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4555            sage: it.read_letter()
     4556            0
     4557        """
     4558        return self._input_tape_iter_.next()
     4559
     4560    def write_letter(self, letter):
     4561        """
     4562        Writes a letter on the output tape.
     4563
     4564        INPUT:
     4565
     4566        - ``letter`` -- the letter to be written.
     4567
     4568        OUTPUT:
     4569       
     4570        Nothing.
     4571
     4572        EXAMPLES::
     4573
     4574            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4575            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4576            ....:     initial_states=['A'], final_states=['A'])
     4577            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4578            sage: it.write_letter(42)
     4579            sage: it.output_tape
     4580            [42]
     4581        """
     4582        self.output_tape.append(letter)
     4583
     4584    def write_word(self, word):
     4585        """
     4586        Writes a word on the output tape.
     4587
     4588        INPUT:
     4589
     4590        - ``word`` -- the word to be written.
     4591
     4592        OUTPUT:
     4593       
     4594        Nothing.
     4595
     4596        EXAMPLES::
     4597
     4598            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4599            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4600            ....:     initial_states=['A'], final_states=['A'])
     4601            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4602            sage: it.write_word([4, 2])
     4603            sage: it.output_tape
     4604            [4, 2]
     4605        """
     4606        for letter in word:
     4607            self.write_letter(letter)
     4608
     4609    def get_next_transition(self, word_in):
     4610        """
     4611        Returns the next transition according to ``word_in``. It is
     4612        assumed that we are in state ``self.current_state``.
     4613
     4614        INPUT:
     4615
     4616        - ``word_in`` -- the input word.
     4617
     4618        OUTPUT:
     4619
     4620        The next transition according to ``word_in``. It is assumed
     4621        that we are in state ``self.current_state``.
     4622
     4623        EXAMPLES::
     4624
     4625            sage: from sage.combinat.finite_state_machine import FSMProcessIterator
     4626            sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
     4627            ....:     initial_states=['A'], final_states=['A'])
     4628            sage: it = FSMProcessIterator(inverter, input_tape=[0, 1])
     4629            sage: it.get_next_transition([0])
     4630            Transition from State 'A' to State 'A': 0|1
     4631        """
     4632        for transition in self.current_state.transitions:
     4633            if transition.word_in == word_in:
     4634                return transition
     4635        raise ValueError
     4636
     4637
     4638#*****************************************************************************
     4639
     4640
     4641def setup_latex_preamble():
     4642    """
     4643    This function adds the package ``tikz`` with support for automata
     4644    to the preamble of Latex so that the finite state machines can be
     4645    drawn nicely.
     4646
     4647    INPUT:
     4648
     4649    Nothing.
     4650
     4651    OUTPUT:
     4652   
     4653    Nothing.
     4654
     4655    TESTS::
     4656
     4657        sage: from sage.combinat.finite_state_machine import setup_latex_preamble
     4658        sage: setup_latex_preamble()
     4659    """
     4660    latex.add_package_to_preamble_if_available('tikz')
     4661    latex.add_to_preamble('\\usetikzlibrary{automata}')
     4662
     4663
     4664#*****************************************************************************