Ticket #15078: trac_15078_fsm_automata_transducers.5.patch

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

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