# AC87 offline analysis: posterior calibration and model mismatch

These scripts build and check the fixed teaching problem behind the
`neural-posterior-calibration` Lab and the simulation-based inference course.
Every record is synthetic. No real detector data are used or implied.

## Model

- Synthetic whitened strain: 128 samples at 4096 Hz (31.25 ms), signal
  `h(t) = A exp(-t/tau) cos(2 pi f t + phi)` with `A = 1` and `phi = 0` fixed.
- Unknowns: frequency `f` in Hz and damping time `tau` in ms, uniform prior on
  200-300 Hz and 2-10 ms.
- Noise: white Gaussian, known standard deviation `sigma` per sample, in the
  same whitened units as `A`.
- Mismatch families, never shown to the network: a schematic second damped
  mode, a sine-Gaussian glitch at 20 ms, and noise louder than assumed. Their
  ratios are teaching choices, not Kerr overtone values or detector glitches.
- Exact reference: a two-stage 64 x 64 grid posterior with a 0.99999 mass window.
- Neural posterior: a 5-component Gaussian mixture in logit space, from a
  20-vector template projection plus `log(sigma / 0.3)`, two tanh layers of 128
  units, 23,198 parameters. Trained only on well-specified simulations with
  `sigma` log-uniform on 0.15-0.6.

## Environment

Tested with Python 3.9.6 and NumPy 2.0.2 (`requirements.txt`) on an arm64
macOS CPU. Matrix products use `einsum`; NumPy 2.0.2 with Apple Accelerate
printed spurious matmul warnings for finite inputs.

## Run order

Run each command from this folder. Each script reads `inputs/` and writes
`outputs/`.

| Step | Command | Wall clock | Output |
| --- | --- | --- | --- |
| 1 | `python3 calibrate_linear_gaussian.py` | 2.2 s | `outputs/linear-gaussian-sbc.json` |
| 2 | `python3 validate_grid_posterior.py` | 14.4 s | `outputs/grid-validation.json` |
| 3 (optional) | `python3 train_neural_posterior.py` | 413.8 s | `outputs/frozen-neural-posterior.json`, `outputs/training-log.json` |
| 4 | `python3 validate_neural_posterior.py` | 67.7 s | `outputs/neural-validation.json`, `outputs/browser-parity-cases.json` |

Step 3 retrains the network from seed 87 on 30.72 million fresh simulations.
Bit-identical weights are only expected on the same NumPy build; steps 1, 2
and 4 were rerun from the saved inputs and frozen network and reproduced
their outputs exactly apart from the recorded wall-clock time.

## Fingerprints (SHA-256)

| File | SHA-256 |
| --- | --- |
| `outputs/linear-gaussian-sbc.json` | `8bcd42461d0654bd4bdd6bebe0523ae89137582a83ec08b8b11e1aeaa4dcebd4` |
| `outputs/frozen-neural-posterior.json` | `bb213976acf54c968fa658aa8ba7b60647bf51f889fc393cf5f2d427448ed575` |
| `outputs/browser-parity-cases.json` | `8f53fbf837c91511af0748f5422e236ad692eb558fe08bdd4c37900e27847ba0` |

`grid-validation.json` and `neural-validation.json` record their own wall
clock, so compare their numbers rather than their bytes.

## What the outputs establish

- `linear-gaussian-sbc.json`: rank histograms and central-interval coverage for
  an exact conjugate posterior and four wrong ones, each compared with an
  analytic prediction that does not reuse the simulation, plus a 200-run
  false-alarm study.
- `grid-validation.json`: the grid posterior against the Fisher limit, a
  Metropolis sampler, its own SBC, and the linear bias formula for a small
  unmodelled signal.
- `neural-validation.json`: SBC and expected coverage of the network over the
  prior, conditional coverage in noise bands, held-out noise levels and
  mismatch families, importance-sampling efficiency, residual tests and
  Jensen-Shannon divergence to the grid posterior.
- `failure-cases.json`: what went wrong while building this, which check
  caught it, and one limitation that no check here detects.

`export_lab_data.py` copies the frozen inputs into the site's Lab data folder.
It is part of the site build, not of the reader's rerun.
