Merge pull request #19 from NicklasXYZ/main

Add distance measures, fix typos, update deps
This commit is contained in:
Nicklas Sindlev Andersen 2024-04-14 17:32:58 +02:00 committed by GitHub
commit d2aa3923bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 988 additions and 286 deletions

View file

@ -3,7 +3,7 @@
packages = [ packages = [
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, { name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, { name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
] ]
[requirements] [requirements]

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"> ////<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.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.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" 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> ////<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> ////<script>
//// document.addEventListener("DOMContentLoaded", function() { //// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, { //// renderMathInElement(document.body, {
@ -44,6 +44,9 @@
import gleam/int import gleam/int
import gleam/list import gleam/list
import gleam/option
import gleam/pair
import gleam/result
import gleam_community/maths/conversion import gleam_community/maths/conversion
import gleam_community/maths/elementary import gleam_community/maths/elementary
import gleam_community/maths/piecewise import gleam_community/maths/piecewise
@ -54,8 +57,9 @@ import gleam_community/maths/piecewise
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// The function calculates the greatest common multiple of two integers $$x, y \in \mathbb{Z}$$. /// The function calculates the greatest common divisor of two integers
/// The greatest common multiple is the largest positive integer that is divisible by both $$x$$ and $$y$$. /// $$x, y \in \mathbb{Z}$$. The greatest common divisor is the largest positive
/// integer that is divisible by both $$x$$ and $$y$$.
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -64,10 +68,10 @@ import gleam_community/maths/piecewise
/// import gleam_community/maths/arithmetics /// import gleam_community/maths/arithmetics
/// ///
/// pub fn example() { /// pub fn example() {
/// arithmetics.lcm(1, 1) /// arithmetics.gcd(1, 1)
/// |> should.equal(1) /// |> should.equal(1)
/// ///
/// arithmetics.lcm(100, 10) /// arithmetics.gcd(100, 10)
/// |> should.equal(10) /// |> should.equal(10)
/// ///
/// arithmetics.gcd(-36, -17) /// arithmetics.gcd(-36, -17)
@ -104,8 +108,9 @@ fn do_gcd(x: Int, y: Int) -> Int {
/// </div> /// </div>
/// ///
/// ///
/// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo of $$x$$ by $$y$$, /// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo
/// denoted as $$x \mod y$$, is the remainder $$r$$ of the division of $$x$$ by $$y$$, such that: /// 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|, /// x = q \cdot y + r \quad \text{and} \quad 0 \leq r < |y|,
@ -113,13 +118,15 @@ fn do_gcd(x: Int, y: Int) -> Int {
/// ///
/// where $$q$$ is an integer that represents the quotient of the division. /// 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 /// The Euclidean modulo function of two numbers, is the remainder operation most
/// mathematics. This differs from the standard truncating modulo operation frequently employed in /// commonly utilized in mathematics. This differs from the standard truncating
/// programming via the `%` operator. Unlike the `%` operator, which may return negative results /// modulo operation frequently employed in programming via the `%` operator.
/// depending on the divisor's sign, the Euclidean modulo function is designed to /// Unlike the `%` operator, which may return negative results depending on the
/// always yield a positive outcome, ensuring consistency with mathematical conventions. /// 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> /// <details>
@ -161,8 +168,9 @@ pub fn int_euclidean_modulo(x: Int, y: Int) -> Int {
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// The function calculates the least common multiple of two integers $$x, y \in \mathbb{Z}$$. /// The function calculates the least common multiple of two integers
/// The least common multiple is the smallest positive integer that has both $$x$$ and $$y$$ as factors. /// $$x, y \in \mathbb{Z}$$. The least common multiple is the smallest positive
/// integer that has both $$x$$ and $$y$$ as factors.
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -200,7 +208,8 @@ pub fn lcm(x: Int, y: Int) -> Int {
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -251,7 +260,8 @@ fn find_divisors(n: Int) -> List(Int) {
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -289,29 +299,32 @@ pub fn proper_divisors(n: Int) -> List(Int) {
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// Calculate the sum of the elements in a list: /// Calculate the (weighted) sum of the elements in a list:
/// ///
/// \\[ /// \\[
/// \sum_{i=1}^n x_i /// \sum_{i=1}^n w_i x_i
/// \\] /// \\]
/// ///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ 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{R}$$ is
/// the value in the input list indexed by $$i$$, while the $$w_i \in \mathbb{R}$$
/// are corresponding weights ($$w_i = 1.0\\;\forall i=1...n$$ by default).
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/arithmetics /// import gleam_community/maths/arithmetics
/// ///
/// pub fn example () { /// pub fn example () {
/// // An empty list returns an error /// // An empty list returns an error
/// [] /// []
/// |> arithmetics.float_sum() /// |> arithmetics.float_sum(option.None)
/// |> should.equal(0.0) /// |> should.equal(0.0)
/// ///
/// // Valid input returns a result /// // Valid input returns a result
/// [1.0, 2.0, 3.0] /// [1.0, 2.0, 3.0]
/// |> arithmetics.float_sum() /// |> arithmetics.float_sum(option.None)
/// |> should.equal(6.0) /// |> should.equal(6.0)
/// } /// }
/// </details> /// </details>
@ -322,12 +335,18 @@ pub fn proper_divisors(n: Int) -> List(Int) {
/// </a> /// </a>
/// </div> /// </div>
/// ///
pub fn float_sum(arr: List(Float)) -> Float { pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float {
case arr { case arr, weights {
[] -> 0.0 [], _ -> 0.0
_ -> _, option.None ->
arr arr
|> list.fold(0.0, fn(acc: Float, a: Float) -> Float { a +. acc }) |> list.fold(0.0, fn(acc: Float, a: Float) -> Float { a +. acc })
_, option.Some(warr) -> {
list.zip(arr, warr)
|> list.fold(0.0, fn(acc: Float, a: #(Float, Float)) -> Float {
pair.first(a) *. pair.second(a) +. acc
})
}
} }
} }
@ -343,7 +362,8 @@ pub fn float_sum(arr: List(Float)) -> Float {
/// \sum_{i=1}^n x_i /// \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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -385,29 +405,32 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// Calculate the product of the elements in a list: /// Calculate the (weighted) product of the elements in a list:
/// ///
/// \\[ /// \\[
/// \prod_{i=1}^n x_i /// \prod_{i=1}^n x_i^{w_i}
/// \\] /// \\]
/// ///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ 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{R}$$ is
/// /// the value in the input list indexed by $$i$$, while the $$w_i \in \mathbb{R}$$
/// are corresponding weights ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/arithmetics /// import gleam_community/maths/arithmetics
/// ///
/// pub fn example () { /// pub fn example () {
/// // An empty list returns 0.0 /// // An empty list returns 1.0
/// [] /// []
/// |> arithmetics.float_product() /// |> arithmetics.float_product(option.None)
/// |> should.equal(0.0) /// |> should.equal(1.0)
/// ///
/// // Valid input returns a result /// // Valid input returns a result
/// [1.0, 2.0, 3.0] /// [1.0, 2.0, 3.0]
/// |> arithmetics.float_product() /// |> arithmetics.float_product(option.None)
/// |> should.equal(6.0) /// |> should.equal(6.0)
/// } /// }
/// </details> /// </details>
@ -418,12 +441,36 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// </a> /// </a>
/// </div> /// </div>
/// ///
pub fn float_product(arr: List(Float)) -> Float { pub fn float_product(
case arr { arr: List(Float),
[] -> 1.0 weights: option.Option(List(Float)),
_ -> ) -> Result(Float, String) {
case arr, weights {
[], _ ->
1.0
|> Ok
_, option.None ->
arr arr
|> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc }) |> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
|> Ok
_, option.Some(warr) -> {
let results =
list.zip(arr, warr)
|> list.map(fn(a: #(Float, Float)) -> Result(Float, String) {
pair.first(a)
|> elementary.power(pair.second(a))
})
|> result.all
case results {
Ok(prods) ->
prods
|> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
|> Ok
Error(msg) ->
msg
|> Error
}
}
} }
} }
@ -439,7 +486,8 @@ pub fn float_product(arr: List(Float)) -> Float {
/// \prod_{i=1}^n x_i /// \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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -448,10 +496,10 @@ pub fn float_product(arr: List(Float)) -> Float {
/// import gleam_community/maths/arithmetics /// import gleam_community/maths/arithmetics
/// ///
/// pub fn example () { /// pub fn example () {
/// // An empty list returns 0 /// // An empty list returns 1
/// [] /// []
/// |> arithmetics.int_product() /// |> arithmetics.int_product()
/// |> should.equal(0) /// |> should.equal(1)
/// ///
/// // Valid input returns a result /// // Valid input returns a result
/// [1, 2, 3] /// [1, 2, 3]
@ -487,9 +535,10 @@ pub fn int_product(arr: List(Int)) -> Int {
/// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n /// 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. /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$
/// 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$$. /// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. /// 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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -536,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 /// 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. /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$
/// 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$$. /// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. /// 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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -585,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 /// 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. /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of
/// 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$$. /// $$n$$ elements. That is, $$n$$ is the length of the list and
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. /// $$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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -614,7 +666,7 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
/// </a> /// </a>
/// </div> /// </div>
/// ///
pub fn float_cumumlative_product(arr: List(Float)) -> List(Float) { pub fn float_cumulative_product(arr: List(Float)) -> List(Float) {
case arr { case arr {
[] -> [] [] -> []
_ -> _ ->
@ -635,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 /// 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. /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of
/// 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$$. /// $$n$$ elements. That is, $$n$$ is the length of the list and
/// The value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the given list. /// $$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> /// <details>
/// <summary>Example:</summary> /// <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"> ////<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.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.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" 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> ////<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> ////<script>
//// document.addEventListener("DOMContentLoaded", function() { //// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, { //// 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"> ////<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.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.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" 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> ////<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> ////<script>
//// document.addEventListener("DOMContentLoaded", function() { //// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, { //// 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"> ////<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.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.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" 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> ////<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> ////<script>
//// document.addEventListener("DOMContentLoaded", function() { //// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, { //// 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"> ////<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.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.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" 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> ////<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> ////<script>
//// document.addEventListener("DOMContentLoaded", function() { //// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, { //// renderMathInElement(document.body, {
@ -33,11 +33,14 @@
//// * [`chebyshev_distance`](#chebyshev_distance) //// * [`chebyshev_distance`](#chebyshev_distance)
//// * [`minkowski_distance`](#minkowski_distance) //// * [`minkowski_distance`](#minkowski_distance)
//// * [`cosine_similarity`](#cosine_similarity) //// * [`cosine_similarity`](#cosine_similarity)
//// * [`canberra_distance`](#canberra_distance)
//// * [`braycurtis_distance`](#braycurtis_distance)
//// * **Set & string similarity measures** //// * **Set & string similarity measures**
//// * [`jaccard_index`](#jaccard_index) //// * [`jaccard_index`](#jaccard_index)
//// * [`sorensen_dice_coefficient`](#sorensen_dice_coefficient) //// * [`sorensen_dice_coefficient`](#sorensen_dice_coefficient)
//// * [`tversky_index`](#tversky_index) //// * [`tversky_index`](#tversky_index)
//// * [`overlap_coefficient`](#overlap_coefficient) //// * [`overlap_coefficient`](#overlap_coefficient)
//// * [`levenshtein_distance`](#levenshtein_distance)
//// * **Basic statistical measures** //// * **Basic statistical measures**
//// * [`mean`](#mean) //// * [`mean`](#mean)
//// * [`median`](#median) //// * [`median`](#median)
@ -56,6 +59,62 @@ import gleam/set
import gleam/float import gleam/float
import gleam/int import gleam/int
import gleam/string import gleam/string
import gleam/option
/// 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)),
) -> Result(Bool, String) {
case xarr, yarr {
[], _ ->
"Invalid input argument: The list xarr is empty."
|> Error
_, [] ->
"Invalid input argument: The list yarr is empty."
|> Error
_, _ -> {
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
True, option.None -> {
True
|> Ok
}
True, option.Some(warr) -> {
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
}
}
}
}
}
}
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;"> /// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues"> /// <a href="https://github.com/gleam-community/maths/issues">
@ -63,33 +122,39 @@ import gleam/string
/// </a> /// </a>
/// </div> /// </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 /// 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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary /// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics /// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates /// import gleam_community/maths/predicates
/// ///
/// pub fn example () { /// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// let assert Ok(tol) = elementary.power(-10.0, -6.0)
/// ///
/// [1.0, 1.0, 1.0] /// let assert Ok(result) =
/// |> metrics.norm(1.0) /// [1.0, 1.0, 1.0]
/// |> metrics.norm(1.0, option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol) /// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true() /// |> should.be_true()
/// ///
/// [1.0, 1.0, 1.0] /// let assert Ok(result) =
/// |> metrics.norm(-1.0) /// [1.0, 1.0, 1.0]
/// |> metrics.norm(-1.0, option.None)
/// result
/// |> predicates.is_close(0.3333333333333333, 0.0, tol) /// |> predicates.is_close(0.3333333333333333, 0.0, tol)
/// |> should.be_true() /// |> should.be_true()
/// } /// }
@ -101,19 +166,65 @@ import gleam/string
/// </a> /// </a>
/// </div> /// </div>
/// ///
pub fn norm(arr: List(Float), p: Float) -> Float { pub fn norm(
case arr { arr: List(Float),
[] -> 0.0 p: Float,
_ -> { weights: option.Option(List(Float)),
let agg: Float = ) -> Result(Float, String) {
case arr, weights {
[], _ ->
0.0
|> Ok
_, option.None -> {
let aggregate: Float =
arr 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) = let assert Ok(result) =
elementary.power(piecewise.float_absolute_value(a), p) piecewise.float_absolute_value(element)
result +. acc |> 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 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
}
}
} }
} }
} }
@ -124,35 +235,40 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
/// </a> /// </a>
/// </div> /// </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 /// 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 the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary /// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics /// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates /// import gleam_community/maths/predicates
/// ///
/// pub fn example () { /// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// let assert Ok(tol) = elementary.power(-10.0, -6.0)
/// ///
/// // Empty lists returns an error /// // Empty lists returns an error
/// metrics.manhattan_distance([], []) /// metrics.manhattan_distance([], [], option.None)
/// |> should.be_error() /// |> should.be_error()
/// ///
/// // Differing lengths returns error /// // Differing lengths returns error
/// metrics.manhattan_distance([], [1.0]) /// metrics.manhattan_distance([], [1.0], option.None)
/// |> should.be_error() /// |> 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 /// result
/// |> predicates.is_close(3.0, 0.0, tol) /// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true() /// |> should.be_true()
@ -168,8 +284,9 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
pub fn manhattan_distance( pub fn manhattan_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
minkowski_distance(xarr, yarr, 1.0) minkowski_distance(xarr, yarr, 1.0, weights)
} }
/// <div style="text-align: right;"> /// <div style="text-align: right;">
@ -178,14 +295,17 @@ pub fn manhattan_distance(
/// </a> /// </a>
/// </div> /// </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 /// 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$$. /// and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i$$.
/// The $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
/// ///
/// The Minkowski distance is a generalization of both the Euclidean distance /// The Minkowski distance is a generalization of both the Euclidean distance
/// ($$p=2$$) and the Manhattan distance ($$p = 1$$). /// ($$p=2$$) and the Manhattan distance ($$p = 1$$).
@ -194,29 +314,31 @@ pub fn manhattan_distance(
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary /// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics /// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates /// import gleam_community/maths/predicates
/// ///
/// pub fn example () { /// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// let assert Ok(tol) = elementary.power(-10.0, -6.0)
/// ///
/// // Empty lists returns an error /// // Empty lists returns an error
/// metrics.minkowski_distance([], [], 1.0) /// metrics.minkowski_distance([], [], 1.0, option.None)
/// |> should.be_error() /// |> should.be_error()
/// ///
/// // Differing lengths returns error /// // Differing lengths returns error
/// metrics.minkowski_distance([], [1.0], 1.0) /// metrics.minkowski_distance([], [1.0], 1.0, option.None)
/// |> should.be_error() /// |> should.be_error()
/// ///
/// // Test order < 1 /// // 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() /// |> 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 /// result
/// |> predicates.is_close(3.0, 0.0, tol) /// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true() /// |> should.be_true()
/// } /// }
/// </details> /// </details>
/// ///
@ -230,34 +352,28 @@ pub fn minkowski_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
p: Float, p: Float,
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
case xarr, yarr { case validate_lists(xarr, yarr, weights) {
[], _ -> Error(msg) ->
"Invalid input argument: The list xarr is empty." msg
|> Error |> Error
_, [] -> Ok(_) -> {
"Invalid input argument: The list yarr is empty." case p <. 1.0 {
|> Error
_, _ -> {
let xlen: Int = list.length(xarr)
let ylen: Int = list.length(yarr)
case xlen == ylen {
False ->
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
|> Error
True -> True ->
case p <. 1.0 { "Invalid input argument: p < 1. Valid input is p >= 1."
True -> |> Error
"Invalid input argument: p < 1. Valid input is p >= 1." False -> {
|> Error let differences: List(Float) =
False -> list.zip(xarr, yarr)
list.zip(xarr, yarr) |> list.map(fn(tuple: #(Float, Float)) -> Float {
|> list.map(fn(tuple: #(Float, Float)) -> Float { pair.first(tuple) -. pair.second(tuple)
pair.first(tuple) -. pair.second(tuple) })
})
|> norm(p) let assert Ok(result) = norm(differences, p, weights)
|> Ok result
} |> Ok
}
} }
} }
} }
@ -269,35 +385,40 @@ pub fn minkowski_distance(
/// </a> /// </a>
/// </div> /// </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 /// 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 the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary /// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics /// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates /// import gleam_community/maths/predicates
/// ///
/// pub fn example () { /// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// let assert Ok(tol) = elementary.power(-10.0, -6.0)
/// ///
/// // Empty lists returns an error /// // Empty lists returns an error
/// metrics.euclidean_distance([], []) /// metrics.euclidean_distance([], [], option.None)
/// |> should.be_error() /// |> should.be_error()
/// ///
/// // Differing lengths returns an error /// // Differing lengths returns an error
/// metrics.euclidean_distance([], [1.0]) /// metrics.euclidean_distance([], [1.0], option.None)
/// |> should.be_error() /// |> 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 /// result
/// |> predicates.is_close(2.23606797749979, 0.0, tol) /// |> predicates.is_close(2.23606797749979, 0.0, tol)
/// |> should.be_true() /// |> should.be_true()
@ -313,8 +434,9 @@ pub fn minkowski_distance(
pub fn euclidean_distance( pub fn euclidean_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
minkowski_distance(xarr, yarr, 2.0) minkowski_distance(xarr, yarr, 2.0, weights)
} }
/// <div style="text-align: right;"> /// <div style="text-align: right;">
@ -340,7 +462,7 @@ pub fn euclidean_distance(
/// import gleam_community/maths/metrics /// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates /// import gleam_community/maths/predicates
/// ///
/// pub fn example () { /// pub fn example() {
/// // Empty lists returns an error /// // Empty lists returns an error
/// metrics.chebyshev_distance([], []) /// metrics.chebyshev_distance([], [])
/// |> should.be_error() /// |> should.be_error()
@ -364,31 +486,17 @@ pub fn chebyshev_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
) -> Result(Float, String) { ) -> Result(Float, String) {
case xarr, yarr { case validate_lists(xarr, yarr, option.None) {
[], _ -> Error(msg) ->
"Invalid input argument: The list xarr is empty." msg
|> Error |> Error
_, [] -> Ok(_) -> {
"Invalid input argument: The list yarr is empty." list.zip(xarr, yarr)
|> Error |> list.map(fn(tuple: #(Float, Float)) -> Float {
_, _ -> { { pair.first(tuple) -. pair.second(tuple) }
let xlen: Int = list.length(xarr) |> piecewise.float_absolute_value()
let ylen: Int = list.length(yarr) })
case xlen == ylen { |> piecewise.list_maximum(float.compare)
False ->
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
|> Error
True -> {
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)
}
}
} }
} }
} }
@ -440,7 +548,7 @@ pub fn mean(arr: List(Float)) -> Result(Float, String) {
|> Error |> Error
_ -> _ ->
arr arr
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float { |> fn(a: Float) -> Float {
a /. conversion.int_to_float(list.length(arr)) a /. conversion.int_to_float(list.length(arr))
} }
@ -578,7 +686,7 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) {
let assert Ok(result) = elementary.power(a -. mean, 2.0) let assert Ok(result) = elementary.power(a -. mean, 2.0)
result result
}) })
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float { |> fn(a: Float) -> Float {
a a
/. { /. {
@ -922,39 +1030,44 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
/// </a> /// </a>
/// </div> /// </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}} /// \frac{\sum_{i=1}^n w_{i} \cdot x_i \cdot y_i}
/// \cdot \left(\sum_{i=1}^n y_i^2\right)^{\frac{1}{2}}} /// {\left(\sum_{i=1}^n w_{i} \cdot x_i^2\right)^{\frac{1}{2}}
/// \cdot
/// \left(\sum_{i=1}^n w_{i} \cdot y_i^2\right)^{\frac{1}{2}}}
/// \\; \in \\; \left[-1, 1\right] /// \\; \in \\; \left[-1, 1\right]
/// \\] /// \\]
/// ///
/// In the formula, $$n$$ is the length of the two lists and $$x_i$$, $$y_i$$ are /// 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 /// the values in the respective input lists indexed by $$i$$, while the
/// represents the dot product of the two vectors, while the denominator is the /// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// product of the magnitudes (Euclidean norms) of the two vectors. The cosine /// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
/// 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, /// The cosine similarity provides a value between -1 and 1, where 1 means the
/// and 0 indicates orthogonality. /// vectors are in the same direction, -1 means they are in exactly opposite
/// directions, and 0 indicates orthogonality.
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics /// import gleam_community/maths/metrics
/// ///
/// pub fn example () { /// pub fn example() {
/// // Two orthogonal vectors /// // 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)) /// |> should.equal(Ok(0.0))
/// ///
/// // Two identical (parallel) vectors /// // 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)) /// |> should.equal(Ok(1.0))
/// ///
/// // Two parallel, but oppositely oriented vectors /// // 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)) /// |> should.equal(Ok(-1.0))
/// } /// }
/// </details> /// </details>
@ -968,31 +1081,46 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
pub fn cosine_similarity( pub fn cosine_similarity(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
case xarr, yarr { case validate_lists(xarr, yarr, weights) {
[], _ -> Error(msg) ->
"Invalid input argument: The list xarr is empty." msg
|> Error |> Error
_, [] -> Ok(_) -> {
"Invalid input argument: The list yarr is empty." let zipped_arr: List(#(Float, Float)) = list.zip(xarr, yarr)
|> Error
_, _ -> { let numerator_elements: List(Float) =
let xlen: Int = list.length(xarr) zipped_arr
let ylen: Int = list.length(yarr) |> list.map(fn(tuple: #(Float, Float)) -> Float {
case xlen == ylen { pair.first(tuple) *. pair.second(tuple)
False -> })
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
|> Error case weights {
True -> { option.None -> {
list.fold( let numerator: Float =
list.zip(xarr, yarr), numerator_elements
0.0, |> arithmetics.float_sum(option.None)
fn(acc: Float, a: #(Float, Float)) -> Float {
let result: Float = pair.first(a) *. pair.second(a) let assert Ok(xarr_norm) = norm(xarr, 2.0, option.None)
result +. acc let assert Ok(yarr_norm) = norm(yarr, 2.0, option.None)
}, let denominator: Float = {
) xarr_norm *. yarr_norm
/. { norm(xarr, 2.0) *. norm(yarr, 2.0) } }
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 |> Ok
} }
} }
@ -1014,8 +1142,8 @@ pub fn cosine_similarity(
/// - deletions /// - deletions
/// - substitutions /// - substitutions
/// ///
/// Note: The implementation is primarily based on the elixir implementation /// Note: The implementation is primarily based on the Elixir implementation
/// [https://hex.pm/packages/levenshtein](levenshtein). /// [levenshtein](https://hex.pm/packages/levenshtein).
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -1118,3 +1246,197 @@ fn distance_list_helper(
} }
} }
} }
/// <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 (weighted) Canberra distance between two lists:
///
/// \\[
/// \sum_{i=1}^n w_{i}\frac{\left| x_i - y_i \right|}
/// {\left| x_i \right| + \left| 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$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <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>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
///
pub fn canberra_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
Ok(_) -> {
let arr: List(Float) =
list.zip(xarr, yarr)
|> list.map(canberra_distance_helper)
case weights {
option.None -> {
arr
|> arithmetics.float_sum(option.None)
|> Ok
}
_ -> {
arr
|> arithmetics.float_sum(weights)
|> Ok
}
}
}
}
}
fn canberra_distance_helper(tuple: #(Float, Float)) -> Float {
let numerator: Float =
piecewise.float_absolute_value({ pair.first(tuple) -. pair.second(tuple) })
let denominator: Float = {
piecewise.float_absolute_value(pair.first(tuple))
+. piecewise.float_absolute_value(pair.second(tuple))
}
numerator /. denominator
}
/// <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 (weighted) Bray-Curtis distance between two lists:
///
/// \\[
/// \frac{\sum_{i=1}^n w_{i} \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$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// The Bray-Curtis distance is in the range $$[0, 1]$$ if all entries $$x_i, y_i$$ are
/// positive.
///
/// <details>
/// <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>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
///
pub fn braycurtis_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
Ok(_) -> {
let zipped_arr: List(#(Float, Float)) = list.zip(xarr, yarr)
let numerator_elements: List(Float) =
zipped_arr
|> list.map(fn(tuple: #(Float, Float)) -> Float {
piecewise.float_absolute_value({
pair.first(tuple) -. pair.second(tuple)
})
})
let denominator_elements: List(Float) =
zipped_arr
|> list.map(fn(tuple: #(Float, Float)) -> Float {
piecewise.float_absolute_value({
pair.first(tuple) +. pair.second(tuple)
})
})
case weights {
option.None -> {
let numerator =
numerator_elements
|> arithmetics.float_sum(option.None)
let denominator =
denominator_elements
|> arithmetics.float_sum(option.None)
{ numerator /. denominator }
|> Ok
}
_ -> {
let numerator =
numerator_elements
|> arithmetics.float_sum(weights)
let denominator =
denominator_elements
|> arithmetics.float_sum(weights)
{ numerator /. denominator }
|> Ok
}
}
}
}
}

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"> ////<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.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.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" 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> ////<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> ////<script>
//// document.addEventListener("DOMContentLoaded", function() { //// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, { //// renderMathInElement(document.body, {
@ -490,7 +490,7 @@ fn do_ceiling(a: Float) -> Float
/// The absolute value: /// The absolute value:
/// ///
/// \\[ /// \\[
/// \forall x, y \in \mathbb{R}, \\; |x| \in \mathbb{R}_{+}. /// \forall x \in \mathbb{R}, \\; |x| \in \mathbb{R}_{+}.
/// \\] /// \\]
/// ///
/// The function takes an input $$x$$ and returns a positive float value. /// The function takes an input $$x$$ and returns a positive float value.
@ -519,7 +519,7 @@ pub fn float_absolute_value(x: Float) -> Float {
/// The absolute value: /// The absolute value:
/// ///
/// \\[ /// \\[
/// \forall x, y \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}. /// \forall x \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}.
/// \\] /// \\]
/// ///
/// The function takes an input $$x$$ and returns a positive integer value. /// The function takes an input $$x$$ and returns a positive integer value.
@ -592,7 +592,8 @@ pub fn float_absolute_difference(a: Float, b: Float) -> Float {
/// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}. /// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}.
/// \\] /// \\]
/// ///
/// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer value which is the the absolute difference of the inputs. /// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer
/// value which is the the absolute difference of the inputs.
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -698,7 +699,8 @@ fn do_int_sign(a: Int) -> Int
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// The function takes two arguments $$x, y \in \mathbb{R}$$ and returns $$x$$ such that it has the same sign as $$y$$. /// The function takes two arguments $$x, y \in \mathbb{R}$$ and returns $$x$$
/// such that it has the same sign as $$y$$.
/// ///
/// <div style="text-align: right;"> /// <div style="text-align: right;">
/// <a href="#"> /// <a href="#">
@ -723,7 +725,8 @@ pub fn float_copy_sign(x: Float, y: Float) -> Float {
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$ such that it has the same sign as $$y$$. /// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$
/// such that it has the same sign as $$y$$.
/// ///
/// <div style="text-align: right;"> /// <div style="text-align: right;">
/// <a href="#"> /// <a href="#">

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** //// * **Tests**
//// * [`is_close`](#is_close) //// * [`is_close`](#is_close)
@ -50,8 +51,9 @@ import gleam_community/maths/arithmetics
/// </div> /// </div>
/// ///
/// Determine if a given value $$a$$ is close to or equivalent to a reference value /// 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. /// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance
/// The equivalance of the two given values are then determined based on the equation: /// values. The equivalance of the two given values are then determined based on
/// the equation:
/// ///
/// \\[ /// \\[
/// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|) /// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|)
@ -109,7 +111,8 @@ fn float_absolute_difference(a: Float, b: Float) -> Float {
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -176,7 +179,8 @@ pub fn all_close(
/// ///
/// Determine if a given value is fractional. /// 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> /// <details>
/// <summary>Example</summary> /// <summary>Example</summary>
@ -213,7 +217,8 @@ fn do_ceiling(a: Float) -> Float
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -252,7 +257,9 @@ pub fn is_power(x: Int, y: Int) -> Bool {
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Details</summary> /// <summary>Details</summary>
@ -369,13 +376,16 @@ pub fn is_odd(x: Int) -> Bool {
/// </a> /// </a>
/// </div> /// </div>
/// ///
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a prime number. /// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a
/// A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. /// 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 /// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime.
/// test, so it can mistakenly identify a composite number as prime. However, the probability of such errors decreases /// It is a probabilistic test, so it can mistakenly identify a composite number
/// with more testing iterations (the function uses 64 iterations internally, which is typically more than sufficient). /// as prime. However, the probability of such errors decreases with more testing
/// The Miller-Rabin test is particularly useful for large numbers. /// iterations (the function uses 64 iterations internally, which is typically
/// more than sufficient). The Miller-Rabin test is particularly useful for large
/// numbers.
/// ///
/// <details> /// <details>
/// <summary>Details</summary> /// <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** //// * **Ranges and intervals**
//// * [`arange`](#arange) //// * [`arange`](#arange)
@ -42,8 +43,10 @@ import gleam/list
/// </a> /// </a>
/// </div> /// </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 function returns a list with evenly spaced values within a given interval
/// The list returned includes the given start value but excludes the stop value. /// 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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -98,7 +101,8 @@ pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) {
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -176,7 +180,8 @@ pub fn linear_space(
/// </a> /// </a>
/// </div> /// </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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -236,8 +241,11 @@ pub fn logarithmic_space(
/// </a> /// </a>
/// </div> /// </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 returns a list of numbers spaced evenly on a log scale (a
/// The function is similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints specified directly. /// 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> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
@ -283,7 +291,7 @@ pub fn geometric_space(
) -> Result(List(Float), String) { ) -> Result(List(Float), String) {
case start == 0.0 || stop == 0.0 { case start == 0.0 || stop == 0.0 {
True -> True ->
"" "Invalid input: Neither 'start' nor 'stop' can be zero, as they must be non-zero for logarithmic calculations."
|> Error |> Error
False -> False ->
case num > 0 { case num > 0 {

View file

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

View file

@ -1,5 +1,6 @@
import gleam_community/maths/arithmetics import gleam_community/maths/arithmetics
import gleeunit/should import gleeunit/should
import gleam/option
pub fn int_gcd_test() { pub fn int_gcd_test() {
arithmetics.gcd(1, 1) arithmetics.gcd(1, 1)
@ -100,16 +101,16 @@ pub fn int_divisors_test() {
pub fn float_list_sum_test() { pub fn float_list_sum_test() {
// An empty list returns 0 // An empty list returns 0
[] []
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> should.equal(0.0) |> should.equal(0.0)
// Valid input returns a result // Valid input returns a result
[1.0, 2.0, 3.0] [1.0, 2.0, 3.0]
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> should.equal(6.0) |> should.equal(6.0)
[-2.0, 4.0, 6.0] [-2.0, 4.0, 6.0]
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> should.equal(8.0) |> should.equal(8.0)
} }
@ -132,17 +133,17 @@ pub fn int_list_sum_test() {
pub fn float_list_product_test() { pub fn float_list_product_test() {
// An empty list returns 0 // An empty list returns 0
[] []
|> arithmetics.float_product() |> arithmetics.float_product(option.None)
|> should.equal(1.0) |> should.equal(Ok(1.0))
// Valid input returns a result // Valid input returns a result
[1.0, 2.0, 3.0] [1.0, 2.0, 3.0]
|> arithmetics.float_product() |> arithmetics.float_product(option.None)
|> should.equal(6.0) |> should.equal(Ok(6.0))
[-2.0, 4.0, 6.0] [-2.0, 4.0, 6.0]
|> arithmetics.float_product() |> arithmetics.float_product(option.None)
|> should.equal(-48.0) |> should.equal(Ok(-48.0))
} }
pub fn int_list_product_test() { pub fn int_list_product_test() {
@ -196,16 +197,16 @@ pub fn int_list_cumulative_sum_test() {
pub fn float_list_cumulative_product_test() { pub fn float_list_cumulative_product_test() {
// An empty lists returns an empty list // An empty lists returns an empty list
[] []
|> arithmetics.float_cumumlative_product() |> arithmetics.float_cumulative_product()
|> should.equal([]) |> should.equal([])
// Valid input returns a result // Valid input returns a result
[1.0, 2.0, 3.0] [1.0, 2.0, 3.0]
|> arithmetics.float_cumumlative_product() |> arithmetics.float_cumulative_product()
|> should.equal([1.0, 2.0, 6.0]) |> should.equal([1.0, 2.0, 6.0])
[-2.0, 4.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]) |> should.equal([-2.0, -8.0, -48.0])
} }

View file

@ -3,49 +3,64 @@ import gleam_community/maths/metrics
import gleam_community/maths/predicates import gleam_community/maths/predicates
import gleeunit/should import gleeunit/should
import gleam/set import gleam/set
import gleam/option
pub fn float_list_norm_test() { pub fn float_list_norm_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0) let assert Ok(tol) = elementary.power(-10.0, -6.0)
// An empty lists returns 0.0 // An empty lists returns 0.0
[] []
|> metrics.norm(1.0) |> metrics.norm(1.0, option.None)
|> should.equal(0.0) |> should.equal(Ok(0.0))
// Check that the function agrees, at some arbitrary input // Check that the function agrees, at some arbitrary input
// points, with known function values // points, with known function values
[1.0, 1.0, 1.0] let assert Ok(result) =
|> metrics.norm(1.0) [1.0, 1.0, 1.0]
|> metrics.norm(1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol) |> predicates.is_close(3.0, 0.0, tol)
|> should.be_true() |> should.be_true()
[1.0, 1.0, 1.0] let assert Ok(result) =
|> metrics.norm(-1.0) [1.0, 1.0, 1.0]
|> metrics.norm(-1.0, option.None)
result
|> predicates.is_close(0.3333333333333333, 0.0, tol) |> predicates.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true() |> should.be_true()
[-1.0, -1.0, -1.0] let assert Ok(result) =
|> metrics.norm(-1.0) [-1.0, -1.0, -1.0]
|> metrics.norm(-1.0, option.None)
result
|> predicates.is_close(0.3333333333333333, 0.0, tol) |> predicates.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true() |> should.be_true()
[-1.0, -1.0, -1.0] let assert Ok(result) =
|> metrics.norm(1.0) [-1.0, -1.0, -1.0]
|> metrics.norm(1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol) |> predicates.is_close(3.0, 0.0, tol)
|> should.be_true() |> should.be_true()
[-1.0, -2.0, -3.0] let assert Ok(result) =
|> metrics.norm(-10.0) [-1.0, -2.0, -3.0]
|> metrics.norm(-10.0, option.None)
result
|> predicates.is_close(0.9999007044905545, 0.0, tol) |> predicates.is_close(0.9999007044905545, 0.0, tol)
|> should.be_true() |> should.be_true()
[-1.0, -2.0, -3.0] let assert Ok(result) =
|> metrics.norm(-100.0) [-1.0, -2.0, -3.0]
|> metrics.norm(-100.0, option.None)
result
|> predicates.is_close(1.0, 0.0, tol) |> predicates.is_close(1.0, 0.0, tol)
|> should.be_true() |> should.be_true()
[-1.0, -2.0, -3.0] let assert Ok(result) =
|> metrics.norm(2.0) [-1.0, -2.0, -3.0]
|> metrics.norm(2.0, option.None)
result
|> predicates.is_close(3.7416573867739413, 0.0, tol) |> predicates.is_close(3.7416573867739413, 0.0, tol)
|> should.be_true() |> should.be_true()
} }
@ -54,92 +69,222 @@ pub fn float_list_manhattan_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0) let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error // Empty lists returns an error
metrics.manhattan_distance([], []) metrics.manhattan_distance([], [], option.None)
|> should.be_error() |> should.be_error()
// Differing lengths returns error // Differing lengths returns error
metrics.manhattan_distance([], [1.0]) metrics.manhattan_distance([], [1.0], option.None)
|> should.be_error() |> 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]) let assert Ok(result) =
metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0], option.None)
result result
|> predicates.is_close(3.0, 0.0, tol) |> predicates.is_close(3.0, 0.0, tol)
|> should.be_true() |> 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() { pub fn float_list_minkowski_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0) let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error // Empty lists returns an error
metrics.minkowski_distance([], [], 1.0) metrics.minkowski_distance([], [], 1.0, option.None)
|> should.be_error() |> should.be_error()
// Differing lengths returns error // Differing lengths returns error
metrics.minkowski_distance([], [1.0], 1.0) metrics.minkowski_distance([], [1.0], 1.0, option.None)
|> should.be_error() |> should.be_error()
// Test order < 1 // 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() |> should.be_error()
// Check that the function agrees, at some arbitrary input // Check that the function agrees, at some arbitrary input
// points, with known function values // points, with known function values
let assert Ok(result) = let assert Ok(result) =
metrics.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0) metrics.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0, option.None)
result result
|> predicates.is_close(0.0, 0.0, tol) |> predicates.is_close(0.0, 0.0, tol)
|> should.be_true() |> should.be_true()
let assert Ok(result) = let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0, option.None)
result result
|> predicates.is_close(1.0717734625362931, 0.0, tol) |> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true() |> should.be_true()
let assert Ok(result) = let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0) metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0, option.None)
result result
|> predicates.is_close(1.0069555500567189, 0.0, tol) |> predicates.is_close(1.0069555500567189, 0.0, tol)
|> should.be_true() |> should.be_true()
let assert Ok(result) = let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0, option.None)
result result
|> predicates.is_close(1.0717734625362931, 0.0, tol) |> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true() |> should.be_true()
// Euclidean distance (p = 2) // Euclidean distance (p = 2)
let assert Ok(result) = let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0) metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0, option.None)
result result
|> predicates.is_close(2.23606797749979, 0.0, tol) |> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true() |> should.be_true()
// Manhattan distance (p = 1) // Manhattan distance (p = 1)
let assert Ok(result) = let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0, option.None)
result result
|> predicates.is_close(3.0, 0.0, tol) |> predicates.is_close(3.0, 0.0, tol)
|> should.be_true() |> 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() { pub fn float_list_euclidean_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0) let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error // Empty lists returns an error
metrics.euclidean_distance([], []) metrics.euclidean_distance([], [], option.None)
|> should.be_error() |> should.be_error()
// Differing lengths returns error // Differing lengths returns error
metrics.euclidean_distance([], [1.0]) metrics.euclidean_distance([], [1.0], option.None)
|> should.be_error() |> 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]) let assert Ok(result) =
metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0], option.None)
result result
|> predicates.is_close(2.23606797749979, 0.0, tol) |> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true() |> 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() { pub fn mean_test() {
@ -265,33 +410,80 @@ pub fn overlap_coefficient_test() {
} }
pub fn cosine_similarity_test() { pub fn cosine_similarity_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error // Empty lists returns an error
metrics.cosine_similarity([], []) metrics.cosine_similarity([], [], option.None)
|> should.be_error() |> should.be_error()
// One empty list returns an error // One empty list returns an error
metrics.cosine_similarity([1.0, 2.0, 3.0], []) metrics.cosine_similarity([1.0, 2.0, 3.0], [], option.None)
|> should.be_error() |> should.be_error()
// One empty list returns an error // One empty list returns an error
metrics.cosine_similarity([], [1.0, 2.0, 3.0]) metrics.cosine_similarity([], [1.0, 2.0, 3.0], option.None)
|> should.be_error() |> should.be_error()
// Different sized lists returns an error // Different sized lists returns an error
metrics.cosine_similarity([1.0, 2.0], [1.0, 2.0, 3.0, 4.0]) metrics.cosine_similarity([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
|> should.be_error() |> should.be_error()
// Two orthogonal vectors (represented by lists) // Two orthogonal vectors (represented by lists)
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)) |> should.equal(Ok(0.0))
// Two identical (parallel) vectors (represented by lists) // Two identical (parallel) vectors (represented by lists)
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)) |> should.equal(Ok(1.0))
// Two parallel, but oppositely oriented vectors (represented by lists) // Two parallel, but oppositely oriented vectors (represented by lists)
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)) |> 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() { pub fn chebyshev_distance_test() {
@ -367,3 +559,115 @@ pub fn levenshtein_distance_test() {
) )
|> should.equal(10) |> should.equal(10)
} }
pub fn canberra_distance_test() {
// Empty lists returns an error
metrics.canberra_distance([], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.canberra_distance([1.0, 2.0, 3.0], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.canberra_distance([], [1.0, 2.0, 3.0], 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()
// Try different types of valid input
metrics.canberra_distance([0.0, 0.0], [0.0, 0.0], option.None)
|> should.equal(Ok(0.0))
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.None)
|> should.equal(Ok(2.0))
metrics.canberra_distance([1.0, 0.0], [2.0, 0.0], option.None)
|> should.equal(Ok(1.0 /. 3.0))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 1.0]))
|> should.equal(Ok(2.0))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 0.5]))
|> should.equal(Ok(1.5))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([0.5, 0.5]))
|> should.equal(Ok(1.0))
// Different sized lists (weights) returns an error
metrics.canberra_distance(
[1.0, 2.0, 3.0],
[1.0, 2.0, 3.0],
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() {
// Empty lists returns an error
metrics.braycurtis_distance([], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.braycurtis_distance([1.0, 2.0, 3.0], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.braycurtis_distance([], [1.0, 2.0, 3.0], 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()
// Try different types of valid input
metrics.braycurtis_distance([0.0, 0.0], [0.0, 0.0], option.None)
|> should.equal(Ok(0.0))
metrics.braycurtis_distance([1.0, 2.0], [-2.0, -1.0], option.None)
|> should.equal(Ok(3.0))
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.None)
|> should.equal(Ok(0.4))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([1.0, 1.0]))
|> should.equal(Ok(0.4))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.5, 1.0]))
|> should.equal(Ok(0.375))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.25, 0.25]))
|> should.equal(Ok(0.4))
// Different sized lists (weights) returns an error
metrics.braycurtis_distance(
[1.0, 2.0, 3.0],
[1.0, 2.0, 3.0],
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()
}