From d2e8bad83fa4a12f81bba657b086b5102ac1b025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Mart=C3=ADnez=20Gonz=C3=A1lez?= Date: Fri, 6 Feb 2026 10:14:35 -0500 Subject: [PATCH 1/4] Fix spherical harmonics for new SciPy sph_harm_y API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace deprecated sph_harm with sph_harm_y and update argument ordering to match SciPy’s azimuth/polar convention. Ensure magnetic quantum numbers are always integers, and construct negative-m real harmonics from the imaginary part of positive-m modes. Simplify HORTON-2 m ordering and fix derivative evaluation to avoid invalid float m values. This restores compatibility with SciPy ≥1.17 and fixes test failures. --- pyproject.toml | 2 +- src/grid/utils.py | 31 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 01b3daed5..bcdb937c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ dependencies = [ "numpy>=1.16", "pytest>=8.0.0", - "scipy>=1.4", + "scipy>=1.15.0", "importlib_resources", "sympy", ] diff --git a/src/grid/utils.py b/src/grid/utils.py index dd2ffc28e..64ea03db2 100644 --- a/src/grid/utils.py +++ b/src/grid/utils.py @@ -20,7 +20,7 @@ """Utility Module.""" import numpy as np -from scipy.special import sph_harm +from scipy.special import sph_harm_y _bragg = np.array( [ @@ -560,21 +560,23 @@ def generate_real_spherical_harmonics_scipy(l_max: int, theta: np.ndarray, phi: l_list = np.arange(l_max + 1) for l_val in l_list: # generate m=0 real spherical harmonic - zero_real_sph = sph_harm(0, l_val, theta, phi).real + zero_real_sph = sph_harm_y(l_val, 0, phi, theta).real - # generate order m=positive real spherical harmonic - m_list_p = np.arange(1, l_val + 1, dtype=float) + # generate order m=positive real spherical harmonic: sqrt(2) * (-1)^m * Re(Y_l^m) + m_list_p = np.arange(1, l_val + 1, dtype=int) pos_real_sph = ( - sph_harm(m_list_p[:, None], l_val, theta, phi).real - * np.sqrt(2) - * (-1) ** m_list_p[:, None] # Remove Conway phase from SciPy + np.sqrt(2) + * (-1) ** m_list_p[:, None] + * sph_harm_y(l_val, m_list_p[:, None], phi, theta).real + # Remove Conway phase from SciPy ) - # generate order m=negative real spherical harmonic - m_list_n = np.arange(-1, -l_val - 1, -1, dtype=float) + # generate order m=negative real spherical harmonic: sqrt(2) * (-1)^m * Im(Y_l^m) neg_real_sph = ( - sph_harm(m_list_p[:, None], l_val, theta, phi).imag - * np.sqrt(2) - * (-1) ** m_list_n[:, None] # Remove Conway phase from SciPy + np.sqrt(2) + * (-1) ** np.abs(m_list_p[:, None]) + * sph_harm_y( + l_val, m_list_p[:, None], phi, theta + ).imag # Remove Conway phase from SciPy ) # Convert to horton 2 order @@ -753,7 +755,8 @@ def generate_derivative_real_spherical_harmonics(l_max: int, theta: np.ndarray, sph_harm_vals = generate_real_spherical_harmonics(l_max, theta, phi) i_output = 0 for l_val in l_list: - for m in [0, *sum([[x, -x] for x in range(1, l_val + 1)], [])]: + m_values = [0] + [m for x in range(1, l_val + 1) for m in (x, -x)] + for m in m_values: # Take all spherical harmonics at degree l_val sph_harm_degree = sph_harm_vals[(l_val) ** 2 : (l_val + 1) ** 2, :] @@ -777,7 +780,7 @@ def index_m(m): # Compute it using SciPy, removing conway phase (-1)^m and multiply by 2^0.5. sph_harm_m = ( fac - * sph_harm(np.abs(float(m)) + 1, l_val, theta, phi) + * sph_harm_y(l_val, np.abs(int(m)) + 1, phi, theta) * np.sqrt(2) * (-1.0) ** float(m) ) From 90f95992147e48af14ea8ee32c73e7a42be9a355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Mart=C3=ADnez=20Gonz=C3=A1lez?= Date: Fri, 6 Feb 2026 10:20:57 -0500 Subject: [PATCH 2/4] Drop Python 3.9 and raise NumPy minimum for SciPy compatibility Remove Python 3.9 from CI and require NumPy >= 1.23.5 to match the minimum supported versions needed for SciPy >= 1.15. This prevents incompatible dependency combinations in testing and installation. --- .github/workflows/pytest.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 24b92067d..960a45904 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -18,7 +18,7 @@ jobs: matrix: # os: ["ubuntu-latest", "windows-latest", "macos-latest"] os: ["ubuntu-latest", "windows-latest"] - py: ["3.9", "3.10", "3.11", "3.12", "3.13"] + py: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: "actions/checkout@v6" diff --git a/pyproject.toml b/pyproject.toml index bcdb937c9..0f526c757 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "numpy>=1.16", + "numpy>=1.23.5", "pytest>=8.0.0", "scipy>=1.15.0", "importlib_resources", From 67fe992abebd8a1c7e4fda61bbdfd306f568f180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Mart=C3=ADnez=20Gonz=C3=A1lez?= Date: Fri, 6 Feb 2026 10:45:41 -0500 Subject: [PATCH 3/4] Replace np.trapz with scipy.integrate.trapezoid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use scipy.integrate.trapezoid for numerical integration to support NumPy >= 2.0, where np.trapz has been removed. This fixes test failures while keeping behavior unchanged and consistent with SciPy’s integration API. --- pyproject.toml | 1 - src/grid/tests/test_atomgrid.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0f526c757..13b3bacd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,6 @@ classifiers = [ 'Intended Audience :: Science/Research', "Intended Audience :: Education", "Natural Language :: English", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/grid/tests/test_atomgrid.py b/src/grid/tests/test_atomgrid.py index e4cfe4159..45807c4fc 100644 --- a/src/grid/tests/test_atomgrid.py +++ b/src/grid/tests/test_atomgrid.py @@ -29,6 +29,7 @@ assert_raises, ) from scipy.spatial.transform import Rotation as R +from scipy.integrate import trapezoid from grid.angular import LEBEDEV_DEGREES, AngularGrid from grid.atomgrid import AtomGrid, _get_rgrid_size @@ -688,7 +689,7 @@ def func(sph_points): # Test the integral of spherical average is the integral of Gaussian e^(-x^2)e^(-y^2)... # from -infinity to infinity which is equal to pi^(3/2) - integral = 4.0 * np.pi * np.trapz(y=spherical_avg(oned_grid) * oned_grid**2.0, x=oned_grid) + integral = 4.0 * np.pi * trapezoid(y=spherical_avg(oned_grid) * oned_grid**2.0, x=oned_grid) actual_integral = np.sqrt(np.pi) ** 3.0 assert_allclose(actual_integral, integral) From dec6512356c7d1bc674d9600d8eaf5fe4ecccf1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Mart=C3=ADnez=20Gonz=C3=A1lez?= Date: Fri, 6 Feb 2026 10:47:46 -0500 Subject: [PATCH 4/4] Add copilot small fix suggestion --- src/grid/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grid/utils.py b/src/grid/utils.py index 64ea03db2..fde02b078 100644 --- a/src/grid/utils.py +++ b/src/grid/utils.py @@ -573,7 +573,7 @@ def generate_real_spherical_harmonics_scipy(l_max: int, theta: np.ndarray, phi: # generate order m=negative real spherical harmonic: sqrt(2) * (-1)^m * Im(Y_l^m) neg_real_sph = ( np.sqrt(2) - * (-1) ** np.abs(m_list_p[:, None]) + * (-1) ** m_list_p[:, None] * sph_harm_y( l_val, m_list_p[:, None], phi, theta ).imag # Remove Conway phase from SciPy