-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
BUG: integrate: make select_initial_step
aware of integration interval
#17348
Conversation
It is not clear to me if what you are dealing with here is an actual bug or an implementation choice, given that code execution does not raise. I can see that @nmayorov contributed most of the code you are modifying here, so he probably knows. In any case, why would not just changing the return statement in |
Hi @Patol75, in my view it is a clear bug, as there is no requirement that the function Thus the need for a more complex solution than you propose -- the point is to avoid ever calling |
That is a fair point indeed. The current implementation would require users to handle function calls outside of the expected domain in the case of exotic functions, which does not seem ideal. Nonetheless, I am still wondering if this is a known issue with regards to the implemented scheme, in which case applying a patch seems reasonable, or if there is a mistake in the implementation of the scheme. @mdhaber Do you mind sharing your opinion on these changes? |
Just to highlight: this is a fundamental issue for many physically-motivated problems, rather than just exotic/esoteric applications. Often one wishes to integrate a function that depends on the physical properties of some body, and these are (obviously!) not defined outside the boundary of that body. |
scipy/integrate/_ivp/common.py
Outdated
@@ -63,7 +63,7 @@ def norm(x): | |||
return np.linalg.norm(x) / x.size ** 0.5 | |||
|
|||
|
|||
def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): | |||
def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol, t_bound=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe t_bound
should not be optional, since this function is only used by solve_ivp
solvers which are all affected by the issue you want to resolve.
By the way, the python-wrapped Fortran solver LSODA os solve_ivp
also ensures that t_bound
is not exceeded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd tend to agree -- I just wasn't sure whether there were any external uses that would be broken by an interface change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Despite not having an underscore, select_initial_step
is not public, so if it doesn't break any uses of the function within SciPy, it is arguably OK to change this without a default. Are all the uses of select_initial_step
inside SciPy addressed here?
scipy/integrate/_ivp/common.py
Outdated
if h0 > (t_bound-t0)/direction: | ||
h0 = (t_bound-t0)/direction | ||
# Case where t0 = t_bound | ||
if h0 == 0: return 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if returning 0 will not affect the calling solver. Would it be best to simply raise an error, if that is not already done before ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle, yes -- but I think that would then necessitate refactoring some of the integration routines to handle tbound == t0
as a special case; at present they call select_initial_step
and do various other things before determining that they should return 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the current version works ok with the solvers as they stand -- the existing test-suite includes the case tbound==t0
.
I have just had another look at this PR. I believe there are two main issues highlighted here:
For the second item, it is true that For the first item,
We could change the above to
Within
This could change to
What would you think of such a change? If you do not believe it is a good idea, then any objections to the changes proposed by @valentineap? |
As a matter of general principle: it seems reasonable to handle the case where As for @Patol75's suggested fix to the |
scipy.integrate._ivp.common.select_initial_step
aware of integration interval
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind sharing your opinion on these changes?
Oops, I missed the ping.
Does this address gh-9198 and/or gh-8848? If so, I'd support such a change.
That said, I haven't taken a close look at how this works, and I'd need to ask some basic questions to facilitate review. LMK if you'd like me to take a closer look.
scipy/integrate/_ivp/common.py
Outdated
if h0 > (t_bound-t0)/direction: | ||
h0 = (t_bound-t0)/direction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the logic h = min(h, (t_bound-t0)/direction)
is used below. Consider using the same here idea here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
scipy/integrate/_ivp/common.py
Outdated
@@ -63,7 +63,7 @@ def norm(x): | |||
return np.linalg.norm(x) / x.size ** 0.5 | |||
|
|||
|
|||
def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol): | |||
def select_initial_step(fun, t0, y0, f0, direction, order, rtol, atol, t_bound=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Despite not having an underscore, select_initial_step
is not public, so if it doesn't break any uses of the function within SciPy, it is arguably OK to change this without a default. Are all the uses of select_initial_step
inside SciPy addressed here?
7059564
to
bde6bb7
Compare
Yes, the changes here would prevent any function evaluation outside the integration range specified by the user. It is relatively straightforward to do so, either in the way the PR proposes or the way my message above suggests. But, as I stressed in my first reply here, it is not necessarily obvious for such behaviour to be a bug — consider, for example, @WarrenWeckesser reply in gh-9198. |
I'll limit my reply to the original concern: "
@drhagen was involved in creating the solvers and addressed that in #9198 (comment), stating that it was a bug. After more carefully reading the code and related issues, I think it is something to address, whether it is called a bug or not, but I lean toward "bug" because it seems to have been inadvertent and does not seem to be a useful "feature". Looking at the original discussion (e.g. here), scipy/scipy/integrate/_ivp/rk.py Lines 138 to 139 in c1ed5ec
In any case, SUNDIALS apparently has an option to turn this behavior on and off. We don't, so something should change. If there is a demonstrable benefit to evaluating the integrand beyond (One could argue that only preventing function evaluations beyond |
I completely agree with @mdhaber. I think it is important to highlight that this issue (at least #17348; I haven't fully investigated the various other linked issues) is solely associated with the |
@valentineap Great. In that case, please consider:
|
@mdhaber OK, finally getting around to dealing with this.. This PR modifies
It will close #8848, #9198, and #17341. Example code from each of these issues is now included in the test suite: Enforcing I think this is ready to be considered for merge into main. |
Looks much cleaner now. What about a test for |
@nmayorov I had added I've fixed the |
Just didn't spot it quickly, excellent. |
Great -- so unless there's anything I've missed, I think that's all of @nmayorov's comments addressed. I don't think the 'failing' check( @tylerjereddy Perhaps this is now suitable for merge? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some very minor comments.
Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com>
@valentineap can you edit the description linking those issues too please |
@j-bowhay Done. |
To me everything looks good. |
I'll go ahead and merge it. I believe it's a great fix closing a lot of issues. True, there is always a risk of breaking something in numerical algorithms, but this change looks rather safe and makes the Thank you @valentineap for the work and insisting on finishing it. @tylerjereddy can be included in 0.14.0 then. |
Reference issue
Closes gh-17341
Closes gh-8848
Closes gh-9198
What does this implement/fix?
#17341 reports that
solve_ivp(func, (t0, t_bound), f0)
would evaluatefunc()
outside the range(t0, t_bound)
if|t_bound-t0|
is small.This issue was traced to
select_initial_step
, which was not aware of the far limit,t_bound
, and evaluatesfunc(t0+h0)
for some internally-determined value ofh0
.Fix:
t_bound
toselect_initial_step
and ensure that thath0<=|t_bound-t0|
_ivp
that useselect_initial_step
to take advantage of this argumentAdditional information
If
t0 == t_bound
,select_initial_step
will now return 0. This is a logical result but has potential for divide-by-zero issues.