Source code for specsrbench.build

"""Rebuilding the cache the figures read, from JADES DR4 and the Hub.

Six stages, each consuming what the one before it wrote::

    predictions   paired dataset + Hub checkpoints -> ML predictions
    sets          predictions -> eval / calib / tune sets, galaxy-disjoint
    lsf           raw JADES x1d + line fits       -> the measured LSF kernel
    tune          tune set + kernel               -> classical_params.json
    classical     eval set + kernel + parameters  -> the classical caches
    lines         every reconstruction            -> Gaussian fits, S/N, summary

Only ``lsf`` needs the raw JADES tree, and only ``predictions`` needs ``torch``
and network access to the Hub; the rest run on what the earlier stages wrote.

The order matters in a way that is easy to get wrong.  ``lsf`` reads the line
fits that ``lines`` writes, so the very first build of a fresh tree runs
``lines`` once against the shipped kernel, then ``lsf``, then ``tune`` ->
``classical`` -> ``lines`` again on the measured one.  ``classical`` refuses to
write a cache built with the shipped kernel, which is what stops that first
pass being mistaken for a finished one.
"""
from __future__ import annotations

from importlib import import_module
from pathlib import Path

import numpy as np

__all__ = ["STAGES", "run_stage", "require_npz"]


[docs] def require_npz(path: Path, produced_by: str): """Load an input, or say which stage produces it. A build stage that cannot find its input is the normal state of a fresh clone, not a corruption. Saying so, and naming the command that fixes it, is the difference between a one-line fix and an afternoon spent looking for a cache that was never there. """ if not Path(path).exists(): raise FileNotFoundError( f"missing {path}\n" f" this is written by: {produced_by}\n" f" see `specsrbench paths` for where inputs are being looked for") return np.load(path, allow_pickle=True)
#: stage name -> module implementing it. STAGES: dict[str, str] = { "predictions": "predictions", "sets": "sets", "lsf": "lsf", "tune": "tune", "classical": "classical_cache", "lines": "lines", }
[docs] def run_stage(stage: str, args=None) -> int: """Run one build stage. ``args`` is the parsed CLI namespace, if any.""" mod = import_module(f".{STAGES[stage]}", __package__) argv: list[str] = [] if args is not None: if stage == "lsf" and getattr(args, "jades_root", None): argv += ["--jades-root", str(args.jades_root)] if stage == "predictions" and getattr(args, "dataset", None): argv += ["--dataset", str(args.dataset)] if getattr(args, "nproc", None): argv += ["--nproc", str(args.nproc)] if getattr(args, "dry_run", False): argv += ["--dry-run"] print(f"\n{'=' * 78}\nbuild stage: {stage}\n{'=' * 78}") return mod.main(argv)