-
-
Notifications
You must be signed in to change notification settings - Fork 6
Description
This is a known issue. Since the old repo Scipy for the special functions has been closed and this one is now active I wanted to make sure it did not get lost.
The existing Scipy implementation of the Mathieu functions comes from the book "Computation of Special Functions" by Zhang and Jin (their Fortran code has been ported to C by the Scipy maintainers, but the algos remain the same). One step in the Zhang & Jin impl involves finding the roots of a difficult function. The function is defined using recursion, and different roots correspond to different function orders. Since the function is difficult, when looking for the root corresponding to the nth order function, the rootfinder can wander away from the desired root and find an adjacent one which corresponds to the n+1st or n-1st order.
The problem with the Zhang & Jin algo is shown in the below figure, which shows the Mathieu "characteristic values" (eigenvalues) vs. frequency parameter q. You can see jumps from one branch to another one for certain orders and at certain q values.

Compare this with the plot shown on the DLMF (albeit for smaller range)
The result is that the actual function can randomly jump from one order to another while the q parameter is smoothly swept over a small range per the below figure.

This is manifestly bad since the code will randomly return the wrong function to the user. The correct behavior is that the Mathieu impl doesn't randomly jump to the wrong branch.
I attach code which reproduces these plots to this bug report below.
What I can offer is an alternate implementation which uses a different method to compute the Mathieu eigenvalues -- one which doesn't jump around since it creates a recursion matrix and then computes its eigenspectrum. I had some undergrads work on this project earlier in the summer and they produced this paper describing the method, as well as some preliminary impls:
https://drive.google.com/file/d/1h6bMnNIUY3zBfP9Mh47vgaA-_Ntc29pZ/view
After their work I wrote up a prototype implementation in Matlab, and included a suite of tests. If there is interest on the part of the Scipy team I can translate the impl to C and submit a patch against the existing C codebase. Before I do that, however, I wanted to file this bug report as a way to kick off the process and gauge any interest on the part of the Scipy team.
Best regards,
Stuart
Northeastern University
Boston, Mass, USA
====================================================
import numpy as np
import scipy as sp
from scipy.special import *
import matplotlib.pyplot as plt
# This fcn creates the plots of ce and the eigenvalues
# evidencing the bug in the Mathieu impls.
N = 200 # Number of sample pts
v = np.linspace(-180, 180, N) # Fcn domain -- note this is degrees, not rads.
#--------------------------------------------------------
# First make plot of eigenvalues vs. q. This is an expansion
# of the famous plot DLMF Fig. 28.2.1 to larger m and q values.
# The bug is evidenced by the places where the eigenvalues jump
# from one branch of the plot to another. This happens when the
# rootfinder jumps to the wrong root.
q = np.linspace(0,50,2500) # Freq parameter
# Eigenvalues for ce
for m in range(9):
a = mathieu_a(m,q)
plt.plot(q, a,'b-')
# Eigenvalues for se
for m in range(1,10):
b = mathieu_b(m,q)
plt.plot(q, b,'r--')
plt.title("First Matheiu eigenvalues vs. q")
plt.xlabel("q")
plt.ylabel("Eigenvalues")
plt.show()
#--------------------------------------------------------
# Now create a figure and 10 vertically stacked subplots.
# For some values of q we get the wrong Mathieu function.
fig, axs = plt.subplots(10, 1, figsize=(8, 12), sharex=True)
# Array of q (frequency parameter) values.
qs = np.array([25.0, 25.1, 25.2, 25.3, 25.4, 25.5, 25.6, 25.7, 25.8, 25.9])
N = len(qs)
m = 6 # Order of Mathieu fcn
for i in range(N):
q = qs[i]
ce,_ = mathieu_cem(m,q,v)
axs[i].plot(v, ce)
axs[i].grid(True)
axs[-1].set_xlabel('v')
for i in range(N):
ax = axs[i]
ax.text(-20,-0.6,'q = %3.1f' % qs[i])
fig.suptitle("Mathieu ce(%d,q,v) for varying q" % m)
plt.show()