Skip to content

Rewrite - #44

Open
beykyle wants to merge 76 commits into
mainfrom
rewrite
Open

beykyle wants to merge 76 commits into
mainfrom
rewrite

Conversation

@beykyle

@beykyle beykyle commented Sep 11, 2026 •

Copy link
Copy Markdown
Owner

Significant re-write, superseding #43 and #42

Remove the 0.x tests, notebooks, in-package samplers, drivers and the
declaration classes.  The eight modules the rewrite harvests stay in
place for one commit so that git mv can carry their history into the
new layout.  The 0.x package remains at v0.1.0 and on legacy/0.x.
git mv only, no edits: likelihood_model -> likelihood, covariance ->
terms, observation_from_measurement -> units, model_comparison ->
diagnostics, the two reaction observation modules -> reactions/.  Their
bodies are rewritten in milestones 1, 5 and 6.
Minimal package init and smoke test; pytest fast/slow tiers (slow marker,
deselected by default); py312 lint targets; pandas and scikit-learn out of
the runtime requirements and into the examples extra with dill; docs
reduced to the design and recipes pages; a README for the branch; CI with
format, tests and docs jobs on main and rewrite plus a scheduled slow
job; a trusted-publishing workflow on version tags.
…prior

Frozen dataclass with eq=False, so sharing is by object; bounds validated
and coerced; prior, unit and latex carried for Problem and plotting.
Drop the contextual flag, the context keyword and per_observation_scaling;
scale() uses the new Parameter signature.  Transform, composition, log/exp
and the finite-difference derivative are unchanged.
The measurement adapters return inside from_measurement in milestone 5.
Gaussian, StudentT(nu=None) with a bounded default parameter, Chi2 and the
log_likelihood helper; the Cholesky helper moves to covariance in
milestone 3.  Test helpers gain a dense reference assembly used by the
term tests now and the structured covariance tests later.
Term is a frozen dataclass: fn, params, kind, on, coords, constant.  No
bind, no support indices, no caches; value() evaluates on a given support
and compile resolves on= to rows.  TermContext gains meta(key) for
per-point dataset metadata.  Factories keep their bodies with support=
renamed on= and the _term suffix dropped; kernel() accepts params= to
share hyperparameters between comparisons.  Tests port TermKinds, Coords,
Factories, Kernel and the alpha+Ca study forms against a hand-built dense
reference, plus the term-level halves of recipes 19 and 27.
x4i3 converts every EXFOR cross section to barns while parsing and
exfor_tools labels the result from a three-entry table, so measurement
unit labels come from a small fixed vocabulary.  parse_unit(label) maps
that vocabulary (plus the obvious spellings) to a factor into the
internal unit and a quantity kind, and rejects anything else loudly.
The 0.x pint path could not parse the real labels (barns/ster, b/Sr);
it only worked for hand-built measurements.
Frozen, identity-hashed; x is opaque beyond its first dimension; y_err
validated; norm_err and offset_err stored as a float or an (n,) array;
meta copied.  No comparison transform, mask or solver state.
Model(fn, params) binds to a grid by closing over it; subclasses override
bind() to build solvers from meta.  Composition happens on the bound
predictors: | applies a mean transform after the prediction, + and * are
x-dependent corrections, parameters concatenated left to right.
polynomial(order) is the generic example.
Comparison binds a model to a dataset in a parameter-free space, with the
delta-method errors and the per-point log-Jacobian as constants;
reported_terms() turns the dataset's offset and normalisation
systematics into rank-one modes, propagated at the data and at the
prediction respectively.  Constraint holds the comparisons, terms,
likelihood, weight and per-comparison masks; masked, masked_where and
complement share every object with the original.  Every term's on= is
resolved eagerly and an array term's shape is checked against its
support.
The study labels (L0, E0, L1, ...) meant nothing to a reader who had not
seen the study.  test/helpers.py now carries STUDY_LEGEND, one line per
label, and study_form(label, x, y, ym) builds the terms, values and the
hand-built dense reference; the term tests parametrise over it and the
structured covariance tests will too.  Recipe 18 and the design document
point at the legend.
Sigma = diag(D) + blockdiag(M_b) + U U^T over the active rows of a
constraint: diag terms into D, mode terms as columns of U (a mode across
comparisons is one column with entries in several blocks), matrix terms
into their block, and a matrix term that crosses blocks forces the dense
path.  Per-block Cholesky plus a rank-r correction gives (d2, logdet);
constant pieces are evaluated once and a fully constant covariance is
factored once, with a singular block reported by label at construction.
Verified against the hand-built dense reference on every study form,
across blocks, with masks, and on the dense fallback.
Recipe 18 now carries the full legend table; test/helpers.py keeps an
identical copy and says so, and the design document cites the recipe.
A docs reader never has to open the test suite.
ParameterIndex assigns each distinct Parameter a slot in first-seen order
(predictors, terms, likelihood, constraint by constraint) and rejects
duplicate names with a hint.  The prior is assembled so every slot is
covered exactly once: a marginal truncated to its bounds, the uniform on
finite bounds, or one joint block; a frozen multivariate normal gets a
whitening unit-cube map, other joints supply their own.  Compiled
constraints stack the comparisons, check finiteness on the active points,
stack per-point metadata for terms, resolve every term's rows and gather,
and build the structured covariance.  Problem exposes log_prior,
log_likelihood (weighted sum), log_posterior (prior first), prior_transform,
sample_prior, predict, chi2, columns and the black-box-bayes aliases, and
pickles with dill.  Tests cover the index, the prior rules, compile
errors, masked views, and emcee, dynesty and dill drivers.
One test file per recipe under test/recipes, docstring quoting the
recipe's intent, assertions its expected-behaviour bullets that need no
converged chain; a linear-Gaussian oracle supplies closed-form posteriors
for the linear recipes.  offset() and normalization() now take the
nuisance parameter as their first positional argument, like every other
nuisance factory and like the recipes spell them; magnitude= stays a
keyword.
…heck

Recipe tests for external samplers, custom terms, preprocessing, the
singular-covariance error, shared hyperparameters, a GP across energies
and the hyperprior block.  Two library changes they exposed: a joint prior
block may introduce a parameter no model or term uses (its
hyperparameter), which now gets a slot after the constraints' parameters;
and the block factor B is checked at construction whenever every diag and
matrix term is constant, since modes never enter B, so a block covered
only by modes fails by label before any likelihood call.  Term is
exported from the package.
SafeBayes prefix differences equal the Gaussian conditional; KDUQ deltas
are linear fractions (log=False, also in the recipe); Peelle's puzzle is
pinned by the two-point closed form and by the ordering data-built <
live prediction-built < t0 across replicates, with the recipe text
corrected to say the live mode keeps a log-determinant pull that the t0
refit removes; cut posteriors by closure and by module weights; an
emulator term seeing the model's values; MAP + Laplace against the
oracle; error scale and USU modes; energy-dependent parameters;
Legendre modes; and the eight-schools model in its three spellings.
GLS with Sigma = sigma^2 I + s^2 y y^T collapses by Sherman-Morrison to
t = ybar / (1 + (s/sigma)^2 sum (y_i - ybar)^2), exact per dataset and
sampler-free; the test asserts it, that identical points give no bias,
and that replicate means lie between the leading-order expectation
1/(1 + (n-1) s^2) and the truth.  The recipe text now quotes that form
instead of the 1/(1 + n s^2) factor it had copied unverified.
ElasticXS and IsobaricAnalogPN build their jitr workspace from the
dataset's kinematics in bind() and return a Predictor closing over it;
the elastic basis is cached per model and energy so a plotting grid or a
held-out view reuses it, and the cache is dropped on pickling.  The
harvested set_up_solver functions take one grid; rutherford() is the
closed form of the kinematics and momentum_transfer() a free function.
No compound-correction hook.  Real-solve tests with lmax=10 pin
finiteness, units, the cache, composition, solver-setting forwarding,
the Lane-term IAS, and a dill round trip of a reaction Problem.
Reads the exfor_tools fields, converts angles to radians and cross
sections to b/sr through the fixed label table, converts dXS/dA to and
from dXS/dRuth with the closed-form Rutherford cross section (a
per-angle norm), scales the dimensionful errors with the data and leaves
the fractional normalisation error alone, and fills meta with the
kinematics a reaction model needs.  Every failure names what is wrong.
Reported systematics become modes only when asked for; an EXFOR
measurement lands in b/sr and radians with the Rutherford conversion as
a per-angle norm; one elastic model serves the data grid and a plotting
grid from one cached basis; a cross section and an analysing power from
one measurement share an angle-calibration mode across comparisons.
Recipe 37 now notes that a spanning basis sees the gathered stack and
must split a finite difference at the seam by per-point metadata.
A term on several comparisons sees their rows gathered into one stack,
so a basis that differentiates or smooths along the grid would straddle
the seam.  TermContext now carries segments (the slice of the support
belonging to each spanned comparison, in constraint order), labels, and
split(a); the covariance computes them once per entry from the block
offsets.  Outside a problem a term has one segment.  Recipe 37 takes its
angle-calibration slope per segment, and the design and recipe docs
describe the new fields.
A kernel term closed over its sklearn kernel and nothing on the Term
exposed it, but the predictive band promised by recipe 7 must condition
the discrepancy from the term alone.  KernelTerm is a Term subclass
carrying the kernel, the count of free hyperparameter elements (so the
log-theta and amplitude columns follow from params), the amplitude and
the jitter; the covariance treats it as any matrix term.
… score

predictive_draws and heldout_log_predictive take a compiled Problem and
chain rows in problem.names order; coverage, sharpness, the log posterior
predictive and the evidence bookkeeping are lifted from 0.x.  A held-out
problem built from fit.complement() scores the marginal of its rows,
which is wrong when a term spans the split (a GP over several
experiments); given=fit_problem computes the Gaussian conditional under
the full covariance instead, and equals the marginal when nothing spans.
split_samples and the module-level log_jacobian are gone: columns come
from problem.columns and the Jacobian from problem.log_jacobian().
total_predictive_band(problem, term, predictor, x_pred, samples) locates
the kernel term's constraint and covariance entry, takes its training
rows, comparison space, kernel and amplitude columns from the problem,
and conditions the discrepancy with the rest of the declared covariance
as the regression noise, so a chain with nuisance columns needs no
column arithmetic.  The amplitude is evaluated at the prediction grid
through a TermContext; physical=True maps draws back through the
space's inverse.  GP conditioning against sklearn is lifted from 0.x.
beykyle and others added 30 commits September 11, 2026 09:58
- given= held-out scores and draws compile the fit|held union against the
  held-out problem's own parameter index instead of a throwaway Problem, so
  every constraint reads its own columns, parameters with only a joint prior
  work, and rows masked out of both views stay out
- Comparison rejects a dataset whose meta quantity differs from the model's
- Joint prior blocks: the declared dimension is checked at compile, and a
  one-parameter block of a scipy univariate is that parameter's marginal
  (which also fixes log_prior on numpy >= 2.4); the rvs contract is
  documented as scipy's rvs(size=n, random_state=rng)
- A prediction of the wrong length raises, naming the comparison
- StudentT() defaults nu to Gamma(2, rate 0.1) truncated to nu >= 1, so the
  default compiles and nested-samples
- A parametric covariance singular at theta is zero density, not an exception
- Predictor keeps the meta it was bound with, so total_predictive_band
  amplitudes can read c.meta at the prediction points
- predictive_draws under StudentT draw from the multivariate t
- Problem.log_jacobian weights each constraint's Jacobian by its weight
- Masks are copied, a scalar space derivative gives a per-point Jacobian, and
  a non-finite constraint weight is rejected
- design.md's held-out example passes given= for the GP rung
- README, the docs index and installation pages and the package docstring
  no longer say 1.0 lives on the rewrite branch, and they say how to pin 0.x
  at tag v0.1.0 (branch legacy/0.x)
- publish.yml publishes only v1 and later tags, and refuses a tag that does
  not name the version setuptools_scm builds
- Cell 13 passes given=p_fit to heldout_log_predictive and predictive_draws:
  the L2y normalisation and the Lgp process both span the 90-degree cut, so
  the honest held-out density is p(y_held | y_fit, theta) (review finding)
- Fix the stale nested() call left when 39b7165 renamed it fit_nested()
- Re-executed; the takeaway follows the new held-out scores and coverages
Silent wrong answers:
- A block whose covariance is constant is checked for singularity at
  compile even when another block is parametric, instead of reading as a
  zero density at every theta
- The eigen fallback of _psd_factor is lower-triangular, so held-out scores
  and conditionals that treat it as a Cholesky factor are right
- Kernel jitter is relative to the block's mean variance, so it no longer
  swamps small (b/sr) variances
- The finite-difference derivative step is relative to the data
- Marginals truncated far in the upper tail use sf/isf, and prior_transform
  is clipped to the bounds
- A bounded frozen-MVN joint prior is renormalised by its in-bounds mass
  (seeded, so compiles are deterministic); custom joints must be normalised
- A dataset whose rows are active in two weighted constraints warns
- compare_logz ties at the boundary; it and logz_summary reject NaN
- The Student-t Gamma ratio uses betaln, exact at huge nu

Crashes and misleading errors:
- kernel() with fixed hyperparameters and parametric coords constructs
- dataclasses.replace on a Term no longer duplicates the coords parameters
- Callable terms with the default coords see x as the dataset holds it
- Provenance tuples and 0-d arrays in meta stack at compile
- ElasticXS("dXS/dRuth") with a neutral projectile fails at bind, by name
- One bare callable used as the space of several comparisons is one space
- A kernel whose support is fully masked says so
- NaN or negative norm_err/offset_err and NaN angles are rejected
- from_measurement refuses LAB-frame angles
- A notebook listed in the index but missing fails instead of xfailing
- Delete examples/correlated_observations.ipynb and drop its cross-references
  in recipes 5 and 37; recipe 37 keeps its closed forms and its recipe test
- Rename measurement_to_calibration to local_optical_model_calibration
- New recipe 39, iterative outlier rejection: the outer loop of problems with
  the mask refit between runs, which the chain-dependent-masks entry of "What
  this API does not express" already named as the sanctioned workaround, with
  its recipe test
- Recipe 4 gains the per-dataset spelling (one magnitude per comparison), for
  an experiment that reports no systematic uncertainty at all
- examples/plotstyle.py: shared rcParams, a colourblind-safe palette, hatched
  predictive bands and corner defaults, and the CI format job now covers it
- Smaller cells: setup, the run, post-processing and each plot stand apart
- LaTeX for every symbol, and no unicode greek
- A colleague-style narrative with links: Bayesian inference, the prior,
  chi^2 and the Mahalanobis distance, MCMC, Goodman & Weare's ensemble
  sampler and Foreman-Mackey et al.'s emcee, the posterior predictive
- Plots through examples/plotstyle.py, and .bind(x_fine) without the empty
  dict that the review asked about
- Dropped the empty trailing cell
- Same data, seeds and sampler settings, so the fit is the one it was
- examples/error_models.ipynb keeps the one-dataset covariance ladder
  (recipes 2, 4, 19); the two-dataset study moves out
- examples/sharing_error_models.ipynb is new (recipe 5): sharing a parameter
  against sharing a mode, a normalisation mode per dataset, one mode spanning
  both, and the assembled covariance shown directly.  It also takes over case
  A, which the deleted correlated_observations notebook used to carry
- Both get the review's general pass: smaller cells, LaTeX, a colleague-style
  narrative with linked references (Peelle's Pertinent Puzzle, D'Agostini,
  Fruhwirth et al.), plots through plotstyle with hatched bands
- The commentary now matches what the fits actually print: inferring the noise
  covers the truth but triples the slope error, and case A, declared for two
  experiments that drifted apart, inflates until the line says nothing
- robust_likelihoods keeps the Student-t study (recipe 9) and gains the
  iterative rejection of recipe 39: fit, drop the points more than three pulls
  away, refit until the mask settles.  The first round over-rejects a good
  point and the second puts it back, which is the argument for the loop
- examples/error_scale_and_usu.ipynb is new (recipe 34): the global scale on
  the reported errors under both likelihoods, and the USU offset on the
  technique we suspect, with Hanson and Capote et al. linked
- Includes the markdown you had rewritten in your working tree, with the three
  typos fixed ("must the the same", "as a a unknown", "This could demo, i.e.")
- The general pass: smaller cells, LaTeX, plotstyle with hatched bands,
  .bind(x) without the empty dict
- Both halves redraw their data in the original RNG order, so every number is
  the one that was committed before
- a_0 = 0 and not inferred, as the review asks.  That alone leaves the overall
  scale unidentifiable, so one experiment is now absolutely normalised and the
  rest are relative to it; the notebook says why
- A settings dict of five experiments, easy to comment in and out, with
  hand-picked renormalisations: three consistent with what was quoted, one
  quoted at 3 % that is really 15 % off and alone in its range, and one that
  quoted 25 % it never needed
- Six cases in two groups.  Correct: the quoted modes, an inferred
  normalisation per dataset (recipe 6), an inferred systematic magnitude per
  dataset (recipe 4).  Wrong: Peelle's mode from the data, systematics
  ignored, statistical errors inferred instead
- Believing the bad quote distorts the curve while its parameter pulls stay
  innocent, because the coefficient errors explode: RMS 1.27 against 0.004 for
  the best case.  Both inferring cases recover it, and the inferred systematic
  for the mis-quoted set comes out at 10 % against the 3 % quoted
- Peelle's puzzle gets the two-point panel where it actually bites, reproducing
  D'Agostini's closed form exactly (0.8333)
- New test: a LaTeX macro in a plain string makes a control character
  ("$\rho$" is a carriage return), which is how the first rebuild of this
  notebook broke.  The guard walks the AST, so it sees f-strings too
- Only mean-zero GPs now, per the review: the sampled Legendre mean correction
  is gone, so this notebook teaches recipe 7 alone
- Kennedy & O'Hagan framed at the top, with the distinction between learning
  the covariance of a mismatch and giving the mismatch its own parameters
- Amplitudes grow with x through T.exp_growth_amplitude, which is the prior
  knowledge that the data stop looking like a line at large x
- The toy is now a line plus a defect growing as 0.03 x^2, so (m, b) have true
  values and the corner shows what each error model does to them: statistics
  only is 60 sigma out and confident, a diagonal model error covers only by
  becoming useless, a constant-amplitude GP is still 2.6 sigma out, and the
  growing amplitude lands at 0.4 sigma
- The optical-model half gets the same three rungs and its own random
  generator, so changing the toy no longer silently changes its data.  The GP
  wins on evidence by 570 log units and recovers V_v, while W_v stays nine
  sigma out because volume and surface absorption are degenerate at one energy
- The learned discrepancy is now read off total_predictive_band, which knows
  about the amplitude, rather than from the bare kernel, which does not
- Registers the recipe sets for the two notebooks whose rebuilds are still
  executing (local_optical_model_calibration, alpha_ca_error_model_comparison)
- Theory is always drawn on a fine angle grid.  The curve at theta_0 was
  evaluated at the downsampled data angles, which aliases the diffraction
  pattern and is the ragged plot the review flagged
- Evidence and Bayes factors are introduced before the table, with links, and
  with the two caveats that matter: a Bayes factor depends on the priors, and
  evidences in different comparison spaces need the Jacobian first
- New section (recipe 17): posterior predictive draws that carry the
  covariance, plus an empirical coverage curve.  All three rungs over-cover in
  sample (0.79, 0.81, 0.78 against a nominal 0.68) while separating sharply
  out of sample (0.49, 0.60, 0.80)
- The TODO intro is replaced, keeping the EXFOR, Oeschler, jitr and
  Kennedy-O'Hagan links
- The general pass: smaller cells, LaTeX throughout, plotstyle with hatched
  bands.  Every number is unchanged: Lgp still wins the evidence by 6.0 to
  10.9 log units and the held-out score at -50.45
- Real data: EXFOR O1199007, p + 40Ca elastic at 35 MeV, ratio to Rutherford,
  59 angles, 2.3 % statistical errors, committed as a CSV so the notebook runs
  anywhere.  The exfor_tools query is shown in the narrative, including that
  EXFOR spells elastic (P,EL) and a product tuple silently returns nothing
- The SimpleNamespace stand-in is now a dataclass, as the review asked, and it
  still exercises from_measurement: the unit contract, the CM-frame check, and
  the ratio-to-absolute conversion through the Rutherford closed form
- This entry quotes no systematics at all, which changes the notebook's spine.
  Three error models are compared instead: the quoted errors alone, an inferred
  normalisation (recipe 4), and an inferred model error (recipe 26, which this
  notebook previously cited without ever demonstrating)
- The result is worth the rewrite.  A normalisation buys almost nothing (log Z
  -3361 against -3479) because it rescales every angle alike, while an inferred
  model error takes chi2/N from 122 to 0.9 at delta = 30 %: a seven-parameter
  local potential describes this measurement to 30 %, not to 2.3 %
- Answers the review's question about the toy: the truth *is* covered, at 0.3
  sigma; the old corner range squeezed the posterior into a dot.  Tempered and
  untempered predictive posteriors are now compared by coverage as well
- Charged projectile, so the potential gains a Coulomb term, and the quantity
  check keeps a ratio measurement from being fitted with an absolute model
The control-character map was written as a one-line dict that exceeded the
line length; black splits it.  No behaviour change.
- Eight schools now stands on its own: BDA3 cited and linked, the data
  explained, and no pooling / complete pooling / partial pooling laid out
  before the non-centred spelling, so a reader need not have read chapter 5
- The bumps are 65 % of their old size, chosen by an A/B at the notebook's real
  sampler settings rather than by eye.  The misspecified fit still fails
  clearly (coverage error 0.52 in sample, 0.45 held out) while the hierarchy
  repairs both to 0.18 and wins the held-out score, 20.3 against 7.7
- New plot: y_true(x; E) and y_data(x; E) at several energies, colour-coded by
  energy, offset in y for legibility and labelled with plt.text
- New plot: predictive draws after calibration in that same layout, with the
  three cases told apart by hatching rather than colour
- The hierarchy ties the correct mapping on evidence (142.28 against 143.07)
  while both sit 230 log units above the misspecified fit, and pays for its
  held-out prediction in width: 0.202 against 0.062
- Fixes the narrative that called an absolute error of 0.03 "honest 3 % errors"
- The general pass: the 81-line case builder is now three cells, LaTeX
  throughout, plotstyle, links
- rx.predictive.grid_draws draws the posterior predictive on any grid by
  re-evaluating each covariance term from its own definition at the new
  points: inferred noise, fractional noise, model error, normalisation and
  systematic modes, parametric matrix terms and GP kernels all carry over.
  Point-by-point terms (reported statistical errors, fixed arrays, per-point
  magnitudes) raise, naming the term and the ways out.  At the measured points
  it matches predictive_draws, and it handles Student-t, physical=True and
  constraints spanning several comparisons
- gp_predictive_draws replaces total_predictive_band: unconditioned grid_draws
  on the kernel's constraint, plus conditioned=True for GP regression on the
  residuals; n_draws becomes n_rep
- predictive_draws gains terms= and statistical= to choose which parts of the
  error model a draw carries; combining them with given= raises
- Breaking: all three draw functions return a percentile band by default and
  the draws with return_draws=True; every caller feeding coverage or sharpness
  now passes it
- StructuredCovariance.matrix(entries=) and CompiledConstraint.entries_for
  build a covariance from a subset of terms
- New TestGridDraws suite and recipe 40, "Predict on a new grid, error model
  included"; recipes 7, 15 and 17, design.md, groundup_design.md and api.rst
  updated
- gp_discrepancy: rebuilt around mean-zero discrepancies with the three
  predictive objects written out, the band next to the data with its coverage,
  and the envelope of departures the data allow
- alpha_ca: a fifth rung, Lgpn, whose GP amplitude grows with angle; it wins on
  evidence (dlogZ 3.5 +/- 1.3 over Lgp) and held-out log predictive (-47.8
  against -50.5), and a new section shows where the potential fails
- Both notebooks rewritten in the walkthrough voice with links (Gaussian
  process, Matern kernel, Bayes factor, nested sampling, Kennedy & O'Hagan,
  Skilling, Speagle, Vehtari et al.), calculation and printing split into
  separate cells, and the evidence verdict printed as words
- The other notebooks draw model-only bands with grid_draws(model_only=True)
- design.md runtimes re-measured solo: gp_discrepancy 483 s, alpha_ca 1280 s
… coverage

- The narrative moves to the walkthrough voice with links to the Bayesian
  vocabulary, a note on Black Box Bayes, and typo fixes
- The fit infers a constant noise sigma instead of trusting the reported
  errors (0.114 +/- 0.020 against a reported 0.1), so the error model is a
  function of x that can travel to a new grid
- The posterior predictive on the fine grid contrasts the line's band with a
  new measurement's, and explains the predictive_draws / grid_draws split and
  why reported per-point errors cannot be forecast; a demo cell shows
  grid_draws refusing them
- Coverage is shown for both: the model alone undercovers (55 % at a nominal
  90 %) while the measurement band follows the diagonal
- The chi2 cell also checks log_likelihood and explains the log-determinant
- Walkers start in a small ball about the prior mean; prior draws stranded a
  walker at a huge sigma and inflated the posterior of b
Both factories built the same diagonal term scaled by a basis; the only
difference was which basis.  One factory with an `averaging` flag says it
once: `z = ym`, or `z = 0.5 * (y + ym)` when `averaging`.

Note the default changed.  `model_error` defaulted to `averaging=True`;
`proportional_error` defaults to `False`, the old `noise_fraction`
behaviour.  A caller porting `model_error(delta)` by name substitution
silently moves to the `ym` basis and must pass `averaging=True` to keep
the KDUQ spelling, as recipe 26 and the gp_discrepancy notebook now do.

Renamed throughout the docs, the recipes, the tests, and gp_discrepancy.
`from_measurement` only ever read attributes off its argument, so an
`exfor_tools` Distribution worked and a dict of the same fields did not.
`_as_record` wraps a Mapping in a SimpleNamespace and checks the eight
required fields up front, so a missing one raises naming itself rather
than surfacing as an AttributeError halfway through the conversion.
`x_units` and `subentry` stay optional.

This is what the optical-model notebook needs to build a Dataset from
the EXFOR entry it types out by hand.
`reported_terms()` built the normalisation mode from `_reported(norm_err,
n)`, which broadcasts a scalar to a per-point array.  An array has no
value away from the measured points, so `grid_draws` had to drop the
term and the predictive on a fine grid lost the reported normalisation.

A scalar `norm_err` is a fraction of the prediction and nothing else, so
it has a value anywhere: keep it scalar and the mode stays a function of
`c.ym`, exactly like `T.normalization(magnitude=)`.  An offset, or a
per-point normalisation, is still defined only at the measured points.
State the model as `y_i + eps_i = eta(x_i) = y_m(x_i; m, b)` before the
priors, so the likelihood that follows is read off the equation rather
than asserted, and say which predictive to compare against the truth and
which against data.  The walker-ball paragraph stays: the code still
starts from `theta0`.

Re-executed end to end, so the execution counts are sequential again and
the `%%time` on the sampler reports a real number.  `_python` in
test_notebooks_index strips magics before `ast.parse`, which otherwise
fails on `%%time`.
The notebook jumped straight to the covariance ladder.  Start instead
from counts in a detector: binomial, Poisson in the small-bin limit,
normal in the many-count limit, and a diagonal multivariate normal whose
log is -chi^2/2 -- which makes plain what the three assumptions behind
least squares are, and therefore what each rung of the ladder relaxes.

Also drops a trailing empty cell.
With one normalisation mode spanning both experiments the posterior
grows a long thin tail: the shared scale runs away and the line is
barely constrained.  emcee walkers that wander in stay in, and a few
stuck ones move the means and widths the notebook reports.  Nested
sampling works inwards from the whole prior and does not get stuck that
way, so this notebook now uses dynesty and says why.

The comparison plot gets one panel per error model instead of four
bands over each other.
Student-t and outlier rejection both answer "this point does not belong
to the measurement".  Tempering is reached for in the same spirit but
answers something else, so the notebook now ends by trying it on a
correctly specified 200-point line: the prior on the corner plot, the
posterior widening with the weight, and what that does to predictive
coverage.

Recipe 12 joins the notebook's citation line and the design's table.
Both now compare in log space with the same error model the jitr
quickstart uses -- reported statistics plus an inferred point-to-point
and an inferred common relative error, written as custom terms -- rather
than each inventing its own ladder.

local_optical_model_calibration reads EXFOR O1199007 from a plain dict
(no exfor_tools import), and scores the jitr model against linear
residuals with the reported errors alone: by evidence with the Jacobian,
by the predictive for the potential alone on a grid and the full
predictive at the measured angles, and by three coverage curves each.

alpha_ca_error_model_comparison keeps the Matern GP in angle, with its
length-scale prior spanning one point spacing to the full range, and
compares a constant amplitude against one growing as an inferred power
of q.  Its cut is shorter than the full study, which section 3 of the
design still describes.

Citation lines and the design's table follow the new contents.
`--ExecutePreprocessor.record_timing` writes an iopub timestamp block
into every cell's metadata, which re-dates the whole file on each run
and says nothing.  The other notebooks carry none; these two now match.
One cell carried execution count 15 among cells 1-12, so it had been
re-run after the ones below it and the stored outputs no longer
provably came from the stored source.  Executed end to end.
The rewrite cut the five experiments down to four and renamed them
exp 0..4 -> exp P..S, but stopped before the end of the notebook, and
the file was committed with the failure in it: the cell defining `free`
was deleted while eight uses of it remained, so cell 24 carried a
NameError and every cell after it was unexecuted.  Past that break the
covariance gallery still indexed `comps["exp 0"]`, `comps["exp 1"]`,
`comps["exp 2"]` and `datasets["exp 0"]`, which raise KeyError, and a
plot title still read "Five experiments, five normalisations".

Every dataset has a non-zero quoted systematic here, so `free` is just
`comps`.  The prose that pointed at `exp 3`'s 3 % quote and `exp 4`
needing none of its 25 % now points at `exp R`, 20 % off against a 5 %
quote, and `exp Q`, 20 % off against a quote of 25 % -- the two the
table below actually shows.  The paragraph before the band plots says
which of them draws residuals, since only the second one does now.

Also drops an unused `minimize_scalar` import and a commented-out
duplicate of the sampling line, and re-runs the notebook end to end:
23 cells, sequential, no errors.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant