diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index ee7cefaa6ce..f12db063720 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -283,7 +283,7 @@ def __normalize__(cls, k, coefficient_ring, **kwds): sage: Seq2 = kRegularSequenceSpace(2, ZZ) sage: Seq2.category() - Category of sets + Category of modules over Integer Ring sage: Seq2.alphabet() {0, 1} """ diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 0b5d134c6c2..8c65e0356ff 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -75,9 +75,11 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from functools import wraps + from sage.misc.cachefunc import cached_method from sage.misc.superseded import experimental -from sage.structure.element import Element +from sage.structure.element import ModuleElement from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -297,7 +299,76 @@ def prefix_set(self): if p + a not in self.elements] -class RecognizableSeries(Element): +def minimize_result(operation): + r""" + A decorator for operations that enables control of + automatic minimization on the result. + + INPUT: + + - ``operation`` -- a method + + OUTPUT: + + A method with the following additional argument: + + - ``minimize`` -- (default: ``None``) a boolean or ``None``. + If ``True``, then :meth:`minimized` is called after the operation, + if ``False``, then not. If this argument is ``None``, then + the default specified by the parent's ``minimize_results`` is used. + + TESTS:: + + sage: from sage.combinat.recognizable_series import minimize_result + sage: class P(object): + ....: pass + sage: p = P() + sage: class S(object): + ....: def __init__(self, s): + ....: self.s = s + ....: def __repr__(self): + ....: return self.s + ....: def parent(self): + ....: return p + ....: def minimized(self): + ....: return S(self.s + ' minimized') + ....: @minimize_result + ....: def operation(self): + ....: return S(self.s + ' result') + + sage: p.minimize_results = True + sage: S('some').operation() + some result minimized + sage: S('some').operation(minimize=True) + some result minimized + sage: S('some').operation(minimize=False) + some result + + sage: p.minimize_results = False + sage: S('some').operation() + some result + sage: S('some').operation(minimize=True) + some result minimized + sage: S('some').operation(minimize=False) + some result + """ + @wraps(operation) + def minimized(self, *args, **kwds): + minimize = kwds.pop('minimize', None) + if minimize is None: + minimize = self.parent().minimize_results + + result = operation(self, *args, **kwds) + + if minimize: + result = result.minimized() + + return result + + return minimized + + +class RecognizableSeries(ModuleElement): def __init__(self, parent, mu, left, right): r""" A recognizable series. @@ -585,7 +656,6 @@ def coefficient_of_word(self, w, multiply_left=True, multiply_right=True): result = result * self.right return result - __getitem__ = coefficient_of_word @cached_method @@ -1009,6 +1079,314 @@ def alpha(c): P = self.parent() return P.element_class(P, mu_prime, left_prime, right_prime) + def dimension(self): + r""" + Return the dimension of this recognizable series. + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 1]), right=vector([1, 0])).dimension() + 2 + """ + return self.mu.first().nrows() + + @minimize_result + def _add_(self, other): + r""" + Return the sum of this recognizable series and the ``other`` + recognizable series. + + INPUT: + + - ``other`` -- a :class:`RecognizableSeries` with the same parent + as this recognizable series + + - ``minimize`` -- (default: ``None``) a boolean or ``None``. + If ``True``, then :meth:`minimized` is called after the operation, + if ``False``, then not. If this argument is ``None``, then + the default specified by the parent's ``minimize_results`` is used. + + OUTPUT: + + A :class:`RecognizableSeries`. + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: E = Seq2((Matrix([[0, 1], [0, 1]]), Matrix([[0, 0], [0, 1]])), + ....: vector([1, 0]), vector([1, 1])) + sage: E + 2-regular sequence 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, ... + sage: O = Seq2((Matrix([[0, 0], [0, 1]]), Matrix([[0, 1], [0, 1]])), + ....: vector([1, 0]), vector([0, 1])) + sage: O + 2-regular sequence 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, ... + sage: I = E + O # indirect doctest + sage: I + 2-regular sequence 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... + sage: I.linear_representation() + ((1), + Finite family {0: [1], + 1: [1]}, + (1)) + """ + from sage.modules.free_module_element import vector + P = self.parent() + + result = P.element_class( + P, + dict((a, self.mu[a].block_sum(other.mu[a])) for a in P.alphabet()), + vector(tuple(self.left) + tuple(other.left)), + vector(tuple(self.right) + tuple(other.right))) + + return result + + def _neg_(self): + r""" + Return the additive inverse of this recognizable series. + + OUTPUT: + + A :class:`RecognizableSeries` + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: E = Seq2((Matrix([[0, 1], [0, 1]]), Matrix([[0, 0], [0, 1]])), + ....: vector([1, 0]), vector([1, 1])) + sage: -E + 2-regular sequence -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, ... + sage: Z = E - E + sage: Z.is_trivial_zero() + True + """ + P = self.parent() + return P.element_class(P, self.mu, -self.left, self.right) + + def _rmul_(self, other): + r""" + Multiply this recognizable series from the right + by an element ``other`` of its coefficient (semi-)ring. + + INPUT: + + - ``other`` -- an element of the coefficient (semi-)ring + + OUTPUT: + + A :class:`RecognizableSeries` + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: E = Seq2((Matrix([[0, 1], [0, 1]]), Matrix([[0, 0], [0, 1]])), + ....: vector([1, 0]), vector([1, 1])) + sage: M = 2 * E # indirect doctest + sage: M + 2-regular sequence 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, ... + sage: M.linear_representation() + ((2, 0), + Finite family {0: [0 1] + [0 1], + 1: [0 0] + [0 1]}, + (1, 1)) + + TESTS:: + + sage: 1 * E is E + True + + We test that ``_rmul_`` and ``_lmul_`` are actually called:: + + sage: def print_name(f): + ....: def f_with_printed_name(*args, **kwds): + ....: print(f.__name__) + ....: return f(*args, **kwds) + ....: return f_with_printed_name + + sage: E._rmul_ = print_name(E._rmul_) + sage: E._lmul_ = print_name(E._lmul_) + sage: 2 * E + _rmul_ + 2-regular sequence 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, ... + sage: E * 2 + _lmul_ + _lmul_ + 2-regular sequence 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, ... + """ + if other.is_one(): + return self + P = self.parent() + return P.element_class(P, self.mu, other*self.left, self.right) + + def _lmul_(self, other): + r""" + Multiply this recognizable series from the left + by an element ``other`` of its coefficient (semi-)ring. + + INPUT: + + - ``other`` -- an element of the coefficient (semi-)ring + + OUTPUT: + + A :class:`RecognizableSeries` + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: E = Seq2((Matrix([[0, 1], [0, 1]]), Matrix([[0, 0], [0, 1]])), + ....: vector([1, 0]), vector([1, 1])) + sage: M = E * 2 # indirect doctest + sage: M + 2-regular sequence 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, ... + sage: M.linear_representation() + ((1, 0), + Finite family {0: [0 1] + [0 1], + 1: [0 0] + [0 1]}, + (2, 2)) + + TESTS:: + + sage: E * 1 is E + True + + The following is not tested, as `MS^i` for integers `i` does + not work, thus ``vector([m])`` fails. (See :trac:`21317` for + details.) + + :: + + sage: MS = MatrixSpace(ZZ,2,2) + sage: Rec = RecognizableSeriesSpace(MS, [0, 1]) + sage: m = MS.an_element() + sage: S = Rec((Matrix([[m]]), Matrix([[m]])), # not tested + ....: vector([m]), vector([m])) + sage: S # not tested + sage: M = m * S # not tested indirect doctest + sage: M # not tested + sage: M.linear_representation() # not tested + """ + if other.is_one(): + return self + P = self.parent() + return P.element_class(P, self.mu, self.left, self.right*other) + + @minimize_result + def hadamard_product(self, other): + r""" + Return the Hadamard product of this recognizable series + and the ``other`` recognizable series, i.e., multiply the two + series coefficient-wise. + + INPUT: + + - ``other`` -- a :class:`RecognizableSeries` with the same parent + as this recognizable series + + - ``minimize`` -- (default: ``None``) a boolean or ``None``. + If ``True``, then :meth:`minimized` is called after the operation, + if ``False``, then not. If this argument is ``None``, then + the default specified by the parent's ``minimize_results`` is used. + + OUTPUT: + + A :class:`RecognizableSeries` + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + + sage: E = Seq2((Matrix([[0, 1], [0, 1]]), Matrix([[0, 0], [0, 1]])), + ....: vector([1, 0]), vector([1, 1])) + sage: E + 2-regular sequence 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, ... + + sage: O = Seq2((Matrix([[0, 0], [0, 1]]), Matrix([[0, 1], [0, 1]])), + ....: vector([1, 0]), vector([0, 1])) + sage: O + 2-regular sequence 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, ... + + sage: C = Seq2((Matrix([[2, 0], [2, 1]]), Matrix([[0, 1], [-2, 3]])), + ....: vector([1, 0]), vector([0, 1])) + sage: C + 2-regular sequence 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... + + :: + + sage: CE = C.hadamard_product(E) + sage: CE + 2-regular sequence 0, 0, 2, 0, 4, 0, 6, 0, 8, 0, ... + sage: CE.linear_representation() + ((1, 0, 0), + Finite family {0: [0 1 0] + [0 2 0] + [0 2 1], + 1: [ 0 0 0] + [ 0 0 1] + [ 0 -2 3]}, + (0, 0, 2)) + + sage: Z = E.hadamard_product(O) + sage: Z + 2-regular sequence 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... + sage: Z.linear_representation() + ((), + Finite family {0: [], + 1: []}, + ()) + + TESTS:: + + sage: EC = E.hadamard_product(C, minimize=False) + sage: EC + 2-regular sequence 0, 0, 2, 0, 4, 0, 6, 0, 8, 0, ... + sage: EC.linear_representation() + ((1, 0, 0, 0), + Finite family {0: [0 0 2 0] + [0 0 2 1] + [0 0 2 0] + [0 0 2 1], + 1: [ 0 0 0 0] + [ 0 0 0 0] + [ 0 0 0 1] + [ 0 0 -2 3]}, + (0, 1, 0, 1)) + sage: MEC = EC.minimized() + sage: MEC + 2-regular sequence 0, 0, 2, 0, 4, 0, 6, 0, 8, 0, ... + sage: MEC.linear_representation() + ((1, 0, 0), + Finite family {0: [0 1 0] + [0 2 0] + [0 2 1], + 1: [ 0 0 0] + [ 0 0 1] + [ 0 -2 3]}, + (0, 0, 2)) + """ + from sage.matrix.constructor import Matrix + from sage.modules.free_module_element import vector + P = self.parent() + + def tensor_product(left, right): + T = left.tensor_product(right) + T.subdivide() + return T + result = P.element_class( + P, + dict((a, tensor_product(self.mu[a], other.mu[a])) + for a in P.alphabet()), + vector(tensor_product(Matrix(self.left), Matrix(other.left))), + vector(tensor_product(Matrix(self.right), Matrix(other.right)))) + + return result + class RecognizableSeriesSpace(UniqueRepresentation, Parent): r""" @@ -1093,7 +1471,8 @@ def __classcall__(cls, *args, **kwds): def __normalize__(cls, coefficient_ring=None, alphabet=None, indices=None, - category=None): + category=None, + minimize_results=True): r""" Normalizes the input in order to ensure a unique representation. @@ -1104,7 +1483,7 @@ def __normalize__(cls, sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) # indirect doctest sage: Rec.category() - Category of sets + Category of modules over Integer Ring sage: RecognizableSeriesSpace([0, 1], [0, 1]) Traceback (most recent call last): ... @@ -1146,13 +1525,13 @@ def __normalize__(cls, raise ValueError( 'Coefficient ring {} is not a semiring.'.format(coefficient_ring)) - from sage.categories.sets_cat import Sets - category = category or Sets() + from sage.categories.modules import Modules + category = category or Modules(coefficient_ring) - return (coefficient_ring, indices, category) + return (coefficient_ring, indices, category, minimize_results) @experimental(trac_number=21202) - def __init__(self, coefficient_ring, indices, category): + def __init__(self, coefficient_ring, indices, category, minimize_results): r""" See :class:`RecognizableSeriesSpace` for details. @@ -1165,12 +1544,17 @@ def __init__(self, coefficient_ring, indices, category): - ``category`` -- (default: ``None``) the category of this space + - ``minimize_results`` -- (default: ``True``) a boolean. If set, then + :meth:`RecognizableSeries.minimized` is automatically called + after performing operations. + TESTS:: sage: RecognizableSeriesSpace(ZZ, [0, 1]) Space of recognizable series on {0, 1} with coefficients in Integer Ring """ self._indices_ = indices + self._minimize_results_ = minimize_results super(RecognizableSeriesSpace, self).__init__( category=category, base=coefficient_ring) @@ -1224,6 +1608,24 @@ def coefficient_ring(self): """ return self.base() + @property + def minimize_results(self): + r""" + A boolean indicating whether + :meth:`RecognizableSeries.minimized` is automatically called + after performing operations. + + TESTS:: + + sage: RecognizableSeriesSpace(ZZ, [0, 1]).minimize_results + True + sage: RecognizableSeriesSpace(ZZ, [0, 1], minimize_results=True).minimize_results + True + sage: RecognizableSeriesSpace(ZZ, [0, 1], minimize_results=False).minimize_results + False + """ + return self._minimize_results_ + def _repr_(self): r""" Return a representation string of this recognizable sequence @@ -1242,20 +1644,35 @@ def _repr_(self): 'with coefficients in {}'.format(self.alphabet(), self.coefficient_ring()) - def zero(self): - """ - Return the zero of this recognizable series space. + @cached_method + def one_hadamard(self): + r""" + Return the identity with respect to the + :meth:`~RecognizableSeries.hadamard_product`, i.e. the + coefficient-wise multiplication. - This can be removed once this recognizable series space is - at least an additive magma. + OUTPUT: + + A :class:`RecognizableSeries` EXAMPLES:: sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) - sage: Rec.zero() - 0 + sage: Rec.one_hadamard() + [] + [0] + [1] + [00] + [01] + [10] + + [11] + [000] + [001] + [010] + ... + + TESTS:: + + sage: Rec.one_hadamard() is Rec.one_hadamard() + True """ - return self(0) + from sage.matrix.constructor import Matrix + from sage.modules.free_module_element import vector + + one = self.coefficient_ring()(1) + return self(dict((a, Matrix([[one]])) for a in self.alphabet()), + vector([one]), vector([one])) def _element_constructor_(self, data, left=None, right=None):