Skip to content

Updated Mathieu function implementation#99

Open
brorson wants to merge 44 commits into
scipy:mainfrom
brorson:Brorson-Mathieu-impl
Open

Updated Mathieu function implementation#99
brorson wants to merge 44 commits into
scipy:mainfrom
brorson:Brorson-Mathieu-impl

Conversation

@brorson

@brorson brorson commented Feb 23, 2026

Copy link
Copy Markdown

I am back. I withdrew my last pull request for my updated Mathieu function impl since I decided to polish the implementation further. Now I am back after making improvements to the impl and fixing some bugs. I received some help from Ho-Chul Shin, whose paper on computing the Mathieu functions appeared in the SIAM Journal in Dec 2025.

Besides improving my implementation I wrote a paper on the details of my implementation. I realize it's difficult to accept a big blob of code implementing a difficult special function without understanding what's in the code. I attempt to explain my implementation in this paper:

https://github.com/brorson/ScipyMathieuPaper

I maintain both Latex and .pdf copies on that GitHub page.

As part of my impl I subjected my functions to some tests to validate their correctness. Results of testing are presented in that paper.

Please reach out to me with any questions -- I am eager to help get this into the SciPy/xsf trunk.

Stuart Brorson
Northeatern University
Boston, Mass, USA

main.c to test_mathieu.cpp in the tests dir.  I ran clang-format on all
.h files.
and lapack which are requred for Mathieu fcns.
Also fix the same build problem when using pixi.  Also rerun clang-format
on all files.
@brorson

brorson commented Feb 27, 2026

Copy link
Copy Markdown
Author

I figured I'd pop in here again and show some test results. All tests are on GitHub under https://github.com/brorson/ScipyMathieuTesting You can use them to check my impl if desired.

Meanwhile here is the eigenvalue spectrum of the existing SciPy Mathieu implementation and my new one:

image image
Existing impl New impl

Here are the Wronskian errors RMS(W(Mx1, Mx2) - 2/pi) for Mc and Ms computed by the existing SciPy impl:
| image | image |

And the new impl:
| image | image |

@brorson

brorson commented Mar 12, 2026

Copy link
Copy Markdown
Author

Fixes #47

@rgommers rgommers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for continuing to work on this @brorson! The algorithmic part of this looks pretty solid to me, and the accuracy results you posted on the issue look like a significant improvement over what we have today. @steppi may have more thoughts, but also looking at the review of the previous PR, this seems to be close to final.

There are quite a few issues with the C++ code that would need resolving, and I believe @steppi offered some help. I did a high-level review - my impression is that the size of the diff in this PR can be at least halved with some refactoring. Tests, public API, and LAPACK usage all need work to make this mergeable - I added high-level comments on those inline. None of that is super difficult, and some of the refactoring to reduce code duplication could even be done quickly with an LLM (it's straightforward enough that they'll probably get it first-time-right). It'd probably be better to leave any refactoring until after all tests pass though, to make it easy to verify that the refactoring didn't change anything. Hence, I'd suggest this sequence of steps:

  1. Rewrite the added tests to fit the xsf test suite pattern, and ensure that test pass/fail works as expected.
  2. Update or remove the existing tests as needed to make them pass - probably loosening tolerances or changing reference values for a bunch of them.
    • At this point, the whole test suite should pass, at least with OpenBLAS on Linux (it won't build on all platforms or with all LAPACK libraries)
  3. Then, do all the refactors for code duplication, dead code, and controlling the public API better.
  4. At the end, deal with LAPACK linkage and build system integration (I may be able to help with this).

@steppi could you have a quick look, does that seem like the right plan?

printf("===========================================\n");
printf("At end of run, pass = %d, fail = %d\n", pass, fail);

return 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Returning 0 unconditionally looks like the tests will always appear to pass. Taking a step back: these tests look more like a local debug helper - they should be using TEST_CASE, CAPTURE and REQUIRE like the other test files, and no printf.

It looks like these tests need a complete overhaul; I think @steppi had offered some help with that: #60 (comment)

Comment thread meson.build
cpp = meson.get_compiler('cpp')

# ========================================
# BLAS/LAPACK Dependencies -- needed for Mathieu fcns.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Adding a LAPACK dependency to all of xsf won't work, that's way too invasive - and it also actually won't work in its current form, since this is missing handling of symbol mangling which is very much nontrivial to add. Instead, there are two ways of dealing with this that I can think of:

  1. An adaptor pattern. Use the dstevd signature, but with an xsf-specific name. Then make the consumer responsible for adding a header file to their build that defines that name to the dstevd in the LAPACK library they use in their build.
  2. Avoid LAPACK completely, and use a handwritten algorithm to obtain the eigenvalues + eigenvectors for order m (one more numerical routine, but seems feasible) and might end up faster.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What I'm thinking here is thatxsf doesn't always need to contain standalone implementations of functions.
Along the lines of what I'd suggested here, scipy/scipy#24871 (comment), I think it can just supply numerical kernels necessary for implementing the functions that can be glued together on the SciPy/CuPy sides. I'd like to write the handwritten algorithm in a way that could be used with what I'm proposing here cupy/cupy#9839, but for now I think the LAPACK calls and full implementation can stay in SciPy.

namespace mathieu {

//------------------------------------------------------
int mathieu_coeffs_ee(int N, double q, int m, double *AA) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is a lot of code duplication in this file, should be possible to factor that out, which will also make the code and in particular the actual differences between these four functions easier to understand.

return retcode;
}

// The peak Fourier coeff tracks m. Therefore,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is a lot of code duplication for this Fourier summation. The size of this file could be cut in half it looks like by factoring that out.


// Sort ww vector from lowest to highest
// quickSort(ww, 0, N-1);
// print_matrix(ww, N, 1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Aside from this commented out code needing to be removed: this whole code block is essentially duplicated from the one higher up, can be factored out.


// Min and max macros for scalars.
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can be replaced with std::min and std::max

}
} // for (k=(N-1) ...

// I should do a sort before doing the sum

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For this kind of comment, it'd be good to remove the ones that are no longer relevant, and for the ones that are relevant but can be deferred for future work, change them to // TODO: ... with a bit more context on why it would help to address the TODO.

Comment thread include/xsf/mathieu.h
#include "mathieu/mathieu_coeffs.h"
#include "mathieu/mathieu_eigs.h"
#include "mathieu/mathieu_fcns.h"
#include "mathieu/matrix_utils.h"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's a lot that leaks into the public API here. Using namespace detail would be a solution that is done in many places in xsf.

namespace mathieu {

//==================================================================
double besselj(int k, double z) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These wrappers should probably be removed, xsf::cyl_bessel_j & co can be called directly instead.

#include <vector>

// In case of gcc use Float128 for more accuracy.
#ifndef _Float128

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should use HAVE_FLOAT128, which is written to a config header as part of this PR exactly to enable the usage in this file. This ifndef as written will always be true, since _Float128 isn't a macro.

@rgommers

Copy link
Copy Markdown
Member

attempt to explain my implementation in this paper:

https://github.com/brorson/ScipyMathieuPaper

That's an impressive amount of work, and valuable to have such a paper as a reference.

@steppi

steppi commented Apr 30, 2026

Copy link
Copy Markdown
Member

I looked through everything closely. Algorithmically I think things look solid. I think your plan is the right general idea @rgommers, except that I'd like to skip dealing with LAPACK in xsf entirely and make it a home only for straightforward numerical ufunc and gufunc kernels that in principle can work on CPU and GPU. This means that xsf won't contain end to end implementations of Mathieu functions, but only the kernels needed for the SciPy and future CuPy implementations, and will only contain tests of these kernels.

I will take things from here and work towards getting this ready to merge. I will break the work up into a series of small PRs against this branch.

@steppi

steppi commented May 9, 2026

Copy link
Copy Markdown
Member

@brorson. I now plan to submit two PRs against main that contain refactored versions of the work in this PR. The first, which is essentially done will be for mathieu_a, mathieu_b, mathieu_sem, and mathieu_cem, Before getting to mathieu_modcem1, mathieu_modcem2, mathieu_modsem1, and mathieu_modsem2, I want to push through @JaRoSchm's work here #50 so we can avoid the redundant calculations currently happening to get Bessel functions and their derivatives of adjacent orders in the Fourier-Bessel series summation. I will include commits from your PR in mine so that you can be properly credited as a co-author.

@JaRoSchm

Copy link
Copy Markdown
Contributor

@steppi, thanks for tagging me and nice to see progress on this front and that it's also helpful for others! Just be aware that you might stumble across cases, where these "vectorized order" variants give different results than the "single order" ones. These are probably due to bugs from the Fortran to C translation (#92, #93, #138 and at least one more I already have found locally). If you find such cases, please feel free to ping me. I think I managed to find a quite efficient way for locating these errors (by comparing to an automatic Fortran-C translation in https://github.com/JaRoSchm/pyamos/). Actually, I am in the process of slowly working through my own "test suite" for locating such cases (https://github.com/JaRoSchm/pyamos/blob/main/compare_vectorized_xsf.py). After getting this to pass I also planned to getting back to #50.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants