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

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

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

Conversation

EigenVector22
Copy link

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
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
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
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
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
Author

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

@EigenVector22
Copy link
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
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
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
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

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

Also, Updates documentation and adds doctests for the new syntax
and associated error handling. Ensures code coverage for
the new argument parsing.

Fixes sagemath#38761
@EigenVector22 EigenVector22 force-pushed the fix-limit-syntax-38761 branch from 40a04c1 to 8858361 Compare March 31, 2025 02:27
@EigenVector22
Copy link
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.

if not isinstance(v, Expression):
v = SR(v)
if not v.is_symbol():
raise TypeError("limit variable must be a variable, not a constant")
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that should be a satisfactory solution

Copy link
Author

Choose a reason for hiding this comment

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

cool

@EigenVector22
Copy link
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!

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
3 participants