#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ScalarSource {
Constant(f64),
Uniform {
low: f64,
high: f64,
},
Histogram(Histogram),
}
#[derive(Clone, Copy, Debug)]
pub struct ScalarProposalResult {
pub value: f64,
pub weight: f64,
}
impl ScalarSource {
pub fn constant(value: f64) -> Self {
Self::Constant(value)
}
pub fn uniform(low: f64, high: f64) -> Self {
Self::Uniform { low, high }
}
pub fn histogram(histogram: Histogram) -> Self {
Self::Histogram(histogram)
}
pub fn support(&self) -> LadduPhysicsResult<(f64, f64)> {
match self {
Self::Constant(value) if value.is_finite() => Ok((*value, *value)),
Self::Constant(value) => Err(LadduPhysicsError::invalid_value(
"constant scalar source",
"finite",
value,
)),
Self::Uniform { low, high } if low.is_finite() && high.is_finite() && high > low => {
Ok((*low, *high))
}
Self::Uniform { low, high } => Err(LadduPhysicsError::invalid_relation(format!(
"uniform scalar source requires finite low < high, got [{low}, {high}]"
))),
Self::Histogram(histogram) => {
Self::histogram_density(histogram).map(|density| density.support())
}
}
}
pub fn sample(&self, rng: &mut ProposalRng) -> LadduPhysicsResult<ScalarProposalResult> {
match self {
Self::Constant(value) if value.is_finite() => Ok(ScalarProposalResult {
value: *value,
weight: 1.0,
}),
Self::Constant(value) => Err(LadduPhysicsError::invalid_value(
"constant scalar source",
"finite",
value,
)),
Self::Uniform { low, high } if low.is_finite() && high.is_finite() && high > low => {
Ok(ScalarProposalResult {
value: low + rng.uniform() * (high - low),
weight: high - low,
})
}
Self::Uniform { low, high } => Err(LadduPhysicsError::invalid_relation(format!(
"uniform scalar source requires finite low < high, got [{low}, {high}]"
))),
Self::Histogram(histogram) => {
let mut histogram_rng = fastrand::Rng::with_seed(rng.next_u64());
let value = histogram.sample(&mut histogram_rng)?;
histogram.bin_index(value).ok_or_else(|| {
LadduPhysicsError::invalid_relation(
"sampled histogram value does not belong to an in-range bin",
)
})?;
let probability_density = Self::histogram_density(histogram)?.density(
histogram.bin_edges()[0],
histogram.bin_edges()[histogram.bin_edges().len() - 1],
value,
);
Ok(ScalarProposalResult {
value,
weight: probability_density.recip(),
})
}
}
}
fn histogram_density(histogram: &Histogram) -> LadduPhysicsResult<PiecewiseDensity> {
PiecewiseDensity::from_histogram(histogram).map_err(|_| {
LadduPhysicsError::invalid_value(
"histogram scalar-source counts",
"finite and nonnegative with positive finite total weight",
format!("{:?}", histogram.counts()),
)
})
}
}