Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
59cb670
docs: fix headless-fit guidance in llms.txt and roadmap numbering in …
InfinityMonkeyAtWork Jul 14, 2026
ab58128
test: default make_project to auto_export=False for xdist isolation
InfinityMonkeyAtWork Jul 14, 2026
0145638
fix: guard the MCMC spawn pool against notebook __main__ re-execution
InfinityMonkeyAtWork Jul 14, 2026
3ee4587
docs: plan the results-ownership and plotting-disentanglement work
InfinityMonkeyAtWork Jul 14, 2026
e9c6868
persist correl and acceptance_fraction in fit slots (schema 3)
InfinityMonkeyAtWork Jul 16, 2026
825037d
relocate results accessors to FitResults, backed by persisted slots
InfinityMonkeyAtWork Jul 16, 2026
374a65f
purify the fitlib SbS conversion functions
InfinityMonkeyAtWork Jul 16, 2026
6233ac4
route auto-export through the slot exporter, drop the legacy save path
InfinityMonkeyAtWork Jul 16, 2026
bd14edc
persist fit provenance and complete SbS parameter metadata (schema 3)
InfinityMonkeyAtWork Jul 17, 2026
e5ac0fc
add the explicit plotting API on FitResults with real axes
InfinityMonkeyAtWork Jul 17, 2026
fdbcb81
reconcile docs and TODO for the results-ownership work, bump to 0.14.0
InfinityMonkeyAtWork Jul 17, 2026
9955d13
archive the results-ownership design record, clear PLAN.md
InfinityMonkeyAtWork Jul 17, 2026
40dd55f
copy the MCMC payload arrays in FitResults.get_mcmc
InfinityMonkeyAtWork Jul 17, 2026
1acabcf
docs: plan auto-export removal and the typed fit-result object
InfinityMonkeyAtWork Jul 17, 2026
1d67c90
remove auto-export: fits never write, diagnostics render on demand
InfinityMonkeyAtWork Jul 18, 2026
1727cf6
replace the raw 5-list fit result with a typed FitOutput
InfinityMonkeyAtWork Jul 18, 2026
1cf62ec
consolidate initial-parameter naming on par_ini
InfinityMonkeyAtWork Jul 18, 2026
458018c
fix review findings: plot_sbs_slices param leak, stale benchmark call…
InfinityMonkeyAtWork Jul 19, 2026
674deed
close out the results-ownership branch: archive phases 7-8, clear PLAN
InfinityMonkeyAtWork Jul 19, 2026
23c9383
persist per-component 1D fit data in SavedFitSlot (schema 4)
InfinityMonkeyAtWork Jul 20, 2026
5c97fb6
persist aux_axis at the per-file archive level (schema 4 -> 5)
InfinityMonkeyAtWork Jul 21, 2026
47f6125
add full_range display mode to FitResults.plot_fit
InfinityMonkeyAtWork Jul 21, 2026
850f1d4
fix: persisted init_value reflected stage-1 output for two-stage fits
InfinityMonkeyAtWork Jul 21, 2026
a461404
persist fit_ini / params_init in SavedFitSlot (schema 5 -> 6)
InfinityMonkeyAtWork Jul 21, 2026
34a8a78
document schema-4/5/6 plotting features in CHANGELOG, clear PLAN.md
InfinityMonkeyAtWork Jul 21, 2026
462df80
uniform "initial guess" titling across describe_model(detail=1)
InfinityMonkeyAtWork Jul 21, 2026
8222355
fix describe_model title to use the resolved model name, not model_info
InfinityMonkeyAtWork Jul 21, 2026
f4745e2
fix two stale/incorrect CHANGELOG bullets
InfinityMonkeyAtWork Jul 21, 2026
7bae7ed
FitResults.plot_fit: title the 2D/SbS panels like the 1D path already…
InfinityMonkeyAtWork Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/benchmark/benchmark_gir.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,8 +588,8 @@ def capture_par_variability(example_num, *, n_starts=4):
fitted_runs.append({name: fitted[name] for name in free_names})

try:
redchi = model.result[1].redchi
except (AttributeError, IndexError, TypeError):
redchi = model.result.par_fin.redchi
except AttributeError: # result is None or placeholder par_fin
redchi = float("nan")
labels.append(label)
redchis.append(redchi)
Expand Down
23 changes: 12 additions & 11 deletions .claude/skills/check-example/check_example_mechanics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

Covers the statically-checkable parts of docs/ai/check-example.md: stripped
outputs (9), roadmap/TOC numbering (5), required files (3), committed truth (2),
auto_export (4), side-effect artifacts (4), and prose-voice candidates (10).
removed config keys (4), side-effect artifacts (4), and prose-voice
candidates (10).
Prints a PASS / WARN / FAIL / INFO line per check — INFO marks a fact the agent
must resolve by reading (evidence, not a verdict). The judgment criteria
(1, 6, 7, 8, 11, plus the prose/message parts of 5 and 10) are graded by reading
Expand Down Expand Up @@ -280,22 +281,22 @@ def check_truth(ex: Path) -> tuple[str, str]:


#
def check_auto_export(ex: Path) -> tuple[str, str]:
"""Criterion 4 — project.yaml sets auto_export: False."""
def check_removed_config_keys(ex: Path) -> tuple[str, str]:
"""Criterion 4 — project.yaml carries no removed config keys.

Fits never write to disk since v0.14.0; a leftover ``auto_export:`` /
``path_results:`` key makes ``Project()`` raise at load.
"""

pj = ex / "project.yaml"
if not pj.is_file():
# INFO: a %run-preamble notebook inherits a sibling's config.
return "INFO", "no project.yaml — resolve: %run preamble inherits config?"
text = pj.read_text(encoding="utf-8")
m = re.search(r"^\s*auto_export\s*:\s*(\w+)", text, re.MULTILINE)
if m and m.group(1).lower() == "false":
return "PASS", "auto_export: False"
# INFO, not WARN: only a defect if export is not the notebook's topic —
# the agent decides that by reading.
m = re.search(r"^\s*(auto_export|path_results)\s*:", text, re.MULTILINE)
if m:
return "INFO", f"auto_export: {m.group(1)} — resolve: is export the topic?"
return "INFO", "no auto_export key — resolve: is export the topic?"
return "FAIL", f"removed key '{m.group(1)}' present — Project() will raise"
return "PASS", "no removed config keys"


#
Expand Down Expand Up @@ -378,7 +379,7 @@ def report_example(ex: Path) -> tuple[int, int]:
rows.append(("9 Stripped outputs", "FAIL", "no example.ipynb"))
rows.append(("3 Required files", *check_required_files(ex)))
rows.append(("2 Committed truth", *check_truth(ex)))
rows.append(("4 auto_export", *check_auto_export(ex)))
rows.append(("4 Removed keys", *check_removed_config_keys(ex)))
rows.append(("4 Artifacts", *check_artifacts(ex)))
for name, status, detail in rows:
print(f"{status:4} | {name:22} | {detail}")
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
This file is maintained using the shared changelog workflow in
[`docs/ai/changelog.md`](docs/ai/changelog.md).

## [Unreleased]

### Added

- **Fit slots persist the correlation matrix and MCMC acceptance fraction** (archive schema 3): `SavedFitSlot.correl` stores the varying-parameter correlation matrix when the optimizer produced covariance (`None` for covariance-less methods and project-level joint fits, slice 0 for SbS), and the `mcmc` payload gains emcee's per-walker `acceptance_fraction`. Schema-2 archives still load (new fields read as `None`); appends remain same-version only.
- **Fit slots record optimizer provenance and complete SbS parameter metadata** (also schema 3): every slot gains `fit_settings` — stages, per-stage methods, `try_ci`, SbS seeding recipe (`seed_source`/`seed_adapt`/`seed_values`), and MCMC sampling settings when enabled (worker counts deliberately excluded; serial ≡ parallel is a tested invariant). SbS slots additionally gain `params_meta` (the slice-invariant `[name, vary, min, max, expr]` metadata) and `params_stderr` (per-slice standard errors, previously discarded — the future weights for 1D trace fitting).
- `FitResults.get_fit_results` / `get_correlations` / `get_conf_intervals` / `get_mcmc`: the results accessors now live on `FitResults`, read the latest matching persisted slot (`file=` / `model=` / `fit_type=` filters), and therefore also work for SbS fits (slice-0 payloads) and on archives loaded with `FitResults.load`. The `File.get_*` methods remain as thin delegates with an added optional `model=` filter.
- **Explicit plotting API**: `FitResults.plot_fit` (observed/fit/residual for any fit type) and `FitResults.plot_param_evolution` (SbS per-parameter evolution, varied parameters by default), with `File.plot_fit` / `File.plot_param_evolution` sugar. `FitResults` now carries fingerprint-matched axes providers (live `File`s via `Project.results`, `SavedFile`s via `FitResults.load`), so plots — including the upgraded `plot_residuals` — use real energy/time axes on live sessions *and* loaded archives, falling back to array indices only when no provider matches. Styling resolves as explicit `config=` > the live file's `plot_config` > defaults (styling is deliberately not persisted in archives). The fit methods' inline display now routes through this same API, so the figure shown at fit time is exactly the figure the API reproduces later.
- `FitResults.plot_mcmc` (with `File.plot_mcmc` sugar): re-renders the MCMC diagnostics — per-walker acceptance fraction and the corner plot — from the persisted slot payload, on live sessions and loaded archives alike.
- `File.plot_sbs_slices`: on-demand per-slice fit panels for the most recent Slice-by-Slice fit (slice data, per-slice seeded initial guess, final fit, component decomposition — more than the old auto-written per-slice PNGs showed). Live-session only (reads `results_sbs`); `save_path=None` means display-only, pass a directory to also write one PNG per slice.
- **Fit slots persist per-component 1D curves** (archive schema 4): baseline/spectrum/SbS slots gain `components` (per-component fit curves, SbS per-slice) and `component_names`, so `FitResults.plot_fit` can render the component decomposition from a loaded archive with no live `Model`.
- **Fit slots persist each file's auxiliary physical axis** (archive schema 5): `SavedFile` gains `aux_axis` (`File.aux_axis`, e.g. depth, used by `par_profile`-attached models) — the one array in the already-persisted full-data family (`data`/`energy`/`time`) that reloading an archive with no live `File` previously lost entirely.
- **`FitResults.plot_fit` gains a `full_range` display mode** (`PlotConfig.full_range`, default `True`): shows the real full data/axis for the file, with fit/residual/components drawn only inside the fit window (`NaN` outside — never a fabricated value) and dashed ROI boundary lines, matching `describe_model`'s visual language. All 4 fit methods' post-fit display now uses this mode by default.
- **Fit slots persist the true initial guess** (archive schema 6): `SavedFitSlot.fit_ini` (model evaluated at the pre-fit seed, all 4 fit types) and, for Slice-by-Slice, per-slice `params_init` (every slice's true seed values, mirroring `params_stderr`'s shape). `FitResults.plot_fit` renders the archive-side dotted-gold "initial guess" overlay via new `PlotConfig.show_init` (default `True`), matching the live fit display.

### Fixed

- **`init_value` was silently wrong for `stages=2` fits**: lmfit resets `Parameter.init_value` to `Parameter.value` at the start of every stage, so a two-stage fit's persisted `init_value` (in `SavedFitSlot.params` and the printed `lmfit.fit_report`) reflected stage 1's output rather than the true original seed. `fitlib.fit_wrapper` now restores the true seed after fitting; `stages=1` fits were unaffected.

### Changed

- **`describe_model(detail=1)` labels every panel as an initial guess.** The energy-resolved (1D) case already titled its figure `"...: initial guess"`; the Dynamics and 2D cases now do too (`fitlib.plt_fit_res_2d` gains an optional `title=` figure-level suptitle), so none of the three can be mistaken for a saved fit result at a glance.
- **`FitResults.plot_fit`'s 2D/SbS figures now identify the file and model.** The 1D path already set a rich title (`_slot_title`: file, model, fit type, yaml stem); the 2D/SbS path rendered generic, unlabeled panels, so e.g. looping `Project.fit_2d()` over several files showed visually identical figures with no way to tell them apart.
- **Breaking: fits never write to disk.** `fit_baseline` / `fit_spectrum` / `fit_slice_by_slice` / `fit_2d` and `Project.fit_2d` compute, display (per `show_output`), and record fit slots — persistence is always an explicit `save_fits` (HDF5 archive) or `export_fits` (grouped CSV/PNG tree) call, both fed from the slot history, with the single default output root `./fit_results/{Project.name}/`. Everything the automatic writes used to produce is reproducible: parameter tables, confidence intervals, and MCMC chains are persisted in the slot and exported by `export_fits`; the MCMC walker/corner PNGs via `plot_mcmc`; the SbS per-slice PNGs via `plot_sbs_slices`; the component-decomposed `fit_1d.csv` via `save_baseline_fit` / `save_spectrum_fit` (kept, no longer auto-called). Accepted losses: per-slice `par_ini` CSVs (not exported to the legacy CSV tree, but the true seed values/curves are preserved in the archive itself — `SavedFitSlot.params_init`/`fit_ini`, schema 6) and the `lmfit.fit_report` text dumps (all their contents are persisted). Interactive display renders from the captured fit slot, so the figure shown equals the figure the plot API reproduces later; `Project.fit_2d` / `Project.fit_baselines` show per-file plots instead of PNGs read back from disk.
- **Breaking: `File.get_correlations` raises for covariance-less fits** (e.g. Nelder without numdifftools, project joint fits) instead of returning an identity-with-zeros matrix that misread as "uncorrelated".
- **Breaking: `Project.name` defaults to `"my_project"`** (was `"test"`), so a bare `save_fits()` / `export_fits()` lands in a clearly-placeholder `fit_results/my_project/` instead of colliding with test-suite naming.
- **Breaking (advanced API): fit results are a typed `FitOutput` object.** `fitlib.fit_wrapper` returns a frozen `FitOutput` dataclass (fields `par_ini`, `par_fin`, `conf_ci`, `emcee_fin`, `emcee_ci`) instead of the raw five-element list, and `Model.result` / the per-slice entries of `File.results_sbs` hold it. Positional indexing (`model.result[1].params`) becomes attribute access (`model.result.par_fin.params`); an unfitted model's `result` is now `None` instead of `[]`, and a skipped MCMC yields `emcee_fin=None`. `Project.fit_2d`'s per-file stand-in result is a real (minimal) `lmfit` `MinimizerResult` instead of a `SimpleNamespace`. In the same naming consolidation, `fitlib.plt_fit_res_1d`'s `par_init` parameter is now `par_ini`, matching the `par_ini`/`par_fin` field pair. The persisted `SavedFitSlot` record and all `FitResults` accessors are unchanged.
- `fitlib.results_to_df` and `fitlib.results_to_fit_2d` are pure conversions: the CSV writes, per-parameter plotting, and `save_df`/`save_2d` flags were removed along with the legacy save path that used them.

### Removed

- **Breaking: `Project.auto_export`, `Project.path_results`, and `File.model_path`.** With fits no longer writing, the auto-export toggle and the second output-root convention are gone; a `project.yaml` that still sets `auto_export:` or `path_results:` fails loudly at load with migration guidance. `fitlib.fit_wrapper` loses its `save_output` / `save_path` / `num_fmt` / `delim` parameters and the CSV/TXT dump block; its emcee figures are display-only (`show_output`), reproducible later via `plot_mcmc`.
- **Breaking: `File.save_sbs_fit` / `File.save_2d_fit`** (deprecated since the export pipeline landed) and their internal `_save_*_fit_legacy` implementations. Use `File.export_fit` / `Project.export_fits`.

## [0.13.0] - 2026-07-13

### Added
Expand Down
10 changes: 1 addition & 9 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
# Active Plan

No active multi-step feature in progress.

- Backlog lives in [`TODO.md`](TODO.md).
- Shipped-feature design notes live in [`docs/design/`](docs/design/) (and
archived deep-dives in [`docs/design/archive/`](docs/design/archive/)).

Populate this file when starting the next multi-step feature; clear it on
completion per `CLAUDE.md` (archive the design into `docs/design/` if the
decisions are durable, otherwise let the changelog stand as the record).
No active multi-step feature in progress. See `TODO.md` for longer-term goals.
Loading