Allow passing weights in distance calculations

This commit is contained in:
NicklasXYZ 2024-04-14 16:54:10 +02:00
parent b7f7b29e48
commit 2313363a9e
11 changed files with 626 additions and 249 deletions

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
@ -57,8 +57,9 @@ import gleam_community/maths/piecewise
/// </a>
/// </div>
///
/// The function calculates the greatest common multiple of two integers $$x, y \in \mathbb{Z}$$.
/// The greatest common multiple is the largest positive integer that is divisible by both $$x$$ and $$y$$.
/// The function calculates the greatest common divisor of two integers
/// $$x, y \in \mathbb{Z}$$. The greatest common divisor is the largest positive
/// integer that is divisible by both $$x$$ and $$y$$.
///
/// <details>
/// <summary>Example:</summary>
@ -67,10 +68,10 @@ import gleam_community/maths/piecewise
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.lcm(1, 1)
/// arithmetics.gcd(1, 1)
/// |> should.equal(1)
///
/// arithmetics.lcm(100, 10)
/// arithmetics.gcd(100, 10)
/// |> should.equal(10)
///
/// arithmetics.gcd(-36, -17)
@ -107,8 +108,9 @@ fn do_gcd(x: Int, y: Int) -> Int {
/// </div>
///
///
/// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo of $$x$$ by $$y$$,
/// denoted as $$x \mod y$$, is the remainder $$r$$ of the division of $$x$$ by $$y$$, such that:
/// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo
/// of $$x$$ by $$y$$, denoted as $$x \mod y$$, is the remainder $$r$$ of the
/// division of $$x$$ by $$y$$, such that:
///
/// \\[
/// x = q \cdot y + r \quad \text{and} \quad 0 \leq r < |y|,
@ -116,13 +118,15 @@ fn do_gcd(x: Int, y: Int) -> Int {
///
/// where $$q$$ is an integer that represents the quotient of the division.
///
/// The Euclidean modulo function of two numbers, is the remainder operation most commonly utilized in
/// mathematics. This differs from the standard truncating modulo operation frequently employed in
/// programming via the `%` operator. Unlike the `%` operator, which may return negative results
/// depending on the divisor's sign, the Euclidean modulo function is designed to
/// always yield a positive outcome, ensuring consistency with mathematical conventions.
/// The Euclidean modulo function of two numbers, is the remainder operation most
/// commonly utilized in mathematics. This differs from the standard truncating
/// modulo operation frequently employed in programming via the `%` operator.
/// Unlike the `%` operator, which may return negative results depending on the
/// divisor's sign, the Euclidean modulo function is designed to always yield a
/// positive outcome, ensuring consistency with mathematical conventions.
///
/// Note that like the Gleam division operator `/` this will return `0` if one of the arguments is `0`.
/// Note that like the Gleam division operator `/` this will return `0` if one of
/// the arguments is `0`.
///
///
/// <details>
@ -164,8 +168,9 @@ pub fn int_euclidean_modulo(x: Int, y: Int) -> Int {
/// </a>
/// </div>
///
/// The function calculates the least common multiple of two integers $$x, y \in \mathbb{Z}$$.
/// The least common multiple is the smallest positive integer that has both $$x$$ and $$y$$ as factors.
/// The function calculates the least common multiple of two integers
/// $$x, y \in \mathbb{Z}$$. The least common multiple is the smallest positive
/// integer that has both $$x$$ and $$y$$ as factors.
///
/// <details>
/// <summary>Example:</summary>
@ -203,7 +208,8 @@ pub fn lcm(x: Int, y: Int) -> Int {
/// </a>
/// </div>
///
/// The function returns all the positive divisors of an integer, including the number iteself.
/// The function returns all the positive divisors of an integer, including the
/// number itself.
///
/// <details>
/// <summary>Example:</summary>
@ -254,7 +260,8 @@ fn find_divisors(n: Int) -> List(Int) {
/// </a>
/// </div>
///
/// The function returns all the positive divisors of an integer, excluding the number iteself.
/// The function returns all the positive divisors of an integer, excluding the
/// number iteself.
///
/// <details>
/// <summary>Example:</summary>
@ -355,7 +362,8 @@ pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float
/// \sum_{i=1}^n x_i
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is
/// the value in the input list indexed by $$i$$.
///
/// <details>
/// <summary>Example:</summary>
@ -415,10 +423,10 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns 0.0
/// // An empty list returns 1.0
/// []
/// |> arithmetics.float_product(option.None)
/// |> should.equal(0.0)
/// |> should.equal(1.0)
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
@ -478,7 +486,8 @@ pub fn float_product(
/// \prod_{i=1}^n x_i
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is
/// the value in the input list indexed by $$i$$.
///
/// <details>
/// <summary>Example:</summary>
@ -487,10 +496,10 @@ pub fn float_product(
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns 0
/// // An empty list returns 1
/// []
/// |> arithmetics.int_product()
/// |> should.equal(0)
/// |> should.equal(1)
///
/// // Valid input returns a result
/// [1, 2, 3]
@ -526,9 +535,10 @@ pub fn int_product(arr: List(Int)) -> Int {
/// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$
/// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$
/// is the value in the input list indexed by $$i$$. The value $$v_j$$ is thus the
/// sum of the $$1$$ to $$j$$ first elements in the given list.
///
/// <details>
/// <summary>Example:</summary>
@ -575,9 +585,10 @@ pub fn float_cumulative_sum(arr: List(Float)) -> List(Float) {
/// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$
/// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$
/// is the value in the input list indexed by $$i$$. The value $$v_j$$ is thus the
/// sum of the $$1$$ to $$j$$ first elements in the given list.
///
/// <details>
/// <summary>Example:</summary>
@ -624,9 +635,11 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
/// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of
/// $$n$$ elements. That is, $$n$$ is the length of the list and
/// $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. The
/// value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the
/// given list.
///
/// <details>
/// <summary>Example:</summary>
@ -653,7 +666,7 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
/// </a>
/// </div>
///
pub fn float_cumumlative_product(arr: List(Float)) -> List(Float) {
pub fn float_cumulative_product(arr: List(Float)) -> List(Float) {
case arr {
[] -> []
_ ->
@ -674,9 +687,11 @@ pub fn float_cumumlative_product(arr: List(Float)) -> List(Float) {
/// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of
/// $$n$$ elements. That is, $$n$$ is the length of the list and
/// $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. The
/// value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the
/// given list.
///
/// <details>
/// <summary>Example:</summary>

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
@ -61,9 +61,10 @@ import gleam/int
import gleam/string
import gleam/option
/// Utility function that checks all lists have the expected length
/// Primarily used by all distance measures taking List(Float) as input
fn check_lists(
/// Utility function that checks all lists have the expected length and contents
/// The function is primarily used by all distance measures taking 'List(Float)'
/// as input
fn validate_lists(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
@ -76,9 +77,9 @@ fn check_lists(
"Invalid input argument: The list yarr is empty."
|> Error
_, _ -> {
let xlen: Int = list.length(xarr)
let ylen: Int = list.length(yarr)
case xlen == ylen, weights {
let xarr_length: Int = list.length(xarr)
let yarr_length: Int = list.length(yarr)
case xarr_length == yarr_length, weights {
False, _ ->
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
|> Error
@ -87,11 +88,11 @@ fn check_lists(
|> Ok
}
True, option.Some(warr) -> {
let wlen: Int = list.length(warr)
case xlen == wlen {
True ->
True
|> Ok
let warr_length: Int = list.length(warr)
case xarr_length == warr_length {
True -> {
validate_weights(warr)
}
False ->
"Invalid input argument: length(weights) != length(xarr) and length(weights) != length(yarr). Valid input is when length(weights) == length(xarr) == length(yarr)."
|> Error
@ -102,25 +103,40 @@ fn check_lists(
}
}
fn validate_weights(warr: List(Float)) -> Result(Bool, String) {
// Check that all the given weights are positive
let assert Ok(minimum) = piecewise.list_minimum(warr, float.compare)
case minimum >=. 0.0 {
False ->
"Invalid input argument: One or more weights are negative. Valid input is when all weights are >= 0."
|> Error
True ->
True
|> Ok
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Calculate the $$p$$-norm of a list (representing a vector):
/// Calculate the (weighted) $$p$$-norm of a list (representing a vector):
///
/// \\[
/// \left( \sum_{i=1}^n \left|x_i\right|^{p} \right)^{\frac{1}{p}}
/// \left( \sum_{i=1}^n w_{i} \left|x_{i}\right|^{p} \right)^{\frac{1}{p}}
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in
/// the input list indexed by $$i$$.
/// the input list indexed by $$i$$, while $$w_i \in \mathbb{R}_{+}$$ is
/// a corresponding positive weight ($$w_i = 1.0\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
@ -128,13 +144,17 @@ fn check_lists(
/// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// let assert Ok(result) =
/// [1.0, 1.0, 1.0]
/// |> metrics.norm(1.0)
/// |> metrics.norm(1.0, option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true()
///
/// let assert Ok(result) =
/// [1.0, 1.0, 1.0]
/// |> metrics.norm(-1.0)
/// |> metrics.norm(-1.0, option.None)
/// result
/// |> predicates.is_close(0.3333333333333333, 0.0, tol)
/// |> should.be_true()
/// }
@ -146,19 +166,65 @@ fn check_lists(
/// </a>
/// </div>
///
pub fn norm(arr: List(Float), p: Float) -> Float {
case arr {
[] -> 0.0
_ -> {
let agg: Float =
pub fn norm(
arr: List(Float),
p: Float,
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case arr, weights {
[], _ ->
0.0
|> Ok
_, option.None -> {
let aggregate: Float =
arr
|> list.fold(0.0, fn(acc: Float, a: Float) -> Float {
|> list.fold(0.0, fn(accumulator: Float, element: Float) -> Float {
let assert Ok(result) =
elementary.power(piecewise.float_absolute_value(a), p)
result +. acc
piecewise.float_absolute_value(element)
|> elementary.power(p)
result +. accumulator
})
let assert Ok(result) = elementary.power(agg, 1.0 /. p)
let assert Ok(result) = elementary.power(aggregate, 1.0 /. p)
result
|> Ok
}
_, option.Some(warr) -> {
let arr_length: Int = list.length(arr)
let warr_length: Int = list.length(warr)
case arr_length == warr_length {
True -> {
case validate_weights(warr) {
Ok(_) -> {
let tuples: List(#(Float, Float)) = list.zip(arr, warr)
let aggregate: Float =
tuples
|> list.fold(
0.0,
fn(accumulator: Float, tuple: #(Float, Float)) -> Float {
let first_element: Float = pair.first(tuple)
let second_element: Float = pair.second(tuple)
let assert Ok(result) =
elementary.power(
piecewise.float_absolute_value(first_element),
p,
)
second_element *. result +. accumulator
},
)
let assert Ok(result) = elementary.power(aggregate, 1.0 /. p)
result
|> Ok
}
Error(msg) ->
msg
|> Error
}
}
False -> {
"Invalid input argument: length(weights) != length(arr). Valid input is when length(weights) == length(arr)."
|> Error
}
}
}
}
}
@ -169,19 +235,23 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
/// </a>
/// </div>
///
/// Calculate the Manhattan distance between two lists (representing vectors):
/// Calculate the (weighted) Manhattan distance between two lists (representing
/// vectors):
///
/// \\[
/// \sum_{i=1}^n \left|x_i - y_i \right|
/// \sum_{i=1}^n w_{i} \left|x_i - y_i \right|
/// \\]
///
/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the
/// values in the respective input lists indexed by $$i$$.
/// values in the respective input lists indexed by $$i$$, while
/// $$w_i \in \mathbb{R}_{+}$$ is a corresponding positive weight
/// ($$w_i = 1.0\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
@ -190,14 +260,15 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// // Empty lists returns an error
/// metrics.manhattan_distance([], [])
/// metrics.manhattan_distance([], [], option.None)
/// |> should.be_error()
///
/// // Differing lengths returns error
/// metrics.manhattan_distance([], [1.0])
/// metrics.manhattan_distance([], [1.0], option.None)
/// |> should.be_error()
///
/// let assert Ok(result) = metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0])
/// let assert Ok(result) =
/// metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0], option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true()
@ -224,14 +295,17 @@ pub fn manhattan_distance(
/// </a>
/// </div>
///
/// Calculate the Minkowski distance between two lists (representing vectors):
/// Calculate the (weighted) Minkowski distance between two lists (representing
/// vectors):
///
/// \\[
/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}}
/// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}}
/// \\]
///
/// In the formula, $$p >= 1$$ is the order, $$n$$ is the length of the two lists
/// and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i$$.
/// $$w_i \in \mathbb{R}_{+}$$ is a corresponding positive weight
/// ($$w_i = 1.0\;\forall i=1...n$$ by default).
///
/// The Minkowski distance is a generalization of both the Euclidean distance
/// ($$p=2$$) and the Manhattan distance ($$p = 1$$).
@ -240,6 +314,7 @@ pub fn manhattan_distance(
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
@ -248,18 +323,19 @@ pub fn manhattan_distance(
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// // Empty lists returns an error
/// metrics.minkowski_distance([], [], 1.0)
/// metrics.minkowski_distance([], [], 1.0, option.None)
/// |> should.be_error()
///
/// // Differing lengths returns error
/// metrics.minkowski_distance([], [1.0], 1.0)
/// metrics.minkowski_distance([], [1.0], 1.0, option.None)
/// |> should.be_error()
///
/// // Test order < 1
/// metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0)
/// metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0, option.None)
/// |> should.be_error()
///
/// let assert Ok(result) = metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0)
/// let assert Ok(result) =
/// metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0, option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true()
@ -278,7 +354,7 @@ pub fn minkowski_distance(
p: Float,
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case check_lists(xarr, yarr, weights) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
@ -287,17 +363,21 @@ pub fn minkowski_distance(
True ->
"Invalid input argument: p < 1. Valid input is p >= 1."
|> Error
False ->
False -> {
let differences: List(Float) =
list.zip(xarr, yarr)
|> list.map(fn(tuple: #(Float, Float)) -> Float {
pair.first(tuple) -. pair.second(tuple)
})
|> norm(p)
let assert Ok(result) = norm(differences, p, weights)
result
|> Ok
}
}
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
@ -305,19 +385,23 @@ pub fn minkowski_distance(
/// </a>
/// </div>
///
/// Calculate the Euclidean distance between two lists (representing vectors):
/// Calculate the (weighted) Euclidean distance between two lists (representing
/// vectors):
///
/// \\[
/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}}
/// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}}
/// \\]
///
/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the
/// values in the respective input lists indexed by $$i$$.
/// values in the respective input lists indexed by $$i$$, while
/// $$w_i \in \mathbb{R}_{+}$$ is a corresponding positive weight
/// ($$w_i = 1.0\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
@ -326,14 +410,15 @@ pub fn minkowski_distance(
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// // Empty lists returns an error
/// metrics.euclidean_distance([], [])
/// metrics.euclidean_distance([], [], option.None)
/// |> should.be_error()
///
/// // Differing lengths returns an error
/// metrics.euclidean_distance([], [1.0])
/// metrics.euclidean_distance([], [1.0], option.None)
/// |> should.be_error()
///
/// let assert Ok(result) = metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0])
/// let assert Ok(result) =
/// metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0], option.None)
/// result
/// |> predicates.is_close(2.23606797749979, 0.0, tol)
/// |> should.be_true()
@ -400,20 +485,17 @@ pub fn euclidean_distance(
pub fn chebyshev_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case check_lists(xarr, yarr, weights) {
case validate_lists(xarr, yarr, option.None) {
Error(msg) ->
msg
|> Error
Ok(_) -> {
let differences =
list.zip(xarr, yarr)
|> list.map(fn(tuple: #(Float, Float)) -> Float {
{ pair.first(tuple) -. pair.second(tuple) }
|> piecewise.float_absolute_value()
})
differences
|> piecewise.list_maximum(float.compare)
}
}
@ -948,39 +1030,44 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
/// </a>
/// </div>
///
/// Calculate the cosine similarity between two lists (representing vectors):
/// Calculate the (weighted) cosine similarity between two lists (representing
/// vectors):
///
/// \\[
/// \frac{\sum_{i=1}^n x_i \cdot y_i}{\left(\sum_{i=1}^n x_i^2\right)^{\frac{1}{2}}
/// \cdot \left(\sum_{i=1}^n y_i^2\right)^{\frac{1}{2}}}
/// \frac{\sum_{i=1}^n w_i \cdot x_i \cdot y_i}
/// {\left(\sum_{i=1}^n x_i^2\right)^{\frac{1}{2}}
/// \cdot
/// \left(\sum_{i=1}^n w_i y_i^2\right)^{\frac{1}{2}}}
/// \\; \in \\; \left[-1, 1\right]
/// \\]
///
/// In the formula, $$n$$ is the length of the two lists and $$x_i$$, $$y_i$$ are
/// the values in the respective input lists indexed by $$i$$. The numerator
/// represents the dot product of the two vectors, while the denominator is the
/// product of the magnitudes (Euclidean norms) of the two vectors. The cosine
/// similarity provides a value between -1 and 1, where 1 means the vectors are
/// in the same direction, -1 means they are in exactly opposite directions,
/// and 0 indicates orthogonality.
/// the values in the respective input lists indexed by $$i$$, while
/// $$w_i \in \mathbb{R}_{+}$$ is a corresponding positive weight
/// ($$w_i = 1.0\;\forall i=1...n$$ by default).
///
/// The cosine similarity provides a value between -1 and 1, where 1 means the
/// vectors are in the same direction, -1 means they are in exactly opposite
/// directions, and 0 indicates orthogonality.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics
///
/// pub fn example() {
/// // Two orthogonal vectors
/// metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0])
/// metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0], option.None)
/// |> should.equal(Ok(0.0))
///
/// // Two identical (parallel) vectors
/// metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
/// metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], option.None)
/// |> should.equal(Ok(1.0))
///
/// // Two parallel, but oppositely oriented vectors
/// metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0])
/// metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0], option.None)
/// |> should.equal(Ok(-1.0))
/// }
/// </details>
@ -996,22 +1083,48 @@ pub fn cosine_similarity(
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case check_lists(xarr, yarr, weights) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
Ok(_) -> {
list.fold(
list.zip(xarr, yarr),
0.0,
fn(acc: Float, a: #(Float, Float)) -> Float {
let result: Float = pair.first(a) *. pair.second(a)
result +. acc
},
)
/. { norm(xarr, 2.0) *. norm(yarr, 2.0) }
let zipped_arr: List(#(Float, Float)) = list.zip(xarr, yarr)
let numerator_elements: List(Float) =
zipped_arr
|> list.map(fn(tuple: #(Float, Float)) -> Float {
pair.first(tuple) *. pair.second(tuple)
})
case weights {
option.None -> {
let numerator: Float =
numerator_elements
|> arithmetics.float_sum(option.None)
let assert Ok(xarr_norm) = norm(xarr, 2.0, option.None)
let assert Ok(yarr_norm) = norm(yarr, 2.0, option.None)
let denominator: Float = {
xarr_norm *. yarr_norm
}
numerator /. denominator
|> Ok
}
_ -> {
let numerator: Float =
numerator_elements
|> arithmetics.float_sum(weights)
let assert Ok(xarr_norm) = norm(xarr, 2.0, weights)
let assert Ok(yarr_norm) = norm(yarr, 2.0, weights)
let denominator: Float = {
xarr_norm *. yarr_norm
}
numerator /. denominator
|> Ok
}
}
}
}
}
@ -1144,9 +1257,23 @@ fn distance_list_helper(
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics
///
/// pub fn example() {
/// // Empty lists returns an error
/// metrics.canberra_distance([], [], option.None)
/// |> should.be_error()
///
/// // Different sized lists returns an error
/// metrics.canberra_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
/// |> should.be_error()
///
/// // Valid inputs
/// metrics.canberra_distance([1.0, 2.0], [-2.0, -1.0], option.None)
/// |> should.equal(Ok(2.0))
///
/// metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 0.5]))
/// }
/// </details>
///
@ -1162,7 +1289,7 @@ pub fn canberra_distance(
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case check_lists(xarr, yarr, weights) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
@ -1170,6 +1297,7 @@ pub fn canberra_distance(
let arr: List(Float) =
list.zip(xarr, yarr)
|> list.map(canberra_distance_helper)
case weights {
option.None -> {
arr
@ -1206,9 +1334,24 @@ fn canberra_distance_helper(tuple: #(Float, Float)) -> Float {
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics
///
/// pub fn example() {
/// // Empty lists returns an error
/// metrics.braycurtis_distance([], [], option.None)
/// |> should.be_error()
///
/// // Different sized lists returns an error
/// metrics.braycurtis_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
/// |> should.be_error()
///
/// // Valid inputs
/// metrics.braycurtis_distance([1.0, 0.0], [0.0, 2.0], option.None)
/// |> should.equal(Ok(1.0))
///
/// metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.5, 1.0]))
/// |> should.equal(Ok(0.375))
/// }
/// </details>
///
@ -1224,7 +1367,7 @@ pub fn braycurtis_distance(
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case check_lists(xarr, yarr, weights) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -23,7 +23,8 @@
////
//// ---
////
//// Predicates: A module containing functions for testing various mathematical properties of numbers.
//// Predicates: A module containing functions for testing various mathematical
//// properties of numbers.
////
//// * **Tests**
//// * [`is_close`](#is_close)
@ -50,8 +51,9 @@ import gleam_community/maths/arithmetics
/// </div>
///
/// Determine if a given value $$a$$ is close to or equivalent to a reference value
/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance values.
/// The equivalance of the two given values are then determined based on the equation:
/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance
/// values. The equivalance of the two given values are then determined based on
/// the equation:
///
/// \\[
/// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|)
@ -109,7 +111,8 @@ fn float_absolute_difference(a: Float, b: Float) -> Float {
/// </a>
/// </div>
///
/// Determine if a list of values are close to or equivalent to a another list of reference values.
/// Determine if a list of values are close to or equivalent to a another list of
/// reference values.
///
/// <details>
/// <summary>Example:</summary>
@ -176,7 +179,8 @@ pub fn all_close(
///
/// Determine if a given value is fractional.
///
/// `True` is returned if the given value is fractional, otherwise `False` is returned.
/// `True` is returned if the given value is fractional, otherwise `False` is
/// returned.
///
/// <details>
/// <summary>Example</summary>
@ -213,7 +217,8 @@ fn do_ceiling(a: Float) -> Float
/// </a>
/// </div>
///
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a power of another integer value $$y \in \mathbb{Z}$$.
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a
/// power of another integer value $$y \in \mathbb{Z}$$.
///
/// <details>
/// <summary>Example:</summary>
@ -252,7 +257,9 @@ pub fn is_power(x: Int, y: Int) -> Bool {
/// </a>
/// </div>
///
/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a perfect number. A number is perfect if it is equal to the sum of its proper positive divisors.
/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a
/// perfect number. A number is perfect if it is equal to the sum of its proper
/// positive divisors.
///
/// <details>
/// <summary>Details</summary>
@ -369,13 +376,16 @@ pub fn is_odd(x: Int) -> Bool {
/// </a>
/// </div>
///
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a prime number.
/// A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself.
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a
/// prime number. A prime number is a natural number greater than 1 that has no
/// positive divisors other than 1 and itself.
///
/// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime. It is a probabilistic
/// test, so it can mistakenly identify a composite number as prime. However, the probability of such errors decreases
/// with more testing iterations (the function uses 64 iterations internally, which is typically more than sufficient).
/// The Miller-Rabin test is particularly useful for large numbers.
/// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime.
/// It is a probabilistic test, so it can mistakenly identify a composite number
/// as prime. However, the probability of such errors decreases with more testing
/// iterations (the function uses 64 iterations internally, which is typically
/// more than sufficient). The Miller-Rabin test is particularly useful for large
/// numbers.
///
/// <details>
/// <summary>Details</summary>

View file

@ -23,7 +23,8 @@
////
//// ---
////
//// Sequences: A module containing functions for generating various types of sequences, ranges and intervals.
//// Sequences: A module containing functions for generating various types of
//// sequences, ranges and intervals.
////
//// * **Ranges and intervals**
//// * [`arange`](#arange)
@ -42,8 +43,10 @@ import gleam/list
/// </a>
/// </div>
///
/// The function returns a list with evenly spaced values within a given interval based on a start, stop value and a given increment (step-length) between consecutive values.
/// The list returned includes the given start value but excludes the stop value.
/// The function returns a list with evenly spaced values within a given interval
/// based on a start, stop value and a given increment (step-length) between
/// consecutive values. The list returned includes the given start value but
/// excludes the stop value.
///
/// <details>
/// <summary>Example:</summary>
@ -98,7 +101,8 @@ pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) {
/// </a>
/// </div>
///
/// Generate a linearly spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded.
/// Generate a linearly spaced list of points over a specified interval. The
/// endpoint of the interval can optionally be included/excluded.
///
/// <details>
/// <summary>Example:</summary>
@ -176,7 +180,8 @@ pub fn linear_space(
/// </a>
/// </div>
///
/// Generate a logarithmically spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded.
/// Generate a logarithmically spaced list of points over a specified interval. The
/// endpoint of the interval can optionally be included/excluded.
///
/// <details>
/// <summary>Example:</summary>
@ -236,8 +241,11 @@ pub fn logarithmic_space(
/// </a>
/// </div>
///
/// The function returns a list of numbers spaced evenly on a log scale (a geometric progression). Each point in the list is a constant multiple of the previous.
/// The function is similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints specified directly.
/// The function returns a list of numbers spaced evenly on a log scale (a
/// geometric progression). Each point in the list is a constant multiple of the
/// previous. The function is similar to the
/// [`logarithmic_space`](#logarithmic_space) function, but with endpoints
/// specified directly.
///
/// <details>
/// <summary>Example:</summary>
@ -283,7 +291,7 @@ pub fn geometric_space(
) -> Result(List(Float), String) {
case start == 0.0 || stop == 0.0 {
True ->
""
"Invalid input: Neither 'start' nor 'stop' can be zero, as they must be non-zero for logarithmic calculations."
|> Error
False ->
case num > 0 {

View file

@ -100,8 +100,8 @@ pub fn erf(x: Float) -> Float {
/// </a>
/// </div>
///
/// The gamma function over the real numbers. The function is essentially equal to the
/// factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$
/// The gamma function over the real numbers. The function is essentially equal to
/// the factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$
///
/// The implemented gamma function is approximated through Lanczos approximation
/// using the same coefficients used by the GNU Scientific Library.

View file

@ -197,16 +197,16 @@ pub fn int_list_cumulative_sum_test() {
pub fn float_list_cumulative_product_test() {
// An empty lists returns an empty list
[]
|> arithmetics.float_cumumlative_product()
|> arithmetics.float_cumulative_product()
|> should.equal([])
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_cumumlative_product()
|> arithmetics.float_cumulative_product()
|> should.equal([1.0, 2.0, 6.0])
[-2.0, 4.0, 6.0]
|> arithmetics.float_cumumlative_product()
|> arithmetics.float_cumulative_product()
|> should.equal([-2.0, -8.0, -48.0])
}

View file

@ -10,43 +10,57 @@ pub fn float_list_norm_test() {
// An empty lists returns 0.0
[]
|> metrics.norm(1.0)
|> should.equal(0.0)
|> metrics.norm(1.0, option.None)
|> should.equal(Ok(0.0))
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) =
[1.0, 1.0, 1.0]
|> metrics.norm(1.0)
|> metrics.norm(1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[1.0, 1.0, 1.0]
|> metrics.norm(-1.0)
|> metrics.norm(-1.0, option.None)
result
|> predicates.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -1.0, -1.0]
|> metrics.norm(-1.0)
|> metrics.norm(-1.0, option.None)
result
|> predicates.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -1.0, -1.0]
|> metrics.norm(1.0)
|> metrics.norm(1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> metrics.norm(-10.0)
|> metrics.norm(-10.0, option.None)
result
|> predicates.is_close(0.9999007044905545, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> metrics.norm(-100.0)
|> metrics.norm(-100.0, option.None)
result
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> metrics.norm(2.0)
|> metrics.norm(2.0, option.None)
result
|> predicates.is_close(3.7416573867739413, 0.0, tol)
|> should.be_true()
}
@ -62,12 +76,45 @@ pub fn float_list_manhattan_test() {
metrics.manhattan_distance([], [1.0], option.None)
|> should.be_error()
// manhattan distance (p = 1)
// Try with valid input (same as Minkowski distance with p = 1)
let assert Ok(result) =
metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0], option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
metrics.manhattan_distance([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], option.None)
|> should.equal(Ok(9.0))
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 1.0, 1.0]),
)
|> should.equal(Ok(9.0))
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 2.0, 3.0]),
)
|> should.equal(Ok(18.0))
// Try invalid input with weights (different sized lists returns an error)
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn float_list_minkowski_test() {
@ -124,6 +171,58 @@ pub fn float_list_minkowski_test() {
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
// Try different valid input
let assert Ok(result) =
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
4.0,
option.None,
)
result
|> predicates.is_close(3.9482220388574776, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
4.0,
option.Some([1.0, 1.0, 1.0]),
)
result
|> predicates.is_close(3.9482220388574776, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
4.0,
option.Some([1.0, 2.0, 3.0]),
)
result
|> predicates.is_close(4.6952537402198615, 0.0, tol)
|> should.be_true()
// Try invalid input with weights (different sized lists returns an error)
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
2.0,
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
2.0,
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn float_list_euclidean_test() {
@ -137,12 +236,55 @@ pub fn float_list_euclidean_test() {
metrics.euclidean_distance([], [1.0], option.None)
|> should.be_error()
// Euclidean distance (p = 2)
// Try with valid input (same as Minkowski distance with p = 2)
let assert Ok(result) =
metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0], option.None)
result
|> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true()
// Try different valid input
let assert Ok(result) =
metrics.euclidean_distance([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], option.None)
result
|> predicates.is_close(5.196152422706632, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 1.0, 1.0]),
)
result
|> predicates.is_close(5.196152422706632, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 2.0, 3.0]),
)
result
|> predicates.is_close(7.3484692283495345, 0.0, tol)
|> should.be_true()
// Try invalid input with weights (different sized lists returns an error)
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn mean_test() {
@ -268,6 +410,8 @@ pub fn overlap_coefficient_test() {
}
pub fn cosine_similarity_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error
metrics.cosine_similarity([], [], option.None)
|> should.be_error()
@ -295,43 +439,84 @@ pub fn cosine_similarity_test() {
// Two parallel, but oppositely oriented vectors (represented by lists)
metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0], option.None)
|> should.equal(Ok(-1.0))
// Try with arbitrary valid input
let assert Ok(result) =
metrics.cosine_similarity([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], option.None)
result
|> predicates.is_close(0.9746318461970762, 0.0, tol)
|> should.be_true()
// Try valid input with weights
let assert Ok(result) =
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 1.0, 1.0]),
)
result
|> predicates.is_close(0.9746318461970762, 0.0, tol)
|> should.be_true()
// Try with different weights
let assert Ok(result) =
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 2.0, 3.0]),
)
result
|> predicates.is_close(0.9855274566525745, 0.0, tol)
|> should.be_true()
// Try invalid input with weights (different sized lists returns an error)
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn chebyshev_distance_test() {
// Empty lists returns an error
metrics.chebyshev_distance([], [], option.None)
metrics.chebyshev_distance([], [])
|> should.be_error()
// One empty list returns an error
metrics.chebyshev_distance([1.0, 2.0, 3.0], [], option.None)
metrics.chebyshev_distance([1.0, 2.0, 3.0], [])
|> should.be_error()
// One empty list returns an error
metrics.chebyshev_distance([], [1.0, 2.0, 3.0], option.None)
metrics.chebyshev_distance([], [1.0, 2.0, 3.0])
|> should.be_error()
// Different sized lists returns an error
metrics.chebyshev_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
metrics.chebyshev_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0])
|> should.be_error()
// Try different types of valid input
metrics.chebyshev_distance([1.0, 0.0], [0.0, 2.0], option.None)
metrics.chebyshev_distance([1.0, 0.0], [0.0, 2.0])
|> should.equal(Ok(2.0))
metrics.chebyshev_distance([1.0, 0.0], [2.0, 0.0], option.None)
metrics.chebyshev_distance([1.0, 0.0], [2.0, 0.0])
|> should.equal(Ok(1.0))
metrics.chebyshev_distance([1.0, 0.0], [-2.0, 0.0], option.None)
metrics.chebyshev_distance([1.0, 0.0], [-2.0, 0.0])
|> should.equal(Ok(3.0))
metrics.chebyshev_distance(
[-5.0, -10.0, -3.0],
[-1.0, -12.0, -3.0],
option.None,
)
metrics.chebyshev_distance([-5.0, -10.0, -3.0], [-1.0, -12.0, -3.0])
|> should.equal(Ok(4.0))
metrics.chebyshev_distance([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], option.None)
metrics.chebyshev_distance([1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
|> should.equal(Ok(0.0))
}
@ -421,6 +606,14 @@ pub fn canberra_distance_test() {
option.Some([1.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.canberra_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn braycurtis_distance_test() {
@ -469,4 +662,12 @@ pub fn braycurtis_distance_test() {
option.Some([1.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.braycurtis_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}