11# Architecture
22
3- ** Analysis Date:** 2026-10-15 ** Branch analysed:** ` develop ` @ ` 66adaf54 `
3+ ** Analysis Date:** 2026-10-15 ** Branch analysed:** ` develop ` @
4+ ` 66adaf54 `
45
56## Pattern Overview
67
@@ -27,8 +28,8 @@ is computed by pluggable engine wrappers (refnx, refl1d) behind
2728- Inequality constraints are enforced by patching the BUMPS driver in
2829 the core's namespace, because the core offers no hook
2930- Shared objects (one material in two layers, one assembly in two
30- models) serialize once through ` sample/references.py ` ( ` file_format `
31- 3 )
31+ models) serialize once through ` sample/references.py `
32+ ( ` file_format ` 3)
3233
3334## Layers
3435
@@ -64,14 +65,16 @@ is computed by pluggable engine wrappers (refnx, refl1d) behind
6465- Purpose: Post-hoc posterior analysis and chain persistence
6566- Location: ` src/easyreflectometry/analysis/bayesian.py `
6667- Contains: ` PosteriorResults ` (summary, corner, trace, distribution,
67- credible_interval, gelman_rubin, save), ` posterior_predictive_* ` ,
68- ` save_posterior ` / ` load_posterior ` with fingerprinted sidecar
68+ credible* interval, gelman_rubin, save),
69+ ` posterior_predictive*\* ` , ` save_posterior ` /` load_posterior ` with
70+ fingerprinted sidecar
6971- Depends on: plotly (required), arviz (optional), numpy
7072
7173** Model / Sample Layer:**
7274
7375- Purpose: Physical description of the sample and measurement
74- - Location: ` src/easyreflectometry/model/ ` , ` src/easyreflectometry/sample/ `
76+ - Location: ` src/easyreflectometry/model/ ` ,
77+ ` src/easyreflectometry/sample/ `
7578- Contains: ` Model ` (scale, background, resolution, ` total_thickness `
7679 derived parameter), ` Sample ` and assemblies/layers/materials,
7780 ` LayerMagnetism ` , ` MaterialDensity.sld_coupled ` , ` volume_fraction ` ,
@@ -119,21 +122,22 @@ is computed by pluggable engine wrappers (refnx, refl1d) behind
119122
120123** Classical Fit (project path):**
121124
122- 1 . GUI/user calls ` Project.prepare_fit(experiments=None, objective=None, skip_invalid=False) `
125+ 1 . GUI/user calls
126+ ` Project.prepare_fit(experiments=None, objective=None, skip_invalid=False) `
1231272 . ` _fit_scope ` selects experiments with ` include_in_fit ` and a model;
124128 ` _fitter_for ` builds ` MultiFitter.for_experiments(...) ` with a deep
125129 copy of ` fit_settings ` and a weak ` constraints_factory_provider `
1261303 . ` MultiFitter.prepare ` applies the objective (` hybrid ` default:
127- Mighell for zero-variance points, WLS otherwise), binds each dataset's
128- fit function to its own resolution ( ` _resolution_for_fitted_points `
129- for ` legacy_mask ` ), collects ` added_roots ` (free parameters of
130- left-out models that fitted parameters follow), builds the core
131- fitter via ` _core_fitter_for ` + ` FitSettings.configure ` , checks
132- preconditions (finite bounds for engines that need them, warns on
133- starts at bounds)
131+ Mighell for zero-variance points, WLS otherwise), binds each
132+ dataset's fit function to its own resolution
133+ ( ` _resolution_for_fitted_points ` for ` legacy_mask ` ), collects
134+ ` added_roots ` (free parameters of left-out models that fitted
135+ parameters follow), builds the core fitter via ` _core_fitter_for ` +
136+ ` FitSettings.configure ` , checks preconditions (finite bounds for
137+ engines that need them, warns on starts at bounds)
1341384 . Caller runs ` prepared.execute(**kwargs) ` or ` core_fitter.fit(...) ` in
135- a thread; the ` _ConstrainedEasyScienceMultiFitter.fit ` property
136- wraps the call in ` _bumps_constraints.applied(factory) `
139+ a thread; the ` _ConstrainedEasyScienceMultiFitter.fit ` property wraps
140+ the call in ` _bumps_constraints.applied(factory) `
1371415 . ` Project.record_fit(prepared, results, status) ` computes classical
138142 metrics from the measured arrays (` prepared.finalize ` ), stores a
139143 ` FitRun ` on ` last_fit ` and pushes results to ` project.fitter `
@@ -148,22 +152,23 @@ is computed by pluggable engine wrappers (refnx, refl1d) behind
148152
149153** Polarized Fit:**
150154
151- - ` fit_polarized(polarized_dataset) ` or ` for_experiments([...]) ` : one fit
152- function per spin channel, all against one model, each bound to its
153- channel via ` CalculatorFactory.fit_func_for(channel, resolution) ` .
155+ - ` fit_polarized(polarized_dataset) ` or ` for_experiments([...]) ` : one
156+ fit function per spin channel, all against one model, each bound to
157+ its channel via ` CalculatorFactory.fit_func_for(channel, resolution) ` .
154158 Structural parameters shared, magnetic parameters constrained by all
155159 channels.
156160
157161** Bayesian MCMC:**
158162
1591631 . ` Project.prepare_sample() ` → ` for_experiments ` fitter with settings
160164 snapshot; or ` fitter.mcmc_sample(data) ` directly
161- 2 . ` mcmc_sample ` requires a BUMPS minimizer (`minimizer.package ==
162- 'bumps'` ), refuses all-zero variances unless ` objective='mighell'`,
163- calls ` prepare(action='sampling') `
165+ 2 . ` mcmc_sample ` requires a BUMPS minimizer
166+ ( ` minimizer.package == 'bumps'` ), refuses all-zero variances unless
167+ ` objective='mighell' ` , calls ` prepare(action='sampling') `
1641683 . Builds ` Sampler(core_fitter, x, y, weights) ` inside the constraints
165- context, patches ` sampler.extend ` to re-enter it, keeps the sampler on
166- ` fitter.sampler ` , runs ` sampler.sample(samples, burn, thin, population, sampler_kwargs, progress_callback, abort_test) `
169+ context, patches ` sampler.extend ` to re-enter it, keeps the sampler
170+ on ` fitter.sampler ` , runs
171+ ` sampler.sample(samples, burn, thin, population, sampler_kwargs, progress_callback, abort_test) `
1671724 . Returns ` {'draws', 'param_names', 'state', 'logp'} ` ; user wraps in
168173 ` PosteriorResults ` or calls ` save_posterior `
169174
@@ -191,10 +196,10 @@ is computed by pluggable engine wrappers (refnx, refl1d) behind
191196
192197## Key Abstractions
193198
194- ** ` PreparedFit ` (fitting.py):** Snapshot pattern. Everything a run needs,
195- fixed at prepare time; owns copies of measured arrays, the configured
196- core fitter and both full-resolution (` curve_funcs ` ) and fitted-point
197- (` fit_funcs ` ) evaluators.
199+ ** ` PreparedFit ` (fitting.py):** Snapshot pattern. Everything a run
200+ needs, fixed at prepare time; owns copies of measured arrays, the
201+ configured core fitter and both full-resolution (` curve_funcs ` ) and
202+ fitted-point (` fit_funcs ` ) evaluators.
198203
199204** ` FitRun ` (fitting.py):** Frozen record of a run (status, minimizer,
200205objective, inputs by experiment key, pooled and per-dataset statistics).
@@ -205,10 +210,10 @@ Outlives model changes; `remapped()` follows experiment re-keying.
205210validated by ` MinimizerOption ` schemas). ` configure(fitter) ` and
206211` engine_kwargs() ` are the two consumers.
207212
208- ** ` _ConstrainedEasyScienceMultiFitter ` / ` _bumps_constraints ` :** Property
209- override plus monkeypatch. The subclass overrides the core's read-only
210- ` fit ` property so the raw ` easy_science_multi_fitter.fit(...) ` path
211- resolves the owner's constraints provider in the calling thread and
213+ ** ` _ConstrainedEasyScienceMultiFitter ` / ` _bumps_constraints ` :**
214+ Property override plus monkeypatch. The subclass overrides the core's
215+ read-only ` fit ` property so the raw ` easy_science_multi_fitter.fit(...) `
216+ path resolves the owner's constraints provider in the calling thread and
212217consumes an explicit ` constraints_factory ` keyword (the core would
213218swallow it via ` **kwargs ` ). ` install() ` , run at import of
214219` _bumps_constraints ` , wraps ` minimizer_bumps.FitDriver ` so each
@@ -220,15 +225,16 @@ Non-BUMPS engines raise `NON_BUMPS_ERROR`.
220225restricted symbol table; ` build_constraints_factory ` returns BUMPS
221226penalty callables.
222227
223- ** Contrasts (` contrasts.py ` ):** A contrast is an ordinary ` Model ` sharing
224- structure with a reference by identity or by ` constrain_equal ` .
228+ ** Contrasts (` contrasts.py ` ):** A contrast is an ordinary ` Model `
229+ sharing structure with a reference by identity or by ` constrain_equal ` .
225230` derive_contrast ` copies touched assemblies, shares the rest and ties
226231parameters; ` plan_link ` / ` apply_link ` / ` unlink ` manage equalities
227232between two models of one layout with full state snapshots.
228233
229234** ` CalculatorBase ` / ` WrapperBase ` / ` CalculatorFactory ` :** Template +
230- Adapter + Factory. Items are claimed per calculator (` _claim ` ), per-model
231- resolution registry, magnetism toggles, channel-bound fit functions.
235+ Adapter + Factory. Items are claimed per calculator (` _claim ` ),
236+ per-model resolution registry, magnetism toggles, channel-bound fit
237+ functions.
232238
233239** ` references ` (sample/references.py):** Context-managed shared-object
234240serialization (` writing() ` , ` reading(report) ` , ` suspended() ` ), with
@@ -278,8 +284,8 @@ issues; refuse silent degradation.
278284handler configured. ` warnings ` is the main user-facing channel.
279285
280286** Validation:** ` FitSettings.validate ` , ` MinimizerOption.validate ` ,
281- ` InequalitySpec.validate_syntax ` + ` check_units ` , ORSO column validation,
282- ` _check_fit_preconditions ` .
287+ ` InequalitySpec.validate_syntax ` + ` check_units ` , ORSO column
288+ validation, ` _check_fit_preconditions ` .
283289
284290** Threading:** The constraints factory lives in a ` contextvars `
285291variable, resolved in the calling thread; ` PreparedFit ` owns array
0 commit comments