diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index f9f2e929fe3..dda85132dc7 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -80,15 +80,13 @@ def __init__(self, Qdim): 7 sage: idx._cardinality 128 - sage: idx._maximal_set - 1111111 sage: i = idx.an_element(); i 0 sage: type(i) """ self._nbits = Qdim - self._cardinality = 2**Qdim + self._cardinality = 2 ** Qdim # the if statement here is in case Qdim is 0. category = FiniteEnumeratedSets().Facade() Parent.__init__(self, category=category, facade=True) @@ -2599,9 +2597,24 @@ class ExteriorAlgebraIdeal(Ideal_nc): def __init__(self, ring, gens, coerce=True, side="twosided"): """ Initialize ``self``. + + EXAMPLES: + + We skip the category test because the ideals are not a proper + element class of the monoid of all ideals:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x*y - x, x*y - 1]) + sage: TestSuite(I).run(skip="_test_category") + + sage: I = E.ideal([x*y - 3, 0, 2*3]) + sage: TestSuite(I).run(skip="_test_category") + + sage: I = E.ideal([]) + sage: TestSuite(I).run(skip="_test_category") """ self._groebner_strategy = None - self._homogeneous = all(x.is_super_homogeneous() for x in gens) + self._homogeneous = all(x.is_super_homogeneous() for x in gens if x) if self._homogeneous: side = "twosided" Ideal_nc.__init__(self, ring, gens, coerce, side) @@ -2620,6 +2633,11 @@ def reduce(self, f): x + y sage: I.reduce(x*y + x*y*z) 0 + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a+b*c]) + sage: I.reduce(I.gen(0) * d) + 0 """ if self._groebner_strategy is None: self.groebner_basis() @@ -2680,6 +2698,31 @@ def __richcmp__(self, other, op): True sage: I <= E.ideal([x]) False + + sage: E. = ExteriorAlgebra(QQ) + sage: p = a + b*c + sage: IT = E.ideal([p], side="twosided") + sage: IR = E.ideal([p], side="right") + sage: IL = E.ideal([p], side="left") + sage: IR == IL + False + sage: IR <= IL + False + sage: IR >= IL + False + sage: IL.reduce(p * d) + 2*a*d + sage: IR.reduce(d * p) + -2*a*d + + sage: IR <= IT + True + sage: IL <= IT + True + sage: IT <= IL + False + sage: IT <= IR + False """ if not isinstance(other, ExteriorAlgebraIdeal): if op == op_EQ: @@ -2697,35 +2740,41 @@ def __richcmp__(self, other, op): elif op == op_GT: return other.__richcmp__(self, op_LT) - if self.side() == other.side(): - s_gens = set(g for g in self.gens() if g) - o_gens = set(g for g in other.gens() if g) - if set(s_gens) == set(o_gens): - return rich_to_bool(op, 0) - - contained = all(f in other for f in s_gens) - if op == op_LE: - return contained + s_gens = set(g for g in self.gens() if g) + o_gens = set(g for g in other.gens() if g) - contains = all(f in self for f in o_gens) - if op == op_EQ: - return contained and contains - if op == op_NE: - return not (contained and contains) - # remaining case < - return contained and not contains + if self.side() != other.side(): + if other.side() == "right": + X = set(t * f for t in self.ring().basis() for f in s_gens) + s_gens.update(X) + elif other.side() == "left": + X = set(f * t for t in self.ring().basis() for f in s_gens) + s_gens.update(X) + if set(s_gens) == set(o_gens): + return rich_to_bool(op, 0) - if op in [op_LT, op_LE] and other.side() == "twosided": - if not all(f in other for f in set(self.gens()) if f): - return False - if op == op_LE: - return True - return self.__richcmp__(other, op_NE) + contained = all(f in other for f in s_gens) + if op == op_LE: + return contained + if op == op_NE and not contained: + return True - # Otherwise we will fallback to linear algebra containment - # TODO: Implement this - return NotImplemented + if self.side() != other.side(): + if self.side() == "right": + X = set(t * f for t in self.ring().basis() for f in o_gens) + s_gens.update(X) + elif self.side() == "left": + X = set(f * t for t in self.ring().basis() for f in o_gens) + s_gens.update(X) + + contains = all(f in self for f in o_gens) + if op == op_EQ: + return contained and contains + if op == op_NE: + return not (contained and contains) + # remaining case < + return contained and not contains def groebner_basis(self, term_order="neglex"): r""" diff --git a/src/sage/algebras/clifford_algebra_element.pyx b/src/sage/algebras/clifford_algebra_element.pyx index 0c35b57d983..ba2d89b880d 100644 --- a/src/sage/algebras/clifford_algebra_element.pyx +++ b/src/sage/algebras/clifford_algebra_element.pyx @@ -8,7 +8,7 @@ AUTHORS: """ #***************************************************************************** -# Copyright (C) 2022 Trevor Karn +# Copyright (C) 2022 Trevor K. Karn # (C) 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify diff --git a/src/sage/algebras/exterior_algebra_groebner.pxd b/src/sage/algebras/exterior_algebra_groebner.pxd index 4e235dae6ef..37ac62d08cc 100644 --- a/src/sage/algebras/exterior_algebra_groebner.pxd +++ b/src/sage/algebras/exterior_algebra_groebner.pxd @@ -17,6 +17,7 @@ cdef class GroebnerStrategy: cdef int side cdef MonoidElement ideal cdef bint homogeneous + cdef Integer rank cdef public tuple groebner_basis cdef inline bint build_S_poly(self, CliffordAlgebraElement f, CliffordAlgebraElement g) @@ -39,8 +40,8 @@ cdef class GroebnerStrategyNegLex(GroebnerStrategy): pass cdef class GroebnerStrategyDegRevLex(GroebnerStrategy): - cdef Integer rank + pass cdef class GroebnerStrategyDegLex(GroebnerStrategy): - cdef Integer rank + pass diff --git a/src/sage/algebras/exterior_algebra_groebner.pyx b/src/sage/algebras/exterior_algebra_groebner.pyx index d76f797bbe8..771af149931 100644 --- a/src/sage/algebras/exterior_algebra_groebner.pyx +++ b/src/sage/algebras/exterior_algebra_groebner.pyx @@ -6,11 +6,11 @@ of exterior algebra. AUTHORS: -- Trevor Karn, Travis Scrimshaw (July 2022): Initial implementation +- Trevor K. Karn, Travis Scrimshaw (July 2022): Initial implementation """ #***************************************************************************** -# Copyright (C) 2022 Trevor Karn +# Copyright (C) 2022 Trevor K. Karn # (C) 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify @@ -53,7 +53,8 @@ cdef class GroebnerStrategy: self.ideal = I self.groebner_basis = (None,) self.E = I.ring() - self.homogeneous = all(x.is_super_homogeneous() for x in I.gens()) + self.homogeneous = I._homogeneous + self.rank = Integer(self.E.ngens()) if self.homogeneous or I.side() == "left": self.side = 0 elif I.side() == "right": @@ -120,25 +121,24 @@ cdef class GroebnerStrategy: Perform the preprocessing step. """ cdef CliffordAlgebraElement f, g, f0, f1 + cdef set additions cdef set L = set() - if self.side == 0: + if self.side == 1: for f0, f1 in P: if self.build_S_poly(f0, f1): - L.add(self.partial_S_poly_left(f0, f1)) - L.add(self.partial_S_poly_left(f1, f0)) - elif self.side == 1: - for f0, f1 in P: - if self.build_S_poly(f0, f1): - L.add(self.partial_S_poly_right(f0, f1) for f0,f1 in P) - L.add(self.partial_S_poly_right(f1, f0) for f0,f1 in P) - if self.side == 2: + L.add(self.partial_S_poly_right(f0, f1)) + L.add(self.partial_S_poly_right(f1, f0)) + else: # We compute a left Gröbner basis for two-sided ideals for f0, f1 in P: if self.build_S_poly(f0, f1): L.add(self.partial_S_poly_left(f0, f1)) L.add(self.partial_S_poly_left(f1, f0)) - L.add(self.partial_S_poly_right(f0, f1)) - L.add(self.partial_S_poly_right(f1, f0)) + + if self.side == 2 and not self.homogeneous: + # Add in all S-poly times positive degree monomials + additions = set(f * t for t in self.E.basis() for f in L) + L.update(f for f in additions if f) cdef set done = set(self.leading_supp(f) for f in L) cdef set monL = set() @@ -168,7 +168,7 @@ cdef class GroebnerStrategy: cdef set L = self.preprocessing(P, G) cdef Py_ssize_t i from sage.matrix.constructor import matrix - r = 2 ** self.E.ngens() - 1 # r for "rank" or "reverso" + cdef Integer r = Integer(2) ** self.rank - Integer(1) # r for "rank" or "reverso" M = matrix({(i, r - self.bitset_to_int( m)): c for i,f in enumerate(L) for m,c in ( f)._monomial_coefficients.items()}, @@ -186,18 +186,29 @@ cdef class GroebnerStrategy: EXAMPLES:: sage: E. = ExteriorAlgebra(QQ) - sage: I = E.ideal([x*y - x, x*y -1]) + sage: I = E.ideal([x*y - x, x*y - 1], side="left") sage: I.groebner_basis() (1,) - sage: J = E.ideal([x*y - x, 2*x*y - 2]) + sage: J = E.ideal([x*y - x, 2*x*y - 2], side="left") sage: J.groebner_basis() (1,) + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a+b*c]) + sage: I.groebner_basis() + (b*c + a, a*c, a*b, a*d) """ cdef FrozenBitset p0, p1 cdef long deg cdef Py_ssize_t i, j, k + cdef set additions cdef list G = [f for f in self.ideal.gens() if f] # Remove 0s + if self.side == 2 and not self.homogeneous: + # Add in all S-poly times positive degree monomials + additions = set(f * t for t in self.E.basis() for f in G) + G.extend(f for f in additions if f) + cdef Py_ssize_t n = len(G) cdef dict P = {} cdef list Gp @@ -401,13 +412,6 @@ cdef class GroebnerStrategyDegRevLex(GroebnerStrategy): """ Gröbner basis strategy implementing degree revlex ordering. """ - def __init__(self, I): - """ - Initialize ``self``. - """ - GroebnerStrategy.__init__(self, I) - self.rank = Integer(self.E.ngens()) - cdef inline Integer bitset_to_int(self, FrozenBitset X): """ Convert ``X`` to an :class:`Integer`. @@ -451,13 +455,6 @@ cdef class GroebnerStrategyDegLex(GroebnerStrategy): """ Gröbner basis strategy implementing degree lex ordering. """ - def __init__(self, I): - """ - Initialize ``self``. - """ - GroebnerStrategy.__init__(self, I) - self.rank = Integer(self.E.ngens()) - cdef inline Integer bitset_to_int(self, FrozenBitset X): """ Convert ``X`` to an :class:`Integer`.