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

Reuse pole detection/reintegration logic from heurisch_wrapper in meijerint #20270

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

ehren
Copy link
Contributor

@ehren ehren commented Oct 16, 2020

References to other Issues or PRs

Fixes #5949

Brief description of what is fixed or changed

Moves the code in heurisch_wrapper to a shared function, pole_reintegrate that can also be used by meijerint (via a new meijerint_indefinite_wrapper.

Other comments

reused heurisch_wrapper logic originally from #1622

Release Notes

  • integrals
    • meijerg=True option of integrate now returns a Piecewise handling cases where the indefinite integral returned by meijerint_indefinite could have a zero denominator. This affects any uses of the integrals module that rely on meijerint. This behavior can be disabled using the integrate option conds='none'.

…ntegrate (since we're already checking for poles in the denominator); this avoids unnecessary calls to denoms/solve.
…tion that can be shared by heurisch and meijerg
…of a nan by solve when handling a Piecewise (see also sympygh-20269).

Because we're calling solve on an equation derived from the integration
result anyway, it should be safe to ignore any special cases arrising from
poles in the denominator of this integration result.
@sympy-bot
Copy link

Hi, I am the SymPy bot (v161). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • integrals
    • meijerg=True option of integrate now returns a Piecewise handling cases where the indefinite integral returned by meijerint_indefinite could have a zero denominator. This affects any uses of the integrals module that rely on meijerint. This behavior can be disabled using the integrate option conds='none'. (#20270 by @ehren)

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.8.

Click here to see the pull request description that was parsed.
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->

Fixes #5949

#### Brief description of what is fixed or changed

Moves the code in `heurisch_wrapper` to a shared function, `pole_reintegrate` that can also be used by `meijerint` (via a new `meijerint_indefinite_wrapper`.

#### Other comments

reused heurisch_wrapper logic originally from #1622

#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->

* integrals
  * `meijerg=True` option of `integrate` now returns a `Piecewise` handling cases where the indefinite integral returned by `meijerint_indefinite` could have a zero denominator. This affects any uses of the `integrals` module that rely on `meijerint`. This behavior can be disabled using the `integrate` option `conds='none'`.

<!-- END RELEASE NOTES -->

@ehren
Copy link
Contributor Author

ehren commented Oct 16, 2020

Regarding the change to ode:

When solving this:

eq3 = (Eq(diff(x(t),t), y(t)*x(t)), Eq(diff(y(t),t), x(t)**3))

This integral comes up:

Integral(1/(u*sqrt(C1 + 6*u**3)), u).doit()

which after these changes results in

Piecewise((-2*asinh(sqrt(6)*sqrt(C1)/(6*u**(3/2)))/(3*sqrt(C1)), Ne(C1, 0)), (-sqrt(6)/(9*sqrt(u**3)), True))

Then calling solve on an equation derived from this:

solve(-C2 - t - 3*Piecewise((-2*asinh(sqrt(6)*sqrt(C1)/(6*u**(3/2)))/(3*sqrt(C1)), Ne(C1, 0)), (-sqrt(6)/(9*sqrt(u**3)), True)))

results in the solution to the ode involving nan

[Piecewise((6**(2/3)/(6*(sinh(sqrt(C1)*(C2 + t)/2)/sqrt(C1))**(2/3)), Ne(C1, 0)), (nan, True)), Piecewise((2**(1/3)*3**(2/3)*((C2 + t)**(-2))**(1/3)/3, Eq(C1, 0)), (nan, True)), Piecewise((-2**(1/3)*3**(2/3)*((C2 + t)**(-2))**(1/3)/6 - 2**(1/3)*3**(1/6)*I*((C2 + t)**(-2))**(1/3)/2, Eq(C1, 0)), (nan, True)), Piecewise((-2**(1/3)*3**(2/3)*((C2 + t)**(-2))**(1/3)/6 + 2**(1/3)*3**(1/6)*I*((C2 + t)**(-2))**(1/3)/2, Eq(C1, 0)), (nan, True))]

I considered just changing the definition of the constant to C1 = Symbol("C1", zero=False) but this could rule out valid trivial solutions. I think because the result is then used in an equation passed to solve ignoring the Piecewise solution of integrate is perfectly safe.

@asmeurer
Copy link
Member

I don't think it's a good idea to put assumptions on the constants introduced by the ode solver. That would be very opaque to end-users.

@asmeurer
Copy link
Member

Should we be using this wrapper in manualintegrate too?

@jksuom
Copy link
Member

jksuom commented Oct 16, 2020

How about implementing a wrapper like this in the main code? Different integrators could be used in special cases.

>>> from sympy import sin
>>> from sympy.abc import x, n, k
>>> meijerint_indefinite_wrapper(sin(x**(n*k + 1)), x)
Piecewise((x*x**(k*n)*x**(k*n/(k*n + 1))*x**(1/(k*n + 1))*gamma(1/2 + 1/(2*(k*n + 1)))*hyper((1/2 + 1/(2*(k*n + 1)),), (3/2, 3/2 + 1/(2*(k*n + 1))), -x**2*x**(2*k*n)/4)/(2*k*n*gamma(3/2 + 1/(2*(k*n + 1))) + 2*gamma(3/2 + 1/(2*(k*n + 1)))), Ne(k, -1/n)), (x*sin(1), True))
Copy link
Member

Choose a reason for hiding this comment

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

I believe you can line wrap this and the doctest will still pass.


def reintegrator(f, x):
expr = heurisch(f, x, rewrite, hints, mappings, retries, degree_offset,
unnecessary_permutations, _try_heurisch)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure why any of the integration algorithms should reuse only that algorithm for the recursive integration, rather than integrate(). Does anything break for heurisch if we change this? Heurisch in particular is the slowest algorithm, which is always tried last, so it's better to avoid using it if we can. It's also unclear to me that all these arguments to heurisch should be the same for the new substituted integrand.

The only place where it might make sense to keep the same algorithm would be Risch, if we can be sure the "simplified" integral is also of the form that Risch can handle (most likely this is the case, though I'd have to look at the specific places where the divisions happen to be sure). Even then, though, Risch has many NotImplementedError holes, so it still might not make sense there.



def pole_reintegrate(f, x, antideriv,
reintegrator=lambda f, x: integrate(f, x, conds="none")):
Copy link
Member

Choose a reason for hiding this comment

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

Another concern is that if someone passes a specific algorithm to integrate, like integrate(meijerg=True), should the reintegrator also only use that algorithm? I'm unsure. It does seem to make sense for the flag to be passed through, but it could lead to the confusing situation where integrate(f, x) is computed with, say, meijerg, but integrate(f, x, meijerg=True) doesn't produce the same result, because meijerg isn't what is actually used by the default reintegrator.

Takes an integrand f with integration variable x with an initially
computed antiderivative and checks for poles in the denominator. For each
of these poles, the integral is reevaluated, and the final integration
result is given in terms of a Piecewise.
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain the arguments here, or give an example?

@asmeurer
Copy link
Member

I'm not sure if you already explained this, but what is the advantage of having a function that checks for denominators rather than hooking into the specific place(s) in the integration code where a division happens? It seems to me that the latter would be preferred if it is possible, as it would avoid unnecessarily splitting out cases where a constant also appears in the denominator of the integrand. I see that the helper function here tries to handle those cases, but it still requires extra work to detect them (if I am reading the code correctly, there is a solve() call for every denominator).

slns0 = []
for d in denoms(f):
try:
slns0 += solve(d, dict=True, exclude=(x,))
Copy link
Member

Choose a reason for hiding this comment

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

Why do we have to solve here too? Can't we filter out the common denominators from f without calling solve? That would save a lot of unnecessary work if f has a lot of complicated symbolic constant expressions in denominators, which are unrelated to the integration variable. It's also possible for such expressions to not be solvable, in which case they would be filtered if I am reading this code correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comes from #2112 but we can just check if d.subs(already_computed_solution) == 0 but still looking at optimizations. Have a few changes coming regarding the comments.

@ehren
Copy link
Contributor Author

ehren commented Oct 20, 2020

How about implementing a wrapper like this in the main code? Different integrators could be used in special cases.

Should we be using this wrapper in manualintegrate too?

I experimented with wrapping the entire _eval_integral but found this is unnecessary with manualintegrate. I seem to have a fix for meijerg that follows similar logic to manualintegrate and doesn't require solve or denoms etc. Still have a few lingering test cases to fix but will send an update.

@czgdp1807
Copy link
Member

@ehren Some tests seem to fail. Would you like to debug these? Please let us know if you are still working on it. Thanks for your contributions.

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.

integration of (x**(n-1))*sqrt(1+x**n)
6 participants