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

Implement iteration over some polynomial rings #39399

Draft
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

user202729
Copy link
Contributor

@user202729 user202729 commented Jan 28, 2025

As in the title. Only the case of univariate polynomial ring over finite base ring is handled.

📝 Checklist

  • The title is concise and informative.
  • 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 and checked the documentation preview.

⌛ Dependencies

Copy link

github-actions bot commented Jan 28, 2025

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

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.

In addition, it would be good to add this to the category of InfiniteEnumeratedSets over finite fields.

src/sage/rings/polynomial/polynomial_ring.py Outdated Show resolved Hide resolved
@user202729
Copy link
Contributor Author

There's this thing in the code base

        def _test_enumerated_set_iter_cardinality(self, **options):
            """
            Check that the methods :meth:`.cardinality` and
            :meth:`.__iter__` are consistent.

            See also :class:`TestSuite`.

            For infinite enumerated sets:

            * :meth:`.cardinality` is supposed to return ``infinity``

            * :meth:`.list` is supposed to raise a :exc:`NotImplementedError`.

            EXAMPLES::

                sage: NN = InfiniteEnumeratedSets().example()
                sage: NN._test_enumerated_set_iter_cardinality()
            """
            tester = self._tester(**options)
            from sage.rings.infinity import infinity
            tester.assertEqual(self.cardinality(), infinity)
            tester.assertRaises(NotImplementedError, self.list)

I assume it means it is somehow intended that they should implement cardinality() themselves, and the method checks it is implemented correctly…?

@tscrim
Copy link
Collaborator

tscrim commented Feb 3, 2025

Well, okay, unless there are no generators (in which case it would obviously be in FiniteEnumeratedSets()).

@user202729
Copy link
Contributor Author

Ah, I misunderstood. You want to add PolynomialRing to InfiniteEnumeratedSets(), not add cardinality() to objects that derives from InfiniteEnumeratedSets.

@tscrim
Copy link
Collaborator

tscrim commented Feb 3, 2025

So nothing should derive from another class (in the OOP sense), but be an object in the appropriate category (in Sage's way of passing the category to Parent.__init__). If there are no generators and you set the category to be FiniteEnumeratedSets() and otherwise in InfiniteSets() or InfiniteEnumeratedSets(), then you would not need to implement cardinality() I believe. Although I have no strict objection to writing this method, it is redundant from the methods provided by the categories.

@tscrim
Copy link
Collaborator

tscrim commented Feb 3, 2025

By no generators, I was implicitly equating that with the base ring being the zero ring.

@user202729
Copy link
Contributor Author

user202729 commented Feb 3, 2025

Okay I understood what you mean.

(FiniteEnumeratedSets implements a naive function to compute cardinality)

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.

Thank you. If tests pass, then it can be changed to a positive review.

@user202729
Copy link
Contributor Author

Curiously enough list(—) tries to call __len__ if it's available:

In [16]: class A:
    ...: ^Idef __len__(self):
    ...: ^I^Iprint("len")
    ...: ^I^Ireturn 1
    ...: ^Idef __iter__(self):
    ...: ^I^Iprint("iter")
    ...: ^I^Iyield 5

In [17]: [*A()]
len
iter
Out[17]: [5]

In [18]: [*iter(A())]
iter
Out[18]: [5]

In [19]: class A:
    ...: ^Idef __iter__(self):
    ...: ^I^Iprint("iter")
    ...: ^I^Iyield 5

In [20]: [*A()]
iter
Out[20]: [5]

which implies… if __len__ is slow (as in our case of FiniteEnumeratedSets default implementation of cardinality), it may backfires (slower performance, need to iterate twice).

@tscrim
Copy link
Collaborator

tscrim commented Feb 3, 2025

Indeed, and things provided by the category can be slow as they are usually very general implementations. Of course, if you know the size, then you can just return that.

In this case, I see it as more pedagogical since nobody will should ask for the cardinality of a polynomial ring...(especially in a tight loop!) Thus, I would not implement it to reduce the maintenance burden.

@user202729
Copy link
Contributor Author

user202729 commented Feb 3, 2025

The different category leads to different overload of an_element being used, thus lead to downstream test failure. A convenient patch appear to be implementing an_element manually to return the generator (as in the old behavior).

@user202729
Copy link
Contributor Author

Thinking about it, it may be cleaner to implement this in the category of free modules with countable basis over finite/countable base ring, which will handle all of [univariate polynomial ring, multivariate polynomial ring, Laurent polynomial ring, etc.] at once.

Being more general leads to being slower though, let's see.

@tscrim
Copy link
Collaborator

tscrim commented Feb 12, 2025

Right now we don't have a category for modules with enumerated bases. I think you're right in doing the implementation at that level of generality, and likely there won't be any useful speed difference. Do you want to get that done or finalize this PR?

For the latter, your _an_element_ will need a doctest.

@user202729
Copy link
Contributor Author

user202729 commented Feb 12, 2025

  • actually module with basis suffices, then the category can provide an __iter__ that tries to __iter__ the basis (it is not necessary that the basis is enumerated, only that __iter__ is defined — enumerated means the ordering is canonical according to the documentation, I don't know what exactly that mean but ℤ is enumerated and ℚ is not)

  • there will be a little speed difference — there is no equivalent of linear_combination_of_basis method (or _element_constructor_(<list of monomials>)) as far as I know, so in order to generate a method you need to add up basis elements multiplies ring elements, which is around O(n^2) instead of O(n). Of course, unless linear_combination_of_basis ("in __iter__ order") is implemented.

    Though providing a linear_combination_of_basis isn't that hard.

Anyway… so I think I'd do the generalized route.

@user202729 user202729 marked this pull request as draft February 12, 2025 09:54
@tscrim
Copy link
Collaborator

tscrim commented Feb 12, 2025

Okay, sounds good.

Let me note that enumerated means that every element will be seen in finite time; that is, you have a bijection $\phi \colon \mathbb{N} \to X$. The choice of this map $\phi$ is the canonical part. $\mathbb{Q}_{\geq0}$ is enumerated by the diagonal slices and you can get the negatives from the usual alternating as in $\mathbb{Z}$.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants