Steenrod Algebra Modules - Thematic Tutorials

Skip to content
Back to top
View this page
Edit this page

# Steenrod Algebra Modules

Author: Robert R. Bruner, Michael J. Catanzaro, Sverre Lunoee–Nielsen, and Koen van Woerden

Let \(p\) be a prime number. The mod \(p\) Steenrod algebra \(A\) is a connected algebra over \(\GF{p}\), the finite field of \(p\) elements. All modules presented here will be defined over \(A\) or one of its sub-Hopf algebras. E.g.:

Sage

    sage: A = SteenrodAlgebra(p=2)

Python

    >>> from sage.all import *
    >>> A = SteenrodAlgebra(p=Integer(2))

The constructor of the module class takes as arguments an ordered tuple of degrees and the algebra over which the module is defined, together with an optional set of relations:

Sage

    sage: from sage.modules.fp_graded.steenrod.module import SteenrodFPModule
    sage: F = SteenrodFPModule(A, [0, 1, 7]); F
    Free graded left module on 3 generators over mod 2 Steenrod algebra, milnor basis

[Python]

    >>> from sage.all import *
    >>> from sage.modules.fp_graded.steenrod.module import SteenrodFPModule
    >>> F = SteenrodFPModule(A, [Integer(0), Integer(1), Integer(7)]); F
    Free graded left module on 3 generators over mod 2 Steenrod algebra, milnor basis

Denote the module generators of an \(A\)-module \(M\) by \(g_{d_1}, \ldots, g_{d_N}\), where subscripts denote their degrees. A homogeneous relation of degree \(n\) has the form

\[\sum_{i=1}^N a_i\cdot g_{d_i} = 0,\]

where the homogeneous coefficients \(a_1, \ldots, a_N\) lie in \(A\), such that \(\deg(a_i) + \deg(g_{d_i}) = n\) for \(i = 1, \ldots, N\). To create a module with relations, the coefficients for each relation is given:

Sage

    sage: r1 = [Sq(8), Sq(7), 0]   # First relation
    sage: r2 = [Sq(7), 0, 1]       # Second relation
    sage: M = SteenrodFPModule(A, [0, 1, 7], relations=[r1, r2]); M
    Finitely presented left module on 3 generators and 2 relations
     over mod 2 Steenrod algebra, milnor basis

Python

    >>> from sage.all import *
    >>> r1 = [Sq(Integer(8)), Sq(Integer(7)), Integer(0)]   # First relation
    >>> r2 = [Sq(Integer(7)), Integer(0), Integer(1)]       # Second relation
    >>> M = SteenrodFPModule(A, [Integer(0), Integer(1), Integer(7)], relations=[r1, r2]); M
    Finitely presented left module on 3 generators and 2 relations
     over mod 2 Steenrod algebra, milnor basis

The resulting module will have three generators in the degrees we gave them:

Sage

    sage: M.generator_degrees()
    (0, 1, 7)

[Python]

    >>> from sage.all import *
    >>> M.generator_degrees()
    (0, 1, 7)

The connectivity of a module over a connected graded algebra is the minimum degree of all its module generators. Thus, if the module is non-trivial, the connectivity is an integer:

Sage

    sage: M.connectivity()
    0

Python

    >>> from sage.all import *
    >>> M.connectivity()
    0

Each module is defined over a Steenrod algebra or some sub-Hopf algebra of it, given by its base ring:

Sage

    sage: M.base_ring()
    mod 2 Steenrod algebra, milnor basis
    sage: SteenrodFPModule(SteenrodAlgebra(p=2,profile=(3,2,1)), [0]).base_ring()
    sub-Hopf algebra of mod 2 Steenrod algebra, milnor basis, profile function
    [3, 2, 1]

[Python]

    >>> from sage.all import *
    >>> M.base_ring()
    mod 2 Steenrod algebra, milnor basis
    >>> SteenrodFPModule(SteenrodAlgebra(p=Integer(2),profile=(Integer(3),Integer(2),Integer(1))), [Integer(0)]).base_ring()
    sub-Hopf algebra of mod 2 Steenrod algebra, milnor basis, profile function
    [3, 2, 1]

Note

Calling `algebra()` will not return the desired algebra. Users should use the `base_ring()` method.

## Module elements

Module elements are displayed in terms of generators, which by default are called `g[degree]`:

Sage

    sage: M.an_element(n=5)
    Sq(2,1)*g[0] + Sq(4)*g[1]

    sage: e = M.an_element(n=15); e
    Sq(0,0,0,1)*g[0] + Sq(1,2,1)*g[1] + Sq(8)*g[7]
    sage: e.dense_coefficient_list()
    [Sq(0,0,0,1), Sq(1,2,1), Sq(8)]

Python

    >>> from sage.all import *
    >>> M.an_element(n=Integer(5))
    Sq(2,1)*g[0] + Sq(4)*g[1]

    >>> e = M.an_element(n=Integer(15)); e
    Sq(0,0,0,1)*g[0] + Sq(1,2,1)*g[1] + Sq(8)*g[7]
    >>> e.dense_coefficient_list()
    [Sq(0,0,0,1), Sq(1,2,1), Sq(8)]

The generators are themselves elements of the module:

Sage

    sage: gens = M.generators(); gens
    (g[0], g[1], g[7])
    sage: gens[0] in M
    True

[Python]

    >>> from sage.all import *
    >>> gens = M.generators(); gens
    (g[0], g[1], g[7])
    >>> gens[Integer(0)] in M
    True

One can produce an element from a given set of algebra coefficients:

Sage

    sage: coeffs = [Sq(15), Sq(10)*Sq(1,1), Sq(8)]
    sage: x = M(coeffs); x
    Sq(15)*g[0] + (Sq(4,1,1)+Sq(7,0,1)+Sq(11,1))*g[1] + Sq(8)*g[7]

Python

    >>> from sage.all import *
    >>> coeffs = [Sq(Integer(15)), Sq(Integer(10))*Sq(Integer(1),Integer(1)), Sq(Integer(8))]
    >>> x = M(coeffs); x
    Sq(15)*g[0] + (Sq(4,1,1)+Sq(7,0,1)+Sq(11,1))*g[1] + Sq(8)*g[7]

The module action produces new elements:

Sage

    sage: Sq(2) * x
    Sq(14,1)*g[0] + (Sq(7,1)+Sq(10))*g[7]

[Python]

    >>> from sage.all import *
    >>> Sq(Integer(2)) * x
    Sq(14,1)*g[0] + (Sq(7,1)+Sq(10))*g[7]

Each non-zero homogeneous element has a well-defined degree:

Sage

    sage: x.degree()
    15

Python

    >>> from sage.all import *
    >>> x.degree()
    15

but the zero element does not:

Sage

    sage: zero = M.zero(); zero
    0
    sage: zero.degree()
    Traceback (most recent call last):
    ...
    ValueError: the zero element does not have a well-defined degree

[Python]

    >>> from sage.all import *
    >>> zero = M.zero(); zero
    0
    >>> zero.degree()
    Traceback (most recent call last):
    ...
    ValueError: the zero element does not have a well-defined degree

At this point it may be useful to point out that elements are not reduced to a minimal representation when they are created. A normalization can be enforced, however:

Sage

    sage: g7 = M([0, 0, 1]); g7
    g[7]
    sage: g7.normalize()
    Sq(7)*g[0]
    sage: g7 == g7.normalize()
    True

    sage: m = M([Sq(7), 0, 0])
    sage: s = m + g7; s         # m and g7 are related by m = Sq(7)*g[0] = g[7],
    Sq(7)*g[0] + g[7]
    sage: s == 0                # so their sum should zero.
    True
    sage: s.normalize()         # Its normalized form is more revealing.
    0

Python

    >>> from sage.all import *
    >>> g7 = M([Integer(0), Integer(0), Integer(1)]); g7
    g[7]
    >>> g7.normalize()
    Sq(7)*g[0]
    >>> g7 == g7.normalize()
    True

    >>> m = M([Sq(Integer(7)), Integer(0), Integer(0)])
    >>> s = m + g7; s         # m and g7 are related by m = Sq(7)*g[0] = g[7],
    Sq(7)*g[0] + g[7]
    >>> s == Integer(0)                # so their sum should zero.
    True
    >>> s.normalize()         # Its normalized form is more revealing.
    0

For every integer \(n\), the set of module elements of degree \(n\) form a vector space over the ground field \(\GF{p}\). A basis for this vector space can be computed:

Sage

    sage: M.basis_elements(7)
    (Sq(0,0,1)*g[0],
     Sq(1,2)*g[0],
     Sq(4,1)*g[0],
     Sq(7)*g[0],
     Sq(0,2)*g[1],
     Sq(3,1)*g[1],
     Sq(6)*g[1])

[Python]

    >>> from sage.all import *
    >>> M.basis_elements(Integer(7))
    (Sq(0,0,1)*g[0],
     Sq(1,2)*g[0],
     Sq(4,1)*g[0],
     Sq(7)*g[0],
     Sq(0,2)*g[1],
     Sq(3,1)*g[1],
     Sq(6)*g[1])

Note that the third generator \(g_7\) of degree 7 is apparently missing from the basis above. This is because of the relation \(\operatorname{Sq}^7(g_0) = g_7\).

A vector space presentation can be produced:

Sage

    sage: M.vector_presentation(5)
    Vector space quotient V/W of dimension 4 over Finite Field of size 2 where
    V: Vector space of dimension 4 over Finite Field of size 2
    W: Vector space of degree 4 and dimension 0 over Finite Field of size 2
       Basis matrix:
       []

Python

    >>> from sage.all import *
    >>> M.vector_presentation(Integer(5))
    Vector space quotient V/W of dimension 4 over Finite Field of size 2 where
    V: Vector space of dimension 4 over Finite Field of size 2
    W: Vector space of degree 4 and dimension 0 over Finite Field of size 2
       Basis matrix:
       []

Given any element, its coordinates with respect to this basis can be computed:

Sage

    sage: x = M.an_element(7); x
    Sq(0,0,1)*g[0] + Sq(3,1)*g[1] + g[7]
    sage: v = x.vector_presentation(); v
    (1, 0, 0, 1, 0, 1, 0)

[Python]

    >>> from sage.all import *
    >>> x = M.an_element(Integer(7)); x
    Sq(0,0,1)*g[0] + Sq(3,1)*g[1] + g[7]
    >>> v = x.vector_presentation(); v
    (1, 0, 0, 1, 0, 1, 0)

Going the other way, any element can be constructed by specifying its coordinates:

Sage

    sage: x_ = M.element_from_coordinates((1, 0, 0, 1, 0, 1, 0), 7)
    sage: x_
    (Sq(0,0,1)+Sq(7))*g[0] + Sq(3,1)*g[1]
    sage: x_ == x
    True

Python

    >>> from sage.all import *
    >>> x_ = M.element_from_coordinates((Integer(1), Integer(0), Integer(0), Integer(1), Integer(0), Integer(1), Integer(0)), Integer(7))
    >>> x_
    (Sq(0,0,1)+Sq(7))*g[0] + Sq(3,1)*g[1]
    >>> x_ == x
    True

## Module homomorphisms

Homomorphisms of \(A\)-modules \(M\to N\) are linear maps of their underlying \(\GF{p}\)-vector spaces which commute with the \(A\)-module structure. Homomorphisms are required to be homogeneneous but need not be degree zero.

To create a homomorphism, first create the object modeling the set of all such homomorphisms using the function `Hom`:

Sage

    sage: Hko = SteenrodFPModule(A, [0], [[Sq(2)], [Sq(1)]])
    sage: homspace = Hom(Hko, Hko); homspace
    Set of Morphisms
     from Finitely presented left module on 1 generator and 2 relations
          over mod 2 Steenrod algebra, milnor basis
       to Finitely presented left module on 1 generator and 2 relations
          over mod 2 Steenrod algebra, milnor basis
       in Category of finitely presented graded modules
          over mod 2 Steenrod algebra, milnor basis

Python

    >>> from sage.all import *
    >>> Hko = SteenrodFPModule(A, [Integer(0)], [[Sq(Integer(2))], [Sq(Integer(1))]])
    >>> homspace = Hom(Hko, Hko); homspace
    Set of Morphisms
     from Finitely presented left module on 1 generator and 2 relations
          over mod 2 Steenrod algebra, milnor basis
       to Finitely presented left module on 1 generator and 2 relations
          over mod 2 Steenrod algebra, milnor basis
       in Category of finitely presented graded modules
          over mod 2 Steenrod algebra, milnor basis

Just as with module elements, homomorphisms are created using the homspace. The only argument is a list of elements in the codomain, giving the images of the module generators of the domain:

Sage

    sage: gen = Hko.generator(0)  # the generator of the codomain module
    sage: values = [Sq(0, 0, 1) * gen]; values
    [Sq(0,0,1)*g[0]]
    sage: f = homspace(values)

[Python]

    >>> from sage.all import *
    >>> gen = Hko.generator(Integer(0))  # the generator of the codomain module
    >>> values = [Sq(Integer(0), Integer(0), Integer(1)) * gen]; values
    [Sq(0,0,1)*g[0]]
    >>> f = homspace(values)

The resulting homomorphism is the one sending the \(i\)-th generator of the domain to the \(i\)-th codomain value given:

Sage

    sage: f
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> Sq(0,0,1)*g[0]

Python

    >>> from sage.all import *
    >>> f
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> Sq(0,0,1)*g[0]

Homomorphisms can be evaluated on elements of the domain module:

Sage

    sage: v1 = f(Sq(4)*gen); v1
    Sq(4,0,1)*g[0]

    sage: v2 = f(Sq(2)*Sq(4)*gen); v2
    (Sq(3,1,1)+Sq(6,0,1))*g[0]

[Python]

    >>> from sage.all import *
    >>> v1 = f(Sq(Integer(4))*gen); v1
    Sq(4,0,1)*g[0]

    >>> v2 = f(Sq(Integer(2))*Sq(Integer(4))*gen); v2
    (Sq(3,1,1)+Sq(6,0,1))*g[0]

and they respect the module action:

Sage

    sage: f(Sq(4)*gen) == Sq(4)*f(gen)
    True

    sage: f(Sq(2)*Sq(4)*gen) == Sq(2)*Sq(4)*f(gen)
    True

Python

    >>> from sage.all import *
    >>> f(Sq(Integer(4))*gen) == Sq(Integer(4))*f(gen)
    True

    >>> f(Sq(Integer(2))*Sq(Integer(4))*gen) == Sq(Integer(2))*Sq(Integer(4))*f(gen)
    True

Convenience methods exist for creating the trivial morphism:

Sage

    sage: x = Sq(4)*Sq(7)*gen
    sage: x == 0
    False
    sage: zero_map = homspace.zero(); zero_map
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> 0
    sage: zero_map(x)
    0
    sage: zero_map(x).is_zero()
    True

[Python]

    >>> from sage.all import *
    >>> x = Sq(Integer(4))*Sq(Integer(7))*gen
    >>> x == Integer(0)
    False
    >>> zero_map = homspace.zero(); zero_map
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> 0
    >>> zero_map(x)
    0
    >>> zero_map(x).is_zero()
    True

as well as the identity endomorphism:

Sage

    sage: one = Hom(Hko, Hko).identity(); one
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> g[0]
    sage: one.is_endomorphism()
    True
    sage: one(x) == x
    True
    sage: one.is_identity()
    True

Python

    >>> from sage.all import *
    >>> one = Hom(Hko, Hko).identity(); one
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> g[0]
    >>> one.is_endomorphism()
    True
    >>> one(x) == x
    True
    >>> one.is_identity()
    True

Any non-trivial homomorphism has a well defined degree:

Sage

    sage: f.degree()
    7

[Python]

    >>> from sage.all import *
    >>> f.degree()
    7

but just as for module elements, the trivial homomorphism does not:

Sage

    sage: zero_map = homspace.zero()
    sage: zero_map.degree()
    Traceback (most recent call last):
    ...
    ValueError: the zero morphism does not have a well-defined degree

Python

    >>> from sage.all import *
    >>> zero_map = homspace.zero()
    >>> zero_map.degree()
    Traceback (most recent call last):
    ...
    ValueError: the zero morphism does not have a well-defined degree

Any two homomorphisms can be added as long as they are of the same degree:

Sage

    sage: f1 = homspace([Hko([Sq(0,0,3) + Sq(0,2,0,1)])])
    sage: f2 = homspace([Hko([Sq(8,2,1)])])
    sage: (f1 + f2).is_zero()
    False
    sage: f1 + f2
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> (Sq(0,0,3)+Sq(0,2,0,1)+Sq(8,2,1))*g[0]

[Python]

    >>> from sage.all import *
    >>> f1 = homspace([Hko([Sq(Integer(0),Integer(0),Integer(3)) + Sq(Integer(0),Integer(2),Integer(0),Integer(1))])])
    >>> f2 = homspace([Hko([Sq(Integer(8),Integer(2),Integer(1))])])
    >>> (f1 + f2).is_zero()
    False
    >>> f1 + f2
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> (Sq(0,0,3)+Sq(0,2,0,1)+Sq(8,2,1))*g[0]

or when at least one of them is zero:

Sage

    sage: f + zero_map == f
    True

Python

    >>> from sage.all import *
    >>> f + zero_map == f
    True

but not if they have different degrees:

Sage

    sage: F = SteenrodFPModule(A, [0])
    sage: b4 = Hom(F, F)([Sq(4) * F.generator(0)])
    sage: b8 = Hom(F, F)([Sq(8) * F.generator(0)])
    sage: b4 + b8
    Traceback (most recent call last):
    ...
    ValueError: morphisms do not have the same degree

[Python]

    >>> from sage.all import *
    >>> F = SteenrodFPModule(A, [Integer(0)])
    >>> b4 = Hom(F, F)([Sq(Integer(4)) * F.generator(Integer(0))])
    >>> b8 = Hom(F, F)([Sq(Integer(8)) * F.generator(Integer(0))])
    >>> b4 + b8
    Traceback (most recent call last):
    ...
    ValueError: morphisms do not have the same degree

Finally, additive inverses exist:

Sage

    sage: (f - f) == 0
    True

Python

    >>> from sage.all import *
    >>> (f - f) == Integer(0)
    True

The restriction of a homomorphism to the vector space of \(n\)-dimensional module elements is a linear transformation:

Sage

    sage: f_21 = f.vector_presentation(21); f_21
    Vector space morphism represented by the matrix:
    [1 0 0 0 0 0]
    [0 0 0 0 0 0]
    [1 0 0 0 0 0]
    Domain:   Vector space quotient V/W of dimension 3 over Finite Field of size 2 where
              V: Vector space of dimension 20 over Finite Field of size 2
              W: Vector space of degree 20 and dimension 17 over Finite Field of size 2
                 Basis matrix:
                 [1 0 0 0 0 0 0 0 0 0 0 0 1 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 0]
                 [0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
                 [0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1]
                 [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1]
                 [0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 1]
                 [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1]
                 [0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 1]
                 [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 0 0 0 1 0 0 0 0 0 0 0 0 0 1]
                 [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 0 0 0 1 0 0 0 0 0 0 0 1]
                 [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1]
                 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1]
                 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 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 0 0 0 1 1]
    Codomain: Vector space quotient V/W of dimension 6 over Finite Field of size 2 where
              V: Vector space of dimension 35 over Finite Field of size 2
              W: Vector space of degree 35 and dimension 29 over Finite Field of size 2
                 Basis matrix:
                 29 x 35 dense matrix over Finite Field of size 2

[Python]

    >>> from sage.all import *
    >>> f_21 = f.vector_presentation(Integer(21)); f_21
    Vector space morphism represented by the matrix:
    [1 0 0 0 0 0]
    [0 0 0 0 0 0]
    [1 0 0 0 0 0]
    Domain:   Vector space quotient V/W of dimension 3 over Finite Field of size 2 where
              V: Vector space of dimension 20 over Finite Field of size 2
              W: Vector space of degree 20 and dimension 17 over Finite Field of size 2
                 Basis matrix:
                 [1 0 0 0 0 0 0 0 0 0 0 0 1 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 0]
                 [0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
                 [0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1]
                 [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1]
                 [0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 0 0 0 1]
                 [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1]
                 [0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 1]
                 [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 0 0 0 1 0 0 0 0 0 0 0 0 0 1]
                 [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 0 0 0 1 0 0 0 0 0 0 0 1]
                 [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1]
                 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1]
                 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 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 0 0 0 1 1]
    Codomain: Vector space quotient V/W of dimension 6 over Finite Field of size 2 where
              V: Vector space of dimension 35 over Finite Field of size 2
              W: Vector space of degree 35 and dimension 29 over Finite Field of size 2
                 Basis matrix:
                 29 x 35 dense matrix over Finite Field of size 2

This is compatible with the vector presentations of its domain and codomain modules:

Sage

    sage: f.domain() is Hko
    True
    sage: f.codomain() is Hko
    True
    sage: f_21.domain() is Hko.vector_presentation(21)
    True
    sage: f_21.codomain() is Hko.vector_presentation(21 + f.degree())
    True

Python

    >>> from sage.all import *
    >>> f.domain() is Hko
    True
    >>> f.codomain() is Hko
    True
    >>> f_21.domain() is Hko.vector_presentation(Integer(21))
    True
    >>> f_21.codomain() is Hko.vector_presentation(Integer(21) + f.degree())
    True

Elements in the preimage of a homomorphism can be found:

Sage

    sage: f.solve(Sq(2)*Sq(4)*Sq(7)*gen)
    Sq(0,2)*g[0]

    sage: f.solve(Sq(8)*gen) is None
    True

[Python]

    >>> from sage.all import *
    >>> f.solve(Sq(Integer(2))*Sq(Integer(4))*Sq(Integer(7))*gen)
    Sq(0,2)*g[0]

    >>> f.solve(Sq(Integer(8))*gen) is None
    True

Homomorphisms can be composed as expected:

Sage

    sage: g = homspace([Sq(0, 0, 0, 1)*gen]); g
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> Sq(0,0,0,1)*g[0]

    sage: g*f
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> Sq(0,0,1,1)*g[0]

    sage: one = homspace.identity()
    sage: f*one == f
    True

Python

    >>> from sage.all import *
    >>> g = homspace([Sq(Integer(0), Integer(0), Integer(0), Integer(1))*gen]); g
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> Sq(0,0,0,1)*g[0]

    >>> g*f
    Module endomorphism of Finitely presented left module on 1 generator
     and 2 relations over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> Sq(0,0,1,1)*g[0]

    >>> one = homspace.identity()
    >>> f*one == f
    True

### Homological algebra

The category of modules over \(A\) is Abelian, so kernels, images and cokernels all exist and can be computed using `the morphisms`.

Note

Methods for morphisms like

- `kernel_inclusion()`,

- `cokernel_projection()`,

- `image()`,

- `homology()`

compute sub- and quotient modules related to homomorphisms, but they do not return instances of the module class. Rather, they return the natural homomorphisms which connect these modules to the modules that gave rise to them.

For example the function `kernel_inclusion()` returns an injective homomorphism which is onto the kernel submodule we asked it to compute, while the function `cokernel_projection()` provides a surjective homomorphism onto the cokernel module.

In each case, getting a reference to the module instance requires calling `domain()` or `codomain()` on the returned homomorphism, depending on the case.

Refer to each function’s documentation for specific details.

### Cokernels

In the following example, we define a cyclic module \(H\ZZ\) with one relation in two ways: first explicitly, and then as the cokernel of a homomorphism of free modules. We then construct a candidate for an isomorphism and check that it is both injective and surjective:

Sage

    sage: HZ = SteenrodFPModule(A, [0], [[Sq(1)]]); HZ
    Finitely presented left module on 1 generator and 1 relation
     over mod 2 Steenrod algebra, milnor basis

    sage: F = SteenrodFPModule(A, [0])
    sage: j = Hom(F, F)([Sq(1)*F.generator(0)])
    sage: coker = j.cokernel_projection()  # the natural quotient homomorphism onto the cokernel.
    sage: hz = coker.codomain(); hz
    Finitely presented left module on 1 generator and 1 relation
     over mod 2 Steenrod algebra, milnor basis

    sage: a = Hom(HZ, hz)([hz.generator(0)])
    sage: a.is_injective()
    True
    sage: a.is_surjective()
    True

Python

    >>> from sage.all import *
    >>> HZ = SteenrodFPModule(A, [Integer(0)], [[Sq(Integer(1))]]); HZ
    Finitely presented left module on 1 generator and 1 relation
     over mod 2 Steenrod algebra, milnor basis

    >>> F = SteenrodFPModule(A, [Integer(0)])
    >>> j = Hom(F, F)([Sq(Integer(1))*F.generator(Integer(0))])
    >>> coker = j.cokernel_projection()  # the natural quotient homomorphism onto the cokernel.
    >>> hz = coker.codomain(); hz
    Finitely presented left module on 1 generator and 1 relation
     over mod 2 Steenrod algebra, milnor basis

    >>> a = Hom(HZ, hz)([hz.generator(Integer(0))])
    >>> a.is_injective()
    True
    >>> a.is_surjective()
    True

### Kernels

When computing the kernel of a homomorphism \(f\), the result is an injective homomorphism into the domain of \(f\):

[Sage]

    sage: k = f.kernel_inclusion(); k
    Module morphism:
      From: Finitely presented left module on 1 generator and 3 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[7] |--> Sq(0,0,1)*g[0]
    sage: k.codomain() == f.domain()
    True
    sage: k.is_injective()
    True

    sage: ker = k.domain()
    sage: ker
    Finitely presented left module on 1 generator and 3 relations
     over mod 2 Steenrod algebra, milnor basis

[Python]

    >>> from sage.all import *
    >>> k = f.kernel_inclusion(); k
    Module morphism:
      From: Finitely presented left module on 1 generator and 3 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[7] |--> Sq(0,0,1)*g[0]
    >>> k.codomain() == f.domain()
    True
    >>> k.is_injective()
    True

    >>> ker = k.domain()
    >>> ker
    Finitely presented left module on 1 generator and 3 relations
     over mod 2 Steenrod algebra, milnor basis

We can check that the injective image of \(k\) is the kernel of \(f\) by showing that \(f\) factors as \(h\circ c\), where \(c\) is the quotient map to the cokernel of \(k\), and \(h\) is injective:

[Sage]

    sage: K = k.codomain()        # We want to check that this really is the kernel of f.
    sage: coker = k.cokernel_projection()    # coker is the natural map: Hko -> coker(f) with kernel K.
    sage: h = Hom(coker.codomain(), Hko)(f.values())
    sage: h*coker == f            # Is K contained in ker(f) ?
    True
    sage: h.is_injective()        # Is ker(f) contained in K ?
    True

[Python]

    >>> from sage.all import *
    >>> K = k.codomain()        # We want to check that this really is the kernel of f.
    >>> coker = k.cokernel_projection()    # coker is the natural map: Hko -> coker(f) with kernel K.
    >>> h = Hom(coker.codomain(), Hko)(f.values())
    >>> h*coker == f            # Is K contained in ker(f) ?
    True
    >>> h.is_injective()        # Is ker(f) contained in K ?
    True

### Images

The method `image()` behaves similarly, returning an injective homomorphism with image equal to the submodule \(\operatorname{im}(f)\):

Sage

    sage: i = f.image(); i
    Module morphism:
      From: Finitely presented left module on 1 generator and 3 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[7] |--> Sq(0,0,1)*g[0]
    sage: i.codomain() == f.codomain()
    True
    sage: i.is_injective()
    True

Python

    >>> from sage.all import *
    >>> i = f.image(); i
    Module morphism:
      From: Finitely presented left module on 1 generator and 3 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[7] |--> Sq(0,0,1)*g[0]
    >>> i.codomain() == f.codomain()
    True
    >>> i.is_injective()
    True

We can check that the injective image of \(i\) is the image of \(f\) by lifting \(f\) over \(i\), and showing that the lift is surjective:

Sage

    sage: f_ = f.lift(i); f_
    Module morphism:
      From: Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 3 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> g[7]
    sage: i*f_ == f             # Is im(i) contained in im(f) ?
    True
    sage: f_.is_surjective()    # Is im(f) contained in im(i) ?
    True

[Python]

    >>> from sage.all import *
    >>> f_ = f.lift(i); f_
    Module morphism:
      From: Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 3 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> g[7]
    >>> i*f_ == f             # Is im(i) contained in im(f) ?
    True
    >>> f_.is_surjective()    # Is im(f) contained in im(i) ?
    True

When a pair of composable homomorphisms \(g\circ f: M\to N\to L\) satisfy the condition \(g\circ f = 0\), the sub-quotient \(\ker(g) / \operatorname{im}(f)\) can be computed and is given by the natural quotient homomorphism with domain \(\ker(g)\):

Sage

    sage: f * f == 0        # Does the kernel of f contain the image of f ?
    True
    sage: K = f.kernel_inclusion()    # k: ker(f) -> Hko
    sage: h = f.homology(f) # h: ker(f) -> ker(f) / im(f)
    sage: h.codomain()      # This is the homology module.
    Finitely presented left module on 1 generator and 4 relations
     over mod 2 Steenrod algebra, milnor basis

Python

    >>> from sage.all import *
    >>> f * f == Integer(0)        # Does the kernel of f contain the image of f ?
    True
    >>> K = f.kernel_inclusion()    # k: ker(f) -> Hko
    >>> h = f.homology(f) # h: ker(f) -> ker(f) / im(f)
    >>> h.codomain()      # This is the homology module.
    Finitely presented left module on 1 generator and 4 relations
     over mod 2 Steenrod algebra, milnor basis

### Free resolutions

Finally, free resolutions can be computed. These calculations usually take some time to complete, so it is usually a good idea to raise the verbose flag to output progress information.

The following examples are taken from Michael Catanzaro’s thesis where the first version of this software appeared:

Sage

    sage: res = Hko.resolution(6, verbose=True)
    Computing f_1 (1/6)
    Computing f_2 (2/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [1, 8]: 1 2 3 4 5 6 7 8.
    Computing f_3 (3/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [2, 10]: 2 3 4 5 6 7 8 9 10.
    Computing f_4 (4/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [3, 13]: 3 4 5 6 7 8 9 10 11 12 13.
    Computing f_5 (5/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [4, 18]: 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18.
    Computing f_6 (6/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [5, 20]: 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20.

Python

    >>> from sage.all import *
    >>> res = Hko.resolution(Integer(6), verbose=True)
    Computing f_1 (1/6)
    Computing f_2 (2/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [1, 8]: 1 2 3 4 5 6 7 8.
    Computing f_3 (3/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [2, 10]: 2 3 4 5 6 7 8 9 10.
    Computing f_4 (4/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [3, 13]: 3 4 5 6 7 8 9 10 11 12 13.
    Computing f_5 (5/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [4, 18]: 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18.
    Computing f_6 (6/6)
    Computing using the profile:
    (2, 1)
    Resolving the kernel in the range of dimensions [5, 20]: 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20.

The result of the calculation is a list of all the maps in the resolution:

Sage

    sage: [f.domain() for f in res]
    [Free graded left module on 1 generator over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 2 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 2 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 2 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 3 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 4 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 4 generators over mod 2 Steenrod algebra, milnor basis]

    sage: def is_complex(res):
    ....:     for i in range(len(res)-1):
    ....:         f = (res[i]*res[i+1])
    ....:         if not f.is_zero():
    ....:             return False
    ....:     return True
    ....:
    sage: is_complex(res)
    True

    sage: def is_exact(res):
    ....:     for i in range(len(res)-1):
    ....:         h = res[i].homology(res[i+1])
    ....:         if not h.codomain().is_trivial():
    ....:             return False
    ....:     return True

    sage: is_exact(res)
    True

    sage: [r.codomain().generator_degrees() for r in res]
    [(0,), (0,), (2, 1), (2, 4), (3, 7), (4, 8, 12), (5, 9, 13, 14)]

    sage: [r.values() for r in res]
    [(g[0],),
     (Sq(2)*g[0], Sq(1)*g[0]),
     (Sq(1)*g[1], Sq(3)*g[1] + Sq(2)*g[2]),
     (Sq(1)*g[2], Sq(2,1)*g[2] + Sq(3)*g[4]),
     (Sq(1)*g[3], Sq(2,1)*g[3] + Sq(1)*g[7], Sq(2,1)*g[7]),
     (Sq(1)*g[4],
      Sq(2,1)*g[4] + Sq(1)*g[8],
      Sq(2,1)*g[8] + Sq(1)*g[12],
      Sq(2)*g[12]),
     (Sq(1)*g[5],
      Sq(2,1)*g[5] + Sq(1)*g[9],
      Sq(2,1)*g[9] + Sq(1)*g[13],
      Sq(0,1)*g[13] + Sq(2)*g[14])]

[Python]

    >>> from sage.all import *
    >>> [f.domain() for f in res]
    [Free graded left module on 1 generator over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 2 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 2 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 2 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 3 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 4 generators over mod 2 Steenrod algebra, milnor basis,
     Free graded left module on 4 generators over mod 2 Steenrod algebra, milnor basis]

    >>> def is_complex(res):
    ...     for i in range(len(res)-Integer(1)):
    ...         f = (res[i]*res[i+Integer(1)])
    ...         if not f.is_zero():
    ...             return False
    ...     return True
    ....:
    >>> is_complex(res)
    True

    >>> def is_exact(res):
    ...     for i in range(len(res)-Integer(1)):
    ...         h = res[i].homology(res[i+Integer(1)])
    ...         if not h.codomain().is_trivial():
    ...             return False
    ...     return True

    >>> is_exact(res)
    True

    >>> [r.codomain().generator_degrees() for r in res]
    [(0,), (0,), (2, 1), (2, 4), (3, 7), (4, 8, 12), (5, 9, 13, 14)]

    >>> [r.values() for r in res]
    [(g[0],),
     (Sq(2)*g[0], Sq(1)*g[0]),
     (Sq(1)*g[1], Sq(3)*g[1] + Sq(2)*g[2]),
     (Sq(1)*g[2], Sq(2,1)*g[2] + Sq(3)*g[4]),
     (Sq(1)*g[3], Sq(2,1)*g[3] + Sq(1)*g[7], Sq(2,1)*g[7]),
     (Sq(1)*g[4],
      Sq(2,1)*g[4] + Sq(1)*g[8],
      Sq(2,1)*g[8] + Sq(1)*g[12],
      Sq(2)*g[12]),
     (Sq(1)*g[5],
      Sq(2,1)*g[5] + Sq(1)*g[9],
      Sq(2,1)*g[9] + Sq(1)*g[13],
      Sq(0,1)*g[13] + Sq(2)*g[14])]

## Example: Counting lifts

In this more elaborate example we show how to find all possible lifts of a particular homomorphism. We will do this in two ways, and as a check of validity, we will compare the results in the end.

We will work with the following modules over the mod 2 Steenrod algebra \(A\):

\begin{align} H\ZZ &= A/A\cdot \operatorname{Sq}(1)\\ Hko &= A/A\cdot \{\operatorname{Sq}(2), \operatorname{Sq}(1)\} \,. \end{align}

There is a natural projection \(q: H\ZZ\to Hko\), and a non-trivial endomorphism of degree 28, represented as a degree zero map \(f: \Sigma^{28}Hko\to Hko\) that we define below.

The problem we will solve is to find all possible homomorphisms \(f': \Sigma^{28}Hko\to H\ZZ\), making the following diagram into a commuting triangle:

\[H\ZZ\xrightarrow{q} Hko \xleftarrow{f} \Sigma^{28} Hko.\]

We begin by defining the modules and the homomorphisms \(f\) and \(q\). In the following, we let \(L = \Sigma^{28}Hko\):

Sage

    sage: from sage.modules.fp_graded.steenrod.module import SteenrodFPModule
    sage: A = SteenrodAlgebra(2)
    sage: Hko = SteenrodFPModule(A, [0], [[Sq(2)],[Sq(1)]])
    sage: HZ = SteenrodFPModule(A, [0], [[Sq(1)]])
    sage: L = Hko.suspension(28)

Python

    >>> from sage.all import *
    >>> from sage.modules.fp_graded.steenrod.module import SteenrodFPModule
    >>> A = SteenrodAlgebra(Integer(2))
    >>> Hko = SteenrodFPModule(A, [Integer(0)], [[Sq(Integer(2))],[Sq(Integer(1))]])
    >>> HZ = SteenrodFPModule(A, [Integer(0)], [[Sq(Integer(1))]])
    >>> L = Hko.suspension(Integer(28))

The projection:

Sage

    sage: q = Hom(HZ, Hko)([Hko.generator(0)])
    sage: q
    Module morphism:
      From: Finitely presented left module on 1 generator and 1 relation
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> g[0]

[Python]

    >>> from sage.all import *
    >>> q = Hom(HZ, Hko)([Hko.generator(Integer(0))])
    >>> q
    Module morphism:
      From: Finitely presented left module on 1 generator and 1 relation
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[0] |--> g[0]

The map to lift over \(q\):

Sage

    sage: f = Hom(L, Hko)([Sq(0,2,1,1)*Hko.generator(0)])
    sage: f
    Module morphism:
      From: Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[28] |--> Sq(0,2,1,1)*g[0]

    sage: f.is_zero()   # f is non-trivial.
    False

Python

    >>> from sage.all import *
    >>> f = Hom(L, Hko)([Sq(Integer(0),Integer(2),Integer(1),Integer(1))*Hko.generator(Integer(0))])
    >>> f
    Module morphism:
      From: Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[28] |--> Sq(0,2,1,1)*g[0]

    >>> f.is_zero()   # f is non-trivial.
    False

We will count the number of different lifts in two ways. First, we will simply compute the vector space of all possible maps \(L \to H\ZZ\), and then check which of those become \(f\) when composed with \(q\):

Sage

    sage: basis = Hom(L, HZ).basis_elements(0)   # The basis for the vector space of degree 0 maps L -> HZ

    sage: from itertools import product
    sage: def from_coords(c):
    ....:     '''
    ....:     Create a linear combination of the three basis homomorphisms.
    ....:     '''
    ....:     return c[0]*basis[0] + c[1]*basis[1] + c[2]*basis[2]

    sage: for coords in product([0,1], repeat=3):
    ....:     print('%s: %s' % (coords, q*from_coords(coords) == f))
    (0, 0, 0): False
    (0, 0, 1): False
    (0, 1, 0): True
    (0, 1, 1): True
    (1, 0, 0): True
    (1, 0, 1): True
    (1, 1, 0): False
    (1, 1, 1): False

[Python]

    >>> from sage.all import *
    >>> basis = Hom(L, HZ).basis_elements(Integer(0))   # The basis for the vector space of degree 0 maps L -> HZ

    >>> from itertools import product
    >>> def from_coords(c):
    ...     '''
    ...     Create a linear combination of the three basis homomorphisms.
    ...     '''
    ...     return c[Integer(0)]*basis[Integer(0)] + c[Integer(1)]*basis[Integer(1)] + c[Integer(2)]*basis[Integer(2)]

    >>> for coords in product([Integer(0),Integer(1)], repeat=Integer(3)):
    ...     print('%s: %s' % (coords, q*from_coords(coords) == f))
    (0, 0, 0): False
    (0, 0, 1): False
    (0, 1, 0): True
    (0, 1, 1): True
    (1, 0, 0): True
    (1, 0, 1): True
    (1, 1, 0): False
    (1, 1, 1): False

From this we conclude that four out of eight different homomorphisms \(L \to H\ZZ\) are lifts of \(f\):

Sage

    sage: lifts = [from_coords((0,1,0)),
    ....:          from_coords((0,1,1)),
    ....:          from_coords((1,0,0)),
    ....:          from_coords((1,0,1))]
    sage: lifts
    [Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> Sq(6,5,1)*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(6,5,1)+Sq(18,1,1))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> Sq(10,1,0,1)*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(10,1,0,1)+Sq(18,1,1))*g[0]]

Python

    >>> from sage.all import *
    >>> lifts = [from_coords((Integer(0),Integer(1),Integer(0))),
    ...          from_coords((Integer(0),Integer(1),Integer(1))),
    ...          from_coords((Integer(1),Integer(0),Integer(0))),
    ...          from_coords((Integer(1),Integer(0),Integer(1)))]
    >>> lifts
    [Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> Sq(6,5,1)*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(6,5,1)+Sq(18,1,1))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> Sq(10,1,0,1)*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(10,1,0,1)+Sq(18,1,1))*g[0]]

Alternatively we can use left-exactness of the functor \(\operatorname{Hom}_A(L, -)\) to enumerate all possible lifts of \(f\). Start by finding a single lift of \(f\) over the projection \(q\):

Sage

    sage: fl = f.lift(q); fl
    Module morphism:
      From: Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 1 relation
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[28] |--> (Sq(4,3,0,1)+Sq(6,0,1,1)+Sq(7,2,0,1)+Sq(10,1,0,1))*g[0]

[Python]

    >>> from sage.all import *
    >>> fl = f.lift(q); fl
    Module morphism:
      From: Finitely presented left module on 1 generator and 2 relations
            over mod 2 Steenrod algebra, milnor basis
      To:   Finitely presented left module on 1 generator and 1 relation
            over mod 2 Steenrod algebra, milnor basis
      Defn: g[28] |--> (Sq(4,3,0,1)+Sq(6,0,1,1)+Sq(7,2,0,1)+Sq(10,1,0,1))*g[0]

We verify that `fl` is indeed a lift:

Sage

    sage: q * fl == f
    True

Python

    >>> from sage.all import *
    >>> q * fl == f
    True

There is an exact sequence

\[0 \to \operatorname{Hom}_A(L, \ker(q)) \xrightarrow{iK_*} \operatorname{Hom}_A(L, H\ZZ) \xrightarrow{q_*} \operatorname{Hom}_A(L, Hko),\]

which means that the indeterminacy of choosing a lift for \(f \in \operatorname{Hom}_A(L, Hko)\) is represented by an element in \(\operatorname{Hom}_A(L,\ker(q))\). Therefore, we can proceed to count the number of lifts by computing this vector space of homomorphisms:

Sage

    sage: iK = q.kernel_inclusion()
    sage: K = iK.domain()
    sage: K.generator_degrees()
    (2,)
    sage: K.relations()
    (Sq(2)*g[2],)
    sage: ind = Hom(L, K).basis_elements(0); len(ind)
    2

[Python]

    >>> from sage.all import *
    >>> iK = q.kernel_inclusion()
    >>> K = iK.domain()
    >>> K.generator_degrees()
    (2,)
    >>> K.relations()
    (Sq(2)*g[2],)
    >>> ind = Hom(L, K).basis_elements(Integer(0)); len(ind)
    2

So now we know that the vector space of indeterminacies is 2-dimensional over the field of two elements. This means that there are four distinct lifts of \(f\) over \(q\), and we can construct these by taking the one lift we already found, and add to it all the different elements in the image of \(iK_*\):

Sage

    sage: flift = [fl,
    ....:          fl + iK * ind[0],
    ....:          fl + iK * ind[1],
    ....:          fl + iK * (ind[0] + ind[1])]
    sage: flift
    [Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(4,3,0,1)+Sq(6,0,1,1)+Sq(7,2,0,1)+Sq(10,1,0,1))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(0,7,1)+Sq(3,6,1)+Sq(4,1,3)+Sq(6,0,1,1)+Sq(6,5,1)+Sq(7,0,3))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(4,3,0,1)+Sq(6,0,1,1)+Sq(7,2,0,1)+Sq(10,1,0,1)+Sq(12,3,1)+Sq(15,2,1)+Sq(18,1,1))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(0,7,1)+Sq(3,6,1)+Sq(4,1,3)+Sq(6,0,1,1)+Sq(6,5,1)+Sq(7,0,3)+Sq(12,3,1)+Sq(15,2,1)+Sq(18,1,1))*g[0]]

Python

    >>> from sage.all import *
    >>> flift = [fl,
    ...          fl + iK * ind[Integer(0)],
    ...          fl + iK * ind[Integer(1)],
    ...          fl + iK * (ind[Integer(0)] + ind[Integer(1)])]
    >>> flift
    [Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(4,3,0,1)+Sq(6,0,1,1)+Sq(7,2,0,1)+Sq(10,1,0,1))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(0,7,1)+Sq(3,6,1)+Sq(4,1,3)+Sq(6,0,1,1)+Sq(6,5,1)+Sq(7,0,3))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(4,3,0,1)+Sq(6,0,1,1)+Sq(7,2,0,1)+Sq(10,1,0,1)+Sq(12,3,1)+Sq(15,2,1)+Sq(18,1,1))*g[0],
     Module morphism:
       From: Finitely presented left module on 1 generator and 2 relations over mod 2 Steenrod algebra, milnor basis
       To:   Finitely presented left module on 1 generator and 1 relation over mod 2 Steenrod algebra, milnor basis
       Defn: g[28] |--> (Sq(0,7,1)+Sq(3,6,1)+Sq(4,1,3)+Sq(6,0,1,1)+Sq(6,5,1)+Sq(7,0,3)+Sq(12,3,1)+Sq(15,2,1)+Sq(18,1,1))*g[0]]

As a test of correctness, we now compare the two sets of lifts. As they stand, it is not obvious that the lists `flift` and `lifts` are the same (up to a re-ordering of list elements), so the following comparison is reassuring:

Sage

    sage: flift[0] == lifts[2]
    True
    sage: flift[1] == lifts[0]
    True
    sage: flift[2] == lifts[3]
    True
    sage: flift[3] == lifts[1]
    True

[Python]

    >>> from sage.all import *
    >>> flift[Integer(0)] == lifts[Integer(2)]
    True
    >>> flift[Integer(1)] == lifts[Integer(0)]
    True
    >>> flift[Integer(2)] == lifts[Integer(3)]
    True
    >>> flift[Integer(3)] == lifts[Integer(1)]
    True