From 61e305716aa1f92528c65b6f84d24acf37628857 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 4 Apr 2014 13:41:02 +0200 Subject: [PATCH 01/14] new bahaviour if .process and .__call__ --- src/sage/combinat/finite_state_machine.py | 307 ++++++++++++++++++---- 1 file changed, 260 insertions(+), 47 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index efdaaba6e47..919a19578c2 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -79,6 +79,7 @@ sage: fsm Finite state machine with 2 states + A simple Automaton (recognizing NAFs) --------------------------------------- @@ -100,25 +101,28 @@ So let's test the automaton with some input:: - sage: NAF([0])[0] + sage: NAF([0]) True - sage: NAF([0, 1])[0] + sage: NAF([0, 1]) True - sage: NAF([1, -1])[0] + sage: NAF([1, -1]) False - sage: NAF([0, -1, 0, 1])[0] + sage: NAF([0, -1, 0, 1]) True - sage: NAF([0, -1, -1, -1, 0])[0] + sage: NAF([0, -1, -1, -1, 0]) False - sage: NAF([-1, 0, 0, 1, 1])[0] + sage: NAF([-1, 0, 0, 1, 1]) False Alternatively, we could call that by :: - sage: NAF.process([-1, 0, 0, 1, 1])[0] - False + sage: NAF.process([0, -1, 0, 1]) + (True, 'B') + +which gives additionally the state in which we arrived. + A simple transducer (binary inverter) ------------------------------------- @@ -141,7 +145,7 @@ Now we apply a word to it and see what the transducer does:: sage: inverter([0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1]) - (True, 'A', [1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0]) + [1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0] ``True`` means, that we landed in a final state, that state is labeled ``'A'``, and we also got an output. @@ -150,7 +154,7 @@ A transducer which performs division by `3` in binary ----------------------------------------------------- -Now we build a transducer, which divides a binary number by 3. +Now we build a transducer, which divides a binary number by `3`. The labels of the states are the remainder of the division. The transition function is @@ -165,6 +169,7 @@ ....: write = 1 ....: return (state_to, write) +which assumes reading a binary number from left to right. We get the transducer with :: @@ -172,13 +177,20 @@ sage: D = Transducer(f, initial_states=[0], final_states=[0], ....: input_alphabet=[0, 1]) -Now we want to divide 13 by 3:: +Let us try to divide `12` by `3`:: + + sage: D([1, 1, 0, 0]) + [0, 1, 0, 0] + +Now we want to divide `13` by `3`:: sage: D([1, 1, 0, 1]) - (False, 1, [0, 1, 0, 0]) + Traceback (most recent call last): + ... + ValueError: Invalid input sequence. -So we have 13 : 3 = 4 and the reminder is 1. ``False`` means 13 is not -divisible by 3. +So we have `13 : 3 = 4` and the reminder is `1`. The raised ``ValueError`` +means `13` is not divisible by `3`. Using the hook-functions @@ -236,7 +248,7 @@ our finite automaton by a counter:: sage: from sage.combinat.finite_state_machine import FSMState, FSMTransition - sage: C = Automaton() + sage: C = FiniteStateMachine() sage: def update_counter(state, process): ....: l = process.read_letter() ....: process.fsm.counter += 1 if l == 1 else -1 @@ -521,8 +533,6 @@ def __init__(self, label, word_out=None, sage: FSMState('final', is_final=True) 'final' """ - if label is None or label == "": - raise ValueError, "You have to specify a label for the state." self._label_ = label if isinstance(word_out, list): @@ -1735,7 +1745,8 @@ def __imul__(self, other): def __call__(self, *args, **kwargs): """ - Calls either method :meth:`.composition` or :meth:`.process`. + Calls either method :meth:`.composition` or :meth:`.process` + (with ``full_output=False``). EXAMPLES:: @@ -1743,7 +1754,7 @@ def __call__(self, *args, **kwargs): sage: A = FSMState('A', is_initial=True, is_final=True) sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]}) sage: binary_inverter([0, 1, 0, 0, 1, 1]) - (True, 'A', [1, 0, 1, 1, 0, 0]) + [1, 0, 1, 1, 0, 0] :: @@ -1762,6 +1773,8 @@ def __call__(self, *args, **kwargs): if is_FiniteStateMachine(args[0]): return self.composition(*args, **kwargs) if hasattr(args[0], '__iter__'): + if not kwargs.has_key('full_output'): + kwargs['full_output'] = False return self.process(*args, **kwargs) raise TypeError, "Do not know what to do with that arguments." @@ -2710,10 +2723,12 @@ def process(self, *args, **kwargs): A triple, where - - the first entry is true if the input string is accepted, + - the first entry is ``True`` if the input string is accepted, - the second gives the reached state after processing the - input tape, and + input tape (This is a state with label ``None`` if the input + could not be processed, i.e., when at one point no + transition to go could be found.), and - the third gives a list of the output labels used during processing (in the case the finite state machine runs as @@ -2721,15 +2736,13 @@ def process(self, *args, **kwargs): Note that in the case the finite state machine is not deterministic, one possible path is gone. This means, that in - this case the output can be wrong. Use - :meth:`.determinisation` to get a deterministic finite state - machine and try again. + this case the output can be wrong. EXAMPLES:: sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_initial = True, is_final = True) - sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]}) + sage: binary_inverter = FiniteStateMachine({A:[(A, 0, 1), (A, 1, 0)]}) sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) (True, 'A', [1, 0, 1, 1, 0, 0]) @@ -2742,20 +2755,27 @@ def process(self, *args, **kwargs): sage: NAF_ = FSMState('_', is_initial = True, is_final = True) sage: NAF1 = FSMState('1', is_final = True) - sage: NAF = Automaton( + sage: NAF = FiniteStateMachine( ....: {NAF_: [(NAF_, 0), (NAF1, 1)], NAF1: [(NAF_, 0)]}) sage: [NAF.process(w)[0] for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], - ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] + ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False] - """ + if not kwargs.has_key('full_output'): + kwargs['full_output'] = True + it = self.iter_process(*args, **kwargs) for _ in it: pass - return (it.accept_input, it.current_state, it.output_tape) + + # process output (is the same for the abstract finite state machine) + if kwargs['full_output']: + return (it.accept_input, it.current_state, it.output_tape) + else: + return (it.accept_input, it.current_state, it.output_tape) - def iter_process(self, input_tape=None, initial_state=None): + def iter_process(self, input_tape=None, initial_state=None, **kwargs): """ See `process` for more informations. @@ -2769,7 +2789,7 @@ def iter_process(self, input_tape=None, initial_state=None): sage: it.output_tape [1, 0, 0] """ - return FSMProcessIterator(self, input_tape, initial_state) + return FSMProcessIterator(self, input_tape, initial_state, **kwargs) #************************************************************************* @@ -4363,18 +4383,18 @@ class Automaton(FiniteStateMachine): ....: initial_states=['P'], final_states=['Q']) sage: A Automaton with 2 states - sage: A([0])[0] + sage: A([0]) True - sage: A([1,1,0])[0] + sage: A([1, 1, 0]) True - sage: A([1,0,1])[0] + sage: A([1, 0, 1]) False - Note that the full output of the commands above gives more - information and looks like this:: + Note that the full output of the commands can be obtained by + calling :meth:`.process` and looks like this:: - sage: A([1,0,1]) - (False, 'P', []) + sage: A.process([1,0,1]) + (False, 'P') TESTS:: @@ -4489,7 +4509,7 @@ def determinisation(self): sage: auto.is_deterministic() False sage: auto.process(list('aaab')) - (False, 'A', []) + (False, 'A') sage: auto.states() ['A', 'C', 'B'] sage: auto.determinisation() @@ -4652,6 +4672,89 @@ def _minimization_Moore_(self): return self.quotient(self.equivalence_classes()) + def process(self, *args, **kwargs): + """ + Returns whether the automaton accepts the input and the state + where the computation stops. + + INPUT: + + - ``input_tape`` -- The input tape can be a list with entries from + the input alphabet. + + - ``initial_state`` -- (default: ``None``) The state in which + to start. If this parameter is ``None`` and there is only + one initial state in the machine, then this state is taken. + + - ``full_output`` -- (default: ``True``) If set, then the full + output is given, otherwise only if the sequence is accepted + or not (the first entry below only). + + OUTPUT: + + The full output is a pair, where + + - the first entry is ``True`` if the input string is accepted and + + - the second gives the reached state after processing the + input tape (This is a state with label ``None`` if the input + could not be processed, i.e., when at one point no + transition to go could be found.). + + Note that in the case the automaton is not + deterministic, one possible path is gone. This means, that in + this case the output can be wrong. Use + :meth:`.determinisation` to get a deterministic automaton + machine and try again. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import FSMState + sage: NAF_ = FSMState('_', is_initial = True, is_final = True) + sage: NAF1 = FSMState('1', is_final = True) + sage: NAF = Automaton( + ....: {NAF_: [(NAF_, 0), (NAF1, 1)], NAF1: [(NAF_, 0)]}) + sage: [NAF.process(w) for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], + ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] + [(True, '_'), (True, '1'), (False, None), + (True, '1'), (False, None), (False, None)] + + If we just want a condensed output, we use:: + + sage: [NAF.process(w, full_output=False) + ....: for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], + ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] + [True, True, False, True, False, False] + + It is equivalent to:: + + sage: [NAF(w) for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], + ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] + [True, True, False, True, False, False] + + TESTS:: + + sage: NAF.process([2, 2]) + (False, None) + sage: NAF.add_transition(('_', 's', 3)) + Transition from '_' to 's': 3|- + sage: NAF.process([3]) + (False, 's') + """ + if not kwargs.has_key('full_output'): + kwargs['full_output'] = True + + it = self.iter_process(*args, **kwargs) + for _ in it: + pass + + # process output + if kwargs['full_output']: + return (it.accept_input, it.current_state) + else: + return it.accept_input + + #***************************************************************************** @@ -4694,10 +4797,10 @@ class Transducer(FiniteStateMachine): sage: T Transducer with 2 states sage: T([0]) - (True, 'N', [1]) + [1] sage: T([1,1,0]) - (True, 'N', [0, 0, 1]) - sage: ZZ(T(15.digits(base=2)+[0])[2], base=2) + [0, 0, 1] + sage: ZZ(T(15.digits(base=2)+[0]), base=2) 16 Note that we have padded the binary input sequence by a `0` so @@ -4807,6 +4910,111 @@ def simplification(self): return fsm.quotient(fsm.equivalence_classes()) + def process(self, *args, **kwargs): + """ + Returns whether the transducer accepts the input, the state + where the computation stops and which output is generated. + + INPUT: + + - ``input_tape`` -- The input tape can be a list with entries from + the input alphabet. + + - ``initial_state`` -- (default: ``None``) The state in which + to start. If this parameter is ``None`` and there is only + one initial state in the machine, then this state is taken. + + - ``full_output`` -- (default: ``True``) If set, then the full + output is given, otherwise only the generated output (the + third entry below only). If the input is not accepted, a + ``ValueError`` is raised. + + OUTPUT: + + The full output is a triple, where + + - the first entry is ``True`` if the input string is accepted, + + - the second gives the reached state after processing the + input tape (This is a state with label ``None`` if the input + could not be processed, i.e., when at one point no + transition to go could be found.), and + + - the third gives a list of the output labels used during + processing (in the case the finite state machine runs as + transducer). + + Note that in the case the transducer is not + deterministic, one possible path is gone. This means, that in + this case the output can be wrong. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import FSMState + sage: A = FSMState('A', is_initial = True, is_final = True) + sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]}) + sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) + (True, 'A', [1, 0, 1, 1, 0, 0]) + + Alternatively, we can invoke this function by:: + + sage: binary_inverter([0, 1, 0, 0, 1, 1]) + [1, 0, 1, 1, 0, 0] + + The following transducer transforms `0^n 1` to `1^n 2`:: + + sage: T = Transducer([(0, 0, 0, 1), (0, 1, 1, 2)]) + sage: T.state(0).is_initial = True + sage: T.state(1).is_final = True + + We can see the different possibilites of the output by:: + + sage: [T.process(w) for w in [[1], [0, 1], [0, 0, 1], [0, 1, 1], + ....: [0], [0, 0], [2, 0], [0, 1, 2]]] + [(True, 1, [2]), (True, 1, [1, 2]), + (True, 1, [1, 1, 2]), (False, None, None), + (False, 0, [1]), (False, 0, [1, 1]), + (False, None, None), (False, None, None)] + + If we just want a condensed output, we use:: + + sage: [T.process(w, full_output=False) + ....: for w in [[1], [0, 1], [0, 0, 1]]] + [[2], [1, 2], [1, 1, 2]] + sage: T.process([0, 1, 2], full_output=False) + Traceback (most recent call last): + ... + ValueError: Invalid input sequence. + + It is equivalent to:: + + sage: [T(w) for w in [[1], [0, 1], [0, 0, 1]]] + [[2], [1, 2], [1, 1, 2]] + sage: T([0, 1, 2]) + Traceback (most recent call last): + ... + ValueError: Invalid input sequence. + """ + if not kwargs.has_key('full_output'): + kwargs['full_output'] = True + + it = self.iter_process(*args, **kwargs) + for _ in it: + pass + + # process output + if kwargs['full_output']: + if it.current_state.label() is None: + return (it.accept_input, it.current_state, None) + else: + return (it.accept_input, it.current_state, it.output_tape) + else: + if not it.accept_input: + raise ValueError, "Invalid input sequence." + return it.output_tape + + + #***************************************************************************** @@ -4907,7 +5115,7 @@ class FSMProcessIterator: sage: it.accept_input True """ - def __init__(self, fsm, input_tape=None, initial_state=None): + def __init__(self, fsm, input_tape=None, initial_state=None, **kwargs): """ See :class:`FSMProcessIterator` for more information. @@ -5014,7 +5222,7 @@ def next(self): except StopIteration: # this means input tape is finished if len(next_word) > 0: - self.accept_input = False + self.current_state = FSMState(None) raise StopIteration # process transition @@ -5031,7 +5239,7 @@ def next(self): # this means, either input tape is finished or # someone has thrown StopIteration manually (in one # of the hooks) - if not self.current_state.is_final: + if self.current_state.label is None or not self.current_state.is_final: self.accept_input = False if not hasattr(self, 'accept_input'): self.accept_input = True @@ -5127,7 +5335,8 @@ def get_next_transition(self, word_in): OUTPUT: The next transition according to ``word_in``. It is assumed - that we are in state ``self.current_state``. + that we are in state ``self.current_state``. If no transition + matches, a ``ValueError`` is thorwn. EXAMPLES:: @@ -5137,11 +5346,15 @@ def get_next_transition(self, word_in): sage: it = FSMProcessIterator(inverter, input_tape=[0, 1]) sage: it.get_next_transition([0]) Transition from 'A' to 'A': 0|1 + sage: it.get_next_transition([2]) + Traceback (most recent call last): + ... + ValueError: No transition with input [2] found. """ for transition in self.current_state.transitions: if transition.word_in == word_in: return transition - raise ValueError + raise ValueError, "No transition with input %s found." % (word_in,) #***************************************************************************** From 6b03c0dc2653473384ac118f7069d8f1d83bb504 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 4 Apr 2014 14:10:15 +0200 Subject: [PATCH 02/14] Deprecation-Warning added --- src/sage/combinat/finite_state_machine.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 919a19578c2..b1e45f6fcf1 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -414,6 +414,7 @@ def full_group_by(l, key=lambda x: x): #***************************************************************************** FSMEmptyWordSymbol = '-' +FSMOldOutputProcess = False def FSMLetterSymbol(letter): """ @@ -4741,6 +4742,15 @@ def process(self, *args, **kwargs): sage: NAF.process([3]) (False, 's') """ + if FSMOldOutputProcess: + from sage.misc.superseded import deprecation + deprecation(33333, "The output of Automaton.process " + "(and thus of Automaton.__call__) " + "will change. Please use the corresponding " + "functions from FiniteStateMachine " + "for the original output.") + return super(Automaton, self).process(*args, **kwargs) + if not kwargs.has_key('full_output'): kwargs['full_output'] = True @@ -4995,6 +5005,15 @@ def process(self, *args, **kwargs): ... ValueError: Invalid input sequence. """ + if FSMOldOutputProcess: + from sage.misc.superseded import deprecation + deprecation(33333, "The output of Transducer.process " + "(and thus of Transducer.__call__) " + "will change. Please use the corresponding " + "functions from FiniteStateMachine " + "for the original output.") + return super(Transducer, self).process(*args, **kwargs) + if not kwargs.has_key('full_output'): kwargs['full_output'] = True From 5eb611a44c77807c2d21001da2580cf434d8a683 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 4 Apr 2014 14:16:46 +0200 Subject: [PATCH 03/14] warnings added; FSMOldOutputProcess documented --- src/sage/combinat/finite_state_machine.py | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index b1e45f6fcf1..eb7817e0d8d 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -118,7 +118,7 @@ :: - sage: NAF.process([0, -1, 0, 1]) + Sage: NAF.process([0, -1, 0, 1]) (True, 'B') which gives additionally the state in which we arrived. @@ -1746,9 +1746,19 @@ def __imul__(self, other): def __call__(self, *args, **kwargs): """ + .. WARNING:: + + The default output of this method is scheduled to change. + This docstring describes the new default behaviour, which can + already be achieved by setting + ``FSMOldOutputProcess`` to ``False``. + Calls either method :meth:`.composition` or :meth:`.process` (with ``full_output=False``). + By setting ``FSMOldOutputProcess`` to ``False`` + the new desired output is produced. + EXAMPLES:: sage: from sage.combinat.finite_state_machine import FSMState @@ -4675,6 +4685,13 @@ def _minimization_Moore_(self): def process(self, *args, **kwargs): """ + .. WARNING:: + + The default output of this method is scheduled to change. + This docstring describes the new default behaviour, which can + already be achieved by setting + ``FSMOldOutputProcess`` to ``False``. + Returns whether the automaton accepts the input and the state where the computation stops. @@ -4708,6 +4725,9 @@ def process(self, *args, **kwargs): :meth:`.determinisation` to get a deterministic automaton machine and try again. + By setting ``FSMOldOutputProcess`` to ``False`` + the new desired output is produced. + EXAMPLES:: sage: from sage.combinat.finite_state_machine import FSMState @@ -4922,6 +4942,13 @@ def simplification(self): def process(self, *args, **kwargs): """ + .. WARNING:: + + The default output of this method is scheduled to change. + This docstring describes the new default behaviour, which can + already be achieved by setting + ``FSMOldOutputProcess`` to ``False``. + Returns whether the transducer accepts the input, the state where the computation stops and which output is generated. @@ -4958,6 +4985,9 @@ def process(self, *args, **kwargs): deterministic, one possible path is gone. This means, that in this case the output can be wrong. + By setting ``FSMOldOutputProcess`` to ``False`` + the new desired output is produced. + EXAMPLES:: sage: from sage.combinat.finite_state_machine import FSMState From 4bde242c28b9c3a15d9cbea37610ce35dec9ee4a Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 4 Apr 2014 14:35:47 +0200 Subject: [PATCH 04/14] activated old output of process and added "FSMOldProcessOutput=False" to relevant doctest --- src/sage/combinat/finite_state_machine.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index eb7817e0d8d..112edb0e123 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -101,6 +101,7 @@ So let's test the automaton with some input:: + sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: NAF([0]) True sage: NAF([0, 1]) @@ -414,7 +415,7 @@ def full_group_by(l, key=lambda x: x): #***************************************************************************** FSMEmptyWordSymbol = '-' -FSMOldOutputProcess = False +FSMOldProcessOutput = True # See trac #33333 (depreciation). def FSMLetterSymbol(letter): """ @@ -1751,16 +1752,17 @@ def __call__(self, *args, **kwargs): The default output of this method is scheduled to change. This docstring describes the new default behaviour, which can already be achieved by setting - ``FSMOldOutputProcess`` to ``False``. + ``FSMOldProcessOutput`` to ``False``. Calls either method :meth:`.composition` or :meth:`.process` (with ``full_output=False``). - By setting ``FSMOldOutputProcess`` to ``False`` + By setting ``FSMOldProcessOutput`` to ``False`` the new desired output is produced. EXAMPLES:: + sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_initial=True, is_final=True) sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]}) @@ -4690,7 +4692,7 @@ def process(self, *args, **kwargs): The default output of this method is scheduled to change. This docstring describes the new default behaviour, which can already be achieved by setting - ``FSMOldOutputProcess`` to ``False``. + ``FSMOldProcessOutput`` to ``False``. Returns whether the automaton accepts the input and the state where the computation stops. @@ -4725,11 +4727,12 @@ def process(self, *args, **kwargs): :meth:`.determinisation` to get a deterministic automaton machine and try again. - By setting ``FSMOldOutputProcess`` to ``False`` + By setting ``FSMOldProcessOutput`` to ``False`` the new desired output is produced. EXAMPLES:: + sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: from sage.combinat.finite_state_machine import FSMState sage: NAF_ = FSMState('_', is_initial = True, is_final = True) sage: NAF1 = FSMState('1', is_final = True) @@ -4762,7 +4765,7 @@ def process(self, *args, **kwargs): sage: NAF.process([3]) (False, 's') """ - if FSMOldOutputProcess: + if FSMOldProcessOutput: from sage.misc.superseded import deprecation deprecation(33333, "The output of Automaton.process " "(and thus of Automaton.__call__) " @@ -4947,7 +4950,7 @@ def process(self, *args, **kwargs): The default output of this method is scheduled to change. This docstring describes the new default behaviour, which can already be achieved by setting - ``FSMOldOutputProcess`` to ``False``. + ``FSMOldProcessOutput`` to ``False``. Returns whether the transducer accepts the input, the state where the computation stops and which output is generated. @@ -4985,11 +4988,12 @@ def process(self, *args, **kwargs): deterministic, one possible path is gone. This means, that in this case the output can be wrong. - By setting ``FSMOldOutputProcess`` to ``False`` + By setting ``FSMOldProcessOutput`` to ``False`` the new desired output is produced. EXAMPLES:: + sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False # activate new output behavior sage: from sage.combinat.finite_state_machine import FSMState sage: A = FSMState('A', is_initial = True, is_final = True) sage: binary_inverter = Transducer({A:[(A, 0, 1), (A, 1, 0)]}) @@ -5035,7 +5039,7 @@ def process(self, *args, **kwargs): ... ValueError: Invalid input sequence. """ - if FSMOldOutputProcess: + if FSMOldProcessOutput: from sage.misc.superseded import deprecation deprecation(33333, "The output of Transducer.process " "(and thus of Transducer.__call__) " From 2d43fdda50bd5bad3b079e7861fbb725471d1d04 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Sat, 5 Apr 2014 07:28:36 +0200 Subject: [PATCH 05/14] FiniteStateMachine: add failing doctests: non-hashable colors The following behaviour is undesired (docstrings says that colors are tuples of the colors of the constituent states, but this is not the case and leads to problems in Automaton.determinisation): sage: A = Automaton([[0, 0, 0]], initial_states=[0]) sage: B = A.product_FiniteStateMachine(A, ....: lambda t1, t2: (0, None)) sage: B.states()[0].color [None, None] sage: B.determinisation() Traceback (most recent call last): ... TypeError: unhashable type: 'list' sage: A = Automaton([[0, 0, 0]], initial_states=[0]) sage: B = A.composition(A, algorithm='explorative') sage: B.states()[0].color [None, None] sage: B.determinisation() Traceback (most recent call last): ... TypeError: unhashable type: 'list' Inserted doctests documenting this undesired behaviour. --- src/sage/combinat/finite_state_machine.py | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 61fefa07053..6161a760071 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3625,6 +3625,21 @@ def product_FiniteStateMachine(self, other, function, ....: only_accessible_components=False) sage: H.states() [(0, 0), (1, 0), (0, 1), (1, 1)] + + TESTS: + + Check that colors are correctly dealt with. In particular, the + new colors have to be hashable such that + :meth:`Automaton.determinisation` does not fail:: + + sage: A = Automaton([[0, 0, 0]], initial_states=[0]) + sage: B = A.product_FiniteStateMachine(A, + ....: lambda t1, t2: (0, None)) + sage: B.states()[0].color + (None, None) + sage: B.determinisation() + Automaton with 1 states + """ result = self.empty_copy() if new_input_alphabet is not None: @@ -3902,6 +3917,17 @@ def _composition_explorative_(self, other): Transition from ('B', 1) to ('B', 1): 0|0, Transition from ('B', 1) to ('B', 2): 1|0] + Check that colors are correctly dealt with. In particular, the + new colors have to be hashable such that + :meth:`Automaton.determinisation` does not fail:: + + sage: A = Automaton([[0, 0, 0]], initial_states=[0]) + sage: B = A.composition(A, algorithm='explorative') + sage: B.states()[0].color + (None, None) + sage: B.determinisation() + Automaton with 1 states + TODO: The explorative algorithm should be re-implemented using the From 547968aaa6eff0d3c96018bc72f080a188881134 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Sat, 5 Apr 2014 07:30:02 +0200 Subject: [PATCH 06/14] FiniteStateMachine.product_FiniteStateMachine, composition: fix unhashable colors This fixes the failing doctests introduced in commit 2d43fdda50bd5bad3b079e7861fbb725471d1d04. --- src/sage/combinat/finite_state_machine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 6161a760071..0983571350d 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3663,7 +3663,7 @@ def product_FiniteStateMachine(self, other, function, state.is_initial = True if all(map(lambda s: s.is_final, state.label())): state.is_final = True - state.color = map(lambda s: s.color, state.label()) + state.color = tuple(map(lambda s: s.color, state.label())) if only_accessible_components: if new_input_alphabet is None: @@ -3965,7 +3965,7 @@ def composition_transition(state, input): for state in F.states(): if all(map(lambda s: s.is_final, state.label())): state.is_final = True - state.color = map(lambda s: s.color, state.label()) + state.color = tuple(map(lambda s: s.color, state.label())) return F From d51be51b8299930abc0e6b72700eca79cd978cfc Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Sat, 5 Apr 2014 07:31:42 +0200 Subject: [PATCH 07/14] Automaton.determinisation(): docstring on hashable colors When Automaton.determinisation() is to be called, colors have to be hashable. This is now documented both in FSMState and in Automaton.determinisation. --- src/sage/combinat/finite_state_machine.py | 33 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 0983571350d..138e6716fc3 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -494,8 +494,10 @@ class FSMState(SageObject): - ``color`` -- (default: ``None``) In order to distinguish states, they can be given an arbitrary "color" (an arbitrary object). - This is used in :meth:`equivalence_classes`: states of different - colors are never considered to be equivalent. + This is used in :meth:`FiniteStateMachine.equivalence_classes`: + states of different colors are never considered to be + equivalent. Note that :meth:`Automaton.determinisation` requires + that ``color`` is hashable. OUTPUT: @@ -513,6 +515,19 @@ class FSMState(SageObject): sage: A == B False + Note that :meth:`Automaton.determinisation` requires that ``color`` + is hashable:: + + sage: A = Automaton([[0, 0, 0]], initial_states=[0]) + sage: A.state(0).color = [] + sage: A.determinisation() + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + sage: A.state(0).color = () + sage: A.determinisation() + Automaton with 1 states + """ def __init__(self, label, word_out=None, is_initial=False, is_final=False, @@ -4720,6 +4735,8 @@ def determinisation(self): The labels of the states of the new automaton are frozensets of states of ``self``. The color of a new state is the frozenset of colors of the constituent states of ``self``. + Therefore, the colors of the constituent states have to be + hashable. The input alphabet must be specified. It is restricted to nice cases: input words have to have length at most `1`. @@ -4751,6 +4768,18 @@ def determinisation(self): [frozenset(['A']), frozenset(['A', 'B']), frozenset(['A', 'C']), frozenset(['A', 'C', 'B'])] + Note that colors of states have to be hashable:: + + sage: A = Automaton([[0, 0, 0]], initial_states=[0]) + sage: A.state(0).color = [] + sage: A.determinisation() + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + sage: A.state(0).color = () + sage: A.determinisation() + Automaton with 1 states + TESTS: This is from #15078, comment 13. From 2269f8b00c6ddfd55a22512eb9245e7204cae09d Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 11 Apr 2014 13:50:06 +0200 Subject: [PATCH 08/14] finite_state_machine.process: Minor changes Removed full_output from FiniteStateMachine.process, Simplified if/else doing the same, Added doctests, Corrected typos. --- src/sage/combinat/finite_state_machine.py | 72 +++++++++++++---------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 112edb0e123..2aa94f67ed7 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -119,7 +119,7 @@ :: - Sage: NAF.process([0, -1, 0, 1]) + sage: NAF.process([0, -1, 0, 1]) (True, 'B') which gives additionally the state in which we arrived. @@ -190,7 +190,7 @@ ... ValueError: Invalid input sequence. -So we have `13 : 3 = 4` and the reminder is `1`. The raised ``ValueError`` +The raised ``ValueError`` means `13` is not divisible by `3`. @@ -415,7 +415,7 @@ def full_group_by(l, key=lambda x: x): #***************************************************************************** FSMEmptyWordSymbol = '-' -FSMOldProcessOutput = True # See trac #33333 (depreciation). +FSMOldProcessOutput = True # See trac #33333 (deprecation). def FSMLetterSymbol(letter): """ @@ -2774,18 +2774,12 @@ def process(self, *args, **kwargs): ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False] """ - if not kwargs.has_key('full_output'): - kwargs['full_output'] = True - it = self.iter_process(*args, **kwargs) for _ in it: pass # process output (is the same for the abstract finite state machine) - if kwargs['full_output']: - return (it.accept_input, it.current_state, it.output_tape) - else: - return (it.accept_input, it.current_state, it.output_tape) + return (it.accept_input, it.current_state, it.output_tape) def iter_process(self, input_tape=None, initial_state=None, **kwargs): @@ -4707,7 +4701,7 @@ def process(self, *args, **kwargs): one initial state in the machine, then this state is taken. - ``full_output`` -- (default: ``True``) If set, then the full - output is given, otherwise only if the sequence is accepted + output is given, otherwise only whether the sequence is accepted or not (the first entry below only). OUTPUT: @@ -4716,13 +4710,13 @@ def process(self, *args, **kwargs): - the first entry is ``True`` if the input string is accepted and - - the second gives the reached state after processing the + - the second gives the state reached after processing the input tape (This is a state with label ``None`` if the input could not be processed, i.e., when at one point no transition to go could be found.). Note that in the case the automaton is not - deterministic, one possible path is gone. This means, that in + deterministic, one possible path is gone. This means that in this case the output can be wrong. Use :meth:`.determinisation` to get a deterministic automaton machine and try again. @@ -4756,13 +4750,13 @@ def process(self, *args, **kwargs): ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False] - TESTS:: + :: - sage: NAF.process([2, 2]) + sage: NAF.process([2]) (False, None) - sage: NAF.add_transition(('_', 's', 3)) - Transition from '_' to 's': 3|- - sage: NAF.process([3]) + sage: NAF.add_transition(('_', 's', 2)) + Transition from '_' to 's': 2|- + sage: NAF.process([2]) (False, 's') """ if FSMOldProcessOutput: @@ -4981,8 +4975,7 @@ def process(self, *args, **kwargs): transition to go could be found.), and - the third gives a list of the output labels used during - processing (in the case the finite state machine runs as - transducer). + processing. Note that in the case the transducer is not deterministic, one possible path is gone. This means, that in @@ -5000,7 +4993,7 @@ def process(self, *args, **kwargs): sage: binary_inverter.process([0, 1, 0, 0, 1, 1]) (True, 'A', [1, 0, 1, 1, 0, 0]) - Alternatively, we can invoke this function by:: + If we are only interested in the output, we can also use:: sage: binary_inverter([0, 1, 0, 0, 1, 1]) [1, 0, 1, 1, 0, 0] @@ -5078,15 +5071,13 @@ def is_FSMProcessIterator(PI): TESTS:: sage: from sage.combinat.finite_state_machine import is_FSMProcessIterator, FSMProcessIterator - sage: is_FSMProcessIterator(FSMProcessIterator(FiniteStateMachine())) - Traceback (most recent call last): - ... - ValueError: No state is initial. + sage: is_FSMProcessIterator(FSMProcessIterator(FiniteStateMachine([[0, 0, 0, 0]], initial_states=[0]))) + True """ return isinstance(PI, FSMProcessIterator) -class FSMProcessIterator: +class FSMProcessIterator(SageObject): """ This class is for processing an input string on a finite state machine. @@ -5111,21 +5102,21 @@ class FSMProcessIterator: iterable. - ``initial_state`` -- The initial state in which the machine - starts. If this is ``None``, the unique inital state of the - finite state machine is takes. If there are several, an error is - reported. + starts. If this is ``None``, the unique inital state of the finite + state machine is takes. If there are several, a ``ValueError`` is + raised. The process (iteration) stops if there are no more input letters on the tape. In this case a StopIteration exception is thrown. As result the following attributes are available: - - ``accept_input`` -- Is True if the reached state is a final state. + - ``accept_input`` -- Is ``True`` if the reached state is a final state. - ``current_state`` -- The current/reached state in the process. - ``output_tape`` -- The written output. - Current values of those attribtes (except ``accept_input``) are + Current values of those attributes (except ``accept_input``) are (also) available during the iteration. OUTPUT: @@ -5167,6 +5158,23 @@ class FSMProcessIterator: ('A', [1, 0, 0, 1, 0, 1, 0]) sage: it.accept_input True + + TESTS:: + + sage: T = Transducer([[0, 0, 0, 0]]) + sage: T.process([]) + Traceback (most recent call last): + ... + ValueError: No state is initial. + + :: + + sage: T = Transducer([[0, 1, 0, 0]], initial_states=[0, 1]) + sage: T.process([]) + Traceback (most recent call last): + ... + ValueError: Several initial states. + """ def __init__(self, fsm, input_tape=None, initial_state=None, **kwargs): """ @@ -5389,7 +5397,7 @@ def get_next_transition(self, word_in): The next transition according to ``word_in``. It is assumed that we are in state ``self.current_state``. If no transition - matches, a ``ValueError`` is thorwn. + matches, a ``ValueError`` is thrown. EXAMPLES:: From a3423ff7a44a703a3da695e98c6dc6c0516ca388 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 11 Apr 2014 14:30:31 +0200 Subject: [PATCH 09/14] disallowing state ``None`` --- src/sage/combinat/finite_state_machine.py | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 2aa94f67ed7..fda2c6fa7ce 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -506,6 +506,10 @@ class FSMState(SageObject): - ``hook`` -- (default: ``None``) A function which is called when the state is reached during processing input. + - ``allow_label_None`` -- (default: ``False``) If ``True`` allows also + ``None`` as label. Note that a state with label ``None`` is used in + :class:`FSMProcessIterator`. + OUTPUT: Returns a state of a finite state machine. @@ -522,10 +526,22 @@ class FSMState(SageObject): sage: A == B False + It is not allowed to use ``None`` as a label:: + + sage: from sage.combinat.finite_state_machine import FSMState + sage: FSMState(None) + Traceback (most recent call last): + ... + ValueError: Label None reserved for a special state, choose another label. + + This can be overridden by:: + + sage: FSMState(None, allow_label_None=True) + None """ def __init__(self, label, word_out=None, is_initial=False, is_final=False, - hook=None): + hook=None, allow_label_None=False): """ See :class:`FSMState` for more information. @@ -535,6 +551,9 @@ def __init__(self, label, word_out=None, sage: FSMState('final', is_final=True) 'final' """ + if not allow_label_None and label is None: + raise ValueError, "Label None reserved for a special state, " \ + "choose another label." self._label_ = label if isinstance(word_out, list): @@ -5283,7 +5302,8 @@ def next(self): except StopIteration: # this means input tape is finished if len(next_word) > 0: - self.current_state = FSMState(None) + self.current_state = FSMState(None, + allow_label_None=True) raise StopIteration # process transition From 75aa9e18cff712cc97365838e8fcb8cd23fed5e6 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 11 Apr 2014 14:40:57 +0200 Subject: [PATCH 10/14] corrected doctests (after change of output) --- src/sage/combinat/finite_state_machine.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 0bc22013d73..2e6930679d0 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4471,9 +4471,9 @@ def intersection(self, other, only_accessible_components=True): ....: final_states=['B'], ....: determine_alphabets=True) sage: res = aut1.intersection(aut2) - sage: (aut1([1, 1])[0], aut2([1, 1])[0], res([1, 1])[0]) + sage: (aut1([1, 1]), aut2([1, 1]), res([1, 1])) (True, False, False) - sage: (aut1([1, 0])[0], aut2([1, 0])[0], res([1, 0])[0]) + sage: (aut1([1, 0]), aut2([1, 0]), res([1, 0])) (True, True, True) sage: res.transitions() [Transition from ('1', 'A') to ('2', 'A'): 1|-, @@ -5130,9 +5130,9 @@ def cartesian_product(self, other, only_accessible_components=True): [Transition from ('A', 0) to ('A', 1): 0|(0, 'b'),(None, 'c'), Transition from ('A', 0) to ('A', 0): 1|(1, 'b'), Transition from ('A', 1) to ('A', 1): 0|(0, 'a')] - sage: result([1, 0, 0])[2] + sage: result([1, 0, 0]) [(1, 'b'), (0, 'b'), (None, 'c'), (0, 'a')] - sage: (transducer1([1, 0, 0])[2], transducer2([1, 0, 0])[2]) + sage: (transducer1([1, 0, 0]), transducer2([1, 0, 0])) ([1, 0, 0], ['b', 'b', 'c', 'a']) If ``other`` is an automaton, then :meth:`.cartesian_product` returns @@ -5164,7 +5164,7 @@ def cartesian_product(self, other, only_accessible_components=True): ....: final_states=[0, 1], ....: determine_alphabets=True) sage: res = NAF.cartesian_product(aut11) - sage: res([1, 0, 0, 1, 0, 1, 0])[2] + sage: res([1, 0, 0, 1, 0, 1, 0]) [(1, None), (0, None), (0, None), (1, None), (0, None), (1, None)] This is obvious because if the standard binary expansion does not have From 70294bf300ddc75671aba8f517fd464aacaecbb4 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 11 Apr 2014 15:39:58 +0200 Subject: [PATCH 11/14] trac ticket number added --- src/sage/combinat/finite_state_machine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 7a23549d247..c08f96c6fa3 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -416,7 +416,7 @@ def full_group_by(l, key=lambda x: x): FSMEmptyWordSymbol = '-' FSMOldCodeTransducerCartesianProduct = True -FSMOldProcessOutput = True # See trac #33333 (deprecation). +FSMOldProcessOutput = True # See trac #16132 (deprecation). def FSMLetterSymbol(letter): """ @@ -5137,7 +5137,7 @@ def process(self, *args, **kwargs): """ if FSMOldProcessOutput: from sage.misc.superseded import deprecation - deprecation(33333, "The output of Automaton.process " + deprecation(16132, "The output of Automaton.process " "(and thus of Automaton.__call__) " "will change. Please use the corresponding " "functions from FiniteStateMachine " @@ -5711,7 +5711,7 @@ def process(self, *args, **kwargs): """ if FSMOldProcessOutput: from sage.misc.superseded import deprecation - deprecation(33333, "The output of Transducer.process " + deprecation(16132, "The output of Transducer.process " "(and thus of Transducer.__call__) " "will change. Please use the corresponding " "functions from FiniteStateMachine " From ed93dd3be386540d686ffea0c831ebd71f7ea871 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Sun, 13 Apr 2014 06:21:12 +0200 Subject: [PATCH 12/14] Minor changes during review. - Removed one comment which I think was hardly understandable - corrected one doc link - PEP8 - Added an introductory sentence to one example --- src/sage/combinat/finite_state_machine.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index c08f96c6fa3..a0b59a6e960 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3040,14 +3040,12 @@ def process(self, *args, **kwargs): it = self.iter_process(*args, **kwargs) for _ in it: pass - - # process output (is the same for the abstract finite state machine) return (it.accept_input, it.current_state, it.output_tape) def iter_process(self, input_tape=None, initial_state=None, **kwargs): """ - See `process` for more informations. + See :meth:`.process` for more informations. EXAMPLES:: @@ -4657,7 +4655,7 @@ class Automaton(FiniteStateMachine): Note that the full output of the commands can be obtained by calling :meth:`.process` and looks like this:: - sage: A.process([1,0,1]) + sage: A.process([1, 0, 1]) (False, 'P') TESTS:: @@ -5126,7 +5124,8 @@ def process(self, *args, **kwargs): ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False] - :: + The following example illustrates the difference between + non-existing paths and reaching a non-final state:: sage: NAF.process([2]) (False, None) From 3820c49851711ebffb12e230b27e9faad2d79b6d Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 15 Apr 2014 15:35:44 +0200 Subject: [PATCH 13/14] corrected whitespaceerror --- src/sage/combinat/finite_state_machine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index a0b59a6e960..c246989fd02 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -5124,7 +5124,7 @@ def process(self, *args, **kwargs): ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False] - The following example illustrates the difference between + The following example illustrates the difference between non-existing paths and reaching a non-final state:: sage: NAF.process([2]) From afc15e590d313a04c2a2aedb67203994865908ee Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Thu, 17 Apr 2014 15:01:35 +0200 Subject: [PATCH 14/14] Replaced two raise ..., ... by raise ...(...) in the spirit of #15990 before this patch, so two more such issues introduced by this patch have been modified in order to avoid potential future painful merge conflicts. --- src/sage/combinat/finite_state_machine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 06fedf11f28..9cc1344ea39 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -6056,7 +6056,7 @@ def process(self, *args, **kwargs): return (it.accept_input, it.current_state, it.output_tape) else: if not it.accept_input: - raise ValueError, "Invalid input sequence." + raise ValueError("Invalid input sequence.") return it.output_tape @@ -6416,7 +6416,7 @@ def get_next_transition(self, word_in): for transition in self.current_state.transitions: if transition.word_in == word_in: return transition - raise ValueError, "No transition with input %s found." % (word_in,) + raise ValueError("No transition with input %s found." % (word_in,)) #*****************************************************************************