Ticket #15078: trac_15078_fsm_automata_transducers.7.patch

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

    # HG changeset patch
    # User Daniel Krenn <math+sage@danielkrenn.at>
    # Date 1384187087 -3600
    # Node ID 9f7d4de34297c12847e2346cdfceff774d990b9b
    # Parent  7285f9d8a2aa2116547de1028090bb921e1d128e
    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  
    8080   designs
    8181   species
    8282   developer
     83   sage/combinat/finite_state_machine
    8384   words
    8485
    8586   sage/combinat/dict_addition
  • sage/combinat/all.py

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