summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHydroH <ixlesis@gmail.com>2024-10-31 20:41:50 +0800
committerGitHub <noreply@github.com>2024-10-31 12:41:50 +0000
commit066e9349f96b7bc56536d48aaae29d98f0ee76fa (patch)
tree1d2f4e6a6edb7a279dac3c75a1c6ebfbaa9bb1ca
parent30427ac842000d10666b602a2a313058bc32708e (diff)
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>
-rw-r--r--crates/typst-library/src/foundations/calc.rs33
-rw-r--r--tests/suite/foundations/calc.typ16
2 files changed, 49 insertions, 0 deletions
diff --git a/crates/typst-library/src/foundations/calc.rs b/crates/typst-library/src/foundations/calc.rs
index f12ca74c..e7eb1405 100644
--- a/crates/typst-library/src/foundations/calc.rs
+++ b/crates/typst-library/src/foundations/calc.rs
@@ -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 {
diff --git a/tests/suite/foundations/calc.typ b/tests/suite/foundations/calc.typ
index 5726dafa..23fdea89 100644
--- a/tests/suite/foundations/calc.typ
+++ b/tests/suite/foundations/calc.typ
@@ -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%)