Finite state machines, automata, transducers#
This module adds support for finite state machines, automata and transducers.
For creating automata and transducers you can use classes
Automaton
andTransducer
(or the more general classFiniteStateMachine
)
or the generators
automata
andtransducers
which contain preconstructed and commonly used automata and transducers. See also the examples below.
Contents#
FiniteStateMachine
and derived classes Transducer
and Automaton
#
Accessing parts of a finite state machine#
Get a state by its label |
|
List of states |
|
Iterator over the states |
|
List of initial states |
|
Iterator over initial states |
|
List of final states |
|
Iterator over final states |
|
Get a transition by its states and labels |
|
List of transitions |
|
Iterator over the transitions |
|
List of predecessors of a state |
|
Induced sub-machine |
|
Accessible components |
|
Coaccessible components |
|
Final components (connected components which cannot be left again) |
(Modified) Copies#
Returns an empty deep copy |
|
Returns a deep copy |
|
Returns a relabeled deep copy |
|
Extends an automaton to a transducer |
Manipulation#
Add a state |
|
Add states |
|
Delete a state |
|
Add a transition |
|
Add transitions |
|
Input alphabet |
|
Output alphabet |
|
Hook for handling duplicate transitions |
|
Add transitions by a transition function |
|
Delete a transition |
|
Remove epsilon transitions (not implemented) |
|
Split transitions with input words of length |
|
Determine input and output alphabets |
|
Determine input alphabet |
|
Determine output alphabet |
|
Construct final output by implicitly reading trailing letters; cf. |
Properties#
Checks for a state |
|
Checks for an initial state |
|
Checks for initial states |
|
Checks for an final state |
|
Checks for final states |
|
Checks for a transition |
|
Checks for a deterministic machine |
|
Checks for a complete machine |
|
Checks for a connected machine |
|
Checks for equivalent automata |
|
Checks for a Markov chain |
|
Checks whether the colors of all states are equal |
|
Determine the number of successful paths |
|
Main terms of expectation and variance of sums of labels |
|
Moments of the waiting time for first true output |
|
Epsilon successors of a state |
|
Compute Markov chain with Parry measure |
Operations#
Disjoint union |
|
Concatenation |
|
Kleene star |
|
Complement of an automaton |
|
Intersection of automata |
|
Intersection of transducers |
|
Cartesian product of a transducer with another finite state machine |
|
Product of finite state machines |
|
Composition (output of other is input of self) |
|
Input projection (output is deleted) |
|
Output projection (old output is new input) |
|
Input or output projection |
|
Transposition (all transitions are reversed) |
|
Machine with final output constructed by implicitly reading trailing letters, cf. |
|
Determinisation of an automaton |
|
Completion of a finite state machine |
|
Process input |
|
Process input with shortened output |
|
Process input of an automaton (output differs from general case) |
|
Process input of a transducer (output differs from general case) |
|
Return process iterator |
|
Return all possible output words |
|
Return all possible accepted words |
Simplification#
Prepone output where possible |
|
List of equivalent states |
|
Quotient with respect to equivalence classes |
|
Merge transitions while adding input |
|
Simplification of a Markov chain |
|
Minimization of an automaton |
|
Simplification of a transducer |
Conversion#
LaTeX output#
Set options |
|
Set coordinates of the states |
|
Default formatting of words in transition labels |
|
Format negative numbers as overlined number |
|
Format words in transition labels in reversed order |
See also
FSMState
#
Final output of a state |
|
Describes whether a state is final or not |
|
Describes whether a state is initial or not |
|
Probability of starting in this state as part of a Markov chain |
|
Label of a state |
|
Returns a relabeled deep copy of a state |
|
Checks whether two states are fully equal (including all attributes) |
FSMTransition
#
State in which transition starts |
|
State in which transition ends |
|
Input word of the transition |
|
Output word of the transition |
|
Returns a deep copy of the transition |
FSMProcessIterator
#
Makes one step in processing the input tape |
|
Reads a word from the input tape |
|
Returns the finished branches during process |
Helper Functions#
Checks whether all elements of |
|
Group iterable by values of some key |
|
Determine whether list starts with the given prefix |
|
Returns a string associated to the input letter |
|
Returns a string associated to a word |
|
Tests whether an object inherits from |
|
Tests whether an object inherits from |
|
Tests whether an object inherits from |
|
Default function for handling duplicate transitions |
|
Raise error when inserting a duplicate transition |
|
Add input when inserting a duplicate transition |
Examples#
We start with a general FiniteStateMachine
. Later there will
be also an Automaton
and a Transducer
.
A simple finite state machine#
We can easily create a finite state machine by
sage: fsm = FiniteStateMachine()
sage: fsm
Empty finite state machine
By default this is the empty finite state machine, so not very interesting. Let’s create and add some states and transitions:
sage: day = fsm.add_state('day')
sage: night = fsm.add_state('night')
sage: sunrise = fsm.add_transition(night, day)
sage: sunset = fsm.add_transition(day, night)
Let us look at sunset
more closely:
sage: sunset
Transition from 'day' to 'night': -|-
Note that could also have created and added the transitions directly by:
sage: fsm.add_transition('day', 'night')
Transition from 'day' to 'night': -|-
This would have had added the states automatically, since they are present in the transitions.
Anyhow, we got the following finite state machine:
sage: fsm
Finite state machine with 2 states
We can also obtain the underlying directed graph
by
sage: fsm.graph()
Looped multi-digraph on 2 vertices
To visualize a finite state machine, we can use
latex()
and run the result through LaTeX,
see the section on LaTeX output
below.
Alternatively, we could have created the finite state machine above simply by
sage: FiniteStateMachine([('night', 'day'), ('day', 'night')])
Finite state machine with 2 states
See FiniteStateMachine
for a lot of possibilities to create
finite state machines.
A simple Automaton (recognizing NAFs)#
We want to build an automaton which recognizes non-adjacent forms (NAFs), i.e., sequences which have no adjacent non-zeros. We use \(0\), \(1\), and \(-1\) as digits:
sage: NAF = Automaton(
....: {'A': [('A', 0), ('B', 1), ('B', -1)], 'B': [('A', 0)]})
sage: NAF.state('A').is_initial = True
sage: NAF.state('A').is_final = True
sage: NAF.state('B').is_final = True
sage: NAF
Automaton with 2 states
Of course, we could have specified the initial and final states
directly in the definition of NAF
by initial_states=['A']
and
final_states=['A', 'B']
.
So let’s test the automaton with some input:
sage: NAF([0])
True
sage: NAF([0, 1])
True
sage: NAF([1, -1])
False
sage: NAF([0, -1, 0, 1])
True
sage: NAF([0, -1, -1, -1, 0])
False
sage: NAF([-1, 0, 0, 1, 1])
False
Alternatively, we could call that by
sage: NAF.process([0, -1, 0, 1])
(True, 'B')
which gives additionally the state in which we arrived.
We can also let an automaton act on a word:
sage: W = Words([-1, 0, 1]); W
Finite and infinite words over {-1, 0, 1}
sage: w = W([1, 0, 1, 0, -1]); w
word: 1,0,1,0,-1
sage: NAF(w)
True
Recognizing NAFs via Automata Operations#
Alternatively, we can use automata operations to recognize NAFs; for
simplicity, we only use the input alphabet [0, 1]
. On the one
hand, we can construct such an automaton by forbidding the word
11
:
sage: forbidden = automata.ContainsWord([1, 1], input_alphabet=[0, 1])
sage: NAF_negative = forbidden.complement()
sage: NAF_negative([1, 1, 0, 1])
False
sage: NAF_negative([1, 0, 1, 0, 1])
True
On the other hand, we can write this as a regular expression and translate that into automata operations:
sage: zero = automata.Word([0])
sage: one = automata.Word([1])
sage: epsilon = automata.EmptyWord(input_alphabet=[0, 1])
sage: NAF_positive = (zero + one*zero).kleene_star() * (epsilon + one)
We check that the two approaches are equivalent:
sage: NAF_negative.is_equivalent(NAF_positive)
True
See also
ContainsWord()
,
Word()
,
complement()
,
kleene_star()
,
EmptyWord()
,
is_equivalent()
.
LaTeX output#
We can visualize a finite state machine by converting it to LaTeX by
using the usual function latex()
. Within LaTeX,
TikZ is used for typesetting the graphics, see the
Wikipedia article PGF/TikZ.
sage: print(latex(NAF)) # abs tol 1e-3
\begin{tikzpicture}[auto, initial text=, >=latex]
\node[state, accepting, initial] (v0) at (3.000000, 0.000000) {$\text{\texttt{A}}$};
\node[state, accepting] (v1) at (-3.000000, 0.000000) {$\text{\texttt{B}}$};
\path[->] (v0) edge[loop above] node {$0$} ();
\path[->] (v0.185.00) edge node[rotate=360.00, anchor=north] {$1, -1$} (v1.355.00);
\path[->] (v1.5.00) edge node[rotate=0.00, anchor=south] {$0$} (v0.175.00);
\end{tikzpicture}
We can turn this into a graphical representation.
sage: view(NAF) # not tested
To actually see this, use the live documentation in the Sage notebook and execute the cells in this and the previous section.
Several options can be set to customize the output, see
latex_options()
for details. In particular,
we use format_letter_negative()
to format
\(-1\) as \(\overline{1}\).
sage: NAF.latex_options(
....: coordinates={'A': (0, 0),
....: 'B': (6, 0)},
....: initial_where={'A': 'below'},
....: format_letter=NAF.format_letter_negative,
....: format_state_label=lambda x:
....: r'\mathcal{%s}' % x.label()
....: )
sage: print(latex(NAF))
\begin{tikzpicture}[auto, initial text=, >=latex]
\node[state, accepting, initial, initial where=below] (v0) at (0.000000, 0.000000) {$\mathcal{A}$};
\node[state, accepting] (v1) at (6.000000, 0.000000) {$\mathcal{B}$};
\path[->] (v0) edge[loop above] node {$0$} ();
\path[->] (v0.5.00) edge node[rotate=0.00, anchor=south] {$1, \overline{1}$} (v1.175.00);
\path[->] (v1.185.00) edge node[rotate=360.00, anchor=north] {$0$} (v0.355.00);
\end{tikzpicture}
sage: view(NAF) # not tested
To use the output of latex()
in your own
\(\LaTeX\) file, you have to include
\usepackage{tikz}
\usetikzlibrary{automata}
into the preamble of your file.
A simple transducer (binary inverter)#
Let’s build a simple transducer, which rewrites a binary word by inverting each bit:
sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]},
....: initial_states=['A'], final_states=['A'])
We can look at the states and transitions:
sage: inverter.states()
['A']
sage: for t in inverter.transitions():
....: print(t)
Transition from 'A' to 'A': 0|1
Transition from 'A' to 'A': 1|0
Now we apply a word to it and see what the transducer does:
sage: inverter([0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1])
[1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0]
True
means, that we landed in a final state, that state is labeled
'A'
, and we also got an output.
Transducers and (in)finite Words#
A transducer can also act on everything iterable, in particular, on Sage’s words.
sage: W = Words([0, 1]); W
Finite and infinite words over {0, 1}
Let us take the inverter from the previous section and feed some finite word into it:
sage: w = W([1, 1, 0, 1]); w
word: 1101
sage: inverter(w)
word: 0010
We see that the output is again a word (this is a consequence of
calling process()
with automatic_output_type
).
We can even input something infinite like an infinite word:
sage: tm = words.ThueMorseWord(); tm
word: 0110100110010110100101100110100110010110...
sage: inverter(tm)
word: 1001011001101001011010011001011001101001...
A transducer which performs division by \(3\) in binary#
Now we build a transducer, which divides a binary number by \(3\). The labels of the states are the remainder of the division. The transition function is
sage: def f(state_from, read):
....: if state_from + read <= 1:
....: state_to = 2*state_from + read
....: write = 0
....: else:
....: state_to = 2*state_from + read - 3
....: write = 1
....: return (state_to, write)
which assumes reading a binary number from left to right. We get the transducer with
sage: D = Transducer(f, initial_states=[0], final_states=[0],
....: input_alphabet=[0, 1])
Let us try to divide \(12\) by \(3\):
sage: D([1, 1, 0, 0])
[0, 1, 0, 0]
Now we want to divide \(13\) by \(3\):
sage: D([1, 1, 0, 1])
Traceback (most recent call last):
...
ValueError: Invalid input sequence.
The raised ValueError
means \(13\) is not divisible by \(3\).
Gray Code#
The Gray code is a binary numeral system where two successive values differ in only one bit, cf. the Wikipedia article Gray_code. The Gray code of an integer \(n\) is obtained by a bitwise xor between the binary expansion of \(n\) and the binary expansion of \(\lfloor n/2\rfloor\); the latter corresponds to a shift by one position in binary.
The purpose of this example is to construct a transducer converting the standard binary expansion to the Gray code by translating this construction into operations with transducers.
For this construction, the least significant digit is at the left-most position. Note that it is easier to shift everything to the right first, i.e., multiply by \(2\) instead of building \(\lfloor n/2\rfloor\). Then, we take the input xor with the right shift of the input and forget the first letter.
We first construct a transducer shifting the binary expansion to the right. This requires storing the previously read digit in a state.
sage: def shift_right_transition(state, digit):
....: if state == 'I':
....: return (digit, None)
....: else:
....: return (digit, state)
sage: shift_right_transducer = Transducer(
....: shift_right_transition,
....: initial_states=['I'],
....: input_alphabet=[0, 1],
....: final_states=[0])
sage: shift_right_transducer.transitions()
[Transition from 'I' to 0: 0|-,
Transition from 'I' to 1: 1|-,
Transition from 0 to 0: 0|0,
Transition from 0 to 1: 1|0,
Transition from 1 to 0: 0|1,
Transition from 1 to 1: 1|1]
sage: shift_right_transducer([0, 1, 1, 0])
[0, 1, 1]
sage: shift_right_transducer([1, 0, 0])
[1, 0]
The output of the shifts above look a bit weird (from a right-shift
transducer, we would expect, for example, that [1, 0, 0]
was
mapped to [0, 1, 0]
), since we write None
instead of the zero
at the left. Further, note that only \(0\) is listed as a final state
as we have to enforce that a most significant zero is read as the last
input letter in order to flush the last digit:
sage: shift_right_transducer([0, 1, 0, 1])
Traceback (most recent call last):
...
ValueError: Invalid input sequence.
Next, we construct the transducer performing the xor operation. We also
have to take None
into account as our shift_right_transducer
waits one iteration until it starts writing output. This corresponds
with our intention to forget the first letter.
sage: def xor_transition(state, digits):
....: if digits[0] is None or digits[1] is None:
....: return (0, None)
....: else:
....: return (0, digits[0].__xor__(digits[1]))
sage: from itertools import product
sage: xor_transducer = Transducer(
....: xor_transition,
....: initial_states=[0],
....: final_states=[0],
....: input_alphabet=list(product([None, 0, 1], [0, 1])))
sage: xor_transducer.transitions()
[Transition from 0 to 0: (None, 0)|-,
Transition from 0 to 0: (None, 1)|-,
Transition from 0 to 0: (0, 0)|0,
Transition from 0 to 0: (0, 1)|1,
Transition from 0 to 0: (1, 0)|1,
Transition from 0 to 0: (1, 1)|0]
sage: xor_transducer([(None, 0), (None, 1), (0, 0), (0, 1), (1, 0), (1, 1)])
[0, 1, 1, 0]
sage: xor_transducer([(0, None)])
Traceback (most recent call last):
...
ValueError: Invalid input sequence.
The transducer computing the Gray code is then constructed as a
Cartesian product
between the
shifted version and the original input (represented here by the
shift_right_transducer
and the identity transducer
,
respectively). This Cartesian product is then fed into the
xor_transducer
as a composition
of transducers.
sage: product_transducer = shift_right_transducer.cartesian_product(transducers.Identity([0, 1]))
sage: Gray_transducer = xor_transducer(product_transducer)
We use construct_final_word_out()
to make sure that all output
is written; otherwise, we would have to make sure that a sufficient number of trailing
zeros is read.
sage: Gray_transducer.construct_final_word_out([0])
sage: Gray_transducer.transitions()
[Transition from (('I', 0), 0) to ((0, 0), 0): 0|-,
Transition from (('I', 0), 0) to ((1, 0), 0): 1|-,
Transition from ((0, 0), 0) to ((0, 0), 0): 0|0,
Transition from ((0, 0), 0) to ((1, 0), 0): 1|1,
Transition from ((1, 0), 0) to ((0, 0), 0): 0|1,
Transition from ((1, 0), 0) to ((1, 0), 0): 1|0]
There is a prepackaged transducer
for Gray code, let’s see whether they agree. We have to use
relabeled()
to relabel our states with
integers.
sage: constructed = Gray_transducer.relabeled()
sage: packaged = transducers.GrayCode()
sage: constructed == packaged
True
Finally, we check that this indeed computes the Gray code of the first 10 non-negative integers.
sage: for n in srange(10):
....: Gray_transducer(n.bits())
[]
[1]
[1, 1]
[0, 1]
[0, 1, 1]
[1, 1, 1]
[1, 0, 1]
[0, 0, 1]
[0, 0, 1, 1]
[1, 0, 1, 1]
Using the hook-functions#
Let’s use the previous example “division by
3” to demonstrate the optional
state and transition parameters hook
.
First, we define what those functions should do. In our case, this is just saying in which state we are and which transition we take
sage: def state_hook(process, state, output):
....: print("We are now in State %s." % (state.label(),))
sage: from sage.combinat.finite_state_machine import FSMWordSymbol
sage: def transition_hook(transition, process):
....: print("Currently we go from %s to %s, "
....: "reading %s and writing %s." % (
....: transition.from_state, transition.to_state,
....: FSMWordSymbol(transition.word_in),
....: FSMWordSymbol(transition.word_out)))
Now, let’s add these hook-functions to the existing transducer:
sage: for s in D.iter_states():
....: s.hook = state_hook
sage: for t in D.iter_transitions():
....: t.hook = transition_hook
Rerunning the process again now gives the following output:
sage: D.process([1, 1, 0, 1], check_epsilon_transitions=False)
We are now in State 0.
Currently we go from 0 to 1, reading 1 and writing 0.
We are now in State 1.
Currently we go from 1 to 0, reading 1 and writing 1.
We are now in State 0.
Currently we go from 0 to 0, reading 0 and writing 0.
We are now in State 0.
Currently we go from 0 to 1, reading 1 and writing 0.
We are now in State 1.
(False, 1, [0, 1, 0, 0])
The example above just explains the basic idea of using hook-functions. In the following, we will use those hooks more seriously.
Warning
The hooks of the states are also called while exploring the epsilon
successors of a state (during processing). In the example above, we
used check_epsilon_transitions=False
to avoid this (and also
therefore got a cleaner output).
Warning
The arguments used when calling a hook have changed in
github issue #16538 from hook(state, process)
to
hook(process, state, output)
.
Detecting sequences with same number of \(0\) and \(1\)#
Suppose we have a binary input and want to accept all sequences with the same number of \(0\) and \(1\). This cannot be done with a finite automaton. Anyhow, we can make usage of the hook functions to extend our finite automaton by a counter:
sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
sage: C = FiniteStateMachine()
sage: def update_counter(process, state, output):
....: try:
....: l = process.preview_word()
....: except RuntimeError:
....: raise StopIteration
....: process.fsm.counter += 1 if l == 1 else -1
....: if process.fsm.counter > 0:
....: next_state = 'positive'
....: elif process.fsm.counter < 0:
....: next_state = 'negative'
....: else:
....: next_state = 'zero'
....: return FSMTransition(state, process.fsm.state(next_state),
....: l, process.fsm.counter)
sage: C.add_state(FSMState('zero', hook=update_counter,
....: is_initial=True, is_final=True))
'zero'
sage: C.add_state(FSMState('positive', hook=update_counter))
'positive'
sage: C.add_state(FSMState('negative', hook=update_counter))
'negative'
Now, let’s input some sequence:
sage: C.counter = 0; C([1, 1, 1, 1, 0, 0])
(False, 'positive', [1, 2, 3, 4, 3, 2])
The result is False, since there are four \(1\) but only two \(0\). We
land in the state positive
and we can also see the values of the
counter in each step.
Let’s try some other examples:
sage: C.counter = 0; C([1, 1, 0, 0])
(True, 'zero', [1, 2, 1, 0])
sage: C.counter = 0; C([0, 1, 0, 0])
(False, 'negative', [-1, 0, -1, -2])
See also methods Automaton.process()
and
Transducer.process()
(or even
FiniteStateMachine.process()
), the explanation of the parameter
hook
and the examples in FSMState
and
FSMTransition
, and the description and examples in
FSMProcessIterator
for more information on processing and
hooks.
REFERENCES:
Clemens Heuberger, Sara Kropf, and Helmut Prodinger, Output sum of transducers: Limiting distribution and periodic fluctuation, Electron. J. Combin. 22 (2015), #P2.19.
AUTHORS:
Daniel Krenn (2012-03-27): initial version
Clemens Heuberger (2012-04-05): initial version
Sara Kropf (2012-04-17): initial version
Clemens Heuberger (2013-08-21): release candidate for Sage patch
Daniel Krenn (2013-08-21): release candidate for Sage patch
Sara Kropf (2013-08-21): release candidate for Sage patch
Clemens Heuberger (2013-09-02): documentation improved
Daniel Krenn (2013-09-13): comments from trac worked in
- Clemens Heuberger (2013-11-03): output (labels) of determinisation,
product, composition, etc. changed (for consistency), representation of state changed, documentation improved
Daniel Krenn (2013-11-04): whitespaces in documentation corrected
Clemens Heuberger (2013-11-04): full_group_by added
Daniel Krenn (2013-11-04): next release candidate for Sage patch
Sara Kropf (2013-11-08): fix for adjacency matrix
Clemens Heuberger (2013-11-11): fix for prepone_output
- Daniel Krenn (2013-11-11): comments from github issue #15078 included:
docstring of FiniteStateMachine rewritten, Automaton and Transducer inherited from FiniteStateMachine
- Daniel Krenn (2013-11-25): documentation improved according to
comments from github issue #15078
Clemens Heuberger, Daniel Krenn, Sara Kropf (2014-02-21–2014-07-18): A huge bunch of improvements. Details see github issue #15841, github issue #15847, github issue #15848, github issue #15849, github issue #15850, github issue #15922, github issue #15923, github issue #15924, github issue #15925, github issue #15928, github issue #15960, github issue #15961, github issue #15962, github issue #15963, github issue #15975, github issue #16016, github issue #16024, github issue #16061, github issue #16128, github issue #16132, github issue #16138, github issue #16139, github issue #16140, github issue #16143, github issue #16144, github issue #16145, github issue #16146, github issue #16191, github issue #16200, github issue #16205, github issue #16206, github issue #16207, github issue #16229, github issue #16253, github issue #16254, github issue #16255, github issue #16266, github issue #16355, github issue #16357, github issue #16387, github issue #16425, github issue #16539, github issue #16555, github issue #16557, github issue #16588, github issue #16589, github issue #16666, github issue #16668, github issue #16674, github issue #16675, github issue #16677.
Daniel Krenn (2015-09-14): cleanup github issue #18227
ACKNOWLEDGEMENT:
Clemens Heuberger, Daniel Krenn and Sara Kropf are supported by the Austrian Science Fund (FWF): P 24644-N26.
Methods#
- class sage.combinat.finite_state_machine.Automaton(*args, **kwargs)#
Bases:
FiniteStateMachine
This creates an automaton, which is a finite state machine, whose transitions have input labels.
An automaton has additional features like creating a deterministic and a minimized automaton.
See class
FiniteStateMachine
for more information.EXAMPLES:
We can create an automaton recognizing even numbers (given in binary and read from left to right) in the following way:
sage: A = Automaton([('P', 'Q', 0), ('P', 'P', 1), ....: ('Q', 'P', 1), ('Q', 'Q', 0)], ....: initial_states=['P'], final_states=['Q']) sage: A Automaton with 2 states sage: A([0]) True sage: A([1, 1, 0]) True sage: A([1, 0, 1]) False
Note that the full output of the commands can be obtained by calling
process()
and looks like this:sage: A.process([1, 0, 1]) (False, 'P')
- cartesian_product(other, only_accessible_components=True)#
Return a new automaton which accepts an input if it is accepted by both given automata.
INPUT:
other
– an automatononly_accessible_components
– IfTrue
(default), then the result is piped throughaccessible_components()
. If nonew_input_alphabet
is given, it is determined bydetermine_alphabets()
.
OUTPUT:
A new automaton which computes the intersection (see below) of the languages of
self
andother
.The set of states of the new automaton is the Cartesian product of the set of states of both given automata. There is a transition \(((A, B), (C, D), a)\) in the new automaton if there are transitions \((A, C, a)\) and \((B, D, a)\) in the old automata.
The methods
intersection()
andcartesian_product()
are the same (for automata).EXAMPLES:
sage: aut1 = Automaton([('1', '2', 1), ....: ('2', '2', 1), ....: ('2', '2', 0)], ....: initial_states=['1'], ....: final_states=['2'], ....: determine_alphabets=True) sage: aut2 = Automaton([('A', 'A', 1), ....: ('A', 'B', 0), ....: ('B', 'B', 0), ....: ('B', 'A', 1)], ....: initial_states=['A'], ....: final_states=['B'], ....: determine_alphabets=True) sage: res = aut1.intersection(aut2) sage: (aut1([1, 1]), aut2([1, 1]), res([1, 1])) (True, False, False) sage: (aut1([1, 0]), aut2([1, 0]), res([1, 0])) (True, True, True) sage: res.transitions() [Transition from ('1', 'A') to ('2', 'A'): 1|-, Transition from ('2', 'A') to ('2', 'B'): 0|-, Transition from ('2', 'A') to ('2', 'A'): 1|-, Transition from ('2', 'B') to ('2', 'B'): 0|-, Transition from ('2', 'B') to ('2', 'A'): 1|-]
For automata with epsilon-transitions, intersection is not well defined. But for any finite state machine, epsilon-transitions can be removed by
remove_epsilon_transitions()
.sage: a1 = Automaton([(0, 0, 0), ....: (0, 1, None), ....: (1, 1, 1), ....: (1, 2, 1)], ....: initial_states=[0], ....: final_states=[1], ....: determine_alphabets=True) sage: a2 = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 1)], ....: initial_states=[0], ....: final_states=[1], ....: determine_alphabets=True) sage: a1.intersection(a2) Traceback (most recent call last): ... ValueError: An epsilon-transition (with empty input) was found. sage: a1.remove_epsilon_transitions() # not tested (since not implemented yet) sage: a1.intersection(a2) # not tested
- complement()#
Return the complement of this automaton.
OUTPUT:
An
Automaton
.If this automaton recognizes language \(\mathcal{L}\) over an input alphabet \(\mathcal{A}\), then the complement recognizes \(\mathcal{A}\setminus\mathcal{L}\).
EXAMPLES:
sage: A = automata.Word([0, 1]) sage: [w for w in ([], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1]) ....: if A(w)] [[0, 1]] sage: Ac = A.complement() sage: Ac.transitions() [Transition from 0 to 1: 0|-, Transition from 0 to 3: 1|-, Transition from 2 to 3: 0|-, Transition from 2 to 3: 1|-, Transition from 1 to 2: 1|-, Transition from 1 to 3: 0|-, Transition from 3 to 3: 0|-, Transition from 3 to 3: 1|-] sage: [w for w in ([], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1]) ....: if Ac(w)] [[], [0], [1], [0, 0], [1, 0], [1, 1]]
The automaton must be deterministic:
sage: A = automata.Word([0]) * automata.Word([1]) sage: A.complement() Traceback (most recent call last): ... ValueError: The finite state machine must be deterministic. sage: Ac = A.determinisation().complement() sage: [w for w in ([], [0], [1], [0, 0], [0, 1], [1, 0], [1, 1]) ....: if Ac(w)] [[], [0], [1], [0, 0], [1, 0], [1, 1]]
- determinisation()#
Return a deterministic automaton which accepts the same input words as the original one.
OUTPUT:
A new automaton, which is deterministic.
The labels of the states of the new automaton are frozensets of states of
self
. The color of a new state is the frozenset of colors of the constituent states ofself
. Therefore, the colors of the constituent states have to be hashable. However, if all constituent states have colorNone
, then the resulting color isNone
, too.The input alphabet must be specified.
EXAMPLES:
sage: aut = Automaton([('A', 'A', 0), ('A', 'B', 1), ('B', 'B', 1)], ....: initial_states=['A'], final_states=['B']) sage: aut.determinisation().transitions() [Transition from frozenset({'A'}) to frozenset({'A'}): 0|-, Transition from frozenset({'A'}) to frozenset({'B'}): 1|-, Transition from frozenset({'B'}) to frozenset(): 0|-, Transition from frozenset({'B'}) to frozenset({'B'}): 1|-, Transition from frozenset() to frozenset(): 0|-, Transition from frozenset() to frozenset(): 1|-]
sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1), ....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)], ....: initial_states=['A'], final_states=['C']) sage: A.determinisation().states() [frozenset({'A'}), frozenset({'A', 'B'}), frozenset({'A', 'C'}), frozenset({'A', 'B', 'C'})]
sage: A = Automaton([(0, 1, 1), (0, 2, [1, 1]), (0, 3, [1, 1, 1]), ....: (1, 0, -1), (2, 0, -2), (3, 0, -3)], ....: initial_states=[0], final_states=[0, 1, 2, 3]) sage: B = A.determinisation().relabeled().coaccessible_components() sage: sorted(B.transitions()) [Transition from 0 to 1: 1|-, Transition from 1 to 0: -1|-, Transition from 1 to 3: 1|-, Transition from 3 to 0: -2|-, Transition from 3 to 4: 1|-, Transition from 4 to 0: -3|-]
Note that colors of states have to be hashable:
sage: A = Automaton([[0, 0, 0]], initial_states=[0]) sage: A.state(0).color = [] sage: A.determinisation() Traceback (most recent call last): ... TypeError: unhashable type: 'list' sage: A.state(0).color = () sage: A.determinisation() Automaton with 1 state
If the colors of all constituent states are
None
, the resulting color isNone
, too (github issue #19199):sage: A = Automaton([(0, 0, 0)], ....: initial_states=[0], ....: final_states=[0]) sage: [s.color for s in A.determinisation().iter_states()] [None]
- intersection(other, only_accessible_components=True)#
Return a new automaton which accepts an input if it is accepted by both given automata.
INPUT:
other
– an automatononly_accessible_components
– IfTrue
(default), then the result is piped throughaccessible_components()
. If nonew_input_alphabet
is given, it is determined bydetermine_alphabets()
.
OUTPUT:
A new automaton which computes the intersection (see below) of the languages of
self
andother
.The set of states of the new automaton is the Cartesian product of the set of states of both given automata. There is a transition \(((A, B), (C, D), a)\) in the new automaton if there are transitions \((A, C, a)\) and \((B, D, a)\) in the old automata.
The methods
intersection()
andcartesian_product()
are the same (for automata).EXAMPLES:
sage: aut1 = Automaton([('1', '2', 1), ....: ('2', '2', 1), ....: ('2', '2', 0)], ....: initial_states=['1'], ....: final_states=['2'], ....: determine_alphabets=True) sage: aut2 = Automaton([('A', 'A', 1), ....: ('A', 'B', 0), ....: ('B', 'B', 0), ....: ('B', 'A', 1)], ....: initial_states=['A'], ....: final_states=['B'], ....: determine_alphabets=True) sage: res = aut1.intersection(aut2) sage: (aut1([1, 1]), aut2([1, 1]), res([1, 1])) (True, False, False) sage: (aut1([1, 0]), aut2([1, 0]), res([1, 0])) (True, True, True) sage: res.transitions() [Transition from ('1', 'A') to ('2', 'A'): 1|-, Transition from ('2', 'A') to ('2', 'B'): 0|-, Transition from ('2', 'A') to ('2', 'A'): 1|-, Transition from ('2', 'B') to ('2', 'B'): 0|-, Transition from ('2', 'B') to ('2', 'A'): 1|-]
For automata with epsilon-transitions, intersection is not well defined. But for any finite state machine, epsilon-transitions can be removed by
remove_epsilon_transitions()
.sage: a1 = Automaton([(0, 0, 0), ....: (0, 1, None), ....: (1, 1, 1), ....: (1, 2, 1)], ....: initial_states=[0], ....: final_states=[1], ....: determine_alphabets=True) sage: a2 = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 1)], ....: initial_states=[0], ....: final_states=[1], ....: determine_alphabets=True) sage: a1.intersection(a2) Traceback (most recent call last): ... ValueError: An epsilon-transition (with empty input) was found. sage: a1.remove_epsilon_transitions() # not tested (since not implemented yet) sage: a1.intersection(a2) # not tested
- is_equivalent(other)#
Test whether two automata are equivalent, i.e., accept the same language.
INPUT:
other
– anAutomaton
.
EXAMPLES:
sage: A = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 1)], ....: initial_states=[0], ....: final_states=[0]) sage: B = Automaton([('a', 'a', 0), ('a', 'b', 1), ('b', 'a', 1)], ....: initial_states=['a'], ....: final_states=['a']) sage: A.is_equivalent(B) True sage: B.add_transition('b', 'a', 0) Transition from 'b' to 'a': 0|- sage: A.is_equivalent(B) False
- language(max_length=None, **kwargs)#
Return all words accepted by this automaton.
INPUT:
max_length
– an integer orNone
(default). Only inputs of length at mostmax_length
will be considered. IfNone
, then this iterates over all possible words without length restrictions.kwargs
– will be passed on to theprocess iterator
. Seeprocess()
for a description.
OUTPUT:
An iterator.
EXAMPLES:
sage: NAF = Automaton( ....: {'A': [('A', 0), ('B', 1), ('B', -1)], ....: 'B': [('A', 0)]}, ....: initial_states=['A'], final_states=['A', 'B']) sage: list(NAF.language(3)) [[], [0], [-1], [1], [-1, 0], [0, 0], [1, 0], [0, -1], [0, 1], [-1, 0, 0], [0, -1, 0], [0, 0, 0], [0, 1, 0], [1, 0, 0], [-1, 0, -1], [-1, 0, 1], [0, 0, -1], [0, 0, 1], [1, 0, -1], [1, 0, 1]]
See also
- minimization(algorithm=None)#
Return the minimization of the input automaton as a new automaton.
INPUT:
algorithm
– Either Moore’s algorithm (byalgorithm='Moore'
or as default for deterministic automata) or Brzozowski’s algorithm (whenalgorithm='Brzozowski'
or when the automaton is not deterministic) is used.
OUTPUT:
A new automaton.
The resulting automaton is deterministic and has a minimal number of states.
EXAMPLES:
sage: A = Automaton([('A', 'A', 1), ('A', 'A', 0), ('A', 'B', 1), ....: ('B', 'C', 0), ('C', 'C', 1), ('C', 'C', 0)], ....: initial_states=['A'], final_states=['C']) sage: B = A.minimization(algorithm='Brzozowski') sage: B_trans = B.transitions(B.states()[1]) sage: B_trans # random [Transition from frozenset({frozenset({'B', 'C'}), frozenset({'A', 'C'}), frozenset({'A', 'B', 'C'})}) to frozenset({frozenset({'C'}), frozenset({'B', 'C'}), frozenset({'A', 'C'}), frozenset({'A', 'B', 'C'})}): 0|-, Transition from frozenset({frozenset({'B', 'C'}), frozenset({'A', 'C'}), frozenset({'A', 'B', 'C'})}) to frozenset({frozenset({'B', 'C'}), frozenset({'A', 'C'}), frozenset({'A', 'B', 'C'})}): 1|-] sage: len(B.states()) 3 sage: C = A.minimization(algorithm='Brzozowski') sage: C_trans = C.transitions(C.states()[1]) sage: B_trans == C_trans True sage: len(C.states()) 3
sage: aut = Automaton([('1', '2', 'a'), ('2', '3', 'b'), ....: ('3', '2', 'a'), ('2', '1', 'b'), ....: ('3', '4', 'a'), ('4', '3', 'b')], ....: initial_states=['1'], final_states=['1']) sage: min = aut.minimization(algorithm='Brzozowski') sage: [len(min.states()), len(aut.states())] [3, 4] sage: min = aut.minimization(algorithm='Moore') Traceback (most recent call last): ... NotImplementedError: Minimization via Moore's Algorithm is only implemented for deterministic finite state machines
- process(*args, **kwargs)#
Return whether the automaton accepts the input and the state where the computation stops.
INPUT:
input_tape
– the input tape can be a list or an iterable with entries from the input alphabet. If we are working with a multi-tape machine (see parameteruse_multitape_input
and notes below), then the tape is a list or tuple of tracks, each of which can be a list or an iterable with entries from the input alphabet.initial_state
orinitial_states
– the initial state(s) in which the machine starts. Either specify a single one withinitial_state
or a list of them withinitial_states
. If both are given,initial_state
will be appended toinitial_states
. If neither is specified, the initial states of the finite state machine are taken.list_of_outputs
– (default:None
) a boolean orNone
. IfTrue
, then the outputs are given in list form (even if we have no or only one single output). IfFalse
, then the result is never a list (an exception is raised if the result cannot be returned). Iflist_of_outputs=None
the method determines automatically what to do (e.g. if a non-deterministic machine returns more than one path, then the output is returned in list form).only_accepted
– (default:False
) a boolean. If set, then the first argument in the output is guaranteed to beTrue
(if the output is a list, then the first argument of each element will beTrue
).full_output
– (default:True
) a boolean. If set, then the full output is given, otherwise only whether the sequence is accepted or not (the first entry below only).always_include_output
– if set (not by default), always return a triple containing the (non-existing) output. This is in order to obtain output compatible with that ofFiniteStateMachine.process()
. If this parameter is set,full_output
has no effect.format_output
– a function that translates the written output (which is in form of a list) to something more readable. By default (None
) identity is used here.check_epsilon_transitions
– (default:True
) a boolean. IfFalse
, then epsilon transitions are not taken into consideration during process.write_final_word_out
– (default:True
) a boolean specifying whether the final output words should be written or not.use_multitape_input
– (default:False
) a boolean. IfTrue
, then the multi-tape mode of the process iterator is activated. See also the notes below for multi-tape machines.process_all_prefixes_of_input
– (default:False
) a boolean. IfTrue
, then each prefix of the input word is processed (instead of processing the whole input word at once). Consequently, there is an output generated for each of these prefixes.process_iterator_class
– (default:None
) a class inherited fromFSMProcessIterator
. IfNone
, thenFSMProcessIterator
is taken. An instance of this class is created and is used during the processing.
OUTPUT:
The full output is a pair (or a list of pairs, cf. parameter
list_of_outputs
), wherethe first entry is
True
if the input string is accepted andthe second gives the state reached after processing the input tape (This is a state with label
None
if the input could not be processed, i.e., if at one point no transition to go on could be found.).
If
full_output
isFalse
, then only the first entry is returned.If
always_include_output
is set, an additional third entry[]
is included.Note that in the case the automaton is not deterministic, all possible paths are taken into account. You can use
determinisation()
to get a deterministic automaton machine.This function uses an iterator which, in its simplest form, goes from one state to another in each step. To decide which way to go, it uses the input words of the outgoing transitions and compares them to the input tape. More precisely, in each step, the iterator takes an outgoing transition of the current state, whose input label equals the input letter of the tape.
If the choice of the outgoing transition is not unique (i.e., we have a non-deterministic finite state machine), all possibilities are followed. This is done by splitting the process into several branches, one for each of the possible outgoing transitions.
The process (iteration) stops if all branches are finished, i.e., for no branch, there is any transition whose input word coincides with the processed input tape. This can simply happen when the entire tape was read.
Also see
__call__()
for a version ofprocess()
with shortened output.Internally this function creates and works with an instance of
FSMProcessIterator
. This iterator can also be obtained withiter_process()
.If working with multi-tape finite state machines, all input words of transitions are words of \(k\)-tuples of letters. Moreover, the input tape has to consist of \(k\) tracks, i.e., be a list or tuple of \(k\) iterators, one for each track.
Warning
Working with multi-tape finite state machines is still experimental and can lead to wrong outputs.
EXAMPLES:
In the following examples, we construct an automaton which accepts non-adjacent forms (see also the example on non-adjacent forms in the documentation of the module Finite state machines, automata, transducers) and then test it by feeding it with several binary digit expansions.
sage: NAF = Automaton( ....: {'_': [('_', 0), ('1', 1)], '1': [('_', 0)]}, ....: initial_states=['_'], final_states=['_', '1']) sage: [NAF.process(w) for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [(True, '_'), (True, '1'), (False, None), (True, '1'), (False, None), (False, None)]
If we just want a condensed output, we use:
sage: [NAF.process(w, full_output=False) ....: for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False]
It is equivalent to:
sage: [NAF(w) for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False]
The following example illustrates the difference between non-existing paths and reaching a non-final state:
sage: NAF.process([2]) (False, None) sage: NAF.add_transition(('_', 's', 2)) Transition from '_' to 's': 2|- sage: NAF.process([2]) (False, 's')
A simple example of a (non-deterministic) multi-tape automaton is the following: It checks whether the two input tapes have the same number of ones:
sage: M = Automaton([('=', '=', (1, 1)), ....: ('=', '=', (None, 0)), ....: ('=', '=', (0, None)), ....: ('=', '<', (None, 1)), ....: ('<', '<', (None, 1)), ....: ('<', '<', (None, 0)), ....: ('=', '>', (1, None)), ....: ('>', '>', (1, None)), ....: ('>', '>', (0, None))], ....: initial_states=['='], ....: final_states=['=']) sage: M.process(([1, 0, 1], [1, 0]), use_multitape_input=True) (False, '>') sage: M.process(([0, 1, 0], [0, 1, 1]), use_multitape_input=True) (False, '<') sage: M.process(([1, 1, 0, 1], [0, 0, 1, 0, 1, 1]), ....: use_multitape_input=True) (True, '=')
Alternatively, we can use the following (non-deterministic) multi-tape automaton for the same check:
sage: N = Automaton([('=', '=', (0, 0)), ....: ('=', '<', (None, 1)), ....: ('<', '<', (0, None)), ....: ('<', '=', (1, None)), ....: ('=', '>', (1, None)), ....: ('>', '>', (None, 0)), ....: ('>', '=', (None, 1))], ....: initial_states=['='], ....: final_states=['=']) sage: N.process(([1, 0, 1], [1, 0]), use_multitape_input=True) (False, '>') sage: N.process(([0, 1, 0], [0, 1, 1]), use_multitape_input=True) (False, '<') sage: N.process(([1, 1, 0, 1], [0, 0, 1, 0, 1, 1]), ....: use_multitape_input=True) (True, '=')
- shannon_parry_markov_chain()#
Compute a time homogeneous Markov chain such that all words of a given length recognized by the original automaton occur as the output with the same weight; the transition probabilities correspond to the Parry measure.
OUTPUT:
A Markov chain. Its input labels are the transition probabilities, the output labels the labels of the original automaton. In order to obtain equal weight for all words of the same length, an “exit weight” is needed. It is stored in the attribute
color
of the states of the Markov chain. The weights of the words of the same length sum up to one up to an exponentially small error.The stationary distribution of this Markov chain is saved as the initial probabilities of the states.
The transition probabilities correspond to the Parry measure (see [S1948] and [P1964]).
The automaton is assumed to be deterministic, irreducible and aperiodic. All states must be final.
EXAMPLES:
sage: NAF = Automaton([(0, 0, 0), (0, 1, 1), (0, 1, -1), ....: (1, 0, 0)], initial_states=[0], ....: final_states=[0, 1]) sage: P_NAF = NAF.shannon_parry_markov_chain() sage: P_NAF.transitions() [Transition from 0 to 0: 1/2|0, Transition from 0 to 1: 1/4|1, Transition from 0 to 1: 1/4|-1, Transition from 1 to 0: 1|0] sage: for s in P_NAF.iter_states(): ....: print(s.color) 3/4 3/2
The stationary distribution is also computed and saved as the initial probabilities of the returned Markov chain:
sage: for s in P_NAF.states(): ....: print("{} {}".format(s, s.initial_probability)) 0 2/3 1 1/3
The automaton is assumed to be deterministic, irreducible and aperiodic:
sage: A = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 1), (1, 1, 0)], ....: initial_states=[0]) sage: A.shannon_parry_markov_chain() Traceback (most recent call last): ... NotImplementedError: Automaton must be strongly connected. sage: A = Automaton([(0, 0, 0), (0, 1, 0)], ....: initial_states=[0]) sage: A.shannon_parry_markov_chain() Traceback (most recent call last): ... NotImplementedError: Automaton must be deterministic. sage: A = Automaton([(0, 1, 0), (1, 0, 0)], ....: initial_states=[0]) sage: A.shannon_parry_markov_chain() Traceback (most recent call last): ... NotImplementedError: Automaton must be aperiodic.
All states must be final:
sage: A = Automaton([(0, 1, 0), (0, 0, 1), (1, 0, 0)], ....: initial_states=[0]) sage: A.shannon_parry_markov_chain() Traceback (most recent call last): ... NotImplementedError: All states must be final.
ALGORITHM:
See [HKP2015a], Lemma 4.1.
REFERENCES:
[HKP2015a]Clemens Heuberger, Sara Kropf, and Helmut Prodinger, Analysis of Carries in Signed Digit Expansions, arXiv 1503.08816.
[P1964]William Parry, Intrinsic Markov chains, Transactions of the American Mathematical Society 112, 1964, pp. 55-66. doi:10.1090/S0002-9947-1964-0161372-1.
[S1948]Claude E. Shannon, A mathematical theory of communication, The Bell System Technical Journal 27, 1948, 379-423, doi:10.1002/j.1538-7305.1948.tb01338.x.
- with_output(word_out_function=None)#
Construct a transducer out of this automaton.
INPUT:
word_out_function
– (default:None
) a function. It transforms atransition
to the output word for this transition.If this is
None
, then the output word will be equal to the input word of each transition.
OUTPUT:
A transducer.
EXAMPLES:
sage: A = Automaton([(0, 0, 'A'), (0, 1, 'B'), (1, 2, 'C')]) sage: T = A.with_output(); T Transducer with 3 states sage: T.transitions() [Transition from 0 to 0: 'A'|'A', Transition from 0 to 1: 'B'|'B', Transition from 1 to 2: 'C'|'C']
This result is in contrast to:
sage: Transducer(A).transitions() [Transition from 0 to 0: 'A'|-, Transition from 0 to 1: 'B'|-, Transition from 1 to 2: 'C'|-]
where no output labels are created.
Here is another example:
sage: T2 = A.with_output(lambda t: [c.lower() for c in t.word_in]) sage: T2.transitions() [Transition from 0 to 0: 'A'|'a', Transition from 0 to 1: 'B'|'b', Transition from 1 to 2: 'C'|'c']
We can obtain the same result by composing two transducers. As inner transducer of the composition, we use
with_output()
without the optional argumentword_out_function
(which makes the output of each transition equal to its input); as outer transducer we use amap-transducer
(for converting to lower case). This givessage: L = transducers.map(lambda x: x.lower(), ['A', 'B', 'C']) sage: L.composition(A.with_output()).relabeled().transitions() [Transition from 0 to 0: 'A'|'a', Transition from 0 to 1: 'B'|'b', Transition from 1 to 2: 'C'|'c']
See also
input_projection()
,output_projection()
,Transducer
,transducers.map()
.
- sage.combinat.finite_state_machine.FSMLetterSymbol(letter)#
Return a string associated to the input letter.
INPUT:
letter
– the input letter orNone
(representing the empty word).
OUTPUT:
If
letter
isNone
the symbol for the empty wordFSMEmptyWordSymbol
is returned, otherwise the string associated to the letter.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMLetterSymbol sage: FSMLetterSymbol(0) '0' sage: FSMLetterSymbol(None) '-'
- class sage.combinat.finite_state_machine.FSMProcessIterator(fsm, input_tape=None, initial_state=None, initial_states=[], use_multitape_input=False, check_epsilon_transitions=True, write_final_word_out=True, format_output=None, process_all_prefixes_of_input=False, **kwargs)#
Bases:
SageObject
,Iterator
This class takes an input, feeds it into a finite state machine (automaton or transducer, in particular), tests whether this was successful and calculates the written output.
INPUT:
fsm
– the finite state machine on which the input should be processed.input_tape
– the input tape can be a list or an iterable with entries from the input alphabet. If we are working with a multi-tape machine (see parameteruse_multitape_input
and notes below), then the tape is a list or tuple of tracks, each of which can be a list or an iterable with entries from the input alphabet.initial_state
orinitial_states
– the initial state(s) in which the machine starts. Either specify a single one withinitial_state
or a list of them withinitial_states
. If both are given,initial_state
will be appended toinitial_states
. If neither is specified, the initial states of the finite state machine are taken.format_output
– a function that translates the written output (which is in form of a list) to something more readable. By default (None
) identity is used here.check_epsilon_transitions
– (default:True
) a boolean. IfFalse
, then epsilon transitions are not taken into consideration during process.write_final_word_out
– (default:True
) a boolean specifying whether the final output words should be written or not.use_multitape_input
– (default:False
) a boolean. IfTrue
, then the multi-tape mode of the process iterator is activated. See also the notes below for multi-tape machines.process_all_prefixes_of_input
– (default:False
) a boolean. IfTrue
, then each prefix of the input word is processed (instead of processing the whole input word at once). Consequently, there is an output generated for each of these prefixes.
OUTPUT:
An iterator.
In its simplest form, it behaves like an iterator which, in each step, goes from one state to another. To decide which way to go, it uses the input words of the outgoing transitions and compares them to the input tape. More precisely, in each step, the process iterator takes an outgoing transition of the current state, whose input label equals the input letter of the tape. The output label of the transition, if present, is written on the output tape.
If the choice of the outgoing transition is not unique (i.e., we have a non-deterministic finite state machine), all possibilities are followed. This is done by splitting the process into several branches, one for each of the possible outgoing transitions.
The process (iteration) stops if all branches are finished, i.e., for no branch, there is any transition whose input word coincides with the processed input tape. This can simply happen when the entire tape was read. When the process stops, a
StopIteration
exception is thrown.Warning
Processing an input tape of length \(n\) usually takes at least \(n+1\) iterations, since there will be \(n+1\) states visited (in the case the taken transitions have input words consisting of single letters).
An instance of this class is generated when
FiniteStateMachine.process()
orFiniteStateMachine.iter_process()
of a finite state machine, an automaton, or a transducer is invoked.When working with multi-tape finite state machines, all input words of transitions are words of \(k\)-tuples of letters. Moreover, the input tape has to consist of \(k\) tracks, i.e., be a list or tuple of \(k\) iterators, one for each track.
Warning
Working with multi-tape finite state machines is still experimental and can lead to wrong outputs.
EXAMPLES:
The following transducer reads binary words and outputs a word, where blocks of ones are replaced by just a single one. Further only words that end with a zero are accepted.
sage: T = Transducer({'A': [('A', 0, 0), ('B', 1, None)], ....: 'B': [('B', 1, None), ('A', 0, [1, 0])]}, ....: initial_states=['A'], final_states=['A']) sage: input = [1, 1, 0, 0, 1, 0, 1, 1, 1, 0] sage: T.process(input) (True, 'A', [1, 0, 0, 1, 0, 1, 0])
The function
FiniteStateMachine.process()
(internally) uses aFSMProcessIterator
. We can do that manually, too, and get full access to the iteration process:sage: from sage.combinat.finite_state_machine import FSMProcessIterator sage: it = FSMProcessIterator(T, input_tape=input) sage: for current in it: ....: print(current) process (1 branch) + at state 'B' +-- tape at 1, [[]] process (1 branch) + at state 'B' +-- tape at 2, [[]] process (1 branch) + at state 'A' +-- tape at 3, [[1, 0]] process (1 branch) + at state 'A' +-- tape at 4, [[1, 0, 0]] process (1 branch) + at state 'B' +-- tape at 5, [[1, 0, 0]] process (1 branch) + at state 'A' +-- tape at 6, [[1, 0, 0, 1, 0]] process (1 branch) + at state 'B' +-- tape at 7, [[1, 0, 0, 1, 0]] process (1 branch) + at state 'B' +-- tape at 8, [[1, 0, 0, 1, 0]] process (1 branch) + at state 'B' +-- tape at 9, [[1, 0, 0, 1, 0]] process (1 branch) + at state 'A' +-- tape at 10, [[1, 0, 0, 1, 0, 1, 0]] process (0 branches) sage: it.result() [Branch(accept=True, state='A', output=[1, 0, 0, 1, 0, 1, 0])]
sage: T = Transducer([(0, 0, 0, 'a'), (0, 1, 0, 'b'), ....: (1, 2, 1, 'c'), (2, 0, 0, 'd'), ....: (2, 1, None, 'd')], ....: initial_states=[0], final_states=[2]) sage: sorted(T.process([0, 0, 1], format_output=lambda o: ''.join(o))) [(False, 1, 'abcd'), (True, 2, 'abc')] sage: it = FSMProcessIterator(T, input_tape=[0, 0, 1], ....: format_output=lambda o: ''.join(o)) sage: for current in it: ....: print(current) process (2 branches) + at state 0 +-- tape at 1, [['a']] + at state 1 +-- tape at 1, [['b']] process (2 branches) + at state 0 +-- tape at 2, [['a', 'a']] + at state 1 +-- tape at 2, [['a', 'b']] process (2 branches) + at state 1 +-- tape at 3, [['a', 'b', 'c', 'd']] + at state 2 +-- tape at 3, [['a', 'b', 'c']] process (0 branches) sage: sorted(it.result()) [Branch(accept=False, state=1, output='abcd'), Branch(accept=True, state=2, output='abc')]
See also
FiniteStateMachine.process()
,Automaton.process()
,Transducer.process()
,FiniteStateMachine.iter_process()
,FiniteStateMachine.__call__()
,next()
.- class Current#
Bases:
dict
This class stores the branches which have to be processed during iteration and provides a nicer formatting of them.
This class is derived from
dict
. It is returned by thenext
-function during iteration.EXAMPLES:
In the following example you can see the dict directly and then the nicer output provided by this class:
sage: from sage.combinat.finite_state_machine import FSMProcessIterator sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) sage: it = FSMProcessIterator(inverter, input_tape=[0, 1]) sage: for current in it: ....: print(dict(current)) ....: print(current) {((1, 0),): {'A': Branch(tape_cache=tape at 1, outputs=[[1]])}} process (1 branch) + at state 'A' +-- tape at 1, [[1]] {((2, 0),): {'A': Branch(tape_cache=tape at 2, outputs=[[1, 0]])}} process (1 branch) + at state 'A' +-- tape at 2, [[1, 0]] {} process (0 branches)
- class FinishedBranch(accept, state, output)#
Bases:
tuple
A
named tuple
representing the attributes of a branch, once it is fully processed.- accept#
Alias for field number 0
- output#
Alias for field number 2
- state#
Alias for field number 1
- next()#
Makes one step in processing the input tape.
INPUT:
Nothing.
OUTPUT:
It returns the current status of the iterator (see below). A
StopIteration
exception is thrown when there is/was nothing to do (i.e. all branches ended with previous call ofnext()
).The current status is a dictionary (encapsulated into an instance of
Current
). The keys are positions on the tape. The value corresponding to such a position is again a dictionary, where each entry represents a branch of the process. This dictionary maps the current state of a branch to a pair consisting of a tape cache and a list of output words, which were written during reaching this current state.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMProcessIterator sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) sage: it = FSMProcessIterator(inverter, input_tape=[0, 1]) sage: next(it) process (1 branch) + at state 'A' +-- tape at 1, [[1]] sage: next(it) process (1 branch) + at state 'A' +-- tape at 2, [[1, 0]] sage: next(it) process (0 branches) sage: next(it) Traceback (most recent call last): ... StopIteration
- preview_word(track_number=None, length=1, return_word=False)#
Read a word from the input tape.
INPUT:
track_number
– an integer orNone
. IfNone
, then a tuple of words (one from each track) is returned.length
– (default:1
) the length of the word(s).return_word
– (default:False
) a boolean. If set, then a word is returned, otherwise a single letter (in which caselength
has to be1
).
OUTPUT:
A single letter or a word.
An exception
StopIteration
is thrown if the tape (at least one track) has reached its end.Typically, this method is called from a hook-function of a state.
EXAMPLES:
sage: inverter = Transducer({'A': [('A', 0, 'one'), ....: ('A', 1, 'zero')]}, ....: initial_states=['A'], final_states=['A']) sage: def state_hook(process, state, output): ....: print("We are now in state %s." % (state.label(),)) ....: try: ....: w = process.preview_word() ....: except RuntimeError: ....: raise StopIteration ....: print("Next on the tape is a %s." % (w,)) sage: inverter.state('A').hook = state_hook sage: it = inverter.iter_process( ....: input_tape=[0, 1, 1], ....: check_epsilon_transitions=False) sage: for _ in it: ....: pass We are now in state A. Next on the tape is a 0. We are now in state A. Next on the tape is a 1. We are now in state A. Next on the tape is a 1. We are now in state A. sage: it.result() [Branch(accept=True, state='A', output=['one', 'zero', 'zero'])]
- result(format_output=None)#
Return the already finished branches during process.
INPUT:
format_output
– a function converting the output from list form to something more readable (default: output the list directly).
OUTPUT:
A list of triples
(accepted, state, output)
.See also the parameter
format_output
ofFSMProcessIterator
.EXAMPLES:
sage: inverter = Transducer({'A': [('A', 0, 'one'), ('A', 1, 'zero')]}, ....: initial_states=['A'], final_states=['A']) sage: it = inverter.iter_process(input_tape=[0, 1, 1]) sage: for _ in it: ....: pass sage: it.result() [Branch(accept=True, state='A', output=['one', 'zero', 'zero'])] sage: it.result(lambda L: ', '.join(L)) [(True, 'A', 'one, zero, zero')]
Using both the parameter
format_output
ofFSMProcessIterator
and the parameterformat_output
ofresult()
leads to concatenation of the two functions:sage: it = inverter.iter_process(input_tape=[0, 1, 1], ....: format_output=lambda L: ', '.join(L)) sage: for _ in it: ....: pass sage: it.result() [Branch(accept=True, state='A', output='one, zero, zero')] sage: it.result(lambda L: ', '.join(L)) [(True, 'A', 'o, n, e, ,, , z, e, r, o, ,, , z, e, r, o')]
- class sage.combinat.finite_state_machine.FSMState(label, word_out=None, is_initial=False, is_final=False, final_word_out=None, initial_probability=None, hook=None, color=None, allow_label_None=False)#
Bases:
SageObject
Class for a state of a finite state machine.
INPUT:
label
– the label of the state.word_out
– (default:None
) a word that is written when the state is reached.is_initial
– (default:False
)is_final
– (default:False
)final_word_out
– (default:None
) a word that is written when the state is reached as the last state of some input; only for final states.initial_probability
– (default:None
) The probability of starting in this state if it is a state of a Markov chain.hook
– (default:None
) A function which is called when the state is reached during processing input. It takes two input parameters: the first is the current state (to allow using the same hook for several states), the second is the current process iterator object (to have full access to everything; e.g. the next letter from the input tape can be read in). It can output the next transition, i.e. the transition to take next. If it returnsNone
the process iterator chooses. Moreover, this function can raise aStopIteration
exception to stop processing of a finite state machine the input immediately. See also the example below.color
– (default:None
) In order to distinguish states, they can be given an arbitrary “color” (an arbitrary object). This is used inFiniteStateMachine.equivalence_classes()
: states of different colors are never considered to be equivalent. Note thatAutomaton.determinisation()
requires thatcolor
is hashable.allow_label_None
– (default:False
) IfTrue
allows alsoNone
as label. Note that a state with labelNone
is used inFSMProcessIterator
.
OUTPUT:
A state of a finite state machine.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('state 1', word_out=0, is_initial=True) sage: A 'state 1' sage: A.label() 'state 1' sage: B = FSMState('state 2') sage: A == B False
We can also define a final output word of a final state which is used if the input of a transducer leads to this state. Such final output words are used in subsequential transducers.
sage: C = FSMState('state 3', is_final=True, final_word_out='end') sage: C.final_word_out ['end']
The final output word can be a single letter,
None
or a list of letters:sage: A = FSMState('A') sage: A.is_final = True sage: A.final_word_out = 2 sage: A.final_word_out [2] sage: A.final_word_out = [2, 3] sage: A.final_word_out [2, 3]
Only final states can have a final output word which is not
None
:sage: B = FSMState('B') sage: B.final_word_out is None True sage: B.final_word_out = 2 Traceback (most recent call last): ... ValueError: Only final states can have a final output word, but state B is not final.
Setting the
final_word_out
of a final state toNone
is the same as setting it to[]
and is also the default for a final state:sage: C = FSMState('C', is_final=True) sage: C.final_word_out [] sage: C.final_word_out = None sage: C.final_word_out [] sage: C.final_word_out = [] sage: C.final_word_out []
It is not allowed to use
None
as a label:sage: from sage.combinat.finite_state_machine import FSMState sage: FSMState(None) Traceback (most recent call last): ... ValueError: Label None reserved for a special state, choose another label.
This can be overridden by:
sage: FSMState(None, allow_label_None=True) None
Note that
Automaton.determinisation()
requires thatcolor
is hashable:sage: A = Automaton([[0, 0, 0]], initial_states=[0]) sage: A.state(0).color = [] sage: A.determinisation() Traceback (most recent call last): ... TypeError: unhashable type: 'list' sage: A.state(0).color = () sage: A.determinisation() Automaton with 1 state
We can use a hook function of a state to stop processing. This is done by raising a
StopIteration
exception. The following code demonstrates this:sage: T = Transducer([(0, 1, 9, 'a'), (1, 2, 9, 'b'), ....: (2, 3, 9, 'c'), (3, 4, 9, 'd')], ....: initial_states=[0], ....: final_states=[4], ....: input_alphabet=[9]) sage: def stop(process, state, output): ....: raise StopIteration() sage: T.state(3).hook = stop sage: T.process([9, 9, 9, 9]) (False, 3, ['a', 'b', 'c'])
- copy()#
Return a (shallow) copy of the state.
INPUT:
Nothing.
OUTPUT:
A new state.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') sage: A.is_initial = True sage: A.is_final = True sage: A.final_word_out = [1] sage: A.color = 'green' sage: A.initial_probability = 1/2 sage: B = copy(A) sage: B.fully_equal(A) True sage: A.label() is B.label() True sage: A.is_initial is B.is_initial True sage: A.is_final is B.is_final True sage: A.final_word_out is B.final_word_out True sage: A.color is B.color True sage: A.initial_probability is B.initial_probability True
- deepcopy(memo=None)#
Return a deep copy of the state.
INPUT:
memo
– (default:None
) a dictionary storing already processed elements.
OUTPUT:
A new state.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState((1, 3), color=[1, 2], ....: is_final=True, final_word_out=3, ....: initial_probability=1/3) sage: B = deepcopy(A) sage: B (1, 3) sage: B.label() == A.label() True sage: B.label is A.label False sage: B.color == A.color True sage: B.color is A.color False sage: B.is_final == A.is_final True sage: B.is_final is A.is_final True sage: B.final_word_out == A.final_word_out True sage: B.final_word_out is A.final_word_out False sage: B.initial_probability == A.initial_probability True
- property final_word_out#
The final output word of a final state which is written if the state is reached as the last state of the input of the finite state machine. For a non-final state, the value is
None
.final_word_out
can be a single letter, a list orNone
, but for a final-state, it is always saved as a list.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_final=True, final_word_out=2) sage: A.final_word_out [2] sage: A.final_word_out = 3 sage: A.final_word_out [3] sage: A.final_word_out = [3, 4] sage: A.final_word_out [3, 4] sage: A.final_word_out = None sage: A.final_word_out [] sage: B = FSMState('B') sage: B.final_word_out is None True
A non-final state cannot have a final output word:
sage: B.final_word_out = [3, 4] Traceback (most recent call last): ... ValueError: Only final states can have a final output word, but state B is not final.
- fully_equal(other, compare_color=True)#
Check whether two states are fully equal, i.e., including all attributes except
hook
.INPUT:
self
– a state.other
– a state.compare_color
– IfTrue
(default) colors are compared as well, otherwise not.
OUTPUT:
True
orFalse
.Note that usual comparison by
==
does only compare the labels.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') sage: B = FSMState('A', is_initial=True) sage: A.fully_equal(B) False sage: A == B True sage: A.is_initial = True; A.color = 'green' sage: A.fully_equal(B) False sage: A.fully_equal(B, compare_color=False) True
- initial_probability = None#
- property is_final#
Describes whether the state is final or not.
True
if the state is final andFalse
otherwise.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_final=True, final_word_out=3) sage: A.is_final True sage: A.is_final = False Traceback (most recent call last): ... ValueError: State A cannot be non-final, because it has a final output word. Only final states can have a final output word. sage: A.final_word_out = None sage: A.is_final = False sage: A.is_final False
- is_initial = False#
- label()#
Return the label of the state.
INPUT:
Nothing.
OUTPUT:
The label of the state.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('state') sage: A.label() 'state'
- relabeled(label, memo=None)#
Return a deep copy of the state with a new label.
INPUT:
label
– the label of new state.memo
– (default:None
) a dictionary storing already processed elements.
OUTPUT:
A new state.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') sage: A.relabeled('B') 'B'
- class sage.combinat.finite_state_machine.FSMTransition(from_state, to_state, word_in=None, word_out=None, hook=None)#
Bases:
SageObject
Class for a transition of a finite state machine.
INPUT:
from_state
– state from which transition starts.to_state
– state in which transition ends.word_in
– the input word of the transitions (when the finite state machine is used as automaton)word_out
– the output word of the transitions (when the finite state machine is used as transducer)
OUTPUT:
A transition of a finite state machine.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition sage: A = FSMState('A') sage: B = FSMState('B') sage: S = FSMTransition(A, B, 0, 1) sage: T = FSMTransition('A', 'B', 0, 1) sage: T == S True sage: U = FSMTransition('A', 'B', 0) sage: U == T False
- copy()#
Return a (shallow) copy of the transition.
OUTPUT:
A new transition.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMTransition sage: t = FSMTransition('A', 'B', 0) sage: copy(t) Transition from 'A' to 'B': 0|-
- deepcopy(memo=None)#
Return a deep copy of the transition.
INPUT:
memo
– (default:None
) a dictionary storing already processed elements.
OUTPUT:
A new transition.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMTransition sage: t = FSMTransition('A', 'B', 0) sage: deepcopy(t) Transition from 'A' to 'B': 0|-
- from_state = None#
State from which the transition starts. Read-only.
- to_state = None#
State in which the transition ends. Read-only.
- word_in = None#
Input word of the transition. Read-only.
- word_out = None#
Output word of the transition. Read-only.
- sage.combinat.finite_state_machine.FSMWordSymbol(word)#
Return a string of
word
. It may returns the symbol of the empty wordFSMEmptyWordSymbol
.INPUT:
word
– the input word.
OUTPUT:
A string of
word
.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMWordSymbol sage: FSMWordSymbol([0, 1, 1]) '0,1,1'
- class sage.combinat.finite_state_machine.FiniteStateMachine(data=None, initial_states=None, final_states=None, input_alphabet=None, output_alphabet=None, determine_alphabets=None, with_final_word_out=None, store_states_dict=True, on_duplicate_transition=None)#
Bases:
SageObject
Class for a finite state machine.
A finite state machine is a finite set of states connected by transitions.
INPUT:
data
– can be any of the following:a dictionary of dictionaries (of transitions),
a dictionary of lists (of states or transitions),
a list (of transitions),
a function (transition function),
an other instance of a finite state machine.
initial_states
andfinal_states
– the initial and final states of this machineinput_alphabet
andoutput_alphabet
– the input and output alphabets of this machinedetermine_alphabets
– IfTrue
, then the functiondetermine_alphabets()
is called afterdata
was read and processed, ifFalse
, then not. If it isNone
, then it is decided during the construction of the finite state machine whetherdetermine_alphabets()
should be called.with_final_word_out
– If given (notNone
), then the functionwith_final_word_out()
(more precisely, its inplace pendantconstruct_final_word_out()
) is called with inputletters=with_final_word_out
at the end of the creation process.store_states_dict
– IfTrue
, then additionally the states are stored in an internal dictionary for speed up.on_duplicate_transition
– A function which is called when a transition is inserted intoself
which already existed (samefrom_state
, sameto_state
, sameword_in
, sameword_out
).This function is assumed to take two arguments, the first being the already existing transition, the second being the new transition (as an
FSMTransition
). The function must return the (possibly modified) original transition.By default, we have
on_duplicate_transition=None
, which is interpreted ason_duplicate_transition=duplicate_transition_ignore
, whereduplicate_transition_ignore
is a predefined function ignoring the occurrence. Other such predefined functions areduplicate_transition_raise_error
andduplicate_transition_add_input
.
OUTPUT:
A finite state machine.
The object creation of
Automaton
andTransducer
is the same as the one described here (i.e. just replace the wordFiniteStateMachine
byAutomaton
orTransducer
).Each transition of an automaton has an input label. Automata can, for example, be determinised (see
Automaton.determinisation()
) and minimized (seeAutomaton.minimization()
). Each transition of a transducer has an input and an output label. Transducers can, for example, be simplified (seeTransducer.simplification()
).EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition
See documentation for more examples.
We illustrate the different input formats:
The input-data can be a dictionary of dictionaries, where
the keys of the outer dictionary are state-labels (from-states of transitions),
the keys of the inner dictionaries are state-labels (to-states of transitions),
the values of the inner dictionaries specify the transition more precisely.
The easiest is to use a tuple consisting of an input and an output word:
sage: FiniteStateMachine({'a':{'b':(0, 1), 'c':(1, 1)}}) Finite state machine with 3 states
Instead of the tuple anything iterable (e.g. a list) can be used as well.
If you want to use the arguments of
FSMTransition
directly, you can use a dictionary:sage: FiniteStateMachine({'a':{'b':{'word_in':0, 'word_out':1}, ....: 'c':{'word_in':1, 'word_out':1}}}) Finite state machine with 3 states
In the case you already have instances of
FSMTransition
, it is possible to use them directly:sage: FiniteStateMachine({'a':{'b':FSMTransition('a', 'b', 0, 1), ....: 'c':FSMTransition('a', 'c', 1, 1)}}) Finite state machine with 3 states
The input-data can be a dictionary of lists, where the keys are states or label of states.
The list-elements can be states:
sage: a = FSMState('a') sage: b = FSMState('b') sage: c = FSMState('c') sage: FiniteStateMachine({a:[b, c]}) Finite state machine with 3 states
Or the list-elements can simply be labels of states:
sage: FiniteStateMachine({'a':['b', 'c']}) Finite state machine with 3 states
The list-elements can also be transitions:
sage: FiniteStateMachine({'a':[FSMTransition('a', 'b', 0, 1), ....: FSMTransition('a', 'c', 1, 1)]}) Finite state machine with 3 states
Or they can be tuples of a label, an input word and an output word specifying a transition:
sage: FiniteStateMachine({'a':[('b', 0, 1), ('c', 1, 1)]}) Finite state machine with 3 states
The input-data can be a list, where its elements specify transitions:
sage: FiniteStateMachine([FSMTransition('a', 'b', 0, 1), ....: FSMTransition('a', 'c', 1, 1)]) Finite state machine with 3 states
It is possible to skip
FSMTransition
in the example above:sage: FiniteStateMachine([('a', 'b', 0, 1), ('a', 'c', 1, 1)]) Finite state machine with 3 states
The parameters of the transition are given in tuples. Anyhow, anything iterable (e.g. a list) is possible.
You can also name the parameters of the transition. For this purpose you take a dictionary:
sage: FiniteStateMachine([{'from_state':'a', 'to_state':'b', ....: 'word_in':0, 'word_out':1}, ....: {'from_state':'a', 'to_state':'c', ....: 'word_in':1, 'word_out':1}]) Finite state machine with 3 states
Other arguments, which
FSMTransition
accepts, can be added, too.The input-data can also be function acting as transition function:
This function has two input arguments:
a label of a state (from which the transition starts),
a letter of the (input-)alphabet (as input-label of the transition).
It returns a tuple with the following entries:
a label of a state (to which state the transition goes),
a letter of or a word over the (output-)alphabet (as output-label of the transition).
It may also output a list of such tuples if several transitions from the from-state and the input letter exist (this means that the finite state machine is non-deterministic).
If the transition does not exist, the function should raise a
LookupError
or return an empty list.When constructing a finite state machine in this way, some initial states and an input alphabet have to be specified.
sage: def f(state_from, read): ....: if int(state_from) + read <= 2: ....: state_to = 2*int(state_from)+read ....: write = 0 ....: else: ....: state_to = 2*int(state_from) + read - 5 ....: write = 1 ....: return (str(state_to), write) sage: F = FiniteStateMachine(f, input_alphabet=[0, 1], ....: initial_states=['0'], ....: final_states=['0']) sage: F([1, 0, 1]) (True, '0', [0, 0, 1])
The input-data can be an other instance of a finite state machine:
sage: F = FiniteStateMachine() sage: G = Transducer(F) sage: G == F True
The other parameters cannot be specified in that case. If you want to change these, use the attributes
FSMState.is_initial
,FSMState.is_final
,input_alphabet
,output_alphabet
,on_duplicate_transition
and methodsdetermine_alphabets()
,construct_final_word_out()
on the new machine, respectively.
The following examples demonstrate the use of
on_duplicate_transition
:sage: F = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]]) sage: F.transitions() [Transition from 'a' to 'a': 1/2|-]
sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error sage: F1 = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]], ....: on_duplicate_transition=duplicate_transition_raise_error) Traceback (most recent call last): ... ValueError: Attempting to re-insert transition Transition from 'a' to 'a': 1/2|-
Use
duplicate_transition_add_input
to emulate a Markov chain, the input labels are considered as transition probabilities:sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input sage: F = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]], ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.transitions() [Transition from 'a' to 'a': 1|-]
Use
with_final_word_out
to construct final output:sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 0)], ....: initial_states=[0], ....: final_states=[0], ....: with_final_word_out=0) sage: for s in T.iter_final_states(): ....: print("{} {}".format(s, s.final_word_out)) 0 [] 1 [0]
- __call__(*args, **kwargs)#
Call either method
composition()
orprocess()
(withfull_output=False
). If the input is not finite (is_finite
of input isFalse
), theniter_process()
(withiterator_type='simple'
) is called. Moreover, the flagautomatic_output_type
is set (unlessformat_output
is specified). See the documentation of these functions for possible parameters.EXAMPLES:
The following code performs a
composition()
:sage: F = Transducer([('A', 'B', 1, 0), ('B', 'B', 1, 1), ....: ('B', 'B', 0, 0)], ....: initial_states=['A'], final_states=['B']) sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0), ....: (2, 2, 0, 1), (2, 1, 1, 1)], ....: initial_states=[1], final_states=[1]) sage: H = G(F) sage: H.states() [('A', 1), ('B', 1), ('B', 2)]
An automaton or transducer can also act on an input (an list or other iterable of letters):
sage: binary_inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) sage: binary_inverter([0, 1, 0, 0, 1, 1]) [1, 0, 1, 1, 0, 0]
We can also let them act on words:
sage: W = Words([0, 1]); W Finite and infinite words over {0, 1} sage: binary_inverter(W([0, 1, 1, 0, 1, 1])) word: 100100
Infinite words work as well:
sage: words.FibonacciWord() word: 0100101001001010010100100101001001010010... sage: binary_inverter(words.FibonacciWord()) word: 1011010110110101101011011010110110101101...
When only one successful path is found in a non-deterministic transducer, the result of that path is returned.
sage: T = Transducer([(0, 1, 0, 1), (0, 2, 0, 2)], ....: initial_states=[0], final_states=[1]) sage: T.process([0]) [(False, 2, [2]), (True, 1, [1])] sage: T([0]) [1]
- accessible_components()#
Return a new finite state machine with the accessible states of self and all transitions between those states.
INPUT:
Nothing.
OUTPUT:
A finite state machine with the accessible states of self and all transitions between those states.
A state is accessible if there is a directed path from an initial state to the state. If self has no initial states then a copy of the finite state machine self is returned.
EXAMPLES:
sage: F = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 0), (1, 0, 1)], ....: initial_states=[0]) sage: F.accessible_components() Automaton with 2 states
sage: F = Automaton([(0, 0, 1), (0, 0, 1), (1, 1, 0), (1, 0, 1)], ....: initial_states=[0]) sage: F.accessible_components() Automaton with 1 state
See also
- add_from_transition_function(function, initial_states=None, explore_existing_states=True)#
Constructs a finite state machine from a transition function.
INPUT:
function
may return a tuple (new_state, output_word) or a list of such tuples.initial_states
– If no initial states are given, the already existing initial states of self are taken.If
explore_existing_states
is True (default), then already existing states in self (e.g. already given final states) will also be processed if they are reachable from the initial states.
OUTPUT:
Nothing.
EXAMPLES:
sage: F = FiniteStateMachine(initial_states=['A'], ....: input_alphabet=[0, 1]) sage: def f(state, input): ....: return [('A', input), ('B', 1-input)] sage: F.add_from_transition_function(f) sage: F.transitions() [Transition from 'A' to 'A': 0|0, Transition from 'A' to 'B': 0|1, Transition from 'A' to 'A': 1|1, Transition from 'A' to 'B': 1|0, Transition from 'B' to 'A': 0|0, Transition from 'B' to 'B': 0|1, Transition from 'B' to 'A': 1|1, Transition from 'B' to 'B': 1|0]
Initial states can also be given as a parameter:
sage: F = FiniteStateMachine(input_alphabet=[0,1]) sage: def f(state, input): ....: return [('A', input), ('B', 1-input)] sage: F.add_from_transition_function(f,initial_states=['A']) sage: F.initial_states() ['A']
Already existing states in the finite state machine (the final states in the example below) are also explored:
sage: F = FiniteStateMachine(initial_states=[0], ....: final_states=[1], ....: input_alphabet=[0]) sage: def transition_function(state, letter): ....: return 1 - state, [] sage: F.add_from_transition_function(transition_function) sage: F.transitions() [Transition from 0 to 1: 0|-, Transition from 1 to 0: 0|-]
If
explore_existing_states=False
, however, this behavior is turned off, i.e., already existing states are not explored:sage: F = FiniteStateMachine(initial_states=[0], ....: final_states=[1], ....: input_alphabet=[0]) sage: def transition_function(state, letter): ....: return 1 - state, [] sage: F.add_from_transition_function(transition_function, ....: explore_existing_states=False) sage: F.transitions() [Transition from 0 to 1: 0|-]
- add_state(state)#
Adds a state to the finite state machine and returns the new state. If the state already exists, that existing state is returned.
INPUT:
state
is either an instance ofFSMState
or, otherwise, a label of a state.
OUTPUT:
The new or existing state.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: F = FiniteStateMachine() sage: A = FSMState('A', is_initial=True) sage: F.add_state(A) 'A'
- add_states(states)#
Adds several states. See add_state for more information.
INPUT:
states
– a list of states or iterator over states.
OUTPUT:
Nothing.
EXAMPLES:
sage: F = FiniteStateMachine() sage: F.add_states(['A', 'B']) sage: F.states() ['A', 'B']
- add_transition(*args, **kwargs)#
Adds a transition to the finite state machine and returns the new transition.
If the transition already exists, the return value of
self.on_duplicate_transition
is returned. See the documentation ofFiniteStateMachine
.INPUT:
The following forms are all accepted:
sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition sage: A = FSMState('A') sage: B = FSMState('B') sage: FSM = FiniteStateMachine() sage: FSM.add_transition(FSMTransition(A, B, 0, 1)) Transition from 'A' to 'B': 0|1 sage: FSM = FiniteStateMachine() sage: FSM.add_transition(A, B, 0, 1) Transition from 'A' to 'B': 0|1 sage: FSM = FiniteStateMachine() sage: FSM.add_transition(A, B, word_in=0, word_out=1) Transition from 'A' to 'B': 0|1 sage: FSM = FiniteStateMachine() sage: FSM.add_transition('A', 'B', {'word_in': 0, 'word_out': 1}) Transition from 'A' to 'B': {'word_in': 0, 'word_out': 1}|- sage: FSM = FiniteStateMachine() sage: FSM.add_transition(from_state=A, to_state=B, ....: word_in=0, word_out=1) Transition from 'A' to 'B': 0|1 sage: FSM = FiniteStateMachine() sage: FSM.add_transition({'from_state': A, 'to_state': B, ....: 'word_in': 0, 'word_out': 1}) Transition from 'A' to 'B': 0|1 sage: FSM = FiniteStateMachine() sage: FSM.add_transition((A, B, 0, 1)) Transition from 'A' to 'B': 0|1 sage: FSM = FiniteStateMachine() sage: FSM.add_transition([A, B, 0, 1]) Transition from 'A' to 'B': 0|1
If the states
A
andB
are not instances ofFSMState
, then it is assumed that they are labels of states.OUTPUT:
The new transition.
- add_transitions_from_function(function, labels_as_input=True)#
Adds one or more transitions if
function(state, state)
says that there are some.INPUT:
function
– a transition function. Given two statesfrom_state
andto_state
(or their labels iflabel_as_input
is true), this function shall return a tuple(word_in, word_out)
to add a transition fromfrom_state
toto_state
with input and output labelsword_in
andword_out
, respectively. If no such addition is to be added, the transition function shall returnNone
. The transition function may also return a list of such tuples in order to add multiple transitions between the pair of states.label_as_input
– (default:True
)
OUTPUT:
Nothing.
EXAMPLES:
sage: F = FiniteStateMachine() sage: F.add_states(['A', 'B', 'C']) sage: def f(state1, state2): ....: if state1 == 'C': ....: return None ....: return (0, 1) sage: F.add_transitions_from_function(f) sage: len(F.transitions()) 6
Multiple transitions are also possible:
sage: F = FiniteStateMachine() sage: F.add_states([0, 1]) sage: def f(state1, state2): ....: if state1 != state2: ....: return [(0, 1), (1, 0)] ....: else: ....: return None sage: F.add_transitions_from_function(f) sage: F.transitions() [Transition from 0 to 1: 0|1, Transition from 0 to 1: 1|0, Transition from 1 to 0: 0|1, Transition from 1 to 0: 1|0]
- adjacency_matrix(input=None, entry=None)#
Return the adjacency matrix of the underlying graph.
INPUT:
input
– Only transitions with input labelinput
are respected.entry
– The functionentry
takes a transition and the return value is written in the matrix as the entry(transition.from_state, transition.to_state)
. The default value (None
) of entry takes the variablex
to the power of the sum of the output word of the transition.
OUTPUT:
A matrix.
If any label of a state is not an integer, the finite state machine is relabeled at the beginning. If there are more than one transitions between two states, then the different return values of
entry
are added up.EXAMPLES:
sage: B = FiniteStateMachine({0:{0:(0, 0), 'a':(1, 0)}, ....: 'a':{2:(0, 0), 3:(1, 0)}, ....: 2:{0:(1, 1), 4:(0, 0)}, ....: 3:{'a':(0, 1), 2:(1, 1)}, ....: 4:{4:(1, 1), 3:(0, 1)}}, ....: initial_states=[0]) sage: B.adjacency_matrix() # optional - sage.symbolic [1 1 0 0 0] [0 0 1 1 0] [x 0 0 0 1] [0 x x 0 0] [0 0 0 x x]
This is equivalent to:
sage: matrix(B) # optional - sage.symbolic [1 1 0 0 0] [0 0 1 1 0] [x 0 0 0 1] [0 x x 0 0] [0 0 0 x x]
It is also possible to use other entries in the adjacency matrix:
sage: B.adjacency_matrix(entry=(lambda transition: 1)) [1 1 0 0 0] [0 0 1 1 0] [1 0 0 0 1] [0 1 1 0 0] [0 0 0 1 1] sage: var('t') # optional - sage.symbolic t sage: B.adjacency_matrix(1, entry=(lambda transition: # optional - sage.symbolic ....: exp(I*transition.word_out[0]*t))) [ 0 1 0 0 0] [ 0 0 0 1 0] [e^(I*t) 0 0 0 0] [ 0 0 e^(I*t) 0 0] [ 0 0 0 0 e^(I*t)] sage: a = Automaton([(0, 1, 0), ....: (1, 2, 0), ....: (2, 0, 1), ....: (2, 1, 0)], ....: initial_states=[0], ....: final_states=[0]) sage: a.adjacency_matrix() # optional - sage.symbolic [0 1 0] [0 0 1] [1 1 0]
- asymptotic_moments(variable=None)#
Return the main terms of expectation and variance of the sum of output labels and its covariance with the sum of input labels.
INPUT:
variable
– a symbol denoting the length of the input, by default \(n\).
OUTPUT:
A dictionary consisting of
expectation
– \(e n + \operatorname{Order}(1)\),variance
– \(v n + \operatorname{Order}(1)\),covariance
– \(c n + \operatorname{Order}(1)\)
for suitable constants \(e\), \(v\) and \(c\).
Assume that all input and output labels are numbers and that
self
is complete and has only one final component. Assume further that this final component is aperiodic. Furthermore, assume that there is exactly one initial state and that all states are final.Denote by \(X_n\) the sum of output labels written by the finite state machine when reading a random input word of length \(n\) over the input alphabet (assuming equidistribution).
Then the expectation of \(X_n\) is \(en+O(1)\), the variance of \(X_n\) is \(vn+O(1)\) and the covariance of \(X_n\) and the sum of input labels is \(cn+O(1)\), cf. [HKW2015], Theorem 3.9.
In the case of non-integer input or output labels, performance degrades significantly. For rational input and output labels, consider rescaling to integers. This limitation comes from the fact that determinants over polynomial rings can be computed much more efficiently than over the symbolic ring. In fact, we compute (parts) of a trivariate generating function where the input and output labels are exponents of some indeterminates, see [HKW2015], Theorem 3.9 for details. If those exponents are integers, we can use a polynomial ring.
EXAMPLES:
A trivial example: write the negative of the input:
sage: T = Transducer([(0, 0, 0, 0), (0, 0, 1, -1)], ....: initial_states=[0], ....: final_states=[0]) sage: T([0, 1, 1]) [0, -1, -1] sage: moments = T.asymptotic_moments() # optional - sage.symbolic sage: moments['expectation'] # optional - sage.symbolic -1/2*n + Order(1) sage: moments['variance'] # optional - sage.symbolic 1/4*n + Order(1) sage: moments['covariance'] # optional - sage.symbolic -1/4*n + Order(1)
For the case of the Hamming weight of the non-adjacent-form (NAF) of integers, cf. the Wikipedia article Non-adjacent_form and the example on recognizing NAFs, the following agrees with the results in [HP2007].
We first use the transducer to convert the standard binary expansion to the NAF given in [HP2007]. We use the parameter
with_final_word_out
such that we do not have to add sufficiently many trailing zeros:sage: NAF = Transducer([(0, 0, 0, 0), ....: (0, '.1', 1, None), ....: ('.1', 0, 0, [1, 0]), ....: ('.1', 1, 1, [-1, 0]), ....: (1, 1, 1, 0), ....: (1, '.1', 0, None)], ....: initial_states=[0], ....: final_states=[0], ....: with_final_word_out=[0])
As an example, we compute the NAF of \(27\) by this transducer.
sage: binary_27 = 27.bits() sage: binary_27 [1, 1, 0, 1, 1] sage: NAF_27 = NAF(binary_27) sage: NAF_27 [-1, 0, -1, 0, 0, 1, 0] sage: ZZ(NAF_27, base=2) 27
Next, we are only interested in the Hamming weight:
sage: def weight(state, input): ....: if input is None: ....: result = 0 ....: else: ....: result = ZZ(input != 0) ....: return (0, result) sage: weight_transducer = Transducer(weight, ....: input_alphabet=[-1, 0, 1], ....: initial_states=[0], ....: final_states=[0]) sage: NAFweight = weight_transducer.composition(NAF) sage: NAFweight.transitions() [Transition from (0, 0) to (0, 0): 0|0, Transition from (0, 0) to ('.1', 0): 1|-, Transition from ('.1', 0) to (0, 0): 0|1,0, Transition from ('.1', 0) to (1, 0): 1|1,0, Transition from (1, 0) to ('.1', 0): 0|-, Transition from (1, 0) to (1, 0): 1|0] sage: NAFweight(binary_27) [1, 0, 1, 0, 0, 1, 0]
Now, we actually compute the asymptotic moments:
sage: moments = NAFweight.asymptotic_moments() # optional - sage.symbolic sage: moments['expectation'] # optional - sage.symbolic 1/3*n + Order(1) sage: moments['variance'] # optional - sage.symbolic 2/27*n + Order(1) sage: moments['covariance'] # optional - sage.symbolic Order(1)
This is Example 3.16 in [HKW2015], where a transducer with variable output labels is given. There, the aim was to choose the output labels of this very simple transducer such that the input and output sum are asymptotically independent, i.e., the constant \(c\) vanishes.
sage: var('a_1, a_2, a_3, a_4') # optional - sage.symbolic (a_1, a_2, a_3, a_4) sage: T = Transducer([[0, 0, 0, a_1], [0, 1, 1, a_3], # optional - sage.symbolic ....: [1, 0, 0, a_4], [1, 1, 1, a_2]], ....: initial_states=[0], final_states=[0, 1]) sage: moments = T.asymptotic_moments() # optional - sage.symbolic verbose 0 (...) Non-integer output weights lead to significant performance degradation. sage: moments['expectation'] # optional - sage.symbolic 1/4*(a_1 + a_2 + a_3 + a_4)*n + Order(1) sage: moments['covariance'] # optional - sage.symbolic -1/4*(a_1 - a_2)*n + Order(1)
Therefore, the asymptotic covariance vanishes if and only if \(a_2=a_1\).
This is Example 4.3 in [HKW2015], dealing with the transducer converting the binary expansion of an integer into Gray code (cf. the Wikipedia article Gray_code and the example on Gray code):
sage: moments = transducers.GrayCode().asymptotic_moments() # optional - sage.symbolic sage: moments['expectation'] # optional - sage.symbolic 1/2*n + Order(1) sage: moments['variance'] # optional - sage.symbolic 1/4*n + Order(1) sage: moments['covariance'] # optional - sage.symbolic Order(1)
This is the first part of Example 4.4 in [HKW2015], counting the number of 10 blocks in the standard binary expansion. The least significant digit is at the left-most position:
sage: block10 = transducers.CountSubblockOccurrences( ....: [1, 0], ....: input_alphabet=[0, 1]) sage: sorted(block10.transitions()) [Transition from () to (): 0|0, Transition from () to (1,): 1|0, Transition from (1,) to (): 0|1, Transition from (1,) to (1,): 1|0] sage: moments = block10.asymptotic_moments() # optional - sage.symbolic sage: moments['expectation'] # optional - sage.symbolic 1/4*n + Order(1) sage: moments['variance'] # optional - sage.symbolic 1/16*n + Order(1) sage: moments['covariance'] # optional - sage.symbolic Order(1)
This is the second part of Example 4.4 in [HKW2015], counting the number of 11 blocks in the standard binary expansion. The least significant digit is at the left-most position:
sage: block11 = transducers.CountSubblockOccurrences( ....: [1, 1], ....: input_alphabet=[0, 1]) sage: sorted(block11.transitions()) [Transition from () to (): 0|0, Transition from () to (1,): 1|0, Transition from (1,) to (): 0|0, Transition from (1,) to (1,): 1|1] sage: var('N') # optional - sage.symbolic N sage: moments = block11.asymptotic_moments(N) # optional - sage.symbolic sage: moments['expectation'] # optional - sage.symbolic 1/4*N + Order(1) sage: moments['variance'] # optional - sage.symbolic 5/16*N + Order(1) sage: correlation = (moments['covariance'].coefficient(N) / # optional - sage.symbolic ....: (1/2 * sqrt(moments['variance'].coefficient(N)))) sage: correlation # optional - sage.symbolic 2/5*sqrt(5)
This is Example 4.5 in [HKW2015], counting the number of 01 blocks minus the number of 10 blocks in the standard binary expansion. The least significant digit is at the left-most position:
sage: block01 = transducers.CountSubblockOccurrences( ....: [0, 1], ....: input_alphabet=[0, 1]) sage: product_01x10 = block01.cartesian_product(block10) sage: block_difference = transducers.sub([0, 1])(product_01x10) sage: T = block_difference.simplification().relabeled() sage: T.transitions() [Transition from 0 to 2: 0|-1, Transition from 0 to 0: 1|0, Transition from 1 to 2: 0|0, Transition from 1 to 0: 1|0, Transition from 2 to 2: 0|0, Transition from 2 to 0: 1|1] sage: moments = T.asymptotic_moments() # optional - sage.symbolic sage: moments['expectation'] # optional - sage.symbolic Order(1) sage: moments['variance'] # optional - sage.symbolic Order(1) sage: moments['covariance'] # optional - sage.symbolic Order(1)
The finite state machine must have a unique final component:
sage: T = Transducer([(0, -1, -1, -1), (0, 1, 1, 1), ....: (-1, -1, -1, -1), (-1, -1, 1, -1), ....: (1, 1, -1, 1), (1, 1, 1, 1)], ....: initial_states=[0], ....: final_states=[0, 1, -1]) sage: T.asymptotic_moments() Traceback (most recent call last): ... NotImplementedError: asymptotic_moments is only implemented for finite state machines with one final component.
In this particular example, the first letter of the input decides whether we reach the loop at \(-1\) or the loop at \(1\). In the first case, we have \(X_n = -n\), while we have \(X_n = n\) in the second case. Therefore, the expectation \(E(X_n)\) of \(X_n\) is \(E(X_n) = 0\). We get \((X_n-E(X_n))^2 = n^2\) in all cases, which results in a variance of \(n^2\).
So this example shows that the variance may be non-linear if there is more than one final component.
ALGORITHM:
See [HKW2015], Theorem 3.9.
REFERENCES:
[HP2007] (1,2)Clemens Heuberger and Helmut Prodinger, The Hamming Weight of the Non-Adjacent-Form under Various Input Statistics, Periodica Mathematica Hungarica Vol. 55 (1), 2007, pp. 81–96, doi:10.1007/s10998-007-3081-z.
- coaccessible_components()#
Return the sub-machine induced by the coaccessible states of this finite state machine.
OUTPUT:
A finite state machine of the same type as this finite state machine.
EXAMPLES:
sage: A = automata.ContainsWord([1, 1], ....: input_alphabet=[0, 1]).complement().minimization().relabeled() sage: A.transitions() [Transition from 0 to 0: 0|-, Transition from 0 to 0: 1|-, Transition from 1 to 2: 0|-, Transition from 1 to 0: 1|-, Transition from 2 to 2: 0|-, Transition from 2 to 1: 1|-] sage: A.initial_states() [2] sage: A.final_states() [1, 2] sage: C = A.coaccessible_components() sage: C.transitions() [Transition from 1 to 2: 0|-, Transition from 2 to 2: 0|-, Transition from 2 to 1: 1|-]
- completion(sink=None)#
Return a completion of this finite state machine.
INPUT:
sink
– either an instance ofFSMState
or a label for the sink (default:None
). IfNone
, the least available non-zero integer is used.
OUTPUT:
A
FiniteStateMachine
of the same type as this finite state machine.The resulting finite state machine is a complete version of this finite state machine. A finite state machine is considered to be complete if each transition has an input label of length one and for each pair \((q, a)\) where \(q\) is a state and \(a\) is an element of the input alphabet, there is exactly one transition from \(q\) with input label \(a\).
If this finite state machine is already complete, a deep copy is returned. Otherwise, a new non-final state (usually called a sink) is created and transitions to this sink are introduced as appropriate.
EXAMPLES:
sage: F = FiniteStateMachine([(0, 0, 0, 0), ....: (0, 1, 1, 1), ....: (1, 1, 0, 0)]) sage: F.is_complete() False sage: G1 = F.completion() sage: G1.is_complete() True sage: G1.transitions() [Transition from 0 to 0: 0|0, Transition from 0 to 1: 1|1, Transition from 1 to 1: 0|0, Transition from 1 to 2: 1|-, Transition from 2 to 2: 0|-, Transition from 2 to 2: 1|-] sage: G2 = F.completion('Sink') sage: G2.is_complete() True sage: G2.transitions() [Transition from 0 to 0: 0|0, Transition from 0 to 1: 1|1, Transition from 1 to 1: 0|0, Transition from 1 to 'Sink': 1|-, Transition from 'Sink' to 'Sink': 0|-, Transition from 'Sink' to 'Sink': 1|-] sage: F.completion(1) Traceback (most recent call last): ... ValueError: The finite state machine already contains a state '1'.
An input alphabet must be given:
sage: F = FiniteStateMachine([(0, 0, 0, 0), ....: (0, 1, 1, 1), ....: (1, 1, 0, 0)], ....: determine_alphabets=False) sage: F.is_complete() Traceback (most recent call last): ... ValueError: No input alphabet is given. Try calling determine_alphabets().
Non-deterministic machines are not allowed.
sage: F = FiniteStateMachine([(0, 0, 0, 0), (0, 1, 0, 0)]) sage: F.is_complete() False sage: F.completion() Traceback (most recent call last): ... ValueError: The finite state machine must be deterministic. sage: F = FiniteStateMachine([(0, 0, [0, 0], 0)]) sage: F.is_complete() False sage: F.completion() Traceback (most recent call last): ... ValueError: The finite state machine must be deterministic.
- composition(other, algorithm=None, only_accessible_components=True)#
Return a new transducer which is the composition of
self
andother
.INPUT:
other
– a transduceralgorithm
– can be one of the followingdirect
– The composition is calculated directly.There can be arbitrarily many initial and final states, but the input and output labels must have length \(1\).
Warning
The output of
other
is fed intoself
.explorative
– An explorative algorithm is used.The input alphabet of self has to be specified.
Warning
The output of
other
is fed intoself
.
If algorithm is
None
, then the algorithm is chosen automatically (at the moment alwaysdirect
, except when there are output words ofother
or input words ofself
of length greater than \(1\)).
OUTPUT:
A new transducer.
The labels of the new finite state machine are pairs of states of the original finite state machines. The color of a new state is the tuple of colors of the constituent states.
EXAMPLES:
sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)], ....: initial_states=['A', 'B'], final_states=['B'], ....: determine_alphabets=True) sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1), ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: initial_states=[1], final_states=[2], ....: determine_alphabets=True) sage: Hd = F.composition(G, algorithm='direct') sage: Hd.initial_states() [(1, 'B'), (1, 'A')] sage: Hd.transitions() [Transition from (1, 'B') to (1, 'A'): 1|1, Transition from (1, 'A') to (2, 'B'): 0|0, Transition from (2, 'B') to (2, 'A'): 0|1, Transition from (2, 'A') to (2, 'B'): 1|0] sage: He = F.composition(G, algorithm='explorative') sage: He.initial_states() [(1, 'A'), (1, 'B')] sage: He.transitions() [Transition from (1, 'A') to (2, 'B'): 0|0, Transition from (1, 'B') to (1, 'A'): 1|1, Transition from (2, 'B') to (2, 'A'): 0|1, Transition from (2, 'A') to (2, 'B'): 1|0] sage: Hd == He True
The following example has output of length \(> 1\), so the explorative algorithm has to be used (and is selected automatically).
sage: F = Transducer([('A', 'B', 1, [1, 0]), ('B', 'B', 1, 1), ....: ('B', 'B', 0, 0)], ....: initial_states=['A'], final_states=['B']) sage: G = Transducer([(1, 1, 0, 0), (1, 2, 1, 0), ....: (2, 2, 0, 1), (2, 1, 1, 1)], ....: initial_states=[1], final_states=[1]) sage: He = G.composition(F, algorithm='explorative') sage: He.transitions() [Transition from ('A', 1) to ('B', 2): 1|0,1, Transition from ('B', 2) to ('B', 2): 0|1, Transition from ('B', 2) to ('B', 1): 1|1, Transition from ('B', 1) to ('B', 1): 0|0, Transition from ('B', 1) to ('B', 2): 1|0] sage: Ha = G.composition(F) sage: Ha == He True
Final output words are also considered:
sage: F = Transducer([('A', 'B', 1, 0), ('B', 'A', 0, 1)], ....: initial_states=['A', 'B'], ....: final_states=['A', 'B']) sage: F.state('A').final_word_out = 0 sage: F.state('B').final_word_out = 1 sage: G = Transducer([(1, 1, 1, 0), (1, 2, 0, 1), ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: initial_states=[1], final_states=[2]) sage: G.state(2).final_word_out = 0 sage: Hd = F.composition(G, algorithm='direct') sage: Hd.final_states() [(2, 'B')] sage: He = F.composition(G, algorithm='explorative') sage: He.final_states() [(2, 'B')]
Note that
(2, 'A')
is not final, as the final output \(0\) of state \(2\) of \(G\) cannot be processed in state'A'
of \(F\).sage: [s.final_word_out for s in Hd.final_states()] [[1, 0]] sage: [s.final_word_out for s in He.final_states()] [[1, 0]] sage: Hd == He True
Here is a non-deterministic example with intermediate output length \(>1\).
sage: F = Transducer([(1, 1, 1, ['a', 'a']), (1, 2, 1, 'b'), ....: (2, 1, 2, 'a'), (2, 2, 2, 'b')], ....: initial_states=[1, 2]) sage: G = Transducer([('A', 'A', 'a', 'i'), ....: ('A', 'B', 'a', 'l'), ....: ('B', 'B', 'b', 'e')], ....: initial_states=['A', 'B']) sage: G(F).transitions() [Transition from (1, 'A') to (1, 'A'): 1|'i','i', Transition from (1, 'A') to (1, 'B'): 1|'i','l', Transition from (1, 'B') to (2, 'B'): 1|'e', Transition from (2, 'A') to (1, 'A'): 2|'i', Transition from (2, 'A') to (1, 'B'): 2|'l', Transition from (2, 'B') to (2, 'B'): 2|'e']
Be aware that after composition, different transitions may share the same output label (same python object):
sage: F = Transducer([ ('A','B',0,0), ('B','A',0,0)], ....: initial_states=['A'], ....: final_states=['A']) sage: F.transitions()[0].word_out is F.transitions()[1].word_out False sage: G = Transducer([('C','C',0,1)], ....: initial_states=['C'], ....: final_states=['C']) sage: H = G.composition(F) sage: H.transitions()[0].word_out is H.transitions()[1].word_out True
- concatenation(other)#
Concatenate this finite state machine with another finite state machine.
INPUT:
other
– aFiniteStateMachine
.
OUTPUT:
A
FiniteStateMachine
of the same type as this finite state machine.Assume that both finite state machines are automata. If \(\mathcal{L}_1\) is the language accepted by this automaton and \(\mathcal{L}_2\) is the language accepted by the other automaton, then the language accepted by the concatenated automaton is \(\{ w_1w_2 \mid w_1\in\mathcal{L}_1, w_2\in\mathcal{L}_2\}\) where \(w_1w_2\) denotes the concatenation of the words \(w_1\) and \(w_2\).
Assume that both finite state machines are transducers and that this transducer maps words \(w_1\in\mathcal{L}_1\) to words \(f_1(w_1)\) and that the other transducer maps words \(w_2\in\mathcal{L}_2\) to words \(f_2(w_2)\). Then the concatenated transducer maps words \(w_1w_2\) with \(w_1\in\mathcal{L}_1\) and \(w_2\in\mathcal{L}_2\) to \(f_1(w_1)f_2(w_2)\). Here, \(w_1w_2\) and \(f_1(w_1)f_2(w_2)\) again denote concatenation of words.
The input alphabet is the union of the input alphabets (if possible) and
None
otherwise. In the latter case, try callingdetermine_alphabets()
.Instead of
A.concatenation(B)
, the notationA * B
can be used.EXAMPLES:
Concatenation of two automata:
sage: A = automata.Word([0]) sage: B = automata.Word([1]) sage: C = A.concatenation(B) sage: C.transitions() [Transition from (0, 0) to (0, 1): 0|-, Transition from (0, 1) to (1, 0): -|-, Transition from (1, 0) to (1, 1): 1|-] sage: [w ....: for w in ([0, 0], [0, 1], [1, 0], [1, 1]) ....: if C(w)] [[0, 1]] sage: from sage.combinat.finite_state_machine import ( ....: is_Automaton, is_Transducer) sage: is_Automaton(C) True
Concatenation of two transducers:
sage: A = Transducer([(0, 1, 0, 1), (0, 1, 1, 2)], ....: initial_states=[0], ....: final_states=[1]) sage: B = Transducer([(0, 1, 0, 1), (0, 1, 1, 0)], ....: initial_states=[0], ....: final_states=[1]) sage: C = A.concatenation(B) sage: C.transitions() [Transition from (0, 0) to (0, 1): 0|1, Transition from (0, 0) to (0, 1): 1|2, Transition from (0, 1) to (1, 0): -|-, Transition from (1, 0) to (1, 1): 0|1, Transition from (1, 0) to (1, 1): 1|0] sage: [(w, C(w)) for w in ([0, 0], [0, 1], [1, 0], [1, 1])] [([0, 0], [1, 1]), ([0, 1], [1, 0]), ([1, 0], [2, 1]), ([1, 1], [2, 0])] sage: is_Transducer(C) True
Alternative notation as multiplication:
sage: C == A * B True
Final output words are taken into account:
sage: A = Transducer([(0, 1, 0, 1)], ....: initial_states=[0], ....: final_states=[1]) sage: A.state(1).final_word_out = 2 sage: B = Transducer([(0, 1, 0, 3)], ....: initial_states=[0], ....: final_states=[1]) sage: B.state(1).final_word_out = 4 sage: C = A * B sage: C([0, 0]) [1, 2, 3, 4]
Handling of the input alphabet:
sage: A = Automaton([(0, 0, 0)]) sage: B = Automaton([(0, 0, 1)], input_alphabet=[1, 2]) sage: C = Automaton([(0, 0, 2)], determine_alphabets=False) sage: D = Automaton([(0, 0, [[0, 0]])], input_alphabet=[[0, 0]]) sage: A.input_alphabet [0] sage: B.input_alphabet [1, 2] sage: C.input_alphabet is None True sage: D.input_alphabet [[0, 0]] sage: (A * B).input_alphabet [0, 1, 2] sage: (A * C).input_alphabet is None True sage: (A * D).input_alphabet is None True
See also
- construct_final_word_out(letters, allow_non_final=True)#
This is an inplace version of
with_final_word_out()
. Seewith_final_word_out()
for documentation and examples.
- copy()#
Return a (shallow) copy of the finite state machine.
OUTPUT:
A new finite state machine.
- deepcopy(memo=None)#
Return a deep copy of the finite state machine.
INPUT:
memo
– (default:None
) a dictionary storing already processed elements.
OUTPUT:
A new finite state machine.
EXAMPLES:
sage: F = FiniteStateMachine([('A', 'A', 0, 1), ('A', 'A', 1, 0)]) sage: deepcopy(F) Finite state machine with 1 state
- default_format_letter = <sage.misc.latex.Latex object>#
- default_format_transition_label(word)#
Default formatting of words in transition labels for LaTeX output.
INPUT:
word
– list of lettersOUTPUT:
String representation of
word
suitable to be typeset in mathematical mode.For a non-empty word: Concatenation of the letters, piped through
self.format_letter
and separated by blanks.For an empty word:
sage.combinat.finite_state_machine.EmptyWordLaTeX
.
There is also a variant
format_transition_label_reversed()
writing the words in reversed order.EXAMPLES:
Example of a non-empty word:
sage: T = Transducer() sage: print(T.default_format_transition_label( ....: ['a', 'alpha', 'a_1', '0', 0, (0, 1)])) \text{\texttt{a}} \text{\texttt{alpha}} \text{\texttt{a{\char`\_}1}} 0 0 \left(0, 1\right)
In the example above,
'a'
and'alpha'
should perhaps be symbols:sage: var('a alpha a_1') # optional - sage.symbolic (a, alpha, a_1) sage: print(T.default_format_transition_label([a, alpha, a_1])) # optional - sage.symbolic a \alpha a_{1}
Example of an empty word:
sage: print(T.default_format_transition_label([])) \varepsilon
We can change this by setting
sage.combinat.finite_state_machine.EmptyWordLaTeX
:sage: sage.combinat.finite_state_machine.EmptyWordLaTeX = '' sage: T.default_format_transition_label([]) ''
Finally, we restore the default value:
sage: sage.combinat.finite_state_machine.EmptyWordLaTeX = r'\varepsilon'
This method is the default value for
FiniteStateMachine.format_transition_label
. That can be changed to be any other function:sage: A = Automaton([(0, 1, 0)]) sage: def custom_format_transition_label(word): ....: return "t" sage: A.latex_options(format_transition_label=custom_format_transition_label) sage: print(latex(A)) \begin{tikzpicture}[auto, initial text=, >=latex] \node[state] (v0) at (3.000000, 0.000000) {$0$}; \node[state] (v1) at (-3.000000, 0.000000) {$1$}; \path[->] (v0) edge node[rotate=360.00, anchor=south] {$t$} (v1); \end{tikzpicture}
- delete_state(s)#
Deletes a state and all transitions coming or going to this state.
INPUT:
s
– a label of a state or anFSMState
.
OUTPUT:
Nothing.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMTransition sage: t1 = FSMTransition('A', 'B', 0) sage: t2 = FSMTransition('B', 'B', 1) sage: F = FiniteStateMachine([t1, t2]) sage: F.delete_state('A') sage: F.transitions() [Transition from 'B' to 'B': 1|-]
- delete_transition(t)#
Deletes a transition by removing it from the list of transitions of the state, where the transition starts.
INPUT:
t
– a transition.
OUTPUT:
Nothing.
EXAMPLES:
sage: F = FiniteStateMachine([('A', 'B', 0), ('B', 'A', 1)]) sage: F.delete_transition(('A', 'B', 0)) sage: F.transitions() [Transition from 'B' to 'A': 1|-]
- determine_alphabets(reset=True)#
Determine the input and output alphabet according to the transitions in this finite state machine.
INPUT:
reset
– If reset isTrue
, then the existing input and output alphabets are erased, otherwise new letters are appended to the existing alphabets.
OUTPUT:
Nothing.
After this operation the input alphabet and the output alphabet of this finite state machine are a list of letters.
Todo
At the moment, the letters of the alphabets need to be hashable.
EXAMPLES:
sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: final_states=[1], ....: determine_alphabets=False) sage: T.state(1).final_word_out = [1, 4] sage: (T.input_alphabet, T.output_alphabet) (None, None) sage: T.determine_alphabets() sage: (T.input_alphabet, T.output_alphabet) ([0, 1, 2], [0, 1, 4])
- determine_input_alphabet(reset=True)#
Determine the input alphabet according to the transitions of this finite state machine.
INPUT:
reset
– a boolean (default:True
). IfTrue
, then the existing input alphabet is erased, otherwise new letters are appended to the existing alphabet.
OUTPUT:
Nothing.
After this operation the input alphabet of this finite state machine is a list of letters.
Todo
At the moment, the letters of the alphabet need to be hashable.
EXAMPLES:
sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: final_states=[1], ....: determine_alphabets=False) sage: (T.input_alphabet, T.output_alphabet) (None, None) sage: T.determine_input_alphabet() sage: (T.input_alphabet, T.output_alphabet) ([0, 1, 2], None)
See also
- determine_output_alphabet(reset=True)#
Determine the output alphabet according to the transitions of this finite state machine.
INPUT:
reset
– a boolean (default:True
). IfTrue
, then the existing output alphabet is erased, otherwise new letters are appended to the existing alphabet.
OUTPUT:
Nothing.
After this operation the output alphabet of this finite state machine is a list of letters.
Todo
At the moment, the letters of the alphabet need to be hashable.
EXAMPLES:
sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: final_states=[1], ....: determine_alphabets=False) sage: T.state(1).final_word_out = [1, 4] sage: (T.input_alphabet, T.output_alphabet) (None, None) sage: T.determine_output_alphabet() sage: (T.input_alphabet, T.output_alphabet) (None, [0, 1, 4])
See also
- digraph(edge_labels='words_in_out')#
Return the graph of the finite state machine with labeled vertices and labeled edges.
INPUT:
edge_label
: (default:'words_in_out'
) can be'words_in_out'
(labels will be strings'i|o'
)a function with which takes as input a transition and outputs (returns) the label
OUTPUT:
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') sage: T = Transducer() sage: T.graph() Looped multi-digraph on 0 vertices sage: T.add_state(A) 'A' sage: T.graph() Looped multi-digraph on 1 vertex sage: T.add_transition(('A', 'A', 0, 1)) Transition from 'A' to 'A': 0|1 sage: T.graph() Looped multi-digraph on 1 vertex
See also
- disjoint_union(other)#
Return the disjoint union of this and another finite state machine.
INPUT:
other
– aFiniteStateMachine
.
OUTPUT:
A finite state machine of the same type as this finite state machine.
In general, the disjoint union of two finite state machines is non-deterministic. In the case of a automata, the language accepted by the disjoint union is the union of the languages accepted by the constituent automata. In the case of transducer, for each successful path in one of the constituent transducers, there will be one successful path with the same input and output labels in the disjoint union.
The labels of the states of the disjoint union are pairs
(i, s)
: for each states
of this finite state machine, there is a state(0, s)
in the disjoint union; for each states
of the other finite state machine, there is a state(1, s)
in the disjoint union.The input alphabet is the union of the input alphabets (if possible) and
None
otherwise. In the latter case, try callingdetermine_alphabets()
.The disjoint union can also be written as
A + B
orA | B
.EXAMPLES:
sage: A = Automaton([(0, 1, 0), (1, 0, 1)], ....: initial_states=[0], ....: final_states=[0]) sage: A([0, 1, 0, 1]) True sage: B = Automaton([(0, 1, 0), (1, 2, 0), (2, 0, 1)], ....: initial_states=[0], ....: final_states=[0]) sage: B([0, 0, 1]) True sage: C = A.disjoint_union(B) sage: C Automaton with 5 states sage: C.transitions() [Transition from (0, 0) to (0, 1): 0|-, Transition from (0, 1) to (0, 0): 1|-, Transition from (1, 0) to (1, 1): 0|-, Transition from (1, 1) to (1, 2): 0|-, Transition from (1, 2) to (1, 0): 1|-] sage: C([0, 0, 1]) True sage: C([0, 1, 0, 1]) True sage: C([1]) False sage: C.initial_states() [(0, 0), (1, 0)]
Instead of
.disjoint_union
, alternative notations are available:sage: C1 = A + B sage: C1 == C True sage: C2 = A | B sage: C2 == C True
In general, the disjoint union is not deterministic.:
sage: C.is_deterministic() False sage: D = C.determinisation().minimization() sage: D.is_equivalent(Automaton([(0, 0, 0), (0, 0, 1), ....: (1, 7, 0), (1, 0, 1), (2, 6, 0), (2, 0, 1), ....: (3, 5, 0), (3, 0, 1), (4, 0, 0), (4, 2, 1), ....: (5, 0, 0), (5, 3, 1), (6, 4, 0), (6, 0, 1), ....: (7, 4, 0), (7, 3, 1)], ....: initial_states=[1], ....: final_states=[1, 2, 3])) True
Disjoint union of transducers:
sage: T1 = Transducer([(0, 0, 0, 1)], ....: initial_states=[0], ....: final_states=[0]) sage: T2 = Transducer([(0, 0, 0, 2)], ....: initial_states=[0], ....: final_states=[0]) sage: T1([0]) [1] sage: T2([0]) [2] sage: T = T1.disjoint_union(T2) sage: T([0]) Traceback (most recent call last): ... ValueError: Found more than one accepting path. sage: T.process([0]) [(True, (0, 0), [1]), (True, (1, 0), [2])]
Handling of the input alphabet (see github issue #18989):
sage: A = Automaton([(0, 0, 0)]) sage: B = Automaton([(0, 0, 1)], input_alphabet=[1, 2]) sage: C = Automaton([(0, 0, 2)], determine_alphabets=False) sage: D = Automaton([(0, 0, [[0, 0]])], input_alphabet=[[0, 0]]) sage: A.input_alphabet [0] sage: B.input_alphabet [1, 2] sage: C.input_alphabet is None True sage: D.input_alphabet [[0, 0]] sage: (A + B).input_alphabet [0, 1, 2] sage: (A + C).input_alphabet is None True sage: (A + D).input_alphabet is None True
- empty_copy(memo=None, new_class=None)#
Return an empty deep copy of the finite state machine, i.e.,
input_alphabet
,output_alphabet
,on_duplicate_transition
are preserved, but states and transitions are not.INPUT:
memo
– a dictionary storing already processed elements.new_class
– a class for the copy. By default (None
), the class ofself
is used.
OUTPUT:
A new finite state machine.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error sage: F = FiniteStateMachine([('A', 'A', 0, 2), ('A', 'A', 1, 3)], ....: input_alphabet=[0, 1], ....: output_alphabet=[2, 3], ....: on_duplicate_transition=duplicate_transition_raise_error) sage: FE = F.empty_copy(); FE Empty finite state machine sage: FE.input_alphabet [0, 1] sage: FE.output_alphabet [2, 3] sage: FE.on_duplicate_transition == duplicate_transition_raise_error True
- epsilon_successors(state)#
Return the dictionary with states reachable from
state
without reading anything from an input tape as keys. The values are lists of outputs.INPUT:
state
– the state whose epsilon successors should be determined.
OUTPUT:
A dictionary mapping states to a list of output words.
The states in the output are the epsilon successors of
state
. Each word of the list of output words is a word written when taking a path fromstate
to the corresponding state.EXAMPLES:
sage: T = Transducer([(0, 1, None, 'a'), (1, 2, None, 'b')]) sage: T.epsilon_successors(0) {1: [['a']], 2: [['a', 'b']]} sage: T.epsilon_successors(1) {2: [['b']]} sage: T.epsilon_successors(2) {}
If there is a cycle with only epsilon transitions, then this cycle is only processed once and there is no infinite loop:
sage: S = Transducer([(0, 1, None, 'a'), (1, 0, None, 'b')]) sage: S.epsilon_successors(0) {0: [['a', 'b']], 1: [['a']]} sage: S.epsilon_successors(1) {0: [['b']], 1: [['b', 'a']]}
- equivalence_classes()#
Return a list of equivalence classes of states.
OUTPUT:
A list of equivalence classes of states.
Two states \(a\) and \(b\) are equivalent if and only if there is a bijection \(\varphi\) between paths starting at \(a\) and paths starting at \(b\) with the following properties: Let \(p_a\) be a path from \(a\) to \(a'\) and \(p_b\) a path from \(b\) to \(b'\) such that \(\varphi(p_a)=p_b\), then
\(p_a.\mathit{word}_\mathit{in}=p_b.\mathit{word}_\mathit{in}\),
\(p_a.\mathit{word}_\mathit{out}=p_b.\mathit{word}_\mathit{out}\),
\(a'\) and \(b'\) have the same output label, and
\(a'\) and \(b'\) are both final or both non-final and have the same final output word.
The function
equivalence_classes()
returns a list of the equivalence classes to this equivalence relation.This is one step of Moore’s minimization algorithm.
See also
EXAMPLES:
sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0), ....: ("B", "C", 0, 0), ("B", "C", 1, 1), ....: ("C", "D", 0, 1), ("C", "D", 1, 0), ....: ("D", "A", 0, 0), ("D", "A", 1, 1)]) sage: sorted(fsm.equivalence_classes()) [['A', 'C'], ['B', 'D']] sage: fsm.state("A").is_final = True sage: sorted(fsm.equivalence_classes()) [['A'], ['B'], ['C'], ['D']] sage: fsm.state("C").is_final = True sage: sorted(fsm.equivalence_classes()) [['A', 'C'], ['B', 'D']] sage: fsm.state("A").final_word_out = 1 sage: sorted(fsm.equivalence_classes()) [['A'], ['B'], ['C'], ['D']] sage: fsm.state("C").final_word_out = 1 sage: sorted(fsm.equivalence_classes()) [['A', 'C'], ['B', 'D']]
- final_components()#
Return the final components of a finite state machine as finite state machines.
OUTPUT:
A list of finite state machines, each representing a final component of
self
.A final component of a transducer
T
is a strongly connected componentC
such that there are no transitions ofT
leavingC
.The final components are the only parts of a transducer which influence the main terms of the asymptotic behaviour of the sum of output labels of a transducer, see [HKP2015] and [HKW2015].
EXAMPLES:
sage: T = Transducer([['A', 'B', 0, 0], ['B', 'C', 0, 1], ....: ['C', 'B', 0, 1], ['A', 'D', 1, 0], ....: ['D', 'D', 0, 0], ['D', 'B', 1, 0], ....: ['A', 'E', 2, 0], ['E', 'E', 0, 0]]) sage: FC = T.final_components() sage: sorted(FC[0].transitions()) [Transition from 'B' to 'C': 0|1, Transition from 'C' to 'B': 0|1] sage: FC[1].transitions() [Transition from 'E' to 'E': 0|0]
Another example (cycle of length 2):
sage: T = Automaton([[0, 1, 0], [1, 0, 0]]) sage: len(T.final_components()) == 1 True sage: T.final_components()[0].transitions() [Transition from 0 to 1: 0|-, Transition from 1 to 0: 0|-]
- final_states()#
Return a list of all final states.
OUTPUT:
A list of all final states.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_final=True) sage: B = FSMState('B', is_initial=True) sage: C = FSMState('C', is_final=True) sage: F = FiniteStateMachine([(A, B), (A, C)]) sage: F.final_states() ['A', 'C']
- format_letter = <sage.misc.latex.Latex object>#
- format_letter_negative(letter)#
Format negative numbers as overlined numbers, everything else by standard LaTeX formatting.
INPUT:
letter
– anything.OUTPUT:
Overlined absolute value if letter is a negative integer,
latex(letter)
otherwise.EXAMPLES:
sage: A = Automaton([(0, 0, -1)]) sage: list(map(A.format_letter_negative, [-1, 0, 1, 'a', None])) ['\\overline{1}', 0, 1, \text{\texttt{a}}, \mathrm{None}] sage: A.latex_options(format_letter=A.format_letter_negative) sage: print(latex(A)) \begin{tikzpicture}[auto, initial text=, >=latex] \node[state] (v0) at (3.000000, 0.000000) {$0$}; \path[->] (v0) edge[loop above] node {$\overline{1}$} (); \end{tikzpicture}
- format_transition_label(word)#
Default formatting of words in transition labels for LaTeX output.
INPUT:
word
– list of lettersOUTPUT:
String representation of
word
suitable to be typeset in mathematical mode.For a non-empty word: Concatenation of the letters, piped through
self.format_letter
and separated by blanks.For an empty word:
sage.combinat.finite_state_machine.EmptyWordLaTeX
.
There is also a variant
format_transition_label_reversed()
writing the words in reversed order.EXAMPLES:
Example of a non-empty word:
sage: T = Transducer() sage: print(T.default_format_transition_label( ....: ['a', 'alpha', 'a_1', '0', 0, (0, 1)])) \text{\texttt{a}} \text{\texttt{alpha}} \text{\texttt{a{\char`\_}1}} 0 0 \left(0, 1\right)
In the example above,
'a'
and'alpha'
should perhaps be symbols:sage: var('a alpha a_1') # optional - sage.symbolic (a, alpha, a_1) sage: print(T.default_format_transition_label([a, alpha, a_1])) # optional - sage.symbolic a \alpha a_{1}
Example of an empty word:
sage: print(T.default_format_transition_label([])) \varepsilon
We can change this by setting
sage.combinat.finite_state_machine.EmptyWordLaTeX
:sage: sage.combinat.finite_state_machine.EmptyWordLaTeX = '' sage: T.default_format_transition_label([]) ''
Finally, we restore the default value:
sage: sage.combinat.finite_state_machine.EmptyWordLaTeX = r'\varepsilon'
This method is the default value for
FiniteStateMachine.format_transition_label
. That can be changed to be any other function:sage: A = Automaton([(0, 1, 0)]) sage: def custom_format_transition_label(word): ....: return "t" sage: A.latex_options(format_transition_label=custom_format_transition_label) sage: print(latex(A)) \begin{tikzpicture}[auto, initial text=, >=latex] \node[state] (v0) at (3.000000, 0.000000) {$0$}; \node[state] (v1) at (-3.000000, 0.000000) {$1$}; \path[->] (v0) edge node[rotate=360.00, anchor=south] {$t$} (v1); \end{tikzpicture}
- format_transition_label_reversed(word)#
Format words in transition labels in reversed order.
INPUT:
word
– list of letters.OUTPUT:
String representation of
word
suitable to be typeset in mathematical mode, letters are written in reversed order.This is the reversed version of
default_format_transition_label()
.In digit expansions, digits are frequently processed from the least significant to the most significant position, but it is customary to write the least significant digit at the right-most position. Therefore, the labels have to be reversed.
EXAMPLES:
sage: T = Transducer([(0, 0, 0, [1, 2, 3])]) sage: T.format_transition_label_reversed([1, 2, 3]) '3 2 1' sage: T.latex_options(format_transition_label=T.format_transition_label_reversed) sage: print(latex(T)) \begin{tikzpicture}[auto, initial text=, >=latex] \node[state] (v0) at (3.000000, 0.000000) {$0$}; \path[->] (v0) edge[loop above] node {$0\mid 3 2 1$} (); \end{tikzpicture}
- graph(edge_labels='words_in_out')#
Return the graph of the finite state machine with labeled vertices and labeled edges.
INPUT:
edge_label
: (default:'words_in_out'
) can be'words_in_out'
(labels will be strings'i|o'
)a function with which takes as input a transition and outputs (returns) the label
OUTPUT:
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') sage: T = Transducer() sage: T.graph() Looped multi-digraph on 0 vertices sage: T.add_state(A) 'A' sage: T.graph() Looped multi-digraph on 1 vertex sage: T.add_transition(('A', 'A', 0, 1)) Transition from 'A' to 'A': 0|1 sage: T.graph() Looped multi-digraph on 1 vertex
See also
- has_final_state(state)#
Return whether
state
is one of the final states of the finite state machine.INPUT:
state
can be aFSMState
or a label.
OUTPUT:
True or False.
EXAMPLES:
sage: FiniteStateMachine(final_states=['A']).has_final_state('A') True
- has_final_states()#
Return whether the finite state machine has a final state.
OUTPUT:
True or False.
EXAMPLES:
sage: FiniteStateMachine().has_final_states() False
- has_initial_state(state)#
Return whether
state
is one of the initial states of the finite state machine.INPUT:
state
can be aFSMState
or a label.
OUTPUT:
True or False.
EXAMPLES:
sage: F = FiniteStateMachine([('A', 'A')], initial_states=['A']) sage: F.has_initial_state('A') True
- has_initial_states()#
Return whether the finite state machine has an initial state.
OUTPUT:
True or False.
EXAMPLES:
sage: FiniteStateMachine().has_initial_states() False
- has_state(state)#
Return whether
state
is one of the states of the finite state machine.INPUT:
state
can be aFSMState
or a label of a state.
OUTPUT:
True or False.
EXAMPLES:
sage: FiniteStateMachine().has_state('A') False
- has_transition(transition)#
Return whether
transition
is one of the transitions of the finite state machine.INPUT:
transition
has to be aFSMTransition
.
OUTPUT:
True or False.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMTransition sage: t = FSMTransition('A', 'A', 0, 1) sage: FiniteStateMachine().has_transition(t) False sage: FiniteStateMachine().has_transition(('A', 'A', 0, 1)) Traceback (most recent call last): ... TypeError: Transition is not an instance of FSMTransition.
- induced_sub_finite_state_machine(states)#
Return a sub-finite-state-machine of the finite state machine induced by the given states.
INPUT:
states
– a list (or an iterator) of states (either labels or instances ofFSMState
) of the sub-finite-state-machine.
OUTPUT:
A new finite state machine. It consists (of deep copies) of the given states and (deep copies) of all transitions of
self
between these states.EXAMPLES:
sage: FSM = FiniteStateMachine([(0, 1, 0), (0, 2, 0), ....: (1, 2, 0), (2, 0, 0)]) sage: sub_FSM = FSM.induced_sub_finite_state_machine([0, 1]) sage: sub_FSM.states() [0, 1] sage: sub_FSM.transitions() [Transition from 0 to 1: 0|-] sage: FSM.induced_sub_finite_state_machine([3]) Traceback (most recent call last): ... ValueError: 3 is not a state of this finite state machine.
- initial_states()#
Return a list of all initial states.
OUTPUT:
A list of all initial states.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_initial=True) sage: B = FSMState('B') sage: F = FiniteStateMachine([(A, B, 1, 0)]) sage: F.initial_states() ['A']
- input_alphabet = None#
A list of letters representing the input alphabet of the finite state machine.
It can be set by the parameter
input_alphabet
when initializing a finite state machine, seeFiniteStateMachine
.It can also be set by the method
determine_alphabets()
.See also
- input_projection()#
Return an automaton where the output of each transition of self is deleted.
OUTPUT:
An automaton.
EXAMPLES:
sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1), ....: ('B', 'B', 1, 0)]) sage: G = F.input_projection() sage: G.transitions() [Transition from 'A' to 'B': 0|-, Transition from 'A' to 'A': 1|-, Transition from 'B' to 'B': 1|-]
- intersection(other)#
- is_Markov_chain(is_zero=None)#
Checks whether
self
is a Markov chain where the transition probabilities are modeled as input labels.INPUT:
is_zero
– by default (is_zero=None
), checking for zero is simply done byis_zero()
. This parameter can be used to provide a more sophisticated check for zero, e.g. in the case of symbolic probabilities, see the examples below.
OUTPUT:
True
orFalse
.on_duplicate_transition
must beduplicate_transition_add_input()
, the sum of the input weights of the transitions leaving a state must add up to 1 and the sum of initial probabilities must add up to 1 (or all beNone
).EXAMPLES:
sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], ....: [1, 0, 1/2, 0], [1, 1, 1/2, 1]], ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.is_Markov_chain() True
on_duplicate_transition
must beduplicate_transition_add_input()
:sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], ....: [1, 0, 1/2, 0], [1, 1, 1/2, 1]]) sage: F.is_Markov_chain() False
Sum of input labels of the transitions leaving states must be 1:
sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], ....: [1, 0, 1/2, 0]], ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.is_Markov_chain() False
The initial probabilities of all states must be
None
or they must sum up to 1. The initial probabilities of all states have to be set in the latter case:sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], ....: [1, 0, 1, 0]], ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.is_Markov_chain() True sage: F.state(0).initial_probability = 1/4 sage: F.is_Markov_chain() False sage: F.state(1).initial_probability = 7 sage: F.is_Markov_chain() False sage: F.state(1).initial_probability = 3/4 sage: F.is_Markov_chain() True
If the probabilities are variables in the symbolic ring,
assume()
will do the trick:sage: var('p q') # optional - sage.symbolic (p, q) sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], # optional - sage.symbolic ....: on_duplicate_transition=duplicate_transition_add_input) sage: assume(p + q == 1) # optional - sage.symbolic sage: (p + q - 1).is_zero() # optional - sage.symbolic True sage: F.is_Markov_chain() # optional - sage.symbolic True sage: forget() # optional - sage.symbolic sage: del(p, q) # optional - sage.symbolic
If the probabilities are variables in some polynomial ring, the parameter
is_zero
can be used:sage: R.<p, q> = PolynomialRing(QQ) sage: def is_zero_polynomial(polynomial): ....: return polynomial in (p + q - 1)*R sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.state(0).initial_probability = p + q sage: F.is_Markov_chain() False sage: F.is_Markov_chain(is_zero_polynomial) True
- is_complete()#
Return whether the finite state machine is complete.
OUTPUT:
True
orFalse
A finite state machine is considered to be complete if each transition has an input label of length one and for each pair \((q, a)\) where \(q\) is a state and \(a\) is an element of the input alphabet, there is exactly one transition from \(q\) with input label \(a\).
EXAMPLES:
sage: fsm = FiniteStateMachine([(0, 0, 0, 0), ....: (0, 1, 1, 1), ....: (1, 1, 0, 0)], ....: determine_alphabets=False) sage: fsm.is_complete() Traceback (most recent call last): ... ValueError: No input alphabet is given. Try calling determine_alphabets(). sage: fsm.input_alphabet = [0, 1] sage: fsm.is_complete() False sage: fsm.add_transition((1, 1, 1, 1)) Transition from 1 to 1: 1|1 sage: fsm.is_complete() True sage: fsm.add_transition((0, 0, 1, 0)) Transition from 0 to 0: 1|0 sage: fsm.is_complete() False
- is_connected()#
- is_deterministic()#
Return whether the finite finite state machine is deterministic.
OUTPUT:
True
orFalse
A finite state machine is considered to be deterministic if each transition has input label of length one and for each pair \((q,a)\) where \(q\) is a state and \(a\) is an element of the input alphabet, there is at most one transition from \(q\) with input label \(a\). Furthermore, the finite state may not have more than one initial state.
EXAMPLES:
sage: fsm = FiniteStateMachine() sage: fsm.add_transition(('A', 'B', 0, [])) Transition from 'A' to 'B': 0|- sage: fsm.is_deterministic() True sage: fsm.add_transition(('A', 'C', 0, [])) Transition from 'A' to 'C': 0|- sage: fsm.is_deterministic() False sage: fsm.add_transition(('A', 'B', [0,1], [])) Transition from 'A' to 'B': 0,1|- sage: fsm.is_deterministic() False
Check that github issue #18556 is fixed:
sage: Automaton().is_deterministic() True sage: Automaton(initial_states=[0]).is_deterministic() True sage: Automaton(initial_states=[0, 1]).is_deterministic() False
- is_monochromatic()#
Check whether the colors of all states are equal.
OUTPUT:
True
orFalse
EXAMPLES:
sage: G = transducers.GrayCode() sage: [s.color for s in G.iter_states()] [None, None, None] sage: G.is_monochromatic() True sage: G.state(1).color = 'blue' sage: G.is_monochromatic() False
- iter_final_states()#
Return an iterator of the final states.
OUTPUT:
An iterator over all initial states.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_final=True) sage: B = FSMState('B', is_initial=True) sage: C = FSMState('C', is_final=True) sage: F = FiniteStateMachine([(A, B), (A, C)]) sage: [s.label() for s in F.iter_final_states()] ['A', 'C']
- iter_initial_states()#
Return an iterator of the initial states.
OUTPUT:
An iterator over all initial states.
EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_initial=True) sage: B = FSMState('B') sage: F = FiniteStateMachine([(A, B, 1, 0)]) sage: [s.label() for s in F.iter_initial_states()] ['A']
- iter_process(input_tape=None, initial_state=None, process_iterator_class=None, iterator_type=None, automatic_output_type=False, **kwargs)#
This function returns an iterator for processing the input. See
process()
(which runs this iterator until the end) for more information.INPUT:
iterator_type
– IfNone
(default), then an instance ofFSMProcessIterator
is returned. If this is'simple'
only an iterator over one output is returned (an exception is raised if this is not the case, i.e., if the process has branched).
See
process()
for a description of the other parameters.OUTPUT:
An iterator.
EXAMPLES:
We can use
iter_process()
to deal with infinite words:sage: inverter = Transducer({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) sage: words.FibonacciWord() word: 0100101001001010010100100101001001010010... sage: it = inverter.iter_process( ....: words.FibonacciWord(), iterator_type='simple') sage: Words([0,1])(it) word: 1011010110110101101011011010110110101101...
This can also be done by:
sage: inverter.iter_process(words.FibonacciWord(), ....: iterator_type='simple', ....: automatic_output_type=True) word: 1011010110110101101011011010110110101101...
or even simpler by:
sage: inverter(words.FibonacciWord()) word: 1011010110110101101011011010110110101101...
To see what is going on, we use
iter_process()
without arguments:sage: from itertools import islice sage: it = inverter.iter_process(words.FibonacciWord()) sage: for current in islice(it, 4r): ....: print(current) process (1 branch) + at state 'A' +-- tape at 1, [[1]] process (1 branch) + at state 'A' +-- tape at 2, [[1, 0]] process (1 branch) + at state 'A' +-- tape at 3, [[1, 0, 1]] process (1 branch) + at state 'A' +-- tape at 4, [[1, 0, 1, 1]]
The following show the difference between using the
'simple'
-option and not using it. With this option, we havesage: it = inverter.iter_process(input_tape=[0, 1, 1], ....: iterator_type='simple') sage: for i, o in enumerate(it): ....: print('step %s: output %s' % (i, o)) step 0: output 1 step 1: output 0 step 2: output 0
So
iter_process()
is a generator expression which gives a new output letter in each step (and not more). In many cases this is sufficient.Doing the same without the
'simple'
-option does not give the output directly; it has to be extracted first. On the other hand, additional information is presented:sage: it = inverter.iter_process(input_tape=[0, 1, 1]) sage: for current in it: ....: print(current) process (1 branch) + at state 'A' +-- tape at 1, [[1]] process (1 branch) + at state 'A' +-- tape at 2, [[1, 0]] process (1 branch) + at state 'A' +-- tape at 3, [[1, 0, 0]] process (0 branches) sage: it.result() [Branch(accept=True, state='A', output=[1, 0, 0])]
One can see the growing of the output (the list of lists at the end of each entry).
Even if the transducer has transitions with empty or multiletter output, the simple iterator returns one new output letter in each step:
sage: T = Transducer([(0, 0, 0, []), ....: (0, 0, 1, [1]), ....: (0, 0, 2, [2, 2])], ....: initial_states=[0]) sage: it = T.iter_process(input_tape=[0, 1, 2, 0, 1, 2], ....: iterator_type='simple') sage: for i, o in enumerate(it): ....: print('step %s: output %s' % (i, o)) step 0: output 1 step 1: output 2 step 2: output 2 step 3: output 1 step 4: output 2 step 5: output 2
- iter_states()#
Return an iterator of the states.
OUTPUT:
An iterator of the states of the finite state machine.
EXAMPLES:
sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) sage: [s.label() for s in FSM.iter_states()] ['1', '2']
- iter_transitions(from_state=None)#
Return an iterator of all transitions.
INPUT:
from_state
– (default:None
) Iffrom_state
is given, then a list of transitions starting there is given.
OUTPUT:
An iterator of all transitions.
EXAMPLES:
sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) sage: [(t.from_state.label(), t.to_state.label()) ....: for t in FSM.iter_transitions('1')] [('1', '2')] sage: [(t.from_state.label(), t.to_state.label()) ....: for t in FSM.iter_transitions('2')] [('2', '2')] sage: [(t.from_state.label(), t.to_state.label()) ....: for t in FSM.iter_transitions()] [('1', '2'), ('2', '2')]
- kleene_star()#
Compute the Kleene closure of this finite state machine.
OUTPUT:
A
FiniteStateMachine
of the same type as this finite state machine.Assume that this finite state machine is an automaton recognizing the language \(\mathcal{L}\). Then the Kleene star recognizes the language \(\mathcal{L}^*=\{ w_1\ldots w_n \mid n\ge 0, w_j\in\mathcal{L} \text{ for all } j\}\).
Assume that this finite state machine is a transducer realizing a function \(f\) on some alphabet \(\mathcal{L}\). Then the Kleene star realizes a function \(g\) on \(\mathcal{L}^*\) with \(g(w_1\ldots w_n)=f(w_1)\ldots f(w_n)\).
EXAMPLES:
Kleene star of an automaton:
sage: A = automata.Word([0, 1]) sage: B = A.kleene_star() sage: B.transitions() [Transition from 0 to 1: 0|-, Transition from 2 to 0: -|-, Transition from 1 to 2: 1|-] sage: from sage.combinat.finite_state_machine import ( ....: is_Automaton, is_Transducer) sage: is_Automaton(B) True sage: [w for w in ([], [0, 1], [0, 1, 0], [0, 1, 0, 1], [0, 1, 1, 1]) ....: if B(w)] [[], [0, 1], [0, 1, 0, 1]]
Kleene star of a transducer:
sage: T = Transducer([(0, 1, 0, 1), (0, 1, 1, 0)], ....: initial_states=[0], ....: final_states=[1]) sage: S = T.kleene_star() sage: S.transitions() [Transition from 0 to 1: 0|1, Transition from 0 to 1: 1|0, Transition from 1 to 0: -|-] sage: is_Transducer(S) True sage: for w in ([], [0], [1], [0, 0], [0, 1]): ....: print("{} {}".format(w, S.process(w))) [] (True, 0, []) [0] [(True, 0, [1]), (True, 1, [1])] [1] [(True, 0, [0]), (True, 1, [0])] [0, 0] [(True, 0, [1, 1]), (True, 1, [1, 1])] [0, 1] [(True, 0, [1, 0]), (True, 1, [1, 0])]
Final output words are taken into account:
sage: T = Transducer([(0, 1, 0, 1)], ....: initial_states=[0], ....: final_states=[1]) sage: T.state(1).final_word_out = 2 sage: S = T.kleene_star() sage: sorted(S.process([0, 0])) [(True, 0, [1, 2, 1, 2]), (True, 1, [1, 2, 1, 2])]
Final output words may lead to undesirable situations if initial states and final states coincide:
sage: T = Transducer(initial_states=[0], final_states=[0]) sage: T.state(0).final_word_out = 1 sage: T([]) [1] sage: S = T.kleene_star() sage: S([]) Traceback (most recent call last): ... RuntimeError: State 0 is in an epsilon cycle (no input), but output is written.
- language(max_length=None, **kwargs)#
Return all words that can be written by this transducer.
INPUT:
max_length
– an integer orNone
(default). Only output words which come from inputs of length at mostmax_length
will be considered. IfNone
, then this iterates over all possible words without length restrictions.kwargs
– will be passed on to theprocess iterator
. Seeprocess()
for a description.
OUTPUT:
An iterator.
EXAMPLES:
sage: NAF = Transducer([('I', 0, 0, None), ('I', 1, 1, None), ....: (0, 0, 0, 0), (0, 1, 1, 0), ....: (1, 0, 0, 1), (1, 2, 1, -1), ....: (2, 1, 0, 0), (2, 2, 1, 0)], ....: initial_states=['I'], final_states=[0], ....: input_alphabet=[0, 1]) sage: sorted(NAF.language(4), ....: key=lambda o: (ZZ(o, base=2), len(o))) [[], [0], [0, 0], [0, 0, 0], [1], [1, 0], [1, 0, 0], [0, 1], [0, 1, 0], [-1, 0, 1], [0, 0, 1], [1, 0, 1]]
sage: iterator = NAF.language() sage: next(iterator) [] sage: next(iterator) [0] sage: next(iterator) [1] sage: next(iterator) [0, 0] sage: next(iterator) [0, 1]
See also
- latex_options(coordinates=None, format_state_label=None, format_letter=None, format_transition_label=None, loop_where=None, initial_where=None, accepting_style=None, accepting_distance=None, accepting_where=None, accepting_show_empty=None)#
Set options for LaTeX output via
latex()
and thereforeview()
.INPUT:
coordinates
– a dictionary or a function mapping labels of states to pairs interpreted as coordinates. If no coordinates are given, states a placed equidistantly on a circle of radius \(3\). See alsoset_coordinates()
.format_state_label
– a function mapping labels of states to a string suitable for typesetting in LaTeX’s mathematics mode. If not given,latex()
is used.format_letter
– a function mapping letters of the input and output alphabets to a string suitable for typesetting in LaTeX’s mathematics mode. If not given,default_format_transition_label()
useslatex()
.format_transition_label
– a function mapping words over the input and output alphabets to a string suitable for typesetting in LaTeX’s mathematics mode. If not given,default_format_transition_label()
is used.loop_where
– a dictionary or a function mapping labels of initial states to one of'above'
,'left'
,'below'
,'right'
. If not given,'above'
is used.initial_where
– a dictionary or a function mapping labels of initial states to one of'above'
,'left'
,'below'
,'right'
. If not given, TikZ’ default (currently'left'
) is used.accepting_style
– one of'accepting by double'
and'accepting by arrow'
. If not given,'accepting by double'
is used unless there are non-empty final output words.accepting_distance
– a string giving a LaTeX length used for the length of the arrow leading from a final state. If not given, TikZ’ default (currently'3ex'
) is used unless there are non-empty final output words, in which case'7ex'
is used.accepting_where
– a dictionary or a function mapping labels of final states to one of'above'
,'left'
,'below'
,'right'
. If not given, TikZ’ default (currently'right'
) is used. If the final state has a final output word, it is also possible to give an angle in degrees.accepting_show_empty
– ifTrue
the arrow of an empty final output word is labeled as well. Note that this implicitly impliesaccepting_style='accepting by arrow'
. If not given, the defaultFalse
is used.
OUTPUT:
Nothing.
As TikZ (cf. the Wikipedia article PGF/TikZ) is used to typeset the graphics, the syntax is oriented on TikZ’ syntax.
This is a convenience function collecting all options for LaTeX output. All of its functionality can also be achieved by directly setting the attributes
coordinates
,format_label
,loop_where
,initial_where
, andaccepting_where
ofFSMState
(here,format_label
is a callable without arguments, everything else is a specific value);format_label
ofFSMTransition
(format_label
is a callable without arguments);format_state_label
,format_letter
,format_transition_label
,accepting_style
,accepting_distance
, andaccepting_show_empty
ofFiniteStateMachine
.
This function, however, also (somewhat) checks its input and serves to collect documentation on all these options.
The function can be called several times, only those arguments which are not
None
are taken into account. By the same means, it can be combined with directly setting some attributes as outlined above.EXAMPLES:
See also the section on LaTeX output in the introductory examples of this module.
sage: T = Transducer(initial_states=[4], ....: final_states=[0, 3]) sage: for j in srange(4): ....: T.add_transition(4, j, 0, [0, j]) ....: T.add_transition(j, 4, 0, [0, -j]) ....: T.add_transition(j, j, 0, 0) Transition from 4 to 0: 0|0,0 Transition from 0 to 4: 0|0,0 Transition from 0 to 0: 0|0 Transition from 4 to 1: 0|0,1 Transition from 1 to 4: 0|0,-1 Transition from 1 to 1: 0|0 Transition from 4 to 2: 0|0,2 Transition from 2 to 4: 0|0,-2 Transition from 2 to 2: 0|0 Transition from 4 to 3: 0|0,3 Transition from 3 to 4: 0|0,-3 Transition from 3 to 3: 0|0 sage: T.add_transition(4, 4, 0, 0) Transition from 4 to 4: 0|0 sage: T.state(3).final_word_out = [0, 0] sage: T.latex_options( ....: coordinates={4: (0, 0), ....: 0: (-6, 3), ....: 1: (-2, 3), ....: 2: (2, 3), ....: 3: (6, 3)}, ....: format_state_label=lambda x: r'\mathbf{%s}' % x, ....: format_letter=lambda x: r'w_{%s}' % x, ....: format_transition_label=lambda x: ....: r"{\scriptstyle %s}" % T.default_format_transition_label(x), ....: loop_where={4: 'below', 0: 'left', 1: 'above', ....: 2: 'right', 3:'below'}, ....: initial_where=lambda x: 'above', ....: accepting_style='accepting by double', ....: accepting_distance='10ex', ....: accepting_where={0: 'left', 3: 45} ....: ) sage: T.state(4).format_label=lambda: r'\mathcal{I}' sage: latex(T) \begin{tikzpicture}[auto, initial text=, >=latex] \node[state, initial, initial where=above] (v0) at (0.000000, 0.000000) {$\mathcal{I}$}; \node[state, accepting, accepting where=left] (v1) at (-6.000000, 3.000000) {$\mathbf{0}$}; \node[state, accepting, accepting where=45] (v2) at (6.000000, 3.000000) {$\mathbf{3}$}; \path[->] (v2.45.00) edge node[rotate=45.00, anchor=south] {$$ \mid {\scriptstyle w_{0} w_{0}}$} ++(45.00:10ex); \node[state] (v3) at (-2.000000, 3.000000) {$\mathbf{1}$}; \node[state] (v4) at (2.000000, 3.000000) {$\mathbf{2}$}; \path[->] (v1) edge[loop left] node[rotate=90, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); \path[->] (v1.-21.57) edge node[rotate=-26.57, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{0}}$} (v0.148.43); \path[->] (v3) edge[loop above] node {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); \path[->] (v3.-51.31) edge node[rotate=-56.31, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{-1}}$} (v0.118.69); \path[->] (v4) edge[loop right] node[rotate=90, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); \path[->] (v4.-118.69) edge node[rotate=56.31, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{-2}}$} (v0.51.31); \path[->] (v2) edge[loop below] node {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); \path[->] (v2.-148.43) edge node[rotate=26.57, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{-3}}$} (v0.21.57); \path[->] (v0.158.43) edge node[rotate=333.43, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{0}}$} (v1.328.43); \path[->] (v0.128.69) edge node[rotate=303.69, anchor=north] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{1}}$} (v3.298.69); \path[->] (v0.61.31) edge node[rotate=56.31, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{2}}$} (v4.231.31); \path[->] (v0.31.57) edge node[rotate=26.57, anchor=south] {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0} w_{3}}$} (v2.201.57); \path[->] (v0) edge[loop below] node {${\scriptstyle w_{0}}\mid {\scriptstyle w_{0}}$} (); \end{tikzpicture} sage: view(T) # not tested
To actually see this, use the live documentation in the Sage notebook and execute the cells.
By changing some of the options, we get the following output:
sage: T.latex_options( ....: format_transition_label=T.default_format_transition_label, ....: accepting_style='accepting by arrow', ....: accepting_show_empty=True ....: ) sage: latex(T) \begin{tikzpicture}[auto, initial text=, >=latex, accepting text=, accepting/.style=accepting by arrow, accepting distance=10ex] \node[state, initial, initial where=above] (v0) at (0.000000, 0.000000) {$\mathcal{I}$}; \node[state] (v1) at (-6.000000, 3.000000) {$\mathbf{0}$}; \path[->] (v1.180.00) edge node[rotate=360.00, anchor=south] {$$ \mid \varepsilon$} ++(180.00:10ex); \node[state] (v2) at (6.000000, 3.000000) {$\mathbf{3}$}; \path[->] (v2.45.00) edge node[rotate=45.00, anchor=south] {$$ \mid w_{0} w_{0}$} ++(45.00:10ex); \node[state] (v3) at (-2.000000, 3.000000) {$\mathbf{1}$}; \node[state] (v4) at (2.000000, 3.000000) {$\mathbf{2}$}; \path[->] (v1) edge[loop left] node[rotate=90, anchor=south] {$w_{0}\mid w_{0}$} (); \path[->] (v1.-21.57) edge node[rotate=-26.57, anchor=south] {$w_{0}\mid w_{0} w_{0}$} (v0.148.43); \path[->] (v3) edge[loop above] node {$w_{0}\mid w_{0}$} (); \path[->] (v3.-51.31) edge node[rotate=-56.31, anchor=south] {$w_{0}\mid w_{0} w_{-1}$} (v0.118.69); \path[->] (v4) edge[loop right] node[rotate=90, anchor=north] {$w_{0}\mid w_{0}$} (); \path[->] (v4.-118.69) edge node[rotate=56.31, anchor=north] {$w_{0}\mid w_{0} w_{-2}$} (v0.51.31); \path[->] (v2) edge[loop below] node {$w_{0}\mid w_{0}$} (); \path[->] (v2.-148.43) edge node[rotate=26.57, anchor=north] {$w_{0}\mid w_{0} w_{-3}$} (v0.21.57); \path[->] (v0.158.43) edge node[rotate=333.43, anchor=north] {$w_{0}\mid w_{0} w_{0}$} (v1.328.43); \path[->] (v0.128.69) edge node[rotate=303.69, anchor=north] {$w_{0}\mid w_{0} w_{1}$} (v3.298.69); \path[->] (v0.61.31) edge node[rotate=56.31, anchor=south] {$w_{0}\mid w_{0} w_{2}$} (v4.231.31); \path[->] (v0.31.57) edge node[rotate=26.57, anchor=south] {$w_{0}\mid w_{0} w_{3}$} (v2.201.57); \path[->] (v0) edge[loop below] node {$w_{0}\mid w_{0}$} (); \end{tikzpicture} sage: view(T) # not tested
- markov_chain_simplification()#
Consider
self
as Markov chain with probabilities as input labels and simplify it.INPUT:
Nothing.
OUTPUT:
Simplified version of
self
.EXAMPLES:
sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input sage: T = Transducer([[1, 2, 1/4, 0], [1, -2, 1/4, 0], [1, -2, 1/2, 0], ....: [2, 2, 1/4, 1], [2, -2, 1/4, 1], [-2, -2, 1/4, 1], ....: [-2, 2, 1/4, 1], [2, 3, 1/2, 2], [-2, 3, 1/2, 2]], ....: initial_states=[1], ....: final_states=[3], ....: on_duplicate_transition=duplicate_transition_add_input) sage: T1 = T.markov_chain_simplification() sage: sorted(T1.transitions()) [Transition from ((1,),) to ((2, -2),): 1|0, Transition from ((2, -2),) to ((2, -2),): 1/2|1, Transition from ((2, -2),) to ((3,),): 1/2|2]
- merged_transitions()#
Merges transitions which have the same
from_state
,to_state
andword_out
while adding theirword_in
.INPUT:
Nothing.
OUTPUT:
A finite state machine with merged transitions. If no mergers occur, return
self
.EXAMPLES:
sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input sage: T = Transducer([[1, 2, 1/4, 1], [1, -2, 1/4, 1], [1, -2, 1/2, 1], ....: [2, 2, 1/4, 1], [2, -2, 1/4, 1], [-2, -2, 1/4, 1], ....: [-2, 2, 1/4, 1], [2, 3, 1/2, 1], [-2, 3, 1/2, 1]], ....: on_duplicate_transition=duplicate_transition_add_input) sage: T1 = T.merged_transitions() sage: T1 is T False sage: sorted(T1.transitions()) [Transition from -2 to -2: 1/4|1, Transition from -2 to 2: 1/4|1, Transition from -2 to 3: 1/2|1, Transition from 1 to 2: 1/4|1, Transition from 1 to -2: 3/4|1, Transition from 2 to -2: 1/4|1, Transition from 2 to 2: 1/4|1, Transition from 2 to 3: 1/2|1]
Applying the function again does not change the result:
sage: T2 = T1.merged_transitions() sage: T2 is T1 True
- moments_waiting_time(test=<class 'bool'>, is_zero=None, expectation_only=False)#
If this finite state machine acts as a Markov chain, return the expectation and variance of the number of steps until first writing
True
.INPUT:
test
– (default:bool
) a callable deciding whether an output label is to be consideredTrue
. By default, the standard conversion to boolean is used.is_zero
– (default:None
) a callable deciding whether an expression for a probability is zero. By default, checking for zero is simply done byis_zero()
. This parameter can be used to provide a more sophisticated check for zero, e.g. in the case of symbolic probabilities, see the examples below. This parameter is passed on tois_Markov_chain()
. This parameter only affects the input of the Markov chain.expectation_only
– (default:False
) if set, the variance is not computed (in order to save time). By default, the variance is computed.
OUTPUT:
A dictionary (if
expectation_only=False
) consisting ofexpectation
,variance
.
Otherwise, just the expectation is returned (no dictionary for
expectation_only=True
).Expectation and variance of the number of steps until first writing
True
(as determined by the parametertest
).ALGORITHM:
Relies on a (classical and easy) probabilistic argument, cf. [FGT1992], Eqns. (6) and (7).
For the variance, see [FHP2015], Section 2.
EXAMPLES:
The simplest example is to wait for the first \(1\) in a \(0\)-\(1\)-string where both digits appear with probability \(1/2\). In fact, the waiting time equals \(k\) if and only if the string starts with \(0^{k-1}1\). This event occurs with probability \(2^{-k}\). Therefore, the expected waiting time and the variance are \(\sum_{k\ge 1} k2^{-k}=2\) and \(\sum_{k\ge 1} (k-2)^2 2^{-k}=2\):
sage: var('k') k sage: sum(k * 2^(-k), k, 1, infinity) 2 sage: sum((k-2)^2 * 2^(-k), k, 1, infinity) 2
We now compute the same expectation and variance by using a Markov chain:
sage: from sage.combinat.finite_state_machine import ( ....: duplicate_transition_add_input) sage: T = Transducer( ....: [(0, 0, 1/2, 0), (0, 0, 1/2, 1)], ....: on_duplicate_transition=\ ....: duplicate_transition_add_input, ....: initial_states=[0], ....: final_states=[0]) sage: T.moments_waiting_time() {'expectation': 2, 'variance': 2} sage: T.moments_waiting_time(expectation_only=True) 2
In the following, we replace the output
0
by-1
and demonstrate the use of the parametertest
:sage: T.delete_transition((0, 0, 1/2, 0)) sage: T.add_transition((0, 0, 1/2, -1)) Transition from 0 to 0: 1/2|-1 sage: T.moments_waiting_time(test=lambda x: x<0) {'expectation': 2, 'variance': 2}
Make sure that the transducer is actually a Markov chain. Although this is checked by the code, unexpected behaviour may still occur if the transducer looks like a Markov chain. In the following example, we ‘forget’ to assign probabilities, but due to a coincidence, all ‘probabilities’ add up to one. Nevertheless, \(0\) is never written, so the expectation is \(1\).
sage: T = Transducer([(0, 0, 0, 0), (0, 0, 1, 1)], ....: on_duplicate_transition=\ ....: duplicate_transition_add_input, ....: initial_states=[0], ....: final_states=[0]) sage: T.moments_waiting_time() {'expectation': 1, 'variance': 0}
If
True
is never written, the moments are+Infinity
:sage: T = Transducer([(0, 0, 1, 0)], ....: on_duplicate_transition=\ ....: duplicate_transition_add_input, ....: initial_states=[0], ....: final_states=[0]) sage: T.moments_waiting_time() {'expectation': +Infinity, 'variance': +Infinity}
Let \(h\) and \(r\) be positive integers. We consider random strings of letters \(1\), \(\ldots\), \(r\) where the letter \(j\) occurs with probability \(p_j\). Let \(B\) be the random variable giving the first position of a block of \(h\) consecutive identical letters. Then
\[\begin{split}\begin{aligned} \mathbb{E}(B)&=\frac1{\displaystyle\sum_{i=1}^r \frac1{p_i^{-1}+\cdots+p_i^{-h}}},\\ \mathbb{V}(B)&=\frac{\displaystyle\sum_{i=1}^r\biggl( \frac{p_i +p_i^h}{1-p_i^h} - 2h\frac{ p_i^h(1-p_i)}{(1-p_i^h)^2}\biggr)} {\displaystyle\biggl(\sum_{i=1}^r \frac1{p_i^{-1}+\cdots+p_i^{-h}}\biggr)^2} \end{aligned}\end{split}\]cf. [S1986], p. 62, or [FHP2015], Theorem 1. We now verify this with a transducer approach.
sage: def test(h, r): ....: R = PolynomialRing( ....: QQ, ....: names=['p_%d' % j for j in range(r)]) ....: p = R.gens() ....: def is_zero(polynomial): ....: return polynomial in (sum(p) - 1) * R ....: theory_expectation = 1/(sum(1/sum(p[j]^(-i) ....: for i in range(1, h+1)) ....: for j in range(r))) ....: theory_variance = sum( ....: (p[i] + p[i]^h)/(1 - p[i]^h) ....: - 2*h*p[i]^h * (1 - p[i])/(1 - p[i]^h)^2 ....: for i in range(r) ....: ) * theory_expectation^2 ....: alphabet = list(range(r)) ....: counters = [ ....: transducers.CountSubblockOccurrences([j]*h, ....: alphabet) ....: for j in alphabet] ....: all_counter = counters[0].cartesian_product( ....: counters[1:]) ....: adder = transducers.add(input_alphabet=[0, 1], ....: number_of_operands=r) ....: probabilities = Transducer( ....: [(0, 0, p[j], j) for j in alphabet], ....: initial_states=[0], ....: final_states=[0], ....: on_duplicate_transition=\ ....: duplicate_transition_add_input) ....: chain = adder(all_counter(probabilities)) ....: result = chain.moments_waiting_time( ....: is_zero=is_zero) ....: return is_zero((result['expectation'] - ....: theory_expectation).numerator()) \ ....: and \ ....: is_zero((result['variance'] - ....: theory_variance).numerator()) sage: test(2, 2) True sage: test(2, 3) True sage: test(3, 3) True
Consider the alphabet \(\{0, \ldots, r-1\}\), some \(1\le j\le r\) and some \(h\ge 1\). For some probabilities \(p_0\), \(\ldots\), \(p_{r-1}\), we consider infinite words where the letters occur independently with the given probabilities. The random variable \(B_j\) is the first position \(n\) such that there exist \(j\) of the \(r\) letters having an \(h\)-run. The expectation of \(B_j\) is given in [FHP2015], Theorem 2. Here, we verify this result by using transducers:
sage: def test(h, r, j): ....: R = PolynomialRing( ....: QQ, ....: names=['p_%d' % i for i in range(r)]) ....: p = R.gens() ....: def is_zero(polynomial): ....: return polynomial in (sum(p) - 1) * R ....: alphabet = list(range(r)) ....: counters = [ ....: transducers.Wait([0, 1])( ....: transducers.CountSubblockOccurrences( ....: [i]*h, ....: alphabet)) ....: for i in alphabet] ....: all_counter = counters[0].cartesian_product( ....: counters[1:]) ....: adder = transducers.add(input_alphabet=[0, 1], ....: number_of_operands=r) ....: threshold = transducers.map( ....: f=lambda x: x >= j, ....: input_alphabet=srange(r+1)) ....: probabilities = Transducer( ....: [(0, 0, p[i], i) for i in alphabet], ....: initial_states=[0], ....: final_states=[0], ....: on_duplicate_transition=\ ....: duplicate_transition_add_input) ....: chain = threshold(adder(all_counter( ....: probabilities))) ....: result = chain.moments_waiting_time( ....: is_zero=is_zero, ....: expectation_only=True) ....: R_v = PolynomialRing( ....: QQ, ....: names=['p_%d' % i for i in range(r)]) ....: v = R_v.gens() ....: S = 1/(1 - sum(v[i]/(1+v[i]) ....: for i in range(r))) ....: alpha = [(p[i] - p[i]^h)/(1 - p[i]) ....: for i in range(r)] ....: gamma = [p[i]/(1 - p[i]) for i in range(r)] ....: alphabet_set = set(alphabet) ....: expectation = 0 ....: for q in range(j): ....: for M in Subsets(alphabet_set, q): ....: summand = S ....: for i in M: ....: summand = summand.subs( ....: {v[i]: gamma[i]}) -\ ....: summand.subs({v[i]: alpha[i]}) ....: for i in alphabet_set - set(M): ....: summand = summand.subs( ....: {v[i]: alpha[i]}) ....: expectation += summand ....: return is_zero((result - expectation).\ ....: numerator()) sage: test(2, 3, 2) True
REFERENCES:
[FGT1992]Philippe Flajolet, Danièle Gardy, Loÿs Thimonier, Birthday paradox, coupon collectors, caching algorithms and self-organizing search, Discrete Appl. Math. 39 (1992), 207–229, doi:10.1016/0166-218X(92)90177-C.
[FHP2015] (1,2,3)Uta Freiberg, Clemens Heuberger, Helmut Prodinger, Application of Smirnov Words to Waiting Time Distributions of Runs, arXiv 1503.08096.
[S1986]Gábor J. Székely, Paradoxes in Probability Theory and Mathematical Statistics, D. Reidel Publishing Company.
- number_of_words(variable=None, base_ring=None)#
Return the number of successful input words of given length.
INPUT:
variable
– a symbol denoting the length of the words, by default \(n\).base_ring
– Ring (default:QQbar
) in which to compute the eigenvalues.
OUTPUT:
A symbolic expression.
EXAMPLES:
sage: NAFpm = Automaton([(0, 0, 0), (0, 1, 1), ....: (0, 1, -1), (1, 0, 0)], ....: initial_states=[0], ....: final_states=[0, 1]) sage: N = NAFpm.number_of_words(); N # optional - sage.symbolic 4/3*2^n - 1/3*(-1)^n sage: all(len(list(NAFpm.language(s))) # optional - sage.symbolic ....: - len(list(NAFpm.language(s-1))) == N.subs(n=s) ....: for s in srange(1, 6)) True
An example with non-rational eigenvalues. By default, eigenvalues are elements of the
field of algebraic numbers
.sage: NAFp = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 0)], ....: initial_states=[0], ....: final_states=[0, 1]) sage: N = NAFp.number_of_words(); N # optional - sage.symbolic 1.170820393249937?*1.618033988749895?^n - 0.1708203932499369?*(-0.618033988749895?)^n sage: all(len(list(NAFp.language(s))) # optional - sage.symbolic ....: - len(list(NAFp.language(s-1))) == N.subs(n=s) ....: for s in srange(1, 6)) True
We specify a suitable
base_ring
to obtain a radical expression. To do so, we first compute the characteristic polynomial and then construct a number field generated by its roots.sage: M = NAFp.adjacency_matrix(entry=lambda t: 1) sage: M.characteristic_polynomial() # optional - sage.symbolic x^2 - x - 1 sage: R.<phi> = NumberField(x^2-x-1, embedding=1.6) # optional - sage.symbolic sage: N = NAFp.number_of_words(base_ring=R); N # optional - sage.symbolic 1/2*(1/2*sqrt(5) + 1/2)^n*(3*sqrt(1/5) + 1) - 1/2*(-1/2*sqrt(5) + 1/2)^n*(3*sqrt(1/5) - 1) sage: all(len(list(NAFp.language(s))) # optional - sage.symbolic ....: - len(list(NAFp.language(s-1))) == N.subs(n=s) ....: for s in srange(1, 6)) True
In this special case, we might also use the constant
golden_ratio
:sage: R.<phi> = NumberField(x^2-x-1, embedding=golden_ratio) # optional - sage.symbolic sage: N = NAFp.number_of_words(base_ring=R); N # optional - sage.symbolic 1/5*(3*golden_ratio + 1)*golden_ratio^n - 1/5*(3*golden_ratio - 4)*(-golden_ratio + 1)^n sage: all(len(list(NAFp.language(s))) # optional - sage.symbolic ....: - len(list(NAFp.language(s-1))) == N.subs(n=s) ....: for s in srange(1, 6)) True
The adjacency matrix of the following example is a Jordan matrix of size 3 to the eigenvalue 4:
sage: J3 = Automaton([(0, 1, -1), (1, 2, -1)], ....: initial_states=[0], ....: final_states=[0, 1, 2]) sage: for i in range(3): ....: for j in range(4): ....: new_transition = J3.add_transition(i, i, j) sage: J3.adjacency_matrix(entry=lambda t: 1) [4 1 0] [0 4 1] [0 0 4] sage: N = J3.number_of_words(); N # optional - sage.symbolic 1/2*4^(n - 2)*(n - 1)*n + 4^(n - 1)*n + 4^n sage: all(len(list(J3.language(s))) # optional - sage.symbolic ....: - len(list(J3.language(s-1))) == N.subs(n=s) ....: for s in range(1, 6)) True
Here is an automaton without cycles, so with eigenvalue \(0\).
sage: A = Automaton([(j, j+1, 0) for j in range(3)], ....: initial_states=[0], ....: final_states=list(range(3))) sage: A.number_of_words() # optional - sage.symbolic 1/2*0^(n - 2)*(n - 1)*n + 0^(n - 1)*n + 0^n
- on_duplicate_transition(old_transition, new_transition)#
Which function to call when a duplicate transition is inserted.
It can be set by the parameter
on_duplicate_transition
when initializing a finite state machine, seeFiniteStateMachine
.
- output_alphabet = None#
A list of letters representing the output alphabet of the finite state machine.
It can be set by the parameter
output_alphabet
when initializing a finite state machine, seeFiniteStateMachine
.It can also be set by the method
determine_alphabets()
.See also
- output_projection()#
Return a automaton where the input of each transition of self is deleted and the new input is the original output.
OUTPUT:
An automaton.
EXAMPLES:
sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1), ....: ('B', 'B', 1, 0)]) sage: G = F.output_projection() sage: G.transitions() [Transition from 'A' to 'B': 1|-, Transition from 'A' to 'A': 1|-, Transition from 'B' to 'B': 0|-]
Final output words are also considered correctly:
sage: H = Transducer([('A', 'B', 0, 1), ('A', 'A', 1, 1), ....: ('B', 'B', 1, 0), ('A', ('final', 0), 0, 0)], ....: final_states=['A', 'B']) sage: H.state('B').final_word_out = 2 sage: J = H.output_projection() sage: J.states() ['A', 'B', ('final', 0), ('final', 1)] sage: J.transitions() [Transition from 'A' to 'B': 1|-, Transition from 'A' to 'A': 1|-, Transition from 'A' to ('final', 0): 0|-, Transition from 'B' to 'B': 0|-, Transition from 'B' to ('final', 1): 2|-] sage: J.final_states() ['A', ('final', 1)]
- plot()#
Plots a graph of the finite state machine with labeled vertices and labeled edges.
INPUT:
Nothing.
OUTPUT:
A plot of the graph of the finite state machine.
- predecessors(state, valid_input=None)#
Lists all predecessors of a state.
INPUT:
state
– the state from which the predecessors should be listed.valid_input
– Ifvalid_input
is a list, then we only consider transitions whose input labels are contained invalid_input
.state
has to be aFSMState
(not a label of a state). If input labels of length larger than \(1\) are used, thenvalid_input
has to be a list of lists.
OUTPUT:
A list of states.
EXAMPLES:
sage: A = Transducer([('I', 'A', 'a', 'b'), ('I', 'B', 'b', 'c'), ....: ('I', 'C', 'c', 'a'), ('A', 'F', 'b', 'a'), ....: ('B', 'F', ['c', 'b'], 'b'), ('C', 'F', 'a', 'c')], ....: initial_states=['I'], final_states=['F']) sage: A.predecessors(A.state('A')) ['A', 'I'] sage: A.predecessors(A.state('F'), valid_input=['b', 'a']) ['F', 'C', 'A', 'I'] sage: A.predecessors(A.state('F'), valid_input=[['c', 'b'], 'a']) ['F', 'C', 'B']
- prepone_output()#
For all paths, shift the output of the path from one transition to the earliest possible preceding transition of the path.
INPUT:
Nothing.
OUTPUT:
Nothing.
Apply the following to each state \(s\) (except initial states) of the finite state machine as often as possible:
If the letter \(a\) is a prefix of the output label of all transitions from \(s\) (including the final output of \(s\)), then remove it from all these labels and append it to all output labels of all transitions leading to \(s\).
We assume that the states have no output labels, but final outputs are allowed.
EXAMPLES:
sage: A = Transducer([('A', 'B', 1, 1), ....: ('B', 'B', 0, 0), ....: ('B', 'C', 1, 0)], ....: initial_states=['A'], ....: final_states=['C']) sage: A.prepone_output() sage: A.transitions() [Transition from 'A' to 'B': 1|1,0, Transition from 'B' to 'B': 0|0, Transition from 'B' to 'C': 1|-]
sage: B = Transducer([('A', 'B', 0, 1), ....: ('B', 'C', 1, [1, 1]), ....: ('B', 'C', 0, 1)], ....: initial_states=['A'], ....: final_states=['C']) sage: B.prepone_output() sage: B.transitions() [Transition from 'A' to 'B': 0|1,1, Transition from 'B' to 'C': 1|1, Transition from 'B' to 'C': 0|-]
If initial states are not labeled as such, unexpected results may be obtained:
sage: C = Transducer([(0,1,0,0)]) sage: C.prepone_output() verbose 0 (...: finite_state_machine.py, prepone_output) All transitions leaving state 0 have an output label with prefix 0. However, there is no inbound transition and it is not an initial state. This routine (possibly called by simplification) therefore erased this prefix from all outbound transitions. sage: C.transitions() [Transition from 0 to 1: 0|-]
Also the final output of final states can be changed:
sage: T = Transducer([('A', 'B', 0, 1), ....: ('B', 'C', 1, [1, 1]), ....: ('B', 'C', 0, 1)], ....: initial_states=['A'], ....: final_states=['B']) sage: T.state('B').final_word_out = [1] sage: T.prepone_output() sage: T.transitions() [Transition from 'A' to 'B': 0|1,1, Transition from 'B' to 'C': 1|1, Transition from 'B' to 'C': 0|-] sage: T.state('B').final_word_out []
sage: S = Transducer([('A', 'B', 0, 1), ....: ('B', 'C', 1, [1, 1]), ....: ('B', 'C', 0, 1)], ....: initial_states=['A'], ....: final_states=['B']) sage: S.state('B').final_word_out = [0] sage: S.prepone_output() sage: S.transitions() [Transition from 'A' to 'B': 0|1, Transition from 'B' to 'C': 1|1,1, Transition from 'B' to 'C': 0|1] sage: S.state('B').final_word_out [0]
Output labels do not have to be hashable:
sage: C = Transducer([(0, 1, 0, []), ....: (1, 0, 0, [vector([0, 0]), 0]), ....: (1, 1, 1, [vector([0, 0]), 1]), ....: (0, 0, 1, 0)], ....: determine_alphabets=False, ....: initial_states=[0]) sage: C.prepone_output() sage: sorted(C.transitions()) [Transition from 0 to 1: 0|(0, 0), Transition from 0 to 0: 1|0, Transition from 1 to 0: 0|0, Transition from 1 to 1: 1|1,(0, 0)]
- process(*args, **kwargs)#
Return whether the finite state machine accepts the input, the state where the computation stops and which output is generated.
INPUT:
input_tape
– the input tape can be a list or an iterable with entries from the input alphabet. If we are working with a multi-tape machine (see parameteruse_multitape_input
and notes below), then the tape is a list or tuple of tracks, each of which can be a list or an iterable with entries from the input alphabet.initial_state
orinitial_states
– the initial state(s) in which the machine starts. Either specify a single one withinitial_state
or a list of them withinitial_states
. If both are given,initial_state
will be appended toinitial_states
. If neither is specified, the initial states of the finite state machine are taken.list_of_outputs
– (default:None
) a boolean orNone
. IfTrue
, then the outputs are given in list form (even if we have no or only one single output). IfFalse
, then the result is never a list (an exception is raised if the result cannot be returned). Iflist_of_outputs=None
, the method determines automatically what to do (e.g. if a non-deterministic machine returns more than one path, then the output is returned in list form).only_accepted
– (default:False
) a boolean. If set, then the first argument in the output is guaranteed to beTrue
(if the output is a list, then the first argument of each element will beTrue
).always_include_output
– if set (not by default), always include the output. This is inconsequential for aFiniteStateMachine
, but can be used in derived classes where the output is suppressed by default, cf.Automaton.process()
.format_output
– a function that translates the written output (which is in form of a list) to something more readable. By default (None
) identity is used here.check_epsilon_transitions
– (default:True
) a boolean. IfFalse
, then epsilon transitions are not taken into consideration during process.write_final_word_out
– (default:True
) a boolean specifying whether the final output words should be written or not.use_multitape_input
– (default:False
) a boolean. IfTrue
, then the multi-tape mode of the process iterator is activated. See also the notes below for multi-tape machines.process_all_prefixes_of_input
– (default:False
) a boolean. IfTrue
, then each prefix of the input word is processed (instead of processing the whole input word at once). Consequently, there is an output generated for each of these prefixes.process_iterator_class
– (default:None
) a class inherited fromFSMProcessIterator
. IfNone
, thenFSMProcessIterator
is taken. An instance of this class is created and is used during the processing.automatic_output_type
– (default:False
) a boolean. If set and the input has a parent, then the output will have the same parent. If the input does not have a parent, then the output will be of the same type as the input.
OUTPUT:
A triple (or a list of triples, cf. parameter
list_of_outputs
), wherethe first entry is
True
if the input string is accepted,the second gives the reached state after processing the input tape (This is a state with label
None
if the input could not be processed, i.e., if at one point no transition to go on could be found.), andthe third gives a list of the output labels written during processing (in the case the finite state machine runs as transducer).
Note that in the case the finite state machine is not deterministic, all possible paths are taken into account.
This function uses an iterator which, in its simplest form, goes from one state to another in each step. To decide which way to go, it uses the input words of the outgoing transitions and compares them to the input tape. More precisely, in each step, the iterator takes an outgoing transition of the current state, whose input label equals the input letter of the tape. The output label of the transition, if present, is written on the output tape.
If the choice of the outgoing transition is not unique (i.e., we have a non-deterministic finite state machine), all possibilities are followed. This is done by splitting the process into several branches, one for each of the possible outgoing transitions.
The process (iteration) stops if all branches are finished, i.e., for no branch, there is any transition whose input word coincides with the processed input tape. This can simply happen when the entire tape was read.
Also see
__call__()
for a version ofprocess()
with shortened output.Internally this function creates and works with an instance of
FSMProcessIterator
. This iterator can also be obtained withiter_process()
.If working with multi-tape finite state machines, all input words of transitions are words of \(k\)-tuples of letters. Moreover, the input tape has to consist of \(k\) tracks, i.e., be a list or tuple of \(k\) iterators, one for each track.
Warning
Working with multi-tape finite state machines is still experimental and can lead to wrong outputs.
EXAMPLES:
sage: binary_inverter = FiniteStateMachine({'A': [('A', 0, 1), ('A', 1, 0)]}, ....: initial_states=['A'], final_states=['A']) sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) (True, 'A', [1, 0, 1, 1, 0, 0])
Alternatively, we can invoke this function by:
sage: binary_inverter([0, 1, 0, 0, 1, 1]) (True, 'A', [1, 0, 1, 1, 0, 0])
Below we construct a finite state machine which tests if an input is a non-adjacent form, i.e., no two neighboring letters are both nonzero (see also the example on non-adjacent forms in the documentation of the module Finite state machines, automata, transducers):
sage: NAF = FiniteStateMachine( ....: {'_': [('_', 0), (1, 1)], 1: [('_', 0)]}, ....: initial_states=['_'], final_states=['_', 1]) sage: [NAF.process(w)[0] for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False]
Working only with the first component (i.e., returning whether accepted or not) usually corresponds to using the more specialized class
Automaton
.Non-deterministic finite state machines can be handled as well.
sage: T = Transducer([(0, 1, 0, 0), (0, 2, 0, 0)], ....: initial_states=[0]) sage: T.process([0]) [(False, 1, [0]), (False, 2, [0])]
Here is another non-deterministic finite state machine. Note that we use
format_output
(seeFSMProcessIterator
) to convert the written outputs (all characters) to strings.sage: T = Transducer([(0, 1, [0, 0], 'a'), (0, 2, [0, 0, 1], 'b'), ....: (0, 1, 1, 'c'), (1, 0, [], 'd'), ....: (1, 1, 1, 'e')], ....: initial_states=[0], final_states=[0, 1]) sage: T.process([0], format_output=lambda o: ''.join(o)) (False, None, None) sage: T.process([0, 0], format_output=lambda o: ''.join(o)) [(True, 0, 'ad'), (True, 1, 'a')] sage: T.process([1], format_output=lambda o: ''.join(o)) [(True, 0, 'cd'), (True, 1, 'c')] sage: T.process([1, 1], format_output=lambda o: ''.join(o)) [(True, 0, 'cdcd'), (True, 0, 'ced'), (True, 1, 'cdc'), (True, 1, 'ce')] sage: T.process([0, 0, 1], format_output=lambda o: ''.join(o)) [(False, 2, 'b'), (True, 0, 'adcd'), (True, 0, 'aed'), (True, 1, 'adc'), (True, 1, 'ae')] sage: T.process([0, 0, 1], format_output=lambda o: ''.join(o), ....: only_accepted=True) [(True, 0, 'adcd'), (True, 0, 'aed'), (True, 1, 'adc'), (True, 1, 'ae')]
A simple example of a multi-tape finite state machine is the following: It writes the length of the first tape many letters
a
and then the length of the second tape many lettersb
:sage: M = FiniteStateMachine([(0, 0, (1, None), 'a'), ....: (0, 1, [], []), ....: (1, 1, (None, 1), 'b')], ....: initial_states=[0], ....: final_states=[1]) sage: M.process(([1, 1], [1]), use_multitape_input=True) (True, 1, ['a', 'a', 'b'])
- product_FiniteStateMachine(other, function, new_input_alphabet=None, only_accessible_components=True, final_function=None, new_class=None)#
Return a new finite state machine whose states are \(d\)-tuples of states of the original finite state machines.
INPUT:
other
– a finite state machine (for \(d=2\)) or a list (or iterable) of \(d-1\) finite state machines.function
has to accept \(d\) transitions from \(A_j\) to \(B_j\) for \(j\in\{1, \ldots, d\}\) and returns a pair(word_in, word_out)
which is the label of the transition \(A=(A_1, \ldots, A_d)\) to \(B=(B_1, \ldots, B_d)\). If there is no transition from \(A\) to \(B\), thenfunction
should raise aLookupError
.new_input_alphabet
(optional) – the new input alphabet as a list.only_accessible_components
– IfTrue
(default), then the result is piped throughaccessible_components()
. If nonew_input_alphabet
is given, it is determined bydetermine_alphabets()
.final_function
– A function mapping \(d\) final states of the original finite state machines to the final output of the corresponding state in the new finite state machine. By default, the final output is the empty word if both final outputs of the constituent states are empty; otherwise, aValueError
is raised.new_class
– Class of the new finite state machine. By default (None
), the class ofself
is used.
OUTPUT:
A finite state machine whose states are \(d\)-tuples of states of the original finite state machines. A state is initial or final if all constituent states are initial or final, respectively.
The labels of the transitions are defined by
function
.The final output of a final state is determined by calling
final_function
on the constituent states.The color of a new state is the tuple of colors of the constituent states of
self
andother
. However, if all constituent states have colorNone
, then the state has colorNone
, too.EXAMPLES:
sage: F = Automaton([('A', 'B', 1), ('A', 'A', 0), ('B', 'A', 2)], ....: initial_states=['A'], final_states=['B'], ....: determine_alphabets=True) sage: G = Automaton([(1, 1, 1)], initial_states=[1], final_states=[1]) sage: def addition(transition1, transition2): ....: return (transition1.word_in[0] + transition2.word_in[0], ....: None) sage: H = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False) sage: H.transitions() [Transition from ('A', 1) to ('B', 1): 2|-, Transition from ('A', 1) to ('A', 1): 1|-, Transition from ('B', 1) to ('A', 1): 3|-] sage: [s.color for s in H.iter_states()] [None, None] sage: H1 = F.product_FiniteStateMachine(G, addition, [0, 1, 2, 3], only_accessible_components=False) sage: H1.states()[0].label()[0] is F.states()[0] True sage: H1.states()[0].label()[1] is G.states()[0] True
sage: F = Automaton([(0,1,1/4), (0,0,3/4), (1,1,3/4), (1,0,1/4)], ....: initial_states=[0] ) sage: G = Automaton([(0,0,1), (1,1,3/4), (1,0,1/4)], ....: initial_states=[0] ) sage: H = F.product_FiniteStateMachine( ....: G, lambda t1,t2: (t1.word_in[0]*t2.word_in[0], None)) sage: H.states() [(0, 0), (1, 0)]
sage: F = Automaton([(0,1,1/4), (0,0,3/4), (1,1,3/4), (1,0,1/4)], ....: initial_states=[0] ) sage: G = Automaton([(0,0,1), (1,1,3/4), (1,0,1/4)], ....: initial_states=[0] ) sage: H = F.product_FiniteStateMachine(G, ....: lambda t1,t2: (t1.word_in[0]*t2.word_in[0], None), ....: only_accessible_components=False) sage: H.states() [(0, 0), (1, 0), (0, 1), (1, 1)]
Also final output words are considered according to the function
final_function
:sage: F = Transducer([(0, 1, 0, 1), (1, 1, 1, 1), (1, 1, 0, 1)], ....: final_states=[1]) sage: F.state(1).final_word_out = 1 sage: G = Transducer([(0, 0, 0, 1), (0, 0, 1, 0)], final_states=[0]) sage: G.state(0).final_word_out = 1 sage: def minus(t1, t2): ....: return (t1.word_in[0] - t2.word_in[0], ....: t1.word_out[0] - t2.word_out[0]) sage: H = F.product_FiniteStateMachine(G, minus) Traceback (most recent call last): ... ValueError: A final function must be given. sage: def plus(s1, s2): ....: return s1.final_word_out[0] + s2.final_word_out[0] sage: H = F.product_FiniteStateMachine(G, minus, ....: final_function=plus) sage: H.final_states() [(1, 0)] sage: H.final_states()[0].final_word_out [2]
Products of more than two finite state machines are also possible:
sage: def plus(s1, s2, s3): ....: if s1.word_in == s2.word_in == s3.word_in: ....: return (s1.word_in, ....: sum(s.word_out[0] for s in (s1, s2, s3))) ....: else: ....: raise LookupError sage: T0 = transducers.CountSubblockOccurrences([0, 0], [0, 1, 2]) sage: T1 = transducers.CountSubblockOccurrences([1, 1], [0, 1, 2]) sage: T2 = transducers.CountSubblockOccurrences([2, 2], [0, 1, 2]) sage: T = T0.product_FiniteStateMachine([T1, T2], plus) sage: T.transitions() [Transition from ((), (), ()) to ((0,), (), ()): 0|0, Transition from ((), (), ()) to ((), (1,), ()): 1|0, Transition from ((), (), ()) to ((), (), (2,)): 2|0, Transition from ((0,), (), ()) to ((0,), (), ()): 0|1, Transition from ((0,), (), ()) to ((), (1,), ()): 1|0, Transition from ((0,), (), ()) to ((), (), (2,)): 2|0, Transition from ((), (1,), ()) to ((0,), (), ()): 0|0, Transition from ((), (1,), ()) to ((), (1,), ()): 1|1, Transition from ((), (1,), ()) to ((), (), (2,)): 2|0, Transition from ((), (), (2,)) to ((0,), (), ()): 0|0, Transition from ((), (), (2,)) to ((), (1,), ()): 1|0, Transition from ((), (), (2,)) to ((), (), (2,)): 2|1] sage: T([0, 0, 1, 1, 2, 2, 0, 1, 2, 2]) [0, 1, 0, 1, 0, 1, 0, 0, 0, 1]
other
can also be an iterable:sage: T == T0.product_FiniteStateMachine(iter([T1, T2]), plus) True
- projection(what='input')#
Return an Automaton which transition labels are the projection of the transition labels of the input.
INPUT:
what
– (default:input
) eitherinput
oroutput
.
OUTPUT:
An automaton.
EXAMPLES:
sage: F = FiniteStateMachine([('A', 'B', 0, 1), ('A', 'A', 1, 1), ....: ('B', 'B', 1, 0)]) sage: G = F.projection(what='output') sage: G.transitions() [Transition from 'A' to 'B': 1|-, Transition from 'A' to 'A': 1|-, Transition from 'B' to 'B': 0|-]
- quotient(classes)#
Construct the quotient with respect to the equivalence classes.
INPUT:
classes
is a list of equivalence classes of states.
OUTPUT:
A finite state machine.
The labels of the new states are tuples of states of the
self
, corresponding toclasses
.Assume that \(c\) is a class, and \(a\) and \(b\) are states in \(c\). Then there is a bijection \(\varphi\) between the transitions from \(a\) and the transitions from \(b\) with the following properties: if \(\varphi(t_a)=t_b\), then
\(t_a.\mathit{word}_\mathit{in}=t_b.\mathit{word}_\mathit{in}\),
\(t_a.\mathit{word}_\mathit{out}=t_b.\mathit{word}_\mathit{out}\), and
\(t_a\) and \(t_b\) lead to some equivalent states \(a'\) and \(b'\).
Non-initial states may be merged with initial states, the resulting state is an initial state.
All states in a class must have the same
is_final
,final_word_out
andword_out
values.EXAMPLES:
sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0), ....: ("B", "C", 0, 0), ("B", "C", 1, 1), ....: ("C", "D", 0, 1), ("C", "D", 1, 0), ....: ("D", "A", 0, 0), ("D", "A", 1, 1)]) sage: fsmq = fsm.quotient([[fsm.state("A"), fsm.state("C")], ....: [fsm.state("B"), fsm.state("D")]]) sage: fsmq.transitions() [Transition from ('A', 'C') to ('B', 'D'): 0|1, Transition from ('A', 'C') to ('B', 'D'): 1|0, Transition from ('B', 'D') to ('A', 'C'): 0|0, Transition from ('B', 'D') to ('A', 'C'): 1|1] sage: fsmq.relabeled().transitions() [Transition from 0 to 1: 0|1, Transition from 0 to 1: 1|0, Transition from 1 to 0: 0|0, Transition from 1 to 0: 1|1] sage: fsmq1 = fsm.quotient(fsm.equivalence_classes()) sage: fsmq1 == fsmq True sage: fsm.quotient([[fsm.state("A"), fsm.state("B"), fsm.state("C"), fsm.state("D")]]) Traceback (most recent call last): ... AssertionError: Transitions of state 'A' and 'B' are incompatible.
- relabeled(memo=None, labels=None)#
Return a deep copy of the finite state machine, but the states are relabeled.
INPUT:
memo
– (default:None
) a dictionary storing already processed elements.labels
– (default:None
) a dictionary or callable mapping old labels to new labels. IfNone
, then the new labels are integers starting with 0.
OUTPUT:
A new finite state machine.
EXAMPLES:
sage: FSM1 = FiniteStateMachine([('A', 'B'), ('B', 'C'), ('C', 'A')]) sage: FSM1.states() ['A', 'B', 'C'] sage: FSM2 = FSM1.relabeled() sage: FSM2.states() [0, 1, 2] sage: FSM3 = FSM1.relabeled(labels={'A': 'a', 'B': 'b', 'C': 'c'}) sage: FSM3.states() ['a', 'b', 'c'] sage: FSM4 = FSM2.relabeled(labels=lambda x: 2*x) sage: FSM4.states() [0, 2, 4]
- remove_epsilon_transitions()#
- set_coordinates(coordinates, default=True)#
Set coordinates of the states for the LaTeX representation by a dictionary or a function mapping labels to coordinates.
INPUT:
coordinates
– a dictionary or a function mapping labels of states to pairs interpreted as coordinates.default
– IfTrue
, then states not given bycoordinates
get a default position on a circle of radius 3.
OUTPUT:
Nothing.
EXAMPLES:
sage: F = Automaton([[0, 1, 1], [1, 2, 2], [2, 0, 0]]) sage: F.set_coordinates({0: (0, 0), 1: (2, 0), 2: (1, 1)}) sage: F.state(0).coordinates (0, 0)
We can also use a function to determine the coordinates:
sage: F = Automaton([[0, 1, 1], [1, 2, 2], [2, 0, 0]]) sage: F.set_coordinates(lambda l: (l, 3/(l+1))) sage: F.state(2).coordinates (2, 1)
- split_transitions()#
Return a new transducer, where all transitions in self with input labels consisting of more than one letter are replaced by a path of the corresponding length.
OUTPUT:
A new transducer.
EXAMPLES:
sage: A = Transducer([('A', 'B', [1, 2, 3], 0)], ....: initial_states=['A'], final_states=['B']) sage: A.split_transitions().states() [('A', ()), ('B', ()), ('A', (1,)), ('A', (1, 2))]
- state(state)#
Return the state of the finite state machine.
INPUT:
state
– Ifstate
is not an instance ofFSMState
, then it is assumed that it is the label of a state.
OUTPUT:
The state of the finite state machine corresponding to
state
.If no state is found, then a
LookupError
is thrown.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A') sage: FSM = FiniteStateMachine([(A, 'B'), ('C', A)]) sage: FSM.state('A') == A True sage: FSM.state('xyz') Traceback (most recent call last): ... LookupError: No state with label xyz found.
- states()#
Return the states of the finite state machine.
OUTPUT:
The states of the finite state machine as list.
EXAMPLES:
sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) sage: FSM.states() ['1', '2']
- transition(transition)#
Return the transition of the finite state machine.
INPUT:
transition
– Iftransition
is not an instance ofFSMTransition
, then it is assumed that it is a tuple(from_state, to_state, word_in, word_out)
.
OUTPUT:
The transition of the finite state machine corresponding to
transition
.If no transition is found, then a
LookupError
is thrown.EXAMPLES:
sage: from sage.combinat.finite_state_machine import FSMTransition sage: t = FSMTransition('A', 'B', 0) sage: F = FiniteStateMachine([t]) sage: F.transition(('A', 'B', 0)) Transition from 'A' to 'B': 0|- sage: id(t) == id(F.transition(('A', 'B', 0))) True
- transitions(from_state=None)#
Return a list of all transitions.
INPUT:
from_state
– (default:None
) Iffrom_state
is given, then a list of transitions starting there is given.
OUTPUT:
A list of all transitions.
EXAMPLES:
sage: FSM = Automaton([('1', '2', 1), ('2', '2', 0)]) sage: FSM.transitions() [Transition from '1' to '2': 1|-, Transition from '2' to '2': 0|-]
- transposition(reverse_output_labels=True)#
Return a new finite state machine, where all transitions of the input finite state machine are reversed.
INPUT:
reverse_output_labels
– a boolean (default:True
): whether to reverse output labels.
OUTPUT:
A new finite state machine.
EXAMPLES:
sage: aut = Automaton([('A', 'A', 0), ('A', 'A', 1), ('A', 'B', 0)], ....: initial_states=['A'], final_states=['B']) sage: aut.transposition().transitions('B') [Transition from 'B' to 'A': 0|-]
sage: aut = Automaton([('1', '1', 1), ('1', '2', 0), ('2', '2', 0)], ....: initial_states=['1'], final_states=['1', '2']) sage: aut.transposition().initial_states() ['1', '2']
sage: A = Automaton([(0, 1, [1, 0])], ....: initial_states=[0], ....: final_states=[1]) sage: A([1, 0]) True sage: A.transposition()([0, 1]) True
sage: T = Transducer([(0, 1, [1, 0], [1, 0])], ....: initial_states=[0], ....: final_states=[1]) sage: T([1, 0]) [1, 0] sage: T.transposition()([0, 1]) [0, 1] sage: T.transposition(reverse_output_labels=False)([0, 1]) [1, 0]
- with_final_word_out(letters, allow_non_final=True)#
Constructs a new finite state machine with final output words for all states by implicitly reading trailing letters until a final state is reached.
INPUT:
letters
– either an element of the input alphabet or a list of such elements. This is repeated cyclically when needed.allow_non_final
– a boolean (default:True
) which indicates whether we allow that some states may be non-final in the resulting finite state machine. I.e., ifFalse
then each state has to have a path to a final state with input label matchingletters
.
OUTPUT:
A finite state machine.
The inplace version of this function is
construct_final_word_out()
.Suppose for the moment a single element
letter
as input forletters
. This is equivalent toletters = [letter]
. We will discuss the general case below.Let
word_in
be a word over the input alphabet and assume that the original finite state machine transformsword_in
toword_out
reaching a possibly non-final states
. Let further \(k\) be the minimum number of lettersletter
such that there is a path froms
to some final statef
whose input label consists of \(k\) copies ofletter
and whose output label ispath_word_out
. Then the states
of the resulting finite state machine is a final state with final outputpath_word_out + f.final_word_out
. Therefore, the new finite state machine transformsword_in
toword_out + path_word_out + f.final_word_out
.This is e.g. useful for finite state machines operating on digit expansions: there, it is sometimes required to read a sufficient number of trailing zeros (at the most significant positions) in order to reach a final state and to flush all carries. In this case, this method constructs an essentially equivalent finite state machine in the sense that it not longer requires adding sufficiently many trailing zeros. However, it is the responsibility of the user to make sure that if adding trailing zeros to the input anyway, the output is equivalent.
If
letters
consists of more than one letter, then it is assumed that (not necessarily complete) cycles ofletters
are appended as trailing input.See also
EXAMPLES:
A simple transducer transforming \(00\) blocks to \(01\) blocks:
sage: T = Transducer([(0, 1, 0, 0), (1, 0, 0, 1)], ....: initial_states=[0], ....: final_states=[0]) sage: T.process([0, 0, 0]) (False, 1, [0, 1, 0]) sage: T.process([0, 0, 0, 0]) (True, 0, [0, 1, 0, 1]) sage: F = T.with_final_word_out(0) sage: for f in F.iter_final_states(): ....: print("{} {}".format(f, f.final_word_out)) 0 [] 1 [1] sage: F.process([0, 0, 0]) (True, 1, [0, 1, 0, 1]) sage: F.process([0, 0, 0, 0]) (True, 0, [0, 1, 0, 1])
A more realistic example: Addition of \(1\) in binary. We construct a transition function transforming the input to its binary expansion:
sage: def binary_transition(carry, input): ....: value = carry + input ....: if value.mod(2) == 0: ....: return (value/2, 0) ....: else: ....: return ((value-1)/2, 1)
Now, we only have to start with a carry of \(1\) to get the required transducer:
sage: T = Transducer(binary_transition, ....: input_alphabet=[0, 1], ....: initial_states=[1], ....: final_states=[0])
We test this for the binary expansion of \(7\):
sage: T.process([1, 1, 1]) (False, 1, [0, 0, 0])
The final carry \(1\) has not be flushed yet, we have to add a trailing zero:
sage: T.process([1, 1, 1, 0]) (True, 0, [0, 0, 0, 1])
We check that with this trailing zero, the transducer performs as advertised:
sage: all(ZZ(T(k.bits()+[0]), base=2) == k + 1 ....: for k in srange(16)) True
However, most of the time, we produce superfluous trailing zeros:
sage: T(11.bits()+[0]) [0, 0, 1, 1, 0]
We now use this method:
sage: F = T.with_final_word_out(0) sage: for f in F.iter_final_states(): ....: print("{} {}".format(f, f.final_word_out)) 1 [1] 0 []
The same tests as above, but we do not have to pad with trailing zeros anymore:
sage: F.process([1, 1, 1]) (True, 1, [0, 0, 0, 1]) sage: all(ZZ(F(k.bits()), base=2) == k + 1 ....: for k in srange(16)) True
No more trailing zero in the output:
sage: F(11.bits()) [0, 0, 1, 1] sage: all(F(k.bits())[-1] == 1 ....: for k in srange(16)) True
Here is an example, where we allow trailing repeated \(10\):
sage: T = Transducer([(0, 1, 0, 'a'), ....: (1, 2, 1, 'b'), ....: (2, 0, 0, 'c')], ....: initial_states=[0], ....: final_states=[0]) sage: F = T.with_final_word_out([1, 0]) sage: for f in F.iter_final_states(): ....: print(str(f) + ' ' + ''.join(f.final_word_out)) 0 1 bc
Trying this with trailing repeated \(01\) does not produce a
final_word_out
for state1
, but for state2
:sage: F = T.with_final_word_out([0, 1]) sage: for f in F.iter_final_states(): ....: print(str(f) + ' ' + ''.join(f.final_word_out)) 0 2 c
Here another example with a more-letter trailing input:
sage: T = Transducer([(0, 1, 0, 'a'), ....: (1, 2, 0, 'b'), (1, 2, 1, 'b'), ....: (2, 3, 0, 'c'), (2, 0, 1, 'e'), ....: (3, 1, 0, 'd'), (3, 1, 1, 'd')], ....: initial_states=[0], ....: final_states=[0], ....: with_final_word_out=[0, 0, 1, 1]) sage: for f in T.iter_final_states(): ....: print(str(f) + ' ' + ''.join(f.final_word_out)) 0 1 bcdbcdbe 2 cdbe 3 dbe
- class sage.combinat.finite_state_machine.Transducer(data=None, initial_states=None, final_states=None, input_alphabet=None, output_alphabet=None, determine_alphabets=None, with_final_word_out=None, store_states_dict=True, on_duplicate_transition=None)#
Bases:
FiniteStateMachine
This creates a transducer, which is a finite state machine, whose transitions have input and output labels.
An transducer has additional features like creating a simplified transducer.
See class
FiniteStateMachine
for more information.EXAMPLES:
We can create a transducer performing the addition of 1 (for numbers given in binary and read from right to left) in the following way:
sage: T = Transducer([('C', 'C', 1, 0), ('C', 'N', 0, 1), ....: ('N', 'N', 0, 0), ('N', 'N', 1, 1)], ....: initial_states=['C'], final_states=['N']) sage: T Transducer with 2 states sage: T([0]) [1] sage: T([1,1,0]) [0, 0, 1] sage: ZZ(T(15.digits(base=2)+[0]), base=2) 16
Note that we have padded the binary input sequence by a \(0\) so that the transducer can reach its final state.
- cartesian_product(other, only_accessible_components=True)#
Return a new transducer which can simultaneously process an input with the machines
self
andother
where the output labels are \(d\)-tuples of the original output labels.INPUT:
other
- a finite state machine (if \(d=2\)) or a list (or other iterable) of \(d-1\) finite state machinesonly_accessible_components
– IfTrue
(default), then the result is piped throughaccessible_components()
. If nonew_input_alphabet
is given, it is determined bydetermine_alphabets()
.
OUTPUT:
A transducer which can simultaneously process an input with
self
and the machine(s) inother
.The set of states of the new transducer is the Cartesian product of the set of states of
self
andother
.Let \((A_j, B_j, a_j, b_j)\) for \(j\in\{1, \ldots, d\}\) be transitions in the machines
self
and inother
. Then there is a transition \(((A_1, \ldots, A_d), (B_1, \ldots, B_d), a, (b_1, \ldots, b_d))\) in the new transducer if \(a_1 = \cdots = a_d =: a\).EXAMPLES:
sage: transducer1 = Transducer([('A', 'A', 0, 0), ....: ('A', 'A', 1, 1)], ....: initial_states=['A'], ....: final_states=['A'], ....: determine_alphabets=True) sage: transducer2 = Transducer([(0, 1, 0, ['b', 'c']), ....: (0, 0, 1, 'b'), ....: (1, 1, 0, 'a')], ....: initial_states=[0], ....: final_states=[1], ....: determine_alphabets=True) sage: result = transducer1.cartesian_product(transducer2) sage: result Transducer with 2 states sage: result.transitions() [Transition from ('A', 0) to ('A', 1): 0|(0, 'b'),(None, 'c'), Transition from ('A', 0) to ('A', 0): 1|(1, 'b'), Transition from ('A', 1) to ('A', 1): 0|(0, 'a')] sage: result([1, 0, 0]) [(1, 'b'), (0, 'b'), (None, 'c'), (0, 'a')] sage: (transducer1([1, 0, 0]), transducer2([1, 0, 0])) ([1, 0, 0], ['b', 'b', 'c', 'a'])
Also final output words are correctly processed:
sage: transducer1.state('A').final_word_out = 2 sage: result = transducer1.cartesian_product(transducer2) sage: result.final_states()[0].final_word_out [(2, None)]
The following transducer counts the number of 11 blocks minus the number of 10 blocks over the alphabet