From 752cf3a926ab0b654559314ff74672c02e17b76a Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 25 Jun 2022 11:23:32 -0500 Subject: [PATCH 01/29] Fix comparison with .one_basis() --- src/sage/modules/with_basis/indexed_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 834d92f96d9..76026892820 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -333,7 +333,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): elif coeff == "-1": coeff = "-" elif b._l > 0: - if len(coeff) > 0 and monomial == 1 and strip_one: + if len(coeff) > 0 and monomial == self.parent().one_basis() and strip_one: b = empty_ascii_art # "" else: b = AsciiArt([scalar_mult]) + b From 7cf899081ffe597fae73ead06995a5d028c9837c Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 25 Jul 2022 10:07:45 -0500 Subject: [PATCH 02/29] Fix to work with algebras without a one_basis --- src/sage/modules/with_basis/indexed_element.pyx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 76026892820..9720b9a31fa 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -306,6 +306,13 @@ cdef class IndexedFreeModuleElement(ModuleElement): * * sage: ascii_art(M.zero()) 0 + sage: DA = DescentAlgebra(QQ, 4) + sage: ascii_art(DA.an_element()) + 2*B + 2*B + 3*B + * ** * + * * ** + * * * + * """ from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() @@ -322,6 +329,11 @@ cdef class IndexedFreeModuleElement(ModuleElement): if scalar_mult is None: scalar_mult = "*" + try: + one_basis = self.parent().one_basis() + except AttributeError: + one_basis = None + for (monomial,c) in terms: b = repr_monomial(monomial) # PCR if c != 0: @@ -333,7 +345,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): elif coeff == "-1": coeff = "-" elif b._l > 0: - if len(coeff) > 0 and monomial == self.parent().one_basis() and strip_one: + if len(coeff) > 0 and monomial == one_basis and strip_one: b = empty_ascii_art # "" else: b = AsciiArt([scalar_mult]) + b From dc8370dfff5f084535911bfcc132a5c310b8243b Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 25 Jul 2022 10:19:00 -0500 Subject: [PATCH 03/29] Change one in _unicode_art_ --- src/sage/modules/with_basis/indexed_element.pyx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 9720b9a31fa..6b861a42da1 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -403,6 +403,11 @@ cdef class IndexedFreeModuleElement(ModuleElement): if scalar_mult is None: scalar_mult = "*" + try: + one_basis = self.parent().one_basis() + except AttributeError: + one_basis = None + for (monomial, c) in terms: b = repr_monomial(monomial) # PCR if c != 0: @@ -414,7 +419,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): elif coeff == "-1": coeff = "-" elif b._l > 0: - if len(coeff) > 0 and monomial == 1 and strip_one: + if len(coeff) > 0 and monomial == one_basis and strip_one: b = empty_unicode_art # "" else: b = UnicodeArt([scalar_mult]) + b From 0e17b69bb598a0ca5ebb053de142f97559f53dd2 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 20 Jun 2022 12:03:26 -0500 Subject: [PATCH 04/29] Initial commit/minimal change --- src/sage/algebras/clifford_algebra.py | 9 ++++++++- src/sage/data_structures/bitset.pyx | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 0c583b04f6c..b5111e15ee1 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -19,6 +19,7 @@ from sage.misc.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation +from sage.data_structures.bitset import FrozenBitset from copy import copy from sage.categories.algebras_with_basis import AlgebrasWithBasis @@ -493,7 +494,13 @@ def __init__(self, Q, names, category=None): self._quadratic_form = Q R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) - indices = SubsetsSorted(range(Q.dim())) + format_style = f"0{Q.dim()}b" + from functools import partial + # use a slice to reverse string order because Bitset and format(x, 'b') use different conventions + def index_function(x): + return FrozenBitset(format(x, format_style)[::-1], + capacity=Q.dim()) + indices = Family(range(2**Q.dim()), partial(index_function), lazy=True) CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) diff --git a/src/sage/data_structures/bitset.pyx b/src/sage/data_structures/bitset.pyx index 29bbeeb0d1c..a09387c139a 100644 --- a/src/sage/data_structures/bitset.pyx +++ b/src/sage/data_structures/bitset.pyx @@ -390,6 +390,7 @@ cdef class FrozenBitset: else: # an iterable iter = list(iter) if iter: + print(iter) need_capacity = max(iter) + 1 else: need_capacity = 0 From 212d238ed750dcbce8a232e9fc755c7e9b2fc06a Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 20 Jun 2022 15:41:01 -0500 Subject: [PATCH 05/29] Fix .one_basis() --- src/sage/algebras/clifford_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index b5111e15ee1..fd83ae8fa9d 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -760,7 +760,7 @@ def one_basis(self): sage: Cl.one_basis() () """ - return () + return FrozenBitset('0') def is_commutative(self): """ From 11853bc23d3bb89b6a59a4d5eb0e18141019595b Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 20 Jun 2022 15:54:30 -0500 Subject: [PATCH 06/29] Remove accidental print statement --- src/sage/data_structures/bitset.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/data_structures/bitset.pyx b/src/sage/data_structures/bitset.pyx index a09387c139a..29bbeeb0d1c 100644 --- a/src/sage/data_structures/bitset.pyx +++ b/src/sage/data_structures/bitset.pyx @@ -390,7 +390,6 @@ cdef class FrozenBitset: else: # an iterable iter = list(iter) if iter: - print(iter) need_capacity = max(iter) + 1 else: need_capacity = 0 From a7937836817834d51f2ed0004bf6d02c8a0192c7 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 21 Jun 2022 12:31:12 -0500 Subject: [PATCH 07/29] Add _basis_index_function as a method, fix some doctests, and fix some type issues (tuple -> FrozenBitset) --- src/sage/algebras/clifford_algebra.py | 73 ++++++++++++++++++++------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index fd83ae8fa9d..ba222c16cdc 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -19,7 +19,7 @@ from sage.misc.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation -from sage.data_structures.bitset import FrozenBitset +from sage.data_structures.bitset import Bitset, FrozenBitset from copy import copy from sage.categories.algebras_with_basis import AlgebrasWithBasis @@ -178,9 +178,9 @@ def list(self): sage: Cl. = CliffordAlgebra(Q) sage: elt = 5*x + y sage: elt.list() - [((0,), 5), ((1,), 1)] + [(1, 5), (01, 1)] """ - return sorted(self._monomial_coefficients.items(), key=lambda m_c : (-len(m_c[0]), m_c[0])) + return sorted(self._monomial_coefficients.items(), key=lambda m_c : (-len(m_c[0]), m_c)) def support(self): """ @@ -195,7 +195,7 @@ def support(self): sage: Cl. = CliffordAlgebra(Q) sage: elt = 5*x + y sage: elt.support() - [(0,), (1,)] + [1, 01] """ return sorted(self._monomial_coefficients.keys(), key=lambda x: (-len(x), x)) @@ -494,13 +494,8 @@ def __init__(self, Q, names, category=None): self._quadratic_form = Q R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) - format_style = f"0{Q.dim()}b" from functools import partial - # use a slice to reverse string order because Bitset and format(x, 'b') use different conventions - def index_function(x): - return FrozenBitset(format(x, format_style)[::-1], - capacity=Q.dim()) - indices = Family(range(2**Q.dim()), partial(index_function), lazy=True) + indices = Family(range(2**Q.dim()), partial(self._basis_index_function), lazy=True) CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) @@ -681,15 +676,57 @@ def _element_constructor_(self, x): if x in self.free_module(): R = self.base_ring() if x.parent().base_ring() is R: - return self.element_class(self, {(i,): c for i,c in x.items()}) - return self.element_class(self, {(i,): R(c) for i,c in x.items() if R(c) != R.zero()}) + return self.element_class(self, {FrozenBitset((i,)): c for i,c in x.items()}) + # if the base ring is different, attempt to coerce it into R + return self.element_class(self, {FrozenBitset((i,)): R(c) for i,c in x.items() if R(c) != R.zero()}) if (isinstance(x, CliffordAlgebraElement) and self.has_coerce_map_from(x.parent())): R = self.base_ring() return self.element_class(self, {i: R(c) for i,c in x if R(c) != R.zero()}) - return super()._element_constructor_(x) + if isinstance(x, tuple): + R = self.base_ring() + return self.element_class(self, {FrozenBitset((i,)): R.one() for i in x}) + + return super(CliffordAlgebra, self)._element_constructor_(x) + + def _basis_index_function(self, x): + """ + Given an integer indexing the basis, return the correct + bitset. + + For backwards compatibility, tuples are also accepted. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl = CliffordAlgebra(Q) + sage: Cl._basis_index_function(7) + 111 + sage: Cl._basis_index_function(5) + 101 + sage: Cl._basis_index_function(4) + 001 + + sage: Cl._basis_index_function((0, 1, 2)) + 111 + sage: Cl._basis_index_function((0, 2)) + 101 + sage: Cl._basis_index_function((2,)) + 001 + """ + Q = self._quadratic_form + format_style = f"0{Q.dim()}b" + + # if the input is a tuple, assume that it has + # entries in {0, ..., 2**Q.dim()-1} + if isinstance(x, tuple): + return FrozenBitset(x, capacity = Q.dim()) + + # slice the output of format in order to make conventions + # of format and FrozenBitset agree. + return FrozenBitset(format(x, format_style)[::-1], capacity=Q.dim()) def gen(self, i): """ @@ -706,7 +743,7 @@ def gen(self, i): sage: [Cl.gen(i) for i in range(3)] [x, y, z] """ - return self._from_dict({(i,): self.base_ring().one()}, remove_zeros=False) + return self._from_dict({FrozenBitset((i,)): self.base_ring().one()}, remove_zeros=False) def algebra_generators(self): """ @@ -751,14 +788,16 @@ def ngens(self): @cached_method def one_basis(self): """ - Return the basis index of the element `1`. + Return the basis index of the element ``1``. The element ``1`` + is indexed by the emptyset, which is represented by the + :class:`sage.data_structures.bitset.Bitset` ``0``. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl. = CliffordAlgebra(Q) sage: Cl.one_basis() - () + 0 """ return FrozenBitset('0') @@ -1032,7 +1071,7 @@ def lift_module_morphism(self, m, names=None): Cl = CliffordAlgebra(Q, names) n = self._quadratic_form.dim() - f = lambda x: self.prod(self._from_dict( {(j,): m[j,i] for j in range(n)}, + f = lambda x: self.prod(self._from_dict( {FrozenBitset((j,)): m[j,i] for j in range(n)}, remove_zeros=True ) for i in x) cat = AlgebrasWithBasis(self.category().base_ring()).Super().FiniteDimensional() From 7803b032af3e5539dace7b73bd73331886301cdf Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 21 Jun 2022 13:21:11 -0500 Subject: [PATCH 08/29] Fix multiplication --- src/sage/algebras/clifford_algebra.py | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index ba222c16cdc..d3eda87f071 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -124,15 +124,16 @@ def _mul_(self, other): # ``e[i]`` * (the element described by the dictionary ``cur``) # (where ``e[i]`` is the ``i``-th standard basis vector). for mr,cr in cur.items(): + # Commute the factor as necessary until we are in order - pos = 0 for j in mr: if i <= j: break # Add the additional term from the commutation - t = list(mr) - t.pop(pos) - t = tuple(t) + # get a non-frozen bitset to manipulate + t = Bitset(mr) # a mutable copy + t.discard(j) + t = FrozenBitset(t) next[t] = next.get(t, zero) + cr * Q[i,j] # Note: ``Q[i,j] == Q(e[i]+e[j]) - Q(e[i]) - Q(e[j])`` for # ``i != j``, where ``e[k]`` is the ``k``-th standard @@ -140,22 +141,21 @@ def _mul_(self, other): cr = -cr if next[t] == zero: del next[t] - pos += 1 # Check to see if we have a squared term or not - t = list(mr) - if i in t: - t.remove(i) + mr = Bitset(mr) # temporarily mutable + if i in mr: + mr.discard(i) cr *= Q[i,i] # Note: ``Q[i,i] == Q(e[i])`` where ``e[i]`` is the # ``i``-th standard basis vector. else: - t.insert(pos, i) - # Note that ``t`` is now sorted. - t = tuple(t) - next[t] = next.get(t, zero) + cr - if next[t] == zero: - del next[t] + # mr is implicitly sorted + mr.add(i) + mr = FrozenBitset(mr) # refreeze it + next[mr] = next.get(mr, zero) + cr + if next[mr] == zero: + del next[mr] cur = next # Add the distributed terms to the total From d353cc8fd3907984f028d0090346058dd549a851 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 21 Jun 2022 14:04:39 -0500 Subject: [PATCH 09/29] Fix repr sorting issue and add some small tests --- src/sage/algebras/clifford_algebra.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index d3eda87f071..470b9bc4847 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -180,7 +180,7 @@ def list(self): sage: elt.list() [(1, 5), (01, 1)] """ - return sorted(self._monomial_coefficients.items(), key=lambda m_c : (-len(m_c[0]), m_c)) + return sorted(self._monomial_coefficients.items(), key=lambda m : (-len(m[0]), list(m[0]))) def support(self): """ @@ -197,7 +197,7 @@ def support(self): sage: elt.support() [1, 01] """ - return sorted(self._monomial_coefficients.keys(), key=lambda x: (-len(x), x)) + return sorted(self._monomial_coefficients.keys(), key=lambda x: (-len(x), list(x))) def reflection(self): r""" @@ -487,8 +487,7 @@ def __init__(self, Q, names, category=None): sage: Q = QuadraticForm(ZZ, 9) sage: Cl = CliffordAlgebra(Q) sage: ba = Cl.basis().keys() - sage: all( tuple(sorted(S)) in ba - ....: for S in Subsets(range(9)) ) + sage: all(FrozenBitset(format(i,'b')[::-1]) in ba for i in range(2**9)) True """ self._quadratic_form = Q @@ -525,6 +524,8 @@ def _repr_term(self, m): sage: Cl. = CliffordAlgebra(Q) sage: Cl._repr_term((0,2)) 'x*z' + sage: Cl._repr_term(FrozenBitset('101')) + 'x*z' sage: Cl._repr_term(()) '1' sage: Cl._repr_term((1,)) From bb0cdd748388cea18e92e27879b8eff5800b64f5 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 21 Jun 2022 14:51:32 -0500 Subject: [PATCH 10/29] First draft of basis iterator --- src/sage/algebras/clifford_algebra.py | 56 +++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 470b9bc4847..f11237fa564 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -32,7 +32,7 @@ from sage.matrix.args import MatrixArgs from sage.sets.family import Family from sage.combinat.free_module import CombinatorialFreeModule -from sage.combinat.subset import SubsetsSorted +from sage.combinat.subset import Subsets from sage.quadratic_forms.quadratic_form import QuadraticForm from sage.algebras.weyl_algebra import repr_from_monomials from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass @@ -657,7 +657,7 @@ def _element_constructor_(self, x): sage: Cl(2/3) Traceback (most recent call last): ... - TypeError: do not know how to make x (= 2/3) an element of self ... + TypeError: do not know how to make x=2/3 an element of self sage: Clp(2/3) 2/3 sage: Clp(x) @@ -690,7 +690,57 @@ def _element_constructor_(self, x): R = self.base_ring() return self.element_class(self, {FrozenBitset((i,)): R.one() for i in x}) - return super(CliffordAlgebra, self)._element_constructor_(x) + try: + return super(CliffordAlgebra, self)._element_constructor_(x) + except TypeError: + raise TypeError(f'do not know how to make {x=} an element of self') + + def _basis_index_keys(self): + r""" + This gives the same values as range(2**Q.dim()), + but starting with elements that have 1 set bit, + then 2, then three, etc. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl = CliffordAlgebra(Q) + + Notice that the monomial order is strictly lexicographic, + not degree-lexicographic when we use `range`. + + sage: for i in range(2**Q.dim()): print(Cl.basis()[i]) + 1 + x + y + x*y + z + x*z + y*z + x*y*z + + Notice that ``x*y`` comes before ``z`` if we use ``range``. + This isn't mathematically *wrong* but is not usually what + we want. On the other hand, using this method gives a + degree-respecting monomial order:: + + sage: for i in Cl.basis(): print(b) # indirect doctest + 1 + x + y + z + x*y + x*z + y*z + x*y*z + """ + n = self._quadratic_form.dim() + + for s in Subsets(range(n)): # it is ok here because this is only called when iterating over the bases, not in hash etc. + if not s: + yield 0 + continue + yield FrozenBitset(s).__hash__() def _basis_index_function(self, x): """ From 8c06eeda187859aade60a01ce1fd7bd7a385d712 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 21 Jun 2022 18:26:11 -0500 Subject: [PATCH 11/29] Second attempt at iterating --- src/sage/algebras/clifford_algebra.py | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index f11237fa564..4188f97ed8a 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -494,7 +494,10 @@ def __init__(self, Q, names, category=None): R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) from functools import partial - indices = Family(range(2**Q.dim()), partial(self._basis_index_function), lazy=True) + indices = Family(self._basis_index_keys(self), #make basis_index_keys a class so I can give it a len? + partial(self._basis_index_function), + lazy=True, + name = 'Bitsets') CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) @@ -695,7 +698,7 @@ def _element_constructor_(self, x): except TypeError: raise TypeError(f'do not know how to make {x=} an element of self') - def _basis_index_keys(self): + class _basis_index_keys: r""" This gives the same values as range(2**Q.dim()), but starting with elements that have 1 set bit, @@ -734,13 +737,26 @@ def _basis_index_keys(self): y*z x*y*z """ - n = self._quadratic_form.dim() - for s in Subsets(range(n)): # it is ok here because this is only called when iterating over the bases, not in hash etc. - if not s: - yield 0 - continue - yield FrozenBitset(s).__hash__() + def __init__(self, Cl): + self._Cl = Cl + self._quadratic_form = Cl._quadratic_form + + def __repr__(self): # dunder b/c not a parent subclass? + return f"Subsets of {{1,2,...,{self._quadratic_form.dim()}}}" + + def __len__(self): + return 2**self._quadratic_form.dim() + + def __iter__(self): + import itertools + n = self._quadratic_form.dim() + yield 0 + k = 1 + while k <= n: + for C in itertools.combinations(range(n),k): + yield sum(1 << i for i in C) + k += 1 def _basis_index_function(self, x): """ From 67a7b67ecd22359917d791e466c280a0ccb69028 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Thu, 23 Jun 2022 15:39:06 -0500 Subject: [PATCH 12/29] Add index class and first draft of exterior __mul__ --- src/sage/algebras/clifford_algebra.py | 183 ++++++++++---------------- 1 file changed, 66 insertions(+), 117 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 4188f97ed8a..f9f78e91bf7 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -19,11 +19,13 @@ from sage.misc.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent from sage.data_structures.bitset import Bitset, FrozenBitset from copy import copy from sage.categories.algebras_with_basis import AlgebrasWithBasis from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.modules.with_basis.morphism import ModuleMorphismByLinearity from sage.categories.poor_man_map import PoorManMap from sage.rings.integer_ring import ZZ @@ -320,6 +322,60 @@ def conjugate(self): clifford_conjugate = conjugate +class CliffordAlgebraIndices(Parent): + r""" + A facade parent for the Clifford algebra + """ + def __call__(self, el): + if not isinstance(el, Element): + return self._element_constructor_(el) + else: + return Parent.__call__(self, el) + + def __init__(self, Qdim): + self._nbits = Qdim + self._cardinality = 2**Qdim + # the if statement here is in case Qdim is 0. + self._maximal_set = FrozenBitset('1'*self._nbits) if self._nbits else FrozenBitset('0') + category = FiniteEnumeratedSets().Facade() + Parent.__init__(self, category=category, facade=True) + + def __element_constructor__(self, x): + + if isinstance(x, (list, tuple, set, frozenset)): + if len(x) > self._nbits: + raise ValueError(f"{x=} is too long") + return FrozenBitset(x) + + if isinstance(x, int): + return FrozenBitset((x,)) + + def cardinality(self): + + return self._cardinality + + def _repr_(self): + return f"Subsets of {{1,2,...,{self._quadratic_form.dim()}}}" + + def __len__(self): + return self._cardinality + + def __iter__(self): + import itertools + n = self._nbits + yield FrozenBitset('0') + k = 1 + while k <= n: + for C in itertools.combinations(range(n),k): + yield FrozenBitset(C) + k += 1 + + def __contains__(self, other): + + if isinstance(other, int): + return (other < self._cardinality) and (other >= 0) + return self._maximal_set.issuperset(other) + class CliffordAlgebra(CombinatorialFreeModule): r""" The Clifford algebra of a quadratic form. @@ -493,11 +549,7 @@ def __init__(self, Q, names, category=None): self._quadratic_form = Q R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) - from functools import partial - indices = Family(self._basis_index_keys(self), #make basis_index_keys a class so I can give it a len? - partial(self._basis_index_function), - lazy=True, - name = 'Bitsets') + indices = CliffordAlgebraIndices(Q.dim()) CombinatorialFreeModule.__init__(self, R, indices, category=category) self._assign_names(names) @@ -698,103 +750,6 @@ def _element_constructor_(self, x): except TypeError: raise TypeError(f'do not know how to make {x=} an element of self') - class _basis_index_keys: - r""" - This gives the same values as range(2**Q.dim()), - but starting with elements that have 1 set bit, - then 2, then three, etc. - - EXAMPLES:: - - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl = CliffordAlgebra(Q) - - Notice that the monomial order is strictly lexicographic, - not degree-lexicographic when we use `range`. - - sage: for i in range(2**Q.dim()): print(Cl.basis()[i]) - 1 - x - y - x*y - z - x*z - y*z - x*y*z - - Notice that ``x*y`` comes before ``z`` if we use ``range``. - This isn't mathematically *wrong* but is not usually what - we want. On the other hand, using this method gives a - degree-respecting monomial order:: - - sage: for i in Cl.basis(): print(b) # indirect doctest - 1 - x - y - z - x*y - x*z - y*z - x*y*z - """ - - def __init__(self, Cl): - self._Cl = Cl - self._quadratic_form = Cl._quadratic_form - - def __repr__(self): # dunder b/c not a parent subclass? - return f"Subsets of {{1,2,...,{self._quadratic_form.dim()}}}" - - def __len__(self): - return 2**self._quadratic_form.dim() - - def __iter__(self): - import itertools - n = self._quadratic_form.dim() - yield 0 - k = 1 - while k <= n: - for C in itertools.combinations(range(n),k): - yield sum(1 << i for i in C) - k += 1 - - def _basis_index_function(self, x): - """ - Given an integer indexing the basis, return the correct - bitset. - - For backwards compatibility, tuples are also accepted. - - EXAMPLES:: - - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl = CliffordAlgebra(Q) - sage: Cl._basis_index_function(7) - 111 - sage: Cl._basis_index_function(5) - 101 - sage: Cl._basis_index_function(4) - 001 - - sage: Cl._basis_index_function((0, 1, 2)) - 111 - sage: Cl._basis_index_function((0, 2)) - 101 - sage: Cl._basis_index_function((2,)) - 001 - """ - Q = self._quadratic_form - format_style = f"0{Q.dim()}b" - - # if the input is a tuple, assume that it has - # entries in {0, ..., 2**Q.dim()-1} - if isinstance(x, tuple): - return FrozenBitset(x, capacity = Q.dim()) - - # slice the output of format in order to make conventions - # of format and FrozenBitset agree. - return FrozenBitset(format(x, format_style)[::-1], capacity=Q.dim()) - def gen(self, i): """ Return the ``i``-th standard generator of the algebra ``self``. @@ -1225,7 +1180,7 @@ def lift_isometry(self, m, names=None): Cl = CliffordAlgebra(Q, names) n = Q.dim() - f = lambda x: Cl.prod(Cl._from_dict( {(j,): m[j,i] for j in range(n)}, + f = lambda x: Cl.prod(Cl._from_dict( {FrozenBitset((j,)): m[j,i] for j in range(n)}, remove_zeros=True ) for i in x) cat = AlgebrasWithBasis(self.category().base_ring()).Super().FiniteDimensional() @@ -2075,26 +2030,20 @@ def _mul_(self, other): for ml,cl in self: for mr,cr in other: # Create the next term - t = list(mr) + t = Bitset(mr) + + if ml.intersection(mr): + # if they intersect nontrivially, move along. + continue + for i in reversed(ml): - pos = 0 for j in t: - if i == j: - pos = None - break if i < j: break - pos += 1 cr = -cr - if pos is None: - t = None - break - t.insert(pos, i) - - if t is None: # The next term is 0, move along - continue + t.add(i) - t = tuple(t) + t = FrozenBitset(t) d[t] = d.get(t, zero) + cl * cr if d[t] == zero: del d[t] From c50c471ee5efe684ecd8d1190bc989230f7ffa5e Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 24 Jun 2022 09:11:13 -0500 Subject: [PATCH 13/29] Fix some bugs and rewrite exterior multplication algorithm --- src/sage/algebras/clifford_algebra.py | 53 ++++++++++++++++++--------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index f9f78e91bf7..abe46b6313c 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -355,7 +355,7 @@ def cardinality(self): return self._cardinality def _repr_(self): - return f"Subsets of {{1,2,...,{self._quadratic_form.dim()}}}" + return f"Subsets of {{1,2,...,{self._nbits}}}" def __len__(self): return self._cardinality @@ -1648,7 +1648,7 @@ def lift_morphism(self, phi, names=None): n = phi.nrows() R = self.base_ring() E = ExteriorAlgebra(R, names, n) - f = lambda x: E.prod(E._from_dict( {(j,): phi[j,i] for j in range(n)}, + f = lambda x: E.prod(E._from_dict( {FrozenBitset((j,)): phi[j,i] for j in range(n)}, remove_zeros=True ) for i in x) cat = AlgebrasWithBasis(R).Super().FiniteDimensional() @@ -2024,26 +2024,35 @@ def _mul_(self, other): sage: (x+y) * (y+z) x*y + x*z + y*z """ + n = self.parent().ngens() zero = self.parent().base_ring().zero() d = {} - for ml,cl in self: - for mr,cr in other: - # Create the next term - t = Bitset(mr) - + for ml,cl in self: # ml for "monomial on the left" + for mr,cr in other: # mr for "monomial on the right" if ml.intersection(mr): # if they intersect nontrivially, move along. continue - for i in reversed(ml): - for j in t: - if i < j: - break + if not mr: + t = ml + else: + t = ml.union(mr) + it = iter(mr) + j = next(it) + + num_cross = 0 # keep track of the number of signs + for i in ml: + while i > j: + num_cross += 1 + try: + j = next(it) + except StopIteration: + break + + if num_cross % 2: cr = -cr - t.add(i) - t = FrozenBitset(t) d[t] = d.get(t, zero) + cl * cr if d[t] == zero: del d[t] @@ -2480,7 +2489,7 @@ def _on_basis(self, m): sage: E. = ExteriorAlgebra(QQ) sage: par = E.boundary({(0,1): z, (1,2): x, (2,0): y}) - sage: par._on_basis(()) + sage: par._on_basis(FrozenBitset('0')) 0 sage: par._on_basis((0,)) 0 @@ -2491,12 +2500,22 @@ def _on_basis(self, m): sage: par._on_basis((0,1,2)) 0 """ + from itertools import combinations E = self.domain() sc = self._s_coeff keys = sc.keys() - return E.sum((-1)**b * sc[(i,j)] - * E.monomial(m[:a] + m[a+1:a+b+1] + m[a+b+2:]) - for a,i in enumerate(m) for b,j in enumerate(m[a+1:]) if (i,j) in keys) + + s = E.base_ring().zero() + + for b, (i,j) in enumerate(combinations(m, 2)): + t = Bitset(m) + if (i,j) not in keys: + continue + t.discard(i) + t.discard(j) + s += (-1)**b * sc[(i,j)] * E.monomial(FrozenBitset(t)) + + return s @cached_method def chain_complex(self, R=None): From dcd2c92e76883e97bea83a5572d102588cedf26f Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 25 Jun 2022 10:29:03 -0500 Subject: [PATCH 14/29] Coboundary and boundary fixes --- src/sage/algebras/clifford_algebra.py | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index abe46b6313c..380e2120c43 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2023,8 +2023,13 @@ def _mul_(self, other): 0 sage: (x+y) * (y+z) x*y + x*z + y*z + + sage: E. = ExteriorAlgebra(QQ) + sage: (x * y) * (w * z) + -x*y*z*w + sage: x*y*w*z + -x*y*z*w """ - n = self.parent().ngens() zero = self.parent().base_ring().zero() d = {} @@ -2592,7 +2597,10 @@ def chain_complex(self, R=None): mat = [] for b in basis: ret = self._on_basis(b) - mat.append([ret[p] for p in prev_basis]) + try: + mat.append([ret.coefficient(p) for p in prev_basis]) + except AttributeError: # if ret is in E.base_ring() + mat.append([E.base_ring()(ret)]) data[deg] = Matrix(mat).transpose().change_ring(R) prev_basis = basis @@ -2731,7 +2739,7 @@ def __init__(self, E, s_coeff): zero = E.zero() B = E.basis() for k, v in dict(s_coeff).items(): - k = B[k] + k = B[FrozenBitset(k)] for m,c in v: self._cos_coeff[m] = self._cos_coeff.get(m, zero) + c * k ExteriorAlgebraDifferential.__init__(self, E, s_coeff) @@ -2776,8 +2784,23 @@ def _on_basis(self, m): E = self.domain() cc = self._cos_coeff keys = cc.keys() - return E.sum((-1)**a * E.monomial(m[:a]) * cc[(i,)] * E.monomial(m[a+1:]) - for a,i in enumerate(m) if (i,) in keys) + + s = E.base_ring().zero() + + sgn = 0 + + for i in m: + sgn += 1 + if FrozenBitset((i,)) in keys: + key_basis, key_coeff = cc[FrozenBitset((i,))].list()[0] + m_temp = Bitset(m) + if key_basis.intersection(m_temp): + continue + m_temp.discard(i) + m_temp.update(key_basis) + + s = s + (-1)**(sgn % 2) * key_coeff * E.monomial(FrozenBitset(m_temp)) + return s @cached_method def chain_complex(self, R=None): @@ -2854,7 +2877,10 @@ def chain_complex(self, R=None): mat = [] for b in basis: ret = self._on_basis(b) - mat.append([ret[p] for p in next_basis]) + try: + mat.append([ret.coefficient(p) for p in next_basis]) + except AttributeError: # if ret is in E.base_ring() + mat.append([E.base_ring()(ret)]) data[deg] = Matrix(mat).transpose().change_ring(R) basis = next_basis From 9030b03e2e2de680a7b68f4b6e35a936949d24df Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 25 Jun 2022 10:38:10 -0500 Subject: [PATCH 15/29] Fix sum initialization --- src/sage/algebras/clifford_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 380e2120c43..9792bd1005b 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2510,7 +2510,7 @@ def _on_basis(self, m): sc = self._s_coeff keys = sc.keys() - s = E.base_ring().zero() + s = E.zero() for b, (i,j) in enumerate(combinations(m, 2)): t = Bitset(m) From eae2294457a6e2efea29a66ba52a5ca26969a9bd Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 25 Jun 2022 11:17:54 -0500 Subject: [PATCH 16/29] Fix _element_constructor_ and asscii_art --- src/sage/algebras/clifford_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 9792bd1005b..fb1f9308119 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -340,7 +340,7 @@ def __init__(self, Qdim): category = FiniteEnumeratedSets().Facade() Parent.__init__(self, category=category, facade=True) - def __element_constructor__(self, x): + def _element_constructor_(self, x): if isinstance(x, (list, tuple, set, frozenset)): if len(x) > self._nbits: @@ -1504,7 +1504,7 @@ def _ascii_art_term(self, m): if len(m) == 0: return ascii_art('1') wedge = '/\\' - return ascii_art(*[self.variable_names()[i] for i in m], sep=wedge) + return ascii_art(*[repr(self.basis()[FrozenBitset((i,))]) for i in m], sep=wedge) def _unicode_art_term(self, m): """ From 48c5d074d3299cc550fc81e6baaca55017a52707 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 25 Jun 2022 12:24:41 -0500 Subject: [PATCH 17/29] Fix __getitem__ when using Bitsets --- src/sage/algebras/clifford_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index fb1f9308119..86b7bff759f 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -1774,7 +1774,7 @@ def coproduct_on_basis(self, a): """ from sage.combinat.combinat import unshuffle_iterator one = self.base_ring().one() - return self.tensor_square().sum_of_terms(unshuffle_iterator(a, one), + return self.tensor_square().sum_of_terms(unshuffle_iterator(tuple(a), one), distinct=True) def antipode_on_basis(self, m): @@ -1982,7 +1982,7 @@ def lifted_form(x, y): m = len(my) if m != n: continue - matrix_list = [M[mx[i], my[j]] + matrix_list = [M[next(iter(mx)), next(iter(my))] for i in range(n) for j in range(n)] MA = MatrixArgs(R, n, matrix_list) From 3d4a66d9e7ace7ab85114306bbd5821032554734 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 25 Jun 2022 20:19:19 -0700 Subject: [PATCH 18/29] Fix sign issue --- src/sage/algebras/clifford_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 86b7bff759f..4b6ac340f5f 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2652,7 +2652,7 @@ class ExteriorAlgebraCoboundary(ExteriorAlgebraDifferential): cross product `\times` of `\RR^3`:: sage: E. = ExteriorAlgebra(QQ) - sage: d = E.coboundary({(0,1): z, (1,2): x, (2,0): y}) + sage: d = E.coboundary({(0,1): z, (1,2): x, (0, 2): -y}) sage: d(x) y*z sage: d(y) @@ -2765,7 +2765,7 @@ def _on_basis(self, m): cross product:: sage: E. = ExteriorAlgebra(QQ) - sage: d = E.coboundary({(0,1): z, (1,2): x, (2,0): y}) + sage: d = E.coboundary({(0,1): z, (1,2): x, (0,2): -y}) sage: d._on_basis(()) 0 sage: d._on_basis((0,)) From afc9948d4584580e06abaf37dd25de31e3726485 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 28 Jun 2022 07:17:44 -0700 Subject: [PATCH 19/29] Fix bug in making codifferential matrices --- src/sage/algebras/clifford_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 4b6ac340f5f..7fac01a2594 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2880,7 +2880,7 @@ def chain_complex(self, R=None): try: mat.append([ret.coefficient(p) for p in next_basis]) except AttributeError: # if ret is in E.base_ring() - mat.append([E.base_ring()(ret)]) + mat.append([E.base_ring()(ret)]*len(next_basis)) data[deg] = Matrix(mat).transpose().change_ring(R) basis = next_basis From 2b75b2ac95f1b05b4a058b5fad4a100574910f08 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sun, 3 Jul 2022 11:57:41 -0700 Subject: [PATCH 20/29] Fix bug with lifted bilinear form, a bug with zeros in module morphisms, and make a check more intuitive --- src/sage/algebras/clifford_algebra.py | 9 ++++++--- src/sage/modules/with_basis/morphism.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 7fac01a2594..39b10b1e17b 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -1982,9 +1982,7 @@ def lifted_form(x, y): m = len(my) if m != n: continue - matrix_list = [M[next(iter(mx)), next(iter(my))] - for i in range(n) - for j in range(n)] + matrix_list = [M[i,j] for i in mx for j in my] MA = MatrixArgs(R, n, matrix_list) del matrix_list result += cx * cy * MA.matrix(False).determinant() @@ -2739,6 +2737,11 @@ def __init__(self, E, s_coeff): zero = E.zero() B = E.basis() for k, v in dict(s_coeff).items(): + + if k[0] > k[1]: #k will have length 2 + k = sorted(k) + v = -v + k = B[FrozenBitset(k)] for m,c in v: self._cos_coeff[m] = self._cos_coeff.get(m, zero) + c * k diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index b8fe98111b2..2efcc4f25e9 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -404,10 +404,10 @@ def __call__(self, *args): if self._is_module_with_basis_over_same_base_ring: return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) - for (index, coeff) in mc.items()) + for (index, coeff) in mc.items() if self._on_basis(index)) else: return sum((coeff * self._on_basis(*(before+(index,)+after)) - for (index, coeff) in mc.items()), self._zero) + for (index, coeff) in mc.items() if self._on_basis(index)), self._zero) # As per the specs of Map, we should in fact implement _call_. # However we currently need to abuse Map.__call__ (which strict From f12b70cf9e9d985b4eb5695038df1b037e2c0aaf Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sun, 3 Jul 2022 13:31:03 -0700 Subject: [PATCH 21/29] Fix sign error --- src/sage/algebras/clifford_algebra.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 39b10b1e17b..5b4ae44c739 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2793,7 +2793,6 @@ def _on_basis(self, m): sgn = 0 for i in m: - sgn += 1 if FrozenBitset((i,)) in keys: key_basis, key_coeff = cc[FrozenBitset((i,))].list()[0] m_temp = Bitset(m) @@ -2803,6 +2802,8 @@ def _on_basis(self, m): m_temp.update(key_basis) s = s + (-1)**(sgn % 2) * key_coeff * E.monomial(FrozenBitset(m_temp)) + + sgn += 1 return s @cached_method From 153ba07797e682d53c86d2cacc190c53c0f9b8e0 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 5 Jul 2022 07:43:35 -0700 Subject: [PATCH 22/29] Fix coboundary on basis --- src/sage/algebras/clifford_algebra.py | 30 +++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 5b4ae44c739..20727291e6d 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2788,23 +2788,27 @@ def _on_basis(self, m): cc = self._cos_coeff keys = cc.keys() - s = E.base_ring().zero() + tot = E.zero() - sgn = 0 + for sgn, i in enumerate(m): + k = FrozenBitset((i,)) + if k in keys: + below = tuple(j for j in m if j < i) + above = tuple(j for j in m if j > i) - for i in m: - if FrozenBitset((i,)) in keys: - key_basis, key_coeff = cc[FrozenBitset((i,))].list()[0] - m_temp = Bitset(m) - if key_basis.intersection(m_temp): - continue - m_temp.discard(i) - m_temp.update(key_basis) + # a hack to deal with empty bitsets + if len(below) == 0: + below = FrozenBitset('0') + else: + below = FrozenBitset(below) + if len(above) == 0: + above = FrozenBitset('0') + else: + above = FrozenBitset(above) - s = s + (-1)**(sgn % 2) * key_coeff * E.monomial(FrozenBitset(m_temp)) + tot = tot + (-1)**sgn * E.monomial(below) * cc[k] * E.monomial(above) - sgn += 1 - return s + return tot @cached_method def chain_complex(self, R=None): From 929f55b8948d4b0f6cd463703fe6d78aea902678 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 5 Jul 2022 07:47:55 -0700 Subject: [PATCH 23/29] Clean up code a bit --- src/sage/algebras/clifford_algebra.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 20727291e6d..0bbc0c2d7dd 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2798,15 +2798,16 @@ def _on_basis(self, m): # a hack to deal with empty bitsets if len(below) == 0: - below = FrozenBitset('0') + below = E.one() else: - below = FrozenBitset(below) + below = E.monomial(FrozenBitset(below)) + if len(above) == 0: - above = FrozenBitset('0') + above = E.one() else: - above = FrozenBitset(above) + above = E.monomial(FrozenBitset(above)) - tot = tot + (-1)**sgn * E.monomial(below) * cc[k] * E.monomial(above) + tot = tot + (-1)**sgn * below * cc[k] * above return tot From e70115aaf15f31ccd8543abbd1ad41a8302b92c0 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 5 Jul 2022 12:28:19 -0700 Subject: [PATCH 24/29] Add doctest --- src/sage/algebras/clifford_algebra.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 0bbc0c2d7dd..071102a84b5 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2025,8 +2025,10 @@ def _mul_(self, other): sage: E. = ExteriorAlgebra(QQ) sage: (x * y) * (w * z) -x*y*z*w - sage: x*y*w*z + sage: x * y * w * z -x*y*z*w + sage: (z * w) * (x * y) + x*y*z*w """ zero = self.parent().base_ring().zero() d = {} From 328897d2a66012ac2c129af39ab4843bd6ef939c Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 8 Jul 2022 09:41:32 -0500 Subject: [PATCH 25/29] All tests pass --- src/sage/algebras/clifford_algebra.py | 38 ++++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 071102a84b5..8e110e7661f 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -550,7 +550,7 @@ def __init__(self, Q, names, category=None): R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) indices = CliffordAlgebraIndices(Q.dim()) - CombinatorialFreeModule.__init__(self, R, indices, category=category) + CombinatorialFreeModule.__init__(self, R, indices, category=category, sorting_key=tuple) self._assign_names(names) def _repr_(self): @@ -794,6 +794,7 @@ def gens(self): """ return tuple(self.algebra_generators()) + @cached_method def ngens(self): """ Return the number of algebra generators of ``self``. @@ -1767,15 +1768,19 @@ def coproduct_on_basis(self, a): sage: E.coproduct_on_basis((0,)) 1 # x + x # 1 sage: E.coproduct_on_basis((0,1)) - 1 # x*y + x # y + x*y # 1 - y # x + 1 # x*y + x # y - y # x + x*y # 1 sage: E.coproduct_on_basis((0,1,2)) - 1 # x*y*z + x # y*z + x*y # z + x*y*z # 1 - - x*z # y - y # x*z + y*z # x + z # x*y + 1 # x*y*z + x # y*z - y # x*z + x*y # z + + z # x*y - x*z # y + y*z # x + x*y*z # 1 + """ from sage.combinat.combinat import unshuffle_iterator one = self.base_ring().one() - return self.tensor_square().sum_of_terms(unshuffle_iterator(tuple(a), one), - distinct=True) + L = unshuffle_iterator(tuple(a),one) + return self.tensor_square()._from_dict( + {tuple(FrozenBitset(e) if e else FrozenBitset('0') for e in t): c for t,c in L if c}, + coerce=False, + remove_zeros=False) def antipode_on_basis(self, m): r""" @@ -2030,8 +2035,10 @@ def _mul_(self, other): sage: (z * w) * (x * y) x*y*z*w """ - zero = self.parent().base_ring().zero() + P = self.parent() + zero = P.base_ring().zero() d = {} + n = P.ngens() for ml,cl in self: # ml for "monomial on the left" for mr,cr in other: # mr for "monomial on the right" @@ -2047,22 +2054,23 @@ def _mul_(self, other): j = next(it) num_cross = 0 # keep track of the number of signs + tot_cross = 0 for i in ml: while i > j: num_cross += 1 try: j = next(it) except StopIteration: - break - - if num_cross % 2: + j = n + 1 + tot_cross += num_cross + if tot_cross % 2: cr = -cr d[t] = d.get(t, zero) + cl * cr if d[t] == zero: del d[t] - return self.__class__(self.parent(), d) + return self.__class__(P, d) def interior_product(self, x): r""" @@ -2270,7 +2278,11 @@ def __classcall__(cls, E, s_coeff): sage: par1 = ExteriorAlgebraDifferential(E, {(0,1): z, (1,2): x, (2,0): y}) sage: par2 = ExteriorAlgebraDifferential(E, {(0,1): z, (1,2): x, (0,2): -y}) sage: par3 = ExteriorAlgebraDifferential(E, {(1,0): {2:-1}, (1,2): {0:1}, (2,0):{1:1}}) - sage: par1 is par2 and par2 is par3 + sage: par1 is par2 + True + sage: par1 is par3 + True + sage: par2 is par3 True sage: par4 = ExteriorAlgebraDifferential(E, {}) @@ -2287,7 +2299,7 @@ def __classcall__(cls, E, s_coeff): if isinstance(v, dict): R = E.base_ring() - v = E._from_dict({(i,): R(c) for i, c in v.items()}) + v = E._from_dict({FrozenBitset((i,)): R(c) for i,c in v.items()}) else: # Make sure v is in ``E`` v = E(v) From 8f34ece11cfd7f4e408d58b758576724719018c8 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 5 Jul 2022 13:26:10 -0700 Subject: [PATCH 26/29] Add doctest --- src/sage/algebras/clifford_algebra.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 8e110e7661f..12458bb1aff 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2056,8 +2056,9 @@ def _mul_(self, other): num_cross = 0 # keep track of the number of signs tot_cross = 0 for i in ml: + num_cross_new = 0 while i > j: - num_cross += 1 + num_cross_new += 1 try: j = next(it) except StopIteration: From 326f72abe9f20272a6c0e8de9316ea9fadc9c986 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 8 Jul 2022 09:50:16 -0500 Subject: [PATCH 27/29] Fix merge error --- src/sage/algebras/clifford_algebra.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 12458bb1aff..8e110e7661f 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2056,9 +2056,8 @@ def _mul_(self, other): num_cross = 0 # keep track of the number of signs tot_cross = 0 for i in ml: - num_cross_new = 0 while i > j: - num_cross_new += 1 + num_cross += 1 try: j = next(it) except StopIteration: From 296edb87aa5d907591921402c5507c5b6021d965 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 22 Jul 2022 19:39:33 -0500 Subject: [PATCH 28/29] Add tests for index class --- src/sage/algebras/clifford_algebra.py | 94 ++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 8e110e7661f..c0310164da5 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -20,6 +20,7 @@ from sage.misc.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent +from sage.structure.element import Element from sage.data_structures.bitset import Bitset, FrozenBitset from copy import copy @@ -324,15 +325,44 @@ def conjugate(self): class CliffordAlgebraIndices(Parent): r""" - A facade parent for the Clifford algebra + A facade parent for the indices of Clifford algebra. + Users should not create instances of this class directly. """ def __call__(self, el): + r""" + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx([1,3,6]) + 0101001 + sage: E = ExteriorAlgebra(QQ, 7) + sage: B = E.basis() + + """ + if not isinstance(el, Element): return self._element_constructor_(el) else: return Parent.__call__(self, el) def __init__(self, Qdim): + r""" + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx._nbits + 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 # the if statement here is in case Qdim is 0. @@ -341,7 +371,22 @@ def __init__(self, Qdim): Parent.__init__(self, category=category, facade=True) def _element_constructor_(self, x): + r""" + EXAMPLES:: + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx([1,3,6]) + 0101001 + sage: for i in range(7): print(idx(i)) + 1 + 01 + 001 + 0001 + 00001 + 000001 + 0000001 + """ if isinstance(x, (list, tuple, set, frozenset)): if len(x) > self._nbits: raise ValueError(f"{x=} is too long") @@ -351,16 +396,53 @@ def _element_constructor_(self, x): return FrozenBitset((x,)) def cardinality(self): + r""" + EXAMPLES:: + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx.cardinality() == 2^7 + True + """ return self._cardinality def _repr_(self): + r""" + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7); idx + Subsets of {1,2,...,7} + """ return f"Subsets of {{1,2,...,{self._nbits}}}" def __len__(self): + r""" + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7); + sage: len(idx) == 2^7 + True + """ return self._cardinality def __iter__(self): + r""" + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(3); + sage: for i in idx: print(i) + 0 + 1 + 01 + 001 + 11 + 101 + 011 + 111 + """ import itertools n = self._nbits yield FrozenBitset('0') @@ -371,6 +453,16 @@ def __iter__(self): k += 1 def __contains__(self, other): + r""" + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(3); + sage: int(8) in idx # representing the set {4} + False + sage: FrozenBitset('1') in idx + True + """ if isinstance(other, int): return (other < self._cardinality) and (other >= 0) From 6dcb98ef550bd94ee0a1df55f74a9dcda65e5be6 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 22 Jul 2022 20:18:35 -0500 Subject: [PATCH 29/29] PEP8 compliance --- src/sage/algebras/clifford_algebra.py | 198 +++++++++++++++++--------- 1 file changed, 130 insertions(+), 68 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index c0310164da5..dfdafc49a06 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -84,7 +84,9 @@ def _latex_(self): sage: latex( (x1 - x2)*x0 + 5*x0*x1*x2 ) 5 x_{0} x_{1} x_{2} - x_{0} x_{1} + x_{0} x_{2} - 1 """ - return repr_from_monomials(self.list(), self.parent()._latex_term, True) + return repr_from_monomials(self.list(), + self.parent()._latex_term, + True) def _mul_(self, other): """ @@ -115,9 +117,9 @@ def _mul_(self, other): zero = self.parent().base_ring().zero() d = {} - for ml,cl in self: + for ml, cl in self: # Distribute the current term ``cl`` * ``ml`` over ``other``. - cur = copy(other._monomial_coefficients) # The current distribution of the term + cur = copy(other._monomial_coefficients) # The current distribution of the term for i in reversed(ml): # Distribute the current factor ``e[i]`` (the ``i``-th # element of the standard basis). @@ -126,7 +128,7 @@ def _mul_(self, other): # the dictionary describing the element # ``e[i]`` * (the element described by the dictionary ``cur``) # (where ``e[i]`` is the ``i``-th standard basis vector). - for mr,cr in cur.items(): + for mr, cr in cur.items(): # Commute the factor as necessary until we are in order for j in mr: @@ -134,10 +136,10 @@ def _mul_(self, other): break # Add the additional term from the commutation # get a non-frozen bitset to manipulate - t = Bitset(mr) # a mutable copy + t = Bitset(mr) # a mutable copy t.discard(j) t = FrozenBitset(t) - next[t] = next.get(t, zero) + cr * Q[i,j] + next[t] = next.get(t, zero) + cr * Q[i, j] # Note: ``Q[i,j] == Q(e[i]+e[j]) - Q(e[i]) - Q(e[j])`` for # ``i != j``, where ``e[k]`` is the ``k``-th standard # basis vector. @@ -146,23 +148,23 @@ def _mul_(self, other): del next[t] # Check to see if we have a squared term or not - mr = Bitset(mr) # temporarily mutable + mr = Bitset(mr) # temporarily mutable if i in mr: mr.discard(i) - cr *= Q[i,i] + cr *= Q[i, i] # Note: ``Q[i,i] == Q(e[i])`` where ``e[i]`` is the # ``i``-th standard basis vector. else: # mr is implicitly sorted mr.add(i) - mr = FrozenBitset(mr) # refreeze it + mr = FrozenBitset(mr) # refreeze it next[mr] = next.get(mr, zero) + cr if next[mr] == zero: del next[mr] cur = next # Add the distributed terms to the total - for index,coeff in cur.items(): + for index, coeff in cur.items(): d[index] = d.get(index, zero) + cl * coeff if d[index] == zero: del d[index] @@ -183,7 +185,7 @@ def list(self): sage: elt.list() [(1, 5), (01, 1)] """ - return sorted(self._monomial_coefficients.items(), key=lambda m : (-len(m[0]), list(m[0]))) + return sorted(self._monomial_coefficients.items(), key=lambda m: (-len(m[0]), list(m[0]))) def support(self): """ @@ -237,7 +239,7 @@ def reflection(self): sage: all(x.reflection().reflection() == x for x in Cl.basis()) True """ - return self.__class__(self.parent(), {m: (-1)**len(m) * c for m,c in self}) + return self.__class__(self.parent(), {m: (-1)**len(m) * c for m, c in self}) degree_negation = reflection @@ -283,7 +285,7 @@ def transpose(self): if not self._monomial_coefficients: return P.zero() g = P.gens() - return P.sum(c * P.prod(g[i] for i in reversed(m)) for m,c in self) + return P.sum(c * P.prod(g[i] for i in reversed(m)) for m, c in self) def conjugate(self): r""" @@ -322,6 +324,65 @@ def conjugate(self): clifford_conjugate = conjugate + # TODO: This is a general function which should be moved to a + # superalgebras category when one is implemented. + def supercommutator(self, x): + r""" + Return the supercommutator of ``self`` and ``x``. + + Let `A` be a superalgebra. The *supercommutator* of homogeneous + elements `x, y \in A` is defined by + + .. MATH:: + + [x, y\} = x y - (-1)^{|x| |y|} y x + + and extended to all elements by linearity. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: a = x*y - z + sage: b = x - y + y*z + sage: a.supercommutator(b) + -5*x*y + 8*x*z - 2*y*z - 6*x + 12*y - 5*z + sage: a.supercommutator(Cl.one()) + 0 + sage: Cl.one().supercommutator(a) + 0 + sage: Cl.zero().supercommutator(a) + 0 + sage: a.supercommutator(Cl.zero()) + 0 + + sage: Q = QuadraticForm(ZZ, 2, [-1,1,-3]) + sage: Cl. = CliffordAlgebra(Q) + sage: [a.supercommutator(b) for a in Cl.basis() for b in Cl.basis()] + [0, 0, 0, 0, 0, -2, 1, -x - 2*y, 0, 1, + -6, 6*x + y, 0, x + 2*y, -6*x - y, 0] + sage: [a*b-b*a for a in Cl.basis() for b in Cl.basis()] + [0, 0, 0, 0, 0, 0, 2*x*y - 1, -x - 2*y, 0, + -2*x*y + 1, 0, 6*x + y, 0, x + 2*y, -6*x - y, 0] + + Exterior algebras inherit from Clifford algebras, so + supercommutators work as well. We verify the exterior algebra + is supercommutative:: + + sage: E. = ExteriorAlgebra(QQ) + sage: all(b1.supercommutator(b2) == 0 + ....: for b1 in E.basis() for b2 in E.basis()) + True + """ + P = self.parent() + ret = P.zero() + for ms, cs in self: + for mx, cx in x: + ret += P.term(ms, cs) * P.term(mx, cx) + s = (-1)**(P.degree_on_basis(ms) * P.degree_on_basis(mx)) + ret -= s * P.term(mx, cx) * P.term(ms, cs) + return ret + class CliffordAlgebraIndices(Parent): r""" @@ -338,7 +399,6 @@ def __call__(self, el): 0101001 sage: E = ExteriorAlgebra(QQ, 7) sage: B = E.basis() - """ if not isinstance(el, Element): @@ -421,7 +481,7 @@ def __len__(self): EXAMPLES:: sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices - sage: idx = CliffordAlgebraIndices(7); + sage: idx = CliffordAlgebraIndices(7); sage: len(idx) == 2^7 True """ @@ -432,7 +492,7 @@ def __iter__(self): EXAMPLES:: sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices - sage: idx = CliffordAlgebraIndices(3); + sage: idx = CliffordAlgebraIndices(3); sage: for i in idx: print(i) 0 1 @@ -448,7 +508,7 @@ def __iter__(self): yield FrozenBitset('0') k = 1 while k <= n: - for C in itertools.combinations(range(n),k): + for C in itertools.combinations(range(n), k): yield FrozenBitset(C) k += 1 @@ -457,7 +517,7 @@ def __contains__(self, other): EXAMPLES:: sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices - sage: idx = CliffordAlgebraIndices(3); + sage: idx = CliffordAlgebraIndices(3); sage: int(8) in idx # representing the set {4} False sage: FrozenBitset('1') in idx @@ -468,6 +528,7 @@ def __contains__(self, other): return (other < self._cardinality) and (other >= 0) return self._maximal_set.issuperset(other) + class CliffordAlgebra(CombinatorialFreeModule): r""" The Clifford algebra of a quadratic form. @@ -609,7 +670,7 @@ def __classcall_private__(cls, Q, names=None): names = tuple(names) if len(names) != Q.dim(): if len(names) == 1: - names = tuple( '{}{}'.format(names[0], i) for i in range(Q.dim()) ) + names = tuple('{}{}'.format(names[0], i) for i in range(Q.dim())) else: raise ValueError("the number of variables does not match the number of generators") return super().__classcall__(cls, Q, names) @@ -824,14 +885,14 @@ def _element_constructor_(self, x): if x in self.free_module(): R = self.base_ring() if x.parent().base_ring() is R: - return self.element_class(self, {FrozenBitset((i,)): c for i,c in x.items()}) + return self.element_class(self, {FrozenBitset((i, )): c for i, c in x.items()}) # if the base ring is different, attempt to coerce it into R - return self.element_class(self, {FrozenBitset((i,)): R(c) for i,c in x.items() if R(c) != R.zero()}) + return self.element_class(self, {FrozenBitset((i, )): R(c) for i, c in x.items() if R(c) != R.zero()}) if (isinstance(x, CliffordAlgebraElement) - and self.has_coerce_map_from(x.parent())): + and self.has_coerce_map_from(x.parent())): R = self.base_ring() - return self.element_class(self, {i: R(c) for i,c in x if R(c) != R.zero()}) + return self.element_class(self, {i: R(c) for i, c in x if R(c) != R.zero()}) if isinstance(x, tuple): R = self.base_ring() @@ -857,7 +918,7 @@ def gen(self, i): sage: [Cl.gen(i) for i in range(3)] [x, y, z] """ - return self._from_dict({FrozenBitset((i,)): self.base_ring().one()}, remove_zeros=False) + return self._from_dict({FrozenBitset((i, )): self.base_ring().one()}, remove_zeros=False) def algebra_generators(self): """ @@ -870,7 +931,7 @@ def algebra_generators(self): sage: Cl.algebra_generators() Finite family {'x': x, 'y': y, 'z': z} """ - d = {x: self.gen(i) for i,x in enumerate(self.variable_names())} + d = {x: self.gen(i) for i, x in enumerate(self.variable_names())} return Family(self.variable_names(), lambda x: d[x]) def gens(self): @@ -1186,9 +1247,8 @@ def lift_module_morphism(self, m, names=None): Cl = CliffordAlgebra(Q, names) n = self._quadratic_form.dim() - f = lambda x: self.prod(self._from_dict( {FrozenBitset((j,)): m[j,i] for j in range(n)}, - remove_zeros=True ) - for i in x) + f = lambda x: self.prod(self._from_dict({FrozenBitset((j, )): m[j, i] for j in range(n)}, + remove_zeros=True) for i in x) cat = AlgebrasWithBasis(self.category().base_ring()).Super().FiniteDimensional() return Cl.module_morphism(on_basis=f, codomain=self, category=cat) @@ -1273,9 +1333,8 @@ def lift_isometry(self, m, names=None): Cl = CliffordAlgebra(Q, names) n = Q.dim() - f = lambda x: Cl.prod(Cl._from_dict( {FrozenBitset((j,)): m[j,i] for j in range(n)}, - remove_zeros=True ) - for i in x) + f = lambda x: Cl.prod(Cl._from_dict({FrozenBitset((j, )): m[j, i] for j in range(n)}, + remove_zeros=True) for i in x) cat = AlgebrasWithBasis(self.category().base_ring()).Super().FiniteDimensional() return self.module_morphism(on_basis=f, codomain=Cl, category=cat) @@ -1347,16 +1406,16 @@ def center_basis(self): K = list(B.keys()) k = len(K) d = {} - for a,i in enumerate(K): + for a, i in enumerate(K): Bi = B[i] - for b,j in enumerate(K): + for b, j in enumerate(K): Bj = B[j] - for m,c in (Bi*Bj - Bj*Bi): + for m, c in (Bi*Bj - Bj*Bi): d[(a, K.index(m)+k*b)] = c m = Matrix(R, d, nrows=k, ncols=k*k, sparse=True) - from_vector = lambda x: self.sum_of_terms(((K[i], c) for i,c in x.items()), + from_vector = lambda x: self.sum_of_terms(((K[i], c) for i, c in x.items()), distinct=True) - return tuple(map( from_vector, m.kernel().basis() )) + return tuple(map(from_vector, m.kernel().basis())) # Same as center except for superalgebras @cached_method @@ -1426,23 +1485,24 @@ def supercenter_basis(self): K = list(B.keys()) k = len(K) d = {} - for a,i in enumerate(K): + for a, i in enumerate(K): Bi = B[i] - for b,j in enumerate(K): + for b, j in enumerate(K): Bj = B[j] if len(i) % 2 and len(j) % 2: supercommutator = Bi * Bj + Bj * Bi else: supercommutator = Bi * Bj - Bj * Bi - for m,c in supercommutator: - d[(a, K.index(m)+k*b)] = c - m = Matrix(R, d, nrows=k, ncols=k*k, sparse=True) - from_vector = lambda x: self.sum_of_terms(((K[i], c) for i,c in x.items()), + for m, c in supercommutator: + d[(a, K.index(m) + k * b)] = c + m = Matrix(R, d, nrows=k, ncols=k * k, sparse=True) + from_vector = lambda x: self.sum_of_terms(((K[i], c) for i, c in x.items()), distinct=True) - return tuple(map( from_vector, m.kernel().basis() )) + return tuple(map(from_vector, m.kernel().basis())) Element = CliffordAlgebraElement + class ExteriorAlgebra(CliffordAlgebra): r""" An exterior algebra of a free module over a commutative ring. @@ -1527,7 +1587,7 @@ def __classcall_private__(cls, R, names=None, n=None): names = tuple(names) if n is not None and len(names) != n: if len(names) == 1: - names = tuple( '{}{}'.format(names[0], i) for i in range(n) ) + names = tuple('{}{}'.format(names[0], i) for i in range(n)) else: raise ValueError("the number of variables does not match the number of generators") return super().__classcall__(cls, R, names) @@ -1597,7 +1657,7 @@ def _ascii_art_term(self, m): if len(m) == 0: return ascii_art('1') wedge = '/\\' - return ascii_art(*[repr(self.basis()[FrozenBitset((i,))]) for i in m], sep=wedge) + return ascii_art(*[repr(self.basis()[FrozenBitset((i, ))]) for i in m], sep=wedge) def _unicode_art_term(self, m): """ @@ -1741,9 +1801,8 @@ def lift_morphism(self, phi, names=None): n = phi.nrows() R = self.base_ring() E = ExteriorAlgebra(R, names, n) - f = lambda x: E.prod(E._from_dict( {FrozenBitset((j,)): phi[j,i] for j in range(n)}, - remove_zeros=True ) - for i in x) + f = lambda x: E.prod(E._from_dict({FrozenBitset((j, )): phi[j, i] for j in range(n)}, + remove_zeros=True) for i in x) cat = AlgebrasWithBasis(R).Super().FiniteDimensional() return self.module_morphism(on_basis=f, codomain=E, category=cat) @@ -1868,9 +1927,9 @@ def coproduct_on_basis(self, a): """ from sage.combinat.combinat import unshuffle_iterator one = self.base_ring().one() - L = unshuffle_iterator(tuple(a),one) + L = unshuffle_iterator(tuple(a), one) return self.tensor_square()._from_dict( - {tuple(FrozenBitset(e) if e else FrozenBitset('0') for e in t): c for t,c in L if c}, + {tuple(FrozenBitset(e) if e else FrozenBitset('0') for e in t): c for t, c in L if c}, coerce=False, remove_zeros=False) @@ -2079,7 +2138,7 @@ def lifted_form(x, y): m = len(my) if m != n: continue - matrix_list = [M[i,j] for i in mx for j in my] + matrix_list = [M[i, j] for i in mx for j in my] MA = MatrixArgs(R, n, matrix_list) del matrix_list result += cx * cy * MA.matrix(False).determinant() @@ -2132,8 +2191,8 @@ def _mul_(self, other): d = {} n = P.ngens() - for ml,cl in self: # ml for "monomial on the left" - for mr,cr in other: # mr for "monomial on the right" + for ml, cl in self: # ml for "monomial on the left" + for mr, cr in other: # mr for "monomial on the right" if ml.intersection(mr): # if they intersect nontrivially, move along. continue @@ -2145,7 +2204,7 @@ def _mul_(self, other): it = iter(mr) j = next(it) - num_cross = 0 # keep track of the number of signs + num_cross = 0 # keep track of the number of signs tot_cross = 0 for i in ml: while i > j: @@ -2245,7 +2304,7 @@ def interior_product(self, x): """ P = self.parent() return P.sum([c * cx * P.interior_product_on_basis(m, mx) - for m,c in self for mx,cx in x]) + for m, c in self for mx, cx in x]) antiderivation = interior_product @@ -2339,10 +2398,12 @@ def scalar(self, other): return (self.transpose() * other).constant_coefficient() ##################################################################### -## Differentials +# Differentials + class ExteriorAlgebraDifferential(ModuleMorphismByLinearity, - UniqueRepresentation, metaclass=InheritComparisonClasscallMetaclass): + UniqueRepresentation, + metaclass=InheritComparisonClasscallMetaclass): r""" Internal class to store the data of a boundary or coboundary of an exterior algebra `\Lambda(L)` defined by the structure @@ -2386,12 +2447,12 @@ def __classcall__(cls, E, s_coeff): d = {} for k, v in dict(s_coeff).items(): - if not v: # Strip terms with 0 + if not v: # Strip terms with 0 continue if isinstance(v, dict): R = E.base_ring() - v = E._from_dict({FrozenBitset((i,)): R(c) for i,c in v.items()}) + v = E._from_dict({FrozenBitset((i, )): R(c) for i, c in v.items()}) else: # Make sure v is in ``E`` v = E(v) @@ -2466,6 +2527,7 @@ def homology(self, deg=None, **kwds): """ return self.chain_complex().homology(deg, **kwds) + class ExteriorAlgebraBoundary(ExteriorAlgebraDifferential): r""" The boundary `\partial` of an exterior algebra `\Lambda(L)` defined @@ -2616,13 +2678,13 @@ def _on_basis(self, m): s = E.zero() - for b, (i,j) in enumerate(combinations(m, 2)): + for b, (i, j) in enumerate(combinations(m, 2)): t = Bitset(m) - if (i,j) not in keys: + if (i, j) not in keys: continue t.discard(i) t.discard(j) - s += (-1)**b * sc[(i,j)] * E.monomial(FrozenBitset(t)) + s += (-1)**b * sc[(i, j)] * E.monomial(FrozenBitset(t)) return s @@ -2695,7 +2757,7 @@ def chain_complex(self, R=None): # Construct the transition matrices data = {} prev_basis = basis_by_deg[0] - for deg in range(1,n+1): + for deg in range(1, n+1): # Make sure within each basis we're sorted by lex basis = sorted(basis_by_deg[deg]) mat = [] @@ -2703,13 +2765,14 @@ def chain_complex(self, R=None): ret = self._on_basis(b) try: mat.append([ret.coefficient(p) for p in prev_basis]) - except AttributeError: # if ret is in E.base_ring() + except AttributeError: # if ret is in E.base_ring() mat.append([E.base_ring()(ret)]) data[deg] = Matrix(mat).transpose().change_ring(R) prev_basis = basis return ChainComplex(data, degree=-1) + class ExteriorAlgebraCoboundary(ExteriorAlgebraDifferential): r""" The coboundary `d` of an exterior algebra `\Lambda(L)` defined @@ -2844,12 +2907,12 @@ def __init__(self, E, s_coeff): B = E.basis() for k, v in dict(s_coeff).items(): - if k[0] > k[1]: #k will have length 2 + if k[0] > k[1]: # k will have length 2 k = sorted(k) v = -v k = B[FrozenBitset(k)] - for m,c in v: + for m, c in v: self._cos_coeff[m] = self._cos_coeff.get(m, zero) + c * k ExteriorAlgebraDifferential.__init__(self, E, s_coeff) @@ -2907,7 +2970,6 @@ def _on_basis(self, m): below = E.one() else: below = E.monomial(FrozenBitset(below)) - if len(above) == 0: above = E.one() else: @@ -2994,7 +3056,7 @@ def chain_complex(self, R=None): ret = self._on_basis(b) try: mat.append([ret.coefficient(p) for p in next_basis]) - except AttributeError: # if ret is in E.base_ring() + except AttributeError: # if ret is in E.base_ring() mat.append([E.base_ring()(ret)]*len(next_basis)) data[deg] = Matrix(mat).transpose().change_ring(R) basis = next_basis