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

BUG: Fix ellipj when m is near 1.0 (#8480) #8548

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

FormerPhysicist
Copy link
Contributor

This pull request fixes ellipj when m is near 1.0. Previously, ellipj was using equations 16.15.1-16.15.4 from Abramowitz and Stegun's Handbook of Mathematical Functions to calculate sn, cn, dn, and phi when m was near 1.0.

However, these formulas were only valid when phi < pi/2 (or equivalently when u<K, where K=ellipk(m) is the quarter-period) and so ellipj failed when u>K.

To fix this issue, this pull request will instead use formula 16.15.4 to calculate the "principal" part of the amplitude phi, or the remainder when dphi=phi/(pi/2). The total phi will be set to dphi+n*pi/2 where n=floor(u/K) (basically the number of quarter periods in u) and this total phi will be used to correctly compute sn=sin(phi) and cn=cos(phi). With this fix, ellipj will produce the right values for all u, when m is very near 1.0.

Closes #8480

Copy link
Member

@larsoner larsoner left a comment

Choose a reason for hiding this comment

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

Changes look reasonable to me, I've found Abramowitz and Stegun useful in the past, and the tests appear to validate the functionality. But it would be better if someone with more knowledge in this domain could look!

except when `m` is within 1e-9 of 0 or 1.

In the latter case, when m is near 1, the functions `sn`, `cn`, and `dn`
are found using an approximation for `phi` given by 16.15.4 in [2].
Copy link
Member

Choose a reason for hiding this comment

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

[2]_ to make it link properly.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@larsoner fixed

are found using an approximation for `phi` given by 16.15.4 in [2].
As this approximation is only valid when `phi < pi/2` (i.e.,
when `u<K`), the computation only uses 16.15.4 to find the "principal"
part of `phi` or the remainder `dphi=phi%(pi/2)`. The returned value
Copy link
Member

Choose a reason for hiding this comment

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

double-backticks on code for it to render properly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which parts? Do you just mean the dphi=phi%(pi/2) part? Or everywhere I've used one set of backticks (e.g., should phi just be italicized (one pair of ) or as code (two pairs of )?)

/* Evaluate the complete elliptic integral using the
1-m<<1 approximation in ellpk.c */
K = ellpk(1.0-m);
uabs=fabs(u);
Copy link
Member

Choose a reason for hiding this comment

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

indentation off by one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed


FuncData(dn, dataset, (0, 1), 2, rtol=1e-10).check()
# Add the points that will use the m near 1 method
m = 0.99999999999
Copy link
Member

Choose a reason for hiding this comment

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

it's probably worth going a step further. In addition to the one right at the threshold (0.9999999999), how about one just outside it (0.9999999998) and beyond it (0.99999999999)? It helps validate the choice of threshold.

Copy link
Member

Choose a reason for hiding this comment

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

Is it better to write 1. - 1e-10 for readability of the digits?

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@ilayn
Copy link
Member

ilayn commented Mar 13, 2018

Other than the PEP8 whitespace after comma issues in test_mpmath.py also looks good to me. But indeed a domain expert look would be better for the contents.

@rgommers rgommers added scipy.special maintenance Items related to regular maintenance tasks labels Mar 17, 2018
@person142
Copy link
Member

Before I review this in earnest:

  • The tab -> spaces changes in ellpj.c are reasonable, but better done in a separate PR. That can get merged quickly and then the code changes here will be clearer.
  • Please remove the unrelated whitespace changes in test_mpmath.py

@FormerPhysicist
Copy link
Contributor Author

@person142 Done

@person142
Copy link
Member

Can you comment on your general approach versus e.g. just doing what Boost does (reading their source code is fine):

http://www.boost.org/doc/libs/1_52_0/libs/math/doc/sf_and_dist/html/math_toolkit/special/jacobi/jacobi_elliptic.html

In general argument reduction by irrational numbers (here pi/2) is inaccurate and should be avoided if possible.

In the latter case, when m is near 1, the functions `sn`, `cn`, and `dn`
are found using an approximation for `phi` given by 16.15.4 in [2]_.
As this approximation is only valid when `phi < pi/2` (i.e.,
when `u<K`), the computation only uses 16.15.4 to find the "principal"
Copy link
Member

Choose a reason for hiding this comment

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

ReST weirdness: single backticks are for references, double backticks for code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you elaborate what you mean by 'reference'? Looking at the other function docstrings, it appears that they use a single backtick for variables and math equations (sometimes with the ReST role(?) keyword :math:). Should I change the existing text for ellpj to use double backticks?

@rgommers rgommers added the needs-work Items that are pending response from the author label Jun 10, 2018
@FormerPhysicist
Copy link
Contributor Author

I've finally gotten around to looking at the source code for boost version 1.67 implementation of ellpj and there appear to be two main differences with scipy's current implementation:

  1. They handle the m>1 case using equations 16.11 from Abramowitz. Currently, scipy doesn't attempt to calculate ellpj at all when m>1 and just returns NAN.

  2. They don't provide any special handling when m is close to unity. They just continue to use the arithmetic-geometric mean algorithm* (AGM). Scipy on the other hand tries to use the approximations in equations 16.15 of Abramowitz which are valid for m close to 1 and small u. My pull request would fix our use of these approximations so that u can be large.

However, what's also interesting, is that boost had a section that attempted to treat the m close to unity case, but it's been commented out with the comments:

/* Can't get this to work to adequate precision - disabled for now...
//
// Asymptotic forms from A&S 16.15:

Skimming their code, it looks like they were trying the same naive implementation of 16.15 that scipy does and I wonder if they were getting the same error that my pull request addresses.

So the TL;DR: either we stick with AGM for m close to unity, or we can use the modified version of 16.15 proposed by vasilevgmaslov

(*Note that AGM seems to be a standard way of getting ellpj for m<1; scipy uses it when m is < 1 and not close to unity.)

@tylerjereddy tylerjereddy added this to the 1.5.0 milestone Jan 13, 2020
@tylerjereddy
Copy link
Contributor

@ilayn @person142 @larsoner Any realistic chance this is ready and merged in ~1 week for branching? No reviewer activity in almost two years.

@person142 person142 removed this from the 1.5.0 milestone May 20, 2020
@person142
Copy link
Member

@tylerjereddy I removed the milestone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance Items related to regular maintenance tasks needs-work Items that are pending response from the author scipy.special
Projects
None yet
Development

Successfully merging this pull request may close these issues.

scipy.special.ellipj does not work correctly at k close to 1
7 participants