Skip to content

For AI agents

A dense, rule-oriented map of liulu for coding agents. Read this first, then the specific page you need. The canonical spec is plan.md in the repo root; the project working agreement is CLAUDE.md.

What this package is

A streaming-model RSD predictor: given real-space \(\xi(r)\) + pairwise velocity moments + a velocity PDF, it returns redshift-space \(\xi^s(s_\perp,s_\parallel)\) and multipoles \(\xi_0,\xi_2,\xi_4\). Entry point: from liulu import StreamingModel.

Hard architecture rules (do not violate)

  1. liulu/physics/ = astrophysical content only. No np.trapz, scipy.integrate, FFTs, or grid construction. New physics = new class inheriting an ABC in physics/base.py.
  2. liulu/numerics/ = pure numerics. No cosmological parameters, no knowledge of what \(\xi\) or \(\mathcal{P}\) mean. Signatures are f, a, b, tol style — never a Cosmology or moments object.
  3. liulu/model.py is the only wiring point, via constructor injection.

Conventions (load-bearing)

  • Units: Mpc/\(h\), km/s, \(aH\) in km/s/(Mpc/\(h\)). Convert only at I/O.
  • Moments: m_ij (about origin) / c_ij (central); i=radial, j=tangential order. Odd tangential orders are auto-zero. Dispatcher: moments.get_moment(r, r_order, t_order, mode). Do not add parallel sigma_*/gamma_*/kappa_* accessors — those are inputs to TabulatedMoments, converted to c_ij.
  • Geometry: integrate over y (real-space LOS sep); r=sqrt(s_perp^2+y^2), v_los=aH*(s_par-y)*sign(y). PDF signature pdf(v_los, r_perp, r_par) with r_par >= 0 — pass |y|.
  • LOS projection (physics/los_projection.py) is geometry, not a model — not an ABC. Extend by adding higher c_ij methods, never by hand-unrolling.

Gotchas that have bitten before

  • Compare to binned measurements with multipoles_binned(s_edges), never multipoles(s_centres). A pair-count estimator (pycorr) reports the \(s^2\)-weighted bin average; centre evaluation over-predicts the steep \(\xi_0\) by ~1.7% (log bins) and is first-order wrong through the FoG \(\xi_2\) curvature/zero-crossing — on AbacusSummit it masqueraded as a ~15% "FoG failure" (truth: ~4%). Likewise tabulate \(\xi(r)\) at the pair-weighted separation (pycorr return_sep, or numerics.binning.pair_weighted_centres), not the geometric centre (~−2% on steep \(\xi\)). The two artefacts cancelled on the DEGRACE grids — a sub-percent match can be two ~2% bugs hiding each other.
  • n_y >= 300 in StreamingModel. Below that the Fingers-of-God dip in \(\xi_2\) (\(s\sim3\)\(5\)) is under-resolved and any fit/objective on it is biased.
  • TabulatedXi defaults to log–log interpolation (near-exact for power-law \(\xi\)); log-linear overestimates the convex curve and biased the monopole ~1.4% high. Falls back to log-linear where \(\xi\le0\).
  • Above \(r_\max\), TabulatedXi(tail=...): "zero" (default, \(\xi\to0\)) or "power" (power-law continuation, slope clamped negative). Use "power" for tables short vs the integral's \(r\) reach \(\sqrt{s_{\perp,\max}^2+y_\text{max}^2}\) — a hard \(\xi\to0\) truncation biases the large-\(s\) quadrupole (~3% / 3σ at \(s\approx45\)). Feed streaming-model inputs through numerics.extrapolation.extend_table ("power" for \(\xi\), "rise" for the still-rising velocity variances, "edge" for mean infall + shape moments).
  • Moments edge-extrapolate below \(r_\min\) so dispersions plateau; a zero variance would collapse the PDF to a delta and silently drop pairs.
  • Skew-t caches its \((\gamma_1,\gamma_2)\to(\alpha,\nu)\) table at liulu/data/skewt_param_table.npz; bump _SKEWT_TABLE_VERSION on format change. NIG needs no table (closed-form inversion).
  • When measuring moments with the external PairVel.jl, set JULIA_NUM_THREADS=1 — multithreaded PyJulia segfaults via a PyCall finalizer race.

Velocity PDF — settled recommendation

Use NIGPDF (analytic, zero-tuning, best four-moment shape). Do not tune the GH class index: a constant \(\lambda\) calibrated on one simulation (by PDF shape or by \(\xi_2\)) fails to transfer to another (DEGRACE-tuned \(\lambda\simeq+0.2\)\(0.3\) makes AbacusSummit worse than NIG), and a scale-dependent \(\lambda(r)\) additionally overfits within a simulation. NIG already saturates the measurable accuracy at \(s\gtrsim2.5\) Mpc/\(h\) (empirical-PDF identity test); the only genuine headroom is the deep FoG, which needs a measured/emulated PDF or \(>\)4 moments. Evidence: Validation.

Where things live

need file
driver / integral / multipoles liulu/model.py
ABCs liulu/physics/base.py
xi(r) providers liulu/physics/real_space.py
pairwise moments liulu/physics/pairwise_velocity.py
velocity PDFs liulu/physics/velocity_pdf.py
LOS projection liulu/physics/los_projection.py
interpolation liulu/numerics/interpolation.py
bin geometry (pair-weighted centres) liulu/numerics/binning.py
Hankel transforms liulu/numerics/hankel.py
tests liulu/tests/
spec plan.md

Environment / commands

python -m pytest liulu/tests/ -v   # tests (self-contained, no external data)
mkdocs build                       # this site

Run these in whatever Python environment the package is installed in — no specific environment manager is assumed.