Skip to content

Adds limit(expr, v, a) syntax #39812

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 9 commits into
base: develop
Choose a base branch
from

Conversation

EigenVector22
Copy link
Contributor

Implemented the limit(expr, variable, value) positional syntax to allow limits with respect to indexed variables or other variables not usable as keyword arguments.

Also, Updated the documentation and added doctests for the new syntax and associated error handling. Ensuring code coverage for the new argument parsing.

Fixes #38761 by allowing limits to be taken with respect to indexed variables like x[0] or other symbolic expressions not usable as keyword arguments.

While testing, the tests passed except the optional fricas ones that were failing before too.

📝 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

Got the idea and direction of fixing the issue and the link to the relevant conversations in the given PR: #38780

@EigenVector22
Copy link
Contributor Author

Hello, @vincentmacri, @nbruin,

can you please review this and
suggest if any thing else needs to be done,

Thanks,

@@ -429,13 +429,16 @@
from sage.misc.latex import latex
from sage.misc.parser import Parser, LookupNameMaker
from sage.structure.element import Expression
from sage.symbolic.expression import Expression
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this import? It shadows the one above. Figure out which one you need (why not the one that was already there?) and remove the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes you right, the second import just overwrites the 1st one, made the necessary changes
Thanks,

Copy link
Contributor

Choose a reason for hiding this comment

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

sage.structure.element.Expression and sage.symbolic.expression.Expression aren't the same. Do you have a reason to change which one is imported here? There was probably a good reason why the code here originally imported the abstract base class rather than the concrete one.

Copy link
Contributor

Choose a reason for hiding this comment

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

This still needs addressing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay, i guess i probably get this now, since the limit function is operating on symbolic expressions (converted via SR), so the abstract base class is probably sufficient, am i right?

@EigenVector22 EigenVector22 force-pushed the fix-limit-syntax-38761 branch from 28b5916 to f468ab1 Compare March 28, 2025 16:22
@vincentmacri
Copy link
Contributor

I'll leave commenting on the code itself to @nbruin since he wrote the first draft of this.

All I'll add is that I find the usage of "new syntax" versus "old syntax" in the docstrings a bit confusing. It makes it sounds like we plan to deprecate the old syntax, which I don't think we do plan on doing (or should plan on doing unless there's a good reason).

I'm not sure what terms would be better. Maybe just show examples of both without referring to one as being new or old, and just mention in the examples why the syntax you're adding here is preferred/needed in some situations. For example, when I added additional functionality to the subs method for function field elements I made sure to have examples of the different syntax you could use. Given the motivation of this PR being to be able to compute limits where the x=a syntax doesn't work, I would explicitly say in the example for the limit(expr, v, a) syntax when you might prefer to use it. (In retrospect I think the same issue with x=a that you're fixing here applied to what I did there for the subs method, I didn't mention it in the example for the dictionary syntax there because it didn't occur to me that you might have indexed variables or something, although the dictionary syntax should support it.)

@EigenVector22
Copy link
Contributor Author

Hello Vincent,

Thanks for your feedback on the documentation. I also think that labeling the syntaxes as "new" and "old" maybe could imply we are deprecating x=a, which isn’t the case—both syntaxes are meant to stay supported, as of now unless( as you said) ......

I just wanted to ask that what should be next course of action ?
Should I update the docstrings to like remove the "new" and "old" labels and instead present both syntaxes side-by-side with examples. I will TRY to clarify like when each one of them is appropriate to use

I am thinking to include something like this in the docstrings:
Use limit(expr, x=a) for straightforward variable substitutions, but opt for limit(expr, v, a) when v is an indexed variable like y[1], since keyword arguments can’t directly handle indexed names.

Is this the right way of representation?

Thanks,

@nbruin
Copy link
Contributor

nbruin commented Mar 29, 2025

I am thinking to include something like this in the docstrings: Use limit(expr, x=a) for straightforward variable substitutions, but opt for limit(expr, v, a) when v is an indexed variable like y[1], since keyword arguments can’t directly handle indexed names.

No, just be neutral:

There are two ways of invoking limit. One can write limit(expr, x=a, <keywords>) or limit(expr, x, a, <keywords>). In the first option, x must be a valid python identifier. Its string representation is used to create the corresponding symbolic variable with respect to which to take the limit. In the second option, x can simply be a symbolic variable. For symbolic variables that do not have a string representation that is a valid python identifier, for instance if x is an indexed symbolic variable, the second option is required.

@EigenVector22 EigenVector22 force-pushed the fix-limit-syntax-38761 branch 2 times, most recently from 05b3f07 to e806bf7 Compare March 29, 2025 09:30
@EigenVector22
Copy link
Contributor Author

I have made the necessary changes to the doctests and added examples where necessary, can you please review it.
Thanks,

@EigenVector22
Copy link
Contributor Author

@nbruin any other feedback regarding the code?

is_getitem = hasattr(v.operator(), '__name__') and v.operator().__name__ == '__getitem__'
if not (is_getitem and v.operands()):
# If it’s not an indexed variable, checking if it’s numeric
if v.is_numeric():
Copy link
Contributor

Choose a reason for hiding this comment

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

certainly not necessary to further differentiate if you know you're going to raise an error

if not (is_getitem and v.operands()):
# If it’s not an indexed variable, checking if it’s numeric
if v.is_numeric():
raise TypeError(f"Limit variable must be a variable, not a constant number: {v}")
Copy link
Contributor

Choose a reason for hiding this comment

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

Just raise a more generic message here. You don't need to give the value that is causing the error. That's in the backtrace.

try:
a = SR(a)
except TypeError:
raise TypeError(f"Cannot convert limit point to symbolic ring: {a}")
Copy link
Contributor

Choose a reason for hiding this comment

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

just let SR(a) raise the error.

@@ -1498,38 +1699,53 @@ def mma_free_limit(expression, v, a, dir=None):

EXAMPLES::
Copy link
Contributor

Choose a reason for hiding this comment

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

My expertise does not extend to mma_free_limit so I cannot review that code.

@EigenVector22 EigenVector22 force-pushed the fix-limit-syntax-38761 branch from e806bf7 to 40a04c1 Compare March 29, 2025 21:41
@EigenVector22
Copy link
Contributor Author

I've applied the suggested changes from the latest review.

  • Regarding the use of kwargv instead of argv: Initially, I intended to use kwargv since the documentation highlighted it as a
    standard practice. However, I later opted for argv, my bad, should my discussed this beforehand

  • I also removed some unnecessary, bloated lines of code from the PR. I've been working on this for 2–3 weeks, and the build
    broke a couple of times during that period. T_T

Everything has now been updated as suggested. Do you have any further feedback?

@EigenVector22 EigenVector22 requested a review from nbruin March 29, 2025 22:43
@EigenVector22
Copy link
Contributor Author

@vincentmacri any other feedback from your side ,?

Copy link
Contributor

@nbruin nbruin left a comment

Choose a reason for hiding this comment

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

Please make sure you address all concerns. I think I have reflagged issues that I flagged before and that did not receive a response.

If you disagree with my assessment on any one of the items you can respond with your reasons and then we can take it from there.

@@ -429,13 +429,16 @@
from sage.misc.latex import latex
from sage.misc.parser import Parser, LookupNameMaker
from sage.structure.element import Expression
from sage.symbolic.expression import Expression
Copy link
Contributor

Choose a reason for hiding this comment

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

This still needs addressing


if len(args) == 2: # Syntax: limit(ex, v, a, ...)
if kwargs: # Cannot mix positional v, a with keyword args
raise ValueError(f"Use either limit(expr, v, a, ...) or limit(expr, v=a, ...) syntax. "
Copy link
Contributor

Choose a reason for hiding this comment

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

Error message should be an uncapitalized phrase describing the problem; not advice. So:

'''
Cannot mix positional specification of limit variable and point with keyword variable arguments
'''

No need for arguments in message: those can be obtained from traceback.

I don't think advice of the form Use either ... or ... is necessary here. Any advice would go after the primary error message.

elif len(args) == 0: # Potential syntax: limit(ex, v=a, ...) or limit(ex)
if len(kwargs) == 1:
k, = kwargs.keys()
if not isinstance(k, str):
Copy link
Contributor

Choose a reason for hiding this comment

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

this test is not required:

sage: def f(*args,**kwargs): return args, kwargs
sage: f(**{10:1})
TypeError: keywords must be strings

k, = kwargs.keys()
if not isinstance(k, str):
raise ValueError(f"Invalid variable specification in keyword argument: {k} (must be a string)")
try:
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to guard with try. Just let error propagate.


# Ensuring v is a symbolic expression
if not isinstance(v, Expression):
try:
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to guard with "try". Just let it raise on error by itself

# Ensuring v is a symbolic expression
if not isinstance(v, Expression):
try:
v = SR(v)
Copy link
Contributor

Choose a reason for hiding this comment

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

create what you need: use

v=SR.symbol(v)

instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nbruin
This approach is creating an Error:

For the call

limit(x^2 + 5, 5, 10):

v = 5 is an integer, not an instance of Expression

SR.symbol(v) is called with v = 5, but SR.symbol() expects a string to create a symbolic variable. Passing integer like 5 causes a

TypeError: expected string or bytes-like object, got 'sage.rings.integer.Integer'.

I think this error is occuring because the code assumes v can be converted to a symbolic variable directly, but a constant like 5 isn’t a valid limit variable, limits must be taken with respect to a variable (e.g., x), not a constant,

its working fine with this approach:

# Ensuring v is a symbolic expression and a valid limit variable
if not isinstance(v, Expression):
    v = SR(v)
if not v.is_symbol():
    raise TypeError("limit variable must be a variable, not a constant")

v = var(k)
a = argv[k]
# Check if v is a valid limit variable
if not v.is_symbol() and v.is_constant():
Copy link
Contributor

Choose a reason for hiding this comment

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

you will have a symbol here with the change above, so no reason to check

@EigenVector22 EigenVector22 force-pushed the fix-limit-syntax-38761 branch from 40a04c1 to 8858361 Compare March 31, 2025 02:27
@EigenVector22
Copy link
Contributor Author

@nbruin does everything looks good now?

Copy link
Contributor

@nbruin nbruin left a comment

Choose a reason for hiding this comment

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

OK that looks fine now. I did find it painful in the review process to not have the comments and markings from the previous review available. Part of that might be github's interface. Another component is that you squash your commits in a forced push, destroying history. It might be better to just make successive commits. If you really want to squash before merge, you could squash at the very end to make a branch with a different (more concise) history but with the same effect on the tree.

@EigenVector22
Copy link
Contributor Author

@nbruin , thanks so much for reviewing the PR thoroughly and providing detailed feedback.
I have tried to incorporate your feedback into my recent PRs.

Regarding the comment on squashing commits with a forced push: I initially thought that working alone on the branch wouldn't be an issue, but I see how it complicates code review. I'll adjust my approach and make successive commits going ahead

Thanks again!

@EigenVector22
Copy link
Contributor Author

hello, @roed314 can you review and run the workflows if possible,
Thanks,

@roed314
Copy link
Contributor

roed314 commented Apr 1, 2025

Done. It still looks like there's something strange going on with force pushes (if you look at the Files Changed you'll see a bunch of changes that are just 10.6 vs 10.6.rc1).

Copy link

github-actions bot commented Apr 1, 2025

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

Comment on lines 1218 to 1220
The positional `limit(expr, v, a)` syntax is particularly useful when
the limit variable `v` is an indexed variable or another expression
that cannot be used as a keyword argument (fixes :issue:`38761`)::
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The positional `limit(expr, v, a)` syntax is particularly useful when
the limit variable `v` is an indexed variable or another expression
that cannot be used as a keyword argument (fixes :issue:`38761`)::
The positional ``limit(expr, v, a)`` syntax is particularly useful
when the limit variable ``v`` is an indexed variable or another
expression that cannot be used as a keyword argument
(fixes :issue:`38761`)::

When referring to code, typeset it with two ticks (code) rather than one tick (LaTeX).

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@vincentmacri vincentmacri left a comment

Choose a reason for hiding this comment

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

Minor formatting fix

return SR(l)
except TypeError:
return l

Copy link
Contributor

Choose a reason for hiding this comment

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

Why the "try" guard and the alternatives? Do you have any examples where l cannot be coerced into ex.parent() but it can be coerced into SR?
Do you have any examples where l cannot be coerced into SR but the return value is still interesting?

Note that raising an error is often better than returning anomalous output, so you should really only guard like this if you're positive that this leads to concretely better output.

In the original code, the return line was just

return ex.parent()(l)

and no issue was raised about that and the stated goal of this PR doesn't mention changing the way that limit returns its value either. I think you need a good reason to change how it returns its value.

Copy link
Contributor Author

@EigenVector22 EigenVector22 Apr 3, 2025

Choose a reason for hiding this comment

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

i think the i by mistake deleted the reply i posted to this yesterday,

basically, the added the second fallback after the SR(), was due to being speculative for FURTHER errors, so as a safety net I added l, but yes, on a comparative if we see, letting the error surface after SR() FAILS would be a better choice, at least we'll get some info about it,
made the changes

@EigenVector22 EigenVector22 requested a review from nbruin April 3, 2025 18:42
try:
return original_parent(l)
except (TypeError, ValueError):
return SR(l)
Copy link
Contributor

@nbruin nbruin Apr 3, 2025

Choose a reason for hiding this comment

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

A bit better already but why the try/except? Do you have an example where original_parent(l) fails and SR(l) succeeds? I don't think you gain anything with this complication of the original code.
I already asked you this in the previous review but you didn't provide an answer or removed the complication of the code.

Copy link
Contributor Author

@EigenVector22 EigenVector22 Apr 3, 2025

Choose a reason for hiding this comment

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

i think there is a very example for this, consider limit(f, x=0) where f(x) = sin(1/x) the result l=ind as x approaches 0, original_parent(l) will raise a TypError, except block catches this TypeError and executes SR(l), which is SR(ind), i think this example is sufficient to show the necessity of try and except, and i don't think they bring in complexity of any sorts, it equips us better to handle such cases,

Copy link
Contributor

Choose a reason for hiding this comment

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

Did you try? As far as I can see, ind is a completely valid value to be coerced into a "callable function ring":

sage: f(x)=sin(1/x)
sage: ind = limit(f(x),x=0); ind
ind
sage: parent(f)(ind)
x |--> ind

So I don't think the "except" clause will ever be triggered when return SR(l) would succeed.
(Whether the answer is a useful one is a different question, but that leads to design decisions that need to be considered separately)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, i just ran it locally, it does pass!, i was arguing on my wrong premise, apologies,

should i make the change and finally remove return SR(l)?,

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I don't think there is any reason to change that line from what it originally was.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cool, i'll revert to the original version

@EigenVector22
Copy link
Contributor Author

Made the FINAL changes

@EigenVector22
Copy link
Contributor Author

@roed314 if possible can you run the CIs for this PR?
Thanks,

@nbruin
Copy link
Contributor

nbruin commented Apr 4, 2025

Looks like the tests did run but failed. It doesn't look like the failure is connected to the changes made on this ticket. Hopefully this gets fixed soon (or otherwise clarified) so that this PR can be properly tested.

@EigenVector22
Copy link
Contributor Author

Yup, you are right, the failures aren't connected to any changes made on this ticket, these failures also came while testing locally, they are linked to fricas and giac packages,
Whom should i contact regarding these failures? if you could suggest,
Thanks

Copy link
Contributor

@vincentmacri vincentmacri left a comment

Choose a reason for hiding this comment

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

This should fix the failing linter check.

@EigenVector22
Copy link
Contributor Author

Committed the suggestions for the above changes @vincentmacri, could you also guide a bit about these fricas and giac failures

@vincentmacri
Copy link
Contributor

@vincentmacri, could you also guide a bit about these fricas and giac failures

Sorry, but I have no clue about that. I don't know too much about Sage's interaction with external libraries. Do those tests pass on your machine?

@EigenVector22
Copy link
Contributor Author

No, I tested them on my develop branch as well and they still failed there, on the issue branch these were actually the only tests that were still failing everything else had passed

@vincentmacri
Copy link
Contributor

@dimpase I've seen you do some work on maintaining the external libraries. Do you have any idea what might be happening here?

@dimpase
Copy link
Member

dimpase commented Apr 9, 2025

fricas failures might be due to a too old version of fricas installed on the box. We don't enforce it, but there is a ready to be merged #39796 doing this.

giac - no idea. giac is a backet case IMHO.

@nbruin
Copy link
Contributor

nbruin commented Apr 9, 2025

I think I have seen CI tests on other PRs succeeding recently when tests on this one were failing for fricas and giac. That would indicate that there might be something on this PR that is problematic. I don't recall seeing anything that pointed to a possible interaction, but I think we do need to make certain that any failures found on this PR are also happening on baseline. It's a little annoying that there only seems to be easy access to the latest CI results (which are waiting for approval at the moment). Seeing the history of success/failure of tests on this PR would be very helpful in assessing how likely it is that these problems are just due to infrastructure.

@dimpase
Copy link
Member

dimpase commented Apr 11, 2025

I think I have seen CI tests on other PRs succeeding recently when tests on this one were failing for fricas and giac. That would indicate that there might be something on this PR that is problematic. I don't recall seeing anything that pointed to a possible interaction, but I think we do need to make certain that any failures found on this PR are also happening on baseline. It's a little annoying that there only seems to be easy access to the latest CI results (which are waiting for approval at the moment). Seeing the history of success/failure of tests on this PR would be very helpful in assessing how likely it is that these problems are just due to infrastructure.

We are seeing warnings regarding os.fork() etc. use on Python 3.12+, related to fricas calls, which come from python/cpython#100229. See e.g. #39796

These are indicating potential problems with pexpect interfaces, I guess.

@EigenVector22
Copy link
Contributor Author

EigenVector22 commented Apr 12, 2025

These are indicating potential problems with pexpect interfaces, I guess.

just wanted to ask, what exactly are the role of the interfaces mentioned?, and what can we do to tackle this problem, thanks

@dimpase
Copy link
Member

dimpase commented Apr 12, 2025

pexpect interfaces, in src/sage/interfaces/, are for exchanging data with external executables, e.g. fricas, without the need to create temporary files.

@EigenVector22
Copy link
Contributor Author

Thanks

@EigenVector22
Copy link
Contributor Author

EigenVector22 commented Apr 20, 2025

Hello, what are the next steps to take to progress on this PR, anything that i have to do, or now does it relies only on the pexpect interfaces?
Thanks

Copy link
Member

@dimpase dimpase left a comment

Choose a reason for hiding this comment

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

does this trigger CI?

@EigenVector22
Copy link
Contributor Author

EigenVector22 commented Apr 20, 2025

nope,

@vincentmacri
Copy link
Contributor

@roed314 would you mind approving the workflow run again?

@dimpase
Copy link
Member

dimpase commented Apr 23, 2025

@roed314 - I am in the core team of sagemath (which has mostly people not active any more, by the way), why can't I approve CI runs?

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.

Unable to compute limit() over a variable coming from a list
5 participants