Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3af0f23
layout work for fit-saving branch
InfinityMonkeyAtWork Apr 30, 2026
d3a344c
updating path for saved individual files
InfinityMonkeyAtWork Apr 30, 2026
45a7227
in-memory fit-history layer
InfinityMonkeyAtWork May 1, 2026
b7fcb01
updating individual file fit results saving
InfinityMonkeyAtWork May 3, 2026
196c445
fit history bug fixes
InfinityMonkeyAtWork May 3, 2026
3e49d4e
update PLAN md
InfinityMonkeyAtWork May 3, 2026
0156f3b
add HDF5 fit-archive schema, writer, and identity helpers
InfinityMonkeyAtWork May 4, 2026
df2133d
fit-archive HDF5 reader + FitResults.load
InfinityMonkeyAtWork May 4, 2026
fea4e4b
Project save/load API + slot identity fix
InfinityMonkeyAtWork May 5, 2026
06e1481
Project.export_fits: slot-driven CSV/PNG export
InfinityMonkeyAtWork May 6, 2026
9b20d68
FitResults comparison API + File delegate cleanup
InfinityMonkeyAtWork May 6, 2026
6a862e6
fit-archive: round-trip + parity tests, architecture docs
InfinityMonkeyAtWork May 7, 2026
0dd1cb0
updated example restructuring plan
InfinityMonkeyAtWork May 17, 2026
614183b
Project.auto_export: gate fit-completion CSV/PNG side effects
InfinityMonkeyAtWork May 18, 2026
78c5d7f
adding example to try model comparison
InfinityMonkeyAtWork May 18, 2026
5d4615c
speeding up "not slow" test suite
InfinityMonkeyAtWork May 18, 2026
7cad267
fit-saving: stable chi2/sigma column schema + persistent File.set_sig…
InfinityMonkeyAtWork May 23, 2026
17c4c57
fit-saving pre-PR housekeeping
InfinityMonkeyAtWork May 27, 2026
4ff2cc5
update CHANGELOG and version
InfinityMonkeyAtWork May 27, 2026
bac56d1
improved model comparison example notebook
InfinityMonkeyAtWork May 28, 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ examples/simulatorBL931/

# Generated fit results (created by trspecfit)
*_fits/
# Fit archives — Project.save_fits / File.save_fit default sink is
# ./fit_results/<project>.fit.h5 (notebooks may also pass a bare filename).
*.fit.h5
/fit_results/
examples/**/fit_results/

# Generated simulated data (from simulator and ML training examples)
/simulated_data/
Expand Down
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ repos:
pass_filenames: false
stages: [pre-commit]

- id: pytest
name: pytest
entry: .venv/bin/pytest -q -m "not slow"
language: system
pass_filenames: false
stages: [pre-commit]
# - id: pytest
# name: pytest
# entry: .venv/bin/pytest -q -m "not slow"
# language: system
# pass_filenames: false
# stages: [pre-commit]

- id: nbstripout
name: nbstripout
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ 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]
## [0.9.0] - 2026-05-27

### Added

- **Fit-results archive — save, reload, and compare fits.** Every completed `File.fit_baseline` / `fit_spectrum` / `fit_slice_by_slice` / `fit_2d` is now captured as a slot in an in-memory fit history, surfaced as a read-only `FitResults` view through the new `Project.results` property (so refits no longer clobber earlier results the way `File.model_*.result` did). `Project.save_fits(path)` persists the current snapshot to a self-contained HDF5 archive (default `./fit_results/<project>.fit.h5`; stores raw data, per-slot `observed` + `fit` on the actual fit grid, metrics, and full fit-view identity), and `FitResults.load(path)` / `Project.load_fits(path)` reload it — the former with no live `Project` needed. `FitResults` (now a top-level export alongside `File`, `Project`, `Simulator`, `PlotConfig`) provides `.find()`, `.get()`, `.files()`, `.models()`, `.compare_models()`, and `.plot_residuals()`; `File.compare_models()` is kept as per-file sugar. `Project.export_fits()` / `File.export_fit()` give a one-way CSV + PNG dump of the current fits. v1 archives fit *outputs* and metrics, not a rehydratable model graph; project-scoped joint-fit slots are deferred.
- **σ-calibrated fit metrics and `File.set_sigma()`.** Each fit now computes and stores `chi2_raw`, `chi2_red_raw`, `chi2`, `chi2_red`, `r2`, `aic`, and `bic` (per-slice for Slice-by-Slice; a single value otherwise). Raw lmfit-unweighted diagnostics are always named `chi2_raw` / `chi2_red_raw`; the σ-calibrated values (`≈ 1` for a fit at the noise floor) are always named `chi2` / `chi2_red` and are `NaN` unless a noise sigma was set. `File.set_sigma(...)` records forward-looking file noise state (also inheritable from flat `project.yaml` defaults) that materializes into each saved slot; requesting calibrated metrics with no sigma set raises with a pointer to `set_sigma` / the raw metric name.
- **`Project.auto_export` toggle**: gates the automatic CSV/PNG side effects of `File.fit_baseline` / `fit_spectrum` / `fit_slice_by_slice` / `fit_2d` and `Project.fit_2d`. Default `True` preserves existing behavior; set `project.auto_export = False` (or `auto_export: false` in `project.yaml`) to suppress fit-completion writes for parameter sweeps, ML training-data generation, and real-time-fitting workflows. Explicit `File.export_fit` / `Project.export_fits` / `Project.save_fits` always write regardless of the flag. The legacy `save_baseline_fit` / `save_spectrum_fit` / `_save_sbs_fit_legacy` / `_save_2d_fit_legacy` auto-calls and the `fit_wrapper(save_output=...)` CSV dumps are gated by `auto_export`. The in-fit `plt_fit_res_1d` plot calls are skipped entirely (not just suppressed) when neither saving nor showing is wanted — important for SbS where rendering each per-slice figure is non-trivial work. A small pure helper `utils.plot._save_img_flag(save=..., show=...)` maps already-decided booleans onto the legacy `save_img` int, keeping the save/show decisions explicit at each call site rather than hidden inside a Project method.
- **Slice-by-Slice parallelism**: `File.fit_slice_by_slice()` accepts an `n_workers` keyword argument that dispatches per-slice fits across a `ProcessPoolExecutor` using the `spawn` start method (only portable option — Windows lacks `fork`). Default is `os.cpu_count() - 1`, capped at the number of slices. Set `n_workers=1` to keep the original serial path as a debug escape hatch. Workers reuse one pickled model installed at startup, render plots with the non-interactive Agg backend, and report progress via `tqdm`. On Linux/macOS spawn startup is a few hundred ms per worker; on Windows ~1-2s per worker, so very small fits (~< 20 slices) usually want `n_workers=1`. SbS seeding is now explicit too: `seed_source` chooses the shared template (`"model"`, `"baseline"`, or `"explicit"`), and `seed_adapt` controls the optional per-slice x0 tweak (`None` or `"argmax_shift"`).
- `Model`, `Component`, and `Par` are now pickleable (and therefore deep-copyable) via `__getstate__` / `__setstate__`. This enables `copy.deepcopy(model)` and lets live models cross process boundaries, which unblocks future multiprocessing workflows and fixes latent MCMC parallelism (see `Fixed`). Pickled instances are for short-lived transfer, not persistence — parent back-references (`parent_file`, `parent_model`) and transient fit state (`const`, `args`) are nulled.
- Example workflow `10_model_comparison/` walks the full save / load / compare loop end to end: fit two competing models at baseline, Slice-by-Slice, and 2D levels, rank them with `File.compare_models()` (including `sbs_aggregation` modes), persist with `File.save_fit()`, and reload + re-compare via `FitResults.load()` with no live `Project`.

### Changed

Expand All @@ -28,6 +32,7 @@ This file is maintained using the shared changelog workflow in

- **MCMC `workers > 1`**: `lmfit.emcee(workers=N)` via `ulmfit.MC(workers=N)` previously failed with `TypeError: cannot pickle 'module' object` because the residual closure carried a live module reference. The pickleable-model work plus the `spec_lib` removal close both sources of the error; MCMC parallel sampling now works end-to-end.
- **Cross-component expressions across the pickle boundary**: `Model.__getstate__` nulled `parent_model` on every `Par`, which broke `Par._evaluate_dynamic_expression` because it resolves expression references through `parent_model.get_all_parameters()`. Any model whose expression on one Par references a `t_vary` or `p_vary` Par on a different component (e.g. roundtrip family F12) raised `NameError` after unpickling. `Model.__setstate__` now rewires the intra-Model `parent_model` back-refs (Components, Pars, and any attached `Par.t_model` / `Par.p_model` sub-Models) from `self`, so `lmfit.emcee(workers > 1)` and `fit_slice_by_slice(n_workers > 1)` work on those models too.
- **Profiled parameters with convolution (IRF) dynamics on the compiled fast path**: a profiled parameter whose time-dynamics included a convolution (e.g. `MonoExpPosIRF`) failed to lower through the GIR `schedule_2d` backend. The convolution-chain walk now resolves to the underlying parameter node correctly, so profile-param IRF dynamics fit through the compiled evaluator instead of falling back.

## [0.8.0] - 2026-04-20

Expand Down
9 changes: 7 additions & 2 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Active Plan

No active multi-step feature work. See [TODO.md](TODO.md) for the long-term
backlog and `docs/design/archive/` for completed plans.
No active multi-step feature in progress.

This file holds the working plan for active, multi-step feature work (see
`CLAUDE.md` → "Persistent State Management"). When a feature completes, its
plan is archived under `docs/design/archive/` and this file is cleared.

Most recently archived: [Fit Results Save/Load](docs/design/archive/fit_results_save_load_plan.md).
10 changes: 8 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

## Fitting

- [ ] **Fit results save/load**: HDF5 output for fit results, `File.load_fit()` to restore, `File.compare_models()` for model comparison. Keep project/global-fit outputs separate from true file-level fits so users do not assume identical per-file fields/statistics.
- [ ] **Mismatched initial guesses**: round-trip tests — one each for basic, profile, profile+dynamics.

Note: `fitlib.py` hardcodes `__lnsigma` value/min/max for MCMC sampling — make configurable via `mc_settings` if users need it.

## Noise and simulation

- [ ] **Simulator noise-language cleanup**: align simulator docs/metadata with the fit-results noise schema. Keep simulator `noise_type` meaning "noise distribution / random generator" (`gaussian`, `poisson`, `none`), not sigma shape. Fix the stale `Simulator.set_noise_type()` docstring that mentions `uniform`; clarify `detection` vs. `noise_type` vs. `noise_level`; and, for analog Gaussian simulations, consider saving the derived `sigma_data = noise_level * max(abs(clean_data))` alongside existing metadata. For parameter sweeps, store derived `sigma_data` per configuration when it depends on each clean dataset.
- [ ] **Future `sigma_type` expansion in FitResults**: after the first constant, user-supplied sigma schema lands, extend uncertainty handling beyond scalar `sigma_data`. Keep `noise_type` for the statistical assumption/distribution and use `sigma_type` for sigma shape: initially `constant`, later `per_spectrum` and `per_point`. Add HDF5 storage, validation, baseline/SBS/2D alignment, `compare_models()` behavior, and tests for vector/matrix sigma. Defer automatic Poisson-derived sigma until residual-space variance propagation is explicit.

## Performance & architecture

- [ ] **Project-level fit backend**: `Project.fit_2d()` already supports `Project`/`File`/`Static` vary levels, but it currently evaluates through `fit_project_mcp()` and `Model.create_value_2d()` rather than the GIR scheduler/evaluator path. Decide whether to lower the multi-file residual to GIR or explicitly prefer project-managed per-file loops when we want maximum graph-IR speedups.
Expand All @@ -23,6 +27,7 @@ Note: `fitlib.py` hardcodes `__lnsigma` value/min/max for MCMC sampling — make
- [ ] **Document API tiers**: add a short guide that separates stable user API (`Project`, `File`, `Simulator`, `PlotConfig`), advanced public API (`mcp.Model`, `Component`, `Par`, `ParameterSweep`, `MC`), and internal implementation modules (`graph_ir`, `eval_1d`, `eval_2d`, low-level parsing/HDF5 helpers). Use this as the source of truth for docs, tests, examples, and AI-agent guidance.
- [ ] **Add tool-neutral agent orientation**: add `AGENTS.md` or `docs/ai/agent-orientation.md` pointing agents to `CLAUDE.md`, `TODO.md`, `PLAN.md`, `docs/design/repo_architecture.md`, supported-model docs, common commands, and API-change guardrails. Keep it concise so any LLM can quickly find the intended workflow and repo boundaries.
- [ ] **Add more AI-friendly task recipes**: extend `docs/ai/` with checklists for common repo changes, such as adding YAML syntax, adding plotting options, changing fitting workflows, modifying GIR/evaluator behavior, extending save/load fields, and preparing a release.
- [ ] **Upgrade example organization**: reorganize notebooks around user workflow tracks (data preparation, single-file fitting, multi-file fitting, synthetic data) and update the docs navigation accordingly. See [docs/design/examples_upgrade.md](docs/design/examples_upgrade.md).
- [ ] **Add minimal runnable workflow examples**: supplement notebooks with small script-like examples or docs snippets for the canonical public workflows: load data, load a model, set limits, fit baseline, fit 2D, inspect results, simulate data, and run a parameter sweep.
- [ ] **Improve public validation errors**: make user-facing errors state what failed, where it failed (file/model/component/parameter when applicable), and what the user or agent should change next. Prioritize YAML parsing, model loading, fit setup, and unsupported-model fallback paths.
- [ ] **Tighten public type hints and aliases**: reduce ambiguous `Any` on public APIs, document key aliases such as `ModelRef`, and keep return types crisp for IDEs, Pyright, generated docs, and LLM code navigation.
Expand All @@ -31,4 +36,5 @@ Note: `fitlib.py` hardcodes `__lnsigma` value/min/max for MCMC sampling — make
## Build & release

- [ ] **Automate tagging and pushing**: automate `git tag v1.2.3` + `git push v1.2.3` as part of the release workflow.
- [ ] **Remove legacy/backwards-compat code**: before v1.0.0 release, audit codebase for legacy fallbacks and backwards compatibility shims and consider removing.
- [ ] **Remove legacy/backwards-compat code**: before v1.0.0 release, audit codebase for legacy fallbacks and backwards compatibility shims and consider removing. Known shims slated for removal:
- `File.save_sbs_fit` / `File.save_2d_fit` (deprecated wrappers — replace internal callers and drop the public methods plus their `_save_*_fit_legacy` impls; users should migrate to `File.export_fit(fit_type=...)`).
Loading