Opened 3 years ago

Closed 2 years ago

Last modified 2 years ago

#20745 closed enhancement (fixed)

Implement simplicial sets

Reported by: jhpalmieri Owned by:
Priority: major Milestone: sage-7.5
Component: algebraic topology Keywords: days74
Cc: tscrim, jeremy.l.martin, cnassau Merged in:
Authors: John Palmieri Reviewers: Christian Nassau
Report Upstream: N/A Work issues:
Branch: 35790d2 (Commits) Commit:
Dependencies: Stopgaps:

Description (last modified by cnassau)

As the summary says...

For possible follow-up tickets:

  • simplicial abelian groups and Eilenberg-Mac Lane spaces.
  • effective homology and related computations that Kenzo can do but we can't yet.
  • mapping spaces.
  • Discrete Morse theory.
  • universal covering spaces for classifying spaces, support for maps between infinite simplicial sets.

Attachments (2)

catalan.py (3.7 KB) - added by cnassau 3 years ago.
implementation of the catalan simplicial set
suggestions.diff (5.1 KB) - added by cnassau 3 years ago.

Download all attachments as: .zip

Change History (113)

comment:1 Changed 3 years ago by jhpalmieri

  • Branch set to u/jhpalmieri/simplicial_sets
  • Commit set to de4e351cf4f30b5a197fd302cd2c9e5a347f3f4d

Here is an initial implementation. It is not ready for review.


New commits:

b63026dsimplicial sets, initial implementation
de4e351simplicial sets: construct CP^n using Kenzo output.

comment:2 Changed 3 years ago by git

  • Commit changed from de4e351cf4f30b5a197fd302cd2c9e5a347f3f4d to 7e8f261d5f7d4700020668836d1dacba2660b801

Branch pushed to git repo; I updated commit sha1. New commits:

7e8f261Simplicial sets: delete code for pushouts: it is incomplete and broken.

comment:3 Changed 3 years ago by git

  • Commit changed from 7e8f261d5f7d4700020668836d1dacba2660b801 to 05bf22845f13d6c6ea293aa3d067d383345bac39

Branch pushed to git repo; I updated commit sha1. New commits:

9bc215bSimplicial sets: pushouts, etc.
05bf228Merge branch 'develop' into t/20745/simplicial_sets

comment:4 Changed 3 years ago by jhpalmieri

Pushouts are now fixed.

comment:5 Changed 3 years ago by tscrim

  • Cc tscrim jeremy.l.martin added

comment:6 Changed 3 years ago by git

  • Commit changed from 05bf22845f13d6c6ea293aa3d067d383345bac39 to b3eb08e9548fa604e5a2f9f43edec20ade5e4b8f

Branch pushed to git repo; I updated commit sha1. New commits:

b3eb08eSimplicial sets: full-fledged pushouts and pullbacks

comment:7 Changed 3 years ago by git

  • Commit changed from b3eb08e9548fa604e5a2f9f43edec20ade5e4b8f to c5f3a75208f05542bd668ac4ba063de61949df4e

Branch pushed to git repo; I updated commit sha1. New commits:

23ae53eMerge branch 'develop' into t/20745/simplicial_sets
c5f3a75simplicial sets: trivial changes to nerve construction

comment:8 Changed 3 years ago by git

  • Commit changed from c5f3a75208f05542bd668ac4ba063de61949df4e to 37f505e97e341c4fb89d56b8f717e85e28c79e7b

Branch pushed to git repo; I updated commit sha1. New commits:

5f73a44simplicial sets: fix stupidity
bbee5f8Simplicial sets: implement possibly infinite simplicial sets.
37f505eSimplicial sets: simplicial model for the Hopf fibration.

comment:9 Changed 3 years ago by git

  • Commit changed from 37f505e97e341c4fb89d56b8f717e85e28c79e7b to 7d4e7e56a4a83351469a9c516f5506218197d39f

Branch pushed to git repo; I updated commit sha1. This was a forced push. New commits:

7d4e7e5Simplicial sets: simplicial model for the Hopf fibration.

comment:10 Changed 3 years ago by git

  • Commit changed from 7d4e7e56a4a83351469a9c516f5506218197d39f to b9d40ee2ceeceaf8465269131625cd6ccb6d17ad

Branch pushed to git repo; I updated commit sha1. New commits:

4fa2921Simplicial sets: define the constant map between pointed simplicial sets.
b9d40eeSimplicial sets: iterator (slow!) for sets of morphisms.

comment:11 Changed 3 years ago by jhpalmieri

  • Status changed from new to needs_review

I'm marking this as "needs review" now. One possible future development: mapping spaces for simplicial sets: given simplicial sets K and L, you can view Hom(K,L) as a simplicial set. This would be another example of an infinite simplicial set, so any computations would be done using an appropriate n-skeleton.

comment:12 Changed 3 years ago by jhpalmieri

  • Authors set to John Palmieri

comment:13 Changed 3 years ago by git

  • Commit changed from b9d40ee2ceeceaf8465269131625cd6ccb6d17ad to 899e5698dc17532ebb158f4497de9aa2b20afeaa

Branch pushed to git repo; I updated commit sha1. New commits:

899e569Fix some Sphinx references.

comment:14 Changed 3 years ago by git

  • Commit changed from 899e5698dc17532ebb158f4497de9aa2b20afeaa to 1230ce16d2cd5c7cda4b26dcda0f7774ca46e437

Branch pushed to git repo; I updated commit sha1. New commits:

1230ce1Simplicial sets: products and coproducts of maps.

comment:15 Changed 3 years ago by jhpalmieri

  • Status changed from needs_review to needs_work

comment:16 Changed 3 years ago by git

  • Commit changed from 1230ce16d2cd5c7cda4b26dcda0f7774ca46e437 to 9fbfee64243c72d653f7cada254eca756fa4622f

Branch pushed to git repo; I updated commit sha1. New commits:

a518237Add is_connected method to generic cell complexes.
8b615cdsimplicial sets: start to improve implementation for infinite simplicial sets
d0b13d9merging with 7.3.beta9
c1ddea1Simplicial sets: improve implementation for infinite simplicial sets.
56620b7simplicial sets: make smash product class inherit from Factors.
d41a256simplicial set: change a little terminology.
456eb80fix some whitespace
ac7f6easimplicial sets: minor editing of docstrings, some reorganization
e628254simplicial sets: documentation in categories/simplicial_sets.py
9fbfee6Simplicial sets: make Pushout, Pullback, etc., inherit from UniqueRepresentation:

comment:17 Changed 3 years ago by jhpalmieri

  • Status changed from needs_work to needs_review

comment:18 Changed 3 years ago by jhpalmieri

  • Description modified (diff)

I'm marking this as "needs_review" now.

comment:19 Changed 3 years ago by git

  • Commit changed from 9fbfee64243c72d653f7cada254eca756fa4622f to bcb0332845a09120cb294b77a70148c4d465b5d9

Branch pushed to git repo; I updated commit sha1. New commits:

bcb0332disambiguate two references

comment:20 Changed 3 years ago by git

  • Commit changed from bcb0332845a09120cb294b77a70148c4d465b5d9 to 94c21df3bb5018899dd23ff05e0881cb905f43e5

Branch pushed to git repo; I updated commit sha1. This was a forced push. New commits:

8522c66Merge branch 'develop' into simplicial_sets
94c21dfdisambiguate two references

comment:21 follow-up: Changed 3 years ago by cnassau

This looks like a lot of very nice and very useful code! I have just started to play around with it and have two quick comments:

  • SimplicialSets is not in the global namespace, whereas SimplicialComplex is globally available per default.
  • I tried TestSuite(RP7).run() and got lots of errors, presumably because an_element is not implemented.

I have no fixed opinion whether these observations count as faults or facts, though.

comment:22 Changed 3 years ago by cnassau

  • Cc cnassau added

comment:23 in reply to: ↑ 21 Changed 3 years ago by jhpalmieri

Hi Christian, thanks for taking a look!

Replying to cnassau:

This looks like a lot of very nice and very useful code! I have just started to play around with it and have two quick comments:

  • SimplicialSets is not in the global namespace, whereas SimplicialComplex is globally available per default.

It is more fiddly to construct simplicial sets than simplicial complexes, to the point that I don't know if ordinary users will want to use SimplicialSet much. (With hindsight, I would also not have included DeltaComplex in the global namespace either.) My hope is that users will use the predefined simplicial sets (especially simplices and spheres) and build other simplicial sets from those using quotients, products, mapping cones, etc.

  • I tried TestSuite(RP7).run() and got lots of errors, presumably because an_element is not implemented.

I didn't try that. It might be worth looking into.

comment:24 Changed 3 years ago by cnassau

I just tried (and eventually succeeded) to construct the quotient RP7/RP4 and to compute its cohomology. My first approach failed, since I found no way to define the inclusion map:

sage: RP4 = sage.homology.simplicial_set_catalog.RealProjectiveSpace(4)
sage: RP7 = sage.homology.simplicial_set_catalog.RealProjectiveSpace(7)
sage: X=Hom(RP4,RP7)
sage: X({i:i for i in RP4.nondegenerate_simplices()})
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
...
ValueError: this simplex is not in this simplicial set

Can this be done somehow?

A possibly related problem is that the ambient spaces are different; maybe they (and the projective spaces) should have unique representation?

sage: RP4.ambient_space() is RP7.ambient_space()
False

(Apologies if this is explained in the documentation somewhere - I usually only read documentation as a last resort.)

comment:25 follow-up: Changed 3 years ago by jhpalmieri

I thought about unique representation; my conclusion was that it was the wrong thing to do. If you construct two 4-spheres, you want them to be different so you can take their disjoint union, etc. A related implementation detail is that when you construct an abstract n-simplex, it is always distinct from previously created ones.

The easiest way to do what you're talking about is

sage: RP7 = simplicial_sets.RealProjectiveSpace(7)
sage: RP4 = RP7.n_skeleton(4)
sage: RP4.inclusion_map()
...
sage: RP7.quotient(RP4)
...

Or I think you also do

sage: C2 = groups.misc.MultiplicativeAbelian([2])
sage: BC2 = simplicial_sets.ClassifyingSpace(C2)
sage: RP7 = BC2.n_skeleton(7)
sage: RP4 = BC2.n_skeleton(4)

to get a common ambient space.

comment:26 in reply to: ↑ 25 Changed 3 years ago by cnassau

Replying to jhpalmieri:

I thought about unique representation; my conclusion was that it was the wrong thing to do. If you construct two 4-spheres, you want them to be different so you can take their disjoint union, etc. A related implementation detail is that when you construct an abstract n-simplex, it is always distinct from previously created ones.

I get your point and I now see that this is also very well explained in the documentation.

I don't understand the origin of the term f_vector: I can see that this is also used in the other cell complex related parts, but what might the "f" stand for? I'm just curious here...

comment:27 Changed 3 years ago by jhpalmieri

f-vector is standard in combinatorics, but I don't know what the "f" means. "face", I'm guessing?

comment:28 Changed 3 years ago by cnassau

I have just tried to implement the "Catalan simplicial set", modeled on your code for the nerve of a monoid. It seems to work fine, but I get this error from all_n_simplices:

sage: Cat.all_n_simplices(3)
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
...
NotImplementedError: this simplicial set may be infinite, so specify max_dim

I think this could be fixed by changing one line in the definition of all_n_simplices, i.e. change

 non_degen = [_ for _ in self.nondegenerate_simplices() if _.dimension() <= n]

to

 non_degen = [_ for _ in self.nondegenerate_simplices(max_dim=n) if _.dimension() <= n]

I have noted that the classifying spaces currently do not have an "all_n_simplices" method.

sage: X
Classifying space of Finite Field of size 3
sage: X.all_n_simplices(5)
...
AttributeError: 'Nerve_with_category' object has no attribute 'all_n_simplices'

Is this deliberate?

comment:29 Changed 3 years ago by jhpalmieri

This isn't deliberate. I think it's a good idea to make the change you suggest and to implement all_n_simplices for classifying spaces. I'm testing this right now; I'll push the change if it works.

comment:30 Changed 3 years ago by git

  • Commit changed from 94c21df3bb5018899dd23ff05e0881cb905f43e5 to 8dac89119b0fbcdad9505f32513d251da4aaaa33

Branch pushed to git repo; I updated commit sha1. New commits:

6eb29f4Merge branch 'develop' into simplicial_sets
8dac891simplicial sets: move all_n_simplices from the class of finite

comment:31 follow-up: Changed 3 years ago by cnassau

It seems there are some other calls to nondegenerate_simplices(...) that should have an approriate max_dim value: if I try to compute the homology of this Catalan simplicial set I otherwise get an error:

sage: Cat.chain_complex(dimensions=range(5))
...
NotImplementedError: this simplicial set may be infinite, so specify max_dim

I'm attaching my code so that you can see what I'm doing.

One strange thing is that my Cat has a couple of useful methods (e.g. homology) that are missing from the classifying spaces. The only relevant difference that I'm aware of is their categories, though that should add *more* methods, not hide them:

sage: Cat.categories()
[Category of simplicial sets,
 Category of sets,
 Category of sets with partial maps,
 Category of objects]
sage: simplicial_sets.ClassifyingSpace(GF(3)).categories()
[Category of pointed simplicial sets,
 Category of simplicial sets,
 Category of sets,
 Category of sets with partial maps,
 Category of objects]

I think something like

simplicial_sets.ClassifyingSpace(GF(3)).betti(3)

should probably work out of the box (i.e. without explicitly taking a skeleton).

Changed 3 years ago by cnassau

implementation of the catalan simplicial set

comment:32 in reply to: ↑ 31 Changed 3 years ago by jhpalmieri

Replying to cnassau:

It seems there are some other calls to nondegenerate_simplices(...) that should have an approriate max_dim value: if I try to compute the homology of this Catalan simplicial set I otherwise get an error:

The issue here is that your Cat inherits from SimplicialSet:

sage: sage.homology.simplicial_set.SimplicialSet?
Init signature: sage.homology.simplicial_set.SimplicialSet(self, data, base_point=None, name=None, check=True, category=None)
Docstring:     
   A finite simplicial set.

   ...

So it is assumed to be finite, which means it has more methods defined for it, like chain_complex, which are not defined for (for example) classifying spaces. Classifying spaces instead inherit from SimplicialSet_arbitrary. Note that SimplicialSet is just an alias for SimplicialSet_finite, and we could get rid of this alias and delete SimplicialSet if it would be clearer that way.

I think something like

simplicial_sets.ClassifyingSpace(GF(3)).betti(3)

should probably work out of the box (i.e. without explicitly taking a skeleton).

Methods like betti come from the class GenericCellComplex, and those are assumed to be finite. So we would need to implement betti for SimplicialSet_arbitrary, which we could do. Maybe also chain_complex and n_chains.

comment:33 Changed 3 years ago by tscrim

  • Description modified (diff)

At some point, we should lift up some of the things I did for Hochschild (co)homology to the chain complex and homology method to handle infinite-dimensional complexes (with each dimension a finite complex). Although, off-hand I can't think of a good way to handle the cases when there are an infinite number of cells in a given dimension.

comment:34 Changed 3 years ago by cnassau

I'm puzzled by the following output: the zeroeth cohomology only comes out right once:

sage: X=simplicial_sets.ClassifyingSpace(GF(3)) ; X
Classifying space of Finite Field of size 3
sage: X.cohomology(0)
Z
sage: X.cohomology((0,))
{0: 0}
sage: X.cohomology(range(4))
{0: 0, 1: 0, 2: 0, 3: 0}

I could imagine that this is a pointed vs. non-pointed issue, but I see the same behaviour for my Catalan set, which has no basepoint.

PS: I now see that the documentation specifies reduced homology, but the answer seems inconsistent nonetheless...

Last edited 3 years ago by cnassau (previous) (diff)

comment:35 Changed 3 years ago by jhpalmieri

That's a bug in the simplicial set chain complex code. Here is a patched version, which also implements chain_complex and n_chains for arbitrary simplicial sets, not just finite ones.

comment:36 Changed 3 years ago by git

  • Commit changed from 8dac89119b0fbcdad9505f32513d251da4aaaa33 to 1443151ed3285ccfa132d9edf784c8eb9960ea90

Branch pushed to git repo; I updated commit sha1. New commits:

1443151simplicial sets: implement betti, chain_complex, and n_chains for

comment:37 Changed 3 years ago by git

  • Commit changed from 1443151ed3285ccfa132d9edf784c8eb9960ea90 to cecb9bc13cc3167d0b6a6c73c7b71000db7b19ef

Branch pushed to git repo; I updated commit sha1. New commits:

cecb9bcfix typo in documentation

comment:38 follow-up: Changed 3 years ago by cnassau

Here's a minor thing I just noted: latex() of cells in a classifying space doesn't print correctly:

sage: Q=simplicial_sets.ClassifyingSpace(groups.misc.WeylGroup('B2'))
sage: Q.n_cells(1)
[[ 0  1]
 [-1  0], [ 0 -1]
 [ 1  0], [ 0 -1]
 [-1  0], [ 1  0]
 [ 0 -1], [-1  0]
 [ 0  1], [-1  0]
 [ 0 -1], [0 1]
 [1 0]]
sage: latex(Q.n_cells(1))
\left[\Delta^{1}, \Delta^{1}, \Delta^{1}, \Delta^{1}, \Delta^{1}, \Delta^{1}, \Delta^{1}\right]

On a related note, I don't quite like the very verbose printing of degeneracies (but that might be a matter of taste). I would probably prefer something like "s_0(f * f)" instead of "Simplex obtained by applying degeneracy s_0 to f * f".

comment:39 follow-up: Changed 3 years ago by cnassau

I think it would be valuable if one could construct simplices in a classifying space from the corresponding sequence of monoid elements. Here's how I currently construct a map from RP2 to BD8:

sage: D8=DihedralGroup(4)
sage: X=simplicial_sets.ClassifyingSpace(D8)
sage: g=D8((1,3))
sage: RP2=simplicial_sets.RealProjectiveSpace(2)
sage: e0,e1,e2 = RP2.nondegenerate_simplices()
sage: H=Hom(RP2,X)
sage: X.n_skeleton(2) ;# hack to create X._simplex_data
Simplicial set with 57 non-degenerate simplices
sage: i=H({e0:X.base_point(),e1:dict(X._simplex_data)[(g,)],e2:dict(X._simplex_data)[(g,g)]}) ; i
Simplicial set morphism:
  From: RP^2
  To:   Classifying space of Dihedral group of order 8 as a permutation group
  Defn: [1, f, f * f] --> [(), (1,3), (1,3) * (1,3)]

It would be nice to have a method on X that wraps the dict(X._simplex_data)[(g1,g2,...)] (and is able to recognize degenerate simplices and implicitly constructs the required n-skeleton).

I would also like to use the universal covering space for BD8 here, but since this is infinite there would currently be no way to construct the ED8 -> BD8 as a simplical map. I'll add this to the description of this ticket as a possible follow-up topic.

comment:40 Changed 3 years ago by cnassau

  • Description modified (diff)

comment:41 in reply to: ↑ 38 Changed 3 years ago by jhpalmieri

Replying to cnassau:

Here's a minor thing I just noted: latex() of cells in a classifying space doesn't print correctly:

Is there a standard way of customizing the LaTeX names of SageObjects? We could fix this anyway, but as you say, it's pretty minor.

On a related note, I don't quite like the very verbose printing of degeneracies (but that might be a matter of taste). I would probably prefer something like "s_0(f * f)" instead of "Simplex obtained by applying degeneracy s_0 to f * f".

The standard way of printing a nondegenerate simplex is "Non-degenerate simplex of dimension d". I don't think that "s_0 (Non-degenerate simplex ...)" looks very good. I suppose we could check if the nondegenerate simplex has a custom name and use "s_0 (name)" if so, the verbose version if not.

If you feel like making these changes, go ahead. They're not at the top of my list, though.

comment:42 in reply to: ↑ 39 Changed 3 years ago by jhpalmieri

Replying to cnassau:

I think it would be valuable if one could construct simplices in a classifying space from the corresponding sequence of monoid elements. Here's how I currently construct a map from RP2 to BD8:

sage: D8=DihedralGroup(4)
sage: X=simplicial_sets.ClassifyingSpace(D8)
sage: g=D8((1,3))
sage: RP2=simplicial_sets.RealProjectiveSpace(2)
sage: e0,e1,e2 = RP2.nondegenerate_simplices()
sage: H=Hom(RP2,X)
sage: X.n_skeleton(2) ;# hack to create X._simplex_data
Simplicial set with 57 non-degenerate simplices
sage: i=H({e0:X.base_point(),e1:dict(X._simplex_data)[(g,)],e2:dict(X._simplex_data)[(g,g)]}) ; i
Simplicial set morphism:
  From: RP^2
  To:   Classifying space of Dihedral group of order 8 as a permutation group
  Defn: [1, f, f * f] --> [(), (1,3), (1,3) * (1,3)]

It would be nice to have a method on X that wraps the dict(X._simplex_data)[(g1,g2,...)] (and is able to recognize degenerate simplices and implicitly constructs the required n-skeleton).

That sounds like a good idea.

comment:43 Changed 3 years ago by cnassau

One thing that strikes me as problematic is that you're not using the Parent/Element? framework for the simplicial sets: currently, the sets themselves are declared to be Parent objects, but their simplices are implemented by a separate AbstractSimplex? class. Shouldn't it be "better" (and more in line with the general Sage "philosophy") to realize the simplices as Element instances? or is there maybe a good reason why you did not take that course?

I actually feel a bit bad about bringing this up, because re-wiring the code to use the Element framework would not just be a lot of work, but also potentially open a can of worms with somewhat unclear benefits.

But anyway, regarding the latex representation of cells in a BG, for example, it would be nice to have a dedicated element class for simplices in a nerve; these elements would remember the chain of monoid elements used to construct them and forward calls to _repr_ and _latex_ to those monoid elements. Among other things, this avoids setting a custom_name for all (nondegenerate) simplices, which might be regarded a little wasteful. [Of course, it does set a custom "_chain" attribute then, which takes up the same space - but this is arguably more "valuable"]

Actually, I just tried this approach out (i.e. the Nerve with a custom Element subclass). It seems to work fine - except that for the life of me I cannot get the sorting of these elements in line with the existing code from AbstractSimplex?... so plenty of doctests fail. This is probably just a small indication of the nasty suprises that might be lurking here.

Anyway, I'm just curious to hear what your view on this is!

comment:44 Changed 3 years ago by tscrim

One benefit to using the Parent/Element framework is coercion; in particular, for input of morphisms. Another is you can take advantage of coercion for rich comparisons (side note, IIRC, you do not need __ne__ with @total_ordering). In principle, it should be as easy as changing the inheritance from SageObject to Element and setting Element = AbstractSimplex in the parent class. To get coercions, a few _coerce_map_from_ might need to be implemented.

From now (finally) having a moment to take a skim-through, I would separate the examples into a separate file for easier readability and separation-of-concerns.

comment:45 follow-ups: Changed 3 years ago by jhpalmieri

I'm finally getting back to this.

  • I think you do need to define __ne__ with @total_ordering: I think @total_ordering only deals with <, >, etc., not !=. Looking at the @total_ordering source code seems to confirm this; furthermore, if I delete that method, I get all sorts of breakages.
  • Now I remember why I'm not using the Parent/Element framework: when you define an Element, you are supposed to assign a parent to it, but when defining simplices for a simplicial set, you often want to define the simplices first, without assigning a parent to them. In particular, you should be able to define an abstract simplex without having a parent attached to it, and this disagrees with the documentation for the Element class in Sage:
        Subtypes must either call ``__init__()`` to set ``_parent``, or may
        set ``_parent`` themselves if that would be more efficient.
    
    You also might want one simplex to be an element of several different simplicial sets (not two copies of that simplex, but that exact simplex, for example when constructing subsimplices). Suggestions for how to get around this?

comment:46 Changed 3 years ago by git

  • Commit changed from cecb9bc13cc3167d0b6a6c73c7b71000db7b19ef to 64c8d0a232bd0969978066607a6cd3ca42096d88

Branch pushed to git repo; I updated commit sha1. New commits:

64c8d0aSimplicial sets: move examples to a separate file.

comment:47 in reply to: ↑ 45 Changed 3 years ago by tscrim

Replying to jhpalmieri:

I'm finally getting back to this.

  • I think you do need to define __ne__ with @total_ordering: I think @total_ordering only deals with <, >, etc., not !=. Looking at the @total_ordering source code seems to confirm this; furthermore, if I delete that method, I get all sorts of breakages.

Okay, good to know. Thanks. I really wish we had a default __ne__ for SageObject that just called return not self == other...

  • Now I remember why I'm not using the Parent/Element framework: when you define an Element, you are supposed to assign a parent to it, but when defining simplices for a simplicial set, you often want to define the simplices first, without assigning a parent to them. In particular, you should be able to define an abstract simplex without having a parent attached to it, and this disagrees with the documentation for the Element class in Sage:
        Subtypes must either call ``__init__()`` to set ``_parent``, or may
        set ``_parent`` themselves if that would be more efficient.
    
    You also might want one simplex to be an element of several different simplicial sets (not two copies of that simplex, but that exact simplex, for example when constructing subsimplices). Suggestions for how to get around this?

You can point to the underlying data between simplicies, which would minimize the overhead. The other option would be to have an Element version of an abstract simplex, where you copy all of the data. However, both of these have some drawbacks, and it might depend more on how this will be used what the best course of action is. The good news is that these are easy enough to refactor, so future changes should be straightforward if we decide to not change anything now.

comment:48 Changed 3 years ago by jhpalmieri

I'm inclined not to modify the Element structure right now.

I looked into the TestSuite.

  • it's easy enough to implement _an_element_: just return a vertex (as long as it has one).
  • I also get a failure because SimplicialSet_finite inherits from GenericCellComplex, which has an @abstract_method which is not defined for simplicial sets: join. I will define a join method for simplicial sets which just raises a NotImplementedError, rather than just skip this test. (Or implement joins, but I don't think I want to do that right now.)
  • I also get a pickling error, and I don't know how to debug it:
        File "sage/structure/sage_object.pyx", line 436, in sage.structure.sage_object.SageObject.dumps (build/cythonized/sage/structure/sage_object.c:3727)
          s = cPickle.dumps(self, protocol=2)
      PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed
    
    Any suggestions?

comment:49 in reply to: ↑ 45 ; follow-ups: Changed 3 years ago by cnassau

Replying to jhpalmieri:

  • Now I remember why I'm not using the Parent/Element framework: when you define an Element, you are supposed to assign a parent to it, but when defining simplices for a simplicial set, you often want to define the simplices first, without assigning a parent to them. In particular, you should be able to define an abstract simplex without having a parent attached to it, and this disagrees with the documentation for the Element class in Sage:
        Subtypes must either call ``__init__()`` to set ``_parent``, or may
        set ``_parent`` themselves if that would be more efficient.
    
    You also might want one simplex to be an element of several different simplicial sets (not two copies of that simplex, but that exact simplex, for example when constructing subsimplices). Suggestions for how to get around this?

I have to think about these questions a bit longer. You are probably referring to constructions like these from the doctests:

sage: v = AbstractSimplex(0, name='v')
sage: a = AbstractSimplex(0, name='a')
sage: b = AbstractSimplex(0, name='b')
sage: c = AbstractSimplex(0, name='c')
sage: e0 = AbstractSimplex(1, name='e_0')
sage: e1 = AbstractSimplex(1, name='e_1')
sage: e2 = AbstractSimplex(1, name='e_2')
sage: X = SimplicialSet({e2: (b, a)})
sage: Y0 = SimplicialSet({e2: (b,a), e0: (c,b), e1: (c,a)})

My feeling is that AbstractSimplex could maybe become a SimplicialSet itself, namely an alias for a generic n-simplex. Whether this works out seems hard to say without actually trying to implement it. I think there are potential benefits of sticking closer to the Sage parent/element framework, but whether this is worthwhile depends on the pain implied by the implementation.

comment:50 follow-up: Changed 3 years ago by tscrim

It looks like you're trying to pickle a lambda function, which you cannot do. IIRC, the usual workaround is to have an explicitly named function/method somewhere.

comment:51 in reply to: ↑ 49 Changed 3 years ago by jhpalmieri

Replying to cnassau:

I have to think about these questions a bit longer. You are probably referring to constructions like these from the doctests:

sage: v = AbstractSimplex(0, name='v')
sage: a = AbstractSimplex(0, name='a')
sage: b = AbstractSimplex(0, name='b')
sage: c = AbstractSimplex(0, name='c')
sage: e0 = AbstractSimplex(1, name='e_0')
sage: e1 = AbstractSimplex(1, name='e_1')
sage: e2 = AbstractSimplex(1, name='e_2')
sage: X = SimplicialSet({e2: (b, a)})
sage: Y0 = SimplicialSet({e2: (b,a), e0: (c,b), e1: (c,a)})

My feeling is that AbstractSimplex could maybe become a SimplicialSet itself, namely an alias for a generic n-simplex. Whether this works out seems hard to say without actually trying to implement it. I think there are potential benefits of sticking closer to the Sage parent/element framework, but whether this is worthwhile depends on the pain implied by the implementation.

It's not just doctests. Any time you want to define a simplicial set in Sage, you build it out of instances of AbstractSimplex. To construct a sphere, you define a 0-simplex and an n-simplex, and then tell Sage what each face of the n-simplex is (= the unique degenerate (n-1)-simplex coming from the vertex).

So I guess we could have a hierarchy of classes: a basic simplicial set to model simplices, then a full-fledged simplicial set built out of those basic ones? Would the full-fledged simplicial sets have the basic ones as elements? I'm not sure how it would work. If you have ideas for an implementation, let me know.

comment:52 in reply to: ↑ 50 Changed 3 years ago by jhpalmieri

Replying to tscrim:

It looks like you're trying to pickle a lambda function, which you cannot do. IIRC, the usual workaround is to have an explicitly named function/method somewhere.

Oh, I see. For some of the examples of simplicial sets, like the n-sphere, I have lines like

    S._latex_ = lambda: 'S^{{{}}}'.format(n)
    return S

So maybe the class SimplicialSet_finite (or SimplicialSet_arbitrary) should have a custom latex_name argument which gets used in its _latex_ method, to avoid this sort of thing.

comment:53 Changed 3 years ago by git

  • Commit changed from 64c8d0a232bd0969978066607a6cd3ca42096d88 to 18710835161be845f2ea06e0ecfa7fdff1f995fd

Branch pushed to git repo; I updated commit sha1. New commits:

1871083Simplicial sets: move Nerve to simplicial_set_examples, add

comment:54 Changed 3 years ago by jhpalmieri

Here is partial progress: latex names for AbstractSimplex. I'll work on latex names for simplicial sets and on the pickling issue later.

comment:55 in reply to: ↑ 49 ; follow-up: Changed 3 years ago by cnassau

Replying to cnassau:

My feeling is that AbstractSimplex could maybe become a SimplicialSet itself, namely an alias for a generic n-simplex. Whether this works out seems hard to say without actually trying to implement it. I think there are potential benefits of sticking closer to the Sage parent/element framework, but whether this is worthwhile depends on the pain implied by the implementation.

This idea was somehwat off, but the following seems to work:

1) create a new parent singleton SetOfAbstractSimplices as a parent for all AbstractSimplex objects

2) make SimplicialSets a subcategory of FacadeSets, so they can all share their elements with the SetOfAbstractSimplices

I have implemented this (very roughly) and think this might be a feasible approach. My changes are shown in this commit:

https://git.sagemath.org/sage.git/commit/?id=bcc53e3b9100163d7c1c2d0a42aceb9458f83c8a

One of the benefits is that simplicial sets can then have custom element classes: this is illustrated in the Nerve, where simplices only remember the chain of group elements, not their "custom name" or "custom latex name". And their names are only computed when somebody actually asks for them.

(caveat: "latex(degeneration(nerve-simplex))" is still wrong with this code: there should probably be a _latex_nondegenerate_ method that the Nerve.Element class should override, but these are details left for later).

Pickling remains a problem because of the lack of unique representation; I suggest to disable the _test_pickling for the simplicial stuff (have done that for elements in that commit).

In case you could warm up to these suggestions there might be a few further rearrangements, that might prove useful; much of the AbstractSimplex? code should probably moved into the ElementMethods? of the SimplicialSets? category, for example.

comment:56 in reply to: ↑ 55 Changed 3 years ago by tscrim

Replying to cnassau:

2) make SimplicialSets a subcategory of FacadeSets, so they can all share their elements with the SetOfAbstractSimplices

Please no, I've seen very subtle behavior problems with facade sets/parents. Facades don't "share" elements, they just allow you to do P(foo), but you run into trouble when you really do want elements of P. A much better approach is to pass underlying data and have the elements behave like adapter classes (it just can require more lines of documentation). I'm +1 on having actual element classes, but it doesn't need to be here for this to get a positive review.

Pickling remains a problem because of the lack of unique representation; I suggest to disable the _test_pickling for the simplicial stuff (have done that for elements in that commit).

Unique representation is just one way to deal with pickling, but it is not the only way. comment:52 is the likely culprit to me, and I'm +1 on having a (customizable) _latex_name attribute/input. We do this, e.g., in CombinatorialFreeModule and in the manifolds.

comment:57 Changed 3 years ago by git

  • Commit changed from 18710835161be845f2ea06e0ecfa7fdff1f995fd to cd9cfffdf37d0c9e24ac5ecc0c255130f5945e23

Branch pushed to git repo; I updated commit sha1. New commits:

cd9cfffsimplicial sets: add latex_name for SimplicialSet_finite.

comment:58 follow-up: Changed 3 years ago by jhpalmieri

I removed the lambda functions, but pickling still doesn't work, with a different error this time:

  File "/Users/palmieri/Desktop/Sage_stuff/git/sage/local/lib/python2.7/site-packages/sage/homology/simplicial_set.py", line 3227, in __hash__
    return hash(self._data)
  File "sage/structure/parent.pyx", line 855, in sage.structure.parent.Parent.__getattr__ (build/cythonized/sage/structure/parent.c:8320)
    attr = getattr_from_other_class(self, self._category.parent_class, name)
  File "sage/structure/misc.pyx", line 253, in sage.structure.misc.getattr_from_other_class (build/cythonized/sage/structure/misc.c:1755)
    raise dummy_attribute_error
AttributeError: 'SimplicialSet_finite_with_category' object has no attribute '_data'

I don't understand this because when I define a simplicial set, first of all, self._data is defined in the __init__ method, and second of all, I can access X._data from the command line with no problem. I must be missing something obvious.

comment:59 in reply to: ↑ 58 ; follow-up: Changed 3 years ago by cnassau

Replying to jhpalmieri:

I removed the lambda functions, but pickling still doesn't work, with a different error this time:

I don't think there's a chance to make pickling work to the satisfaction of the TestSuite: you can never have "loads(dumps(simplex)) == simplex" since both sides are different python objects and equality of AbstractSimplex objects is tied to their id. And since SimplicialSet objects are uniquely determined by their simplices this means that they too can't be recovered from their pickle.

Of course, the "loads(dumps(...))" call itself should eventually work. It seems pickle takes the hash before the object is fully constructed:

sage: from sage.structure.sage_object import StringIO
sage: pickle.load(StringIO(dumps(CP2,compress=false)))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-695809fd4beb> in <module>()
...
/waste/cn/sage-git/local/lib/python/pickle.pyc in load_setitem(self)
-> 1202         dict[key] = value
   1203     dispatch[SETITEM] = load_setitem

I have no idea why this might be the case, though... a hidden recursive reference, maybe...? FWIW, pickling succeeds if the __hash__ routine returns -1 in case of a missing _data member.

comment:60 in reply to: ↑ 59 Changed 3 years ago by tscrim

Replying to cnassau:

Replying to jhpalmieri:

I removed the lambda functions, but pickling still doesn't work, with a different error this time:

I don't think there's a chance to make pickling work to the satisfaction of the TestSuite: you can never have "loads(dumps(simplex)) == simplex" since both sides are different python objects and equality of AbstractSimplex objects is tied to their id. And since SimplicialSet objects are uniquely determined by their simplices this means that they too can't be recovered from their pickle.

I would say that is a bad equality check (that is only for nondegenerate simplices). It should (along with the hash) compare the underlying simplices. I think the first check, which was suppose to be an optimization, should just be removed (likewise for __hash__).

comment:61 follow-ups: Changed 3 years ago by jhpalmieri

I can certainly make the change

  • src/sage/homology/simplicial_set.py

    diff --git a/src/sage/homology/simplicial_set.py b/src/sage/homology/simplicial_set.py
    index 2f9987a..3ef1c2f 100644
    a b class AbstractSimplex(SageObject): 
    484484        """
    485485        if self.is_nondegenerate():
    486486            return id(self)
    487         return hash(self.nondegenerate()) ^ hash(self._degens)
     487        return id(self.nondegenerate()) ^ hash(self._degens)
    488488
    489489    def __eq__(self, other):
    490490        """
    class AbstractSimplex(SageObject): 
    512512        """
    513513        if not isinstance(other, AbstractSimplex):
    514514            return False
    515         if self.is_nondegenerate() and other.is_nondegenerate():
    516             return self is other
    517515        return (self._degens == other._degens
    518516                and self.nondegenerate() is other.nondegenerate())
    519517

loads(dumps(x)) == x will still fail, which is fine with me.

This is for AbstractSimplex, though. My other problem was for pickling with SimplicialSet_finite, which I still don't understand. (I agree with Christian that loads(dumps(X)) == X will fail, but I would like loads(dumps(X)) to not raise an error.)

comment:62 in reply to: ↑ 61 Changed 3 years ago by cnassau

Replying to jhpalmieri:

My other problem was for pickling with SimplicialSet_finite, which I still don't understand. (I agree with Christian that loads(dumps(X)) == X will fail, but I would like loads(dumps(X)) to not raise an error.)

The problem seems to be that every AbstractSimplex in a SimplicialSet keeps a list of its parents in the _faces attribute. I just deactivated these lines and "loads(dumps(...))" works again:

        # Finally, store the faces of each nondegenerate simplex as an
        # attribute of the simplex itself.
        for x in simplices:
            x._faces[self] = data[x]

I think during unpickling python tries to re-populate the x._faces for each x in its _data, and here it needs the hash of self even though self is not fully reconstructed, yet.

FWIW, I think this _faces attribute in the AbstractSimplex should probably be removed. The category should specify a "faces(element)" parent method, while the actual parent implementations are free to implement this in whatever way they like.

Last edited 3 years ago by cnassau (previous) (diff)

comment:63 follow-up: Changed 3 years ago by jhpalmieri

The current data structure is flawed, I think. I think it's a good idea that two simplicial sets can contain the same AbstractSimplex, but then to make geometric sense, that AbstractSimplex should have the same faces in each of those simplicial sets (possibly up to some identifications, if a quotient operation is involved). In other words, I agree that the ._faces attribute, keyed by simplicial sets, should be removed.

So I've been trying to think of other options. Here's one idea:

  • PreSimplex -- like the current AbstractSimplex, it would have a dimension and optionally, degeneracies, an underlying nondegenerate PreSimplex, a name, etc. It's not a full-fledged simplex because it doesn't have faces.
  • SimplicialSet_finite. Define by specifying a dictionary, as now, where each key is a PreSimplex and the corresponding value is its list of faces. At that point, each PreSimplex would define an Element of the simplicial set and would remember its faces. Could you also specify Elements of other simplicial sets as keys? If you specify the same PreSimplex as belonging to two different simplicial sets, what should happen? Raise an error unless the faces match up?

comment:64 Changed 3 years ago by git

  • Commit changed from cd9cfffdf37d0c9e24ac5ecc0c255130f5945e23 to 2b2a2c9df6428459b2a0875f4e483842e2635335

Branch pushed to git repo; I updated commit sha1. New commits:

2b2a2c9simplicial sets: remove ._faces attribute from AbstractSimplex. Minor cleanup.

comment:65 Changed 3 years ago by jhpalmieri

Here are minor changes: remove the ._faces attribute and other minor cleanup. It may still be a good idea to change the data structure more, though.

comment:66 Changed 3 years ago by jhpalmieri

I guess my proposal is not that different from the current branch, except that it also would involve a new Element class for any PreSimplex/AbstractSimplex contained in a simplicial set. This would have the advantage of using the parent/element framework. I still had a few questions in comment:63.

I'm open to other ideas, too.

comment:67 in reply to: ↑ 63 ; follow-up: Changed 3 years ago by cnassau

Replying to jhpalmieri:

I think it's a good idea that two simplicial sets can contain the same AbstractSimplex, but then to make geometric sense, that AbstractSimplex should have the same faces in each of those simplicial sets (possibly up to some identifications, if a quotient operation is involved).

I like your basic approach to construct a simplicial set from a collection of simplices + additional face information. However, instead of creating a PreSimplex class without faces we could also just *add* faces to the AbstractSimplex, which turns them into regular $n$-simplices (this should also be easier to explain). Consider the code

sage: a,b = [AbstractSimplex(0,name=x) for x in "ab"]
sage: e,f = [AbstractSimplex(1,name=x) for x in "ef"]
sage: X = SimplicialSet_finite({e:(a,b),f:(a,b)})
sage: Y = SimplicialSet_finite({e:(e.face(0),e.face(1)),f:(e.face(0),e.face(1))})
sage: Z = SimplicialSet_finite({e:(f.face(0),f.face(0)),f:(f.face(0),f.face(1))})

As far as the Parent/Element framework is concerned, none of $a,b,e,f$ would be actual elements of $X$, $Y$, or $Z$; rather they would be Elements of the SetOfAbstractSimplices singleton (which models the infinite coproduct of all \Delta_n^{(x)} where $x$ can be, say, any python object).

To create elements you would have to cast them to the parent (which might sometimes happen implicitly):

sage: X.n_cells(0) == [a,b]
False
sage: X.n_cells(0) == [X(a), X(b)]
True
sage: Y.n_cells(0)
[ Face d0 of e, Face d1 of e]
sage: Z.n_cells(0)
[ Face d0 of f, Face d1 of f ]
sage: Z(e.face(1)) == Z(e).face(1)
True
sage: Z(e).faces()
( Face d0 of f, Face d0 of f )

Here my assumption is that we're *not* following my earlier suggestion to use FacadeSets; rather every element/simplex belongs to a unique parent, following the advice of Travis in comment 56.

comment:68 in reply to: ↑ 61 Changed 3 years ago by tscrim

Replying to jhpalmieri:

I can certainly make the change

Not quite, the change I was suggesting was

  • src/sage/homology/simplicial_set.py

    diff --git a/src/sage/homology/simplicial_set.py b/src/sage/homology/simplicial_set.py
    index 2f9987a..3ef1c2f 100644
    a b class AbstractSimplex(SageObject): (this hunk was shorter than expected) 
    484484        """
    485         if self.is_nondegenerate():
    486             return id(self)
    487485        return hash(self.nondegenerate()) ^ hash(self._degens)
    488486
    489487    def __eq__(self, other):
    490488        """
    class AbstractSimplex(SageObject): 
    512512        """
    513513        if not isinstance(other, AbstractSimplex):
    514514            return False
    515         if self.is_nondegenerate() and other.is_nondegenerate():
    516             return self is other
    517515        return (self._degens == other._degens
    518516                and self.nondegenerate() is other.nondegenerate())
    519517

comment:69 Changed 3 years ago by jhpalmieri

Travis, with this change to __hash__, hash(self.nondegenerate()) would not be defined. If you make your change plus change that to id(self.nondegenerate()) ^ hash(self._degens), then it somehow messes up the ordering on simplices: the ordering becomes random, somehow depending on id more than with the current code. I don't know the details, but I also don't know why you object to keeping the original if.

comment:70 Changed 3 years ago by tscrim

Ah, I see. I was thinking the non-degenerate simplex was an instance of Simplex. So this problem with picking is basically what we had to contend with for SageManifolds. What we did was use UniqueRepresentation with add a hidden key value that was the time the object was created. You could use the same trick here for non-degenerate simplicies. You could then also use UniqueRepresentation for the degenerate simplicies as well and would have a much faster equality and hash operations.

comment:71 in reply to: ↑ 67 ; follow-up: Changed 3 years ago by jhpalmieri

Replying to cnassau:

Replying to jhpalmieri:

I think it's a good idea that two simplicial sets can contain the same AbstractSimplex, but then to make geometric sense, that AbstractSimplex should have the same faces in each of those simplicial sets (possibly up to some identifications, if a quotient operation is involved).

I like your basic approach to construct a simplicial set from a collection of simplices + additional face information. However, instead of creating a PreSimplex class without faces we could also just *add* faces to the AbstractSimplex, which turns them into regular $n$-simplices (this should also be easier to explain). Consider the code

sage: a,b = [AbstractSimplex(0,name=x) for x in "ab"]
sage: e,f = [AbstractSimplex(1,name=x) for x in "ef"]
sage: X = SimplicialSet_finite({e:(a,b),f:(a,b)})
sage: Y = SimplicialSet_finite({e:(e.face(0),e.face(1)),f:(e.face(0),e.face(1))})
sage: Z = SimplicialSet_finite({e:(f.face(0),f.face(0)),f:(f.face(0),f.face(1))})

So an AbstractSimplex may or may not have faces, right? In your example, e and f do not have faces at the start, but after defining X, they do. So I guess at the start, e.face(0) would raise an error.

This gets back to my questions: what should happen if you do

sage: a,b,c = [AbstractSimplex(0,name=x) for x in "abc"]
sage: e,f = [AbstractSimplex(1,name=x) for x in "ef"]
sage: K = SimplicialSet_finite({e:(a,b)})
sage: L = SimplicialSet_finite({e:(b,c)})

Raise an error because of the inconsistent face data for e?

Should you also be able to do

sage: M = SimplicialSet_finite({f:(a,b), e:True})

or some other non-iterable value for e, which would just mean: e has already been defined, so use its existing faces? Or pass a tuple instead of a dictionary as defining data, where each entry is either a full-fledged simplex or a pair (instance of AbstractSimplex, list of its faces)? Maybe implement the latter as follows: if sigma is an instance of AbstractSimplex, then sigma[0] would return sigma while sigma[1] would return its tuple of faces. Then you could easily deal with a tuple which has entries either of the form (f: (a,b)) or e.

Is the point of the SetOfAbstractSimplices construction is just to have a parent around, so that AbstractSimplex can inherit from Element?

comment:72 Changed 3 years ago by git

  • Commit changed from 2b2a2c9df6428459b2a0875f4e483842e2635335 to 7ac9b30d34cf8ef9c30d0617f002421a78f8c763

Branch pushed to git repo; I updated commit sha1. New commits:

7ac9b30Simplicial sets: fix pickling by making NonDegenerateSimplex inherit

comment:73 Changed 3 years ago by git

  • Commit changed from 7ac9b30d34cf8ef9c30d0617f002421a78f8c763 to afbad4505350d79488d56067151c6f109782693a

Branch pushed to git repo; I updated commit sha1. New commits:

afbad45Simplicial sets: AbstractSimplex now inherits from UniqueRepresentation

comment:74 Changed 3 years ago by jhpalmieri

Progress toward Travis's remarks: this make NonDegenerateSimplex a new class which inherits from UniqueRepresentation. It fixes pickling, and now simplicial sets (at least the first few examples I tried) pass their test suite. AbstractSimplex now also inherits from UniqueRepresentation. I had to add new inequality methods, since as far as I can tell, UniqueRepresentation doesn't work with @total_ordering.

comment:75 in reply to: ↑ 71 Changed 3 years ago by cnassau

Replying to jhpalmieri:

So an AbstractSimplex may or may not have faces, right? In your example, e and f do not have faces at the start, but after defining X, they do. So I guess at the start, e.face(0) would raise an error.

No, sorry, what I meant is that AbstractSimplex should become a fully funcional simplex in its own right. So e.face(0) would print as Face d0 of e (and maybe still be an instance of AbstractSimplex, but I'm not sure on this).

This gets back to my questions: what should happen if you do

sage: a,b,c = [AbstractSimplex(0,name=x) for x in "abc"]
sage: e,f = [AbstractSimplex(1,name=x) for x in "ef"]
sage: K = SimplicialSet_finite({e:(a,b)})
sage: L = SimplicialSet_finite({e:(b,c)})

Raise an error because of the inconsistent face data for e?

No, no error: the point is that e is neither a simplex of K nor of L, it can just be coerced into those sets. Then K(e) != L(e) since these simplices belong to different parents. And there's no problem with K(e.face(0)) = K(e).face(0) != L(e).face(0).

Should you also be able to do

sage: M = SimplicialSet_finite({f:(a,b), e:True})

or some other non-iterable value for e, which would just mean: e has already been defined, so use its existing faces?

Yes, I think that sounds like a good idea. This should be equivalent to {f:(a,b), e:(e.face(0),e.face(1))}, so e defines a disjoint 1-simplex (assuming that a,b are not faces of e).

Or pass a tuple instead of a dictionary as defining data, where each entry is either a full-fledged simplex or a pair (instance of AbstractSimplex, list of its faces)? Maybe implement the latter as follows: if sigma is an instance of AbstractSimplex, then sigma[0] would return sigma while sigma[1] would return its tuple of faces. Then you could easily deal with a tuple which has entries either of the form (f: (a,b)) or e.

This sounds like a nice way to make the defining data a bit simpler to specify (although it's probably just syntactic sugar).

Is the point of the SetOfAbstractSimplices construction is just to have a parent around, so that AbstractSimplex can inherit from Element?

Essentially yes. And it might make the AbstractSimplex? business easier to grasp from a mathematical point of view: the face data defining a SimplicialSet_finite $X$ amount to the specification of an assembly map

\coprod_{i\in I} \Delta_{n_i}  \rightarrow   X

An AbstractSimplex should be the tautological simplex $\iota_n\in\Delta_n$ for one of these summands.

comment:76 Changed 3 years ago by jhpalmieri

Okay, I think I understand. I guess each non-degenerate AbstractSimplex corresponds to one of the \Delta_n's, and then the degeneracies are clear mathematically, and I think we handle them as in the current code. So then I have the same question you do, which is what structure should the face of a non-degenerate AbstractSimplex have?

  • We could just create a class FaceOfAbstractSimplex for any (iterated) face of one of the \Delta_n's. It wouldn't have to do much, since most manipulations will occur within a simplicial set other than SetOfAbstractSimplices.
  • Or we could think that when we define the n-dimensional AbstractSimplex indexed by x, we in fact define a family of i-simplices, i <= n, namely the actual simplex \Delta_{n,x} and all of its faces, all indexed by x. Thus e.face(0) would be an AbstractSimplex with the same indexing element as e, but of one dimensional lower.

comment:77 follow-up: Changed 3 years ago by tscrim

Unless you pass a unique tag up in the __classcall__, it won't get put into the key for UniqueRepresentation. Actually, probably what you had before was good. (Sorry, I haven't had time to full digest the code, clearly stuff off my plate slowly but surely,) You probably could just put UniqueRepresentation first so its __hash__ and __eq__ get placed first by the MRO.

You might also consider having a separate class for degenerate simplicies and then have some constructor (e.g., a ClasscallMetaclass with __classcall_private__ in AbstractSimplex or have a global function entry point of Simplex, which probably should become a subclass of AbstractSimplex and/or NonDegenerateSimplex).

I am also against this idea of having a singleton parent for the set of all simplicies. It feels like overkill and I fear we could quickly find ourselves in a form of pointer hell (parent hell?) trying to keep everything organized.

comment:78 Changed 3 years ago by jhpalmieri

In the most recent version, I got rid of the line __hash__ = UniqueRepresentation.__hash__ or whatever it was. (In the earlier version, if I put UniqueRepresentation first, then it seemed that I had to override the four methods __lt__, __gt__, etc., instead of the two methods __eq__ and __hash__. This is moot now.) The most recent version also has a global function entry point (now called AbstractSimplex) for both AbstractSimplex_class and NonDegenerateSimplex, although in practice, I think I really just need NonDegenerateSimplex. I have to examine that.

Also, I only want the unique tag for NonDegenerateSimplex, not for AbstractSimplex_class: NonDegenerateSimplex inherits from AbstractSimplex_class, and in particular, if I start with the same non-degenerate simplex and apply the same degeneracies two different times, I should get the degenerate simplex. So for AbstractSimplex_class, the determining data should be the underlying non-degenerate simplex plus the degeneracies, but not the time stamp. That is,

sage: sigma.apply_degeneracies(1, 0) == sigma.apply_degeneracies(1, 0)

should return True (as opposed to NonDegenerateSimplex(3) == NonDegenerateSimplex(3), which should return False).

With what I had a while ago, there were some pickling issues. I don't know at which stage in the revisions they went away, though.

I am not sure about the singleton parent. I can see advantages and drawbacks. It might be good to have AbstractSimplex_class inherit from Element. An alternative is to have a separate Element class for SimplicialSet_finite (or for SimplicialSet_arbitrary) which inherits from AbstractSimplex_class.

comment:79 in reply to: ↑ 77 Changed 3 years ago by cnassau

Replying to tscrim:

I am also against this idea of having a singleton parent for the set of all simplicies. It feels like overkill and I fear we could quickly find ourselves in a form of pointer hell (parent hell?) trying to keep everything organized.

The only question is whether AbstractSimplex should become an Element or not: if they are Elements, they need a Parent and a singleton is the best choice (the alternative is one parent per AbstractSimplex which is the pointer/parent hell that you mention).

comment:80 Changed 3 years ago by git

  • Commit changed from afbad4505350d79488d56067151c6f109782693a to 6e2128d11f18858110557fc2d0506799dd1f177c

Branch pushed to git repo; I updated commit sha1. New commits:

6e2128dsimplicial sets: add some documentation about uniqueness of simplices.

comment:81 Changed 3 years ago by git

  • Commit changed from 6e2128d11f18858110557fc2d0506799dd1f177c to 410d09f17b5a99ab40a70b29e25935785c59be52

Branch pushed to git repo; I updated commit sha1. New commits:

410d09fsimplicial sets: remove unique_tag from __classcall_private__ for

comment:82 Changed 3 years ago by git

  • Commit changed from 410d09f17b5a99ab40a70b29e25935785c59be52 to 4f2e6fd77b81e80a2f18267c294b0e15d5e5b427

Branch pushed to git repo; I updated commit sha1. New commits:

4f2e6fdsimplicial sets: improve the 'image' method for morphisms.

comment:83 Changed 3 years ago by jhpalmieri

Before the last push I was seeing sporadic failures when doctesting simplicial_set_morphism.py. After that change, I ran tests 20 times and had no failures, so I think it is fixed.

comment:84 Changed 3 years ago by git

  • Commit changed from 4f2e6fd77b81e80a2f18267c294b0e15d5e5b427 to 341ed16ec82b6bd074f6b55721315a8edfde334a

Branch pushed to git repo; I updated commit sha1. New commits:

341ed16simplicial sets: less verbose printing of simplices

comment:85 Changed 3 years ago by jhpalmieri

There are some issues with having a class inherit from both Element and UniqueRepresentation. If I get rid of UniqueRepresentation, I think I only lose pickling, and I'm not very concerned by that. So I'm thinking of replacing it with WithEqualityById for nondegenerate simplices.

comment:86 follow-up: Changed 3 years ago by jhpalmieri

I also just did a little testing, timing creation of some simplicial set. Using WithEqualityById is always at least as fast as using UniqueRepresentation, in some cases about 25% faster. To clarify, I am suggesting making the changes

  • src/sage/homology/simplicial_set.py

    diff --git a/src/sage/homology/simplicial_set.py b/src/sage/homology/simplicial_set.py
    index bc2c114..721ae42 100644
    a b lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') 
    285286########################################################################
    286287# The classes for simplices.
    287288
    288 class AbstractSimplex_class(UniqueRepresentation, SageObject):
     289class AbstractSimplex_class(SageObject):
    289290    """
    290291    A simplex of dimension ``dim``.
    291292
    class AbstractSimplex_class(UniqueRepresentation, SageObject): 
    892942        return simplex
    893943
    894944
    895 class NonDegenerateSimplex(AbstractSimplex_class):
    896     def __init__(self, dim, name=None, latex_name=None,
    897                  unique_tag=None):
     945class NonDegenerateSimplex(WithEqualityById, AbstractSimplex_class):
     946    def __init__(self, dim, name=None, latex_name=None):
    898947        """
    899948        A nondegenerate simplex.
    900949

and the necessary followups.

Last edited 3 years ago by jhpalmieri (previous) (diff)

comment:87 in reply to: ↑ 86 ; follow-up: Changed 3 years ago by cnassau

Replying to jhpalmieri:

I also just did a little testing, timing creation of some simplicial set. Using WithEqualityById is always at least as fast as using UniqueRepresentation, in some cases about 25% faster.

I think UniqueRepresentation should be reserved for Parents, so this change seems to go in the right direction.

comment:88 in reply to: ↑ 87 Changed 3 years ago by tscrim

Replying to cnassau:

Replying to jhpalmieri:

I also just did a little testing, timing creation of some simplicial set. Using WithEqualityById is always at least as fast as using UniqueRepresentation, in some cases about 25% faster.

I think UniqueRepresentation should be reserved for Parents, so this change seems to go in the right direction.

That is a fallacy. Just because it is used frequently there, does not mean it must only be used there. It is designed to work with objects that are uniquely defined by their inputs.

Now it is a hack, to perhaps a slight abuse, to use UniqueRepresentation for NonDegenerateSimplex pickling. However, good supporting support means it is much easier to pass between threads (maybe only processes?), where IIRC the pickling is used for that communication. Without good equality, then we cannot use UniqueRepresentation for SimplicialSet (I know we currently don't, but I'm just trying to say it imposes this limitation).

I think the root of the problem is the design of the parent and elements. I'm thinking we might want to lean towards doing something closer to what we have for SimplicialComplex. We define NonDegenerateSimplex (uniquely) by its set of vertices (or just by its dimension). The names of the (abstract) simplices could become an attribute of SimplicialSet (given at initialization of SimplicialSet). Perhaps in some ways I'm also looking to simplify the constructor, which the fact you must import things is a code smell. It just feels like things are a bit overly complex.

comment:89 Changed 3 years ago by jhpalmieri

Simplicial sets are intrinsically more complicated to define than simplicial complexes. I also don't understand what you're suggesting. Can you give a sample of how you would like to be able to define a simplicial set? Here is one model for a circle, with two vertices and two edges:

sage: from sage.homology.simplicial_set import AbstractSimplex
sage: v = AbstractSimplex(0)
sage: w = AbstractSimplex(0)
sage: e = AbstractSimplex(1)
sage: f = AbstractSimplex(1)
sage: SimplicialSet({e: (v,w), f: (v,w)})

How else might this look?

comment:90 Changed 3 years ago by tscrim

  • Milestone changed from sage-7.3 to sage-7.5

Something like

SimplicialSet({0: (0, 1), 1: (0, 1)})

where the keys are the names of the top-dimensional simplices and the tuples are the vertices. So an n-sphere with a latex name to the (top) simplex would look like:

SimplicialSet({0: (0,)*n}, latex_names={0: "\\mathbb{S}^{%s}"%n)

Internally, we still have classes for the abstract simplicies, but the equality/construction is checking the set of vertices, degenerices, and (latex) name.

Perhaps I'm oversimplifying things because I didn't see a complicated example.

There's also the question of do we want to allow mutable simplicial sets.

comment:91 Changed 3 years ago by jhpalmieri

In a simplicial set, simplices are not determined by their vertices, and not everything is determined by the top-dimensional faces. You can have an edge starting and ending at the same vertex, and then you can have a 2-simplex which has that edge for one of its faces. You can also have degenerate simplices, for example a vertex determines a degenerate 1-simplex. And then you can have a 2-simplex which has the same degenerate 1-simplex for all of its faces: this gives a 2-sphere, where you are collapsing the boundary of the 2-simplex to a single vertex.

Note also that you have to distinguish between a 2-simplex one of whose faces is a degenerate 1-simplex (imagine a triangle where you collapse one edge to a point) and a 2-simplex one of whose faces is a non-degenerate edge starting and ending at the same vertex (a triangle where you have glued two corners together).

For every non-degenerate simplex in a simplicial set, you have to specify each of its faces, and each of those faces might be non-degenerate or might be obtained by applying a sequence of degeneracies to a lower-dimensional non-degenerate simplex. So I think you need a good way to define simplices of various dimensions so that you can have a specification like

{sigma: (face_0, face_1, ..., face_n)}

as part of the dictionary defining a simplicial set.

comment:92 Changed 3 years ago by jhpalmieri

Also, I should point out that an n-simplex determines n+1 degenerate simplices one dimension higher, so if you have a 3-simplex sigma whose faces are obtained by applying degeneracies to 1-simplices, you can't just say sigma: (e,f,g) -- you have to say sigma: (s_0 e, s_0 f, s_1 g) or in Python syntax:

sigma: (e.apply_degeneracies(0), ...)

(or something along those lines).

Edit: and if it's a 3-simplex, it should have 4 faces, not 3.

Last edited 3 years ago by jhpalmieri (previous) (diff)

comment:93 Changed 3 years ago by git

  • Commit changed from 341ed16ec82b6bd074f6b55721315a8edfde334a to eabd3f84717b4c9b59268528321b604eb01e7947

Branch pushed to git repo; I updated commit sha1. New commits:

eabd3f8Simplicial sets: define _latex_ method more widely and use it in Nerve

comment:94 Changed 3 years ago by git

  • Commit changed from eabd3f84717b4c9b59268528321b604eb01e7947 to 603a3cf2b119ba8842be0bdc52b45b06bebbd9b0

Branch pushed to git repo; I updated commit sha1. New commits:

603a3cfSimplicial sets: use WithEqualityById instead of UniqueRepresentation

comment:95 Changed 3 years ago by jhpalmieri

I've checked again, and simplicial set construction is faster this way, so I've pushed the change.

I also tried to implement a Parent/Element? structure, but I couldn't get it to work. It was easy enough to create a single parent for all instances of AbstractSimplex_class, but then assigning a parent to each simplex when creating a simplicial set was causing difficulties. I tried creating a new class, inheriting from AbstractSimplex_class, for a simplex as an element of a specific simplicial set. The main point was to assign a parent to each instance, and otherwise use the methods from AbstractSimplex_class, but something was going wrong with equality (I think), so (for example) morphisms weren't working: the simplices that were supposed to be in the codomain were claimed to not be in the codomain. I spent quite a while with it, and now I'm giving up because I don't see enough benefit to making the change.

comment:96 Changed 3 years ago by git

  • Commit changed from 603a3cf2b119ba8842be0bdc52b45b06bebbd9b0 to e1ae3f24b2de53d401872c6b81c388280ca410c3

Branch pushed to git repo; I updated commit sha1. New commits:

e1ae3f2Merge branch 'develop' into t/20745/simplicial_sets

comment:97 Changed 3 years ago by jhpalmieri

Merged with 7.5.beta0, so I moved the references to the master bibliography file.

comment:98 Changed 3 years ago by git

  • Commit changed from e1ae3f24b2de53d401872c6b81c388280ca410c3 to 600157d4d7b2e5cfa736a4eb3e2c4fc235cee074

Branch pushed to git repo; I updated commit sha1. New commits:

600157dSimplicial sets: add _latex_ methods to the constructions.

comment:99 Changed 3 years ago by jhpalmieri

It seems that morphisms and homsets in Sage don't have default _latex_ methods. Odd.

comment:100 follow-up: Changed 3 years ago by jhpalmieri

Ping.

Changed 3 years ago by cnassau

comment:101 in reply to: ↑ 100 Changed 3 years ago by cnassau

Replying to jhpalmieri:

Ping.

Pong (and sorry for the prolonged delay)!

Part of me still thinks that it would be nice to use the Parent/Element? framework, but since I don't see any non-intrusive way to incorporate this myself I think it is the wise choice to just go ahead with the code as it is. This is a significant amount of valuable code, after all, and it would be a pity if its inclusion into Sage would be delayed much further.

I went over the code once more and noted a couple of very minor possible improvements; these are outlined in the file "suggestions.diff" that I have just attached. Please let me know what you think about those and let's hope to then close this ticket before christmas.

Cheers, Christian

comment:102 Changed 3 years ago by jhpalmieri

I like your suggested changes, so I've implemented them (here and there with minor rewordings). I also like using "base" much better than "X" for the simplicial set from which you build a cone or suspension, so I changed not only the public methods but also the code and the attribute self._X.

comment:103 Changed 3 years ago by git

  • Commit changed from 600157d4d7b2e5cfa736a4eb3e2c4fc235cee074 to 6f01bda365933115981e6008b8c0a61915719fac

Branch pushed to git repo; I updated commit sha1. New commits:

3147f18Merge branch 'develop' into t/20745/simplicial_sets
6f01bdatrac 20745: some rewordings of docstrings and comments,

comment:104 follow-up: Changed 3 years ago by cnassau

I think I stumbled over a little bug looking at products of the Point with itself. Using other simplicial sets seems to work:

sage: P=simplicial_sets.Point()
sage: P.product(P)
...
ValueError: the base point is not a simplex in this simplicial set
sage: Q = sage.homology.simplicial_set_constructions.ProductOfSimplicialSets((P,P,P)) ; Q
Point x Point x Point
sage: Q.all_n_simplices(2)
...
AttributeError: 'ProductOfSimplicialSets_with_category' object has no attribute '_simplices'
sage: Q.n_skeleton(3)
...
TypeError: unbound method n_skeleton() must be called with SimplicialSet_finite instance as first argument (got ProductOfSimplicialSets_with_category instance instead)

comment:105 Changed 3 years ago by git

  • Commit changed from 6f01bda365933115981e6008b8c0a61915719fac to 35790d29bdbe4a9b61b80076e4acc64eb650ea03

Branch pushed to git repo; I updated commit sha1. New commits:

35790d2trac 20745: fix bug with product of a point with itself

comment:106 in reply to: ↑ 104 Changed 3 years ago by jhpalmieri

Replying to cnassau:

I think I stumbled over a little bug looking at products of the Point with itself. Using other simplicial sets seems to work:

sage: P=simplicial_sets.Point()
sage: P.product(P)
...
ValueError: the base point is not a simplex in this simplicial set

I can fix this one: see the latest commit.

sage: Q = sage.homology.simplicial_set_constructions.ProductOfSimplicialSets((P,P,P)) ; Q
Point x Point x Point
sage: Q.all_n_simplices(2)
...
AttributeError: 'ProductOfSimplicialSets_with_category' object has no attribute '_simplices'
sage: Q.n_skeleton(3)
...
TypeError: unbound method n_skeleton() must be called with SimplicialSet_finite instance as first argument (got ProductOfSimplicialSets_with_category instance instead)

I don't know if this is a bug. The intended way to construct products is with the product method for simplicial sets. If you want to use a class constructor, you should use ProductOfSimplicialSets_finite if all of the arguments are finite. When I do that, I don't see any problems.

comment:107 Changed 3 years ago by cnassau

  • Reviewers set to Christian Nassau
  • Status changed from needs_review to positive_review

I doubt it makes sense to wait for patchbot results; the failures reported for the last two months ("Unable to read current working directory: No such file or directory") are clearly bogus (not enough space under /tmp maybe). I manually tested src/categories and src/homology and all looks fine. The documentation can also be built and looks nice. I'd say this ticket is ready to go...

comment:108 Changed 3 years ago by jhpalmieri

That's great! Thanks very much.

comment:109 Changed 2 years ago by vbraun

  • Branch changed from u/jhpalmieri/simplicial_sets to 35790d29bdbe4a9b61b80076e4acc64eb650ea03
  • Resolution set to fixed
  • Status changed from positive_review to closed

comment:110 Changed 2 years ago by fbissey

  • Commit 35790d29bdbe4a9b61b80076e4acc64eb650ea03 deleted

Another ticket which is using SAGE_SRC at runtime instead of something appropriate. You data is properly installed in SAGE_EXTCODE, use it. Follow up at #22220.

comment:111 Changed 2 years ago by jdemeyer

It would have been nice to lazily-import all of this. Because of this ticket, Sage now requires pyparsing at startup (see src/sage/homology/simplicial_set_examples.py).

Note: See TracTickets for help on using tickets.