Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
Fixing containment and reduction of exterior algebra ideals.
Browse files Browse the repository at this point in the history
  • Loading branch information
tscrim committed Aug 2, 2022
1 parent 47281fa commit 692f2f5
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 63 deletions.
107 changes: 78 additions & 29 deletions src/sage/algebras/clifford_algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
<class 'sage.data_structures.bitset.FrozenBitset'>
"""
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)
Expand Down Expand Up @@ -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.<y, x> = 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)
Expand All @@ -2620,6 +2633,11 @@ def reduce(self, f):
x + y
sage: I.reduce(x*y + x*y*z)
0
sage: E.<a,b,c,d> = 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()
Expand Down Expand Up @@ -2680,6 +2698,31 @@ def __richcmp__(self, other, op):
True
sage: I <= E.ideal([x])
False
sage: E.<a,b,c,d> = 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:
Expand All @@ -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"""
Expand Down
2 changes: 1 addition & 1 deletion src/sage/algebras/clifford_algebra_element.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ AUTHORS:
"""

#*****************************************************************************
# Copyright (C) 2022 Trevor Karn <karnx018 at umn.edu>
# Copyright (C) 2022 Trevor K. Karn <karnx018 at umn.edu>
# (C) 2022 Travis Scrimshaw <tcscrims at gmail.com>
#
# This program is free software: you can redistribute it and/or modify
Expand Down
5 changes: 3 additions & 2 deletions src/sage/algebras/exterior_algebra_groebner.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

59 changes: 28 additions & 31 deletions src/sage/algebras/exterior_algebra_groebner.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <karnx018 at umn.edu>
# Copyright (C) 2022 Trevor K. Karn <karnx018 at umn.edu>
# (C) 2022 Travis Scrimshaw <tcscrims at gmail.com>
#
# This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -53,7 +53,8 @@ cdef class GroebnerStrategy:
self.ideal = I
self.groebner_basis = (None,)
self.E = <Parent> 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":
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(<FrozenBitset> m)): c
for i,f in enumerate(L)
for m,c in (<CliffordAlgebraElement> f)._monomial_coefficients.items()},
Expand All @@ -186,18 +186,29 @@ cdef class GroebnerStrategy:
EXAMPLES::
sage: E.<y, x> = 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.<a,b,c,d> = 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
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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`.
Expand Down

0 comments on commit 692f2f5

Please sign in to comment.