pub struct TabulatedResolution { /* private fields */ }Expand description
A tabulated resolution function from Monte Carlo instrument simulation.
Contains reference kernels R(Δt; E_ref) at discrete energies, stored in TOF-offset space (μs). Kernels are interpolated between reference energies and converted from TOF to energy space when applied.
§Offset orientation
Positive Δt = delayed emission (the moderator storage tail); the
kernel mode sits at Δt = 0. At apply time the broadener gathers
theory at t − Δt (convolution — see Self::broaden), so the
positive-Δt tail reads theory from earlier TOF = higher energy and
broadened dips acquire their tail toward lower apparent energy.
§File Format (VENUS/FTS)
FTS BL10 case i00dd folded triang FWHM 350 ns PSR ← header
----- ← separator
5.00000e-004 0.00000e+000 ← energy block start
-53.458917835671329 2.051764258257523e-04 ← (tof_offset_μs, weight)
...
← blank line separates blocks
1.00000e-003 0.00000e+000 ← next energy block
...Implementations§
Source§impl TabulatedResolution
impl TabulatedResolution
Sourcepub fn ref_energies(&self) -> &[f64]
pub fn ref_energies(&self) -> &[f64]
Reference energies (eV), sorted ascending.
Sourcepub fn kernels(&self) -> &[(Vec<f64>, Vec<f64>)]
pub fn kernels(&self) -> &[(Vec<f64>, Vec<f64>)]
For each reference energy: (tof_offsets_μs, weights) pairs. Weights are peak-normalized (max=1.0).
Sourcepub fn flight_path_m(&self) -> f64
pub fn flight_path_m(&self) -> f64
Flight path length in meters (needed for TOF↔energy conversion).
Sourcepub fn detector_bin_probabilities(
&self,
true_energy_ev: f64,
detector_time_edges_us: &[f64],
timing_offset_us: f64,
) -> Result<Vec<f64>, ResolutionParseError>
pub fn detector_bin_probabilities( &self, true_energy_ev: f64, detector_time_edges_us: &[f64], timing_offset_us: f64, ) -> Result<Vec<f64>, ResolutionParseError>
Probability that a neutron of known true energy is recorded in each supplied detector-time bin.
The tabulated pulse is selected and interpolated at true_energy_ev;
an energy outside the tabulated reference range uses the nearest
reference kernel unchanged (the same clamping the broadening path
applies). Its offsets are relative to the reference pulse mode, so
the nominal arrival is
timing_offset_us + TOF_FACTOR * flight_path_m / sqrt(E).
timing_offset_us is the effective clock/energy-axis offset calibrated
for the measurement; this method does not invent an absolute moderator
emission time that is absent from a mode-centred UDR file.
The returned vector has one entry per adjacent edge pair and is not renormalized to the supplied window. Probability outside the measured window remains outside it; the quantified acquisition-window loss at this energy is one minus the sum of the returned vector (the pipeline-map contract’s R5·7 window-loss disclosure).
§Errors
Returns ResolutionParseError::InvalidFormat unless the true energy
is positive and finite, the timing offset is finite, and at least two
finite bin edges are supplied in strictly increasing order. It also
fails if the interpolated tabulated pulse has zero area.
Sourcepub fn width_corrected(
&self,
s0: f64,
p: f64,
e_ref: f64,
) -> Result<TabulatedResolution, ResolutionError>
pub fn width_corrected( &self, s0: f64, p: f64, e_ref: f64, ) -> Result<TabulatedResolution, ResolutionError>
Width-corrected copy of this tabulated kernel.
Shape-preserving instrument-resolution calibration knob: each
reference-energy block’s TOF offsets are scaled by
s(E) = s0 · (E / e_ref)^p about the block’s intensity centroid, so
the kernel widens/narrows without moving its centroid — width and position
stay orthogonal (t0/L handle absolute position). Weights are unchanged;
the apply-time trapezoidal renormalization preserves unit area.
Exactness note: the orthogonality is exact at reference
energies. Between references, interpolated_kernel’s
width-normalized blend re-scales each block about the mode
(offset 0), so the applied centroid picks up a second-order
dependence on the width exponent p (measured ~1 % of σ for
|p| ≤ 0.1 on widely spaced references) — absorbed by the
jointly fitted t0 in calibration.
The pivot is the trapezoidal-weighted centroid Σ o·w·dt / Σ w·dt,
using the same dt quadrature weights as the broadening integral (see
Self::broaden). Because dt is itself affine in the offsets, the width
scale multiplies every dt by s, so the integrated centroid is preserved
exactly on any offset grid (uniform or not) — not just on uniform grids
where the trapezoidal and plain centroids happen to coincide.
s0 = 1, p = 0 returns a width-identical copy. This is the fittable model
behind the udr_corr resolution-calibration family: it trusts the
Monte-Carlo shape and calibrates only its width / energy-dependence.
§Errors
Returns ResolutionError::InvalidWidthCorrection unless s0 is finite
and > 0, e_ref is finite and > 0, and p is finite. A non-positive
s0 would reverse/collapse the (ascending) offset ordering the broadening
loop assumes, so it is rejected up front rather than silently clamped.
Sourcepub fn with_flight_path(
&self,
flight_path_m: f64,
) -> Result<Self, ResolutionParseError>
pub fn with_flight_path( &self, flight_path_m: f64, ) -> Result<Self, ResolutionParseError>
The same kernel table read against a different flight path.
The stored offsets are times relative to this table’s own anchor, and the flight path enters only the TOF↔energy map they are applied through. So rebinding it is exact and needs no resynthesis — the kernel itself is a property of the moderator, not of how far the neutron then flew.
This is what a fitted L_scale requires: the data’s energy grid is
built with L·L_scale, and a kernel still reading L applies a width
wrong by that same factor.
§Errors
Returns ResolutionParseError::InvalidFormat if flight_path_m is
not positive and finite.
Sourcepub fn kernel_support_ev(&self, e_ev: f64) -> f64
pub fn kernel_support_ev(&self, e_ev: f64) -> f64
Kernel support at energy e_ev, in eV: the larger of the two distances
in Self::gather_bounds_ev. Returns 0.0 for non-positive or
non-finite e_ev, an empty kernel set, or a non-positive flight path.
Sourcepub fn gather_bounds_ev(&self, e_ev: f64) -> (f64, f64)
pub fn gather_bounds_ev(&self, e_ev: f64) -> (f64, f64)
The lowest and highest energies the kernel at e_ev gathers theory
from, as (low, high) in eV with low ≤ e_ev ≤ high, over the points
Self::broaden keeps and in its arithmetic. Returns (e_ev, e_ev)
for non-positive or non-finite e_ev, an empty kernel set, or a
non-positive flight path.
Source§impl TabulatedResolution
impl TabulatedResolution
Sourcepub fn from_text(
text: &str,
flight_path_m: f64,
) -> Result<Self, ResolutionParseError>
pub fn from_text( text: &str, flight_path_m: f64, ) -> Result<Self, ResolutionParseError>
Parse a VENUS/FTS resolution file.
§Arguments
text— File contents as a string.flight_path_m— Flight path length in meters.
Sourcepub fn from_kernels(
ref_energies: Vec<f64>,
kernels: Vec<(Vec<f64>, Vec<f64>)>,
flight_path_m: f64,
) -> Result<Self, ResolutionParseError>
pub fn from_kernels( ref_energies: Vec<f64>, kernels: Vec<(Vec<f64>, Vec<f64>)>, flight_path_m: f64, ) -> Result<Self, ResolutionParseError>
Build a tabulated resolution directly from synthesized kernels.
Used by the analytical crate::ikeda_carpenter::IkedaCarpenter model,
which generates (tof_offset_µs, weight) kernels at a set of reference
energies and then rides the exact same broadening machinery as a
Monte-Carlo file. Validates the same invariants from_text enforces:
non-empty + strictly ascending reference energies, one kernel per energy,
each kernel non-empty with matching offset/weight lengths, and all-finite
offsets/weights.
§Errors
Returns ResolutionParseError::InvalidFormat if the flight path is
not positive and finite, if the reference energies are empty / not
strictly ascending, if the energy and kernel counts differ, if any
kernel is empty, if a kernel’s offset and weight vectors differ in
length, or if any offset/weight is non-finite.
Sourcepub fn from_file(
path: &str,
flight_path_m: f64,
) -> Result<Self, ResolutionParseError>
pub fn from_file( path: &str, flight_path_m: f64, ) -> Result<Self, ResolutionParseError>
Parse a VENUS/FTS resolution file from disk.
Sourcepub fn broaden(
&self,
energies: &[f64],
spectrum: &[f64],
) -> Result<Vec<f64>, ResolutionError>
pub fn broaden( &self, energies: &[f64], spectrum: &[f64], ) -> Result<Vec<f64>, ResolutionError>
Apply tabulated resolution broadening to a spectrum.
For each energy point:
- Find bracketing reference energies and interpolate kernel (log-space)
- Convert TOF offsets to energy offsets using exact TOF↔energy relation
- Convolve spectrum with interpolated kernel (trapezoidal integration)
Kernel points whose delayed-emission offset reaches the nominal
flight time at the target energy (dt ≥ TOF(E)) gather from
past infinite energy; they are dropped and the kernel
renormalized over the surviving points, mirroring the grid-edge
handling — see the tail-truncation note on broaden_presorted.
§Errors
Returns ResolutionError::LengthMismatch if the arrays differ in
length, or ResolutionError::UnsortedEnergies if the energy grid is
not sorted in non-descending order.
Sourcepub fn plan(&self, energies: &[f64]) -> Result<ResolutionPlan, ResolutionError>
pub fn plan(&self, energies: &[f64]) -> Result<ResolutionPlan, ResolutionError>
Build a reusable broadening plan for a specific target energy grid.
Validates that energies is non-descending — the same sorted-grid
precondition enforced by TabulatedResolution::broaden via
validate_inputs. An
unsorted grid would produce a silently-wrong plan (misbracketed
e_prime lookups against e_min / e_max), so it must be
caught at build time rather than returning garbage from
ResolutionPlan::apply.
The plan hoists every quantity that depends only on
(target_energies, self.ref_energies, self.flight_path_m) —
namely the TOF conversion, the log-space kernel interpolation,
the per-kernel-point e_prime and spectrum-bracket lookup, and
the trapezoidal integration widths. Applying the plan to a
spectrum becomes a pure gather + multiply-add loop.
Build cost: same as one call to the private broaden_presorted
helper (O(N_target × N_kernel) TOF / bracket / interp work, plus
~2 × N_kernel log-interp ops per target energy for
interpolated_kernel). Apply cost per target: 1 branch +
~3 loads + 3 flops per retained entry, plus the final divide —
typically < 10 % of the build cost. The payoff comes from
reusing one plan across many spectra.
Bit-exact with broaden_presorted: pre-computes the same
floating-point sequences (TOF, e_prime, dt_width, frac,
weight, norm) in the same order.
§Errors
Returns ResolutionError::UnsortedEnergies if energies is
not non-descending.
Trait Implementations§
Source§impl Clone for TabulatedResolution
impl Clone for TabulatedResolution
Source§fn clone(&self) -> TabulatedResolution
fn clone(&self) -> TabulatedResolution
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for TabulatedResolution
impl RefUnwindSafe for TabulatedResolution
impl Send for TabulatedResolution
impl Sync for TabulatedResolution
impl Unpin for TabulatedResolution
impl UnsafeUnpin for TabulatedResolution
impl UnwindSafe for TabulatedResolution
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more