P3: renames, demo cleanup, timing, yaml configs, docs rebuild¶
Scope for this branch (refactor/p0-fixes), decided 2026-08-27. Six independent chunks,
done in this order so each is independently testable before the next starts.
1. Rename observables/abundance.py -> observables/number_counts.py¶
git mv src/clenspy/observables/abundance.py src/clenspy/observables/number_counts.pyClusterAbundance->ClusterCountseverywhere (class def, docstrings,__repr__).Update importers:
observables/__init__.py,observables/deltasigma.py(demo import),covariance/counts.pydocstring reference,covariance/halo_to_halo.pydocstring reference,tests/test_observables.py,tests/test_covariance.py,docs/api/index.md(.. automodule:: clenspy.observables.abundance->.number_counts).sg -p 'ClusterAbundance' -l pythonto catch call sitessgfinds that grep-only passes would miss in string contexts (docstrings render the same either tool).
2. Rename cosmology/mass_function.py -> cosmology/halo_mass_function.py¶
Module filename only.
TinkerMassFunctionclass name and themass_functioncallable parameter/attribute onClusterCountsare not touched by this rename.Update importers:
cosmology/__init__.py,observables/number_counts.py’s demo import,docs/api/index.md(.. automodule:: clenspy.cosmology.mass_function), any test file importing the module path directly.
3. ClusterCounts.mean_richness() – a real third contraction¶
Currently only mean_mass() and mean_redshift() exist, both averaging a quantity that
lives on the native (ln M, z) grid. Observed richness \(\lambda^{\rm ob}\) is not on that
grid – it is integrated out inside SelectionFunction.S_ij, so there is nothing to
average directly yet.
Design. Add the first moment of \(\lambda^{\rm ob}\) within each bin as a second
contraction the selection layer exposes, analogous to how S_ij is the zeroth moment:
evaluated the same way richness_kernel/selection_function already build \(\mathcal
S_i\) (EMG closed form in \(\lambda^{\rm ob}\), quadrature only over \(\lambda^{\rm tr}\)), so
this is a new closed-form first moment next to the existing zeroth moment, not a new
integration scheme. Concretely:
add
first_moment_lambda_ob(...)toclenspy.selection.richness_kernel(or a method next toS_ijonSelectionFunction) returning \(\langle\lambda^{\rm ob}\rangle\) per \((\lambda^{\rm tr}, z)\) node, then letClusterCountscontract it through the existingaverage()machinery like any other per-halo quantity – it already broadcasts over(n_m, n_z)and reduces over the weight.ClusterCounts.mean_richness()wraps that: build the(n_m, n_z)grid of \(\langle \lambda^{\rm ob}\rangle\) from the selection object, callself.average(...).Sanity check to assert in tests:
mean_richness()must land inside each bin’s \([\lambda_i, \lambda_{i+1})\) edges, the same propertymean_redshift()already checks for \(z\).
This is the piece most likely to need a design iteration once richness_kernel’s actual
moment machinery is read closely – flagged here rather than guessed at further.
4. Demo: drop the \(\Omega(z)\) cancellation block, print mean \(\lambda^{\rm ob}\) and mean mass¶
In number_counts.py’s __main__ (formerly abundance.py): remove the “Omega(z)
cancels in average…” block at the bottom (the doubled ClusterCounts and its two
ratio prints). Replace with a table of mean_richness() and mean_mass() per
\((\lambda, z)\) bin, in the same per-bin print style already used for mean_mass() /
mean_redshift() above it.
5. time_method decorator rollout¶
clenspy.utils.decorators.time_method already exists and works (records into
self.timings, prints if self.verbose). Apply it to:
observablesclasses:ClusterCounts.weight,.counts,.average(the three hot paths);StackedDeltaSigma.profile.covariance/classes:CountsCovariance.cov_poisson,.cov_sample_variance;DeltaSigmaGaussianCovariance.cov(and._spectra,._kernel_matrixif those turn out to dominate);DeltaSigmaHaloToHaloCovariance.cov,.mean_profile.TinkerMassFunction:walkandoutputs–walkis the one that loopssigma_grid.sigma2/dlnsigma2_dlnrin pure Python over (by default) the 969-point production grid, the actual cost center.multiplicity/coefficientsare cheap vectorised closed forms and do not need it.
No behavior change for callers who never set self.verbose; self.timings is opt-in
storage. Existing tests must keep passing unmodified since the decorator is
transparent (functools.wraps, passes args/kwargs through, returns the same value).
6. Configs: JSON -> YAML, plus a Buzzard config¶
Add
pyyamltopyproject.tomldependencies(confirmed importable in the current venv already, just not declared).Convert
src/clenspy/configs/des_y1.jsonanddes_y3.jsonto.yaml, keeping every key and_provenance/_note_*string verbatim – these are load-bearing documentation, not comments to drop in translation.src/clenspy/survey/survey.py:load_configreads.yamlviayaml.safe_load;available_configs()globs*.yaml;CONFIG_DIR / f"{name}.json"->.yaml.pyproject.toml[tool.setuptools.package-data]:"configs/*.json"->"configs/*.yaml".Update
tests/test_survey.pyand any other reference to.jsonconfigs.New:
src/clenspy/configs/buzzard.yaml. Buzzard is a simulated light-cone, not a real survey with a fitted \(\Omega(z)\) footprint or metacal source properties – need to decide whatomega_zandsourcesshould be (flat mock footprint? reuse Y3’s source shape as a placeholder, same patterndes_y3.json’s own_note_pz_is_a_placeholderalready uses for its unfinished source n(z)?). This needs the Buzzard sky area / mock bin edges theb_sel/Buzzard calibration work already ported (selection/bsel.py, recent commit “Port b_sel”) – reuse those numbers rather than inventing new ones, with a_provenancenote pointing at that module.
7. Move halo/bias.py -> cosmology/bias.py¶
BiasModel already only imports from cosmology (fiducial, sigma) and nothing from
halo – it is a structure-formation fit calibrated on the same peak-height \(\nu =
\delta_c/\sigma(M)\) as the Tinker (2008) mass function and the child18/duffy08
concentration relations, all three already living in cosmology/. halo/ is density
profiles (NFW, Einasto, two-halo term); it does not belong there.
git mv src/clenspy/halo/bias.py src/clenspy/cosmology/bias.pyhalo/__init__.py: drop theBiasModelimport/re-export.cosmology/__init__.py: addfrom .bias import BiasModel, add to__all__.Update every importer found by
sg -p 'from ..halo import $$$' -l pythonandsg -p 'from .halo import $$$' -l python, plus docstring/prose mentions:covariance/halo_to_halo.py,covariance/counts.py,cosmology/mass_function.py(already says “the Tinker (2010) bias inclenspy.halo.BiasModel” – becomesclenspy.cosmology.BiasModel),cosmology/concentration.py,cosmology/sigma.py,kernels/limber.py,selection/bsel.py,selection/__init__.py,lensing/profile.py,examples/demo_basic_usage.py,tests/test_bias.py,tests/test_bsel.py,tests/test_covariance.py,tests/test_mass_function.py,docs/api/index.md(move theBiasModelautosummary entry from theclenspy.haloblock to theclenspy.cosmologyblock),docs/index.md,docs/notation.md,docs/development.md.Keep
BiasModelimportable asclenspy.halo.BiasModelfor one release? No – nothing in this codebase is at a public release yet (0.1.0, pre-alpha internal refactor branch), so this is a clean move, not a deprecation shim.
8. Docs rebuild¶
Once 1-6 are in and tests pass: regenerate docs/api/index.md module paths for the two
renames, then rebuild via the existing sphinx setup (docs/conf.py, autosummary ->
generated/) so the built docs reflect the new names and the new buzzard config /
mean_richness method. Last step, so it captures everything above rather than needing a
second pass.