From 6564361e510d8ee78f39f7e6e85b9da4cf13e8b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:43:28 +0000 Subject: [PATCH 1/2] Initial plan From 4e03643587e500c26f6b00a60a6eb447ff3ab07c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:50:22 +0000 Subject: [PATCH 2/2] Address PR review comments: trailing whitespace, validation, docstring, grammar, unused variable Co-authored-by: skilledwolf <18141588+skilledwolf@users.noreply.github.com> --- README.md | 2 +- pyproject.toml | 2 +- src/quantumhall_matrixelements/__init__.py | 13 +++++----- src/quantumhall_matrixelements/diagnostic.py | 8 +++---- .../exchange_hankel.py | 10 ++++---- .../exchange_legendre.py | 15 +++++------- src/quantumhall_matrixelements/planewave.py | 24 ++++++++++--------- tests/test_exchange_legendre.py | 4 +++- tests/test_validation.py | 4 +++- 9 files changed, 43 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index d1f0ea0..3a4617c 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ The package provides two backends for computing exchange kernels: - **Recommended for**: Reference calculations and verifying the Gauss–Legendre backend. ## Notes -The following wavefunction used to find all matrix elements: +The following wavefunction is used to find all matrix elements: $$ \Psi_{nX}^\sigma(x,y) diff --git a/pyproject.toml b/pyproject.toml index aa2a12f..ff312f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "quantumhall_matrixelements" description = "Landau-level plane-wave form factors and exchange kernels for quantum Hall systems." readme = "README.md" authors = [ - { name = "Tobias Wolf", email = "public@wolft.xyz" }, + { name = "Tobias Wolf", email = "public@wolft.xyz" }, { name = "Sparsh Mishra" } ] license = { text = "MIT" } diff --git a/src/quantumhall_matrixelements/__init__.py b/src/quantumhall_matrixelements/__init__.py index 8617950..6243285 100644 --- a/src/quantumhall_matrixelements/__init__.py +++ b/src/quantumhall_matrixelements/__init__.py @@ -10,15 +10,16 @@ """ from __future__ import annotations +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _metadata_version from typing import TYPE_CHECKING import numpy as np -from importlib.metadata import PackageNotFoundError, version as _metadata_version -from .diagnostic import get_form_factors_opposite_field, get_exchange_kernels_opposite_field -from .planewave import get_form_factors +from .diagnostic import get_exchange_kernels_opposite_field, get_form_factors_opposite_field from .exchange_hankel import get_exchange_kernels_hankel from .exchange_legendre import get_exchange_kernels_GaussLegendre +from .planewave import get_form_factors if TYPE_CHECKING: from numpy.typing import NDArray @@ -28,13 +29,13 @@ def get_exchange_kernels( - G_magnitudes: "RealArray", - G_angles: "RealArray", + G_magnitudes: RealArray, + G_angles: RealArray, nmax: int, *, method: str | None = None, **kwargs, -) -> "ComplexArray": +) -> ComplexArray: """Dispatcher for exchange kernels. Parameters diff --git a/src/quantumhall_matrixelements/diagnostic.py b/src/quantumhall_matrixelements/diagnostic.py index 1bd9ff3..917be85 100644 --- a/src/quantumhall_matrixelements/diagnostic.py +++ b/src/quantumhall_matrixelements/diagnostic.py @@ -18,7 +18,7 @@ ] -def get_form_factors_opposite_field(F: "ComplexArray") -> "ComplexArray": +def get_form_factors_opposite_field(F: ComplexArray) -> ComplexArray: """Transform form factors to the opposite magnetic-field sign (σ→-σ). Parameters @@ -39,7 +39,7 @@ def get_form_factors_opposite_field(F: "ComplexArray") -> "ComplexArray": return np.conj(F) * phase -def get_exchange_kernels_opposite_field(Xs: "ComplexArray") -> "ComplexArray": +def get_exchange_kernels_opposite_field(Xs: ComplexArray) -> ComplexArray: """Transform exchange kernels to the opposite magnetic-field sign (σ→-σ). Parameters @@ -61,8 +61,8 @@ def get_exchange_kernels_opposite_field(Xs: "ComplexArray") -> "ComplexArray": def verify_exchange_kernel_symmetries( - G_magnitudes: "RealArray", - G_angles: "RealArray", + G_magnitudes: RealArray, + G_angles: RealArray, nmax: int, rtol: float = 1e-7, atol: float = 1e-9, diff --git a/src/quantumhall_matrixelements/exchange_hankel.py b/src/quantumhall_matrixelements/exchange_hankel.py index e63d52d..b69724b 100644 --- a/src/quantumhall_matrixelements/exchange_hankel.py +++ b/src/quantumhall_matrixelements/exchange_hankel.py @@ -1,7 +1,7 @@ """Exchange kernels via Hankel transforms.""" from __future__ import annotations -from functools import lru_cache +from functools import cache from typing import TYPE_CHECKING import numpy as np @@ -23,7 +23,7 @@ def _N_order(n1: int, m1: int, n2: int, m2: int) -> int: def _parity_factor(N: int) -> int: return (-1) ** ((N - abs(N)) // 2) -@lru_cache(maxsize=None) +@cache def _get_hankel_transformer(order: int) -> HankelTransform: """Cached HankelTransform instance for a given Bessel order.""" return HankelTransform(nu=order, N=6000, h=7e-6) @@ -72,14 +72,14 @@ def _radial_exchange_integrand_rgamma( def get_exchange_kernels_hankel( - G_magnitudes: "RealArray", - G_angles: "RealArray", + G_magnitudes: RealArray, + G_angles: RealArray, nmax: int, *, potential: str | callable = "coulomb", kappa: float = 1.0, sign_magneticfield: int = -1, -) -> "ComplexArray": +) -> ComplexArray: """Compute X_{n1,m1,n2,m2}(G) via Hankel transforms (κ=1 convention). This backend parametrizes the radial integral via Hankel transforms with diff --git a/src/quantumhall_matrixelements/exchange_legendre.py b/src/quantumhall_matrixelements/exchange_legendre.py index f26d0e2..85e6bca 100644 --- a/src/quantumhall_matrixelements/exchange_legendre.py +++ b/src/quantumhall_matrixelements/exchange_legendre.py @@ -1,12 +1,11 @@ """Exchange kernels via Gauss-Legendre quadrature with rational mapping.""" from __future__ import annotations -from functools import lru_cache +from functools import cache from typing import TYPE_CHECKING import numpy as np import scipy.special as sps - from scipy.special import roots_legendre if TYPE_CHECKING: @@ -20,13 +19,13 @@ def _parity_factor(N: int) -> int: """(-1)^((N-|N|)/2) → (-1)^N for N<0, and 1 for N>=0.""" - return (-1) ** ((N - abs(N)) // 2) + return (-1) ** ((N - abs(N)) // 2) -@lru_cache(maxsize=None) +@cache def _logfact(n: int) -> float: return float(sps.gammaln(n + 1)) -@lru_cache(maxsize=None) +@cache def _legendre_nodes_weights_mapped(nquad: int, scale: float): """ Gauss-Legendre nodes/weights mapped from [-1, 1] to [0, inf). @@ -50,7 +49,7 @@ def get_exchange_kernels_GaussLegendre( scale: float = 0.5, ell: float = 1.0, sign_magneticfield: int = -1, -) -> "ComplexArray": +) -> ComplexArray: """Compute exchange kernels X_{n1,m1,n2,m2}(G) using Gauss-Legendre quadrature. This function evaluates the exchange matrix elements for a 2D electron gas @@ -159,9 +158,7 @@ def get_exchange_kernels_GaussLegendre( arg = Gscaled[:, None] * sqrt2z[None, :] # (nG, nquad) # Callable potential: evaluated once on the quadrature grid - if is_coulomb: - Veff = None - else: + if not is_coulomb: qvals = sqrt2z / float(ell) # (nquad,) Veff = pot_fn(qvals) / (2.0 * np.pi * float(ell) ** 2) Veff = np.asarray(Veff) diff --git a/src/quantumhall_matrixelements/planewave.py b/src/quantumhall_matrixelements/planewave.py index 04a7b23..92845db 100644 --- a/src/quantumhall_matrixelements/planewave.py +++ b/src/quantumhall_matrixelements/planewave.py @@ -16,13 +16,13 @@ IntArray = NDArray[np.int64] def _analytic_form_factor( - n_row: "IntArray", - n_col: "IntArray", - q_magnitudes: "RealArray", - q_angles: "RealArray", + n_row: IntArray, + n_col: IntArray, + q_magnitudes: RealArray, + q_angles: RealArray, lB: float, sign_magneticfield: int = -1, -) -> "ComplexArray": +) -> ComplexArray: """Vectorized Landau level form factor F_{n_row, n_col}(q). F_{n',n}(q) = i^{|n-n'|} e^{i(n-n')θ} @@ -44,7 +44,7 @@ def _analytic_form_factor( laguerre_poly = eval_genlaguerre(n_min, delta_n_abs, arg_z) - angles = -sign_magneticfield * (n_col - n_row) * q_angles + (np.pi / 2) * delta_n_abs + angles = -sign_magneticfield * (n_col - n_row) * q_angles + (np.pi / 2) * delta_n_abs angular_phase = np.cos(angles) + 1j * np.sin(angles) F = ( @@ -57,12 +57,12 @@ def _analytic_form_factor( return F def get_form_factors( - q_magnitudes: "RealArray", - q_angles: "RealArray", + q_magnitudes: RealArray, + q_angles: RealArray, nmax: int, lB: float = 1.0, sign_magneticfield: int = -1, -) -> "ComplexArray": +) -> ComplexArray: """Precompute F_{n',n}(G) for all G and Landau levels. Parameters @@ -84,6 +84,8 @@ def get_form_factors( F : (nG, nmax, nmax) complex array Plane-wave form factors F_{n',n}(G). """ + if sign_magneticfield not in (1, -1): + raise ValueError("sign_magneticfield must be 1 or -1") n_indices = np.arange(nmax) F = _analytic_form_factor( n_row=n_indices[None, :, None], @@ -96,8 +98,8 @@ def get_form_factors( # Just to be explicit, we apply the symmetry transformation explicitly here # but we could have also passed sign_magneticfield to _analytic_form_factor # --> same result - if sign_magneticfield == 1: - F = get_form_factors_opposite_field(F) + if sign_magneticfield == 1: + F = get_form_factors_opposite_field(F) return F diff --git a/tests/test_exchange_legendre.py b/tests/test_exchange_legendre.py index f4d651a..12ef296 100644 --- a/tests/test_exchange_legendre.py +++ b/tests/test_exchange_legendre.py @@ -1,5 +1,7 @@ import numpy as np -from quantumhall_matrixelements import get_exchange_kernels_GaussLegendre, get_exchange_kernels + +from quantumhall_matrixelements import get_exchange_kernels, get_exchange_kernels_GaussLegendre + def test_legendre_basic_shape(): nmax = 2 diff --git a/tests/test_validation.py b/tests/test_validation.py index 424460c..9bd57e6 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,7 +1,9 @@ import numpy as np + from quantumhall_matrixelements import get_exchange_kernels from quantumhall_matrixelements.diagnostic import verify_exchange_kernel_symmetries + def test_cross_backend_consistency(): """ Verify that 'gausslegendre' and 'hankel' backends produce consistent results. @@ -41,7 +43,7 @@ def test_large_n_consistency(): Gs_dimless, thetas, nmax, method="hankel", sign_magneticfield=-1 ) - # At nmax=12, we expect ~1e-4 difference due to quadrature limits + # At nmax=12, differences up to ~3e-3 are acceptable due to quadrature limits assert np.allclose(X_gl, X_hk, rtol=3e-3, atol=3e-3), \ "Mismatch at large nmax exceeded relaxed tolerance"