Skip to main content

calibrate_energy

Function calibrate_energy 

Source
pub fn calibrate_energy(
    energies_nominal: &[f64],
    transmission: &[f64],
    uncertainty: &[f64],
    isotopes: &[ResonanceData],
    abundances: &[f64],
    assumed_flight_path_m: f64,
    temperature_k: f64,
    resolution: Option<&InstrumentParams>,
) -> Result<CalibrationResult, PipelineError>
Expand description

Calibrate the energy axis of a TOF neutron measurement.

Given a measured 1D transmission spectrum and known sample composition (e.g. natural Hf), finds the (L, t₀) that minimize chi² by aligning the ENDF resonance positions with the measured dips.

§Search strategy

Issue #634: the former three-phase (L, t₀) grid scan — ~900 (L, t₀) candidates × a ~25-evaluation golden-section density search each, i.e. ~35 000 forward evaluations, >10 min on production windows — is replaced by a staged global-then-local search built on the fit_energy_scale Levenberg–Marquardt path:

  1. a coarse joint (t₀, L_scale) scan (7 × 7 candidates, each with an exact golden-section density in log10(n) over the documented [1e-5, 1e-2] atoms/barn band — the per-candidate density is what keeps the anchor JOINT, like the old grid);
  2. a direct dip-match anchor: measured dip positions (plateau-robust depth-weighted run centres, so saturated flat-bottom dips locate correctly) matched to the known resonance energies and solved as an affine TOF map by least squares — the discriminating anchor along the (t₀, L_scale) degeneracy valley;
  3. a fine joint pit-scan (11 × 11 at 0.25 µs / 0.05 % steps, narrow-band golden density) around EACH anchor — the compact descendant of the old Phase-2/3 grids, needed because the chi² landscape’s sub-bin aliasing pits are ~±0.3 µs wide;
  4. a multi-start descent from both fine anchors (per anchor: its own density plus log-spaced starts; per start: a direct joint LM AND an exact-density ↔ alignment-only-LM alternation), with every candidate scored by the original valid-bins chi² and the argmin returned.

Net cost is ~4× fewer forward evaluations than the old grid on production windows, and the LM refinement removes the old grid’s resolution floor (0.001 % L, 0.05 µs t₀) — the optimum is continuous. The internal LM fits disable the fitters’ peak-match seed (with_energy_scale_seed(false)): stages 1–3 already provide a stronger anchor, and the seed’s strict-local-minimum dip detector mislocates saturated flat-bottom dips.

Each LM fit constrains L_scale to ±1 % (ENERGY_SCALE_L_SCALE_*); when a fit rails on that box (a larger true offset), the search re-anchors on the corrected grid and composes the affine TOF maps, so the documented ±1.5 % flight-path band remains covered.

The golden-section seed runs on a slightly wider band (~5e-6 to ~2e-2), and if the fitted density optimum saturates that band — effectively at 1e-5 or 1e-2, or anywhere beyond — the function returns Err(PipelineError::InvalidParameter) rather than a silent boundary-saturated answer, because a true minimum at or past the edge almost always means the real optimum lies outside the supported interval and the caller should supply a better initial estimate or check the sample composition.

§Arguments

  • energies_nominal — Energy grid computed with assumed L (ascending, eV)
  • transmission — Measured transmission values (same length)
  • uncertainty — Per-bin uncertainty (same length)
  • isotopes — ENDF resonance data for each isotope
  • abundances — Natural abundance fractions (same length as isotopes, sum ≤ 1)
  • assumed_flight_path_m — The L used to compute energies_nominal
  • temperature_k — Sample temperature for Doppler broadening
  • resolution — Optional instrument resolution function. When provided, the forward model includes Doppler + resolution broadening, producing more accurate (L, t₀) fits. Without resolution, fitted parameters absorb the missing broadening and may be biased.

§Returns

CalibrationResult with the fitted (L, t₀, n_total) and corrected energies.