Skip to content

Refactor legacy FKEM non-Limber implementation into modular, documented code #1261

Description

@nikosarcevic

We currently have a legacy FKEM non-Limber implemetnation in
pyccl/nonlimber_fkem/legacy_fkem.py (legacy_nonlimber_fkem; note it was called _nonlimber_FKEM.py and lived directly in pyccl dir as a protected module), which is a single monolithic function without any documentation or unit tests.
We now have a refactored FKEM layout:


pyccl/nonlimber_fkem/
    __init__.py
    core.py
    chi_grid.py
    power_spectra.py
    tracers.py
    single_ell.py
    transforms.py
    legacy_fkem.py   # (legacy code that was previously called _nonlimber_FKEM.py)

This issue tracks replacing the legacy implementation with the new modular code and adding proper tests + docs

Problems with the legacy implementation

The old legacy_nonlimber_fkem has several structural and usability problems:

  • Monolithic, hard-to-test function
    One huge function (legacy_nonlimber_fkem) does everything: power spectrum parsing, tracer collection setup, χ-grid construction, FFTLog transforms, non-Limber vs Limber comparison, and auto-Limber transition. There is no separation of concerns, making it extremely fragile and impossible to unit-test components in isolation.

  • Silent fallback to standard Limber
    If p_of_k_a and pk_linear are not of the same type, the code emits a CCLWarning and silently returns (-1, [], status) and “defaults to Limber”. There is no logging or structured information about when/how FKEM has effectively been disabled, so users think they are running non-Limber when they’re not.

  • No explicit checks or diagnostics for FKEM behaviour
    There are no unit tests that exercise FKEM internals. The only coverage is via test_cells.py, which just smoke-tests ccl.angular_cl, without checking:

    • whether FKEM is actually used only in galax clustering
    • how the Limber transition is chosen
    • whether FKEM is numerically stable for different tracer combinations
  • Auto-Limber logic is opaque and untested
    The l_limber="auto" behaviour is implemented inline via a relative error condition:
    if (abs(cells[-1] / cl_limber_nonlin[-1] - 1) < limber_max_error and l_limber == "auto") ...
    This is done inside the main ell loop, with no documentation, no per-ell diagnostics, and no tests that verify the chosen transition scale. It’s effectively a black box.

  • integration issues for some WL tracers
    In practice, the legacy code has triggered CCL integration errors for some WL tracer combinations (e.g. due to how kernels, chi-ranges, and FFTLog grids are set up). Failures just propagate via status with no clear message to the Python user.

  • Magic numbers and hard-wired FFTLog setup
    Key FKEM configuration is baked into the function as magic constants (nu, nu2, kpow = 3, k_low = 1e-5, chi-grid heuristics, etc.), with no clean way to expose or document them. This makes it hard to reason about accuracy and to reproduce the setup from the original FKEM paper.

  • No user-facing documentation
    Beyond a short header comment, there is no proper docstring, no rendered docs, and no explanation of:

    • what FKEM does,
    • what arguments it expects,
    • how the Limber transition is chosen,
    • or what accuracy knobs (nchi, chi_min, limber_max_error, etc.) mean.
      Given that FKEM is supposed to be part of the CCL v3 paper, shipping this undocumented, monolithic implementation is not acceptable.

Goal of the refactor

The new pyccl/nonlimber_fkem/ modules (core.py, chi_grid.py, power_spectra.py, tracers.py, single_ell.py, transforms.py) are intended to:

  • factor out each responsibility (power spectra handling, tracer collections, chi-grid, per-ℓ computation, transforms) into testable units.
  • Make all fallbacks (e.g. to standard Limber) explicit and logged, not silent.
  • implement and test a well-defined auto-Limber transition (with clear diagnostics)
  • qdd proper user-facing documetnation and examples, consistent with what we claim in the CCL v3 paper.

This issue is about finishing that transition: deprecating the legacy implementation, wiring up the new modular FKEM in the public API, and adding tests + docs so FKEM is something we can confidently show in the v3 paper.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions