Commit f9386474 authored by Gaetano Scandariato's avatar Gaetano Scandariato
Browse files

feat: start NUTS or the ensemble near given values without a peak search;...

feat: start NUTS or the ensemble near given values without a peak search; comparison runs without the search

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent ac180fe4
Loading
Loading
Loading
Loading
+6 −0
Changes for docs/config/sampling.md: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -81,6 +81,10 @@ earlier `_peaks.json` (`init_from_peaks`, and `init_peak`, its rank, 1 by defaul
prior medians (with NumPyro's own adaptation) and the ensemble from prior draws, which needs a much longer adaptation
or burn-in.

Or, to start as another code does, e.g. allesfitter's walkers near the values of its parameter file:
`init_values: {free parameter: value, ...}` (every free parameter) and `init_radius` (default `1e-4`, a uniform
ball in unconstrained space).

```yaml
sampling:
  production_sampler: nuts
@@ -412,6 +416,8 @@ reports:
| `sampling.peak_weight_threshold` | no | `0.001` | float ($\ge 0$) | Approximate evidence, relative to the best peak, above which a peak is significant |
| `sampling.init_from_peaks` | no | — | string | With `peak_search: false`: the `_peaks.json` of an earlier run, to start NUTS or the ensemble from |
| `sampling.init_peak` | no | `1` | int ($\ge 1$) | Rank of the saved peak to start from |
| `sampling.init_values` | no | — | dict | With `peak_search: false`: start NUTS or the ensemble near these values (one per free parameter), as codes such as allesfitter do |
| `sampling.init_radius` | no | `1e-4` | float ($\ge 0$) | Half-width of the uniform ball around `init_values`, in unconstrained space |
| `sampling.prior_bounds` | no | — | dict | `{free parameter: [low, high]}`: truncate those priors to a box (written by the peak search for each peak) |
| **Nested sampling** | | | | |
| `sampling.n_live_if_ns` | no | `500` | int ($\ge 50$) | Live points |
+2 −2
Changes for paper_benchmarks/machines/sampler_comparison_server.sh: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
# ensemble samplers, each run until converged, on the H100; one job at a time under heavy.lock, each stopped after
# MAX_H hours (default 4). Runs in the refactor/samplers clone (sync_refactor_server.sh), whose src/ is put first on
# the path.
# usage: sampler_comparison_server.sh <work dir> [clone folder, default PyAETNA_refactor]
# usage: [SAMPLERS="ns_nosearch nuts_nosearch ..."] [MAX_H=4] sampler_comparison_server.sh <work dir> [clone folder]
set -u
W=$(realpath "$1"); C=${2:-PyAETNA_refactor}; B=$W/$C/paper_benchmarks
LOG=$W/logs/sampler_comparison_server.log; MAX_H=${MAX_H:-4}
@@ -13,7 +13,7 @@ say() { echo "$(date '+%F %T') $*" >> "$LOG"; }
cd "$B" || exit 1
say "start queue, clone $C"
for bench in wasp18_phase_curve k2140_juliet k2100_pyaneti toi216_ttv ttv_scaling; do
  for sampler in ns nuts ess aies; do
  for sampler in ${SAMPLERS:-ns nuts ess aies}; do
    run=$bench/runs/cmp_${sampler}_h100
    [ -d "$run" ] || continue
    grep -q "exit=0" "$run/done.txt" 2>/dev/null && continue
+15 −6
Changes for paper_benchmarks/sampler_comparison/make_runs.py: 15 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -10,15 +10,18 @@ sampler:
* ``ess`` and ``aies``: 4 walkers per free parameter from the peak search, until ``TARGET_ESS`` independent draws,
  R-hat <= 1.01 and a run longer than 50 autocorrelation times (at most 50 000 steps).

Every run starts with the peak search (``on_multiple_peaks: continue``, so that a benchmark with several peaks is
still timed; the peaks are in ``<config>_peaks.json``). The folders are ``<benchmark>/runs/cmp_<sampler>_<machine>``,
so that ``run.sh`` finds the data of the benchmark.
Each sampler runs twice: after the peak search (``on_multiple_peaks: continue``, so that a benchmark with several
peaks is still timed; the peaks are in ``<config>_peaks.json``), and without it (``peak_search: false``: nested
sampling and the ensembles start from prior draws, NUTS from the prior medians), to measure what the search saves.
The folders are ``<benchmark>/runs/cmp_<sampler>[_nosearch]_<machine>``, so that ``run.sh`` finds the data of the
benchmark.

usage: python make_runs.py [machine suffix, default h100]
"""

from __future__ import annotations

import itertools
import sys
from pathlib import Path

@@ -79,18 +82,24 @@ def main() -> None:
            for k, v in (config.get("sampling") or {}).items()
            if k.endswith("_if_ns")
        }
        for sampler, keys in SAMPLERS.items():
        for (sampler, keys), search in itertools.product(
            SAMPLERS.items(), (True, False)
        ):
            cfg = dict(config)
            cfg["sampling"] = {**COMMON, **keys, **(ns_keys if sampler == "ns" else {})}
            if not search:
                cfg["sampling"].pop("on_multiple_peaks")
                cfg["sampling"]["peak_search"] = False
            reports = dict(cfg.get("reports") or {})
            reports["run"] = (
                False  # timing only; the reports are made separately when needed
            )
            cfg["reports"] = reports
            run = B / bench / "runs" / f"cmp_{sampler}_{machine}"
            tag = sampler if search else f"{sampler}_nosearch"
            run = B / bench / "runs" / f"cmp_{tag}_{machine}"
            run.mkdir(parents=True, exist_ok=True)
            (run / f"{name}.yaml").write_text(
                f"# sampler comparison ({sampler}), from {source}: see paper_benchmarks/sampler_comparison/make_runs.py\n"
                f"# sampler comparison ({tag}), from {source}: see paper_benchmarks/sampler_comparison/make_runs.py\n"
                + yaml.safe_dump(cfg, sort_keys=False)
            )
            print(f"wrote {run.relative_to(B)}/{name}.yaml")
+5 −2
Changes for paper_benchmarks/sampler_comparison/summarise.py: 5 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -46,7 +46,9 @@ def main() -> None:
    rows = []
    for bench, _source, name in CASES:
        for run in sorted((B / bench / "runs").glob("cmp_*")):
            sampler, machine = run.name.split("_")[1:3]
            parts = run.name.split("_")[1:]
            sampler, machine = parts[0], parts[-1]
            search = "nosearch" not in parts
            out = run / "PyAETNAouts"
            res_file = out / f"{name}_results.json"
            done = (
@@ -54,7 +56,8 @@ def main() -> None:
                if (run / "done.txt").exists()
                else None
            )
            row: dict = {"benchmark": bench, "sampler": sampler, "machine": machine, "exit": done,
            row: dict = {"benchmark": bench, "sampler": sampler, "peak_search": search,
                         "machine": machine, "exit": done,
                         "wall_s": wall_s(run / "time.out")}  # fmt: skip
            peaks_file = out / f"{name}_peaks.json"
            if peaks_file.exists():
+2 −0
Changes for src/PyAETNA/sampling/config/validation.py: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ SECTION_KEYS: dict[str, frozenset[str]] = {
            "ess_moves",
            "init_from_peaks",
            "init_peak",
            "init_radius",
            "init_values",
            "inner_steps_per_dim_if_ns",
            "max_iterations_if_ns",
            "max_rhat",
Loading