From 2a1ebcde83e5ce12b0336ec17be178867fcd9bfa Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 13:39:54 +0200 Subject: [PATCH 01/38] Propose minimizer input/output split ADR --- .../minimizer-input-output-split.md | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split.md b/docs/dev/adrs/suggestions/minimizer-input-output-split.md new file mode 100644 index 000000000..6ba9278c1 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split.md @@ -0,0 +1,332 @@ +# ADR: Minimizer Input/Output Split + +**Status:** Proposed **Date:** 2026-05-24 + +## Status Note + +This proposal revisits and supersedes Alternative D in +[`minimizer-category-consolidation.md`](../accepted/minimizer-category-consolidation.md) +("Strict input-only `minimizer` plus a separate `fit_result`"), which +was rejected during the consolidation work on the assumption that the +input/output mix on `analysis.minimizer` was symmetric with the +input/output mix on `Parameter`. Implementation experience shows that +analogy does not hold and the mix has produced measurable UX and +duplication problems documented below. + +## Context + +After +[`minimizer-category-consolidation.md`](../accepted/minimizer-category-consolidation.md) +landed, `analysis.minimizer` holds both writable user inputs and +fit-filled outputs in a single namespace. A user typing +`analysis.minimizer.help()` before any fit sees roughly twenty +properties, the majority of which are `None`, `0`, or empty strings, +with no signal which they may write and which the fit fills in. + +The current shape on the live category surfaces: + +- **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, + `burn_in_steps`, `thinning_interval`, `population_size`, + `parallel_workers`, `initialization_method`, `random_seed`, + `credible_interval_inner`, `credible_interval_outer` (Bayesian). +- **Fit-filled outputs (no public setter, only `_set_*` internals):** + `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, + `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, + `correlation_available`, `runtime_seconds`, `iterations_performed`, + `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, + `sampler_completed`, `acceptance_rate_mean`, `gelman_rubin_max`, + `effective_sample_size_min`, `best_log_posterior` (Bayesian). + +Three of the output fields already overlap with `analysis.fit_result`: + +| Concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | +| ------- | ----------------------------- | ------------------------------ | +| Wall time | `runtime_seconds` | `fitting_time` | +| Iteration count | `iterations_performed` (LSQ) | `iterations` | +| χ² | `objective_value` | `reduced_chi_square` | + +So a reader who wants "how long did the fit take" must already pick +between two places. The current layout has both **input/output mixed +inside `minimizer`** and **output duplication between `minimizer` and +`fit_result`**. + +The consolidation ADR's two-line argument for keeping inputs and outputs +together was: + +1. **Symmetry with `Parameter`.** A `Parameter` holds both its + user-set initial value and its refined value plus uncertainty. +2. **One-place discoverability** > strict purity. + +The symmetry argument does not actually transfer. +`Parameter.value` and `Parameter.uncertainty` describe the *same scalar +quantity* before and after refinement; they share a name, semantics, +and lifecycle. `minimizer.sampling_steps` (a user request) and +`minimizer.gelman_rubin_max` (a diagnostic the sampler reports) are +about completely different things and only share a namespace because +the consolidation ADR put them there. "One-place" is also already +broken: scalar fit outputs are split across `minimizer`, `fit_result`, +`fit_parameters`, and `fit_parameter_correlations` today. + +## Decision + +### 1. Split `analysis.minimizer` into inputs and outputs + +`analysis.minimizer` keeps only **writable user settings**. The +fit-filled output fields move to `analysis.fit_result`, which also +becomes a switchable category that swaps in lockstep with the +minimizer's family so each family can declare its own output schema. + +After this ADR: + +| Category | Role | Family | Writable | +| -------- | ---- | ------ | -------- | +| `analysis.minimizer` | user-supplied settings | Family A (already) | yes | +| `analysis.fit_result` | scalar fit outputs | Family A (new) | no (internal `_set_*` only) | +| `analysis.fit_parameters` | per-parameter snapshots and posterior summary rows | (loop, unchanged) | no | +| `analysis.fit_parameter_correlations` | upper-triangle correlation rows | (loop, unchanged) | no | + +The output split is paired by minimizer family: + +- `analysis.minimizer = LmfitLeastsqMinimizer` ↔ + `analysis.fit_result = LeastSquaresFitResult` +- `analysis.minimizer = BumpsDreamMinimizer` ↔ + `analysis.fit_result = BayesianFitResult` + +Both swap together when `analysis.minimizer.type` changes, via a single +`Analysis._swap_minimizer` hook that replaces both instances at once. +This preserves the consolidation ADR's "no `_bayesian_*` mirror" +guarantee — there is exactly one output category, not seven — while +making the input/output boundary unambiguous. + +### 2. Field assignments + +**`analysis.minimizer` after the split** (writable settings only): + +- LSQ: `max_iterations`. +- Bayesian: `sampling_steps`, `burn_in_steps`, `thinning_interval`, + `population_size`, `parallel_workers`, `initialization_method`, + `random_seed`, `credible_interval_inner`, `credible_interval_outer`. + +**`analysis.fit_result` after the split** (outputs only). Common fields +live on `FitResultBase`; family-specific fields on the concrete classes: + +- `FitResultBase`: `success`, `message`, `iterations`, `fitting_time`, + `reduced_chi_square`, `result_kind`. +- `LeastSquaresFitResult` adds: `objective_name`, `objective_value`, + `n_data_points`, `n_parameters`, `n_free_parameters`, + `degrees_of_freedom`, `covariance_available`, `correlation_available`, + `exit_reason`. +- `BayesianFitResult` adds: `point_estimate_name`, `sampler_completed`, + `acceptance_rate_mean`, `gelman_rubin_max`, + `effective_sample_size_min`, `best_log_posterior`. + +The three overlapping pairs from §"Context" are resolved by **dropping +the `minimizer` copy** and keeping the `fit_result` copy: + +- `minimizer.runtime_seconds` removed; `fit_result.fitting_time` is the + single source. +- `minimizer.iterations_performed` removed; + `fit_result.iterations` is the single source. +- `minimizer.objective_value` removed; + `fit_result.reduced_chi_square` is the single source (or the + per-family `LeastSquaresFitResult.objective_value` if the raw, + un-normalised value matters separately). + +### 3. CIF layout follows the Python split + +The `_minimizer.*` block becomes settings-only. A new `_fit_result.*` +block (already present today for the common header fields) absorbs +every fit output. The set of `_fit_result.*` tags depends on the active +`_minimizer.type`, matching the same shape-shifting convention that +`_minimizer.*` itself already uses per +[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md). + +Example deterministic fit: + +``` +_minimizer.type 'lmfit (leastsq)' +_minimizer.max_iterations 1000 + +_fit_result.result_kind deterministic +_fit_result.success true +_fit_result.message converged +_fit_result.iterations 87 +_fit_result.fitting_time 12.34 +_fit_result.reduced_chi_square 1.42 +_fit_result.objective_name chi_square +_fit_result.objective_value 1532.4 +_fit_result.n_data_points 1024 +_fit_result.n_parameters 12 +_fit_result.n_free_parameters 8 +_fit_result.degrees_of_freedom 1016 +_fit_result.covariance_available true +_fit_result.correlation_available true +_fit_result.exit_reason converged +``` + +Example Bayesian fit: + +``` +_minimizer.type 'bumps (dream)' +_minimizer.sampling_steps 3000 +_minimizer.burn_in_steps 600 +_minimizer.thinning_interval 1 +_minimizer.population_size 4 +_minimizer.parallel_workers 0 +_minimizer.initialization_method latin_hypercube +_minimizer.random_seed ? +_minimizer.credible_interval_inner 0.68 +_minimizer.credible_interval_outer 0.95 + +_fit_result.result_kind bayesian +_fit_result.success true +_fit_result.message 'sampler converged' +_fit_result.iterations 3000 +_fit_result.fitting_time 124.7 +_fit_result.reduced_chi_square 1.18 +_fit_result.point_estimate_name best_sample +_fit_result.sampler_completed true +_fit_result.acceptance_rate_mean 0.27 +_fit_result.gelman_rubin_max 1.03 +_fit_result.effective_sample_size_min 482 +_fit_result.best_log_posterior -1234.56 +``` + +### 4. The runtime `analysis.fit_results` object is the same data shaped differently + +`analysis.fit_results` (plural, runtime) is the rich `FitResults` / +`BayesianFitResults` Python object that holds posterior samples, +predictive summaries, raw engine results, and reporting helpers. This +ADR does **not** rename it. After the split it remains the +"give-me-everything" accessor; `analysis.fit_result.*` (singular, CIF +category) holds the persisted scalar projection of the same fit. The +naming pair stays as today. + +A small UX win is added: `analysis.show_fit_summary()` prints settings +and outputs side-by-side, so users do not have to mentally join the +two categories. The method lives on `Analysis`, reads +`self.minimizer.*` and `self.fit_result.*`, and prints one table. + +### 5. Help and discoverability + +`analysis.minimizer.help()` after the split lists ~7 properties for +Bayesian and 1 for LSQ — every one writable. The "is this writable" +question disappears. + +`analysis.fit_result.help()` lists 6 common output properties plus the +family-specific ones, all clearly read-only. + +### 6. No new selector wiring + +`analysis.minimizer.type` remains the single user-facing selector. The +swap hook updates **both** `analysis.minimizer` and +`analysis.fit_result` instances atomically (via +`Analysis._swap_minimizer`). The new `fit_result` switchable does +**not** expose its own `type` property — there is no scenario where the +user would set `fit_result.type` independently of `minimizer.type`, and +hiding the selector keeps the convention "one minimizer concept, one +user-facing type" intact. + +## Consequences + +### Positive + +- **Clear writable surface.** `analysis.minimizer.help()` shows only + settings. Inputs and outputs no longer mix in one namespace. +- **Single source for every output field.** The three current + duplications (`runtime_seconds`/`fitting_time`, + `iterations_performed`/`iterations`, `objective_value`/ + `reduced_chi_square`) collapse to one location each. +- **Family-specific outputs have a natural home.** Currently + `minimizer.gelman_rubin_max` lives on the Bayesian minimizer class; + after the split it lives on the paired `BayesianFitResult` class. + The two categories pair symmetrically and emcee inherits the pattern + for free. +- **CIF stays compact.** No new CIF blocks beyond `_fit_result.*` which + is already present. The settings/outputs split is reflected in the + CIF tag prefix. +- **The "are we done with a fit?" check becomes simple.** + `bool(analysis.fit_result.success.value)` answers it directly without + scanning a mixed input/output namespace. + +### Trade-offs + +- **Settings and matching outputs are two-place reads.** Mitigation: + `analysis.show_fit_summary()` presents both. The current layout + already requires multi-place reads; this just makes the rule + consistent. +- **`fit_result` becomes a switchable category.** Switchable-category + cost is small (one factory + one swap hook on the owner), and the + swap is co-triggered by `minimizer.type` so no second selector + appears to the user. +- **Saved CIF files from the post-consolidation layout cannot load + unchanged.** Beta posture (no legacy shims) applies. Tutorial + fixtures regenerate via `pixi run script-tests`. Tutorial `ed-24` + already carries a narrow archive normaliser; the new layout would + extend it once. +- **Reopens a decision from a recently accepted ADR.** Documented + explicitly above in §"Status Note". + +### ADRs amended by this ADR + +- [`minimizer-category-consolidation.md`](../accepted/minimizer-category-consolidation.md) + — §1 ("Unified `minimizer` category replaces all sampler-input and + fit-result categories") becomes a partial rule: the unified + `minimizer` holds inputs; outputs move to the paired `fit_result`. + §"Alternatives Considered → D" updated to record the + reversal and the implementation evidence that prompted it. +- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) + — §"Minimizer fit projection" rewritten to describe the split + (`_minimizer.*` settings-only, `_fit_result.*` outputs including + family-specific fields). +- [`runtime-fit-results.md`](../accepted/runtime-fit-results.md) + — closing paragraph references this ADR alongside the existing two. + +## Deferred Work + +- **Renaming `analysis.fit_results` (plural runtime object).** The + plural/singular pair is mildly confusing but the rename has wide + blast radius (tests, tutorials, every BayesianFitResults reference). + Track separately if the confusion remains after `show_fit_summary()` + lands. +- **`fit_result` switchable beyond Family A.** This ADR introduces a + paired switch only for the minimizer. If future categories grow the + same input/output asymmetry (e.g. extinction, peak), apply the same + pattern then; do not generalise pre-emptively. +- **CIF compatibility helper for ID 35 archive.** The + `_normalize_id35_archive_for_tutorial` helper in `ed-24.py` already + has a roadmap to deletion; the new CIF layout extends the rename map + one more line. No new architecture decision needed. + +## Alternatives Considered + +### A. Keep current mixed-category layout, fix only the duplications + +Drop `minimizer.runtime_seconds`, `.iterations_performed`, +`.objective_value` and route every reader to `fit_result.*`. Rejected +because it leaves the input/output mix on `minimizer` intact and +therefore does not fix the `minimizer.help()` discoverability problem. + +### B. Mark fields with metadata, keep one category + +Add an `is_input: bool` marker to each descriptor and have +`minimizer.help()` group inputs vs outputs in display. Rejected because +it ships the structural problem unchanged — the CIF still mixes both +under `_minimizer.*`, the duplications with `fit_result` remain, and +the `_set_*` vs writable-setter split is still ad-hoc. + +### C. Move outputs into the runtime `fit_results` object, not a CIF category + +Persist only settings in CIF; outputs live in `analysis.fit_results` at +runtime and `analysis/results.h5` on disk. Rejected because the small +scalar outputs (success, χ², runtime, R̂) are exactly what users want to +read from CIF without unpacking HDF5, and the consolidation ADR +explicitly puts them in CIF (`_minimizer.*` today). + +### D. Rename `fit_result` to mirror minimizer (`minimizer_result`) + +Make the pairing rule explicit in the name (`` and `_result`). +Rejected because the recently-accepted +[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +ADR deliberately drops `_type` and other suffixes from category names; +adding `_result` walks the convention back. From 520585acf748f4d49b14bc26c3b3a5a5632be139 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 13:52:05 +0200 Subject: [PATCH 02/38] Address review 1 findings on minimizer input/output split --- .../minimizer-input-output-split.md | 172 +++++++++++++----- .../minimizer-input-output-split_reply-1.md | 134 ++++++++++++++ .../minimizer-input-output-split_review-1.md | 19 ++ 3 files changed, 279 insertions(+), 46 deletions(-) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split.md b/docs/dev/adrs/suggestions/minimizer-input-output-split.md index 6ba9278c1..5d4120e3b 100644 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split.md +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split.md @@ -72,28 +72,56 @@ broken: scalar fit outputs are split across `minimizer`, `fit_result`, ### 1. Split `analysis.minimizer` into inputs and outputs `analysis.minimizer` keeps only **writable user settings**. The -fit-filled output fields move to `analysis.fit_result`, which also -becomes a switchable category that swaps in lockstep with the -minimizer's family so each family can declare its own output schema. +fit-filled output fields move to `analysis.fit_result`, which gets a +class hierarchy parallel to `minimizer` so each minimizer family can +declare its own output schema. After this ADR: -| Category | Role | Family | Writable | -| -------- | ---- | ------ | -------- | -| `analysis.minimizer` | user-supplied settings | Family A (already) | yes | -| `analysis.fit_result` | scalar fit outputs | Family A (new) | no (internal `_set_*` only) | -| `analysis.fit_parameters` | per-parameter snapshots and posterior summary rows | (loop, unchanged) | no | -| `analysis.fit_parameter_correlations` | upper-triangle correlation rows | (loop, unchanged) | no | - -The output split is paired by minimizer family: - -- `analysis.minimizer = LmfitLeastsqMinimizer` ↔ - `analysis.fit_result = LeastSquaresFitResult` -- `analysis.minimizer = BumpsDreamMinimizer` ↔ - `analysis.fit_result = BayesianFitResult` +| Category | Role | Writable | +| -------- | ---- | -------- | +| `analysis.minimizer` | user-supplied settings | yes | +| `analysis.fit_result` | scalar fit outputs | no (internal `_set_*` only) | +| `analysis.fit_parameters` | per-parameter snapshots and posterior summary rows | no | +| `analysis.fit_parameter_correlations` | upper-triangle correlation rows | no | + +**`fit_result` is not a user-facing switchable category.** It is an +internal projection paired with the active `minimizer`. It does not +expose `fit_result.type` or `fit_result.show_supported()`; the only way +the user changes the active `fit_result` class is by setting +`analysis.minimizer.type`, which the owner's `_swap_minimizer` hook +uses to instantiate both `self._minimizer` and `self._fit_result` +atomically. This is an explicit, documented exception to the global +selector contract from +[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +§1 because there is no user choice involved at the `fit_result` level +— the minimizer family fully determines the result schema. See the +new exception text added to that ADR (listed under §"ADRs amended"). + +**Family mapping is one-to-one between minimizer family and result +class.** Every minimizer registered under +`MinimizerTypeEnum` maps to exactly one `FitResult` concrete class +according to its family: + +| `MinimizerTypeEnum` member | Minimizer family | Paired `FitResult` class | +| -------------------------- | ---------------- | ------------------------ | +| `LMFIT` | LSQ | `LeastSquaresFitResult` | +| `LMFIT_LEASTSQ` | LSQ | `LeastSquaresFitResult` | +| `LMFIT_LEAST_SQUARES` | LSQ | `LeastSquaresFitResult` | +| `DFOLS` | LSQ | `LeastSquaresFitResult` | +| `BUMPS` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_LM` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_AMOEBA` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_DE` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_DREAM` | Bayesian | `BayesianFitResult` | +| `EMCEE` *(when added)* | Bayesian | `BayesianFitResult` | + +The pairing rule is encoded once on the minimizer base classes +(`LeastSquaresMinimizerBase._fit_result_class = LeastSquaresFitResult`, +`BayesianMinimizerBase._fit_result_class = BayesianFitResult`) so +`_swap_minimizer` reads the paired class off the new minimizer instance +and does not need a per-tag dispatch. -Both swap together when `analysis.minimizer.type` changes, via a single -`Analysis._swap_minimizer` hook that replaces both instances at once. This preserves the consolidation ADR's "no `_bayesian_*` mirror" guarantee — there is exactly one output category, not seven — while making the input/output boundary unambiguous. @@ -107,6 +135,23 @@ making the input/output boundary unambiguous. `population_size`, `parallel_workers`, `initialization_method`, `random_seed`, `credible_interval_inner`, `credible_interval_outer`. +`credible_interval_inner` and `credible_interval_outer` are +**promoted from output-only to writable input** by this ADR. Today +they have only an internal `_set_*` and are written from +`_store_posterior_fit_projection`, which makes them effectively +hard-coded to `0.68` / `0.95`. After the split, the user sets them +before `analysis.fit()`; the Bayesian posterior-summary path reads +the two values when generating the per-parameter interval columns +(`posterior_interval_68_low/high`, `posterior_interval_95_low/high`). +The column names stay numeric (`68`, `95`) for backwards compatibility +with deterministic-fit rows that have empty values there; mismatching +user-supplied levels (e.g. `credible_interval_inner = 0.5`) are +warned about at fit time so the column names do not silently lie. + +A separate suggestion ADR can later generalise the interval column +naming (e.g. `posterior_interval_low_`); that is deferred work +and not in scope here. + **`analysis.fit_result` after the split** (outputs only). Common fields live on `FitResultBase`; family-specific fields on the concrete classes: @@ -127,10 +172,15 @@ the `minimizer` copy** and keeping the `fit_result` copy: single source. - `minimizer.iterations_performed` removed; `fit_result.iterations` is the single source. -- `minimizer.objective_value` removed; - `fit_result.reduced_chi_square` is the single source (or the - per-family `LeastSquaresFitResult.objective_value` if the raw, - un-normalised value matters separately). +- `minimizer.objective_value` removed. `LeastSquaresFitResult` keeps + **two distinct fields**: `objective_value` (raw χ² returned by the + minimizer's objective function) and `reduced_chi_square` (= χ² / + `degrees_of_freedom`). They are not duplicates; the unreduced value + is what the solver actually optimises and is useful for diagnostics + on small-dof fits, while the reduced value is what every user-facing + table and plot displays. `BayesianFitResult` does not carry + `objective_value` because the Bayesian engine optimises the log + posterior rather than χ² directly. ### 3. CIF layout follows the Python split @@ -202,10 +252,14 @@ ADR does **not** rename it. After the split it remains the category) holds the persisted scalar projection of the same fit. The naming pair stays as today. -A small UX win is added: `analysis.show_fit_summary()` prints settings -and outputs side-by-side, so users do not have to mentally join the -two categories. The method lives on `Analysis`, reads -`self.minimizer.*` and `self.fit_result.*`, and prints one table. +A small UX win is added under the accepted display facade +([`display-ux.md`](../accepted/display-ux.md)): the existing +`project.display.fit.results()` entry point gains a "Settings used" +table above the existing results tables, populated from +`analysis.minimizer.*`. No new `Analysis`-level display method is +added; the user-facing surface stays exactly where the display ADR +put it. Internally the helper reads `self.minimizer.*` and +`self.fit_result.*` and renders one combined view. ### 5. Help and discoverability @@ -221,11 +275,15 @@ family-specific ones, all clearly read-only. `analysis.minimizer.type` remains the single user-facing selector. The swap hook updates **both** `analysis.minimizer` and `analysis.fit_result` instances atomically (via -`Analysis._swap_minimizer`). The new `fit_result` switchable does -**not** expose its own `type` property — there is no scenario where the -user would set `fit_result.type` independently of `minimizer.type`, and -hiding the selector keeps the convention "one minimizer concept, one -user-facing type" intact. +`Analysis._swap_minimizer`, which reads the paired +`_fit_result_class` off the new minimizer base). The paired +`fit_result` is not a user-facing switchable: there is no +`fit_result.type` and no `fit_result.show_supported()`, per the +documented exception added to +[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +(see §"ADRs amended"). This keeps "one minimizer concept, one +user-facing type" intact and removes the temptation to swap the result +class independently of the minimizer. ## Consequences @@ -233,10 +291,11 @@ user-facing type" intact. - **Clear writable surface.** `analysis.minimizer.help()` shows only settings. Inputs and outputs no longer mix in one namespace. -- **Single source for every output field.** The three current - duplications (`runtime_seconds`/`fitting_time`, - `iterations_performed`/`iterations`, `objective_value`/ - `reduced_chi_square`) collapse to one location each. +- **Single source for every output field.** The two current real + duplications (`runtime_seconds`/`fitting_time` and + `iterations_performed`/`iterations`) collapse to one location each. + The `objective_value`/`reduced_chi_square` pair is **not** a + duplication and both stay (see §2 for the distinction). - **Family-specific outputs have a natural home.** Currently `minimizer.gelman_rubin_max` lives on the Bayesian minimizer class; after the split it lives on the paired `BayesianFitResult` class. @@ -252,13 +311,15 @@ user-facing type" intact. ### Trade-offs - **Settings and matching outputs are two-place reads.** Mitigation: - `analysis.show_fit_summary()` presents both. The current layout + `project.display.fit.results()` presents both. The current layout already requires multi-place reads; this just makes the rule consistent. -- **`fit_result` becomes a switchable category.** Switchable-category - cost is small (one factory + one swap hook on the owner), and the - swap is co-triggered by `minimizer.type` so no second selector - appears to the user. +- **`fit_result` becomes an internally-paired category.** The pairing + cost is small (one paired-instance assignment in the existing + `_swap_minimizer` hook) and is invisible to the user — there is no + second `fit_result.type` selector. The exception to the global + selector contract is documented explicitly in §"ADRs amended" + alongside the selector ADR. - **Saved CIF files from the post-consolidation layout cannot load unchanged.** Beta posture (no legacy shims) applies. Tutorial fixtures regenerate via `pixi run script-tests`. Tutorial `ed-24` @@ -281,18 +342,37 @@ user-facing type" intact. family-specific fields). - [`runtime-fit-results.md`](../accepted/runtime-fit-results.md) — closing paragraph references this ADR alongside the existing two. +- [`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) + — §1 ("The category owns its selector") gains a paragraph carving + out one documented exception: a category that is fully determined by + another category's `type` (today only `fit_result`, derived from + `minimizer.type`) is allowed to omit `category.type` and + `category.show_supported()`. The mechanism is described in §1 of + this ADR. The user-facing selector convention is otherwise unchanged. +- [`display-ux.md`](../accepted/display-ux.md) — §"Fit results display" + expanded to mention that `project.display.fit.results()` now prints + a "Settings used" block above the result tables, sourced from + `analysis.minimizer.*`. No new public entry point is added. ## Deferred Work - **Renaming `analysis.fit_results` (plural runtime object).** The plural/singular pair is mildly confusing but the rename has wide blast radius (tests, tutorials, every BayesianFitResults reference). - Track separately if the confusion remains after `show_fit_summary()` - lands. -- **`fit_result` switchable beyond Family A.** This ADR introduces a - paired switch only for the minimizer. If future categories grow the - same input/output asymmetry (e.g. extinction, peak), apply the same - pattern then; do not generalise pre-emptively. + Track separately if the confusion remains after the combined + display lands. +- **Paired internal categories beyond `minimizer` / `fit_result`.** + This ADR introduces the paired pattern for the minimizer only. If + future categories grow the same input/output asymmetry (e.g. + extinction, peak), apply the same pattern then; do not generalise + pre-emptively. +- **Generalising the posterior-interval column naming.** Today the + per-parameter posterior summary uses fixed numeric column names + (`posterior_interval_68_low`, etc.). Promoting + `credible_interval_inner/outer` to user settings raises the + question of whether the column names should follow. Deferred to a + separate suggestion ADR so this proposal stays focused on the + input/output split. - **CIF compatibility helper for ID 35 archive.** The `_normalize_id35_archive_for_tutorial` helper in `ed-24.py` already has a roadmap to deletion; the new CIF layout extends the rename map diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md new file mode 100644 index 000000000..1fcc4842a --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md @@ -0,0 +1,134 @@ +# Reply to Review 1: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-1.md`](minimizer-input-output-split_review-1.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +All five findings agreed. Each was addressed by editing the ADR text; +no item is deferred. The ADR is updated in the same commit as this +reply. + +## Findings + +### Finding 1 — Switchable category contract violation + +**Verdict: agree.** The original text used the defined term +"switchable category" (per the accepted selector ADR) but then said +`fit_result` would not expose `type` or `show_supported()` — which is +precisely what the selector contract requires. The selector ADR was +also missing from the amended-ADR list. + +**Action taken.** Rewrote §1 so the paired result class is described +as an **internal projection paired with the active minimizer**, not as +a switchable category. Explicit text: "It does not expose +`fit_result.type` or `fit_result.show_supported()`; the only way the +user changes the active `fit_result` class is by setting +`analysis.minimizer.type`." The exception to the global selector +contract is now documented in two places: (a) §1 of this ADR ("This is +an explicit, documented exception …"); (b) the new amended-ADR entry +for `switchable-category-owned-selectors.md`, which records the carve- +out: a category whose active class is fully determined by another +category's `type` may omit `type` and `show_supported()`. §6 ("No new +selector wiring") and §"Trade-offs" updated to match the new wording. + +**Plan section:** §1 (rewritten), §6 (updated), §"Trade-offs" +(updated), §"ADRs amended" (added `switchable-category-owned-selectors.md`). + +### Finding 2 — `objective_value`/`reduced_chi_square` inconsistency + +**Verdict: agree.** The original text said "drop +`minimizer.objective_value`" with `reduced_chi_square` as the single +source, but the LSQ field list and the deterministic CIF example +included **both** — leaving an implementer free to drop or keep +`objective_value` and both claim ADR compliance. + +**Action taken.** Rewrote the resolution bullet in §2 to state +explicitly that `objective_value` (raw χ²) and `reduced_chi_square` +(= χ² / `degrees_of_freedom`) are **distinct fields**, both kept on +`LeastSquaresFitResult`. The raw value is what the solver optimises +and is useful for diagnostics on small-dof fits; the reduced value is +what user-facing tables and plots display. `BayesianFitResult` does +not carry `objective_value` because the Bayesian engine optimises the +log posterior. The Positive-consequences bullet about "duplications" +no longer claims this pair was a duplication; only the +`runtime_seconds` and `iterations` pairs are real duplications, and +the bullet now says so. + +**Plan section:** §2 (resolution bullet rewritten), §"Positive" +(bullet adjusted). + +### Finding 3 — `credible_interval_*` semantics + +**Verdict: agree.** The ADR listed +`credible_interval_inner/credible_interval_outer` as current writable +inputs and kept them on the settings-only `minimizer`, but in the +live code they have only internal `_set_*` and live in +`_result_descriptor_names`. The "settings-only minimizer" boundary +needed a clear answer for these two fields. + +**Action taken.** Added a paragraph to §2 making the promotion +explicit: the two interval levels are **promoted from output-only to +writable input** by this ADR. They are set before `analysis.fit()`; +the Bayesian posterior-summary path reads them at fit time and +generates the per-parameter interval columns from those levels. +Documented that the column names stay numeric (`68`, `95`) for now — +mismatching user levels are warned about at fit time so the names do +not silently lie — and added a Deferred-Work entry to track a later +ADR that may generalise the column naming. + +**Plan section:** §2 (new paragraph), §"Deferred Work" (new entry). + +### Finding 4 — `analysis.show_fit_summary()` bypasses display facade + +**Verdict: agree.** The accepted Display UX ADR moves user-facing fit +reporting to `project.display.fit.results()`. Adding a new +`analysis.show_fit_summary()` would reintroduce exactly the split that +ADR cleaned up, and `display-ux.md` was missing from the amended-ADR +list. + +**Action taken.** Removed the new `analysis.show_fit_summary()` +method entirely. Replaced it with an extension to the existing +`project.display.fit.results()` entry point: it now prints a "Settings +used" block above the result tables, populated from +`analysis.minimizer.*`. No new public entry point is added. Added +`display-ux.md` to the amended-ADR list with a sentence describing +exactly this extension. §4 (runtime-object section) and +§"Trade-offs" updated to reference the display-facade method instead. + +**Plan section:** §4 (updated paragraph), §"Trade-offs" (updated), +§"ADRs amended" (added `display-ux.md`). + +### Finding 5 — Pairing table only names two classes + +**Verdict: agree.** The original §1 paired `LmfitLeastsqMinimizer ↔ +LeastSquaresFitResult` and `BumpsDreamMinimizer ↔ BayesianFitResult` +as examples. With nine `MinimizerTypeEnum` members today plus emcee +on the horizon, that left the swap/load behaviour underspecified for +non-default deterministic minimizers. + +**Action taken.** Added a full mapping table to §1 listing every +current `MinimizerTypeEnum` member with its family and paired result +class, plus a row for `EMCEE` (when added). Added a sentence +documenting that the pairing rule is encoded once on the minimizer +base classes (`LeastSquaresMinimizerBase._fit_result_class = +LeastSquaresFitResult`, `BayesianMinimizerBase._fit_result_class = +BayesianFitResult`) so the swap hook reads the paired class off the +new minimizer instance and does not need a per-tag dispatch. + +**Plan section:** §1 (new mapping table + pairing-rule paragraph). + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — ADR updated per all five findings above. +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md`](minimizer-input-output-split_reply-1.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md new file mode 100644 index 000000000..5e0bb1550 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md @@ -0,0 +1,19 @@ +# Review 1: Minimizer Input/Output Split + +## Findings + +1. **High — The ADR creates a switchable category that violates the accepted selector contract.** The proposal says `analysis.fit_result` "becomes a switchable category" that swaps with `analysis.minimizer` (`minimizer-input-output-split.md:74-77`, `:95-99`), but later says the new `fit_result` switchable does **not** expose its own `type` property (`:221-228`). That conflicts with the accepted switchable-category contract, which says every in-scope switchable category exposes `category.type` and `category.show_supported()` as the whole public selector surface, with one `_.type` CIF identity tag (`../accepted/switchable-category-owned-selectors.md:82-105`, `:124-149`, `:367-372`). The ADR does not list that accepted ADR under "ADRs amended by this ADR", so an implementer has no authoritative rule for whether `fit_result` is a normal switchable category, an internal paired projection factory, or a new exception to the global selector convention. Please either avoid calling it a switchable category and specify the internal pairing mechanism, or explicitly amend the selector ADR with a paired-output-category exception including the Python and CIF surface. + +2. **High — The `objective_value`/`reduced_chi_square` decision is internally inconsistent.** The context identifies `minimizer.objective_value` and `fit_result.reduced_chi_square` as the duplicated χ² concept (`minimizer-input-output-split.md:40-46`), and the decision says the split drops `minimizer.objective_value` while `fit_result.reduced_chi_square` becomes the single source, "or" `LeastSquaresFitResult.objective_value` remains if the raw un-normalised value matters (`:123-133`). But the field list and deterministic CIF example include both `reduced_chi_square` and `objective_value` on `fit_result` (`:113-118`, `:150-158`). That leaves the key persistence/API question undecided: if `objective_value` is a distinct raw objective, it is not a duplicate and should be documented as such; if it is the same χ² concept, it should be removed from `LeastSquaresFitResult` and the CIF example. As written, two implementers could make opposite choices and both claim to follow the ADR. + +3. **Medium — The ADR treats credible interval levels as writable minimizer settings, but the current implementation treats them as fit-filled projection fields.** The ADR lists `credible_interval_inner` and `credible_interval_outer` as current writable inputs and keeps them on settings-only `analysis.minimizer` after the split (`minimizer-input-output-split.md:28-31`, `:103-108`). In the live category, those properties have only internal `_set_credible_interval_*` methods and are included in `_result_descriptor_names`, while `_setting_descriptor_names` excludes them (`src/easydiffraction/analysis/categories/minimizer/bayesian_base.py:42-66`, `:347-366`). If the intent is to turn summary interval levels into user settings, the ADR should say so explicitly and describe how they affect posterior summary generation; otherwise they belong with outputs or another summary-configuration category. Without that decision, the "settings-only minimizer" boundary is still ambiguous for Bayesian summaries. + +4. **Medium — `analysis.show_fit_summary()` bypasses the accepted display facade.** The ADR adds a new public `analysis.show_fit_summary()` method that prints settings and outputs side-by-side (`minimizer-input-output-split.md:205-208`, `:254-257`). The accepted Display UX ADR moved user-facing fit reporting to `project.display.fit.results()` and moved `project.analysis.display.fit_results()` out of the primary public API (`../accepted/display-ux.md:42-46`, `:66-101`, `:190-201`). Adding a new analysis-level display method reintroduces the split that ADR removed. Please either place the summary under the accepted display facade, or list `display-ux.md` as amended and explain why fit summaries are an exception. + +5. **Medium — The fit-result pairing table only names two minimizer classes even though the active minimizer set is larger.** The ADR says the output split is "paired by minimizer family" but lists only `LmfitLeastsqMinimizer` and `BumpsDreamMinimizer` (`minimizer-input-output-split.md:88-93`). The current minimizer enum has nine tags, including several non-default least-squares classes (`src/easydiffraction/analysis/minimizers/enums.py:13-21`). The ADR should state whether every least-squares minimizer maps to `LeastSquaresFitResult` and every Bayesian minimizer maps to `BayesianFitResult`, or provide an explicit per-tag mapping. Otherwise the swap/load behavior for non-default deterministic minimizers is underspecified. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 93ddceaa1b2723009f23d50eba5273cfb3b2bd30 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 13:58:46 +0200 Subject: [PATCH 03/38] Address review 2: demote credible intervals; clarify context table --- .../minimizer-input-output-split.md | 68 +++---- .../minimizer-input-output-split_reply-2.md | 175 ++++++++++++++++++ .../minimizer-input-output-split_review-2.md | 21 +++ 3 files changed, 230 insertions(+), 34 deletions(-) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split.md b/docs/dev/adrs/suggestions/minimizer-input-output-split.md index 5d4120e3b..30fd50feb 100644 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split.md +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split.md @@ -37,18 +37,20 @@ The current shape on the live category surfaces: `sampler_completed`, `acceptance_rate_mean`, `gelman_rubin_max`, `effective_sample_size_min`, `best_log_posterior` (Bayesian). -Three of the output fields already overlap with `analysis.fit_result`: +Three current output fields straddle `analysis.minimizer` and +`analysis.fit_result`: -| Concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | -| ------- | ----------------------------- | ------------------------------ | -| Wall time | `runtime_seconds` | `fitting_time` | -| Iteration count | `iterations_performed` (LSQ) | `iterations` | -| χ² | `objective_value` | `reduced_chi_square` | +| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | +| -------------- | ----------------------------- | ------------------------------ | ------------ | +| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | +| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | +| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | So a reader who wants "how long did the fit take" must already pick between two places. The current layout has both **input/output mixed -inside `minimizer`** and **output duplication between `minimizer` and -`fit_result`**. +inside `minimizer`** and **fit-output content split across `minimizer` +and `fit_result`** (whether the two scalars per row are the same value +or not). §2 resolves each row above explicitly. The consolidation ADR's two-line argument for keeping inputs and outputs together was: @@ -133,24 +135,20 @@ making the input/output boundary unambiguous. - LSQ: `max_iterations`. - Bayesian: `sampling_steps`, `burn_in_steps`, `thinning_interval`, `population_size`, `parallel_workers`, `initialization_method`, - `random_seed`, `credible_interval_inner`, `credible_interval_outer`. - -`credible_interval_inner` and `credible_interval_outer` are -**promoted from output-only to writable input** by this ADR. Today -they have only an internal `_set_*` and are written from -`_store_posterior_fit_projection`, which makes them effectively -hard-coded to `0.68` / `0.95`. After the split, the user sets them -before `analysis.fit()`; the Bayesian posterior-summary path reads -the two values when generating the per-parameter interval columns -(`posterior_interval_68_low/high`, `posterior_interval_95_low/high`). -The column names stay numeric (`68`, `95`) for backwards compatibility -with deterministic-fit rows that have empty values there; mismatching -user-supplied levels (e.g. `credible_interval_inner = 0.5`) are -warned about at fit time so the column names do not silently lie. + `random_seed`. -A separate suggestion ADR can later generalise the interval column -naming (e.g. `posterior_interval_low_`); that is deferred work -and not in scope here. +`credible_interval_inner` and `credible_interval_outer` **stay on the +output side** in this ADR, attached to `BayesianFitResult` (see +below). They are persisted with the fixed values `0.68` and `0.95`, +matching the per-parameter interval columns +(`posterior_interval_68_low/high`, `posterior_interval_95_low/high`). +Promoting the levels to user-writable settings would let the user +choose a 50% interval that then gets persisted under a column named +`posterior_interval_68_low` — a data-integrity problem rather than a +UX problem. A future suggestion ADR can promote them to settings and +generalise the column naming (e.g. `posterior_interval_low_`) +in one combined change; doing one without the other is unsafe and +out of scope here. **`analysis.fit_result` after the split** (outputs only). Common fields live on `FitResultBase`; family-specific fields on the concrete classes: @@ -162,6 +160,7 @@ live on `FitResultBase`; family-specific fields on the concrete classes: `degrees_of_freedom`, `covariance_available`, `correlation_available`, `exit_reason`. - `BayesianFitResult` adds: `point_estimate_name`, `sampler_completed`, + `credible_interval_inner`, `credible_interval_outer`, `acceptance_rate_mean`, `gelman_rubin_max`, `effective_sample_size_min`, `best_log_posterior`. @@ -225,8 +224,6 @@ _minimizer.population_size 4 _minimizer.parallel_workers 0 _minimizer.initialization_method latin_hypercube _minimizer.random_seed ? -_minimizer.credible_interval_inner 0.68 -_minimizer.credible_interval_outer 0.95 _fit_result.result_kind bayesian _fit_result.success true @@ -236,6 +233,8 @@ _fit_result.fitting_time 124.7 _fit_result.reduced_chi_square 1.18 _fit_result.point_estimate_name best_sample _fit_result.sampler_completed true +_fit_result.credible_interval_inner 0.68 +_fit_result.credible_interval_outer 0.95 _fit_result.acceptance_rate_mean 0.27 _fit_result.gelman_rubin_max 1.03 _fit_result.effective_sample_size_min 482 @@ -366,13 +365,14 @@ class independently of the minimizer. future categories grow the same input/output asymmetry (e.g. extinction, peak), apply the same pattern then; do not generalise pre-emptively. -- **Generalising the posterior-interval column naming.** Today the - per-parameter posterior summary uses fixed numeric column names - (`posterior_interval_68_low`, etc.). Promoting - `credible_interval_inner/outer` to user settings raises the - question of whether the column names should follow. Deferred to a - separate suggestion ADR so this proposal stays focused on the - input/output split. +- **User-configurable credible-interval levels.** The two interval + levels currently stay at the hardcoded `0.68` / `0.95`, matching the + fixed per-parameter column names (`posterior_interval_68_low` etc.). + Promoting the levels to user settings requires generalising the + column naming at the same time to avoid the data-integrity hole + where a `0.50` level lands in a column called + `posterior_interval_68_low`. Both pieces belong in a follow-on ADR + so this proposal stays focused on the input/output split. - **CIF compatibility helper for ID 35 archive.** The `_normalize_id35_archive_for_tutorial` helper in `ed-24.py` already has a roadmap to deletion; the new CIF layout extends the rename map diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md new file mode 100644 index 000000000..f79add35a --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md @@ -0,0 +1,175 @@ +# Reply to Review 2: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-2.md`](minimizer-input-output-split_review-2.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Context note on the review + +The review header records that `_reply-1.md` was not visible when +review 2 was prepared, only the direct edits to the ADR. After +auditing the current ADR with `git grep` against each of the cited +phrases, three of the four findings (F1, F3-part, F4) reference text +that no longer appears in the file — they describe the pre-reply-1 +shape of the ADR. F2 is a genuinely new and substantive concern; the +half of F3 that talks about the §Context table phrasing is also a +real issue worth addressing. + +Each finding is handled below: F2 and the §Context half of F3 trigger +real ADR edits; F1, F4, and the §Consequences half of F3 are +confirmed-already-addressed with current line numbers cited. + +## Findings + +### Finding 1 — `fit_result` switchable inconsistency + +**Verdict: partial — substantively resolved in reply 1; documenting +current state here.** + +The review cites lines that the reply-1 edits already replaced. +Current ADR state (verified by `grep -n switchable +docs/dev/adrs/suggestions/minimizer-input-output-split.md`): + +- Line 88: "**`fit_result` is not a user-facing switchable + category.**" +- Line 96: §1 cross-references + `switchable-category-owned-selectors.md` for the documented + exception. +- Line 280-282: §6 reiterates "The paired `fit_result` is not a + user-facing switchable: there is no `fit_result.type` and no + `fit_result.show_supported()`". +- Line 345: `switchable-category-owned-selectors.md` is listed under + "ADRs amended" with the explicit exception text: + > §1 ("The category owns its selector") gains a paragraph carving + > out one documented exception: a category that is fully determined + > by another category's `type` (today only `fit_result`, derived + > from `minimizer.type`) is allowed to omit `category.type` and + > `category.show_supported()`. + +No remaining text calls `fit_result` a switchable category, and the +amended-ADR list does include the selector ADR. The cited line +numbers (`:269-278`, `:302-311`, `:342-345`, `:320-333`) do not match +the corresponding ranges in the current file. No further ADR change. + +**Plan section:** §1 (lines 80–127), §6 (lines 273–286), §"ADRs +amended" (lines 339–357) — all carry the reply-1 edits. + +### Finding 2 — Configurable credible-interval levels would mislabel persisted columns + +**Verdict: agree — data-integrity concern, real and substantive.** + +Reply 1 promoted `credible_interval_inner` / `credible_interval_outer` +to user-writable settings while keeping the fixed +`posterior_interval_68_low/high` / `posterior_interval_95_low/high` +per-parameter column names. The reviewer correctly observes that this +allows, for example, a user-set `0.50` interval to be persisted under +a column named `posterior_interval_68_low`. That is a data-integrity +hole, not a UX warning surface. + +**Action taken.** Demoted the two interval-level fields back to the +output side. They now live on `BayesianFitResult` (alongside +`acceptance_rate_mean`, `gelman_rubin_max`, etc.) with the fixed +values `0.68` and `0.95`, matching the column names. The §2 paragraph +that introduced the promotion is rewritten to explain the choice +explicitly: + +> Promoting the levels to user-writable settings would let the user +> choose a 50% interval that then gets persisted under a column named +> `posterior_interval_68_low` — a data-integrity problem rather than a +> UX problem. A future suggestion ADR can promote them to settings +> and generalise the column naming (e.g. +> `posterior_interval_low_`) in one combined change; doing one +> without the other is unsafe and out of scope here. + +The Bayesian field list in §2 moved the two fields from `minimizer` +to `BayesianFitResult`. The Bayesian CIF example in §3 moved the two +`_minimizer.credible_interval_*` lines to `_fit_result.*`. The +Deferred-Work entry was rewritten to make the combined level+naming +follow-on explicit. + +This resolves the only "settings-only minimizer" boundary question +that survived reply 1 in the opposite direction from reply 1, taking +the "or another summary-configuration category" branch that review 1 +F3 originally offered. + +**Plan section:** §2 (lines 131–151), §3 Bayesian example +(lines 217–240), §"Deferred Work" (interval-naming entry). + +### Finding 3 — `objective_value` / `reduced_chi_square` framing + +**Verdict: agree on the §Context phrasing; already addressed in +§Consequences.** + +The §Consequences "Positive" bullet was rewritten in reply 1 to say +explicitly that the `objective_value`/`reduced_chi_square` pair is +**not** a duplication (current lines 294–298 — verified by `grep -n +"real duplications"`). That half is already correct. + +The §Context table did still call all three rows an "overlap" without +distinguishing real duplications from cross-category misplacement, +which is genuinely inconsistent with the §2 clarification. The +reviewer is right to flag it. + +**Action taken.** Rewrote the §Context table to add a fourth +"Relationship" column that classifies each row: + +- Wall time → real duplication +- Iteration count → real duplication +- Objective vs reduced χ² → cross-category misplacement (two related + but distinct scalars where the raw χ² sits on `minimizer` instead + of with the rest of the fit outputs) + +The surrounding sentence now reads "fit-output content split across +`minimizer` and `fit_result` (whether the two scalars per row are the +same value or not). §2 resolves each row above explicitly." This +removes the implicit "all three are duplicates" claim while keeping +the table as the entry point for the resolution in §2. + +**Plan section:** §Context (lines 40–55), §"Positive" bullet +(lines 294–298 — unchanged, already correct). + +### Finding 4 — `analysis.show_fit_summary()` bypasses display facade + +**Verdict: partial — substantively resolved in reply 1; documenting +current state here.** + +Reply 1 removed the new `analysis.show_fit_summary()` method and +extended the existing `project.display.fit.results()` entry point +instead. Current ADR state: + +- Lines 255–262: §4 says "A small UX win is added under the accepted + display facade ([`display-ux.md`](../accepted/display-ux.md)): the + existing `project.display.fit.results()` entry point gains a + 'Settings used' table above the existing results tables, populated + from `analysis.minimizer.*`. No new `Analysis`-level display method + is added". +- Line 313–314: §"Trade-offs" cites `project.display.fit.results()` + as the mitigation (not `analysis.show_fit_summary()`). +- Lines 352–356: `display-ux.md` is listed under "ADRs amended" with + the explicit extension text. + +`grep -n show_fit_summary +docs/dev/adrs/suggestions/minimizer-input-output-split.md` returns +zero hits. The cited line numbers (`:255-258`, `:304-307`, +`:320-333`) do not match the corresponding ranges in the current +file. No further ADR change. + +**Plan section:** §4 (lines 245–262), §"Trade-offs" (line 313), +§"ADRs amended" (lines 352–356) — all carry the reply-1 edits. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — ADR updated for F2 (demote credible intervals back to outputs) and + the §Context half of F3 (table phrasing). +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md`](minimizer-input-output-split_reply-2.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md new file mode 100644 index 000000000..257c188f1 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md @@ -0,0 +1,21 @@ +# Review 2: Minimizer Input/Output Split + +Context: no `minimizer-input-output-split_reply-1.md` file was present. +This review covers the direct edits made to +`minimizer-input-output-split.md` after review 1. + +## Findings + +1. **High — The `fit_result` selector exception is still internally inconsistent and not actually wired into the amended-ADR list.** The updated text says `fit_result` is not a user-facing switchable category, and claims this is an explicit exception to `switchable-category-owned-selectors.md` with new exception text added there and listed under "ADRs amended" (`minimizer-input-output-split.md:88-99`). But the same ADR still calls it "the new `fit_result` switchable" and says "`fit_result` becomes a switchable category" in later sections (`:269-278`, `:302-311`, `:342-345`), while the "ADRs amended" list still omits `switchable-category-owned-selectors.md` (`:320-333`). That leaves the core architectural contract unresolved: is `fit_result` a switchable category with an exception, or an internal paired projection that should not use switchable terminology? Please make the terminology consistent and either add the actual selector-ADR amendment/list entry or remove the claim that it has been amended. + +2. **High — Configurable credible interval levels make the persisted `_fit_parameter` interval columns misleading.** The update promotes `credible_interval_inner` and `credible_interval_outer` to user-writable settings, but keeps the persisted per-parameter columns named `posterior_interval_68_*` and `posterior_interval_95_*` even when the user chooses different levels; the ADR only says to warn at fit time (`minimizer-input-output-split.md:138-153`). That means a saved CIF can contain, for example, a 50% interval in a column named `posterior_interval_68_low`, which is a data-integrity problem rather than just a UX warning. Please either constrain those settings to the two fixed levels until generalized column names are designed, or include the generalized persistence shape in this ADR so the saved column names match the values they contain. + +3. **Medium — `objective_value` and `reduced_chi_square` are still described as duplicates after the ADR now says they are distinct fields.** The decision now says `LeastSquaresFitResult.objective_value` is raw χ² and `FitResultBase.reduced_chi_square` is χ² divided by degrees of freedom, and explicitly says they are not duplicates (`minimizer-input-output-split.md:175-183`). However the context still lists `minimizer.objective_value` vs `fit_result.reduced_chi_square` as the overlapping χ² concept (`:40-46`), and the consequences still say the `objective_value`/`reduced_chi_square` duplication collapses to one location (`:284-289`). Please revise the context/consequences so the duplication being fixed is the old cross-category placement, not a claim that raw objective and reduced χ² are the same scalar. + +4. **Medium — `analysis.show_fit_summary()` still bypasses the accepted display facade.** The ADR still adds an analysis-level display method and uses it as the mitigation for two-place reads (`minimizer-input-output-split.md:255-258`, `:304-307`). The accepted Display UX ADR makes `project.display` the user-facing display facade and puts fit reporting under `project.display.fit.results()` (`../accepted/display-ux.md:42-46`, `:66-101`, `:190-201`), and this ADR still does not list `display-ux.md` as amended (`minimizer-input-output-split.md:320-333`). Please move this summary to the accepted display facade or explicitly amend the display ADR. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From e946d2f59aacf2d0b195ca350c80532928dfaebc Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:00:37 +0200 Subject: [PATCH 04/38] Address review 3: fix context list for credible-interval fields --- .../minimizer-input-output-split.md | 10 +- .../minimizer-input-output-split_reply-3.md | 128 ++++++++++++++++++ .../minimizer-input-output-split_review-3.md | 19 +++ 3 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split.md b/docs/dev/adrs/suggestions/minimizer-input-output-split.md index 30fd50feb..b6dbe78bd 100644 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split.md +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split.md @@ -27,15 +27,17 @@ The current shape on the live category surfaces: - **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, `burn_in_steps`, `thinning_interval`, `population_size`, - `parallel_workers`, `initialization_method`, `random_seed`, - `credible_interval_inner`, `credible_interval_outer` (Bayesian). + `parallel_workers`, `initialization_method`, `random_seed` + (Bayesian). - **Fit-filled outputs (no public setter, only `_set_*` internals):** `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, `correlation_available`, `runtime_seconds`, `iterations_performed`, `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, - `sampler_completed`, `acceptance_rate_mean`, `gelman_rubin_max`, - `effective_sample_size_min`, `best_log_posterior` (Bayesian). + `sampler_completed`, `credible_interval_inner`, + `credible_interval_outer`, `acceptance_rate_mean`, + `gelman_rubin_max`, `effective_sample_size_min`, + `best_log_posterior` (Bayesian). Three current output fields straddle `analysis.minimizer` and `analysis.fit_result`: diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md new file mode 100644 index 000000000..cefd1926f --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md @@ -0,0 +1,128 @@ +# Reply to Review 3: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-3.md`](minimizer-input-output-split_review-3.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Context note on the review + +The review header again records that the preceding reply +(`_reply-2.md`) was not visible. After auditing the current ADR +against each finding, F1 and F3 reference text that was already +revised in reply-2; the cited line numbers no longer correspond to +text matching the complaint. F2 is a real bug introduced in the +original draft and never fixed by either reply — the §Context list +incorrectly attributes user-writable status to the credible-interval +levels that the live code exposes as outputs. + +F2 triggers an ADR edit. F1 and F3 are confirmed already-addressed +with current line numbers and quoted text. + +## Findings + +### Finding 1 — Writable arbitrary credible interval levels persist into misleading column names + +**Verdict: partial — substantively resolved in reply 2.** + +The review reads the ADR as still promoting the credible-interval +levels to user settings. The reply-2 edits demoted them back to the +output side, attached to `BayesianFitResult`. Current ADR state +(verified by `grep -n credible_interval +docs/dev/adrs/suggestions/minimizer-input-output-split.md`): + +- Line 140: §2 paragraph reads "`credible_interval_inner` and + `credible_interval_outer` **stay on the output side** in this ADR, + attached to `BayesianFitResult` (see below)". +- Line 163: `BayesianFitResult` field list includes + `credible_interval_inner`, `credible_interval_outer`. +- Lines 236–237: the Bayesian CIF example shows + `_fit_result.credible_interval_inner 0.68` and + `_fit_result.credible_interval_outer 0.95` (no longer under + `_minimizer.*`). +- §"Deferred Work" carries the combined level+naming follow-on: + > Promoting the levels to user settings requires generalising the + > column naming at the same time to avoid the data-integrity hole + > where a `0.50` level lands in a column called + > `posterior_interval_68_low`. Both pieces belong in a follow-on + > ADR so this proposal stays focused on the input/output split. + +The data-integrity hole the reviewer is concerned about cannot occur +under the current ADR text — the levels are fixed at `0.68` / `0.95`, +matching the column names. The cited line range (`:138-153`) maps to +a paragraph that, in the current file, says the opposite of what the +review attributes to it. No further ADR change. + +**Plan section:** §2 (lines 138–151), §3 Bayesian CIF example +(lines 236–237), §"Deferred Work". + +### Finding 2 — Context list still describes credible-interval levels as currently writable + +**Verdict: agree — real bug, dating from the original draft.** + +The §Context list at lines 26–37 enumerates the **current live** +shape of `analysis.minimizer`. Since the original draft, the +"Writable inputs" bullet has incorrectly included +`credible_interval_inner` and `credible_interval_outer`. The live +code in +[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) +exposes these only via the internal `_set_credible_interval_*` +methods and lists them under `_result_descriptor_names`, not +`_setting_descriptor_names`. Neither reply 1 nor reply 2 corrected +the §Context list itself, even after both replies adjusted the +downstream decisions. + +**Action taken.** Moved the two field names from "Writable inputs" +to "Fit-filled outputs (no public setter, only `_set_*` internals)" +in the §Context list. The §Context now accurately mirrors the live +category surface, and the rest of the ADR (which already treats them +as outputs after reply 2) reads consistently end-to-end. + +**Plan section:** §Context, lines 26–37 (one field move). + +### Finding 3 — Context still presents `objective_value` and `reduced_chi_square` as a duplicate pair + +**Verdict: partial — substantively resolved in reply 2.** + +Reply 2 rewrote the §Context table to add a fourth "Relationship" +column distinguishing real duplications from cross-category +misplacement. Current ADR state (lines 40–53): + +``` +| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | +| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | +| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | +| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | +``` + +The third row no longer claims `objective_value` and +`reduced_chi_square` are duplicates; it explicitly describes them as +two distinct scalars with one of them sitting in the wrong category. +The surrounding sentence at line 49–53 reads "fit-output content +split across `minimizer` and `fit_result` (whether the two scalars +per row are the same value or not)". §2 then resolves each row +explicitly. + +The cited line range (`:40-46`) lands inside the rewritten table; the +phrasing the reviewer attributes to those lines (a claim that all +three rows are duplicates) no longer exists in the file. No further +ADR change. + +**Plan section:** §Context table (lines 43–47), §"Positive" bullet +(lines 294–298, already correct). + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — §Context "Writable inputs" / "Fit-filled outputs" lists corrected + for the credible-interval fields (F2). +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md`](minimizer-input-output-split_reply-3.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md new file mode 100644 index 000000000..5f974e77b --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md @@ -0,0 +1,19 @@ +# Review 3: Minimizer Input/Output Split + +Context: no `minimizer-input-output-split_reply-2.md` file was present. +This review covers the direct edits made to +`minimizer-input-output-split.md` after review 2. + +## Findings + +1. **High — Writable arbitrary credible interval levels still make the persisted `_fit_parameter` columns misleading.** The ADR promotes `credible_interval_inner` and `credible_interval_outer` to user-writable settings, then says the existing fixed `posterior_interval_68_*` / `posterior_interval_95_*` column names remain even when the user picks different levels (`minimizer-input-output-split.md:138-153`). The deferred-work section explicitly postpones the column-naming generalization (`:369-375`). That means this ADR would allow saving a 50% interval in a column named `posterior_interval_68_low`, which is a persisted data-integrity problem. A fit-time warning does not fix the saved schema. Please either constrain these settings to the fixed 0.68 / 0.95 values until generalized column names are accepted, or make the generalized persistence shape part of this ADR. + +2. **Medium — The context and decision now disagree on whether credible interval levels are currently writable.** The context still lists `credible_interval_inner` and `credible_interval_outer` under the current live "Writable inputs" (`minimizer-input-output-split.md:26-32`), but the new decision text says they are "promoted from output-only to writable input" and today have only internal `_set_*` methods (`:138-142`). The latter matches the live code. Please update the context list so it does not describe the current API as already writable. + +3. **Medium — The context still presents `objective_value` and `reduced_chi_square` as a duplicate pair after the decision says they are distinct.** The context still says three output fields overlap and lists the χ² row as `minimizer.objective_value` vs `fit_result.reduced_chi_square` (`minimizer-input-output-split.md:40-46`). The decision and consequences now say raw `objective_value` and reduced χ² are not duplicates and both stay (`:175-183`, `:294-298`). Please revise the context so it separates the two real duplicates from the raw-objective-vs-reduced-χ² distinction. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 1ec867d7c900cbc1c963d688e953adab0923f007 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:01:46 +0200 Subject: [PATCH 05/38] Reply to review 4: all findings already addressed in earlier replies --- .../minimizer-input-output-split_reply-4.md | 129 ++++++++++++++++++ .../minimizer-input-output-split_review-4.md | 19 +++ 2 files changed, 148 insertions(+) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md new file mode 100644 index 000000000..3b0dc37d4 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md @@ -0,0 +1,129 @@ +# Reply to Review 4: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-4.md`](minimizer-input-output-split_review-4.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Context note on the review + +Review 4 records that it follows reply 1, but the three findings +match review 3 verbatim and reference the same pre-reply-2 text +(credible intervals "promoted" to user-writable settings; §Context +table presenting all three rows as duplicates). Reply 2 demoted the +credible intervals back to outputs, and reply 3 fixed the §Context +list and the §Context table. All three findings here describe text +that is no longer in the current ADR. + +No ADR edits are required for review 4. The current state matches the +reviewer's "or" branch in each case. Each finding is documented below +with line numbers verified by `grep` against the current file. + +## Findings + +### Finding 1 — Writable arbitrary credible interval levels + +**Verdict: confirmed addressed in reply 2 (no further change).** + +The reviewer's "or" branch reads: "Please either constrain these +settings to the fixed 0.68 / 0.95 values until generalized columns +are accepted, or make the generalized persistence shape part of this +ADR." Reply 2 took the first branch. Current ADR state (verified by +`grep -n credible_interval +docs/dev/adrs/suggestions/minimizer-input-output-split.md`): + +- Line 140: §2 paragraph reads: "`credible_interval_inner` and + `credible_interval_outer` **stay on the output side** in this ADR, + attached to `BayesianFitResult` (see below). They are persisted + with the fixed values `0.68` and `0.95`, matching the per-parameter + interval columns". +- Line 163: `BayesianFitResult` field list owns both fields. +- Lines 236–237: Bayesian CIF example shows + `_fit_result.credible_interval_inner 0.68` / + `_fit_result.credible_interval_outer 0.95` under `_fit_result.*`, + not `_minimizer.*`. +- §"Deferred Work" carries the combined level+naming follow-on, + explicitly saying doing one without the other is unsafe. + +The data-integrity hole the reviewer is concerned about cannot occur +under the current ADR — the levels are fixed at the values that match +the column names. The cited line range (`:138-153`) maps to text +saying the levels are fixed, not the prior text saying they are +user-writable. + +**Plan section:** §2 (lines 138–151), §3 Bayesian CIF example +(lines 236–237), §"Deferred Work". + +### Finding 2 — Context list still describes credible-interval levels as currently writable + +**Verdict: confirmed addressed in reply 3 (no further change).** + +Reply 3 moved `credible_interval_inner` and `credible_interval_outer` +from the "Writable inputs" bullet to the "Fit-filled outputs" bullet +in the §Context list. Current ADR state (verified by `grep -n -A 2 +"Writable inputs"`): + +- Line 28–31 "Writable inputs": `max_iterations` (LSQ); + `sampling_steps`, `burn_in_steps`, `thinning_interval`, + `population_size`, `parallel_workers`, `initialization_method`, + `random_seed` (Bayesian). +- Line 32–38 "Fit-filled outputs": …Bayesian list includes + `point_estimate_name`, `sampler_completed`, + `credible_interval_inner`, `credible_interval_outer`, + `acceptance_rate_mean`, …. + +The §Context list now matches the live code at +[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) +where the two fields appear in `_result_descriptor_names` and have +only internal `_set_*` methods. + +**Plan section:** §Context lists (lines 26–38). + +### Finding 3 — Context still presents `objective_value` and `reduced_chi_square` as a duplicate pair + +**Verdict: confirmed addressed in reply 2 (no further change).** + +Reply 2 rewrote the §Context table to add a "Relationship" column. +Current ADR state (verified by `sed -n '43,47p'`): + +``` +| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | +| -------------- | ----------------------------- | ------------------------------ | ------------ | +| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | +| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | +| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | +``` + +The third row no longer claims they are duplicates; it classifies the +relationship as cross-category misplacement of two distinct scalars. +The surrounding sentence at lines 49–53 reads "fit-output content +split across `minimizer` and `fit_result` (whether the two scalars +per row are the same value or not). §2 resolves each row above +explicitly." + +**Plan section:** §Context table (lines 43–47). + +## Process note + +This is the third consecutive review whose findings describe text +that has been edited away by an earlier reply. If the review process +is not picking up replies, the most useful next step is to confirm +each reply file is being read alongside the ADR — every reply lists +its own files-touched section with the exact paths, and reading them +in order resolves the apparent regression in each round. The ADR +text after reply 3 already takes the "or" branch the reviewer +recommends in every finding above; the next review can profitably +focus on issues not yet raised. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. No ADR edits were made in this round. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md`](minimizer-input-output-split_reply-4.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md new file mode 100644 index 000000000..93340106a --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md @@ -0,0 +1,19 @@ +# Review 4: Minimizer Input/Output Split + +Context: this review follows +`minimizer-input-output-split_reply-1.md`. The reply addresses review 1, +but the current ADR still has the issues below. + +## Findings + +1. **High — Writable arbitrary credible interval levels still make the persisted `_fit_parameter` columns misleading.** The ADR promotes `credible_interval_inner` and `credible_interval_outer` to user-writable settings, then keeps the fixed `posterior_interval_68_*` / `posterior_interval_95_*` column names even when users choose different levels (`minimizer-input-output-split.md:138-153`). The deferred-work section explicitly postpones the column-name generalization (`:369-375`). This would allow saving, for example, a 50% interval in a column named `posterior_interval_68_low`. A fit-time warning does not make the persisted schema truthful. Please either constrain these settings to the fixed 0.68 / 0.95 values until generalized columns are accepted, or make the generalized persistence shape part of this ADR. + +2. **Medium — The context and decision disagree on whether credible interval levels are currently writable.** The context still lists `credible_interval_inner` and `credible_interval_outer` under the current live "Writable inputs" (`minimizer-input-output-split.md:26-32`), but the decision says they are "promoted from output-only to writable input" and today have only internal `_set_*` methods (`:138-142`). The latter matches the live code. Please update the context list so it does not describe the current API as already writable. + +3. **Medium — The context still presents `objective_value` and `reduced_chi_square` as a duplicate pair after the decision says they are distinct.** The context still says three output fields overlap and lists the χ² row as `minimizer.objective_value` vs `fit_result.reduced_chi_square` (`minimizer-input-output-split.md:40-46`). The decision and consequences now say raw `objective_value` and reduced χ² are not duplicates and both stay (`:175-183`, `:294-298`). Please revise the context so it separates the two real duplications from the raw-objective-vs-reduced-χ² distinction. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 1a0a303dc105c02f55956f6cf5a53a4ddad7a8ab Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:07:25 +0200 Subject: [PATCH 06/38] Reply to review 5: both findings already addressed --- .../minimizer-input-output-split_reply-5.md | 96 +++++++++++++++++++ .../minimizer-input-output-split_review-5.md | 16 ++++ 2 files changed, 112 insertions(+) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md new file mode 100644 index 000000000..f82aebc95 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md @@ -0,0 +1,96 @@ +# Reply to Review 5: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-5.md`](minimizer-input-output-split_review-5.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Context note on the review + +Review 5 records that `_reply-2.md` was not visible. Both findings +repeat issues that were resolved by reply 3 (F1, §Context list move +of credible intervals from "Writable inputs" to "Fit-filled outputs") +and reply 2 (F2, §Context table gaining the "Relationship" column). +Current line numbers verified below by `sed`/`grep`. No ADR edits are +required for review 5. + +## Findings + +### Finding 1 — Context still says credible intervals are currently writable + +**Verdict: confirmed addressed in reply 3 (no further change).** + +`sed -n '26,38p' docs/dev/adrs/suggestions/minimizer-input-output-split.md` +returns: + +``` +The current shape on the live category surfaces: + +- **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, + `burn_in_steps`, `thinning_interval`, `population_size`, + `parallel_workers`, `initialization_method`, `random_seed` + (Bayesian). +- **Fit-filled outputs (no public setter, only `_set_*` internals):** + `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, + `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, + `correlation_available`, `runtime_seconds`, `iterations_performed`, + `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, + `sampler_completed`, `credible_interval_inner`, + `credible_interval_outer`, `acceptance_rate_mean`, +``` + +`credible_interval_inner` and `credible_interval_outer` appear under +"Fit-filled outputs" (line 37–38), not under "Writable inputs" +(line 28–31). This matches the live code in +[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) +where the two fields appear in `_result_descriptor_names` and expose +only internal `_set_credible_interval_*` methods. + +**Plan section:** §Context lists, lines 26–38. + +### Finding 2 — Context still presents `objective_value` / `reduced_chi_square` as a duplicate pair + +**Verdict: confirmed addressed in reply 2 (no further change).** + +`sed -n '40,47p' docs/dev/adrs/suggestions/minimizer-input-output-split.md` +returns: + +``` +Three current output fields straddle `analysis.minimizer` and +`analysis.fit_result`: + +| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | +| -------------- | ----------------------------- | ------------------------------ | ------------ | +| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | +| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | +| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | +``` + +The third row classifies the χ² pair as "Cross-category misplacement +— two related but distinct scalars", not as a duplication. The +intro sentence reads "Three current output fields straddle …", not +"Three of the output fields already overlap" as in the original. + +**Plan section:** §Context table, lines 43–47. + +## Process note + +Both findings here describe text that no longer appears in the file +after reply 2 and reply 3. If the reviewer cannot see the reply +files, the easiest signal is that every reply file lists a "Summary +of files touched" section with the exact ADR sections and line +ranges it edited; reading the most recent `_reply-N.md` alongside the +ADR resolves the apparent regression. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. No ADR edits were made in this round. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md new file mode 100644 index 000000000..b0fa6554b --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md @@ -0,0 +1,16 @@ +# Review 5: Minimizer Input/Output Split + +Context: no `minimizer-input-output-split_reply-2.md` file was present. +This review covers the direct edits made after review 4. + +## Findings + +1. **Medium — The context still says credible interval levels are currently writable inputs.** The decision now keeps `credible_interval_inner` and `credible_interval_outer` on the output side as fixed `BayesianFitResult` fields (`minimizer-input-output-split.md:138-149`, `:160-163`), which resolves the persistence risk from review 4. But the context still lists those two fields under the current live "Writable inputs" (`:26-32`). Please move them out of that current-writable list so the context matches the decision and live code. + +2. **Medium — The context still presents `objective_value` and `reduced_chi_square` as a duplicate pair after the decision says they are distinct.** The context still says three output fields overlap and lists the χ² row as `minimizer.objective_value` vs `fit_result.reduced_chi_square` (`minimizer-input-output-split.md:40-46`). The decision and consequences now say raw `objective_value` and reduced χ² are not duplicates and both stay (`:172-180`, `:291-298`). Please revise the context so it separates the two real duplications from the raw-objective-vs-reduced-χ² distinction. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 8b82af02eb0cf711c8834110fef432aae4feadca Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:08:30 +0200 Subject: [PATCH 07/38] Reply to review 6: single finding already addressed in reply 3 --- .../minimizer-input-output-split_reply-6.md | 59 +++++++++++++++++++ .../minimizer-input-output-split_review-6.md | 14 +++++ 2 files changed, 73 insertions(+) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md new file mode 100644 index 000000000..74f7e2178 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md @@ -0,0 +1,59 @@ +# Reply to Review 6: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-6.md`](minimizer-input-output-split_review-6.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Context note on the review + +Review 6 follows reply 2 (the reviewer can see that one), but reply 3 +appears to not be visible — the single finding here is exactly what +reply 3 fixed. Current ADR state verified below; no ADR edits required. + +## Findings + +### Finding 1 — Context still says credible interval levels are currently writable inputs + +**Verdict: confirmed addressed in reply 3 (no further change).** + +`sed -n '26,38p' docs/dev/adrs/suggestions/minimizer-input-output-split.md` +returns: + +``` +The current shape on the live category surfaces: + +- **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, + `burn_in_steps`, `thinning_interval`, `population_size`, + `parallel_workers`, `initialization_method`, `random_seed` + (Bayesian). +- **Fit-filled outputs (no public setter, only `_set_*` internals):** + `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, + `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, + `correlation_available`, `runtime_seconds`, `iterations_performed`, + `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, + `sampler_completed`, `credible_interval_inner`, + `credible_interval_outer`, `acceptance_rate_mean`, +``` + +`credible_interval_inner` and `credible_interval_outer` are in the +"Fit-filled outputs" bullet (Bayesian half, line 37–38), not in the +"Writable inputs" bullet (line 28–31). This matches the live code at +[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) +(`_result_descriptor_names` tuple, internal `_set_credible_interval_*` +methods). + +**Plan section:** §Context, lines 26–38. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. No ADR edits were made in this round. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md new file mode 100644 index 000000000..de3180b6d --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md @@ -0,0 +1,14 @@ +# Review 6: Minimizer Input/Output Split + +Context: this review follows +`minimizer-input-output-split_reply-2.md`. + +## Findings + +1. **Medium — The context still says credible interval levels are currently writable inputs.** The decision now keeps `credible_interval_inner` and `credible_interval_outer` on the output side as fixed `BayesianFitResult` fields (`minimizer-input-output-split.md:140-151`, `:162-165`), and the deferred-work section explicitly treats user-configurable interval levels as future work (`:368-375`). However the context still lists those two fields under the current live "Writable inputs" (`:26-32`). Please remove them from that current-writable list, or move them to the fit-filled-output list, so the motivation matches the decision and live code. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 9fdc888b21ca9b0c743183909331edffd3aca892 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:09:05 +0200 Subject: [PATCH 08/38] Reply to review 7: clean signoff, no findings --- .../minimizer-input-output-split_reply-7.md | 48 +++++++++++++++++++ .../minimizer-input-output-split_review-7.md | 21 ++++++++ 2 files changed, 69 insertions(+) create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md create mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md new file mode 100644 index 000000000..960238591 --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md @@ -0,0 +1,48 @@ +# Reply to Review 7: Minimizer Input/Output Split + +Reply to +[`minimizer-input-output-split_review-7.md`](minimizer-input-output-split_review-7.md) +for the ADR at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Verdict + +Review 7 records no findings. Acknowledged; the ADR is unchanged in +this round. + +The reviewer's summary confirms the four substantive properties of +the current ADR: + +- `analysis.minimizer` stays input-only. +- Scalar fit outputs move to the paired internal `analysis.fit_result` + projection. +- The selector-contract exception is documented (both in §1 of this + ADR and as an entry under "ADRs amended" against + `switchable-category-owned-selectors.md`). +- Credible interval levels stay on the output side at the fixed + `0.68` / `0.95` values, with the combined level+naming promotion + deferred to a follow-on ADR. +- The §Context table classifies the two real duplications (wall + time, iteration count) separately from the cross-category + misplacement of raw χ² vs reduced χ². + +## Next steps + +The ADR is ready to move from `docs/dev/adrs/suggestions/` to +`docs/dev/adrs/accepted/` whenever the maintainer decides to promote +it, at which point an implementation plan +(`docs/dev/plans/minimizer-input-output-split.md`) can be drafted +using the same slug. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. No ADR edits were made in this round. + +## Summary of files touched by this reply + +- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md`](minimizer-input-output-split_reply-7.md) + — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md new file mode 100644 index 000000000..ac35f439a --- /dev/null +++ b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md @@ -0,0 +1,21 @@ +# Review 7: Minimizer Input/Output Split + +Context: this review follows +`minimizer-input-output-split_reply-3.md`. + +## Findings + +No issues found in the current ADR. + +The current text consistently keeps `analysis.minimizer` input-only, +moves scalar outputs to the paired internal `analysis.fit_result` +projection, documents the selector-contract exception, keeps fixed +credible interval levels on the output side, and separates the two real +duplicate scalar pairs from the raw-objective-vs-reduced-chi-square +distinction. + +## Checks + +Skipped by instruction: this is a static ADR review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From b6867714631f429ae54e354438f2136d479fc166 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:18:34 +0200 Subject: [PATCH 09/38] Draft minimizer input/output split implementation plan --- .../dev/plans/minimizer-input-output-split.md | 506 ++++++++++++++++++ 1 file changed, 506 insertions(+) create mode 100644 docs/dev/plans/minimizer-input-output-split.md diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md new file mode 100644 index 000000000..81cfb06d9 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -0,0 +1,506 @@ +# Plan: Minimizer Input/Output Split + +> This plan follows +> [`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). +> No deliberate exceptions. + +## ADR + +Implements +[`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](../adrs/suggestions/minimizer-input-output-split.md). +This plan promotes that ADR from Suggestion → Accepted during +implementation (step P1.16). + +Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): + +- [`accepted/minimizer-category-consolidation.md`](../adrs/accepted/minimizer-category-consolidation.md) + — §1 becomes a partial rule; §"Alternatives Considered → D" records + the reversal. +- [`accepted/analysis-cif-fit-state.md`](../adrs/accepted/analysis-cif-fit-state.md) + — §"Minimizer fit projection" rewritten for the settings-only + `_minimizer.*` / outputs-on-`_fit_result.*` shape. +- [`accepted/runtime-fit-results.md`](../adrs/accepted/runtime-fit-results.md) + — closing paragraph references this ADR alongside the existing two. +- [`accepted/switchable-category-owned-selectors.md`](../adrs/accepted/switchable-category-owned-selectors.md) + — §1 gains the documented "fully-determined paired category" + exception. +- [`accepted/display-ux.md`](../adrs/accepted/display-ux.md) — + `project.display.fit.results()` prints a "Settings used" block. + +## Branch and PR + +- Branch: `minimizer-input-output-split` (continued from the branch + the ADR was drafted on). Do not push unless asked. +- Each step in §"Implementation steps (Phase 1)" must be staged with + explicit paths and committed locally **before** moving to the next + step. See `.github/copilot-instructions.md` → **Commits**. +- After P1.17, stop and wait for the user review gate before starting + Phase 2. + +## Decisions already made (from the ADR) + +1. `analysis.minimizer` holds **writable user settings only**. +2. `analysis.fit_result` becomes a class hierarchy paired with + `analysis.minimizer`. `FitResultBase` carries common fields; + `LeastSquaresFitResult` and `BayesianFitResult` add family-specific + ones. +3. `fit_result` is **not a user-facing switchable category**. No + `fit_result.type`, no `fit_result.show_supported()`. The owner's + `_swap_minimizer` hook installs both the minimizer and the paired + `fit_result` atomically. +4. Pairing rule is encoded on the minimizer base classes: + `LeastSquaresMinimizerBase._fit_result_class = LeastSquaresFitResult`, + `BayesianMinimizerBase._fit_result_class = BayesianFitResult`. +5. `objective_value` (raw χ²) and `reduced_chi_square` are distinct + fields, both kept on `LeastSquaresFitResult`. +6. `credible_interval_inner` / `credible_interval_outer` stay on the + output side (`BayesianFitResult`) at the fixed `0.68` / `0.95` + values. User-configurable levels deferred to a follow-on ADR. +7. The display extension lives under + `project.display.fit.results()`; no new `Analysis`-level display + method is added. +8. Beta posture: hard cutover, no shims, no deprecation warnings. + Tutorials and saved fixtures regenerate. + +## Open questions + +- **Existing `analysis.fit_result.from_cif` parameter ordering.** + After this split, the CIF restore for `_fit_result.*` must run + **after** `_minimizer.*` is read so the paired class is known + before the result descriptors load. The current `_restore_*` order + in [`serialize.py`](../../../src/easydiffraction/io/cif/serialize.py) + reads `_minimizer.*` first via `_swap_minimizer`, then iterates the + rest. P1.6 must ensure `_fit_result` is included in the iteration + only after the swap has installed the paired class. Confirm during + P1.6 implementation. +- **Posterior-summary code path that currently writes + `_set_credible_interval_*` on `minimizer`.** After P1.11, the + setters move to `BayesianFitResult`. The + `_store_posterior_fit_projection` method in + [`analysis.py`](../../../src/easydiffraction/analysis/analysis.py) + must be updated to call + `self._fit_result._set_credible_interval_inner(...)` instead of + `self.minimizer._set_*`. Verify the order of operations against the + test in + [`test_results_sidecar.py`](../../../tests/unit/easydiffraction/io/test_results_sidecar.py). + +## Concrete files likely to change + +### Created + +- `src/easydiffraction/analysis/categories/fit_result/base.py` — + rename existing `FitResult` class to `FitResultBase` (or extract a + base). Common output descriptors live here. +- `src/easydiffraction/analysis/categories/fit_result/lsq.py` — + `LeastSquaresFitResult` with LSQ-specific output descriptors. +- `src/easydiffraction/analysis/categories/fit_result/bayesian.py` — + `BayesianFitResult` with Bayesian-specific output descriptors, + including `credible_interval_inner` / `credible_interval_outer`. +- `src/easydiffraction/analysis/categories/fit_result/factory.py` — + internal-use factory keyed by minimizer family. Not exposed to the + user (no `show_supported`); used only by `Analysis._swap_minimizer`. +- `tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py` +- `tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py` +- `tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py` +- `tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py` + +### Modified + +- `src/easydiffraction/analysis/categories/fit_result/__init__.py` + — add explicit imports for every new concrete class to trigger + factory registration. +- `src/easydiffraction/analysis/categories/fit_result/default.py` + — rewritten to import from the new family modules; the existing + `FitResult` is renamed to `FitResultBase` and absorbed. +- `src/easydiffraction/analysis/categories/minimizer/base.py` — add + `_fit_result_class: ClassVar[type]` declaration. +- `src/easydiffraction/analysis/categories/minimizer/lsq_base.py` + — set `_fit_result_class = LeastSquaresFitResult`; **remove** + `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, + `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, + `correlation_available`, `runtime_seconds`, `iterations_performed`, + `exit_reason` from descriptor declarations and from + `_result_descriptor_names`. `_setting_descriptor_names` stays + `('max_iterations',)`. +- `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py` + — set `_fit_result_class = BayesianFitResult`; remove + `runtime_seconds`, `point_estimate_name`, `sampler_completed`, + `credible_interval_inner`, `credible_interval_outer`, + `acceptance_rate_mean`, `gelman_rubin_max`, + `effective_sample_size_min`, `best_log_posterior` from descriptor + declarations and from `_result_descriptor_names`. + `_setting_descriptor_names` keeps the seven Bayesian inputs. +- `src/easydiffraction/analysis/analysis.py` — extend + `_swap_minimizer` to also instantiate the paired `fit_result` via + the minimizer's `_fit_result_class`; add `analysis.fit_result` + property; route every `self._minimizer._set_*` result-writer call + in `_store_least_squares_result_projection` / + `_store_posterior_fit_projection` / + `_restore_fit_results_from_projection` to + `self._fit_result._set_*` instead. +- `src/easydiffraction/io/cif/serialize.py` — emit/read + `_fit_result.*` from the paired class; remove the removed + `_minimizer.*` output tags from the serialise/deserialise paths. + Update the legacy-tag rejection message. +- `src/easydiffraction/project/display.py` — extend + `project.display.fit.results()` to print a "Settings used" block + populated from `analysis.minimizer.*` above the existing result + tables. +- All tutorials referencing `analysis.minimizer.` + (e.g. `runtime_seconds`, `gelman_rubin_max`, `objective_value`) → + `analysis.fit_result.`. List enumerated at P1.15 + start via `git grep`. +- Tests reading `analysis.minimizer.` → migrate to + `analysis.fit_result.`. P2.1 enumerates. + +### Deleted + +- None. The existing `fit_result/default.py` is rewritten in place; + the existing `FitResult` class becomes `FitResultBase`. + +## Implementation steps (Phase 1) + +Mark `[x]` as each step lands. + +- [ ] **P1.1 — Rename `FitResult` to `FitResultBase`; keep current + common fields.** In + `src/easydiffraction/analysis/categories/fit_result/default.py`, + rename the class. The factory registration stays on the old + class name to avoid breaking the existing + `FitResultFactory.default_tag()` lookup until P1.4 introduces + the family-keyed factory. Update + `src/easydiffraction/analysis/categories/fit_result/__init__.py` + to re-export the renamed class under both the new and old name + (the old `FitResult` re-export is removed at P1.16). Commit: + `Rename FitResult to FitResultBase` + +- [ ] **P1.2 — Add `LeastSquaresFitResult` class.** New file + `src/easydiffraction/analysis/categories/fit_result/lsq.py`. + `LeastSquaresFitResult(FitResultBase)` declares: `objective_name`, + `objective_value`, `n_data_points`, `n_parameters`, + `n_free_parameters`, `degrees_of_freedom`, + `covariance_available`, `correlation_available`, `exit_reason`. + Numeric defaults follow `None` / `allow_none=True` (matching the + consolidation cleanup); bool defaults `False`; string defaults + `''`. Declare `_expected_descriptor_names`, + `_result_descriptor_names` for parity with the minimizer + hierarchy. Tests deferred to Phase 2. Commit: + `Add LeastSquaresFitResult class` + +- [ ] **P1.3 — Add `BayesianFitResult` class.** New file + `src/easydiffraction/analysis/categories/fit_result/bayesian.py`. + `BayesianFitResult(FitResultBase)` declares: + `point_estimate_name`, `sampler_completed`, + `credible_interval_inner` (default `0.68`), + `credible_interval_outer` (default `0.95`), + `acceptance_rate_mean`, `gelman_rubin_max`, + `effective_sample_size_min`, `best_log_posterior`. Declare + `_expected_descriptor_names`, `_result_descriptor_names`. + Tests deferred to Phase 2. Commit: + `Add BayesianFitResult class` + +- [ ] **P1.4 — Add `FitResultCategoryFactory`.** New file + `src/easydiffraction/analysis/categories/fit_result/factory.py`. + `FitResultCategoryFactory(FactoryBase)` registers + `FitResultBase` (default tag), `LeastSquaresFitResult`, + `BayesianFitResult`. Update + `src/easydiffraction/analysis/categories/fit_result/__init__.py` + to explicitly import every concrete class. The factory is used + only by `Analysis._swap_minimizer`; no + `show_supported`/`type`-style user surface is exposed. Commit: + `Add FitResultCategoryFactory` + +- [ ] **P1.5 — Declare `_fit_result_class` on minimizer bases.** In + `src/easydiffraction/analysis/categories/minimizer/lsq_base.py`, + add `_fit_result_class: ClassVar[type] = LeastSquaresFitResult`. + In + `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py`, + add `_fit_result_class: ClassVar[type] = BayesianFitResult`. Add + the matching declaration to + `src/easydiffraction/analysis/categories/minimizer/base.py` with + `_fit_result_class: ClassVar[type] = FitResultBase` as a safety + fallback (no concrete minimizer instantiates the bare base, but + `_swap_minimizer` reads through this attribute). Commit: + `Declare paired _fit_result_class on minimizer bases` + +- [ ] **P1.6 — Wire `Analysis._swap_minimizer` to install both + instances.** In + `src/easydiffraction/analysis/analysis.py`: + - Construct `self._fit_result = + new_minimizer._fit_result_class()` after the new minimizer is + created in `_replace_minimizer`. The old `fit_result` instance is + detached (`_parent = None`). + - Add `analysis.fit_result` read-only property. + - Wire `self._fit_result._parent = self` in + `_attach_category_parents`. + - The Analysis `__init__` constructs the initial `_fit_result` from + the default minimizer's `_fit_result_class`. + + Commit: `Wire fit_result swap to minimizer swap` + +- [ ] **P1.7 — Route LSQ result writers to `fit_result`.** In + `src/easydiffraction/analysis/analysis.py`, + `_store_least_squares_result_projection` currently writes to + `self.minimizer._set_objective_name(...)` etc. Reroute every + such call to `self.fit_result._set_*`. Same for + `_restore_fit_results_from_projection`'s LSQ branch + (it reads `self.minimizer.objective_name.value` etc. — change + to `self.fit_result..value`). Commit: + `Route LSQ result writers to fit_result` + +- [ ] **P1.8 — Route Bayesian result writers to `fit_result`.** Same + treatment for `_store_posterior_fit_projection` and the + Bayesian branch of `_restore_fit_results_from_projection`. + Includes the `_set_credible_interval_*` calls — they now target + `self.fit_result._set_credible_interval_*`. Commit: + `Route Bayesian result writers to fit_result` + +- [ ] **P1.9 — Remove duplicate fields from LSQ minimizer base.** In + `src/easydiffraction/analysis/categories/minimizer/lsq_base.py`, + delete the descriptor declarations and properties for + `optimizer_name`, `method_name`, `objective_name`, + `objective_value`, `n_data_points`, `n_parameters`, + `n_free_parameters`, `degrees_of_freedom`, + `covariance_available`, `correlation_available`, + `runtime_seconds`, `iterations_performed`, `exit_reason`. Remove + these names from `_expected_descriptor_names` and + `_result_descriptor_names`. `_setting_descriptor_names` is + already `('max_iterations',)` and stays. + + Note: `optimizer_name` and `method_name` were already removed by + the consolidation work (`_engine_metadata` dict replaces them); + this step is the bulk removal of the remaining LSQ outputs. + + Commit: `Remove LSQ output descriptors from minimizer base` + +- [ ] **P1.10 — Remove duplicate fields from Bayesian minimizer + base.** In + `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py`, + delete the descriptor declarations and properties for + `runtime_seconds`, `point_estimate_name`, `sampler_completed`, + `credible_interval_inner`, `credible_interval_outer`, + `acceptance_rate_mean`, `gelman_rubin_max`, + `effective_sample_size_min`, `best_log_posterior`. Remove these + names from `_expected_descriptor_names` and + `_result_descriptor_names`. The `_setting_descriptor_names` + tuple keeps the seven Bayesian inputs. + + Commit: `Remove Bayesian output descriptors from minimizer base` + +- [ ] **P1.11 — Update CIF emit/read for the split.** In + `src/easydiffraction/io/cif/serialize.py`: + - `_minimizer.*` emit/read continues to handle settings only (the + minimizer category's `from_cif` walks its remaining descriptors). + - Add `_fit_result.*` emit/read after `_minimizer.*` so the paired + class is known before the result descriptors load. The order is + enforced by `Analysis._serializable_categories()` putting + `self.fit_result` directly after `self.minimizer`. + - Update the legacy-tag rejection message in + `_raise_for_legacy_analysis_tags` to include the now-removed + `_minimizer.` tags (e.g. + `_minimizer.runtime_seconds`, `_minimizer.gelman_rubin_max`) as + legacy markers that should raise a clear error rather than load + silently. + + Commit: `Serialize fit outputs to _fit_result.* tags` + +- [ ] **P1.12 — Update `Analysis._serializable_categories` and + `_fit_state_categories`.** In + `src/easydiffraction/analysis/analysis.py`: + - `_serializable_categories` already includes `self.fit_result` via + the `_fit_state_categories` helper; verify after P1.6 that + `self.fit_result` is the new paired instance, not the old + common-only `FitResult`. + - The dead branch in `_fit_state_categories` (review-9 finding F4, + open issue #101) can be cleaned up here since this step is already + touching the function. The plan does not require the cleanup; if + taken, mention "closes #101" in the commit message. + + Commit: `Verify fit_result is the paired instance in serializer` + +- [ ] **P1.13 — Update `project.display.fit.results()` to add a + "Settings used" block.** In + `src/easydiffraction/project/display.py`, extend the existing + results-display method to print, above the current tables, a + one-section table titled "Settings used" populated from + `analysis.minimizer.*`. Use the same `render_table` machinery + the rest of the display facade uses. Commit: + `Add settings-used block to fit.results display` + +- [ ] **P1.14 — Amend the five accepted ADRs listed in §"ADR".** For + each, apply the matching paragraph from the ADR's §"ADRs + amended" section: + - `minimizer-category-consolidation.md` — §1 partial-rule + qualification; §"Alternatives Considered → D" reversal record. + - `analysis-cif-fit-state.md` — §"Minimizer fit projection" + rewrite. + - `runtime-fit-results.md` — closing-paragraph reference. + - `switchable-category-owned-selectors.md` — §1 paired-category + exception paragraph. + - `display-ux.md` — `project.display.fit.results()` settings-block + note. + - Update `docs/dev/adrs/index.md` to add the new ADR row under + "Accepted" (per P1.16 promotion). + + Commit: `Amend affected ADRs for minimizer input/output split` + +- [ ] **P1.15 — Update tutorials.** `git grep` `docs/docs/tutorials/` + for `analysis.minimizer.` references and rewrite + each to `analysis.fit_result.`. The replacement + list is the union of fields removed in P1.9 and P1.10. Run + `pixi run notebook-prepare` to regenerate the `.ipynb` files. + + Verification grep (must return empty against + `docs/docs/tutorials/`): + + ``` + git grep -nE 'analysis\.minimizer\.(runtime_seconds|iterations_performed|objective_value|objective_name|n_data_points|n_parameters|n_free_parameters|degrees_of_freedom|covariance_available|correlation_available|exit_reason|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' docs/docs/tutorials/ + ``` + + Commit: `Update tutorials to read outputs from fit_result` + +- [ ] **P1.16 — Promote ADR + update index.** + - `git mv docs/dev/adrs/suggestions/minimizer-input-output-split.md + docs/dev/adrs/accepted/minimizer-input-output-split.md`. Flip the + Status header to `Accepted`. + - Move the seven `_reply-N.md` and seven `_review-N.md` siblings: + keep them next to the ADR if the project convention preserves + history under `accepted/`; delete them if the convention is to + drop the deliberation artefacts on promotion (the + `switchable-category-owned-selectors` precedent deleted them). + Per the precedent, delete on promotion. + - Update `docs/dev/adrs/index.md` — move the row for this ADR from + Suggestion → Accepted. + + Commit: `Promote minimizer-input-output-split ADR` + +- [ ] **P1.17 — Phase 1 review gate.** No code change. Re-run the + P1.15 tutorial grep against `src/`, `docs/docs/tutorials/`, and + `tests/`. The `src/` and `docs/docs/tutorials/` scopes must + return empty. The `tests/` sweep is deferred to P2.1, which + migrates the tests. Then stop and request user review. After + approval, proceed to Phase 2. + +## Verification (Phase 2) + +Each command captures its log with a zsh-safe exit-code variable as +required by `.github/copilot-instructions.md` → **Workflow**. + +- [ ] **P2.1 — Migrate existing tests off the removed minimizer + output fields.** `git grep` `tests/` for the same patterns as + P1.15. Replace each `analysis.minimizer.` with + `analysis.fit_result.`. Same for `_set_*` writers + in test fixtures (e.g. + `analysis.minimizer._set_runtime_seconds(...)` → + `analysis.fit_result._set_runtime_seconds(...)`). + + Layout check: + + ``` + pixi run test-structure-check > /tmp/easydiffraction-test-structure-check.log 2>&1; \ + test_structure_check_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-test-structure-check.log; \ + exit $test_structure_check_exit_code + ``` + + Final stale-reference grep (all four must return empty): + + ``` + git grep -nE 'analysis\.minimizer\.(runtime_seconds|iterations_performed|objective_value|objective_name|n_data_points|n_parameters|n_free_parameters|degrees_of_freedom|covariance_available|correlation_available|exit_reason|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ + git grep -nE '_minimizer\.(runtime_seconds|iterations_performed|objective_value|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ + ``` + +- [ ] **P2.2 — Add unit tests for new modules.** New tests under + `tests/unit/easydiffraction/analysis/categories/fit_result/`: + - `test_base.py` — `FitResultBase` defaults, `_reset_result_descriptors`. + - `test_lsq.py` — `LeastSquaresFitResult` defaults; CIF round-trip + of LSQ outputs. + - `test_bayesian.py` — `BayesianFitResult` defaults including the + fixed credible interval levels; CIF round-trip. + - `test_factory.py` — pairing rule via + `LeastSquaresMinimizerBase._fit_result_class` and + `BayesianMinimizerBase._fit_result_class`. + + Layout check: + + ``` + pixi run test-structure-check > /tmp/easydiffraction-test-structure-check.log 2>&1; \ + test_structure_check_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-test-structure-check.log; \ + exit $test_structure_check_exit_code + ``` + +- [ ] **P2.3 — Auto-fixes and static checks.** + + ``` + pixi run fix > /tmp/easydiffraction-fix.log 2>&1; \ + fix_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-fix.log; \ + exit $fix_exit_code + ``` + + Then: + + ``` + pixi run check > /tmp/easydiffraction-check.log 2>&1; \ + check_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-check.log; \ + exit $check_exit_code + ``` + + Iterate `pixi run check` until clean. Do not raise lint + thresholds — refactor instead. + +- [ ] **P2.4 — Unit tests.** + + ``` + pixi run unit-tests > /tmp/easydiffraction-unit-tests.log 2>&1; \ + unit_tests_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-unit-tests.log; \ + exit $unit_tests_exit_code + ``` + +- [ ] **P2.5 — Integration tests.** + + ``` + pixi run integration-tests > /tmp/easydiffraction-integration-tests.log 2>&1; \ + integration_tests_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-integration-tests.log; \ + exit $integration_tests_exit_code + ``` + +- [ ] **P2.6 — Script tests.** + + ``` + pixi run script-tests > /tmp/easydiffraction-script-tests.log 2>&1; \ + script_tests_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-script-tests.log; \ + exit $script_tests_exit_code + ``` + + This regenerates `tmp/tutorials/projects/*` fixtures with the + new CIF layout (`_minimizer.*` settings-only, + `_fit_result.*` outputs). + +## Suggested Pull Request + +**Title:** Split minimizer settings from fit-result outputs + +**Description (user-facing):** + +`analysis.minimizer` now holds only the settings you can change — +`sampling_steps`, `max_iterations`, and the other input knobs. Once a +fit completes, every output the project records (wall time, χ², the +Bayesian diagnostics, the LSQ counters) lives on a paired +`analysis.fit_result`. The pairing happens automatically when you +change minimizer type, so users never see a separate result selector. + +`project.display.fit.results()` now prints a "Settings used" block +above the existing result tables, so the settings that produced the +fit and the outputs the fit produced are visible side-by-side without +having to open two namespaces. + +The CIF layout follows the same split: `_minimizer.*` holds settings +only, `_fit_result.*` holds outputs. Saved projects from the previous +layout do not load unchanged (the project is in beta; no legacy +shims). Tutorials and saved-fixture regeneration land in this PR. From 731ca20e0a1fae452ee025f67e0f844c63ff1257 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:25:28 +0200 Subject: [PATCH 10/38] Address plan review 1: reset paths, defaults, imports, factory --- .../dev/plans/minimizer-input-output-split.md | 173 +++++++++++++----- .../minimizer-input-output-split_reply-1.md | 148 +++++++++++++++ .../minimizer-input-output-split_review-1.md | 17 ++ 3 files changed, 291 insertions(+), 47 deletions(-) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-1.md create mode 100644 docs/dev/plans/minimizer-input-output-split_review-1.md diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 81cfb06d9..2fbc29c94 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -96,9 +96,13 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): - `src/easydiffraction/analysis/categories/fit_result/bayesian.py` — `BayesianFitResult` with Bayesian-specific output descriptors, including `credible_interval_inner` / `credible_interval_outer`. -- `src/easydiffraction/analysis/categories/fit_result/factory.py` — - internal-use factory keyed by minimizer family. Not exposed to the - user (no `show_supported`); used only by `Analysis._swap_minimizer`. +- *(`src/easydiffraction/analysis/categories/fit_result/factory.py` + already exists; this plan extends it rather than creating it. See + P1.4 — the factory becomes a registration helper for the two new + family classes; the authoritative swap mechanism is the + `_fit_result_class` attribute on the paired minimizer base, not a + factory lookup. The factory is still useful for introspection / + testing.)* - `tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py` - `tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py` - `tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py` @@ -162,17 +166,39 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): Mark `[x]` as each step lands. -- [ ] **P1.1 — Rename `FitResult` to `FitResultBase`; keep current - common fields.** In +- [ ] **P1.1 — Rename `FitResult` to `FitResultBase`; update every + import site.** In `src/easydiffraction/analysis/categories/fit_result/default.py`, - rename the class. The factory registration stays on the old - class name to avoid breaking the existing - `FitResultFactory.default_tag()` lookup until P1.4 introduces - the family-keyed factory. Update - `src/easydiffraction/analysis/categories/fit_result/__init__.py` - to re-export the renamed class under both the new and old name - (the old `FitResult` re-export is removed at P1.16). Commit: - `Rename FitResult to FitResultBase` + rename the class. The factory `@register` decorator stays on + the renamed class so the default-tag lookup keeps working until + P1.4 extends the factory. + + Update every package-level import that referenced the old + name. `git grep -nP '\bFitResult\b' src/ tests/` lists the + sites at plan time: + + - `src/easydiffraction/analysis/__init__.py` (line 14 today) + - `src/easydiffraction/analysis/categories/__init__.py` + (line 14 today) + - `src/easydiffraction/analysis/categories/fit_result/__init__.py` + - `src/easydiffraction/analysis/analysis.py` (import line 18; + type annotation on the `fit_result` property at line 432; + `self._fit_result = FitResult()` at line 483; same + construction at line 1208) + + All four `FitResult` import/annotation/construction sites in + `analysis.py` become `FitResultBase` after this step. The two + `self._fit_result = FitResult()` construction sites (init and + `_clear_persisted_fit_state`) become + `FitResultBase()` temporarily; P1.6 retargets them to the + paired class. + + Re-run `git grep -nP '\bFitResult\b' src/` at the end of this + step — every remaining hit must be the renamed class name or a + module path, not the old bare class. Tests are migrated by + P2.1. + + Commit: `Rename FitResult to FitResultBase` - [ ] **P1.2 — Add `LeastSquaresFitResult` class.** New file `src/easydiffraction/analysis/categories/fit_result/lsq.py`. @@ -180,12 +206,19 @@ Mark `[x]` as each step lands. `objective_value`, `n_data_points`, `n_parameters`, `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, `correlation_available`, `exit_reason`. - Numeric defaults follow `None` / `allow_none=True` (matching the - consolidation cleanup); bool defaults `False`; string defaults - `''`. Declare `_expected_descriptor_names`, - `_result_descriptor_names` for parity with the minimizer - hierarchy. Tests deferred to Phase 2. Commit: - `Add LeastSquaresFitResult class` + **All defaults are `None` with `allow_none=True`**, matching the + consolidation cleanup that previously moved LSQ outputs off `0` / + `false` / `''` so a pre-fit CIF emits `?` rather than a value + that looks like a degenerate result. This applies to numeric, + integer-like, string, and bool fields alike; the descriptor + helpers in `LeastSquaresMinimizerBase` + ([`lsq_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/lsq_base.py)) + that currently produce these descriptors are the model — they + can be lifted into `LeastSquaresFitResult` verbatim before being + removed from `lsq_base.py` at P1.9. Declare + `_expected_descriptor_names`, `_result_descriptor_names` for + parity with the minimizer hierarchy. Tests deferred to Phase 2. + Commit: `Add LeastSquaresFitResult class` - [ ] **P1.3 — Add `BayesianFitResult` class.** New file `src/easydiffraction/analysis/categories/fit_result/bayesian.py`. @@ -199,16 +232,24 @@ Mark `[x]` as each step lands. Tests deferred to Phase 2. Commit: `Add BayesianFitResult class` -- [ ] **P1.4 — Add `FitResultCategoryFactory`.** New file - `src/easydiffraction/analysis/categories/fit_result/factory.py`. - `FitResultCategoryFactory(FactoryBase)` registers - `FitResultBase` (default tag), `LeastSquaresFitResult`, - `BayesianFitResult`. Update +- [ ] **P1.4 — Register fit-result classes with the existing + `FitResultFactory`.** The factory already exists at + [`src/easydiffraction/analysis/categories/fit_result/factory.py`](../../../src/easydiffraction/analysis/categories/fit_result/factory.py) + and currently registers only the default common class. Update + it to also register `LeastSquaresFitResult` and + `BayesianFitResult` with their family tags. Update `src/easydiffraction/analysis/categories/fit_result/__init__.py` - to explicitly import every concrete class. The factory is used - only by `Analysis._swap_minimizer`; no - `show_supported`/`type`-style user surface is exposed. Commit: - `Add FitResultCategoryFactory` + to explicitly import every concrete class (so registration + fires on package import, per the repo's standard pattern). + + **Authoritative mechanism:** `Analysis._swap_minimizer` + constructs the paired fit-result via the minimizer's + `_fit_result_class` attribute (P1.5), not via a factory + lookup. The factory is kept as a registration helper for + introspection and testing; do not add a public selector surface + (`type`, `show_supported`) since `fit_result` is internally + paired, per ADR §1. Commit: + `Register fit-result family classes with factory` - [ ] **P1.5 — Declare `_fit_result_class` on minimizer bases.** In `src/easydiffraction/analysis/categories/minimizer/lsq_base.py`, @@ -224,19 +265,52 @@ Mark `[x]` as each step lands. `Declare paired _fit_result_class on minimizer bases` - [ ] **P1.6 — Wire `Analysis._swap_minimizer` to install both - instances.** In + instances, and update every `_fit_result` reset path.** In `src/easydiffraction/analysis/analysis.py`: - - Construct `self._fit_result = + + - `__init__` constructs the initial `_fit_result` from the default + minimizer's `_fit_result_class`: + `self._fit_result = self._minimizer._fit_result_class()`. The + line 483 `self._fit_result = FitResultBase()` (after P1.1) is + replaced. + - `_replace_minimizer` constructs `self._fit_result = new_minimizer._fit_result_class()` after the new minimizer is - created in `_replace_minimizer`. The old `fit_result` instance is - detached (`_parent = None`). - - Add `analysis.fit_result` read-only property. + created. The old `fit_result` is detached (`_parent = None`) + before being replaced. + - `_clear_persisted_fit_state` (line 1204 today) currently calls + `self._clear_minimizer_result_projection()` and then + `self._fit_result = FitResult()`. After P1.1 + the split, both + lines must change: + - `self._fit_result = self.minimizer._fit_result_class()` + replaces the bare `FitResultBase()` construction. This keeps + the paired class invariant whenever the persisted state is + reset. + - `self._clear_minimizer_result_projection()` currently calls + `self.minimizer._reset_result_descriptors()`. After P1.9/P1.10 + remove the result descriptors from the minimizer, this method + becomes a no-op. **Retarget it to + `self.fit_result._reset_result_descriptors()`** and rename it + to `_clear_fit_result_projection`. Update the call sites + (line 1204; potentially others — `git grep` confirms). + - Add `analysis.fit_result` read-only property + (`return self._fit_result`). Type annotation: + `FitResultBase` (the family classes inherit from it). - Wire `self._fit_result._parent = self` in - `_attach_category_parents`. - - The Analysis `__init__` constructs the initial `_fit_result` from - the default minimizer's `_fit_result_class`. + `_attach_category_parents`. Every `_fit_result` reassignment in + the methods above must also set `_parent` on the new instance. + + Verification at the end of this step: + + ``` + git grep -nE 'self\._fit_result\s*=' src/easydiffraction/analysis/analysis.py + ``` - Commit: `Wire fit_result swap to minimizer swap` + Every match must construct via `self.minimizer._fit_result_class()` + (or `new_minimizer._fit_result_class()` in `_replace_minimizer`), + not a bare class name. There must be no remaining + `self._fit_result = FitResultBase()` after this step. + + Commit: `Wire fit_result swap and reset paths to paired class` - [ ] **P1.7 — Route LSQ result writers to `fit_result`.** In `src/easydiffraction/analysis/analysis.py`, @@ -255,21 +329,26 @@ Mark `[x]` as each step lands. `self.fit_result._set_credible_interval_*`. Commit: `Route Bayesian result writers to fit_result` -- [ ] **P1.9 — Remove duplicate fields from LSQ minimizer base.** In +- [ ] **P1.9 — Remove output fields from LSQ minimizer base.** In `src/easydiffraction/analysis/categories/minimizer/lsq_base.py`, delete the descriptor declarations and properties for - `optimizer_name`, `method_name`, `objective_name`, - `objective_value`, `n_data_points`, `n_parameters`, - `n_free_parameters`, `degrees_of_freedom`, + `objective_name`, `objective_value`, `n_data_points`, + `n_parameters`, `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, `correlation_available`, `runtime_seconds`, `iterations_performed`, `exit_reason`. Remove these names from `_expected_descriptor_names` and - `_result_descriptor_names`. `_setting_descriptor_names` is - already `('max_iterations',)` and stays. - - Note: `optimizer_name` and `method_name` were already removed by - the consolidation work (`_engine_metadata` dict replaces them); - this step is the bulk removal of the remaining LSQ outputs. + `_result_descriptor_names`. `_setting_descriptor_names` stays + `('max_iterations',)`. After this step, + `_result_descriptor_names` on `LeastSquaresMinimizerBase` is + `()` and `_reset_result_descriptors()` is a no-op on every LSQ + minimizer — confirming the P1.6 retarget of + `_clear_minimizer_result_projection` to operate on + `self.fit_result` is the correct call site. + + Note: `optimizer_name` and `method_name` were already removed + by the consolidation work (`_engine_metadata` dict replaces + them); this step is the bulk removal of the remaining LSQ + outputs. Commit: `Remove LSQ output descriptors from minimizer base` diff --git a/docs/dev/plans/minimizer-input-output-split_reply-1.md b/docs/dev/plans/minimizer-input-output-split_reply-1.md new file mode 100644 index 000000000..3c126a60f --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-1.md @@ -0,0 +1,148 @@ +# Reply to Review 1: Minimizer Input/Output Split Plan + +Reply to +[`minimizer-input-output-split_review-1.md`](minimizer-input-output-split_review-1.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +All four findings agreed. Each was addressed by editing the plan +text; no item is deferred. The plan is updated in the same commit as +this reply. + +## Findings + +### Finding 1 — `_clear_persisted_fit_state` resets `_fit_result` to common-only class + +**Verdict: agree — real omission in P1.6.** + +The current `_clear_persisted_fit_state` (analysis.py:1204) does +`self._fit_result = FitResult()` and calls +`_clear_minimizer_result_projection()`. The original P1.6 wired the +initial `_fit_result` and the swap path but said nothing about the +reset path that fires before every fit. After the split, that path +would discard the paired `LeastSquaresFitResult` / +`BayesianFitResult` and reinstall the bare common class — so the +first family-specific `_set_*` call in P1.7 / P1.8 would write to an +attribute that no longer exists on the live instance. + +**Action taken.** Extended P1.6 to enumerate every `_fit_result` +construction and reset site in `analysis.py`: + +- `__init__` builds via `self._minimizer._fit_result_class()`. +- `_replace_minimizer` builds via + `new_minimizer._fit_result_class()` after detaching the old. +- `_clear_persisted_fit_state` rebuilds via + `self.minimizer._fit_result_class()` so the paired-class invariant + survives a reset. +- `_clear_minimizer_result_projection` is renamed + `_clear_fit_result_projection` and retargeted to call + `self.fit_result._reset_result_descriptors()`, since + `LeastSquaresMinimizerBase` / `BayesianMinimizerBase` lose their + `_result_descriptor_names` content at P1.9 / P1.10. +- A `git grep -nE 'self\._fit_result\s*=' src/easydiffraction/analysis/analysis.py` + check at the end of P1.6 confirms every construction goes through + the paired class. P1.9 also notes that the LSQ + `_result_descriptor_names` becomes `()` after the removal, so the + rename in P1.6 is the correct landing. + +**Plan section:** P1.6 (rewritten), P1.9 (added confirmation note). + +### Finding 2 — P1.2 regresses pre-fit LSQ output defaults + +**Verdict: agree — would undo a consolidation-cleanup decision.** + +The consolidation cleanup (Review 8 F6, addressed in commit +`28d4291cb`) explicitly moved LSQ result descriptors to +`default=None, allow_none=True` so a pre-fit CIF emits `?` rather +than misleading `0` / `false` / `''` values. The original P1.2 said +"bool defaults `False`; string defaults `''`", which would undo that +fix on every LSQ output relocated to `LeastSquaresFitResult`. + +**Action taken.** Rewrote P1.2 to specify: + +> All defaults are `None` with `allow_none=True`, matching the +> consolidation cleanup that previously moved LSQ outputs off `0` / +> `false` / `''` so a pre-fit CIF emits `?` rather than a value that +> looks like a degenerate result. This applies to numeric, +> integer-like, string, and bool fields alike; the descriptor +> helpers in `LeastSquaresMinimizerBase` that currently produce +> these descriptors are the model — they can be lifted into +> `LeastSquaresFitResult` verbatim before being removed from +> `lsq_base.py` at P1.9. + +The lift-verbatim instruction also reduces drift risk: the +helper functions move with their existing defaults rather than being +rewritten. + +**Plan section:** P1.2 (rewritten). + +### Finding 3 — Package-level `FitResult` imports outside `fit_result/__init__.py` + +**Verdict: agree — four sites missing from the plan.** + +`git grep -nP '\bFitResult\b' src/` lists four references that the +original P1.1 did not call out explicitly: + +- `src/easydiffraction/analysis/__init__.py:14` +- `src/easydiffraction/analysis/categories/__init__.py:14` +- `src/easydiffraction/analysis/analysis.py:18` (import) +- `src/easydiffraction/analysis/analysis.py:432` (property type + annotation) +- `src/easydiffraction/analysis/analysis.py:483` (construction in + `__init__`) +- `src/easydiffraction/analysis/analysis.py:1208` (construction in + `_clear_persisted_fit_state`) + +Without explicit migration, after P1.16 removes the old `FitResult` +re-export, these imports break or keep loading the wrong class. + +**Action taken.** Rewrote P1.1 to enumerate every site +explicitly, with a `git grep -nP '\bFitResult\b' src/` verification +gate at the end of the step. The two `self._fit_result = +FitResult()` constructions become `FitResultBase()` temporarily and +are then retargeted to the paired class in P1.6. + +**Plan section:** P1.1 (rewritten with the full call-out list and +verification grep). + +### Finding 4 — Fit-result factory inconsistency + +**Verdict: agree — `factory.py` already exists, and the original +text was internally inconsistent about whether it or +`_fit_result_class` is authoritative for swap.** + +The current `fit_result/factory.py` is a 15-line `FactoryBase` +registration shell. The original P1.4 called it "new" and said the +factory is used by `_swap_minimizer`, while P1.6 said +`_replace_minimizer` constructs via `new_minimizer._fit_result_class()` +directly — pick one. + +**Action taken.** Two coordinated edits: + +- The §"Created" list now says the factory exists; this plan extends + rather than creates it. The same note clarifies that the + authoritative swap mechanism is `_fit_result_class` (on the + minimizer base), with the factory as a registration/introspection + helper. +- P1.4 now says "Register fit-result classes with the existing + `FitResultFactory`" and adds an explicit "Authoritative mechanism" + paragraph stating that `_swap_minimizer` reads the paired class + off `new_minimizer._fit_result_class`, not via a factory lookup. + +**Plan section:** §"Created" (factory bullet), P1.4 (rewritten). + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — plan updated per all four findings above (P1.1, P1.2, P1.4, P1.6, + P1.9 + §"Created" bullet). +- [`docs/dev/plans/minimizer-input-output-split_reply-1.md`](minimizer-input-output-split_reply-1.md) + — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-1.md b/docs/dev/plans/minimizer-input-output-split_review-1.md new file mode 100644 index 000000000..51591fd50 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-1.md @@ -0,0 +1,17 @@ +# Review 1: Minimizer Input/Output Split Plan + +## Findings + +1. **High — New fits will reset `fit_result` back to the common-only class unless `_clear_persisted_fit_state()` is updated.** P1.6 wires the initial and swapped `_fit_result` instances from the active minimizer's `_fit_result_class` (`minimizer-input-output-split.md:226-237`), then P1.7/P1.8 route family-specific `_set_*` calls to `self.fit_result` (`:241-256`). But the current fit path calls `_capture_fit_parameter_state()`, which calls `_clear_persisted_fit_state()` before storing results; that method currently replaces `_fit_result` with the common `FitResult()` class and calls `_clear_minimizer_result_projection()` (`src/easydiffraction/analysis/analysis.py:1204-1219`). If the plan does not update that reset path, the first post-split fit will discard the paired `LeastSquaresFitResult` / `BayesianFitResult` instance before P1.7/P1.8 write family-specific fields. Add an explicit step to reset `_fit_result` with `self.minimizer._fit_result_class()`, attach `_parent`, and either remove or retarget `_clear_minimizer_result_projection()` to the new fit-result object. + +2. **High — P1.2 regresses pre-fit LSQ output defaults from unknown to false/empty-string.** P1.2 says LSQ bool outputs default to `False` and string outputs default to `''` (`minimizer-input-output-split.md:177-188`). The existing minimizer result descriptors deliberately default string, integer-like, and bool result fields to `None` so CIF emits `?` before any fit and users do not read `false`, `0`, or an empty string as an actual fit result (`src/easydiffraction/analysis/categories/minimizer/lsq_base.py:113-126`, `:145-175`). Moving the fields to `LeastSquaresFitResult` should preserve that no-fit semantics. Please change P1.2 to keep LSQ string and bool output defaults as `None` / `allow_none=True`, except for any field where the ADR explicitly chooses a concrete pre-fit default. + +3. **Medium — The import/export surfaces outside `fit_result/__init__.py` are missing from the plan.** The plan updates only `src/easydiffraction/analysis/categories/fit_result/__init__.py` for the new fit-result classes (`minimizer-input-output-split.md:109-114`, `:202-211`). The repo also explicitly imports `FitResult` from `src/easydiffraction/analysis/__init__.py` and `src/easydiffraction/analysis/categories/__init__.py`, and `analysis.py` imports and annotates it directly (`src/easydiffraction/analysis/__init__.py:14-15`, `src/easydiffraction/analysis/categories/__init__.py:14`, `src/easydiffraction/analysis/analysis.py:18`, `:432`). Once the old `FitResult` re-export is removed at P1.16 (`minimizer-input-output-split.md:165-175`), these surfaces can break or keep exporting the wrong class. Add explicit steps to update the package-level imports and all direct `FitResult` type annotations/constructions. + +4. **Medium — The fit-result factory plan is internally inconsistent.** The "Created" list and P1.4 call `src/easydiffraction/analysis/categories/fit_result/factory.py` a new file, but it already exists (`minimizer-input-output-split.md:99-101`, `:202-211`; `src/easydiffraction/analysis/categories/fit_result/factory.py:1-15`). More importantly, P1.4 says the factory is used only by `Analysis._swap_minimizer`, while P1.6 says `_replace_minimizer` constructs `new_minimizer._fit_result_class()` directly (`minimizer-input-output-split.md:202-211`, `:226-237`). Pick one mechanism. If `_fit_result_class` is authoritative, the factory is just a registration/testing helper and the plan should say that; if the factory is authoritative, P1.6 should use it explicitly. + +## Checks + +Skipped by instruction: this is a static plan review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 8471d0eb7ed7f1b5b31644642479700864145cd1 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:32:13 +0200 Subject: [PATCH 11/38] Address plan review 2: migration map, reset hooks, CIF ordering --- .../dev/plans/minimizer-input-output-split.md | 140 ++++++++++++++---- .../minimizer-input-output-split_reply-2.md | 117 +++++++++++++++ .../minimizer-input-output-split_review-2.md | 15 ++ 3 files changed, 244 insertions(+), 28 deletions(-) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-2.md create mode 100644 docs/dev/plans/minimizer-input-output-split_review-2.md diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 2fbc29c94..dad49c056 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -166,13 +166,42 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): Mark `[x]` as each step lands. -- [ ] **P1.1 — Rename `FitResult` to `FitResultBase`; update every - import site.** In +- [ ] **P1.1 — Rename `FitResult` to `FitResultBase`; add reset + hooks; update every import site.** In `src/easydiffraction/analysis/categories/fit_result/default.py`, rename the class. The factory `@register` decorator stays on the renamed class so the default-tag lookup keeps working until P1.4 extends the factory. + Add two class-level hooks to `FitResultBase` matching the + `MinimizerCategoryBase` shape introduced by the consolidation + work + ([`minimizer/base.py:69-74`](../../../src/easydiffraction/analysis/categories/minimizer/base.py)): + + ```python + _result_descriptor_names: ClassVar[tuple[str, ...]] = ( + 'success', 'message', 'iterations', + 'fitting_time', 'reduced_chi_square', 'result_kind', + ) + + def _reset_result_descriptors(self) -> None: + """Reset fit-result descriptors to declared defaults.""" + for name in self._result_descriptor_names: + descriptor = getattr(self, name) + if isinstance(descriptor, GenericDescriptorBase): + descriptor.value = descriptor._value_spec.default_value() + ``` + + `LeastSquaresFitResult` (P1.2) and `BayesianFitResult` (P1.3) + then add their own field names to `_result_descriptor_names` + so the inherited helper resets every relevant descriptor. + + This must land in P1.1 because P1.6 retargets + `_clear_minimizer_result_projection` (renamed + `_clear_fit_result_projection`) to call + `self.fit_result._reset_result_descriptors()`, and that method + must exist on `FitResultBase` before the swap is wired. + Update every package-level import that referenced the old name. `git grep -nP '\bFitResult\b' src/ tests/` lists the sites at plan time: @@ -198,7 +227,7 @@ Mark `[x]` as each step lands. module path, not the old bare class. Tests are migrated by P2.1. - Commit: `Rename FitResult to FitResultBase` + Commit: `Rename FitResult to FitResultBase, add reset hooks` - [ ] **P1.2 — Add `LeastSquaresFitResult` class.** New file `src/easydiffraction/analysis/categories/fit_result/lsq.py`. @@ -370,10 +399,24 @@ Mark `[x]` as each step lands. `src/easydiffraction/io/cif/serialize.py`: - `_minimizer.*` emit/read continues to handle settings only (the minimizer category's `from_cif` walks its remaining descriptors). - - Add `_fit_result.*` emit/read after `_minimizer.*` so the paired - class is known before the result descriptors load. The order is - enforced by `Analysis._serializable_categories()` putting - `self.fit_result` directly after `self.minimizer`. + - The read-side order is **already** correct in the current code + ([`serialize.py:553-555`](../../../src/easydiffraction/io/cif/serialize.py)): + `_set_minimizer_type` runs before `analysis.minimizer.from_cif`, + and the paired `fit_result` swap fires inside + `_set_minimizer_type` after P1.6. The `fit_result.from_cif(block)` + call inside `_restore_common_fit_state` + ([line 590](../../../src/easydiffraction/io/cif/serialize.py)) + therefore reads `_fit_result.*` into the already-paired class. + No reordering is required. + - The emit-side order follows + `Analysis._serializable_categories()` and + `_fit_state_categories()` exactly as today: `self.fit_result` is + **conditionally** included only when persisted fit state exists + (`self._has_persisted_fit_state()`). Pre-fit projects continue + to emit no `_fit_result.*` tags. Do not promote `fit_result` to + an unconditional category in `_serializable_categories` — + keeping the conditional preserves the current "no spurious + defaults emitted before a fit" behavior. - Update the legacy-tag rejection message in `_raise_for_legacy_analysis_tags` to include the now-removed `_minimizer.` tags (e.g. @@ -383,19 +426,21 @@ Mark `[x]` as each step lands. Commit: `Serialize fit outputs to _fit_result.* tags` -- [ ] **P1.12 — Update `Analysis._serializable_categories` and - `_fit_state_categories`.** In - `src/easydiffraction/analysis/analysis.py`: - - `_serializable_categories` already includes `self.fit_result` via - the `_fit_state_categories` helper; verify after P1.6 that - `self.fit_result` is the new paired instance, not the old - common-only `FitResult`. - - The dead branch in `_fit_state_categories` (review-9 finding F4, - open issue #101) can be cleaned up here since this step is already - touching the function. The plan does not require the cleanup; if - taken, mention "closes #101" in the commit message. - - Commit: `Verify fit_result is the paired instance in serializer` +- [ ] **P1.12 — Confirm `_fit_state_categories` returns the paired + `fit_result`.** In + `src/easydiffraction/analysis/analysis.py`, + `_fit_state_categories()` already returns `[self.fit_parameters, + self.fit_result, self.fit_parameter_correlations]` when + persisted fit state exists. After P1.6 wires the paired-class + construction, `self.fit_result` is automatically the paired + `LeastSquaresFitResult` / `BayesianFitResult` instance — no + method body change is needed. The dead branch in + `_fit_state_categories` (review-9 finding F4, open issue #101) + can be cleaned up here since this step is already reading the + function. The plan does not require the cleanup; if taken, + mention "closes #101" in the commit message. + + Commit: `Confirm fit_result paired instance flows through serializer` - [ ] **P1.13 — Update `project.display.fit.results()` to add a "Settings used" block.** In @@ -425,9 +470,36 @@ Mark `[x]` as each step lands. - [ ] **P1.15 — Update tutorials.** `git grep` `docs/docs/tutorials/` for `analysis.minimizer.` references and rewrite - each to `analysis.fit_result.`. The replacement - list is the union of fields removed in P1.9 and P1.10. Run - `pixi run notebook-prepare` to regenerate the `.ipynb` files. + each per the migration table below. The two **collapsed** rows + target existing common fields on `FitResultBase` (already + written by the existing common projection writer); they are not + 1:1 renames of the old setter/getter name. The other rows are + moved-but-keep-the-name relocations. + + | Old (removed at P1.9 / P1.10) | New | Notes | + | --- | --- | --- | + | `analysis.minimizer.runtime_seconds` | `analysis.fit_result.fitting_time` | Collapsed onto existing common field; setter remains `fit_result._set_fitting_time(...)` (already in `FitResultBase`). | + | `analysis.minimizer.iterations_performed` | `analysis.fit_result.iterations` | Collapsed onto existing common field; setter remains `fit_result._set_iterations(...)`. | + | `analysis.minimizer.objective_name` | `analysis.fit_result.objective_name` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.objective_value` | `analysis.fit_result.objective_value` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.n_data_points` | `analysis.fit_result.n_data_points` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.n_parameters` | `analysis.fit_result.n_parameters` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.n_free_parameters` | `analysis.fit_result.n_free_parameters` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.degrees_of_freedom` | `analysis.fit_result.degrees_of_freedom` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.covariance_available` | `analysis.fit_result.covariance_available` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.correlation_available` | `analysis.fit_result.correlation_available` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.exit_reason` | `analysis.fit_result.exit_reason` | Moved to `LeastSquaresFitResult`. | + | `analysis.minimizer.point_estimate_name` | `analysis.fit_result.point_estimate_name` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.sampler_completed` | `analysis.fit_result.sampler_completed` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.credible_interval_inner` | `analysis.fit_result.credible_interval_inner` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.credible_interval_outer` | `analysis.fit_result.credible_interval_outer` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.acceptance_rate_mean` | `analysis.fit_result.acceptance_rate_mean` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.gelman_rubin_max` | `analysis.fit_result.gelman_rubin_max` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.effective_sample_size_min` | `analysis.fit_result.effective_sample_size_min` | Moved to `BayesianFitResult`. | + | `analysis.minimizer.best_log_posterior` | `analysis.fit_result.best_log_posterior` | Moved to `BayesianFitResult`. | + + Run `pixi run notebook-prepare` to regenerate the `.ipynb` + files. Verification grep (must return empty against `docs/docs/tutorials/`): @@ -467,11 +539,23 @@ required by `.github/copilot-instructions.md` → **Workflow**. - [ ] **P2.1 — Migrate existing tests off the removed minimizer output fields.** `git grep` `tests/` for the same patterns as - P1.15. Replace each `analysis.minimizer.` with - `analysis.fit_result.`. Same for `_set_*` writers - in test fixtures (e.g. - `analysis.minimizer._set_runtime_seconds(...)` → - `analysis.fit_result._set_runtime_seconds(...)`). + P1.15. Apply the same migration table from P1.15 — including + the two collapsed rows (`runtime_seconds` → `fitting_time`, + `iterations_performed` → `iterations`) where the setter name + also changes. Examples: + + - Reader rewrite: + `analysis.minimizer.gelman_rubin_max` → + `analysis.fit_result.gelman_rubin_max`. + - Reader rewrite with collapse: + `analysis.minimizer.runtime_seconds` → + `analysis.fit_result.fitting_time`. + - Setter rewrite (moved-but-kept name): + `analysis.minimizer._set_gelman_rubin_max(...)` → + `analysis.fit_result._set_gelman_rubin_max(...)`. + - Setter rewrite (collapsed name): + `analysis.minimizer._set_runtime_seconds(...)` → + `analysis.fit_result._set_fitting_time(...)`. Layout check: diff --git a/docs/dev/plans/minimizer-input-output-split_reply-2.md b/docs/dev/plans/minimizer-input-output-split_reply-2.md new file mode 100644 index 000000000..9511120d0 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-2.md @@ -0,0 +1,117 @@ +# Reply to Review 2: Minimizer Input/Output Split Plan + +Reply to +[`minimizer-input-output-split_review-2.md`](minimizer-input-output-split_review-2.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +All three findings agreed. Each was addressed by editing the plan +text in the same commit as this reply. + +## Findings + +### Finding 1 — Removed output names treated as one-to-one renames + +**Verdict: agree — would mis-migrate the two collapsed-name fields.** + +The ADR collapses `runtime_seconds` onto the existing +`fit_result.fitting_time` and `iterations_performed` onto +`fit_result.iterations`. The original P1.15 and P2.1 said "replace +each `analysis.minimizer.` with +`analysis.fit_result.`" and used +`_set_runtime_seconds` → `_set_runtime_seconds` as the fixture +example — both are wrong for those two fields, where the target +field and setter already exist under different names on +`FitResultBase`. + +**Action taken.** Rewrote P1.15 with an explicit migration table +covering all 19 removed fields. The two collapsed rows are flagged +with a "Collapsed onto existing common field" note and point at the +existing `fit_result._set_fitting_time(...)` / +`fit_result._set_iterations(...)` setters. The other 17 rows are +moved-but-keep-the-name relocations (e.g. +`analysis.minimizer.gelman_rubin_max` → +`analysis.fit_result.gelman_rubin_max`). + +P2.1 was rewritten to point at the same migration table and to +include both setter-rename examples — moved-but-kept and collapsed — +so test fixtures get the right setter on each side. + +**Plan section:** P1.15 (migration table added), P2.1 (examples +updated). + +### Finding 2 — `_reset_result_descriptors()` not yet defined on `FitResultBase` + +**Verdict: agree — Phase 1 wired a call to a method that did not +exist yet.** + +The method lives on `MinimizerCategoryBase` +([`minimizer/base.py:69-74`](../../../src/easydiffraction/analysis/categories/minimizer/base.py)) +and the existing `FitResult` class has no equivalent. Adding it only +in P1.2 / P1.3 (on the family classes) wouldn't help, because P1.6 +calls it on the union type `FitResultBase`. If P1.6 ran before the +helper was added, every `_clear_persisted_fit_state` reset would +raise `AttributeError`. + +**Action taken.** Extended P1.1 to add two class-level hooks on +`FitResultBase` during the rename: + +- `_result_descriptor_names: ClassVar[tuple[str, ...]]` initialised + to the existing common fields (`success`, `message`, `iterations`, + `fitting_time`, `reduced_chi_square`, `result_kind`). +- `_reset_result_descriptors()` implemented exactly as on + `MinimizerCategoryBase` (walk `_result_descriptor_names`, reset to + `_value_spec.default_value()`). + +The family classes (P1.2 / P1.3) extend `_result_descriptor_names` +with their own field names so the inherited helper resets the full +descriptor set on the active paired class. The plan now ensures the +helper is in place before P1.6 wires the swap and reset retargeting. + +**Plan section:** P1.1 (added hooks paragraph), P1.6 (already +correct now that the helper is guaranteed present). + +### Finding 3 — CIF ordering steps contradicted each other + +**Verdict: agree — P1.11 and P1.12 stated incompatible contracts.** + +P1.11 claimed the order was enforced by `_serializable_categories` +putting `fit_result` directly after `minimizer`. P1.12 said +`_serializable_categories` already includes `fit_result` via +`_fit_state_categories`. The current code conditionally appends +`fit_result` only when `_has_persisted_fit_state()` is true, so +following P1.11 literally would make pre-fit projects emit a default +`_fit_result.*` block — a regression in CIF compactness. + +**Action taken.** Rewrote both steps to pick the conditional +contract: + +- P1.11 now says the read-side order is **already correct** because + `_set_minimizer_type` runs before `analysis.minimizer.from_cif` + and the paired `fit_result` swap fires inside + `_set_minimizer_type` after P1.6. The emit-side + `_serializable_categories()` / `_fit_state_categories()` shape is + preserved (conditional inclusion); explicit "do not promote + `fit_result` to unconditional" instruction added. +- P1.12 now confirms that + `_fit_state_categories()` returns the paired instance + automatically once P1.6 wires the construction — no method body + change needed. + +**Plan section:** P1.11 (rewritten), P1.12 (rewritten). + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — plan updated per all three findings (P1.1, P1.11, P1.12, P1.15, + P2.1). +- [`docs/dev/plans/minimizer-input-output-split_reply-2.md`](minimizer-input-output-split_reply-2.md) + — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-2.md b/docs/dev/plans/minimizer-input-output-split_review-2.md new file mode 100644 index 000000000..53232faaa --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-2.md @@ -0,0 +1,15 @@ +# Review 2: Minimizer Input/Output Split Plan + +## Findings + +1. **High — Removed output names are still described as one-to-one `fit_result` replacements.** P1.15 and P2.1 say to replace every `analysis.minimizer.` with `analysis.fit_result.`, and even give `analysis.minimizer._set_runtime_seconds(...)` → `analysis.fit_result._set_runtime_seconds(...)` as the fixture migration example (`minimizer-input-output-split.md:426-474`). That replacement is wrong for the fields the ADR intentionally collapses into existing common fit-result names: `runtime_seconds` becomes `fit_result.fitting_time`, and `iterations_performed` becomes `fit_result.iterations`. The current common category already exposes `_set_fitting_time` and `_set_iterations`, not `_set_runtime_seconds` / `_set_iterations_performed` (`src/easydiffraction/analysis/categories/fit_result/default.py:113-126`). If the plan is followed literally, tutorials/tests will be migrated to attributes and setters that do not exist after P1.2/P1.3. Please add an explicit migration map for renamed outputs, at minimum `runtime_seconds` → `fitting_time` and `iterations_performed` → `iterations`, and say the existing common projection writer owns those values rather than moving the old minimizer `_set_runtime_seconds` call verbatim. + +2. **High — P1.6 retargets reset calls to a method that no fit-result class is told to implement.** P1.6 says `_clear_minimizer_result_projection()` should become `_clear_fit_result_projection()` and call `self.fit_result._reset_result_descriptors()` (`minimizer-input-output-split.md:280-294`). Today `_reset_result_descriptors()` exists only on `MinimizerCategoryBase` (`src/easydiffraction/analysis/categories/minimizer/base.py:69-74`), while the current `FitResult` class has no equivalent helper (`src/easydiffraction/analysis/categories/fit_result/default.py:20-135`). P1.2/P1.3 ask the new family classes to declare `_result_descriptor_names`, and P2.2 later mentions testing `FitResultBase._reset_result_descriptors`, but no Phase 1 implementation step actually adds that method before P1.6 starts calling it. Add an explicit P1.1/P1.2 instruction to put `_result_descriptor_names` and `_reset_result_descriptors()` on `FitResultBase` (or a shared helper) before retargeting the clear path. + +3. **Medium — The CIF ordering steps contradict each other and may change pre-fit emission.** P1.11 says `_fit_result.*` order is enforced by `Analysis._serializable_categories()` putting `self.fit_result` directly after `self.minimizer` (`minimizer-input-output-split.md:369-376`), but P1.12 immediately says `_serializable_categories` already includes `self.fit_result` via `_fit_state_categories` (`minimizer-input-output-split.md:386-392`). In current code, `fit_result` is appended only when persisted fit state exists (`src/easydiffraction/analysis/analysis.py:852-873`, `:1182-1186`), and CIF read order is already handled separately by `analysis_from_cif`: minimizer type is restored before `analysis.fit_result.from_cif(block)` runs (`src/easydiffraction/io/cif/serialize.py:552-590`). If an implementer follows P1.11 literally by moving `fit_result` directly into the main category list after `minimizer`, pre-fit CIFs may start emitting default `_fit_result.*` fields. Please make the plan choose one contract: keep `fit_result` conditional inside `_fit_state_categories` and describe the ordering as "after minimizer when persisted", or explicitly require a guarded direct insertion that preserves the current no-persisted-fit-state behavior. + +## Checks + +Skipped by instruction: this is a static plan review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 317d901051fef4ad1289dff6761062d9b31bb0cb Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:34:06 +0200 Subject: [PATCH 12/38] Address plan review 3: lead P1.11 with no-reordering rule --- .../dev/plans/minimizer-input-output-split.md | 45 +++++++------ .../minimizer-input-output-split_reply-3.md | 63 +++++++++++++++++++ .../minimizer-input-output-split_review-3.md | 23 +++++++ 3 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-3.md create mode 100644 docs/dev/plans/minimizer-input-output-split_review-3.md diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index dad49c056..56341d545 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -397,26 +397,33 @@ Mark `[x]` as each step lands. - [ ] **P1.11 — Update CIF emit/read for the split.** In `src/easydiffraction/io/cif/serialize.py`: + + **No category-list reordering is performed in this step.** Neither + `Analysis._serializable_categories()` nor + `Analysis._fit_state_categories()` is restructured. `fit_result` + stays conditionally included by `_fit_state_categories()` only + when `self._has_persisted_fit_state()` is true — exactly as today. + Pre-fit projects continue to emit no `_fit_result.*` block. + + The only changes in this step are content updates inside the + existing emit/read flow: + - `_minimizer.*` emit/read continues to handle settings only (the - minimizer category's `from_cif` walks its remaining descriptors). - - The read-side order is **already** correct in the current code - ([`serialize.py:553-555`](../../../src/easydiffraction/io/cif/serialize.py)): - `_set_minimizer_type` runs before `analysis.minimizer.from_cif`, - and the paired `fit_result` swap fires inside - `_set_minimizer_type` after P1.6. The `fit_result.from_cif(block)` - call inside `_restore_common_fit_state` - ([line 590](../../../src/easydiffraction/io/cif/serialize.py)) - therefore reads `_fit_result.*` into the already-paired class. - No reordering is required. - - The emit-side order follows - `Analysis._serializable_categories()` and - `_fit_state_categories()` exactly as today: `self.fit_result` is - **conditionally** included only when persisted fit state exists - (`self._has_persisted_fit_state()`). Pre-fit projects continue - to emit no `_fit_result.*` tags. Do not promote `fit_result` to - an unconditional category in `_serializable_categories` — - keeping the conditional preserves the current "no spurious - defaults emitted before a fit" behavior. + minimizer category's `from_cif` walks its remaining descriptors + after P1.9 / P1.10 removed the output descriptors). + - `_fit_result.*` emit/read picks up the new family-specific + descriptors automatically because P1.6 wires the paired class + (`LeastSquaresFitResult` or `BayesianFitResult`) onto + `self._fit_result`. The existing + `analysis.fit_result.from_cif(block)` call inside + `_restore_common_fit_state` + ([`serialize.py:590`](../../../src/easydiffraction/io/cif/serialize.py)) + reads `_fit_result.*` tags into the already-paired class — no + reordering, no new call. + - The read-side already restores `minimizer.type` first + ([`serialize.py:553-555`](../../../src/easydiffraction/io/cif/serialize.py)), + so the paired-class swap fires before `fit_result.from_cif` runs. + No code change is required here. - Update the legacy-tag rejection message in `_raise_for_legacy_analysis_tags` to include the now-removed `_minimizer.` tags (e.g. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-3.md b/docs/dev/plans/minimizer-input-output-split_reply-3.md new file mode 100644 index 000000000..8fb5063e2 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-3.md @@ -0,0 +1,63 @@ +# Reply to Review 3: Minimizer Input/Output Split Plan + +Reply to +[`minimizer-input-output-split_review-3.md`](minimizer-input-output-split_review-3.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +## Findings + +### Finding 1 — CIF ordering steps still contradictory + +**Verdict: partial — intent was correct in reply 2, wording could be +misread.** + +The reviewer acknowledges review 2 F1 and F2 are addressed and +re-raises only the CIF-ordering concern. Reading the post-reply-2 +text, P1.11 did already state the conditional contract explicitly +("`self.fit_result` is **conditionally** included only when persisted +fit state exists … Do not promote `fit_result` to an unconditional +category"). However, the same step opened with a paragraph about the +**read** side that mentioned "after `_minimizer.*`" and the reviewer +appears to have carried that ordering language across into the +**emit** side, where it is exactly what we do not want. + +**Action taken.** Rewrote P1.11 with the no-reordering rule as the +**first** sentence of the step, before any read/emit detail: + +> **No category-list reordering is performed in this step.** Neither +> `Analysis._serializable_categories()` nor +> `Analysis._fit_state_categories()` is restructured. `fit_result` +> stays conditionally included by `_fit_state_categories()` only +> when `self._has_persisted_fit_state()` is true — exactly as today. +> Pre-fit projects continue to emit no `_fit_result.*` block. + +The remaining bullets list only content-level changes (which +descriptors flow through emit/read, the legacy-tag rejection +message), and each is explicitly framed as "no reordering, no new +call" or "no code change is required here". The earlier phrasing +about the read order being "already correct" was retained but moved +below the no-reordering rule and reframed as a confirmation rather +than a directive. + +P1.12 was already clear that `_fit_state_categories()` is unchanged +(it just walks the paired instance after P1.6 wires it). No further +P1.12 edits. + +**Plan section:** P1.11 (rewritten with the no-reordering rule as +the first paragraph; intent unchanged but explicit). + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, +or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — P1.11 reworded to lead with the no-reordering rule. +- [`docs/dev/plans/minimizer-input-output-split_reply-3.md`](minimizer-input-output-split_reply-3.md) + — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-3.md b/docs/dev/plans/minimizer-input-output-split_review-3.md new file mode 100644 index 000000000..8be148422 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-3.md @@ -0,0 +1,23 @@ +# Review 3: Minimizer Input/Output Split Plan + +## Findings + +No issues found. + +Review 2's remaining concerns are addressed in the current plan: + +- P1.1 adds `_result_descriptor_names` and `_reset_result_descriptors()` + to `FitResultBase` before P1.6 retargets reset calls. +- P1.15 and P2.1 now include an explicit migration table for collapsed + field names (`runtime_seconds` → `fitting_time`, + `iterations_performed` → `iterations`) and moved fields. +- P1.11 now explicitly preserves the existing conditional + `_fit_state_categories()` emission path and says no + `_serializable_categories()` reordering is performed, so pre-fit + projects keep emitting no `_fit_result.*` block. + +## Checks + +Skipped by instruction: this is a static plan review only. I did not run +tests, `pixi run fix`, `pixi run check`, or any other build or +verification command. From 5f3491a7d0886733d6cc42c72a9d5ff20f2f2d3e Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:39:49 +0200 Subject: [PATCH 13/38] Rename FitResult to FitResultBase, add reset hooks --- .../dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/analysis/__init__.py | 2 +- src/easydiffraction/analysis/analysis.py | 8 +- .../analysis/categories/__init__.py | 2 +- .../categories/fit_result/__init__.py | 2 +- .../analysis/categories/fit_result/base.py | 153 ++++++++++++++++++ .../analysis/categories/fit_result/default.py | 132 +-------------- 7 files changed, 164 insertions(+), 137 deletions(-) create mode 100644 src/easydiffraction/analysis/categories/fit_result/base.py diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 56341d545..456612fcf 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -166,7 +166,7 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): Mark `[x]` as each step lands. -- [ ] **P1.1 — Rename `FitResult` to `FitResultBase`; add reset +- [x] **P1.1 — Rename `FitResult` to `FitResultBase`; add reset hooks; update every import site.** In `src/easydiffraction/analysis/categories/fit_result/default.py`, rename the class. The factory `@register` decorator stays on diff --git a/src/easydiffraction/analysis/__init__.py b/src/easydiffraction/analysis/__init__.py index 186c375c8..325c9882a 100644 --- a/src/easydiffraction/analysis/__init__.py +++ b/src/easydiffraction/analysis/__init__.py @@ -11,7 +11,7 @@ from easydiffraction.analysis.categories.fit_parameters import FitParameterItem from easydiffraction.analysis.categories.fit_parameters import FitParameters from easydiffraction.analysis.categories.fit_parameters import FitParametersFactory -from easydiffraction.analysis.categories.fit_result import FitResult +from easydiffraction.analysis.categories.fit_result import FitResultBase from easydiffraction.analysis.categories.fit_result import FitResultFactory from easydiffraction.analysis.categories.joint_fit import JointFitCollection from easydiffraction.analysis.categories.joint_fit import JointFitFactory diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 87676646e..779796b4c 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -15,7 +15,7 @@ from easydiffraction.analysis.categories.constraints.factory import ConstraintsFactory from easydiffraction.analysis.categories.fit_parameter_correlations import FitParameterCorrelations from easydiffraction.analysis.categories.fit_parameters import FitParameters -from easydiffraction.analysis.categories.fit_result import FitResult +from easydiffraction.analysis.categories.fit_result import FitResultBase from easydiffraction.analysis.categories.fitting_mode import FittingMode from easydiffraction.analysis.categories.fitting_mode import FittingModeFactory from easydiffraction.analysis.categories.joint_fit import JointFitCollection @@ -429,7 +429,7 @@ def fit_parameters(self) -> FitParameters: return self._fit_parameters @property - def fit_result(self) -> FitResult: + def fit_result(self) -> FitResultBase: """Persisted common fit-result status metadata.""" return self._fit_result @@ -480,7 +480,7 @@ def __init__(self, project: object) -> None: ) self._sequential_fit_extract = SequentialFitExtractCollection() self._fit_parameters = FitParameters() - self._fit_result = FitResult() + self._fit_result = FitResultBase() self._fit_parameter_correlations = FitParameterCorrelations() self._has_persisted_fit_state_data = False self._persisted_fit_state_sidecar: dict[str, object] = {} @@ -1205,7 +1205,7 @@ def _clear_persisted_fit_state(self) -> None: """Reset all persisted fit-state categories before a new fit.""" self._clear_minimizer_result_projection() self._fit_parameters = FitParameters() - self._fit_result = FitResult() + self._fit_result = FitResultBase() self._fit_parameter_correlations = FitParameterCorrelations() self._set_has_persisted_fit_state(value=False) self._persisted_fit_state_sidecar = {} diff --git a/src/easydiffraction/analysis/categories/__init__.py b/src/easydiffraction/analysis/categories/__init__.py index d6dcda46b..54fea54e5 100644 --- a/src/easydiffraction/analysis/categories/__init__.py +++ b/src/easydiffraction/analysis/categories/__init__.py @@ -11,7 +11,7 @@ from easydiffraction.analysis.categories.fit_parameter_correlations import FitParameterCorrelations from easydiffraction.analysis.categories.fit_parameters import FitParameterItem from easydiffraction.analysis.categories.fit_parameters import FitParameters -from easydiffraction.analysis.categories.fit_result import FitResult +from easydiffraction.analysis.categories.fit_result import FitResultBase from easydiffraction.analysis.categories.fitting_mode import FittingMode from easydiffraction.analysis.categories.fitting_mode import FittingModeFactory from easydiffraction.analysis.categories.joint_fit import JointFitCollection diff --git a/src/easydiffraction/analysis/categories/fit_result/__init__.py b/src/easydiffraction/analysis/categories/fit_result/__init__.py index 8578ab47b..42426c8a8 100644 --- a/src/easydiffraction/analysis/categories/fit_result/__init__.py +++ b/src/easydiffraction/analysis/categories/fit_result/__init__.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from easydiffraction.analysis.categories.fit_result.default import FitResult +from easydiffraction.analysis.categories.fit_result.base import FitResultBase from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory diff --git a/src/easydiffraction/analysis/categories/fit_result/base.py b/src/easydiffraction/analysis/categories/fit_result/base.py new file mode 100644 index 000000000..2d82bf63a --- /dev/null +++ b/src/easydiffraction/analysis/categories/fit_result/base.py @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Common fit-result status category.""" + +from __future__ import annotations + +from typing import ClassVar + +from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory +from easydiffraction.analysis.enums import FitResultKindEnum +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.core.variable import BoolDescriptor +from easydiffraction.core.variable import GenericDescriptorBase +from easydiffraction.core.variable import IntegerDescriptor +from easydiffraction.core.variable import NumericDescriptor +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.io.cif.handler import CifHandler + + +@FitResultFactory.register +class FitResultBase(CategoryItem): + """Common persisted fit-result status metadata.""" + + _category_code = 'fit_result' + _result_descriptor_names: ClassVar[tuple[str, ...]] = ( + 'success', + 'message', + 'iterations', + 'fitting_time', + 'reduced_chi_square', + 'result_kind', + ) + + type_info = TypeInfo( + tag='default', + description='Common persisted fit-result status metadata', + ) + + def __init__(self) -> None: + super().__init__() + self._result_kind = StringDescriptor( + name='result_kind', + description='Kind of the latest persisted fit-result projection.', + value_spec=AttributeSpec( + default=FitResultKindEnum.default().value, + validator=MembershipValidator( + allowed=[member.value for member in FitResultKindEnum] + ), + ), + cif_handler=CifHandler(names=['_fit_result.result_kind']), + ) + self._success = BoolDescriptor( + name='success', + description='Whether the latest persisted fit-result projection succeeded.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_fit_result.success']), + ) + self._message = StringDescriptor( + name='message', + description='Status message for the latest persisted fit-result projection.', + value_spec=AttributeSpec(default=''), + cif_handler=CifHandler(names=['_fit_result.message']), + ) + self._iterations = IntegerDescriptor( + name='iterations', + description='Iteration count for the latest persisted fit-result projection.', + value_spec=AttributeSpec(default=0), + cif_handler=CifHandler(names=['_fit_result.iterations']), + ) + self._fitting_time = NumericDescriptor( + name='fitting_time', + description='Fitting time in seconds for the latest persisted projection.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_fit_result.fitting_time']), + ) + self._reduced_chi_square = NumericDescriptor( + name='reduced_chi_square', + description='Reduced chi-square for the latest persisted projection.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_fit_result.reduced_chi_square']), + ) + + @property + def result_kind(self) -> StringDescriptor: + """Kind of the latest persisted fit-result projection.""" + return self._result_kind + + def _set_result_kind(self, value: str) -> None: + """Set the result kind for internal callers.""" + self._result_kind.value = value + + @property + def success(self) -> BoolDescriptor: + """ + Whether the latest persisted fit-result projection succeeded. + """ + return self._success + + def _set_success(self, *, value: bool) -> None: + """Set the success flag for internal callers.""" + self._success.value = value + + @property + def message(self) -> StringDescriptor: + """ + Status message for the latest persisted fit-result projection. + """ + return self._message + + def _set_message(self, value: str) -> None: + """Set the fit-result message for internal callers.""" + self._message.value = value + + @property + def iterations(self) -> IntegerDescriptor: + """ + Iteration count for the latest persisted fit-result projection. + """ + return self._iterations + + def _set_iterations(self, value: int) -> None: + """Set the iteration count for internal callers.""" + self._iterations.value = value + + @property + def fitting_time(self) -> NumericDescriptor: + """ + Fitting time in seconds for the latest persisted projection. + """ + return self._fitting_time + + def _set_fitting_time(self, value: float | None) -> None: + """Set the fitting time for internal callers.""" + self._fitting_time.value = value + + @property + def reduced_chi_square(self) -> NumericDescriptor: + """Reduced chi-square for the latest persisted projection.""" + return self._reduced_chi_square + + def _set_reduced_chi_square(self, value: float | None) -> None: + """Set the reduced chi-square for internal callers.""" + self._reduced_chi_square.value = value + + def _reset_result_descriptors(self) -> None: + """Reset fit-result descriptors to declared defaults.""" + for name in self._result_descriptor_names: + descriptor = getattr(self, name) + if isinstance(descriptor, GenericDescriptorBase): + descriptor.value = descriptor._value_spec.default_value() diff --git a/src/easydiffraction/analysis/categories/fit_result/default.py b/src/easydiffraction/analysis/categories/fit_result/default.py index 564df008e..38c500e1e 100644 --- a/src/easydiffraction/analysis/categories/fit_result/default.py +++ b/src/easydiffraction/analysis/categories/fit_result/default.py @@ -1,135 +1,9 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -"""Common fit-result status category.""" +"""Default fit-result category import.""" from __future__ import annotations -from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory -from easydiffraction.analysis.enums import FitResultKindEnum -from easydiffraction.core.category import CategoryItem -from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.validation import AttributeSpec -from easydiffraction.core.validation import MembershipValidator -from easydiffraction.core.variable import BoolDescriptor -from easydiffraction.core.variable import IntegerDescriptor -from easydiffraction.core.variable import NumericDescriptor -from easydiffraction.core.variable import StringDescriptor -from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.analysis.categories.fit_result.base import FitResultBase - -@FitResultFactory.register -class FitResult(CategoryItem): - """Common persisted fit-result status metadata.""" - - _category_code = 'fit_result' - - type_info = TypeInfo( - tag='default', - description='Common persisted fit-result status metadata', - ) - - def __init__(self) -> None: - super().__init__() - self._result_kind = StringDescriptor( - name='result_kind', - description='Kind of the latest persisted fit-result projection.', - value_spec=AttributeSpec( - default=FitResultKindEnum.default().value, - validator=MembershipValidator( - allowed=[member.value for member in FitResultKindEnum] - ), - ), - cif_handler=CifHandler(names=['_fit_result.result_kind']), - ) - self._success = BoolDescriptor( - name='success', - description='Whether the latest persisted fit-result projection succeeded.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_fit_result.success']), - ) - self._message = StringDescriptor( - name='message', - description='Status message for the latest persisted fit-result projection.', - value_spec=AttributeSpec(default=''), - cif_handler=CifHandler(names=['_fit_result.message']), - ) - self._iterations = IntegerDescriptor( - name='iterations', - description='Iteration count for the latest persisted fit-result projection.', - value_spec=AttributeSpec(default=0), - cif_handler=CifHandler(names=['_fit_result.iterations']), - ) - self._fitting_time = NumericDescriptor( - name='fitting_time', - description='Fitting time in seconds for the latest persisted projection.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_fit_result.fitting_time']), - ) - self._reduced_chi_square = NumericDescriptor( - name='reduced_chi_square', - description='Reduced chi-square for the latest persisted projection.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_fit_result.reduced_chi_square']), - ) - - @property - def result_kind(self) -> StringDescriptor: - """Kind of the latest persisted fit-result projection.""" - return self._result_kind - - def _set_result_kind(self, value: str) -> None: - """Set the result kind for internal callers.""" - self._result_kind.value = value - - @property - def success(self) -> BoolDescriptor: - """ - Whether the latest persisted fit-result projection succeeded. - """ - return self._success - - def _set_success(self, *, value: bool) -> None: - """Set the success flag for internal callers.""" - self._success.value = value - - @property - def message(self) -> StringDescriptor: - """ - Status message for the latest persisted fit-result projection. - """ - return self._message - - def _set_message(self, value: str) -> None: - """Set the fit-result message for internal callers.""" - self._message.value = value - - @property - def iterations(self) -> IntegerDescriptor: - """ - Iteration count for the latest persisted fit-result projection. - """ - return self._iterations - - def _set_iterations(self, value: int) -> None: - """Set the iteration count for internal callers.""" - self._iterations.value = value - - @property - def fitting_time(self) -> NumericDescriptor: - """ - Fitting time in seconds for the latest persisted projection. - """ - return self._fitting_time - - def _set_fitting_time(self, value: float | None) -> None: - """Set the fitting time for internal callers.""" - self._fitting_time.value = value - - @property - def reduced_chi_square(self) -> NumericDescriptor: - """Reduced chi-square for the latest persisted projection.""" - return self._reduced_chi_square - - def _set_reduced_chi_square(self, value: float | None) -> None: - """Set the reduced chi-square for internal callers.""" - self._reduced_chi_square.value = value +DEFAULT_FIT_RESULT_CLASS: type[FitResultBase] = FitResultBase From 23e56dbda93eb132f261f31ea7a043bf1df0ef22 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:40:57 +0200 Subject: [PATCH 14/38] Add LeastSquaresFitResult class --- .../dev/plans/minimizer-input-output-split.md | 2 +- .../analysis/categories/fit_result/lsq.py | 218 ++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/analysis/categories/fit_result/lsq.py diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 456612fcf..9024fc060 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -229,7 +229,7 @@ Mark `[x]` as each step lands. Commit: `Rename FitResult to FitResultBase, add reset hooks` -- [ ] **P1.2 — Add `LeastSquaresFitResult` class.** New file +- [x] **P1.2 — Add `LeastSquaresFitResult` class.** New file `src/easydiffraction/analysis/categories/fit_result/lsq.py`. `LeastSquaresFitResult(FitResultBase)` declares: `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, diff --git a/src/easydiffraction/analysis/categories/fit_result/lsq.py b/src/easydiffraction/analysis/categories/fit_result/lsq.py new file mode 100644 index 000000000..3775ee472 --- /dev/null +++ b/src/easydiffraction/analysis/categories/fit_result/lsq.py @@ -0,0 +1,218 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Least-squares fit-result category.""" + +from __future__ import annotations + +from typing import ClassVar + +from easydiffraction.analysis.categories.fit_result.base import FitResultBase +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import BoolDescriptor +from easydiffraction.core.variable import NumericDescriptor +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.io.cif.handler import CifHandler + + +class LeastSquaresFitResult(FitResultBase): + """Persisted least-squares fit-result metadata.""" + + type_info = TypeInfo( + tag='least_squares', + description='Persisted least-squares fit-result metadata', + ) + _result_descriptor_names: ClassVar[tuple[str, ...]] = ( + *FitResultBase._result_descriptor_names, + 'objective_name', + 'objective_value', + 'n_data_points', + 'n_parameters', + 'n_free_parameters', + 'degrees_of_freedom', + 'covariance_available', + 'correlation_available', + 'exit_reason', + ) + _expected_descriptor_names: ClassVar[tuple[str, ...]] = _result_descriptor_names + + def __init__(self) -> None: + super().__init__() + self._objective_name = self._string_result_descriptor( + 'objective_name', + 'Objective function name for the persisted deterministic fit.', + ) + self._objective_value = self._numeric_result_descriptor( + 'objective_value', + 'Objective value for the persisted deterministic fit.', + ) + self._n_data_points = self._integer_result_descriptor( + 'n_data_points', + 'Number of data points used in the persisted deterministic fit.', + ) + self._n_parameters = self._integer_result_descriptor( + 'n_parameters', + 'Number of parameters considered in the persisted deterministic fit.', + ) + self._n_free_parameters = self._integer_result_descriptor( + 'n_free_parameters', + 'Number of free parameters in the persisted deterministic fit.', + ) + self._degrees_of_freedom = self._integer_result_descriptor( + 'degrees_of_freedom', + 'Degrees of freedom for the persisted deterministic fit.', + ) + self._covariance_available = self._bool_result_descriptor( + 'covariance_available', + 'Whether covariance was available for the persisted deterministic fit.', + ) + self._correlation_available = self._bool_result_descriptor( + 'correlation_available', + 'Whether correlations were available for the persisted deterministic fit.', + ) + self._exit_reason = self._string_result_descriptor( + 'exit_reason', + 'Backend exit reason for the persisted deterministic fit.', + ) + + @staticmethod + def _string_result_descriptor(name: str, description: str) -> StringDescriptor: + """ + Create a string-valued result descriptor. + + Defaults to ``None`` so a CIF written before any fit emits ``?`` + rather than an empty string, matching the shared "no fit" + semantics of numeric, integer-like, and bool result fields. + """ + return StringDescriptor( + name=name, + description=description, + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[f'_fit_result.{name}']), + ) + + @staticmethod + def _numeric_result_descriptor( + name: str, + description: str, + *, + default: float | None = None, + allow_none: bool = True, + ) -> NumericDescriptor: + """Create a numeric result descriptor.""" + return NumericDescriptor( + name=name, + description=description, + value_spec=AttributeSpec(default=default, allow_none=allow_none), + cif_handler=CifHandler(names=[f'_fit_result.{name}']), + ) + + @staticmethod + def _integer_result_descriptor(name: str, description: str) -> NumericDescriptor: + """ + Create an integer-like numeric result descriptor. + + Defaults to ``None`` so a CIF written before any fit emits ``?`` + rather than ``0``; the scientist audience reads ``0`` as a + degenerate result, not as "no fit yet". + """ + return NumericDescriptor( + name=name, + description=description, + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[f'_fit_result.{name}']), + ) + + @staticmethod + def _bool_result_descriptor(name: str, description: str) -> BoolDescriptor: + """ + Create a boolean result descriptor. + + Defaults to ``None`` so a CIF written before any fit emits ``?`` + rather than ``false``; ``false`` would otherwise read as an + active result instead of "no fit happened yet". + """ + return BoolDescriptor( + name=name, + description=description, + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[f'_fit_result.{name}']), + ) + + @property + def objective_name(self) -> StringDescriptor: + """ + Objective function name for the persisted deterministic fit. + """ + return self._objective_name + + def _set_objective_name(self, value: str | None) -> None: + self._objective_name.value = value + + @property + def objective_value(self) -> NumericDescriptor: + """Objective value for the persisted deterministic fit.""" + return self._objective_value + + def _set_objective_value(self, value: float | None) -> None: + self._objective_value.value = value + + @property + def n_data_points(self) -> NumericDescriptor: + """ + Number of data points used in the persisted deterministic fit. + """ + return self._n_data_points + + def _set_n_data_points(self, value: float | None) -> None: + self._n_data_points.value = value + + @property + def n_parameters(self) -> NumericDescriptor: + """Number of parameters in the persisted deterministic fit.""" + return self._n_parameters + + def _set_n_parameters(self, value: float | None) -> None: + self._n_parameters.value = value + + @property + def n_free_parameters(self) -> NumericDescriptor: + """ + Number of free parameters in the persisted deterministic fit. + """ + return self._n_free_parameters + + def _set_n_free_parameters(self, value: float | None) -> None: + self._n_free_parameters.value = value + + @property + def degrees_of_freedom(self) -> NumericDescriptor: + """Degrees of freedom for the persisted deterministic fit.""" + return self._degrees_of_freedom + + def _set_degrees_of_freedom(self, value: float | None) -> None: + self._degrees_of_freedom.value = value + + @property + def covariance_available(self) -> BoolDescriptor: + """Whether deterministic covariance was available.""" + return self._covariance_available + + def _set_covariance_available(self, *, value: bool | None) -> None: + self._covariance_available.value = value + + @property + def correlation_available(self) -> BoolDescriptor: + """Whether deterministic correlations were available.""" + return self._correlation_available + + def _set_correlation_available(self, *, value: bool | None) -> None: + self._correlation_available.value = value + + @property + def exit_reason(self) -> StringDescriptor: + """Backend exit reason for the persisted deterministic fit.""" + return self._exit_reason + + def _set_exit_reason(self, value: str | None) -> None: + self._exit_reason.value = value From 0f625f7dbfaedcfe9da634c11b3afa7b2a24edb4 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:42:06 +0200 Subject: [PATCH 15/38] Add BayesianFitResult class --- .../dev/plans/minimizer-input-output-split.md | 2 +- .../categories/fit_result/bayesian.py | 205 ++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/analysis/categories/fit_result/bayesian.py diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 9024fc060..5f17bb8ba 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -249,7 +249,7 @@ Mark `[x]` as each step lands. parity with the minimizer hierarchy. Tests deferred to Phase 2. Commit: `Add LeastSquaresFitResult class` -- [ ] **P1.3 — Add `BayesianFitResult` class.** New file +- [x] **P1.3 — Add `BayesianFitResult` class.** New file `src/easydiffraction/analysis/categories/fit_result/bayesian.py`. `BayesianFitResult(FitResultBase)` declares: `point_estimate_name`, `sampler_completed`, diff --git a/src/easydiffraction/analysis/categories/fit_result/bayesian.py b/src/easydiffraction/analysis/categories/fit_result/bayesian.py new file mode 100644 index 000000000..bff8ea1f9 --- /dev/null +++ b/src/easydiffraction/analysis/categories/fit_result/bayesian.py @@ -0,0 +1,205 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Bayesian fit-result category.""" + +from __future__ import annotations + +from typing import ClassVar + +from easydiffraction.analysis.categories.fit_result.base import FitResultBase +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import BoolDescriptor +from easydiffraction.core.variable import NumericDescriptor +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.io.cif.handler import CifHandler + + +class BayesianFitResult(FitResultBase): + """Persisted Bayesian fit-result metadata.""" + + type_info = TypeInfo( + tag='bayesian', + description='Persisted Bayesian fit-result metadata', + ) + _result_descriptor_names: ClassVar[tuple[str, ...]] = ( + *FitResultBase._result_descriptor_names, + 'point_estimate_name', + 'sampler_completed', + 'credible_interval_inner', + 'credible_interval_outer', + 'acceptance_rate_mean', + 'gelman_rubin_max', + 'effective_sample_size_min', + 'best_log_posterior', + ) + _expected_descriptor_names: ClassVar[tuple[str, ...]] = _result_descriptor_names + + def __init__(self) -> None: + super().__init__() + self._point_estimate_name = self._point_estimate_name_descriptor() + self._sampler_completed = self._sampler_completed_descriptor() + self._credible_interval_inner = self._credible_interval_inner_descriptor() + self._credible_interval_outer = self._credible_interval_outer_descriptor() + self._acceptance_rate_mean = self._acceptance_rate_mean_descriptor() + self._gelman_rubin_max = self._gelman_rubin_max_descriptor() + self._effective_sample_size_min = self._effective_sample_size_min_descriptor() + self._best_log_posterior = self._best_log_posterior_descriptor() + + @staticmethod + def _point_estimate_name_descriptor() -> StringDescriptor: + """Create a point-estimate-name descriptor.""" + return StringDescriptor( + name='point_estimate_name', + description='Committed sampled point estimate name.', + value_spec=AttributeSpec(default='best_sample'), + cif_handler=CifHandler(names=['_fit_result.point_estimate_name']), + ) + + @staticmethod + def _sampler_completed_descriptor() -> BoolDescriptor: + """Create a sampler-completed descriptor.""" + return BoolDescriptor( + name='sampler_completed', + description='Whether the sampler completed and returned posterior data.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_fit_result.sampler_completed']), + ) + + @staticmethod + def _credible_interval_inner_descriptor() -> NumericDescriptor: + """Create an inner credible-interval descriptor.""" + return NumericDescriptor( + name='credible_interval_inner', + description='Inner credible-interval level used in summaries.', + value_spec=AttributeSpec(default=0.68), + cif_handler=CifHandler(names=['_fit_result.credible_interval_inner']), + ) + + @staticmethod + def _credible_interval_outer_descriptor() -> NumericDescriptor: + """Create an outer credible-interval descriptor.""" + return NumericDescriptor( + name='credible_interval_outer', + description='Outer credible-interval level used in summaries.', + value_spec=AttributeSpec(default=0.95), + cif_handler=CifHandler(names=['_fit_result.credible_interval_outer']), + ) + + @staticmethod + def _acceptance_rate_mean_descriptor() -> NumericDescriptor: + """Create an acceptance-rate descriptor.""" + return NumericDescriptor( + name='acceptance_rate_mean', + description='Mean sampler acceptance rate.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_fit_result.acceptance_rate_mean']), + ) + + @staticmethod + def _gelman_rubin_max_descriptor() -> NumericDescriptor: + """Create a Gelman-Rubin descriptor.""" + return NumericDescriptor( + name='gelman_rubin_max', + description='Maximum rank-normalized split R-hat.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_fit_result.gelman_rubin_max']), + ) + + @staticmethod + def _effective_sample_size_min_descriptor() -> NumericDescriptor: + """Create an effective-sample-size descriptor.""" + return NumericDescriptor( + name='effective_sample_size_min', + description='Minimum bulk effective sample size.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_fit_result.effective_sample_size_min']), + ) + + @staticmethod + def _best_log_posterior_descriptor() -> NumericDescriptor: + """Create a best-log-posterior descriptor.""" + return NumericDescriptor( + name='best_log_posterior', + description='Best log-posterior value found.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_fit_result.best_log_posterior']), + ) + + @property + def point_estimate_name(self) -> StringDescriptor: + """Committed sampled point estimate name.""" + return self._point_estimate_name + + def _set_point_estimate_name(self, value: str) -> None: + """Set the point-estimate name for internal callers.""" + self._point_estimate_name.value = value + + @property + def sampler_completed(self) -> BoolDescriptor: + """Whether the sampler completed and returned posterior data.""" + return self._sampler_completed + + def _set_sampler_completed(self, *, value: bool) -> None: + """Set the sampler-completed flag for internal callers.""" + self._sampler_completed.value = value + + @property + def credible_interval_inner(self) -> NumericDescriptor: + """Inner credible-interval level used in summaries.""" + return self._credible_interval_inner + + def _set_credible_interval_inner(self, value: float) -> None: + """ + Set the inner credible-interval level for internal callers. + """ + self._credible_interval_inner.value = value + + @property + def credible_interval_outer(self) -> NumericDescriptor: + """Outer credible-interval level used in summaries.""" + return self._credible_interval_outer + + def _set_credible_interval_outer(self, value: float) -> None: + """ + Set the outer credible-interval level for internal callers. + """ + self._credible_interval_outer.value = value + + @property + def acceptance_rate_mean(self) -> NumericDescriptor: + """Mean sampler acceptance rate.""" + return self._acceptance_rate_mean + + def _set_acceptance_rate_mean(self, value: float | None) -> None: + """Set the acceptance-rate mean for internal callers.""" + self._acceptance_rate_mean.value = value + + @property + def gelman_rubin_max(self) -> NumericDescriptor: + """Maximum rank-normalized split R-hat.""" + return self._gelman_rubin_max + + def _set_gelman_rubin_max(self, value: float | None) -> None: + """Set the maximum R-hat for internal callers.""" + self._gelman_rubin_max.value = value + + @property + def effective_sample_size_min(self) -> NumericDescriptor: + """Minimum bulk effective sample size.""" + return self._effective_sample_size_min + + def _set_effective_sample_size_min(self, value: float | None) -> None: + """ + Set the minimum effective sample size for internal callers. + """ + self._effective_sample_size_min.value = value + + @property + def best_log_posterior(self) -> NumericDescriptor: + """Best log-posterior value found.""" + return self._best_log_posterior + + def _set_best_log_posterior(self, value: float | None) -> None: + """Set the best log-posterior for internal callers.""" + self._best_log_posterior.value = value From fe072fb17cf53b73ccf54e54401fd5da563ec109 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:42:51 +0200 Subject: [PATCH 16/38] Register fit-result family classes with factory --- docs/dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/analysis/categories/fit_result/__init__.py | 2 ++ src/easydiffraction/analysis/categories/fit_result/bayesian.py | 2 ++ src/easydiffraction/analysis/categories/fit_result/lsq.py | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 5f17bb8ba..35d65d75d 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -261,7 +261,7 @@ Mark `[x]` as each step lands. Tests deferred to Phase 2. Commit: `Add BayesianFitResult class` -- [ ] **P1.4 — Register fit-result classes with the existing +- [x] **P1.4 — Register fit-result classes with the existing `FitResultFactory`.** The factory already exists at [`src/easydiffraction/analysis/categories/fit_result/factory.py`](../../../src/easydiffraction/analysis/categories/fit_result/factory.py) and currently registers only the default common class. Update diff --git a/src/easydiffraction/analysis/categories/fit_result/__init__.py b/src/easydiffraction/analysis/categories/fit_result/__init__.py index 42426c8a8..6bc137ee8 100644 --- a/src/easydiffraction/analysis/categories/fit_result/__init__.py +++ b/src/easydiffraction/analysis/categories/fit_result/__init__.py @@ -2,4 +2,6 @@ # SPDX-License-Identifier: BSD-3-Clause from easydiffraction.analysis.categories.fit_result.base import FitResultBase +from easydiffraction.analysis.categories.fit_result.bayesian import BayesianFitResult from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory +from easydiffraction.analysis.categories.fit_result.lsq import LeastSquaresFitResult diff --git a/src/easydiffraction/analysis/categories/fit_result/bayesian.py b/src/easydiffraction/analysis/categories/fit_result/bayesian.py index bff8ea1f9..1e97106ee 100644 --- a/src/easydiffraction/analysis/categories/fit_result/bayesian.py +++ b/src/easydiffraction/analysis/categories/fit_result/bayesian.py @@ -7,6 +7,7 @@ from typing import ClassVar from easydiffraction.analysis.categories.fit_result.base import FitResultBase +from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.variable import BoolDescriptor @@ -15,6 +16,7 @@ from easydiffraction.io.cif.handler import CifHandler +@FitResultFactory.register class BayesianFitResult(FitResultBase): """Persisted Bayesian fit-result metadata.""" diff --git a/src/easydiffraction/analysis/categories/fit_result/lsq.py b/src/easydiffraction/analysis/categories/fit_result/lsq.py index 3775ee472..9e8e31ec9 100644 --- a/src/easydiffraction/analysis/categories/fit_result/lsq.py +++ b/src/easydiffraction/analysis/categories/fit_result/lsq.py @@ -7,6 +7,7 @@ from typing import ClassVar from easydiffraction.analysis.categories.fit_result.base import FitResultBase +from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.variable import BoolDescriptor @@ -15,6 +16,7 @@ from easydiffraction.io.cif.handler import CifHandler +@FitResultFactory.register class LeastSquaresFitResult(FitResultBase): """Persisted least-squares fit-result metadata.""" From 31a0ca6cfa54ba29e118b636c8b636e238a14696 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:43:26 +0200 Subject: [PATCH 17/38] Declare paired _fit_result_class on minimizer bases --- docs/dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/analysis/categories/minimizer/base.py | 2 ++ .../analysis/categories/minimizer/bayesian_base.py | 2 ++ src/easydiffraction/analysis/categories/minimizer/lsq_base.py | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 35d65d75d..d2d86752e 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -280,7 +280,7 @@ Mark `[x]` as each step lands. paired, per ADR §1. Commit: `Register fit-result family classes with factory` -- [ ] **P1.5 — Declare `_fit_result_class` on minimizer bases.** In +- [x] **P1.5 — Declare `_fit_result_class` on minimizer bases.** In `src/easydiffraction/analysis/categories/minimizer/lsq_base.py`, add `_fit_result_class: ClassVar[type] = LeastSquaresFitResult`. In diff --git a/src/easydiffraction/analysis/categories/minimizer/base.py b/src/easydiffraction/analysis/categories/minimizer/base.py index 1e0a6c672..fb583964d 100644 --- a/src/easydiffraction/analysis/categories/minimizer/base.py +++ b/src/easydiffraction/analysis/categories/minimizer/base.py @@ -6,6 +6,7 @@ from typing import ClassVar +from easydiffraction.analysis.categories.fit_result.base import FitResultBase from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.category import CategoryItem from easydiffraction.core.switchable import SwitchableCategoryBase @@ -25,6 +26,7 @@ class MinimizerCategoryBase(CategoryItem, SwitchableCategoryBase): _native_key_map: ClassVar[dict[str, str]] = {} _setting_descriptor_names: ClassVar[tuple[str, ...]] = () _result_descriptor_names: ClassVar[tuple[str, ...]] = () + _fit_result_class: ClassVar[type[FitResultBase]] = FitResultBase def __init__(self) -> None: super().__init__() diff --git a/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py b/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py index 7e999b859..7e613266d 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py +++ b/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py @@ -6,6 +6,7 @@ from typing import ClassVar +from easydiffraction.analysis.categories.fit_result.bayesian import BayesianFitResult from easydiffraction.analysis.categories.minimizer.base import MinimizerCategoryBase from easydiffraction.analysis.minimizers.enums import InitializationMethodEnum from easydiffraction.core.validation import AttributeSpec @@ -21,6 +22,7 @@ class BayesianMinimizerBase(MinimizerCategoryBase): """Shared behavior for Bayesian minimizer categories.""" + _fit_result_class: ClassVar[type] = BayesianFitResult _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( 'sampling_steps', 'burn_in_steps', diff --git a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py index bbe72b0fa..d26aed588 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py +++ b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py @@ -6,6 +6,7 @@ from typing import ClassVar +from easydiffraction.analysis.categories.fit_result.lsq import LeastSquaresFitResult from easydiffraction.analysis.categories.minimizer.base import MinimizerCategoryBase from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator @@ -20,6 +21,7 @@ class LeastSquaresMinimizerBase(MinimizerCategoryBase): """Shared behavior for least-squares minimizer categories.""" _default_max_iterations: ClassVar[int] = 1000 + _fit_result_class: ClassVar[type] = LeastSquaresFitResult _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( 'max_iterations', 'objective_name', From 090c1e329fd118f3f92f0923cbe8847c7168333f Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:44:56 +0200 Subject: [PATCH 18/38] Wire fit_result swap and reset paths to paired class --- docs/dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/analysis/analysis.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index d2d86752e..c5bd5d992 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -293,7 +293,7 @@ Mark `[x]` as each step lands. `_swap_minimizer` reads through this attribute). Commit: `Declare paired _fit_result_class on minimizer bases` -- [ ] **P1.6 — Wire `Analysis._swap_minimizer` to install both +- [x] **P1.6 — Wire `Analysis._swap_minimizer` to install both instances, and update every `_fit_result` reset path.** In `src/easydiffraction/analysis/analysis.py`: diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 779796b4c..15bf97840 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -480,7 +480,7 @@ def __init__(self, project: object) -> None: ) self._sequential_fit_extract = SequentialFitExtractCollection() self._fit_parameters = FitParameters() - self._fit_result = FitResultBase() + self._fit_result = self._minimizer._fit_result_class() self._fit_parameter_correlations = FitParameterCorrelations() self._has_persisted_fit_state_data = False self._persisted_fit_state_sidecar: dict[str, object] = {} @@ -1062,8 +1062,11 @@ def _replace_minimizer( self._warn_about_minimizer_swap_defaults(old_defaults, new_minimizer) old_minimizer._parent = None + self._fit_result._parent = None self._minimizer = new_minimizer + self._fit_result = new_minimizer._fit_result_class() self._minimizer._parent = self + self._fit_result._parent = self self._fitter = Fitter(value) if announce: console.paragraph('Current minimizer changed to') @@ -1203,16 +1206,18 @@ def _fit_state_categories(self) -> list[object]: def _clear_persisted_fit_state(self) -> None: """Reset all persisted fit-state categories before a new fit.""" - self._clear_minimizer_result_projection() + self._clear_fit_result_projection() self._fit_parameters = FitParameters() - self._fit_result = FitResultBase() + self._fit_result._parent = None + self._fit_result = self.minimizer._fit_result_class() + self._fit_result._parent = self self._fit_parameter_correlations = FitParameterCorrelations() self._set_has_persisted_fit_state(value=False) self._persisted_fit_state_sidecar = {} - def _clear_minimizer_result_projection(self) -> None: - """Reset result-only fields on the active minimizer category.""" - self.minimizer._reset_result_descriptors() + def _clear_fit_result_projection(self) -> None: + """Reset result-only fields on the active fit-result category.""" + self.fit_result._reset_result_descriptors() def _capture_fit_parameter_state(self, parameters: list[Parameter]) -> None: """Capture pre-fit parameter state.""" From 078c76950cd23a82552e9dfe44b4139a4e686c2e Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:46:13 +0200 Subject: [PATCH 19/38] Route LSQ result writers to fit_result --- .../dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/analysis/analysis.py | 44 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index c5bd5d992..959f1abba 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -341,7 +341,7 @@ Mark `[x]` as each step lands. Commit: `Wire fit_result swap and reset paths to paired class` -- [ ] **P1.7 — Route LSQ result writers to `fit_result`.** In +- [x] **P1.7 — Route LSQ result writers to `fit_result`.** In `src/easydiffraction/analysis/analysis.py`, `_store_least_squares_result_projection` currently writes to `self.minimizer._set_objective_name(...)` etc. Reroute every diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 15bf97840..0fb9e4b67 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -778,21 +778,21 @@ def _restore_fit_results_from_projection(self) -> object | None: fitting_time=fitting_time, optimizer_name=engine_metadata['optimizer_name'], method_name=engine_metadata['method_name'], - objective_name=self.minimizer.objective_name.value, - objective_value=self.minimizer.objective_value.value, - n_data_points=_int_or_none(self.minimizer.n_data_points.value), - n_parameters=_int_or_none(self.minimizer.n_parameters.value), - n_free_parameters=_int_or_none(self.minimizer.n_free_parameters.value), - degrees_of_freedom=_int_or_none(self.minimizer.degrees_of_freedom.value), - covariance_available=self.minimizer.covariance_available.value, - correlation_available=self.minimizer.correlation_available.value, - runtime_seconds=self.minimizer.runtime_seconds.value, - iterations_performed=_int_or_none(self.minimizer.iterations_performed.value), - exit_reason=self.minimizer.exit_reason.value, + objective_name=self.fit_result.objective_name.value, + objective_value=self.fit_result.objective_value.value, + n_data_points=_int_or_none(self.fit_result.n_data_points.value), + n_parameters=_int_or_none(self.fit_result.n_parameters.value), + n_free_parameters=_int_or_none(self.fit_result.n_free_parameters.value), + degrees_of_freedom=_int_or_none(self.fit_result.degrees_of_freedom.value), + covariance_available=self.fit_result.covariance_available.value, + correlation_available=self.fit_result.correlation_available.value, + runtime_seconds=fitting_time, + iterations_performed=_int_or_none(self.fit_result.iterations.value), + exit_reason=self.fit_result.exit_reason.value, ) restored_results.message = self.fit_result.message.value restored_results.iterations = int(self.fit_result.iterations.value) - restored_results.chi_square = self.minimizer.objective_value.value + restored_results.chi_square = self.fit_result.objective_value.value self.fit_results = restored_results return restored_results @@ -1377,17 +1377,15 @@ def _store_least_squares_result_projection( else None ) - self.minimizer._set_objective_name('chi_square') - self.minimizer._set_objective_value(self._resolve_objective_value(results)) - self.minimizer._set_n_data_points(n_data_points) - self.minimizer._set_n_parameters(n_parameters) - self.minimizer._set_n_free_parameters(n_free_parameters) - self.minimizer._set_degrees_of_freedom(degrees_of_freedom) - self.minimizer._set_covariance_available(value=covariance is not None) - self.minimizer._set_correlation_available(value=correlation_matrix is not None) - self.minimizer._set_runtime_seconds(results.fitting_time) - self.minimizer._set_iterations_performed(results.iterations) - self.minimizer._set_exit_reason(results.message) + self.fit_result._set_objective_name('chi_square') + self.fit_result._set_objective_value(self._resolve_objective_value(results)) + self.fit_result._set_n_data_points(n_data_points) + self.fit_result._set_n_parameters(n_parameters) + self.fit_result._set_n_free_parameters(n_free_parameters) + self.fit_result._set_degrees_of_freedom(degrees_of_freedom) + self.fit_result._set_covariance_available(value=covariance is not None) + self.fit_result._set_correlation_available(value=correlation_matrix is not None) + self.fit_result._set_exit_reason(results.message) if correlation_matrix is not None: self._store_correlation_projection( From 558ec9d19a6509cd0c60d7d5d8a17de56080fab1 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:47:11 +0200 Subject: [PATCH 20/38] Route Bayesian result writers to fit_result --- .../dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/analysis/analysis.py | 31 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 959f1abba..7be3647d3 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -351,7 +351,7 @@ Mark `[x]` as each step lands. to `self.fit_result..value`). Commit: `Route LSQ result writers to fit_result` -- [ ] **P1.8 — Route Bayesian result writers to `fit_result`.** Same +- [x] **P1.8 — Route Bayesian result writers to `fit_result`.** Same treatment for `_store_posterior_fit_projection` and the Bayesian branch of `_restore_fit_results_from_projection`. Includes the `_set_credible_interval_*` calls — they now target diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 0fb9e4b67..5173a3000 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -736,13 +736,13 @@ def _restore_fit_results_from_projection(self) -> object | None: starting_parameters=list(restored_parameters), fitting_time=fitting_time, sampler_name=sampler_name, - point_estimate_name=self.minimizer.point_estimate_name.value, + point_estimate_name=self.fit_result.point_estimate_name.value, posterior_samples=posterior_samples, posterior_parameter_summaries=self._restored_posterior_summaries(), posterior_predictive=self._restored_predictive_summaries(), credible_interval_levels=( - float(self.minimizer.credible_interval_inner.value), - float(self.minimizer.credible_interval_outer.value), + float(self.fit_result.credible_interval_inner.value), + float(self.fit_result.credible_interval_outer.value), ), sampler_settings={ 'steps': int(sampler_settings.get('steps', 0)), @@ -755,14 +755,14 @@ def _restore_fit_results_from_projection(self) -> object | None: }, convergence_diagnostics={ 'converged': False, - 'max_r_hat': self.minimizer.gelman_rubin_max.value, - 'min_ess_bulk': self.minimizer.effective_sample_size_min.value, + 'max_r_hat': self.fit_result.gelman_rubin_max.value, + 'min_ess_bulk': self.fit_result.effective_sample_size_min.value, 'n_draws': int(sample_shape[0]), 'n_chains': int(sample_shape[1]), 'n_parameters': int(sample_shape[2]), }, - sampler_completed=bool(self.minimizer.sampler_completed.value), - best_log_posterior=self.minimizer.best_log_posterior.value, + sampler_completed=bool(self.fit_result.sampler_completed.value), + best_log_posterior=self.fit_result.best_log_posterior.value, ) restored_results.message = self.fit_result.message.value restored_results.iterations = int(self.fit_result.iterations.value) @@ -1682,15 +1682,14 @@ def _store_posterior_fit_projection(self, results: BayesianFitResults) -> None: point_estimate_name = results.point_estimate_name or 'best_sample' convergence = results.convergence_diagnostics - self.minimizer._set_runtime_seconds(results.fitting_time) - self.minimizer._set_point_estimate_name(point_estimate_name) - self.minimizer._set_sampler_completed(value=results.sampler_completed) - self.minimizer._set_best_log_posterior(results.best_log_posterior) - self.minimizer._set_credible_interval_inner(credible_interval_inner) - self.minimizer._set_credible_interval_outer(credible_interval_outer) - self.minimizer._set_gelman_rubin_max(convergence.get('max_r_hat')) - self.minimizer._set_effective_sample_size_min(convergence.get('min_ess_bulk')) - self.minimizer._set_acceptance_rate_mean(convergence.get('acceptance_rate_mean')) + self.fit_result._set_point_estimate_name(point_estimate_name) + self.fit_result._set_sampler_completed(value=results.sampler_completed) + self.fit_result._set_best_log_posterior(results.best_log_posterior) + self.fit_result._set_credible_interval_inner(credible_interval_inner) + self.fit_result._set_credible_interval_outer(credible_interval_outer) + self.fit_result._set_gelman_rubin_max(convergence.get('max_r_hat')) + self.fit_result._set_effective_sample_size_min(convergence.get('min_ess_bulk')) + self.fit_result._set_acceptance_rate_mean(convergence.get('acceptance_rate_mean')) self._store_posterior_samples_sidecar_projection(results) live_parameters = { From 104736939a76a0a59ce70ce16d5d452f18699b01 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:48:41 +0200 Subject: [PATCH 21/38] Remove LSQ output descriptors from minimizer base --- .../dev/plans/minimizer-input-output-split.md | 2 +- .../analysis/categories/minimizer/lsq_base.py | 231 +----------------- 2 files changed, 2 insertions(+), 231 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 7be3647d3..fa25eba52 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -358,7 +358,7 @@ Mark `[x]` as each step lands. `self.fit_result._set_credible_interval_*`. Commit: `Route Bayesian result writers to fit_result` -- [ ] **P1.9 — Remove output fields from LSQ minimizer base.** In +- [x] **P1.9 — Remove output fields from LSQ minimizer base.** In `src/easydiffraction/analysis/categories/minimizer/lsq_base.py`, delete the descriptor declarations and properties for `objective_name`, `objective_value`, `n_data_points`, diff --git a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py index d26aed588..dfbf16cd5 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py +++ b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py @@ -10,10 +10,7 @@ from easydiffraction.analysis.categories.minimizer.base import MinimizerCategoryBase from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator -from easydiffraction.core.variable import BoolDescriptor from easydiffraction.core.variable import IntegerDescriptor -from easydiffraction.core.variable import NumericDescriptor -from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler @@ -24,83 +21,16 @@ class LeastSquaresMinimizerBase(MinimizerCategoryBase): _fit_result_class: ClassVar[type] = LeastSquaresFitResult _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( 'max_iterations', - 'objective_name', - 'objective_value', - 'n_data_points', - 'n_parameters', - 'n_free_parameters', - 'degrees_of_freedom', - 'covariance_available', - 'correlation_available', - 'runtime_seconds', - 'iterations_performed', - 'exit_reason', ) _native_key_map: ClassVar[dict[str, str]] = { 'max_iterations': 'max_iterations', } _setting_descriptor_names: ClassVar[tuple[str, ...]] = ('max_iterations',) - _result_descriptor_names: ClassVar[tuple[str, ...]] = ( - 'objective_name', - 'objective_value', - 'n_data_points', - 'n_parameters', - 'n_free_parameters', - 'degrees_of_freedom', - 'covariance_available', - 'correlation_available', - 'runtime_seconds', - 'iterations_performed', - 'exit_reason', - ) + _result_descriptor_names: ClassVar[tuple[str, ...]] = () def __init__(self) -> None: super().__init__() self._max_iterations = self._max_iterations_descriptor(self._default_max_iterations) - self._objective_name = self._string_result_descriptor( - 'objective_name', - 'Objective function name for the persisted deterministic fit.', - ) - self._objective_value = self._numeric_result_descriptor( - 'objective_value', - 'Objective value for the persisted deterministic fit.', - ) - self._n_data_points = self._integer_result_descriptor( - 'n_data_points', - 'Number of data points used in the persisted deterministic fit.', - ) - self._n_parameters = self._integer_result_descriptor( - 'n_parameters', - 'Number of parameters considered in the persisted deterministic fit.', - ) - self._n_free_parameters = self._integer_result_descriptor( - 'n_free_parameters', - 'Number of free parameters in the persisted deterministic fit.', - ) - self._degrees_of_freedom = self._integer_result_descriptor( - 'degrees_of_freedom', - 'Degrees of freedom for the persisted deterministic fit.', - ) - self._covariance_available = self._bool_result_descriptor( - 'covariance_available', - 'Whether covariance was available for the persisted deterministic fit.', - ) - self._correlation_available = self._bool_result_descriptor( - 'correlation_available', - 'Whether correlations were available for the persisted deterministic fit.', - ) - self._runtime_seconds = self._numeric_result_descriptor( - 'runtime_seconds', - 'Runtime in seconds for the persisted deterministic fit.', - ) - self._iterations_performed = self._integer_result_descriptor( - 'iterations_performed', - 'Number of iterations performed by the persisted deterministic fit.', - ) - self._exit_reason = self._string_result_descriptor( - 'exit_reason', - 'Backend exit reason for the persisted deterministic fit.', - ) @staticmethod def _max_iterations_descriptor(default: int) -> IntegerDescriptor: @@ -112,71 +42,6 @@ def _max_iterations_descriptor(default: int) -> IntegerDescriptor: cif_handler=CifHandler(names=['_minimizer.max_iterations']), ) - @staticmethod - def _string_result_descriptor(name: str, description: str) -> StringDescriptor: - """ - Create a string-valued result descriptor. - - Defaults to ``None`` so a CIF written before any fit emits ``?`` - rather than an empty string, matching the "no fit happened yet" - semantics shared with the numeric/integer/bool variants. - """ - return StringDescriptor( - name=name, - description=description, - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=[f'_minimizer.{name}']), - ) - - @staticmethod - def _numeric_result_descriptor( - name: str, - description: str, - *, - default: float | None = None, - allow_none: bool = True, - ) -> NumericDescriptor: - """Create a numeric result descriptor.""" - return NumericDescriptor( - name=name, - description=description, - value_spec=AttributeSpec(default=default, allow_none=allow_none), - cif_handler=CifHandler(names=[f'_minimizer.{name}']), - ) - - @staticmethod - def _integer_result_descriptor(name: str, description: str) -> NumericDescriptor: - """ - Create an integer-like numeric result descriptor. - - Defaults to ``None`` so a CIF written before any fit emits ``?`` - rather than ``0``; the scientist audience reads ``0`` as a - degenerate result, not as "no fit yet". - """ - return NumericDescriptor( - name=name, - description=description, - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=[f'_minimizer.{name}']), - ) - - @staticmethod - def _bool_result_descriptor(name: str, description: str) -> BoolDescriptor: - """ - Create a boolean result descriptor. - - Defaults to ``None`` so a CIF written before any fit emits ``?`` - rather than ``false``; ``false`` would otherwise read as - "covariance/correlation was actively unavailable" instead of "no - fit happened yet". - """ - return BoolDescriptor( - name=name, - description=description, - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=[f'_minimizer.{name}']), - ) - @property def max_iterations(self) -> IntegerDescriptor: """Maximum solver iterations.""" @@ -185,97 +50,3 @@ def max_iterations(self) -> IntegerDescriptor: @max_iterations.setter def max_iterations(self, value: int) -> None: self._max_iterations.value = value - - @property - def objective_name(self) -> StringDescriptor: - """ - Objective function name for the persisted deterministic fit. - """ - return self._objective_name - - def _set_objective_name(self, value: str | None) -> None: - self._objective_name.value = value - - @property - def objective_value(self) -> NumericDescriptor: - """Objective value for the persisted deterministic fit.""" - return self._objective_value - - def _set_objective_value(self, value: float | None) -> None: - self._objective_value.value = value - - @property - def n_data_points(self) -> NumericDescriptor: - """ - Number of data points used in the persisted deterministic fit. - """ - return self._n_data_points - - def _set_n_data_points(self, value: float | None) -> None: - self._n_data_points.value = value - - @property - def n_parameters(self) -> NumericDescriptor: - """Number of parameters in the persisted deterministic fit.""" - return self._n_parameters - - def _set_n_parameters(self, value: float | None) -> None: - self._n_parameters.value = value - - @property - def n_free_parameters(self) -> NumericDescriptor: - """ - Number of free parameters in the persisted deterministic fit. - """ - return self._n_free_parameters - - def _set_n_free_parameters(self, value: float | None) -> None: - self._n_free_parameters.value = value - - @property - def degrees_of_freedom(self) -> NumericDescriptor: - """Degrees of freedom for the persisted deterministic fit.""" - return self._degrees_of_freedom - - def _set_degrees_of_freedom(self, value: float | None) -> None: - self._degrees_of_freedom.value = value - - @property - def covariance_available(self) -> BoolDescriptor: - """Whether deterministic covariance was available.""" - return self._covariance_available - - def _set_covariance_available(self, *, value: bool | None) -> None: - self._covariance_available.value = value - - @property - def correlation_available(self) -> BoolDescriptor: - """Whether deterministic correlations were available.""" - return self._correlation_available - - def _set_correlation_available(self, *, value: bool | None) -> None: - self._correlation_available.value = value - - @property - def runtime_seconds(self) -> NumericDescriptor: - """Runtime in seconds for the persisted deterministic fit.""" - return self._runtime_seconds - - def _set_runtime_seconds(self, value: float | None) -> None: - self._runtime_seconds.value = value - - @property - def iterations_performed(self) -> NumericDescriptor: - """Number of iterations performed by the deterministic fit.""" - return self._iterations_performed - - def _set_iterations_performed(self, value: float | None) -> None: - self._iterations_performed.value = value - - @property - def exit_reason(self) -> StringDescriptor: - """Backend exit reason for the persisted deterministic fit.""" - return self._exit_reason - - def _set_exit_reason(self, value: str | None) -> None: - self._exit_reason.value = value From d298e5c678ff78531bd66c0a8118a9f02fbc6064 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:50:10 +0200 Subject: [PATCH 22/38] Remove Bayesian output descriptors from minimizer base --- .../dev/plans/minimizer-input-output-split.md | 2 +- .../categories/minimizer/bayesian_base.py | 200 +----------------- .../categories/minimizer/bumps_dream.py | 11 +- 3 files changed, 3 insertions(+), 210 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index fa25eba52..2abbacc52 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -381,7 +381,7 @@ Mark `[x]` as each step lands. Commit: `Remove LSQ output descriptors from minimizer base` -- [ ] **P1.10 — Remove duplicate fields from Bayesian minimizer +- [x] **P1.10 — Remove duplicate fields from Bayesian minimizer base.** In `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py`, delete the descriptor declarations and properties for diff --git a/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py b/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py index 7e613266d..30ceafa50 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py +++ b/src/easydiffraction/analysis/categories/minimizer/bayesian_base.py @@ -12,9 +12,7 @@ from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator from easydiffraction.core.validation import RangeValidator -from easydiffraction.core.variable import BoolDescriptor from easydiffraction.core.variable import IntegerDescriptor -from easydiffraction.core.variable import NumericDescriptor from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler @@ -31,15 +29,6 @@ class BayesianMinimizerBase(MinimizerCategoryBase): 'parallel_workers', 'initialization_method', 'random_seed', - 'runtime_seconds', - 'point_estimate_name', - 'sampler_completed', - 'credible_interval_inner', - 'credible_interval_outer', - 'acceptance_rate_mean', - 'gelman_rubin_max', - 'effective_sample_size_min', - 'best_log_posterior', ) _native_key_map: ClassVar[dict[str, str]] = { 'sampling_steps': 'steps', @@ -59,17 +48,7 @@ class BayesianMinimizerBase(MinimizerCategoryBase): 'initialization_method', 'random_seed', ) - _result_descriptor_names: ClassVar[tuple[str, ...]] = ( - 'runtime_seconds', - 'point_estimate_name', - 'sampler_completed', - 'credible_interval_inner', - 'credible_interval_outer', - 'acceptance_rate_mean', - 'gelman_rubin_max', - 'effective_sample_size_min', - 'best_log_posterior', - ) + _result_descriptor_names: ClassVar[tuple[str, ...]] = () _supported_initialization_methods: ClassVar[tuple[InitializationMethodEnum, ...]] = ( InitializationMethodEnum.LATIN_HYPERCUBE, ) @@ -160,96 +139,6 @@ def _random_seed_descriptor() -> IntegerDescriptor: cif_handler=CifHandler(names=['_minimizer.random_seed']), ) - @staticmethod - def _runtime_seconds_descriptor() -> NumericDescriptor: - """Create a runtime-seconds descriptor.""" - return NumericDescriptor( - name='runtime_seconds', - description='Wall time of the fit in seconds.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_minimizer.runtime_seconds']), - ) - - @staticmethod - def _point_estimate_name_descriptor() -> StringDescriptor: - """Create a point-estimate-name descriptor.""" - return StringDescriptor( - name='point_estimate_name', - description='Committed sampled point estimate name.', - value_spec=AttributeSpec(default='best_sample'), - cif_handler=CifHandler(names=['_minimizer.point_estimate_name']), - ) - - @staticmethod - def _sampler_completed_descriptor() -> BoolDescriptor: - """Create a sampler-completed descriptor.""" - return BoolDescriptor( - name='sampler_completed', - description='Whether the sampler completed and returned posterior data.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_minimizer.sampler_completed']), - ) - - @staticmethod - def _credible_interval_inner_descriptor() -> NumericDescriptor: - """Create an inner credible-interval descriptor.""" - return NumericDescriptor( - name='credible_interval_inner', - description='Inner credible-interval level used in summaries.', - value_spec=AttributeSpec(default=0.68), - cif_handler=CifHandler(names=['_minimizer.credible_interval_inner']), - ) - - @staticmethod - def _credible_interval_outer_descriptor() -> NumericDescriptor: - """Create an outer credible-interval descriptor.""" - return NumericDescriptor( - name='credible_interval_outer', - description='Outer credible-interval level used in summaries.', - value_spec=AttributeSpec(default=0.95), - cif_handler=CifHandler(names=['_minimizer.credible_interval_outer']), - ) - - @staticmethod - def _acceptance_rate_mean_descriptor() -> NumericDescriptor: - """Create an acceptance-rate descriptor.""" - return NumericDescriptor( - name='acceptance_rate_mean', - description='Mean sampler acceptance rate.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_minimizer.acceptance_rate_mean']), - ) - - @staticmethod - def _gelman_rubin_max_descriptor() -> NumericDescriptor: - """Create a Gelman-Rubin descriptor.""" - return NumericDescriptor( - name='gelman_rubin_max', - description='Maximum rank-normalized split R-hat.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_minimizer.gelman_rubin_max']), - ) - - @staticmethod - def _effective_sample_size_min_descriptor() -> NumericDescriptor: - """Create an effective-sample-size descriptor.""" - return NumericDescriptor( - name='effective_sample_size_min', - description='Minimum bulk effective sample size.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_minimizer.effective_sample_size_min']), - ) - - @staticmethod - def _best_log_posterior_descriptor() -> NumericDescriptor: - """Create a best-log-posterior descriptor.""" - return NumericDescriptor( - name='best_log_posterior', - description='Best log-posterior value found.', - value_spec=AttributeSpec(default=None, allow_none=True), - cif_handler=CifHandler(names=['_minimizer.best_log_posterior']), - ) - @property def sampling_steps(self) -> IntegerDescriptor: """Total sampler iterations per chain.""" @@ -317,90 +206,3 @@ def random_seed(self) -> IntegerDescriptor: @random_seed.setter def random_seed(self, value: int | None) -> None: self._random_seed.value = value - - @property - def runtime_seconds(self) -> NumericDescriptor: - """Wall time of the fit in seconds.""" - return self._runtime_seconds - - def _set_runtime_seconds(self, value: float | None) -> None: - """Set the fit runtime for internal callers.""" - self._runtime_seconds.value = value - - @property - def point_estimate_name(self) -> StringDescriptor: - """Committed sampled point estimate name.""" - return self._point_estimate_name - - def _set_point_estimate_name(self, value: str) -> None: - """Set the point-estimate name for internal callers.""" - self._point_estimate_name.value = value - - @property - def sampler_completed(self) -> BoolDescriptor: - """Whether the sampler completed and returned posterior data.""" - return self._sampler_completed - - def _set_sampler_completed(self, *, value: bool) -> None: - """Set the sampler-completed flag for internal callers.""" - self._sampler_completed.value = value - - @property - def credible_interval_inner(self) -> NumericDescriptor: - """Inner credible-interval level used in summaries.""" - return self._credible_interval_inner - - def _set_credible_interval_inner(self, value: float) -> None: - """ - Set the inner credible-interval level for internal callers. - """ - self._credible_interval_inner.value = value - - @property - def credible_interval_outer(self) -> NumericDescriptor: - """Outer credible-interval level used in summaries.""" - return self._credible_interval_outer - - def _set_credible_interval_outer(self, value: float) -> None: - """ - Set the outer credible-interval level for internal callers. - """ - self._credible_interval_outer.value = value - - @property - def acceptance_rate_mean(self) -> NumericDescriptor: - """Mean sampler acceptance rate.""" - return self._acceptance_rate_mean - - def _set_acceptance_rate_mean(self, value: float | None) -> None: - """Set the acceptance-rate mean for internal callers.""" - self._acceptance_rate_mean.value = value - - @property - def gelman_rubin_max(self) -> NumericDescriptor: - """Maximum rank-normalized split R-hat.""" - return self._gelman_rubin_max - - def _set_gelman_rubin_max(self, value: float | None) -> None: - """Set the maximum R-hat for internal callers.""" - self._gelman_rubin_max.value = value - - @property - def effective_sample_size_min(self) -> NumericDescriptor: - """Minimum bulk effective sample size.""" - return self._effective_sample_size_min - - def _set_effective_sample_size_min(self, value: float | None) -> None: - """ - Set the minimum effective sample size for internal callers. - """ - self._effective_sample_size_min.value = value - - @property - def best_log_posterior(self) -> NumericDescriptor: - """Best log-posterior value found.""" - return self._best_log_posterior - - def _set_best_log_posterior(self, value: float | None) -> None: - """Set the best log-posterior for internal callers.""" - self._best_log_posterior.value = value diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py b/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py index 2fb8caebd..13b844e86 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py @@ -20,7 +20,7 @@ @MinimizerCategoryFactory.register class BumpsDreamMinimizer(BayesianMinimizerBase): - """Persisted settings and results for the BUMPS DREAM minimizer.""" + """Persisted settings for the BUMPS DREAM minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { 'optimizer_name': 'bumps (dream)', @@ -41,12 +41,3 @@ def __init__(self) -> None: self._parallel_workers = self._parallel_workers_descriptor(DEFAULT_PARALLEL_WORKERS) self._initialization_method = self._initialization_method_descriptor() self._random_seed = self._random_seed_descriptor() - self._runtime_seconds = self._runtime_seconds_descriptor() - self._point_estimate_name = self._point_estimate_name_descriptor() - self._sampler_completed = self._sampler_completed_descriptor() - self._credible_interval_inner = self._credible_interval_inner_descriptor() - self._credible_interval_outer = self._credible_interval_outer_descriptor() - self._acceptance_rate_mean = self._acceptance_rate_mean_descriptor() - self._gelman_rubin_max = self._gelman_rubin_max_descriptor() - self._effective_sample_size_min = self._effective_sample_size_min_descriptor() - self._best_log_posterior = self._best_log_posterior_descriptor() From 451280a1fddc02e095f0bfa4f6142a5ecb727b63 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:51:19 +0200 Subject: [PATCH 23/38] Serialize fit outputs to _fit_result.* tags --- .../dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/io/cif/serialize.py | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 2abbacc52..6a9190f9f 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -395,7 +395,7 @@ Mark `[x]` as each step lands. Commit: `Remove Bayesian output descriptors from minimizer base` -- [ ] **P1.11 — Update CIF emit/read for the split.** In +- [x] **P1.11 — Update CIF emit/read for the split.** In `src/easydiffraction/io/cif/serialize.py`: **No category-list reordering is performed in this step.** Neither diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 929b2217c..c161e24e9 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -571,8 +571,6 @@ def _has_persisted_fit_state_sections(block: object) -> bool: """Return True when any persisted fit-state section is present.""" scalar_tags = ( '_fit_result.result_kind', - '_minimizer.runtime_seconds', - '_minimizer.best_log_posterior', ) loop_tags = ( '_fit_parameter.param_unique_name', @@ -610,6 +608,29 @@ def _restore_persisted_fit_state(analysis: object, block: object) -> None: ) +_MINIMIZER_OUTPUT_LEGACY_TAGS = ( + '_minimizer.objective_name', + '_minimizer.objective_value', + '_minimizer.n_data_points', + '_minimizer.n_parameters', + '_minimizer.n_free_parameters', + '_minimizer.degrees_of_freedom', + '_minimizer.covariance_available', + '_minimizer.correlation_available', + '_minimizer.runtime_seconds', + '_minimizer.iterations_performed', + '_minimizer.exit_reason', + '_minimizer.point_estimate_name', + '_minimizer.sampler_completed', + '_minimizer.credible_interval_inner', + '_minimizer.credible_interval_outer', + '_minimizer.acceptance_rate_mean', + '_minimizer.gelman_rubin_max', + '_minimizer.effective_sample_size_min', + '_minimizer.best_log_posterior', +) + + def _collect_legacy_analysis_tags(block: object) -> list[str]: """Return deprecated analysis CIF tags present in a block.""" legacy_tags: list[str] = [] @@ -617,6 +638,9 @@ def _collect_legacy_analysis_tags(block: object) -> list[str]: legacy_tags.append('_joint_fit_experiment.id') if _has_cif_loop(block, '_joint_fit_experiment.weight'): legacy_tags.append('_joint_fit_experiment.weight') + for tag in _MINIMIZER_OUTPUT_LEGACY_TAGS: + if _has_cif_value(block, tag): + legacy_tags.append(tag) return legacy_tags @@ -629,7 +653,8 @@ def _raise_for_legacy_analysis_tags(block: object) -> None: msg = ( 'Legacy analysis CIF tags are no longer supported: ' f'{legacy_tags}. Use _minimizer.type, _fitting_mode.type, ' - '_minimizer.*, _joint_fit.experiment_id, and _joint_fit.weight.' + '_minimizer.* for settings, _fit_result.* for fit outputs, ' + '_joint_fit.experiment_id, and _joint_fit.weight.' ) raise ValueError(msg) From b48cf0a747a934a6745774d7f2ce2b504665698e Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:51:37 +0200 Subject: [PATCH 24/38] Confirm fit_result paired instance flows through serializer --- docs/dev/plans/minimizer-input-output-split.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 6a9190f9f..12962ffc3 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -433,7 +433,7 @@ Mark `[x]` as each step lands. Commit: `Serialize fit outputs to _fit_result.* tags` -- [ ] **P1.12 — Confirm `_fit_state_categories` returns the paired +- [x] **P1.12 — Confirm `_fit_state_categories` returns the paired `fit_result`.** In `src/easydiffraction/analysis/analysis.py`, `_fit_state_categories()` already returns `[self.fit_parameters, From dcc4d1bc56c4ec16127f7ddafee8dc078e77ec97 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:53:25 +0200 Subject: [PATCH 25/38] Add settings-used block to fit.results display --- .../dev/plans/minimizer-input-output-split.md | 2 +- src/easydiffraction/project/display.py | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 12962ffc3..8e464a08c 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -449,7 +449,7 @@ Mark `[x]` as each step lands. Commit: `Confirm fit_result paired instance flows through serializer` -- [ ] **P1.13 — Update `project.display.fit.results()` to add a +- [x] **P1.13 — Update `project.display.fit.results()` to add a "Settings used" block.** In `src/easydiffraction/project/display.py`, extend the existing results-display method to print, above the current tables, a diff --git a/src/easydiffraction/project/display.py b/src/easydiffraction/project/display.py index 168d16773..560c34b7c 100644 --- a/src/easydiffraction/project/display.py +++ b/src/easydiffraction/project/display.py @@ -8,6 +8,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING +from easydiffraction.core.variable import GenericDescriptorBase from easydiffraction.datablocks.experiment.item.base import intensity_category_for from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum @@ -17,6 +18,7 @@ from easydiffraction.display.progress import ACTIVITY_LABEL_PROCESSING from easydiffraction.display.progress import activity_indicator from easydiffraction.utils.enums import VerbosityEnum +from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_object_help from easydiffraction.utils.utils import render_table @@ -87,7 +89,42 @@ def __init__(self, project: Project) -> None: def results(self) -> None: """Show the latest fit summary and fitted parameter table.""" - self._project.analysis.display.fit_results() + analysis = self._project.analysis + if analysis.fit_results is None: + analysis.display.fit_results() + return + + self._show_settings_used() + analysis.display.fit_results() + + def _show_settings_used(self) -> None: + """Show minimizer settings used for the latest fit.""" + rows = self._settings_used_rows() + if not rows: + return + + console.paragraph('Settings used') + render_table( + columns_headers=['Name', 'Value', 'Description'], + columns_alignment=['left', 'right', 'left'], + columns_data=rows, + ) + + def _settings_used_rows(self) -> list[list[str]]: + """Return minimizer setting rows for display.""" + minimizer = self._project.analysis.minimizer + rows: list[list[str]] = [] + for name in minimizer._setting_descriptor_names: + descriptor = getattr(minimizer, name) + if isinstance(descriptor, GenericDescriptorBase): + rows.append([ + name, + str(descriptor.value), + descriptor.description or '', + ]) + else: + rows.append([name, str(descriptor), '']) + return rows def correlations( self, From 5599bc68d98de9415c9211fdb2457b1e40fdb0b8 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 14:58:53 +0200 Subject: [PATCH 26/38] Amend affected ADRs for minimizer input/output split --- .../adrs/accepted/analysis-cif-fit-state.md | 32 +++++++-------- docs/dev/adrs/accepted/display-ux.md | 6 +++ .../minimizer-category-consolidation.md | 41 +++++++++++-------- docs/dev/adrs/accepted/runtime-fit-results.md | 6 ++- .../switchable-category-owned-selectors.md | 8 ++++ docs/dev/adrs/index.md | 1 + .../dev/plans/minimizer-input-output-split.md | 2 +- 7 files changed, 59 insertions(+), 37 deletions(-) diff --git a/docs/dev/adrs/accepted/analysis-cif-fit-state.md b/docs/dev/adrs/accepted/analysis-cif-fit-state.md index 9883a3b94..59cfc0804 100644 --- a/docs/dev/adrs/accepted/analysis-cif-fit-state.md +++ b/docs/dev/adrs/accepted/analysis-cif-fit-state.md @@ -26,7 +26,7 @@ Analysis-owned fit state needs to persist: - pre-fit scalar snapshots for recovery workflows - compact status metadata for the latest saved fit projection - deterministic correlation summaries -- minimizer-specific fit outputs on the active `_minimizer.*` category +- minimizer-specific fit outputs on the paired `_fit_result.*` category - per-parameter posterior summaries on `_fit_parameter` - large posterior arrays and plot caches in `analysis/results.h5` @@ -47,8 +47,7 @@ Persist analysis-owned fit state as explicit analysis categories in Do not add a dedicated `_fit_state` category or `_fit_state.schema_version`. Persisted fit state is detected from -`_fit_result`, `_fit_parameter`, `_fit_parameter_correlation`, and -fit-output fields on `_minimizer.*`. +`_fit_result`, `_fit_parameter`, and `_fit_parameter_correlation`. ### Common fit-state categories @@ -77,7 +76,8 @@ pre-fit scalar snapshots: - `posterior_gelman_rubin` - `posterior_effective_sample_size_bulk` -`_fit_result` stores the latest saved fit header: +`_fit_result` stores the latest saved fit header and scalar +family-specific fit outputs: - `result_kind` - `success` @@ -92,9 +92,10 @@ pairs are stored. ### Minimizer fit projection -The active `_minimizer.*` category stores both user-selected solver -inputs and fit-filled outputs. Deterministic minimizer classes store -compact fit output counts: +The active `_minimizer.*` category stores user-selected solver inputs +only. Scalar outputs are written to the paired `_fit_result.*` +category. Deterministic fit-result classes add compact fit output +counts: - `objective_name` - `objective_value` @@ -104,8 +105,6 @@ compact fit output counts: - `degrees_of_freedom` - `covariance_available` - `correlation_available` -- `runtime_seconds` -- `iterations_performed` - `exit_reason` Do not persist a `_deterministic_parameter_result` category. Final @@ -113,8 +112,7 @@ deterministic parameter values and uncertainties already persist in the model CIF files, and restored deterministic ordering comes from `_fit_parameter`. -Bayesian minimizer classes store sampler inputs and fit outputs under -`_minimizer.*`, including: +Bayesian minimizer classes store sampler inputs under `_minimizer.*`: - `sampling_steps` - `burn_in_steps` @@ -123,7 +121,9 @@ Bayesian minimizer classes store sampler inputs and fit outputs under - `parallel_workers` - `initialization_method` - `random_seed` -- `runtime_seconds` + +Bayesian fit-result classes store scalar outputs under `_fit_result.*`: + - `point_estimate_name` - `sampler_completed` - `credible_interval_inner` @@ -170,10 +170,10 @@ posterior displays. Load order is: 1. standard analysis configuration -2. common fit-state categories -3. `_minimizer.*` fit-output fields according to the active - `_minimizer.type` -4. posterior sidecar arrays when a Bayesian result is expected +2. `_minimizer.*` settings according to the active `_minimizer.type` +3. common and family-specific `_fit_result.*` fields on the paired class +4. `_fit_parameter` and `_fit_parameter_correlation` +5. posterior sidecar arrays when a Bayesian result is expected Persist backend runtime objects, optimizer instances, and raw driver payloads nowhere in this design. diff --git a/docs/dev/adrs/accepted/display-ux.md b/docs/dev/adrs/accepted/display-ux.md index 0b33b708e..379bdb8c8 100644 --- a/docs/dev/adrs/accepted/display-ux.md +++ b/docs/dev/adrs/accepted/display-ux.md @@ -200,6 +200,12 @@ Use these naming rules: path for `versus`. - `posterior.*` names are used only when posterior samples are required. +`project.display.fit.results()` also prints a "Settings used" block +above the result tables. The block is sourced from +`analysis.minimizer.*` so the minimizer inputs and paired +`analysis.fit_result.*` outputs are visible from the accepted display +facade without adding a new `Analysis`-level display method. + ## Rejected Alternatives Flat display facade: diff --git a/docs/dev/adrs/accepted/minimizer-category-consolidation.md b/docs/dev/adrs/accepted/minimizer-category-consolidation.md index f86b56a95..671a1a091 100644 --- a/docs/dev/adrs/accepted/minimizer-category-consolidation.md +++ b/docs/dev/adrs/accepted/minimizer-category-consolidation.md @@ -55,31 +55,34 @@ samplers. ## Decision -### 1. Unified `minimizer` category replaces all sampler-input and fit-result categories +### 1. Unified `minimizer` category replaces sampler-input categories Introduce a single switchable category `minimizer` on `Analysis`. Its concrete class is determined by `Analysis.minimizer_type`. The category -holds both user-writable inputs and fit-filled outputs in one place. +now holds user-writable minimizer inputs only. The later +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) +ADR reverses the fit-output half of this rule: scalar fit outputs live +on the paired `fit_result` category instead of on `minimizer`. The following categories are removed: - `bayesian_sampler` — fields move into the Bayesian concrete classes of `minimizer`. - `bayesian_result`, `bayesian_convergence` — fields move into the - Bayesian concrete classes of `minimizer` (`runtime_seconds`, + Bayesian concrete classes of `fit_result` (`fitting_time`, `acceptance_rate_mean`, `gelman_rubin_max`, `effective_sample_size_min`, `best_log_posterior`, …). - `deterministic_result` — fields move into the deterministic concrete - classes of `minimizer` (`runtime_seconds`, `iterations_performed`, - `exit_reason`, …). + classes of `fit_result` (`fitting_time`, `iterations`, + `objective_value`, `exit_reason`, …). - `bayesian_parameter_posterior` — replaced by `Parameter.posterior` (see §3). - `bayesian_distribution_cache`, `bayesian_pair_cache`, `bayesian_predictive_dataset` — replaced by HDF5 sidecar (see §4). -`fit_result` and `fit_parameter` (analysis-owned bounds, success flag, -reduced chi-square, message, fit time, iterations) remain unchanged as -fit-mode-agnostic header categories. +`fit_parameter` (analysis-owned bounds) remains a fit-state category. +`fit_result` remains the common fit header category and is extended by +the input/output split ADR with family-specific scalar outputs. ### 2. Selectors move to the `Analysis` owner @@ -388,10 +391,11 @@ category's class-level `_engine_metadata` dict. ### Trade-offs -- `minimizer` is the first category that mixes writable user inputs and - writable fit-filled outputs in the same scope. This is a small new - convention but is the natural generalization of how `Parameter` - already holds both user input and refined value on the same object. +- `minimizer` no longer mixes writable user inputs and fit-filled + outputs in the same scope. That stricter boundary is recorded by + [`minimizer-input-output-split.md`](minimizer-input-output-split.md); + `Parameter` remains the refinement-in-place precedent for model + values rather than minimizer diagnostics. - The set of `_minimizer.*` tags present in CIF depends on the active `_fitting.minimizer_type`. Loading a CIF whose tags don't match the minimizer's allowed set raises (clear validation, not silent @@ -461,9 +465,10 @@ category count for each new sampler and entrenches the convention break. ### D. Strict input-only `minimizer` plus a separate `fit_result` -Keep the categories single-concept (inputs xor outputs) at the cost of -two-place lookup for related info. Rejected in favour of the -one-category-mixes-both shape (§1, §"Trade-offs") because the existing -`Parameter` model already mixes input and refined value on the same -object, and one-place discoverability is more valuable than strict -purity. +Originally rejected in favour of the one-category-mixes-both shape (§1, +§"Trade-offs"). Reversed by +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) +after implementation showed the `Parameter` analogy does not hold for +minimizer settings versus fit diagnostics. The current design keeps +`minimizer` input-only and moves scalar fit outputs to the paired +`fit_result` category. diff --git a/docs/dev/adrs/accepted/runtime-fit-results.md b/docs/dev/adrs/accepted/runtime-fit-results.md index 282b9841d..cac3d0752 100644 --- a/docs/dev/adrs/accepted/runtime-fit-results.md +++ b/docs/dev/adrs/accepted/runtime-fit-results.md @@ -30,8 +30,10 @@ raw driver payloads remain runtime-only unless a narrower ADR defines a persisted projection. The accepted [`analysis-cif-fit-state.md`](analysis-cif-fit-state.md) and [`minimizer-category-consolidation.md`](minimizer-category-consolidation.md) -ADRs define the current compact projection for fit headers, -minimizer-owned outputs, parameter posterior summaries, and the +ADRs, as amended by +[`minimizer-input-output-split.md`](minimizer-input-output-split.md), +define the current compact projection for fit headers, paired +fit-result outputs, parameter posterior summaries, and the `analysis/results.h5` sidecar. ## Consequences diff --git a/docs/dev/adrs/accepted/switchable-category-owned-selectors.md b/docs/dev/adrs/accepted/switchable-category-owned-selectors.md index 19fb491bb..e9e0f8fd3 100644 --- a/docs/dev/adrs/accepted/switchable-category-owned-selectors.md +++ b/docs/dev/adrs/accepted/switchable-category-owned-selectors.md @@ -101,6 +101,14 @@ Owner-level shims are removed (no `._type`, no `show_supported__types()`, no `show_current__type()`). The owner exposes only the category itself, e.g. `analysis.minimizer`. +Exception: an internally paired category whose concrete class is fully +determined by another category's `type` may omit its own public +selector. Today this applies only to `analysis.fit_result`, whose class +is derived from `analysis.minimizer.type` by the +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) +ADR. It has no `fit_result.type`, no `fit_result.show_supported()`, +and no `_fit_result.type` CIF tag. + The owner still owns the swap mechanism (it holds the slot) but the swap is _initiated_ from the category through a back-reference. diff --git a/docs/dev/adrs/index.md b/docs/dev/adrs/index.md index 28859e145..e17f4828a 100644 --- a/docs/dev/adrs/index.md +++ b/docs/dev/adrs/index.md @@ -21,6 +21,7 @@ folders. | Analysis and fitting | Accepted | Parameter Correlation Persistence | Persists deterministic and posterior correlation summaries in `_fit_parameter_correlation` | [`parameter-correlation-persistence.md`](accepted/parameter-correlation-persistence.md) | | Analysis and fitting | Suggestion | Fit Output Files and Data Exports | Narrows remaining archive/export questions after adopting `results.csv` and `results.h5`. | [`fit-output-files-and-data-exports.md`](suggestions/fit-output-files-and-data-exports.md) | | Analysis and fitting | Accepted | Minimizer Category Consolidation | Collapses the seven Bayesian categories into one owner-level switchable `minimizer` category with HDF5 sidecar. | [`minimizer-category-consolidation.md`](accepted/minimizer-category-consolidation.md) | +| Analysis and fitting | Accepted | Minimizer Input/Output Split | Keeps `analysis.minimizer` input-only and moves scalar fit outputs to paired `analysis.fit_result` classes. | [`minimizer-input-output-split.md`](accepted/minimizer-input-output-split.md) | | Analysis and fitting | Superseded | Parameter-Level Posterior Projection | Superseded by minimizer-category consolidation; kept as historical context for `parameter.posterior`. | [`parameter-posterior-summary.md`](suggestions/parameter-posterior-summary.md) | | Analysis and fitting | Suggestion | Undo Fit | Builds rollback semantics and CLI behavior on already-persisted pre-fit scalar snapshots. | [`undo-fit.md`](suggestions/undo-fit.md) | | Core model | Accepted | Category Owners and Real Datablocks | Introduces `CategoryOwner` so singleton sections do not pretend to be real CIF datablocks. | [`category-owner-sections.md`](accepted/category-owner-sections.md) | diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 8e464a08c..c48a08c6d 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -458,7 +458,7 @@ Mark `[x]` as each step lands. the rest of the display facade uses. Commit: `Add settings-used block to fit.results display` -- [ ] **P1.14 — Amend the five accepted ADRs listed in §"ADR".** For +- [x] **P1.14 — Amend the five accepted ADRs listed in §"ADR".** For each, apply the matching paragraph from the ADR's §"ADRs amended" section: - `minimizer-category-consolidation.md` — §1 partial-rule From 85494ba38d2261307b69a29f8a3a6a0a54230664 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 15:13:12 +0200 Subject: [PATCH 27/38] Update tutorials to read outputs from fit_result --- docs/dev/plans/minimizer-input-output-split.md | 2 +- docs/docs/tutorials/ed-24.ipynb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index c48a08c6d..2a79048fe 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -475,7 +475,7 @@ Mark `[x]` as each step lands. Commit: `Amend affected ADRs for minimizer input/output split` -- [ ] **P1.15 — Update tutorials.** `git grep` `docs/docs/tutorials/` +- [x] **P1.15 — Update tutorials.** `git grep` `docs/docs/tutorials/` for `analysis.minimizer.` references and rewrite each per the migration table below. The two **collapsed** rows target existing common fields on `FitResultBase` (already diff --git a/docs/docs/tutorials/ed-24.ipynb b/docs/docs/tutorials/ed-24.ipynb index 869a30919..4e77ed733 100644 --- a/docs/docs/tutorials/ed-24.ipynb +++ b/docs/docs/tutorials/ed-24.ipynb @@ -46,9 +46,7 @@ "cell_type": "code", "execution_count": null, "id": "3", - "metadata": { - "lines_to_next_cell": 1 - }, + "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", From c5da0b0fcf9f18f74e6f593f18c5ed00c8af5bd0 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 15:15:15 +0200 Subject: [PATCH 28/38] Remove essdiffraction dependency --- pixi.lock | 1124 ----------------- pyproject.toml | 1 - .../dream/test_package_import.py | 17 +- 3 files changed, 3 insertions(+), 1139 deletions(-) diff --git a/pixi.lock b/pixi.lock index c421cdaaa..e17dcaa8c 100644 --- a/pixi.lock +++ b/pixi.lock @@ -184,7 +184,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl @@ -218,31 +217,22 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/b4/0887c88ddfaba1d7140ea335144eb904af97550786ee58bdb295ff10d255/crysfml-0.6.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -260,14 +250,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -276,7 +263,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl @@ -288,8 +274,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl @@ -298,15 +282,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl @@ -317,38 +298,27 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/04/c5bb20d64417d20cba0105277235c51969444fa873000fbc26ac0a3fc5a8/gemmi-0.7.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -520,11 +490,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl @@ -554,37 +522,29 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -600,14 +560,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -617,7 +574,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl @@ -629,9 +585,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl @@ -639,18 +593,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl @@ -658,36 +608,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl @@ -855,7 +795,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl @@ -863,7 +802,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/1d/77/928ea2e70641ca177a11140062cc5840d421795f2e82749d408d0cce900a/narwhals-2.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl @@ -886,32 +824,24 @@ environments: - pypi: https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -929,15 +859,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -946,7 +873,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl @@ -959,8 +885,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl @@ -969,16 +893,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl @@ -989,37 +909,27 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -1203,7 +1113,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -1213,7 +1122,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/e7/cd78635d0ece7e4d3393f2c1d2ebabf6ff4bd615da142891b1d42ad58abf/scipp-26.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl @@ -1227,7 +1135,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/75/98a7eb100dc5cfd20b019046452f08d5e67dfbacc71d8f28763d32426fd3/spglib-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl @@ -1237,35 +1144,27 @@ environments: - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1283,14 +1182,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -1301,7 +1197,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl @@ -1313,9 +1208,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/61/3c1ea8c10bf4f6bf83c33a7f5b4a3143f4cc1f979859dec5498b6cc31900/pycifrw-5.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl @@ -1324,12 +1217,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl @@ -1341,36 +1232,25 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/59/a32a241d861cf180853a11c8e5a67641cb1b2af13c3a5ccce83ec07e2c9f/chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/88/5a431cd1ea7587408a66947384b39beb2ab2bcc1c87b7c4082f05036719f/gemmi-0.7.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl osx-arm64: @@ -1543,7 +1423,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/1d/9f9e30d76300b0150afaa8b37fab9a0194d44fd4f6b1e5038aca4a1440ed/crysfml-0.6.2-cp312-cp312-macosx_14_0_arm64.whl @@ -1579,32 +1458,23 @@ environments: - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/7b/537a61906eac58d94131273084d21d4eb219f5453f0ed30de3aca580a2b4/scipp-26.3.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1620,14 +1490,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -1638,7 +1505,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl @@ -1650,9 +1516,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b0/3e/a6497e1c2c9bc6ed2b79e0f2d31a4ce509fd2a9eed4e4f7ac63eda8113cb/gemmi-0.7.5-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl @@ -1663,16 +1527,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/8c/d4907ad4f6bdc5bf79462d8767728713a7b316918a7444df372958a0e417/spglib-2.6.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl @@ -1682,33 +1542,23 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl win-64: @@ -1875,12 +1725,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1a/1f/86b4d15221096cb5500bcd73bf350745749e3ba056cdd7a7f75f126f154e/scipp-26.3.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1a/c7/78200c18404ded028758b28b588aa1f4f3acd851271a74156a2a3db9eadf/crysfml-0.6.2-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1d/77/928ea2e70641ca177a11140062cc5840d421795f2e82749d408d0cce900a/narwhals-2.21.2-py3-none-any.whl @@ -1908,32 +1756,24 @@ environments: - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/0c/8297c8d978c919ad6318011631a6123082d5da940da5f8612e75a247d739/diffpy_pdffit2-1.6.0-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -1952,16 +1792,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/82/b54e646be7b938fcbdda10030c6533bd2bb1a59930a1381cc83d6050a49c/spglib-2.6.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -1970,7 +1806,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl @@ -1984,10 +1819,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl @@ -1997,14 +1830,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl @@ -2013,34 +1843,24 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/f2/53be7a4ba5816e13c39be0f728facac4bcb39cf4903ceeec54b006511c8f/gemmi-0.7.5-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl py-314-env: @@ -2223,7 +2043,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl @@ -2257,31 +2076,22 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/46/b4/0887c88ddfaba1d7140ea335144eb904af97550786ee58bdb295ff10d255/crysfml-0.6.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2299,14 +2109,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -2315,7 +2122,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl @@ -2327,8 +2133,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl @@ -2337,15 +2141,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl @@ -2356,38 +2157,27 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/04/c5bb20d64417d20cba0105277235c51969444fa873000fbc26ac0a3fc5a8/gemmi-0.7.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -2559,11 +2349,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl @@ -2593,37 +2381,29 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2639,14 +2419,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -2656,7 +2433,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl @@ -2668,9 +2444,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl @@ -2678,18 +2452,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl @@ -2697,36 +2467,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl @@ -2894,7 +2654,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl @@ -2902,7 +2661,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/1d/77/928ea2e70641ca177a11140062cc5840d421795f2e82749d408d0cce900a/narwhals-2.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl @@ -2925,32 +2683,24 @@ environments: - pypi: https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2968,15 +2718,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl @@ -2985,7 +2732,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl @@ -2998,8 +2744,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/aa/ec/d9be3bd1db141e76b2f525c265f70e66edd30a51a3307d8edf0ef1909c54/jupytext-1.19.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl @@ -3008,16 +2752,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl @@ -3028,37 +2768,27 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -8066,7 +7796,6 @@ packages: - build ; extra == 'dev' - copier ; extra == 'dev' - docstripy ; extra == 'dev' - - essdiffraction ; extra == 'dev' - format-docstring ; extra == 'dev' - gitpython ; extra == 'dev' - interrogate ; extra == 'dev' @@ -8375,32 +8104,6 @@ packages: - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - name: scipp - version: 26.3.1 - sha256: 1f103f6c5a33b08773206c613fe2dd9c02585f5c4e44b77311c54b7828a758ed - requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl name: pyparsing version: 3.3.2 @@ -8455,15 +8158,6 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - name: mpltoolbox - version: 26.2.0 - sha256: cd2668db4216fc4d7c2ba37974961aa61445f1517527b645b6082930e35ba7f0 - requires_dist: - - matplotlib - - ipympl ; extra == 'test' - - pytest>=8.0 ; extra == 'test' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl name: interrogate version: 1.7.0 @@ -8576,32 +8270,6 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1a/1f/86b4d15221096cb5500bcd73bf350745749e3ba056cdd7a7f75f126f154e/scipp-26.3.1-cp312-cp312-win_amd64.whl - name: scipp - version: 26.3.1 - sha256: 8b036876edf7895d17644f59711037d2d7d9ad048b1a503200646d8229fb1ad7 - requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/1a/c7/78200c18404ded028758b28b588aa1f4f3acd851271a74156a2a3db9eadf/crysfml-0.6.2-cp312-cp312-win_amd64.whl name: crysfml version: 0.6.2 @@ -8663,58 +8331,6 @@ packages: version: 0.0.4 sha256: 571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/1e/e7/cd78635d0ece7e4d3393f2c1d2ebabf6ff4bd615da142891b1d42ad58abf/scipp-26.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: scipp - version: 26.3.1 - sha256: 7525c843f673ef5461d229095054a701aeb3233db29af137fdf4bbf0884ad9d4 - requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - name: scipp - version: 26.3.1 - sha256: 26291c0a882b9d5aac868c6d6f2508b79baa821ed30060a22c50620dbcce9e75 - requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/1f/7e/c2cfe0bdbec1f5ce2bd92e03311038e1c491dfd54824606f38a61167a3f0/crysfml-0.6.2-cp314-cp314-macosx_14_0_arm64.whl name: crysfml version: 0.6.2 @@ -9144,32 +8760,6 @@ packages: name: distlib version: 0.4.0 sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 -- pypi: https://files.pythonhosted.org/packages/33/75/98a7eb100dc5cfd20b019046452f08d5e67dfbacc71d8f28763d32426fd3/spglib-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: spglib - version: 2.6.0 - sha256: a8e9c34da1e2428c3a8bd4e209e5356d12d454d8ac54120d5ba4a437d3abe7ba - requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl name: format-docstring version: 0.2.7 @@ -9434,11 +9024,6 @@ packages: requires_dist: - regex ; extra == 'extras' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - name: widgetsnbextension - version: 4.0.15 - sha256: 8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366 - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl name: asciichartpy version: 1.5.25 @@ -9493,13 +9078,6 @@ packages: - setuptools-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - name: cyclebane - version: 24.10.0 - sha256: 902dd318667e4a222afc270cc5bc72c67d5d6047d2e0e1c36018885fb80f5e5d - requires_dist: - - networkx - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl name: mpmath version: 1.3.0 @@ -9545,58 +9123,6 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: scipp - version: 26.3.1 - sha256: 2ef08ba8d83542807f9f9833ba8f01583215c1629693bfadb1d6508cbdeb335c - requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/44/7b/537a61906eac58d94131273084d21d4eb219f5453f0ed30de3aca580a2b4/scipp-26.3.1-cp312-cp312-macosx_14_0_arm64.whl - name: scipp - version: 26.3.1 - sha256: 2608ba21e2c550abe864598e8cfffe22d7e7be70ff9f9b03d44868e353b241c9 - requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/46/b4/0887c88ddfaba1d7140ea335144eb904af97550786ee58bdb295ff10d255/crysfml-0.6.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: crysfml version: 0.6.2 @@ -9685,38 +9211,6 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - name: dask - version: 2026.3.0 - sha256: be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d - requires_dist: - - click>=8.1 - - cloudpickle>=3.0.0 - - fsspec>=2021.9.0 - - packaging>=20.0 - - partd>=1.4.0 - - pyyaml>=5.3.1 - - toolz>=0.12.0 - - importlib-metadata>=4.13.0 ; python_full_version < '3.12' - - numpy>=1.24 ; extra == 'array' - - dask[array] ; extra == 'dataframe' - - pandas>=2.0 ; extra == 'dataframe' - - pyarrow>=16.0 ; extra == 'dataframe' - - distributed>=2026.3.0,<2026.3.1 ; extra == 'distributed' - - bokeh>=3.1.0 ; extra == 'diagnostics' - - jinja2>=2.10.3 ; extra == 'diagnostics' - - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' - - pyarrow>=16.0 ; extra == 'complete' - - lz4>=4.3.2 ; extra == 'complete' - - pandas[test] ; extra == 'test' - - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-mock ; extra == 'test' - - pytest-rerunfailures ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - pre-commit ; extra == 'test' - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl name: scipy version: 1.17.1 @@ -9852,22 +9346,6 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - name: ipywidgets - version: 8.1.8 - sha256: ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e - requires_dist: - - comm>=0.1.3 - - ipython>=6.1.0 - - traitlets>=4.3.1 - - widgetsnbextension~=4.0.14 - - jupyterlab-widgets~=3.0.15 - - jsonschema ; extra == 'test' - - ipykernel ; extra == 'test' - - pytest>=3.6.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytz ; extra == 'test' - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl name: ruff version: 0.15.14 @@ -10021,16 +9499,6 @@ packages: requires_dist: - pyyaml>=3.10 ; extra == 'watchmedo' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - name: scippnexus - version: 26.1.1 - sha256: 899a0a5e71291b7809d902c17b6c74addf5a805397eabcec557491ff74eead12 - requires_dist: - - scipp>=24.2.0 - - scipy>=1.10.0 - - h5py>=3.12 - - pytest>=7.0 ; extra == 'test' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl name: pillow version: 12.2.0 @@ -10099,35 +9567,6 @@ packages: version: 0.5.2 sha256: 81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/63/9f/724f66a48309dd97a2ff58f491d6ffd925f35d1278a5e55dc9a5ac6a156b/scippneutron-26.5.0-py3-none-any.whl - name: scippneutron - version: 26.5.0 - sha256: e9cfad09b974867c6dc2a175cd2e575e06eaa951b2409e9ef863db237853bf99 - requires_dist: - - python-dateutil>=2.8 - - email-validator>=2 - - h5py>=3.12 - - lazy-loader>=0.4 - - mpltoolbox>=24.6.0 - - numpy>=1.20 - - plopp>=26.4.1 - - pydantic>=2 - - scipp>=25.8.0 - - scippnexus>=23.11.0 - - scipy>=1.7.0 - - scipp[all]>=25.8.0 ; extra == 'all' - - pooch>=1.5 ; extra == 'all' - - hypothesis>=6.100 ; extra == 'test' - - ipympl>0.9.0 ; extra == 'test' - - ipykernel>6.30 ; extra == 'test' - - pace-neutrons>=0.3 ; extra == 'test' - - pooch>=1.5 ; extra == 'test' - - psutil>=5.0 ; extra == 'test' - - pytest>=7.0 ; extra == 'test' - - pytest-xdist>=3.0 ; extra == 'test' - - pythreejs>=2.4.1 ; extra == 'test' - - sciline>=25.1.0 ; extra == 'test' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl name: propcache version: 0.5.2 @@ -10401,27 +9840,6 @@ packages: version: 1.8.0 sha256: 494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - name: ase - version: 3.28.0 - sha256: 0e24056302d7307b7247f90de281de15e3031c14cf400bedb1116c3b0d0e50b8 - requires_dist: - - numpy>=1.21.6 - - scipy>=1.8.1 - - matplotlib>=3.5.2 - - sphinx ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinxcontrib-video ; extra == 'docs' - - sphinx-gallery ; extra == 'docs' - - pillow ; extra == 'docs' - - pytest>=7.4.0 ; extra == 'test' - - pytest-xdist>=3.2.0 ; extra == 'test' - - spglib>=1.9 ; extra == 'spglib' - - mypy ; extra == 'lint' - - ruff ; extra == 'lint' - - types-docutils ; extra == 'lint' - - types-pymysql ; extra == 'lint' - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl name: mkdocstrings version: 1.0.4 @@ -10449,18 +9867,6 @@ packages: requires_dist: - diffpy-structure requires_python: '>=3.12,<3.15' -- pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - name: partd - version: 1.4.2 - sha256: 978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f - requires_dist: - - locket - - toolz - - numpy>=1.20.0 ; extra == 'complete' - - pandas>=1.3 ; extra == 'complete' - - pyzmq ; extra == 'complete' - - blosc ; extra == 'complete' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl name: jinja2-ansible-filters version: 1.3.2 @@ -10754,39 +10160,6 @@ packages: requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - name: essreduce - version: 26.4.1 - sha256: 1758a18fffca9c7c2a6fa9547cf87bf45f9d52fc3ccbdffcf7524f71bc060424 - requires_dist: - - sciline>=25.11.0 - - scipp>=26.3.1 - - scippneutron>=25.11.1 - - scippnexus>=25.6.0 - - graphviz>=0.20 ; extra == 'test' - - ipywidgets>=8.1 ; extra == 'test' - - matplotlib>=3.10.7 ; extra == 'test' - - numba>=0.63 ; extra == 'test' - - pooch>=1.9.0 ; extra == 'test' - - pytest>=7.0 ; extra == 'test' - - scipy>=1.14 ; extra == 'test' - - tof>=25.12.0 ; extra == 'test' - - autodoc-pydantic ; extra == 'docs' - - graphviz>=0.20 ; extra == 'docs' - - ipykernel ; extra == 'docs' - - ipython!=8.7.0 ; extra == 'docs' - - ipywidgets>=8.1 ; extra == 'docs' - - myst-parser ; extra == 'docs' - - nbsphinx ; extra == 'docs' - - numba>=0.63 ; extra == 'docs' - - plopp ; extra == 'docs' - - pydata-sphinx-theme>=0.14 ; extra == 'docs' - - sphinx>=7 ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-design ; extra == 'docs' - - tof>=25.12.0 ; extra == 'docs' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl name: lazy-loader version: '0.5' @@ -10816,16 +10189,6 @@ packages: version: 1.1.2 sha256: 1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - name: traittypes - version: 0.2.3 - sha256: 49016082ce740d6556d9bb4672ee2d899cd14f9365f17cbb79d5d96b47096d4e - requires_dist: - - traitlets>=4.2.2 - - numpy ; extra == 'test' - - pandas ; extra == 'test' - - xarray ; extra == 'test' - - pytest ; extra == 'test' - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl name: py3dmol version: 2.5.5 @@ -10847,32 +10210,6 @@ packages: - python-docs-theme ; extra == 'doc' - uncertainties[arrays,doc,test] ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/8f/82/b54e646be7b938fcbdda10030c6533bd2bb1a59930a1381cc83d6050a49c/spglib-2.6.0-cp312-cp312-win_amd64.whl - name: spglib - version: 2.6.0 - sha256: 86d0fd355689e58becd2cda609b03c3a0d9ad9d6f761cefd08b970db6f314eae - requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl name: paginate version: 0.5.7 @@ -10953,26 +10290,6 @@ packages: - numpy>=1.22 ; extra == 'express' - kaleido>=1.1.0 ; extra == 'kaleido' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - name: graphviz - version: '0.21' - sha256: 54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42 - requires_dist: - - build ; extra == 'dev' - - wheel ; extra == 'dev' - - twine ; extra == 'dev' - - flake8 ; extra == 'dev' - - flake8-pyproject ; extra == 'dev' - - pep8-naming ; extra == 'dev' - - tox>=3 ; extra == 'dev' - - pytest>=7,<8.1 ; extra == 'test' - - pytest-mock>=3 ; extra == 'test' - - pytest-cov ; extra == 'test' - - coverage ; extra == 'test' - - sphinx>=5,<7 ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-rtd-theme>=0.2.5 ; extra == 'docs' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/91/6d/c00cb0d69d2e240c233c65b7f76d10522731156b28a2135bb97a05abc32c/easydiffraction-0.17.0-py3-none-any.whl name: easydiffraction version: 0.17.0 @@ -11146,49 +10463,6 @@ packages: - pycodestyle>=2.12.0 - tomli ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - name: networkx - version: 3.6.1 - sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 - requires_dist: - - asv ; extra == 'benchmarking' - - virtualenv ; extra == 'benchmarking' - - numpy>=1.25 ; extra == 'default' - - scipy>=1.11.2 ; extra == 'default' - - matplotlib>=3.8 ; extra == 'default' - - pandas>=2.0 ; extra == 'default' - - pre-commit>=4.1 ; extra == 'developer' - - mypy>=1.15 ; extra == 'developer' - - sphinx>=8.0 ; extra == 'doc' - - pydata-sphinx-theme>=0.16 ; extra == 'doc' - - sphinx-gallery>=0.18 ; extra == 'doc' - - numpydoc>=1.8.0 ; extra == 'doc' - - pillow>=10 ; extra == 'doc' - - texext>=0.6.7 ; extra == 'doc' - - myst-nb>=1.1 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - osmnx>=2.0.0 ; extra == 'example' - - momepy>=0.7.2 ; extra == 'example' - - contextily>=1.6 ; extra == 'example' - - seaborn>=0.13 ; extra == 'example' - - cairocffi>=1.7 ; extra == 'example' - - igraph>=0.11 ; extra == 'example' - - scikit-learn>=1.5 ; extra == 'example' - - iplotx>=0.9.0 ; extra == 'example' - - lxml>=4.6 ; extra == 'extra' - - pygraphviz>=1.14 ; extra == 'extra' - - pydot>=3.0.1 ; extra == 'extra' - - sympy>=1.10 ; extra == 'extra' - - build>=0.10 ; extra == 'release' - - twine>=4.0 ; extra == 'release' - - wheel>=0.40 ; extra == 'release' - - changelist==0.5 ; extra == 'release' - - pytest>=7.2 ; extra == 'test' - - pytest-cov>=4.0 ; extra == 'test' - - pytest-xdist>=3.0 ; extra == 'test' - - pytest-mpl ; extra == 'test-extras' - - pytest-randomly ; extra == 'test-extras' - requires_python: '>=3.11,!=3.14.1' - pypi: https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl name: h5py version: 3.16.0 @@ -11466,11 +10740,6 @@ packages: - pytest ; extra == 'testing' - tox ; extra == 'testing' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - name: jupyterlab-widgets - version: 3.0.16 - sha256: 45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8 - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl name: numpy version: 2.4.6 @@ -11501,23 +10770,6 @@ packages: - prettytable - ply - numpy -- pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - name: sciline - version: 25.11.1 - sha256: 13c378287b8157e819b9b67d7e973c65bc6bdc545a3602d18204c365b0c336f9 - requires_dist: - - cyclebane>=24.6.0 - - pytest ; extra == 'test' - - pytest-randomly>=3 ; extra == 'test' - - dask ; extra == 'test' - - graphviz ; extra == 'test' - - jsonschema ; extra == 'test' - - numpy ; extra == 'test' - - pandas ; extra == 'test' - - pydantic ; extra == 'test' - - rich ; extra == 'test' - - rich ; extra == 'progress' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/b0/3e/a6497e1c2c9bc6ed2b79e0f2d31a4ce509fd2a9eed4e4f7ac63eda8113cb/gemmi-0.7.5-cp312-cp312-macosx_11_0_arm64.whl name: gemmi version: 0.7.5 @@ -11751,33 +11003,6 @@ packages: - xlsxwriter>=3.2.0 ; extra == 'all' - zstandard>=0.23.0 ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - name: dnspython - version: 2.8.0 - sha256: 01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af - requires_dist: - - black>=25.1.0 ; extra == 'dev' - - coverage>=7.0 ; extra == 'dev' - - flake8>=7 ; extra == 'dev' - - hypercorn>=0.17.0 ; extra == 'dev' - - mypy>=1.17 ; extra == 'dev' - - pylint>=3 ; extra == 'dev' - - pytest-cov>=6.2.0 ; extra == 'dev' - - pytest>=8.4 ; extra == 'dev' - - quart-trio>=0.12.0 ; extra == 'dev' - - sphinx-rtd-theme>=3.0.0 ; extra == 'dev' - - sphinx>=8.2.0 ; extra == 'dev' - - twine>=6.1.0 ; extra == 'dev' - - wheel>=0.45.0 ; extra == 'dev' - - cryptography>=45 ; extra == 'dnssec' - - h2>=4.2.0 ; extra == 'doh' - - httpcore>=1.0.0 ; extra == 'doh' - - httpx>=0.28.0 ; extra == 'doh' - - aioquic>=1.2.0 ; extra == 'doq' - - idna>=3.10 ; extra == 'idna' - - trio>=0.30 ; extra == 'trio' - - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl name: pillow version: 12.2.0 @@ -11824,32 +11049,6 @@ packages: version: 1.2.0 sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/bd/8c/d4907ad4f6bdc5bf79462d8767728713a7b316918a7444df372958a0e417/spglib-2.6.0-cp312-cp312-macosx_11_0_arm64.whl - name: spglib - version: 2.6.0 - sha256: 83ea2e90addc7232017c793a32d94b47bc68040c595671d1cbb836ede4349510 - requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp version: 3.13.5 @@ -11868,32 +11067,6 @@ packages: - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/bf/34/1fe99124be59579ebd24316522e1da780979c856977b142c0dcd878b0a2d/spglib-2.6.0.tar.gz - name: spglib - version: 2.6.0 - sha256: d66eda2ba00a1e14fd96ec9c3b4dbf8ab0fb3f124643e35785c71ee455b408eb - requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl name: docstring-parser-fork version: 0.0.14 @@ -11963,44 +11136,6 @@ packages: version: 1.5.0 sha256: bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - name: essdiffraction - version: 26.5.1 - sha256: 8a6c779078c71be250714619214069221ab7968a69580d4e4d3f4b3e9a1a53ad - requires_dist: - - dask>=2022.1.0 - - essreduce>=26.4.0 - - graphviz - - numpy>=2 - - plopp>=26.2.0 - - pythreejs>=2.4.1 - - sciline>=25.4.1 - - scipp>=25.11.0 - - scippneutron>=26.3.0 - - scippnexus>=23.12.0 - - tof>=25.12.0 - - ncrystal[cif]>=4.1.0 - - spglib!=2.7 - - pandas>=2.1.2 ; extra == 'test' - - pooch>=1.5 ; extra == 'test' - - pytest>=7.0 ; extra == 'test' - - ipywidgets>=8.1.7 ; extra == 'test' - - autodoc-pydantic ; extra == 'docs' - - ipykernel ; extra == 'docs' - - ipympl ; extra == 'docs' - - ipython!=8.7.0 ; extra == 'docs' - - myst-parser ; extra == 'docs' - - nbsphinx ; extra == 'docs' - - pandas ; extra == 'docs' - - pooch ; extra == 'docs' - - pydata-sphinx-theme>=0.14 ; extra == 'docs' - - sphinx ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-design ; extra == 'docs' - - sphinxcontrib-bibtex ; extra == 'docs' - - pyarrow ; extra == 'docs' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl name: mkdocs-plugin-inline-svg version: 0.1.0 @@ -12015,11 +11150,6 @@ packages: requires_dist: - colorama ; sys_platform == 'win32' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl - name: ncrystal-core - version: 4.4.2 - sha256: 9b28a90b63849e6a3a807a0a59f7c2ee57e4c64f5643b2dcb6a798ac8ccf666a - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl name: dfo-ls version: 1.6.5 @@ -12034,11 +11164,6 @@ packages: - sphinx-rtd-theme ; extra == 'dev' - trustregion>=1.1 ; extra == 'trustregion' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl - name: ncrystal-core - version: 4.4.2 - sha256: b7e6101a6850aa18cf441825214381614db444ffcba648de8266fe1c4d1024ce - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl name: trove-classifiers version: 2026.5.22.10 @@ -12331,114 +11456,6 @@ packages: name: funcy version: '2.0' sha256: 53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0 -- pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - name: fsspec - version: 2026.4.0 - sha256: 11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2 - requires_dist: - - adlfs ; extra == 'abfs' - - adlfs ; extra == 'adl' - - pyarrow>=1 ; extra == 'arrow' - - dask ; extra == 'dask' - - distributed ; extra == 'dask' - - pre-commit ; extra == 'dev' - - ruff>=0.5 ; extra == 'dev' - - numpydoc ; extra == 'doc' - - sphinx ; extra == 'doc' - - sphinx-design ; extra == 'doc' - - sphinx-rtd-theme ; extra == 'doc' - - yarl ; extra == 'doc' - - dropbox ; extra == 'dropbox' - - dropboxdrivefs ; extra == 'dropbox' - - requests ; extra == 'dropbox' - - adlfs ; extra == 'full' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full' - - dask ; extra == 'full' - - distributed ; extra == 'full' - - dropbox ; extra == 'full' - - dropboxdrivefs ; extra == 'full' - - fusepy ; extra == 'full' - - gcsfs>2024.2.0 ; extra == 'full' - - libarchive-c ; extra == 'full' - - ocifs ; extra == 'full' - - panel ; extra == 'full' - - paramiko ; extra == 'full' - - pyarrow>=1 ; extra == 'full' - - pygit2 ; extra == 'full' - - requests ; extra == 'full' - - s3fs>2024.2.0 ; extra == 'full' - - smbprotocol ; extra == 'full' - - tqdm ; extra == 'full' - - fusepy ; extra == 'fuse' - - gcsfs>2024.2.0 ; extra == 'gcs' - - pygit2 ; extra == 'git' - - requests ; extra == 'github' - - gcsfs ; extra == 'gs' - - panel ; extra == 'gui' - - pyarrow>=1 ; extra == 'hdfs' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http' - - libarchive-c ; extra == 'libarchive' - - ocifs ; extra == 'oci' - - s3fs>2024.2.0 ; extra == 's3' - - paramiko ; extra == 'sftp' - - smbprotocol ; extra == 'smb' - - paramiko ; extra == 'ssh' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test' - - numpy ; extra == 'test' - - pytest ; extra == 'test' - - pytest-asyncio!=0.22.0 ; extra == 'test' - - pytest-benchmark ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-mock ; extra == 'test' - - pytest-recording ; extra == 'test' - - pytest-rerunfailures ; extra == 'test' - - requests ; extra == 'test' - - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream' - - dask[dataframe,test] ; extra == 'test-downstream' - - moto[server]>4,<5 ; extra == 'test-downstream' - - pytest-timeout ; extra == 'test-downstream' - - xarray ; extra == 'test-downstream' - - adlfs ; extra == 'test-full' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full' - - backports-zstd ; python_full_version < '3.14' and extra == 'test-full' - - cloudpickle ; extra == 'test-full' - - dask ; extra == 'test-full' - - distributed ; extra == 'test-full' - - dropbox ; extra == 'test-full' - - dropboxdrivefs ; extra == 'test-full' - - fastparquet ; extra == 'test-full' - - fusepy ; extra == 'test-full' - - gcsfs ; extra == 'test-full' - - jinja2 ; extra == 'test-full' - - kerchunk ; extra == 'test-full' - - libarchive-c ; extra == 'test-full' - - lz4 ; extra == 'test-full' - - notebook ; extra == 'test-full' - - numpy ; extra == 'test-full' - - ocifs ; extra == 'test-full' - - pandas<3.0.0 ; extra == 'test-full' - - panel ; extra == 'test-full' - - paramiko ; extra == 'test-full' - - pyarrow ; extra == 'test-full' - - pyarrow>=1 ; extra == 'test-full' - - pyftpdlib ; extra == 'test-full' - - pygit2 ; extra == 'test-full' - - pytest ; extra == 'test-full' - - pytest-asyncio!=0.22.0 ; extra == 'test-full' - - pytest-benchmark ; extra == 'test-full' - - pytest-cov ; extra == 'test-full' - - pytest-mock ; extra == 'test-full' - - pytest-recording ; extra == 'test-full' - - pytest-rerunfailures ; extra == 'test-full' - - python-snappy ; extra == 'test-full' - - requests ; extra == 'test-full' - - smbprotocol ; extra == 'test-full' - - tqdm ; extra == 'test-full' - - urllib3 ; extra == 'test-full' - - zarr ; extra == 'test-full' - - zstandard ; python_full_version < '3.14' and extra == 'test-full' - - tqdm ; extra == 'tqdm' - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl name: pycodestyle version: 2.14.0 @@ -12451,27 +11468,6 @@ packages: requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - name: pythreejs - version: 2.4.2 - sha256: 8418807163ad91f4df53b58c4e991b26214852a1236f28f1afeaadf99d095818 - requires_dist: - - ipywidgets>=7.2.1 - - ipydatawidgets>=1.1.1 - - numpy - - traitlets - - sphinx>=1.5 ; extra == 'docs' - - nbsphinx>=0.2.13 ; extra == 'docs' - - nbsphinx-link ; extra == 'docs' - - sphinx-rtd-theme ; extra == 'docs' - - scipy ; extra == 'examples' - - matplotlib ; extra == 'examples' - - scikit-image ; extra == 'examples' - - ipywebrtc ; extra == 'examples' - - nbval ; extra == 'test' - - pytest-check-links ; extra == 'test' - - numpy>=1.14 ; extra == 'test' - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl name: pillow version: 12.2.0 @@ -12560,11 +11556,6 @@ packages: requires_dist: - pyyaml>=3.10 ; extra == 'watchmedo' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - name: locket - version: 1.0.0 - sha256: b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3 - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl name: watchdog version: 6.0.0 @@ -12627,21 +11618,6 @@ packages: requires_dist: - typing-extensions>=4.12.0 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - name: ncrystal-python - version: 4.4.2 - sha256: f419318d088fade6bcff1e39e15baf6fe69fcf5306dd681fca1106d1f63a89ce - requires_dist: - - numpy>=1.22 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - name: email-validator - version: 2.3.0 - sha256: 80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 - requires_dist: - - dnspython>=2.0.0 - - idna>=2.0.0 - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl name: markdown version: 3.10.2 @@ -12674,37 +11650,6 @@ packages: version: 1.5.4 sha256: 7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - name: plopp - version: 26.4.2 - sha256: 5cab99bb0905ce08a1d1d7d82f0f64cee7d594269ec1bd01a8a361bd14ab7bff - requires_dist: - - lazy-loader>=0.4 - - matplotlib>=3.8 - - scipp>=25.8.0 ; extra == 'scipp' - - plopp[scipp] ; extra == 'all' - - ipympl>0.8.4 ; extra == 'all' - - pythreejs>=2.4.1 ; extra == 'all' - - mpltoolbox>=24.6.0 ; extra == 'all' - - ipywidgets>=8.1.0 ; extra == 'all' - - graphviz>=0.20.3 ; extra == 'all' - - plopp[scipp] ; extra == 'test' - - graphviz>=0.20.3 ; extra == 'test' - - h5py>=3.12 ; extra == 'test' - - ipympl>=0.8.4 ; extra == 'test' - - ipywidgets>=8.1.0 ; extra == 'test' - - ipykernel>=6.26,<7 ; extra == 'test' - - mpltoolbox>=24.6.0 ; extra == 'test' - - pandas>=2.2.2 ; extra == 'test' - - plotly>=5.15.0 ; extra == 'test' - - pooch>=1.5 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'test' - - pytest>=8.0 ; extra == 'test' - - pythreejs>=2.4.1 ; extra == 'test' - - scipy>=1.10.0 ; extra == 'test' - - xarray>=2024.5.0 ; extra == 'test' - - anywidget>=0.9.0 ; extra == 'test' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/e5/04/c5bb20d64417d20cba0105277235c51969444fa873000fbc26ac0a3fc5a8/gemmi-0.7.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: gemmi version: 0.7.5 @@ -12771,11 +11716,6 @@ packages: requires_dist: - numpy>=1.21.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: ncrystal-core - version: 4.4.2 - sha256: d0d9c47cd017b7cefc52dde50546d7c151bfdd75d345e42e2b3e74ab5fe83c62 - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl name: multidict version: 6.7.1 @@ -12783,21 +11723,6 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - name: ipydatawidgets - version: 4.3.5 - sha256: d590cdb7c364f2f6ab346f20b9d2dd661d27a834ef7845bc9d7113118f05ec87 - requires_dist: - - ipywidgets>=7.0.0 - - numpy - - traittypes>=0.2.0 - - sphinx ; extra == 'docs' - - recommonmark ; extra == 'docs' - - sphinx-rtd-theme ; extra == 'docs' - - pytest>=4 ; extra == 'test' - - pytest-cov ; extra == 'test' - - nbval>=0.9.2 ; extra == 'test' - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl name: pathspec version: 1.1.1 @@ -12851,55 +11776,11 @@ packages: version: 0.1.4 sha256: 27b3b67cf898684e646d569f017cb27046774ad23866cb0bdf51d5f76a46476b requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - name: tof - version: 26.3.0 - sha256: e89783a072b05fdb53d9e76fbf919dc8935e75e118fdaf17ca5cc33727ef002b - requires_dist: - - plopp>=23.10.0 - - pooch>=1.5.0 - - scipp>=25.1.0 - - lazy-loader>=0.3 - - pytest>=8.0 ; extra == 'test' - - scippneutron>=24.12.0 ; extra == 'test' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl name: py version: 1.11.0 sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - name: ncrystal - version: 4.4.2 - sha256: e02fa7d743addc3fbea23287737a88b8d01192450fdca51554d3f9032fe4617c - requires_dist: - - ncrystal-core==4.4.2 - - ncrystal-python==4.4.2 - - spglib>=2.1.0 ; extra == 'composer' - - ase>=3.23.0 ; extra == 'cif' - - gemmi>=0.6.1 ; extra == 'cif' - - spglib>=2.1.0 ; extra == 'cif' - - endf-parserpy>=0.14.3 ; extra == 'endf' - - matplotlib>=3.6.0 ; extra == 'plot' - - ase>=3.23.0 ; extra == 'all' - - endf-parserpy>=0.14.3 ; extra == 'all' - - gemmi>=0.6.1 ; extra == 'all' - - matplotlib>=3.6.0 ; extra == 'all' - - spglib>=2.1.0 ; extra == 'all' - - pyyaml>=6.0.0 ; extra == 'devel' - - ase>=3.23.0 ; extra == 'devel' - - cppcheck ; extra == 'devel' - - endf-parserpy>=0.14.3 ; extra == 'devel' - - gemmi>=0.6.1 ; extra == 'devel' - - matplotlib>=3.6.0 ; extra == 'devel' - - mpmath>=1.3.0 ; extra == 'devel' - - numpy>=1.22 ; extra == 'devel' - - pybind11>=2.11.0 ; extra == 'devel' - - ruff>=0.8.1 ; extra == 'devel' - - simple-build-system>=1.6.0 ; extra == 'devel' - - spglib>=2.1.0 ; extra == 'devel' - - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'devel' - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl name: ghp-import version: 2.1.0 @@ -12934,11 +11815,6 @@ packages: - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - name: toolz - version: 1.1.0 - sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl name: aiosignal version: 1.4.0 diff --git a/pyproject.toml b/pyproject.toml index 507a069d1..d953cfa19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,6 @@ dependencies = [ [project.optional-dependencies] dev = [ - 'essdiffraction', # ESS-specific diffraction library 'GitPython', # Interact with Git repositories 'build', # Building the package 'pre-commit', # Pre-commit hooks diff --git a/tests/integration/scipp-analysis/dream/test_package_import.py b/tests/integration/scipp-analysis/dream/test_package_import.py index 45b5bc95b..d17da2007 100644 --- a/tests/integration/scipp-analysis/dream/test_package_import.py +++ b/tests/integration/scipp-analysis/dream/test_package_import.py @@ -3,17 +3,16 @@ """Tests for verifying package installation and version consistency. -These tests check that easydiffraction and essdiffraction packages are -installed and are not older than the latest PyPI release. +These tests check that easydiffraction is installed and can be found on +PyPI. """ import importlib.metadata import pytest import requests -from packaging.version import Version -PACKAGE_NAMES = ['easydiffraction', 'essdiffraction'] +PACKAGE_NAMES = ['easydiffraction'] PYPI_URL = 'https://pypi.org/pypi/{}/json' @@ -37,16 +36,6 @@ def get_latest_version( return None -def get_base_version( - version_str: str, -) -> str: - """Extract MAJOR.MINOR.PATCH from version string, ignoring local - identifiers. - """ - v = Version(version_str) - return v.base_version - - @pytest.mark.parametrize('package_name', PACKAGE_NAMES) def test_package_import( package_name: str, From 5d5c5b769d86df6a30426ca9280b113a17f7784d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 15:17:56 +0200 Subject: [PATCH 29/38] Promote minimizer-input-output-split ADR --- .../minimizer-input-output-split.md | 26 +-- .../minimizer-input-output-split_reply-1.md | 134 -------------- .../minimizer-input-output-split_reply-2.md | 175 ------------------ .../minimizer-input-output-split_reply-3.md | 128 ------------- .../minimizer-input-output-split_reply-4.md | 129 ------------- .../minimizer-input-output-split_reply-5.md | 96 ---------- .../minimizer-input-output-split_reply-6.md | 59 ------ .../minimizer-input-output-split_reply-7.md | 48 ----- .../minimizer-input-output-split_review-1.md | 19 -- .../minimizer-input-output-split_review-2.md | 21 --- .../minimizer-input-output-split_review-3.md | 19 -- .../minimizer-input-output-split_review-4.md | 19 -- .../minimizer-input-output-split_review-5.md | 16 -- .../minimizer-input-output-split_review-6.md | 14 -- .../minimizer-input-output-split_review-7.md | 21 --- .../dev/plans/minimizer-input-output-split.md | 2 +- 16 files changed, 14 insertions(+), 912 deletions(-) rename docs/dev/adrs/{suggestions => accepted}/minimizer-input-output-split.md (94%) delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md delete mode 100644 docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split.md b/docs/dev/adrs/accepted/minimizer-input-output-split.md similarity index 94% rename from docs/dev/adrs/suggestions/minimizer-input-output-split.md rename to docs/dev/adrs/accepted/minimizer-input-output-split.md index b6dbe78bd..a496dc3a1 100644 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split.md +++ b/docs/dev/adrs/accepted/minimizer-input-output-split.md @@ -1,11 +1,11 @@ # ADR: Minimizer Input/Output Split -**Status:** Proposed **Date:** 2026-05-24 +**Status:** Accepted **Date:** 2026-05-24 ## Status Note This proposal revisits and supersedes Alternative D in -[`minimizer-category-consolidation.md`](../accepted/minimizer-category-consolidation.md) +[`minimizer-category-consolidation.md`](minimizer-category-consolidation.md) ("Strict input-only `minimizer` plus a separate `fit_result`"), which was rejected during the consolidation work on the assumption that the input/output mix on `analysis.minimizer` was symmetric with the @@ -16,7 +16,7 @@ duplication problems documented below. ## Context After -[`minimizer-category-consolidation.md`](../accepted/minimizer-category-consolidation.md) +[`minimizer-category-consolidation.md`](minimizer-category-consolidation.md) landed, `analysis.minimizer` holds both writable user inputs and fit-filled outputs in a single namespace. A user typing `analysis.minimizer.help()` before any fit sees roughly twenty @@ -97,7 +97,7 @@ the user changes the active `fit_result` class is by setting uses to instantiate both `self._minimizer` and `self._fit_result` atomically. This is an explicit, documented exception to the global selector contract from -[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +[`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) §1 because there is no user choice involved at the `fit_result` level — the minimizer family fully determines the result schema. See the new exception text added to that ADR (listed under §"ADRs amended"). @@ -190,7 +190,7 @@ block (already present today for the common header fields) absorbs every fit output. The set of `_fit_result.*` tags depends on the active `_minimizer.type`, matching the same shape-shifting convention that `_minimizer.*` itself already uses per -[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md). +[`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md). Example deterministic fit: @@ -254,7 +254,7 @@ category) holds the persisted scalar projection of the same fit. The naming pair stays as today. A small UX win is added under the accepted display facade -([`display-ux.md`](../accepted/display-ux.md)): the existing +([`display-ux.md`](display-ux.md)): the existing `project.display.fit.results()` entry point gains a "Settings used" table above the existing results tables, populated from `analysis.minimizer.*`. No new `Analysis`-level display method is @@ -281,7 +281,7 @@ swap hook updates **both** `analysis.minimizer` and `fit_result` is not a user-facing switchable: there is no `fit_result.type` and no `fit_result.show_supported()`, per the documented exception added to -[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +[`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) (see §"ADRs amended"). This keeps "one minimizer concept, one user-facing type" intact and removes the temptation to swap the result class independently of the minimizer. @@ -331,26 +331,26 @@ class independently of the minimizer. ### ADRs amended by this ADR -- [`minimizer-category-consolidation.md`](../accepted/minimizer-category-consolidation.md) +- [`minimizer-category-consolidation.md`](minimizer-category-consolidation.md) — §1 ("Unified `minimizer` category replaces all sampler-input and fit-result categories") becomes a partial rule: the unified `minimizer` holds inputs; outputs move to the paired `fit_result`. §"Alternatives Considered → D" updated to record the reversal and the implementation evidence that prompted it. -- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) +- [`analysis-cif-fit-state.md`](analysis-cif-fit-state.md) — §"Minimizer fit projection" rewritten to describe the split (`_minimizer.*` settings-only, `_fit_result.*` outputs including family-specific fields). -- [`runtime-fit-results.md`](../accepted/runtime-fit-results.md) +- [`runtime-fit-results.md`](runtime-fit-results.md) — closing paragraph references this ADR alongside the existing two. -- [`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +- [`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) — §1 ("The category owns its selector") gains a paragraph carving out one documented exception: a category that is fully determined by another category's `type` (today only `fit_result`, derived from `minimizer.type`) is allowed to omit `category.type` and `category.show_supported()`. The mechanism is described in §1 of this ADR. The user-facing selector convention is otherwise unchanged. -- [`display-ux.md`](../accepted/display-ux.md) — §"Fit results display" +- [`display-ux.md`](display-ux.md) — §"Fit results display" expanded to mention that `project.display.fit.results()` now prints a "Settings used" block above the result tables, sourced from `analysis.minimizer.*`. No new public entry point is added. @@ -409,6 +409,6 @@ explicitly puts them in CIF (`_minimizer.*` today). Make the pairing rule explicit in the name (`` and `_result`). Rejected because the recently-accepted -[`switchable-category-owned-selectors.md`](../accepted/switchable-category-owned-selectors.md) +[`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) ADR deliberately drops `_type` and other suffixes from category names; adding `_result` walks the convention back. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md deleted file mode 100644 index 1fcc4842a..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md +++ /dev/null @@ -1,134 +0,0 @@ -# Reply to Review 1: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-1.md`](minimizer-input-output-split_review-1.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -All five findings agreed. Each was addressed by editing the ADR text; -no item is deferred. The ADR is updated in the same commit as this -reply. - -## Findings - -### Finding 1 — Switchable category contract violation - -**Verdict: agree.** The original text used the defined term -"switchable category" (per the accepted selector ADR) but then said -`fit_result` would not expose `type` or `show_supported()` — which is -precisely what the selector contract requires. The selector ADR was -also missing from the amended-ADR list. - -**Action taken.** Rewrote §1 so the paired result class is described -as an **internal projection paired with the active minimizer**, not as -a switchable category. Explicit text: "It does not expose -`fit_result.type` or `fit_result.show_supported()`; the only way the -user changes the active `fit_result` class is by setting -`analysis.minimizer.type`." The exception to the global selector -contract is now documented in two places: (a) §1 of this ADR ("This is -an explicit, documented exception …"); (b) the new amended-ADR entry -for `switchable-category-owned-selectors.md`, which records the carve- -out: a category whose active class is fully determined by another -category's `type` may omit `type` and `show_supported()`. §6 ("No new -selector wiring") and §"Trade-offs" updated to match the new wording. - -**Plan section:** §1 (rewritten), §6 (updated), §"Trade-offs" -(updated), §"ADRs amended" (added `switchable-category-owned-selectors.md`). - -### Finding 2 — `objective_value`/`reduced_chi_square` inconsistency - -**Verdict: agree.** The original text said "drop -`minimizer.objective_value`" with `reduced_chi_square` as the single -source, but the LSQ field list and the deterministic CIF example -included **both** — leaving an implementer free to drop or keep -`objective_value` and both claim ADR compliance. - -**Action taken.** Rewrote the resolution bullet in §2 to state -explicitly that `objective_value` (raw χ²) and `reduced_chi_square` -(= χ² / `degrees_of_freedom`) are **distinct fields**, both kept on -`LeastSquaresFitResult`. The raw value is what the solver optimises -and is useful for diagnostics on small-dof fits; the reduced value is -what user-facing tables and plots display. `BayesianFitResult` does -not carry `objective_value` because the Bayesian engine optimises the -log posterior. The Positive-consequences bullet about "duplications" -no longer claims this pair was a duplication; only the -`runtime_seconds` and `iterations` pairs are real duplications, and -the bullet now says so. - -**Plan section:** §2 (resolution bullet rewritten), §"Positive" -(bullet adjusted). - -### Finding 3 — `credible_interval_*` semantics - -**Verdict: agree.** The ADR listed -`credible_interval_inner/credible_interval_outer` as current writable -inputs and kept them on the settings-only `minimizer`, but in the -live code they have only internal `_set_*` and live in -`_result_descriptor_names`. The "settings-only minimizer" boundary -needed a clear answer for these two fields. - -**Action taken.** Added a paragraph to §2 making the promotion -explicit: the two interval levels are **promoted from output-only to -writable input** by this ADR. They are set before `analysis.fit()`; -the Bayesian posterior-summary path reads them at fit time and -generates the per-parameter interval columns from those levels. -Documented that the column names stay numeric (`68`, `95`) for now — -mismatching user levels are warned about at fit time so the names do -not silently lie — and added a Deferred-Work entry to track a later -ADR that may generalise the column naming. - -**Plan section:** §2 (new paragraph), §"Deferred Work" (new entry). - -### Finding 4 — `analysis.show_fit_summary()` bypasses display facade - -**Verdict: agree.** The accepted Display UX ADR moves user-facing fit -reporting to `project.display.fit.results()`. Adding a new -`analysis.show_fit_summary()` would reintroduce exactly the split that -ADR cleaned up, and `display-ux.md` was missing from the amended-ADR -list. - -**Action taken.** Removed the new `analysis.show_fit_summary()` -method entirely. Replaced it with an extension to the existing -`project.display.fit.results()` entry point: it now prints a "Settings -used" block above the result tables, populated from -`analysis.minimizer.*`. No new public entry point is added. Added -`display-ux.md` to the amended-ADR list with a sentence describing -exactly this extension. §4 (runtime-object section) and -§"Trade-offs" updated to reference the display-facade method instead. - -**Plan section:** §4 (updated paragraph), §"Trade-offs" (updated), -§"ADRs amended" (added `display-ux.md`). - -### Finding 5 — Pairing table only names two classes - -**Verdict: agree.** The original §1 paired `LmfitLeastsqMinimizer ↔ -LeastSquaresFitResult` and `BumpsDreamMinimizer ↔ BayesianFitResult` -as examples. With nine `MinimizerTypeEnum` members today plus emcee -on the horizon, that left the swap/load behaviour underspecified for -non-default deterministic minimizers. - -**Action taken.** Added a full mapping table to §1 listing every -current `MinimizerTypeEnum` member with its family and paired result -class, plus a row for `EMCEE` (when added). Added a sentence -documenting that the pairing rule is encoded once on the minimizer -base classes (`LeastSquaresMinimizerBase._fit_result_class = -LeastSquaresFitResult`, `BayesianMinimizerBase._fit_result_class = -BayesianFitResult`) so the swap hook reads the paired class off the -new minimizer instance and does not need a per-tag dispatch. - -**Plan section:** §1 (new mapping table + pairing-rule paragraph). - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — ADR updated per all five findings above. -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-1.md`](minimizer-input-output-split_reply-1.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md deleted file mode 100644 index f79add35a..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md +++ /dev/null @@ -1,175 +0,0 @@ -# Reply to Review 2: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-2.md`](minimizer-input-output-split_review-2.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Context note on the review - -The review header records that `_reply-1.md` was not visible when -review 2 was prepared, only the direct edits to the ADR. After -auditing the current ADR with `git grep` against each of the cited -phrases, three of the four findings (F1, F3-part, F4) reference text -that no longer appears in the file — they describe the pre-reply-1 -shape of the ADR. F2 is a genuinely new and substantive concern; the -half of F3 that talks about the §Context table phrasing is also a -real issue worth addressing. - -Each finding is handled below: F2 and the §Context half of F3 trigger -real ADR edits; F1, F4, and the §Consequences half of F3 are -confirmed-already-addressed with current line numbers cited. - -## Findings - -### Finding 1 — `fit_result` switchable inconsistency - -**Verdict: partial — substantively resolved in reply 1; documenting -current state here.** - -The review cites lines that the reply-1 edits already replaced. -Current ADR state (verified by `grep -n switchable -docs/dev/adrs/suggestions/minimizer-input-output-split.md`): - -- Line 88: "**`fit_result` is not a user-facing switchable - category.**" -- Line 96: §1 cross-references - `switchable-category-owned-selectors.md` for the documented - exception. -- Line 280-282: §6 reiterates "The paired `fit_result` is not a - user-facing switchable: there is no `fit_result.type` and no - `fit_result.show_supported()`". -- Line 345: `switchable-category-owned-selectors.md` is listed under - "ADRs amended" with the explicit exception text: - > §1 ("The category owns its selector") gains a paragraph carving - > out one documented exception: a category that is fully determined - > by another category's `type` (today only `fit_result`, derived - > from `minimizer.type`) is allowed to omit `category.type` and - > `category.show_supported()`. - -No remaining text calls `fit_result` a switchable category, and the -amended-ADR list does include the selector ADR. The cited line -numbers (`:269-278`, `:302-311`, `:342-345`, `:320-333`) do not match -the corresponding ranges in the current file. No further ADR change. - -**Plan section:** §1 (lines 80–127), §6 (lines 273–286), §"ADRs -amended" (lines 339–357) — all carry the reply-1 edits. - -### Finding 2 — Configurable credible-interval levels would mislabel persisted columns - -**Verdict: agree — data-integrity concern, real and substantive.** - -Reply 1 promoted `credible_interval_inner` / `credible_interval_outer` -to user-writable settings while keeping the fixed -`posterior_interval_68_low/high` / `posterior_interval_95_low/high` -per-parameter column names. The reviewer correctly observes that this -allows, for example, a user-set `0.50` interval to be persisted under -a column named `posterior_interval_68_low`. That is a data-integrity -hole, not a UX warning surface. - -**Action taken.** Demoted the two interval-level fields back to the -output side. They now live on `BayesianFitResult` (alongside -`acceptance_rate_mean`, `gelman_rubin_max`, etc.) with the fixed -values `0.68` and `0.95`, matching the column names. The §2 paragraph -that introduced the promotion is rewritten to explain the choice -explicitly: - -> Promoting the levels to user-writable settings would let the user -> choose a 50% interval that then gets persisted under a column named -> `posterior_interval_68_low` — a data-integrity problem rather than a -> UX problem. A future suggestion ADR can promote them to settings -> and generalise the column naming (e.g. -> `posterior_interval_low_`) in one combined change; doing one -> without the other is unsafe and out of scope here. - -The Bayesian field list in §2 moved the two fields from `minimizer` -to `BayesianFitResult`. The Bayesian CIF example in §3 moved the two -`_minimizer.credible_interval_*` lines to `_fit_result.*`. The -Deferred-Work entry was rewritten to make the combined level+naming -follow-on explicit. - -This resolves the only "settings-only minimizer" boundary question -that survived reply 1 in the opposite direction from reply 1, taking -the "or another summary-configuration category" branch that review 1 -F3 originally offered. - -**Plan section:** §2 (lines 131–151), §3 Bayesian example -(lines 217–240), §"Deferred Work" (interval-naming entry). - -### Finding 3 — `objective_value` / `reduced_chi_square` framing - -**Verdict: agree on the §Context phrasing; already addressed in -§Consequences.** - -The §Consequences "Positive" bullet was rewritten in reply 1 to say -explicitly that the `objective_value`/`reduced_chi_square` pair is -**not** a duplication (current lines 294–298 — verified by `grep -n -"real duplications"`). That half is already correct. - -The §Context table did still call all three rows an "overlap" without -distinguishing real duplications from cross-category misplacement, -which is genuinely inconsistent with the §2 clarification. The -reviewer is right to flag it. - -**Action taken.** Rewrote the §Context table to add a fourth -"Relationship" column that classifies each row: - -- Wall time → real duplication -- Iteration count → real duplication -- Objective vs reduced χ² → cross-category misplacement (two related - but distinct scalars where the raw χ² sits on `minimizer` instead - of with the rest of the fit outputs) - -The surrounding sentence now reads "fit-output content split across -`minimizer` and `fit_result` (whether the two scalars per row are the -same value or not). §2 resolves each row above explicitly." This -removes the implicit "all three are duplicates" claim while keeping -the table as the entry point for the resolution in §2. - -**Plan section:** §Context (lines 40–55), §"Positive" bullet -(lines 294–298 — unchanged, already correct). - -### Finding 4 — `analysis.show_fit_summary()` bypasses display facade - -**Verdict: partial — substantively resolved in reply 1; documenting -current state here.** - -Reply 1 removed the new `analysis.show_fit_summary()` method and -extended the existing `project.display.fit.results()` entry point -instead. Current ADR state: - -- Lines 255–262: §4 says "A small UX win is added under the accepted - display facade ([`display-ux.md`](../accepted/display-ux.md)): the - existing `project.display.fit.results()` entry point gains a - 'Settings used' table above the existing results tables, populated - from `analysis.minimizer.*`. No new `Analysis`-level display method - is added". -- Line 313–314: §"Trade-offs" cites `project.display.fit.results()` - as the mitigation (not `analysis.show_fit_summary()`). -- Lines 352–356: `display-ux.md` is listed under "ADRs amended" with - the explicit extension text. - -`grep -n show_fit_summary -docs/dev/adrs/suggestions/minimizer-input-output-split.md` returns -zero hits. The cited line numbers (`:255-258`, `:304-307`, -`:320-333`) do not match the corresponding ranges in the current -file. No further ADR change. - -**Plan section:** §4 (lines 245–262), §"Trade-offs" (line 313), -§"ADRs amended" (lines 352–356) — all carry the reply-1 edits. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — ADR updated for F2 (demote credible intervals back to outputs) and - the §Context half of F3 (table phrasing). -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-2.md`](minimizer-input-output-split_reply-2.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md deleted file mode 100644 index cefd1926f..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md +++ /dev/null @@ -1,128 +0,0 @@ -# Reply to Review 3: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-3.md`](minimizer-input-output-split_review-3.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Context note on the review - -The review header again records that the preceding reply -(`_reply-2.md`) was not visible. After auditing the current ADR -against each finding, F1 and F3 reference text that was already -revised in reply-2; the cited line numbers no longer correspond to -text matching the complaint. F2 is a real bug introduced in the -original draft and never fixed by either reply — the §Context list -incorrectly attributes user-writable status to the credible-interval -levels that the live code exposes as outputs. - -F2 triggers an ADR edit. F1 and F3 are confirmed already-addressed -with current line numbers and quoted text. - -## Findings - -### Finding 1 — Writable arbitrary credible interval levels persist into misleading column names - -**Verdict: partial — substantively resolved in reply 2.** - -The review reads the ADR as still promoting the credible-interval -levels to user settings. The reply-2 edits demoted them back to the -output side, attached to `BayesianFitResult`. Current ADR state -(verified by `grep -n credible_interval -docs/dev/adrs/suggestions/minimizer-input-output-split.md`): - -- Line 140: §2 paragraph reads "`credible_interval_inner` and - `credible_interval_outer` **stay on the output side** in this ADR, - attached to `BayesianFitResult` (see below)". -- Line 163: `BayesianFitResult` field list includes - `credible_interval_inner`, `credible_interval_outer`. -- Lines 236–237: the Bayesian CIF example shows - `_fit_result.credible_interval_inner 0.68` and - `_fit_result.credible_interval_outer 0.95` (no longer under - `_minimizer.*`). -- §"Deferred Work" carries the combined level+naming follow-on: - > Promoting the levels to user settings requires generalising the - > column naming at the same time to avoid the data-integrity hole - > where a `0.50` level lands in a column called - > `posterior_interval_68_low`. Both pieces belong in a follow-on - > ADR so this proposal stays focused on the input/output split. - -The data-integrity hole the reviewer is concerned about cannot occur -under the current ADR text — the levels are fixed at `0.68` / `0.95`, -matching the column names. The cited line range (`:138-153`) maps to -a paragraph that, in the current file, says the opposite of what the -review attributes to it. No further ADR change. - -**Plan section:** §2 (lines 138–151), §3 Bayesian CIF example -(lines 236–237), §"Deferred Work". - -### Finding 2 — Context list still describes credible-interval levels as currently writable - -**Verdict: agree — real bug, dating from the original draft.** - -The §Context list at lines 26–37 enumerates the **current live** -shape of `analysis.minimizer`. Since the original draft, the -"Writable inputs" bullet has incorrectly included -`credible_interval_inner` and `credible_interval_outer`. The live -code in -[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) -exposes these only via the internal `_set_credible_interval_*` -methods and lists them under `_result_descriptor_names`, not -`_setting_descriptor_names`. Neither reply 1 nor reply 2 corrected -the §Context list itself, even after both replies adjusted the -downstream decisions. - -**Action taken.** Moved the two field names from "Writable inputs" -to "Fit-filled outputs (no public setter, only `_set_*` internals)" -in the §Context list. The §Context now accurately mirrors the live -category surface, and the rest of the ADR (which already treats them -as outputs after reply 2) reads consistently end-to-end. - -**Plan section:** §Context, lines 26–37 (one field move). - -### Finding 3 — Context still presents `objective_value` and `reduced_chi_square` as a duplicate pair - -**Verdict: partial — substantively resolved in reply 2.** - -Reply 2 rewrote the §Context table to add a fourth "Relationship" -column distinguishing real duplications from cross-category -misplacement. Current ADR state (lines 40–53): - -``` -| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | -| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | -| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | -| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | -``` - -The third row no longer claims `objective_value` and -`reduced_chi_square` are duplicates; it explicitly describes them as -two distinct scalars with one of them sitting in the wrong category. -The surrounding sentence at line 49–53 reads "fit-output content -split across `minimizer` and `fit_result` (whether the two scalars -per row are the same value or not)". §2 then resolves each row -explicitly. - -The cited line range (`:40-46`) lands inside the rewritten table; the -phrasing the reviewer attributes to those lines (a claim that all -three rows are duplicates) no longer exists in the file. No further -ADR change. - -**Plan section:** §Context table (lines 43–47), §"Positive" bullet -(lines 294–298, already correct). - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — §Context "Writable inputs" / "Fit-filled outputs" lists corrected - for the credible-interval fields (F2). -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-3.md`](minimizer-input-output-split_reply-3.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md deleted file mode 100644 index 3b0dc37d4..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md +++ /dev/null @@ -1,129 +0,0 @@ -# Reply to Review 4: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-4.md`](minimizer-input-output-split_review-4.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Context note on the review - -Review 4 records that it follows reply 1, but the three findings -match review 3 verbatim and reference the same pre-reply-2 text -(credible intervals "promoted" to user-writable settings; §Context -table presenting all three rows as duplicates). Reply 2 demoted the -credible intervals back to outputs, and reply 3 fixed the §Context -list and the §Context table. All three findings here describe text -that is no longer in the current ADR. - -No ADR edits are required for review 4. The current state matches the -reviewer's "or" branch in each case. Each finding is documented below -with line numbers verified by `grep` against the current file. - -## Findings - -### Finding 1 — Writable arbitrary credible interval levels - -**Verdict: confirmed addressed in reply 2 (no further change).** - -The reviewer's "or" branch reads: "Please either constrain these -settings to the fixed 0.68 / 0.95 values until generalized columns -are accepted, or make the generalized persistence shape part of this -ADR." Reply 2 took the first branch. Current ADR state (verified by -`grep -n credible_interval -docs/dev/adrs/suggestions/minimizer-input-output-split.md`): - -- Line 140: §2 paragraph reads: "`credible_interval_inner` and - `credible_interval_outer` **stay on the output side** in this ADR, - attached to `BayesianFitResult` (see below). They are persisted - with the fixed values `0.68` and `0.95`, matching the per-parameter - interval columns". -- Line 163: `BayesianFitResult` field list owns both fields. -- Lines 236–237: Bayesian CIF example shows - `_fit_result.credible_interval_inner 0.68` / - `_fit_result.credible_interval_outer 0.95` under `_fit_result.*`, - not `_minimizer.*`. -- §"Deferred Work" carries the combined level+naming follow-on, - explicitly saying doing one without the other is unsafe. - -The data-integrity hole the reviewer is concerned about cannot occur -under the current ADR — the levels are fixed at the values that match -the column names. The cited line range (`:138-153`) maps to text -saying the levels are fixed, not the prior text saying they are -user-writable. - -**Plan section:** §2 (lines 138–151), §3 Bayesian CIF example -(lines 236–237), §"Deferred Work". - -### Finding 2 — Context list still describes credible-interval levels as currently writable - -**Verdict: confirmed addressed in reply 3 (no further change).** - -Reply 3 moved `credible_interval_inner` and `credible_interval_outer` -from the "Writable inputs" bullet to the "Fit-filled outputs" bullet -in the §Context list. Current ADR state (verified by `grep -n -A 2 -"Writable inputs"`): - -- Line 28–31 "Writable inputs": `max_iterations` (LSQ); - `sampling_steps`, `burn_in_steps`, `thinning_interval`, - `population_size`, `parallel_workers`, `initialization_method`, - `random_seed` (Bayesian). -- Line 32–38 "Fit-filled outputs": …Bayesian list includes - `point_estimate_name`, `sampler_completed`, - `credible_interval_inner`, `credible_interval_outer`, - `acceptance_rate_mean`, …. - -The §Context list now matches the live code at -[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) -where the two fields appear in `_result_descriptor_names` and have -only internal `_set_*` methods. - -**Plan section:** §Context lists (lines 26–38). - -### Finding 3 — Context still presents `objective_value` and `reduced_chi_square` as a duplicate pair - -**Verdict: confirmed addressed in reply 2 (no further change).** - -Reply 2 rewrote the §Context table to add a "Relationship" column. -Current ADR state (verified by `sed -n '43,47p'`): - -``` -| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | -| -------------- | ----------------------------- | ------------------------------ | ------------ | -| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | -| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | -| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | -``` - -The third row no longer claims they are duplicates; it classifies the -relationship as cross-category misplacement of two distinct scalars. -The surrounding sentence at lines 49–53 reads "fit-output content -split across `minimizer` and `fit_result` (whether the two scalars -per row are the same value or not). §2 resolves each row above -explicitly." - -**Plan section:** §Context table (lines 43–47). - -## Process note - -This is the third consecutive review whose findings describe text -that has been edited away by an earlier reply. If the review process -is not picking up replies, the most useful next step is to confirm -each reply file is being read alongside the ADR — every reply lists -its own files-touched section with the exact paths, and reading them -in order resolves the apparent regression in each round. The ADR -text after reply 3 already takes the "or" branch the reviewer -recommends in every finding above; the next review can profitably -focus on issues not yet raised. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. No ADR edits were made in this round. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-4.md`](minimizer-input-output-split_reply-4.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md deleted file mode 100644 index f82aebc95..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md +++ /dev/null @@ -1,96 +0,0 @@ -# Reply to Review 5: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-5.md`](minimizer-input-output-split_review-5.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Context note on the review - -Review 5 records that `_reply-2.md` was not visible. Both findings -repeat issues that were resolved by reply 3 (F1, §Context list move -of credible intervals from "Writable inputs" to "Fit-filled outputs") -and reply 2 (F2, §Context table gaining the "Relationship" column). -Current line numbers verified below by `sed`/`grep`. No ADR edits are -required for review 5. - -## Findings - -### Finding 1 — Context still says credible intervals are currently writable - -**Verdict: confirmed addressed in reply 3 (no further change).** - -`sed -n '26,38p' docs/dev/adrs/suggestions/minimizer-input-output-split.md` -returns: - -``` -The current shape on the live category surfaces: - -- **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, - `burn_in_steps`, `thinning_interval`, `population_size`, - `parallel_workers`, `initialization_method`, `random_seed` - (Bayesian). -- **Fit-filled outputs (no public setter, only `_set_*` internals):** - `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, - `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, - `correlation_available`, `runtime_seconds`, `iterations_performed`, - `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, - `sampler_completed`, `credible_interval_inner`, - `credible_interval_outer`, `acceptance_rate_mean`, -``` - -`credible_interval_inner` and `credible_interval_outer` appear under -"Fit-filled outputs" (line 37–38), not under "Writable inputs" -(line 28–31). This matches the live code in -[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) -where the two fields appear in `_result_descriptor_names` and expose -only internal `_set_credible_interval_*` methods. - -**Plan section:** §Context lists, lines 26–38. - -### Finding 2 — Context still presents `objective_value` / `reduced_chi_square` as a duplicate pair - -**Verdict: confirmed addressed in reply 2 (no further change).** - -`sed -n '40,47p' docs/dev/adrs/suggestions/minimizer-input-output-split.md` -returns: - -``` -Three current output fields straddle `analysis.minimizer` and -`analysis.fit_result`: - -| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | -| -------------- | ----------------------------- | ------------------------------ | ------------ | -| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | -| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | -| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | -``` - -The third row classifies the χ² pair as "Cross-category misplacement -— two related but distinct scalars", not as a duplication. The -intro sentence reads "Three current output fields straddle …", not -"Three of the output fields already overlap" as in the original. - -**Plan section:** §Context table, lines 43–47. - -## Process note - -Both findings here describe text that no longer appears in the file -after reply 2 and reply 3. If the reviewer cannot see the reply -files, the easiest signal is that every reply file lists a "Summary -of files touched" section with the exact ADR sections and line -ranges it edited; reading the most recent `_reply-N.md` alongside the -ADR resolves the apparent regression. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. No ADR edits were made in this round. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md deleted file mode 100644 index 74f7e2178..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md +++ /dev/null @@ -1,59 +0,0 @@ -# Reply to Review 6: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-6.md`](minimizer-input-output-split_review-6.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Context note on the review - -Review 6 follows reply 2 (the reviewer can see that one), but reply 3 -appears to not be visible — the single finding here is exactly what -reply 3 fixed. Current ADR state verified below; no ADR edits required. - -## Findings - -### Finding 1 — Context still says credible interval levels are currently writable inputs - -**Verdict: confirmed addressed in reply 3 (no further change).** - -`sed -n '26,38p' docs/dev/adrs/suggestions/minimizer-input-output-split.md` -returns: - -``` -The current shape on the live category surfaces: - -- **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, - `burn_in_steps`, `thinning_interval`, `population_size`, - `parallel_workers`, `initialization_method`, `random_seed` - (Bayesian). -- **Fit-filled outputs (no public setter, only `_set_*` internals):** - `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, - `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, - `correlation_available`, `runtime_seconds`, `iterations_performed`, - `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, - `sampler_completed`, `credible_interval_inner`, - `credible_interval_outer`, `acceptance_rate_mean`, -``` - -`credible_interval_inner` and `credible_interval_outer` are in the -"Fit-filled outputs" bullet (Bayesian half, line 37–38), not in the -"Writable inputs" bullet (line 28–31). This matches the live code at -[`bayesian_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/bayesian_base.py) -(`_result_descriptor_names` tuple, internal `_set_credible_interval_*` -methods). - -**Plan section:** §Context, lines 26–38. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. No ADR edits were made in this round. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md deleted file mode 100644 index 960238591..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md +++ /dev/null @@ -1,48 +0,0 @@ -# Reply to Review 7: Minimizer Input/Output Split - -Reply to -[`minimizer-input-output-split_review-7.md`](minimizer-input-output-split_review-7.md) -for the ADR at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Verdict - -Review 7 records no findings. Acknowledged; the ADR is unchanged in -this round. - -The reviewer's summary confirms the four substantive properties of -the current ADR: - -- `analysis.minimizer` stays input-only. -- Scalar fit outputs move to the paired internal `analysis.fit_result` - projection. -- The selector-contract exception is documented (both in §1 of this - ADR and as an entry under "ADRs amended" against - `switchable-category-owned-selectors.md`). -- Credible interval levels stay on the output side at the fixed - `0.68` / `0.95` values, with the combined level+naming promotion - deferred to a follow-on ADR. -- The §Context table classifies the two real duplications (wall - time, iteration count) separately from the cross-category - misplacement of raw χ² vs reduced χ². - -## Next steps - -The ADR is ready to move from `docs/dev/adrs/suggestions/` to -`docs/dev/adrs/accepted/` whenever the maintainer decides to promote -it, at which point an implementation plan -(`docs/dev/plans/minimizer-input-output-split.md`) can be drafted -using the same slug. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. No ADR edits were made in this round. - -## Summary of files touched by this reply - -- [`docs/dev/adrs/suggestions/minimizer-input-output-split_reply-7.md`](minimizer-input-output-split_reply-7.md) - — this reply. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md deleted file mode 100644 index 5e0bb1550..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-1.md +++ /dev/null @@ -1,19 +0,0 @@ -# Review 1: Minimizer Input/Output Split - -## Findings - -1. **High — The ADR creates a switchable category that violates the accepted selector contract.** The proposal says `analysis.fit_result` "becomes a switchable category" that swaps with `analysis.minimizer` (`minimizer-input-output-split.md:74-77`, `:95-99`), but later says the new `fit_result` switchable does **not** expose its own `type` property (`:221-228`). That conflicts with the accepted switchable-category contract, which says every in-scope switchable category exposes `category.type` and `category.show_supported()` as the whole public selector surface, with one `_.type` CIF identity tag (`../accepted/switchable-category-owned-selectors.md:82-105`, `:124-149`, `:367-372`). The ADR does not list that accepted ADR under "ADRs amended by this ADR", so an implementer has no authoritative rule for whether `fit_result` is a normal switchable category, an internal paired projection factory, or a new exception to the global selector convention. Please either avoid calling it a switchable category and specify the internal pairing mechanism, or explicitly amend the selector ADR with a paired-output-category exception including the Python and CIF surface. - -2. **High — The `objective_value`/`reduced_chi_square` decision is internally inconsistent.** The context identifies `minimizer.objective_value` and `fit_result.reduced_chi_square` as the duplicated χ² concept (`minimizer-input-output-split.md:40-46`), and the decision says the split drops `minimizer.objective_value` while `fit_result.reduced_chi_square` becomes the single source, "or" `LeastSquaresFitResult.objective_value` remains if the raw un-normalised value matters (`:123-133`). But the field list and deterministic CIF example include both `reduced_chi_square` and `objective_value` on `fit_result` (`:113-118`, `:150-158`). That leaves the key persistence/API question undecided: if `objective_value` is a distinct raw objective, it is not a duplicate and should be documented as such; if it is the same χ² concept, it should be removed from `LeastSquaresFitResult` and the CIF example. As written, two implementers could make opposite choices and both claim to follow the ADR. - -3. **Medium — The ADR treats credible interval levels as writable minimizer settings, but the current implementation treats them as fit-filled projection fields.** The ADR lists `credible_interval_inner` and `credible_interval_outer` as current writable inputs and keeps them on settings-only `analysis.minimizer` after the split (`minimizer-input-output-split.md:28-31`, `:103-108`). In the live category, those properties have only internal `_set_credible_interval_*` methods and are included in `_result_descriptor_names`, while `_setting_descriptor_names` excludes them (`src/easydiffraction/analysis/categories/minimizer/bayesian_base.py:42-66`, `:347-366`). If the intent is to turn summary interval levels into user settings, the ADR should say so explicitly and describe how they affect posterior summary generation; otherwise they belong with outputs or another summary-configuration category. Without that decision, the "settings-only minimizer" boundary is still ambiguous for Bayesian summaries. - -4. **Medium — `analysis.show_fit_summary()` bypasses the accepted display facade.** The ADR adds a new public `analysis.show_fit_summary()` method that prints settings and outputs side-by-side (`minimizer-input-output-split.md:205-208`, `:254-257`). The accepted Display UX ADR moved user-facing fit reporting to `project.display.fit.results()` and moved `project.analysis.display.fit_results()` out of the primary public API (`../accepted/display-ux.md:42-46`, `:66-101`, `:190-201`). Adding a new analysis-level display method reintroduces the split that ADR removed. Please either place the summary under the accepted display facade, or list `display-ux.md` as amended and explain why fit summaries are an exception. - -5. **Medium — The fit-result pairing table only names two minimizer classes even though the active minimizer set is larger.** The ADR says the output split is "paired by minimizer family" but lists only `LmfitLeastsqMinimizer` and `BumpsDreamMinimizer` (`minimizer-input-output-split.md:88-93`). The current minimizer enum has nine tags, including several non-default least-squares classes (`src/easydiffraction/analysis/minimizers/enums.py:13-21`). The ADR should state whether every least-squares minimizer maps to `LeastSquaresFitResult` and every Bayesian minimizer maps to `BayesianFitResult`, or provide an explicit per-tag mapping. Otherwise the swap/load behavior for non-default deterministic minimizers is underspecified. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md deleted file mode 100644 index 257c188f1..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-2.md +++ /dev/null @@ -1,21 +0,0 @@ -# Review 2: Minimizer Input/Output Split - -Context: no `minimizer-input-output-split_reply-1.md` file was present. -This review covers the direct edits made to -`minimizer-input-output-split.md` after review 1. - -## Findings - -1. **High — The `fit_result` selector exception is still internally inconsistent and not actually wired into the amended-ADR list.** The updated text says `fit_result` is not a user-facing switchable category, and claims this is an explicit exception to `switchable-category-owned-selectors.md` with new exception text added there and listed under "ADRs amended" (`minimizer-input-output-split.md:88-99`). But the same ADR still calls it "the new `fit_result` switchable" and says "`fit_result` becomes a switchable category" in later sections (`:269-278`, `:302-311`, `:342-345`), while the "ADRs amended" list still omits `switchable-category-owned-selectors.md` (`:320-333`). That leaves the core architectural contract unresolved: is `fit_result` a switchable category with an exception, or an internal paired projection that should not use switchable terminology? Please make the terminology consistent and either add the actual selector-ADR amendment/list entry or remove the claim that it has been amended. - -2. **High — Configurable credible interval levels make the persisted `_fit_parameter` interval columns misleading.** The update promotes `credible_interval_inner` and `credible_interval_outer` to user-writable settings, but keeps the persisted per-parameter columns named `posterior_interval_68_*` and `posterior_interval_95_*` even when the user chooses different levels; the ADR only says to warn at fit time (`minimizer-input-output-split.md:138-153`). That means a saved CIF can contain, for example, a 50% interval in a column named `posterior_interval_68_low`, which is a data-integrity problem rather than just a UX warning. Please either constrain those settings to the two fixed levels until generalized column names are designed, or include the generalized persistence shape in this ADR so the saved column names match the values they contain. - -3. **Medium — `objective_value` and `reduced_chi_square` are still described as duplicates after the ADR now says they are distinct fields.** The decision now says `LeastSquaresFitResult.objective_value` is raw χ² and `FitResultBase.reduced_chi_square` is χ² divided by degrees of freedom, and explicitly says they are not duplicates (`minimizer-input-output-split.md:175-183`). However the context still lists `minimizer.objective_value` vs `fit_result.reduced_chi_square` as the overlapping χ² concept (`:40-46`), and the consequences still say the `objective_value`/`reduced_chi_square` duplication collapses to one location (`:284-289`). Please revise the context/consequences so the duplication being fixed is the old cross-category placement, not a claim that raw objective and reduced χ² are the same scalar. - -4. **Medium — `analysis.show_fit_summary()` still bypasses the accepted display facade.** The ADR still adds an analysis-level display method and uses it as the mitigation for two-place reads (`minimizer-input-output-split.md:255-258`, `:304-307`). The accepted Display UX ADR makes `project.display` the user-facing display facade and puts fit reporting under `project.display.fit.results()` (`../accepted/display-ux.md:42-46`, `:66-101`, `:190-201`), and this ADR still does not list `display-ux.md` as amended (`minimizer-input-output-split.md:320-333`). Please move this summary to the accepted display facade or explicitly amend the display ADR. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md deleted file mode 100644 index 5f974e77b..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-3.md +++ /dev/null @@ -1,19 +0,0 @@ -# Review 3: Minimizer Input/Output Split - -Context: no `minimizer-input-output-split_reply-2.md` file was present. -This review covers the direct edits made to -`minimizer-input-output-split.md` after review 2. - -## Findings - -1. **High — Writable arbitrary credible interval levels still make the persisted `_fit_parameter` columns misleading.** The ADR promotes `credible_interval_inner` and `credible_interval_outer` to user-writable settings, then says the existing fixed `posterior_interval_68_*` / `posterior_interval_95_*` column names remain even when the user picks different levels (`minimizer-input-output-split.md:138-153`). The deferred-work section explicitly postpones the column-naming generalization (`:369-375`). That means this ADR would allow saving a 50% interval in a column named `posterior_interval_68_low`, which is a persisted data-integrity problem. A fit-time warning does not fix the saved schema. Please either constrain these settings to the fixed 0.68 / 0.95 values until generalized column names are accepted, or make the generalized persistence shape part of this ADR. - -2. **Medium — The context and decision now disagree on whether credible interval levels are currently writable.** The context still lists `credible_interval_inner` and `credible_interval_outer` under the current live "Writable inputs" (`minimizer-input-output-split.md:26-32`), but the new decision text says they are "promoted from output-only to writable input" and today have only internal `_set_*` methods (`:138-142`). The latter matches the live code. Please update the context list so it does not describe the current API as already writable. - -3. **Medium — The context still presents `objective_value` and `reduced_chi_square` as a duplicate pair after the decision says they are distinct.** The context still says three output fields overlap and lists the χ² row as `minimizer.objective_value` vs `fit_result.reduced_chi_square` (`minimizer-input-output-split.md:40-46`). The decision and consequences now say raw `objective_value` and reduced χ² are not duplicates and both stay (`:175-183`, `:294-298`). Please revise the context so it separates the two real duplicates from the raw-objective-vs-reduced-χ² distinction. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md deleted file mode 100644 index 93340106a..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-4.md +++ /dev/null @@ -1,19 +0,0 @@ -# Review 4: Minimizer Input/Output Split - -Context: this review follows -`minimizer-input-output-split_reply-1.md`. The reply addresses review 1, -but the current ADR still has the issues below. - -## Findings - -1. **High — Writable arbitrary credible interval levels still make the persisted `_fit_parameter` columns misleading.** The ADR promotes `credible_interval_inner` and `credible_interval_outer` to user-writable settings, then keeps the fixed `posterior_interval_68_*` / `posterior_interval_95_*` column names even when users choose different levels (`minimizer-input-output-split.md:138-153`). The deferred-work section explicitly postpones the column-name generalization (`:369-375`). This would allow saving, for example, a 50% interval in a column named `posterior_interval_68_low`. A fit-time warning does not make the persisted schema truthful. Please either constrain these settings to the fixed 0.68 / 0.95 values until generalized columns are accepted, or make the generalized persistence shape part of this ADR. - -2. **Medium — The context and decision disagree on whether credible interval levels are currently writable.** The context still lists `credible_interval_inner` and `credible_interval_outer` under the current live "Writable inputs" (`minimizer-input-output-split.md:26-32`), but the decision says they are "promoted from output-only to writable input" and today have only internal `_set_*` methods (`:138-142`). The latter matches the live code. Please update the context list so it does not describe the current API as already writable. - -3. **Medium — The context still presents `objective_value` and `reduced_chi_square` as a duplicate pair after the decision says they are distinct.** The context still says three output fields overlap and lists the χ² row as `minimizer.objective_value` vs `fit_result.reduced_chi_square` (`minimizer-input-output-split.md:40-46`). The decision and consequences now say raw `objective_value` and reduced χ² are not duplicates and both stay (`:175-183`, `:294-298`). Please revise the context so it separates the two real duplications from the raw-objective-vs-reduced-χ² distinction. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md deleted file mode 100644 index b0fa6554b..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-5.md +++ /dev/null @@ -1,16 +0,0 @@ -# Review 5: Minimizer Input/Output Split - -Context: no `minimizer-input-output-split_reply-2.md` file was present. -This review covers the direct edits made after review 4. - -## Findings - -1. **Medium — The context still says credible interval levels are currently writable inputs.** The decision now keeps `credible_interval_inner` and `credible_interval_outer` on the output side as fixed `BayesianFitResult` fields (`minimizer-input-output-split.md:138-149`, `:160-163`), which resolves the persistence risk from review 4. But the context still lists those two fields under the current live "Writable inputs" (`:26-32`). Please move them out of that current-writable list so the context matches the decision and live code. - -2. **Medium — The context still presents `objective_value` and `reduced_chi_square` as a duplicate pair after the decision says they are distinct.** The context still says three output fields overlap and lists the χ² row as `minimizer.objective_value` vs `fit_result.reduced_chi_square` (`minimizer-input-output-split.md:40-46`). The decision and consequences now say raw `objective_value` and reduced χ² are not duplicates and both stay (`:172-180`, `:291-298`). Please revise the context so it separates the two real duplications from the raw-objective-vs-reduced-χ² distinction. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md deleted file mode 100644 index de3180b6d..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-6.md +++ /dev/null @@ -1,14 +0,0 @@ -# Review 6: Minimizer Input/Output Split - -Context: this review follows -`minimizer-input-output-split_reply-2.md`. - -## Findings - -1. **Medium — The context still says credible interval levels are currently writable inputs.** The decision now keeps `credible_interval_inner` and `credible_interval_outer` on the output side as fixed `BayesianFitResult` fields (`minimizer-input-output-split.md:140-151`, `:162-165`), and the deferred-work section explicitly treats user-configurable interval levels as future work (`:368-375`). However the context still lists those two fields under the current live "Writable inputs" (`:26-32`). Please remove them from that current-writable list, or move them to the fit-filled-output list, so the motivation matches the decision and live code. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md b/docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md deleted file mode 100644 index ac35f439a..000000000 --- a/docs/dev/adrs/suggestions/minimizer-input-output-split_review-7.md +++ /dev/null @@ -1,21 +0,0 @@ -# Review 7: Minimizer Input/Output Split - -Context: this review follows -`minimizer-input-output-split_reply-3.md`. - -## Findings - -No issues found in the current ADR. - -The current text consistently keeps `analysis.minimizer` input-only, -moves scalar outputs to the paired internal `analysis.fit_result` -projection, documents the selector-contract exception, keeps fixed -credible interval levels on the output side, and separates the two real -duplicate scalar pairs from the raw-objective-vs-reduced-chi-square -distinction. - -## Checks - -Skipped by instruction: this is a static ADR review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 2a79048fe..907f5d435 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -517,7 +517,7 @@ Mark `[x]` as each step lands. Commit: `Update tutorials to read outputs from fit_result` -- [ ] **P1.16 — Promote ADR + update index.** +- [x] **P1.16 — Promote ADR + update index.** - `git mv docs/dev/adrs/suggestions/minimizer-input-output-split.md docs/dev/adrs/accepted/minimizer-input-output-split.md`. Flip the Status header to `Accepted`. From 46f1eba7adb68ce7179fda1c4f94ad52e5d0dd99 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 15:18:56 +0200 Subject: [PATCH 30/38] Complete Phase 1 minimizer split review gate --- docs/dev/plans/minimizer-input-output-split.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 907f5d435..9a626026c 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -532,7 +532,7 @@ Mark `[x]` as each step lands. Commit: `Promote minimizer-input-output-split ADR` -- [ ] **P1.17 — Phase 1 review gate.** No code change. Re-run the +- [x] **P1.17 — Phase 1 review gate.** No code change. Re-run the P1.15 tutorial grep against `src/`, `docs/docs/tutorials/`, and `tests/`. The `src/` and `docs/docs/tutorials/` scopes must return empty. The `tests/` sweep is deferred to P2.1, which From 60788b2201ac23c350da99ea06325b4b7213bdc1 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 15:22:09 +0200 Subject: [PATCH 31/38] Propose IUCr CIF tag alignment for fit outputs --- .../suggestions/iucr-cif-tag-alignment.md | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md diff --git a/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md b/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md new file mode 100644 index 000000000..095b5e281 --- /dev/null +++ b/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md @@ -0,0 +1,188 @@ +# ADR: IUCr CIF Tag Alignment for Fit Outputs + +**Status:** Proposed **Date:** 2026-05-24 + +## Status Note + +This suggestion captures research done during the +[`minimizer-input-output-split`](accepted/minimizer-input-output-split.md) +work. That ADR's `_fit_result.*` CIF prefix is functional but +diverges from the IUCr core and powder dictionaries. This proposal +records the divergence and a plausible alignment path so it is not +lost; the work is **not** in scope for the input/output-split PR. + +## Context + +After the input/output split, fit outputs persist under +`_fit_result.*`. The IUCr maintains two relevant dictionaries that +already cover much of the same ground: + +- [`COMCIFS/cif_core`](https://github.com/COMCIFS/cif_core) — core + CIF dictionary, refinement parameters under `_refine_ls.*` (51 + items) and aggregate reflection statistics under `_reflns.*`. +- [`COMCIFS/Powder_Dictionary`](https://github.com/COMCIFS/Powder_Dictionary) + (`cif_pow.dic`) — powder-specific refinement under + `_pd_proc_ls.*` (9 items). + +Cross-reference today: + +| Concept | Our `_fit_result.*` | IUCr core (`_refine_ls.*`) | IUCr powder (`_pd_proc_ls.*`) | +| --- | --- | --- | --- | +| Reduced χ² / GoF | `reduced_chi_square` | `goodness_of_fit_all` (S, plus `_su`) | derived from `prof_wR_factor`/`prof_wR_expected` | +| R-factor unweighted | — | `r_factor_all`, `r_factor_gt` | `prof_R_factor` | +| R-factor weighted | — | `wr_factor_all`, `wr_factor_gt`, `wr_factor_ref` | `prof_wR_factor` | +| R-expected | — | (derived from S and counts) | `prof_wR_expected` | +| Number of data points | `n_data_points` | `number_reflns`, `number_reflns_gt` | derive from `_pd_proc.number_of_points` | +| Number of parameters | `n_parameters` | `number_parameters` | (in `_refine_ls.*`) | +| Number of restraints | — | `number_restraints` | same | +| Number of constraints | — | `number_constraints` | same | +| Shift / σ | — | `shift_over_su_max`, `shift_over_su_mean` | same | +| Profile function | — | — | `profile_function` | +| Background function | — | — | `background_function` | +| Wall time | `fitting_time` | (none) | (none) | +| Iteration count | `iterations` | (none) | (none) | +| Success flag, message | `success`, `message` | (none) | (none) | +| Bayesian R̂, ESS, accept | various | (none) | (none) | + +Two consequences: + +1. **Real gaps.** Our serialization omits R-factors, + restraint/constraint counts, shift/σ diagnostics, and powder + profile/background function names. These are fields that + crystallographers expect in a saved CIF. +2. **Naming divergence.** Where IUCr does have a tag, we use a + different prefix (`_fit_result.*` vs `_refine_ls.*` / + `_pd_proc_ls.*`). Our CIFs are valid but cannot be consumed by + external tools that expect IUCr-standard names. + +## Decision + +### 1. Use IUCr tag names where they exist + +For every `_fit_result.*` field that has a one-to-one IUCr +counterpart, emit the IUCr tag instead. The Python attribute name +stays (`analysis.fit_result.reduced_chi_square`); only the +`cif_handler` `names` tuple changes. Examples: + +- `fit_result.reduced_chi_square` → emits + `_refine_ls.goodness_of_fit_all` for single-crystal, + `_pd_proc_ls.prof_wR_factor` + `_pd_proc_ls.prof_wR_expected` for + powder (or both, with the GoF derived on the powder side). +- `fit_result.n_data_points` → `_refine_ls.number_reflns` + (single-crystal), `_pd_proc.number_of_points` (powder). +- `fit_result.n_parameters` → `_refine_ls.number_parameters`. + +The emitted prefix becomes shape-shifting based on +`experiment.type.scattering_type` and `experiment.type.sample_form` +— the same convention powder packages use today. The Python API +remains uniform. + +### 2. Add the missing IUCr fields to `fit_result` + +Promote the following from "gap" to "available" on the existing +`LeastSquaresFitResult`: + +- `r_factor_all`, `wr_factor_all` — emitted as the standard tags; + computed by the LSQ projection writer from residuals. +- `prof_r_factor`, `prof_wr_factor`, `prof_wr_expected` — + powder-specific variants, computed the same way against the + profile data. +- `number_restraints`, `number_constraints` — current count from + the analysis model. +- `shift_over_su_max`, `shift_over_su_mean` — last-iteration + convergence diagnostic. +- `profile_function`, `background_function` (powder) — string-form + descriptions of the active peak and background categories. + +### 3. Keep our own prefix for fields IUCr does not cover + +`fitting_time`, `iterations`, `success`, `message`, every Bayesian +diagnostic (`gelman_rubin_max`, `acceptance_rate_mean`, etc.), and +the `result_kind`/`point_estimate_name` markers have no IUCr home +today. They stay under a project-specific prefix. Two options for +that prefix: + +- Keep `_fit_result.*` for the non-IUCr fields only. The CIF then + mixes prefixes (`_refine_ls.*` + `_fit_result.*`) which is + unusual but legal. +- Use a clearly-namespaced extension like `_easydiffraction.*` or + `_eddict_fit.*` so external tools recognise the fields as + non-IUCr. + +Decide during implementation; the second option is friendlier to +external CIF readers. + +### 4. Bayesian Rietveld output has no IUCr precedent today + +There is no IUCr convention for sampler convergence (R̂, ESS, +acceptance) or posterior diagnostics. This proposal does not invent +one. The Bayesian-specific fields stay under the project prefix +chosen in §3. If a community standard emerges, a follow-on ADR can +absorb it. + +## Consequences + +### Positive + +- Saved CIFs become consumable by standard IUCr tools (publCIF, + checkCIF, journal submission pipelines). +- The "what is the R-factor of this fit?" question has an answer in + the saved file — currently we only record reduced χ². +- Powder users get the conventional Rp / Rwp / Rexp triplet. +- We pick up restraint/constraint accounting that the structure + side of the project already tracks but does not currently emit. + +### Trade-offs + +- Shape-shifting CIF prefix per experiment family is more work to + implement than a single `_fit_result.*` prefix. Roughly: one + `cif_handler` per descriptor that maps to a different IUCr tag + depending on context; the Python API stays uniform. +- Bayesian fields remain non-standard. There is no way around that + until IUCr defines tags for Bayesian Rietveld. +- Existing saved projects from the post-split layout cannot load + unchanged. Beta posture applies; one more legacy-rename pass in + tutorial `ed-24` covers it. + +### ADRs amended by this ADR + +- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) + — replace the `_fit_result.*` projection description with the + IUCr-aligned tag set; document the per-experiment-family + shape-shifting. +- [`minimizer-input-output-split.md`](../accepted/minimizer-input-output-split.md) + — the `_fit_result.*` examples in §3 are updated to use IUCr tags + for the covered fields; the non-IUCr fields keep the + project-prefix examples. + +## Deferred Work + +- The exact prefix for non-IUCr fields (§3 option choice). +- IUCr-Bayesian alignment if a community standard appears. +- Single-crystal `r_factor_gt` / `wr_factor_gt` (greater-than-σ + subsets) need a "threshold expression" decision. The + `_reflns.threshold_expression` field already covers it on the + reflection side; the LSQ projection writer needs to know the + threshold to compute the `_gt` variants. +- Whether to also emit `_refine.special_details` for human-readable + fit notes. + +## Alternatives Considered + +### A. Keep `_fit_result.*` as-is + +Simplest. Saved CIFs stay self-contained but are not IUCr-portable. +Defensible if the project never targets external CIF interop. + +### B. Emit both prefixes for the covered fields + +`_fit_result.reduced_chi_square` and +`_refine_ls.goodness_of_fit_all` both present, holding the same +value. Belt-and-braces, doubles the surface area of every saved +file, and the two values can drift. + +### C. Adopt IUCr tags only for tags we already need + +Add the R-factor fields (§2) under our own `_fit_result.*` prefix. +Smaller diff, fixes the missing-field gap but not the naming +divergence. Pick if IUCr interop is genuinely not a goal. From 6e24d62c82f5bc49fa498ee79dd05b4e965a0278 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 15:27:43 +0200 Subject: [PATCH 32/38] Add review 4 of input/output split post-Phase 1 --- .../minimizer-input-output-split_review-4.md | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 docs/dev/plans/minimizer-input-output-split_review-4.md diff --git a/docs/dev/plans/minimizer-input-output-split_review-4.md b/docs/dev/plans/minimizer-input-output-split_review-4.md new file mode 100644 index 000000000..4306ad139 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-4.md @@ -0,0 +1,262 @@ +# Review 4: Minimizer Input/Output Split Branch (Post-Phase 1) + +Reviewed plan: +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) + +Reviewed ADR: +[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) + +This review follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, +or any build/verification command was executed**. This is a static +read of the 31 commits on `minimizer-input-output-split` against +`origin/develop`, plus the new accepted ADR and amended ADRs. + +## Scope + +The branch carries: + +- Phase 1 of the input/output split plan (17 commits, P1.1–P1.17 + + ADR promotion + an unrelated dependency cleanup). +- The accepted ADR + [`minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) + (promoted from suggestions at P1.16). +- A new suggestion ADR + [`iucr-cif-tag-alignment.md`](../adrs/suggestions/iucr-cif-tag-alignment.md) + capturing a separate IUCr-alignment proposal for future work. + +Phase 2 (P2.1–P2.6: test migration, fix, check, unit/integration/ +script tests) has not started yet; this review confirms the gate. + +## Summary + +Phase 1 implementation matches the plan. Each plan step maps 1:1 to +a commit. The new `fit_result` family classes are in place +([base.py](src/easydiffraction/analysis/categories/fit_result/base.py), +[lsq.py](src/easydiffraction/analysis/categories/fit_result/lsq.py), +[bayesian.py](src/easydiffraction/analysis/categories/fit_result/bayesian.py)); +`Analysis._swap_minimizer` wires the paired instance atomically; +`_clear_persisted_fit_state` reinstates the paired class on reset; +the LSQ and Bayesian minimizer bases no longer carry their output +descriptors; CIF serialisation routes outputs to `_fit_result.*` and +rejects the legacy `_minimizer.` tags loudly; the five +affected ADRs are amended; the display facade now prints a "Settings +used" block above the existing fit-result tables; tutorials are +migrated. + +All stale-reference greps return clean: + +- `analysis.minimizer.` in `src/`, `docs/docs/tutorials/`, + `tests/` — empty. +- `_minimizer.` in `src/`, `docs/docs/tutorials/`, + `tests/` — only present inside `serialize.py` as the legacy-tag + rejection list (intentional). + +Five small observations follow. None block Phase 2; F1 and F2 are +worth deciding consciously rather than letting them carry forward. + +## Findings + +### F1 — `FitResultBase` common-header defaults diverge from the family-class pattern + +`FitResultBase` +([base.py:42-71](src/easydiffraction/analysis/categories/fit_result/base.py:42)) +keeps the legacy common-class defaults: + +- `success` → `default=False` +- `message` → `default=''` +- `iterations` → `default=0` + +The new family classes +([lsq.py:80-142](src/easydiffraction/analysis/categories/fit_result/lsq.py:80) +and parts of +[bayesian.py](src/easydiffraction/analysis/categories/fit_result/bayesian.py)) +deliberately use `default=None, allow_none=True` for every numeric, +string, and bool result field — with explicit docstrings explaining +why: a CIF written before any fit should emit `?` rather than `0` / +`false` / empty-string, because the scientist audience reads `0` +and `false` as a real fit result. + +Pre-fit CIFs under the current layout therefore emit: + +``` +_fit_result.success false # reads as "fit ran and failed" +_fit_result.iterations 0 # reads as "fit ran for 0 iterations" +``` + +The plan inherited these defaults from the pre-split common class +(see P1.1 — "keep current common fields") so this is not a +regression introduced by this PR. But the divergence is now visible +side-by-side in the same category hierarchy: hovering on +`fit_result.iterations` lands in `FitResultBase` (defaults to `0`), +hovering on `fit_result.n_parameters` lands in `LeastSquaresFitResult` +(defaults to `None`), with no principled reason for the difference. + +Bayesian-side mirror: `point_estimate_name` defaults to +`'best_sample'` and `sampler_completed` defaults to `False` +([bayesian.py:51-69](src/easydiffraction/analysis/categories/fit_result/bayesian.py:51)), +while `acceptance_rate_mean` and friends default to `None`. Same +inconsistency. + +Suggested follow-up: align `FitResultBase` common fields (and the +Bayesian `point_estimate_name` / `sampler_completed`) with the +`None`/`allow_none=True` convention. Small diff; the existing +`_set_*` callers already pass real values when a fit runs. Defer to +a follow-on if not in scope for this PR. + +### F2 — `_clear_persisted_fit_state` resets descriptors then immediately replaces the instance + +[analysis.py:1207-1216](src/easydiffraction/analysis/analysis.py:1207): + +```python +def _clear_persisted_fit_state(self) -> None: + self._clear_fit_result_projection() # resets descriptors + self._fit_parameters = FitParameters() + self._fit_result._parent = None + self._fit_result = self.minimizer._fit_result_class() # replaces instance + self._fit_result._parent = self + self._fit_parameter_correlations = FitParameterCorrelations() + ... +``` + +`_clear_fit_result_projection()` walks +`_result_descriptor_names` and resets each to its declared default +on the **old** instance, which is then thrown away on the next +line. A fresh instance has defaults by construction; the reset call +is dead work. + +Two clean shapes are possible: + +- Drop the `_clear_fit_result_projection()` call and rely on the + fresh-instance construction. Simpler. +- Drop the instance replacement and rely on + `_clear_fit_result_projection()`. Then the paired-class invariant + is preserved only as long as `minimizer.type` does not change + between fits, which is true today but is the kind of invariant a + future refactor could quietly break. + +The first is the smaller diff. Cosmetic; not a correctness issue +because both paths produce the same end state. + +### F3 — `_settings_used_rows` carries an unreachable `else` branch + +[display.py:117-129](src/easydiffraction/project/display.py:117): + +```python +def _settings_used_rows(self) -> list[list[str]]: + minimizer = self._project.analysis.minimizer + rows: list[list[str]] = [] + for name in minimizer._setting_descriptor_names: + descriptor = getattr(minimizer, name) + if isinstance(descriptor, GenericDescriptorBase): + rows.append([...]) + else: + rows.append([name, str(descriptor), '']) + return rows +``` + +Every entry in `_setting_descriptor_names` resolves to a +`GenericDescriptorBase` subclass (verified by reading +[`lsq_base.py`](src/easydiffraction/analysis/categories/minimizer/lsq_base.py) +and +[`bayesian_base.py`](src/easydiffraction/analysis/categories/minimizer/bayesian_base.py)). +The `else` branch is defensive coding against an unreachable state. +Per +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) +→ **Change Discipline** "No defensive checks for unlikely edge +cases." + +Suggested follow-up: drop the `else` branch and the +`isinstance(descriptor, GenericDescriptorBase)` guard. Optional — +this is one screen of code and the fallback is harmless. + +### F4 — Phase 2 not started; one test file expected to break + +`grep -rlE 'minimizer\.' tests/` returns one hit: + +- [`test_lsq_base.py`](tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py) + still asserts on `minimizer.objective_name`, `minimizer.objective_value`, + `minimizer.iterations_performed`, etc. — fields that no longer + exist on the minimizer hierarchy after P1.9. + +This is exactly what P2.1 ("Migrate existing tests off the removed +minimizer output fields") covers, and the plan explicitly defers +test migration to Phase 2. Confirming the gate: Phase 2 must run +before the branch is merge-ready. The migration table in P1.15 / +P2.1 is the right reference for the rewrites. + +Informational; the gate matches the plan's Phase 1 → Phase 2 split. + +### F5 — Unrelated dependency cleanup landed alongside Phase 1 + +Commit `c5da0b0fc Remove essdiffraction dependency` touches +[`pixi.lock`](pixi.lock) (-1124 lines), [`pyproject.toml`](pyproject.toml) +(-1 line), and +[`scipp-analysis/dream/test_package_import.py`](tests/integration/scipp-analysis/dream/test_package_import.py) +(-14 lines). It is unrelated to the input/output split work. + +Per +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) +→ **Change Discipline** "Don't add features or refactor unless +asked" and the project's "one focused PR" convention, this would +normally land on its own. Bundling it here makes the PR description +slightly less accurate (the user-facing surface is unchanged by the +essdiffraction removal). If the next reviewer flags it, the cleanest +response is to split it out as a separate PR; otherwise it is small +enough to keep. + +Informational only. + +## Documentation + +- Five accepted ADRs amended as listed in the plan §"ADR": + `minimizer-category-consolidation.md`, `analysis-cif-fit-state.md`, + `runtime-fit-results.md`, `switchable-category-owned-selectors.md`, + `display-ux.md`. +- `docs/dev/adrs/index.md` lists the new ADR under Accepted. +- The plan's `_review-N` / `_reply-N` siblings remain alongside the + plan (per the project's plan-vs-ADR retention precedent — plans + keep their deliberation files until the plan itself is deleted on + merge). +- The IUCr alignment idea is parked as + [`iucr-cif-tag-alignment.md`](../adrs/suggestions/iucr-cif-tag-alignment.md) + under suggestions, with a §Status Note explaining it is captured + for future work, not in scope for the current PR. +- Tutorials regenerated per the P1.15 migration table. + +## Verification commands run for this review + +No `pixi run`, no lint, no test. Static reads only: + +```text +git log --oneline origin/develop..HEAD # 31 commits +git diff origin/develop...HEAD --stat # 33 files +git grep -nE 'analysis\.minimizer\.(runtime_seconds|iterations_performed|objective_value|objective_name|n_data_points|n_parameters|n_free_parameters|degrees_of_freedom|covariance_available|correlation_available|exit_reason|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ +git grep -nE '_minimizer\.(runtime_seconds|iterations_performed|objective_value|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ +grep -rlE 'minimizer\.' tests/ +``` + +The first two return empty against `src/`, `docs/docs/tutorials/`, +and `tests/`. The third returns only `serialize.py` (the +legacy-tag rejection list — intentional). The fourth returns one +file (the Phase 2 migration target). + +## Recommended next steps + +1. **Decide F1 (`FitResultBase` defaults).** Either align the common + header with the family-class None-defaults convention, or + document the divergence as intentional in the accepted ADR and + move on. +2. **Address F2 (redundant reset) and F3 (unreachable else).** Both + are one-line cleanups; either bundle into Phase 2 cleanup or + leave for a follow-on. +3. **Start Phase 2.** P2.1 migrates the one stranded test file; + P2.2 adds unit tests for the new fit_result classes (none exist + yet under `tests/unit/easydiffraction/analysis/categories/fit_result/`); + P2.3–P2.6 run `pixi run fix`, `check`, and the three test + suites. +4. **Optional: split out `c5da0b0fc` into its own PR** for the + cleanest history; otherwise note it in the PR description so a + reviewer is not surprised by the `pixi.lock` churn. From 9878c9f582cc658bce20bc77cdd344d65adef064 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 16:16:26 +0200 Subject: [PATCH 33/38] Reply to minimizer input-output review 4 --- .../minimizer-input-output-split_reply-4.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-4.md diff --git a/docs/dev/plans/minimizer-input-output-split_reply-4.md b/docs/dev/plans/minimizer-input-output-split_reply-4.md new file mode 100644 index 000000000..6be37080f --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-4.md @@ -0,0 +1,99 @@ +# Reply to Review 4: Minimizer Input/Output Split Branch + +Reply to +[`minimizer-input-output-split_review-4.md`](minimizer-input-output-split_review-4.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Context: Review 4 is a post-Phase-1 static review. Phase 2 has not +started. The review confirms the Phase 1 gate and raises five findings, +none marked as a blocker. + +## Findings + +### F1 — `FitResultBase` common-header defaults diverge from family classes + +**Verdict: agree.** The split makes the old common-header defaults more +visible: `success=False`, `message=''`, and `iterations=0` now sit next +to family-specific result descriptors that deliberately use +`default=None, allow_none=True`. The same mismatch exists for Bayesian +`point_estimate_name='best_sample'` and `sampler_completed=False`. + +**Decision.** Address during Phase 2 cleanup before adding the new +`fit_result` unit tests. Align the common result header and Bayesian +result-only fields with the `None`/`allow_none=True` convention so +pre-fit CIF output keeps using `?` rather than values that look like a +completed or failed fit. + +### F2 — `_clear_persisted_fit_state` resets descriptors before replacing the instance + +**Verdict: agree.** Calling `_clear_fit_result_projection()` on the old +instance immediately before replacing `self._fit_result` is dead work. + +**Decision.** Address during Phase 2 cleanup. Keep the fresh-instance +shape and drop the redundant reset call; that preserves the paired-class +invariant and removes the wasted descriptor walk. + +### F3 — `_settings_used_rows` carries an unreachable fallback branch + +**Verdict: agree.** `_setting_descriptor_names` is a controlled +class-level declaration and each name should resolve to a descriptor. +The fallback branch is defensive code for an invalid internal +declaration. + +**Decision.** Address during Phase 2 cleanup. Drop the fallback and let +an invalid `_setting_descriptor_names` entry fail loudly. + +### F4 — Phase 2 not started; one test file expected to break + +**Verdict: agree.** This is exactly the expected Phase 1 to Phase 2 +gate. `test_lsq_base.py` still targets result fields that moved from +`analysis.minimizer` to `analysis.fit_result`. + +**Decision.** No extra plan change needed. P2.1 remains the migration +step for the stranded tests, and P2.2 adds the missing fit-result unit +test coverage. + +### F5 — Unrelated dependency cleanup landed alongside Phase 1 + +**Verdict: agree that it is unrelated to the input/output split ADR.** +The `essdiffraction` removal was a direct CI dependency cleanup request, +not part of this design change. + +**Decision.** Keep it in the branch unless the PR owner wants a cleaner +history split. If it remains bundled, call it out explicitly in the PR +description so reviewers understand the `pixi.lock` churn is a separate +CI/dependency cleanup. + +## Phase 2 Carry-Forward + +Before running the normal Phase 2 verification sequence, include a small +cleanup pass for F1 through F3: + +1. Align pre-fit result defaults to `None` where result fields are not + known yet. +2. Remove the redundant old-instance fit-result reset before replacing + `self._fit_result`. +3. Remove the unreachable settings-table fallback branch. + +Then proceed with the existing Phase 2 checklist: + +- P2.1 test migration. +- P2.2 new `fit_result` unit tests. +- P2.3 `pixi run fix` and `pixi run check`. +- P2.4 `pixi run unit-tests`. +- P2.5 `pixi run integration-tests`. +- P2.6 `pixi run script-tests`. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, or +test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/plans/minimizer-input-output-split_reply-4.md`](minimizer-input-output-split_reply-4.md) + — this reply. From a2857d9d60c62e3134d7256c0239c1dc87b71fa8 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 16:16:56 +0200 Subject: [PATCH 34/38] Complete minimizer input-output Phase 2 --- .../adrs/accepted/analysis-cif-fit-state.md | 5 +- .../minimizer-category-consolidation.md | 10 +- .../accepted/minimizer-input-output-split.md | 217 +++++----- docs/dev/adrs/accepted/runtime-fit-results.md | 6 +- .../switchable-category-owned-selectors.md | 4 +- docs/dev/adrs/index.md | 2 +- .../suggestions/iucr-cif-tag-alignment.md | 164 ++++---- docs/dev/package-structure/full.md | 11 +- docs/dev/package-structure/short.md | 5 +- .../dev/plans/minimizer-input-output-split.md | 377 +++++++++--------- .../minimizer-input-output-split_reply-1.md | 92 +++-- .../minimizer-input-output-split_reply-2.md | 88 ++-- .../minimizer-input-output-split_reply-3.md | 59 ++- .../minimizer-input-output-split_review-1.md | 67 +++- .../minimizer-input-output-split_review-2.md | 63 ++- .../minimizer-input-output-split_review-4.md | 176 ++++---- src/easydiffraction/analysis/analysis.py | 93 +++-- .../analysis/calculators/crysfml.py | 40 +- .../analysis/calculators/pdffit.py | 14 +- .../analysis/categories/fit_result/base.py | 12 +- .../categories/fit_result/bayesian.py | 8 +- .../analysis/categories/minimizer/lsq_base.py | 4 +- src/easydiffraction/analysis/sequential.py | 121 +++--- src/easydiffraction/core/singleton.py | 45 +-- src/easydiffraction/display/plotting.py | 73 ++-- src/easydiffraction/io/cif/serialize.py | 8 +- src/easydiffraction/project/display.py | 14 +- .../categories/fit_result/test_base.py | 50 +++ .../categories/fit_result/test_bayesian.py | 52 +++ .../categories/fit_result/test_factory.py | 37 ++ .../categories/fit_result/test_lsq.py | 55 +++ .../categories/minimizer/test_lsq_base.py | 25 +- .../analysis/categories/test_fit_result.py | 4 +- .../analysis/categories/test_fit_state.py | 25 +- .../io/test_results_sidecar.py | 6 +- .../easydiffraction/project/test_display.py | 1 + .../project/test_project_load.py | 16 +- 37 files changed, 1188 insertions(+), 861 deletions(-) create mode 100644 tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py create mode 100644 tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py create mode 100644 tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py create mode 100644 tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py diff --git a/docs/dev/adrs/accepted/analysis-cif-fit-state.md b/docs/dev/adrs/accepted/analysis-cif-fit-state.md index 59cfc0804..0cc5e658a 100644 --- a/docs/dev/adrs/accepted/analysis-cif-fit-state.md +++ b/docs/dev/adrs/accepted/analysis-cif-fit-state.md @@ -93,9 +93,8 @@ pairs are stored. ### Minimizer fit projection The active `_minimizer.*` category stores user-selected solver inputs -only. Scalar outputs are written to the paired `_fit_result.*` -category. Deterministic fit-result classes add compact fit output -counts: +only. Scalar outputs are written to the paired `_fit_result.*` category. +Deterministic fit-result classes add compact fit output counts: - `objective_name` - `objective_value` diff --git a/docs/dev/adrs/accepted/minimizer-category-consolidation.md b/docs/dev/adrs/accepted/minimizer-category-consolidation.md index 671a1a091..ed1e24f36 100644 --- a/docs/dev/adrs/accepted/minimizer-category-consolidation.md +++ b/docs/dev/adrs/accepted/minimizer-category-consolidation.md @@ -60,9 +60,9 @@ samplers. Introduce a single switchable category `minimizer` on `Analysis`. Its concrete class is determined by `Analysis.minimizer_type`. The category now holds user-writable minimizer inputs only. The later -[`minimizer-input-output-split.md`](minimizer-input-output-split.md) -ADR reverses the fit-output half of this rule: scalar fit outputs live -on the paired `fit_result` category instead of on `minimizer`. +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) ADR +reverses the fit-output half of this rule: scalar fit outputs live on +the paired `fit_result` category instead of on `minimizer`. The following categories are removed: @@ -394,8 +394,8 @@ category's class-level `_engine_metadata` dict. - `minimizer` no longer mixes writable user inputs and fit-filled outputs in the same scope. That stricter boundary is recorded by [`minimizer-input-output-split.md`](minimizer-input-output-split.md); - `Parameter` remains the refinement-in-place precedent for model - values rather than minimizer diagnostics. + `Parameter` remains the refinement-in-place precedent for model values + rather than minimizer diagnostics. - The set of `_minimizer.*` tags present in CIF depends on the active `_fitting.minimizer_type`. Loading a CIF whose tags don't match the minimizer's allowed set raises (clear validation, not silent diff --git a/docs/dev/adrs/accepted/minimizer-input-output-split.md b/docs/dev/adrs/accepted/minimizer-input-output-split.md index a496dc3a1..13a33a5be 100644 --- a/docs/dev/adrs/accepted/minimizer-input-output-split.md +++ b/docs/dev/adrs/accepted/minimizer-input-output-split.md @@ -27,26 +27,24 @@ The current shape on the live category surfaces: - **Writable inputs:** `max_iterations` (LSQ); `sampling_steps`, `burn_in_steps`, `thinning_interval`, `population_size`, - `parallel_workers`, `initialization_method`, `random_seed` - (Bayesian). + `parallel_workers`, `initialization_method`, `random_seed` (Bayesian). - **Fit-filled outputs (no public setter, only `_set_*` internals):** `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, `correlation_available`, `runtime_seconds`, `iterations_performed`, `exit_reason` (LSQ); `runtime_seconds`, `point_estimate_name`, `sampler_completed`, `credible_interval_inner`, - `credible_interval_outer`, `acceptance_rate_mean`, - `gelman_rubin_max`, `effective_sample_size_min`, - `best_log_posterior` (Bayesian). + `credible_interval_outer`, `acceptance_rate_mean`, `gelman_rubin_max`, + `effective_sample_size_min`, `best_log_posterior` (Bayesian). Three current output fields straddle `analysis.minimizer` and `analysis.fit_result`: -| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | -| -------------- | ----------------------------- | ------------------------------ | ------------ | -| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | -| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | -| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | +| Output concept | Field on `analysis.minimizer` | Field on `analysis.fit_result` | Relationship | +| ----------------------- | ----------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Wall time | `runtime_seconds` | `fitting_time` | Real duplication — same scalar in two places. | +| Iteration count | `iterations_performed` (LSQ) | `iterations` | Real duplication — same scalar in two places. | +| Objective vs reduced χ² | `objective_value` (raw χ²) | `reduced_chi_square` (χ² / dof) | Cross-category misplacement — two related but distinct scalars where the raw value sits on `minimizer` instead of with the rest of the fit outputs. | So a reader who wants "how long did the fit take" must already pick between two places. The current layout has both **input/output mixed @@ -57,18 +55,18 @@ or not). §2 resolves each row above explicitly. The consolidation ADR's two-line argument for keeping inputs and outputs together was: -1. **Symmetry with `Parameter`.** A `Parameter` holds both its - user-set initial value and its refined value plus uncertainty. +1. **Symmetry with `Parameter`.** A `Parameter` holds both its user-set + initial value and its refined value plus uncertainty. 2. **One-place discoverability** > strict purity. -The symmetry argument does not actually transfer. -`Parameter.value` and `Parameter.uncertainty` describe the *same scalar -quantity* before and after refinement; they share a name, semantics, -and lifecycle. `minimizer.sampling_steps` (a user request) and +The symmetry argument does not actually transfer. `Parameter.value` and +`Parameter.uncertainty` describe the _same scalar quantity_ before and +after refinement; they share a name, semantics, and lifecycle. +`minimizer.sampling_steps` (a user request) and `minimizer.gelman_rubin_max` (a diagnostic the sampler reports) are -about completely different things and only share a namespace because -the consolidation ADR put them there. "One-place" is also already -broken: scalar fit outputs are split across `minimizer`, `fit_result`, +about completely different things and only share a namespace because the +consolidation ADR put them there. "One-place" is also already broken: +scalar fit outputs are split across `minimizer`, `fit_result`, `fit_parameters`, and `fit_parameter_correlations` today. ## Decision @@ -82,43 +80,42 @@ declare its own output schema. After this ADR: -| Category | Role | Writable | -| -------- | ---- | -------- | -| `analysis.minimizer` | user-supplied settings | yes | -| `analysis.fit_result` | scalar fit outputs | no (internal `_set_*` only) | -| `analysis.fit_parameters` | per-parameter snapshots and posterior summary rows | no | -| `analysis.fit_parameter_correlations` | upper-triangle correlation rows | no | +| Category | Role | Writable | +| ------------------------------------- | -------------------------------------------------- | --------------------------- | +| `analysis.minimizer` | user-supplied settings | yes | +| `analysis.fit_result` | scalar fit outputs | no (internal `_set_*` only) | +| `analysis.fit_parameters` | per-parameter snapshots and posterior summary rows | no | +| `analysis.fit_parameter_correlations` | upper-triangle correlation rows | no | **`fit_result` is not a user-facing switchable category.** It is an internal projection paired with the active `minimizer`. It does not expose `fit_result.type` or `fit_result.show_supported()`; the only way the user changes the active `fit_result` class is by setting -`analysis.minimizer.type`, which the owner's `_swap_minimizer` hook -uses to instantiate both `self._minimizer` and `self._fit_result` -atomically. This is an explicit, documented exception to the global -selector contract from +`analysis.minimizer.type`, which the owner's `_swap_minimizer` hook uses +to instantiate both `self._minimizer` and `self._fit_result` atomically. +This is an explicit, documented exception to the global selector +contract from [`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) -§1 because there is no user choice involved at the `fit_result` level -— the minimizer family fully determines the result schema. See the -new exception text added to that ADR (listed under §"ADRs amended"). +§1 because there is no user choice involved at the `fit_result` level — +the minimizer family fully determines the result schema. See the new +exception text added to that ADR (listed under §"ADRs amended"). **Family mapping is one-to-one between minimizer family and result -class.** Every minimizer registered under -`MinimizerTypeEnum` maps to exactly one `FitResult` concrete class -according to its family: +class.** Every minimizer registered under `MinimizerTypeEnum` maps to +exactly one `FitResult` concrete class according to its family: | `MinimizerTypeEnum` member | Minimizer family | Paired `FitResult` class | | -------------------------- | ---------------- | ------------------------ | -| `LMFIT` | LSQ | `LeastSquaresFitResult` | -| `LMFIT_LEASTSQ` | LSQ | `LeastSquaresFitResult` | -| `LMFIT_LEAST_SQUARES` | LSQ | `LeastSquaresFitResult` | -| `DFOLS` | LSQ | `LeastSquaresFitResult` | -| `BUMPS` | LSQ | `LeastSquaresFitResult` | -| `BUMPS_LM` | LSQ | `LeastSquaresFitResult` | -| `BUMPS_AMOEBA` | LSQ | `LeastSquaresFitResult` | -| `BUMPS_DE` | LSQ | `LeastSquaresFitResult` | -| `BUMPS_DREAM` | Bayesian | `BayesianFitResult` | -| `EMCEE` *(when added)* | Bayesian | `BayesianFitResult` | +| `LMFIT` | LSQ | `LeastSquaresFitResult` | +| `LMFIT_LEASTSQ` | LSQ | `LeastSquaresFitResult` | +| `LMFIT_LEAST_SQUARES` | LSQ | `LeastSquaresFitResult` | +| `DFOLS` | LSQ | `LeastSquaresFitResult` | +| `BUMPS` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_LM` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_AMOEBA` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_DE` | LSQ | `LeastSquaresFitResult` | +| `BUMPS_DREAM` | Bayesian | `BayesianFitResult` | +| `EMCEE` _(when added)_ | Bayesian | `BayesianFitResult` | The pairing rule is encoded once on the minimizer base classes (`LeastSquaresMinimizerBase._fit_result_class = LeastSquaresFitResult`, @@ -140,17 +137,16 @@ making the input/output boundary unambiguous. `random_seed`. `credible_interval_inner` and `credible_interval_outer` **stay on the -output side** in this ADR, attached to `BayesianFitResult` (see -below). They are persisted with the fixed values `0.68` and `0.95`, -matching the per-parameter interval columns -(`posterior_interval_68_low/high`, `posterior_interval_95_low/high`). -Promoting the levels to user-writable settings would let the user -choose a 50% interval that then gets persisted under a column named -`posterior_interval_68_low` — a data-integrity problem rather than a -UX problem. A future suggestion ADR can promote them to settings and -generalise the column naming (e.g. `posterior_interval_low_`) -in one combined change; doing one without the other is unsafe and -out of scope here. +output side** in this ADR, attached to `BayesianFitResult` (see below). +They are persisted with the fixed values `0.68` and `0.95`, matching the +per-parameter interval columns (`posterior_interval_68_low/high`, +`posterior_interval_95_low/high`). Promoting the levels to user-writable +settings would let the user choose a 50% interval that then gets +persisted under a column named `posterior_interval_68_low` — a +data-integrity problem rather than a UX problem. A future suggestion ADR +can promote them to settings and generalise the column naming (e.g. +`posterior_interval_low_`) in one combined change; doing one +without the other is unsafe and out of scope here. **`analysis.fit_result` after the split** (outputs only). Common fields live on `FitResultBase`; family-specific fields on the concrete classes: @@ -171,14 +167,14 @@ the `minimizer` copy** and keeping the `fit_result` copy: - `minimizer.runtime_seconds` removed; `fit_result.fitting_time` is the single source. -- `minimizer.iterations_performed` removed; - `fit_result.iterations` is the single source. +- `minimizer.iterations_performed` removed; `fit_result.iterations` is + the single source. - `minimizer.objective_value` removed. `LeastSquaresFitResult` keeps **two distinct fields**: `objective_value` (raw χ² returned by the minimizer's objective function) and `reduced_chi_square` (= χ² / - `degrees_of_freedom`). They are not duplicates; the unreduced value - is what the solver actually optimises and is useful for diagnostics - on small-dof fits, while the reduced value is what every user-facing + `degrees_of_freedom`). They are not duplicates; the unreduced value is + what the solver actually optimises and is useful for diagnostics on + small-dof fits, while the reduced value is what every user-facing table and plot displays. `BayesianFitResult` does not carry `objective_value` because the Bayesian engine optimises the log posterior rather than χ² directly. @@ -186,8 +182,8 @@ the `minimizer` copy** and keeping the `fit_result` copy: ### 3. CIF layout follows the Python split The `_minimizer.*` block becomes settings-only. A new `_fit_result.*` -block (already present today for the common header fields) absorbs -every fit output. The set of `_fit_result.*` tags depends on the active +block (already present today for the common header fields) absorbs every +fit output. The set of `_fit_result.*` tags depends on the active `_minimizer.type`, matching the same shape-shifting convention that `_minimizer.*` itself already uses per [`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md). @@ -257,10 +253,10 @@ A small UX win is added under the accepted display facade ([`display-ux.md`](display-ux.md)): the existing `project.display.fit.results()` entry point gains a "Settings used" table above the existing results tables, populated from -`analysis.minimizer.*`. No new `Analysis`-level display method is -added; the user-facing surface stays exactly where the display ADR -put it. Internally the helper reads `self.minimizer.*` and -`self.fit_result.*` and renders one combined view. +`analysis.minimizer.*`. No new `Analysis`-level display method is added; +the user-facing surface stays exactly where the display ADR put it. +Internally the helper reads `self.minimizer.*` and `self.fit_result.*` +and renders one combined view. ### 5. Help and discoverability @@ -276,11 +272,10 @@ family-specific ones, all clearly read-only. `analysis.minimizer.type` remains the single user-facing selector. The swap hook updates **both** `analysis.minimizer` and `analysis.fit_result` instances atomically (via -`Analysis._swap_minimizer`, which reads the paired -`_fit_result_class` off the new minimizer base). The paired -`fit_result` is not a user-facing switchable: there is no -`fit_result.type` and no `fit_result.show_supported()`, per the -documented exception added to +`Analysis._swap_minimizer`, which reads the paired `_fit_result_class` +off the new minimizer base). The paired `fit_result` is not a +user-facing switchable: there is no `fit_result.type` and no +`fit_result.show_supported()`, per the documented exception added to [`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) (see §"ADRs amended"). This keeps "one minimizer concept, one user-facing type" intact and removes the temptation to swap the result @@ -299,12 +294,12 @@ class independently of the minimizer. duplication and both stay (see §2 for the distinction). - **Family-specific outputs have a natural home.** Currently `minimizer.gelman_rubin_max` lives on the Bayesian minimizer class; - after the split it lives on the paired `BayesianFitResult` class. - The two categories pair symmetrically and emcee inherits the pattern - for free. + after the split it lives on the paired `BayesianFitResult` class. The + two categories pair symmetrically and emcee inherits the pattern for + free. - **CIF stays compact.** No new CIF blocks beyond `_fit_result.*` which - is already present. The settings/outputs split is reflected in the - CIF tag prefix. + is already present. The settings/outputs split is reflected in the CIF + tag prefix. - **The "are we done with a fit?" check becomes simple.** `bool(analysis.fit_result.success.value)` answers it directly without scanning a mixed input/output namespace. @@ -322,10 +317,10 @@ class independently of the minimizer. selector contract is documented explicitly in §"ADRs amended" alongside the selector ADR. - **Saved CIF files from the post-consolidation layout cannot load - unchanged.** Beta posture (no legacy shims) applies. Tutorial - fixtures regenerate via `pixi run script-tests`. Tutorial `ed-24` - already carries a narrow archive normaliser; the new layout would - extend it once. + unchanged.** Beta posture (no legacy shims) applies. Tutorial fixtures + regenerate via `pixi run script-tests`. Tutorial `ed-24` already + carries a narrow archive normaliser; the new layout would extend it + once. - **Reopens a decision from a recently accepted ADR.** Documented explicitly above in §"Status Note". @@ -335,46 +330,44 @@ class independently of the minimizer. — §1 ("Unified `minimizer` category replaces all sampler-input and fit-result categories") becomes a partial rule: the unified `minimizer` holds inputs; outputs move to the paired `fit_result`. - §"Alternatives Considered → D" updated to record the - reversal and the implementation evidence that prompted it. -- [`analysis-cif-fit-state.md`](analysis-cif-fit-state.md) - — §"Minimizer fit projection" rewritten to describe the split - (`_minimizer.*` settings-only, `_fit_result.*` outputs including - family-specific fields). -- [`runtime-fit-results.md`](runtime-fit-results.md) - — closing paragraph references this ADR alongside the existing two. + §"Alternatives Considered → D" updated to record the reversal and the + implementation evidence that prompted it. +- [`analysis-cif-fit-state.md`](analysis-cif-fit-state.md) — §"Minimizer + fit projection" rewritten to describe the split (`_minimizer.*` + settings-only, `_fit_result.*` outputs including family-specific + fields). +- [`runtime-fit-results.md`](runtime-fit-results.md) — closing paragraph + references this ADR alongside the existing two. - [`switchable-category-owned-selectors.md`](switchable-category-owned-selectors.md) - — §1 ("The category owns its selector") gains a paragraph carving - out one documented exception: a category that is fully determined by + — §1 ("The category owns its selector") gains a paragraph carving out + one documented exception: a category that is fully determined by another category's `type` (today only `fit_result`, derived from `minimizer.type`) is allowed to omit `category.type` and - `category.show_supported()`. The mechanism is described in §1 of - this ADR. The user-facing selector convention is otherwise unchanged. -- [`display-ux.md`](display-ux.md) — §"Fit results display" - expanded to mention that `project.display.fit.results()` now prints - a "Settings used" block above the result tables, sourced from + `category.show_supported()`. The mechanism is described in §1 of this + ADR. The user-facing selector convention is otherwise unchanged. +- [`display-ux.md`](display-ux.md) — §"Fit results display" expanded to + mention that `project.display.fit.results()` now prints a "Settings + used" block above the result tables, sourced from `analysis.minimizer.*`. No new public entry point is added. ## Deferred Work - **Renaming `analysis.fit_results` (plural runtime object).** The - plural/singular pair is mildly confusing but the rename has wide - blast radius (tests, tutorials, every BayesianFitResults reference). - Track separately if the confusion remains after the combined - display lands. -- **Paired internal categories beyond `minimizer` / `fit_result`.** - This ADR introduces the paired pattern for the minimizer only. If - future categories grow the same input/output asymmetry (e.g. - extinction, peak), apply the same pattern then; do not generalise - pre-emptively. + plural/singular pair is mildly confusing but the rename has wide blast + radius (tests, tutorials, every BayesianFitResults reference). Track + separately if the confusion remains after the combined display lands. +- **Paired internal categories beyond `minimizer` / `fit_result`.** This + ADR introduces the paired pattern for the minimizer only. If future + categories grow the same input/output asymmetry (e.g. extinction, + peak), apply the same pattern then; do not generalise pre-emptively. - **User-configurable credible-interval levels.** The two interval levels currently stay at the hardcoded `0.68` / `0.95`, matching the fixed per-parameter column names (`posterior_interval_68_low` etc.). - Promoting the levels to user settings requires generalising the - column naming at the same time to avoid the data-integrity hole - where a `0.50` level lands in a column called - `posterior_interval_68_low`. Both pieces belong in a follow-on ADR - so this proposal stays focused on the input/output split. + Promoting the levels to user settings requires generalising the column + naming at the same time to avoid the data-integrity hole where a + `0.50` level lands in a column called `posterior_interval_68_low`. + Both pieces belong in a follow-on ADR so this proposal stays focused + on the input/output split. - **CIF compatibility helper for ID 35 archive.** The `_normalize_id35_archive_for_tutorial` helper in `ed-24.py` already has a roadmap to deletion; the new CIF layout extends the rename map @@ -394,8 +387,8 @@ therefore does not fix the `minimizer.help()` discoverability problem. Add an `is_input: bool` marker to each descriptor and have `minimizer.help()` group inputs vs outputs in display. Rejected because it ships the structural problem unchanged — the CIF still mixes both -under `_minimizer.*`, the duplications with `fit_result` remain, and -the `_set_*` vs writable-setter split is still ad-hoc. +under `_minimizer.*`, the duplications with `fit_result` remain, and the +`_set_*` vs writable-setter split is still ad-hoc. ### C. Move outputs into the runtime `fit_results` object, not a CIF category diff --git a/docs/dev/adrs/accepted/runtime-fit-results.md b/docs/dev/adrs/accepted/runtime-fit-results.md index cac3d0752..c0cbf7b29 100644 --- a/docs/dev/adrs/accepted/runtime-fit-results.md +++ b/docs/dev/adrs/accepted/runtime-fit-results.md @@ -32,9 +32,9 @@ persisted projection. The accepted [`minimizer-category-consolidation.md`](minimizer-category-consolidation.md) ADRs, as amended by [`minimizer-input-output-split.md`](minimizer-input-output-split.md), -define the current compact projection for fit headers, paired -fit-result outputs, parameter posterior summaries, and the -`analysis/results.h5` sidecar. +define the current compact projection for fit headers, paired fit-result +outputs, parameter posterior summaries, and the `analysis/results.h5` +sidecar. ## Consequences diff --git a/docs/dev/adrs/accepted/switchable-category-owned-selectors.md b/docs/dev/adrs/accepted/switchable-category-owned-selectors.md index e9e0f8fd3..f74fdf849 100644 --- a/docs/dev/adrs/accepted/switchable-category-owned-selectors.md +++ b/docs/dev/adrs/accepted/switchable-category-owned-selectors.md @@ -106,8 +106,8 @@ determined by another category's `type` may omit its own public selector. Today this applies only to `analysis.fit_result`, whose class is derived from `analysis.minimizer.type` by the [`minimizer-input-output-split.md`](minimizer-input-output-split.md) -ADR. It has no `fit_result.type`, no `fit_result.show_supported()`, -and no `_fit_result.type` CIF tag. +ADR. It has no `fit_result.type`, no `fit_result.show_supported()`, and +no `_fit_result.type` CIF tag. The owner still owns the swap mechanism (it holds the slot) but the swap is _initiated_ from the category through a back-reference. diff --git a/docs/dev/adrs/index.md b/docs/dev/adrs/index.md index e17f4828a..fcdf7bbfa 100644 --- a/docs/dev/adrs/index.md +++ b/docs/dev/adrs/index.md @@ -21,7 +21,7 @@ folders. | Analysis and fitting | Accepted | Parameter Correlation Persistence | Persists deterministic and posterior correlation summaries in `_fit_parameter_correlation` | [`parameter-correlation-persistence.md`](accepted/parameter-correlation-persistence.md) | | Analysis and fitting | Suggestion | Fit Output Files and Data Exports | Narrows remaining archive/export questions after adopting `results.csv` and `results.h5`. | [`fit-output-files-and-data-exports.md`](suggestions/fit-output-files-and-data-exports.md) | | Analysis and fitting | Accepted | Minimizer Category Consolidation | Collapses the seven Bayesian categories into one owner-level switchable `minimizer` category with HDF5 sidecar. | [`minimizer-category-consolidation.md`](accepted/minimizer-category-consolidation.md) | -| Analysis and fitting | Accepted | Minimizer Input/Output Split | Keeps `analysis.minimizer` input-only and moves scalar fit outputs to paired `analysis.fit_result` classes. | [`minimizer-input-output-split.md`](accepted/minimizer-input-output-split.md) | +| Analysis and fitting | Accepted | Minimizer Input/Output Split | Keeps `analysis.minimizer` input-only and moves scalar fit outputs to paired `analysis.fit_result` classes. | [`minimizer-input-output-split.md`](accepted/minimizer-input-output-split.md) | | Analysis and fitting | Superseded | Parameter-Level Posterior Projection | Superseded by minimizer-category consolidation; kept as historical context for `parameter.posterior`. | [`parameter-posterior-summary.md`](suggestions/parameter-posterior-summary.md) | | Analysis and fitting | Suggestion | Undo Fit | Builds rollback semantics and CLI behavior on already-persisted pre-fit scalar snapshots. | [`undo-fit.md`](suggestions/undo-fit.md) | | Core model | Accepted | Category Owners and Real Datablocks | Introduces `CategoryOwner` so singleton sections do not pretend to be real CIF datablocks. | [`category-owner-sections.md`](accepted/category-owner-sections.md) | diff --git a/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md b/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md index 095b5e281..052412cbd 100644 --- a/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md +++ b/docs/dev/adrs/suggestions/iucr-cif-tag-alignment.md @@ -6,43 +6,43 @@ This suggestion captures research done during the [`minimizer-input-output-split`](accepted/minimizer-input-output-split.md) -work. That ADR's `_fit_result.*` CIF prefix is functional but -diverges from the IUCr core and powder dictionaries. This proposal -records the divergence and a plausible alignment path so it is not -lost; the work is **not** in scope for the input/output-split PR. +work. That ADR's `_fit_result.*` CIF prefix is functional but diverges +from the IUCr core and powder dictionaries. This proposal records the +divergence and a plausible alignment path so it is not lost; the work is +**not** in scope for the input/output-split PR. ## Context -After the input/output split, fit outputs persist under -`_fit_result.*`. The IUCr maintains two relevant dictionaries that -already cover much of the same ground: +After the input/output split, fit outputs persist under `_fit_result.*`. +The IUCr maintains two relevant dictionaries that already cover much of +the same ground: -- [`COMCIFS/cif_core`](https://github.com/COMCIFS/cif_core) — core - CIF dictionary, refinement parameters under `_refine_ls.*` (51 - items) and aggregate reflection statistics under `_reflns.*`. +- [`COMCIFS/cif_core`](https://github.com/COMCIFS/cif_core) — core CIF + dictionary, refinement parameters under `_refine_ls.*` (51 items) and + aggregate reflection statistics under `_reflns.*`. - [`COMCIFS/Powder_Dictionary`](https://github.com/COMCIFS/Powder_Dictionary) - (`cif_pow.dic`) — powder-specific refinement under - `_pd_proc_ls.*` (9 items). + (`cif_pow.dic`) — powder-specific refinement under `_pd_proc_ls.*` (9 + items). Cross-reference today: -| Concept | Our `_fit_result.*` | IUCr core (`_refine_ls.*`) | IUCr powder (`_pd_proc_ls.*`) | -| --- | --- | --- | --- | -| Reduced χ² / GoF | `reduced_chi_square` | `goodness_of_fit_all` (S, plus `_su`) | derived from `prof_wR_factor`/`prof_wR_expected` | -| R-factor unweighted | — | `r_factor_all`, `r_factor_gt` | `prof_R_factor` | -| R-factor weighted | — | `wr_factor_all`, `wr_factor_gt`, `wr_factor_ref` | `prof_wR_factor` | -| R-expected | — | (derived from S and counts) | `prof_wR_expected` | -| Number of data points | `n_data_points` | `number_reflns`, `number_reflns_gt` | derive from `_pd_proc.number_of_points` | -| Number of parameters | `n_parameters` | `number_parameters` | (in `_refine_ls.*`) | -| Number of restraints | — | `number_restraints` | same | -| Number of constraints | — | `number_constraints` | same | -| Shift / σ | — | `shift_over_su_max`, `shift_over_su_mean` | same | -| Profile function | — | — | `profile_function` | -| Background function | — | — | `background_function` | -| Wall time | `fitting_time` | (none) | (none) | -| Iteration count | `iterations` | (none) | (none) | -| Success flag, message | `success`, `message` | (none) | (none) | -| Bayesian R̂, ESS, accept | various | (none) | (none) | +| Concept | Our `_fit_result.*` | IUCr core (`_refine_ls.*`) | IUCr powder (`_pd_proc_ls.*`) | +| ----------------------- | -------------------- | ------------------------------------------------ | ------------------------------------------------ | +| Reduced χ² / GoF | `reduced_chi_square` | `goodness_of_fit_all` (S, plus `_su`) | derived from `prof_wR_factor`/`prof_wR_expected` | +| R-factor unweighted | — | `r_factor_all`, `r_factor_gt` | `prof_R_factor` | +| R-factor weighted | — | `wr_factor_all`, `wr_factor_gt`, `wr_factor_ref` | `prof_wR_factor` | +| R-expected | — | (derived from S and counts) | `prof_wR_expected` | +| Number of data points | `n_data_points` | `number_reflns`, `number_reflns_gt` | derive from `_pd_proc.number_of_points` | +| Number of parameters | `n_parameters` | `number_parameters` | (in `_refine_ls.*`) | +| Number of restraints | — | `number_restraints` | same | +| Number of constraints | — | `number_constraints` | same | +| Shift / σ | — | `shift_over_su_max`, `shift_over_su_mean` | same | +| Profile function | — | — | `profile_function` | +| Background function | — | — | `background_function` | +| Wall time | `fitting_time` | (none) | (none) | +| Iteration count | `iterations` | (none) | (none) | +| Success flag, message | `success`, `message` | (none) | (none) | +| Bayesian R̂, ESS, accept | various | (none) | (none) | Two consequences: @@ -50,19 +50,19 @@ Two consequences: restraint/constraint counts, shift/σ diagnostics, and powder profile/background function names. These are fields that crystallographers expect in a saved CIF. -2. **Naming divergence.** Where IUCr does have a tag, we use a - different prefix (`_fit_result.*` vs `_refine_ls.*` / - `_pd_proc_ls.*`). Our CIFs are valid but cannot be consumed by - external tools that expect IUCr-standard names. +2. **Naming divergence.** Where IUCr does have a tag, we use a different + prefix (`_fit_result.*` vs `_refine_ls.*` / `_pd_proc_ls.*`). Our + CIFs are valid but cannot be consumed by external tools that expect + IUCr-standard names. ## Decision ### 1. Use IUCr tag names where they exist -For every `_fit_result.*` field that has a one-to-one IUCr -counterpart, emit the IUCr tag instead. The Python attribute name -stays (`analysis.fit_result.reduced_chi_square`); only the -`cif_handler` `names` tuple changes. Examples: +For every `_fit_result.*` field that has a one-to-one IUCr counterpart, +emit the IUCr tag instead. The Python attribute name stays +(`analysis.fit_result.reduced_chi_square`); only the `cif_handler` +`names` tuple changes. Examples: - `fit_result.reduced_chi_square` → emits `_refine_ls.goodness_of_fit_all` for single-crystal, @@ -73,9 +73,9 @@ stays (`analysis.fit_result.reduced_chi_square`); only the - `fit_result.n_parameters` → `_refine_ls.number_parameters`. The emitted prefix becomes shape-shifting based on -`experiment.type.scattering_type` and `experiment.type.sample_form` -— the same convention powder packages use today. The Python API -remains uniform. +`experiment.type.scattering_type` and `experiment.type.sample_form` — +the same convention powder packages use today. The Python API remains +uniform. ### 2. Add the missing IUCr fields to `fit_result` @@ -85,40 +85,37 @@ Promote the following from "gap" to "available" on the existing - `r_factor_all`, `wr_factor_all` — emitted as the standard tags; computed by the LSQ projection writer from residuals. - `prof_r_factor`, `prof_wr_factor`, `prof_wr_expected` — - powder-specific variants, computed the same way against the - profile data. -- `number_restraints`, `number_constraints` — current count from - the analysis model. -- `shift_over_su_max`, `shift_over_su_mean` — last-iteration - convergence diagnostic. + powder-specific variants, computed the same way against the profile + data. +- `number_restraints`, `number_constraints` — current count from the + analysis model. +- `shift_over_su_max`, `shift_over_su_mean` — last-iteration convergence + diagnostic. - `profile_function`, `background_function` (powder) — string-form descriptions of the active peak and background categories. ### 3. Keep our own prefix for fields IUCr does not cover `fitting_time`, `iterations`, `success`, `message`, every Bayesian -diagnostic (`gelman_rubin_max`, `acceptance_rate_mean`, etc.), and -the `result_kind`/`point_estimate_name` markers have no IUCr home -today. They stay under a project-specific prefix. Two options for -that prefix: - -- Keep `_fit_result.*` for the non-IUCr fields only. The CIF then - mixes prefixes (`_refine_ls.*` + `_fit_result.*`) which is - unusual but legal. +diagnostic (`gelman_rubin_max`, `acceptance_rate_mean`, etc.), and the +`result_kind`/`point_estimate_name` markers have no IUCr home today. +They stay under a project-specific prefix. Two options for that prefix: + +- Keep `_fit_result.*` for the non-IUCr fields only. The CIF then mixes + prefixes (`_refine_ls.*` + `_fit_result.*`) which is unusual but + legal. - Use a clearly-namespaced extension like `_easydiffraction.*` or - `_eddict_fit.*` so external tools recognise the fields as - non-IUCr. + `_eddict_fit.*` so external tools recognise the fields as non-IUCr. Decide during implementation; the second option is friendlier to external CIF readers. ### 4. Bayesian Rietveld output has no IUCr precedent today -There is no IUCr convention for sampler convergence (R̂, ESS, -acceptance) or posterior diagnostics. This proposal does not invent -one. The Bayesian-specific fields stay under the project prefix -chosen in §3. If a community standard emerges, a follow-on ADR can -absorb it. +There is no IUCr convention for sampler convergence (R̂, ESS, acceptance) +or posterior diagnostics. This proposal does not invent one. The +Bayesian-specific fields stay under the project prefix chosen in §3. If +a community standard emerges, a follow-on ADR can absorb it. ## Consequences @@ -126,11 +123,11 @@ absorb it. - Saved CIFs become consumable by standard IUCr tools (publCIF, checkCIF, journal submission pipelines). -- The "what is the R-factor of this fit?" question has an answer in - the saved file — currently we only record reduced χ². +- The "what is the R-factor of this fit?" question has an answer in the + saved file — currently we only record reduced χ². - Powder users get the conventional Rp / Rwp / Rexp triplet. -- We pick up restraint/constraint accounting that the structure - side of the project already tracks but does not currently emit. +- We pick up restraint/constraint accounting that the structure side of + the project already tracks but does not currently emit. ### Trade-offs @@ -138,34 +135,34 @@ absorb it. implement than a single `_fit_result.*` prefix. Roughly: one `cif_handler` per descriptor that maps to a different IUCr tag depending on context; the Python API stays uniform. -- Bayesian fields remain non-standard. There is no way around that - until IUCr defines tags for Bayesian Rietveld. +- Bayesian fields remain non-standard. There is no way around that until + IUCr defines tags for Bayesian Rietveld. - Existing saved projects from the post-split layout cannot load unchanged. Beta posture applies; one more legacy-rename pass in tutorial `ed-24` covers it. ### ADRs amended by this ADR -- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) - — replace the `_fit_result.*` projection description with the +- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) — + replace the `_fit_result.*` projection description with the IUCr-aligned tag set; document the per-experiment-family shape-shifting. - [`minimizer-input-output-split.md`](../accepted/minimizer-input-output-split.md) - — the `_fit_result.*` examples in §3 are updated to use IUCr tags - for the covered fields; the non-IUCr fields keep the - project-prefix examples. + — the `_fit_result.*` examples in §3 are updated to use IUCr tags for + the covered fields; the non-IUCr fields keep the project-prefix + examples. ## Deferred Work - The exact prefix for non-IUCr fields (§3 option choice). - IUCr-Bayesian alignment if a community standard appears. -- Single-crystal `r_factor_gt` / `wr_factor_gt` (greater-than-σ - subsets) need a "threshold expression" decision. The +- Single-crystal `r_factor_gt` / `wr_factor_gt` (greater-than-σ subsets) + need a "threshold expression" decision. The `_reflns.threshold_expression` field already covers it on the - reflection side; the LSQ projection writer needs to know the - threshold to compute the `_gt` variants. -- Whether to also emit `_refine.special_details` for human-readable - fit notes. + reflection side; the LSQ projection writer needs to know the threshold + to compute the `_gt` variants. +- Whether to also emit `_refine.special_details` for human-readable fit + notes. ## Alternatives Considered @@ -176,13 +173,12 @@ Defensible if the project never targets external CIF interop. ### B. Emit both prefixes for the covered fields -`_fit_result.reduced_chi_square` and -`_refine_ls.goodness_of_fit_all` both present, holding the same -value. Belt-and-braces, doubles the surface area of every saved -file, and the two values can drift. +`_fit_result.reduced_chi_square` and `_refine_ls.goodness_of_fit_all` +both present, holding the same value. Belt-and-braces, doubles the +surface area of every saved file, and the two values can drift. ### C. Adopt IUCr tags only for tags we already need Add the R-factor fields (§2) under our own `_fit_result.*` prefix. -Smaller diff, fixes the missing-field gap but not the naming -divergence. Pick if IUCr interop is genuinely not a goal. +Smaller diff, fixes the missing-field gap but not the naming divergence. +Pick if IUCr interop is genuinely not a goal. diff --git a/docs/dev/package-structure/full.md b/docs/dev/package-structure/full.md index a29064bdb..1f0663352 100644 --- a/docs/dev/package-structure/full.md +++ b/docs/dev/package-structure/full.md @@ -47,10 +47,15 @@ │ │ │ └── 🏷️ class FitParametersFactory │ │ ├── 📁 fit_result │ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class FitResultBase +│ │ │ ├── 📄 bayesian.py +│ │ │ │ └── 🏷️ class BayesianFitResult │ │ │ ├── 📄 default.py -│ │ │ │ └── 🏷️ class FitResult -│ │ │ └── 📄 factory.py -│ │ │ └── 🏷️ class FitResultFactory +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class FitResultFactory +│ │ │ └── 📄 lsq.py +│ │ │ └── 🏷️ class LeastSquaresFitResult │ │ ├── 📁 fitting_mode │ │ │ ├── 📄 __init__.py │ │ │ ├── 📄 default.py diff --git a/docs/dev/package-structure/short.md b/docs/dev/package-structure/short.md index dbe4e8f6d..e0a529800 100644 --- a/docs/dev/package-structure/short.md +++ b/docs/dev/package-structure/short.md @@ -29,8 +29,11 @@ │ │ │ └── 📄 factory.py │ │ ├── 📁 fit_result │ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 bayesian.py │ │ │ ├── 📄 default.py -│ │ │ └── 📄 factory.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 lsq.py │ │ ├── 📁 fitting_mode │ │ │ ├── 📄 __init__.py │ │ │ ├── 📄 default.py diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index 9a626026c..a64a747e1 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -29,8 +29,8 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): ## Branch and PR -- Branch: `minimizer-input-output-split` (continued from the branch - the ADR was drafted on). Do not push unless asked. +- Branch: `minimizer-input-output-split` (continued from the branch the + ADR was drafted on). Do not push unless asked. - Each step in §"Implementation steps (Phase 1)" must be staged with explicit paths and committed locally **before** moving to the next step. See `.github/copilot-instructions.md` → **Commits**. @@ -56,27 +56,26 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): 6. `credible_interval_inner` / `credible_interval_outer` stay on the output side (`BayesianFitResult`) at the fixed `0.68` / `0.95` values. User-configurable levels deferred to a follow-on ADR. -7. The display extension lives under - `project.display.fit.results()`; no new `Analysis`-level display - method is added. +7. The display extension lives under `project.display.fit.results()`; no + new `Analysis`-level display method is added. 8. Beta posture: hard cutover, no shims, no deprecation warnings. Tutorials and saved fixtures regenerate. ## Open questions -- **Existing `analysis.fit_result.from_cif` parameter ordering.** - After this split, the CIF restore for `_fit_result.*` must run - **after** `_minimizer.*` is read so the paired class is known - before the result descriptors load. The current `_restore_*` order - in [`serialize.py`](../../../src/easydiffraction/io/cif/serialize.py) +- **Existing `analysis.fit_result.from_cif` parameter ordering.** After + this split, the CIF restore for `_fit_result.*` must run **after** + `_minimizer.*` is read so the paired class is known before the result + descriptors load. The current `_restore_*` order in + [`serialize.py`](../../../src/easydiffraction/io/cif/serialize.py) reads `_minimizer.*` first via `_swap_minimizer`, then iterates the - rest. P1.6 must ensure `_fit_result` is included in the iteration - only after the swap has installed the paired class. Confirm during - P1.6 implementation. + rest. P1.6 must ensure `_fit_result` is included in the iteration only + after the swap has installed the paired class. Confirm during P1.6 + implementation. - **Posterior-summary code path that currently writes - `_set_credible_interval_*` on `minimizer`.** After P1.11, the - setters move to `BayesianFitResult`. The - `_store_posterior_fit_projection` method in + `_set_credible_interval_*` on `minimizer`.** After P1.11, the setters + move to `BayesianFitResult`. The `_store_posterior_fit_projection` + method in [`analysis.py`](../../../src/easydiffraction/analysis/analysis.py) must be updated to call `self._fit_result._set_credible_interval_inner(...)` instead of @@ -88,21 +87,20 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): ### Created -- `src/easydiffraction/analysis/categories/fit_result/base.py` — - rename existing `FitResult` class to `FitResultBase` (or extract a - base). Common output descriptors live here. +- `src/easydiffraction/analysis/categories/fit_result/base.py` — rename + existing `FitResult` class to `FitResultBase` (or extract a base). + Common output descriptors live here. - `src/easydiffraction/analysis/categories/fit_result/lsq.py` — `LeastSquaresFitResult` with LSQ-specific output descriptors. - `src/easydiffraction/analysis/categories/fit_result/bayesian.py` — `BayesianFitResult` with Bayesian-specific output descriptors, including `credible_interval_inner` / `credible_interval_outer`. -- *(`src/easydiffraction/analysis/categories/fit_result/factory.py` - already exists; this plan extends it rather than creating it. See - P1.4 — the factory becomes a registration helper for the two new - family classes; the authoritative swap mechanism is the - `_fit_result_class` attribute on the paired minimizer base, not a - factory lookup. The factory is still useful for introspection / - testing.)* +- _(`src/easydiffraction/analysis/categories/fit_result/factory.py` + already exists; this plan extends it rather than creating it. See P1.4 + — the factory becomes a registration helper for the two new family + classes; the authoritative swap mechanism is the `_fit_result_class` + attribute on the paired minimizer base, not a factory lookup. The + factory is still useful for introspection / testing.)_ - `tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py` - `tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py` - `tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py` @@ -110,68 +108,67 @@ Affected ADRs that this plan amends (per the ADR's §"ADRs amended"): ### Modified -- `src/easydiffraction/analysis/categories/fit_result/__init__.py` - — add explicit imports for every new concrete class to trigger - factory registration. -- `src/easydiffraction/analysis/categories/fit_result/default.py` - — rewritten to import from the new family modules; the existing +- `src/easydiffraction/analysis/categories/fit_result/__init__.py` — add + explicit imports for every new concrete class to trigger factory + registration. +- `src/easydiffraction/analysis/categories/fit_result/default.py` — + rewritten to import from the new family modules; the existing `FitResult` is renamed to `FitResultBase` and absorbed. - `src/easydiffraction/analysis/categories/minimizer/base.py` — add `_fit_result_class: ClassVar[type]` declaration. -- `src/easydiffraction/analysis/categories/minimizer/lsq_base.py` - — set `_fit_result_class = LeastSquaresFitResult`; **remove** +- `src/easydiffraction/analysis/categories/minimizer/lsq_base.py` — set + `_fit_result_class = LeastSquaresFitResult`; **remove** `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, `correlation_available`, `runtime_seconds`, `iterations_performed`, `exit_reason` from descriptor declarations and from `_result_descriptor_names`. `_setting_descriptor_names` stays `('max_iterations',)`. -- `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py` - — set `_fit_result_class = BayesianFitResult`; remove - `runtime_seconds`, `point_estimate_name`, `sampler_completed`, - `credible_interval_inner`, `credible_interval_outer`, - `acceptance_rate_mean`, `gelman_rubin_max`, +- `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py` — + set `_fit_result_class = BayesianFitResult`; remove `runtime_seconds`, + `point_estimate_name`, `sampler_completed`, `credible_interval_inner`, + `credible_interval_outer`, `acceptance_rate_mean`, `gelman_rubin_max`, `effective_sample_size_min`, `best_log_posterior` from descriptor declarations and from `_result_descriptor_names`. `_setting_descriptor_names` keeps the seven Bayesian inputs. -- `src/easydiffraction/analysis/analysis.py` — extend - `_swap_minimizer` to also instantiate the paired `fit_result` via - the minimizer's `_fit_result_class`; add `analysis.fit_result` - property; route every `self._minimizer._set_*` result-writer call - in `_store_least_squares_result_projection` / +- `src/easydiffraction/analysis/analysis.py` — extend `_swap_minimizer` + to also instantiate the paired `fit_result` via the minimizer's + `_fit_result_class`; add `analysis.fit_result` property; route every + `self._minimizer._set_*` result-writer call in + `_store_least_squares_result_projection` / `_store_posterior_fit_projection` / - `_restore_fit_results_from_projection` to - `self._fit_result._set_*` instead. -- `src/easydiffraction/io/cif/serialize.py` — emit/read - `_fit_result.*` from the paired class; remove the removed - `_minimizer.*` output tags from the serialise/deserialise paths. - Update the legacy-tag rejection message. + `_restore_fit_results_from_projection` to `self._fit_result._set_*` + instead. +- `src/easydiffraction/io/cif/serialize.py` — emit/read `_fit_result.*` + from the paired class; remove the removed `_minimizer.*` output tags + from the serialise/deserialise paths. Update the legacy-tag rejection + message. - `src/easydiffraction/project/display.py` — extend `project.display.fit.results()` to print a "Settings used" block populated from `analysis.minimizer.*` above the existing result tables. -- All tutorials referencing `analysis.minimizer.` - (e.g. `runtime_seconds`, `gelman_rubin_max`, `objective_value`) → - `analysis.fit_result.`. List enumerated at P1.15 - start via `git grep`. +- All tutorials referencing `analysis.minimizer.` (e.g. + `runtime_seconds`, `gelman_rubin_max`, `objective_value`) → + `analysis.fit_result.`. List enumerated at P1.15 start + via `git grep`. - Tests reading `analysis.minimizer.` → migrate to `analysis.fit_result.`. P2.1 enumerates. ### Deleted -- None. The existing `fit_result/default.py` is rewritten in place; - the existing `FitResult` class becomes `FitResultBase`. +- None. The existing `fit_result/default.py` is rewritten in place; the + existing `FitResult` class becomes `FitResultBase`. ## Implementation steps (Phase 1) Mark `[x]` as each step lands. -- [x] **P1.1 — Rename `FitResult` to `FitResultBase`; add reset - hooks; update every import site.** In +- [x] **P1.1 — Rename `FitResult` to `FitResultBase`; add reset hooks; + update every import site.** In `src/easydiffraction/analysis/categories/fit_result/default.py`, - rename the class. The factory `@register` decorator stays on - the renamed class so the default-tag lookup keeps working until - P1.4 extends the factory. + rename the class. The factory `@register` decorator stays on the + renamed class so the default-tag lookup keeps working until P1.4 + extends the factory. Add two class-level hooks to `FitResultBase` matching the `MinimizerCategoryBase` shape introduced by the consolidation @@ -233,17 +230,17 @@ Mark `[x]` as each step lands. `src/easydiffraction/analysis/categories/fit_result/lsq.py`. `LeastSquaresFitResult(FitResultBase)` declares: `objective_name`, `objective_value`, `n_data_points`, `n_parameters`, - `n_free_parameters`, `degrees_of_freedom`, - `covariance_available`, `correlation_available`, `exit_reason`. - **All defaults are `None` with `allow_none=True`**, matching the - consolidation cleanup that previously moved LSQ outputs off `0` / - `false` / `''` so a pre-fit CIF emits `?` rather than a value - that looks like a degenerate result. This applies to numeric, - integer-like, string, and bool fields alike; the descriptor - helpers in `LeastSquaresMinimizerBase` + `n_free_parameters`, `degrees_of_freedom`, `covariance_available`, + `correlation_available`, `exit_reason`. **All defaults are `None` + with `allow_none=True`**, matching the consolidation cleanup that + previously moved LSQ outputs off `0` / `false` / `''` so a pre-fit + CIF emits `?` rather than a value that looks like a degenerate + result. This applies to numeric, integer-like, string, and bool + fields alike; the descriptor helpers in + `LeastSquaresMinimizerBase` ([`lsq_base.py`](../../../src/easydiffraction/analysis/categories/minimizer/lsq_base.py)) - that currently produce these descriptors are the model — they - can be lifted into `LeastSquaresFitResult` verbatim before being + that currently produce these descriptors are the model — they can + be lifted into `LeastSquaresFitResult` verbatim before being removed from `lsq_base.py` at P1.9. Declare `_expected_descriptor_names`, `_result_descriptor_names` for parity with the minimizer hierarchy. Tests deferred to Phase 2. @@ -257,19 +254,18 @@ Mark `[x]` as each step lands. `credible_interval_outer` (default `0.95`), `acceptance_rate_mean`, `gelman_rubin_max`, `effective_sample_size_min`, `best_log_posterior`. Declare - `_expected_descriptor_names`, `_result_descriptor_names`. - Tests deferred to Phase 2. Commit: - `Add BayesianFitResult class` + `_expected_descriptor_names`, `_result_descriptor_names`. Tests + deferred to Phase 2. Commit: `Add BayesianFitResult class` - [x] **P1.4 — Register fit-result classes with the existing `FitResultFactory`.** The factory already exists at [`src/easydiffraction/analysis/categories/fit_result/factory.py`](../../../src/easydiffraction/analysis/categories/fit_result/factory.py) - and currently registers only the default common class. Update - it to also register `LeastSquaresFitResult` and - `BayesianFitResult` with their family tags. Update + and currently registers only the default common class. Update it + to also register `LeastSquaresFitResult` and `BayesianFitResult` + with their family tags. Update `src/easydiffraction/analysis/categories/fit_result/__init__.py` - to explicitly import every concrete class (so registration - fires on package import, per the repo's standard pattern). + to explicitly import every concrete class (so registration fires + on package import, per the repo's standard pattern). **Authoritative mechanism:** `Analysis._swap_minimizer` constructs the paired fit-result via the minimizer's @@ -296,37 +292,34 @@ Mark `[x]` as each step lands. - [x] **P1.6 — Wire `Analysis._swap_minimizer` to install both instances, and update every `_fit_result` reset path.** In `src/easydiffraction/analysis/analysis.py`: - - `__init__` constructs the initial `_fit_result` from the default minimizer's `_fit_result_class`: - `self._fit_result = self._minimizer._fit_result_class()`. The - line 483 `self._fit_result = FitResultBase()` (after P1.1) is - replaced. - - `_replace_minimizer` constructs `self._fit_result = - new_minimizer._fit_result_class()` after the new minimizer is - created. The old `fit_result` is detached (`_parent = None`) - before being replaced. + `self._fit_result = self._minimizer._fit_result_class()`. The line + 483 `self._fit_result = FitResultBase()` (after P1.1) is replaced. + - `_replace_minimizer` constructs + `self._fit_result = new_minimizer._fit_result_class()` after the new + minimizer is created. The old `fit_result` is detached + (`_parent = None`) before being replaced. - `_clear_persisted_fit_state` (line 1204 today) currently calls `self._clear_minimizer_result_projection()` and then - `self._fit_result = FitResult()`. After P1.1 + the split, both - lines must change: - - `self._fit_result = self.minimizer._fit_result_class()` - replaces the bare `FitResultBase()` construction. This keeps - the paired class invariant whenever the persisted state is - reset. + `self._fit_result = FitResult()`. After P1.1 + the split, both lines + must change: + - `self._fit_result = self.minimizer._fit_result_class()` replaces + the bare `FitResultBase()` construction. This keeps the paired + class invariant whenever the persisted state is reset. - `self._clear_minimizer_result_projection()` currently calls `self.minimizer._reset_result_descriptors()`. After P1.9/P1.10 remove the result descriptors from the minimizer, this method becomes a no-op. **Retarget it to - `self.fit_result._reset_result_descriptors()`** and rename it - to `_clear_fit_result_projection`. Update the call sites - (line 1204; potentially others — `git grep` confirms). + `self.fit_result._reset_result_descriptors()`** and rename it to + `_clear_fit_result_projection`. Update the call sites (line 1204; + potentially others — `git grep` confirms). - Add `analysis.fit_result` read-only property - (`return self._fit_result`). Type annotation: - `FitResultBase` (the family classes inherit from it). + (`return self._fit_result`). Type annotation: `FitResultBase` (the + family classes inherit from it). - Wire `self._fit_result._parent = self` in - `_attach_category_parents`. Every `_fit_result` reassignment in - the methods above must also set `_parent` on the new instance. + `_attach_category_parents`. Every `_fit_result` reassignment in the + methods above must also set `_parent` on the new instance. Verification at the end of this step: @@ -335,8 +328,8 @@ Mark `[x]` as each step lands. ``` Every match must construct via `self.minimizer._fit_result_class()` - (or `new_minimizer._fit_result_class()` in `_replace_minimizer`), - not a bare class name. There must be no remaining + (or `new_minimizer._fit_result_class()` in `_replace_minimizer`), not + a bare class name. There must be no remaining `self._fit_result = FitResultBase()` after this step. Commit: `Wire fit_result swap and reset paths to paired class` @@ -344,17 +337,17 @@ Mark `[x]` as each step lands. - [x] **P1.7 — Route LSQ result writers to `fit_result`.** In `src/easydiffraction/analysis/analysis.py`, `_store_least_squares_result_projection` currently writes to - `self.minimizer._set_objective_name(...)` etc. Reroute every - such call to `self.fit_result._set_*`. Same for - `_restore_fit_results_from_projection`'s LSQ branch - (it reads `self.minimizer.objective_name.value` etc. — change - to `self.fit_result..value`). Commit: + `self.minimizer._set_objective_name(...)` etc. Reroute every such + call to `self.fit_result._set_*`. Same for + `_restore_fit_results_from_projection`'s LSQ branch (it reads + `self.minimizer.objective_name.value` etc. — change to + `self.fit_result..value`). Commit: `Route LSQ result writers to fit_result` - [x] **P1.8 — Route Bayesian result writers to `fit_result`.** Same - treatment for `_store_posterior_fit_projection` and the - Bayesian branch of `_restore_fit_results_from_projection`. - Includes the `_set_credible_interval_*` calls — they now target + treatment for `_store_posterior_fit_projection` and the Bayesian + branch of `_restore_fit_results_from_projection`. Includes the + `_set_credible_interval_*` calls — they now target `self.fit_result._set_credible_interval_*`. Commit: `Route Bayesian result writers to fit_result` @@ -367,10 +360,10 @@ Mark `[x]` as each step lands. `runtime_seconds`, `iterations_performed`, `exit_reason`. Remove these names from `_expected_descriptor_names` and `_result_descriptor_names`. `_setting_descriptor_names` stays - `('max_iterations',)`. After this step, - `_result_descriptor_names` on `LeastSquaresMinimizerBase` is - `()` and `_reset_result_descriptors()` is a no-op on every LSQ - minimizer — confirming the P1.6 retarget of + `('max_iterations',)`. After this step, `_result_descriptor_names` + on `LeastSquaresMinimizerBase` is `()` and + `_reset_result_descriptors()` is a no-op on every LSQ minimizer — + confirming the P1.6 retarget of `_clear_minimizer_result_projection` to operate on `self.fit_result` is the correct call site. @@ -381,8 +374,8 @@ Mark `[x]` as each step lands. Commit: `Remove LSQ output descriptors from minimizer base` -- [x] **P1.10 — Remove duplicate fields from Bayesian minimizer - base.** In +- [x] **P1.10 — Remove duplicate fields from Bayesian minimizer base.** + In `src/easydiffraction/analysis/categories/minimizer/bayesian_base.py`, delete the descriptor declarations and properties for `runtime_seconds`, `point_estimate_name`, `sampler_completed`, @@ -390,8 +383,8 @@ Mark `[x]` as each step lands. `acceptance_rate_mean`, `gelman_rubin_max`, `effective_sample_size_min`, `best_log_posterior`. Remove these names from `_expected_descriptor_names` and - `_result_descriptor_names`. The `_setting_descriptor_names` - tuple keeps the seven Bayesian inputs. + `_result_descriptor_names`. The `_setting_descriptor_names` tuple + keeps the seven Bayesian inputs. Commit: `Remove Bayesian output descriptors from minimizer base` @@ -400,14 +393,13 @@ Mark `[x]` as each step lands. **No category-list reordering is performed in this step.** Neither `Analysis._serializable_categories()` nor - `Analysis._fit_state_categories()` is restructured. `fit_result` - stays conditionally included by `_fit_state_categories()` only - when `self._has_persisted_fit_state()` is true — exactly as today. - Pre-fit projects continue to emit no `_fit_result.*` block. - - The only changes in this step are content updates inside the - existing emit/read flow: + `Analysis._fit_state_categories()` is restructured. `fit_result` stays + conditionally included by `_fit_state_categories()` only when + `self._has_persisted_fit_state()` is true — exactly as today. Pre-fit + projects continue to emit no `_fit_result.*` block. + The only changes in this step are content updates inside the existing + emit/read flow: - `_minimizer.*` emit/read continues to handle settings only (the minimizer category's `from_cif` walks its remaining descriptors after P1.9 / P1.10 removed the output descriptors). @@ -422,30 +414,28 @@ Mark `[x]` as each step lands. reordering, no new call. - The read-side already restores `minimizer.type` first ([`serialize.py:553-555`](../../../src/easydiffraction/io/cif/serialize.py)), - so the paired-class swap fires before `fit_result.from_cif` runs. - No code change is required here. + so the paired-class swap fires before `fit_result.from_cif` runs. No + code change is required here. - Update the legacy-tag rejection message in `_raise_for_legacy_analysis_tags` to include the now-removed - `_minimizer.` tags (e.g. - `_minimizer.runtime_seconds`, `_minimizer.gelman_rubin_max`) as - legacy markers that should raise a clear error rather than load - silently. + `_minimizer.` tags (e.g. `_minimizer.runtime_seconds`, + `_minimizer.gelman_rubin_max`) as legacy markers that should raise a + clear error rather than load silently. Commit: `Serialize fit outputs to _fit_result.* tags` - [x] **P1.12 — Confirm `_fit_state_categories` returns the paired - `fit_result`.** In - `src/easydiffraction/analysis/analysis.py`, - `_fit_state_categories()` already returns `[self.fit_parameters, - self.fit_result, self.fit_parameter_correlations]` when - persisted fit state exists. After P1.6 wires the paired-class + `fit_result`.** In `src/easydiffraction/analysis/analysis.py`, + `_fit_state_categories()` already returns + `[self.fit_parameters, self.fit_result, self.fit_parameter_correlations]` + when persisted fit state exists. After P1.6 wires the paired-class construction, `self.fit_result` is automatically the paired - `LeastSquaresFitResult` / `BayesianFitResult` instance — no - method body change is needed. The dead branch in - `_fit_state_categories` (review-9 finding F4, open issue #101) - can be cleaned up here since this step is already reading the - function. The plan does not require the cleanup; if taken, - mention "closes #101" in the commit message. + `LeastSquaresFitResult` / `BayesianFitResult` instance — no method + body change is needed. The dead branch in `_fit_state_categories` + (review-9 finding F4, open issue #101) can be cleaned up here + since this step is already reading the function. The plan does not + require the cleanup; if taken, mention "closes #101" in the commit + message. Commit: `Confirm fit_result paired instance flows through serializer` @@ -454,17 +444,16 @@ Mark `[x]` as each step lands. `src/easydiffraction/project/display.py`, extend the existing results-display method to print, above the current tables, a one-section table titled "Settings used" populated from - `analysis.minimizer.*`. Use the same `render_table` machinery - the rest of the display facade uses. Commit: + `analysis.minimizer.*`. Use the same `render_table` machinery the + rest of the display facade uses. Commit: `Add settings-used block to fit.results display` - [x] **P1.14 — Amend the five accepted ADRs listed in §"ADR".** For - each, apply the matching paragraph from the ADR's §"ADRs - amended" section: + each, apply the matching paragraph from the ADR's §"ADRs amended" + section: - `minimizer-category-consolidation.md` — §1 partial-rule qualification; §"Alternatives Considered → D" reversal record. - - `analysis-cif-fit-state.md` — §"Minimizer fit projection" - rewrite. + - `analysis-cif-fit-state.md` — §"Minimizer fit projection" rewrite. - `runtime-fit-results.md` — closing-paragraph reference. - `switchable-category-owned-selectors.md` — §1 paired-category exception paragraph. @@ -478,9 +467,9 @@ Mark `[x]` as each step lands. - [x] **P1.15 — Update tutorials.** `git grep` `docs/docs/tutorials/` for `analysis.minimizer.` references and rewrite each per the migration table below. The two **collapsed** rows - target existing common fields on `FitResultBase` (already - written by the existing common projection writer); they are not - 1:1 renames of the old setter/getter name. The other rows are + target existing common fields on `FitResultBase` (already written + by the existing common projection writer); they are not 1:1 + renames of the old setter/getter name. The other rows are moved-but-keep-the-name relocations. | Old (removed at P1.9 / P1.10) | New | Notes | @@ -518,38 +507,37 @@ Mark `[x]` as each step lands. Commit: `Update tutorials to read outputs from fit_result` - [x] **P1.16 — Promote ADR + update index.** - - `git mv docs/dev/adrs/suggestions/minimizer-input-output-split.md - docs/dev/adrs/accepted/minimizer-input-output-split.md`. Flip the - Status header to `Accepted`. - - Move the seven `_reply-N.md` and seven `_review-N.md` siblings: - keep them next to the ADR if the project convention preserves - history under `accepted/`; delete them if the convention is to - drop the deliberation artefacts on promotion (the - `switchable-category-owned-selectors` precedent deleted them). - Per the precedent, delete on promotion. + - `git mv docs/dev/adrs/suggestions/minimizer-input-output-split.md docs/dev/adrs/accepted/minimizer-input-output-split.md`. + Flip the Status header to `Accepted`. + - Move the seven `_reply-N.md` and seven `_review-N.md` siblings: keep + them next to the ADR if the project convention preserves history + under `accepted/`; delete them if the convention is to drop the + deliberation artefacts on promotion (the + `switchable-category-owned-selectors` precedent deleted them). Per + the precedent, delete on promotion. - Update `docs/dev/adrs/index.md` — move the row for this ADR from Suggestion → Accepted. Commit: `Promote minimizer-input-output-split ADR` -- [x] **P1.17 — Phase 1 review gate.** No code change. Re-run the - P1.15 tutorial grep against `src/`, `docs/docs/tutorials/`, and - `tests/`. The `src/` and `docs/docs/tutorials/` scopes must - return empty. The `tests/` sweep is deferred to P2.1, which - migrates the tests. Then stop and request user review. After - approval, proceed to Phase 2. +- [x] **P1.17 — Phase 1 review gate.** No code change. Re-run the P1.15 + tutorial grep against `src/`, `docs/docs/tutorials/`, and + `tests/`. The `src/` and `docs/docs/tutorials/` scopes must return + empty. The `tests/` sweep is deferred to P2.1, which migrates the + tests. Then stop and request user review. After approval, proceed + to Phase 2. ## Verification (Phase 2) Each command captures its log with a zsh-safe exit-code variable as required by `.github/copilot-instructions.md` → **Workflow**. -- [ ] **P2.1 — Migrate existing tests off the removed minimizer - output fields.** `git grep` `tests/` for the same patterns as - P1.15. Apply the same migration table from P1.15 — including - the two collapsed rows (`runtime_seconds` → `fitting_time`, - `iterations_performed` → `iterations`) where the setter name - also changes. Examples: +- [x] **P2.1 — Migrate existing tests off the removed minimizer output + fields.** `git grep` `tests/` for the same patterns as P1.15. + Apply the same migration table from P1.15 — including the two + collapsed rows (`runtime_seconds` → `fitting_time`, + `iterations_performed` → `iterations`) where the setter name also + changes. Examples: - Reader rewrite: `analysis.minimizer.gelman_rubin_max` → @@ -580,27 +568,28 @@ required by `.github/copilot-instructions.md` → **Workflow**. git grep -nE '_minimizer\.(runtime_seconds|iterations_performed|objective_value|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ ``` -- [ ] **P2.2 — Add unit tests for new modules.** New tests under +- [x] **P2.2 — Add unit tests for new modules.** New tests under `tests/unit/easydiffraction/analysis/categories/fit_result/`: - - `test_base.py` — `FitResultBase` defaults, `_reset_result_descriptors`. - - `test_lsq.py` — `LeastSquaresFitResult` defaults; CIF round-trip - of LSQ outputs. + - `test_base.py` — `FitResultBase` defaults, + `_reset_result_descriptors`. + - `test_lsq.py` — `LeastSquaresFitResult` defaults; CIF round-trip of + LSQ outputs. - `test_bayesian.py` — `BayesianFitResult` defaults including the fixed credible interval levels; CIF round-trip. - `test_factory.py` — pairing rule via `LeastSquaresMinimizerBase._fit_result_class` and `BayesianMinimizerBase._fit_result_class`. - Layout check: + Layout check: - ``` - pixi run test-structure-check > /tmp/easydiffraction-test-structure-check.log 2>&1; \ - test_structure_check_exit_code=$?; \ - tail -n 200 /tmp/easydiffraction-test-structure-check.log; \ - exit $test_structure_check_exit_code - ``` + ``` + pixi run test-structure-check > /tmp/easydiffraction-test-structure-check.log 2>&1; \ + test_structure_check_exit_code=$?; \ + tail -n 200 /tmp/easydiffraction-test-structure-check.log; \ + exit $test_structure_check_exit_code + ``` -- [ ] **P2.3 — Auto-fixes and static checks.** +- [x] **P2.3 — Auto-fixes and static checks.** ``` pixi run fix > /tmp/easydiffraction-fix.log 2>&1; \ @@ -621,7 +610,7 @@ required by `.github/copilot-instructions.md` → **Workflow**. Iterate `pixi run check` until clean. Do not raise lint thresholds — refactor instead. -- [ ] **P2.4 — Unit tests.** +- [x] **P2.4 — Unit tests.** ``` pixi run unit-tests > /tmp/easydiffraction-unit-tests.log 2>&1; \ @@ -630,7 +619,7 @@ required by `.github/copilot-instructions.md` → **Workflow**. exit $unit_tests_exit_code ``` -- [ ] **P2.5 — Integration tests.** +- [x] **P2.5 — Integration tests.** ``` pixi run integration-tests > /tmp/easydiffraction-integration-tests.log 2>&1; \ @@ -639,7 +628,7 @@ required by `.github/copilot-instructions.md` → **Workflow**. exit $integration_tests_exit_code ``` -- [ ] **P2.6 — Script tests.** +- [x] **P2.6 — Script tests.** ``` pixi run script-tests > /tmp/easydiffraction-script-tests.log 2>&1; \ @@ -662,15 +651,15 @@ required by `.github/copilot-instructions.md` → **Workflow**. `sampling_steps`, `max_iterations`, and the other input knobs. Once a fit completes, every output the project records (wall time, χ², the Bayesian diagnostics, the LSQ counters) lives on a paired -`analysis.fit_result`. The pairing happens automatically when you -change minimizer type, so users never see a separate result selector. +`analysis.fit_result`. The pairing happens automatically when you change +minimizer type, so users never see a separate result selector. -`project.display.fit.results()` now prints a "Settings used" block -above the existing result tables, so the settings that produced the -fit and the outputs the fit produced are visible side-by-side without -having to open two namespaces. +`project.display.fit.results()` now prints a "Settings used" block above +the existing result tables, so the settings that produced the fit and +the outputs the fit produced are visible side-by-side without having to +open two namespaces. The CIF layout follows the same split: `_minimizer.*` holds settings only, `_fit_result.*` holds outputs. Saved projects from the previous -layout do not load unchanged (the project is in beta; no legacy -shims). Tutorials and saved-fixture regeneration land in this PR. +layout do not load unchanged (the project is in beta; no legacy shims). +Tutorials and saved-fixture regeneration land in this PR. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-1.md b/docs/dev/plans/minimizer-input-output-split_reply-1.md index 3c126a60f..77959a696 100644 --- a/docs/dev/plans/minimizer-input-output-split_reply-1.md +++ b/docs/dev/plans/minimizer-input-output-split_reply-1.md @@ -8,9 +8,9 @@ for the plan at This reply follows [`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). -All four findings agreed. Each was addressed by editing the plan -text; no item is deferred. The plan is updated in the same commit as -this reply. +All four findings agreed. Each was addressed by editing the plan text; +no item is deferred. The plan is updated in the same commit as this +reply. ## Findings @@ -21,19 +21,19 @@ this reply. The current `_clear_persisted_fit_state` (analysis.py:1204) does `self._fit_result = FitResult()` and calls `_clear_minimizer_result_projection()`. The original P1.6 wired the -initial `_fit_result` and the swap path but said nothing about the -reset path that fires before every fit. After the split, that path -would discard the paired `LeastSquaresFitResult` / -`BayesianFitResult` and reinstall the bare common class — so the -first family-specific `_set_*` call in P1.7 / P1.8 would write to an -attribute that no longer exists on the live instance. +initial `_fit_result` and the swap path but said nothing about the reset +path that fires before every fit. After the split, that path would +discard the paired `LeastSquaresFitResult` / `BayesianFitResult` and +reinstall the bare common class — so the first family-specific `_set_*` +call in P1.7 / P1.8 would write to an attribute that no longer exists on +the live instance. **Action taken.** Extended P1.6 to enumerate every `_fit_result` construction and reset site in `analysis.py`: - `__init__` builds via `self._minimizer._fit_result_class()`. -- `_replace_minimizer` builds via - `new_minimizer._fit_result_class()` after detaching the old. +- `_replace_minimizer` builds via `new_minimizer._fit_result_class()` + after detaching the old. - `_clear_persisted_fit_state` rebuilds via `self.minimizer._fit_result_class()` so the paired-class invariant survives a reset. @@ -42,11 +42,12 @@ construction and reset site in `analysis.py`: `self.fit_result._reset_result_descriptors()`, since `LeastSquaresMinimizerBase` / `BayesianMinimizerBase` lose their `_result_descriptor_names` content at P1.9 / P1.10. -- A `git grep -nE 'self\._fit_result\s*=' src/easydiffraction/analysis/analysis.py` - check at the end of P1.6 confirms every construction goes through - the paired class. P1.9 also notes that the LSQ - `_result_descriptor_names` becomes `()` after the removal, so the - rename in P1.6 is the correct landing. +- A + `git grep -nE 'self\._fit_result\s*=' src/easydiffraction/analysis/analysis.py` + check at the end of P1.6 confirms every construction goes through the + paired class. P1.9 also notes that the LSQ `_result_descriptor_names` + becomes `()` after the removal, so the rename in P1.6 is the correct + landing. **Plan section:** P1.6 (rewritten), P1.9 (added confirmation note). @@ -54,28 +55,26 @@ construction and reset site in `analysis.py`: **Verdict: agree — would undo a consolidation-cleanup decision.** -The consolidation cleanup (Review 8 F6, addressed in commit -`28d4291cb`) explicitly moved LSQ result descriptors to -`default=None, allow_none=True` so a pre-fit CIF emits `?` rather -than misleading `0` / `false` / `''` values. The original P1.2 said -"bool defaults `False`; string defaults `''`", which would undo that -fix on every LSQ output relocated to `LeastSquaresFitResult`. +The consolidation cleanup (Review 8 F6, addressed in commit `28d4291cb`) +explicitly moved LSQ result descriptors to +`default=None, allow_none=True` so a pre-fit CIF emits `?` rather than +misleading `0` / `false` / `''` values. The original P1.2 said "bool +defaults `False`; string defaults `''`", which would undo that fix on +every LSQ output relocated to `LeastSquaresFitResult`. **Action taken.** Rewrote P1.2 to specify: > All defaults are `None` with `allow_none=True`, matching the > consolidation cleanup that previously moved LSQ outputs off `0` / > `false` / `''` so a pre-fit CIF emits `?` rather than a value that -> looks like a degenerate result. This applies to numeric, -> integer-like, string, and bool fields alike; the descriptor -> helpers in `LeastSquaresMinimizerBase` that currently produce -> these descriptors are the model — they can be lifted into -> `LeastSquaresFitResult` verbatim before being removed from -> `lsq_base.py` at P1.9. +> looks like a degenerate result. This applies to numeric, integer-like, +> string, and bool fields alike; the descriptor helpers in +> `LeastSquaresMinimizerBase` that currently produce these descriptors +> are the model — they can be lifted into `LeastSquaresFitResult` +> verbatim before being removed from `lsq_base.py` at P1.9. -The lift-verbatim instruction also reduces drift risk: the -helper functions move with their existing defaults rather than being -rewritten. +The lift-verbatim instruction also reduces drift risk: the helper +functions move with their existing defaults rather than being rewritten. **Plan section:** P1.2 (rewritten). @@ -99,20 +98,20 @@ original P1.1 did not call out explicitly: Without explicit migration, after P1.16 removes the old `FitResult` re-export, these imports break or keep loading the wrong class. -**Action taken.** Rewrote P1.1 to enumerate every site -explicitly, with a `git grep -nP '\bFitResult\b' src/` verification -gate at the end of the step. The two `self._fit_result = -FitResult()` constructions become `FitResultBase()` temporarily and -are then retargeted to the paired class in P1.6. +**Action taken.** Rewrote P1.1 to enumerate every site explicitly, with +a `git grep -nP '\bFitResult\b' src/` verification gate at the end of +the step. The two `self._fit_result = FitResult()` constructions become +`FitResultBase()` temporarily and are then retargeted to the paired +class in P1.6. **Plan section:** P1.1 (rewritten with the full call-out list and verification grep). ### Finding 4 — Fit-result factory inconsistency -**Verdict: agree — `factory.py` already exists, and the original -text was internally inconsistent about whether it or -`_fit_result_class` is authoritative for swap.** +**Verdict: agree — `factory.py` already exists, and the original text +was internally inconsistent about whether it or `_fit_result_class` is +authoritative for swap.** The current `fit_result/factory.py` is a 15-line `FactoryBase` registration shell. The original P1.4 called it "new" and said the @@ -123,21 +122,20 @@ directly — pick one. **Action taken.** Two coordinated edits: - The §"Created" list now says the factory exists; this plan extends - rather than creates it. The same note clarifies that the - authoritative swap mechanism is `_fit_result_class` (on the - minimizer base), with the factory as a registration/introspection - helper. + rather than creates it. The same note clarifies that the authoritative + swap mechanism is `_fit_result_class` (on the minimizer base), with + the factory as a registration/introspection helper. - P1.4 now says "Register fit-result classes with the existing `FitResultFactory`" and adds an explicit "Authoritative mechanism" - paragraph stating that `_swap_minimizer` reads the paired class - off `new_minimizer._fit_result_class`, not via a factory lookup. + paragraph stating that `_swap_minimizer` reads the paired class off + `new_minimizer._fit_result_class`, not via a factory lookup. **Plan section:** §"Created" (factory bullet), P1.4 (rewritten). ## Verification -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. +This is a static reply only. No `pixi run`, lint, build, formatter, or +test command was executed. ## Summary of files touched by this reply diff --git a/docs/dev/plans/minimizer-input-output-split_reply-2.md b/docs/dev/plans/minimizer-input-output-split_reply-2.md index 9511120d0..d8aef66fb 100644 --- a/docs/dev/plans/minimizer-input-output-split_reply-2.md +++ b/docs/dev/plans/minimizer-input-output-split_reply-2.md @@ -8,8 +8,8 @@ for the plan at This reply follows [`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). -All three findings agreed. Each was addressed by editing the plan -text in the same commit as this reply. +All three findings agreed. Each was addressed by editing the plan text +in the same commit as this reply. ## Findings @@ -19,60 +19,58 @@ text in the same commit as this reply. The ADR collapses `runtime_seconds` onto the existing `fit_result.fitting_time` and `iterations_performed` onto -`fit_result.iterations`. The original P1.15 and P2.1 said "replace -each `analysis.minimizer.` with -`analysis.fit_result.`" and used -`_set_runtime_seconds` → `_set_runtime_seconds` as the fixture -example — both are wrong for those two fields, where the target -field and setter already exist under different names on -`FitResultBase`. +`fit_result.iterations`. The original P1.15 and P2.1 said "replace each +`analysis.minimizer.` with +`analysis.fit_result.`" and used `_set_runtime_seconds` → +`_set_runtime_seconds` as the fixture example — both are wrong for those +two fields, where the target field and setter already exist under +different names on `FitResultBase`. **Action taken.** Rewrote P1.15 with an explicit migration table -covering all 19 removed fields. The two collapsed rows are flagged -with a "Collapsed onto existing common field" note and point at the -existing `fit_result._set_fitting_time(...)` / -`fit_result._set_iterations(...)` setters. The other 17 rows are -moved-but-keep-the-name relocations (e.g. +covering all 19 removed fields. The two collapsed rows are flagged with +a "Collapsed onto existing common field" note and point at the existing +`fit_result._set_fitting_time(...)` / `fit_result._set_iterations(...)` +setters. The other 17 rows are moved-but-keep-the-name relocations (e.g. `analysis.minimizer.gelman_rubin_max` → `analysis.fit_result.gelman_rubin_max`). -P2.1 was rewritten to point at the same migration table and to -include both setter-rename examples — moved-but-kept and collapsed — -so test fixtures get the right setter on each side. +P2.1 was rewritten to point at the same migration table and to include +both setter-rename examples — moved-but-kept and collapsed — so test +fixtures get the right setter on each side. **Plan section:** P1.15 (migration table added), P2.1 (examples updated). ### Finding 2 — `_reset_result_descriptors()` not yet defined on `FitResultBase` -**Verdict: agree — Phase 1 wired a call to a method that did not -exist yet.** +**Verdict: agree — Phase 1 wired a call to a method that did not exist +yet.** The method lives on `MinimizerCategoryBase` ([`minimizer/base.py:69-74`](../../../src/easydiffraction/analysis/categories/minimizer/base.py)) -and the existing `FitResult` class has no equivalent. Adding it only -in P1.2 / P1.3 (on the family classes) wouldn't help, because P1.6 -calls it on the union type `FitResultBase`. If P1.6 ran before the -helper was added, every `_clear_persisted_fit_state` reset would -raise `AttributeError`. +and the existing `FitResult` class has no equivalent. Adding it only in +P1.2 / P1.3 (on the family classes) wouldn't help, because P1.6 calls it +on the union type `FitResultBase`. If P1.6 ran before the helper was +added, every `_clear_persisted_fit_state` reset would raise +`AttributeError`. **Action taken.** Extended P1.1 to add two class-level hooks on `FitResultBase` during the rename: -- `_result_descriptor_names: ClassVar[tuple[str, ...]]` initialised - to the existing common fields (`success`, `message`, `iterations`, +- `_result_descriptor_names: ClassVar[tuple[str, ...]]` initialised to + the existing common fields (`success`, `message`, `iterations`, `fitting_time`, `reduced_chi_square`, `result_kind`). - `_reset_result_descriptors()` implemented exactly as on `MinimizerCategoryBase` (walk `_result_descriptor_names`, reset to `_value_spec.default_value()`). -The family classes (P1.2 / P1.3) extend `_result_descriptor_names` -with their own field names so the inherited helper resets the full -descriptor set on the active paired class. The plan now ensures the -helper is in place before P1.6 wires the swap and reset retargeting. +The family classes (P1.2 / P1.3) extend `_result_descriptor_names` with +their own field names so the inherited helper resets the full descriptor +set on the active paired class. The plan now ensures the helper is in +place before P1.6 wires the swap and reset retargeting. -**Plan section:** P1.1 (added hooks paragraph), P1.6 (already -correct now that the helper is guaranteed present). +**Plan section:** P1.1 (added hooks paragraph), P1.6 (already correct +now that the helper is guaranteed present). ### Finding 3 — CIF ordering steps contradicted each other @@ -86,27 +84,25 @@ putting `fit_result` directly after `minimizer`. P1.12 said following P1.11 literally would make pre-fit projects emit a default `_fit_result.*` block — a regression in CIF compactness. -**Action taken.** Rewrote both steps to pick the conditional -contract: +**Action taken.** Rewrote both steps to pick the conditional contract: - P1.11 now says the read-side order is **already correct** because - `_set_minimizer_type` runs before `analysis.minimizer.from_cif` - and the paired `fit_result` swap fires inside - `_set_minimizer_type` after P1.6. The emit-side - `_serializable_categories()` / `_fit_state_categories()` shape is - preserved (conditional inclusion); explicit "do not promote - `fit_result` to unconditional" instruction added. -- P1.12 now confirms that - `_fit_state_categories()` returns the paired instance - automatically once P1.6 wires the construction — no method body - change needed. + `_set_minimizer_type` runs before `analysis.minimizer.from_cif` and + the paired `fit_result` swap fires inside `_set_minimizer_type` after + P1.6. The emit-side `_serializable_categories()` / + `_fit_state_categories()` shape is preserved (conditional inclusion); + explicit "do not promote `fit_result` to unconditional" instruction + added. +- P1.12 now confirms that `_fit_state_categories()` returns the paired + instance automatically once P1.6 wires the construction — no method + body change needed. **Plan section:** P1.11 (rewritten), P1.12 (rewritten). ## Verification -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. +This is a static reply only. No `pixi run`, lint, build, formatter, or +test command was executed. ## Summary of files touched by this reply diff --git a/docs/dev/plans/minimizer-input-output-split_reply-3.md b/docs/dev/plans/minimizer-input-output-split_reply-3.md index 8fb5063e2..166593e45 100644 --- a/docs/dev/plans/minimizer-input-output-split_reply-3.md +++ b/docs/dev/plans/minimizer-input-output-split_reply-3.md @@ -15,45 +15,44 @@ This reply follows **Verdict: partial — intent was correct in reply 2, wording could be misread.** -The reviewer acknowledges review 2 F1 and F2 are addressed and -re-raises only the CIF-ordering concern. Reading the post-reply-2 -text, P1.11 did already state the conditional contract explicitly -("`self.fit_result` is **conditionally** included only when persisted -fit state exists … Do not promote `fit_result` to an unconditional -category"). However, the same step opened with a paragraph about the -**read** side that mentioned "after `_minimizer.*`" and the reviewer -appears to have carried that ordering language across into the -**emit** side, where it is exactly what we do not want. +The reviewer acknowledges review 2 F1 and F2 are addressed and re-raises +only the CIF-ordering concern. Reading the post-reply-2 text, P1.11 did +already state the conditional contract explicitly ("`self.fit_result` is +**conditionally** included only when persisted fit state exists … Do not +promote `fit_result` to an unconditional category"). However, the same +step opened with a paragraph about the **read** side that mentioned +"after `_minimizer.*`" and the reviewer appears to have carried that +ordering language across into the **emit** side, where it is exactly +what we do not want. **Action taken.** Rewrote P1.11 with the no-reordering rule as the **first** sentence of the step, before any read/emit detail: > **No category-list reordering is performed in this step.** Neither > `Analysis._serializable_categories()` nor -> `Analysis._fit_state_categories()` is restructured. `fit_result` -> stays conditionally included by `_fit_state_categories()` only -> when `self._has_persisted_fit_state()` is true — exactly as today. -> Pre-fit projects continue to emit no `_fit_result.*` block. - -The remaining bullets list only content-level changes (which -descriptors flow through emit/read, the legacy-tag rejection -message), and each is explicitly framed as "no reordering, no new -call" or "no code change is required here". The earlier phrasing -about the read order being "already correct" was retained but moved -below the no-reordering rule and reframed as a confirmation rather -than a directive. - -P1.12 was already clear that `_fit_state_categories()` is unchanged -(it just walks the paired instance after P1.6 wires it). No further -P1.12 edits. - -**Plan section:** P1.11 (rewritten with the no-reordering rule as -the first paragraph; intent unchanged but explicit). +> `Analysis._fit_state_categories()` is restructured. `fit_result` stays +> conditionally included by `_fit_state_categories()` only when +> `self._has_persisted_fit_state()` is true — exactly as today. Pre-fit +> projects continue to emit no `_fit_result.*` block. + +The remaining bullets list only content-level changes (which descriptors +flow through emit/read, the legacy-tag rejection message), and each is +explicitly framed as "no reordering, no new call" or "no code change is +required here". The earlier phrasing about the read order being "already +correct" was retained but moved below the no-reordering rule and +reframed as a confirmation rather than a directive. + +P1.12 was already clear that `_fit_state_categories()` is unchanged (it +just walks the paired instance after P1.6 wires it). No further P1.12 +edits. + +**Plan section:** P1.11 (rewritten with the no-reordering rule as the +first paragraph; intent unchanged but explicit). ## Verification -This is a static reply only. No `pixi run`, lint, build, formatter, -or test command was executed. +This is a static reply only. No `pixi run`, lint, build, formatter, or +test command was executed. ## Summary of files touched by this reply diff --git a/docs/dev/plans/minimizer-input-output-split_review-1.md b/docs/dev/plans/minimizer-input-output-split_review-1.md index 51591fd50..e081e021a 100644 --- a/docs/dev/plans/minimizer-input-output-split_review-1.md +++ b/docs/dev/plans/minimizer-input-output-split_review-1.md @@ -2,13 +2,72 @@ ## Findings -1. **High — New fits will reset `fit_result` back to the common-only class unless `_clear_persisted_fit_state()` is updated.** P1.6 wires the initial and swapped `_fit_result` instances from the active minimizer's `_fit_result_class` (`minimizer-input-output-split.md:226-237`), then P1.7/P1.8 route family-specific `_set_*` calls to `self.fit_result` (`:241-256`). But the current fit path calls `_capture_fit_parameter_state()`, which calls `_clear_persisted_fit_state()` before storing results; that method currently replaces `_fit_result` with the common `FitResult()` class and calls `_clear_minimizer_result_projection()` (`src/easydiffraction/analysis/analysis.py:1204-1219`). If the plan does not update that reset path, the first post-split fit will discard the paired `LeastSquaresFitResult` / `BayesianFitResult` instance before P1.7/P1.8 write family-specific fields. Add an explicit step to reset `_fit_result` with `self.minimizer._fit_result_class()`, attach `_parent`, and either remove or retarget `_clear_minimizer_result_projection()` to the new fit-result object. +1. **High — New fits will reset `fit_result` back to the common-only + class unless `_clear_persisted_fit_state()` is updated.** P1.6 wires + the initial and swapped `_fit_result` instances from the active + minimizer's `_fit_result_class` + (`minimizer-input-output-split.md:226-237`), then P1.7/P1.8 route + family-specific `_set_*` calls to `self.fit_result` (`:241-256`). But + the current fit path calls `_capture_fit_parameter_state()`, which + calls `_clear_persisted_fit_state()` before storing results; that + method currently replaces `_fit_result` with the common `FitResult()` + class and calls `_clear_minimizer_result_projection()` + (`src/easydiffraction/analysis/analysis.py:1204-1219`). If the plan + does not update that reset path, the first post-split fit will + discard the paired `LeastSquaresFitResult` / `BayesianFitResult` + instance before P1.7/P1.8 write family-specific fields. Add an + explicit step to reset `_fit_result` with + `self.minimizer._fit_result_class()`, attach `_parent`, and either + remove or retarget `_clear_minimizer_result_projection()` to the new + fit-result object. -2. **High — P1.2 regresses pre-fit LSQ output defaults from unknown to false/empty-string.** P1.2 says LSQ bool outputs default to `False` and string outputs default to `''` (`minimizer-input-output-split.md:177-188`). The existing minimizer result descriptors deliberately default string, integer-like, and bool result fields to `None` so CIF emits `?` before any fit and users do not read `false`, `0`, or an empty string as an actual fit result (`src/easydiffraction/analysis/categories/minimizer/lsq_base.py:113-126`, `:145-175`). Moving the fields to `LeastSquaresFitResult` should preserve that no-fit semantics. Please change P1.2 to keep LSQ string and bool output defaults as `None` / `allow_none=True`, except for any field where the ADR explicitly chooses a concrete pre-fit default. +2. **High — P1.2 regresses pre-fit LSQ output defaults from unknown to + false/empty-string.** P1.2 says LSQ bool outputs default to `False` + and string outputs default to `''` + (`minimizer-input-output-split.md:177-188`). The existing minimizer + result descriptors deliberately default string, integer-like, and + bool result fields to `None` so CIF emits `?` before any fit and + users do not read `false`, `0`, or an empty string as an actual fit + result + (`src/easydiffraction/analysis/categories/minimizer/lsq_base.py:113-126`, + `:145-175`). Moving the fields to `LeastSquaresFitResult` should + preserve that no-fit semantics. Please change P1.2 to keep LSQ string + and bool output defaults as `None` / `allow_none=True`, except for + any field where the ADR explicitly chooses a concrete pre-fit + default. -3. **Medium — The import/export surfaces outside `fit_result/__init__.py` are missing from the plan.** The plan updates only `src/easydiffraction/analysis/categories/fit_result/__init__.py` for the new fit-result classes (`minimizer-input-output-split.md:109-114`, `:202-211`). The repo also explicitly imports `FitResult` from `src/easydiffraction/analysis/__init__.py` and `src/easydiffraction/analysis/categories/__init__.py`, and `analysis.py` imports and annotates it directly (`src/easydiffraction/analysis/__init__.py:14-15`, `src/easydiffraction/analysis/categories/__init__.py:14`, `src/easydiffraction/analysis/analysis.py:18`, `:432`). Once the old `FitResult` re-export is removed at P1.16 (`minimizer-input-output-split.md:165-175`), these surfaces can break or keep exporting the wrong class. Add explicit steps to update the package-level imports and all direct `FitResult` type annotations/constructions. +3. **Medium — The import/export surfaces outside + `fit_result/__init__.py` are missing from the plan.** The plan + updates only + `src/easydiffraction/analysis/categories/fit_result/__init__.py` for + the new fit-result classes + (`minimizer-input-output-split.md:109-114`, `:202-211`). The repo + also explicitly imports `FitResult` from + `src/easydiffraction/analysis/__init__.py` and + `src/easydiffraction/analysis/categories/__init__.py`, and + `analysis.py` imports and annotates it directly + (`src/easydiffraction/analysis/__init__.py:14-15`, + `src/easydiffraction/analysis/categories/__init__.py:14`, + `src/easydiffraction/analysis/analysis.py:18`, `:432`). Once the old + `FitResult` re-export is removed at P1.16 + (`minimizer-input-output-split.md:165-175`), these surfaces can break + or keep exporting the wrong class. Add explicit steps to update the + package-level imports and all direct `FitResult` type + annotations/constructions. -4. **Medium — The fit-result factory plan is internally inconsistent.** The "Created" list and P1.4 call `src/easydiffraction/analysis/categories/fit_result/factory.py` a new file, but it already exists (`minimizer-input-output-split.md:99-101`, `:202-211`; `src/easydiffraction/analysis/categories/fit_result/factory.py:1-15`). More importantly, P1.4 says the factory is used only by `Analysis._swap_minimizer`, while P1.6 says `_replace_minimizer` constructs `new_minimizer._fit_result_class()` directly (`minimizer-input-output-split.md:202-211`, `:226-237`). Pick one mechanism. If `_fit_result_class` is authoritative, the factory is just a registration/testing helper and the plan should say that; if the factory is authoritative, P1.6 should use it explicitly. +4. **Medium — The fit-result factory plan is internally inconsistent.** + The "Created" list and P1.4 call + `src/easydiffraction/analysis/categories/fit_result/factory.py` a new + file, but it already exists + (`minimizer-input-output-split.md:99-101`, `:202-211`; + `src/easydiffraction/analysis/categories/fit_result/factory.py:1-15`). + More importantly, P1.4 says the factory is used only by + `Analysis._swap_minimizer`, while P1.6 says `_replace_minimizer` + constructs `new_minimizer._fit_result_class()` directly + (`minimizer-input-output-split.md:202-211`, `:226-237`). Pick one + mechanism. If `_fit_result_class` is authoritative, the factory is + just a registration/testing helper and the plan should say that; if + the factory is authoritative, P1.6 should use it explicitly. ## Checks diff --git a/docs/dev/plans/minimizer-input-output-split_review-2.md b/docs/dev/plans/minimizer-input-output-split_review-2.md index 53232faaa..b106d91e0 100644 --- a/docs/dev/plans/minimizer-input-output-split_review-2.md +++ b/docs/dev/plans/minimizer-input-output-split_review-2.md @@ -2,11 +2,68 @@ ## Findings -1. **High — Removed output names are still described as one-to-one `fit_result` replacements.** P1.15 and P2.1 say to replace every `analysis.minimizer.` with `analysis.fit_result.`, and even give `analysis.minimizer._set_runtime_seconds(...)` → `analysis.fit_result._set_runtime_seconds(...)` as the fixture migration example (`minimizer-input-output-split.md:426-474`). That replacement is wrong for the fields the ADR intentionally collapses into existing common fit-result names: `runtime_seconds` becomes `fit_result.fitting_time`, and `iterations_performed` becomes `fit_result.iterations`. The current common category already exposes `_set_fitting_time` and `_set_iterations`, not `_set_runtime_seconds` / `_set_iterations_performed` (`src/easydiffraction/analysis/categories/fit_result/default.py:113-126`). If the plan is followed literally, tutorials/tests will be migrated to attributes and setters that do not exist after P1.2/P1.3. Please add an explicit migration map for renamed outputs, at minimum `runtime_seconds` → `fitting_time` and `iterations_performed` → `iterations`, and say the existing common projection writer owns those values rather than moving the old minimizer `_set_runtime_seconds` call verbatim. +1. **High — Removed output names are still described as one-to-one + `fit_result` replacements.** P1.15 and P2.1 say to replace every + `analysis.minimizer.` with + `analysis.fit_result.`, and even give + `analysis.minimizer._set_runtime_seconds(...)` → + `analysis.fit_result._set_runtime_seconds(...)` as the fixture + migration example (`minimizer-input-output-split.md:426-474`). That + replacement is wrong for the fields the ADR intentionally collapses + into existing common fit-result names: `runtime_seconds` becomes + `fit_result.fitting_time`, and `iterations_performed` becomes + `fit_result.iterations`. The current common category already exposes + `_set_fitting_time` and `_set_iterations`, not `_set_runtime_seconds` + / `_set_iterations_performed` + (`src/easydiffraction/analysis/categories/fit_result/default.py:113-126`). + If the plan is followed literally, tutorials/tests will be migrated + to attributes and setters that do not exist after P1.2/P1.3. Please + add an explicit migration map for renamed outputs, at minimum + `runtime_seconds` → `fitting_time` and `iterations_performed` → + `iterations`, and say the existing common projection writer owns + those values rather than moving the old minimizer + `_set_runtime_seconds` call verbatim. -2. **High — P1.6 retargets reset calls to a method that no fit-result class is told to implement.** P1.6 says `_clear_minimizer_result_projection()` should become `_clear_fit_result_projection()` and call `self.fit_result._reset_result_descriptors()` (`minimizer-input-output-split.md:280-294`). Today `_reset_result_descriptors()` exists only on `MinimizerCategoryBase` (`src/easydiffraction/analysis/categories/minimizer/base.py:69-74`), while the current `FitResult` class has no equivalent helper (`src/easydiffraction/analysis/categories/fit_result/default.py:20-135`). P1.2/P1.3 ask the new family classes to declare `_result_descriptor_names`, and P2.2 later mentions testing `FitResultBase._reset_result_descriptors`, but no Phase 1 implementation step actually adds that method before P1.6 starts calling it. Add an explicit P1.1/P1.2 instruction to put `_result_descriptor_names` and `_reset_result_descriptors()` on `FitResultBase` (or a shared helper) before retargeting the clear path. +2. **High — P1.6 retargets reset calls to a method that no fit-result + class is told to implement.** P1.6 says + `_clear_minimizer_result_projection()` should become + `_clear_fit_result_projection()` and call + `self.fit_result._reset_result_descriptors()` + (`minimizer-input-output-split.md:280-294`). Today + `_reset_result_descriptors()` exists only on `MinimizerCategoryBase` + (`src/easydiffraction/analysis/categories/minimizer/base.py:69-74`), + while the current `FitResult` class has no equivalent helper + (`src/easydiffraction/analysis/categories/fit_result/default.py:20-135`). + P1.2/P1.3 ask the new family classes to declare + `_result_descriptor_names`, and P2.2 later mentions testing + `FitResultBase._reset_result_descriptors`, but no Phase 1 + implementation step actually adds that method before P1.6 starts + calling it. Add an explicit P1.1/P1.2 instruction to put + `_result_descriptor_names` and `_reset_result_descriptors()` on + `FitResultBase` (or a shared helper) before retargeting the clear + path. -3. **Medium — The CIF ordering steps contradict each other and may change pre-fit emission.** P1.11 says `_fit_result.*` order is enforced by `Analysis._serializable_categories()` putting `self.fit_result` directly after `self.minimizer` (`minimizer-input-output-split.md:369-376`), but P1.12 immediately says `_serializable_categories` already includes `self.fit_result` via `_fit_state_categories` (`minimizer-input-output-split.md:386-392`). In current code, `fit_result` is appended only when persisted fit state exists (`src/easydiffraction/analysis/analysis.py:852-873`, `:1182-1186`), and CIF read order is already handled separately by `analysis_from_cif`: minimizer type is restored before `analysis.fit_result.from_cif(block)` runs (`src/easydiffraction/io/cif/serialize.py:552-590`). If an implementer follows P1.11 literally by moving `fit_result` directly into the main category list after `minimizer`, pre-fit CIFs may start emitting default `_fit_result.*` fields. Please make the plan choose one contract: keep `fit_result` conditional inside `_fit_state_categories` and describe the ordering as "after minimizer when persisted", or explicitly require a guarded direct insertion that preserves the current no-persisted-fit-state behavior. +3. **Medium — The CIF ordering steps contradict each other and may + change pre-fit emission.** P1.11 says `_fit_result.*` order is + enforced by `Analysis._serializable_categories()` putting + `self.fit_result` directly after `self.minimizer` + (`minimizer-input-output-split.md:369-376`), but P1.12 immediately + says `_serializable_categories` already includes `self.fit_result` + via `_fit_state_categories` + (`minimizer-input-output-split.md:386-392`). In current code, + `fit_result` is appended only when persisted fit state exists + (`src/easydiffraction/analysis/analysis.py:852-873`, `:1182-1186`), + and CIF read order is already handled separately by + `analysis_from_cif`: minimizer type is restored before + `analysis.fit_result.from_cif(block)` runs + (`src/easydiffraction/io/cif/serialize.py:552-590`). If an + implementer follows P1.11 literally by moving `fit_result` directly + into the main category list after `minimizer`, pre-fit CIFs may start + emitting default `_fit_result.*` fields. Please make the plan choose + one contract: keep `fit_result` conditional inside + `_fit_state_categories` and describe the ordering as "after minimizer + when persisted", or explicitly require a guarded direct insertion + that preserves the current no-persisted-fit-state behavior. ## Checks diff --git a/docs/dev/plans/minimizer-input-output-split_review-4.md b/docs/dev/plans/minimizer-input-output-split_review-4.md index 4306ad139..bbc481e3b 100644 --- a/docs/dev/plans/minimizer-input-output-split_review-4.md +++ b/docs/dev/plans/minimizer-input-output-split_review-4.md @@ -9,17 +9,17 @@ Reviewed ADR: This review follows [`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). -Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, -or any build/verification command was executed**. This is a static -read of the 31 commits on `minimizer-input-output-split` against +Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or +any build/verification command was executed**. This is a static read of +the 31 commits on `minimizer-input-output-split` against `origin/develop`, plus the new accepted ADR and amended ADRs. ## Scope The branch carries: -- Phase 1 of the input/output split plan (17 commits, P1.1–P1.17 + - ADR promotion + an unrelated dependency cleanup). +- Phase 1 of the input/output split plan (17 commits, P1.1–P1.17 + ADR + promotion + an unrelated dependency cleanup). - The accepted ADR [`minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) (promoted from suggestions at P1.16). @@ -27,35 +27,34 @@ The branch carries: [`iucr-cif-tag-alignment.md`](../adrs/suggestions/iucr-cif-tag-alignment.md) capturing a separate IUCr-alignment proposal for future work. -Phase 2 (P2.1–P2.6: test migration, fix, check, unit/integration/ -script tests) has not started yet; this review confirms the gate. +Phase 2 (P2.1–P2.6: test migration, fix, check, unit/integration/ script +tests) has not started yet; this review confirms the gate. ## Summary -Phase 1 implementation matches the plan. Each plan step maps 1:1 to -a commit. The new `fit_result` family classes are in place +Phase 1 implementation matches the plan. Each plan step maps 1:1 to a +commit. The new `fit_result` family classes are in place ([base.py](src/easydiffraction/analysis/categories/fit_result/base.py), [lsq.py](src/easydiffraction/analysis/categories/fit_result/lsq.py), [bayesian.py](src/easydiffraction/analysis/categories/fit_result/bayesian.py)); `Analysis._swap_minimizer` wires the paired instance atomically; -`_clear_persisted_fit_state` reinstates the paired class on reset; -the LSQ and Bayesian minimizer bases no longer carry their output +`_clear_persisted_fit_state` reinstates the paired class on reset; the +LSQ and Bayesian minimizer bases no longer carry their output descriptors; CIF serialisation routes outputs to `_fit_result.*` and -rejects the legacy `_minimizer.` tags loudly; the five -affected ADRs are amended; the display facade now prints a "Settings -used" block above the existing fit-result tables; tutorials are -migrated. +rejects the legacy `_minimizer.` tags loudly; the five affected +ADRs are amended; the display facade now prints a "Settings used" block +above the existing fit-result tables; tutorials are migrated. All stale-reference greps return clean: -- `analysis.minimizer.` in `src/`, `docs/docs/tutorials/`, - `tests/` — empty. +- `analysis.minimizer.` in `src/`, + `docs/docs/tutorials/`, `tests/` — empty. - `_minimizer.` in `src/`, `docs/docs/tutorials/`, `tests/` — only present inside `serialize.py` as the legacy-tag rejection list (intentional). -Five small observations follow. None block Phase 2; F1 and F2 are -worth deciding consciously rather than letting them carry forward. +Five small observations follow. None block Phase 2; F1 and F2 are worth +deciding consciously rather than letting them carry forward. ## Findings @@ -74,10 +73,10 @@ The new family classes and parts of [bayesian.py](src/easydiffraction/analysis/categories/fit_result/bayesian.py)) deliberately use `default=None, allow_none=True` for every numeric, -string, and bool result field — with explicit docstrings explaining -why: a CIF written before any fit should emit `?` rather than `0` / -`false` / empty-string, because the scientist audience reads `0` -and `false` as a real fit result. +string, and bool result field — with explicit docstrings explaining why: +a CIF written before any fit should emit `?` rather than `0` / `false` / +empty-string, because the scientist audience reads `0` and `false` as a +real fit result. Pre-fit CIFs under the current layout therefore emit: @@ -86,25 +85,25 @@ _fit_result.success false # reads as "fit ran and failed" _fit_result.iterations 0 # reads as "fit ran for 0 iterations" ``` -The plan inherited these defaults from the pre-split common class -(see P1.1 — "keep current common fields") so this is not a -regression introduced by this PR. But the divergence is now visible -side-by-side in the same category hierarchy: hovering on -`fit_result.iterations` lands in `FitResultBase` (defaults to `0`), -hovering on `fit_result.n_parameters` lands in `LeastSquaresFitResult` -(defaults to `None`), with no principled reason for the difference. +The plan inherited these defaults from the pre-split common class (see +P1.1 — "keep current common fields") so this is not a regression +introduced by this PR. But the divergence is now visible side-by-side in +the same category hierarchy: hovering on `fit_result.iterations` lands +in `FitResultBase` (defaults to `0`), hovering on +`fit_result.n_parameters` lands in `LeastSquaresFitResult` (defaults to +`None`), with no principled reason for the difference. -Bayesian-side mirror: `point_estimate_name` defaults to -`'best_sample'` and `sampler_completed` defaults to `False` +Bayesian-side mirror: `point_estimate_name` defaults to `'best_sample'` +and `sampler_completed` defaults to `False` ([bayesian.py:51-69](src/easydiffraction/analysis/categories/fit_result/bayesian.py:51)), while `acceptance_rate_mean` and friends default to `None`. Same inconsistency. Suggested follow-up: align `FitResultBase` common fields (and the Bayesian `point_estimate_name` / `sampler_completed`) with the -`None`/`allow_none=True` convention. Small diff; the existing -`_set_*` callers already pass real values when a fit runs. Defer to -a follow-on if not in scope for this PR. +`None`/`allow_none=True` convention. Small diff; the existing `_set_*` +callers already pass real values when a fit runs. Defer to a follow-on +if not in scope for this PR. ### F2 — `_clear_persisted_fit_state` resets descriptors then immediately replaces the instance @@ -121,24 +120,23 @@ def _clear_persisted_fit_state(self) -> None: ... ``` -`_clear_fit_result_projection()` walks -`_result_descriptor_names` and resets each to its declared default -on the **old** instance, which is then thrown away on the next -line. A fresh instance has defaults by construction; the reset call -is dead work. +`_clear_fit_result_projection()` walks `_result_descriptor_names` and +resets each to its declared default on the **old** instance, which is +then thrown away on the next line. A fresh instance has defaults by +construction; the reset call is dead work. Two clean shapes are possible: - Drop the `_clear_fit_result_projection()` call and rely on the fresh-instance construction. Simpler. - Drop the instance replacement and rely on - `_clear_fit_result_projection()`. Then the paired-class invariant - is preserved only as long as `minimizer.type` does not change - between fits, which is true today but is the kind of invariant a - future refactor could quietly break. + `_clear_fit_result_projection()`. Then the paired-class invariant is + preserved only as long as `minimizer.type` does not change between + fits, which is true today but is the kind of invariant a future + refactor could quietly break. -The first is the smaller diff. Cosmetic; not a correctness issue -because both paths produce the same end state. +The first is the smaller diff. Cosmetic; not a correctness issue because +both paths produce the same end state. ### F3 — `_settings_used_rows` carries an unreachable `else` branch @@ -162,50 +160,47 @@ Every entry in `_setting_descriptor_names` resolves to a [`lsq_base.py`](src/easydiffraction/analysis/categories/minimizer/lsq_base.py) and [`bayesian_base.py`](src/easydiffraction/analysis/categories/minimizer/bayesian_base.py)). -The `else` branch is defensive coding against an unreachable state. -Per +The `else` branch is defensive coding against an unreachable state. Per [`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) -→ **Change Discipline** "No defensive checks for unlikely edge -cases." +→ **Change Discipline** "No defensive checks for unlikely edge cases." Suggested follow-up: drop the `else` branch and the -`isinstance(descriptor, GenericDescriptorBase)` guard. Optional — -this is one screen of code and the fallback is harmless. +`isinstance(descriptor, GenericDescriptorBase)` guard. Optional — this +is one screen of code and the fallback is harmless. ### F4 — Phase 2 not started; one test file expected to break `grep -rlE 'minimizer\.' tests/` returns one hit: - [`test_lsq_base.py`](tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py) - still asserts on `minimizer.objective_name`, `minimizer.objective_value`, - `minimizer.iterations_performed`, etc. — fields that no longer - exist on the minimizer hierarchy after P1.9. + still asserts on `minimizer.objective_name`, + `minimizer.objective_value`, `minimizer.iterations_performed`, etc. — + fields that no longer exist on the minimizer hierarchy after P1.9. This is exactly what P2.1 ("Migrate existing tests off the removed -minimizer output fields") covers, and the plan explicitly defers -test migration to Phase 2. Confirming the gate: Phase 2 must run -before the branch is merge-ready. The migration table in P1.15 / -P2.1 is the right reference for the rewrites. +minimizer output fields") covers, and the plan explicitly defers test +migration to Phase 2. Confirming the gate: Phase 2 must run before the +branch is merge-ready. The migration table in P1.15 / P2.1 is the right +reference for the rewrites. Informational; the gate matches the plan's Phase 1 → Phase 2 split. ### F5 — Unrelated dependency cleanup landed alongside Phase 1 Commit `c5da0b0fc Remove essdiffraction dependency` touches -[`pixi.lock`](pixi.lock) (-1124 lines), [`pyproject.toml`](pyproject.toml) -(-1 line), and +[`pixi.lock`](pixi.lock) (-1124 lines), +[`pyproject.toml`](pyproject.toml) (-1 line), and [`scipp-analysis/dream/test_package_import.py`](tests/integration/scipp-analysis/dream/test_package_import.py) (-14 lines). It is unrelated to the input/output split work. Per [`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) -→ **Change Discipline** "Don't add features or refactor unless -asked" and the project's "one focused PR" convention, this would -normally land on its own. Bundling it here makes the PR description -slightly less accurate (the user-facing surface is unchanged by the -essdiffraction removal). If the next reviewer flags it, the cleanest -response is to split it out as a separate PR; otherwise it is small -enough to keep. +→ **Change Discipline** "Don't add features or refactor unless asked" +and the project's "one focused PR" convention, this would normally land +on its own. Bundling it here makes the PR description slightly less +accurate (the user-facing surface is unchanged by the essdiffraction +removal). If the next reviewer flags it, the cleanest response is to +split it out as a separate PR; otherwise it is small enough to keep. Informational only. @@ -216,14 +211,13 @@ Informational only. `runtime-fit-results.md`, `switchable-category-owned-selectors.md`, `display-ux.md`. - `docs/dev/adrs/index.md` lists the new ADR under Accepted. -- The plan's `_review-N` / `_reply-N` siblings remain alongside the - plan (per the project's plan-vs-ADR retention precedent — plans - keep their deliberation files until the plan itself is deleted on - merge). +- The plan's `_review-N` / `_reply-N` siblings remain alongside the plan + (per the project's plan-vs-ADR retention precedent — plans keep their + deliberation files until the plan itself is deleted on merge). - The IUCr alignment idea is parked as [`iucr-cif-tag-alignment.md`](../adrs/suggestions/iucr-cif-tag-alignment.md) - under suggestions, with a §Status Note explaining it is captured - for future work, not in scope for the current PR. + under suggestions, with a §Status Note explaining it is captured for + future work, not in scope for the current PR. - Tutorials regenerated per the P1.15 migration table. ## Verification commands run for this review @@ -238,25 +232,23 @@ git grep -nE '_minimizer\.(runtime_seconds|iterations_performed|objective_value| grep -rlE 'minimizer\.' tests/ ``` -The first two return empty against `src/`, `docs/docs/tutorials/`, -and `tests/`. The third returns only `serialize.py` (the -legacy-tag rejection list — intentional). The fourth returns one -file (the Phase 2 migration target). +The first two return empty against `src/`, `docs/docs/tutorials/`, and +`tests/`. The third returns only `serialize.py` (the legacy-tag +rejection list — intentional). The fourth returns one file (the Phase 2 +migration target). ## Recommended next steps 1. **Decide F1 (`FitResultBase` defaults).** Either align the common - header with the family-class None-defaults convention, or - document the divergence as intentional in the accepted ADR and - move on. -2. **Address F2 (redundant reset) and F3 (unreachable else).** Both - are one-line cleanups; either bundle into Phase 2 cleanup or - leave for a follow-on. -3. **Start Phase 2.** P2.1 migrates the one stranded test file; - P2.2 adds unit tests for the new fit_result classes (none exist - yet under `tests/unit/easydiffraction/analysis/categories/fit_result/`); - P2.3–P2.6 run `pixi run fix`, `check`, and the three test - suites. -4. **Optional: split out `c5da0b0fc` into its own PR** for the - cleanest history; otherwise note it in the PR description so a - reviewer is not surprised by the `pixi.lock` churn. + header with the family-class None-defaults convention, or document + the divergence as intentional in the accepted ADR and move on. +2. **Address F2 (redundant reset) and F3 (unreachable else).** Both are + one-line cleanups; either bundle into Phase 2 cleanup or leave for a + follow-on. +3. **Start Phase 2.** P2.1 migrates the one stranded test file; P2.2 + adds unit tests for the new fit_result classes (none exist yet under + `tests/unit/easydiffraction/analysis/categories/fit_result/`); + P2.3–P2.6 run `pixi run fix`, `check`, and the three test suites. +4. **Optional: split out `c5da0b0fc` into its own PR** for the cleanest + history; otherwise note it in the PR description so a reviewer is not + surprised by the `pixi.lock` churn. diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 5173a3000..9a01d8385 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -15,7 +15,6 @@ from easydiffraction.analysis.categories.constraints.factory import ConstraintsFactory from easydiffraction.analysis.categories.fit_parameter_correlations import FitParameterCorrelations from easydiffraction.analysis.categories.fit_parameters import FitParameters -from easydiffraction.analysis.categories.fit_result import FitResultBase from easydiffraction.analysis.categories.fitting_mode import FittingMode from easydiffraction.analysis.categories.fitting_mode import FittingModeFactory from easydiffraction.analysis.categories.joint_fit import JointFitCollection @@ -56,6 +55,7 @@ from easydiffraction.utils.utils import render_table if TYPE_CHECKING: + from easydiffraction.analysis.categories.fit_result import FitResultBase from easydiffraction.analysis.categories.minimizer.base import MinimizerCategoryBase from easydiffraction.core.posterior import PosteriorParameterSummary @@ -736,7 +736,7 @@ def _restore_fit_results_from_projection(self) -> object | None: starting_parameters=list(restored_parameters), fitting_time=fitting_time, sampler_name=sampler_name, - point_estimate_name=self.fit_result.point_estimate_name.value, + point_estimate_name=self.fit_result.point_estimate_name.value or 'best_sample', posterior_samples=posterior_samples, posterior_parameter_summaries=self._restored_posterior_summaries(), posterior_predictive=self._restored_predictive_summaries(), @@ -764,8 +764,8 @@ def _restore_fit_results_from_projection(self) -> object | None: sampler_completed=bool(self.fit_result.sampler_completed.value), best_log_posterior=self.fit_result.best_log_posterior.value, ) - restored_results.message = self.fit_result.message.value - restored_results.iterations = int(self.fit_result.iterations.value) + restored_results.message = self.fit_result.message.value or '' + restored_results.iterations = _int_or_none(self.fit_result.iterations.value) or 0 self.fit_results = restored_results return restored_results @@ -790,8 +790,8 @@ def _restore_fit_results_from_projection(self) -> object | None: iterations_performed=_int_or_none(self.fit_result.iterations.value), exit_reason=self.fit_result.exit_reason.value, ) - restored_results.message = self.fit_result.message.value - restored_results.iterations = int(self.fit_result.iterations.value) + restored_results.message = self.fit_result.message.value or '' + restored_results.iterations = _int_or_none(self.fit_result.iterations.value) or 0 restored_results.chi_square = self.fit_result.objective_value.value self.fit_results = restored_results return restored_results @@ -1206,7 +1206,6 @@ def _fit_state_categories(self) -> list[object]: def _clear_persisted_fit_state(self) -> None: """Reset all persisted fit-state categories before a new fit.""" - self._clear_fit_result_projection() self._fit_parameters = FitParameters() self._fit_result._parent = None self._fit_result = self.minimizer._fit_result_class() @@ -1216,7 +1215,9 @@ def _clear_persisted_fit_state(self) -> None: self._persisted_fit_state_sidecar = {} def _clear_fit_result_projection(self) -> None: - """Reset result-only fields on the active fit-result category.""" + """ + Reset result-only fields on the active fit-result category. + """ self.fit_result._reset_result_descriptors() def _capture_fit_parameter_state(self, parameters: list[Parameter]) -> None: @@ -1947,33 +1948,14 @@ def _fit_single( self.fitter.minimizer.tracker._set_shared_display_handle(short_display_handle) try: - for expt_name in expt_names: - if verb is VerbosityEnum.FULL: - console.print( - f"📋 Using experiment 🔬 '{expt_name}' for '{mode.value}' fitting" - ) - - experiment = experiments[expt_name] - self.fitter.fit( - structures, - [experiment], - analysis=self, - verbosity=verb, - use_physical_limits=use_physical_limits, - random_seed=self._resolved_fit_random_seed(random_seed), - ) - - # After fitting, snapshot parameter values before - # they get overwritten by the next experiment's fit - results = self.fitter.results - self._snapshot_params(expt_name, results) - self.fit_results = results - - # Short mode: append one summary row and update in-place - if verb is VerbosityEnum.SHORT: - self._fit_single_update_short_table( - short_rows, expt_name, results, short_display_handle - ) + self._fit_single_experiments( + verb, + structures, + experiments, + use_physical_limits=use_physical_limits, + random_seed=random_seed, + short_state=(short_rows, short_display_handle), + ) finally: self.fitter.minimizer.tracker._set_shared_display_handle(None) @@ -1982,6 +1964,47 @@ def _fit_single( with suppress(Exception): short_display_handle.close() + def _fit_single_experiments( + self, + verb: VerbosityEnum, + structures: object, + experiments: object, + *, + use_physical_limits: bool, + random_seed: int | None, + short_state: tuple[list[list[str]], object], + ) -> None: + """Run the per-experiment loop for single-fit mode.""" + short_rows, short_display_handle = short_state + for expt_name in experiments.names: + if verb is VerbosityEnum.FULL: + console.print( + f"📋 Using experiment 🔬 '{expt_name}' for " + f"'{FitModeEnum.SINGLE.value}' fitting" + ) + + experiment = experiments[expt_name] + self.fitter.fit( + structures, + [experiment], + analysis=self, + verbosity=verb, + use_physical_limits=use_physical_limits, + random_seed=self._resolved_fit_random_seed(random_seed), + ) + + results = self.fitter.results + self._snapshot_params(expt_name, results) + self.fit_results = results + + if verb is VerbosityEnum.SHORT: + self._fit_single_update_short_table( + short_rows, + expt_name, + results, + short_display_handle, + ) + @staticmethod def _fit_single_print_header( verb: VerbosityEnum, diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index 85ff5779f..70231b02f 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -129,22 +129,40 @@ def calculate_pattern( crysfml_dict = self._crysfml_dict(structure, experiment) try: - if experiment.type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: - _, y = cfml_py_utilities.cw_powder_pattern_from_dict(crysfml_dict) - elif experiment.type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: - _, y = cfml_py_utilities.tof_powder_pattern_from_dict(crysfml_dict) - else: - print( - f'[CrysfmlCalculator] Error: ' - f'Unsupported beam mode {experiment.type.beam_mode.value}' - ) - return np.array([]) - y = self._adjust_pattern_length(y, len(experiment.data.x)) + y = self._calculate_adjusted_pattern(crysfml_dict, experiment) except KeyError: print('[CrysfmlCalculator] Error: No calculated data') y = [] return np.asarray(y) + def _calculate_adjusted_pattern( + self, + crysfml_dict: dict[str, object], + experiment: ExperimentBase, + ) -> list[float]: + """Calculate a Crysfml pattern and match experiment length.""" + y = self._calculate_raw_pattern(crysfml_dict, experiment) + if y is None: + return [] + return self._adjust_pattern_length(y, len(experiment.data.x)) + + @staticmethod + def _calculate_raw_pattern( + crysfml_dict: dict[str, object], + experiment: ExperimentBase, + ) -> list[float] | None: + """Calculate a Crysfml pattern without length adjustment.""" + if experiment.type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + _, y = cfml_py_utilities.cw_powder_pattern_from_dict(crysfml_dict) + return y + if experiment.type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + _, y = cfml_py_utilities.tof_powder_pattern_from_dict(crysfml_dict) + return y + print( + f'[CrysfmlCalculator] Error: Unsupported beam mode {experiment.type.beam_mode.value}' + ) + return None + def _adjust_pattern_length( # noqa: PLR6301 self, pattern: list[float], diff --git a/src/easydiffraction/analysis/calculators/pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py index ed0717536..a9c3b3697 100644 --- a/src/easydiffraction/analysis/calculators/pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -19,17 +19,19 @@ from easydiffraction.datablocks.experiment.item.base import ExperimentBase from easydiffraction.datablocks.structure.item.base import Structure + +def _open_pdffit_devnull() -> object: + """Open a durable devnull handle for PDFfit stdout redirection.""" + with Path(os.devnull).open('w', encoding='utf-8') as tmp_devnull: + return os.fdopen(os.dup(tmp_devnull.fileno()), 'w') + + try: from diffpy.pdffit2 import PdfFit from diffpy.pdffit2 import redirect_stdout from diffpy.structure.parsers.p_cif import P_cif as pdffit_cif_parser - # Silence the C++ engine output while keeping the handle open - _pdffit_devnull: object | None - with Path(os.devnull).open('w', encoding='utf-8') as _tmp_devnull: - # Duplicate file descriptor so the handle remains - # valid after the context - _pdffit_devnull = os.fdopen(os.dup(_tmp_devnull.fileno()), 'w') + _pdffit_devnull = _open_pdffit_devnull() redirect_stdout(_pdffit_devnull) # TODO: Add the following print to debug mode # print("✅ 'pdffit' calculation engine is successfully imported.") diff --git a/src/easydiffraction/analysis/categories/fit_result/base.py b/src/easydiffraction/analysis/categories/fit_result/base.py index 2d82bf63a..c94a35b09 100644 --- a/src/easydiffraction/analysis/categories/fit_result/base.py +++ b/src/easydiffraction/analysis/categories/fit_result/base.py @@ -55,19 +55,19 @@ def __init__(self) -> None: self._success = BoolDescriptor( name='success', description='Whether the latest persisted fit-result projection succeeded.', - value_spec=AttributeSpec(default=False), + value_spec=AttributeSpec(default=None, allow_none=True), cif_handler=CifHandler(names=['_fit_result.success']), ) self._message = StringDescriptor( name='message', description='Status message for the latest persisted fit-result projection.', - value_spec=AttributeSpec(default=''), + value_spec=AttributeSpec(default=None, allow_none=True), cif_handler=CifHandler(names=['_fit_result.message']), ) self._iterations = IntegerDescriptor( name='iterations', description='Iteration count for the latest persisted fit-result projection.', - value_spec=AttributeSpec(default=0), + value_spec=AttributeSpec(default=None, allow_none=True), cif_handler=CifHandler(names=['_fit_result.iterations']), ) self._fitting_time = NumericDescriptor( @@ -99,7 +99,7 @@ def success(self) -> BoolDescriptor: """ return self._success - def _set_success(self, *, value: bool) -> None: + def _set_success(self, *, value: bool | None) -> None: """Set the success flag for internal callers.""" self._success.value = value @@ -110,7 +110,7 @@ def message(self) -> StringDescriptor: """ return self._message - def _set_message(self, value: str) -> None: + def _set_message(self, value: str | None) -> None: """Set the fit-result message for internal callers.""" self._message.value = value @@ -121,7 +121,7 @@ def iterations(self) -> IntegerDescriptor: """ return self._iterations - def _set_iterations(self, value: int) -> None: + def _set_iterations(self, value: int | None) -> None: """Set the iteration count for internal callers.""" self._iterations.value = value diff --git a/src/easydiffraction/analysis/categories/fit_result/bayesian.py b/src/easydiffraction/analysis/categories/fit_result/bayesian.py index 1e97106ee..91e1310f1 100644 --- a/src/easydiffraction/analysis/categories/fit_result/bayesian.py +++ b/src/easydiffraction/analysis/categories/fit_result/bayesian.py @@ -54,7 +54,7 @@ def _point_estimate_name_descriptor() -> StringDescriptor: return StringDescriptor( name='point_estimate_name', description='Committed sampled point estimate name.', - value_spec=AttributeSpec(default='best_sample'), + value_spec=AttributeSpec(default=None, allow_none=True), cif_handler=CifHandler(names=['_fit_result.point_estimate_name']), ) @@ -64,7 +64,7 @@ def _sampler_completed_descriptor() -> BoolDescriptor: return BoolDescriptor( name='sampler_completed', description='Whether the sampler completed and returned posterior data.', - value_spec=AttributeSpec(default=False), + value_spec=AttributeSpec(default=None, allow_none=True), cif_handler=CifHandler(names=['_fit_result.sampler_completed']), ) @@ -133,7 +133,7 @@ def point_estimate_name(self) -> StringDescriptor: """Committed sampled point estimate name.""" return self._point_estimate_name - def _set_point_estimate_name(self, value: str) -> None: + def _set_point_estimate_name(self, value: str | None) -> None: """Set the point-estimate name for internal callers.""" self._point_estimate_name.value = value @@ -142,7 +142,7 @@ def sampler_completed(self) -> BoolDescriptor: """Whether the sampler completed and returned posterior data.""" return self._sampler_completed - def _set_sampler_completed(self, *, value: bool) -> None: + def _set_sampler_completed(self, *, value: bool | None) -> None: """Set the sampler-completed flag for internal callers.""" self._sampler_completed.value = value diff --git a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py index dfbf16cd5..60e2bc751 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py +++ b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py @@ -19,9 +19,7 @@ class LeastSquaresMinimizerBase(MinimizerCategoryBase): _default_max_iterations: ClassVar[int] = 1000 _fit_result_class: ClassVar[type] = LeastSquaresFitResult - _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( - 'max_iterations', - ) + _expected_descriptor_names: ClassVar[tuple[str, ...]] = ('max_iterations',) _native_key_map: ClassVar[dict[str, str]] = { 'max_iterations': 'max_iterations', } diff --git a/src/easydiffraction/analysis/sequential.py b/src/easydiffraction/analysis/sequential.py index 2c48f40fc..ce9fe54b2 100644 --- a/src/easydiffraction/analysis/sequential.py +++ b/src/easydiffraction/analysis/sequential.py @@ -94,83 +94,80 @@ def _fit_worker( ``reduced_chi_square``, ``iterations``, and per-parameter ``{unique_name}`` / ``{unique_name}.uncertainty``. """ + try: + return _fit_worker_success(template, data_path) + except ( + RuntimeError, + ValueError, + TypeError, + ArithmeticError, + KeyError, + IndexError, + OSError, + ) as exc: + return _fit_worker_error(data_path, exc) + + +def _fit_worker_success( + template: SequentialFitTemplate, + data_path: str, +) -> dict[str, Any]: + """Run one sequential-fit worker and return collected results.""" # Lazy import to avoid circular dependencies and keep the module # importable without heavy imports at top level. from easydiffraction.project.project import Project # noqa: PLC0415 result: dict[str, Any] = {'file_path': data_path} + Project._loading = True try: - # 1. Create a fresh, isolated project - Project._loading = True - try: - project = Project(name='_worker') - finally: - Project._loading = False - - # 2. Load structure from template CIF - project.structures.add_from_cif_str(template.structure_cif) - - # 3. Load experiment from template CIF - # (full config + template data) - project.experiments.add_from_cif_str(template.experiment_cif) - expt = next(iter(project.experiments.values())) - - # 4. Replace data from the new data path - expt._load_ascii_data_to_experiment(data_path) - - # 5. Extract diffrn metadata from the data file - result.update(_extract_diffrn_values(expt, data_path, template.diffrn_extract_rules)) - - # 6. Override parameter values from propagated starting values - _apply_param_overrides(project, template.initial_params) - - # 7. Set free flags - _set_free_params(project, template.free_param_unique_names) - - # 8. Apply constraints - if template.constraints_enabled and template.alias_defs: - _apply_constraints( - project, - template.alias_defs, - template.constraint_defs, - ) - - # 9. Set calculator and minimizer - # (internal, no console output) - from easydiffraction.analysis.fitting import Fitter # noqa: PLC0415 + project = Project(name='_worker') + finally: + Project._loading = False + + project.structures.add_from_cif_str(template.structure_cif) + project.experiments.add_from_cif_str(template.experiment_cif) + expt = next(iter(project.experiments.values())) + expt._load_ascii_data_to_experiment(data_path) + result.update(_extract_diffrn_values(expt, data_path, template.diffrn_extract_rules)) + + _apply_param_overrides(project, template.initial_params) + _set_free_params(project, template.free_param_unique_names) + if template.constraints_enabled and template.alias_defs: + _apply_constraints( + project, + template.alias_defs, + template.constraint_defs, + ) - expt._swap_calculator(template.calculator_tag, announce=False) - project.analysis.fitter = Fitter(template.minimizer_tag) + from easydiffraction.analysis.fitting import Fitter # noqa: PLC0415 - # 10. Fit - original_verbosity = project.verbosity.fit.value - project.verbosity.fit = 'silent' - try: - project.analysis.fit() - finally: - project.verbosity.fit = original_verbosity + expt._swap_calculator(template.calculator_tag, announce=False) + project.analysis.fitter = Fitter(template.minimizer_tag) - # 11. Collect results - result.update(_collect_results(project, template)) + original_verbosity = project.verbosity.fit.value + project.verbosity.fit = 'silent' + try: + project.analysis.fit() + finally: + project.verbosity.fit = original_verbosity - except ( - RuntimeError, - ValueError, - TypeError, - ArithmeticError, - KeyError, - IndexError, - OSError, - ) as exc: - result['success'] = False - result['reduced_chi_square'] = None - result['iterations'] = 0 - result['error'] = str(exc) + result.update(_collect_results(project, template)) return result +def _fit_worker_error(data_path: str, exc: Exception) -> dict[str, Any]: + """Return the standard failed worker result payload.""" + return { + 'file_path': data_path, + 'success': False, + 'reduced_chi_square': None, + 'iterations': 0, + 'error': str(exc), + } + + # ------------------------------------------------------------------ # Helper functions # ------------------------------------------------------------------ diff --git a/src/easydiffraction/core/singleton.py b/src/easydiffraction/core/singleton.py index 9f997e535..95295a9ed 100644 --- a/src/easydiffraction/core/singleton.py +++ b/src/easydiffraction/core/singleton.py @@ -112,28 +112,27 @@ def apply(self) -> None: for lhs_alias, rhs_expr in self._parsed_constraints: try: - # Evaluate the RHS expression using the current values - rhs_value = ae(rhs_expr) - - # asteval silently returns None for undefined names - # instead of raising an exception; errors are stored in - # ae.error. - if ae.error: - error_msgs = '; '.join(str(e.get_error()) for e in ae.error) - ae.error.clear() - log.error( - f"Constraint '{lhs_alias} = {rhs_expr}' could not be " - f'evaluated: {error_msgs}. ' - f'Make sure every name in the expression is registered ' - f'as an alias via analysis.aliases.create().', - exc_type=ValueError, - ) - - # Get the actual parameter object we want to update - param = self._alias_to_param[lhs_alias].param - - # Update its value and mark it as user constrained - param._set_value_user_constrained(rhs_value) - + self._apply_one_constraint(ae, lhs_alias, rhs_expr) except (ValueError, TypeError, ArithmeticError, KeyError, AttributeError) as error: print(f"Failed to apply constraint '{lhs_alias} = {rhs_expr}': {error}") + + def _apply_one_constraint( + self, + ae: Interpreter, + lhs_alias: str, + rhs_expr: str, + ) -> None: + """Evaluate and apply one parsed constraint expression.""" + rhs_value = ae(rhs_expr) + if ae.error: + error_msgs = '; '.join(str(e.get_error()) for e in ae.error) + ae.error.clear() + log.error( + f"Constraint '{lhs_alias} = {rhs_expr}' could not be " + f'evaluated: {error_msgs}. ' + f'Make sure every name in the expression is registered ' + f'as an alias via analysis.aliases.create().', + exc_type=ValueError, + ) + param = self._alias_to_param[lhs_alias].param + param._set_value_user_constrained(rhs_value) diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index cae993fd0..f96c812a4 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -3684,37 +3684,18 @@ def _evaluate_posterior_predictive_draws( dtype=float, ) original_uncertainties = [parameter.uncertainty for parameter in sampled_parameters] - predictive_draws: list[np.ndarray] = [] draw_indices = self._posterior_predictive_draw_indices(flattened_samples.shape[0]) try: - best_sample_prediction, x_values = self._evaluate_posterior_predictive_state( + evaluated = self._evaluate_posterior_predictive_draw_values( + draw_indices=draw_indices, + flattened_samples=flattened_samples, sampled_parameters=sampled_parameters, - values=original_values, experiment=experiment, expt_name=expt_name, x_axis=x_axis, + original_values=original_values, ) - if best_sample_prediction is None or x_values is None: - return None - - for index in draw_indices: - prediction, current_x = self._evaluate_posterior_predictive_state( - sampled_parameters=sampled_parameters, - values=flattened_samples[index], - experiment=experiment, - expt_name=expt_name, - x_axis=x_axis, - ) - if prediction is None or current_x is None: - return None - if ( - prediction.shape != best_sample_prediction.shape - or current_x.shape != x_values.shape - ): - log.warning('Posterior predictive draws returned inconsistent array shapes.') - return None - predictive_draws.append(prediction) finally: self._restore_posterior_predictive_parameters( sampled_parameters=sampled_parameters, @@ -3723,12 +3704,58 @@ def _evaluate_posterior_predictive_draws( expt_name=expt_name, ) + if evaluated is None: + return None + best_sample_prediction, x_values, predictive_draws = evaluated return ( np.asarray(best_sample_prediction, dtype=float), np.asarray(x_values, dtype=float), np.asarray(predictive_draws, dtype=float), ) + def _evaluate_posterior_predictive_draw_values( + self, + *, + draw_indices: np.ndarray, + flattened_samples: np.ndarray, + sampled_parameters: list[object], + experiment: object, + expt_name: str, + x_axis: object, + original_values: np.ndarray, + ) -> tuple[np.ndarray, np.ndarray, list[np.ndarray]] | None: + """Evaluate posterior predictive best sample and draw curves.""" + best_sample_prediction, x_values = self._evaluate_posterior_predictive_state( + sampled_parameters=sampled_parameters, + values=original_values, + experiment=experiment, + expt_name=expt_name, + x_axis=x_axis, + ) + if best_sample_prediction is None or x_values is None: + return None + + predictive_draws: list[np.ndarray] = [] + for index in draw_indices: + prediction, current_x = self._evaluate_posterior_predictive_state( + sampled_parameters=sampled_parameters, + values=flattened_samples[index], + experiment=experiment, + expt_name=expt_name, + x_axis=x_axis, + ) + if prediction is None or current_x is None: + return None + if ( + prediction.shape != best_sample_prediction.shape + or current_x.shape != x_values.shape + ): + log.warning('Posterior predictive draws returned inconsistent array shapes.') + return None + predictive_draws.append(prediction) + + return best_sample_prediction, x_values, predictive_draws + def _restore_posterior_predictive_parameters( self, *, diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index c161e24e9..fcd209d08 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -569,9 +569,7 @@ def analysis_from_cif(analysis: object, cif_text: str) -> None: def _has_persisted_fit_state_sections(block: object) -> bool: """Return True when any persisted fit-state section is present.""" - scalar_tags = ( - '_fit_result.result_kind', - ) + scalar_tags = ('_fit_result.result_kind',) loop_tags = ( '_fit_parameter.param_unique_name', '_fit_parameter_correlation.param_unique_name_i', @@ -638,9 +636,7 @@ def _collect_legacy_analysis_tags(block: object) -> list[str]: legacy_tags.append('_joint_fit_experiment.id') if _has_cif_loop(block, '_joint_fit_experiment.weight'): legacy_tags.append('_joint_fit_experiment.weight') - for tag in _MINIMIZER_OUTPUT_LEGACY_TAGS: - if _has_cif_value(block, tag): - legacy_tags.append(tag) + legacy_tags.extend(tag for tag in _MINIMIZER_OUTPUT_LEGACY_TAGS if _has_cif_value(block, tag)) return legacy_tags diff --git a/src/easydiffraction/project/display.py b/src/easydiffraction/project/display.py index 560c34b7c..5067e40d3 100644 --- a/src/easydiffraction/project/display.py +++ b/src/easydiffraction/project/display.py @@ -8,7 +8,6 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from easydiffraction.core.variable import GenericDescriptorBase from easydiffraction.datablocks.experiment.item.base import intensity_category_for from easydiffraction.datablocks.experiment.item.enums import SampleFormEnum from easydiffraction.datablocks.experiment.item.enums import ScatteringTypeEnum @@ -116,14 +115,11 @@ def _settings_used_rows(self) -> list[list[str]]: rows: list[list[str]] = [] for name in minimizer._setting_descriptor_names: descriptor = getattr(minimizer, name) - if isinstance(descriptor, GenericDescriptorBase): - rows.append([ - name, - str(descriptor.value), - descriptor.description or '', - ]) - else: - rows.append([name, str(descriptor), '']) + rows.append([ + name, + str(descriptor.value), + descriptor.description or '', + ]) return rows def correlations( diff --git a/tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py b/tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py new file mode 100644 index 000000000..410754e1b --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for common fit-result status metadata.""" + +from __future__ import annotations + + +def test_fit_result_base_defaults_unknown_result_values_to_none(): + from easydiffraction.analysis.categories.fit_result.base import FitResultBase + from easydiffraction.analysis.enums import FitResultKindEnum + + fit_result = FitResultBase() + + assert fit_result.result_kind.value == FitResultKindEnum.DETERMINISTIC.value + assert fit_result.success.value is None + assert fit_result.message.value is None + assert fit_result.iterations.value is None + assert fit_result.fitting_time.value is None + assert fit_result.reduced_chi_square.value is None + + +def test_fit_result_base_reset_restores_declared_defaults(): + from easydiffraction.analysis.categories.fit_result.base import FitResultBase + + fit_result = FitResultBase() + fit_result._set_result_kind('bayesian') + fit_result._set_success(value=True) + fit_result._set_message('Fit converged') + fit_result._set_iterations(14) + fit_result._set_fitting_time(0.25) + fit_result._set_reduced_chi_square(1.2) + + fit_result._reset_result_descriptors() + + assert fit_result.result_kind.value == 'deterministic' + assert fit_result.success.value is None + assert fit_result.message.value is None + assert fit_result.iterations.value is None + assert fit_result.fitting_time.value is None + assert fit_result.reduced_chi_square.value is None + + +def test_fit_result_base_serializes_unknown_values_as_cif_unknowns(): + from easydiffraction.analysis.categories.fit_result.base import FitResultBase + + cif_text = FitResultBase().as_cif + + assert '_fit_result.success ?' in cif_text + assert '_fit_result.message ?' in cif_text + assert '_fit_result.iterations ?' in cif_text diff --git a/tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py b/tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py new file mode 100644 index 000000000..40c1a2657 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for Bayesian fit-result metadata.""" + +from __future__ import annotations + +import gemmi + + +def test_bayesian_fit_result_defaults_unknown_outputs_to_none(): + from easydiffraction.analysis.categories.fit_result.bayesian import ( + BayesianFitResult, + ) + + fit_result = BayesianFitResult() + + assert fit_result.point_estimate_name.value is None + assert fit_result.sampler_completed.value is None + assert fit_result.credible_interval_inner.value == 0.68 + assert fit_result.credible_interval_outer.value == 0.95 + assert fit_result.acceptance_rate_mean.value is None + assert fit_result.gelman_rubin_max.value is None + assert fit_result.effective_sample_size_min.value is None + assert fit_result.best_log_posterior.value is None + + +def test_bayesian_fit_result_round_trips_cif_outputs(): + from easydiffraction.analysis.categories.fit_result.bayesian import ( + BayesianFitResult, + ) + + fit_result = BayesianFitResult() + fit_result._set_point_estimate_name('posterior_median') + fit_result._set_sampler_completed(value=True) + fit_result._set_credible_interval_inner(0.5) + fit_result._set_credible_interval_outer(0.9) + fit_result._set_acceptance_rate_mean(0.42) + fit_result._set_gelman_rubin_max(1.01) + fit_result._set_effective_sample_size_min(80) + fit_result._set_best_log_posterior(-12.5) + + restored = BayesianFitResult() + restored.from_cif(gemmi.cif.read_string(f'data_fit_result\n{fit_result.as_cif}').sole_block()) + + assert restored.point_estimate_name.value == 'posterior_median' + assert restored.sampler_completed.value is True + assert restored.credible_interval_inner.value == 0.5 + assert restored.credible_interval_outer.value == 0.9 + assert restored.acceptance_rate_mean.value == 0.42 + assert restored.gelman_rubin_max.value == 1.01 + assert restored.effective_sample_size_min.value == 80 + assert restored.best_log_posterior.value == -12.5 diff --git a/tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py b/tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py new file mode 100644 index 000000000..7352301fb --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for fit-result factory registration and pairing.""" + +from __future__ import annotations + + +def test_fit_result_factory_creates_registered_family_classes(): + import easydiffraction.analysis.categories.fit_result # noqa: F401 + from easydiffraction.analysis.categories.fit_result.bayesian import ( + BayesianFitResult, + ) + from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory + from easydiffraction.analysis.categories.fit_result.lsq import ( + LeastSquaresFitResult, + ) + + assert isinstance(FitResultFactory.create('least_squares'), LeastSquaresFitResult) + assert isinstance(FitResultFactory.create('bayesian'), BayesianFitResult) + + +def test_minimizer_bases_declare_paired_fit_result_classes(): + from easydiffraction.analysis.categories.fit_result.bayesian import ( + BayesianFitResult, + ) + from easydiffraction.analysis.categories.fit_result.lsq import ( + LeastSquaresFitResult, + ) + from easydiffraction.analysis.categories.minimizer.bayesian_base import ( + BayesianMinimizerBase, + ) + from easydiffraction.analysis.categories.minimizer.lsq_base import ( + LeastSquaresMinimizerBase, + ) + + assert LeastSquaresMinimizerBase._fit_result_class is LeastSquaresFitResult + assert BayesianMinimizerBase._fit_result_class is BayesianFitResult diff --git a/tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py b/tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py new file mode 100644 index 000000000..bce6e3670 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for least-squares fit-result metadata.""" + +from __future__ import annotations + +import gemmi + + +def test_least_squares_fit_result_defaults_unknown_outputs_to_none(): + from easydiffraction.analysis.categories.fit_result.lsq import ( + LeastSquaresFitResult, + ) + + fit_result = LeastSquaresFitResult() + + assert fit_result.objective_name.value is None + assert fit_result.objective_value.value is None + assert fit_result.n_data_points.value is None + assert fit_result.n_parameters.value is None + assert fit_result.n_free_parameters.value is None + assert fit_result.degrees_of_freedom.value is None + assert fit_result.covariance_available.value is None + assert fit_result.correlation_available.value is None + assert fit_result.exit_reason.value is None + + +def test_least_squares_fit_result_round_trips_cif_outputs(): + from easydiffraction.analysis.categories.fit_result.lsq import ( + LeastSquaresFitResult, + ) + + fit_result = LeastSquaresFitResult() + fit_result._set_objective_name('chi-square') + fit_result._set_objective_value(1.25) + fit_result._set_n_data_points(120) + fit_result._set_n_parameters(4) + fit_result._set_n_free_parameters(3) + fit_result._set_degrees_of_freedom(117) + fit_result._set_covariance_available(value=True) + fit_result._set_correlation_available(value=False) + fit_result._set_exit_reason('converged') + + restored = LeastSquaresFitResult() + restored.from_cif(gemmi.cif.read_string(f'data_fit_result\n{fit_result.as_cif}').sole_block()) + + assert restored.objective_name.value == 'chi-square' + assert restored.objective_value.value == 1.25 + assert restored.n_data_points.value == 120 + assert restored.n_parameters.value == 4 + assert restored.n_free_parameters.value == 3 + assert restored.degrees_of_freedom.value == 117 + assert restored.covariance_available.value is True + assert restored.correlation_available.value is False + assert restored.exit_reason.value == 'converged' diff --git a/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py b/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py index 2dcf16563..011862282 100644 --- a/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py +++ b/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py @@ -7,32 +7,19 @@ import gemmi -def test_lsq_minimizer_defaults_and_result_reset(): +def test_lsq_minimizer_defaults_to_settings_only(): from easydiffraction.analysis.categories.minimizer.lmfit_leastsq import ( LmfitLeastsqMinimizer, ) minimizer = LmfitLeastsqMinimizer() - minimizer._set_objective_name('chi-square') - minimizer._set_objective_value(1.2) - minimizer._set_covariance_available(value=True) assert minimizer.max_iterations.value == 1000 - assert minimizer.objective_name.value == 'chi-square' + assert minimizer._setting_descriptor_names == ('max_iterations',) + assert minimizer._result_descriptor_names == () - minimizer._reset_result_descriptors() - # LSQ result descriptors default to None so a CIF written before - # any fit emits `?` rather than `''`, `0`, or `False`. See - # minimizer-category-consolidation_review-8 finding F6. - assert minimizer.objective_name.value is None - assert minimizer.objective_value.value is None - assert minimizer.covariance_available.value is None - assert minimizer.n_data_points.value is None - assert minimizer.iterations_performed.value is None - - -def test_lsq_minimizer_reads_cif_unknown_values_as_defaults(): +def test_lsq_minimizer_reads_cif_settings(): from easydiffraction.analysis.categories.minimizer.lmfit_leastsq import ( LmfitLeastsqMinimizer, ) @@ -40,13 +27,9 @@ def test_lsq_minimizer_reads_cif_unknown_values_as_defaults(): document = gemmi.cif.read_string( """data_minimizer _minimizer.max_iterations 42 -_minimizer.objective_name chi-square -_minimizer.objective_value ? """ ) minimizer = LmfitLeastsqMinimizer() minimizer.from_cif(document.sole_block()) assert minimizer.max_iterations.value == 42 - assert minimizer.objective_name.value == 'chi-square' - assert minimizer.objective_value.value is None diff --git a/tests/unit/easydiffraction/analysis/categories/test_fit_result.py b/tests/unit/easydiffraction/analysis/categories/test_fit_result.py index 4c9d869dc..580635c8c 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_fit_result.py +++ b/tests/unit/easydiffraction/analysis/categories/test_fit_result.py @@ -4,10 +4,10 @@ def test_fit_result_factory_create(): - from easydiffraction.analysis.categories.fit_result.default import FitResult + from easydiffraction.analysis.categories.fit_result.base import FitResultBase from easydiffraction.analysis.categories.fit_result.factory import FitResultFactory fit_result = FitResultFactory.create('default') assert FitResultFactory.default_tag() == 'default' - assert isinstance(fit_result, FitResult) + assert isinstance(fit_result, FitResultBase) diff --git a/tests/unit/easydiffraction/analysis/categories/test_fit_state.py b/tests/unit/easydiffraction/analysis/categories/test_fit_state.py index a26d4435b..a3b4fd7cc 100644 --- a/tests/unit/easydiffraction/analysis/categories/test_fit_state.py +++ b/tests/unit/easydiffraction/analysis/categories/test_fit_state.py @@ -36,9 +36,9 @@ def test_fit_parameter_collection_serializes_expected_tags_and_values(): def test_fit_result_serializes_expected_tags_and_enum_value(): - from easydiffraction.analysis.categories.fit_result.default import FitResult + from easydiffraction.analysis.categories.fit_result.base import FitResultBase - fit_result = FitResult() + fit_result = FitResultBase() fit_result._set_result_kind('bayesian') fit_result._set_success(value=True) fit_result._set_message('Sampler completed') @@ -138,7 +138,10 @@ def test_fit_parameter_posterior_summary_serializes_expected_tags(): assert summary.ess_bulk == 120 -def test_dream_minimizer_sampler_and_diagnostics_use_cif_fields(): +def test_dream_sampler_settings_and_diagnostics_use_split_cif_fields(): + from easydiffraction.analysis.categories.fit_result.bayesian import ( + BayesianFitResult, + ) from easydiffraction.analysis.categories.minimizer.bumps_dream import ( BumpsDreamMinimizer, ) @@ -148,15 +151,17 @@ def test_dream_minimizer_sampler_and_diagnostics_use_cif_fields(): minimizer.burn_in_steps = 20 minimizer.parallel_workers = 0 minimizer.random_seed = 123 - minimizer._set_gelman_rubin_max(1.01) - minimizer._set_effective_sample_size_min(80) + fit_result = BayesianFitResult() + fit_result._set_gelman_rubin_max(1.01) + fit_result._set_effective_sample_size_min(80) - cif_text = minimizer.as_cif + minimizer_cif_text = minimizer.as_cif + fit_result_cif_text = fit_result.as_cif - assert '_minimizer.sampling_steps 100' in cif_text - assert '_minimizer.parallel_workers 0' in cif_text - assert '_minimizer.random_seed 123' in cif_text - assert '_minimizer.effective_sample_size_min' in cif_text + assert '_minimizer.sampling_steps 100' in minimizer_cif_text + assert '_minimizer.parallel_workers 0' in minimizer_cif_text + assert '_minimizer.random_seed 123' in minimizer_cif_text + assert '_fit_result.effective_sample_size_min' in fit_result_cif_text def test_fit_parameter_posteriors_preserve_row_order_from_cif(): diff --git a/tests/unit/easydiffraction/io/test_results_sidecar.py b/tests/unit/easydiffraction/io/test_results_sidecar.py index 70dbef7d6..75dbe571e 100644 --- a/tests/unit/easydiffraction/io/test_results_sidecar.py +++ b/tests/unit/easydiffraction/io/test_results_sidecar.py @@ -17,9 +17,11 @@ def _analysis_with_sidecar_payload( include_pair: bool = True, include_predictive: bool = True, ) -> object: - from easydiffraction.analysis.categories.fit_result.default import FitResult + from easydiffraction.analysis.categories.fit_result.bayesian import ( + BayesianFitResult, + ) - fit_result = FitResult() + fit_result = BayesianFitResult() fit_result._set_result_kind('bayesian') posterior_samples = None diff --git a/tests/unit/easydiffraction/project/test_display.py b/tests/unit/easydiffraction/project/test_display.py index 23de62edb..f80d243e4 100644 --- a/tests/unit/easydiffraction/project/test_display.py +++ b/tests/unit/easydiffraction/project/test_display.py @@ -54,6 +54,7 @@ def _recorder(*args, **kwargs): analysis=SimpleNamespace( display=analysis_display, fit_results=SimpleNamespace(posterior_predictive={}), + minimizer=SimpleNamespace(_setting_descriptor_names=()), bayesian_result=SimpleNamespace( has_pair_cache=SimpleNamespace(value=False), has_posterior_predictive=SimpleNamespace(value=False), diff --git a/tests/unit/easydiffraction/project/test_project_load.py b/tests/unit/easydiffraction/project/test_project_load.py index c726172f4..cfbd7652d 100644 --- a/tests/unit/easydiffraction/project/test_project_load.py +++ b/tests/unit/easydiffraction/project/test_project_load.py @@ -211,14 +211,14 @@ def test_round_trips_persisted_deterministic_correlation_summary_for_reloaded_di original.analysis.fit_result._set_iterations(21) original.analysis.fit_result._set_fitting_time(0.74) original.analysis.fit_result._set_reduced_chi_square(1.031) - original.analysis.minimizer._set_objective_name('chi-square') - original.analysis.minimizer._set_objective_value(1.031) - original.analysis.minimizer._set_n_data_points(120) - original.analysis.minimizer._set_n_parameters(2) - original.analysis.minimizer._set_n_free_parameters(2) - original.analysis.minimizer._set_degrees_of_freedom(118) - original.analysis.minimizer._set_covariance_available(value=False) - original.analysis.minimizer._set_correlation_available(value=True) + original.analysis.fit_result._set_objective_name('chi-square') + original.analysis.fit_result._set_objective_value(1.031) + original.analysis.fit_result._set_n_data_points(120) + original.analysis.fit_result._set_n_parameters(2) + original.analysis.fit_result._set_n_free_parameters(2) + original.analysis.fit_result._set_degrees_of_freedom(118) + original.analysis.fit_result._set_covariance_available(value=False) + original.analysis.fit_result._set_correlation_available(value=True) original.analysis.fit_parameter_correlations.create( source_kind='deterministic', param_unique_name_i=parameter_b.unique_name, From cf7c97369ea4c515286b5cba109a3de34035e26d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 16:56:24 +0200 Subject: [PATCH 35/38] Reply to minimizer input-output review 5 --- .../dev/plans/minimizer-input-output-split.md | 9 + .../minimizer-input-output-split_reply-5.md | 72 +++++ .../minimizer-input-output-split_review-5.md | 297 ++++++++++++++++++ 3 files changed, 378 insertions(+) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-5.md create mode 100644 docs/dev/plans/minimizer-input-output-split_review-5.md diff --git a/docs/dev/plans/minimizer-input-output-split.md b/docs/dev/plans/minimizer-input-output-split.md index a64a747e1..0d9361a8a 100644 --- a/docs/dev/plans/minimizer-input-output-split.md +++ b/docs/dev/plans/minimizer-input-output-split.md @@ -663,3 +663,12 @@ The CIF layout follows the same split: `_minimizer.*` holds settings only, `_fit_result.*` holds outputs. Saved projects from the previous layout do not load unchanged (the project is in beta; no legacy shims). Tutorials and saved-fixture regeneration land in this PR. + +Incidental cleanup also bundled in this branch: the unused +`essdiffraction` development dependency is removed, and a handful of +unrelated functions are split into helpers to satisfy the project's +complexity thresholds during Phase 2 verification: +`singleton.ConstraintsHandler.apply_constraints`, +`analysis.sequential._fit_worker`, +`display.plotting._posterior_predictive_*`, and +`calculators.{crysfml,pdffit}._calculate_pattern`. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-5.md b/docs/dev/plans/minimizer-input-output-split_reply-5.md new file mode 100644 index 000000000..b7839efda --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-5.md @@ -0,0 +1,72 @@ +# Reply to Review 5: Minimizer Input/Output Split Branch + +Reply to +[`minimizer-input-output-split_review-5.md`](minimizer-input-output-split_review-5.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Context: Review 5 is a post-Phase-2 static review. It reports no +blockers and lists two cleanup findings plus two informational notes. + +## Findings + +### F1 — `_clear_fit_result_projection` is defined but never called + +**Verdict: agree.** The method became dead private code after the +reply-4 cleanup changed `_clear_persisted_fit_state` to replace +`self._fit_result` with a fresh paired instance. + +**Decision.** Defer this to a follow-up cleanup rather than reopening +the completed Phase 2 implementation for a non-behavioural private +method deletion. If another code cleanup commit is made on this branch +before PR opening, deleting the method there is fine; it is not required +for merge. + +### F2 — Phase 2 bundles lint-driven refactors outside the split + +**Verdict: agree.** These changes are outside the conceptual +minimizer/fit-result split. They were a direct result of the Phase 2 +`pixi run check` complexity gate, and they preserve behaviour while +following the repository rule to refactor rather than raise or silence +lint thresholds. + +**Decision.** Keep the refactors bundled in this branch instead of +rewriting history or splitting them now. The plan's suggested PR +description has been updated to call them out explicitly under +"Incidental cleanup" so reviewers know why unrelated files changed. + +### F3 — `essdiffraction` removal is still bundled + +**Verdict: agree.** This remains unrelated to the input/output split and +was a separate CI dependency cleanup request. + +**Decision.** Keep it bundled, matching the reply-4 decision. The plan's +suggested PR description now mentions the `essdiffraction` removal +alongside the lint-driven refactors. + +### F4 — `FitResultBase.result_kind` exception undocumented + +**Verdict: agree, but treat as informational.** `result_kind` needs a +valid enum default because it is used to decide deterministic versus +Bayesian projection handling. The `None`/`allow_none=True` convention is +still correct for result values that are unknown before a fit. + +**Decision.** Defer the optional explanatory comment to a follow-up. The +current behaviour is intentional, tested through the fit-result unit +tests, and not a merge blocker. + +## Verification + +This is a static reply and PR-description update only. No `pixi run`, +lint, build, formatter, or test command was executed. + +## Summary of files touched by this reply + +- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) + — updated the suggested PR description with the incidental cleanup + note requested by review 5. +- [`docs/dev/plans/minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) + — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-5.md b/docs/dev/plans/minimizer-input-output-split_review-5.md new file mode 100644 index 000000000..a99ffe4c6 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-5.md @@ -0,0 +1,297 @@ +# Review 5: Minimizer Input/Output Split Branch (Post-Phase 2) + +Reviewed plan: +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) + +Reviewed ADR: +[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) + +Prior reviews: +[`_review-1`](minimizer-input-output-split_review-1.md), +[`_review-2`](minimizer-input-output-split_review-2.md), +[`_review-3`](minimizer-input-output-split_review-3.md), +[`_review-4`](minimizer-input-output-split_review-4.md) and matching +replies. + +This review follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or +any other build/verification command was executed**. This is a static +read of the 34 commits on `minimizer-input-output-split` against the +last merged develop ancestor (`973214a5e`), focused on the post-Phase-2 +delta since review 4. + +## Scope + +Compared to review 4, the branch adds three commits: + +- `60788b220` Propose IUCr CIF tag alignment for fit outputs +- `6e24d62c8` Add review 4 of input/output split post-Phase 1 +- `9878c9f58` Reply to minimizer input-output review 4 +- `a2857d9d6` Complete minimizer input-output Phase 2 + +The first three are documentation only. The last lands Phase 2 in a +single commit covering P2.1 (test migration), P2.2 (new `fit_result` +unit tests), and the cleanup carry-forward agreed in +[`_reply-4`](minimizer-input-output-split_reply-4.md) §"Phase 2 +Carry-Forward" (F1 through F3 from review 4). + +## Summary + +Phase 2 closes the plan. The reply-4 cleanup items are applied, the +test migration is clean, and the new unit-test files exist under +`tests/unit/easydiffraction/analysis/categories/fit_result/`. Two new +small findings worth deciding (F1 below — orphaned method; F2 below — +lint-driven refactors bundled into Phase 2). Neither blocks merge. + +## Verification of reply-4 carry-forward + +All three items from +[`_reply-4`](minimizer-input-output-split_reply-4.md) §"Phase 2 +Carry-Forward" landed in `a2857d9d6`: + +### Review-4 F1 — `FitResultBase` defaults aligned + +[`base.py:55-78`](../../../src/easydiffraction/analysis/categories/fit_result/base.py:55) +— `success`, `message`, `iterations`, `fitting_time`, and +`reduced_chi_square` now declare `default=None, allow_none=True`. Only +`result_kind` keeps a default (`FitResultKindEnum.default().value`) +because the enum requires a valid member; that exception is reasonable +but undocumented in the class (see F4 below). + +The Bayesian side mirrors the same fix: +[`bayesian.py:51-69`](../../../src/easydiffraction/analysis/categories/fit_result/bayesian.py:51) +— `point_estimate_name` and `sampler_completed` are now +`default=None, allow_none=True`. The two credible-interval levels keep +their fixed `0.68` / `0.95` defaults per ADR §"Decisions already made" +point 6, which is correct. + +A new test pins the behaviour: +[`test_base.py:43-51`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py:43) +asserts that a pre-fit `FitResultBase` serialises `_fit_result.success`, +`_fit_result.message`, and `_fit_result.iterations` as `?`. + +Resolved. + +### Review-4 F2 — redundant reset before instance replacement removed + +[`analysis.py:1207-1216`](../../../src/easydiffraction/analysis/analysis.py:1207) +— `_clear_persisted_fit_state` now constructs a fresh paired instance +directly: + +```python +def _clear_persisted_fit_state(self) -> None: + self._fit_parameters = FitParameters() + self._fit_result._parent = None + self._fit_result = self.minimizer._fit_result_class() + self._fit_result._parent = self + ... +``` + +The pre-replacement `_clear_fit_result_projection()` call is gone. +Paired-class invariant is preserved by going through +`self.minimizer._fit_result_class()`. Resolved. + +### Review-4 F3 — unreachable `else` branch removed + +[`display.py:112-123`](../../../src/easydiffraction/project/display.py:112) +— `_settings_used_rows` no longer carries the +`isinstance(descriptor, GenericDescriptorBase)` guard or the fallback +branch. Each entry in `_setting_descriptor_names` is trusted to +resolve to a descriptor, matching the project rule "no defensive checks +for unlikely edge cases". Resolved. + +## Verification of plan-level Phase 2 steps + +- **P2.1 — test migration.** `git grep` on the patterns from P1.15 / + P2.1 returns empty against `src/`, `docs/docs/tutorials/`, and + `tests/`. The legacy `_minimizer.*` output tags remain only inside + [`serialize.py`](../../../src/easydiffraction/io/cif/serialize.py) + as the `_MINIMIZER_OUTPUT_LEGACY_TAGS` rejection list (lines 609-629) + — intentional. +- **P2.2 — new `fit_result` unit tests.** Four test files exist under + `tests/unit/easydiffraction/analysis/categories/fit_result/`: + [`test_base.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py) + (defaults, reset, pre-fit CIF unknowns), + [`test_lsq.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py) + (LSQ defaults + CIF round-trip), + [`test_bayesian.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py) + (Bayesian defaults including fixed credible-interval levels + CIF + round-trip), + [`test_factory.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py) + (factory family creation + paired-class declarations on both + minimizer bases). Layout matches the test-structure-check expectation + (a sibling file per source file). +- **P2.3 / P2.4 / P2.5 / P2.6 — auto-fixes, static checks, three + test suites.** Marked `[x]` in the plan. Reviewer did not re-run + them; trust the author's pass and gate on CI. + +## Paired-class invariant — spot check + +Every `self._fit_result = ...` site in +[`analysis.py`](../../../src/easydiffraction/analysis/analysis.py) +constructs through `_fit_result_class`: + +- [`analysis.py:483`](../../../src/easydiffraction/analysis/analysis.py:483) + — `__init__` +- [`analysis.py:1067`](../../../src/easydiffraction/analysis/analysis.py:1067) + — `_replace_minimizer` +- [`analysis.py:1211`](../../../src/easydiffraction/analysis/analysis.py:1211) + — `_clear_persisted_fit_state` + +Each call also re-establishes `_parent = self`, and each prior instance +is explicitly detached (`_parent = None`) before being replaced. Matches +the ADR's atomic-swap requirement. + +Legacy-tag rejection +([`serialize.py:609-629`](../../../src/easydiffraction/io/cif/serialize.py:609)) +enumerates all 19 removed `_minimizer.` tags and raises a +single `ValueError` with the correct migration hint pointing at +`_fit_result.*`. Matches plan §P1.11. + +## Findings (this review) + +### F1 — `_clear_fit_result_projection` is defined but never called + +[`analysis.py:1217-1221`](../../../src/easydiffraction/analysis/analysis.py:1217): + +```python +def _clear_fit_result_projection(self) -> None: + """ + Reset result-only fields on the active fit-result category. + """ + self.fit_result._reset_result_descriptors() +``` + +`git grep -nE _clear_fit_result_projection src/ tests/` returns only +the definition line. The single caller used to be +`_clear_persisted_fit_state`, removed by the reply-4 F2 fix. After +that removal, this method is dead code. + +It is also no longer reachable from outside the class (single-underscore +private API), and the docstring matches the legacy behaviour from +before the fresh-instance approach won out, so keeping it as a future +hook is not justified. + +Suggested follow-up: delete the method. One-line cleanup; deferrable +to a follow-on if not in scope for this PR. + +### F2 — Phase 2 commit bundles five lint-driven refactors outside the input/output split + +The single Phase 2 commit `a2857d9d6` touches files unrelated to the +fit-result split: + +- [`src/easydiffraction/core/singleton.py`](../../../src/easydiffraction/core/singleton.py) + — extracts `_apply_one_constraint` from a `for` body. +- [`src/easydiffraction/analysis/sequential.py`](../../../src/easydiffraction/analysis/sequential.py) + — splits `_fit_worker` into `_fit_worker_success` / + `_fit_worker_error` plus several smaller helpers (~121 lines). +- [`src/easydiffraction/display/plotting.py`](../../../src/easydiffraction/display/plotting.py) + — extracts `_evaluate_posterior_predictive_draw_values` to flatten + a try/finally body (~73 lines). +- [`src/easydiffraction/analysis/calculators/crysfml.py`](../../../src/easydiffraction/analysis/calculators/crysfml.py) + — extracts `_calculate_adjusted_pattern` / + `_calculate_raw_pattern`. +- [`src/easydiffraction/analysis/calculators/pdffit.py`](../../../src/easydiffraction/analysis/calculators/pdffit.py) + — analogous extraction. + +These read as `pixi run check` complexity-threshold refactors. They are +faithful to the project rule "do not raise lint thresholds — refactor +instead" (plan §P2.3), which is the right call mechanically. But they +also have nothing to do with splitting minimizer settings from fit +outputs, so they expand the PR's blast radius and make `git blame` +noisier for unrelated future debugging. + +Two options for the next reviewer / PR author: + +1. **Keep bundled, but call them out in the PR description** under a + short "Incidental cleanup" subsection so a future reader of `git + log` understands why the input/output split commit touched + `singleton.py`. Lowest-friction option. +2. **Split into a follow-up "Apply lint-driven refactors in Phase 2" + commit** on the same branch. Keeps the input/output split commit + focused; one extra commit on the branch. + +Either is acceptable. The cleanup itself is correct. + +### F3 (informational) — `essdiffraction` removal commit still bundled + +[`_review-4`](minimizer-input-output-split_review-4.md) §F5 flagged +the unrelated `c5da0b0fc Remove essdiffraction dependency` commit, and +[`_reply-4`](minimizer-input-output-split_reply-4.md) decided to keep +it on the branch and call it out in the PR description. No change since +review 4. Confirming the decision; this review does not re-open it. + +The plan §"Suggested Pull Request" description does not yet mention the +`essdiffraction` removal or the F2 lint-driven refactors. Suggested +amendment to the PR description before opening: + +> Incidental cleanup also bundled in this PR: the `essdiffraction` +> dependency is removed, and a handful of unrelated functions +> (`singleton.ConstraintsHandler.apply_constraints`, +> `analysis.sequential._fit_worker`, +> `display.plotting._posterior_predictive_*`, +> `calculators.{crysfml,pdffit}._calculate_pattern`) are split into +> helpers to satisfy the project's complexity thresholds. + +### F4 (informational) — `FitResultBase.result_kind` exception undocumented + +[`base.py:44-54`](../../../src/easydiffraction/analysis/categories/fit_result/base.py:44) +— `result_kind` keeps a non-None default because +`FitResultKindEnum` requires a valid member. Every other descriptor on +the same class uses `default=None, allow_none=True` after reply-4 F1. +The asymmetry is intentional but unexplained in code; a one-line +comment would save a future reader from wondering whether it is +another instance of the pattern review-4 F1 flagged. Trivial; defer +to a follow-on if not in scope. + +## Documentation + +- The five amended ADRs and the new accepted ADR are unchanged since + review 4. No further amendments needed for Phase 2. +- The plan's status checklist is fully `[x]` through P2.6. +- `docs/dev/package-structure/full.md` and `short.md` were updated by + `pixi run fix` (per CLAUDE.md §Workflow auto-generation) and should + not be hand-reviewed. + +## Verification commands run for this review + +Static reads only: + +```text +git rev-parse --abbrev-ref HEAD # minimizer-input-output-split +git merge-base HEAD main # 8f9a679ed +git log --oneline 973214a5e..HEAD # 34 commits since last merged PR +git show --stat a2857d9d6 # Phase 2 commit file list +git grep -nE 'analysis\.minimizer\.(runtime_seconds|iterations_performed|objective_value|...)' src/ tests/ docs/docs/tutorials/ +git grep -nE '_minimizer\.(runtime_seconds|...)' src/ tests/ docs/docs/tutorials/ +git grep -nE '_clear_fit_result_projection' src/ tests/ +git grep -nE '\bFitResult\b' src/ tests/ docs/dev/adrs/ docs/dev/plans/ # stale-name sweep +``` + +All four migration-pattern greps return empty against `src/`, +`docs/docs/tutorials/`, and `tests/` (the `_minimizer.*` sweep returns +only the rejection list inside `serialize.py`, which is intentional). +The `_clear_fit_result_projection` grep returns the definition line +only — supporting F1 above. The `\bFitResult\b` sweep with the family +names filtered out returns empty, confirming the P1.1 rename has no +stragglers. + +No `pixi run fix`, `pixi run check`, `pixi run unit-tests`, +`pixi run integration-tests`, or `pixi run script-tests` was executed. +Those gates are deferred to CI and to the author's Phase 2 pass. + +## Recommended next steps + +1. **Decide F1.** Drop the orphaned `_clear_fit_result_projection` + method, or leave the cleanup for a follow-on. +2. **Decide F2.** Either amend the PR description to call out the + five lint-driven refactors, or split them into a separate cleanup + commit on the branch. +3. **Update the PR description** to also mention the `essdiffraction` + removal (F3 carry-over from review 4) so reviewers are not + surprised by the `pixi.lock` churn. +4. **Optional F4 comment** on `FitResultBase.result_kind` explaining + why it keeps a default while the rest do not. +5. **Open the PR.** No blockers found; CI is the next gate. From d5c03b01d6e84ae8f17160c7bd50f7badad13fd7 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 17:04:22 +0200 Subject: [PATCH 36/38] Reply to minimizer input-output review 6 --- docs/dev/issues/open.md | 45 +++++ .../minimizer-input-output-split_reply-6.md | 48 +++++ .../minimizer-input-output-split_review-6.md | 172 ++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-6.md create mode 100644 docs/dev/plans/minimizer-input-output-split_review-6.md diff --git a/docs/dev/issues/open.md b/docs/dev/issues/open.md index 46e85a912..70096e59b 100644 --- a/docs/dev/issues/open.md +++ b/docs/dev/issues/open.md @@ -1782,6 +1782,49 @@ optional-diagnostics components. --- +## 105. 🟢 Remove Orphaned Fit-Result Reset Helper + +**Type:** Cleanup **Source:** `minimizer-input-output-split` review 6. + +`Analysis._clear_fit_result_projection` is a private method with no +callers after `_clear_persisted_fit_state` switched to replacing +`self._fit_result` with a fresh paired result instance. + +**TODOs:** + +- [analysis.py](src/easydiffraction/analysis/analysis.py#L1217) + +**Fix:** delete the unused helper, or reintroduce a caller only if a +future fit-result reset path genuinely needs to preserve the active +instance. + +**Depends on:** nothing. + +--- + +## 106. 🟢 Document `FitResultBase.result_kind` Default Rationale + +**Type:** Code readability **Source:** +`minimizer-input-output-split` review 6. + +Most `FitResultBase` descriptors use `default=None, allow_none=True` so +pre-fit CIF output serializes unknown values as `?`. `result_kind` +intentionally keeps a valid enum default because it drives +deterministic versus Bayesian projection handling, but that exception +is not documented in code. + +**TODOs:** + +- [base.py](src/easydiffraction/analysis/categories/fit_result/base.py#L44) + +**Fix:** add a short code comment near the `result_kind` descriptor +explaining why it keeps a concrete default while unknown result values +use `None`. + +**Depends on:** nothing. + +--- + ## Summary | # | Issue | Severity | Type | @@ -1869,3 +1912,5 @@ optional-diagnostics components. | 91 | Disable TODO checks in CodeFactor PRs | 🟢 Low | CI / Tooling | | 92 | Make `save()` respect verbosity | 🟢 Low | UX | | 93 | Eliminate flicker in live progress tables | 🟡 Med | UX | +| 105 | Remove orphaned fit-result reset helper | 🟢 Low | Cleanup | +| 106 | Document `FitResultBase.result_kind` default | 🟢 Low | Code readability | diff --git a/docs/dev/plans/minimizer-input-output-split_reply-6.md b/docs/dev/plans/minimizer-input-output-split_reply-6.md new file mode 100644 index 000000000..bd5453452 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-6.md @@ -0,0 +1,48 @@ +# Reply to Review 6: Minimizer Input/Output Split Branch + +Reply to +[`minimizer-input-output-split_review-6.md`](minimizer-input-output-split_review-6.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Context: Review 6 is a static review of +[`minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) +and the docs-only commit that added it. It raises one follow-through +finding: the review-5 deferrals should either be applied inline or +tracked in `docs/dev/issues/open.md`. + +## Findings + +### F1 — Deferrals from review 5 not tracked in `docs/dev/issues/open.md` + +**Verdict: agree.** Deferring the dead private method and the optional +`result_kind` explanatory comment was reasonable, but the deferrals +should not live only in the review thread. + +**Decision.** Track both deferred items in +[`../issues/open.md`](../issues/open.md) rather than reopen the +completed Phase 2 implementation for non-behavioural cleanup: + +1. Issue 105 tracks removing the orphaned + `Analysis._clear_fit_result_projection` helper. +2. Issue 106 tracks adding a short comment explaining why + `FitResultBase.result_kind` keeps a concrete enum default while + unknown result values use `None`. + +This resolves the trail requested by review 6. No source files were +changed. + +## Verification + +This is a static reply and issue-log update only. No `pixi run`, lint, +build, formatter, or test command was executed. + +## Summary of files touched by this reply + +- [`../issues/open.md`](../issues/open.md) — added issues 105 and 106 + for the two deferred review-5 cleanup items. +- [`minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) + — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-6.md b/docs/dev/plans/minimizer-input-output-split_review-6.md new file mode 100644 index 000000000..8f3371360 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-6.md @@ -0,0 +1,172 @@ +# Review 6: Reply to Review 5 + +Reviewed reply: +[`minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) + +Original review: +[`minimizer-input-output-split_review-5.md`](minimizer-input-output-split_review-5.md) + +Reviewed plan: +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) + +Reviewed ADR: +[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) + +This review follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or +any other build/verification command was executed**. This is a static +read of the single new commit since review 5 (`cf7c97369 Reply to +minimizer input-output review 5`) plus the touched docs. + +## Scope + +One new commit since review 5. It touches docs only: + +```text +docs/dev/plans/minimizer-input-output-split.md +9 +docs/dev/plans/minimizer-input-output-split_reply-5.md +72 (new) +docs/dev/plans/minimizer-input-output-split_review-5.md +297 (new) +``` + +No source or test files moved. The verification matrix from review 5 +(paired-class invariant, legacy-tag rejection, migration greps, new +fit_result tests) is unchanged and still passes its static checks. + +## Summary + +Reply 5 accepts every finding ("agree" × 4) and applies the two +non-deferred ones (F2 and F3) by amending the plan's PR-description +section. F1 (dead method) and F4 (missing comment on `result_kind`) are +deferred. The reply's structure mirrors the existing reply 4 template +(one section per finding, verdict + decision + pointer), which keeps the +review thread legible. + +The reply is sound. One small follow-through gap: the deferred items +(F1 and F4) are not yet logged anywhere a future contributor would +look — see F1 below. + +## Per-finding assessment + +### Review-5 F1 — `_clear_fit_result_projection` dead method + +**Reply verdict.** Agree; defer to a follow-up cleanup. + +**Current state.** +`git grep -nE _clear_fit_result_projection src/ tests/` still returns +the single definition line at +[`analysis.py:1217`](../../../src/easydiffraction/analysis/analysis.py:1217), +no callers. The deferral was applied without action. + +**Assessment.** The deferral itself is defensible — a private method +with no callers has no behavioural impact and does not block merge. But +the project rule in +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) +→ **Workflow** is explicit: "Open issues / design questions / planned +improvements live in `docs/dev/issues/open.md` (priority-ordered)." + +`docs/dev/issues/open.md` does not currently contain this item. +`grep -nE _clear_fit_result_projection docs/dev/issues/open.md` returns +empty. So the deferral exists only inside this review/reply thread, +which a future contributor reading `open.md` will not see. + +**Suggested follow-up.** Either (a) add a one-line entry to +`docs/dev/issues/open.md` pointing at +[`analysis.py:1217`](../../../src/easydiffraction/analysis/analysis.py:1217) +so the cleanup is queued, or (b) just delete the four lines now — +this is a private, unreferenced method, and the delete is mechanically +smaller than the issue entry. Either is fine; doing neither leaves the +dead code unowned. + +### Review-5 F2 — Lint-driven refactors bundled in Phase 2 + +**Reply verdict.** Agree; keep bundled, call them out in the PR +description. + +**Current state.** +[`minimizer-input-output-split.md:664-672`](minimizer-input-output-split.md:664) +now carries this paragraph: + +> Incidental cleanup also bundled in this branch: the unused +> `essdiffraction` development dependency is removed, and a handful of +> unrelated functions are split into helpers to satisfy the project's +> complexity thresholds during Phase 2 verification: +> `singleton.ConstraintsHandler.apply_constraints`, +> `analysis.sequential._fit_worker`, +> `display.plotting._posterior_predictive_*`, and +> `calculators.{crysfml,pdffit}._calculate_pattern`. + +The four refactor sites enumerated in review 5 F2 are listed verbatim. +Resolved. + +### Review-5 F3 — `essdiffraction` removal still bundled + +**Reply verdict.** Agree; PR description now mentions it. + +**Current state.** Same plan paragraph above, lead sentence: "the +unused `essdiffraction` development dependency is removed". Resolved. + +### Review-5 F4 — `FitResultBase.result_kind` exception undocumented + +**Reply verdict.** Agree, but informational; defer. + +**Current state.** +[`base.py:44-54`](../../../src/easydiffraction/analysis/categories/fit_result/base.py:44) +is unchanged; no comment was added. As with F1, the deferral is +reasonable, but `docs/dev/issues/open.md` does not capture it. + +**Assessment.** Informational only; lower priority than F1 (which is +unowned dead code). If F1 is logged in `open.md`, F4 should be too. If +F1 is deleted instead, F4 can stay deferred without an issue entry — +it's a "nice to have" comment, not a code-quality concern. + +## Findings (this review) + +### F1 — Deferrals from review 5 not tracked in `docs/dev/issues/open.md` + +The project workflow rule routes deferred cleanup items through +`docs/dev/issues/open.md`. Reply 5 defers two findings (review-5 F1 +dead method, review-5 F4 missing comment) without adding either to +that file. Without the log entry, both items live only inside this +review thread; once the PR merges and the plan is deleted (per the +ADR-promotion precedent at P1.16 — deliberation artefacts get +dropped), the deferral context is gone. + +**Suggested action.** Either: + +- Add one short line to `docs/dev/issues/open.md` per deferred item + (with a file-line pointer), or +- Apply the cleanup inline before opening the PR (F1 is a four-line + delete; F4 is a one-line comment). + +Either route closes the trail. The first preserves history, the second +removes the obligation. Both are equally acceptable. + +This is the only outstanding observation from this review round. +Nothing about the code, tests, or paired-class plumbing has changed +since review 5; that assessment still stands. + +## Recommended next steps + +1. **Resolve the tracking gap above.** Pick one of the two routes per + deferred finding. +2. **Open the PR.** No blockers found; CI is the next gate. The plan's + "Suggested Pull Request" section is now complete and accurate + (post-reply-5 PR-description amendment). + +## Verification commands run for this review + +Static reads only: + +```text +git log --oneline a2857d9d6..HEAD # 1 new commit (cf7c97369) +git show --stat cf7c97369 # docs-only +git show cf7c97369 -- docs/dev/plans/minimizer-input-output-split.md +git grep -nE _clear_fit_result_projection src/ tests/ # 1 hit (definition only) +grep -nE _clear_fit_result_projection docs/dev/issues/open.md # empty +test -f docs/dev/issues/open.md # exists +``` + +No `pixi run fix`, `pixi run check`, `pixi run unit-tests`, +`pixi run integration-tests`, or `pixi run script-tests` was executed. From dda94b3b93111c0170b857f7b7ea43f83b8adee1 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 17:09:18 +0200 Subject: [PATCH 37/38] Reply to minimizer input-output review 7 --- .../minimizer-input-output-split_reply-7.md | 36 ++++++ .../minimizer-input-output-split_review-7.md | 117 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 docs/dev/plans/minimizer-input-output-split_reply-7.md create mode 100644 docs/dev/plans/minimizer-input-output-split_review-7.md diff --git a/docs/dev/plans/minimizer-input-output-split_reply-7.md b/docs/dev/plans/minimizer-input-output-split_reply-7.md new file mode 100644 index 000000000..8c6cbe0e6 --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_reply-7.md @@ -0,0 +1,36 @@ +# Reply to Review 7: Minimizer Input/Output Split Branch + +Reply to +[`minimizer-input-output-split_review-7.md`](minimizer-input-output-split_review-7.md) +for the plan at +[`minimizer-input-output-split.md`](minimizer-input-output-split.md). + +This reply follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Context: Review 7 is a static review of +[`minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) +and the docs-only commit that added issue-log tracking for the +review-5 deferred cleanups. + +## Findings + +Review 7 reports no findings. It confirms that reply 6 resolved the +review-6 tracking gap by adding issues 105 and 106 to +[`../issues/open.md`](../issues/open.md), and that both entries are +well-formed and listed in the summary table. + +## Decision + +No further action is needed from this review round. The branch remains +ready for PR opening, with CI as the next gate. + +## Verification + +This is a static reply only. No `pixi run`, lint, build, formatter, or +test command was executed. + +## Summary of files touched by this reply + +- [`minimizer-input-output-split_reply-7.md`](minimizer-input-output-split_reply-7.md) + — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-7.md b/docs/dev/plans/minimizer-input-output-split_review-7.md new file mode 100644 index 000000000..4e033368b --- /dev/null +++ b/docs/dev/plans/minimizer-input-output-split_review-7.md @@ -0,0 +1,117 @@ +# Review 7: Reply to Review 6 + +Reviewed reply: +[`minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) + +Original review: +[`minimizer-input-output-split_review-6.md`](minimizer-input-output-split_review-6.md) + +Reviewed plan: +[`minimizer-input-output-split.md`](minimizer-input-output-split.md) + +Reviewed ADR: +[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) + +This review follows +[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). + +Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or +any other build/verification command was executed**. This is a static +read of the single new commit since review 6 (`d5c03b01d Reply to +minimizer input-output review 6`). + +## Scope + +One new commit since review 6, docs-only: + +```text +docs/dev/issues/open.md +45 +docs/dev/plans/minimizer-input-output-split_reply-6.md +48 (new) +docs/dev/plans/minimizer-input-output-split_review-6.md +172 (new) +``` + +No source or test files moved. + +## Summary + +Reply 6 closes the only finding from review 6 by adding two +priority-ordered entries to +[`docs/dev/issues/open.md`](../issues/open.md): issue 105 (orphaned +helper) and issue 106 (`result_kind` default rationale). Both entries +are well-formed, sequentially numbered, and listed in the summary +table. No new findings. + +## Verification of reply-6 action + +### Review-6 F1 — Deferrals from review 5 not tracked in `open.md` + +**Reply verdict.** Agree; track in `open.md`. + +**Current state.** +[`open.md:1785-1827`](../issues/open.md:1785) now contains two new +entries: + +- **Issue 105 — Remove Orphaned Fit-Result Reset Helper.** Source + attribution: `minimizer-input-output-split` review 6. Points at + [`analysis.py#L1217`](../../../src/easydiffraction/analysis/analysis.py#L1217), + which is the exact definition line of the orphaned method. + Description and proposed fix match review-5 F1. +- **Issue 106 — Document `FitResultBase.result_kind` Default + Rationale.** Source attribution: same. Points at + [`base.py#L44`](../../../src/easydiffraction/analysis/categories/fit_result/base.py#L44), + the descriptor construction line. Description matches review-5 F4 + and reply-5's intent paragraph. + +Both rows also appear in the summary table at the foot of `open.md` +with severity 🟢 Low and the correct types (Cleanup, Code +readability). Numbering is contiguous with the prior tail (104 → 105 → +106). + +One minor nit on attribution accuracy: the substantive findings +originated in review 5 (F1 dead method, F4 `result_kind` comment). +Review 6 only flagged the tracking gap. Sourcing both new issues to +"review 6" is defensible (review 6 caused the issue entries to be +created) and not worth correcting; the pointers and descriptions are +right, which is what matters for a future contributor following the +trail. + +Resolved. + +## Findings (this review) + +None. The action requested by review 6 was applied verbatim; the entry +format matches the surrounding entries; the cross-references between +code and `open.md` are accurate; the summary table is updated. + +The substantive code review from review 5 still stands: paired-class +invariant is solid, legacy-tag rejection is comprehensive, new +fit_result unit tests cover the required behaviour, migration greps +return clean. Nothing has changed since. + +## Recommended next steps + +1. **Open the PR.** No remaining blockers. Plan is fully `[x]`. PR + description (per the post-reply-5 amendment) is accurate. Both + deferred cleanups are tracked in `open.md` and can be picked up + independently of this PR. +2. **CI is the next gate.** The reviewer chain has not run any `pixi + run` command across reviews 4-7; trust the author's Phase 2 pass + and gate on the merge pipeline. + +## Verification commands run for this review + +Static reads only: + +```text +git log --oneline cf7c97369..HEAD # 1 new commit (d5c03b01d) +git show --stat d5c03b01d # docs-only +git show d5c03b01d -- docs/dev/issues/open.md # exact added entries +grep -nE '^## (105|106)\.' docs/dev/issues/open.md # both present, sequential +grep -nE '^\| (105|106) ' docs/dev/issues/open.md # both in summary table +git grep -nE _clear_fit_result_projection src/ tests/ # still 1 hit + # (definition only — by design, + # tracked as issue 105) +``` + +No `pixi run fix`, `pixi run check`, `pixi run unit-tests`, +`pixi run integration-tests`, or `pixi run script-tests` was executed. From 288178d56e8aabb3b4dd65dfff1333939ae990b4 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Sun, 24 May 2026 17:12:59 +0200 Subject: [PATCH 38/38] Drop minimizer-input-output-split review/reply files --- docs/dev/issues/open.md | 10 +- .../minimizer-input-output-split_reply-1.md | 146 --------- .../minimizer-input-output-split_reply-2.md | 113 ------- .../minimizer-input-output-split_reply-3.md | 62 ---- .../minimizer-input-output-split_reply-4.md | 99 ------ .../minimizer-input-output-split_reply-5.md | 72 ----- .../minimizer-input-output-split_reply-6.md | 48 --- .../minimizer-input-output-split_reply-7.md | 36 --- .../minimizer-input-output-split_review-1.md | 76 ----- .../minimizer-input-output-split_review-2.md | 72 ----- .../minimizer-input-output-split_review-3.md | 23 -- .../minimizer-input-output-split_review-4.md | 254 --------------- .../minimizer-input-output-split_review-5.md | 297 ------------------ .../minimizer-input-output-split_review-6.md | 172 ---------- .../minimizer-input-output-split_review-7.md | 117 ------- 15 files changed, 5 insertions(+), 1592 deletions(-) delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-1.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-2.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-3.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-4.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-5.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-6.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_reply-7.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-1.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-2.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-3.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-4.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-5.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-6.md delete mode 100644 docs/dev/plans/minimizer-input-output-split_review-7.md diff --git a/docs/dev/issues/open.md b/docs/dev/issues/open.md index 70096e59b..1e0bfe340 100644 --- a/docs/dev/issues/open.md +++ b/docs/dev/issues/open.md @@ -1804,14 +1804,14 @@ instance. ## 106. 🟢 Document `FitResultBase.result_kind` Default Rationale -**Type:** Code readability **Source:** -`minimizer-input-output-split` review 6. +**Type:** Code readability **Source:** `minimizer-input-output-split` +review 6. Most `FitResultBase` descriptors use `default=None, allow_none=True` so pre-fit CIF output serializes unknown values as `?`. `result_kind` -intentionally keeps a valid enum default because it drives -deterministic versus Bayesian projection handling, but that exception -is not documented in code. +intentionally keeps a valid enum default because it drives deterministic +versus Bayesian projection handling, but that exception is not +documented in code. **TODOs:** diff --git a/docs/dev/plans/minimizer-input-output-split_reply-1.md b/docs/dev/plans/minimizer-input-output-split_reply-1.md deleted file mode 100644 index 77959a696..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-1.md +++ /dev/null @@ -1,146 +0,0 @@ -# Reply to Review 1: Minimizer Input/Output Split Plan - -Reply to -[`minimizer-input-output-split_review-1.md`](minimizer-input-output-split_review-1.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -All four findings agreed. Each was addressed by editing the plan text; -no item is deferred. The plan is updated in the same commit as this -reply. - -## Findings - -### Finding 1 — `_clear_persisted_fit_state` resets `_fit_result` to common-only class - -**Verdict: agree — real omission in P1.6.** - -The current `_clear_persisted_fit_state` (analysis.py:1204) does -`self._fit_result = FitResult()` and calls -`_clear_minimizer_result_projection()`. The original P1.6 wired the -initial `_fit_result` and the swap path but said nothing about the reset -path that fires before every fit. After the split, that path would -discard the paired `LeastSquaresFitResult` / `BayesianFitResult` and -reinstall the bare common class — so the first family-specific `_set_*` -call in P1.7 / P1.8 would write to an attribute that no longer exists on -the live instance. - -**Action taken.** Extended P1.6 to enumerate every `_fit_result` -construction and reset site in `analysis.py`: - -- `__init__` builds via `self._minimizer._fit_result_class()`. -- `_replace_minimizer` builds via `new_minimizer._fit_result_class()` - after detaching the old. -- `_clear_persisted_fit_state` rebuilds via - `self.minimizer._fit_result_class()` so the paired-class invariant - survives a reset. -- `_clear_minimizer_result_projection` is renamed - `_clear_fit_result_projection` and retargeted to call - `self.fit_result._reset_result_descriptors()`, since - `LeastSquaresMinimizerBase` / `BayesianMinimizerBase` lose their - `_result_descriptor_names` content at P1.9 / P1.10. -- A - `git grep -nE 'self\._fit_result\s*=' src/easydiffraction/analysis/analysis.py` - check at the end of P1.6 confirms every construction goes through the - paired class. P1.9 also notes that the LSQ `_result_descriptor_names` - becomes `()` after the removal, so the rename in P1.6 is the correct - landing. - -**Plan section:** P1.6 (rewritten), P1.9 (added confirmation note). - -### Finding 2 — P1.2 regresses pre-fit LSQ output defaults - -**Verdict: agree — would undo a consolidation-cleanup decision.** - -The consolidation cleanup (Review 8 F6, addressed in commit `28d4291cb`) -explicitly moved LSQ result descriptors to -`default=None, allow_none=True` so a pre-fit CIF emits `?` rather than -misleading `0` / `false` / `''` values. The original P1.2 said "bool -defaults `False`; string defaults `''`", which would undo that fix on -every LSQ output relocated to `LeastSquaresFitResult`. - -**Action taken.** Rewrote P1.2 to specify: - -> All defaults are `None` with `allow_none=True`, matching the -> consolidation cleanup that previously moved LSQ outputs off `0` / -> `false` / `''` so a pre-fit CIF emits `?` rather than a value that -> looks like a degenerate result. This applies to numeric, integer-like, -> string, and bool fields alike; the descriptor helpers in -> `LeastSquaresMinimizerBase` that currently produce these descriptors -> are the model — they can be lifted into `LeastSquaresFitResult` -> verbatim before being removed from `lsq_base.py` at P1.9. - -The lift-verbatim instruction also reduces drift risk: the helper -functions move with their existing defaults rather than being rewritten. - -**Plan section:** P1.2 (rewritten). - -### Finding 3 — Package-level `FitResult` imports outside `fit_result/__init__.py` - -**Verdict: agree — four sites missing from the plan.** - -`git grep -nP '\bFitResult\b' src/` lists four references that the -original P1.1 did not call out explicitly: - -- `src/easydiffraction/analysis/__init__.py:14` -- `src/easydiffraction/analysis/categories/__init__.py:14` -- `src/easydiffraction/analysis/analysis.py:18` (import) -- `src/easydiffraction/analysis/analysis.py:432` (property type - annotation) -- `src/easydiffraction/analysis/analysis.py:483` (construction in - `__init__`) -- `src/easydiffraction/analysis/analysis.py:1208` (construction in - `_clear_persisted_fit_state`) - -Without explicit migration, after P1.16 removes the old `FitResult` -re-export, these imports break or keep loading the wrong class. - -**Action taken.** Rewrote P1.1 to enumerate every site explicitly, with -a `git grep -nP '\bFitResult\b' src/` verification gate at the end of -the step. The two `self._fit_result = FitResult()` constructions become -`FitResultBase()` temporarily and are then retargeted to the paired -class in P1.6. - -**Plan section:** P1.1 (rewritten with the full call-out list and -verification grep). - -### Finding 4 — Fit-result factory inconsistency - -**Verdict: agree — `factory.py` already exists, and the original text -was internally inconsistent about whether it or `_fit_result_class` is -authoritative for swap.** - -The current `fit_result/factory.py` is a 15-line `FactoryBase` -registration shell. The original P1.4 called it "new" and said the -factory is used by `_swap_minimizer`, while P1.6 said -`_replace_minimizer` constructs via `new_minimizer._fit_result_class()` -directly — pick one. - -**Action taken.** Two coordinated edits: - -- The §"Created" list now says the factory exists; this plan extends - rather than creates it. The same note clarifies that the authoritative - swap mechanism is `_fit_result_class` (on the minimizer base), with - the factory as a registration/introspection helper. -- P1.4 now says "Register fit-result classes with the existing - `FitResultFactory`" and adds an explicit "Authoritative mechanism" - paragraph stating that `_swap_minimizer` reads the paired class off - `new_minimizer._fit_result_class`, not via a factory lookup. - -**Plan section:** §"Created" (factory bullet), P1.4 (rewritten). - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, or -test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — plan updated per all four findings above (P1.1, P1.2, P1.4, P1.6, - P1.9 + §"Created" bullet). -- [`docs/dev/plans/minimizer-input-output-split_reply-1.md`](minimizer-input-output-split_reply-1.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-2.md b/docs/dev/plans/minimizer-input-output-split_reply-2.md deleted file mode 100644 index d8aef66fb..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-2.md +++ /dev/null @@ -1,113 +0,0 @@ -# Reply to Review 2: Minimizer Input/Output Split Plan - -Reply to -[`minimizer-input-output-split_review-2.md`](minimizer-input-output-split_review-2.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -All three findings agreed. Each was addressed by editing the plan text -in the same commit as this reply. - -## Findings - -### Finding 1 — Removed output names treated as one-to-one renames - -**Verdict: agree — would mis-migrate the two collapsed-name fields.** - -The ADR collapses `runtime_seconds` onto the existing -`fit_result.fitting_time` and `iterations_performed` onto -`fit_result.iterations`. The original P1.15 and P2.1 said "replace each -`analysis.minimizer.` with -`analysis.fit_result.`" and used `_set_runtime_seconds` → -`_set_runtime_seconds` as the fixture example — both are wrong for those -two fields, where the target field and setter already exist under -different names on `FitResultBase`. - -**Action taken.** Rewrote P1.15 with an explicit migration table -covering all 19 removed fields. The two collapsed rows are flagged with -a "Collapsed onto existing common field" note and point at the existing -`fit_result._set_fitting_time(...)` / `fit_result._set_iterations(...)` -setters. The other 17 rows are moved-but-keep-the-name relocations (e.g. -`analysis.minimizer.gelman_rubin_max` → -`analysis.fit_result.gelman_rubin_max`). - -P2.1 was rewritten to point at the same migration table and to include -both setter-rename examples — moved-but-kept and collapsed — so test -fixtures get the right setter on each side. - -**Plan section:** P1.15 (migration table added), P2.1 (examples -updated). - -### Finding 2 — `_reset_result_descriptors()` not yet defined on `FitResultBase` - -**Verdict: agree — Phase 1 wired a call to a method that did not exist -yet.** - -The method lives on `MinimizerCategoryBase` -([`minimizer/base.py:69-74`](../../../src/easydiffraction/analysis/categories/minimizer/base.py)) -and the existing `FitResult` class has no equivalent. Adding it only in -P1.2 / P1.3 (on the family classes) wouldn't help, because P1.6 calls it -on the union type `FitResultBase`. If P1.6 ran before the helper was -added, every `_clear_persisted_fit_state` reset would raise -`AttributeError`. - -**Action taken.** Extended P1.1 to add two class-level hooks on -`FitResultBase` during the rename: - -- `_result_descriptor_names: ClassVar[tuple[str, ...]]` initialised to - the existing common fields (`success`, `message`, `iterations`, - `fitting_time`, `reduced_chi_square`, `result_kind`). -- `_reset_result_descriptors()` implemented exactly as on - `MinimizerCategoryBase` (walk `_result_descriptor_names`, reset to - `_value_spec.default_value()`). - -The family classes (P1.2 / P1.3) extend `_result_descriptor_names` with -their own field names so the inherited helper resets the full descriptor -set on the active paired class. The plan now ensures the helper is in -place before P1.6 wires the swap and reset retargeting. - -**Plan section:** P1.1 (added hooks paragraph), P1.6 (already correct -now that the helper is guaranteed present). - -### Finding 3 — CIF ordering steps contradicted each other - -**Verdict: agree — P1.11 and P1.12 stated incompatible contracts.** - -P1.11 claimed the order was enforced by `_serializable_categories` -putting `fit_result` directly after `minimizer`. P1.12 said -`_serializable_categories` already includes `fit_result` via -`_fit_state_categories`. The current code conditionally appends -`fit_result` only when `_has_persisted_fit_state()` is true, so -following P1.11 literally would make pre-fit projects emit a default -`_fit_result.*` block — a regression in CIF compactness. - -**Action taken.** Rewrote both steps to pick the conditional contract: - -- P1.11 now says the read-side order is **already correct** because - `_set_minimizer_type` runs before `analysis.minimizer.from_cif` and - the paired `fit_result` swap fires inside `_set_minimizer_type` after - P1.6. The emit-side `_serializable_categories()` / - `_fit_state_categories()` shape is preserved (conditional inclusion); - explicit "do not promote `fit_result` to unconditional" instruction - added. -- P1.12 now confirms that `_fit_state_categories()` returns the paired - instance automatically once P1.6 wires the construction — no method - body change needed. - -**Plan section:** P1.11 (rewritten), P1.12 (rewritten). - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, or -test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — plan updated per all three findings (P1.1, P1.11, P1.12, P1.15, - P2.1). -- [`docs/dev/plans/minimizer-input-output-split_reply-2.md`](minimizer-input-output-split_reply-2.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-3.md b/docs/dev/plans/minimizer-input-output-split_reply-3.md deleted file mode 100644 index 166593e45..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-3.md +++ /dev/null @@ -1,62 +0,0 @@ -# Reply to Review 3: Minimizer Input/Output Split Plan - -Reply to -[`minimizer-input-output-split_review-3.md`](minimizer-input-output-split_review-3.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -## Findings - -### Finding 1 — CIF ordering steps still contradictory - -**Verdict: partial — intent was correct in reply 2, wording could be -misread.** - -The reviewer acknowledges review 2 F1 and F2 are addressed and re-raises -only the CIF-ordering concern. Reading the post-reply-2 text, P1.11 did -already state the conditional contract explicitly ("`self.fit_result` is -**conditionally** included only when persisted fit state exists … Do not -promote `fit_result` to an unconditional category"). However, the same -step opened with a paragraph about the **read** side that mentioned -"after `_minimizer.*`" and the reviewer appears to have carried that -ordering language across into the **emit** side, where it is exactly -what we do not want. - -**Action taken.** Rewrote P1.11 with the no-reordering rule as the -**first** sentence of the step, before any read/emit detail: - -> **No category-list reordering is performed in this step.** Neither -> `Analysis._serializable_categories()` nor -> `Analysis._fit_state_categories()` is restructured. `fit_result` stays -> conditionally included by `_fit_state_categories()` only when -> `self._has_persisted_fit_state()` is true — exactly as today. Pre-fit -> projects continue to emit no `_fit_result.*` block. - -The remaining bullets list only content-level changes (which descriptors -flow through emit/read, the legacy-tag rejection message), and each is -explicitly framed as "no reordering, no new call" or "no code change is -required here". The earlier phrasing about the read order being "already -correct" was retained but moved below the no-reordering rule and -reframed as a confirmation rather than a directive. - -P1.12 was already clear that `_fit_state_categories()` is unchanged (it -just walks the paired instance after P1.6 wires it). No further P1.12 -edits. - -**Plan section:** P1.11 (rewritten with the no-reordering rule as the -first paragraph; intent unchanged but explicit). - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, or -test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — P1.11 reworded to lead with the no-reordering rule. -- [`docs/dev/plans/minimizer-input-output-split_reply-3.md`](minimizer-input-output-split_reply-3.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-4.md b/docs/dev/plans/minimizer-input-output-split_reply-4.md deleted file mode 100644 index 6be37080f..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-4.md +++ /dev/null @@ -1,99 +0,0 @@ -# Reply to Review 4: Minimizer Input/Output Split Branch - -Reply to -[`minimizer-input-output-split_review-4.md`](minimizer-input-output-split_review-4.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Context: Review 4 is a post-Phase-1 static review. Phase 2 has not -started. The review confirms the Phase 1 gate and raises five findings, -none marked as a blocker. - -## Findings - -### F1 — `FitResultBase` common-header defaults diverge from family classes - -**Verdict: agree.** The split makes the old common-header defaults more -visible: `success=False`, `message=''`, and `iterations=0` now sit next -to family-specific result descriptors that deliberately use -`default=None, allow_none=True`. The same mismatch exists for Bayesian -`point_estimate_name='best_sample'` and `sampler_completed=False`. - -**Decision.** Address during Phase 2 cleanup before adding the new -`fit_result` unit tests. Align the common result header and Bayesian -result-only fields with the `None`/`allow_none=True` convention so -pre-fit CIF output keeps using `?` rather than values that look like a -completed or failed fit. - -### F2 — `_clear_persisted_fit_state` resets descriptors before replacing the instance - -**Verdict: agree.** Calling `_clear_fit_result_projection()` on the old -instance immediately before replacing `self._fit_result` is dead work. - -**Decision.** Address during Phase 2 cleanup. Keep the fresh-instance -shape and drop the redundant reset call; that preserves the paired-class -invariant and removes the wasted descriptor walk. - -### F3 — `_settings_used_rows` carries an unreachable fallback branch - -**Verdict: agree.** `_setting_descriptor_names` is a controlled -class-level declaration and each name should resolve to a descriptor. -The fallback branch is defensive code for an invalid internal -declaration. - -**Decision.** Address during Phase 2 cleanup. Drop the fallback and let -an invalid `_setting_descriptor_names` entry fail loudly. - -### F4 — Phase 2 not started; one test file expected to break - -**Verdict: agree.** This is exactly the expected Phase 1 to Phase 2 -gate. `test_lsq_base.py` still targets result fields that moved from -`analysis.minimizer` to `analysis.fit_result`. - -**Decision.** No extra plan change needed. P2.1 remains the migration -step for the stranded tests, and P2.2 adds the missing fit-result unit -test coverage. - -### F5 — Unrelated dependency cleanup landed alongside Phase 1 - -**Verdict: agree that it is unrelated to the input/output split ADR.** -The `essdiffraction` removal was a direct CI dependency cleanup request, -not part of this design change. - -**Decision.** Keep it in the branch unless the PR owner wants a cleaner -history split. If it remains bundled, call it out explicitly in the PR -description so reviewers understand the `pixi.lock` churn is a separate -CI/dependency cleanup. - -## Phase 2 Carry-Forward - -Before running the normal Phase 2 verification sequence, include a small -cleanup pass for F1 through F3: - -1. Align pre-fit result defaults to `None` where result fields are not - known yet. -2. Remove the redundant old-instance fit-result reset before replacing - `self._fit_result`. -3. Remove the unreachable settings-table fallback branch. - -Then proceed with the existing Phase 2 checklist: - -- P2.1 test migration. -- P2.2 new `fit_result` unit tests. -- P2.3 `pixi run fix` and `pixi run check`. -- P2.4 `pixi run unit-tests`. -- P2.5 `pixi run integration-tests`. -- P2.6 `pixi run script-tests`. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, or -test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/plans/minimizer-input-output-split_reply-4.md`](minimizer-input-output-split_reply-4.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-5.md b/docs/dev/plans/minimizer-input-output-split_reply-5.md deleted file mode 100644 index b7839efda..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-5.md +++ /dev/null @@ -1,72 +0,0 @@ -# Reply to Review 5: Minimizer Input/Output Split Branch - -Reply to -[`minimizer-input-output-split_review-5.md`](minimizer-input-output-split_review-5.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Context: Review 5 is a post-Phase-2 static review. It reports no -blockers and lists two cleanup findings plus two informational notes. - -## Findings - -### F1 — `_clear_fit_result_projection` is defined but never called - -**Verdict: agree.** The method became dead private code after the -reply-4 cleanup changed `_clear_persisted_fit_state` to replace -`self._fit_result` with a fresh paired instance. - -**Decision.** Defer this to a follow-up cleanup rather than reopening -the completed Phase 2 implementation for a non-behavioural private -method deletion. If another code cleanup commit is made on this branch -before PR opening, deleting the method there is fine; it is not required -for merge. - -### F2 — Phase 2 bundles lint-driven refactors outside the split - -**Verdict: agree.** These changes are outside the conceptual -minimizer/fit-result split. They were a direct result of the Phase 2 -`pixi run check` complexity gate, and they preserve behaviour while -following the repository rule to refactor rather than raise or silence -lint thresholds. - -**Decision.** Keep the refactors bundled in this branch instead of -rewriting history or splitting them now. The plan's suggested PR -description has been updated to call them out explicitly under -"Incidental cleanup" so reviewers know why unrelated files changed. - -### F3 — `essdiffraction` removal is still bundled - -**Verdict: agree.** This remains unrelated to the input/output split and -was a separate CI dependency cleanup request. - -**Decision.** Keep it bundled, matching the reply-4 decision. The plan's -suggested PR description now mentions the `essdiffraction` removal -alongside the lint-driven refactors. - -### F4 — `FitResultBase.result_kind` exception undocumented - -**Verdict: agree, but treat as informational.** `result_kind` needs a -valid enum default because it is used to decide deterministic versus -Bayesian projection handling. The `None`/`allow_none=True` convention is -still correct for result values that are unknown before a fit. - -**Decision.** Defer the optional explanatory comment to a follow-up. The -current behaviour is intentional, tested through the fit-result unit -tests, and not a merge blocker. - -## Verification - -This is a static reply and PR-description update only. No `pixi run`, -lint, build, formatter, or test command was executed. - -## Summary of files touched by this reply - -- [`docs/dev/plans/minimizer-input-output-split.md`](minimizer-input-output-split.md) - — updated the suggested PR description with the incidental cleanup - note requested by review 5. -- [`docs/dev/plans/minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-6.md b/docs/dev/plans/minimizer-input-output-split_reply-6.md deleted file mode 100644 index bd5453452..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-6.md +++ /dev/null @@ -1,48 +0,0 @@ -# Reply to Review 6: Minimizer Input/Output Split Branch - -Reply to -[`minimizer-input-output-split_review-6.md`](minimizer-input-output-split_review-6.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Context: Review 6 is a static review of -[`minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) -and the docs-only commit that added it. It raises one follow-through -finding: the review-5 deferrals should either be applied inline or -tracked in `docs/dev/issues/open.md`. - -## Findings - -### F1 — Deferrals from review 5 not tracked in `docs/dev/issues/open.md` - -**Verdict: agree.** Deferring the dead private method and the optional -`result_kind` explanatory comment was reasonable, but the deferrals -should not live only in the review thread. - -**Decision.** Track both deferred items in -[`../issues/open.md`](../issues/open.md) rather than reopen the -completed Phase 2 implementation for non-behavioural cleanup: - -1. Issue 105 tracks removing the orphaned - `Analysis._clear_fit_result_projection` helper. -2. Issue 106 tracks adding a short comment explaining why - `FitResultBase.result_kind` keeps a concrete enum default while - unknown result values use `None`. - -This resolves the trail requested by review 6. No source files were -changed. - -## Verification - -This is a static reply and issue-log update only. No `pixi run`, lint, -build, formatter, or test command was executed. - -## Summary of files touched by this reply - -- [`../issues/open.md`](../issues/open.md) — added issues 105 and 106 - for the two deferred review-5 cleanup items. -- [`minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_reply-7.md b/docs/dev/plans/minimizer-input-output-split_reply-7.md deleted file mode 100644 index 8c6cbe0e6..000000000 --- a/docs/dev/plans/minimizer-input-output-split_reply-7.md +++ /dev/null @@ -1,36 +0,0 @@ -# Reply to Review 7: Minimizer Input/Output Split Branch - -Reply to -[`minimizer-input-output-split_review-7.md`](minimizer-input-output-split_review-7.md) -for the plan at -[`minimizer-input-output-split.md`](minimizer-input-output-split.md). - -This reply follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Context: Review 7 is a static review of -[`minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) -and the docs-only commit that added issue-log tracking for the -review-5 deferred cleanups. - -## Findings - -Review 7 reports no findings. It confirms that reply 6 resolved the -review-6 tracking gap by adding issues 105 and 106 to -[`../issues/open.md`](../issues/open.md), and that both entries are -well-formed and listed in the summary table. - -## Decision - -No further action is needed from this review round. The branch remains -ready for PR opening, with CI as the next gate. - -## Verification - -This is a static reply only. No `pixi run`, lint, build, formatter, or -test command was executed. - -## Summary of files touched by this reply - -- [`minimizer-input-output-split_reply-7.md`](minimizer-input-output-split_reply-7.md) - — this reply. diff --git a/docs/dev/plans/minimizer-input-output-split_review-1.md b/docs/dev/plans/minimizer-input-output-split_review-1.md deleted file mode 100644 index e081e021a..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-1.md +++ /dev/null @@ -1,76 +0,0 @@ -# Review 1: Minimizer Input/Output Split Plan - -## Findings - -1. **High — New fits will reset `fit_result` back to the common-only - class unless `_clear_persisted_fit_state()` is updated.** P1.6 wires - the initial and swapped `_fit_result` instances from the active - minimizer's `_fit_result_class` - (`minimizer-input-output-split.md:226-237`), then P1.7/P1.8 route - family-specific `_set_*` calls to `self.fit_result` (`:241-256`). But - the current fit path calls `_capture_fit_parameter_state()`, which - calls `_clear_persisted_fit_state()` before storing results; that - method currently replaces `_fit_result` with the common `FitResult()` - class and calls `_clear_minimizer_result_projection()` - (`src/easydiffraction/analysis/analysis.py:1204-1219`). If the plan - does not update that reset path, the first post-split fit will - discard the paired `LeastSquaresFitResult` / `BayesianFitResult` - instance before P1.7/P1.8 write family-specific fields. Add an - explicit step to reset `_fit_result` with - `self.minimizer._fit_result_class()`, attach `_parent`, and either - remove or retarget `_clear_minimizer_result_projection()` to the new - fit-result object. - -2. **High — P1.2 regresses pre-fit LSQ output defaults from unknown to - false/empty-string.** P1.2 says LSQ bool outputs default to `False` - and string outputs default to `''` - (`minimizer-input-output-split.md:177-188`). The existing minimizer - result descriptors deliberately default string, integer-like, and - bool result fields to `None` so CIF emits `?` before any fit and - users do not read `false`, `0`, or an empty string as an actual fit - result - (`src/easydiffraction/analysis/categories/minimizer/lsq_base.py:113-126`, - `:145-175`). Moving the fields to `LeastSquaresFitResult` should - preserve that no-fit semantics. Please change P1.2 to keep LSQ string - and bool output defaults as `None` / `allow_none=True`, except for - any field where the ADR explicitly chooses a concrete pre-fit - default. - -3. **Medium — The import/export surfaces outside - `fit_result/__init__.py` are missing from the plan.** The plan - updates only - `src/easydiffraction/analysis/categories/fit_result/__init__.py` for - the new fit-result classes - (`minimizer-input-output-split.md:109-114`, `:202-211`). The repo - also explicitly imports `FitResult` from - `src/easydiffraction/analysis/__init__.py` and - `src/easydiffraction/analysis/categories/__init__.py`, and - `analysis.py` imports and annotates it directly - (`src/easydiffraction/analysis/__init__.py:14-15`, - `src/easydiffraction/analysis/categories/__init__.py:14`, - `src/easydiffraction/analysis/analysis.py:18`, `:432`). Once the old - `FitResult` re-export is removed at P1.16 - (`minimizer-input-output-split.md:165-175`), these surfaces can break - or keep exporting the wrong class. Add explicit steps to update the - package-level imports and all direct `FitResult` type - annotations/constructions. - -4. **Medium — The fit-result factory plan is internally inconsistent.** - The "Created" list and P1.4 call - `src/easydiffraction/analysis/categories/fit_result/factory.py` a new - file, but it already exists - (`minimizer-input-output-split.md:99-101`, `:202-211`; - `src/easydiffraction/analysis/categories/fit_result/factory.py:1-15`). - More importantly, P1.4 says the factory is used only by - `Analysis._swap_minimizer`, while P1.6 says `_replace_minimizer` - constructs `new_minimizer._fit_result_class()` directly - (`minimizer-input-output-split.md:202-211`, `:226-237`). Pick one - mechanism. If `_fit_result_class` is authoritative, the factory is - just a registration/testing helper and the plan should say that; if - the factory is authoritative, P1.6 should use it explicitly. - -## Checks - -Skipped by instruction: this is a static plan review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/plans/minimizer-input-output-split_review-2.md b/docs/dev/plans/minimizer-input-output-split_review-2.md deleted file mode 100644 index b106d91e0..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-2.md +++ /dev/null @@ -1,72 +0,0 @@ -# Review 2: Minimizer Input/Output Split Plan - -## Findings - -1. **High — Removed output names are still described as one-to-one - `fit_result` replacements.** P1.15 and P2.1 say to replace every - `analysis.minimizer.` with - `analysis.fit_result.`, and even give - `analysis.minimizer._set_runtime_seconds(...)` → - `analysis.fit_result._set_runtime_seconds(...)` as the fixture - migration example (`minimizer-input-output-split.md:426-474`). That - replacement is wrong for the fields the ADR intentionally collapses - into existing common fit-result names: `runtime_seconds` becomes - `fit_result.fitting_time`, and `iterations_performed` becomes - `fit_result.iterations`. The current common category already exposes - `_set_fitting_time` and `_set_iterations`, not `_set_runtime_seconds` - / `_set_iterations_performed` - (`src/easydiffraction/analysis/categories/fit_result/default.py:113-126`). - If the plan is followed literally, tutorials/tests will be migrated - to attributes and setters that do not exist after P1.2/P1.3. Please - add an explicit migration map for renamed outputs, at minimum - `runtime_seconds` → `fitting_time` and `iterations_performed` → - `iterations`, and say the existing common projection writer owns - those values rather than moving the old minimizer - `_set_runtime_seconds` call verbatim. - -2. **High — P1.6 retargets reset calls to a method that no fit-result - class is told to implement.** P1.6 says - `_clear_minimizer_result_projection()` should become - `_clear_fit_result_projection()` and call - `self.fit_result._reset_result_descriptors()` - (`minimizer-input-output-split.md:280-294`). Today - `_reset_result_descriptors()` exists only on `MinimizerCategoryBase` - (`src/easydiffraction/analysis/categories/minimizer/base.py:69-74`), - while the current `FitResult` class has no equivalent helper - (`src/easydiffraction/analysis/categories/fit_result/default.py:20-135`). - P1.2/P1.3 ask the new family classes to declare - `_result_descriptor_names`, and P2.2 later mentions testing - `FitResultBase._reset_result_descriptors`, but no Phase 1 - implementation step actually adds that method before P1.6 starts - calling it. Add an explicit P1.1/P1.2 instruction to put - `_result_descriptor_names` and `_reset_result_descriptors()` on - `FitResultBase` (or a shared helper) before retargeting the clear - path. - -3. **Medium — The CIF ordering steps contradict each other and may - change pre-fit emission.** P1.11 says `_fit_result.*` order is - enforced by `Analysis._serializable_categories()` putting - `self.fit_result` directly after `self.minimizer` - (`minimizer-input-output-split.md:369-376`), but P1.12 immediately - says `_serializable_categories` already includes `self.fit_result` - via `_fit_state_categories` - (`minimizer-input-output-split.md:386-392`). In current code, - `fit_result` is appended only when persisted fit state exists - (`src/easydiffraction/analysis/analysis.py:852-873`, `:1182-1186`), - and CIF read order is already handled separately by - `analysis_from_cif`: minimizer type is restored before - `analysis.fit_result.from_cif(block)` runs - (`src/easydiffraction/io/cif/serialize.py:552-590`). If an - implementer follows P1.11 literally by moving `fit_result` directly - into the main category list after `minimizer`, pre-fit CIFs may start - emitting default `_fit_result.*` fields. Please make the plan choose - one contract: keep `fit_result` conditional inside - `_fit_state_categories` and describe the ordering as "after minimizer - when persisted", or explicitly require a guarded direct insertion - that preserves the current no-persisted-fit-state behavior. - -## Checks - -Skipped by instruction: this is a static plan review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/plans/minimizer-input-output-split_review-3.md b/docs/dev/plans/minimizer-input-output-split_review-3.md deleted file mode 100644 index 8be148422..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-3.md +++ /dev/null @@ -1,23 +0,0 @@ -# Review 3: Minimizer Input/Output Split Plan - -## Findings - -No issues found. - -Review 2's remaining concerns are addressed in the current plan: - -- P1.1 adds `_result_descriptor_names` and `_reset_result_descriptors()` - to `FitResultBase` before P1.6 retargets reset calls. -- P1.15 and P2.1 now include an explicit migration table for collapsed - field names (`runtime_seconds` → `fitting_time`, - `iterations_performed` → `iterations`) and moved fields. -- P1.11 now explicitly preserves the existing conditional - `_fit_state_categories()` emission path and says no - `_serializable_categories()` reordering is performed, so pre-fit - projects keep emitting no `_fit_result.*` block. - -## Checks - -Skipped by instruction: this is a static plan review only. I did not run -tests, `pixi run fix`, `pixi run check`, or any other build or -verification command. diff --git a/docs/dev/plans/minimizer-input-output-split_review-4.md b/docs/dev/plans/minimizer-input-output-split_review-4.md deleted file mode 100644 index bbc481e3b..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-4.md +++ /dev/null @@ -1,254 +0,0 @@ -# Review 4: Minimizer Input/Output Split Branch (Post-Phase 1) - -Reviewed plan: -[`minimizer-input-output-split.md`](minimizer-input-output-split.md) - -Reviewed ADR: -[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) - -This review follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or -any build/verification command was executed**. This is a static read of -the 31 commits on `minimizer-input-output-split` against -`origin/develop`, plus the new accepted ADR and amended ADRs. - -## Scope - -The branch carries: - -- Phase 1 of the input/output split plan (17 commits, P1.1–P1.17 + ADR - promotion + an unrelated dependency cleanup). -- The accepted ADR - [`minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) - (promoted from suggestions at P1.16). -- A new suggestion ADR - [`iucr-cif-tag-alignment.md`](../adrs/suggestions/iucr-cif-tag-alignment.md) - capturing a separate IUCr-alignment proposal for future work. - -Phase 2 (P2.1–P2.6: test migration, fix, check, unit/integration/ script -tests) has not started yet; this review confirms the gate. - -## Summary - -Phase 1 implementation matches the plan. Each plan step maps 1:1 to a -commit. The new `fit_result` family classes are in place -([base.py](src/easydiffraction/analysis/categories/fit_result/base.py), -[lsq.py](src/easydiffraction/analysis/categories/fit_result/lsq.py), -[bayesian.py](src/easydiffraction/analysis/categories/fit_result/bayesian.py)); -`Analysis._swap_minimizer` wires the paired instance atomically; -`_clear_persisted_fit_state` reinstates the paired class on reset; the -LSQ and Bayesian minimizer bases no longer carry their output -descriptors; CIF serialisation routes outputs to `_fit_result.*` and -rejects the legacy `_minimizer.` tags loudly; the five affected -ADRs are amended; the display facade now prints a "Settings used" block -above the existing fit-result tables; tutorials are migrated. - -All stale-reference greps return clean: - -- `analysis.minimizer.` in `src/`, - `docs/docs/tutorials/`, `tests/` — empty. -- `_minimizer.` in `src/`, `docs/docs/tutorials/`, - `tests/` — only present inside `serialize.py` as the legacy-tag - rejection list (intentional). - -Five small observations follow. None block Phase 2; F1 and F2 are worth -deciding consciously rather than letting them carry forward. - -## Findings - -### F1 — `FitResultBase` common-header defaults diverge from the family-class pattern - -`FitResultBase` -([base.py:42-71](src/easydiffraction/analysis/categories/fit_result/base.py:42)) -keeps the legacy common-class defaults: - -- `success` → `default=False` -- `message` → `default=''` -- `iterations` → `default=0` - -The new family classes -([lsq.py:80-142](src/easydiffraction/analysis/categories/fit_result/lsq.py:80) -and parts of -[bayesian.py](src/easydiffraction/analysis/categories/fit_result/bayesian.py)) -deliberately use `default=None, allow_none=True` for every numeric, -string, and bool result field — with explicit docstrings explaining why: -a CIF written before any fit should emit `?` rather than `0` / `false` / -empty-string, because the scientist audience reads `0` and `false` as a -real fit result. - -Pre-fit CIFs under the current layout therefore emit: - -``` -_fit_result.success false # reads as "fit ran and failed" -_fit_result.iterations 0 # reads as "fit ran for 0 iterations" -``` - -The plan inherited these defaults from the pre-split common class (see -P1.1 — "keep current common fields") so this is not a regression -introduced by this PR. But the divergence is now visible side-by-side in -the same category hierarchy: hovering on `fit_result.iterations` lands -in `FitResultBase` (defaults to `0`), hovering on -`fit_result.n_parameters` lands in `LeastSquaresFitResult` (defaults to -`None`), with no principled reason for the difference. - -Bayesian-side mirror: `point_estimate_name` defaults to `'best_sample'` -and `sampler_completed` defaults to `False` -([bayesian.py:51-69](src/easydiffraction/analysis/categories/fit_result/bayesian.py:51)), -while `acceptance_rate_mean` and friends default to `None`. Same -inconsistency. - -Suggested follow-up: align `FitResultBase` common fields (and the -Bayesian `point_estimate_name` / `sampler_completed`) with the -`None`/`allow_none=True` convention. Small diff; the existing `_set_*` -callers already pass real values when a fit runs. Defer to a follow-on -if not in scope for this PR. - -### F2 — `_clear_persisted_fit_state` resets descriptors then immediately replaces the instance - -[analysis.py:1207-1216](src/easydiffraction/analysis/analysis.py:1207): - -```python -def _clear_persisted_fit_state(self) -> None: - self._clear_fit_result_projection() # resets descriptors - self._fit_parameters = FitParameters() - self._fit_result._parent = None - self._fit_result = self.minimizer._fit_result_class() # replaces instance - self._fit_result._parent = self - self._fit_parameter_correlations = FitParameterCorrelations() - ... -``` - -`_clear_fit_result_projection()` walks `_result_descriptor_names` and -resets each to its declared default on the **old** instance, which is -then thrown away on the next line. A fresh instance has defaults by -construction; the reset call is dead work. - -Two clean shapes are possible: - -- Drop the `_clear_fit_result_projection()` call and rely on the - fresh-instance construction. Simpler. -- Drop the instance replacement and rely on - `_clear_fit_result_projection()`. Then the paired-class invariant is - preserved only as long as `minimizer.type` does not change between - fits, which is true today but is the kind of invariant a future - refactor could quietly break. - -The first is the smaller diff. Cosmetic; not a correctness issue because -both paths produce the same end state. - -### F3 — `_settings_used_rows` carries an unreachable `else` branch - -[display.py:117-129](src/easydiffraction/project/display.py:117): - -```python -def _settings_used_rows(self) -> list[list[str]]: - minimizer = self._project.analysis.minimizer - rows: list[list[str]] = [] - for name in minimizer._setting_descriptor_names: - descriptor = getattr(minimizer, name) - if isinstance(descriptor, GenericDescriptorBase): - rows.append([...]) - else: - rows.append([name, str(descriptor), '']) - return rows -``` - -Every entry in `_setting_descriptor_names` resolves to a -`GenericDescriptorBase` subclass (verified by reading -[`lsq_base.py`](src/easydiffraction/analysis/categories/minimizer/lsq_base.py) -and -[`bayesian_base.py`](src/easydiffraction/analysis/categories/minimizer/bayesian_base.py)). -The `else` branch is defensive coding against an unreachable state. Per -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) -→ **Change Discipline** "No defensive checks for unlikely edge cases." - -Suggested follow-up: drop the `else` branch and the -`isinstance(descriptor, GenericDescriptorBase)` guard. Optional — this -is one screen of code and the fallback is harmless. - -### F4 — Phase 2 not started; one test file expected to break - -`grep -rlE 'minimizer\.' tests/` returns one hit: - -- [`test_lsq_base.py`](tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py) - still asserts on `minimizer.objective_name`, - `minimizer.objective_value`, `minimizer.iterations_performed`, etc. — - fields that no longer exist on the minimizer hierarchy after P1.9. - -This is exactly what P2.1 ("Migrate existing tests off the removed -minimizer output fields") covers, and the plan explicitly defers test -migration to Phase 2. Confirming the gate: Phase 2 must run before the -branch is merge-ready. The migration table in P1.15 / P2.1 is the right -reference for the rewrites. - -Informational; the gate matches the plan's Phase 1 → Phase 2 split. - -### F5 — Unrelated dependency cleanup landed alongside Phase 1 - -Commit `c5da0b0fc Remove essdiffraction dependency` touches -[`pixi.lock`](pixi.lock) (-1124 lines), -[`pyproject.toml`](pyproject.toml) (-1 line), and -[`scipp-analysis/dream/test_package_import.py`](tests/integration/scipp-analysis/dream/test_package_import.py) -(-14 lines). It is unrelated to the input/output split work. - -Per -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) -→ **Change Discipline** "Don't add features or refactor unless asked" -and the project's "one focused PR" convention, this would normally land -on its own. Bundling it here makes the PR description slightly less -accurate (the user-facing surface is unchanged by the essdiffraction -removal). If the next reviewer flags it, the cleanest response is to -split it out as a separate PR; otherwise it is small enough to keep. - -Informational only. - -## Documentation - -- Five accepted ADRs amended as listed in the plan §"ADR": - `minimizer-category-consolidation.md`, `analysis-cif-fit-state.md`, - `runtime-fit-results.md`, `switchable-category-owned-selectors.md`, - `display-ux.md`. -- `docs/dev/adrs/index.md` lists the new ADR under Accepted. -- The plan's `_review-N` / `_reply-N` siblings remain alongside the plan - (per the project's plan-vs-ADR retention precedent — plans keep their - deliberation files until the plan itself is deleted on merge). -- The IUCr alignment idea is parked as - [`iucr-cif-tag-alignment.md`](../adrs/suggestions/iucr-cif-tag-alignment.md) - under suggestions, with a §Status Note explaining it is captured for - future work, not in scope for the current PR. -- Tutorials regenerated per the P1.15 migration table. - -## Verification commands run for this review - -No `pixi run`, no lint, no test. Static reads only: - -```text -git log --oneline origin/develop..HEAD # 31 commits -git diff origin/develop...HEAD --stat # 33 files -git grep -nE 'analysis\.minimizer\.(runtime_seconds|iterations_performed|objective_value|objective_name|n_data_points|n_parameters|n_free_parameters|degrees_of_freedom|covariance_available|correlation_available|exit_reason|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ -git grep -nE '_minimizer\.(runtime_seconds|iterations_performed|objective_value|point_estimate_name|sampler_completed|credible_interval_inner|credible_interval_outer|acceptance_rate_mean|gelman_rubin_max|effective_sample_size_min|best_log_posterior)' src/ docs/docs/tutorials/ tests/ -grep -rlE 'minimizer\.' tests/ -``` - -The first two return empty against `src/`, `docs/docs/tutorials/`, and -`tests/`. The third returns only `serialize.py` (the legacy-tag -rejection list — intentional). The fourth returns one file (the Phase 2 -migration target). - -## Recommended next steps - -1. **Decide F1 (`FitResultBase` defaults).** Either align the common - header with the family-class None-defaults convention, or document - the divergence as intentional in the accepted ADR and move on. -2. **Address F2 (redundant reset) and F3 (unreachable else).** Both are - one-line cleanups; either bundle into Phase 2 cleanup or leave for a - follow-on. -3. **Start Phase 2.** P2.1 migrates the one stranded test file; P2.2 - adds unit tests for the new fit_result classes (none exist yet under - `tests/unit/easydiffraction/analysis/categories/fit_result/`); - P2.3–P2.6 run `pixi run fix`, `check`, and the three test suites. -4. **Optional: split out `c5da0b0fc` into its own PR** for the cleanest - history; otherwise note it in the PR description so a reviewer is not - surprised by the `pixi.lock` churn. diff --git a/docs/dev/plans/minimizer-input-output-split_review-5.md b/docs/dev/plans/minimizer-input-output-split_review-5.md deleted file mode 100644 index a99ffe4c6..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-5.md +++ /dev/null @@ -1,297 +0,0 @@ -# Review 5: Minimizer Input/Output Split Branch (Post-Phase 2) - -Reviewed plan: -[`minimizer-input-output-split.md`](minimizer-input-output-split.md) - -Reviewed ADR: -[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) - -Prior reviews: -[`_review-1`](minimizer-input-output-split_review-1.md), -[`_review-2`](minimizer-input-output-split_review-2.md), -[`_review-3`](minimizer-input-output-split_review-3.md), -[`_review-4`](minimizer-input-output-split_review-4.md) and matching -replies. - -This review follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or -any other build/verification command was executed**. This is a static -read of the 34 commits on `minimizer-input-output-split` against the -last merged develop ancestor (`973214a5e`), focused on the post-Phase-2 -delta since review 4. - -## Scope - -Compared to review 4, the branch adds three commits: - -- `60788b220` Propose IUCr CIF tag alignment for fit outputs -- `6e24d62c8` Add review 4 of input/output split post-Phase 1 -- `9878c9f58` Reply to minimizer input-output review 4 -- `a2857d9d6` Complete minimizer input-output Phase 2 - -The first three are documentation only. The last lands Phase 2 in a -single commit covering P2.1 (test migration), P2.2 (new `fit_result` -unit tests), and the cleanup carry-forward agreed in -[`_reply-4`](minimizer-input-output-split_reply-4.md) §"Phase 2 -Carry-Forward" (F1 through F3 from review 4). - -## Summary - -Phase 2 closes the plan. The reply-4 cleanup items are applied, the -test migration is clean, and the new unit-test files exist under -`tests/unit/easydiffraction/analysis/categories/fit_result/`. Two new -small findings worth deciding (F1 below — orphaned method; F2 below — -lint-driven refactors bundled into Phase 2). Neither blocks merge. - -## Verification of reply-4 carry-forward - -All three items from -[`_reply-4`](minimizer-input-output-split_reply-4.md) §"Phase 2 -Carry-Forward" landed in `a2857d9d6`: - -### Review-4 F1 — `FitResultBase` defaults aligned - -[`base.py:55-78`](../../../src/easydiffraction/analysis/categories/fit_result/base.py:55) -— `success`, `message`, `iterations`, `fitting_time`, and -`reduced_chi_square` now declare `default=None, allow_none=True`. Only -`result_kind` keeps a default (`FitResultKindEnum.default().value`) -because the enum requires a valid member; that exception is reasonable -but undocumented in the class (see F4 below). - -The Bayesian side mirrors the same fix: -[`bayesian.py:51-69`](../../../src/easydiffraction/analysis/categories/fit_result/bayesian.py:51) -— `point_estimate_name` and `sampler_completed` are now -`default=None, allow_none=True`. The two credible-interval levels keep -their fixed `0.68` / `0.95` defaults per ADR §"Decisions already made" -point 6, which is correct. - -A new test pins the behaviour: -[`test_base.py:43-51`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py:43) -asserts that a pre-fit `FitResultBase` serialises `_fit_result.success`, -`_fit_result.message`, and `_fit_result.iterations` as `?`. - -Resolved. - -### Review-4 F2 — redundant reset before instance replacement removed - -[`analysis.py:1207-1216`](../../../src/easydiffraction/analysis/analysis.py:1207) -— `_clear_persisted_fit_state` now constructs a fresh paired instance -directly: - -```python -def _clear_persisted_fit_state(self) -> None: - self._fit_parameters = FitParameters() - self._fit_result._parent = None - self._fit_result = self.minimizer._fit_result_class() - self._fit_result._parent = self - ... -``` - -The pre-replacement `_clear_fit_result_projection()` call is gone. -Paired-class invariant is preserved by going through -`self.minimizer._fit_result_class()`. Resolved. - -### Review-4 F3 — unreachable `else` branch removed - -[`display.py:112-123`](../../../src/easydiffraction/project/display.py:112) -— `_settings_used_rows` no longer carries the -`isinstance(descriptor, GenericDescriptorBase)` guard or the fallback -branch. Each entry in `_setting_descriptor_names` is trusted to -resolve to a descriptor, matching the project rule "no defensive checks -for unlikely edge cases". Resolved. - -## Verification of plan-level Phase 2 steps - -- **P2.1 — test migration.** `git grep` on the patterns from P1.15 / - P2.1 returns empty against `src/`, `docs/docs/tutorials/`, and - `tests/`. The legacy `_minimizer.*` output tags remain only inside - [`serialize.py`](../../../src/easydiffraction/io/cif/serialize.py) - as the `_MINIMIZER_OUTPUT_LEGACY_TAGS` rejection list (lines 609-629) - — intentional. -- **P2.2 — new `fit_result` unit tests.** Four test files exist under - `tests/unit/easydiffraction/analysis/categories/fit_result/`: - [`test_base.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_base.py) - (defaults, reset, pre-fit CIF unknowns), - [`test_lsq.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_lsq.py) - (LSQ defaults + CIF round-trip), - [`test_bayesian.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_bayesian.py) - (Bayesian defaults including fixed credible-interval levels + CIF - round-trip), - [`test_factory.py`](../../../tests/unit/easydiffraction/analysis/categories/fit_result/test_factory.py) - (factory family creation + paired-class declarations on both - minimizer bases). Layout matches the test-structure-check expectation - (a sibling file per source file). -- **P2.3 / P2.4 / P2.5 / P2.6 — auto-fixes, static checks, three - test suites.** Marked `[x]` in the plan. Reviewer did not re-run - them; trust the author's pass and gate on CI. - -## Paired-class invariant — spot check - -Every `self._fit_result = ...` site in -[`analysis.py`](../../../src/easydiffraction/analysis/analysis.py) -constructs through `_fit_result_class`: - -- [`analysis.py:483`](../../../src/easydiffraction/analysis/analysis.py:483) - — `__init__` -- [`analysis.py:1067`](../../../src/easydiffraction/analysis/analysis.py:1067) - — `_replace_minimizer` -- [`analysis.py:1211`](../../../src/easydiffraction/analysis/analysis.py:1211) - — `_clear_persisted_fit_state` - -Each call also re-establishes `_parent = self`, and each prior instance -is explicitly detached (`_parent = None`) before being replaced. Matches -the ADR's atomic-swap requirement. - -Legacy-tag rejection -([`serialize.py:609-629`](../../../src/easydiffraction/io/cif/serialize.py:609)) -enumerates all 19 removed `_minimizer.` tags and raises a -single `ValueError` with the correct migration hint pointing at -`_fit_result.*`. Matches plan §P1.11. - -## Findings (this review) - -### F1 — `_clear_fit_result_projection` is defined but never called - -[`analysis.py:1217-1221`](../../../src/easydiffraction/analysis/analysis.py:1217): - -```python -def _clear_fit_result_projection(self) -> None: - """ - Reset result-only fields on the active fit-result category. - """ - self.fit_result._reset_result_descriptors() -``` - -`git grep -nE _clear_fit_result_projection src/ tests/` returns only -the definition line. The single caller used to be -`_clear_persisted_fit_state`, removed by the reply-4 F2 fix. After -that removal, this method is dead code. - -It is also no longer reachable from outside the class (single-underscore -private API), and the docstring matches the legacy behaviour from -before the fresh-instance approach won out, so keeping it as a future -hook is not justified. - -Suggested follow-up: delete the method. One-line cleanup; deferrable -to a follow-on if not in scope for this PR. - -### F2 — Phase 2 commit bundles five lint-driven refactors outside the input/output split - -The single Phase 2 commit `a2857d9d6` touches files unrelated to the -fit-result split: - -- [`src/easydiffraction/core/singleton.py`](../../../src/easydiffraction/core/singleton.py) - — extracts `_apply_one_constraint` from a `for` body. -- [`src/easydiffraction/analysis/sequential.py`](../../../src/easydiffraction/analysis/sequential.py) - — splits `_fit_worker` into `_fit_worker_success` / - `_fit_worker_error` plus several smaller helpers (~121 lines). -- [`src/easydiffraction/display/plotting.py`](../../../src/easydiffraction/display/plotting.py) - — extracts `_evaluate_posterior_predictive_draw_values` to flatten - a try/finally body (~73 lines). -- [`src/easydiffraction/analysis/calculators/crysfml.py`](../../../src/easydiffraction/analysis/calculators/crysfml.py) - — extracts `_calculate_adjusted_pattern` / - `_calculate_raw_pattern`. -- [`src/easydiffraction/analysis/calculators/pdffit.py`](../../../src/easydiffraction/analysis/calculators/pdffit.py) - — analogous extraction. - -These read as `pixi run check` complexity-threshold refactors. They are -faithful to the project rule "do not raise lint thresholds — refactor -instead" (plan §P2.3), which is the right call mechanically. But they -also have nothing to do with splitting minimizer settings from fit -outputs, so they expand the PR's blast radius and make `git blame` -noisier for unrelated future debugging. - -Two options for the next reviewer / PR author: - -1. **Keep bundled, but call them out in the PR description** under a - short "Incidental cleanup" subsection so a future reader of `git - log` understands why the input/output split commit touched - `singleton.py`. Lowest-friction option. -2. **Split into a follow-up "Apply lint-driven refactors in Phase 2" - commit** on the same branch. Keeps the input/output split commit - focused; one extra commit on the branch. - -Either is acceptable. The cleanup itself is correct. - -### F3 (informational) — `essdiffraction` removal commit still bundled - -[`_review-4`](minimizer-input-output-split_review-4.md) §F5 flagged -the unrelated `c5da0b0fc Remove essdiffraction dependency` commit, and -[`_reply-4`](minimizer-input-output-split_reply-4.md) decided to keep -it on the branch and call it out in the PR description. No change since -review 4. Confirming the decision; this review does not re-open it. - -The plan §"Suggested Pull Request" description does not yet mention the -`essdiffraction` removal or the F2 lint-driven refactors. Suggested -amendment to the PR description before opening: - -> Incidental cleanup also bundled in this PR: the `essdiffraction` -> dependency is removed, and a handful of unrelated functions -> (`singleton.ConstraintsHandler.apply_constraints`, -> `analysis.sequential._fit_worker`, -> `display.plotting._posterior_predictive_*`, -> `calculators.{crysfml,pdffit}._calculate_pattern`) are split into -> helpers to satisfy the project's complexity thresholds. - -### F4 (informational) — `FitResultBase.result_kind` exception undocumented - -[`base.py:44-54`](../../../src/easydiffraction/analysis/categories/fit_result/base.py:44) -— `result_kind` keeps a non-None default because -`FitResultKindEnum` requires a valid member. Every other descriptor on -the same class uses `default=None, allow_none=True` after reply-4 F1. -The asymmetry is intentional but unexplained in code; a one-line -comment would save a future reader from wondering whether it is -another instance of the pattern review-4 F1 flagged. Trivial; defer -to a follow-on if not in scope. - -## Documentation - -- The five amended ADRs and the new accepted ADR are unchanged since - review 4. No further amendments needed for Phase 2. -- The plan's status checklist is fully `[x]` through P2.6. -- `docs/dev/package-structure/full.md` and `short.md` were updated by - `pixi run fix` (per CLAUDE.md §Workflow auto-generation) and should - not be hand-reviewed. - -## Verification commands run for this review - -Static reads only: - -```text -git rev-parse --abbrev-ref HEAD # minimizer-input-output-split -git merge-base HEAD main # 8f9a679ed -git log --oneline 973214a5e..HEAD # 34 commits since last merged PR -git show --stat a2857d9d6 # Phase 2 commit file list -git grep -nE 'analysis\.minimizer\.(runtime_seconds|iterations_performed|objective_value|...)' src/ tests/ docs/docs/tutorials/ -git grep -nE '_minimizer\.(runtime_seconds|...)' src/ tests/ docs/docs/tutorials/ -git grep -nE '_clear_fit_result_projection' src/ tests/ -git grep -nE '\bFitResult\b' src/ tests/ docs/dev/adrs/ docs/dev/plans/ # stale-name sweep -``` - -All four migration-pattern greps return empty against `src/`, -`docs/docs/tutorials/`, and `tests/` (the `_minimizer.*` sweep returns -only the rejection list inside `serialize.py`, which is intentional). -The `_clear_fit_result_projection` grep returns the definition line -only — supporting F1 above. The `\bFitResult\b` sweep with the family -names filtered out returns empty, confirming the P1.1 rename has no -stragglers. - -No `pixi run fix`, `pixi run check`, `pixi run unit-tests`, -`pixi run integration-tests`, or `pixi run script-tests` was executed. -Those gates are deferred to CI and to the author's Phase 2 pass. - -## Recommended next steps - -1. **Decide F1.** Drop the orphaned `_clear_fit_result_projection` - method, or leave the cleanup for a follow-on. -2. **Decide F2.** Either amend the PR description to call out the - five lint-driven refactors, or split them into a separate cleanup - commit on the branch. -3. **Update the PR description** to also mention the `essdiffraction` - removal (F3 carry-over from review 4) so reviewers are not - surprised by the `pixi.lock` churn. -4. **Optional F4 comment** on `FitResultBase.result_kind` explaining - why it keeps a default while the rest do not. -5. **Open the PR.** No blockers found; CI is the next gate. diff --git a/docs/dev/plans/minimizer-input-output-split_review-6.md b/docs/dev/plans/minimizer-input-output-split_review-6.md deleted file mode 100644 index 8f3371360..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-6.md +++ /dev/null @@ -1,172 +0,0 @@ -# Review 6: Reply to Review 5 - -Reviewed reply: -[`minimizer-input-output-split_reply-5.md`](minimizer-input-output-split_reply-5.md) - -Original review: -[`minimizer-input-output-split_review-5.md`](minimizer-input-output-split_review-5.md) - -Reviewed plan: -[`minimizer-input-output-split.md`](minimizer-input-output-split.md) - -Reviewed ADR: -[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) - -This review follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or -any other build/verification command was executed**. This is a static -read of the single new commit since review 5 (`cf7c97369 Reply to -minimizer input-output review 5`) plus the touched docs. - -## Scope - -One new commit since review 5. It touches docs only: - -```text -docs/dev/plans/minimizer-input-output-split.md +9 -docs/dev/plans/minimizer-input-output-split_reply-5.md +72 (new) -docs/dev/plans/minimizer-input-output-split_review-5.md +297 (new) -``` - -No source or test files moved. The verification matrix from review 5 -(paired-class invariant, legacy-tag rejection, migration greps, new -fit_result tests) is unchanged and still passes its static checks. - -## Summary - -Reply 5 accepts every finding ("agree" × 4) and applies the two -non-deferred ones (F2 and F3) by amending the plan's PR-description -section. F1 (dead method) and F4 (missing comment on `result_kind`) are -deferred. The reply's structure mirrors the existing reply 4 template -(one section per finding, verdict + decision + pointer), which keeps the -review thread legible. - -The reply is sound. One small follow-through gap: the deferred items -(F1 and F4) are not yet logged anywhere a future contributor would -look — see F1 below. - -## Per-finding assessment - -### Review-5 F1 — `_clear_fit_result_projection` dead method - -**Reply verdict.** Agree; defer to a follow-up cleanup. - -**Current state.** -`git grep -nE _clear_fit_result_projection src/ tests/` still returns -the single definition line at -[`analysis.py:1217`](../../../src/easydiffraction/analysis/analysis.py:1217), -no callers. The deferral was applied without action. - -**Assessment.** The deferral itself is defensible — a private method -with no callers has no behavioural impact and does not block merge. But -the project rule in -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md) -→ **Workflow** is explicit: "Open issues / design questions / planned -improvements live in `docs/dev/issues/open.md` (priority-ordered)." - -`docs/dev/issues/open.md` does not currently contain this item. -`grep -nE _clear_fit_result_projection docs/dev/issues/open.md` returns -empty. So the deferral exists only inside this review/reply thread, -which a future contributor reading `open.md` will not see. - -**Suggested follow-up.** Either (a) add a one-line entry to -`docs/dev/issues/open.md` pointing at -[`analysis.py:1217`](../../../src/easydiffraction/analysis/analysis.py:1217) -so the cleanup is queued, or (b) just delete the four lines now — -this is a private, unreferenced method, and the delete is mechanically -smaller than the issue entry. Either is fine; doing neither leaves the -dead code unowned. - -### Review-5 F2 — Lint-driven refactors bundled in Phase 2 - -**Reply verdict.** Agree; keep bundled, call them out in the PR -description. - -**Current state.** -[`minimizer-input-output-split.md:664-672`](minimizer-input-output-split.md:664) -now carries this paragraph: - -> Incidental cleanup also bundled in this branch: the unused -> `essdiffraction` development dependency is removed, and a handful of -> unrelated functions are split into helpers to satisfy the project's -> complexity thresholds during Phase 2 verification: -> `singleton.ConstraintsHandler.apply_constraints`, -> `analysis.sequential._fit_worker`, -> `display.plotting._posterior_predictive_*`, and -> `calculators.{crysfml,pdffit}._calculate_pattern`. - -The four refactor sites enumerated in review 5 F2 are listed verbatim. -Resolved. - -### Review-5 F3 — `essdiffraction` removal still bundled - -**Reply verdict.** Agree; PR description now mentions it. - -**Current state.** Same plan paragraph above, lead sentence: "the -unused `essdiffraction` development dependency is removed". Resolved. - -### Review-5 F4 — `FitResultBase.result_kind` exception undocumented - -**Reply verdict.** Agree, but informational; defer. - -**Current state.** -[`base.py:44-54`](../../../src/easydiffraction/analysis/categories/fit_result/base.py:44) -is unchanged; no comment was added. As with F1, the deferral is -reasonable, but `docs/dev/issues/open.md` does not capture it. - -**Assessment.** Informational only; lower priority than F1 (which is -unowned dead code). If F1 is logged in `open.md`, F4 should be too. If -F1 is deleted instead, F4 can stay deferred without an issue entry — -it's a "nice to have" comment, not a code-quality concern. - -## Findings (this review) - -### F1 — Deferrals from review 5 not tracked in `docs/dev/issues/open.md` - -The project workflow rule routes deferred cleanup items through -`docs/dev/issues/open.md`. Reply 5 defers two findings (review-5 F1 -dead method, review-5 F4 missing comment) without adding either to -that file. Without the log entry, both items live only inside this -review thread; once the PR merges and the plan is deleted (per the -ADR-promotion precedent at P1.16 — deliberation artefacts get -dropped), the deferral context is gone. - -**Suggested action.** Either: - -- Add one short line to `docs/dev/issues/open.md` per deferred item - (with a file-line pointer), or -- Apply the cleanup inline before opening the PR (F1 is a four-line - delete; F4 is a one-line comment). - -Either route closes the trail. The first preserves history, the second -removes the obligation. Both are equally acceptable. - -This is the only outstanding observation from this review round. -Nothing about the code, tests, or paired-class plumbing has changed -since review 5; that assessment still stands. - -## Recommended next steps - -1. **Resolve the tracking gap above.** Pick one of the two routes per - deferred finding. -2. **Open the PR.** No blockers found; CI is the next gate. The plan's - "Suggested Pull Request" section is now complete and accurate - (post-reply-5 PR-description amendment). - -## Verification commands run for this review - -Static reads only: - -```text -git log --oneline a2857d9d6..HEAD # 1 new commit (cf7c97369) -git show --stat cf7c97369 # docs-only -git show cf7c97369 -- docs/dev/plans/minimizer-input-output-split.md -git grep -nE _clear_fit_result_projection src/ tests/ # 1 hit (definition only) -grep -nE _clear_fit_result_projection docs/dev/issues/open.md # empty -test -f docs/dev/issues/open.md # exists -``` - -No `pixi run fix`, `pixi run check`, `pixi run unit-tests`, -`pixi run integration-tests`, or `pixi run script-tests` was executed. diff --git a/docs/dev/plans/minimizer-input-output-split_review-7.md b/docs/dev/plans/minimizer-input-output-split_review-7.md deleted file mode 100644 index 4e033368b..000000000 --- a/docs/dev/plans/minimizer-input-output-split_review-7.md +++ /dev/null @@ -1,117 +0,0 @@ -# Review 7: Reply to Review 6 - -Reviewed reply: -[`minimizer-input-output-split_reply-6.md`](minimizer-input-output-split_reply-6.md) - -Original review: -[`minimizer-input-output-split_review-6.md`](minimizer-input-output-split_review-6.md) - -Reviewed plan: -[`minimizer-input-output-split.md`](minimizer-input-output-split.md) - -Reviewed ADR: -[`../adrs/accepted/minimizer-input-output-split.md`](../adrs/accepted/minimizer-input-output-split.md) - -This review follows -[`.github/copilot-instructions.md`](../../../.github/copilot-instructions.md). - -Per the reviewer rule, **no tests, `pixi run fix`, `pixi run check`, or -any other build/verification command was executed**. This is a static -read of the single new commit since review 6 (`d5c03b01d Reply to -minimizer input-output review 6`). - -## Scope - -One new commit since review 6, docs-only: - -```text -docs/dev/issues/open.md +45 -docs/dev/plans/minimizer-input-output-split_reply-6.md +48 (new) -docs/dev/plans/minimizer-input-output-split_review-6.md +172 (new) -``` - -No source or test files moved. - -## Summary - -Reply 6 closes the only finding from review 6 by adding two -priority-ordered entries to -[`docs/dev/issues/open.md`](../issues/open.md): issue 105 (orphaned -helper) and issue 106 (`result_kind` default rationale). Both entries -are well-formed, sequentially numbered, and listed in the summary -table. No new findings. - -## Verification of reply-6 action - -### Review-6 F1 — Deferrals from review 5 not tracked in `open.md` - -**Reply verdict.** Agree; track in `open.md`. - -**Current state.** -[`open.md:1785-1827`](../issues/open.md:1785) now contains two new -entries: - -- **Issue 105 — Remove Orphaned Fit-Result Reset Helper.** Source - attribution: `minimizer-input-output-split` review 6. Points at - [`analysis.py#L1217`](../../../src/easydiffraction/analysis/analysis.py#L1217), - which is the exact definition line of the orphaned method. - Description and proposed fix match review-5 F1. -- **Issue 106 — Document `FitResultBase.result_kind` Default - Rationale.** Source attribution: same. Points at - [`base.py#L44`](../../../src/easydiffraction/analysis/categories/fit_result/base.py#L44), - the descriptor construction line. Description matches review-5 F4 - and reply-5's intent paragraph. - -Both rows also appear in the summary table at the foot of `open.md` -with severity 🟢 Low and the correct types (Cleanup, Code -readability). Numbering is contiguous with the prior tail (104 → 105 → -106). - -One minor nit on attribution accuracy: the substantive findings -originated in review 5 (F1 dead method, F4 `result_kind` comment). -Review 6 only flagged the tracking gap. Sourcing both new issues to -"review 6" is defensible (review 6 caused the issue entries to be -created) and not worth correcting; the pointers and descriptions are -right, which is what matters for a future contributor following the -trail. - -Resolved. - -## Findings (this review) - -None. The action requested by review 6 was applied verbatim; the entry -format matches the surrounding entries; the cross-references between -code and `open.md` are accurate; the summary table is updated. - -The substantive code review from review 5 still stands: paired-class -invariant is solid, legacy-tag rejection is comprehensive, new -fit_result unit tests cover the required behaviour, migration greps -return clean. Nothing has changed since. - -## Recommended next steps - -1. **Open the PR.** No remaining blockers. Plan is fully `[x]`. PR - description (per the post-reply-5 amendment) is accurate. Both - deferred cleanups are tracked in `open.md` and can be picked up - independently of this PR. -2. **CI is the next gate.** The reviewer chain has not run any `pixi - run` command across reviews 4-7; trust the author's Phase 2 pass - and gate on the merge pipeline. - -## Verification commands run for this review - -Static reads only: - -```text -git log --oneline cf7c97369..HEAD # 1 new commit (d5c03b01d) -git show --stat d5c03b01d # docs-only -git show d5c03b01d -- docs/dev/issues/open.md # exact added entries -grep -nE '^## (105|106)\.' docs/dev/issues/open.md # both present, sequential -grep -nE '^\| (105|106) ' docs/dev/issues/open.md # both in summary table -git grep -nE _clear_fit_result_projection src/ tests/ # still 1 hit - # (definition only — by design, - # tracked as issue 105) -``` - -No `pixi run fix`, `pixi run check`, `pixi run unit-tests`, -`pixi run integration-tests`, or `pixi run script-tests` was executed.