Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mod 2 (co)homology as a module over the Steenrod algebra #36310

Merged
merged 9 commits into from
Oct 8, 2023

Conversation

jhpalmieri
Copy link
Member

Mod 2 (co)homology as a module over the Steenrod algebra

Implement:

  • vector space duality for (co)homology of cell complexes with field coefficients, switching between homology and cohomology
  • Steenrod operations on homology and cohomology, acting from the left and from the right

The Steenrod operation "Sq(i)" was already implemented for cohomology rings, raising an error if it was used with a base field other than GF(2). Now there is a new class, CohomologyRing_mod2, which should only be used with that base field, and the Sq method is attached to Elements of that new class.

Using Steenrod operations is more transparent: in addition to doing x.Sq(i), users can do Sq(i) * x or x * Sq(i), or indeed a * x or x * a for any element a of SteenrodAlgebra(2). (The elements Sq(i) generate the Steenrod algebra, but they do not constitute a full list of the elements.)

This fixes #6103.

📝 Checklist

  • The title is concise, informative, and self-explanatory.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation accordingly.

⌛ Dependencies

duality between homology and cohomology:
- new method 'dual' to switch between them
- new method 'eval' to evaluate cochains on chains and vice versa
- new method '_test_duality' to make sure that our orderings on the
  bases for homology and cohomology are consistent
- "implement" mod 2 Steenrod operations for cubical complexes by
  converting those complexes to simplicial complexes. Likely very slow.
over the Steenrod algebra.

Create a new class, CohomologyRing_mod2, for this, because these
Steenrod operations on cohomology are only implemented at the prime
2. Use the new class automatically when users create
`K.cohomology_ring(GF(2))` for a complex `K`. Move the existing "Sq"
method to the new class, and create new methods to make the module
action transparent.

Also create a method steenrod_module_map in case (a) someone wants
access to these matrices and (b) for future use, and in particular for
computing the Steenrod algebra action on homology.
This uses the module structure on cohomology together with vector
space dualization to put a module structure on homology. While Sq^n
increases dimension in cohomology by n, it lowers homological degree
by n.
src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
Comment on lines 684 to 685
if a in self.base_ring():
return self.map_coefficients(lambda c: c*a)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some reason you are not using the default implementation given by CFM elements for scalars?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I had tried other things and they hadn't worked. For example I thought that just using return c * self led to an infinite recursion, because * called _acted_upon_. This doesn't seem to be the case, so I must have been confused. I'll change it.

True
"""
# Handle field elements first.
if a in self.base_ring():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about going to CFMElement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is the place where I get the infinite recursion. I think I need to keep this one as is.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to do

ret = CombinatorialFreeModule.Element._acted_upon_(self, a, self_on_left)
if ret is not None:  # did the scalar action
    return ret

which will be a lot faster than the current implementation. If this doing this does get an infinite loop, can you post the traceback? It really shouldn't have such a loop...

src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
src/sage/topology/cell_complex.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two other general comments:

  1. Can you please squash all of the little commits from the previous review? They are a bit annoying and the messages are worse than useless.
  2. This is agnostic to the implementation of $F_2$, right? So I think we need a more robust test as we have, e.g.,
sage: GF(2) == GF(2, impl='ntl')
False

I am thinking of instead testing the characteristic and the cardinality. Actually, is this something for any char 2 field or just $F_2$?

True
"""
# Handle field elements first.
if a in self.base_ring():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to do

ret = CombinatorialFreeModule.Element._acted_upon_(self, a, self_on_left)
if ret is not None:  # did the scalar action
    return ret

which will be a lot faster than the current implementation. If this doing this does get an infinite loop, can you post the traceback? It really shouldn't have such a loop...

"""
# Handle field elements first.
if a in self.base_ring():
return a * self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't what I had in mind. I was thinking of an explicit super() call (or to the specific base class as indicated in the other comment).

src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
jhpalmieri and others added 2 commits September 25, 2023 11:32
This uses the module structure on cohomology together with vector
space dualization to put a module structure on homology. While Sq^n
increases dimension in cohomology by n, it lowers homological degree
by n.

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/homology/homology_vector_space_with_basis.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>

Update src/sage/topology/cell_complex.py

Co-authored-by: Travis Scrimshaw <clfrngrown@aol.com>
@jhpalmieri
Copy link
Member Author

Two other general comments:

1. Can you please squash all of the little commits from the previous review? They are a bit annoying and the messages are worse than useless.

Sure.

2. This is agnostic to the implementation of F2, right? So I think we need a more robust test as we have, e.g.,
sage: GF(2) == GF(2, impl='ntl')
False

This looks like a bug in Sage to me. I can understand why GF(2) is GF(2, impl='ntl') might return False, but not ==. I will work around it.

I am thinking of instead testing the characteristic and the cardinality. Actually, is this something for any char 2 field or just F2?

Just F2.

@tscrim
Copy link
Collaborator

tscrim commented Sep 26, 2023

Two other general comments:

  1. Can you please squash all of the little commits from the previous review? They are a bit annoying and the messages are worse than useless.

Sure.

Thank you.

  1. This is agnostic to the implementation of F2, right? So I think we need a more robust test as we have, e.g.,
sage: GF(2) == GF(2, impl='ntl')
False

This looks like a bug in Sage to me. I can understand why GF(2) is GF(2, impl='ntl') might return False, but not ==. I will work around it.

It's not a bug, but a feature. Equal-but-not-identical parents are a big pain to deal with, and IMO the less we have the better. I think isinstance(X, FiniteField) and X.cardinality() == 2 is a sufficiently good test. Although what you did will work. You should also change the CohomologyRing_mod2.__init__ method and add some doctests using other implementations rather than the default.

Also, for the category, you can do category = Cat.or_subcategory(category) to catch potential bugs; it also can take None as input.

@jhpalmieri
Copy link
Member Author

Regarding GF(2), should I be concerned that SteenrodAlgebra(2) uses a specific implementation of the field, and it may not be the same as the one here? Computations seem to work.

It's not a bug, but a feature. Equal-but-not-identical parents are a big pain to deal with, and IMO the less we have the better. I think isinstance(X, FiniteField) and X.cardinality() == 2 is a sufficiently good test. Although what you did will work. You should also change the CohomologyRing_mod2.__init__ method

I'm not sure what changes you have in mind. The cat.or_subcategory(...) suggestion below, or something else?

and add some doctests using other implementations rather than the default.

It's easy to add some computations of cohomology operations, cup products, etc., but I get errors with TestSuite(...) if I use other implementations, with the message TypeError: cannot pickle 'dict_items' object. (_test_pickling fails with impl='ntl', _test_pickling and _test_elements fail with impl='givaro'.) Okay to ignore these?

Also, for the category, you can do category = Cat.or_subcategory(category) to catch potential bugs; it also can take None as input.

@tscrim
Copy link
Collaborator

tscrim commented Sep 26, 2023

Regarding GF(2), should I be concerned that SteenrodAlgebra(2) uses a specific implementation of the field, and it may not be the same as the one here? Computations seem to work.

Perhaps, but I don't really know that code very well. It might be best to put some kind of warning or note about it.

It's not a bug, but a feature. Equal-but-not-identical parents are a big pain to deal with, and IMO the less we have the better. I think isinstance(X, FiniteField) and X.cardinality() == 2 is a sufficiently good test. Although what you did will work. You should also change the CohomologyRing_mod2.__init__ method

I'm not sure what changes you have in mind. The cat.or_subcategory(...) suggestion below, or something else?

I thought I saw a == GF(2) there.

and add some doctests using other implementations rather than the default.

It's easy to add some computations of cohomology operations, cup products, etc., but I get errors with TestSuite(...) if I use other implementations, with the message TypeError: cannot pickle 'dict_items' object. (_test_pickling fails with impl='ntl', _test_pickling and _test_elements fail with impl='givaro'.) Okay to ignore these?

Yes, although it looks like you found some bugs that need reporting. I might add these TestSuite calls with skipping those particular tests.

@jhpalmieri
Copy link
Member Author

Regarding GF(2), should I be concerned that SteenrodAlgebra(2) uses a specific implementation of the field, and it may not be the same as the one here? Computations seem to work.

Perhaps, but I don't really know that code very well. It might be best to put some kind of warning or note about it.

It's not a bug, but a feature. Equal-but-not-identical parents are a big pain to deal with, and IMO the less we have the better. I think isinstance(X, FiniteField) and X.cardinality() == 2 is a sufficiently good test. Although what you did will work. You should also change the CohomologyRing_mod2.__init__ method

I'm not sure what changes you have in mind. The cat.or_subcategory(...) suggestion below, or something else?

I thought I saw a == GF(2) there.

and add some doctests using other implementations rather than the default.

It's easy to add some computations of cohomology operations, cup products, etc., but I get errors with TestSuite(...) if I use other implementations, with the message TypeError: cannot pickle 'dict_items' object. (_test_pickling fails with impl='ntl', _test_pickling and _test_elements fail with impl='givaro'.) Okay to ignore these?

Yes, although it looks like you found some bugs that need reporting. I might add these TestSuite calls with skipping those particular tests.

Actually it looks like I was wrong about this being agnostic about the implementation of GF(2). Or I wasn't wrong, but some of the old stuff doesn't work with those other implementations. In particular, simplicial complexes don't work well, but simplicial sets do: just let K be your favorite simplicial complex and try K.cohomology_ring(GF(2, impl='ntl')). So I propose that we don't add examples with other implementations here, but instead fix the whole thing on another ticket, adding examples then.

@jhpalmieri
Copy link
Member Author

Actually it looks like I was wrong about this being agnostic about the implementation of GF(2). Or I wasn't wrong, but some of the old stuff doesn't work with those other implementations. In particular, simplicial complexes don't work well, but simplicial sets do: just let K be your favorite simplicial complex and try K.cohomology_ring(GF(2, impl='ntl')). So I propose that we don't add examples with other implementations here, but instead fix the whole thing on another ticket, adding examples then.

Maybe I should say "your favorite nonempty simplicial complex," because it looks like the empty complex works okay. simplicial_complexes.Simplex(0).cohomology_ring(GF(2, impl='ntl')) fails.

@jhpalmieri
Copy link
Member Author

Actually it looks like I was wrong about this being agnostic about the implementation of GF(2). Or I wasn't wrong, but some of the old stuff doesn't work with those other implementations. In particular, simplicial complexes don't work well, but simplicial sets do: just let K be your favorite simplicial complex and try K.cohomology_ring(GF(2, impl='ntl')). So I propose that we don't add examples with other implementations here, but instead fix the whole thing on another ticket, adding examples then.

Maybe I should say "your favorite nonempty simplicial complex," because it looks like the empty complex works okay. simplicial_complexes.Simplex(0).cohomology_ring(GF(2, impl='ntl')) fails.

Actually, now I really need to be persuaded that GF(2, impl='ntl') should not just be thrown in the trash heap:

sage: F = GF(2, impl='ntl')
sage: m_ntl = identity_matrix(1, F)
sage: v_ntl = vector(F, (1,))
sage: m_ntl * v_ntl
---------------------------------------------------------------------------
SignalError                               Traceback (most recent call last)
Cell In [14], line 1
----> 1 m_ntl * v_ntl

File ~/Desktop/Sage/git/sage/src/sage/structure/element.pyx:4099, in sage.structure.element.Matrix.__mul__()
   4097 
   4098         if BOTH_ARE_ELEMENT(cl):
-> 4099             return coercion_model.bin_op(left, right, mul)
   4100 
   4101         cdef long value

File ~/Desktop/Sage/git/sage/src/sage/structure/coerce.pyx:1222, in sage.structure.coerce.CoercionModel.bin_op()
   1220 if action is not None:
   1221     if (<Action>action)._is_left:
-> 1222         return (<Action>action)._act_(x, y)
   1223     else:
   1224         return (<Action>action)._act_(y, x)

File ~/Desktop/Sage/git/sage/src/sage/matrix/action.pyx:328, in sage.matrix.action.MatrixVectorAction._act_()
    326     else:
    327         v = v.dense_vector()
--> 328 return A._matrix_times_vector_(v)
    329 
    330 

File ~/Desktop/Sage/git/sage/src/sage/matrix/matrix_mod2_dense.pyx:614, in sage.matrix.matrix_mod2_dense.Matrix_mod2_dense._matrix_times_vector_()
    612     return VS.zero()
    613 cdef Vector_mod2_dense c = Vector_mod2_dense.__new__(Vector_mod2_dense)
--> 614 sig_str("matrix allocation failed")
    615 c._init(self._nrows, VS)
    616 if c._entries.nrows and c._entries.ncols:

SignalError: matrix allocation failed
sage: v_ntl * m_ntl
------------------------------------------------------------------------
(no backtrace available)
------------------------------------------------------------------------
Unhandled SIGSEGV: A segmentation fault occurred.
This probably occurred because a *compiled* module has a bug
in it and is not properly wrapped with sig_on(), sig_off().
Python will now terminate.
------------------------------------------------------------------------
[1]    49247 segmentation fault  sage

This is a field over which we cannot do linear algebra?

@tscrim
Copy link
Collaborator

tscrim commented Sep 26, 2023

First, you have an input error as identity_matrix(1, F) returns a matrix over ZZ (which isn't great either), but even swapping the argument order I still get the SignalError. There is probably some hidden assumption in the implementation that someone didn't account for different implementations of GF(q). I think there are a number of issues that need to be worked through on a separate PR, but it probably isn't so bad would be my guess.

@jhpalmieri
Copy link
Member Author

First, you have an input error as identity_matrix(1, F) returns a matrix over ZZ (which isn't great either), but even swapping the argument order I still get the SignalError. There is probably some hidden assumption in the implementation that someone didn't account for different implementations of GF(q). I think there are a number of issues that need to be worked through on a separate PR, but it probably isn't so bad would be my guess.

I think that to do it right, someone needs to add doctests for the alternate implementations of finite fields that demonstrate that they can do everything in Sage that the default implementations do. That is a big task, but until it happens, why should anyone trust or expect that GF(2, impl=...) will work?

@jhpalmieri
Copy link
Member Author

These changes seem to fix this particular problem with GF(2, impl='ntl'), but I find them offensive:

diff --git a/src/sage/homology/algebraic_topological_model.py b/src/sage/homology/algebraic_topological_model.py
index c7856836f2..9f1148ad6f 100644
--- a/src/sage/homology/algebraic_topological_model.py
+++ b/src/sage/homology/algebraic_topological_model.py
@@ -218,7 +218,7 @@ def algebraic_topological_model(K, base_ring=None):
 
             # c_bar = c - phi(bdry(c))
             c_bar = c_vec
-            bdry_c = diff * c_vec
+            bdry_c = vector(diff * matrix(len(c_vec), 1, c_vec))
             # Apply phi to bdry_c and subtract from c_bar.
             for (idx, coord) in bdry_c.items():
                 try:
@@ -226,7 +226,7 @@ def algebraic_topological_model(K, base_ring=None):
                 except KeyError:
                     pass
 
-            bdry_c_bar = diff * c_bar
+            bdry_c_bar = vector(diff * matrix(len(c_bar), 1, c_bar))
 
             # Evaluate pi(bdry(c_bar)).
             pi_bdry_c_bar = zero

@tscrim
Copy link
Collaborator

tscrim commented Sep 27, 2023

First, you have an input error as identity_matrix(1, F) returns a matrix over ZZ (which isn't great either), but even swapping the argument order I still get the SignalError. There is probably some hidden assumption in the implementation that someone didn't account for different implementations of GF(q). I think there are a number of issues that need to be worked through on a separate PR, but it probably isn't so bad would be my guess.

I think that to do it right, someone needs to add doctests for the alternate implementations of finite fields that demonstrate that they can do everything in Sage that the default implementations do. That is a big task, but until it happens, why should anyone trust or expect that GF(2, impl=...) will work?

I agree with basically everything you said except the last part. The good news is that it raises an error or crashes rather than return a wrong answer. However, there is more to life than linear algebra (although not much :P): manipulating the fields themselves or polynomials over GF(p, impl=...).

We can also work on it in more piecemeal fashion tool, but hopefully a fix for one will fix the others. At the very least, we can push linear algebra things up to the generic implementation and it really should work there.

@tscrim
Copy link
Collaborator

tscrim commented Sep 27, 2023

These changes seem to fix this particular problem with GF(2, impl='ntl'), but I find them offensive:

I agree, that it's a hack rather than a fix.

For this PR, I think we should keep the code written as is for future-proofing when the other implementations will work. I will leave it up to you what kind of doctests you want for the other implementations.

@jhpalmieri
Copy link
Member Author

"The good news is that it raises an error or crashes" — that's the good news. Fun fact: it seems that matrix-vector multiplication with entries in GF(2, impl='ntl') actually works if you use sparse matrices. The problems occur with dense matrices. Back when I wrote algebraic_topological_model.py, I intentionally used dense matrices over finite fields because according to my tests at the time, they were faster, regardless of sparsity. If sparse matrices had been faster, we would not have found the linear algebra problem because all of this homology stuff would have just worked.

My preference is to not add any doctests for the alternate implementations. Those implementations for GF(2) are currently only tested in a few files in rings/finite_rings. I expect that once linear algebra works for these implementations, everything else should work. This stuff, or rather the older stuff on which this builds, all relies on linear algebra. The fact that Steenrod operations and cup products work for some simplicial sets suggests that there is nothing intrinsic in the homology code relying on the particular implementation of GF(2) beyond linear algebra.

Oh, I just found out that the old stuff doesn't work for all simplicial sets, just for some. Simplicial sets can be somewhat sparser than simplicial complexes, and as a result, the boundary maps in the chain complexes can be zero. I happened to have only tested simplicial sets like that, and when the boundary maps are zero, the algorithm skips the matrix multiplication altogether. I just tried a case with a nontrivial boundary map, and it hits the same problem with matrix multiplication. So even more reason to not include doctests.

I am a little concerned that if we add doctests for GF(2, impl='ntl'), it might encourage users to try out these alternate implementations, and that is not likely to go well. If we do add doctests, I would want to add warnings to them, but then someone has to remember to remove the warnings when things have improved. Similarly if we have TestSuite(...).run(skip=[...]), we have to remember to go back and delete skip=[...] once things work.

In summary: the doctests here wouldn't test anything beyond certain linear algebra computations, and those should be tested elsewhere. There should be nothing in this code that relies on a particular implementation of GF(2). Adding tests could give the wrong impression about the state of GF(2, impl='ntl'), and it also creates more work later to remove warnings and add more tests (as things get fixed).

If you really want doctests, I would probably just want to put in failures marked with "# known bug".

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, fair enough.

Just a few more little details. Once done, you can set a positive review.

src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
src/sage/homology/homology_vector_space_with_basis.py Outdated Show resolved Hide resolved
@jhpalmieri
Copy link
Member Author

Okay, fair enough.

Just a few more little details. Once done, you can set a positive review.

Great, and thank you for all of your feedback!

@github-actions
Copy link

Documentation preview for this PR (built with commit 4df0d1a; changes) is ready! 🎉

vbraun pushed a commit to vbraun/sage that referenced this pull request Oct 1, 2023
…gebra

    
Mod 2 (co)homology as a module over the Steenrod algebra

<!-- ^^^^^
Please provide a concise, informative and self-explanatory title.
Don't put issue numbers in there, do this in the PR body below.
For example, instead of "Fixes sagemath#1234" use "Introduce new method to
calculate 1+1"
-->
<!-- Describe your changes here in detail -->

Implement:

- vector space duality for (co)homology of cell complexes with field
coefficients, switching between homology and cohomology
- Steenrod operations on homology and cohomology, acting from the left
and from the right

The Steenrod operation "Sq(i)" was already implemented for cohomology
rings, raising an error if it was used with a base field other than
GF(2). Now there is a new class, `CohomologyRing_mod2`, which should
only be used with that base field, and the `Sq` method is attached to
Elements of that new class.

Using Steenrod operations is more transparent: in addition to doing
`x.Sq(i)`, users can do `Sq(i) * x` or `x * Sq(i)`, or indeed `a * x` or
`x * a` for any element `a` of `SteenrodAlgebra(2)`. (The elements
`Sq(i)` generate the Steenrod algebra, but they do not constitute a full
list of the elements.)

This fixes sagemath#6103.

<!-- Why is this change required? What problem does it solve? -->
<!-- If this PR resolves an open issue, please link to it here. For
example "Fixes sagemath#12345". -->
<!-- If your change requires a documentation PR, please link it
appropriately. -->

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it
appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
<!-- Feel free to remove irrelevant items. -->

- [X] The title is concise, informative, and self-explanatory.
- [X] The description explains in detail what this PR is about.
- [X] I have linked a relevant issue or discussion.
- [X] I have created tests covering the changes.
- [X] I have updated the documentation accordingly.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on
- sagemath#12345: short description why this is a dependency
- sagemath#34567: ...
-->

<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
    
URL: sagemath#36310
Reported by: John H. Palmieri
Reviewer(s): John H. Palmieri, Travis Scrimshaw
@vbraun vbraun merged commit fe963ca into sagemath:develop Oct 8, 2023
11 of 12 checks passed
@mkoeppe mkoeppe added this to the sage-10.2 milestone Oct 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cohomology of simplicial complexes with finite coefficients as modules over steenrod algebras
4 participants