From 112d7ef3c7b0015369c463f8406550d30d6bb3c7 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 09:15:23 +0200 Subject: [PATCH 01/26] Fixed indentation error in docstring of class sage.combinat.finite_state_machine.FiniteStateMachine --- src/sage/combinat/finite_state_machine.py | 94 +++++++++++------------ 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index efdaaba6e47..8ca1aea29d7 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -1337,60 +1337,60 @@ class FiniteStateMachine(SageObject): NotImplementedError - TESTS:: + TESTS:: - sage: a = FSMState('S_a', 'a') - sage: b = FSMState('S_b', 'b') - sage: c = FSMState('S_c', 'c') - sage: d = FSMState('S_d', 'd') - sage: FiniteStateMachine({a:[b, c], b:[b, c, d], - ....: c:[a, b], d:[a, c]}) - Finite state machine with 4 states + sage: a = FSMState('S_a', 'a') + sage: b = FSMState('S_b', 'b') + sage: c = FSMState('S_c', 'c') + sage: d = FSMState('S_d', 'd') + sage: FiniteStateMachine({a:[b, c], b:[b, c, d], + ....: c:[a, b], d:[a, c]}) + Finite state machine with 4 states - We have several constructions which lead to the same finite - state machine:: + We have several constructions which lead to the same finite + state machine:: - sage: A = FSMState('A') - sage: B = FSMState('B') - sage: C = FSMState('C') - sage: FSM1 = FiniteStateMachine( - ....: {A:{B:{'word_in':0, 'word_out':1}, - ....: C:{'word_in':1, 'word_out':1}}}) - sage: FSM2 = FiniteStateMachine({A:{B:(0, 1), C:(1, 1)}}) - sage: FSM3 = FiniteStateMachine( - ....: {A:{B:FSMTransition(A, B, 0, 1), - ....: C:FSMTransition(A, C, 1, 1)}}) - sage: FSM4 = FiniteStateMachine({A:[(B, 0, 1), (C, 1, 1)]}) - sage: FSM5 = FiniteStateMachine( - ....: {A:[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]}) - sage: FSM6 = FiniteStateMachine( - ....: [{'from_state':A, 'to_state':B, 'word_in':0, 'word_out':1}, - ....: {'from_state':A, 'to_state':C, 'word_in':1, 'word_out':1}]) - sage: FSM7 = FiniteStateMachine([(A, B, 0, 1), (A, C, 1, 1)]) - sage: FSM8 = FiniteStateMachine( - ....: [FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]) - - sage: FSM1 == FSM2 == FSM3 == FSM4 == FSM5 == FSM6 == FSM7 == FSM8 - True + sage: A = FSMState('A') + sage: B = FSMState('B') + sage: C = FSMState('C') + sage: FSM1 = FiniteStateMachine( + ....: {A:{B:{'word_in':0, 'word_out':1}, + ....: C:{'word_in':1, 'word_out':1}}}) + sage: FSM2 = FiniteStateMachine({A:{B:(0, 1), C:(1, 1)}}) + sage: FSM3 = FiniteStateMachine( + ....: {A:{B:FSMTransition(A, B, 0, 1), + ....: C:FSMTransition(A, C, 1, 1)}}) + sage: FSM4 = FiniteStateMachine({A:[(B, 0, 1), (C, 1, 1)]}) + sage: FSM5 = FiniteStateMachine( + ....: {A:[FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]}) + sage: FSM6 = FiniteStateMachine( + ....: [{'from_state':A, 'to_state':B, 'word_in':0, 'word_out':1}, + ....: {'from_state':A, 'to_state':C, 'word_in':1, 'word_out':1}]) + sage: FSM7 = FiniteStateMachine([(A, B, 0, 1), (A, C, 1, 1)]) + sage: FSM8 = FiniteStateMachine( + ....: [FSMTransition(A, B, 0, 1), FSMTransition(A, C, 1, 1)]) + + sage: FSM1 == FSM2 == FSM3 == FSM4 == FSM5 == FSM6 == FSM7 == FSM8 + True - It is possible to skip ``FSMTransition`` in the example above. + It is possible to skip ``FSMTransition`` in the example above. - Some more tests for different input-data:: + Some more tests for different input-data:: - sage: FiniteStateMachine({'a':{'a':[0, 0], 'b':[1, 1]}, - ....: 'b':{'b':[1, 0]}}) - Finite state machine with 2 states + sage: FiniteStateMachine({'a':{'a':[0, 0], 'b':[1, 1]}, + ....: 'b':{'b':[1, 0]}}) + Finite state machine with 2 states - sage: a = FSMState('S_a', 'a') - sage: b = FSMState('S_b', 'b') - sage: c = FSMState('S_c', 'c') - sage: d = FSMState('S_d', 'd') - sage: t1 = FSMTransition(a, b) - sage: t2 = FSMTransition(b, c) - sage: t3 = FSMTransition(b, d) - sage: t4 = FSMTransition(c, d) - sage: FiniteStateMachine([t1, t2, t3, t4]) - Finite state machine with 4 states + sage: a = FSMState('S_a', 'a') + sage: b = FSMState('S_b', 'b') + sage: c = FSMState('S_c', 'c') + sage: d = FSMState('S_d', 'd') + sage: t1 = FSMTransition(a, b) + sage: t2 = FSMTransition(b, c) + sage: t3 = FSMTransition(b, d) + sage: t4 = FSMTransition(c, d) + sage: FiniteStateMachine([t1, t2, t3, t4]) + Finite state machine with 4 states """ #************************************************************************* From a232e7c71a8ec4093d3786e0902817d684239e41 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 15:32:51 +0200 Subject: [PATCH 02/26] implemented FSMTransition.__lt__ and FSMState.__lt__ in order to have default ordering, usable e.g. in doctests. --- src/sage/combinat/finite_state_machine.py | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index efdaaba6e47..41202d52827 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -540,6 +540,27 @@ def __init__(self, label, word_out=None, else: raise TypeError, 'Wrong argument for hook.' + def __lt__(self,other): + """ + Return True, if label of ``self`` is less than label of ``other``. + + INPUT: + + - `self` + - `other` -- two FSMStates + + OUTPUT: + + True or False + + EXAMPLE:: + + sage: from sage.combinat.finite_state_machine import FSMState + sage: FSMState(0) < FSMState(1) + True + """ + return self.label() Date: Fri, 21 Feb 2014 16:48:54 +0200 Subject: [PATCH 03/26] deepcopy + reset label in FiniteStateMachine.quotient in quotient, do not create new state, but make deepcopy + reset label in order to capture possible additional information --- 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 efdaaba6e47..fb099a6cc4c 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4149,17 +4149,17 @@ def quotient(self, classes): # Create new states and build state_mapping for c in classes: - new_state = new.add_state(tuple(c)) + new_label = tuple(c) + new_state = deepcopy(c[0]) + new_state._label_ = new_label + # TODO: Why does c[0].relabeled(new_label) not work? + new.add_state(new_state) for state in c: state_mapping[state] = new_state # Copy data from old transducer for c in classes: new_state = state_mapping[c[0]] - # copy all data from first class member - new_state.is_initial = c[0].is_initial - new_state.is_final = c[0].is_final - new_state.word_out = c[0].word_out for transition in self.iter_transitions(c[0]): new.add_transition( from_state=new_state, From 33442a32e7ce145eddf77515180a3c266cc43cc2 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 17:20:41 +0200 Subject: [PATCH 04/26] Removed Whitespace according PEP 8 --- 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 fb099a6cc4c..080e280204b 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4163,9 +4163,9 @@ def quotient(self, classes): for transition in self.iter_transitions(c[0]): new.add_transition( from_state=new_state, - to_state = state_mapping[transition.to_state], - word_in = transition.word_in, - word_out = transition.word_out) + to_state=state_mapping[transition.to_state], + word_in=transition.word_in, + word_out=transition.word_out) # check that all class members have the same information (modulo classes) for state in c: From e5860f54ecf3390e5e8daf127b1c97d57580097f Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 14:55:10 +0200 Subject: [PATCH 05/26] Inserted DocTest: No Simplification for non-deterministic Transducers. --- src/sage/combinat/finite_state_machine.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index efdaaba6e47..f76b5b2ba67 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4801,6 +4801,20 @@ def simplification(self): Transition from 0 to 1: 1|0, Transition from 1 to 0: 0|0, Transition from 1 to 0: 1|1] + + :: + + sage: fsm = Transducer([("A", "A", 0, 0), + ....: ("A", "B", 1, 1), + ....: ("A", "C", 1, -1), + ....: ("B", "A", 2, 0), + ....: ("C", "A", 2, 0)]) + sage: fsms = fsm.simplification() + Traceback (most recent call last): + ... + NotImplementedError: Minimization via Moore's Algorithm is only implemented for deterministic finite state machines + + """ fsm = deepcopy(self) fsm.prepone_output() From 1c123b47a3331d8dfbc65a329f37438d1db3e172 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 15:05:33 +0200 Subject: [PATCH 06/26] Simplification for non-deterministic transducers via Moore's algorithm In fact, the old code was already able to do that. The check of deterministic finite state machine has been moved to minimization of Automata, because it is still required there. Otherwise, the docstrings have been adapted to explain this generalization. --- src/sage/combinat/finite_state_machine.py | 80 ++++++++++++----------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index f76b5b2ba67..b07d59abf74 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3997,7 +3997,7 @@ def find_common_output(state): def equivalence_classes(self): - """ + r""" Returns a list of equivalence classes of states. INPUT: @@ -4008,14 +4008,15 @@ def equivalence_classes(self): A list of equivalence classes of states. - Two states `a` and `b` are equivalent, if and only if for each - input label word_in the following holds: + Two states `a` and `b` are equivalent, if and only if the following holds: - For paths `p_a` from `a` to `a'` with input label ``word_in`` - and output label ``word_out_a`` and `p_b` from `b` to `b'` - with input label ``word_in`` and output label ``word_out_b``, - we have ``word_out_a=word_out_b``, `a'` and `b'` have the same - output label and are both final or both non-final. + There is a bijection `\varphi` between paths starting at `a` + and paths starting at `b` such that if `\varphi(p_a)=p_b`, + then `p_a.\mathit{word}_{in}=p_b.\mathit{word}_{in}` and + `p_a.\mathit{word}_{out}=p_b.\mathit{word}_{out}` and `p_a` + and `p_b` lead to some states `a'` and `b'` such that `a'` and + `b'` have the same output label and are both final or both + non-final. The function :meth:`.equivalence_classes` returns a list of the equivalence classes to this equivalence relation. @@ -4036,25 +4037,24 @@ def equivalence_classes(self): [['A', 'C'], ['B', 'D']] """ - # Two states a and b are said to be 0-equivalent, if their output - # labels agree and if they are both final or non-final. + # Two states a and b are said to be j-equivalent, if and only + # if the following holds: # - # For some j >= 1, two states a and b are said to be j-equivalent, if - # they are j-1 equivalent and if for each element letter letter_in of - # the input alphabet and transitions t_a from a with input label - # letter_in, output label word_out_a to a' and t_b from b with input - # label letter_in, output label word_out_b to b', we have - # word_out_a=word_out_b and a' and b' are j-1 equivalent. + # There is a bijection `\varphi` between paths of length <= j + # starting at `a` and paths of length <= j starting at `b` + # such that if `\varphi(p_a)=p_b`, then + # `p_a.word_in=p_b.word_in` and `p_a.word_out=p_b.word_out` + # and `p_a` and `p_b` lead to some states `a'` and `b'` such + # that `a'` and `b'` have the same output label and are both + # final or both non-final. # If for some j the relations j-1 equivalent and j-equivalent - # coincide, then they are equal to the equivalence relation described - # in the docstring. - - # classes_current holds the equivalence classes of j-equivalence, - # classes_previous holds the equivalence classes of j-1 equivalence. + # coincide, then they are equal to the equivalence relation + # described in the docstring. - if not self.is_deterministic(): - raise NotImplementedError, "Minimization via Moore's Algorithm is only implemented for deterministic finite state machines" + # classes_current holds the equivalence classes of + # j-equivalence, classes_previous holds the equivalence + # classes of j-1 equivalence. # initialize with 0-equivalence classes_previous = [] @@ -4087,7 +4087,7 @@ def equivalence_classes(self): def quotient(self, classes): - """ + r""" Constructs the quotient with respect to the equivalence classes. @@ -4099,14 +4099,13 @@ def quotient(self, classes): A finite state machine. - Assume that `c` is a class and `s`, `s'` are states in `c`. If - there is a transition from `s` to some `t` with input label - ``word_in`` and output label ``word_out``, then there has to - be a transition from `s'` to some `t'` with input label - ``word_in`` and output label ``word_out`` such that `s'` and - `t'` are states of the same class `c'`. Then there is a - transition from `c` to `c'` in the quotient with input label - ``word_in`` and output label ``word_out``. + Assume that `c` is a class and `s`, `s'` are states in + `c`. Then there is a bijection `\varphi` between the + transitions from `s` and the transitions from `s'` such that + if `\varphi(t_a)=t_b`, then + `t_a.\mathit{word}_{in}=t_b.\mathit{word}_{in}` and + `t_a.\mathit{word}_{out}=t_b.\mathit{word}_{out}` and `t_a` + and `t_b` lead to some equivalent states `t` and `t'`. Non-initial states may be merged with initial states, the resulting state is an initial state. @@ -4649,7 +4648,11 @@ def _minimization_Moore_(self): NotImplementedError: Minimization via Moore's Algorithm is only implemented for deterministic finite state machines """ - return self.quotient(self.equivalence_classes()) + if self.is_deterministic(): + return self.quotient(self.equivalence_classes()) + else: + raise NotImplementedError("Minimization via Moore's Algorithm is only " \ + "implemented for deterministic finite state machines") #***************************************************************************** @@ -4810,10 +4813,13 @@ def simplification(self): ....: ("B", "A", 2, 0), ....: ("C", "A", 2, 0)]) sage: fsms = fsm.simplification() - Traceback (most recent call last): - ... - NotImplementedError: Minimization via Moore's Algorithm is only implemented for deterministic finite state machines - + sage: fsms + Transducer with 2 states + sage: fsms.transitions() + [Transition from ('A',) to ('A',): 0|0, + Transition from ('A',) to ('B', 'C'): 1|1,0, + Transition from ('A',) to ('B', 'C'): 1|-1,0, + Transition from ('B', 'C') to ('A',): 2|-] """ fsm = deepcopy(self) From 9e3e7f95e33ed929ee5f897095249991cf50bfca Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 13:11:43 +0200 Subject: [PATCH 07/26] new hook on_duplicate_transition Previously, when inserting a transition which is already present, this was simply ignored. By providing a hook on_duplicate_transition, user defined behaviour is now possible. As examples, three functions are provided (the default ignore, maintaining the previous behaviour; raise error or add input label). The latter may be used to model a Markov chain where the input labels are interpreted als transition probabilities. --- src/sage/combinat/finite_state_machine.py | 177 +++++++++++++++++++++- 1 file changed, 170 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 866b7d99a8a..0664357a6b0 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -1139,6 +1139,115 @@ def is_FiniteStateMachine(FSM): """ return isinstance(FSM, FiniteStateMachine) +def duplicate_transition_ignore(old_transition, new_transition): + """ + Default function for handling duplicate transitions in finite state machines. + This implementation ignores the occurrence. See the documentation of the + ``on_duplicate_transition`` of :class:`FiniteStateMachine`. + + INPUT: + + - ``old_transition``: A transition in a finite state machine. + + - ``new_transition``: A transition, identical to old_transition, which + is to be inserted into the finite state machine. + + OUTPUT: + + - The same transition, unchanged. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import duplicate_transition_ignore + sage: from sage.combinat.finite_state_machine import FSMTransition + sage: duplicate_transition_ignore(FSMTransition(0,0,1), + ....: FSMTransition(0,0,1)) + Transition from 0 to 0: 1|- + """ + return old_transition + +def duplicate_transition_raise_error(old_transition, new_transition): + """ + Alternative function for handling duplicate transitions in finite state machines. + This implementation raises a ValueError. + + See the documentation of the + ``on_duplicate_transition`` of :class:`FiniteStateMachine`. + + INPUT: + + - ``old_transition``: A transition in a finite state machine. + + - ``new_transition``: A transition, identical to old_transition, which + is to be inserted into the finite state machine. + + OUTPUT: + + Nothing. A ValueError is raised. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error + sage: from sage.combinat.finite_state_machine import FSMTransition + sage: duplicate_transition_raise_error(FSMTransition(0,0,1), + ....: FSMTransition(0,0,1)) + Traceback (most recent call last): + ... + ValueError: Attempting to re-insert transition Transition from 0 to 0: 1|- + """ + raise ValueError("Attempting to re-insert transition %s" % old_transition) + +def duplicate_transition_add_input(old_transition, new_transition): + """ + Alternative function for handling duplicate transitions in finite + state machines. This implementation adds the input label of the + new transition to the input label of the old transition. This is + intended for the case where a Markov chain is modelled by a finite + state machine using the input labels as transition probabilities. + + See the documentation of the ``on_duplicate_transition`` of + :class:`FiniteStateMachine`. + + INPUT: + + - ``old_transition``: A transition in a finite state machine. + + - ``new_transition``: A transition, identical to old_transition, which + is to be inserted into the finite state machine. + + OUTPUT: + + - Transition whose input weight is the sum of the input + weights of ``old_transition`` and ``new_transition``. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input + sage: from sage.combinat.finite_state_machine import FSMTransition + sage: duplicate_transition_add_input(FSMTransition('a','a',1/2), + ....: FSMTransition('a','a',1/2)) + Transition from 'a' to 'a': 1|- + + Input labels must be lists of length 1:: + + sage: duplicate_transition_add_input(FSMTransition('a','a',[1,1]), + ....: FSMTransition('a','a',[1,1])) + Traceback (most recent call last): + ... + TypeError: Trying to use duplicate_transition_add_input on + "Transition from 'a' to 'a': 1,1|-" and + "Transition from 'a' to 'a': 1,1|-", + but input words are assumed to be lists of length 1 + + """ + if hasattr(old_transition.word_in, '__iter__') and len(old_transition.word_in)==1 \ + and hasattr(new_transition.word_in, '__iter__') and len(new_transition.word_in)==1: + old_transition.word_in = [ old_transition.word_in[0] + new_transition.word_in[0] ] + else: + raise TypeError('Trying to use duplicate_transition_add_input on ' + \ + '"%s" and "%s", '% (old_transition, new_transition) + \ + 'but input words are assumed to be lists of length 1' ) + return old_transition class FiniteStateMachine(SageObject): """ @@ -1176,6 +1285,21 @@ class FiniteStateMachine(SageObject): - ``store_states_dict`` -- If ``True``, then additionally the states are stored in an interal dictionary for speed up. + - ``on_duplicate_transition`` -- A function which is called when a + transition is inserted into self which already existed (same + ``from_state``, same ``to_state``, same ``word_in``, same ``word_out``). + + This function is assumed to take two arguments, the first beeing + the already existing transition, the second beeing the new + transition (as an ``FSMTransition``). The function must return the + (possibly modified) original transition. + + By default, ``on_duplicate_transition=duplicate_transition_ignore``, + where ``duplicate_transition_ignore`` is a predefined function + ignoring the occurrence. Other such predefined functions are + ``duplicate_transition_raise_error`` and + ``duplicate_transition_add_input``. + OUTPUT: A finite state machine. @@ -1337,6 +1461,30 @@ class FiniteStateMachine(SageObject): NotImplementedError + The following examples demonstrate the use of ``on_duplicate_transition``:: + + sage: F = FiniteStateMachine([['a','a', 1/2], ['a','a', 1/2]]) + sage: F.transitions() + [Transition from 'a' to 'a': 1/2|-] + + :: + + sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error + sage: F1 = FiniteStateMachine([['a','a', 1/2], ['a','a', 1/2]], + ....: on_duplicate_transition=duplicate_transition_raise_error) + Traceback (most recent call last): + ... + ValueError: Attempting to re-insert transition Transition from 'a' to 'a': 1/2|- + + Use ``duplicate_transition_add_input`` to emulate a Markov chain, + the input labels are considered as transition probabilities:: + + sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input + sage: F = FiniteStateMachine([['a','a', 1/2], ['a','a', 1/2]], + ....: on_duplicate_transition=duplicate_transition_add_input) + sage: F.transitions() + [Transition from 'a' to 'a': 1|-] + TESTS:: sage: a = FSMState('S_a', 'a') @@ -1403,7 +1551,8 @@ def __init__(self, initial_states=None, final_states=None, input_alphabet=None, output_alphabet=None, determine_alphabets=None, - store_states_dict=True): + store_states_dict=True, + on_duplicate_transition=duplicate_transition_ignore): """ See :class:`FiniteStateMachine` for more information. @@ -1437,6 +1586,11 @@ def __init__(self, self.input_alphabet = input_alphabet self.output_alphabet = output_alphabet + if hasattr(on_duplicate_transition, '__call__'): + self.on_duplicate_transition=on_duplicate_transition + else: + raise TypeError, 'on_duplicate_transition must be callable' + if data is None: pass elif is_FiniteStateMachine(data): @@ -1528,7 +1682,7 @@ def __copy__(self): def empty_copy(self, memo=None): """ Returns an empty deep copy of the finite state machine, i.e., - input_alphabet, output_alphabet are preserved, but states and + input_alphabet, output_alphabet, on_duplicate_transition are preserved, but states and transitions are not. INPUT: @@ -1541,19 +1695,24 @@ def empty_copy(self, memo=None): EXAMPLES:: + sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error sage: F = FiniteStateMachine([('A', 'A', 0, 2), ('A', 'A', 1, 3)], ....: input_alphabet=[0,1], - ....: output_alphabet=[2,3]) + ....: output_alphabet=[2,3], + ....: on_duplicate_transition=duplicate_transition_raise_error) sage: FE = F.empty_copy(); FE Finite state machine with 0 states sage: FE.input_alphabet [0, 1] sage: FE.output_alphabet [2, 3] + sage: FE.on_duplicate_transition==duplicate_transition_raise_error + True """ new = self.__class__() new.input_alphabet = deepcopy(self.input_alphabet, memo) new.output_alphabet = deepcopy(self.output_alphabet, memo) + new.on_duplicate_transition = self.on_duplicate_transition return new def __deepcopy__(self, memo): @@ -2844,8 +3003,11 @@ def add_states(self, states): def add_transition(self, *args, **kwargs): """ Adds a transition to the finite state machine and returns the - new transition. If the transition already exists, that - existing transition is returned. + new transition. + + If the transition already exists, the return value of + ``self.on_duplicate_transition`` is returned. See the + documentation of :class:`FiniteStateMachine`. INPUT: @@ -2896,7 +3058,7 @@ def add_transition(self, *args, **kwargs): OUTPUT: - The new or existing transition. + The new transition. """ if len(args) + len(kwargs) == 0: return @@ -2947,7 +3109,8 @@ def _add_fsm_transition_(self, t): Transition from 'A' to 'B': -|- """ try: - return self.transition(t) + existing_transition = self.transition(t) + return self.on_duplicate_transition(existing_transition, t) except LookupError: pass from_state = self.add_state(t.from_state) From 6030cf70d010e14039c505c84f6af727bdfd6899 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 15:22:52 +0200 Subject: [PATCH 08/26] New Doctest for failing simplification when duplicate_transition_add_input is used. --- src/sage/combinat/finite_state_machine.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 0664357a6b0..cf53e2f495e 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4984,6 +4984,18 @@ def simplification(self): Transition from ('A',) to ('B', 'C'): 1|-1,0, Transition from ('B', 'C') to ('A',): 2|-] + :: + + sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input + sage: T = Transducer([('A', 'A', 1/2, 0), + ....: ('A', 'B', 1/4, 1), + ....: ('A', 'C', 1/4, 1), + ....: ('B', 'A', 1, 0), + ....: ('C', 'A', 1, 0)], + ....: initial_states=[0], + ....: final_states=['A', 'B', 'C'], + ....: on_duplicate_transition=duplicate_transition_add_input) + sage: T.simplification().transitions() """ fsm = deepcopy(self) fsm.prepone_output() From 28c28f884b0f311e400c9181f620b8a784312690 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 15:26:40 +0200 Subject: [PATCH 09/26] FiniteStateMachine.quotient() allows on_duplicate_transitions Previously, on_duplicate_transition led to missing transitions in .quotient, the corresponding check has now been improved. --- src/sage/combinat/finite_state_machine.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index cf53e2f495e..8ad5b979832 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4304,7 +4304,7 @@ def quotient(self, classes): sage: fsm.quotient([[fsm.state("A"), fsm.state("B"), fsm.state("C"), fsm.state("D")]]) Traceback (most recent call last): ... - ValueError: There is a transition Transition from 'B' to 'C': 0|0 in the original transducer, but no corresponding transition in the new transducer. + AssertionError: Transitions of state 'A' and 'B' are incompatible. """ new = self.empty_copy() state_mapping = {} @@ -4322,6 +4322,7 @@ def quotient(self, classes): # Copy data from old transducer for c in classes: new_state = state_mapping[c[0]] + sorted_transitions = sorted([(state_mapping[t.to_state], t.word_in, t.word_out) for t in c[0].transitions ]) for transition in self.iter_transitions(c[0]): new.add_transition( from_state=new_state, @@ -4334,14 +4335,8 @@ def quotient(self, classes): new_state.is_initial = new_state.is_initial or state.is_initial assert new_state.is_final == state.is_final, "Class %s mixes final and non-final states" % (c,) assert new_state.word_out == state.word_out, "Class %s mixes different word_out" % (c,) - assert len(self.transitions(state)) == len(new.transitions(new_state)), \ - "Class %s has %d outgoing transitions, but class member %s has %d outgoing transitions" % \ - (c, len(new.transitions(new_state)), state, len(self.transitions(state))) - for transition in self.transitions(state): - try: - new.transition((new_state, state_mapping[transition.to_state], transition.word_in, transition.word_out)) - except LookupError: - raise ValueError, "There is a transition %s in the original transducer, but no corresponding transition in the new transducer." % transition + assert sorted_transitions == sorted([(state_mapping[t.to_state], t.word_in, t.word_out) for t in state.transitions ]), \ + "Transitions of state %s and %s are incompatible." % (c[0], state) return new @@ -4996,6 +4991,9 @@ def simplification(self): ....: final_states=['A', 'B', 'C'], ....: on_duplicate_transition=duplicate_transition_add_input) sage: T.simplification().transitions() + [Transition from ('B', 'C') to ('A',): 1|0, + Transition from ('A',) to ('A',): 1/2|0, + Transition from ('A',) to ('B', 'C'): 1/2|1] """ fsm = deepcopy(self) fsm.prepone_output() From 63060d542fdb464d6eb4a67bd138febcbf8e9d34 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 18:36:33 +0200 Subject: [PATCH 10/26] added one TODO in projection --- src/sage/combinat/finite_state_machine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 8ad5b979832..5a720fc3e28 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3945,6 +3945,10 @@ def projection(self, what='input'): Transition from 'B' to 'B': 0|-] """ new = Automaton() + # TODO: use empty_copy() in order to + # preserve on_duplicate_transition and future extensions. + # for this, empty_copy would need a new optional argument + # use_class=None ? if what == 'input': new.input_alphabet = copy(self.input_alphabet) From 95b1d910bc7d81126abec4b3ffb8338a93ec9c37 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 17:47:16 +0200 Subject: [PATCH 11/26] sorted transitions in one doctest to guarantee consistency --- src/sage/combinat/finite_state_machine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 1ffd6428b85..e65aec37ab9 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -5038,10 +5038,10 @@ def simplification(self): ....: initial_states=[0], ....: final_states=['A', 'B', 'C'], ....: on_duplicate_transition=duplicate_transition_add_input) - sage: T.simplification().transitions() - [Transition from ('B', 'C') to ('A',): 1|0, - Transition from ('A',) to ('A',): 1/2|0, - Transition from ('A',) to ('B', 'C'): 1/2|1] + sage: sorted(T.simplification().transitions()) + [Transition from ('A',) to ('A',): 1/2|0, + Transition from ('A',) to ('B', 'C'): 1/2|1, + Transition from ('B', 'C') to ('A',): 1|0] """ fsm = deepcopy(self) fsm.prepone_output() From ca1cdf33d149b622f3551ae371fbe3f1a91c5de5 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 17:58:47 +0200 Subject: [PATCH 12/26] New attribute FSMState.color to prohibit merging in simplification --- src/sage/combinat/finite_state_machine.py | 38 +++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index e65aec37ab9..473be1c3cbc 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -492,6 +492,11 @@ class FSMState(SageObject): - ``hook`` -- (default: ``None``) A function which is called when the state is reached during processing input. + - ``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. + OUTPUT: Returns a state of a finite state machine. @@ -511,7 +516,7 @@ class FSMState(SageObject): """ def __init__(self, label, word_out=None, is_initial=False, is_final=False, - hook=None): + hook=None, color=None): """ See :class:`FSMState` for more information. @@ -540,6 +545,8 @@ def __init__(self, label, word_out=None, else: raise TypeError, 'Wrong argument for hook.' + self.color = color + def __lt__(self,other): """ Return True, if label of ``self`` is less than label of ``other``. @@ -640,6 +647,7 @@ def __deepcopy__(self, memo): self.is_initial, self.is_final) if hasattr(self, 'hook'): new.hook = deepcopy(self.hook, memo) + new.color = deepcopy(self.color, memo) return new @@ -4226,8 +4234,8 @@ def equivalence_classes(self): then `p_a.\mathit{word}_{in}=p_b.\mathit{word}_{in}` and `p_a.\mathit{word}_{out}=p_b.\mathit{word}_{out}` and `p_a` and `p_b` lead to some states `a'` and `b'` such that `a'` and - `b'` have the same output label and are both final or both - non-final. + `b'` have the same output label, the same color, and are both + final or both non-final. The function :meth:`.equivalence_classes` returns a list of the equivalence classes to this equivalence relation. @@ -4256,8 +4264,8 @@ def equivalence_classes(self): # such that if `\varphi(p_a)=p_b`, then # `p_a.word_in=p_b.word_in` and `p_a.word_out=p_b.word_out` # and `p_a` and `p_b` lead to some states `a'` and `b'` such - # that `a'` and `b'` have the same output label and are both - # final or both non-final. + # that `a'` and `b'` have the same output label, the same + # color and are both final or both non-final. # If for some j the relations j-1 equivalent and j-equivalent # coincide, then they are equal to the equivalence relation @@ -4269,7 +4277,7 @@ def equivalence_classes(self): # initialize with 0-equivalence classes_previous = [] - key_0 = lambda state: (state.is_final, state.word_out) + key_0 = lambda state: (state.is_final, state.color, state.word_out) states_grouped = full_group_by(self.states(), key=key_0) classes_current = [equivalence_class for (key,equivalence_class) in states_grouped] @@ -4383,6 +4391,7 @@ def quotient(self, classes): new_state.is_initial = new_state.is_initial or state.is_initial assert new_state.is_final == state.is_final, "Class %s mixes final and non-final states" % (c,) assert new_state.word_out == state.word_out, "Class %s mixes different word_out" % (c,) + assert new_state.color == state.color, "Class %s mixes different colors" % (c,) assert sorted_transitions == sorted([(state_mapping[t.to_state], t.word_in, t.word_out) for t in state.transitions ]), \ "Transitions of state %s and %s are incompatible." % (c[0], state) return new @@ -5042,6 +5051,23 @@ def simplification(self): [Transition from ('A',) to ('A',): 1/2|0, Transition from ('A',) to ('B', 'C'): 1/2|1, Transition from ('B', 'C') to ('A',): 1|0] + + Illustrating the use of colors in order to avoid identification of states:: + + sage: T = Transducer( [[0,0,0,0], [0,1,1,1], + ....: [1,0,0,0], [1,1,1,1]], + ....: initial_states=[0], + ....: final_states=[0,1]) + sage: sorted(T.simplification().transitions()) + [Transition from (0, 1) to (0, 1): 0|0, + Transition from (0, 1) to (0, 1): 1|1] + sage: T.state(0).color = 0 + sage: T.state(0).color = 1 + sage: sorted(T.simplification().transitions()) + [Transition from (0,) to (0,): 0|0, + Transition from (0,) to (1,): 1|1, + Transition from (1,) to (0,): 0|0, + Transition from (1,) to (1,): 1|1] """ fsm = deepcopy(self) fsm.prepone_output() From 6a37c92b91db8169963bb654ad4834fa12fecd99 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Fri, 21 Feb 2014 18:12:47 +0200 Subject: [PATCH 13/26] Set color of new states in in product_FiniteStateMachine, composition_explorative, and determinisation. --- src/sage/combinat/finite_state_machine.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 473be1c3cbc..5bc8b7fe54b 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3438,6 +3438,9 @@ def accessible_components(self): initial state to the state. If self has no initial states then a copy of the finite state machine self is returned. + The color of a new state is the tuple of colors of the + constituent states of ``self`` and ``other``. + EXAMPLES:: sage: F = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 0), (1, 0, 1)], @@ -3629,6 +3632,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()) if only_accessible_components: if new_input_alphabet is None: @@ -3729,8 +3733,10 @@ def composition(self, other, algorithm=None, A new transducer. - The labels of the new finite state machine are pairs - of states of the original finite state machines. + The labels of the new finite state machine are pairs of states + of the original finite state machines. The color of a new + state is the tuple of colors of the constituent states. + EXAMPLES:: @@ -3917,6 +3923,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()) return F @@ -4318,6 +4325,9 @@ def quotient(self, classes): A finite state machine. + The labels of the new states are tuples of states of the + ``self``, corresponding to ``classes``. + Assume that `c` is a class and `s`, `s'` are states in `c`. Then there is a bijection `\varphi` between the transitions from `s` and the transitions from `s'` such that @@ -4658,8 +4668,9 @@ def determinisation(self): A new automaton, which is deterministic. - The labels of the states of the new automaton are frozensets of - states of ``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``. The input alphabet must be specified. It is restricted to nice cases: input words have to have length at most `1`. @@ -4752,7 +4763,7 @@ def set_transition(states, letter): for state in result.states(): if any(map(lambda s: s.is_final, state.label())): state.is_final = True - + state.color = frozenset(map(lambda s: s.color, state.label())) return result From 142d7024ab43110e87e0abd232376811ed92d8a8 Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Sun, 23 Feb 2014 17:07:17 +0200 Subject: [PATCH 14/26] Correct misplaced sentence in docstrings of accessible components and product_FiniteStateMachine --- 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 5bc8b7fe54b..87516e37d29 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -3438,9 +3438,6 @@ def accessible_components(self): initial state to the state. If self has no initial states then a copy of the finite state machine self is returned. - The color of a new state is the tuple of colors of the - constituent states of ``self`` and ``other``. - EXAMPLES:: sage: F = Automaton([(0, 0, 0), (0, 1, 1), (1, 1, 0), (1, 0, 1)], @@ -3567,6 +3564,9 @@ def product_FiniteStateMachine(self, other, function, The labels of the transitions are defined by ``function``. + The color of a new state is the tuple of colors of the + constituent states of ``self`` and ``other``. + EXAMPLES:: sage: F = Automaton([('A', 'B', 1), ('A', 'A', 0), ('B', 'A', 2)], From 345c1843df37e7f8affb6c0c13f438d4e28cc1cd Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Thu, 6 Mar 2014 16:06:43 +0100 Subject: [PATCH 15/26] rewrote docstring of .__lt__ in FSMState/FSMTransition; corrected spacings --- src/sage/combinat/finite_state_machine.py | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 41202d52827..c80b28946ed 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -540,18 +540,19 @@ def __init__(self, label, word_out=None, else: raise TypeError, 'Wrong argument for hook.' - def __lt__(self,other): + + def __lt__(self, other): """ - Return True, if label of ``self`` is less than label of ``other``. + Returns True if label of ``self`` is less than label of + ``other``. INPUT: - - `self` - - `other` -- two FSMStates + - `other` -- a state. OUTPUT: - True or False + True or False. EXAMPLE:: @@ -559,7 +560,7 @@ def __lt__(self,other): sage: FSMState(0) < FSMState(1) True """ - return self.label() Date: Mon, 10 Mar 2014 14:28:09 +0100 Subject: [PATCH 16/26] corrected spacings (PEP8); added line-breaks in long lines --- src/sage/combinat/finite_state_machine.py | 28 +++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 080e280204b..9edc3a8e109 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4162,24 +4162,32 @@ def quotient(self, classes): new_state = state_mapping[c[0]] for transition in self.iter_transitions(c[0]): new.add_transition( - from_state=new_state, - to_state=state_mapping[transition.to_state], - word_in=transition.word_in, - word_out=transition.word_out) + from_state = new_state, + to_state = state_mapping[transition.to_state], + word_in = transition.word_in, + word_out = transition.word_out) # check that all class members have the same information (modulo classes) for state in c: new_state.is_initial = new_state.is_initial or state.is_initial - assert new_state.is_final == state.is_final, "Class %s mixes final and non-final states" % (c,) - assert new_state.word_out == state.word_out, "Class %s mixes different word_out" % (c,) + assert new_state.is_final == state.is_final, \ + "Class %s mixes final and non-final states" % (c,) + assert new_state.word_out == state.word_out, \ + "Class %s mixes different word_out" % (c,) assert len(self.transitions(state)) == len(new.transitions(new_state)), \ - "Class %s has %d outgoing transitions, but class member %s has %d outgoing transitions" % \ - (c, len(new.transitions(new_state)), state, len(self.transitions(state))) + "Class %s has %d outgoing transitions, but class " \ + "member %s has %d outgoing transitions" % \ + (c, len(new.transitions(new_state)), state, + len(self.transitions(state))) for transition in self.transitions(state): try: - new.transition((new_state, state_mapping[transition.to_state], transition.word_in, transition.word_out)) + new.transition((new_state, + state_mapping[transition.to_state], + transition.word_in, transition.word_out)) except LookupError: - raise ValueError, "There is a transition %s in the original transducer, but no corresponding transition in the new transducer." % transition + raise ValueError, "There is a transition %s in the " \ + "original transducer, but no corresponding " \ + "transition in the new transducer." % transition return new From 4e6864530acb5cd1ca64c504443d23b0d3a0d5ad Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 10 Mar 2014 15:28:38 +0100 Subject: [PATCH 17/26] description of equivalent states rewritten --- src/sage/combinat/finite_state_machine.py | 35 ++++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index b07d59abf74..21dfe72fc70 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4008,15 +4008,16 @@ def equivalence_classes(self): A list of equivalence classes of states. - Two states `a` and `b` are equivalent, if and only if the following holds: + Two states `a` and `b` are equivalent if and only if there is + a bijection `\varphi` between paths starting at `a` and paths + starting at `b` with the following properties: Let `p_a` be a + path from `a` to `a'` and `p_b` a path from `b` to `b'` such + that `\varphi(p_a)=p_b`, then - There is a bijection `\varphi` between paths starting at `a` - and paths starting at `b` such that if `\varphi(p_a)=p_b`, - then `p_a.\mathit{word}_{in}=p_b.\mathit{word}_{in}` and - `p_a.\mathit{word}_{out}=p_b.\mathit{word}_{out}` and `p_a` - and `p_b` lead to some states `a'` and `b'` such that `a'` and - `b'` have the same output label and are both final or both - non-final. + - `p_a.\mathit{word}_\mathit{in}=p_b.\mathit{word}_\mathit{in}`, + - `p_a.\mathit{word}_\mathit{out}=p_b.\mathit{word}_\mathit{out}`, + - `a'` and `b'` have the same output label, and + - `a'` and `b'` are both final or both non-final. The function :meth:`.equivalence_classes` returns a list of the equivalence classes to this equivalence relation. @@ -4037,16 +4038,16 @@ def equivalence_classes(self): [['A', 'C'], ['B', 'D']] """ - # Two states a and b are said to be j-equivalent, if and only - # if the following holds: + # Two states `a` and `b` are j-equivalent if and only if there + # is a bijection `\varphi` between paths of length <= j + # starting at `a` and paths starting at `b` with the following + # properties: Let `p_a` be a path from `a` to `a'` and `p_b` a + # path from `b` to `b'` such that `\varphi(p_a)=p_b`, then # - # There is a bijection `\varphi` between paths of length <= j - # starting at `a` and paths of length <= j starting at `b` - # such that if `\varphi(p_a)=p_b`, then - # `p_a.word_in=p_b.word_in` and `p_a.word_out=p_b.word_out` - # and `p_a` and `p_b` lead to some states `a'` and `b'` such - # that `a'` and `b'` have the same output label and are both - # final or both non-final. + # - `p_a.\mathit{word}_{in}=p_b.\mathit{word}_{in}`, + # - `p_a.\mathit{word}_{out}=p_b.\mathit{word}_{out}`, + # - `a'` and `b'` have the same output label, and + # - `a'` and `b'` are both final or both non-final. # If for some j the relations j-1 equivalent and j-equivalent # coincide, then they are equal to the equivalence relation From f065517acba6ec3cf6e9f0ad231fdc7d2fb494af Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 10 Mar 2014 15:34:22 +0100 Subject: [PATCH 18/26] docstring in quotient adapted to the same style as equivalence classes --- src/sage/combinat/finite_state_machine.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 21dfe72fc70..5079d3a7fe8 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4100,13 +4100,14 @@ def quotient(self, classes): A finite state machine. - Assume that `c` is a class and `s`, `s'` are states in + Assume that `c` is a class, and `a` and `b` are states in `c`. Then there is a bijection `\varphi` between the - transitions from `s` and the transitions from `s'` such that - if `\varphi(t_a)=t_b`, then - `t_a.\mathit{word}_{in}=t_b.\mathit{word}_{in}` and - `t_a.\mathit{word}_{out}=t_b.\mathit{word}_{out}` and `t_a` - and `t_b` lead to some equivalent states `t` and `t'`. + transitions from `a` and the transitions from `b` with the + following properties: if `\varphi(t_a)=t_b`, then + + - `t_a.\mathit{word}_\mathit{in}=t_b.\mathit{word}_\mathit{in}`, + - `t_a.\mathit{word}_\mathit{out}=t_b.\mathit{word}_\mathit{out}`, and + - `t_a` and `t_b` lead to some equivalent states `a'` and `b'`. Non-initial states may be merged with initial states, the resulting state is an initial state. From 336de301a7510e2005c7302d6aa1ebd796be66d1 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Mon, 10 Mar 2014 15:44:16 +0100 Subject: [PATCH 19/26] small changes in docstring of _minimization_Moore_ --- 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 5079d3a7fe8..cc90db58520 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4634,7 +4634,7 @@ def _minimization_Brzozowski_(self): def _minimization_Moore_(self): """ - Returns a minimized automaton by using Brzozowski's algorithm. + Returns a minimized automaton by using Moore's algorithm. See also :meth:`.minimization`. @@ -4814,15 +4814,14 @@ def simplification(self): ....: ("A", "C", 1, -1), ....: ("B", "A", 2, 0), ....: ("C", "A", 2, 0)]) - sage: fsms = fsm.simplification() - sage: fsms + sage: fsm_simplified = fsm.simplification() + sage: fsm_simplified Transducer with 2 states - sage: fsms.transitions() + sage: fsm_simplified.transitions() [Transition from ('A',) to ('A',): 0|0, Transition from ('A',) to ('B', 'C'): 1|1,0, Transition from ('A',) to ('B', 'C'): 1|-1,0, Transition from ('B', 'C') to ('A',): 2|-] - """ fsm = deepcopy(self) fsm.prepone_output() From 9d18de85ad1417e99a7efeff1ac1de6387e10fae Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 Mar 2014 11:42:06 +0100 Subject: [PATCH 20/26] corrected spacings (PEP8); rewrote long line --- src/sage/combinat/finite_state_machine.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 66c8470987b..fc8d9aeff7a 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4326,7 +4326,9 @@ def quotient(self, classes): # Copy data from old transducer for c in classes: new_state = state_mapping[c[0]] - sorted_transitions = sorted([(state_mapping[t.to_state], t.word_in, t.word_out) for t in c[0].transitions ]) + sorted_transitions = sorted( + [(state_mapping[t.to_state], t.word_in, t.word_out) + for t in c[0].transitions]) for transition in self.iter_transitions(c[0]): new.add_transition( from_state = new_state, @@ -4341,8 +4343,10 @@ def quotient(self, classes): "Class %s mixes final and non-final states" % (c,) assert new_state.word_out == state.word_out, \ "Class %s mixes different word_out" % (c,) - assert sorted_transitions == sorted([(state_mapping[t.to_state], t.word_in, t.word_out) for t in state.transitions ]), \ - "Transitions of state %s and %s are incompatible." % (c[0], state) + assert sorted_transitions == sorted( + [(state_mapping[t.to_state], t.word_in, t.word_out) + for t in state.transitions]), \ + "Transitions of state %s and %s are incompatible." % (c[0], state) return new From c518d0808336d148454d290c41ffefc3d5467bac Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 Mar 2014 11:50:47 +0100 Subject: [PATCH 21/26] using relabeled instead of deepcopy (former TODO) --- src/sage/combinat/finite_state_machine.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 9edc3a8e109..21fdbcd9e9a 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4150,9 +4150,7 @@ def quotient(self, classes): # Create new states and build state_mapping for c in classes: new_label = tuple(c) - new_state = deepcopy(c[0]) - new_state._label_ = new_label - # TODO: Why does c[0].relabeled(new_label) not work? + new_state = c[0].relabeled(new_label) new.add_state(new_state) for state in c: state_mapping[state] = new_state From 8ce1bbc591bccb25a05fa25fe1d67a6cc3128f9d Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 Mar 2014 13:53:53 +0100 Subject: [PATCH 22/26] corrected spacings (PEP8); rewritten long lines; corrected typos --- src/sage/combinat/finite_state_machine.py | 135 ++++++++++++---------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index ea1f1522ced..562a06efba7 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -1139,68 +1139,73 @@ def is_FiniteStateMachine(FSM): """ return isinstance(FSM, FiniteStateMachine) + def duplicate_transition_ignore(old_transition, new_transition): """ - Default function for handling duplicate transitions in finite state machines. - This implementation ignores the occurrence. See the documentation of the - ``on_duplicate_transition`` of :class:`FiniteStateMachine`. + Default function for handling duplicate transitions in finite + state machines. This implementation ignores the occurrence. + + See the documentation of the ``on_duplicate_transition`` of + :class:`FiniteStateMachine`. INPUT: - - ``old_transition``: A transition in a finite state machine. - - - ``new_transition``: A transition, identical to old_transition, which - is to be inserted into the finite state machine. + - ``old_transition`` -- A transition in a finite state machine. + + - ``new_transition`` -- A transition, identical to ``old_transition``, + which is to be inserted into the finite state machine. OUTPUT: - - The same transition, unchanged. + The same transition, unchanged. EXAMPLES:: sage: from sage.combinat.finite_state_machine import duplicate_transition_ignore sage: from sage.combinat.finite_state_machine import FSMTransition - sage: duplicate_transition_ignore(FSMTransition(0,0,1), - ....: FSMTransition(0,0,1)) + sage: duplicate_transition_ignore(FSMTransition(0, 0, 1), + ....: FSMTransition(0, 0, 1)) Transition from 0 to 0: 1|- """ return old_transition + def duplicate_transition_raise_error(old_transition, new_transition): """ - Alternative function for handling duplicate transitions in finite state machines. - This implementation raises a ValueError. + Alternative function for handling duplicate transitions in finite + state machines. This implementation raises a ``ValueError``. - See the documentation of the - ``on_duplicate_transition`` of :class:`FiniteStateMachine`. + See the documentation of the ``on_duplicate_transition`` of + :class:`FiniteStateMachine`. INPUT: - - ``old_transition``: A transition in a finite state machine. - - - ``new_transition``: A transition, identical to old_transition, which - is to be inserted into the finite state machine. + - ``old_transition`` -- A transition in a finite state machine. + + - ``new_transition`` -- A transition, identical to ``old_transition``, + which is to be inserted into the finite state machine. OUTPUT: - Nothing. A ValueError is raised. + Nothing. A ``ValueError`` is raised. EXAMPLES:: sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error sage: from sage.combinat.finite_state_machine import FSMTransition - sage: duplicate_transition_raise_error(FSMTransition(0,0,1), - ....: FSMTransition(0,0,1)) + sage: duplicate_transition_raise_error(FSMTransition(0, 0, 1), + ....: FSMTransition(0, 0, 1)) Traceback (most recent call last): ... ValueError: Attempting to re-insert transition Transition from 0 to 0: 1|- """ raise ValueError("Attempting to re-insert transition %s" % old_transition) + def duplicate_transition_add_input(old_transition, new_transition): """ Alternative function for handling duplicate transitions in finite - state machines. This implementation adds the input label of the + state machines. This implementation adds the input label of the new transition to the input label of the old transition. This is intended for the case where a Markov chain is modelled by a finite state machine using the input labels as transition probabilities. @@ -1210,45 +1215,48 @@ def duplicate_transition_add_input(old_transition, new_transition): INPUT: - - ``old_transition``: A transition in a finite state machine. - - - ``new_transition``: A transition, identical to old_transition, which - is to be inserted into the finite state machine. + - ``old_transition`` -- A transition in a finite state machine. + + - ``new_transition`` -- A transition, identical to ``old_transition``, + which is to be inserted into the finite state machine. OUTPUT: - - Transition whose input weight is the sum of the input - weights of ``old_transition`` and ``new_transition``. + A transition whose input weight is the sum of the input + weights of ``old_transition`` and ``new_transition``. EXAMPLES:: sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input sage: from sage.combinat.finite_state_machine import FSMTransition - sage: duplicate_transition_add_input(FSMTransition('a','a',1/2), - ....: FSMTransition('a','a',1/2)) + sage: duplicate_transition_add_input(FSMTransition('a', 'a', 1/2), + ....: FSMTransition('a', 'a', 1/2)) Transition from 'a' to 'a': 1|- Input labels must be lists of length 1:: - sage: duplicate_transition_add_input(FSMTransition('a','a',[1,1]), - ....: FSMTransition('a','a',[1,1])) + sage: duplicate_transition_add_input(FSMTransition('a', 'a', [1, 1]), + ....: FSMTransition('a', 'a', [1, 1])) Traceback (most recent call last): ... - TypeError: Trying to use duplicate_transition_add_input on - "Transition from 'a' to 'a': 1,1|-" and - "Transition from 'a' to 'a': 1,1|-", + TypeError: Trying to use duplicate_transition_add_input on + "Transition from 'a' to 'a': 1,1|-" and + "Transition from 'a' to 'a': 1,1|-", but input words are assumed to be lists of length 1 - """ - if hasattr(old_transition.word_in, '__iter__') and len(old_transition.word_in)==1 \ - and hasattr(new_transition.word_in, '__iter__') and len(new_transition.word_in)==1: - old_transition.word_in = [ old_transition.word_in[0] + new_transition.word_in[0] ] + if (hasattr(old_transition.word_in, '__iter__') + and len(old_transition.word_in) == 1 + and hasattr(new_transition.word_in, '__iter__') + and len(new_transition.word_in) == 1): + old_transition.word_in = [old_transition.word_in[0] + + new_transition.word_in[0]] else: - raise TypeError('Trying to use duplicate_transition_add_input on ' + \ - '"%s" and "%s", '% (old_transition, new_transition) + \ - 'but input words are assumed to be lists of length 1' ) + raise TypeError('Trying to use duplicate_transition_add_input on ' + + '"%s" and "%s", ' % (old_transition, new_transition) + + 'but input words are assumed to be lists of length 1') return old_transition + class FiniteStateMachine(SageObject): """ Class for a finite state machine. @@ -1286,13 +1294,13 @@ class FiniteStateMachine(SageObject): are stored in an interal dictionary for speed up. - ``on_duplicate_transition`` -- A function which is called when a - transition is inserted into self which already existed (same - ``from_state``, same ``to_state``, same ``word_in``, same ``word_out``). + transition is inserted into ``self`` which already existed (same + ``from_state``, same ``to_state``, same ``word_in``, same ``word_out``). - This function is assumed to take two arguments, the first beeing - the already existing transition, the second beeing the new + This function is assumed to take two arguments, the first being + the already existing transition, the second being the new transition (as an ``FSMTransition``). The function must return the - (possibly modified) original transition. + (possibly modified) original transition. By default, ``on_duplicate_transition=duplicate_transition_ignore``, where ``duplicate_transition_ignore`` is a predefined function @@ -1463,14 +1471,14 @@ class FiniteStateMachine(SageObject): The following examples demonstrate the use of ``on_duplicate_transition``:: - sage: F = FiniteStateMachine([['a','a', 1/2], ['a','a', 1/2]]) + sage: F = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]]) sage: F.transitions() [Transition from 'a' to 'a': 1/2|-] :: sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error - sage: F1 = FiniteStateMachine([['a','a', 1/2], ['a','a', 1/2]], + sage: F1 = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]], ....: on_duplicate_transition=duplicate_transition_raise_error) Traceback (most recent call last): ... @@ -1480,7 +1488,7 @@ class FiniteStateMachine(SageObject): the input labels are considered as transition probabilities:: sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input - sage: F = FiniteStateMachine([['a','a', 1/2], ['a','a', 1/2]], + sage: F = FiniteStateMachine([['a', 'a', 1/2], ['a', 'a', 1/2]], ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.transitions() [Transition from 'a' to 'a': 1|-] @@ -1679,11 +1687,12 @@ def __copy__(self): copy = __copy__ + def empty_copy(self, memo=None): """ Returns an empty deep copy of the finite state machine, i.e., - input_alphabet, output_alphabet, on_duplicate_transition are preserved, but states and - transitions are not. + ``input_alphabet``, ``output_alphabet``, ``on_duplicate_transition`` + are preserved, but states and transitions are not. INPUT: @@ -1697,8 +1706,8 @@ def empty_copy(self, memo=None): sage: from sage.combinat.finite_state_machine import duplicate_transition_raise_error sage: F = FiniteStateMachine([('A', 'A', 0, 2), ('A', 'A', 1, 3)], - ....: input_alphabet=[0,1], - ....: output_alphabet=[2,3], + ....: input_alphabet=[0, 1], + ....: output_alphabet=[2, 3], ....: on_duplicate_transition=duplicate_transition_raise_error) sage: FE = F.empty_copy(); FE Finite state machine with 0 states @@ -1706,7 +1715,7 @@ def empty_copy(self, memo=None): [0, 1] sage: FE.output_alphabet [2, 3] - sage: FE.on_duplicate_transition==duplicate_transition_raise_error + sage: FE.on_duplicate_transition == duplicate_transition_raise_error True """ new = self.__class__() @@ -1715,6 +1724,7 @@ def empty_copy(self, memo=None): new.on_duplicate_transition = self.on_duplicate_transition return new + def __deepcopy__(self, memo): """ Returns a deep copy of the finite state machine. @@ -3003,11 +3013,11 @@ def add_states(self, states): def add_transition(self, *args, **kwargs): """ Adds a transition to the finite state machine and returns the - new transition. + new transition. If the transition already exists, the return value of ``self.on_duplicate_transition`` is returned. See the - documentation of :class:`FiniteStateMachine`. + documentation of :class:`FiniteStateMachine`. INPUT: @@ -3110,9 +3120,10 @@ def _add_fsm_transition_(self, t): """ try: existing_transition = self.transition(t) - return self.on_duplicate_transition(existing_transition, t) except LookupError: pass + else: + return self.on_duplicate_transition(existing_transition, t) from_state = self.add_state(t.from_state) self.add_state(t.to_state) from_state.transitions.append(t) @@ -4989,12 +5000,12 @@ def simplification(self): Transition from ('A',) to ('B', 'C'): 1|-1,0, Transition from ('B', 'C') to ('A',): 2|-] - :: + :: sage: from sage.combinat.finite_state_machine import duplicate_transition_add_input - sage: T = Transducer([('A', 'A', 1/2, 0), - ....: ('A', 'B', 1/4, 1), - ....: ('A', 'C', 1/4, 1), + sage: T = Transducer([('A', 'A', 1/2, 0), + ....: ('A', 'B', 1/4, 1), + ....: ('A', 'C', 1/4, 1), ....: ('B', 'A', 1, 0), ....: ('C', 'A', 1, 0)], ....: initial_states=[0], From a6d6f43e1be17728a3e86085b312d66a8b492f24 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 Mar 2014 14:03:54 +0100 Subject: [PATCH 23/26] on_duplicate_transition=None as default --- src/sage/combinat/finite_state_machine.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 562a06efba7..19e1b952955 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -1302,8 +1302,10 @@ class FiniteStateMachine(SageObject): transition (as an ``FSMTransition``). The function must return the (possibly modified) original transition. - By default, ``on_duplicate_transition=duplicate_transition_ignore``, - where ``duplicate_transition_ignore`` is a predefined function + By default, we have ``on_duplicate_transition=None``, which is + interpreted as + ``on_duplicate_transition=duplicate_transition_ignore``, where + ``duplicate_transition_ignore`` is a predefined function ignoring the occurrence. Other such predefined functions are ``duplicate_transition_raise_error`` and ``duplicate_transition_add_input``. @@ -1560,7 +1562,7 @@ def __init__(self, input_alphabet=None, output_alphabet=None, determine_alphabets=None, store_states_dict=True, - on_duplicate_transition=duplicate_transition_ignore): + on_duplicate_transition=None): """ See :class:`FiniteStateMachine` for more information. @@ -1594,6 +1596,8 @@ def __init__(self, self.input_alphabet = input_alphabet self.output_alphabet = output_alphabet + if on_duplicate_transition is None: + on_duplicate_transition = duplicate_transition_ignore if hasattr(on_duplicate_transition, '__call__'): self.on_duplicate_transition=on_duplicate_transition else: From fa01ea3c758b860ee7ab55caab8427b2b1994122 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 Mar 2014 17:56:03 +0100 Subject: [PATCH 24/26] added one spaceing (PEP8) --- 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 c80b28946ed..851690ddba5 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -908,7 +908,7 @@ def __init__(self, from_state, to_state, raise TypeError, 'Wrong argument for hook.' - def __lt__(self,other): + def __lt__(self, other): """ Returns True if ``self`` is less than ``other`` with respect to the key ``(self.from_state, self.word_in, self.to_state, self.word_out)``. From b900b96f5729b95ac8453be94bb2da6e71be6297 Mon Sep 17 00:00:00 2001 From: Daniel Krenn Date: Tue, 11 Mar 2014 18:16:43 +0100 Subject: [PATCH 25/26] docstring of quotient: added paragraph forgotten by merge; added blank line It seems that on the last non-trivial merge 09d6130 - Merge branch 'fsm/on_duplicate_transition' into fsm/state_color this was cut out falsely. --- src/sage/combinat/finite_state_machine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index d31c0e23139..d75092a07c0 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -547,6 +547,7 @@ def __init__(self, label, word_out=None, self.color = color + def __lt__(self, other): """ Returns True if label of ``self`` is less than label of @@ -4341,6 +4342,9 @@ def quotient(self, classes): A finite state machine. + The labels of the new states are tuples of states of the + ``self``, corresponding to ``classes``. + Assume that `c` is a class, and `a` and `b` are states in `c`. Then there is a bijection `\varphi` between the transitions from `a` and the transitions from `b` with the From 12351e2a78fa8bf3b6487495499826eacdf9ebdb Mon Sep 17 00:00:00 2001 From: Clemens Heuberger Date: Wed, 12 Mar 2014 15:49:14 +0100 Subject: [PATCH 26/26] inserted "parameter" in docstring of on_duplicate_transition_... --- src/sage/combinat/finite_state_machine.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 19e1b952955..bb9161749b2 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -1145,8 +1145,8 @@ def duplicate_transition_ignore(old_transition, new_transition): Default function for handling duplicate transitions in finite state machines. This implementation ignores the occurrence. - See the documentation of the ``on_duplicate_transition`` of - :class:`FiniteStateMachine`. + See the documentation of the ``on_duplicate_transition`` parameter + of :class:`FiniteStateMachine`. INPUT: @@ -1175,8 +1175,8 @@ def duplicate_transition_raise_error(old_transition, new_transition): Alternative function for handling duplicate transitions in finite state machines. This implementation raises a ``ValueError``. - See the documentation of the ``on_duplicate_transition`` of - :class:`FiniteStateMachine`. + See the documentation of the ``on_duplicate_transition`` parameter + of :class:`FiniteStateMachine`. INPUT: @@ -1210,8 +1210,8 @@ def duplicate_transition_add_input(old_transition, new_transition): intended for the case where a Markov chain is modelled by a finite state machine using the input labels as transition probabilities. - See the documentation of the ``on_duplicate_transition`` of - :class:`FiniteStateMachine`. + See the documentation of the ``on_duplicate_transition`` parameter + of :class:`FiniteStateMachine`. INPUT: