"""Landau-level plane-wave form factors and exchange kernels.
This package provides reusable numerical kernels for quantum Hall matrix
elements in a Landau-level basis:
- `get_form_factors` for plane-wave form factors :math:`F_{n',n}(G)`.
- `get_guiding_center_form_factors` and
`get_factorized_density_form_factors` for symmetric-gauge building blocks.
- `get_exchange_kernels` (and backend-specific variants) for exchange kernels
:math:`X_{n_1 m_1 n_2 m_2}(G)` built from LL wavefunctions.
- `get_central_onebody_matrix_elements_compressed`,
`get_haldane_pseudopotentials`, and
`get_twobody_disk_from_pseudopotentials_compressed` for symmetric-gauge
one- and two-body workflows.
- Optional symmetry diagnostics for sanity-checking kernel implementations.
"""
from __future__ import annotations
from collections.abc import Iterable
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _metadata_version
from typing import Any, cast
import numpy as np
from numpy.typing import NDArray
from ._materialize import (
DEFAULT_COMPRESSED_LIMIT_BYTES,
DEFAULT_FULL_TENSOR_LIMIT_BYTES,
guard_compressed_values_allocation,
guard_full_tensor_materialization,
materialize_full_tensor,
)
from ._select import DEFAULT_CANONICAL_SELECT_MAX_ENTRIES, estimate_canonical_select_size
from .diagnostic import get_exchange_kernels_opposite_field, get_form_factors_opposite_field
from .exchange_hankel import get_exchange_kernels_hankel
from .exchange_laguerre import (
ExchangeFockPrecompute,
QuadratureParams,
build_exchange_fock_precompute,
get_exchange_kernels_laguerre,
)
from .exchange_ogata import get_exchange_kernels_Ogata
from .fock import build_fockmatrix_apply, get_fockmatrix_constructor, get_fockmatrix_constructor_hf
from .planewave import get_form_factors
from .symmetric import (
get_central_onebody_matrix_elements_compressed,
get_factorized_density_form_factors,
get_guiding_center_form_factors,
get_haldane_pseudopotentials,
get_twobody_disk_from_pseudopotentials_compressed,
materialize_central_onebody_matrix,
materialize_twobody_disk_tensor,
)
ComplexArray = NDArray[np.complex128]
RealArray = NDArray[np.float64]
Quad = tuple[int, int, int, int]
[docs]
def get_exchange_kernels(
G_magnitudes: RealArray,
G_angles: RealArray,
nmax: int,
*,
method: str | None = None,
materialize_limit_bytes: float | int | None = DEFAULT_FULL_TENSOR_LIMIT_BYTES,
canonical_select_max_entries: int | None = DEFAULT_CANONICAL_SELECT_MAX_ENTRIES,
**kwargs: Any,
) -> ComplexArray:
"""Compute and return the fully materialized 5D exchange tensor.
Parameters
----------
G_magnitudes, G_angles :
Arrays describing the reciprocal vectors :math:`G` in polar form.
Both must have the same shape; broadcasting is not applied.
nmax :
Number of Landau levels (0..nmax-1) to include.
method :
Backend selector:
- ``'laguerre'`` (default): Numba-JIT quadrature on [0, qmax] with
Laguerre three-term recurrence. Stable for all nmax and ``|G|``.
- ``'ogata'``: Ogata quadrature (Hankel/Ogata) with an automatic
small-``|G|`` fallback.
- ``'hankel'``: Hankel-transform based implementation (slow but precise).
materialize_limit_bytes :
Soft cap (in bytes) for allocating a full ``(nG, nmax, nmax, nmax, nmax)``
complex tensor. Pass ``None`` to disable this safety check.
canonical_select_max_entries :
Soft cap on the number of canonical select entries constructed when
``select`` is omitted. This prevents accidentally building huge Python
lists with O(nmax^4) entries.
**kwargs :
Additional arguments passed to the backend (e.g. ``nquad``, ``scale``).
Common keywords include ``sign_magneticfield`` (±1) to select the
magnetic-field orientation convention and, for the Laguerre backend,
``workspace_limit_bytes`` to cap dense quadrature-table allocations.
Notes
-----
For the built-in potentials ``'coulomb'`` and ``'constant'``, the ``kappa``
keyword scales the kernel. For callable potentials, the provided function
defines the overall energy scale.
To compute only a small set of entries without allocating the full tensor,
use :func:`get_exchange_kernels_compressed` with an explicit ``select=...``.
"""
chosen = (method or "laguerre").strip().lower()
backend_fn: Any
if chosen in {"hankel", "hk"}:
backend_fn = get_exchange_kernels_hankel
elif chosen in {"ogata", "og"}:
backend_fn = get_exchange_kernels_Ogata
elif chosen in {"laguerre", "lag"}:
backend_fn = get_exchange_kernels_laguerre
else:
raise ValueError(
f"Unknown exchange-kernel method: {method!r}. "
"Use 'laguerre', 'ogata', or 'hankel'."
)
G_magnitudes = np.asarray(G_magnitudes, dtype=float).ravel()
G_angles = np.asarray(G_angles, dtype=float).ravel()
if G_magnitudes.shape != G_angles.shape:
raise ValueError("G_magnitudes and G_angles must have the same shape.")
# Fast-fail before expensive backend work if we'd materialize a huge tensor.
guard_full_tensor_materialization(
select=None,
nmax=int(nmax),
nG=int(G_magnitudes.size),
materialize_full="auto",
materialize_limit_bytes=materialize_limit_bytes,
backend_name=f"{chosen} exchange kernels",
)
values, select_list = cast(
tuple[ComplexArray, list[Quad]],
backend_fn(
G_magnitudes,
G_angles,
nmax,
select=None,
canonical_select_max_entries=canonical_select_max_entries,
**kwargs,
),
)
return materialize_full_tensor(values, select_list, nmax)
[docs]
def get_exchange_kernels_compressed(
G_magnitudes: RealArray,
G_angles: RealArray,
nmax: int,
*,
method: str | None = None,
select: Iterable[Quad] | None = None,
canonical_select_max_entries: int | None = DEFAULT_CANONICAL_SELECT_MAX_ENTRIES,
compressed_limit_bytes: float | int | None = DEFAULT_COMPRESSED_LIMIT_BYTES,
**kwargs: Any,
) -> tuple[ComplexArray, list[Quad]]:
"""Return the compressed exchange-kernel representation ``(values, select_list)``.
Unlike :func:`get_exchange_kernels`, this function never materializes the full
5D tensor, and always returns the select list used by the backend.
If ``select`` is omitted, the backend still constructs the canonical
symmetry-reduced list, so the returned representation remains O(``nmax^4``)
in the number of stored entries. ``compressed_limit_bytes`` caps the
resulting ``(nG, n_select)`` complex output array. Pass an explicit
``select=...`` to compute only the entries you need.
"""
chosen = (method or "laguerre").strip().lower()
backend_fn: Any
if chosen in {"hankel", "hk"}:
backend_fn = get_exchange_kernels_hankel
elif chosen in {"ogata", "og"}:
backend_fn = get_exchange_kernels_Ogata
elif chosen in {"laguerre", "lag"}:
backend_fn = get_exchange_kernels_laguerre
else:
raise ValueError(
f"Unknown exchange-kernel method: {method!r}. "
"Use 'laguerre', 'ogata', or 'hankel'."
)
G_magnitudes = np.asarray(G_magnitudes, dtype=float).ravel()
G_angles = np.asarray(G_angles, dtype=float).ravel()
if G_magnitudes.shape != G_angles.shape:
raise ValueError("G_magnitudes and G_angles must have the same shape.")
select_list: list[Quad] | None
if select is None:
select_list = None
n_select_est = estimate_canonical_select_size(int(nmax))
else:
select_list = [cast(Quad, tuple(int(x) for x in quad)) for quad in select]
n_select_est = len(select_list)
guard_compressed_values_allocation(
nG=int(G_magnitudes.size),
n_select=int(n_select_est),
compressed_limit_bytes=compressed_limit_bytes,
backend_name=f"{chosen} exchange kernels",
)
return cast(
tuple[ComplexArray, list[Quad]],
backend_fn(
G_magnitudes,
G_angles,
nmax,
select=select_list,
canonical_select_max_entries=canonical_select_max_entries,
**kwargs,
),
)
try:
# Version is managed by setuptools_scm and exposed via package metadata.
__version__ = _metadata_version("quantumhall_matrixelements")
except PackageNotFoundError: # pragma: no cover - fallback for local, non-installed usage
__version__ = "0.0"
__all__ = [
"get_form_factors",
"get_guiding_center_form_factors",
"get_factorized_density_form_factors",
"get_form_factors_opposite_field",
"get_exchange_kernels",
"get_exchange_kernels_compressed",
"get_exchange_kernels_opposite_field",
"get_exchange_kernels_hankel",
"get_exchange_kernels_Ogata",
"get_exchange_kernels_laguerre",
"build_fockmatrix_apply",
"get_fockmatrix_constructor",
"get_fockmatrix_constructor_hf",
"QuadratureParams",
"ExchangeFockPrecompute",
"build_exchange_fock_precompute",
"get_central_onebody_matrix_elements_compressed",
"materialize_central_onebody_matrix",
"get_haldane_pseudopotentials",
"get_twobody_disk_from_pseudopotentials_compressed",
"materialize_twobody_disk_tensor",
"__version__",
]