Repository: sagemath/sage

Issue:
cohomology of simplicial complexes with finite coefficients as modules over steenrod algebras
<div id="comment:0"></div>

Implement functionality in sage to return the ZZ-graded cohomology with GF(p)-coefficients of an arbitrary simplicial complex as a module over the Steenrod algebra A(p).

This will depend on #6099, #6100, #6101, and #5882. The methodolgy will be similar to that implemented in the ticket #6102.

CC:  @jhpalmieri @antieau

Component: **algebraic topology**

_Issue created by migration from https://trac.sagemath.org/ticket/6103_




Discussion:

Resolving patch:
diff --git a/src/sage/homology/homology_vector_space_with_basis.py b/src/sage/homology/homology_vector_space_with_basis.py
--- a/src/sage/homology/homology_vector_space_with_basis.py
+++ b/src/sage/homology/homology_vector_space_with_basis.py
@@ -26,17 +26,24 @@
 #                  https://www.gnu.org/licenses/
 ########################################################################
 
+from sage.algebras.steenrod.steenrod_algebra import SteenrodAlgebra
 from sage.misc.cachefunc import cached_method
 from sage.categories.algebras import Algebras
+from sage.categories.category import Category
+from sage.categories.left_modules import LeftModules
+from sage.categories.right_modules import RightModules
 from sage.categories.modules import Modules
 from sage.combinat.free_module import CombinatorialFreeModule
+from sage.matrix.constructor import matrix
+from sage.modules.free_module_element import vector
 from sage.sets.family import Family
 
 try:
     from sage.topology.simplicial_complex import SimplicialComplex
     from sage.topology.simplicial_set import SimplicialSet_arbitrary
+    from sage.topology.cubical_complex import CubicalComplex
 except ImportError:
-    SimplicialComplex = SimplicialSet_arbitrary = ()
+    SimplicialComplex = SimplicialSet_arbitrary = CubicalComplex = ()
 
 
 class HomologyVectorSpaceWithBasis(CombinatorialFreeModule):
@@ -385,6 +392,65 @@ def _to_cycle_on_basis(self, i):
                                          cochains=self._cohomology)
         return chains.from_vector(vec)
 
+    def dual(self):
+        r"""
+        Return the dual space.
+
+        If ``self`` is homology, return the cohomology ring. If
+        ``self`` is cohomology, return the homology as a vector space.
+
+        EXAMPLES::
+
+            sage: T = simplicial_complexes.Torus()
+            sage: hom = T.homology_with_basis(GF(2))
+            sage: coh = T.cohomology_ring(GF(2))
+            sage: hom.dual() is coh
+            True
+            sage: coh.dual() is hom
+            True
+        """
+        if is_GF2(self.base_ring()):
+            if self._cohomology:
+                return HomologyVectorSpaceWithBasis_mod2(self.base_ring(),
+                                                         self.complex())
+            return CohomologyRing_mod2(self.base_ring(), self.complex())
+        if self._cohomology:
+            return HomologyVectorSpaceWithBasis(self.base_ring(),
+                                                self.complex(),
+                                                not self._cohomology)
+        return CohomologyRing(self.base_ring(), self.complex())
+
+    def _test_duality(self, **options):
+        r"""
+        Test if the ordered bases for homology and cohomology are compatible.
+        Return nothing if the test succeeds.
+
+        This checks whether each evaluation map `H^n \otimes H_n \to
+        k` is represented by the identity matrix, in terms of the
+        chosen bases.
+
+        TESTS::
+
+            sage: T = simplicial_complexes.Torus()
+            sage: K = T.suspension()
+            sage: K.set_immutable()
+            sage: H = K.cohomology_ring(QQ)
+            sage: H._test_duality()
+
+            sage: simplicial_complexes.RandomComplex(8, 2, .2).homology_with_basis(GF(2))._test_duality()
+            sage: simplicial_complexes.RandomComplex(8, 2, .4).homology_with_basis(GF(2))._test_duality()
+            sage: simplicial_complexes.RandomComplex(8, 2, .6).homology_with_basis(GF(2))._test_duality()
+
+            sage: simplicial_complexes.RandomComplex(12, 3, .5).homology_with_basis(GF(2))._test_duality() # long time
+        """
+        tester = self._tester(**options)
+        dual = self.dual()
+        dims = [a[0] for a in self._indices]
+        for dim in range(max(max(dims),  tester._max_runs) + 1):
+            n = len(self.basis(dim))
+            m = matrix(n, n, [a.eval(b) for a in self.basis(dim) for b in dual.basis(dim)])
+            tester.assertEqual(m, 1, f"error in dimension {dim}")
+
     class Element(CombinatorialFreeModule.Element):
         def to_cycle(self):
             r"""
@@ -413,6 +479,226 @@ def to_cycle(self):
                 raise ValueError("only defined for homogeneous elements")
             return sum(c * self.parent()._to_cycle_on_basis(i) for i, c in self)
 
+        def eval(self, other):
+            r"""
+            Evaluate ``self`` at ``other``.
+
+            INPUT:
+
+            - ``other`` -- an element of the dual space; if ``self``
+              is an element of cohomology in dimension `n`, then
+              ``other`` should be an element of homology in dimension
+              `n`, and vice versa
+
+            This just calls the :meth:`~sage.homology.chains.Cochains.Element.eval`
+            method on the representing chains and cochains.
+
+            EXAMPLES::
+
+                sage: T = simplicial_complexes.Torus()
+                sage: homology = T.homology_with_basis(QQ)
+                sage: cohomology = T.cohomology_ring(QQ)
+                sage: a1, a2 = homology.basis(1)
+                sage: alpha1, alpha2 = cohomology.basis(1)
+                sage: a1.to_cycle()
+                (0, 3) - (0, 6) + (3, 6)
+                sage: alpha1.to_cycle()
+                -\chi_(1, 3) - \chi_(1, 4) - \chi_(2, 3) - \chi_(2, 4) - \chi_(2, 5) + \chi_(3, 6)
+                sage: a1.eval(alpha1)
+                1
+                sage: alpha2.to_cycle()
+                \chi_(1, 3) + \chi_(1, 4) + \chi_(1, 6) + \chi_(2, 4) - \chi_(4, 5) + \chi_(5, 6)
+                sage: alpha2.eval(a1)
+                0
+                sage: (2 * alpha2).eval(a1 + a2)
+                2
+            """
+            if not self or not other:
+                return self.base_ring().zero()
+            if self.parent()._cohomology:
+                return self.to_cycle().eval(other.to_cycle())
+            else:
+                return other.to_cycle().eval(self.to_cycle())
+
+
+class HomologyVectorSpaceWithBasis_mod2(HomologyVectorSpaceWithBasis):
+    r"""
+    Homology vector space mod 2.
+
+    Based on :class:`HomologyVectorSpaceWithBasis`, with Steenrod
+    operations included.
+
+    .. NOTE::
+
+        This is not intended to be created directly by the user, but
+        instead via the method
+        :meth:`~sage.topology.cell_complex.GenericCellComplex.homology_with_basis`
+        for the class of :class:`cell
+        complexes<sage.topology.cell_complex.GenericCellComplex>`.
+
+    .. TODO::
+
+        Implement Steenrod operations on (co)homology at odd primes,
+        and thereby implement this class over `\GF{p}` for any `p`.
+
+    INPUT:
+
+    - ``base_ring`` -- must be the field ``GF(2)``
+    - ``cell_complex`` -- the cell complex whose homology we are
+      computing
+    - ``category`` -- (optional) a subcategory of modules with basis
+
+    This does not include the ``cohomology`` argument present for
+    :class:`HomologyVectorSpaceWithBasis`: use
+    :class:`CohomologyRing_mod2` for cohomology.
+
+    EXAMPLES:
+
+    Mod 2 cohomology operations are defined on both the left and the
+    right::
+
+        sage: # needs sage.groups
+        sage: RP4 = simplicial_sets.RealProjectiveSpace(5)
+        sage: H = RP4.homology_with_basis(GF(2))
+        sage: x4 = H.basis()[4,0]
+        sage: x4 * Sq(1)
+        h_{3,0}
+        sage: Sq(1) * x4
+        h_{3,0}
+        sage: Sq(2) * x4
+        h_{2,0}
+        sage: Sq(3) * x4
+        h_{1,0}
+        sage: Sq(0,1) * x4
+        h_{1,0}
+        sage: x4 * Sq(0,1)
+        h_{1,0}
+        sage: Sq(3) * x4
+        h_{1,0}
+        sage: x4 * Sq(3)
+        0
+    """
+    def __init__(self, base_ring, cell_complex, category=None):
+        """
+        Initialize ``self``.
+
+        EXAMPLES::
+
+            sage: H = simplicial_complexes.Torus().homology_with_basis(GF(2))
+            sage: TestSuite(H).run()
+            sage: H = simplicial_complexes.Sphere(3).homology_with_basis(GF(2))
+            sage: TestSuite(H).run()
+        """
+        if not is_GF2(base_ring):
+            raise ValueError
+        category = Modules(base_ring).WithBasis().Graded().FiniteDimensional().or_subcategory(category)
+        category = Category.join((category,
+                                  LeftModules(SteenrodAlgebra(2)),
+                                  RightModules(SteenrodAlgebra(2))))
+        HomologyVectorSpaceWithBasis.__init__(self, base_ring,
+                                              cell_complex,
+                                              cohomology=False,
+                                              category=category)
+
+    class Element(HomologyVectorSpaceWithBasis.Element):
+
+        def _acted_upon_(self, a, self_on_left):
+            r"""
+            Define multiplication of ``self`` by ``a``, an
+            element of the Steenrod algebra.
+
+            INPUT:
+
+            - ``a`` - an element of the mod 2 Steenrod algebra
+            - ``self_on_left`` -- ``True`` if we are computing ``self * a``,
+              otherwise ``a * self``
+
+            Algorithm: use the action of the Steenrod algebra `A` on
+            cohomology to construct the action on homology. That is,
+            given a right action of `A` on `H^*`,
+
+            .. MATH::
+
+                \phi_L: H^* \otimes A \to H^*
+
+            we define (a la Boardman [Boa1982]_, p. 190)
+
+            .. MATH::
+
+                S'' \phi_L: A \otimes H_* \to H_*
+
+            using the formula
+
+            .. MATH::
+
+                \langle (S'' \phi) (f \otimes a), x \rangle
+                = \langle f, \phi_L (a \otimes x) \rangle,
+
+            for `f \in H_m`, `a \in A^n`, and `x \in
+            H^{m-n}`. Somewhat more succintly, we define the action `f
+            \cdot a` by
+
+            .. MATH::
+
+                (f \cdot a) (x) = f (a \cdot x).
+
+            So given `f` (a.k.a. ``self``) and `a`, we compute `f (a
+            \cdot x)` for all basis elements `x` in `H^{m-n}`,
+            yielding a vector indexed by those basis elements. Since
+            our basis for homology is dual to the basis for
+            cohomology, we can then use the homology basis to convert
+            the vector to an element of `H_{m-n}`.
+
+            This gives a right module structure. To get a left module
+            structure, use the right module structure after applying
+            the antipode to `a`.
+
+            EXAMPLES::
+
+                sage: # needs sage.groups
+                sage: RP5 = simplicial_sets.RealProjectiveSpace(5)
+                sage: H = RP5.homology_with_basis(GF(2))
+                sage: x5 = list(H.basis(5))[0]
+                sage: Sq(1) * x5
+                0
+                sage: Sq(2) * x5
+                h_{3,0}
+                sage: x5 * Sq(2)
+                h_{3,0}
+
+            TESTS::
+
+                sage: # needs sage.groups
+                sage: RP4 = simplicial_sets.RealProjectiveSpace(5)
+                sage: H = RP4.homology_with_basis(GF(2))
+                sage: x4 = H.basis()[4,0]
+                sage: (Sq(1) * Sq(2)) * x4 != 0
+                True
+                sage: (Sq(1) * Sq(2)) * x4 == Sq(1) * (Sq(2) * x4)
+                True
+                sage: x4 * (Sq(2) * Sq(1)) == (x4 * Sq(2)) * Sq(1)
+                True
+                sage: 1 * x4
+                h_{4,0}
+                sage: x4 * 0
+                0
+            """
+            # Handle field elements first.
+            ret = CombinatorialFreeModule.Element._acted_upon_(self, a, self_on_left)
+            if ret is not None:  # did the scalar action
+                return ret
+            m = self.degree()
+            n = a.degree()
+            if m <= n:
+                return self.parent().zero()
+
+            if not self_on_left: # i.e., module element on left
+                a = a.antipode()
+            P = self.parent()
+            B = list(P.basis(m-n))
+            return P._from_dict({x.support()[0]: self.eval(a * x)
+                                 for x in sorted(self.parent().dual().basis(m-n))})
+
 
 class CohomologyRing(HomologyVectorSpaceWithBasis):
     """
@@ -431,6 +717,7 @@ class CohomologyRing(HomologyVectorSpaceWithBasis):
     - ``base_ring`` -- must be a field
     - ``cell_complex`` -- the cell complex whose homology we are
       computing
+    - ``category`` -- (optional) a subcategory of modules with basis
 
     EXAMPLES::
 
@@ -446,47 +733,23 @@ class CohomologyRing(HomologyVectorSpaceWithBasis):
         -h^{4,0}
         sage: x * x
         -h^{4,0}
-
-    There are mod 2 cohomology operations defined, also, for
-    simplicial complexes and simplicial sets::
-
-        sage: Hmod2 = CP2.cohomology_ring(GF(2))
-        sage: y = Hmod2.basis(2)[2,0]
-        sage: y.Sq(2)
-        h^{4,0}
-
-        sage: # needs sage.groups
-        sage: Y = simplicial_sets.RealProjectiveSpace(6).suspension()
-        sage: H_Y = Y.cohomology_ring(GF(2))
-        sage: b = H_Y.basis()[2,0]
-        sage: b.Sq(1)
-        h^{3,0}
-        sage: b.Sq(2)
-        0
-        sage: c = H_Y.basis()[4,0]
-        sage: c.Sq(1)
-        h^{5,0}
-        sage: c.Sq(2)
-        h^{6,0}
-        sage: c.Sq(3)
-        h^{7,0}
-        sage: c.Sq(4)
-        0
     """
-    def __init__(self, base_ring, cell_complex):
+    def __init__(self, base_ring, cell_complex, category=None):
         """
         Initialize ``self``.
 
         EXAMPLES::
 
             sage: RP2 = simplicial_complexes.ProjectivePlane()
-            sage: H = RP2.cohomology_ring(GF(2))
-            sage: TestSuite(H).run()
             sage: H = RP2.cohomology_ring(GF(5))
             sage: TestSuite(H).run()
+            sage: T = simplicial_complexes.Torus()
+            sage: H = T.cohomology_ring(QQ)
+            sage: TestSuite(H).run()
         """
-        cat = Algebras(base_ring).WithBasis().Graded().FiniteDimensional()
-        HomologyVectorSpaceWithBasis.__init__(self, base_ring, cell_complex, True, cat)
+        if category is None:
+            category = Algebras(base_ring).WithBasis().Graded().FiniteDimensional()
+        HomologyVectorSpaceWithBasis.__init__(self, base_ring, cell_complex, True, category)
 
     def _repr_(self):
         """
@@ -676,6 +939,94 @@ def cup_product(self, other):
             """
             return self * other
 
+
+class CohomologyRing_mod2(CohomologyRing):
+    r"""
+    The mod 2 cohomology ring.
+
+    Based on :class:`CohomologyRing`, with Steenrod operations included.
+
+    .. NOTE::
+
+        This is not intended to be created directly by the user, but
+        instead via the
+        :meth:`cohomology ring<sage.topology.cell_complex.GenericCellComplex.cohomology_ring>`
+        of a :class:`cell
+        complex<sage.topology.cell_complex.GenericCellComplex>`.
+
+    .. TODO::
+
+        Implement Steenrod operations on (co)homology at odd primes,
+        and thereby implement this class over `\GF{p}` for any `p`.
+
+    INPUT:
+
+    - ``base_ring`` -- must be the field ``GF(2)``
+    - ``cell_complex`` -- the cell complex whose homology we are
+      computing
+
+    EXAMPLES:
+
+    Mod 2 cohomology operations are defined on both the left and the
+    right::
+
+        sage: CP2 = simplicial_complexes.ComplexProjectivePlane()
+        sage: Hmod2 = CP2.cohomology_ring(GF(2))
+        sage: y = Hmod2.basis(2)[2,0]
+        sage: y.Sq(2)
+        h^{4,0}
+
+        sage: # needs sage.groups
+        sage: Y = simplicial_sets.RealProjectiveSpace(6).suspension()
+        sage: H_Y = Y.cohomology_ring(GF(2))
+        sage: b = H_Y.basis()[2,0]
+        sage: b.Sq(1)
+        h^{3,0}
+        sage: b.Sq(2)
+        0
+        sage: c = H_Y.basis()[4,0]
+        sage: c.Sq(1)
+        h^{5,0}
+        sage: c.Sq(2)
+        h^{6,0}
+        sage: c.Sq(3)
+        h^{7,0}
+        sage: c.Sq(4)
+        0
+
+    Cohomology can be viewed as a left module over the Steenrod
+    algebra, and also as a right module::
+
+        sage: # needs sage.groups
+        sage: RP4 = simplicial_sets.RealProjectiveSpace(4)
+        sage: H = RP4.cohomology_ring(GF(2))
+        sage: x = H.basis()[1,0]
+        sage: Sq(0,1) * x
+        h^{4,0}
+        sage: Sq(3) * x
+        0
+        sage: x * Sq(3)
+        h^{4,0}
+    """
+    def __init__(self, base_ring, cell_complex):
+        """
+        Initialize ``self``.
+
+        EXAMPLES::
+
+            sage: RP2 = simplicial_complexes.ProjectivePlane()
+            sage: H = RP2.cohomology_ring(GF(2))
+            sage: TestSuite(H).run()
+        """
+        if not is_GF2(base_ring):
+            raise ValueError("the base ring must be GF(2)")
+        category = Algebras(base_ring).WithBasis().Graded().FiniteDimensional()
+        category = Category.join((category,
+                                  LeftModules(SteenrodAlgebra(2)),
+                                  RightModules(SteenrodAlgebra(2))))
+        CohomologyRing.__init__(self, base_ring, cell_complex, category=category)
+
+    class Element(CohomologyRing.Element):
         def Sq(self, i):
             r"""
             Return the result of applying `Sq^i` to this element.
@@ -686,10 +1037,16 @@ def Sq(self, i):
 
             .. WARNING::
 
-               This is only implemented for simplicial complexes.
+                The main implementation is only for simplicial
+                complexes and simplicial sets; cubical complexes are
+                converted to simplicial complexes first. Note that
+                this converted complex may be large and so computations
+                may be slow. There is no implementation for
+                `\Delta`-complexes.
 
             This cohomology operation is only defined in
-            characteristic 2.
+            characteristic 2. Odd primary Steenrod operations are not
+            implemented.
 
             Algorithm: see González-Díaz and Réal [GDR1999]_,
             Corollary 3.2.
@@ -707,6 +1064,8 @@ def Sq(self, i):
                 sage: y.Sq(1)
                 h^{3,0}
 
+                sage: # long time
+                sage: # needs sage.groups
                 sage: RP4 = simplicial_complexes.RealProjectiveSpace(4)
                 sage: H = RP4.cohomology_ring(GF(2))
                 sage: x = H.basis()[1,0]
@@ -714,7 +1073,7 @@ def Sq(self, i):
                 sage: z = H.basis()[3,0]
                 sage: x.Sq(1) == y
                 True
-                sage: z.Sq(1)  # long time
+                sage: z.Sq(1)
                 h^{4,0}
 
             This calculation is much faster with simplicial sets (on
@@ -728,26 +1087,35 @@ def Sq(self, i):
 
             TESTS::
 
-                sage: T = cubical_complexes.Torus()
-                sage: x = T.cohomology_ring(GF(2)).basis()[1,0]
+                sage: RP_cubical = cubical_complexes.RealProjectivePlane()
+                sage: x = RP_cubical.cohomology_ring(GF(2)).basis()[1,0]
                 sage: x.Sq(1)
-                Traceback (most recent call last):
-                ...
-                NotImplementedError: Steenrod squares are only implemented for simplicial complexes and simplicial sets
-                sage: S2 = simplicial_complexes.Sphere(2)
-                sage: x = S2.cohomology_ring(GF(7)).basis()[2,0]
+                h^{2,0}
+                sage: T = delta_complexes.Torus()
+                sage: x = T.cohomology_ring(GF(2)).basis()[1,0]
                 sage: x.Sq(1)
                 Traceback (most recent call last):
                 ...
-                ValueError: Steenrod squares are only defined in characteristic 2
+                NotImplementedError: Steenrod squares are not implemented for this type of cell complex
             """
             P = self.parent()
             scomplex = P.complex()
+            if isinstance(scomplex, CubicalComplex):
+                # Convert cubical complex to simplicial complex, and
+                # convert self to basis element in the new complex's
+                # cohomology ring.
+                scomplex = SimplicialComplex(scomplex, is_mutable=False)
+                P = scomplex.cohomology_ring(self.base_ring())
+                self = P.sum_of_terms(self.monomial_coefficients().items())
             if not isinstance(scomplex, (SimplicialComplex, SimplicialSet_arbitrary)):
-                raise NotImplementedError('Steenrod squares are only implemented for '
-                                          'simplicial complexes and simplicial sets')
+                print(scomplex, isinstance(scomplex, SimplicialComplex))
+                raise NotImplementedError('Steenrod squares are not implemented for '
+                                          'this type of cell complex')
+            scomplex = P.complex()
             base_ring = P.base_ring()
-            if base_ring.characteristic() != 2:
+            if not is_GF2(base_ring):
+                # This should never happen: the class should only be
+                # instantiated in characteristic 2.
                 raise ValueError('Steenrod squares are only defined in characteristic 2')
             # We keep the same notation as in [GDR1999].
             # The trivial cases:
@@ -841,6 +1209,194 @@ def Sq(self, i):
                 ret += P._from_dict(result, remove_zeros=False)
             return ret
 
+        def _acted_upon_(self, a, self_on_left):
+            r"""
+            Define multiplication of ``self`` by ``a``, an
+            element of the Steenrod algebra.
+
+            INPUT:
+
+            - ``a`` -- an element of the mod 2 Steenrod algebra
+            - ``self_on_left`` -- ``True`` if we are computing ``self * a``,
+              otherwise ``a * self``
+
+            Algorithm: for left multiplication by ``a``, since we have
+            :meth:`Sq` to compute multiplication by a single generator
+            `Sq^i`, first convert ``a`` to the Serre-Cartan basis ---
+            that is, sums of products of the elements `Sq^i` --- and
+            then apply :meth:`Sq` repeatedly. Right multiplication by
+            ``a`` is the same as left multiplication by the antipode
+            applied to ``a``.
+
+            EXAMPLES::
+
+                sage: # needs sage.groups
+                sage: SRP4 = simplicial_sets.RealProjectiveSpace(4).suspension()
+                sage: H = SRP4.cohomology_ring(GF(2))
+                sage: x = H.basis()[2,0]
+                sage: Sq(0,1) * x
+                h^{5,0}
+                sage: Sq(3) * x
+                0
+                sage: x * Sq(3)
+                h^{5,0}
+
+            TESTS::
+
+                sage: # needs sage.groups
+                sage: (Sq(2) * Sq(1)) * x == Sq(2) * (Sq(1) * x)
+                True
+                sage: x * (Sq(1) * Sq(2)) == (x * Sq(1)) * Sq(2)
+                True
+                sage: x * 1
+                h^{2,0}
+                sage: 0 * x
+                0
+            """
+            # Handle field elements first.
+            ret = CombinatorialFreeModule.Element._acted_upon_(self, a, self_on_left)
+            if ret is not None:  # did the scalar action
+                return ret
+            if self_on_left: # i.e., module element on left
+                a = a.antipode()
+            b = a.change_basis('adem')
+            ans = self.parent().zero()
+            mono_dict = b.monomial_coefficients()
+            for seq in mono_dict:
+                x = self
+                for i in reversed(seq):
+                    x = x.Sq(i)
+                ans += mono_dict[seq] * x
+            return ans
+
+    def steenrod_module_map(self, deg_domain, deg_codomain, side='left'):
+        r"""
+        Return a component of the module structure map `A \otimes
+        H \to H`, where `H` is this cohomology ring and `A` is the
+        Steenrod algebra.
+
+        INPUT:
+
+        - ``deg_domain`` -- the degree of the domain in the cohomology
+          ring
+
+        - ``deg_codomain`` -- the degree of the codomain in the
+          cohomology ring
+
+        - ``side`` -- (default ``'left'``) whether we are computing
+          the action as a left module action or a right module
+
+        We will write this with respect to the left action;
+        for the right action, just switch all of the the tensors.
+        Writing `m` for ``deg_domain`` and `n` for ``deg_codomain``, this
+        returns `A^{n-m} \otimes H^{m} \to H^{n}`, one single
+        component of the map making `H` into an `A`-module.
+
+        .. WARNING::
+
+           This is only implemented in characteristic two. The main
+           implementation is only for simplicial complexes and simplicial
+           sets; cubical complexes are converted to simplicial complexes
+           first. Note that this converted complex may be large and so
+           computations may be slow. There is no implementation for
+           `\Delta`-complexes.
+
+        ALGORITHM:
+
+        Use the Milnor basis for the truncated Steenrod
+        algebra `A`, and for cohomology, use the basis with which it
+        is equipped. For each pair of basis elements `a` and `h`,
+        compute the product `a \otimes h`, and use this to assemble a
+        matrix defining the action map via multiplication on the
+        appropriate side. That is, if ``side`` is ``'left'``, return a
+        matrix suitable for multiplication on the left, etc.
+
+        EXAMPLES::
+
+            sage: # needs sage.groups
+            sage: RP4 = simplicial_sets.RealProjectiveSpace(4)
+            sage: H = RP4.cohomology_ring(GF(2))
+            sage: H.steenrod_module_map(1, 2)
+            [1]
+            sage: H.steenrod_module_map(1, 3)
+            [0]
+            sage: H.steenrod_module_map(1, 4, 'left')
+            [1 0]
+            sage: H.steenrod_module_map(1, 4, 'right')
+            [1]
+            [1]
+
+        Products of projective spaces::
+
+            sage: RP3 = simplicial_sets.RealProjectiveSpace(3)
+            sage: K = RP3.product(RP3)
+            sage: H = K.cohomology_ring(GF(2))
+            sage: H
+            Cohomology ring of RP^3 x RP^3 over Finite Field of size 2
+
+        There is one column for each element `a \otimes b`, where `a`
+        is a basis element for the Steenrod algebra and `b` is a basis
+        element for the cohomology algebra.  There is one row for each
+        basis element of the cohomology algebra. Unfortunately, the
+        chosen basis for this truncated polynomial algebra is not the
+        monomial basis::
+
+            sage: x1, x2 = H.basis(1)
+            sage: x1 * x1
+            h^{2,0} + h^{2,1}
+            sage: x2 * x2
+            h^{2,2}
+            sage: x1 * x2
+            h^{2,0}
+
+            sage: H.steenrod_module_map(1, 2)
+            [1 0]
+            [1 0]
+            [0 1]
+            sage: H.steenrod_module_map(1, 3, 'left')
+            [0 0]
+            [0 0]
+            [0 0]
+            [0 0]
+            sage: H.steenrod_module_map(1, 3, 'right')
+            [0 0 0 0]
+            [0 0 0 0]
+            sage: H.steenrod_module_map(2, 3)
+            [0 0 0]
+            [1 1 0]
+            [0 0 0]
+            [0 0 0]
+        """
+        side = side.lower()
+        if side not in ['right', 'left']:
+            raise ValueError('side must be either "left" or "right"')
+        base_ring = self.base_ring()
+        char = base_ring.characteristic()
+        # Use sorted(...) so that there are reproducible orders on the bases.
+        A_basis = sorted(SteenrodAlgebra(char).basis(deg_codomain - deg_domain))
+        H_basis_dom = sorted(self.basis(deg_domain))
+        H_basis_cod = self.basis(deg_codomain).keys()
+        H_basis_cod = {elt: idx for idx, elt in enumerate(sorted(H_basis_cod))}
+        entries = []
+        for a in A_basis:
+            for h in H_basis_dom:
+                vec = vector(base_ring, len(H_basis_cod))
+                if side == 'left':
+                    x = a * h
+                else:
+                    x = h * a
+                monos = x.monomial_coefficients()
+                for seq in monos:
+                    vec[H_basis_cod[seq]] = monos[seq]
+                entries.extend(vec)
+        # We built the matrix column by column, so now we take the
+        # transpose.
+        if side == 'left':
+            return matrix(base_ring, len(A_basis) * len(H_basis_dom),
+                          len(H_basis_cod), entries).transpose()
+        return matrix(base_ring, len(A_basis) * len(H_basis_dom),
+                          len(H_basis_cod), entries)
+
 
 def sum_indices(k, i_k_plus_one, S_k_plus_one):
     r"""
@@ -881,3 +1437,19 @@ def sum_indices(k, i_k_plus_one, S_k_plus_one):
         return [[S_k]]
     return [[i_k] + l for i_k in range(S_k, i_k_plus_one)
             for l in sum_indices(k-1, i_k, S_k)]
+
+def is_GF2(R):
+    r"""
+    Return ``True`` iff ``R`` is isomorphic to the field `\GF{2}`.
+
+    EXAMPLES::
+
+        sage: from sage.homology.homology_vector_space_with_basis import is_GF2
+        sage: is_GF2(GF(2))
+        True
+        sage: is_GF2(GF(2, impl='ntl'))
+        True
+        sage: is_GF2(GF(3))
+        False
+    """
+    return 2 == R.characteristic() == R.cardinality()
diff --git a/src/sage/topology/cell_complex.py b/src/sage/topology/cell_complex.py
--- a/src/sage/topology/cell_complex.py
+++ b/src/sage/topology/cell_complex.py
@@ -836,8 +836,7 @@ def homology_with_basis(self, base_ring=QQ, cohomology=False):
         .. SEEALSO::
 
             If ``cohomology`` is ``True``, this returns the cohomology
-            as a graded module. For the ring structure, use
-            :meth:`cohomology_ring`.
+            as a ring: it calls :meth:`cohomology_ring`.
 
         EXAMPLES::
 
@@ -865,7 +864,14 @@ def homology_with_basis(self, base_ring=QQ, cohomology=False):
             sage: list(H.basis(3))                                                      # needs sage.modules
             [h^{3,0}]
         """
-        from sage.homology.homology_vector_space_with_basis import HomologyVectorSpaceWithBasis
+        from sage.homology.homology_vector_space_with_basis import \
+            HomologyVectorSpaceWithBasis, HomologyVectorSpaceWithBasis_mod2, \
+            is_GF2
+
+        if cohomology:
+            return self.cohomology_ring(base_ring)
+        if is_GF2(base_ring):
+            return HomologyVectorSpaceWithBasis_mod2(base_ring, self)
         return HomologyVectorSpaceWithBasis(base_ring, self, cohomology)
 
     def cohomology_ring(self, base_ring=QQ):
@@ -972,7 +978,11 @@ def cohomology_ring(self, base_ring=QQ):
             Cohomology ring of Simplicial complex with 9 vertices and
              18 facets over Rational Field
         """
-        from sage.homology.homology_vector_space_with_basis import CohomologyRing
+        from sage.homology.homology_vector_space_with_basis import CohomologyRing, \
+            CohomologyRing_mod2, is_GF2
+
+        if is_GF2(base_ring):
+            return CohomologyRing_mod2(base_ring, self)
         return CohomologyRing(base_ring, self)
 
     @abstract_method


Test patch: