diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 0c583b04f6c..d40e330819f 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -5,10 +5,12 @@ AUTHORS: - Travis Scrimshaw (2013-09-06): Initial version +- Trevor K. Karn (2022-07-27): Rewrite basis indexing using FrozenBitset """ #***************************************************************************** -# Copyright (C) 2013 Travis Scrimshaw +# Copyright (C) 2013-2022 Travis Scrimshaw +# (C) 2022 Trevor Karn # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,10 +21,14 @@ 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 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 @@ -31,7 +37,6 @@ 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.quadratic_forms.quadratic_form import QuadraticForm from sage.algebras.weyl_algebra import repr_from_monomials from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass @@ -80,7 +85,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): """ @@ -111,9 +118,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). @@ -122,43 +129,43 @@ 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 - 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) - next[t] = next.get(t, zero) + cr * Q[i,j] + # 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 # basis vector. 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) - cr *= Q[i,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 - 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] @@ -177,9 +184,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: (-len(m[0]), list(m[0]))) def support(self): """ @@ -194,9 +201,9 @@ 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)) + return sorted(self._monomial_coefficients.keys(), key=lambda x: (-len(x), list(x))) def reflection(self): r""" @@ -233,7 +240,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 @@ -279,7 +286,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""" @@ -318,6 +325,279 @@ 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(UniqueRepresentation, Parent): + r""" + A facade parent for the indices of Clifford algebra. + Users should not create instances of this class directly. + """ + def __init__(self, Qdim): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx._nbits + 7 + sage: idx._cardinality + 128 + sage: i = idx.an_element(); i + 1111 + sage: type(i) + + """ + self._nbits = 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) + + def _element_constructor_(self, x): + r""" + Construct an element of ``self``. + + 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") + return FrozenBitset(x) + + if isinstance(x, int): + return FrozenBitset((x,)) + + 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 cardinality(self): + r""" + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx.cardinality() == 2^7 + True + sage: len(idx) == 2^7 + True + """ + return self._cardinality + + __len__ = cardinality + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: CliffordAlgebraIndices(7) + Subsets of {0,1,...,6} + sage: CliffordAlgebraIndices(0) + Subsets of {} + sage: CliffordAlgebraIndices(1) + Subsets of {0} + sage: CliffordAlgebraIndices(2) + Subsets of {0,1} + """ + if self._nbits == 0: + return "Subsets of {}" + if self._nbits == 1: + return "Subsets of {0}" + if self._nbits == 2: + return "Subsets of {0,1}" + return f"Subsets of {{0,1,...,{self._nbits-1}}}" + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: latex(CliffordAlgebraIndices(7)) + \mathcal{P}({0,1,\ldots,6}) + sage: latex(CliffordAlgebraIndices(0)) + \mathcal{P}(\emptyset) + sage: latex(CliffordAlgebraIndices(1)) + \mathcal{P}({0}) + sage: latex(CliffordAlgebraIndices(2)) + \mathcal{P}({0,1}) + """ + if self._nbits == 0: + return "\\mathcal{P}(\\emptyset)" + if self._nbits == 1: + return "\\mathcal{P}({0})" + if self._nbits == 2: + return "\\mathcal{P}({0,1})" + return f"\\mathcal{{P}}({{0,1,\\ldots,{self._nbits-1}}})" + + def __iter__(self): + r""" + Iterate over ``self``. + + 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() + k = 1 + while k <= n: + for C in itertools.combinations(range(n), k): + yield FrozenBitset(C) + k += 1 + + def __contains__(self, elt): + r""" + Check containment of ``elt`` in ``self``. + + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(3) + sage: int(8) in idx # representing the set {4} + False + sage: int(5) in idx # representing the set {1,3} + True + sage: FrozenBitset('1') in idx + True + sage: FrozenBitset('000001') in idx + False + """ + if isinstance(elt, int): + return elt < self._cardinality and elt >= 0 + if not isinstance(elt, FrozenBitset): + return False + return elt.capacity() <= self._nbits + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(0) + sage: idx._an_element_() + 0 + sage: idx = CliffordAlgebraIndices(1) + sage: idx._an_element_() + 1 + sage: idx = CliffordAlgebraIndices(2) + sage: idx._an_element_() + 01 + sage: idx = CliffordAlgebraIndices(3) + sage: idx._an_element_() + 11 + """ + if not self._nbits: + return FrozenBitset() + + from sage.combinat.subset import SubsetsSorted + X = SubsetsSorted(range(self._nbits)) + return FrozenBitset(X.an_element()) class CliffordAlgebra(CombinatorialFreeModule): r""" @@ -460,7 +740,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) @@ -486,15 +766,14 @@ 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 R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) - indices = SubsetsSorted(range(Q.dim())) - CombinatorialFreeModule.__init__(self, R, indices, category=category) + indices = CliffordAlgebraIndices(Q.dim()) + CombinatorialFreeModule.__init__(self, R, indices, category=category, sorting_key=tuple) self._assign_names(names) def _repr_(self): @@ -523,6 +802,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,)) @@ -654,7 +935,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) @@ -674,15 +955,23 @@ 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())): + 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()}) - 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}) + + 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 gen(self, i): """ @@ -699,7 +988,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): """ @@ -712,7 +1001,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): @@ -728,6 +1017,7 @@ def gens(self): """ return tuple(self.algebra_generators()) + @cached_method def ngens(self): """ Return the number of algebra generators of ``self``. @@ -744,16 +1034,18 @@ 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 () + return FrozenBitset() def is_commutative(self): """ @@ -1025,9 +1317,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( {(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) @@ -1112,9 +1403,8 @@ 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)}, - 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) @@ -1186,16 +1476,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 @@ -1265,23 +1555,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. @@ -1366,7 +1657,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) @@ -1436,7 +1727,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): """ @@ -1580,9 +1871,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( {(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) @@ -1699,15 +1989,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(a, one), - distinct=True) + L = unshuffle_iterator(tuple(a), one) + return self.tensor_square()._from_dict( + {tuple(FrozenBitset(e) if e else FrozenBitset() for e in t): c for t, c in L if c}, + coerce=False, + remove_zeros=False) def antipode_on_basis(self, m): r""" @@ -1914,9 +2208,7 @@ def lifted_form(x, y): m = len(my) if m != n: continue - matrix_list = [M[mx[i], my[j]] - 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() @@ -1955,38 +2247,51 @@ 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 + 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: - for mr,cr in other: - # Create the next term - t = list(mr) - 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 + 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 - t = tuple(t) + 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 + tot_cross = 0 + for i in ml: + while i > j: + num_cross += 1 + try: + j = next(it) + except StopIteration: + 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""" @@ -2069,7 +2374,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 @@ -2163,10 +2468,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 @@ -2194,7 +2501,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, {}) @@ -2206,12 +2517,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({(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) @@ -2286,6 +2597,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 @@ -2418,7 +2730,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 sage: par._on_basis((0,)) 0 @@ -2429,12 +2741,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.zero() + + for b, (i, j) in enumerate(combinations(m, 2)): + if (i, j) not in keys: + continue + t = Bitset(m) + t.discard(i) + t.discard(j) + s += sc[i, j] * E.term(FrozenBitset(t), (-1)**b) + + return s @cached_method def chain_complex(self, R=None): @@ -2505,18 +2827,19 @@ 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 = [] for b in basis: ret = self._on_basis(b) - mat.append([ret[p] for p in prev_basis]) + mat.append([ret.coefficient(p) for p in prev_basis]) 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 @@ -2563,7 +2886,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) @@ -2650,8 +2973,12 @@ def __init__(self, E, s_coeff): zero = E.zero() B = E.basis() for k, v in dict(s_coeff).items(): - k = B[k] - for m,c in v: + 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 ExteriorAlgebraDifferential.__init__(self, E, s_coeff) @@ -2676,7 +3003,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,)) @@ -2694,9 +3021,28 @@ 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) + + tot = E.zero() + + for sgn, i in enumerate(m): + k = FrozenBitset((i,)) + if k in cc: + below = tuple([j for j in m if j < i]) + above = tuple([j for j in m if j > i]) + + # a hack to deal with empty bitsets + if not below: + below = E.one() + else: + below = E.monomial(FrozenBitset(below)) + if not above: + above = E.one() + else: + above = E.monomial(FrozenBitset(above)) + + tot += (-1)**sgn * below * cc[k] * above + + return tot @cached_method def chain_complex(self, R=None): @@ -2773,8 +3119,12 @@ 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)]*len(next_basis)) data[deg] = Matrix(mat).transpose().change_ring(R) basis = next_basis return ChainComplex(data, degree=1) + diff --git a/src/sage/categories/filtered_modules_with_basis.py b/src/sage/categories/filtered_modules_with_basis.py index bd75b8f8e8a..336c1c326c8 100644 --- a/src/sage/categories/filtered_modules_with_basis.py +++ b/src/sage/categories/filtered_modules_with_basis.py @@ -178,9 +178,9 @@ def basis(self, d=None): sage: E. = ExteriorAlgebra(QQ) sage: E.basis() - Lazy family (Term map from Subsets of {0, 1} to + Lazy family (Term map from Subsets of {0,1} to The exterior algebra of rank 2 over Rational Field(i))_{i in - Subsets of {0, 1}} + Subsets of {0,1}} """ if d is None: from sage.sets.family import Family diff --git a/src/sage/categories/graded_algebras_with_basis.py b/src/sage/categories/graded_algebras_with_basis.py index e80c1b00bf4..abece323a5c 100644 --- a/src/sage/categories/graded_algebras_with_basis.py +++ b/src/sage/categories/graded_algebras_with_basis.py @@ -168,10 +168,10 @@ def one_basis(self): sage: A. = ExteriorAlgebra(QQ) sage: A.one_basis() - () + 0 sage: B = tensor((A, A, A)) sage: B.one_basis() - ((), (), ()) + (0, 0, 0) sage: B.one() 1 # 1 # 1 """ diff --git a/src/sage/modules/with_basis/invariant.py b/src/sage/modules/with_basis/invariant.py index e44c1ece287..f180f442c19 100644 --- a/src/sage/modules/with_basis/invariant.py +++ b/src/sage/modules/with_basis/invariant.py @@ -109,7 +109,7 @@ class FiniteDimensionalInvariantModule(SubmoduleWithBasis): sage: M = algebras.Exterior(QQ, 'x', 3) sage: def cyclic_ext_action(g, m): ....: # cyclically permute generators - ....: return M.prod([M.monomial((g(j+1)-1,)) for j in m]) + ....: return M.prod([M.monomial(FrozenBitset([g(j+1)-1])) for j in m]) If you care about being able to exploit the algebra structure of the exterior algebra (i.e. if you want to multiply elements together), you @@ -398,7 +398,7 @@ def _mul_(self, other): sage: G = CyclicPermutationGroup(3); G.rename('G') sage: M = algebras.Exterior(QQ, 'x', 3) - sage: def on_basis(g,m): return M.prod([M.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: def on_basis(g,m): return M.prod([M.monomial(FrozenBitset([g(j+1)-1])) for j in m]) # cyclically permute generators sage: R = Representation(G, M, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional(), side='right') sage: I = R.invariant_module(); I.rename('I') sage: B = I.basis() @@ -466,7 +466,6 @@ def _acted_upon_(self, scalar, self_on_left=False): sage: g = G.an_element(); g (1,2,3) sage: M = CombinatorialFreeModule(QQ, [1,2,3]) - sage: E = algebras.Exterior(QQ, 'x', 3) sage: from sage.modules.with_basis.representation import Representation sage: R = Representation(G, M, lambda g,x: M.monomial(g(x))) sage: I = R.invariant_module() @@ -480,7 +479,8 @@ def _acted_upon_(self, scalar, self_on_left=False): [2*B[0], 2*B[0], 2*B[0]] - sage: def on_basis(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: E = algebras.Exterior(QQ, 'x', 3) + sage: def on_basis(g,m): return E.prod([E.monomial(FrozenBitset([g(j+1)-1])) for j in m]) # cyclically permute generators sage: R = Representation(G, E, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional()) sage: I = R.invariant_module() sage: B = I.basis() @@ -528,7 +528,7 @@ def _acted_upon_(self, scalar, self_on_left=False): sage: [b._acted_upon_(G((1,3,2)), self_on_left=True) for b in I.basis()] [B[0]] - sage: def on_basis(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: def on_basis(g,m): return E.prod([E.monomial(FrozenBitset([g(j+1)-1])) for j in m]) # cyclically permute generators sage: R = Representation(G, E, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional(), side='right') sage: I = R.invariant_module() sage: B = I.basis() @@ -687,7 +687,7 @@ class FiniteDimensionalTwistedInvariantModule(SubmoduleWithBasis): sage: G = SymmetricGroup(3); G.rename('S3') sage: E = algebras.Exterior(QQ, 'x', 3); E.rename('E') - sage: def action(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) + sage: def action(g,m): return E.prod([E.monomial(FrozenBitset([g(j+1)-1])) for j in m]) sage: from sage.modules.with_basis.representation import Representation sage: EA = Representation(G, E, action, category=Algebras(QQ).WithBasis().FiniteDimensional()) sage: T = EA.twisted_invariant_module([2,0,-1]) diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 89721adffae..815871e038d 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -276,7 +276,7 @@ def __init__(self, semigroup, module, on_basis, side="left", **kwargs): sage: G = CyclicPermutationGroup(3) sage: M = algebras.Exterior(QQ, 'x', 3) sage: from sage.modules.with_basis.representation import Representation - sage: on_basis = lambda g,m: M.prod([M.monomial((g(j+1)-1,)) for j in m]) #cyclically permute generators + sage: on_basis = lambda g,m: M.prod([M.monomial(FrozenBitset([g(j+1)-1])) for j in m]) #cyclically permute generators sage: from sage.categories.algebras import Algebras sage: R = Representation(G, M, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional()) sage: r = R.an_element(); r @@ -451,9 +451,9 @@ def product_by_coercion(self, left, right): ... TypeError: unsupported operand parent(s) for *: 'Representation of The Klein 4 group of order 4, as a permutation - group indexed by Subsets of {0, 1, 2, 3} over Rational Field' and + group indexed by Subsets of {0,1,...,3} over Rational Field' and 'Representation of The Klein 4 group of order 4, as a permutation - group indexed by Subsets of {0, 1, 2, 3} over Rational Field' + group indexed by Subsets of {0,1,...,3} over Rational Field' sage: from sage.categories.algebras import Algebras sage: category = Algebras(QQ).FiniteDimensional().WithBasis()