///|
/// Clamp a floating-point value to a closed interval.
pub fn clamp_double(value : Double, low~ : Double, high~ : Double) -> Double {
if low > high {
clamp_double(value, low=high, high=low)
} else if value < low {
low
} else if value > high {
high
} else {
value
}
}
///|
/// Return a fallback when a value is NaN or infinite.
pub fn finite_or(value : Double, fallback~ : Double) -> Double {
if value.is_nan() || value.is_inf() {
fallback
} else {
value
}
}
///|
/// A division that returns a caller-selected value for a zero denominator.
pub fn safe_divide(
numerator : Double,
denominator : Double,
fallback~ : Double,
) -> Double {
if denominator == 0.0 {
fallback
} else {
numerator / denominator
}
}
///|
/// Sum a vector without changing its order.
pub fn sum_values(values : ArrayView[Double]) -> Double {
let mut total = 0.0
for value in values {
total += value
}
total
}
///|
/// Arithmetic mean, returning zero for an empty vector.
pub fn mean_value(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
sum_values(values) / values.length().to_double()
}
}
///|
/// Population variance, returning zero for fewer than two values.
pub fn variance_value(values : ArrayView[Double]) -> Double {
if values.length() < 2 {
0.0
} else {
let mean = mean_value(values)
let mut total = 0.0
for value in values {
let delta = value - mean
total += delta * delta
}
total / values.length().to_double()
}
}
///|
/// Root-mean-square magnitude.
pub fn rms_value(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
let mut total = 0.0
for value in values {
total += value * value
}
(total / values.length().to_double()).sqrt()
}
}
///|
/// Absolute L2 error normalized by the number of samples.
pub fn l2_error(
expected : ArrayView[Double],
actual : ArrayView[Double],
) -> Double {
let n = expected.length().min(actual.length())
if n == 0 {
0.0
} else {
let mut total = 0.0
for i in 0.. Double {
let n = expected.length().min(actual.length())
let mut result = 0.0
for i in 0.. Double {
let denominator = rms_value(expected)
if denominator == 0.0 {
l2_error(expected, actual)
} else {
l2_error(expected, actual) / denominator
}
}
///|
/// Linear interpolation between two values.
pub fn lerp(a : Double, b : Double, t : Double) -> Double {
a + (b - a) * t
}
///|
/// Cubic smoothstep interpolation on an unclamped parameter.
pub fn smoothstep(edge0~ : Double, edge1~ : Double, value~ : Double) -> Double {
let t = clamp_double(
safe_divide(value - edge0, edge1 - edge0, fallback=0.0),
low=0.0,
high=1.0,
)
t * t * (3.0 - 2.0 * t)
}
///|
/// Return -1, 0, or 1 according to the sign of a value.
pub fn signum(value : Double) -> Int {
if value < 0.0 {
-1
} else if value > 0.0 {
1
} else {
0
}
}
///|
/// True when a value is close to zero under an absolute tolerance.
pub fn nearly_zero(value : Double, tolerance? : Double = 0.000000001) -> Bool {
let absolute = if value < 0.0 { -value } else { value }
absolute <= tolerance
}
///|
/// Return the smallest and largest finite values in a vector.
pub fn min_max(values : ArrayView[Double]) -> (Double, Double) {
if values.length() == 0 {
(0.0, 0.0)
} else {
let mut minimum = values[0]
let mut maximum = values[0]
for value in values[1:] {
minimum = minimum.min(value)
maximum = maximum.max(value)
}
(minimum, maximum)
}
}
///|
/// A small online accumulator for streaming diagnostics.
pub(all) struct StatisticsAccumulator {
mut count : Int
mut sum : Double
mut sum_squares : Double
mut minimum : Double
mut maximum : Double
} derive(Debug)
///|
/// Create an empty streaming accumulator.
pub fn StatisticsAccumulator::new() -> StatisticsAccumulator {
{ count: 0, sum: 0.0, sum_squares: 0.0, minimum: 0.0, maximum: 0.0 }
}
///|
/// Add one value to a streaming accumulator.
pub fn StatisticsAccumulator::push(
self : StatisticsAccumulator,
value : Double,
) -> Unit {
if self.count == 0 {
self.minimum = value
self.maximum = value
} else {
self.minimum = self.minimum.min(value)
self.maximum = self.maximum.max(value)
}
self.count += 1
self.sum += value
self.sum_squares += value * value
}
///|
/// Convert a streaming accumulator into descriptive statistics.
pub fn StatisticsAccumulator::finish(
self : StatisticsAccumulator,
) -> FieldStatistics {
if self.count == 0 {
{
count: 0,
sum: 0.0,
mean: 0.0,
minimum: 0.0,
maximum: 0.0,
variance: 0.0,
l2_norm: 0.0,
}
} else {
let mean = self.sum / self.count.to_double()
let variance = (self.sum_squares / self.count.to_double() - mean * mean).max(
0.0,
)
{
count: self.count,
sum: self.sum,
mean,
minimum: self.minimum,
maximum: self.maximum,
variance,
l2_norm: self.sum_squares.sqrt(),
}
}
}