Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
13 changes: 7 additions & 6 deletions src/quantumhall_matrixelements/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/quantumhall_matrixelements/diagnostic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions src/quantumhall_matrixelements/exchange_hankel.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
15 changes: 6 additions & 9 deletions src/quantumhall_matrixelements/exchange_legendre.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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).
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 13 additions & 11 deletions src/quantumhall_matrixelements/planewave.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')θ}
Expand All @@ -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 = (
Expand All @@ -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
Expand All @@ -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],
Expand All @@ -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

Expand Down
4 changes: 3 additions & 1 deletion tests/test_exchange_legendre.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 3 additions & 1 deletion tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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"

Expand Down