pub struct PrecomputedTransmissionModel {
pub cross_sections: Arc<Vec<Vec<f64>>>,
pub density_indices: Arc<Vec<usize>>,
pub energies: Option<Arc<Vec<f64>>>,
pub instrument: Option<Arc<InstrumentParams>>,
pub resolution_plan: Option<Arc<ResolutionPlan>>,
pub sparse_cubature_plan: Option<Arc<SparseEmpiricalCubaturePlan>>,
pub sparse_scalar_plan: Option<Arc<ScalarSurrogatePlan>>,
pub work_layout: Option<Arc<WorkingGridLayout>>,
}Expand description
Transmission model backed by precomputed Doppler-broadened cross-sections.
The expensive physics steps (resonance → σ(E), Doppler broadening) are
computed once and stored. Each evaluate() call performs Beer-Lambert
and, when instrument is present, resolution broadening on the total
transmission:
T(E) = R ⊗ exp(−Σᵢ nᵢ · σ_{D,i}(E))
Issue #442: resolution broadening is applied to T(E) after Beer-Lambert, not to σ(E) before.
Construct via nereids_physics::transmission::broadened_cross_sections,
then wrap in Arc so the same precomputed data is shared read-only
across all rayon worker threads.
Fields§
§cross_sections: Arc<Vec<Vec<f64>>>Doppler-broadened cross-sections σ_D(E) per isotope, shape [n_isotopes][n_grid_energies].
The grid these σ live on is determined by
work_layout:
work_layoutisSome(Gaussian resolution → auxiliary extended grid): σ live on the working grid, i.e.work_layout.energies, withn_grid_energies == work_layout.energies.len().evaluate()/analytical_jacobian()apply Beer-Lambert + resolution on this working grid and extract the data points LAST viawork_layout.extract(..)— matchingforward_model(issue #608).work_layoutisNone(tabulated resolution, or no resolution): the working grid IS the data grid, so σ live on the data grid (energies), withn_grid_energies == energies.len(). No extraction is needed and the surrogate fast paths + data-gridresolution_planbehave exactly as before.
density_indices: Arc<Vec<usize>>Mapping: params[density_indices[i]] is the density of isotope i.
Wrapped in Arc so that parallel pixel loops can share one copy
via cheap reference-count increments instead of deep-cloning per pixel.
Kept pub (not pub(crate)) because the Python bindings
(nereids-python) construct and access this field directly.
energies: Option<Arc<Vec<f64>>>Energy grid (eV), required for resolution broadening.
None when resolution is disabled — Beer-Lambert only.
instrument: Option<Arc<InstrumentParams>>Instrument resolution parameters.
When Some, resolution broadening is applied to the total
transmission after Beer-Lambert in evaluate().
resolution_plan: Option<Arc<ResolutionPlan>>Optional pre-built broadening plan for (energies, resolution).
When a caller builds the plan once (e.g. spatial dispatch for
a grid shared across every pixel) and passes it via
with_resolution_plan, evaluate() and analytical_jacobian()
skip the per-call kernel-interp / bracket / trap-weight work
and reduce each broadening call to a gather + multiply-add.
None ⇒ fall back to the per-call broadening path, byte-
identical output.
sparse_cubature_plan: Option<Arc<SparseEmpiricalCubaturePlan>>Optional sparse empirical cubature plan (epic #472).
When the plan is present AND its target_energies match this
model’s energy grid AND cubature.k() == n_density_params
AND no temperature / energy-scale fitting is active, the
evaluate() / analytical_jacobian() fast path calls
cubature.forward_and_jacobian(n) directly instead of
exp(-Σ n σ) + apply_resolution. Any guard failure falls
back to the exact path, so the default behaviour is
byte-identical to main.
sparse_scalar_plan: Option<Arc<ScalarSurrogatePlan>>Optional scalar (k = 1) surrogate plan (epic #472, PR #475).
Mutually exclusive with sparse_cubature_plan in practice —
the cubature dispatch fires only for k ≥ 2 and the scalar
plan only for k == 1. The type alias
ScalarSurrogatePlan = ScalarChebyshevPlan is kept as a
stable public name so a future scalar surrogate can swap in
without touching this field or any dispatch call site.
PR #475 picked Chebyshev-in-density over Lanczos Gauss
quadrature after a real-VENUS bench-off (Chebyshev won on
both the accuracy and wall-time axes; see
nereids_physics::surrogate module docs).
work_layout: Option<Arc<WorkingGridLayout>>Working-grid layout matching cross_sections.
Issue #608: when cross_sections is stored on the auxiliary extended
grid (Gaussian resolution), this maps the working grid back to the data
grid so evaluate() / analytical_jacobian() apply resolution on the
working grid and extract the data points last. None ⇒ the working
grid is the data grid (tabulated / no resolution): Beer-Lambert and
resolution run directly on energies and no extraction is needed, which
keeps the surrogate fast paths and the data-grid resolution_plan
byte-identical to before.
Trait Implementations§
Source§impl FitModel for PrecomputedTransmissionModel
impl FitModel for PrecomputedTransmissionModel
Source§fn analytical_jacobian(
&self,
params: &[f64],
free_param_indices: &[usize],
y_current: &[f64],
) -> Option<FlatMatrix>
fn analytical_jacobian( &self, params: &[f64], free_param_indices: &[usize], y_current: &[f64], ) -> Option<FlatMatrix>
Analytical Jacobian for the Beer-Lambert transmission model.
Without resolution: T(E) = exp(-Σᵢ nᵢ · σᵢ(E)) ∂T/∂nᵢ = -σᵢ(E) · T(E)
With resolution (R is a linear operator): T_obs(E) = R[T](E) = R[exp(-Σᵢ nᵢ · σᵢ)](E) ∂T_obs/∂nᵢ = R[-σᵢ(E) · T(E)]
For grouped isotopes sharing density parameter N_g: ∂T_obs/∂N_g = R[-(Σ_{i∈g} σᵢ(E)) · T(E)]
Source§impl ForwardModel for PrecomputedTransmissionModel
impl ForwardModel for PrecomputedTransmissionModel
Source§fn predict(&self, params: &[f64]) -> Result<Vec<f64>, FittingError>
fn predict(&self, params: &[f64]) -> Result<Vec<f64>, FittingError>
Auto Trait Implementations§
impl Freeze for PrecomputedTransmissionModel
impl RefUnwindSafe for PrecomputedTransmissionModel
impl Send for PrecomputedTransmissionModel
impl Sync for PrecomputedTransmissionModel
impl Unpin for PrecomputedTransmissionModel
impl UnsafeUnpin for PrecomputedTransmissionModel
impl UnwindSafe for PrecomputedTransmissionModel
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
§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