Skip to content

theory_fn protocol, default CCL backend, pure order-independent shift draw#4

Draft
cailmdaley wants to merge 7 commits into
mainfrom
feat/theory-backend-protocol
Draft

theory_fn protocol, default CCL backend, pure order-independent shift draw#4
cailmdaley wants to merge 7 commits into
mainfrom
feat/theory-backend-protocol

Conversation

@cailmdaley

Copy link
Copy Markdown
Member

End state

Blinding is driven by a single theory-function protocol instead of a firecrown likelihood. ConcealDataVector obtains both theory vectors (fiducial and hidden) by calling a theory_fn(cosmo_params) -> np.ndarray; the fork ships a default CCL cosmic-shear backend so a standard SACC blinds out of the box, and a caller can supply any callable. import smokescreen imports no theory backend — pyccl enters sys.modules only when the default backend is constructed, and firecrown is never imported at module scope anywhere in the package.

Closes #2

Interfaces

ConcealDataVector(fiducial_params, shifts_dict, sacc_data, *, seed, theory_fn=None, shift_distr="flat")

  • fiducial_params is a plain mapping of cosmological-parameter name → value (CCL-native names); it replaces the old cosmo + likelihood pair. The hidden point is fiducial_params with the drawn deltas overlaid — no pyccl.Cosmology object, no _create_concealed_cosmo.
  • theory_fn=None builds the default CCL backend from sacc_data; supplying a callable overrides it. No systm_dict — systematics, if a backend needs them, are closed over inside its theory_fn.
  • seed is keyword-only with no default (blinding custody depends on a deliberate secret seed). int or str; a string is normalized to an int via SHA-256.
  • The sole construction guard is a length check (len(theory_fn(fiducial_params)) == len(sacc_data.mean)); the value/covariance _verify_sacc_consistency check is dropped, not re-expressed.
  • calculate_concealing_factor("add"|"mult") computes theory_fn(concealed) - theory_fn(fiducial) (or the ratio); apply_concealing_to_likelihood_datavec() returns sacc_data.mean + factor (or * factor).
  • save_concealed_datavector deep-copies the SACC, overwrites its mean, carries the covariance over unchanged, stamps custody metadata + seed, and writes through SACC's own save_fits/save_hdf5 — no firecrown, no save_to_sacc/get_sacc_indices.

draw_param_shifts(shifts_dict, seed, shift_distr="flat") -> dict[str, float] (smokescreen.param_shifts)

  • Pure, order-independent (sorted keys, one scalar draw per key via a local numpy.random.default_rng; never touches process-global np.random).
  • Returns deltas to add to the fiducial, not absolute values. float h → symmetric envelope (-h, +h); (lo, hi) tuple → delta box. "gaussian" accepts only the float form.
  • Seed int used as-is; str reduced to a 64-bit int via SHA-256 of its UTF-8 bytes (stable across runs/machines).

Default CCL backend (smokescreen.backends.ccl.build_ccl_theory_fn)

  • The only import pyccl in the package; imported when the backend is constructed. Given a SACC with weak-lensing n(z) tracers and cosmic-shear rows (galaxy_shear_cl_ee and/or galaxy_shear_xi_plus/xi_minus), it builds a WeakLensingTracer per bin and returns the Cℓ / ξ± vector aligned to sacc_data.mean. Two documented conventions of this convenience backend (its row-to-theory contract): it defaults to CCL-native eisenstein_hu + halofit so it runs on a bare pyccl install with no Boltzmann code (overridable via cosmo_params), and it reads the ξ± theta tag as arcminutes (converted to degrees for pyccl.correlation).

Inherited firecrown path (smokescreen.firecrown_datavector.FirecrownConcealDataVector)

  • Upstream's firecrown-likelihood concealment class, retained so the fork doesn't amputate the upstream integration. All firecrown/pyccl imports are function-local (lazy); the module docstring marks it inherited-from-upstream and unsupported in this fork. Nothing in the package's main modules imports it, no test exercises it, and firecrown is not declared or installed here. The firecrown cosmic-shear example likelihood under examples/ is likewise untouched.

CLI (smokescreen.__main__)

  • datavector reworked onto the new surface: datavector_main(path_to_sacc, fiducial_params, shifts_dict, seed, shift_type='add', shift_distribution='flat', path_to_output=None, keep_original_sacc=False, output_suffix=None), driving the default CCL backend (the firecrown-specific likelihood_path / systematics / reference_cosmology params are gone). encrypt / decrypt are unchanged.

Test coverage

The suite runs firecrown-free (pyccl present) — 57 passed, nothing skipped for a missing firecrown.

  • Blinding mechanics against a synthetic theory_fn closure (importing neither pyccl nor firecrown): construction, concealing factor equals the theory difference bit-for-bit for "add" and the ratio for "mult", apply returns mean ± factor, save writes the blinded mean with the covariance untouched and the custody metadata stamped, and the length guard raises on a mismatch.
  • Shift draw directly: fixed (shifts_dict, seed) reproduces a fixed shift; permuting the key order yields identical per-key deltas; the draw leaves np.random's global state untouched; the drawn value is a delta within the envelope (not an absolute); a string seed and its normalized int agree.
  • Import discipline via subprocess: after a fresh import smokescreen (and import smokescreen.datavector), neither pyccl nor firecrown is in sys.modules; pyccl appears only once the CCL backend is built.
  • Default CCL backend end-to-end on a small in-test cosmic-shear SACC (2 n(z) tracers, ξ±): blinds to a vector that differs from the input with the covariance untouched.

Notes for review

  • _verify_sacc_consistency is intentionally absent (row-to-theory alignment is the backend's contract; with no likelihood there is no second internal vector to compare against). This is a deliberate, non-behavior-preserving change.
  • Packaging, pyccl dependency declaration, CI wiring, and install docs are out of scope here (separate packaging sub-PR); pyproject.toml, CI workflows, and README are untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf

cailmdaley and others added 7 commits July 10, 2026 23:46
Replace the firecrown-likelihood theory path with a single
theory_fn(cosmo_params) -> np.ndarray protocol. ConcealDataVector now
takes (fiducial_params, shifts_dict, sacc_data, *, seed, theory_fn=None,
shift_distr): both theory vectors come from theory_fn (the shipped
default CCL cosmic-shear backend when None, else the caller's), the
concealed point is the fiducial mapping with drawn deltas overlaid, and
the save path writes the blinded mean back through SACC's own API with
the covariance carried over unchanged.

- smokescreen/backends/ccl.py: default cosmic-shear theory_fn built from a
  SACC's n(z) tracers and cosmic-shear rows (Cl_ee, xi_plus/minus). pyccl
  is imported here, so `import smokescreen` imports no theory backend.
- param_shifts.draw_param_shifts: pure, order-independent (sorted keys,
  one scalar draw per key) local-RNG draw returning deltas; seed int|str
  with SHA-256 string normalization; no global np.random.seed.
- Drop _verify_sacc_consistency, _create_concealed_cosmo, systm_dict,
  save_to_sacc/get_sacc_indices, and the firecrown/ParamsMap imports. The
  sole construction guard is a theory-vs-mean length check.
- utils: pyccl/sacc imports made lazy; remove firecrown-only helper.
- __main__: CLI reworked onto the new signature + default backend.

The firecrown cosmic-shear example likelihood is retained in examples/
as inherited-upstream, unsupported code; the package imports it nowhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf
…odule

Preserve upstream's firecrown-likelihood blinding class (pre-refactor
ConcealDataVector) as FirecrownConcealDataVector in its own module, so the
fork does not amputate the upstream firecrown integration a later DESC PR
might restore. All firecrown and pyccl imports are function-local (via a
_import_firecrown() helper and local imports), so importing this module —
or importing smokescreen — imports no firecrown. It is not wired into the
default flow (no main module imports it) and no fork test exercises it; the
module docstring records it as inherited-upstream and unsupported here. The
supported path remains smokescreen.datavector.ConcealDataVector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf
Mechanics tests (construction, draw, concealing factor, apply, save) now
run against a synthetic theory_fn closure, importing neither pyccl nor
firecrown. Adds the order-independence / delta / local-RNG / string-seed
draw tests, subprocess import-discipline tests (pyccl and firecrown absent
from sys.modules after a fresh `import smokescreen`; pyccl present only once
the CCL backend is built), a small default-CCL-backend end-to-end test, and
the length-guard test. CLI tests updated to the new datavector_main
signature. Every firecrown/likelihood/_verify_sacc_consistency test and the
mock-likelihood fixture are removed; no test depends on firecrown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf
… list envelopes

Each key now gets its own RNG derived from (seed, key), so the docstring
contract — a delta depends only on (key, seed, shift_distr) — is actually
true: adding or removing parameters from the shifts envelope no longer
changes other parameters' hidden shifts. Golden-value tests pin the
seed-normalization and both distributions cross-run/cross-machine; a
subset-invariance test guards the per-key contract; gaussian tests now
discriminate the distribution (differs from flat, escapes the flat bound).
[lo, hi] lists (the natural YAML/JSON form) are accepted alongside tuples.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf
…stody and the Cl branch

Unknown shifts_dict keys now raise a clear ValueError instead of a bare
KeyError; the length guard becomes a full shape check (via a shared
_checked_theory_vec, also applied to the concealed-point evaluation), so
an (n,1) theory_fn can no longer broadcast the blinded mean into an (n,n)
matrix and silently corrupt the saved SACC. New tests: column-vector
rejection, unknown-key rejection, seed-is-required TypeError, and cl_ee
rows in the default-CCL-backend fixture so the np.interp Cl branch is
exercised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf
assert_called_once_with(factor_type='add') instead of a bare call check,
matching the shift_distr verification the gaussian variant already does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

theory_fn protocol + default CCL backend + fixed pure draw

1 participant