Add calc.norm() function to compute euclidean norms (#4581)

Co-authored-by: +merlan #flirora <uruwi@protonmail.com>
Co-authored-by: Yip Coekjan <69834864+Coekjan@users.noreply.github.com>
Co-authored-by: Malo <57839069+MDLC01@users.noreply.github.com>
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
HydroH 2024-10-31 20:41:50 +08:00 committed by GitHub
parent 30427ac842
commit 066e9349f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 0 deletions

View File

@ -50,6 +50,7 @@ pub fn module() -> Module {
scope.define_func::<div_euclid>();
scope.define_func::<rem_euclid>();
scope.define_func::<quo>();
scope.define_func::<norm>();
scope.define("inf", f64::INFINITY);
scope.define("pi", std::f64::consts::PI);
scope.define("tau", std::f64::consts::TAU);
@ -1056,6 +1057,38 @@ pub fn quo(
floor(divided).at(span)
}
/// Calculates the p-norm of a sequence of values.
///
/// ```example
/// #calc.norm(1, 2, -3, 0.5) \
/// #calc.norm(p: 3, 1, 2)
/// ```
#[func(title = "𝑝-Norm")]
pub fn norm(
/// The p value to calculate the p-norm of.
#[named]
#[default(Spanned::new(2.0, Span::detached()))]
p: Spanned<f64>,
/// The sequence of values from which to calculate the p-norm.
/// Returns `0.0` if empty.
#[variadic]
values: Vec<f64>,
) -> SourceResult<f64> {
if p.v <= 0.0 {
bail!(p.span, "p must be greater than zero");
}
// Create an iterator over the absolute values.
let abs = values.into_iter().map(f64::abs);
Ok(if p.v.is_infinite() {
// When p is infinity, the p-norm is the maximum of the absolute values.
abs.max_by(|a, b| a.total_cmp(b)).unwrap_or(0.0)
} else {
abs.map(|v| v.powf(p.v)).sum::<f64>().powf(1.0 / p.v)
})
}
/// A value which can be passed to functions that work with integers and floats.
#[derive(Debug, Copy, Clone)]
pub enum Num {

View File

@ -368,3 +368,19 @@
// Error: 2-37 cannot apply this operation to a decimal and a float
// Hint: 2-37 if loss of precision is acceptable, explicitly cast the decimal to a float with `float(value)`
#calc.clamp(decimal("10"), 5.5, 6.6)
--- calc-norm ---
#test(calc.norm(1, 2, -3, 0.5), calc.sqrt(14.25))
#test(calc.norm(3, 4), 5.0)
#test(calc.norm(3, 4), 5.0)
#test(calc.norm(), 0.0)
#test(calc.norm(p: 3, 1, -2), calc.pow(9, 1/3))
#test(calc.norm(p: calc.inf, 1, -2), 2.0)
--- calc-norm-negative-p ---
// Error: 15-17 p must be greater than zero
#calc.norm(p: -1, 1)
--- calc-norm-expected-float ---
// Error: 12-15 expected float, found ratio
#calc.norm(10%)