From 61e305716aa1f92528c65b6f84d24acf37628857 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Fri, 4 Apr 2014 13:41:02 +0200 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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 2269f8b00c6ddfd55a22512eb9245e7204cae09d Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 11 Apr 2014 13:50:06 +0200 Subject: [PATCH 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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,)) #*****************************************************************************