# Ticket #3397: 3397-2008-07-30-main.patch

File 3397-2008-07-30-main.patch, 186.1 KB (added by jhpalmieri, 11 years ago)

new version of Steenrod algebra package, incorporating changes suggested by was

• ## sage/algebras/all.py

# HG changeset patch
# User J. H. Palmieri <palmieri@math.washington.edu>
# Date 1217464162 25200
# Parent  c25e04ebfb67678948024df2a820350c4b503acf
Steenrod algebra

diff -r c25e04ebfb67 -r 9fbc77f226c8 sage/algebras/all.py
 a from quaternion_algebra import (Quaterni hilbert_symbol, fundamental_discriminant) from quaternion_order import QuaternionOrderWithBasis, QuaternionDefiningOrder from quaternion_order_ideal import QuaternionOrderLeftIdeal, QuaternionOrderRightIdeal, QuaternionOrderTwoSidedIdeal from steenrod_algebra import SteenrodAlgebra from steenrod_algebra_element import Sq from steenrod_algebra_bases import steenrod_algebra_basis def is_R_algebra(Q, R):
• ## new file sage/algebras/steenrod_algebra.py

diff -r c25e04ebfb67 -r 9fbc77f226c8 sage/algebras/steenrod_algebra.py
 - r""" The Steenrod algebra AUTHORS: - John H. Palmieri (2008-07-30: version 0.9) This package defines the mod $p$ Steenrod algebra $\mathcal{A}_p$, some of its properties, and ways to define elements of it. From a topological point of view, $\mathcal{A}_p$ is the algebra of stable cohomology operations on mod $p$ cohomology; thus for any topological space $X$, its mod $p$ cohomology algebra $H^*(X,\mathbf{F}_p)$ is a module over $\mathcal{A}_p$. From an algebraic point of view, $\mathcal{A}_p$ is an $\mathbf{F}_p$-algebra; when $p=2$, it is generated by elements $\text{Sq}^i$ for $i\geq 0$ (the \emph{Steenrod squares}), and when $p$ is odd, it is generated by elements $\mathcal{P}^i$ for $i \geq 0$ (the \emph{Steenrod reduced $p$th powers}) along with an element $\beta$ (the \emph{mod $p$ Bockstein}).  The Steenrod algebra is graded: $\text{Sq}^i$ is in degree $i$ for each $i$, $\beta$ is in degree 1, and $\mathcal{P}^i$ is in degree $2(p-1)i$. The unit element is $\text{Sq}^0$ when $p=2$ and $\mathcal{P}^0$ when $p$ is odd.  The generating elements also satisfy the \emph{Adem relations}.  At the prime 2, these have the form $\text{Sq}^a \text{Sq}^b = \sum_{c=0}^{[a/2]} \binom{b-c-1}{a-2c} \text{Sq}^{a+b-c} \text{Sq}^c.$ At odd primes, they are a bit more complicated.  See Steenrod and Epstein [SE] for full details.  These relations lead to the existence of the \emph{Serre-Cartan} basis for $\mathcal{A}_p$. The mod $p$ Steenrod algebra has the structure of a Hopf algebra, and Milnor [Mil] has a beautiful description of the dual, leading to a construction of the \emph{Milnor basis} for $\mathcal{A}_p$.  In this package, elements in the Steenrod algebra are represented, by default, using the Milnor basis. See the documentation for \code{SteenrodAlgebra} for many more details and examples. REFERENCES: [Mil] J. W. Milnor, "The Steenrod algebra and its dual," Ann. of Math. (2) 67 (1958), 150--171. [SE]  N. E. Steenrod and D. B. A. Epstein, Cohomology operations, Ann. of Math. Stud. 50 (Princeton University Press, 1962). """ #***************************************************************************** #       Copyright (C) 2008 John H. Palmieri #  Distributed under the terms of the GNU General Public License (GPL) #***************************************************************************** from sage.rings.ring import Algebra from sage.algebras.algebra_element import AlgebraElement from sage.structure.parent_gens import ParentWithGens from sage.structure.element import RingElement from sage.rings.all import GF from sage.misc.functional import parent from sage.rings.integer import Integer class SteenrodAlgebra_generic(Algebra): r""" The mod $p$ Steenrod algebra. Users should not call this, but use the function 'SteenrodAlgebra' instead.  See that function for extensive documentation. EXAMPLES: sage: sage.algebras.steenrod_algebra.SteenrodAlgebra_generic() mod 2 Steenrod algebra sage: sage.algebras.steenrod_algebra.SteenrodAlgebra_generic(5) mod 5 Steenrod algebra sage: sage.algebras.steenrod_algebra.SteenrodAlgebra_generic(5, 'adem') mod 5 Steenrod algebra """ def __init__(self, p=2, basis='milnor'): """ INPUT: p -- positive prime integer (optional, default = 2) basis -- string (optional, default = 'milnor') OUTPUT: mod p Steenrod algebra with basis EXAMPLES: sage: SteenrodAlgebra()   # 2 is the default prime mod 2 Steenrod algebra sage: SteenrodAlgebra(5) mod 5 Steenrod algebra sage: SteenrodAlgebra(2, 'milnor').Sq(0,1) Sq(0,1) sage: SteenrodAlgebra(2, 'adem').Sq(0,1) Sq^{2} Sq^{1} + Sq^{3} """ from sage.rings.arith import is_prime if is_prime(p): self.prime = p ParentWithGens.__init__(self, GF(p)) self._basis_name = basis else: raise ValueError, "%s is not prime." % p def _repr_(self): """ Printed representation of the Steenrod algebra. EXAMPLES: sage: SteenrodAlgebra(3) mod 3 Steenrod algebra sage: B = SteenrodAlgebra(2003) sage: B mod 2003 Steenrod algebra sage: B._repr_() 'mod 2003 Steenrod algebra' """ return "mod %d Steenrod algebra" % self.prime def _latex_(self): """ LaTeX representation of the Steenrod algebra. EXAMPLES: sage: C = SteenrodAlgebra(3) sage: C mod 3 Steenrod algebra sage: C._latex_() '\\mathcal{A}_{3}' """ return "\\mathcal{A}_{%s}" % self.prime def ngens(self): """ Number of generators of the Steenrod algebra. This returns infinity, since the Steenrod algebra is infinitely generated. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.ngens() +Infinity """ from sage.rings.infinity import Infinity return Infinity def gens(self): """ List of generators for the Steenrod algebra.  Not implemented (mainly because the list of generators is infinite). EXAMPLES: sage: A3 = SteenrodAlgebra(3, 'adem') sage: A3.gens() Traceback (most recent call last): ... NotImplementedError: 'gens' is not implemented for the Steenrod algebra. """ raise NotImplementedError, "'gens' is not implemented " + \ "for the Steenrod algebra." def gen(self, i=0): """ The ith generator of the Steenrod algebra. INPUT: i -- non-negative integer OUTPUT: the ith generator of the Steenrod algebra The $i$th generator is $Sq(2^i)$ at the prime 2; when $p$ is odd, the 0th generator is beta = Q(0), and for $i>0$, the $i$th generator is $P(p^{i-1})$. EXAMPLES: sage: A = SteenrodAlgebra(2) sage: A.gen(4) Sq(16) sage: A.gen(200) Sq(1606938044258990275541962092341162602522202993782792835301376) sage: B = SteenrodAlgebra(5) sage: B.gen(0) Q_0 sage: B.gen(2) P(5) """ if not isinstance(i, (Integer, int)) and i >= 0: raise ValueError, "%s is not a non-negative integer" % i if self.prime == 2: return self.Sq(self.prime**i) else: if i == 0: return self.Q(0) else: return self.P(self.prime**(i-1)) def __cmp__(self,right): """ Two Steenrod algebras are equal iff their associated primes are equal. EXAMPLES: sage: A = SteenrodAlgebra(2) sage: B = SteenrodAlgebra(2, 'adem') sage: cmp(A, B) 0 sage: A.__cmp__(B) 0 sage: A is B False sage: C = SteenrodAlgebra(17) sage: cmp(A,C) -1 """ if type(self) == type(right) and self.prime == right.prime: return 0 else: return -1 def __call__(self, x): """ Try to turn x into an element of self. INPUT: x -- a SteenrodAlgebra element or an element of F_p OUTPUT: x as a member of self Note that this provides a way of converting elements from one basis to another. EXAMPLES: sage: x = Sq(2,1) sage: x Sq(2,1) sage: B = SteenrodAlgebra(2, 'adem') sage: B(x) Sq^{4} Sq^{1} + Sq^{5} """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement if isinstance(x, SteenrodAlgebraElement) and x.parent() == self: dict = x._raw a = SteenrodAlgebraElement(dict['milnor'], p=x._prime, basis=self._basis_name) a._raw = dict return a else: return SteenrodAlgebraElement(x, p=self.prime, basis=self._basis_name) def _coerce_impl(self, x): """ Return the coercion of x into this Steenrod algebra. INPUT: x -- a SteenrodAlgebraElement or an element of F_p OUTPUT: coercion of x into the Steenrod algebra EXAMPLES: sage: A = SteenrodAlgebra(); A mod 2 Steenrod algebra sage: A(1)     # convert 1 to an element of A Sq(0) sage: A(Sq(3)) Sq(3) The algebras that coerce into the mod p Steenrod algebra are: * the mod p Steenrod algebra * its base field GF(p) """ return self._coerce_try(x, [self.base_ring()]) def __contains__(self, x): """ Instances of the class SteenrodAlgebraElement with the same prime are contained in the Steenrod algebra. EXAMPLES: sage: A = SteenrodAlgebra() sage: x = Sq(2) * Sq(1); x Sq(0,1) + Sq(3) sage: x in A True sage: x in SteenrodAlgebra(5) False """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement return (isinstance(x, SteenrodAlgebraElement) and x.parent() == self) \ or (GF(self.prime).__contains__(x)) def is_commutative(self): """ The Steenrod algebra is not commutative. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.is_commutative() False """ return False def is_finite(self): """ The Steenrod algebra is not finite. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.is_finite() False """ return False def order(self): """ The Steenrod algebra has infinite order. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.order() +Infinity """ from sage.rings.infinity import Infinity return Infinity def is_division_algebra(self): """ The Steenrod algebra is not a division algebra. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.is_division_algebra() False """ return False def is_field(self): """ The Steenrod algebra is not a field. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.is_field() False """ return False def is_integral_domain(self): """ The Steenrod algebra is not an integral domain. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.is_integral_domain() False """ return False def is_noetherian(self): """ The Steenrod algebra is not noetherian. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.is_noetherian() False """ return False def category(self): """ The Steenrod algebra is an algebra over $F_p$. EXAMPLES: sage: A = SteenrodAlgebra(3) sage: A.category() Category of algebras over Finite Field of size 3 """ from sage.categories.category_types import Algebras return Algebras(GF(self.prime)) def basis(self, n): """ Basis for self in dimension n INPUT: n -- non-negative integer OUTPUT: basis -- tuple of Steenrod algebra elements EXAMPLES: sage: A3 = SteenrodAlgebra(3) sage: A3.basis(13) (Q_1 P(2), Q_0 P(3)) sage: SteenrodAlgebra(2, 'adem').basis(12) (Sq^{12}, Sq^{11} Sq^{1}, Sq^{9} Sq^{2} Sq^{1}, Sq^{8} Sq^{3} Sq^{1}, Sq^{10} Sq^{2}, Sq^{9} Sq^{3}, Sq^{8} Sq^{4}) """ from steenrod_algebra_bases import steenrod_algebra_basis return steenrod_algebra_basis(n, basis=self._basis_name, p=self.prime) def P(self, *nums): r""" The element $P(a, b, c, ...)$ INPUT: a, b, c, ... -- non-negative integers OUTPUT: element of the Steenrod algebra given by the single basis element P(a, b, c, ...) Note that at the prime 2, this is the same element as $\text{Sq}(a, b, c, ...)$. EXAMPLES: sage: A = SteenrodAlgebra(2) sage: A.P(5) Sq(5) sage: B = SteenrodAlgebra(3) sage: B.P(5,1,1) P(5,1,1) """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement if self.prime == 2: dict = {nums: 1} else: dict = {((), nums): 1} return SteenrodAlgebraElement(dict, p=self.prime, basis=self._basis_name) def Q_exp(self, *nums): r""" The element $Q_0^{e_0} Q_1^{e_1} ...$, given by specifying the exponents. INPUT: e0, e1, ... -- 0s and 1s OUTPUT: The element $Q_0^{e_0} Q_1^{e_1} ...$ Note that at the prime 2, $Q_n$ is the element $\text{Sq}(0,0,...,1)$, where the 1 is in the $n+1$st position. Compare this to the method 'Q', which defines a similar element, but by specifying the tuple of subscripts of terms with exponent 1. EXAMPLES: sage: A2 = SteenrodAlgebra(2) sage: A5 = SteenrodAlgebra(5) sage: A2.Q_exp(0,0,1,1,0) Sq(0,0,1,1) sage: A5.Q_exp(0,0,1,1,0) Q_2 Q_3 sage: A5.Q(2,3) Q_2 Q_3 sage: A5.Q_exp(0,0,1,1,0) == A5.Q(2,3) True """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement if not set(nums).issubset(set((0,1))): raise ValueError, "The tuple %s should consist " % (nums,) + \ "only of 0's and 1's" else: if self.prime == 2: answer = self.Sq(0) index = 0 for n in nums: if n == 1: answer = answer * self.pst(0,index+1) index += 1 return answer else: mono = () index = 0 for e in nums: if e == 1: mono = mono + (index,) index += 1 dict = {((mono), ()): 1} return SteenrodAlgebraElement(dict, p=self.prime, basis=self._basis_name) def Q(self, *nums): r""" The element $Q_{n0} Q_{n1} ...$, given by specifying the subscripts. INPUT: n0, n1, ... -- non-negative integers OUTPUT: The element $Q_{n0} Q_{n1} ...$ Note that at the prime 2, $Q_n$ is the element $\text{Sq}(0,0,...,1)$, where the 1 is in the $n+1$st position. Compare this to the method 'Q_exp', which defines a similar element, but by specifying the tuple of exponents. EXAMPLES: sage: A2 = SteenrodAlgebra(2) sage: A5 = SteenrodAlgebra(5) sage: A2.Q(2,3) Sq(0,0,1,1) sage: A5.Q(1,4) Q_1 Q_4 sage: A5.Q(1,4) == A5.Q_exp(0,1,0,0,1) True """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement if len(nums) != len(set(nums)): return self(0) else: if self.prime == 2: if len(nums) == 0: return Sq(0) else: list = (1+max(nums)) * [0] for i in nums: list[i] = 1 return SteenrodAlgebraElement({tuple(list): 1}, p=2, basis=self._basis_name) else: return SteenrodAlgebraElement({(nums, ()): 1}, p=self.prime, basis=self._basis_name) def pst(self,s,t): r""" The Margolis element $P^s_t$. INPUT: s -- non-negative integer t -- positive integer p -- positive prime number OUTPUT: element of the Steenrod algebra This returns the Margolis element $P^s_t$ of the mod $p$ Steenrod algebra: the element equal to $P(0,0,...,0,p^s)$, where the $p^s$ is in position $t$. EXAMPLES: sage: A2 = SteenrodAlgebra(2) sage: A2.pst(3,5) Sq(0,0,0,0,8) sage: A2.pst(1,2) == Sq(4)*Sq(2) + Sq(2)*Sq(4) True sage: SteenrodAlgebra(5).pst(3,5) P(0,0,0,0,125) """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement if not isinstance(s, (Integer, int)) and s >= 0: raise ValueError, "%s is not a non-negative integer" % s if not isinstance(t, (Integer, int)) and t > 0: raise ValueError, "%s is not a positive integer" % t nums = (0,)*(t-1) + (self.prime**s,) if self.prime == 2: return SteenrodAlgebraElement({nums: 1}, p=2, basis=self._basis_name) else: return SteenrodAlgebraElement({((), nums): 1}, p=self.prime, basis=self._basis_name) class SteenrodAlgebra_mod_two(SteenrodAlgebra_generic): """ The mod 2 Steenrod algebra. Users should not call this, but use the function 'SteenrodAlgebra' instead.  See that function for extensive documentation.  (This differs from SteenrodAlgebra_generic only in that it has a method 'Sq' for defining elements.) """ def Sq(self, *nums): r""" Milnor element $\text{Sq}(a,b,c,...)$. INPUT: a, b, c, ... -- non-negative integers OUTPUT: element of the Steenrod algebra This returns the Milnor basis element $\text{Sq}(a, b, c, ...)$. EXAMPLES: sage: A = SteenrodAlgebra(2) sage: A.Sq(5) Sq(5) sage: A.Sq(5,0,2) Sq(5,0,2) Entries must be non-negative integers; otherwise, an error results. """ from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement if self.prime == 2: dict = {nums: 1} return SteenrodAlgebraElement(dict, p=2, basis=self._basis_name) else: raise ValueError, "Sq is only defined at the prime 2" def SteenrodAlgebra(p=2, basis='milnor'): r""" The mod $p$ Steenrod algebra INPUT: p -- positive prime integer (optional, default = 2) basis -- string (optional, default = 'milnor') OUTPUT: mod p Steenrod algebra with given basis This returns the mod $p$ Steenrod algebra, elements of which are printed using basis. EXAMPLES: Some properties of the Steenrod algebra are available: sage: A = SteenrodAlgebra(2) sage: A.ngens()  # number of generators +Infinity sage: A.gen(5)   # 5th generator Sq(32) sage: A.order() +Infinity sage: A.is_finite() False sage: A.is_commutative() False sage: A.is_noetherian() False sage: A.is_integral_domain() False sage: A.is_field() False sage: A.is_division_algebra() False sage: A.category() Category of algebras over Finite Field of size 2 There are methods for constructing elements of the Steenrod algebra: sage: A2 = SteenrodAlgebra(2); A2 mod 2 Steenrod algebra sage: A2.Sq(1,2,6) Sq(1,2,6) sage: A2.Q(3,4)  # product of Milnor primitives Q_3 and Q_4 Sq(0,0,0,1,1) sage: A2.pst(2,3)  # Margolis pst element Sq(0,0,4) sage: A5 = SteenrodAlgebra(5); A5 mod 5 Steenrod algebra sage: A5.P(1,2,6) P(1,2,6) sage: A5.Q(3,4) Q_3 Q_4 sage: A5.Q(3,4) * A5.P(1,2,6) Q_3 Q_4 P(1,2,6) sage: A5.pst(2,3) P(0,0,25) You can test whether elements are contained in the Steenrod algebra: sage: w = Sq(2) * Sq(4) sage: w in SteenrodAlgebra(2) True sage: w in SteenrodAlgebra(17) False Different bases for the Steenrod algebra: There are two standard vector space bases for the mod $p$ Steenrod algebra: the Milnor basis and the Serre-Cartan basis.  When $p=2$, there are also several other, less well-known, bases.  See the documentation for the function 'steenrod_algebra_basis' for full descriptions of each of the implemented bases. This package implements the following bases at all primes: * 'milnor': Milnor basis. * 'serre-cartan' or 'adem' or 'admissible': Serre-Cartan basis. It implements the following bases when $p=2$: * 'wood_y': Wood's Y basis. * 'wood_z': Wood's Z basis. * 'wall', 'wall_long': Wall's basis. * 'arnon_a', 'arnon_a_long': Arnon's A basis. * 'arnon_c': Arnon's C basis. * 'pst', 'pst_rlex', 'pst_llex', 'pst_deg', 'pst_revz': various $P^s_t$-bases. * 'comm', 'comm_rlex', 'comm_llex', 'comm_deg', 'comm_revz', or these with '_long' appended: various commutator bases. When defining a Steenrod algebra, you can specify a basis.  Then elements of that Steenrod algebra are printed in that basis sage: adem = SteenrodAlgebra(2, 'adem') sage: x = adem.Sq(2,1)  # Sq(-) always means a Milnor basis element sage: x Sq^{4} Sq^{1} + Sq^{5} sage: y = Sq(0,1)    # unadorned Sq defines elements w.r.t. Milnor basis sage: y Sq(0,1) sage: adem(y) Sq^{2} Sq^{1} + Sq^{3} sage: adem5 = SteenrodAlgebra(5, 'serre-cartan') sage: adem5.P(0,2) P^{10} P^{2} + 4 P^{11} P^{1} + 4 P^{12} You can get a list of basis elements in a given dimension: sage: A3 = SteenrodAlgebra(3, 'milnor') sage: A3.basis(13) (Q_1 P(2), Q_0 P(3)) As noted above, several of the bases ('arnon_a', 'wall', 'comm') have alternate, longer, representations.  These provide ways of expressing elements of the Steenrod algebra in terms of the $\text{Sq}^{2^n}$. sage: A_long = SteenrodAlgebra(2, 'arnon_a_long') sage: A_long(Sq(6)) Sq^{1} Sq^{2} Sq^{1} Sq^{2} + Sq^{2} Sq^{4} sage: SteenrodAlgebra(2, 'wall_long')(Sq(6)) Sq^{2} Sq^{1} Sq^{2} Sq^{1} + Sq^{2} Sq^{4} sage: SteenrodAlgebra(2, 'comm_deg_long')(Sq(6)) s_{1} s_{2} s_{12} + s_{2} s_{4} """ basis_name = get_basis_name(basis, p) if p == 2: return SteenrodAlgebra_mod_two(2, basis_name) else: return SteenrodAlgebra_generic(p, basis_name) # Now we specify the names of the implemented bases.  For the Milnor # and Serre-Cartan bases, give a list of synonyms: _steenrod_milnor_basis_names = ['milnor'] _steenrod_serre_cartan_basis_names = ['serre_cartan', 'serre-cartan', 'sc', 'adem', 'admissible'] # For the other bases, use pattern-matching rather than a list of # synonyms: #   * Search for 'wood' and 'y' or 'wood' and 'z' to get the Wood bases. #   * Search for 'arnon' and 'c' for the Arnon C basis. #   * Search for 'arnon' (and no 'c') for the Arnon A basis.  Also see if #     'long' is present, for the long form of the basis. #   * Search for 'wall' for the Wall basis. Also see if 'long' is present. #   * Search for 'pst' for P^s_t bases, then search for the order type: #     'rlex', 'llex', 'deg', 'revz'. #   * For commutator types, search for 'comm', an order type, and also #     check to see if 'long' is present. def get_basis_name(basis, p): """ Return canonical basis named by string basis at the prime p. INPUT: basis -- string p -- positive prime number OUTPUT: basis_name -- string EXAMPLES: sage: sage.algebras.steenrod_algebra.get_basis_name('adem', 2) 'serre-cartan' sage: sage.algebras.steenrod_algebra.get_basis_name('milnor', 2) 'milnor' sage: sage.algebras.steenrod_algebra.get_basis_name('MiLNoR', 5) 'milnor' sage: sage.algebras.steenrod_algebra.get_basis_name('pst-llex', 2) 'pst_llex' """ basis = basis.lower() if basis in _steenrod_milnor_basis_names: result = 'milnor' elif basis in _steenrod_serre_cartan_basis_names: result = 'serre-cartan' elif p == 2 and basis.find('pst') >= 0: if basis.find('rlex') >= 0: result = 'pst_rlex' elif basis.find('llex') >= 0: result = 'pst_llex' elif basis.find('deg') >= 0: result = 'pst_deg' elif basis.find('revz') >= 0: result = 'pst_revz' else: result = 'pst_revz' elif p == 2 and basis.find('comm') >= 0: if basis.find('rlex') >= 0: result = 'comm_rlex' elif basis.find('llex') >= 0: result = 'comm_llex' elif basis.find('deg') >= 0: result = 'comm_deg' elif basis.find('revz') >= 0: result = 'comm_revz' else: result = 'comm_revz' if basis.find('long') >= 0: result = result + '_long' elif p == 2 and basis.find('wood') >= 0: if basis.find('y') >= 0: result = 'woody' else: result = 'woodz' elif p == 2 and basis.find('arnon') >= 0: if basis.find('c') >= 0: result = 'arnonc' else: result = 'arnona' if basis.find('long') >= 0: result = result + '_long' elif p == 2 and basis.find('wall') >= 0: result = 'wall' if basis.find('long') >= 0: result = result + '_long' else: raise ValueError, "%s is not a recognized basis at the prime %s." % (basis, p) return result
• ## new file sage/algebras/steenrod_algebra_bases.py

diff -r c25e04ebfb67 -r 9fbc77f226c8 sage/algebras/steenrod_algebra_bases.py
 - r""" Steenrod algebra bases AUTHORS: - John H. Palmieri (2008-07-30: version 0.9) This package defines functions for computing various bases of the Steenrod algebra, and for converting between the Milnor basis and any other basis. This packages implements a number of different bases, at least at the prime 2.  The Milnor and Serre-Cartan bases are the most familiar and most standard ones, and all of the others are defined in terms of one of these.  The bases are described in the documentation for the function \code{steenrod_algebra_basis}; also see the papers by Monks [M] and Wood [W] for more information about them.  For commutator bases, see the preprint by Palmieri and Zhang [PZ]. * 'milnor': Milnor basis. * 'serre-cartan' or 'adem' or 'admissible': Serre-Cartan basis. The other bases are as follows; these are only defined when $p=2$: * 'wood_y': Wood's Y basis. * 'wood_z': Wood's Z basis. * 'wall', 'wall_long': Wall's basis. * 'arnon_a', 'arnon_a_long': Arnon's A basis. * 'arnon_c': Arnon's C basis. * 'pst', 'pst_rlex', 'pst_llex', 'pst_deg', 'pst_revz': various $P^s_t$-bases. * 'comm', 'comm_rlex', 'comm_llex', 'comm_deg', 'comm_revz', or these with '_long' appended: various commutator bases. EXAMPLES: sage: steenrod_algebra_basis(7,'milnor') (Sq(0,0,1), Sq(1,2), Sq(4,1), Sq(7)) sage: steenrod_algebra_basis(5)   # milnor basis is the default (Sq(2,1), Sq(5)) The third (optional) argument to \code{steenrod_algebra_basis} is the prime $p$: sage: steenrod_algebra_basis(9, 'milnor', p=3) (Q_1 P(1), Q_0 P(2)) sage: steenrod_algebra_basis(9, 'milnor', 3) (Q_1 P(1), Q_0 P(2)) sage: steenrod_algebra_basis(17, 'milnor', 3) (Q_2, Q_1 P(3), Q_0 P(0,1), Q_0 P(4)) Other bases: sage: steenrod_algebra_basis(7,'admissible') (Sq^{7}, Sq^{6} Sq^{1}, Sq^{4} Sq^{2} Sq^{1}, Sq^{5} Sq^{2}) sage: [x.basis('milnor') for x in steenrod_algebra_basis(7,'admissible')] [Sq(7), Sq(4,1) + Sq(7), Sq(0,0,1) + Sq(1,2) + Sq(4,1) + Sq(7), Sq(1,2) + Sq(7)] sage: Aw = SteenrodAlgebra(2, basis = 'wall_long') sage: [Aw(x) for x in steenrod_algebra_basis(7,'admissible')] [Sq^{1} Sq^{2} Sq^{4}, Sq^{2} Sq^{4} Sq^{1}, Sq^{4} Sq^{2} Sq^{1}, Sq^{4} Sq^{1} Sq^{2}] sage: steenrod_algebra_basis(13,'admissible',p=3) (beta P^{3}, P^{3} beta) sage: steenrod_algebra_basis(5,'wall') (Q^{2}_{2} Q^{0}_{0}, Q^{1}_{1} Q^{1}_{0}) sage: steenrod_algebra_basis(5,'wall_long') (Sq^{4} Sq^{1}, Sq^{2} Sq^{1} Sq^{2}) sage: steenrod_algebra_basis(5,'pst-rlex') (P^{0}_{1} P^{2}_{1}, P^{1}_{1} P^{0}_{2}) This file also contains a function \code{milnor_convert} which converts elements from the (default) Milnor basis representation to a representation in another basis.  The output is a dictionary which gives the new representation; its form depends on the chosen basis. For example, in the basis of admissible sequences (a.k.a. the Serre-Cartan basis), each basis element is of the form $\text{Sq}^a \text{Sq}^b ...$, and so is represented by a tuple $(a,b,...)$ of integers.  Thus the dictionary has such tuples as keys, with the coefficient of the basis element as the associated value: sage: from sage.algebras.steenrod_algebra_bases import milnor_convert sage: milnor_convert(Sq(2)*Sq(4) + Sq(2)*Sq(5), 'admissible') {(5, 1): 1, (6, 1): 1, (6,): 1} sage: milnor_convert(Sq(2)*Sq(4) + Sq(2)*Sq(5), 'pst') {((1, 1), (2, 1)): 1, ((0, 1), (1, 1), (2, 1)): 1, ((0, 2), (2, 1)): 1} Users shouldn't need to call \code{milnor_convert}; they should use the \code{basis} method to view a single element in another basis, or define a Steenrod algebra with a different default basis and work in that algebra: sage: x = Sq(2)*Sq(4) + Sq(2)*Sq(5) sage: x Sq(3,1) + Sq(4,1) + Sq(6) + Sq(7) sage: x.basis('milnor')  # 'milnor' is the default basis Sq(3,1) + Sq(4,1) + Sq(6) + Sq(7) sage: x.basis('adem') Sq^{5} Sq^{1} + Sq^{6} + Sq^{6} Sq^{1} sage: x.basis('pst') P^{0}_{1} P^{1}_{1} P^{2}_{1} + P^{0}_{2} P^{2}_{1} + P^{1}_{1} P^{2}_{1} sage: A = SteenrodAlgebra(2, basis='pst') sage: A(Sq(2) * Sq(4) + Sq(2) * Sq(5)) P^{0}_{1} P^{1}_{1} P^{2}_{1} + P^{0}_{2} P^{2}_{1} + P^{1}_{1} P^{2}_{1} ************** INTERNAL DOCUMENTATION: If you want to implement a new basis for the Steenrod algebra: In the file 'steenrod_algebra.py': add functionality to \code{get_basis_name}: this should accept as input various synonyms for the basis, and its output should be an element of \code{_steenrod_basis_unique_names} (see the next file). In the file 'steenrod_algebra_element.py': add name of basis to \code{_steenrod_basis_unique_names} add functionality to \code{string_rep}, which probably involves adding a \code{BASIS_mono_to_string} function add functionality to the \code{_basis_dictionary} method In this file ('steenrod_algebra_bases.py'): add appropriate lines to \code{steenrod_algebra_basis} add a function to compute the basis in a given dimension (to be called by \code{steenrod_algebra_basis}) REFERENCES: [M]  K. G. Monks, "Change of basis, monomial relations, and $P^s_t$ bases for the Steenrod algebra," J. Pure Appl. Algebra 125 (1998), no. 1-3, 235--260. [PZ] J. H. Palmieri and J. J. Zhang, "Commutators in the Steenrod algebra," preprint (2008) [W]  R. M. W. Wood, "Problems in the Steenrod algebra," Bull. London Math. Soc. 30 (1998), no. 5, 449--517. """ #***************************************************************************** #       Copyright (C) 2008 John H. Palmieri #  Distributed under the terms of the GNU General Public License (GPL) #***************************************************************************** from sage.rings.integer import Integer from sage.rings.all import GF from steenrod_algebra import SteenrodAlgebra, get_basis_name, \ _steenrod_serre_cartan_basis_names, _steenrod_milnor_basis_names from steenrod_algebra_element import SteenrodAlgebraElement, Sq, pst # cache matrices from convert_to_milnor_matrix here: _conversion_matrices = {} def convert_to_milnor_matrix(n, basis, p=2): r""" Change-of-basis matrix, 'basis' to Milnor, in dimension $n$, at the prime $p$. INPUT: n -- non-negative integer, the dimension basis -- string, the basis from which to convert p -- positive prime number (optional, default 2) OUTPUT: matrix -- change-of-basis matrix, a square matrix over GF(p) (This is not really intended for casual users, so no error checking is made on the integer $n$, the basis name, or the prime.) EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import convert_to_milnor_matrix sage: convert_to_milnor_matrix(5, 'adem') [0 1] [1 1] sage: convert_to_milnor_matrix(45, 'milnor') 111 x 111 dense matrix over Finite Field of size 2 sage: convert_to_milnor_matrix(12,'wall') [1 0 0 1 0 0 0] [1 1 0 0 0 1 0] [0 1 0 1 0 0 0] [0 0 0 1 0 0 0] [1 1 0 0 1 0 0] [0 0 1 1 1 0 1] [0 0 0 0 1 0 1] The function takes an optional argument, the prime $p$ over which to work: sage: convert_to_milnor_matrix(17,'adem',3) [0 0 1 1] [0 0 0 1] [1 1 1 1] [0 1 0 1] sage: convert_to_milnor_matrix(48,'adem',5) [0 1] [1 1] sage: convert_to_milnor_matrix(36,'adem',3) [0 0 1] [0 1 0] [1 2 0] """ if _conversion_matrices.has_key((n, basis, p)): return _conversion_matrices[(n, basis, p)] from sage.matrix.constructor import matrix milnor_base = steenrod_algebra_basis(n,'milnor',p) rows = [] for poly in steenrod_algebra_basis(n,basis,p): for v in milnor_base: for m in v._basis_dictionary('milnor'): key = m if poly._raw['milnor'].has_key(key): rows = rows + [poly._raw['milnor'][key]] else: rows = rows + [0] d = len(milnor_base) _conversion_matrices[(n, basis, p)] = matrix(GF(p),d,d,rows) return matrix(GF(p),d,d,rows) def convert_from_milnor_matrix(n, basis, p=2): r""" Change-of-basis matrix, Milnor to 'basis', in dimension $n$. INPUT: n -- non-negative integer, the dimension basis -- string, the basis to which to convert p -- positive prime number (optional, default 2) OUTPUT: matrix -- change-of-basis matrix, a square matrix over GF(p) (This is not really intended for casual users, so no error checking is made on the integer $n$, the basis name, or the prime.) EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import convert_from_milnor_matrix, convert_to_milnor_matrix sage: convert_from_milnor_matrix(12,'wall') [1 0 0 1 0 0 0] [0 0 1 1 0 0 0] [0 0 0 1 0 1 1] [0 0 0 1 0 0 0] [1 0 1 0 1 0 0] [1 1 1 0 0 0 0] [1 0 1 0 1 0 1] sage: convert_from_milnor_matrix(38,'serre_cartan') 72 x 72 dense matrix over Finite Field of size 2 sage: x = convert_to_milnor_matrix(20,'wood_y') sage: y = convert_from_milnor_matrix(20,'wood_y') sage: x*y [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0] [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1] The function takes an optional argument, the prime $p$ over which to work: sage: convert_from_milnor_matrix(17,'adem',3) [2 1 1 2] [0 2 0 1] [1 2 0 0] [0 1 0 0] """ mat = convert_to_milnor_matrix(n,basis,p) if mat.nrows() != 0: return convert_to_milnor_matrix(n,basis,p).inverse() else: return mat def make_elt_homogeneous(poly): """ Break element of the Steenrod algebra into a list of homogeneous pieces. INPUT: poly -- an element of the Steenrod algebra OUTPUT: list of homogeneous elements of the Steenrod algebra whose sum is poly EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import make_elt_homogeneous sage: make_elt_homogeneous(Sq(2)*Sq(4) + Sq(2)*Sq(5)) [Sq(3,1) + Sq(6), Sq(4,1) + Sq(7)] """ degrees = set([m.degree() for m in poly])   # set of degrees in poly result = [] for d in degrees: result_d = 0 for m in poly: if m.degree() == d: result_d = result_d + m result.append(result_d) return result def milnor_convert(poly, basis): r""" Convert an element of the Steenrod algebra in the Milnor basis to its representation in the chosen basis. INPUT: poly -- element of the Steenrod algebra basis -- basis to which to convert OUTPUT: dict -- dictionary This returns a dictionary of terms of the form (mono: coeff), where mono is a monomial in 'basis'.  The form of mono depends on the chosen basis. EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import milnor_convert sage: milnor_convert(Sq(2)*Sq(4) + Sq(2)*Sq(5), 'adem') {(5, 1): 1, (6, 1): 1, (6,): 1} sage: A3 = SteenrodAlgebra(3) sage: a = A3.Q(1) * A3.P(2,2); a Q_1 P(2,2) sage: milnor_convert(a, 'adem') {(0, 9, 1, 2, 0): 1, (1, 9, 0, 2, 0): 2} sage: milnor_convert(2 * a, 'adem') {(0, 9, 1, 2, 0): 2, (1, 9, 0, 2, 0): 1} sage: (Sq(2)*Sq(4) + Sq(2)*Sq(5)).basis('adem') Sq^{5} Sq^{1} + Sq^{6} + Sq^{6} Sq^{1} sage: a.basis('adem') P^{9} beta P^{2} + 2 beta P^{9} P^{2} sage: milnor_convert(Sq(2)*Sq(4) + Sq(2)*Sq(5), 'pst') {((1, 1), (2, 1)): 1, ((0, 1), (1, 1), (2, 1)): 1, ((0, 2), (2, 1)): 1} sage: (Sq(2)*Sq(4) + Sq(2)*Sq(5)).basis('pst') P^{0}_{1} P^{1}_{1} P^{2}_{1} + P^{0}_{2} P^{2}_{1} + P^{1}_{1} P^{2}_{1} """ from sage.matrix.constructor import matrix p = poly._prime basis_name = get_basis_name(basis, p) if basis_name.find('long') >= 0: basis_name = basis_name.rsplit('_', 1)[0] if basis_name == 'milnor': return poly._raw['milnor'] dict = {} for x in make_elt_homogeneous(poly): dim = x.degree() vec = [] for mono in steenrod_algebra_basis(dim,'milnor',p): if mono._raw['milnor'].keys()[0] in x._raw['milnor'].keys(): for entry in mono._raw['milnor']: coeff =  x._raw['milnor'][entry] vec = vec + [coeff] else: vec = vec + [0] new_vec = (matrix(GF(p),1,len(vec),vec) * convert_from_milnor_matrix(dim, basis_name, p)).row(0) for (mono,coeff) in zip(steenrod_algebra_basis(dim, basis_name, p), new_vec): if coeff != 0: old_dict = mono._raw[basis_name] for entry in old_dict: dict[entry] = coeff return dict def steenrod_algebra_basis(n, basis='milnor', p=2): r""" Basis for the Steenrod algebra in degree $n$. INPUT: n -- non-negative integer basis -- string, which basis to use  (optional, default = 'milnor') p -- positive prime number (optional, default = 2) OUTPUT: tuple of basis elements for the Steenrod algebra in dimension n The choices for the string basis are as follows: * 'milnor': Milnor basis.  When $p=2$, the Milnor basis consists of symbols of the form $\text{Sq}(m_1, m_2, ..., m_t)$, where each $m_i$ is a non-negative integer and if $t>1$, then the last entry $m_t > 0$.  When $p$ is odd, the Milnor basis consists of symbols of the form $Q_{e_1} Q_{e_2} ... \mathcal{P}(m_1, m_2, ..., m_t)$, where $0 \leq e_1 < e_2 < ...$, each $m_i$ is a non-negative integer, and if $t>1$, then the last entry $m_t > 0$. * 'serre-cartan' or 'adem' or 'admissible': Serre-Cartan basis. The Serre-Cartan basis consists of 'admissible monomials' in the Steenrod operations.  Thus at the prime 2, it consists of monomials $\text{Sq}^{m_1} \text{Sq}^{m_2} ... \text{Sq}^{m_t}$ with $m_i \geq 2m_{i+1}$ for each $i$.  At odd primes, it consists of monomials $\beta^{\epsilon_0} \mathcal{P}^{s_1} \beta^{\epsilon_1} \mathcal{P}^{s_2} ... \mathcal{P}^{s_k} \beta^{\epsilon_k}$ with each $\epsilon_i$ either 0 or 1, $s_i \geq p s_{i+1} + \epsilon_i$, and $s_k \geq 1$. When $p=2$, the element $\text{Sq}^a$ equals the Milnor element $\text{Sq}(a)$; when $p$ is odd, $\mathcal{P}^a = \mathcal{P}(a)$ and $\beta = Q_0$.  Hence for any Serre-Cartan basis element, one can represent it in the Milnor basis by computing an appropriate product using Milnor multiplication. The rest of these bases are only defined when $p=2$. * 'wood_y': Wood's Y basis.  For pairs of non-negative integers $(m,k)$, let $w(m,k) = \text{Sq}^{2^m (2^{k+1}-1)}$.  Wood's $Y$ basis consists of monomials $w(m_0,k_0) ... w(m_t, k_t)$ with $(m_i,k_i) > (m_{i+1},k_{i+1})$, in left lex order. * 'wood_z': Wood's Z basis.  For pairs of non-negative integers $(m,k)$, let $w(m,k) = \text{Sq}^{2^m (2^{k+1}-1)}$.  Wood's $Z$ basis consists of monomials $w(m_0,k_0) ... w(m_t, k_t)$ with $(m_i+k_i,m_i) > (m_{i+1}+k_{i+1},m_{i+1})$, in left lex order. * 'wall' or 'wall_long': Wall's basis.  For any pair of integers $(m,k)$ with $m \geq k \geq 0$, let $Q^m_k = \text{Sq}^{2^k} \text{Sq}^{2^{k+1}} ... \text{Sq}^{2^m}$.  The elements of Wall's basis are monomials $Q^{m_0}_{k_0} ... Q^{m_t}_{k_t}$ with $(m_i, k_i) > (m_{i+1}, k_{i+1})$, ordered left lexicographically. (Note that $Q^m_k$ is the reverse of the element $X^m_k$ used in defining Arnon's A basis.) The standard way of printing elements of the Wall basis is to write elements in terms of the $Q^m_k$.  If one sets the basis to 'wall_long' instead of 'wall', then each $Q^m_k$ is expanded as a product of factors of the form $\text{Sq}^{2^i}$. * 'arnon_a' or 'arnon_a_long': Arnon's A basis.  For any pair of integers $(m,k)$ with $m \geq k \geq 0$, let $X^m_k = \text{Sq}^{2^m} \text{Sq}^{2^{m-1}} ... \text{Sq}^{2^k}$.  The elements of Arnon's A basis are monomials $X^{m_0}_{k_0} ... X^{m_t}_{k_t}$ with $(m_i, k_i) < (m_{i+1}, k_{i+1})$, ordered left lexicographically. (Note that $X^m_k$ is the reverse of the element $Q^m_k$ used in defining Wall's basis.) The standard way of printing elements of Arnon's A basis is to write elements in terms of the $X^m_k$.  If one sets the basis to 'arnon_a_long' instead of 'arnon_a', then each $X^m_k$ is expanded as a product of factors of the form $\text{Sq}^{2^i}$. * 'arnon_c': Arnon's C basis.  The elements of Arnon's C basis are monomials of the form $\text{Sq}^{t_1} ... \text{Sq}^{t_m}$ where for each $i$, we have $t_i \leq 2t_{i+1}$ and $2^i | t_{m-i}$. * 'pst', 'pst_rlex', 'pst_llex', 'pst_deg', 'pst_revz': various $P^s_t$-bases.  For integers $s \geq 0$ and $t > 0$, the element $P^s_t$ is the Milnor basis element $\text{Sq}(0, ..., 0, 2^s, 0, ...)$, with the nonzero entry in position $t$.  To obtain a $P^s_t$-basis, for each set $\{P^{s_1}_{t_1}, ..., P^{s_k}_{t_k}\}$ of (distinct) $P^s_t$'s, one chooses an ordering and forms the resulting monomial.  The set of all such monomials then forms a basis, and so one gets a basis by choosing an ordering on each monomial. The strings 'rlex', 'llex', etc., correspond to the following orderings.  These are all 'global' -- they give a global ordering on the $P^s_t$'s, not different orderings depending on the monomial.  They order the $P^s_t$'s using the pair of integers $(s,t)$ as follows: * 'rlex': right lexicographic ordering * 'llex': left lexicographic ordering * 'deg': ordered by degree, which is the same as left lexicographic ordering on the pair $(s+t,t)$ * 'revz': left lexicographic ordering on the pair $(s+t,s)$, which is the reverse of the ordering used (on elements in the same degrees as the $P^s_t$'s) in Wood's Z basis: 'revz' stands for 'reversed Z'.  This is the default: 'pst' is the same as 'pst_revz'. * 'comm', 'comm_rlex', 'comm_llex', 'comm_deg', 'comm_revz', or any of these with '_long' appended: various commutator bases. Let $c_{i,1} = \text{Sq}^{2^i}$, let $c_{i,2} = [c_{i,1}, c_{i+1,1}]$, and inductively define $c_{i,k} = [c_{i,k-1}, c_{i+k-1,1}]$.  Thus $c_{i,k}$ is a $k$-fold iterated commutator of the elements $\text{Sq}^{2^i}$, ..., $\text{Sq}^{2^{i+k-1}}$.  Note that $\dim c_{i,k} = \dim P^i_k$. To obtain a commutator basis, for each set $\{c_{s_1,t_1}, ..., c_{s_k,t_k}\}$ of (distinct) $c_{s,t}$'s, one chooses an ordering and forms the resulting monomial.  The set of all such monomials then forms a basis, and so one gets a basis by choosing an ordering on each monomial.  The strings 'rlex', etc., have the same meaning as for the orderings on $P^s_t$-bases.  As with the $P^s_t$-bases, 'comm_revz' is the default: 'comm' means 'comm_revz'. The commutator bases have alternative representations, obtained by appending 'long' to their names: instead of, say, $c_{2,2}$, the representation is $s_{48}$, indicating the commutator of $\text{Sq}^4$ and $\text{Sq}^8$, and $c_{0,3}$, which is equal to $[[\text{Sq}^1, \text{Sq}^2], \text{Sq}^4]$, is written as $s_{124}$. EXAMPLES: sage: steenrod_algebra_basis(7,'milnor') (Sq(0,0,1), Sq(1,2), Sq(4,1), Sq(7)) sage: steenrod_algebra_basis(5)   # milnor basis is the default (Sq(2,1), Sq(5)) The third (optional) argument to 'steenrod_algebra_basis' is the prime p: sage: steenrod_algebra_basis(9, 'milnor', p=3) (Q_1 P(1), Q_0 P(2)) sage: steenrod_algebra_basis(9, 'milnor', 3) (Q_1 P(1), Q_0 P(2)) sage: steenrod_algebra_basis(17, 'milnor', 3) (Q_2, Q_1 P(3), Q_0 P(0,1), Q_0 P(4)) Other bases: sage: steenrod_algebra_basis(7,'admissible') (Sq^{7}, Sq^{6} Sq^{1}, Sq^{4} Sq^{2} Sq^{1}, Sq^{5} Sq^{2}) sage: [x.basis('milnor') for x in steenrod_algebra_basis(7,'admissible')] [Sq(7), Sq(4,1) + Sq(7), Sq(0,0,1) + Sq(1,2) + Sq(4,1) + Sq(7), Sq(1,2) + Sq(7)] sage: steenrod_algebra_basis(13,'admissible',p=3) (beta P^{3}, P^{3} beta) sage: steenrod_algebra_basis(5,'wall') (Q^{2}_{2} Q^{0}_{0}, Q^{1}_{1} Q^{1}_{0}) sage: steenrod_algebra_basis(5,'wall_long') (Sq^{4} Sq^{1}, Sq^{2} Sq^{1} Sq^{2}) sage: steenrod_algebra_basis(5,'pst-rlex') (P^{0}_{1} P^{2}_{1}, P^{1}_{1} P^{0}_{2}) """ if not (isinstance(n, (Integer, int)) and n >= 0): raise ValueError, "%s is not a non-negative integer." % n basis_name = get_basis_name(basis, p) if basis_name.find('long') >= 0: long = True basis_name = basis_name.rsplit('_', 1)[0] else: long = False ## Milnor basis if basis_name == 'milnor': return milnor_basis(n,p) ## Serre-Cartan basis elif basis_name == 'serre-cartan': return serre_cartan_basis(n,p) ## Atomic bases elif p == 2 and (basis_name == 'woody' or basis_name == 'woodz' or basis_name == 'wall' or basis_name == 'arnona' or basis_name.find('pst') >= 0 or basis_name.find('comm') >= 0): return atomic_basis(n, basis_name, long=long) ## Arnon 'C' basis elif p == 2 and basis == 'arnonc': return arnonC_basis(n) def restricted_partitions(n, list, no_repeats=False): """ List of 'restricted' partitions of n: partitions with parts taken from list. INPUT: n -- non-negative integer list -- list of positive integers no_repeats -- boolean (optional, default = False), if True, only return partitions with no repeated parts This seems to be faster than RestrictedPartitions, although I don't know why.  Maybe because all I want is the list of partitions (with each partition represented as a list), not the extra stuff provided by RestrictedPartitions. EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import restricted_partitions sage: restricted_partitions(10, [7,5,1]) [[7, 1, 1, 1], [5, 5], [5, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] sage: restricted_partitions(10, [6,5,4,3,2,1], no_repeats=True) [[6, 4], [6, 3, 1], [5, 4, 1], [5, 3, 2], [4, 3, 2, 1]] sage: restricted_partitions(10, [6,4,2]) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] sage: restricted_partitions(10, [6,4,2], no_repeats=True) [[6, 4]] 'list' may have repeated elements.  If 'no_repeats' is False, this has no effect.  If 'no_repeats' is True, and if the repeated elements appear consecutively in 'list', then each element may be used only as many times as it appears in 'list': sage: restricted_partitions(10, [6,4,2,2], no_repeats=True) [[6, 4], [6, 2, 2]] sage: restricted_partitions(10, [6,4,2,2,2], no_repeats=True) [[6, 4], [6, 2, 2], [4, 2, 2, 2]] (If the repeated elements don't appear consecutively, the results are likely meaningless, containing several partitions more than once, for example.) In the following examples, 'no_repeats' is False: sage: restricted_partitions(10, [6,4,2]) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] sage: restricted_partitions(10, [6,4,2,2,2]) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] sage: restricted_partitions(10, [6,4,4,4,2,2,2,2,2,2]) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] """ if n < 0: return [] elif n == 0: return [[]] else: results = [] if no_repeats: index = 1 else: index = 0 old_i = 0 for i in list: if old_i != i: for sigma in restricted_partitions(n-i, list[index:], no_repeats): results.append([i] + sigma) index += 1 old_i = i return results def list_to_hist(list): """ Given list of positive integers [a,b,c,...], return corresponding 'histogram'. That is, in the output [n1, n2, ...], n1 is the number of 1's in the original list, n2 is the number of 2's, etc. INPUT: list -- list of positive integers OUTPUT: answer -- list of non-negative integers EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import list_to_hist sage: list_to_hist([1,2,3,4,2,1,2]) [2, 3, 1, 1] sage: list_to_hist([2,2,2,2]) [0, 4] """ if len(list) == 0: return list else: answer = [0] * max(list) for i in list: answer[i-1] += 1 return answer ######################################################## # Functions for defining bases. They work like this: # First, check to see if the basis has been computed already: look for #   the cached result in _steenrod_bases.  If so, filter the result #   based on the optional argument bound. # Dictionary in which to cache results: _steenrod_bases = {} # Second, if there is no cached result, build directly or recursively, #   whichever is more convenient.  The details are documented for each #   basis.  As each basis element is constructed, so is its dictionary #   for the current basis.  For example, in 'serre_cartan_basis', each #   new element is obtained by taking an element in a lower-dimensional #   basis and multiplying, on the right, by Sq(last).  Call this element #   'new'.  Then set new._raw['serre-cartan'] to be the dictionary of #   Serre-Cartan monomials describing this element.  This makes #   conversion of the element to the chosen basis instantaneous; this is #   useful on its own, and crucial for computing change-of-basis #   matrices for general conversion. # Finally, if there is no cached result, cache the result. def xi_degrees(n,p=2): r""" Decreasing list of degrees of the xi_i's, starting in degree n. INPUT: n -- integer OUTPUT: list -- list of integers When p=2: decreasing list of the degrees of the $\xi_i$'s with degree at most n. At odd primes: decreasing list of these degrees, each divided by 2(p-1). EXAMPLES: sage: sage.algebras.steenrod_algebra_bases.xi_degrees(17) [15, 7, 3, 1] sage: sage.algebras.steenrod_algebra_bases.xi_degrees(17,p=3) [13, 4, 1] sage: sage.algebras.steenrod_algebra_bases.xi_degrees(400,p=17) [307, 18, 1] """ result = [] deg = 1 while deg <= n: result.insert(0,deg) deg = p*deg + 1 return result def milnor_basis(n, p=2): r""" Milnor basis in dimension $n$. INPUT: n -- non-negative integer p -- positive prime number (optional, default 2) OUTPUT: tuple of mod p Milnor basis elements in dimension n At the prime 2, the Milnor basis consists of symbols of the form $\text{Sq}(m_1, m_2, ..., m_t)$, where each $m_i$ is a non-negative integer and if $t>1$, then $m_t \neq 0$. At odd primes, it consists of symbols of the form $Q_{e_1} Q_{e_2} ... P(m_1, m_2, ..., m_t)$, where $0 \leq e_1 < e_2 < ...$, each $m_i$ is a non-negative integer, and if $t>1$, then $m_t \neq 0$. EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import milnor_basis sage: milnor_basis(7) (Sq(0,0,1), Sq(1,2), Sq(4,1), Sq(7)) sage: milnor_basis(7, 2) (Sq(0,0,1), Sq(1,2), Sq(4,1), Sq(7)) sage: milnor_basis(9, 3) (Q_1 P(1), Q_0 P(2)) sage: milnor_basis(17, 3) (Q_2, Q_1 P(3), Q_0 P(0,1), Q_0 P(4)) sage: milnor_basis(48, p=5) (P(0,1), P(6)) sage: len(milnor_basis(100,3)) 13 sage: len(milnor_basis(200,7)) 0 sage: len(milnor_basis(240,7)) 3 """ if n == 0: if p == 2: return (Sq(0),) else: return (SteenrodAlgebra(p).P(0),) else: result = [] if _steenrod_bases.has_key(('milnor',n,p)): result = _steenrod_bases[('milnor',n,p)] else: if p == 2: # build basis from partitions of n, with parts taken # from the list xi_degrees(n) for sigma in restricted_partitions(n,xi_degrees(n)): sigma_exp = list_to_hist(sigma) deg = 1 i = 0 exponents = [] while deg <= len(sigma_exp): exponents.insert(i,sigma_exp[deg-1]) deg = 2*deg + 1 i = i + 1 result.append(SteenrodAlgebraElement({tuple(exponents): 1})) _steenrod_bases[('milnor',n,p)] = tuple(result) else:  # p odd # first find the P part of each basis element. # in this part of the code (the P part), all dimensions are # divided by 2(p-1). for dim in range(n/(2*(p-1)) + 1): if dim == 0: P_result = [[0]] else: P_result = [] for sigma in restricted_partitions(dim, xi_degrees(dim,p)): sigma_exp = list_to_hist(sigma) deg = 1 i = 0 p_mono = [] while deg <= len(sigma_exp): p_mono.insert(i, sigma_exp[deg-1]) deg = p*deg + 1 i = i + 1 if len(p_mono) > 0: P_result.append(p_mono) # now find the Q part of the basis element. # dimensions here are back to normal. for p_mono in P_result: deg = n - 2*dim*(p-1) q_degrees = [1+2*(p-1)*d for d in xi_degrees((deg - 1)/(2*(p-1)), p)] + [1] q_degrees_decrease = q_degrees q_degrees.reverse() if deg % (2*(p-1)) <= len(q_degrees): # if this inequality fails, no way to have a partition # with distinct parts. for sigma in restricted_partitions(deg, q_degrees_decrease, no_repeats = True): index = 0 q_mono = [] for q in q_degrees: if q in sigma: q_mono.append(index) index += 1 result.append(SteenrodAlgebraElement( {(tuple(q_mono), tuple(p_mono)): 1}, p)) _steenrod_bases[('milnor',n,p)] = tuple(result) return tuple(result) def serre_cartan_basis(n, p=2, bound=1): r""" Serre-Cartan basis in dimension $n$. INPUT: n -- non-negative integer bound -- positive integer (optional) prime -- positive prime number (optional, default 2) OUTPUT: tuple of mod p Serre-Cartan basis elements in dimension n The Serre-Cartan basis consists of 'admissible monomials in the Steenrod squares'.  Thus at the prime 2, it consists of monomials $\text{Sq}^{m_1} \text{Sq}^{m_2} ... \text{Sq}^{m_t}$ with $m_i \geq 2m_{i+1}$ for each $i$.  At odd primes, it consists of monomials $\beta^{e_0} P^{s_1} \beta^{e_1} P^{s_2} ... P^{s_k} \beta^{e_k}$ with each $e_i$ either 0 or 1, $s_i \geq p s_{i+1} + e_i$, and $s_k \geq 1$. EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import serre_cartan_basis sage: serre_cartan_basis(7) (Sq^{7}, Sq^{6} Sq^{1}, Sq^{4} Sq^{2} Sq^{1}, Sq^{5} Sq^{2}) sage: serre_cartan_basis(13,3) (beta P^{3}, P^{3} beta) sage: serre_cartan_basis(50,5) (beta P^{5} P^{1} beta, beta P^{6} beta) If optional argument bound is present, include only those monomials whose last term is at least bound (when p=2), or those for which $s_k - epsilon_k >= bound$ (when p is odd). sage: serre_cartan_basis(7, bound=2) (Sq^{7}, Sq^{5} Sq^{2}) sage: serre_cartan_basis(13, 3, bound=3) (beta P^{3},) """ if not (isinstance(bound, (Integer, int)) and bound >= 1): raise ValueError, "%s is not a positive integer." % bound A = SteenrodAlgebra(p, 'serre-cartan') if n == 0: if p == 2: return (Sq(0),) else: return (A.P(0),) else: if _steenrod_bases.has_key(('serre-cartan',n,p)): result = [] lookup = _steenrod_bases[('serre-cartan',n,p)] for vec in lookup: for mono in vec._basis_dictionary('serre-cartan'): if (bound == 1) or \ (p == 2 and mono[-1] >= bound) or \ (p > 2 and (len(mono)<2 or mono[-2] - mono[-1] >= bound)): result.append(vec) else: if p == 2: # Build basis recursively.  last = last term. # last is >= bound, and we will append (last,) to the end of # elements from serre_cartan_basis (n - last, bound=2 * last). # This means that 2 last <= n - last, or 3 last <= n. result = [A(Sq(n))] for last in range(bound, 1+n/3): for vec in serre_cartan_basis(n - last, bound = 2*last): new = vec * Sq(last) for m in vec._basis_dictionary('serre-cartan'): sc_mono = m new._raw['serre-cartan'] = {sc_mono + (last,): 1} result.append(A(new)) if bound == 1: _steenrod_bases[('serre-cartan',n,p)] = tuple(result) else: # p odd if n % (2 * (p-1)) == 0 and n/(2 * (p-1)) >= bound: a = A.P(int(n/(2 * (p-1)))) a._raw['serre-cartan'] = {(0,int(n/(2 * (p-1))),0): 1} result = [a] elif n == 1: a = A.Q(0) a._raw['serre-cartan'] = {(1,): 1} result = [a] else: result = [] # 2 cases: append P^{last}, or append P^{last} beta # case 1: append P^{last} for last in range(bound, 1+n/(2*(p - 1))): if n - 2*(p-1)*last > 0: for vec in serre_cartan_basis(n - 2*(p-1)*last, p, p*last): new = vec * A.P(last,) for m in vec._basis_dictionary('serre-cartan'): sc_mono = m new._raw['serre-cartan'] = {sc_mono + (last,0): 1} result.append(A(new)) # case 2: append P^{last} beta if bound == 1: bound = 0 for last in range(bound+1, 1+n/(2*(p - 1))): basis = serre_cartan_basis(n - 2*(p-1)*last - 1, p, p*last) for vec in basis: if vec == 1: vec._raw['serre-cartan'] = {(0,): 1} new = vec * A.P(last,) * A.Q(0) for m in vec._basis_dictionary('serre-cartan'): sc_mono = m new._raw['serre-cartan'] = {sc_mono + (last,1): 1} result.append(A(new)) if bound <= 1: _steenrod_bases[('serre-cartan',n,p)] = tuple(result) return tuple(result) def atomic_basis(n, basis, long=False): r""" Basis for dimension $n$ made of elements in 'atomic' degrees: degrees of the form $2^i (2^j - 1)$. INPUT: n -- non-negative integer basis -- string, the name of the basis OUTPUT: tuple of basis elements in dimension n The atomic bases include Wood's Y and Z bases, Wall's basis, Arnon's A basis, the $P^s_t$-bases, and the commutator bases. (All of these bases are constructed similarly, hence their constructions have been consolidated into a single function. Also, See the documentation for 'steenrod_algebra_basis' for descriptions of them.) EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import atomic_basis sage: atomic_basis(6,'woody') (Sq^{2} Sq^{3} Sq^{1}, Sq^{4} Sq^{2}, Sq^{6}) sage: atomic_basis(8,'woodz') (Sq^{4} Sq^{3} Sq^{1}, Sq^{7} Sq^{1}, Sq^{6} Sq^{2}, Sq^{8}) sage: atomic_basis(6,'woodz') == atomic_basis(6, 'woody') True sage: atomic_basis(9,'woodz') == atomic_basis(9, 'woody') False Wall's basis: sage: atomic_basis(6,'wall') (Q^{1}_{1} Q^{1}_{0} Q^{0}_{0}, Q^{2}_{2} Q^{1}_{1}, Q^{2}_{1}) Elements of the Wall basis have an alternate, 'long' representation as monomials in the $\text{Sq}^{2^n}$s: sage: atomic_basis(6, 'wall', long=True) (Sq^{2} Sq^{1} Sq^{2} Sq^{1}, Sq^{4} Sq^{2}, Sq^{2} Sq^{4}) Arnon's A basis: sage: atomic_basis(7,'arnona') (X^{0}_{0} X^{1}_{1} X^{2}_{2}, X^{0}_{0} X^{2}_{1}, X^{1}_{0} X^{2}_{2}, X^{2}_{0}) These also have a 'long' representation: sage: atomic_basis(7,'arnona',long=True) (Sq^{1} Sq^{2} Sq^{4}, Sq^{1} Sq^{4} Sq^{2}, Sq^{2} Sq^{1} Sq^{4}, Sq^{4} Sq^{2} Sq^{1}) $P^s_t$-bases: sage: atomic_basis(7,'pst_rlex') (P^{0}_{1} P^{1}_{1} P^{2}_{1}, P^{0}_{1} P^{1}_{2}, P^{2}_{1} P^{0}_{2}, P^{0}_{3}) sage: atomic_basis(7,'pst_llex') (P^{0}_{1} P^{1}_{1} P^{2}_{1}, P^{0}_{1} P^{1}_{2}, P^{0}_{2} P^{2}_{1}, P^{0}_{3}) sage: atomic_basis(7,'pst_deg') (P^{0}_{1} P^{1}_{1} P^{2}_{1}, P^{0}_{1} P^{1}_{2}, P^{0}_{2} P^{2}_{1}, P^{0}_{3}) sage: atomic_basis(7,'pst_revz') (P^{0}_{1} P^{1}_{1} P^{2}_{1}, P^{0}_{1} P^{1}_{2}, P^{0}_{2} P^{2}_{1}, P^{0}_{3}) Commutator bases: sage: atomic_basis(7,'comm_rlex') (c_{0,1} c_{1,1} c_{2,1}, c_{0,1} c_{1,2}, c_{2,1} c_{0,2}, c_{0,3}) sage: atomic_basis(7,'comm_llex') (c_{0,1} c_{1,1} c_{2,1}, c_{0,1} c_{1,2}, c_{0,2} c_{2,1}, c_{0,3}) sage: atomic_basis(7,'comm_deg') (c_{0,1} c_{1,1} c_{2,1}, c_{0,1} c_{1,2}, c_{0,2} c_{2,1}, c_{0,3}) sage: atomic_basis(7,'comm_revz') (c_{0,1} c_{1,1} c_{2,1}, c_{0,1} c_{1,2}, c_{0,2} c_{2,1}, c_{0,3}) Long representations of commutator bases: sage: atomic_basis(7,'comm_revz', long=True) (s_{1} s_{2} s_{4}, s_{1} s_{24}, s_{12} s_{4}, s_{124}) """ def degree_dictionary(n, basis): """ Dictionary of atomic degrees for basis up to degree n. The keys for the dictionary are the atomic degrees -- the numbers of the form 2^i (2^j - 1) -- which are less than or equal to n.  The value associated to such a degree depends on basis; it has the form ((s,t), x), where (s,t) is a pair of integers which indexes the corresponding element, and x is the element in the Milnor basis. """ dict = {} if basis.find('wood') >= 0: k=0 m=0 deg = 2**m * (2**(k+1) - 1) while deg <= n: dict[deg] = ((m,k), Sq(deg)) if m>0: m = m - 1 k = k + 1 else: m = k + 1 k = 0 deg = 2**m * (2**(k+1) - 1) elif basis.find('wall') >= 0 or basis.find('arnon') >= 0: k=0 m=0 deg = 2**k * (2**(m-k+1) - 1) while deg <= n: dict[deg] = ((m,k), Q(m,k,basis)) if k == 0: m = m + 1 k = m else: k = k - 1 deg = 2**k * (2**(m-k+1) - 1) elif basis.find('pst') >= 0 or basis.find('comm') >= 0: s=0 t=1 deg = 2**s * (2**t - 1) while deg <= n: if basis.find('pst') >= 0: dict[deg] = ((s,t), pst(s,t)) else:  # comm dict[deg] = ((s,t), commutator(s,t)) if s == 0: s = t t = 1 else: s = s - 1 t = t + 1 deg = 2**s * (2**t - 1) return dict def sorting_pair(s,t,basis):   # pair used for sorting the basis if basis.find('wood') >= 0 and basis.find('z') >= 0: return (-s-t,-s) elif basis.find('wood') >= 0 or basis.find('wall') >= 0 or \ basis.find('arnon') >= 0: return (-s,-t) elif basis.find('rlex') >= 0: return (t,s) elif basis.find('llex') >= 0: return (s,t) elif basis.find('deg') >= 0: return (s+t,t) elif basis.find('revz') >= 0: return (s+t,s) from sage.misc.misc import prod if long: basis_long_name = basis + "_long" else: basis_long_name = basis A = SteenrodAlgebra(2, basis_long_name) if n == 0: return (A.Sq(0),) else: result = [] if _steenrod_bases.has_key((basis,n)): if basis == basis_long_name: result = _steenrod_bases[(basis,n)] else: result = tuple([A(a) for a in _steenrod_bases[(basis,n)]]) else: degrees_etc = degree_dictionary(n, basis) degrees = degrees_etc.keys() for sigma in restricted_partitions(n, degrees, no_repeats=True): big_list = [degrees_etc[part] for part in sigma] big_list.sort(key=lambda x: x[0], cmp = lambda x, y: cmp(sorting_pair(x[0], x[1], basis), sorting_pair(y[0], y[1], basis))) # reverse = True) # arnon: sort like wall, then reverse end result if basis.find('arnon') >= 0: big_list.reverse() list_of_pairs = [d[0] for d in big_list] mono_list = [d[1] for d in big_list] new = prod(mono_list) new._raw[basis] = {tuple(list_of_pairs): 1} result.append(A(new)) _steenrod_bases[(basis,n)] = tuple(result) return tuple(result) def Q(m,k,basis): r""" Compute $Q^m_k$ (Wall basis) and $X^m_k$ (Arnon's A basis). INPUT: m,k -- non-negative integers with $m >= k$ basis -- 'wall' or 'arnona' OUTPUT: element of Steenrod algebra If basis is 'wall', this returns $Q^m_k = Sq(2^k) Sq(2^(k+1)) ... Sq(2^m)$.  If basis is 'arnona', it returns the reverse of this: $X^m_k = Sq(2^m) ... Sq(2^(k+1)) Sq(2^k)$ EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import Q sage: Q(2,2,'wall') Sq(4) sage: Q(2,2,'arnona') Sq(4) sage: Q(3,2,'wall') Sq(6,2) + Sq(12) sage: Q(3,2,'arnona') Sq(0,4) + Sq(3,3) + Sq(6,2) + Sq(12) """ exponent = 2**k result = Sq(exponent) for i in range(m-k): exponent = exponent * 2 if basis == 'wall': result = result * Sq(exponent) elif basis == 'arnona': result = Sq(exponent) * result return result def arnonC_basis(n,bound=1): r""" Arnon's C basis in dimension $n$. INPUT: n -- non-negative integer bound -- positive integer (optional) OUTPUT: tuple of basis elements in dimension n The elements of Arnon's C basis are monomials of the form $\text{Sq}^{t_1} ... \text{Sq}^{t_m}$ where for each $i$, we have $t_i \leq 2t_{i+1}$ and $2^i | t_{m-i}$. EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import arnonC_basis sage: arnonC_basis(7) (Sq^{7}, Sq^{2} Sq^{5}, Sq^{4} Sq^{3}, Sq^{4} Sq^{2} Sq^{1}) If optional argument bound is present, include only those monomials whose first term is at least as large as bound: sage: arnonC_basis(7,3) (Sq^{7}, Sq^{4} Sq^{3}, Sq^{4} Sq^{2} Sq^{1}) """ if not (isinstance(bound, (Integer, int)) and bound >= 1): raise ValueError, "%s is not a positive integer." % bound A = SteenrodAlgebra(2, 'arnonc') if n == 0: return (A.Sq(0),) else: if _steenrod_bases.has_key(('arnonc',n)): result = [] lookup = _steenrod_bases[('arnonc',n)] for vec in lookup: for mono in vec._basis_dictionary('arnonc'): if mono[0] >= bound: result.append(vec) else: # Build basis recursively.  first = first term. # first is >= bound, and we will prepend (first,) to the # elements from arnonC_basis (n - first, first / 2). # first also must be divisible by 2**(len(old-basis-elt)) # This means that 3 first <= 2 n. result = [A(Sq(n))] for first in range(bound,1+2*n/3): for vec in arnonC_basis(n - first, max(first/2,1)): if len(vec._basis_dictionary('arnonc')) > 1: print "Error in Arnon's C basis!" for m in vec._basis_dictionary('arnonc'): arnonc_mono = m if first % 2**len(arnonc_mono) == 0: new = Sq(first) * vec new._raw['arnonc'] = {(first,) + arnonc_mono: 1} result.append(A(new)) if bound == 1: _steenrod_bases[('arnonc',n)] = tuple(result) return tuple(result) # cache commutators here: _commutators = {} def commutator(s,t): r""" $t$th iterated commutator of consecutive $\text{Sq}^{2^i}$'s. INPUT: s, t: integers OUTPUT: element of the Steenrod algebra If t=1, return $Sq(2^s)$.  Otherwise, return the commutator $[commutator(s,t-1), \text{Sq}(2^{s+t-1})]$. EXAMPLES: sage: from sage.algebras.steenrod_algebra_bases import commutator sage: commutator(1,2) Sq(0,2) sage: commutator(0,4) Sq(0,0,0,1) sage: commutator(2,2) Sq(0,4) + Sq(3,3) NOTES: commutator(0,n) is equal to $\text{Sq}(0,...,0,1)$, with the 1 in the $n$th spot.  commutator(i,n) always has $\text{Sq}(0,...,0,2^i)$, with $2^i$ in the $n$th spot, as a summand, but there may be other terms, as the example of commutator(2,2) illustrates. That is, commutator(s,t) is equal to $P^s_t$, possibly plus other Milnor basis elements. """ if _commutators.has_key((s,t)): return _commutators[(s,t)] if t == 1: answer = Sq(2**s) else: x = commutator(s,t-1) y = Sq(2**(s+t-1)) answer = x*y + y*x _commutators[(s,t)] = answer return answer ############################################################################# def steenrod_basis_error_check(dim,p): """ This performs crude error checking. INPUT: dim -- non-negative integer p -- positive prime number OUTPUT: None This checks to see if the different bases have the same length, and if the change-of-basis matrices are invertible.  If something goes wrong, an error message is printed. This function checks at the prime p as the dimension goes up from 0, in which case the basis functions use the saved basis computations in lower dimensions in the computations.  It also checks as the dimension goes down from the top, in which case it doesn't have access to the saved computations.  (The saved computations are deleted first: the cache _steenrod_bases is set to {} before doing the computations.) EXAMPLES: sage: sage.algebras.steenrod_algebra_bases.steenrod_basis_error_check(12,2) p=2, in decreasing order of dimension, starting in dimension 12. down to dimension  10 down to dimension  5 p=2, now in increasing order of dimension, up to dimension 12 up to dimension  0 up to dimension  5 up to dimension  10 done checking sage: sage.algebras.steenrod_algebra_bases.steenrod_basis_error_check(30,3) p=3, in decreasing order of dimension, starting in dimension 30. down to dimension  30 down to dimension  25 down to dimension  20 down to dimension  15 down to dimension  10 down to dimension  5 p=3, now in increasing order of dimension, up to dimension 30 up to dimension  0 up to dimension  5 up to dimension  10 up to dimension  15 up to dimension  20 up to dimension  25 done checking """ global _steenrod_bases _steenrod_bases = {} if p == 2: bases = ('adem','woody', 'woodz', 'wall', 'arnona', 'arnonc', 'pst_rlex', 'pst_llex', 'pst_deg', 'pst_revz', 'comm_rlex', 'comm_llex', 'comm_deg', 'comm_revz') else: bases = ('adem', 'milnor') print "p=%s, in decreasing order of dimension, starting in dimension %s." % (p, dim) for i in range(dim,0,-1): if i % 5 == 0: print "down to dimension ", i for B in bases: if len(steenrod_algebra_basis(i,'milnor')) != len(steenrod_algebra_basis(i,B)): print "problem with milnor/" + B + " in dimension ", i mat = convert_to_milnor_matrix(i,B,p) if mat.nrows() != 0 and not mat.is_invertible(): print "%s invertibility problem in dim %s at the prime %s" % (B, i, p) _steenrod_bases = {} print "p=%s, now in increasing order of dimension, up to dimension %s" % (p, dim) for i in range(dim): if i % 5 == 0: print "up to dimension ", i for B in bases: if len(steenrod_algebra_basis(i,'milnor')) != len(steenrod_algebra_basis(i,B)): print "problem with milnor/" + B + " in dimension ", i mat = convert_to_milnor_matrix(i,B,p) if mat.nrows() != 0 and not mat.is_invertible(): print "%s invertibility problem in dim %s at the prime %s" % (B, i, p) print "done checking"
• ## new file sage/algebras/steenrod_algebra_element.py

diff -r c25e04ebfb67 -r 9fbc77f226c8 sage/algebras/steenrod_algebra_element.py
 - r""" Steenrod algebra elements AUTHORS: - John H. Palmieri (2008-07-30: version 0.9) This package provides for basic algebra with elements in the mod $p$ Steenrod algebra.  In this package, elements in the Steenrod algebra are represented, by default, using the Milnor basis. EXAMPLES: Basic arithmetic, $p=2$.  To construct an element of the mod 2 Steenrod algebra, use the function \code{Sq}: sage: a = Sq(1,2) sage: b = Sq(4,1) sage: z = a + b sage: z Sq(1,2) + Sq(4,1) sage: Sq(4) * Sq(1,2) Sq(1,1,1) + Sq(2,3) + Sq(5,2) sage: z**2          # non-negative exponents work as they should Sq(1,2,1) + Sq(4,1,1) sage: z**0 Sq(0) Basic arithmetic, $p>2$.  To construct an element of the mod $p$ Steenrod algebra when $p$ is odd, you should first define a Steenrod algebra, using the \code{SteenrodAlgebra} command: sage: SteenrodAlgebra()   # 2 is the default prime mod 2 Steenrod algebra sage: A3 = SteenrodAlgebra(3) sage: A3 mod 3 Steenrod algebra Having done this, the newly created algebra \code{A3} has methods \code{Q} and \code{P} which construct elements of \code{A3}: sage: c = A3.Q(1,3,6); c Q_1 Q_3 Q_6 sage: d = A3.P(2,0,1); d P(2,0,1) sage: c * d Q_1 Q_3 Q_6 P(2,0,1) sage: e = A3.P(3) sage: d * e P(5,0,1) sage: e * d P(1,1,1) + P(5,0,1) sage: c * c 0 sage: e ** 3 2 P(1,2) Note that one can construct an element like \code{c} above in one step, without first constructing the algebra: sage: c = SteenrodAlgebra(3).Q(1,3,6) sage: c Q_1 Q_3 Q_6 And of course, you can do similar constructions with the mod 2 Steenrod algebra: sage: A = SteenrodAlgebra(2); A mod 2 Steenrod algebra sage: A.Sq(2,3,5) Sq(2,3,5) sage: A.P(2,3,5)   # when p=2, P = Sq Sq(2,3,5) sage: A.Q(1,4)     # when p=2, this gives a product of Milnor primitives Sq(0,1,0,0,1) Regardless of the prime, each element has an \code{excess}, and if the element is homogeneous, a \code{degree}.  The excess of $\text{Sq}(i_1,i_2,i_3,...)$ is $i_1 + i_2 + i_3 + ...$; when $p$ is odd, the excess of $Q_{0}^{e_0} Q_{1}^{e_1} ... \mathcal{P}(r_1, r_2, ...)$ is $\sum e_i + 2 \sum r_i$.  The excess of a linear combination of Milnor basis elements is the minimum of the excesses of those basis elements. The degree of $\text{Sq}(i_1,i_2,i_3,...)$ is $sum (2^n-1) i_n$, and when $p$ is odd, the degree of $Q_{0}^{\epsilon_0} Q_{1}^{\epsilon_1} ... \mathcal{P}(r_1, r_2, ...)$ is $\sum \epsilon_i (2p^i - 1) + \sum r_j (2p^j - 2)$.  The degree of a linear combination of such terms is only defined if the terms all have the same degree. Here are some simple examples: sage: z = Sq(1,2) + Sq(4,1) sage: z.degree() 7 sage: (Sq(0,0,1) + Sq(5,3)).degree() Element is not homogeneous. sage: Sq(7,2,1).excess() 10 sage: z.excess() 3 sage: B = SteenrodAlgebra(3) sage: x = B.Q(1,4) sage: y = B.P(1,2,3) sage: x.degree() 166 sage: x.excess() 2 sage: y.excess() 12 Elements have a \code{weight} in the May filtration, which (when $p=2$) is related to the \code{height} function defined by Wall: sage: Sq(2,1,5).may_weight() 9 sage: Sq(2,1,5).wall_height() [2, 3, 2, 1, 1] sage: b = Sq(4)*Sq(8) + Sq(8)*Sq(4) sage: b.may_weight() 2 sage: b.wall_height() [0, 0, 1, 1] Odd primary May weights: sage: A5 = SteenrodAlgebra(5) sage: a = A5.Q(1,2,4) sage: b = A5.P(1,2,1) sage: a.may_weight() 10 sage: b.may_weight() 8 sage: (a * b).may_weight() 18 sage: A5.P(0,0,1).may_weight() 3 Since the Steenrod algebra is a Hopf algebra, every element has an antipode. sage: d = Sq(0,0,1); d Sq(0,0,1) sage: d.antipode() Sq(0,0,1) sage: Sq(4).antipode() Sq(1,1) + Sq(4) sage: (Sq(4) * Sq(2)).antipode() Sq(6) sage: SteenrodAlgebra(7).P(3,1).antipode() 4 P(3,1) + 5 P(11) Applying the antipode twice returns the original element: sage: y = Sq(8)*Sq(4) sage: y == (y.antipode()).antipode() True You can treat elements of the Steenrod algebra like lists of Milnor basis elements: sage: y = Sq(4) * Sq(1,2); y Sq(1,1,1) + Sq(2,3) + Sq(5,2) sage: for m in y: m Sq(1,1,1) Sq(2,3) Sq(5,2) sage: [(m.degree(),m.excess()) for m in y] [(11, 3), (11, 5), (11, 7)] Once you've define a Steenrod algebra, the method \code{pst} is another way to define elements of it: \code{pst(s,t)} defines the Margolis element $P^{s}_{t}$, the basis element $\mathcal{P}(0,...,0,p^s)$ with $p^s$ in position $t$: sage: A2 = SteenrodAlgebra(2) sage: Q2 = A2.pst(0,3) sage: Q2 Sq(0,0,1) sage: Q2*Q2 0 sage: A2.pst(1,2) == Sq(2)*Sq(4) + Sq(4)*Sq(2) True sage: A5 = SteenrodAlgebra(5) sage: A5.pst(2,2) P(0,25) There are a number of different bases available in which to represent elements of the Steenrod algebra.  When $p>2$, the choices are the Milnor basis ('milnor') or the Serre-Cartan basis ('serre-cartan' or 'adem' or 'admissible').  When $p=2$, the choices are those, along with Wood's Y basis ('wood_y'), Wood's Z basis ('wood_z'), Wall's basis ('wall' or 'wall_long'), Arnon's A basis ('arnon_a' or 'arnon_a_long'), Arnon's C basis ('arnon_c'), various $P^s_t$ bases ('pst_ORDER' for various values of ORDER), and various commutator bases ('comm_ORDER' or 'comm_ORDER_long' for various values of ORDER). See documentation for the function \code{steenrod_algebra_basis} for full descriptions of these bases. To access representations of elements with respect to these different bases, you can either use the \code{basis} method for an element, or define a Steenrod algebra with respect to a particular basis and then use that: sage: c = Sq(2) * Sq(1); c Sq(0,1) + Sq(3) sage: c.basis('serre-cartan') Sq^{2} Sq^{1} sage: c.basis('milnor') Sq(0,1) + Sq(3) sage: adem = SteenrodAlgebra(2, 'serre-cartan') sage: x = Sq(7,3,1)   # top class in the subalagebra A(2) sage: adem(x) Sq^{17} Sq^{5} Sq^{1} sage: SteenrodAlgebra(2, 'pst')(x) P^{0}_{1} P^{0}_{2} P^{1}_{1} P^{0}_{3} P^{1}_{2} P^{2}_{1} Multiplication works within bases: sage: adem = SteenrodAlgebra(2, 'adem') sage: x = adem.Sq(5) sage: y = adem.Sq(1) sage: x * y Sq^{5} Sq^{1} Multiplying elements defined with respect to two different bases may have unpredictable results (as far as the basis in which the result is printed): sage: milnor = SteenrodAlgebra(2, 'milnor') sage: xm = milnor.Sq(5) sage: ym = milnor.Sq(1) sage: xm * ym Sq(3,1) sage: xm * y Sq^{5} Sq^{1} sage: x * ym Sq^{5} Sq^{1} Several of these bases ('arnon_a', 'wall', 'comm') have alternate, longer, representations.  These provide ways of expressing elements of the Steenrod algebra in terms of the $\text{Sq}^{2^n}$. sage: Sq(6).basis('arnon_a_long') Sq^{1} Sq^{2} Sq^{1} Sq^{2} + Sq^{2} Sq^{4} sage: Sq(6).basis('wall_long') Sq^{2} Sq^{1} Sq^{2} Sq^{1} + Sq^{2} Sq^{4} sage: SteenrodAlgebra(2,'comm_deg_long')(Sq(6)) s_{1} s_{2} s_{12} + s_{2} s_{4} ************** INTERNAL DOCUMENTATION: Here are details on the class \code{SteenrodAlgebraElement} (for people who want to delve into or extend the code): Attributes for a \code{SteenrodAlgebraElement} self: \code{self._base_field}: $GF(p)$, where $p$ is the associated prime \code{self._prime}: $p$ \code{self._basis}: basis in which to print this element \code{self._raw}: dictionary.  keys are basis names, taken from \code{_steenrod_basis_unique_names}, and the associated values are dictionaries themselves; if the dictionary is nonempty, it gives the representation for that element in the given basis.  If it is empty, that means that the representation in that basis hasn't been computed yet.  The representation of an element with respect to a basis (other than the Milnor basis, which is how elements are stored internally) isn't computed until requested, either by calling the method \code{_basis_dictionary('basis_name')}, or more typically, by calling the method \code{basis('basis_name')} or by defining a Steenrod algebra at that basis and applying its call method to the element. The dictionaries are defined as follows.  In the Milnor basis at the prime 2, for example, since monomials are of the form $\text{Sq}(a,b,c,...)$, then monomials are stored as tuples of integers \code{(a,b,c,...)}.  Thus if $y = \text{Sq}(5,3) + \text{Sq}(0,0,2)$, then \code{y._raw['milnor']} is \code{\{(0, 0, 2): 1, (5, 3): 1\}}.  (The 1's following the colons are the coefficients of the monomials associated to the tuples.)  Each basis has its own representation as a dictionary; Arnon's C basis represents basis elements as tuples of integers, just like the Milnor basis and the Serre-Cartan basis, while the other bases represent basis elements as tuples of pairs of integers.  From the descriptions of the bases given in the file 'steenrod_algebra_bases.py', it should be clear how to associate a tuple of pairs of integers to a basis element.  See also the function \code{string_rep}. When the element is initially defined by calling \code{Sq} or \code{SteenrodAlgebraElement}, typically only the 'milnor' dictionary is non-empty, while if the element is defined by the function \code{steenrod_algebra_basis}, its dictionary for the given basis is also initialized correctly.  For example: sage: B = steenrod_algebra_basis(6,'adem'); B (Sq^{6}, Sq^{5} Sq^{1}, Sq^{4} Sq^{2}) sage: x = B[1]; x Sq^{5} Sq^{1} sage: x._raw {'milnor': {(3, 1): 1}, 'serre-cartan': {(5, 1): 1}} Note that the keys 'milnor' and 'serre-cartan' (a synonym for 'adem') have nonempty associated values. When any element is converted to another basis (by changing the basis and then printing the element), its dictionary for that basis gets stored, as well: sage: x.basis('arnona') X^{0}_{0} X^{1}_{0} X^{1}_{1} sage: x._raw {'arnona': {((0, 0), (1, 0), (1, 1)): 1}, 'milnor': {(3, 1): 1}, 'serre-cartan': {(5, 1): 1}} Methods for a \code{SteenrodAlgebraElement} self: Most of these are self-explanatory. \code{_mul_}: multiply two elements.  This is done using Milnor multiplication, the code for which is in a separate file, 'steenrod_milnor_multiplication'.  In a long computation, it seems that a lot of time is spent here, so one way to speed things up would be to optimize the Milnor multiplication routine. \code{_basis_dictionary}: compute the dictionary of the element with respect to the given basis.  This is basically done by doing a basis conversion from the Milnor basis to the given basis. There are two parts to this function; first, some elements (e.g., $\text{Sq}(2^n)$) may be easy to convert directly.  This is done one basis at a time, and so takes up most of the lines of code. If the element is not recognizable as being easy to convert, then the function \code{milnor_convert} from the file 'steenrod_algebra_bases.py' is called.  This does linear algebra: it computes the Milnor basis and the new basis in the appropriate dimension, computes the change-of-basis matrix, etc. \code{basis}: display the element in the given basis. \code{milnor}: display the element in the Milnor basis. \code{serre_cartan}: display the element in the Serre-Cartan basis. \code{adem}: display the element in the Serre-Cartan basis. \code{_repr_} and \code{_latex_} call the function \code{string_rep}, which has cases depending on the basis. REFERENCES: [Mil] J. W. Milnor, "The Steenrod algebra and its dual," Ann. of Math. (2) 67 (1958), 150--171. [Mon] K. G. Monks, "Change of basis, monomial relations, and $P^s_t$ bases for the Steenrod algebra," J. Pure Appl. Algebra 125 (1998), no. 1-3, 235--260. [Woo] R. M. W. Wood, "Problems in the Steenrod algebra," Bull. London Math. Soc. 30 (1998), no. 5, 449--517. """ #***************************************************************************** #       Copyright (C) 2008 John H. Palmieri #  Distributed under the terms of the GNU General Public License (GPL) #***************************************************************************** from sage.rings.ring import Algebra from sage.algebras.algebra_element import AlgebraElement from sage.structure.parent_gens import ParentWithGens from sage.structure.element import RingElement from sage.rings.all import GF from sage.misc.functional import parent from sage.rings.integer import Integer def check_and_trim(nums): """ Check that list or tuple consists of non-negative integers, and strip trailing zeroes. INPUT: nums -- a list or tuple OUTPUT: new -- a list or tuple If nums contains anything other than a non-negative integer, raise an exception, identifying the right-most problematic entry. Otherwise, return a new list or tuple, obtained from nums by omitting any zeroes from the end. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import check_and_trim sage: check_and_trim([3,4,1]) [3, 4, 1] sage: a=[3,2,1,0,0] sage: check_and_trim(a) [3, 2, 1] sage: a    # check_and_trim doesn't affect its input [3, 2, 1, 0, 0] sage: check_and_trim([0]*127) [] sage: check_and_trim((1,2,3,4,0,0,0))  # works on tuples, too (1, 2, 3, 4) """ index = len(nums) for i in range(index-1, -1, -1): if nums[i] == 0 and i == index - 1: index = i if not (isinstance(nums[i], (Integer, int, long)) and nums[i] >= 0): print type(nums[i]) raise ValueError, "%s is not a non-negative integer" % nums[i] return nums[:index] def convert_perm(m): """ Convert tuple m of non-negative integers to a permutation in one-line form. INPUT: m -- tuple of non-negative integers with no repetitions OUTPUT: list -- conversion of m to a permutation of the set {1,2,...,len(m)} If m=(3,7,4), then one can view m as representing the permutation of the set {3,4,7} sending 3 to 3, 4 to 7, and 7 to 4.  This function converts m to the list [1,3,2], which represents essentially the same permutation, but of the set {1,2,3}.  This list can then be passed to Permutation, and its signature can be computed. EXAMPLES: sage: sage.algebras.steenrod_algebra_element.convert_perm((3,7,4)) [1, 3, 2] sage: sage.algebras.steenrod_algebra_element.convert_perm((5,0,6,3)) [2, 4, 1, 3] """ m2 = list(m) m2.sort() return [list(m).index(x)+1 for x in m2] def base_p_expansion(n, p): r""" Return list of digits in the base p expansion of n. INPUT: n -- non-negative integer p -- positive prime number OUTPUT: list of digits in the base p expansion of n EXAMPLES: sage: sage.algebras.steenrod_algebra_element.base_p_expansion(10,2) [0, 1, 0, 1] sage: sage.algebras.steenrod_algebra_element.base_p_expansion(10,3) [1, 0, 1] sage: sage.algebras.steenrod_algebra_element.base_p_expansion(10,5) [0, 2] sage: sage.algebras.steenrod_algebra_element.base_p_expansion(10,7) [3, 1] sage: sage.algebras.steenrod_algebra_element.base_p_expansion(0,7) [] sage: sage.algebras.steenrod_algebra_element.base_p_expansion(100000,13) [4, 9, 6, 6, 3] """ result = [] while n > 0: remainder = n % p result.append(remainder) n = int((n - remainder)/p) return result def integer_base_2_log(n): """ Largest integer k so that $2^k <= n$ INPUT: n -- positive integer OUTPUT: k -- integer This returns the integer $k$ so that $2^k <= n$ and $2^{k+1} > n$. EXAMPLES: sage: sage.algebras.steenrod_algebra_element.integer_base_2_log(7) 2 sage: sage.algebras.steenrod_algebra_element.integer_base_2_log(8) 3 sage: sage.algebras.steenrod_algebra_element.integer_base_2_log(9) 3 """ answer = 0 while 2**(answer+1) <= n: answer += 1 return answer # These are for internal use only; they are the names used in the code # to represent each basis. _steenrod_basis_unique_names_odd =  ('serre-cartan', 'milnor') _steenrod_basis_unique_names = _steenrod_basis_unique_names_odd + \ ('pst_rlex', 'pst_llex', 'pst_deg', 'pst_revz', 'comm_rlex', 'comm_llex', 'comm_deg', 'comm_revz', 'comm_rlex_long', 'comm_llex_long', 'comm_deg_long', 'comm_revz_long', 'woody', 'woodz', 'arnona', 'arnona_long', 'arnonc', 'wall', 'wall_long') # This dictionary is for caching Steenrod algebras at various primes, # to make sure that SteenrodAlgebraElements defined at the same prime # have the same parent. _steenrod_algebras = {} class SteenrodAlgebraElement(AlgebraElement): r""" Element of the mod p Steenrod algebra. At the prime 2, use the function 'Sq' to define these, as in 'w=Sq(4,3,3)' or 'z=Sq(1,2)+Sq(4,1)' or 'q=Sq(8)*Sq(4) + Sq(12)'. At odd primes, use the methods 'P' and 'Q' to define these, as in 'w=SteenrodAlgebra(3).Q(1,5) * SteenrodAlgebra(3).P(4,3)'. EXAMPLES: sage: w = Sq(4,3,3) sage: w Sq(4,3,3) The function 'Sq', together with addition, provides an easy way to define elements when $p=2$: sage: b = Sq(3) + Sq(0,1) sage: b Sq(0,1) + Sq(3) When $p$ is odd, first define a Steenrod algebra to specify the prime, and then use the methods 'P' and 'Q', together with multiplication and addition: sage: A7 = SteenrodAlgebra(7) sage: u = A7.Q(0,4); u Q_0 Q_4 sage: v = A7.P(1,2,3); v P(1,2,3) sage: u * v Q_0 Q_4 P(1,2,3) sage: 10 * u * v 3 Q_0 Q_4 P(1,2,3) sage: u + v P(1,2,3) + Q_0 Q_4 """ def __init__(self, poly, p=2, basis='milnor'): r""" INPUT: poly -- dictionary with entries of form (monomial: coefficient) Each coefficient is in GF(p), and each monomial is a tuple of non-negative integers (a, b, c, ...), corresponding to the Milnor basis element Sq(a, b, c, ...). At odd primes, the monomials are pairs of tuples: they are of the form ((e0, e1, e2, ...), (r1, r2, ...)), corresponding to the element $Q_{e_0} Q_{e_1} ... P(r_1, r_2, ...)$. Alternatively, poly can be an integer n, in which case it is viewed as being in the field GF(p): the resulting element is n * Sq(0). p -- positive prime number (default 2) OUTPUT: element of the mod p Steenrod algebra EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import SteenrodAlgebraElement sage: SteenrodAlgebraElement({(1,2,3): 1}, 2) Sq(1,2,3) sage: SteenrodAlgebraElement({(1,2,3): 4}, 2) 0 sage: SteenrodAlgebraElement({((0,3), (1,2)): 5}, 7) 5 Q_0 Q_3 P(1,2) sage: SteenrodAlgebraElement({((0,3), (1,2)): 5}, p=7) 5 Q_0 Q_3 P(1,2) The input can also be an integer, in which case it is treated as a multiple, mod p, of the unit element P(0) (a.k.a. Sq(0) at the prime 2): sage: SteenrodAlgebraElement(3,2) Sq(0) sage: SteenrodAlgebraElement(6)   # p=2 is the default prime 0 sage: SteenrodAlgebraElement(6, p=5) P(0) """ from sage.rings.arith import is_prime from sage.algebras.steenrod_algebra import SteenrodAlgebra if not is_prime(p): raise ValueError, "%s is not prime." % p else: # get cached Steenrod algebra at the prime p if _steenrod_algebras.has_key((p,basis)): alg = _steenrod_algebras[(p,basis)] else: _steenrod_algebras[(p,basis)] = SteenrodAlgebra(p,basis) alg = _steenrod_algebras[(p,basis)] if isinstance(poly, SteenrodAlgebraElement): if poly.parent().prime == p: self._raw = poly._raw else: raise ValueError, "Mismatch: %s is defined at the prime %s, \ not %s" % (poly, poly.parent().prime, p) # basic initializations: RingElement.__init__(self, alg) F = alg.base_ring() self._base_field = F self._prime = p self._basis = basis # now comes most of the work: if isinstance(poly, dict): new_poly = {} for mono in poly: if p == 2: # when p=2, mono is a tuple of integers trimmed = check_and_trim(mono) if new_poly.has_key(trimmed): coeff = F(poly[mono] + new_poly[trimmed]) else: coeff = F(poly[mono]) if not coeff.is_zero(): new_poly[trimmed] = coeff else: # when p is odd, mono is either an empty tuple # or a pair of tuples if len(mono) == 0: if new_poly.has_key(mono): coeff = F(poly[mono] + new_poly[mono]) else: coeff = F(poly[mono]) if not coeff.is_zero(): new_poly[mono] = coeff else: # mono is a pair of tuples mono1, mono2 = mono multiplier = 1 # see if there are any repetitions in the Q monomial: if len(mono1) != len(set(mono1)): return self(0) # if not, sort them and introduce the correct sign: if len(mono1) > 0: from sage.combinat.permutation import Permutation multiplier = Permutation( convert_perm(mono1)).signature() mono1 = tuple(sorted(mono1)) trimmed2 = check_and_trim(mono2) if len(mono1) + len(trimmed2) > 0: if new_poly.has_key((mono1,trimmed2)): coeff = F(multiplier * poly[mono] \ + new_poly[(mono1, trimmed2)]) else: coeff = F(multiplier * poly[mono]) if not coeff.is_zero(): new_poly[(mono1,trimmed2)] = coeff else: if new_poly.has_key(()): coeff = F(poly[mono] + new_poly[()]) else: coeff = F(poly[mono]) if not coeff.is_zero(): new_poly[()] = coeff # now define the _raw attribute: a dictionary keyed by the # basis names.  set the 'milnor' value to by new_poly self._raw = {} self._raw['milnor'] = new_poly elif isinstance(poly, (Integer, int)) or poly.parent() == F: # a scalar, i.e., a scalar multiple of Sq(0) or P(0) if F(poly) != 0: new_poly = {(): F(poly)} else: new_poly = {} self._raw = {} if p == 2: basis_list = _steenrod_basis_unique_names else: basis_list = _steenrod_basis_unique_names_odd for basis in basis_list: self._raw[basis] = new_poly elif isinstance(poly, SteenrodAlgebraElement): self._raw = poly._raw else: raise ValueError, "%s is not of the correct form" % poly def is_unit(self): """ True if element has a nonzero scalar multiple of P(0) as a summand, False otherwise. EXAMPLES: sage: z = Sq(4,2) + Sq(7,1) + Sq(3,0,1) sage: z.is_unit() False sage: u = 1 + Sq(3,1) sage: u == Sq(0) + Sq(3,1) True sage: u.is_unit() True sage: A5 = SteenrodAlgebra(5) sage: v = A5.P(0) sage: (v + v + v).is_unit() True """ if self._raw['milnor'].has_key(()): return True else: return False def is_nilpotent(self): """ True if element is not a unit, False otherwise. EXAMPLES: sage: z = Sq(4,2) + Sq(7,1) + Sq(3,0,1) sage: z.is_nilpotent() True sage: u = 1 + Sq(3,1) sage: u == Sq(0) + Sq(3,1) True sage: u.is_nilpotent() False """ return not self.is_unit() def _add_(self, other): """ Addition for elements of the Steenrod algebra. EXAMPLES: sage: a = Sq(3,1) + Sq(6) sage: b = Sq(0,2) sage: c = Sq(6) + Sq(0,2) sage: a + b Sq(0,2) + Sq(3,1) + Sq(6) sage: b + c Sq(6) sage: A7 = SteenrodAlgebra(7) sage: x = A7.P(1,2); y = A7.Q(3,4) * A7.P(1,2) sage: x + 3 * y P(1,2) + 3 Q_3 Q_4 P(1,2) sage: x + 10 * y P(1,2) + 3 Q_3 Q_4 P(1,2) """ F = self._base_field poly1 = self._raw['milnor'] poly2 = other._raw['milnor'] result = poly1.copy() for mono in poly2: if result.has_key(mono): coeff = F(result[mono] + poly2[mono]) else: coeff = F(poly2[mono]) if coeff == 0: del result[mono] else: result[mono] = coeff sum = SteenrodAlgebraElement(result, p=self._prime) return sum def _neg_(self): """ Multiply every coefficient by the element -1 of the base field. EXAMPLES: sage: a = Sq(4,2) + Sq(5) sage: -a Sq(4,2) + Sq(5) sage: A5 = SteenrodAlgebra(5) sage: b = 2 * A5.P(2,0,1) sage: b 2 P(2,0,1) sage: -b 3 P(2,0,1) """ p = self._prime if p == 2: return self else: F = self._base_field dict = self._raw['milnor'] for mono in dict: dict[mono] = F(-dict[mono]) return SteenrodAlgebraElement(dict,p) def _sub_(self, other): """ Subtraction for elements of the Steenrod algebra. EXAMPLES: sage: A7 = SteenrodAlgebra(7) sage: A7.P(2,1) - A7.Q(0,3) P(2,1) + 6 Q_0 Q_3 """ return self._add_(other._neg_()) def _mul_(self, other): """ Multiplication for elements of the Steenrod algebra. EXAMPLES: sage: Sq(2) * Sq(1) Sq(0,1) + Sq(3) sage: Sq(0) * (Sq(6,2) + Sq(9,1)) Sq(6,2) + Sq(9,1) sage: 1 * (Sq(6,2) + Sq(9,1)) Sq(6,2) + Sq(9,1) sage: 4 * (Sq(6,2) + Sq(9,1)) 0 sage: A5 = SteenrodAlgebra(5) sage: A5.P(5) * A5.P(1,2) 3 P(0,3) + P(6,2) sage: A5.Q(1,2,3) * A5.Q(0,5) 4 Q_0 Q_1 Q_2 Q_3 Q_5 sage: A5.Q(1,2,3) * A5.Q(0,3) 0 """ from sage.algebras.steenrod_algebra import SteenrodAlgebra p = self._prime if p == 2: from steenrod_milnor_multiplication import milnor_multiplication else: from steenrod_milnor_multiplication_odd import milnor_multiplication_odd F = self._base_field poly1 = self._raw['milnor'] poly2 = other._raw['milnor'] result = {} for mono1 in poly1: for mono2 in poly2: if len(mono1) == 0:    # multiplying by scalar multiple of one if result.has_key(mono2): result[mono2] = F(result[mono2] + poly1[mono1] * poly2[mono2]) else: result[mono2] = F(poly1[mono1] * poly2[mono2]) elif len(mono2) == 0:    # multiplying by scalar multiple of one if result.has_key(mono1): result[mono1] = F(result[mono1] + poly1[mono1] * poly2[mono2]) else: result[mono1] = F(poly1[mono1] * poly2[mono2]) else: if p == 2: new_dict = milnor_multiplication(mono1, mono2) else: new_dict = milnor_multiplication_odd(mono1, mono2, p=p) for new_mono in new_dict: if result.has_key(new_mono): result[new_mono] = F(result[new_mono] + new_dict[new_mono] * poly1[mono1] * poly2[mono2]) else: result[new_mono] = F(new_dict[new_mono] * poly1[mono1] * poly2[mono2]) product = SteenrodAlgebraElement(result, p=p) return SteenrodAlgebra(p, basis=self._basis)(product) def __cmp__(self,other): """ Two elements are equal iff their difference is zero. EXAMPLES: sage: A5 = SteenrodAlgebra(5) sage: cmp(A5.P(0,1), A5.P(0,2)) -1 sage: cmp(A5.P(0,1), A5.pst(0,2)) 0 """ difference = self - other if len(difference._raw['milnor']) == 0: return 0 else: return -1 def _basis_dictionary(self,basis): r""" Dictionary of terms of the form (mono: coeff), where mono is a monomial in the given basis. INPUT: basis -- string, basis in which to work EXAMPLES: sage: c = Sq(2) * Sq(1) sage: c._basis_dictionary('milnor') {(0, 1): 1, (3,): 1} sage: c Sq(0,1) + Sq(3) sage: c._basis_dictionary('serre-cartan') {(2, 1): 1} sage: c.basis('serre-cartan') Sq^{2} Sq^{1} sage: d = Sq(0,0,1) sage: d._basis_dictionary('arnonc') {(7,): 1, (2, 5): 1, (4, 3): 1, (4, 2, 1): 1} sage: d.basis('arnonc') Sq^{2} Sq^{5} + Sq^{4} Sq^{2} Sq^{1} + Sq^{4} Sq^{3} + Sq^{7} At odd primes: sage: e = 2 * SteenrodAlgebra(3).P(1,2) sage: e._basis_dictionary('milnor') {((), (1, 2)): 2} sage: e 2 P(1,2) sage: e._basis_dictionary('serre-cartan') {(0, 7, 0, 2, 0): 2, (0, 8, 0, 1, 0): 2} sage: e.basis('adem') 2 P^{7} P^{2} + 2 P^{8} P^{1} Implementation: to compute this, take the Milnor representation and change bases.  Store the result in self._raw[basis], for later use; this way, an element only needs to be converted once. """ def is_power_of_two(n): """ True if and only n is a power of 2 """ while n != 0 and n%2 == 0: n = n >> 1 return n == 1 from steenrod_algebra import _steenrod_serre_cartan_basis_names, \ _steenrod_milnor_basis_names, get_basis_name from steenrod_algebra_bases import milnor_convert p = self._prime basis_name = get_basis_name(basis, p) if basis_name == 'milnor': return self._raw['milnor'] if basis_name.find('long') >= 0: basis_name = basis_name.rsplit('_', 1)[0] if self._raw.has_key(basis_name) and len(self._raw[basis_name])>0: return self._raw[basis_name] elif p == 2: dict = {} for mono in self._raw['milnor']: converted = False if dict.has_key(mono): old_coeff = dict[mono] else: old_coeff = 0 new_coeff = old_coeff + self._raw['milnor'][mono] if len(mono) == 0:   # length 0: no conversion if new_coeff != 0: dict[mono] = new_coeff else: del dict[mono] converted = True elif basis_name == 'serre-cartan': if len(mono) == 1:   # length 1: Sq(n) = Sq^{n}, so no conversion if dict.has_key(mono): new_coeff = dict[mono] + self._raw['milnor'][mono] if new_coeff != 0: dict[mono] = new_coeff else: del dict[mono] else: dict[mono] = self._raw['milnor'][mono] converted = True elif basis_name == 'woody': if len(mono) == 1 and is_power_of_two(mono[0]):   # no conversion if new_coeff != 0: dict[((integer_base_2_log(mono[0]),0),)] = new_coeff else: del dict[((integer_base_2_log(mono[0]),0),)] converted = True elif basis_name == 'woodz': if len(mono) == 1 and is_power_of_two(mono[0]):   # no conversion if new_coeff != 0: dict[((integer_base_2_log(mono[0]),0),)] = new_coeff else: del dict[((integer_base_2_log(mono[0]),0),)] converted = True elif basis_name == 'wall': if len(mono) == 1 and is_power_of_two(mono[0]):   # no conversion m = integer_base_2_log(mono[0]) if new_coeff != 0: dict[((m,m),)] = new_coeff else: del dict[((m,m),)] converted = True elif basis_name == 'arnona': if len(mono) == 1 and is_power_of_two(mono[0]):   # no conversion m = integer_base_2_log(mono[0]) if new_coeff != 0: dict[((m,m),)] = new_coeff else: del dict[((m,m),)] converted = True elif basis_name == 'arnonc': if len(mono) == 1:   # no conversion if dict.has_key(mono): new_coeff = dict[mono] + self._raw['milnor'][mono] if new_coeff != 0: dict[mono] = new_coeff else: del dict[mono] else: dict[mono] = self._raw['milnor'][mono] converted = True if not converted: conversion = milnor_convert(  # conversion required SteenrodAlgebraElement({mono: 1}), basis) for new_mono in conversion: if dict.has_key(new_mono): new_coeff = dict[new_mono] + conversion[new_mono] if new_coeff != 0: dict[new_mono] = new_coeff else: del dict[new_mono] else: dict[new_mono] = conversion[new_mono] self._raw[basis] = dict return dict else:  # p odd dict = {} for mono in self._raw['milnor']: converted = False if dict.has_key(mono): old_coeff = dict[mono] else: old_coeff = 0 new_coeff = old_coeff + self._raw['milnor'][mono] if len(mono) == 0:   # length 0: no conversion if new_coeff != 0: dict[mono] = new_coeff else: del dict[mono] converted = True elif basis in _steenrod_serre_cartan_basis_names: if len(mono[0]) == 0 and len(mono[1]) == 1: # length 1: Sq(n) = Sq^{n}, so no conversion new_mono = (0,mono[1][0], 0) if dict.has_key(new_mono): new_coeff = dict[new_mono] + self._raw['milnor'][mono] if new_coeff != 0: dict[new_mono] = new_coeff else: del dict[new_mono] else: dict[new_mono] = self._raw['milnor'][mono] converted = True if not converted: conversion = milnor_convert(  # conversion required SteenrodAlgebraElement({mono: self._raw['milnor'][mono]}, p), basis) for new_mono in conversion: if dict.has_key(new_mono): new_coeff = dict[new_mono] + conversion[new_mono] if new_coeff != 0: dict[new_mono] = new_coeff else: del dict[new_mono] else: dict[new_mono] = conversion[new_mono] self._raw[basis] = dict return dict def basis(self,basis): r""" Representation of element with respect to basis. INPUT: basis -- string, basis in which to work. OUTPUT: Representation of self in given basis The choices for basis are: * 'milnor' for the Milnor basis. * 'serre-cartan', 'serre_cartan', 'sc', 'adem', 'admissible' for the Serre-Cartan basis. * 'wood_y' for Wood's Y basis. * 'wood_z' for Wood's Z basis. * 'wall' for Wall's basis. * 'wall_long' for Wall's basis, alternate representation * 'arnon_a' for Arnon's A basis. * 'arnon_a_long' for Arnon's A basis, alternate representation. * 'arnon_c' for Arnon's C basis. * 'pst', 'pst_rlex', 'pst_llex', 'pst_deg', 'pst_revz' for various $P^s_t$-bases. * 'comm', 'comm_rlex', 'comm_llex', 'comm_deg', 'comm_revz' for various commutator bases. * 'comm_long', 'comm_rlex_long', etc., for commutator bases, alternate representations. See documentation for the function 'steenrod_algebra_basis' for descriptions of the different bases. EXAMPLES: sage: c = Sq(2) * Sq(1) sage: c.basis('milnor') Sq(0,1) + Sq(3) sage: c.basis('serre-cartan') Sq^{2} Sq^{1} sage: d = Sq(0,0,1) sage: d.basis('arnonc') Sq^{2} Sq^{5} + Sq^{4} Sq^{2} Sq^{1} + Sq^{4} Sq^{3} + Sq^{7} """ from sage.algebras.steenrod_algebra import SteenrodAlgebra return SteenrodAlgebra(p=self._prime, basis=basis)(self) def milnor(self): r""" Milnor representation of self. OUTPUT: Milnor representation of self. EXAMPLES: sage: A = SteenrodAlgebra(2, 'adem') sage: x = A (Sq(5) * Sq(2) * Sq(1)); x Sq^{5} Sq^{2} Sq^{1} sage: x.milnor() Sq(1,0,1) + Sq(5,1) """ return self.basis('milnor') def serre_cartan(self): r""" Serre-Cartan representation of self. OUTPUT: Serre-Cartan representation of self. EXAMPLES: sage: x = Sq(0,1); x Sq(0,1) sage: x.serre_cartan() Sq^{2} Sq^{1} + Sq^{3} sage: x.adem()  # 'adem' is a synomym for 'serre_cartan' Sq^{2} Sq^{1} + Sq^{3} """ return self.basis('serre-cartan') adem = serre_cartan def degree(self): r""" Degree of element. OUTPUT: degree -- None, or non-negative integer The degree of $\text{Sq}(i_1,i_2,i_3,...)$ is $i_1 + 3 i_2 + 7 i_3 + ... + (2^n - 1) i_n + ...$. When $p$ is odd, the degree of $Q_{0}^{e_0} Q_{1}^{e_1} ... P(r_1, r_2, ...)$ is $\sum e_i (2p^i - 1) + \sum r_j (2p^j - 2)$. The degree of a sum is undefined (and this returns 'None'), unless each summand has the same degree: that is, unless the element is homogeneous. EXAMPLES: sage: a = Sq(1,2,1) sage: a.degree() 14 sage: for a in Sq(3) + Sq(5,1): a.degree() 3 8 sage: (Sq(3) + Sq(5,1)).degree() Element is not homogeneous. sage: B = SteenrodAlgebra(3) sage: x = B.Q(1,4) sage: y = B.P(1,2,3) sage: x.degree() 166 sage: y.degree() 192 """ def p_degree(m, mult=1, prime=2): """ For m=(n_1, n_2, n_3, ...), Sum_i 2*n_i*(p^i - 1) """ i = 0 deg = 0 for n in m: i += 1 deg += n*mult*(prime**i - 1) return deg def q_degree(m, prime=3): """ For m=(n_0, n_1, n_2, ...), Sum_i 2*p^{n_i} - 1 """ deg = 0 for n in m: deg += 2*prime**n - 1 return deg p = self._prime if p == 2: degrees = [p_degree(mono) for mono in self._raw['milnor']] else: degrees = [q_degree(mono1, prime=p) + p_degree(mono2, prime=p, mult=2) for (mono1, mono2) in self._raw['milnor']] if min(degrees) == max(degrees): return min(degrees) else: print "Element is not homogeneous." return None def excess(self): r""" Excess of element. OUTPUT: excess -- non-negative integer The excess of $\text{Sq}(a,b,c,...)$ is $a + b + c + ...$. When $p$ is odd, the excess of $Q_{0}^{e_0} Q_{1}^{e_1} ... P(r_1, r_2, ...)$ is $\sum e_i + 2 \sum r_i$. The excess of a linear combination of Milnor basis elements is the minimum of the excesses of those basis elements. See [Kra] for the proofs of these assertions. EXAMPLES: sage: a = Sq(1,2,3) sage: a.excess() 6 sage: (Sq(0,0,1) + Sq(4,1) + Sq(7)).excess() 1 sage: [m.excess() for m in (Sq(0,0,1) + Sq(4,1) + Sq(7))] [1, 5, 7] sage: [m for m in (Sq(0,0,1) + Sq(4,1) + Sq(7))] [Sq(0,0,1), Sq(4,1), Sq(7)] sage: B = SteenrodAlgebra(7) sage: a = B.Q(1,2,5) sage: b = B.P(2,2,3) sage: a.excess() 3 sage: b.excess() 14 sage: (a + b).excess() 3 sage: (a * b).excess() 17 REFERENCES: [Kra] D. Kraines, "On excess in the Milnor basis," Bull. London Math. Soc. 3 (1971), 363-365. """ def excess_odd(mono): """ Excess of mono, where mono has the form ((s0, s1, ...), (r1, r2, ...)). Returns the length of the first component, since that is the number of factors, plus twice the sum of the terms in the second component. """ if len(mono) == 0: return 0 else: return len(mono[0]) + 2 * sum(mono[1]) p = self._prime if p == 2: excesses = [sum(mono) for mono in self._raw['milnor']] else: excesses = [excess_odd(mono) for mono in self._raw['milnor']] return min(excesses) def may_weight(self): r""" May's 'weight' of element. OUTPUT: weight -- non-negative integer If we let $F_* (A)$ be the May filtration of the Steenrod algebra, the weight of an element $x$ is the integer $k$ so that $x$ is in $F_k(A)$ and not in $F_{k+1}(A)$.  According to Theorem 2.6 in May's thesis [May], the weight of a Milnor basis element is computed as follows: first, to compute the weight of $P(r_1,r2, ...)$, write each $r_i$ in base $p$ as $r_i = \sum_j p^j r_{ij}$.  Then each nonzero binary digit $r_{ij}$ contributes $i$ to the weight: the weight is $\sum_{i,j} i r_{ij}$.  When $p$ is odd, the weight of $Q_i$ is $i+1$, so the weight of a product $Q_{i_1} Q_{i_2} ...$ is equal $(i_1+1) + (i_2+1) + ...$.  Then the weight of $Q_{i_1} Q_{i_2} ...P(r_1,r2, ...)$ is the sum of $(i_1+1) + (i_2+1) + ...$ and $\sum_{i,j} i r_{ij}$. The weight of a sum of basis elements is the minimum of the weights of the summands. When $p=2$, we compute the weight on Milnor basis elements by adding up the terms in their 'height' -- see the method 'wall_height' for documentation.  (When $p$ is odd, the height of an element is not defined.) EXAMPLES: sage: Sq(0).may_weight() 0 sage: a = Sq(4) sage: a.may_weight() 1 sage: b = Sq(4)*Sq(8) + Sq(8)*Sq(4) sage: b.may_weight() 2 sage: Sq(2,1,5).wall_height() [2, 3, 2, 1, 1] sage: Sq(2,1,5).may_weight() 9 sage: A5 = SteenrodAlgebra(5) sage: a = A5.Q(1,2,4) sage: b = A5.P(1,2,1) sage: a.may_weight() 10 sage: b.may_weight() 8 sage: (a * b).may_weight() 18 sage: A5.P(0,0,1).may_weight() 3 REFERENCES: [May]: J. P. May, "The cohomology of restricted Lie algebras and of Hopf algebras; application to the Steenrod algebra." Thesis, Princeton Univ., 1964. """ from sage.rings.infinity import Infinity p = self._prime if self == 0: return Infinity elif self.is_unit(): return 0 elif p == 2: wt = Infinity for mono in self: wt = min(wt, sum(mono.wall_height())) return wt else: # p odd wt = Infinity for (mono1, mono2) in self._raw['milnor']: P_wt = 0 index = 1 for n in mono2: P_wt += index * sum(base_p_expansion(n,p)) index += 1 wt = min(wt, sum(mono1) + len(mono1) + P_wt) return wt def is_decomposable(self): r""" return True if element is decomposable, False otherwise. OUTPUT: decomposable -- boolean That is, if element is in the square of the augmentation ideal, return True; otherwise, return False. EXAMPLES: sage: a = Sq(6) sage: a.is_decomposable() True sage: for i in range(9): ...       if not Sq(i).is_decomposable(): ...           print Sq(i) Sq(0) Sq(1) Sq(2) Sq(4) Sq(8) """ return self.may_weight() > 1 def wall_height(self): r""" Wall's 'height' of element. OUTPUT: height -- list of non-negative integers The height of an element of the mod 2 Steenrod algebra is a list of non-negative integers, defined as follows: if the element is a monomial in the generators $\text{Sq}(2^i)$, then the $i$th entry in the list is the number of times $\text{Sq}(2^i)$ appears.  For an arbitrary element, write it as a sum of such monomials; then its height is the maximum, ordered right-lexicographically, of the heights of those monomials. When $p$ is odd, the height of an element is not defined. According to Theorem 3 in [Wall], the height of the Milnor basis element $\text{Sq}(r_1, r_2, ...)$ is obtained as follows: write each $r_i$ in binary as $r_i = \sum_j 2^j r_{ij}$. Then each nonzero binary digit $r_{ij}$ contributes 1 to the $k$th entry in the height, for $j \leq k \leq i+j-1$. EXAMPLES: sage: Sq(0).wall_height() [] sage: a = Sq(4) sage: a.wall_height() [0, 0, 1] sage: b = Sq(4)*Sq(8) + Sq(8)*Sq(4) sage: b.wall_height() [0, 0, 1, 1] sage: Sq(0,0,3).wall_height() [1, 2, 2, 1] REFERENCES: [Wall]: C. T. C. Wall, "Generators and relations for the Steenrod algebra," Ann. of Math. (2) \textbf{72} (1960), 429--444. """ def mono_degree(m): """ If m=(n1,n2,n3, ...), returns the degree of Sq(n1,n2,n3,...). That is, it returns sum n_i (2^i - 1). """ i = 0 deg = 0 for n in m: i += 1 deg += n*(2**i - 1) return deg if self._prime > 2: raise NotImplementedError, "Wall height is not defined at odd primes." if self == 0 or self == 1: return [] result = [] for r in self._raw['milnor']: h = [0]*(1+mono_degree(r)) i = 1 for x in r: if x > 0: for j in range(1+integer_base_2_log(x)): if (2**j & x) != 0: for k in range(j,i+j): h[k] += 1 i=i+1 h.reverse() result = max(h, result) result.reverse() return check_and_trim(result) def antipode(self): r""" Antipode of element. OUTPUT: antipode -- element of the Steenrod algebra Algorithm: according to a result of Milnor's, the antipode of $\text{Sq}(n)$ is the sum of all of the Milnor basis elements in dimension $n$.  So: convert the element to the Serre-Cartan basis and use this formula for the antipode of $\text{Sq}(n)$, together with the fact that the antipode is an antihomomorphism: if we call the antipode $c$, then $c(ab) = c(b) c(a)$. At odd primes, a similar method is used: the antipode of $P(n)$ is the sum of the Milnor basis elements in dimension $n*2(p-1)$, and the antipode of $\beta = Q_0$ is $-Q_0$.  So convert to the Serre-Cartan basis, as in the $p=2$ case. EXAMPLES: sage: d = Sq(0,0,1); d Sq(0,0,1) sage: d.antipode() Sq(0,0,1) sage: Sq(4).antipode() Sq(1,1) + Sq(4) sage: (Sq(4) * Sq(2)).antipode() Sq(6) sage: A3 = SteenrodAlgebra(3) sage: A3.P(2).antipode() P(2) sage: A3.P(2,1).antipode() 2 P(2,1) sage: a = SteenrodAlgebra(7).P(3,1) sage: a.antipode() 4 P(3,1) + 5 P(11) Applying the antipode twice returns the original element: sage: y = Sq(8)*Sq(4) sage: y == (y.antipode()).antipode() True """ def sum_of_basis(n,p): """ Antipode of P(n) (i.e., of Sq(n) when p=2). INPUT: n -- integer p -- positive prime number OUTPUT: elt -- element of the Steenrod algebra This returns the sum of all of the elements P(...) in the Milnor basis in dimension $n$ at the prime p """ from steenrod_algebra_bases import steenrod_algebra_basis return sum(steenrod_algebra_basis(n,'milnor',p=p)) from sage.algebras.steenrod_algebra import SteenrodAlgebra from steenrod_algebra_bases import milnor_convert result = 0 p = self._prime if p == 2: for mono in self._basis_dictionary('serre_cartan'): antipode = Sq(0) for n in mono: antipode = sum_of_basis(n, p) * antipode result = result + antipode else: from sage.misc.functional import is_even for mono in self._basis_dictionary('serre_cartan'): antipode = SteenrodAlgebra(p).P(0) index = 0 for n in mono: if is_even(index) and n != 0: antipode = -SteenrodAlgebra(p).Q(0) * antipode else: antipode = sum_of_basis(n*2*(p-1),p) * antipode index += 1 result = result + antipode return result def _repr_(self): """ String representation of element. OUTPUT: string The string depends on the basis over which the element is defined. EXAMPLES: sage: A7 = SteenrodAlgebra(7) sage: x = A7.Q(0,3) * A7.P(2,2) sage: x._repr_() 'Q_0 Q_3 P(2,2)' sage: x Q_0 Q_3 P(2,2) sage: a = Sq(0,0,2) sage: a Sq(0,0,2) sage: A2_adem = SteenrodAlgebra(2,'admissible') sage: A2_adem(a) Sq^{8} Sq^{4} Sq^{2} + Sq^{9} Sq^{4} Sq^{1} + Sq^{10} Sq^{3} Sq^{1} + Sq^{10} Sq^{4} + Sq^{11} Sq^{2} Sq^{1} + Sq^{12} Sq^{2} + Sq^{13} Sq^{1} + Sq^{14} sage: SteenrodAlgebra(2, 'woodz')(a) Sq^{6} Sq^{7} Sq^{1} + Sq^{14} + Sq^{4} Sq^{7} Sq^{3} + Sq^{4} Sq^{7} Sq^{2} Sq^{1} + Sq^{12} Sq^{2} + Sq^{8} Sq^{6} + Sq^{8} Sq^{4} Sq^{2} sage: SteenrodAlgebra(2, 'arnonc')(a) Sq^{4} Sq^{2} Sq^{8} + Sq^{4} Sq^{4} Sq^{6} + Sq^{4} Sq^{6} Sq^{4} + Sq^{6} Sq^{8} + Sq^{8} Sq^{4} Sq^{2} + Sq^{8} Sq^{6} sage: SteenrodAlgebra(2, 'pst_llex')(a) P^{1}_{3} sage: SteenrodAlgebra(2, 'comm_revz')(a) c_{0,1} c_{1,1} c_{0,3} c_{2,1} + c_{0,2} c_{0,3} c_{2,1} + c_{1,3} """ if len(self._raw['milnor']) == 0: return "0" else: return string_rep(self) def _latex_(self): """ LaTeX representation of element. OUTPUT: string The string depends on the basis over which the element is defined. For any element x in the Steenrod algebra, use 'view(x)' to see the typeset LaTeX representation. EXAMPLES: sage: A7 = SteenrodAlgebra(7) sage: x = A7.Q(0,3) * A7.P(2,2) sage: x._latex_() 'Q_{0} Q_{3} \\mathcal{P}(2,2)' sage: latex(x) Q_{0} Q_{3} \mathcal{P}(2,2) sage: b = Sq(0,2) sage: b.basis('adem')._latex_() '\\text{Sq}^{4} \\text{Sq}^{2} + \\text{Sq}^{5} \\text{Sq}^{1} + \\text{Sq}^{6}' sage: b.basis('woody')._latex_() '\\text{Sq}^{2} \\text{Sq}^{3} \\text{Sq}^{1} + \\text{Sq}^{6} + \\text{Sq}^{4} \\text{Sq}^{2}' sage: SteenrodAlgebra(2, 'arnona')(b)._latex_() 'X^{1}_{1} X^{2}_{2}  + X^{2}_{1}' """ if len(self._raw['milnor']) == 0: return "0" else: return string_rep(self,LaTeX=True) def __iter__(self): """ Iterator for looping through summands in an element of the Steenrod algebra. EXAMPLES: sage: z = Sq(0,0,1) + Sq(4,1) + Sq(7) sage: [m for m in z] [Sq(0,0,1), Sq(4,1), Sq(7)] sage: [m.excess() for m in z] [1, 5, 7] sage: for m in z: m * Sq(2) Sq(2,0,1) Sq(0,3) + Sq(6,1) Sq(3,2) sage: a = SteenrodAlgebra(5).P(5,5) sage: a * a P(3,6,1) + 2 P(4,11) + P(9,5,1) + 4 P(10,10) sage: for m in a * a: m P(3,6,1) 2 P(4,11) P(9,5,1) 4 P(10,10) This loops through the summands in the Milnor basis representation of the element.  The element w defined below is a single monomial in the Serre-Cartan basis, but a sum of four monomials in the Milnor basis: sage: w = Sq(4) * Sq(2) * Sq(1) sage: A = SteenrodAlgebra(2, 'adem') sage: w = A(Sq(4) * Sq(2) * Sq(1)); w Sq^{4} Sq^{2} Sq^{1} sage: for m in w: m.basis('adem') Sq^{4} Sq^{2} Sq^{1} + Sq^{5} Sq^{2} + Sq^{6} Sq^{1} + Sq^{7} Sq^{5} Sq^{2} + Sq^{7} Sq^{6} Sq^{1} + Sq^{7} Sq^{7} sage: w.milnor() Sq(0,0,1) + Sq(1,2) + Sq(4,1) + Sq(7) sage: for m in w: m Sq(0,0,1) Sq(1,2) Sq(4,1) Sq(7) """ for m in sorted(self._raw['milnor'].keys()): yield SteenrodAlgebraElement({m: self._raw['milnor'][m]}, p = self._prime) def additive_order(self): """ The additive order of any element of the mod p Steenrod algebra is p. OUTPUT: order -- positive prime number EXAMPLES: sage: z = Sq(4) + Sq(6) + Sq(0) sage: z.additive_order() 2 """ return self._prime def Sq(*nums): """ Milnor element Sq(a,b,c,...). INPUT: a, b, c, ... -- non-negative integers OUTPUT: element of the Steenrod algebra This returns the Milnor basis element $\text{Sq}(a, b, c, ...)$. EXAMPLES: sage: Sq(5) Sq(5) sage: Sq(5) + Sq(2,1) + Sq(5)  # addition is mod 2: Sq(2,1) sage: (Sq(4,3) + Sq(7,2)).degree() 13 Entries must be non-negative integers; otherwise, an error results. This function is a good way to define elements of the Steenrod algebra. """ dict = {nums: 1} return SteenrodAlgebraElement(dict, p=2) def pst(s,t,p=2): """ The Margolis element $P^s_t$. INPUT: s -- non-negative integer t -- positive integer p -- positive prime number (optional, default 2) OUTPUT: element of the Steenrod algebra This returns the Margolis element $P^s_t$ of the mod p Steenrod algebra: the element equal to $P(0,0,...,0,p^s)$, where the $p^s$ is in position $t$. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import pst sage: pst(3,5) Sq(0,0,0,0,8) sage: pst(1,2) + Sq(4)*Sq(2) + Sq(2)*Sq(4) 0 sage: pst(3,5,5) P(0,0,0,0,125) sage: pst(3,5,p=5) P(0,0,0,0,125) """ from sage.algebras.steenrod_algebra import SteenrodAlgebra return SteenrodAlgebra(p).pst(s,t) def degree(x): r""" Degree of x. INPUT: x -- element of the Steenrod algebra OUTPUT: degree -- non-negative integer or None The degree of $\text{Sq}(i_1,i_2,i_3,...)$ is $i_1 + 3 i_2 + 7 i_3 + ... + (2^n - 1) i_n + ...$. When $p$ is odd, the degree of $Q_{0}^{e_0} Q_{1}^{e_1} ... P(r_1, r_2, ...)$ is $\sum e_i (2p^i - 1) + \sum r_j (2p^j - 2)$. The degree of a sum is undefined (and this function returns None), unless each summand has the same degree: that is, unless the element is homogeneous. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import degree sage: a = Sq(1,2,1) sage: degree(a) 14 sage: degree(Sq(3) + Sq(5,1)) Element is not homogeneous. sage: B = SteenrodAlgebra(3) sage: x = B.Q(1,4) sage: y = B.P(1,2,3) sage: degree(x) 166 sage: degree(y) 192 """ return x.degree() def excess(x): r""" Excess of x. INPUT: x -- element of the Steenrod algebra OUTPUT: excess -- non-negative integer The excess of $\text{Sq}(a,b,c,...)$ is $a + b + c + ...$. When $p$ is odd, the excess of $Q_{0}^{e_0} Q_{1}^{e_1} ... P(r_1, r_2, ...)$ is $\sum e_i + 2 \sum r_i$. The excess of a linear combination of Milnor basis elements is the minimum of the excesses of those basis elements. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import excess sage: a = Sq(1,2,3) sage: excess(a) 6 sage: excess(Sq(0,0,1) + Sq(4,1) + Sq(7)) 1 sage: [excess(m) for m in (Sq(0,0,1) + Sq(4,1) + Sq(7))] [1, 5, 7] sage: B = SteenrodAlgebra(7) sage: a = B.Q(1,2,5) sage: b = B.P(2,2,3) sage: excess(a) 3 sage: excess(b) 14 sage: excess(a + b) 3 sage: excess(a * b) 17 """ return x.excess() def milnor(x): r""" Milnor representation of x. INPUT: x -- element of the Steenrod algebra OUTPUT: Milnor representation of x EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import milnor sage: x = Sq(5) * Sq(2) * Sq(1); x.adem() Sq^{5} Sq^{2} Sq^{1} sage: milnor(x) Sq(1,0,1) + Sq(5,1) """ return x.basis('milnor') def serre_cartan(x): r""" Serre-Cartan representation of x. INPUT: x -- element of the Steenrod algebra OUTPUT: Serre-Cartan representation of x EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import serre_cartan sage: x = Sq(3,2); x Sq(3,2) sage: serre_cartan(x) Sq^{7} Sq^{2} """ return x.basis('adem') adem = serre_cartan admissible = serre_cartan ## string representations def string_rep(element, LaTeX=False, sort=True): """ String representation of element. INPUT: element -- element of the Steenrod algebra LaTeX -- boolean (optional, default False), if True, output LaTeX string sort -- boolean (optional, default True), if True, sort output OUTPUT: string -- string representation of element in current basis If LaTeX is True, output a string suitable for LaTeX; otherwise, output a plain string.  If sort is True, sort element left lexicographically; otherwise, no sorting is done, and so the order in which the summands are printed may be unpredictable. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import string_rep sage: a = Sq(0,0,2) sage: A = SteenrodAlgebra(2, 'admissible') sage: string_rep(A(a)) 'Sq^{8} Sq^{4} Sq^{2} + Sq^{9} Sq^{4} Sq^{1} + Sq^{10} Sq^{3} Sq^{1} + Sq^{10} Sq^{4} + Sq^{11} Sq^{2} Sq^{1} + Sq^{12} Sq^{2} + Sq^{13} Sq^{1} + Sq^{14}' sage: b = Sq(0,2) sage: string_rep(A(b),LaTeX=True) '\\text{Sq}^{4} \\text{Sq}^{2} + \\text{Sq}^{5} \\text{Sq}^{1} + \\text{Sq}^{6}' sage: A_wood_z = SteenrodAlgebra(2, 'woodz') sage: string_rep(A_wood_z(a)) 'Sq^{6} Sq^{7} Sq^{1} + Sq^{14} + Sq^{4} Sq^{7} Sq^{3} + Sq^{4} Sq^{7} Sq^{2} Sq^{1} + Sq^{12} Sq^{2} + Sq^{8} Sq^{6} + Sq^{8} Sq^{4} Sq^{2}' sage: string_rep(SteenrodAlgebra(2, 'arnonc')(a), sort=False) 'Sq^{4} Sq^{4} Sq^{6} + Sq^{6} Sq^{8} + Sq^{4} Sq^{2} Sq^{8} + Sq^{4} Sq^{6} Sq^{4} + Sq^{8} Sq^{4} Sq^{2} + Sq^{8} Sq^{6}' sage: string_rep(SteenrodAlgebra(2, 'arnonc')(a)) 'Sq^{4} Sq^{2} Sq^{8} + Sq^{4} Sq^{4} Sq^{6} + Sq^{4} Sq^{6} Sq^{4} + Sq^{6} Sq^{8} + Sq^{8} Sq^{4} Sq^{2} + Sq^{8} Sq^{6}' sage: string_rep(SteenrodAlgebra(2, 'pst_llex')(a)) 'P^{1}_{3}' sage: Ac = SteenrodAlgebra(2, 'comm_revz') sage: string_rep(Ac(a),LaTeX=True,sort=False) 'c_{0,2} c_{0,3} c_{2,1} + c_{1,3} + c_{0,1} c_{1,1} c_{0,3} c_{2,1}' sage: string_rep(Ac(a),LaTeX=True) 'c_{0,1} c_{1,1} c_{0,3} c_{2,1} + c_{0,2} c_{0,3} c_{2,1} + c_{1,3}' sage: string_rep(a) 'Sq(0,0,2)' sage: string_rep(a,LaTeX=True) '\\text{Sq}(0,0,2)' Some odd primary examples: sage: A5 = SteenrodAlgebra(5) sage: a = A5.P(5,1); b = A5.Q(0,1,3) sage: string_rep(b) 'Q_0 Q_1 Q_3' sage: string_rep(a, LaTeX=True) '\\mathcal{P}(5,1)' sage: A5sc = SteenrodAlgebra(5, 'serre-cartan') sage: string_rep(A5sc(a)) 'P^{10} P^{1} + 4 P^{11}' """ if len(element._raw['milnor']) == 0: return "0" p = element._prime basis = element._basis dict = element._basis_dictionary(basis) if basis == 'milnor': mono_to_string = milnor_mono_to_string elif basis == 'serre-cartan': mono_to_string = serre_cartan_mono_to_string elif basis.find('wood') >= 0: mono_to_string = wood_mono_to_string elif basis == 'wall': mono_to_string = wall_mono_to_string elif basis == 'wall_long': mono_to_string = wall_long_mono_to_string elif basis == 'arnona': mono_to_string = arnonA_mono_to_string elif basis == 'arnona_long': mono_to_string = arnonA_long_mono_to_string elif basis == 'arnonc': mono_to_string = serre_cartan_mono_to_string elif basis.find('pst') >= 0: mono_to_string = pst_mono_to_string elif basis.find('comm') >= 0 and basis.find('long') >= 0: mono_to_string = comm_long_mono_to_string elif basis.find('comm') >= 0: mono_to_string = comm_mono_to_string output = "" if sort: sorted_list = sorted(dict.keys()) else: sorted_list = dict for mono in sorted_list: if dict[mono] != 1: coeff = str(dict[mono]) + " " else: coeff = "" output = output + coeff + mono_to_string(mono, LaTeX, p=element._prime) + " + " return output.strip(" +") def milnor_mono_to_string(mono,LaTeX=False,p=2): """ String representation of element of the Milnor basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- if $p=2$, tuple of non-negative integers (a,b,c,...); if $p>2$, pair of tuples of non-negative integers ((e0, e1, e2, ...), (r1, r2, ...)) LaTeX -- boolean (optional, default False), if true, output LaTeX string p -- positive prime number (optional, default 2) OUTPUT: rep -- string This returns a string like 'Sq(a,b,c,...)' when p=2, or a string like 'Q_e0 Q_e1 Q_e2 ... P(r1, r2, ...)' when p is odd. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import milnor_mono_to_string sage: milnor_mono_to_string((1,2,3,4)) 'Sq(1,2,3,4)' sage: milnor_mono_to_string((1,2,3,4),LaTeX=True) '\\text{Sq}(1,2,3,4)' sage: milnor_mono_to_string(((1,0), (2,3,1)), p=3) 'Q_1 Q_0 P(2,3,1)' sage: milnor_mono_to_string(((1,0), (2,3,1)), LaTeX=True, p=3) 'Q_{1} Q_{0} \\mathcal{P}(2,3,1)' The empty tuple represents the unit element Sq(0) (or P(0) at an odd prime): sage: milnor_mono_to_string(()) 'Sq(0)' sage: milnor_mono_to_string((), p=5) 'P(0)' """ if LaTeX: if p == 2: sq = "\\text{Sq}" P = "\\text{Sq}" else: P = "\\mathcal{P}" else: if p == 2: sq = "Sq" P = "Sq" else: P = "P" if len(mono) == 0 or (p > 2 and len(mono[0]) + len(mono[1]) == 0): return P + "(0)" else: if p == 2: string = sq + "(" + str(mono[0]) for n in mono[1:]: string = string + "," + str(n) string = string + ")" else: string = "" if len(mono[0]) > 0: for e in mono[0]: if LaTeX: string = string + "Q_{" + str(e) + "} " else: string = string + "Q_" + str(e) + " " if len(mono[1]) > 0: string = string + P + "(" + str(mono[1][0]) for n in mono[1][1:]: string = string + "," + str(n) string = string + ")" return string.strip(" ") def serre_cartan_mono_to_string(mono,LaTeX=False,p=2): r""" String representation of element of the Serre-Cartan basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of positive integers (a,b,c,...) when $p=2$, or tuple (e0, n1, e1, n2, ...) when $p>2$, where each ei is 0 or 1, and each ni is positive LaTeX -- boolean (optional, default False), if true, output LaTeX string p -- positive prime number (optional, default 2) OUTPUT: rep -- string This returns a string like '$Sq^{a} Sq^{b} Sq^{c} ...$ when $p=2$, or a string like $\beta^{e0} P^{n1} \beta^{e1} P^{n2} ...$ when $p$ is odd. EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import serre_cartan_mono_to_string sage: serre_cartan_mono_to_string((1,2,3,4)) 'Sq^{1} Sq^{2} Sq^{3} Sq^{4}' sage: serre_cartan_mono_to_string((1,2,3,4),LaTeX=True) '\\text{Sq}^{1} \\text{Sq}^{2} \\text{Sq}^{3} \\text{Sq}^{4}' sage: serre_cartan_mono_to_string((0,5,1,1,0), p=3) 'P^{5} beta P^{1}' sage: serre_cartan_mono_to_string((0,5,1,1,0), p=3, LaTeX=True) '\\mathcal{P}^{5} \\beta \\mathcal{P}^{1}' The empty tuple represents the unit element $Sq^0$ (or $P^0$ at an odd prime): sage: serre_cartan_mono_to_string(()) 'Sq^{0}' sage: serre_cartan_mono_to_string((), p=7) 'P^{0}' """ if LaTeX: if p == 2: sq = "\\text{Sq}" P = "\\text{Sq}" else: P = "\\mathcal{P}" else: if p == 2: sq = "Sq" P = "Sq" else: P = "P" if len(mono) == 0: return P + "^{0}" else: if p == 2: string = "" for n in mono: string = string + sq + "^{" + str(n) + "} " else: string = "" index = 0 for n in mono: from sage.misc.functional import is_even if is_even(index): if n == 1: if LaTeX: string = string + "\\beta " else: string = string + "beta " else: string = string + P + "^{" + str(n) + "} " index += 1 return string.strip(" ") def wood_mono_to_string(mono,LaTeX=False,p=2): """ String representation of element of Wood's Y and Z bases. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of non-negative integers (s,t) OUTPUT: string -- concatenation of '$Sq^{2^s (2^{t+1}-1)}$' for each pair (s,t) EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import wood_mono_to_string sage: wood_mono_to_string(((1,2),(3,0))) 'Sq^{14} Sq^{8}' sage: wood_mono_to_string(((1,2),(3,0)),LaTeX=True) '\\text{Sq}^{14} \\text{Sq}^{8}' The empty tuple represents the unit element Sq(0): sage: wood_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (s,t) in mono: string = string + sq + "^{" + \ str(2**s * (2**(t+1)-1)) + "} " return string.strip(" ") def wall_mono_to_string(mono,LaTeX=False,p=2): """ String representation of element of Wall's basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of non-negative integers (m,k) with $m >= k$ OUTPUT: string -- concatenation of '$Q^{m}_{k}$' for each pair (m,k) EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import wall_mono_to_string sage: wall_mono_to_string(((1,2),(3,0))) 'Q^{1}_{2} Q^{3}_{0}' sage: wall_mono_to_string(((1,2),(3,0)),LaTeX=True) 'Q^{1}_{2} Q^{3}_{0}' The empty tuple represents the unit element Sq(0): sage: wall_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (m,k) in mono: string = string + "Q^{" + str(m) + "}_{" \ + str(k) + "} " return string.strip(" ") def wall_long_mono_to_string(mono,LaTeX=False,p=2): """ Alternate string representation of element of Wall's basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of non-negative integers (m,k) with $m >= k$ OUTPUT: string -- concatenation of terms of the form '$Sq^(2^m)$' EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import wall_long_mono_to_string sage: wall_long_mono_to_string(((1,2),(3,0))) 'Sq^{1} Sq^{2} Sq^{4} Sq^{8}' sage: wall_long_mono_to_string(((1,2),(3,0)),LaTeX=True) '\\text{Sq}^{1} \\text{Sq}^{2} \\text{Sq}^{4} \\text{Sq}^{8}' The empty tuple represents the unit element Sq(0): sage: wall_long_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (m,k) in mono: for i in range(k,m+1): string = string + sq + "^{" + str(2**i) + "} " return string.strip(" ") def arnonA_mono_to_string(mono,LaTeX=False,p=2): """ String representation of element of Arnon's A basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of non-negative integers (m,k) with $m >= k$ OUTPUT: string -- concatenation of '$X^{m}_{k}$' for each pair (m,k) EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import arnonA_mono_to_string sage: arnonA_mono_to_string(((1,2),(3,0))) 'X^{1}_{2} X^{3}_{0}' sage: arnonA_mono_to_string(((1,2),(3,0)),LaTeX=True) 'X^{1}_{2} X^{3}_{0}' The empty tuple represents the unit element Sq(0): sage: arnonA_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (m,k) in mono: string = string + "X^{" + str(m) + "}_{" \ + str(k) + "} " return string.strip(" ") def arnonA_long_mono_to_string(mono,LaTeX=False,p=2): """ Alternate string representation of element of Arnon's A basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of non-negative integers (m,k) with $m >= k$ OUTPUT: string -- concatenation of strings of the form '$Sq(2^m)$' EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import arnonA_long_mono_to_string sage: arnonA_long_mono_to_string(((1,2),(3,0))) 'Sq^{8} Sq^{4} Sq^{2} Sq^{1}' sage: arnonA_long_mono_to_string(((1,2),(3,0)),LaTeX=True) '\\text{Sq}^{8} \\text{Sq}^{4} \\text{Sq}^{2} \\text{Sq}^{1}' The empty tuple represents the unit element Sq(0): sage: arnonA_long_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (m,k) in mono: for i in range(m,k-1,-1): string = string + sq + "^{" + str(2**i) + "} " return string.strip(" ") def pst_mono_to_string(mono,LaTeX=False,p=2): r""" String representation of element of a $P^s_t$-basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of integers (s,t) with $s >= 0$, $t > 0$ OUTPUT: string -- concatenation of '$P^{s}_{t}$' for each pair (s,t) EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import pst_mono_to_string sage: pst_mono_to_string(((1,2),(0,3))) 'P^{1}_{2} P^{0}_{3}' sage: pst_mono_to_string(((1,2),(0,3)),LaTeX=True) 'P^{1}_{2} P^{0}_{3}' The empty tuple represents the unit element Sq(0): sage: pst_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (s,t) in mono: string = string + "P^{" + str(s) + "}_{" \ + str(t) + "} " return string.strip(" ") def comm_mono_to_string(mono,LaTeX=False,p=2): r""" String representation of element of a commutator basis. This is used by the _repr_ and _latex_ methods. INPUT: mono -- tuple of pairs of integers (s,t) with $s >= 0$, $t > 0$ OUTPUT: string -- concatenation of '$c_{s,t}$' for each pair (s,t) EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import comm_mono_to_string sage: comm_mono_to_string(((1,2),(0,3))) 'c_{1,2} c_{0,3}' sage: comm_mono_to_string(((1,2),(0,3)),LaTeX=True) 'c_{1,2} c_{0,3}' The empty tuple represents the unit element Sq(0): sage: comm_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (s,t) in mono: string = string + "c_{" + str(s) + "," \ + str(t) + "} " return string.strip(" ") def comm_long_mono_to_string(mono,LaTeX=False,p=2): r""" Alternate string representation of element of a commutator basis. Okay in low dimensions, but gets unwieldy as the dimension increases. INPUT: mono -- tuple of pairs of integers (s,t) with $s >= 0$, $t > 0$ OUTPUT: string -- concatenation of '$s_{2^s ... 2^(s+t-1)}$' for each pair (s,t) EXAMPLES: sage: from sage.algebras.steenrod_algebra_element import comm_long_mono_to_string sage: comm_long_mono_to_string(((1,2),(0,3))) 's_{24} s_{124}' sage: comm_long_mono_to_string(((1,2),(0,3)),LaTeX=True) 's_{24} s_{124}' The empty tuple represents the unit element Sq(0): sage: comm_long_mono_to_string(()) 'Sq(0)' """ if LaTeX: sq = "\\text{Sq}" else: sq = "Sq" if len(mono) == 0: return sq + "(0)" else: string = "" for (s,t) in mono: if s + t > 4: comma = "," else: comma = "" string = string + "s_{" for i in range(t): string = string + str(2**(s+i)) + comma string = string.strip(",") + "} " return string.strip(" ")
• ## new file sage/algebras/steenrod_milnor_multiplication.py

diff -r c25e04ebfb67 -r 9fbc77f226c8 sage/algebras/steenrod_milnor_multiplication.py
 - r""" Milnor multiplication for elements of the mod 2 Steenrod algebra AUTHORS: - John H. Palmieri (2008-07-30: version 0.9) See Milnor's paper [Mil] for proofs, etc. To multiply Milnor basis elements $\text{Sq}(r_1, r_2, ...)$ and $\text{Sq}(s_1, s_2,...)$, form all possible matrices $M$ with rows and columns indexed starting at 0, with position (0,0) deleted (or ignored), with $s_i$ equal to the sum of column $i$ for each $i$, and with $r_j$ equal to the 'weighted' sum of row $j$.  The weights are as follows: elements from column $i$ are multiplied by $2^i$.  For example, to multiply $\text{Sq}(2)$ and $\text{Sq}(1,1)$, form the matrices $\begin{Vmatrix} * & 1 & 1 \\ 2 & 0 & 0 \end{Vmatrix} \quad \text{and} \quad \begin{Vmatrix} * & 0 & 1 \\ 0 & 1 & 0 \end{Vmatrix}$ (The $*$ is the ignored (0,0)-entry of the matrix.)  For each such matrix $M$, compute a multinomial coefficient, mod 2: for each diagonal $\{m_{ij}: i+j=n\}$, compute $(\sum m_{i,j}!) / (m_{0,n}! m_{1,n-1}! ... m_{n,0}!)$.  Multiply these together for all $n$.  (To compute this mod 2, view the entries of the matrix as their base 2 expansions; then this coefficient is zero if and only if there is some diagonal containing two numbers which have a summand in common in their base 2 expansion.  For example, if 3 and 10 are in the same diagonal, the coefficient is zero, because $3=1+2$ and $10=2+8$: they both have a summand of 2.) Now, for each matrix with multinomial coefficient 1, let $t_n$ be the sum of the nth diagonal in the matrix; then $\text{Sq}(r_1, r_2, ...) \text{Sq}(s_1, s_2, ...) = \sum \text{Sq}(t_1, t_2, ...)$ The function \code{milnor_multiplication} takes as input two tuples of non-negative integers, $r$ and $s$, which represent $\text{Sq}(r)=\text{Sq}(r_1, r_2, ...)$ and $\text{Sq}(s)=\text{Sq}(s_1, s_2, ...)$; it returns as output a dictionary whose keys are tuples $t=(t_1, t_2, ...)$ of non-negative integers, and for each tuple the associated value is the coefficient of $\text{Sq}(t)$ in the product formula.  Since we are working mod 2, this coefficient is 1 (if it is zero, the the element is omitted from the dictionary altogether). EXAMPLES: sage: from sage.algebras.steenrod_milnor_multiplication import milnor_multiplication sage: milnor_multiplication((2,), (1,)) {(0, 1): 1, (3,): 1} sage: milnor_multiplication((4,), (2,1)) {(6, 1): 1, (0, 3): 1, (2, 0, 1): 1} sage: milnor_multiplication((2,4), (0,1)) {(2, 5): 1, (2, 0, 0, 1): 1} These examples correspond to the following product computations: \begin{gather*} \text{Sq}(2) \text{Sq}(1) = \text{Sq}(0,1) + \text{Sq}(3) \text{Sq}(4) \text{Sq}(2,1) = \text{Sq}(6,1) + \text{Sq}(0,3) + \text{Sq}(2,0,1) \text{Sq}(2,4) \text{Sq}(0,1) = \text{Sq}(2, 5) + \text{Sq}(2, 0, 0, 1) \end{gather*} REFERENCES: [Mil] J. W. Milnor, "The Steenrod algebra and its dual, Ann. of Math. (2) \textbf{67} (1958), 150--171. """ #***************************************************************************** #       Copyright (C) 2008 John H. Palmieri #  Distributed under the terms of the GNU General Public License (GPL) #***************************************************************************** def milnor_multiplication(r,s): r""" Product of Milnor basis elements r and s. INPUT: r -- tuple of non-negative integers s -- tuple of non-negative integers OUTPUT: Dictionary of terms of the form (tuple: coeff), where 'tuple' is a tuple of non-negative integers and 'coeff' is 1. This computes Milnor matrices for the product of $\text{Sq}(r)$ and $\text{Sq}(s)$, computes their multinomial coefficients, and for each matrix whose coefficient is 1, add $\text{Sq}(t)$ to the output, where $t$ is the tuple formed by the diagonals sums from the matrix. EXAMPLES: sage: from sage.algebras.steenrod_milnor_multiplication import milnor_multiplication sage: milnor_multiplication((2,), (1,)) {(0, 1): 1, (3,): 1} sage: milnor_multiplication((4,), (2,1)) {(6, 1): 1, (0, 3): 1, (2, 0, 1): 1} sage: milnor_multiplication((2,4), (0,1)) {(2, 5): 1, (2, 0, 0, 1): 1} This uses the same algorithm Monks does in his Maple package. """ result = {} rows = len(r) + 1 cols = len(s) + 1 diags = len(r) + len(s) # initialize matrix M = range(rows) for i in range(rows): M[i] = [0]*cols for j in range(1,cols): M[0][j] = s[j-1] for i in range(1,rows): M[i][0] = r[i-1] for j in range(1,cols): M[i][j] = 0 found = True while found: # check diagonals n = 1 okay = 1 diagonal = [0]*diags while n <= diags and okay is not None: nth_diagonal = [M[i][n-i] for i in range(max(0,n-cols+1), min(1+n,rows))] okay = multinomial(nth_diagonal) diagonal[n-1] = okay n = n + 1 if okay is not None: i = diags - 1 while i >= 0 and diagonal[i] == 0: i = i - 1 t = tuple(diagonal[:i+1]) # reduce mod two: if result.has_key(t): del result[t] else: result[t] = 1 # now look for new matrices: found = False i = 1 while not found and i < rows: sum = M[i][0] j = 1 while not found and j < cols: # check to see if column index j is small enough if sum >= 2**j: # now check to see if there's anything above this entry # to add to it temp_col_sum = 0 for k in range(i): temp_col_sum += M[k][j] if temp_col_sum != 0: found = True for row in range(1,i): M[row][0] = r[row-1] for col in range(1,cols): M[0][col] = M[0][col] + M[row][col] M[row][col] = 0 for col in range(1,j): M[0][col] = M[0][col] + M[i][col] M[i][col] = 0 M[0][j] = M[0][j] - 1 M[i][j] = M[i][j] + 1 M[i][0] = sum - 2**j else: sum = sum + M[i][j] * 2**j else: sum = sum + M[i][j] * 2**j j = j + 1 i = i + 1 return result def multinomial(list): """ Multinomial coefficient of list, mod 2. INPUT: list -- list of integers OUTPUT: None if the multinomial coefficient is 0, or sum of list if it is 1 Given the input $[n_1, n_2, n_3, ...]$, this computes the multinomial coefficient $(n_1 + n_2 + n_3 + ...)! / (n_1! n_2! n_3! ...)$, mod 2.  The method is roughly this: expand each $n_i$ in binary.  If there is a 1 in the same digit for any $n_i$ and $n_j$ with $i\neq j$, then the coefficient is 0; otherwise, it is 1. EXAMPLES: sage: from sage.algebras.steenrod_milnor_multiplication import multinomial sage: multinomial([1,2,4]) 7 sage: multinomial([1,2,5]) sage: multinomial([1,2,12,192,256]) 463 This function does not compute any factorials, so the following are actually reasonable to do: sage: multinomial([1,65536]) 65537 sage: multinomial([4,65535]) sage: multinomial([32768,65536]) 98304 """ old_sum = list[0] okay = True i = 1 while okay and i < len(list): j = 1 while okay and j <= min(old_sum, list[i]): if j & old_sum == j: okay = (j & list[i] == 0) j = j << 1 old_sum = old_sum + list[i] i = i + 1 if okay: return old_sum else: return None
• ## new file sage/algebras/steenrod_milnor_multiplication_odd.py

diff -r c25e04ebfb67 -r 9fbc77f226c8 sage/algebras/steenrod_milnor_multiplication_odd.py
 - r""" Milnor multiplication for elements of the odd primary Steenrod algebra AUTHORS: - John H. Palmieri (2008-07-30: version 0.9) See Milnor's paper [Mil] for proofs, etc. Fix an odd prime $p$.  There are three steps to multiply Milnor basis elements $Q_{f_1} Q_{f_2} ... \mathcal{P}(q_1, q_2, ...)$ and $Q_{g_1} Q_{g_2} ... \mathcal{P}(s_1, s_2,...)$: first, use the formula $\mathcal{P}(q_1, q_2, ...) Q_k = Q_k \mathcal{P}(q_1, q_2, ...) + Q_{k+1} \mathcal{P}(q_1 - p^k, q_2, ...) + Q_{k+2} \mathcal{P}(q_1, q_2 - p^k, ...) + ...$ Second, use the fact that the $Q_k$'s form an exterior algebra: $Q_k^2 = 0$ for all $k$, and if $i \neq j$, then $Q_i$ and $Q_j$ anticommute: $Q_i Q_j = -Q_j Q_i$.  After these two steps, the product is of the form $\sum Q_{e_1} Q_{e_2} ... \mathcal{P}(r_1, r_2, ...) \mathcal{P}(s_1, s_2, ...).$ Finally, use Milnor matrices to multiply the pairs of $\mathcal{P}(...)$ terms: form all possible matrices $M$ with rows and columns indexed starting at 0, with position (0,0) deleted (or ignored), with $s_i$ equal to the sum of column $i$ for each $i$, and with $r_j$ equal to the 'weighted' sum of row $j$.  The weights are as follows: elements from column $i$ are multiplied by $p^i$.  For example when $p=5$, to multiply $\mathcal{P}(5)$ and $\mathcal{P}(1,1)$, form the matrices $\begin{Vmatrix} * & 1 & 1 \\ 5 & 0 & 0 \end{Vmatrix} \quad \text{and} \quad \begin{Vmatrix} * & 0 & 1 \\ 0 & 1 & 0 \end{Vmatrix}$ (The $*$ is the ignored (0,0)-entry of the matrix.)  For each such matrix $M$, compute a multinomial coefficient, mod $p$: for each diagonal $\{m_{ij}: i+j=n\}$, compute $(\sum m_{i,j}!) / (m_{0,n}! m_{1,n-1}! ... m_{n,0}!)$.  Multiply these together for all $n$. Now, for each matrix with nonzero multinomial coefficient $b_M$, let $t_n$ be the sum of the $n$th diagonal in the matrix; then $\mathcal{P}(r_1, r_2, ...) \mathcal{P}(s_1, s_2, ...) = \sum b_M \mathcal{P}(t_1, t_2, ...)$ For example when $p=5$, we have $\mathcal{P}(5) \mathcal{P}(1,1) = \mathcal{P}(6,1) + 2 \mathcal{P}(0,2).$ The function \code{milnor_multiplication} takes as input two pairs of tuples of non-negative integers, $(g,q)$ and $(f,s)$, which represent $Q_{g_1} Q_{g_2} ... \mathcal{P}(q_1, q_2, ...)$ and $Q_{f_1} Q_{f_2} ... \mathcal{P}(s_1, s_2, ...)$.  It returns as output a dictionary whose keys are pairs of tuples $(e,t)$ of non-negative integers, and for each tuple the associated value is the coefficient in the product formula. EXAMPLES: sage: from sage.algebras.steenrod_milnor_multiplication_odd import milnor_multiplication_odd sage: milnor_multiplication_odd(((0,2),(5,)), ((1,),(1,)), 5) {((0, 1, 2), (0, 1)): 4, ((0, 1, 2), (6,)): 4} sage: milnor_multiplication_odd(((0,2,4),()), ((1,3),()), 7) {((0, 1, 2, 3, 4), ()): 6} sage: milnor_multiplication_odd(((0,2,4),()), ((1,5),()), 7) {((0, 1, 2, 4, 5), ()): 1} sage: milnor_multiplication_odd(((),(6,)), ((),(2,)), 3) {((), (4, 1)): 1, ((), (8,)): 1, ((), (0, 2)): 1} These examples correspond to the following product computations: \begin{gather*} p=5: \quad Q_0 Q_2 \mathcal{P}(5) Q_1 \mathcal{P}(1) = 4 Q_0 Q_1 Q_2 \mathcal{P}(0,1) + 4 Q_0 Q_1 Q_2 \mathcal{P}(6) \\ p=7: \quad (Q_0 Q_2 Q_4) (Q_1 Q_3) = 6 Q_0 Q_1 Q_2 Q_3 Q_4 \\ p=7: \quad (Q_0 Q_2 Q_4) (Q_1 Q_5) = Q_0 Q_1 Q_2 Q_3 Q_5 \\ p=3: \quad \mathcal{P}(6) \mathcal{P}(2) = \mathcal{P}(0,2) + \mathcal{P}(4,1) + \mathcal{P}(8) \end{gather*} REFERENCES: [Mil] J. W. Milnor, "The Steenrod algebra and its dual, Ann. of Math. (2) \textbf{67} (1958), 150--171. """ #***************************************************************************** #       Copyright (C) 2008 John H. Palmieri #  Distributed under the terms of the GNU General Public License (GPL) #***************************************************************************** def milnor_multiplication_odd(m1,m2,p): r""" Product of Milnor basis elements defined by m1 and m2. INPUT: m1 -- pair of tuples (e,r), where e is an increasing tuple of non-negative integers and r is a tuple of non-negative integers m2 -- pair of tuples (f,s), same format as m1 p -- odd prime number OUTPUT: Dictionary of terms of the form (tuple: coeff), where 'tuple' is a pair of tuples, as for r and s, and 'coeff' is an integer mod p. This computes the product of the Milnor basis elements $Q_e1 Q_e2 ... P(r_1, r_2, ...)$ and $Q_f1 Q_f2 ... P(s_1, s_2, ...)$. EXAMPLES: sage: from sage.algebras.steenrod_milnor_multiplication_odd import milnor_multiplication_odd sage: milnor_multiplication_odd(((0,2),(5,)), ((1,),(1,)), 5) {((0, 1, 2), (0, 1)): 4, ((0, 1, 2), (6,)): 4} sage: milnor_multiplication_odd(((0,2,4),()), ((1,3),()), 7) {((0, 1, 2, 3, 4), ()): 6} sage: milnor_multiplication_odd(((0,2,4),()), ((1,5),()), 7) {((0, 1, 2, 4, 5), ()): 1} sage: milnor_multiplication_odd(((),(6,)), ((),(2,)), 3) {((), (4, 1)): 1, ((), (8,)): 1, ((), (0, 2)): 1} This uses the same algorithm Monks does in his Maple package to iterate through the possible matrices. """ from sage.rings.all import GF F = GF(p) (f,s) = m2 # First compute Q_e0 Q_e1 ... P(r1, r2, ...) Q_f0 Q_f1 ... # Store results (as dictionary of pairs of tuples) in 'answer'. answer = {m1: F(1)} for k in f: old_answer = answer answer = {} for mono in old_answer: if k not in mono[0]: q_mono = set(mono[0]) if len(q_mono) > 0: ind = len(q_mono.intersection(range(k,1+max(q_mono)))) else: ind = 0 coeff = (-1)**ind * old_answer[mono] lst = list(mono[0]) if ind == 0: lst.append(k) else: lst.insert(-ind,k) q_mono = tuple(lst) p_mono = mono[1] answer[(q_mono, p_mono)] = F(coeff) for i in range(1,1+len(mono[1])): if (k+i not in mono[0]) and (p**k <= mono[1][i-1]): q_mono = set(mono[0]) if len(q_mono) > 0: ind = len(q_mono.intersection(range(k+i,1+max(q_mono)))) else: ind = 0 coeff = (-1)**ind lst = list(mono[0]) if ind == 0: lst.append(k+i) else: lst.insert(-ind,k+i) q_mono = tuple(lst) p_mono = list(mono[1]) p_mono[i-1] = p_mono[i-1] - p**k answer[(q_mono, tuple(p_mono))] = F(coeff) # Now for the Milnor matrices.  For each entry '(e,r): coeff' in answer, # multiply r with s.  Record coefficient for matrix and multiply by coeff. # Store in 'result'. if len(s) == 0: result = answer else: result = {} for (e, r) in answer: old_coeff = answer[(e,r)] # Milnor multiplication for r and s rows = len(r) + 1 cols = len(s) + 1 diags = len(r) + len(s) # initialize matrix M = range(rows) for i in range(rows): M[i] = [0]*cols for j in range(1,cols): M[0][j] = s[j-1] for i in range(1,rows): M[i][0] = r[i-1] for j in range(1,cols): M[i][j] = 0 found = True while found: # check diagonals n = 1 coeff = old_coeff diagonal = [0]*diags while n <= diags and coeff != 0: nth_diagonal = [M[i][n-i] for i in range(max(0,n-cols+1), min(1+n,rows))] coeff = coeff * multinomial_odd(nth_diagonal,p) diagonal[n-1] = sum(nth_diagonal) n = n + 1 if coeff != 0: i = diags - 1 while i >= 0 and diagonal[i] == 0: i = i - 1 t = tuple(diagonal[:i+1]) if result.has_key((e,t)): result[(e,t)] = F(coeff + result[t]) else: result[(e,t)] = F(coeff) # now look for new matrices: found = False i = 1 while not found and i < rows: temp_sum = M[i][0] j = 1 while not found and j < cols: # check to see if column index j is small enough if temp_sum >= p**j: # now check to see if there's anything above this entry # to add to it temp_col_sum = 0 for k in range(i): temp_col_sum += M[k][j] if temp_col_sum != 0: found = True for row in range(1,i): M[row][0] = r[row-1] for col in range(1,cols): M[0][col] = M[0][col] + M[row][col] M[row][col] = 0 for col in range(1,j): M[0][col] = M[0][col] + M[i][col] M[i][col] = 0 M[0][j] = M[0][j] - 1 M[i][j] = M[i][j] + 1 M[i][0] = temp_sum - p**j else: temp_sum += M[i][j] * p**j else: temp_sum += M[i][j] * p**j j = j + 1 i = i + 1 return result def multinomial_odd(list,p): """ Multinomial coefficient of list, mod p. INPUT: list -- list of integers p -- a prime number OUTPUT: Associated multinomial coefficient, mod p Given the input $[n_1, n_2, n_3, ...]$, this computes the multinomial coefficient $(n_1 + n_2 + n_3 + ...)! / (n_1! n_2! n_3! ...)$, mod $p$.  The method is this: expand each $n_i$ in base $p$: $n_i = \sum_j p^j n_{ij}$.  Do the same for the sum of the $n_i$'s, which we call $m$: $m = \sum_j p^j m_j$.  Then the multinomial coefficient is congruent, mod $p$, to the product of the multinomial coefficients $m_j! / (n_{1j}! n_{2j}! ...)$. Furthermore, any multinomial coefficient $m! / (n_1! n_2! ...)$ can be computed as a product of binomial coefficients: it equals $\binom{n_1}{n_1} \binom{n_1 + n_2}{n_2} \binom{n_1 + n_2 + n_3}{n_3} ...$ This is convenient because Sage's binomial function returns integers, not rational numbers (as would be produced just by dividing factorials). EXAMPLES: sage: from sage.algebras.steenrod_milnor_multiplication_odd import multinomial_odd sage: multinomial_odd([1,2,4], 2) 1 sage: multinomial_odd([1,2,4], 7) 0 sage: multinomial_odd([1,2,4], 11) 6 sage: multinomial_odd([1,2,4], 101) 4 sage: multinomial_odd([1,2,4], 107) 105 """ from sage.rings.arith import factorial from sage.rings.all import GF from sage.rings.arith import binomial from sage.algebras.steenrod_algebra_element import base_p_expansion n = sum(list) answer = 1 F = GF(p) n_expansion = base_p_expansion(n,p) list_expansion = [base_p_expansion(k,p) for k in list] index = 0 while answer != 0 and index < len(n_expansion): multi = F(1) partial_sum = 0 for exp in list_expansion: if index < len(exp): partial_sum = partial_sum + exp[index] multi = F(multi * binomial(partial_sum, exp[index])) answer = F(answer * multi) index += 1 return answer