Skip to content

Lazy species #38544

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

Open
wants to merge 119 commits into
base: develop
Choose a base branch
from
Open

Lazy species #38544

wants to merge 119 commits into from

Conversation

mantepse
Copy link
Contributor

@mantepse mantepse commented Aug 21, 2024

We provide an implementation of combinatorial species based on the lazy series framework and #38446.

dependencies: #38974

@mantepse mantepse added c: combinatorics gsoc: 2024 Tag for GSoC2024 issues/PRs and removed s: needs review labels Aug 21, 2024
@mantepse mantepse marked this pull request as draft August 21, 2024 14:30
Copy link

github-actions bot commented Aug 21, 2024

Documentation preview for this PR (built with commit ebddbd4; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

@tscrim
Copy link
Collaborator

tscrim commented Jun 5, 2025

A few small things.

Thank you!

I do really think putting the specific species for the univariate case as methods onto the parent class is much better: no repetition of documentation, it directly gives you the corresponding class (which makes it easier to use ?), and it’s a fairly natural way to handle the simple redirection. Yet if you still remain unconvinced, we can leave it the way it currently is.

I'm confused now - I think I followed your idea of separating univariate and multivariate species (#38544 (comment)) and creating proper methods for each. Apparently I misunderstood you.

No, you understood me correctly, I just changed my mind with what might be the best practice. (I"m not 100% convinced yet either though.) Sorry for the confusion.

So what I am thinking is your original idea is best with one small modification (from this comment):

def __init__(self,...):
    if self._arity == 1:
        from types import MethodType
        self.Sets = MethodType(SetSpecies, LazySpeciesUnivariate)
        self.Graphs = MethodType(GraphSpecies, LazySpeciesUnivariate)
        self.SetPartitions = MethodType(SetPartitionSpecies, LazySpeciesUnivariate)
        self.Cycles = MethodType(CycleSpecies, LazySpeciesUnivariate)

This way it isn't a lambda function, but has the full (class-level) docstring. The downside is that they won't be included in the doc. The other variation is keeping both classes, but having the univariate as

class LazySpeciesUnivariate(LazySpecies):
    """
    Univariate lazy species.
    """

from types import MethodType
LazySpeciesUnivariate.Sets = MethodType(SetSpecies, LazySpeciesUnivariate)
LazySpeciesUnivariate.Graphs = MethodType(GraphSpecies, LazySpeciesUnivariate)
LazySpeciesUnivariate.SetPartitions = MethodType(SetPartitionSpecies, LazySpeciesUnivariate)
LazySpeciesUnivariate.Cycles = MethodType(CycleSpecies, LazySpeciesUnivariate)

(Python doesn't seem to have a good mechanism in Python for converting classes into methods like how I am doing.)

@mantepse
Copy link
Contributor Author

mantepse commented Jun 5, 2025

Indeed, it seems that AbstractSetPartition._repr_ behaves badly:

sage -t --warn-long 5.0 --random-seed=89871645352471344146790937796251140637 src/sage/rings/lazy_species.py
**********************************************************************
Error: Failed example:: Got: [{{'a', x}, {1}},
 {{'a'}, {1}, {x}},
 {{1, 'a', x}},
 {{1, 'a'}, {x}},
 {{x, 1}, {'a'}}]
                                                                                                                                                                                                                                                                                                                                                                                          
    sorted(G.structures(["a", 1, x]), key=str)
Expected:
    [{{'a', x}, {1}},
     {{'a'}, {1}, {x}},
     {{1, 'a', x}},
     {{1, 'a'}, {x}},
     {{1, x}, {'a'}}]
Got:
    [{{'a', x}, {1}},
     {{'a'}, {1}, {x}},
     {{1, 'a', x}},
     {{1, 'a'}, {x}},
     {{x, 1}, {'a'}}]
**********************************************************************
1 item had failures:
   1 of   5 in sage.rings.lazy_species.LazySpeciesUnivariate.SetPartitions
    [290 tests, 1 failure, 15.54s wall]

Oh yes. The code is

    def _repr_(self):
...
        try:
            s = [sorted(x) for x in self]
        except TypeError:
            s = [sorted(x, key=str) for x in self]
        return '{' + ', '.join('{' + repr(x)[1:-1] + '}' for x in s) + '}'

and it's a certain M. Rubey who is to blame. #38974. Embarassing.

@mantepse
Copy link
Contributor Author

mantepse commented Jun 5, 2025

    def _repr_(self):
...
        try:
            s = [sorted(x) for x in self]
        except TypeError:
            s = [sorted(x, key=str) for x in self]
        return '{' + ', '.join('{' + repr(x)[1:-1] + '}' for x in s) + '}'

I think the problem is that, for the set partition $\{\{1, x\}, \{"a"\}\}$, the order of 1 and x is random, because they are coerced into the symbolic ring.

@mantepse
Copy link
Contributor Author

mantepse commented Jun 8, 2025

So what I am thinking is your original idea is best with one small modification [...]

I tried the second variant, but it actually doesn't work the way I did it. Apparently, MethodType passes the class rather than the instance of the class.

However, it seems to me that the current approach (i.e., separate classes for univariate and multivariate species and providing proper methods defined in the class) makes sense. I think that we really want to see the methods (Sets, Graphs, ...) in the documentation, and it seems that the pattern of not having a class docstring for SetSpecies and using the SetSpecies.__init__ docstring just for running the testsuite is easy to understand and extend.

@mantepse
Copy link
Contributor Author

Ping?

@tscrim
Copy link
Collaborator

tscrim commented Jun 11, 2025

So what I am thinking is your original idea is best with one small modification [...]

I tried the second variant, but it actually doesn't work the way I did it. Apparently, MethodType passes the class rather than the instance of the class.

Ah, right, it would pass the class rather than the instance. I think I messed up my initial experiment. However, this does work:

sage: class Foo():
....:     def __init__(self, B):
....:         print(B)
sage: class Bar():
....:     def __init__(self, v):
....:         self.v = v
....:         from types import MethodType
....:         self.F = MethodType(Foo, self)
....:     def __repr__(self):
....:         return "Bar: %s"  % self.v
sage: B = Bar(5)
sage: B.F()
Bar: 5
<__main__.Foo object at 0x723f4645e660>

However, it seems to me that the current approach (i.e., separate classes for univariate and multivariate species and providing proper methods defined in the class) makes sense. I think that we really want to see the methods (Sets, Graphs, ...) in the documentation, and it seems that the pattern of not having a class docstring for SetSpecies and using the SetSpecies.__init__ docstring just for running the testsuite is easy to understand and extend.

Yes, that is the downside with my MethodTypes approach. Anyways, let's go with the current version then.

@mantepse
Copy link
Contributor Author

Does there remain anything that I should do or do you just want to have a closer look?

The next (substantial) improvements are #40129, #40163 and #40186. Arithmetic product is easy and in the pipeline. After that, we have truly superseeded the old species framework.

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.

One doc thing and one general question (that I probably should have asked much earlier, sorry).

Should we call these Lazy(Combinatorial)Species and not just (Combinatorial)Species? Having Lazy in front suggests there is or will be a finite precision/non-lazy version, but I don't think this will happen.

Once addressed, it will be a positive review.

@@ -407,3 +407,4 @@ Comprehensive Module List
sage/combinat/words/words
sage/combinat/yang_baxter_graph
sage/rings/cfinite_sequence
sage/rings/lazy_species
Copy link
Collaborator

Choose a reason for hiding this comment

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

We probably should put this with the L's rather than at the bottom. Also, would there be some other natural place to link this doc? It's hard to find just being in the big combinat module list.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you mean, move it between k_tableau and lr_tableau?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, that’s correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I move cfinite_sequence, too, or in a different PR?

Copy link
Collaborator

Choose a reason for hiding this comment

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

While I would normally say it’s better to move it in a different PR, we can just get it done here

@mantepse
Copy link
Contributor Author

One doc thing and one general question (that I probably should have asked much earlier, sorry).

No problem! I'm amazed and it really makes me happy that you do thorough reviews!

Should we call these Lazy(Combinatorial)Species and not just (Combinatorial)Species? Having Lazy in front suggests there is or will be a finite precision/non-lazy version, but I don't think this will happen.

I put the Lazy in front because of PolynomialSpecies, which is not a very good name, and because of uniformity and discoverability. The latter, because I would think that to many formal power series guys species won't occur as a possibly better tool, even when it is.

I agree that a finite precision version won't happen.

We could have CombinatorialSpecies for what's now PolynomialSpecies and LazyCombinatorialSpecies instead of LazySpecies. (By coincidence, LazyC + tab is currently unused, whereas we have LazySy + tab and LazySp + tab. I'd support that, but I won't advocate for it.

The reason for not advocating: AtomicSpecies and MolecularSpecies were useful to me frequently (mostly to generate all of a given grade), whereas PolynomialSpecies are only a unwanted necessity. PolynomialSpecies occur when extracting a term of a LazySpecies and to construct LazySpecies by passing a coefficient function.

@tscrim
Copy link
Collaborator

tscrim commented Jun 12, 2025

I don’t think the lazy in from makes it very discoverable compared to the API we have for similar such objects (mainly completion aka formal_series_ring). It would only be if someone is already working with the other lazy series that they would find it, but they wouldn’t know what it is and ignore it. I think CombinatorialSpecies would be the ideal name with my current understanding. PolynomialSpecies will just remain as it is, which I feel fits well enough with the power series vs polynomials differences. Putting it another way, I don’t think it should be defined by its laziness (in contrast to FPS with finite precision where we have different implementations).

@mantepse
Copy link
Contributor Author

I think CombinatorialSpecies would be the ideal name with my current understanding. PolynomialSpecies will just remain as it is, which I feel fits well enough with the power series vs polynomials differences. Putting it another way, I don’t think it should be defined by its laziness (in contrast to FPS with finite precision where we have different implementations).

Just a brief update: I am not sure. Essentially, that does mean that we should make LazyPowerSeriesRing an implementation of PowerSeriesRing.

Another thing I just noticed is that CombinatorialSpecies(min=None) is currently in the global namespace: it designates a currently undefined species, with valuation min. Not sure how to do the deprecation here.

I guess we could first leave things as they are (perhaps rename LazySpecies to LazyCombinatorialSpecies) and after the deprecation period drop Lazy.

@tscrim
Copy link
Collaborator

tscrim commented Jun 14, 2025

I think CombinatorialSpecies would be the ideal name with my current understanding. PolynomialSpecies will just remain as it is, which I feel fits well enough with the power series vs polynomials differences. Putting it another way, I don’t think it should be defined by its laziness (in contrast to FPS with finite precision where we have different implementations).

Just a brief update: I am not sure. Essentially, that does mean that we should make LazyPowerSeriesRing an implementation of PowerSeriesRing.

In the sense of a common abstract class and creation (with the default precision being passed as oo), then I would not be opposed. However, because there is both a finite precision and lazy version, it makes some sense to have them be separate. That being said, I do think it would be better to have more common API and base classes, but that should be done separately.

Another thing I just noticed is that CombinatorialSpecies(min=None) is currently in the global namespace: it designates a currently undefined species, with valuation min. Not sure how to do the deprecation here.

I guess we could first leave things as they are (perhaps rename LazySpecies to LazyCombinatorialSpecies) and after the deprecation period drop Lazy.

Hmm….that’s really annoying. We can’t really just do a drop-in replacement as it has different API, right? We could do a 2 step deprecation: the CombinatorialSpecies and then Lazy[Combinatorial]Species after removing Lazy from it. Yet that’s a bit of work. There might not be a good option here. :/

@mantepse
Copy link
Contributor Author

I would like to move on here. Meanwhile, I have also implemented the arithmetic product. I agreed to give an online course on enumeration with sagemath at 128.5, but I guess it is too late to have this even in develop.

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, we can merge this now. I would like to deprecate LazyCombinatorialSpecies once we have the full replacement for the old version ready, but we can address that at that time.

@mantepse
Copy link
Contributor Author

Thank you!

vbraun pushed a commit to vbraun/sage that referenced this pull request Jun 18, 2025
sagemathgh-38544: Lazy species
    
We provide an implementation of combinatorial species based on the lazy
series framework and sagemath#38446.

dependencies: sagemath#38974
    
URL: sagemath#38544
Reported by: Martin Rubey
Reviewer(s): Martin Rubey, Travis Scrimshaw
vbraun pushed a commit to vbraun/sage that referenced this pull request Jun 21, 2025
sagemathgh-38544: Lazy species
    
We provide an implementation of combinatorial species based on the lazy
series framework and sagemath#38446.

dependencies: sagemath#38974
    
URL: sagemath#38544
Reported by: Martin Rubey
Reviewer(s): Martin Rubey, Travis Scrimshaw
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.

3 participants