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