This commit is contained in:
NicklasXYZ 2024-12-07 23:46:49 +01:00
parent 77e13b9254
commit 1e31a8c38b
29 changed files with 8517 additions and 9547 deletions

View file

@ -11,46 +11,44 @@ The library supports both targets: Erlang and JavaScript.
```gleam
import gleam/float
import gleam/iterator
import gleam/option.{Some}
import gleam_community/maths/arithmetics
import gleam_community/maths/combinatorics.{WithoutRepetitions}
import gleam_community/maths/elementary
import gleam_community/maths/piecewise
import gleam_community/maths/predicates
import gleam/yielder
import gleam_community/maths
import gleeunit/should
pub fn example() {
// Evaluate the sine function
let result = elementary.sin(elementary.pi())
let result = maths.sin(maths.pi())
// Set the relative and absolute tolerance
let assert Ok(absolute_tol) = elementary.power(10.0, -6.0)
let assert Ok(absolute_tol) = float.power(10.0, -6.0)
let relative_tol = 0.0
// Check that the value is very close to 0.0
// That is, if 'result' is within +/- 10^(-6)
predicates.is_close(result, 0.0, relative_tol, absolute_tol)
maths.is_close(result, 0.0, relative_tol, absolute_tol)
|> should.be_true()
// Find the greatest common divisor
arithmetics.gcd(54, 24)
maths.gcd(54, 24)
|> should.equal(6)
// Find the minimum and maximum of a list
piecewise.extrema([10.0, 3.0, 50.0, 20.0, 3.0], float.compare)
maths.extrema([10.0, 3.0, 50.0, 20.0, 3.0], float.compare)
|> should.equal(Ok(#(3.0, 50.0)))
// Determine if a number is fractional
predicates.is_fractional(0.3333)
maths.is_fractional(0.3333)
|> should.equal(True)
// Generate all k = 2 combinations of [1, 2, 3]
let assert Ok(combinations) =
combinatorics.list_combination([1, 2, 3], 2, Some(WithoutRepetitions))
let assert Ok(combinations) = maths.list_combination([1, 2, 3], 2)
combinations
|> iterator.to_list()
|> yielder.to_list()
|> should.equal([[1, 2], [1, 3], [2, 3]])
// Compute the Cosine Similarity between two (orthogonal) vectors
maths.cosine_similarity([#(-1.0, 1.0), #(1.0, 1.0), #(0.0, -1.0)])
|> should.equal(Ok(0.0))
}
```

View file

@ -8,6 +8,7 @@ gleam = ">= 0.32.0"
[dependencies]
gleam_stdlib = "~> 0.38"
gleam_yielder = ">= 1.1.0 and < 2.0.0"
[dev-dependencies]
gleeunit = "~> 1.0"

View file

@ -2,10 +2,12 @@
# You typically do not need to edit this file
packages = [
{ name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" },
{ name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" },
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
]
[requirements]
gleam_stdlib = { version = "~> 0.38" }
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
gleeunit = { version = "~> 1.0" }

File diff suppressed because it is too large Load diff

View file

@ -1,720 +0,0 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '$', right: '$', display: false},
//// {left: '\\(', right: '\\)', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : true
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// ---
////
//// Arithmetics: A module containing a collection of fundamental mathematical functions relating to simple arithmetics (addition, subtraction, multiplication, etc.), but also number theory.
////
//// * **Division functions**
//// * [`gcd`](#gcd)
//// * [`lcm`](#lcm)
//// * [`divisors`](#divisors)
//// * [`proper_divisors`](#proper_divisors)
//// * [`int_euclidean_modulo`](#int_euclidean_modulo)
//// * **Sums and products**
//// * [`float_sum`](#float_sum)
//// * [`int_sum`](#int_sum)
//// * [`float_product`](#float_product)
//// * [`int_product`](#int_product)
//// * [`float_cumulative_sum`](#float_cumulative_sum)
//// * [`int_cumulative_sum`](#int_cumulative_sum)
//// * [`float_cumulative_product`](#float_cumulative_product)
//// * [`int_cumulative_product`](#int_cumulative_product)
////
import gleam/int
import gleam/list
import gleam/option
import gleam/pair
import gleam/result
import gleam_community/maths/conversion
import gleam_community/maths/elementary
import gleam_community/maths/piecewise
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// 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>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.gcd(1, 1)
/// |> should.equal(1)
///
/// arithmetics.gcd(100, 10)
/// |> should.equal(10)
///
/// arithmetics.gcd(-36, -17)
/// |> should.equal(1)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn gcd(x: Int, y: Int) -> Int {
let absx = piecewise.int_absolute_value(x)
let absy = piecewise.int_absolute_value(y)
do_gcd(absx, absy)
}
fn do_gcd(x: Int, y: Int) -> Int {
case x == 0 {
True -> y
False -> {
let assert Ok(z) = int.modulo(y, x)
do_gcd(z, x)
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </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:
///
/// \\[
/// x = q \cdot y + r \quad \text{and} \quad 0 \leq r < |y|,
/// \\]
///
/// 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.
///
/// Note that like the Gleam division operator `/` this will return `0` if one of
/// the arguments is `0`.
///
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.euclidean_modulo(15, 4)
/// |> should.equal(3)
///
/// arithmetics.euclidean_modulo(-3, -2)
/// |> should.equal(1)
///
/// arithmetics.euclidean_modulo(5, 0)
/// |> should.equal(0)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn int_euclidean_modulo(x: Int, y: Int) -> Int {
case x % y, x, y {
_, 0, _ -> 0
_, _, 0 -> 0
md, _, _ if md < 0 -> md + int.absolute_value(y)
md, _, _ -> md
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </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.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.lcm(1, 1)
/// |> should.equal(1)
///
/// arithmetics.lcm(100, 10)
/// |> should.equal(100)
///
/// arithmetics.lcm(-36, -17)
/// |> should.equal(612)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn lcm(x: Int, y: Int) -> Int {
let absx = piecewise.int_absolute_value(x)
let absy = piecewise.int_absolute_value(y)
absx * absy / do_gcd(absx, absy)
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns all the positive divisors of an integer, including the
/// number itself.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.divisors(4)
/// |> should.equal([1, 2, 4])
///
/// arithmetics.divisors(6)
/// |> should.equal([1, 2, 3, 6])
///
/// arithmetics.proper_divisors(13)
/// |> should.equal([1, 13])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn divisors(n: Int) -> List(Int) {
find_divisors(n)
}
fn find_divisors(n: Int) -> List(Int) {
let nabs = piecewise.float_absolute_value(conversion.int_to_float(n))
let assert Ok(sqrt_result) = elementary.square_root(nabs)
let max = conversion.float_to_int(sqrt_result) + 1
list.range(2, max)
|> list.fold([1, n], fn(acc, i) {
case n % i == 0 {
True -> [i, n / i, ..acc]
False -> acc
}
})
|> list.unique()
|> list.sort(int.compare)
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns all the positive divisors of an integer, excluding the
/// number iteself.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.proper_divisors(4)
/// |> should.equal([1, 2])
///
/// arithmetics.proper_divisors(6)
/// |> should.equal([1, 2, 3])
///
/// arithmetics.proper_divisors(13)
/// |> should.equal([1])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn proper_divisors(n: Int) -> List(Int) {
let divisors = find_divisors(n)
divisors
|> list.take(list.length(divisors) - 1)
}
/// <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) sum of the elements in a list:
///
/// \\[
/// \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\\), while the \\(w_i \in \mathbb{R}\\)
/// are corresponding 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/arithmetics
///
/// pub fn example () {
/// // An empty list returns an error
/// []
/// |> arithmetics.float_sum(option.None)
/// |> should.equal(0.0)
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
/// |> arithmetics.float_sum(option.None)
/// |> should.equal(6.0)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float {
case arr, weights {
[], _ -> 0.0
_, option.None ->
arr
|> list.fold(0.0, fn(acc, a) { a +. acc })
_, option.Some(warr) -> {
list.zip(arr, warr)
|> list.fold(0.0, fn(acc, a) { pair.first(a) *. pair.second(a) +. acc })
}
}
}
/// <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 sum of the elements in a list:
///
/// \\[
/// \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\\).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns 0
/// []
/// |> arithmetics.int_sum()
/// |> should.equal(0)
///
/// // Valid input returns a result
/// [1, 2, 3]
/// |> arithmetics.int_sum()
/// |> should.equal(6)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn int_sum(arr: List(Int)) -> Int {
case arr {
[] -> 0
_ ->
arr
|> list.fold(0, fn(acc, a) { a + acc })
}
}
/// <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) product of the elements in a list:
///
/// \\[
/// \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\\), while the \\(w_i \in \mathbb{R}\\)
/// are corresponding 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/arithmetics
///
/// pub fn example () {
/// // An empty list returns 1.0
/// []
/// |> arithmetics.float_product(option.None)
/// |> should.equal(1.0)
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
/// |> arithmetics.float_product(option.None)
/// |> should.equal(6.0)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn float_product(
arr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, Nil) {
case arr, weights {
[], _ ->
1.0
|> Ok
_, option.None ->
arr
|> list.fold(1.0, fn(acc, a) { a *. acc })
|> Ok
_, option.Some(warr) -> {
list.zip(arr, warr)
|> list.map(fn(a: #(Float, Float)) -> Result(Float, Nil) {
pair.first(a)
|> elementary.power(pair.second(a))
})
|> result.all
|> result.map(fn(prods) {
prods
|> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
})
}
}
}
/// <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 product of the elements in a list:
///
/// \\[
/// \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\\).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns 1
/// []
/// |> arithmetics.int_product()
/// |> should.equal(1)
///
/// // Valid input returns a result
/// [1, 2, 3]
/// |> arithmetics.int_product()
/// |> should.equal(6)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn int_product(arr: List(Int)) -> Int {
case arr {
[] -> 1
_ ->
arr
|> list.fold(1, fn(acc, a) { a * acc })
}
}
/// <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 cumulative sum of the elements in a list:
///
/// \\[
/// 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.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// []
/// |> arithmetics.float_cumulative_sum()
/// |> should.equal([])
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
/// |> arithmetics.float_cumulative_sum()
/// |> should.equal([1.0, 3.0, 6.0])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn float_cumulative_sum(arr: List(Float)) -> List(Float) {
case arr {
[] -> []
_ ->
arr
|> list.scan(0.0, fn(acc, a) { a +. acc })
}
}
/// <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 cumulative sum of the elements in a list:
///
/// \\[
/// 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.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// []
/// |> arithmetics.int_cumulative_sum()
/// |> should.equal([])
///
/// // Valid input returns a result
/// [1, 2, 3]
/// |> arithmetics.int_cumulative_sum()
/// |> should.equal([1, 3, 6])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
case arr {
[] -> []
_ ->
arr
|> list.scan(0, fn(acc, a) { a + acc })
}
}
/// <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 cumulative product of the elements in a list:
///
/// \\[
/// 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.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns an error
/// []
/// |> arithmetics.float_cumulative_product()
/// |> should.equal([])
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
/// |> arithmetics.float_cumulative_product()
/// |> should.equal([1.0, 2.0, 6.0])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn float_cumulative_product(arr: List(Float)) -> List(Float) {
case arr {
[] -> []
_ ->
arr
|> list.scan(1.0, fn(acc, a) { a *. acc })
}
}
/// <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 cumulative product of the elements in a list:
///
/// \\[
/// 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.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns an error
/// []
/// |> arithmetics.int_cumulative_product()
/// |> should.equal([])
///
/// // Valid input returns a result
/// [1, 2, 3]
/// |> arithmetics.int_cumulative_product()
/// |> should.equal([1, 2, 6])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn int_cumulative_product(arr: List(Int)) -> List(Int) {
case arr {
[] -> []
_ ->
arr
|> list.scan(1, fn(acc, a) { a * acc })
}
}

View file

@ -1,630 +0,0 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '$', right: '$', display: false},
//// {left: '\\(', right: '\\)', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : true
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// ---
////
//// Combinatorics: A module that offers mathematical functions related to counting, arrangements,
//// and permutations/combinations.
////
//// * **Combinatorial functions**
//// * [`combination`](#combination)
//// * [`factorial`](#factorial)
//// * [`permutation`](#permutation)
//// * [`list_combination`](#list_combination)
//// * [`list_permutation`](#list_permutation)
//// * [`cartesian_product`](#cartesian_product)
////
import gleam/iterator
import gleam/list
import gleam/option
import gleam/set
import gleam_community/maths/conversion
import gleam_community/maths/elementary
pub type CombinatoricsMode {
WithRepetitions
WithoutRepetitions
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A combinatorial function for computing the number of \\(k\\)-combinations of \\(n\\) elements.
///
/// **Without Repetitions:**
///
/// \\[
/// C(n, k) = \binom{n}{k} = \frac{n!}{k! (n-k)!}
/// \\]
/// Also known as "\\(n\\) choose \\(k\\)" or the binomial coefficient.
///
/// **With Repetitions:**
///
/// \\[
/// C^*(n, k) = \binom{n + k - 1}{k} = \frac{(n + k - 1)!}{k! (n - 1)!}
/// \\]
/// Also known as the "stars and bars" problem in combinatorics.
///
/// The implementation uses an efficient iterative multiplicative formula for computing the result.
///
/// <details>
/// <summary>Details</summary>
///
/// A \\(k\\)-combination is a sequence of \\(k\\) elements selected from \\(n\\) elements where
/// the order of selection does not matter. For example, consider selecting 2 elements from a list
/// of 3 elements: `["A", "B", "C"]`:
///
/// - For \\(k\\)-combinations (without repetitions), where order does not matter, the possible
/// selections are:
/// - `["A", "B"]`
/// - `["A", "C"]`
/// - `["B", "C"]`
///
/// - For \\(k\\)-combinations (with repetitions), where order does not matter but elements can
/// repeat, the possible selections are:
/// - `["A", "A"], ["A", "B"], ["A", "C"]`
/// - `["B", "B"], ["B", "C"], ["C", "C"]`
///
/// - On the contrary, for \\(k\\)-permutations (without repetitions), the order matters, so the
/// possible selections are:
/// - `["A", "B"], ["B", "A"]`
/// - `["A", "C"], ["C", "A"]`
/// - `["B", "C"], ["C", "B"]`
/// </details>
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/option
/// import gleeunit/should
/// import gleam_community/maths/combinatorics
///
/// pub fn example() {
/// // Invalid input gives an error
/// combinatorics.combination(-1, 1, option.None)
/// |> should.be_error()
///
/// // Valid input: n = 4 and k = 0
/// combinatorics.combination(4, 0, option.Some(combinatorics.WithoutRepetitions))
/// |> should.equal(Ok(1))
///
/// // Valid input: k = n (n = 4, k = 4)
/// combinatorics.combination(4, 4, option.Some(combinatorics.WithoutRepetitions))
/// |> should.equal(Ok(1))
///
/// // Valid input: combinations with repetition (n = 2, k = 3)
/// combinatorics.combination(2, 3, option.Some(combinatorics.WithRepetitions))
/// |> should.equal(Ok(4))
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn combination(
n: Int,
k: Int,
mode: option.Option(CombinatoricsMode),
) -> Result(Int, Nil) {
case n, k {
_, _ if n < 0 -> Error(Nil)
_, _ if k < 0 -> Error(Nil)
_, _ -> {
case mode {
option.Some(WithRepetitions) -> combination_with_repetitions(n, k)
_ -> combination_without_repetitions(n, k)
}
}
}
}
fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
combination_without_repetitions(n + k - 1, k)
}
fn combination_without_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
case n, k {
_, _ if k == 0 || k == n -> {
1 |> Ok
}
_, _ -> {
let min = case k < n - k {
True -> k
False -> n - k
}
list.range(1, min)
|> list.fold(1, fn(acc, x) { acc * { n + 1 - x } / x })
|> 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>
///
/// A combinatorial function for computing the total number of combinations of \\(n\\)
/// elements, that is \\(n!\\).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/combinatorics
///
/// pub fn example() {
/// // Invalid input gives an error
/// combinatorics.factorial(-1)
/// |> should.be_error()
///
/// // Valid input returns a result (n = 0)
/// combinatorics.factorial(0)
/// |> should.equal(Ok(1))
///
/// combinatorics.factorial(3)
/// |> should.equal(Ok(6))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn factorial(n) -> Result(Int, Nil) {
case n {
_ if n < 0 -> Error(Nil)
0 -> Ok(1)
1 -> Ok(1)
_ ->
list.range(1, n)
|> list.fold(1, fn(acc, x) { acc * x })
|> 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>
///
/// A combinatorial function for computing the number of \\(k\\)-permutations.
///
/// **Without** repetitions:
///
/// \\[
/// P(n, k) = \binom{n}{k} \cdot k! = \frac{n!}{(n - k)!}
/// \\]
///
/// **With** repetitions:
///
/// \\[
/// P^*(n, k) = n^k
/// \\]
///
/// The implementation uses an efficient iterative multiplicative formula for computing the result.
///
/// <details>
/// <summary>Details</summary>
///
/// A \\(k\\)-permutation (without repetitions) is a sequence of \\(k\\) elements selected from \
/// \\(n\\) elements where the order of selection matters. For example, consider selecting 2
/// elements from a list of 3 elements: `["A", "B", "C"]`:
///
/// - For \\(k\\)-permutations (without repetitions), the order matters, so the possible selections
/// are:
/// - `["A", "B"], ["B", "A"]`
/// - `["A", "C"], ["C", "A"]`
/// - `["B", "C"], ["C", "B"]`
///
/// - For \\(k\\)-permutations (with repetitions), the order also matters, but we have repeated
/// selections:
/// - `["A", "A"], ["A", "B"], ["A", "C"]`
/// - `["B", "A"], ["B", "B"], ["B", "C"]`
/// - `["C", "A"], ["C", "B"], ["C", "C"]`
///
/// - On the contrary, for \\(k\\)-combinations (without repetitions), where order does not matter,
/// the possible selections are:
/// - `["A", "B"]`
/// - `["A", "C"]`
/// - `["B", "C"]`
/// </details>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/option
/// import gleeunit/should
/// import gleam_community/maths/combinatorics
///
/// pub fn example() {
/// // Invalid input gives an error
/// combinatorics.permutation(-1, 1, option.None)
/// |> should.be_error()
///
/// // Valid input returns a result (n = 4, k = 0)
/// combinatorics.permutation(4, 0, option.Some(combinatorics.WithoutRepetitions))
/// |> should.equal(Ok(1))
///
/// // Valid input returns the correct number of permutations (n = 4, k = 2)
/// combinatorics.permutation(4, 2, option.Some(combinatorics.WithoutRepetitions))
/// |> should.equal(Ok(12))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn permutation(
n: Int,
k: Int,
mode: option.Option(CombinatoricsMode),
) -> Result(Int, Nil) {
case n, k, mode {
_, _, _ if n < 0 -> Error(Nil)
_, _, _ if k < 0 -> Error(Nil)
_, _, option.Some(WithRepetitions) -> permutation_with_repetitions(n, k)
_, _, _ -> permutation_without_repetitions(n, k)
}
}
fn permutation_without_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
case n, k {
_, _ if k < 0 || k > n -> {
0 |> Ok
}
_, _ if k == 0 -> {
1 |> Ok
}
_, _ ->
list.range(0, k - 1)
|> list.fold(1, fn(acc, x) { acc * { n - x } })
|> Ok
}
}
fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
let n_float = conversion.int_to_float(n)
let k_float = conversion.int_to_float(k)
// 'n' ank 'k' are positive integers, so no errors here...
let assert Ok(result) = elementary.power(n_float, k_float)
result
|> conversion.float_to_int()
|> 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>
///
/// Generates all possible combinations of \\(k\\) elements selected from a given list of size
/// \\(n\\).
///
/// The function can handle cases with and without repetitions
/// (see more details [here](#combination)). Also, note that repeated elements are treated as
/// distinct.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/set
/// import gleam/option
/// import gleam/iterator
/// import gleeunit/should
/// import gleam_community/maths/combinatorics
///
/// pub fn example () {
/// // Generate all 3-combinations without repetition
/// let assert Ok(result) =
/// combinatorics.list_combination(
/// [1, 2, 3, 4],
/// 3,
/// option.Some(combinatorics.WithoutRepetitions),
/// )
///
/// result
/// |> iterator.to_list()
/// |> set.from_list()
/// |> should.equal(set.from_list([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn list_combination(
arr: List(a),
k: Int,
mode: option.Option(CombinatoricsMode),
) -> Result(iterator.Iterator(List(a)), Nil) {
case k, mode {
_, _ if k < 0 -> Error(Nil)
_, option.Some(WithRepetitions) -> list_combination_with_repetitions(arr, k)
_, _ -> list_combination_without_repetitions(arr, k)
}
}
fn list_combination_without_repetitions(
arr: List(a),
k: Int,
) -> Result(iterator.Iterator(List(a)), Nil) {
case k, list.length(arr) {
_, arr_length if k > arr_length -> Error(Nil)
// Special case: When k = n, then the entire list is the only valid combination
_, arr_length if k == arr_length -> {
iterator.single(arr) |> Ok
}
_, _ -> {
Ok(
do_list_combination_without_repetitions(iterator.from_list(arr), k, []),
)
}
}
}
fn do_list_combination_without_repetitions(
arr: iterator.Iterator(a),
k: Int,
prefix: List(a),
) -> iterator.Iterator(List(a)) {
case k {
0 -> iterator.single(list.reverse(prefix))
_ ->
case arr |> iterator.step {
iterator.Done -> iterator.empty()
iterator.Next(x, xs) -> {
let with_x =
do_list_combination_without_repetitions(xs, k - 1, [x, ..prefix])
let without_x = do_list_combination_without_repetitions(xs, k, prefix)
iterator.concat([with_x, without_x])
}
}
}
}
fn list_combination_with_repetitions(
arr: List(a),
k: Int,
) -> Result(iterator.Iterator(List(a)), Nil) {
Ok(do_list_combination_with_repetitions(iterator.from_list(arr), k, []))
}
fn do_list_combination_with_repetitions(
arr: iterator.Iterator(a),
k: Int,
prefix: List(a),
) -> iterator.Iterator(List(a)) {
case k {
0 -> iterator.single(list.reverse(prefix))
_ ->
case arr |> iterator.step {
iterator.Done -> iterator.empty()
iterator.Next(x, xs) -> {
let with_x =
do_list_combination_with_repetitions(arr, k - 1, [x, ..prefix])
let without_x = do_list_combination_with_repetitions(xs, k, prefix)
iterator.concat([with_x, without_x])
}
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Generates all possible permutations of \\(k\\) elements selected from a given list of size
/// \\(n\\).
///
/// The function can handle cases with and without repetitions
/// (see more details [here](#permutation)). Also, note that repeated elements are treated as
/// distinct.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/set
/// import gleam/option
/// import gleam/iterator
/// import gleeunit/should
/// import gleam_community/maths/combinatorics
///
/// pub fn example () {
/// // Generate all 3-permutations without repetition
/// let assert Ok(result) =
/// combinatorics.list_permutation(
/// [1, 2, 3],
/// 3,
/// option.Some(combinatorics.WithoutRepetitions),
/// )
///
/// result
/// |> iterator.to_list()
/// |> set.from_list()
/// |> should.equal(
/// set.from_list([
/// [1, 2, 3],
/// [2, 1, 3],
/// [3, 1, 2],
/// [1, 3, 2],
/// [2, 3, 1],
/// [3, 2, 1],
/// ]),
/// )
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
///
pub fn list_permutation(
arr: List(a),
k: Int,
mode: option.Option(CombinatoricsMode),
) -> Result(iterator.Iterator(List(a)), Nil) {
case k, mode {
_, _ if k < 0 -> Error(Nil)
_, option.Some(WithRepetitions) ->
Ok(list_permutation_with_repetitions(arr, k))
_, _ -> list_permutation_without_repetitions(arr, k)
}
}
fn remove_first_by_index(
arr: iterator.Iterator(#(Int, a)),
index_to_remove: Int,
) -> iterator.Iterator(#(Int, a)) {
iterator.flat_map(arr, fn(arg) {
let #(index, element) = arg
case index == index_to_remove {
True -> iterator.empty()
False -> iterator.single(#(index, element))
}
})
}
fn list_permutation_without_repetitions(
arr: List(a),
k: Int,
) -> Result(iterator.Iterator(List(a)), Nil) {
case k, list.length(arr) {
_, arr_length if k > arr_length -> Error(Nil)
_, _ -> {
let indexed_arr = list.index_map(arr, fn(x, i) { #(i, x) })
Ok(do_list_permutation_without_repetitions(
iterator.from_list(indexed_arr),
k,
))
}
}
}
fn do_list_permutation_without_repetitions(
arr: iterator.Iterator(#(Int, a)),
k: Int,
) -> iterator.Iterator(List(a)) {
case k {
0 -> iterator.single([])
_ ->
iterator.flat_map(arr, fn(arg) {
let #(index, element) = arg
let remaining = remove_first_by_index(arr, index)
let permutations =
do_list_permutation_without_repetitions(remaining, k - 1)
iterator.map(permutations, fn(permutation) { [element, ..permutation] })
})
}
}
fn list_permutation_with_repetitions(
arr: List(a),
k: Int,
) -> iterator.Iterator(List(a)) {
let indexed_arr = list.index_map(arr, fn(x, i) { #(i, x) })
do_list_permutation_with_repetitions(indexed_arr, k)
}
fn do_list_permutation_with_repetitions(
arr: List(#(Int, a)),
k: Int,
) -> iterator.Iterator(List(a)) {
case k {
0 -> iterator.single([])
_ ->
iterator.flat_map(arr |> iterator.from_list, fn(arg) {
let #(_, element) = arg
// Allow the same element (by index) to be reused in future recursive calls
let permutations = do_list_permutation_with_repetitions(arr, k - 1)
// Prepend the current element to each generated permutation
iterator.map(permutations, fn(permutation) { [element, ..permutation] })
})
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Generate a list containing all combinations of pairs of elements coming from two given lists.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/set
/// import gleeunit/should
/// import gleam_community/maths/combinatorics
///
/// pub fn example () {
/// // Cartesian product of two empty sets
/// set.from_list([])
/// |> combinatorics.cartesian_product(set.from_list([]))
/// |> should.equal(set.from_list([]))
///
/// // Cartesian product of two sets with numeric values
/// set.from_list([1.0, 10.0])
/// |> combinatorics.cartesian_product(set.from_list([1.0, 2.0]))
/// |> should.equal(
/// set.from_list([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]),
/// )
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn cartesian_product(xset: set.Set(a), yset: set.Set(b)) -> set.Set(#(a, b)) {
xset
|> set.fold(set.new(), fn(accumulator0: set.Set(#(a, b)), member0: a) {
set.fold(yset, accumulator0, fn(accumulator1: set.Set(#(a, b)), member1: b) {
set.insert(accumulator1, #(member0, member1))
})
})
}

View file

@ -1,185 +0,0 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '$', right: '$', display: false},
//// {left: '\\(', right: '\\)', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : true
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// ---
////
//// Conversion: A module containing various functions for converting between types and quantities.
////
//// * **Misc. functions**
//// * [`float_to_int`](#float_to_int)
//// * [`int_to_float`](#int_to_float)
//// * [`degrees_to_radians`](#degrees_to_radians)
//// * [`radians_to_degrees`](#radians_to_degrees)
////
import gleam/int
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A function that produces a number of type `Float` from an `Int`.
///
/// Note: The function is equivalent to the `int.to_float` function in the Gleam stdlib.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/conversion
///
/// pub fn example() {
/// conversion.int_to_float(-1)
/// |> should.equal(-1.0)
///
/// conversion.int_to_float(1)
/// |> should.equal(1.0)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn int_to_float(x: Int) -> Float {
int.to_float(x)
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns the integral part of a given floating point value.
/// That is, everything after the decimal point of a given floating point value is discarded
/// and only the integer value before the decimal point is returned.
///
/// <details>
/// <summary>Example</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/conversion
/// import gleam_community/maths/piecewise
///
/// pub fn example() {
/// conversion.float_to_int(12.0654)
/// |> should.equal(12)
///
/// // Note: Making the following function call is equivalent
/// // but instead of returning a value of type 'Int' a value
/// // of type 'Float' is returned.
/// piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundToZero))
/// |> should.equal(Ok(12.0))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn float_to_int(x: Float) -> Int {
do_to_int(x)
}
@external(erlang, "erlang", "trunc")
@external(javascript, "../../maths.mjs", "truncate")
fn do_to_int(a: Float) -> Int
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Convert a value in degrees to a value measured in radians.
/// That is, \\(1 \text{ degrees } = \frac{\pi}{180} \text{ radians }\\).
///
/// <details>
/// <summary>Example</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/conversion
/// import gleam_community/maths/elementary
///
/// pub fn example() {
/// conversion.degrees_to_radians(360.)
/// |> should.equal(2. *. elementary.pi())
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn degrees_to_radians(x: Float) -> Float {
x *. do_pi() /. 180.0
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Convert a value in degrees to a value measured in radians.
/// That is, \\(1 \text{ radians } = \frac{180}{\pi} \text{ degrees }\\).
///
/// <details>
/// <summary>Example</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/conversion
/// import gleam_community/maths/elementary
///
/// pub fn example() {
/// conversion.radians_to_degrees(0.0)
/// |> should.equal(0.0)
///
/// conversion.radians_to_degrees(2. *. elementary.pi())
/// |> should.equal(360.)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn radians_to_degrees(x: Float) -> Float {
x *. 180.0 /. do_pi()
}
@external(erlang, "math", "pi")
@external(javascript, "../../maths.mjs", "pi")
fn do_pi() -> Float

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,590 +0,0 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '$', right: '$', display: false},
//// {left: '\\(', right: '\\)', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : true
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// ---
////
//// Predicates: A module containing functions for testing various mathematical
//// properties of numbers.
////
//// * **Tests**
//// * [`is_close`](#is_close)
//// * [`list_all_close`](#all_close)
//// * [`is_fractional`](#is_fractional)
//// * [`is_between`](#is_between)
//// * [`is_power`](#is_power)
//// * [`is_perfect`](#is_perfect)
//// * [`is_even`](#is_even)
//// * [`is_odd`](#is_odd)
//// * [`is_divisible`](#is_divisible)
//// * [`is_multiple`](#is_multiple)
//// * [`is_prime`](#is_prime)
////
import gleam/int
import gleam/list
import gleam/option
import gleam/pair
import gleam_community/maths/arithmetics
import gleam_community/maths/elementary
import gleam_community/maths/piecewise
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </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:
///
/// \\[
/// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|)
/// \\]
///
/// `True` is returned if statement holds, otherwise `False` is returned.
/// <details>
/// <summary>Example</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// let val = 99.
/// let ref_val = 100.
/// // We set 'atol' and 'rtol' such that the values are equivalent
/// // if 'val' is within 1 percent of 'ref_val' +/- 0.1
/// let rtol = 0.01
/// let atol = 0.10
/// floatx.is_close(val, ref_val, rtol, atol)
/// |> should.be_true()
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_close(a: Float, b: Float, rtol: Float, atol: Float) -> Bool {
let x = float_absolute_difference(a, b)
let y = atol +. rtol *. float_absolute_value(b)
case x <=. y {
True -> True
False -> False
}
}
fn float_absolute_value(x: Float) -> Float {
case x >. 0.0 {
True -> x
False -> -1.0 *. x
}
}
fn float_absolute_difference(a: Float, b: Float) -> Float {
a -. b
|> float_absolute_value()
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Determine if a list of values are close to or equivalent to a another list of
/// reference values.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/list
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// let val = 99.
/// let ref_val = 100.
/// let xarr = list.repeat(val, 42)
/// let yarr = list.repeat(ref_val, 42)
/// // We set 'atol' and 'rtol' such that the values are equivalent
/// // if 'val' is within 1 percent of 'ref_val' +/- 0.1
/// let rtol = 0.01
/// let atol = 0.10
/// predicates.all_close(xarr, yarr, rtol, atol)
/// |> fn(zarr: Result(List(Bool), Nil)) -> Result(Bool, Nil) {
/// case zarr {
/// Ok(arr) ->
/// arr
/// |> list.all(fn(a) { a })
/// |> Ok
/// _ -> Nil |> Error
/// }
/// }
/// |> should.equal(Ok(True))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn all_close(
xarr: List(Float),
yarr: List(Float),
rtol: Float,
atol: Float,
) -> Result(List(Bool), Nil) {
let xlen = list.length(xarr)
let ylen = list.length(yarr)
case xlen == ylen {
False -> Error(Nil)
True ->
list.zip(xarr, yarr)
|> list.map(fn(z) { is_close(pair.first(z), pair.second(z), rtol, atol) })
|> 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>
///
/// Determine if a given value is fractional.
///
/// `True` is returned if the given value is fractional, otherwise `False` is
/// returned.
///
/// <details>
/// <summary>Example</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// predicates.is_fractional(0.3333)
/// |> should.equal(True)
///
/// predicates.is_fractional(1.0)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_fractional(x: Float) -> Bool {
do_ceiling(x) -. x >. 0.0
}
@external(erlang, "math", "ceil")
@external(javascript, "../../maths.mjs", "ceiling")
fn do_ceiling(a: Float) -> Float
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </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}\\).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// // Check if 4 is a power of 2 (it is)
/// predicates.is_power(4, 2)
/// |> should.equal(True)
///
/// // Check if 5 is a power of 2 (it is not)
/// predicates.is_power(5, 2)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_power(x: Int, y: Int) -> Bool {
let assert Ok(value) =
elementary.logarithm(int.to_float(x), option.Some(int.to_float(y)))
let truncated = piecewise.truncate(value, option.Some(0))
let remainder = value -. truncated
remainder == 0.0
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </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.
///
/// <details>
/// <summary>Details</summary>
///
/// For example:
/// - \\(6\\) is a perfect number since the divisors of 6 are \\(1 + 2 + 3 = 6\\).
/// - \\(28\\) is a perfect number since the divisors of 28 are \\(1 + 2 + 4 + 7 + 14 = 28\\).
///
/// </details>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_perfect(6)
/// |> should.equal(True)
///
/// predicates.is_perfect(28)
/// |> should.equal(True)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_perfect(n: Int) -> Bool {
do_sum(arithmetics.proper_divisors(n)) == n
}
fn do_sum(arr: List(Int)) -> Int {
case arr {
[] -> 0
_ ->
arr
|> list.fold(0, fn(acc, a) { a + acc })
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is even.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_even(-3)
/// |> should.equal(False)
///
/// predicates.is_even(-4)
/// |> should.equal(True)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_even(x: Int) -> Bool {
x % 2 == 0
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is odd.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_odd(-3)
/// |> should.equal(True)
///
/// predicates.is_odd(-4)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_odd(x: Int) -> Bool {
x % 2 != 0
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </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.
///
/// 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>
///
/// Examples of prime numbers:
/// - \\(2\\) is a prime number since it has only two divisors: \\(1\\) and \\(2\\).
/// - \\(7\\) is a prime number since it has only two divisors: \\(1\\) and \\(7\\).
/// - \\(4\\) is not a prime number since it has divisors other than \\(1\\) and itself, such
/// as \\(2\\).
///
/// </details>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_prime(2)
/// |> should.equal(True)
///
/// predicates.is_prime(4)
/// |> should.equal(False)
///
/// // Test the 2nd Carmichael number
/// predicates.is_prime(1105)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_prime(x: Int) -> Bool {
case x {
x if x < 2 -> {
False
}
x if x == 2 -> {
True
}
_ -> {
miller_rabin_test(x, 64)
}
}
}
fn miller_rabin_test(n: Int, k: Int) -> Bool {
case n, k {
_, 0 -> True
_, _ -> {
// Generate a random int in the range [2, n]
let random_candidate = 2 + int.random(n - 2)
case powmod_with_check(random_candidate, n - 1, n) == 1 {
True -> miller_rabin_test(n, k - 1)
False -> False
}
}
}
}
fn powmod_with_check(base: Int, exponent: Int, modulus: Int) -> Int {
case exponent, { exponent % 2 } == 0 {
0, _ -> 1
_, True -> {
let x = powmod_with_check(base, exponent / 2, modulus)
case { x * x } % modulus, x != 1 && x != { modulus - 1 } {
1, True -> 0
_, _ -> { x * x } % modulus
}
}
_, _ -> { base * powmod_with_check(base, exponent - 1, modulus) } % modulus
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A function that tests whether a given real number \\(x \in \mathbb{R}\\) is strictly
/// between two other real numbers, \\(a,b \in \mathbb{R}\\), such that \\(a < x < b\\).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_between(5.5, 5.0, 6.0)
/// |> should.equal(True)
///
/// predicates.is_between(5.0, 5.0, 6.0)
/// |> should.equal(False)
///
/// predicates.is_between(6.0, 5.0, 6.0)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_between(x: Float, lower: Float, upper: Float) -> Bool {
lower <. x && x <. upper
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A function that tests whether a given integer \\(n \in \mathbb{Z}\\) is divisible by another
/// integer \\(d \in \mathbb{Z}\\), such that \\(n \mod d = 0\\).
///
/// <details>
/// <summary>Details</summary>
///
/// For example:
/// - \\(n = 10\\) is divisible by \\(d = 2\\) because \\(10 \mod 2 = 0\\).
/// - \\(n = 7\\) is not divisible by \\(d = 3\\) because \\(7 \mod 3 \neq 0\\).
///
/// </details>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_divisible(10, 2)
/// |> should.equal(True)
///
/// predicates.is_divisible(7, 3)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_divisible(n: Int, d: Int) -> Bool {
n % d == 0
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A function that tests whether a given integer \\(m \in \mathbb{Z}\\) is a multiple of another
/// integer \\(k \in \mathbb{Z}\\), such that \\(m = k \times q\\), with \\(q \in \mathbb{Z}\\).
///
/// <details>
/// <summary>Details</summary>
///
/// For example:
/// - \\(m = 15\\) is a multiple of \\(k = 5\\) because \\(15 = 5 \times 3\\).
/// - \\(m = 14\\) is not a multiple of \\(k = 5\\) because \\(14 \div 5\\) does not yield an
/// integer quotient.
///
/// </details>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/predicates
///
/// pub fn example() {
/// predicates.is_multiple(15, 5)
/// |> should.equal(True)
///
/// predicates.is_multiple(14, 5)
/// |> should.equal(False)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn is_multiple(m: Int, k: Int) -> Bool {
m % k == 0
}

View file

@ -1,328 +0,0 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '$', right: '$', display: false},
//// {left: '\\(', right: '\\)', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : true
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// ---
////
//// Sequences: A module containing functions for generating various types of
//// sequences, ranges and intervals.
////
//// * **Ranges and intervals**
//// * [`arange`](#arange)
//// * [`linear_space`](#linear_space)
//// * [`logarithmic_space`](#logarithmic_space)
//// * [`geometric_space`](#geometric_space)
////
import gleam/iterator
import gleam_community/maths/conversion
import gleam_community/maths/elementary
import gleam_community/maths/piecewise
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns an iterator generating evenly spaced values within a given interval.
/// based on a start value but excludes the stop value. The spacing between values is determined
/// by the step size provided. The function supports both positive and negative step values.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/iterator
/// import gleeunit/should
/// import gleam_community/maths/sequences
///
/// pub fn example () {
/// sequences.arange(1.0, 5.0, 1.0)
/// |> iterator.to_list()
/// |> should.equal([1.0, 2.0, 3.0, 4.0])
///
/// // No points returned since
/// // start is smaller than stop and the step is positive
/// sequences.arange(5.0, 1.0, 1.0)
/// |> iterator.to_list()
/// |> should.equal([])
///
/// // Points returned since
/// // start smaller than stop but negative step
/// sequences.arange(5.0, 1.0, -1.0)
/// |> iterator.to_list()
/// |> should.equal([5.0, 4.0, 3.0, 2.0])
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn arange(
start: Float,
stop: Float,
step: Float,
) -> iterator.Iterator(Float) {
case start >=. stop && step >. 0.0 || start <=. stop && step <. 0.0 {
True -> iterator.empty()
False -> {
let direction = case start <=. stop {
True -> {
1.0
}
False -> {
-1.0
}
}
let step_abs = piecewise.float_absolute_value(step)
let num =
piecewise.float_absolute_value(start -. stop) /. step_abs
|> conversion.float_to_int()
iterator.range(0, num - 1)
|> iterator.map(fn(i) {
start +. conversion.int_to_float(i) *. step_abs *. direction
})
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns an iterator for generating linearly spaced points over a specified
/// interval. The endpoint of the interval can optionally be included/excluded. The number of
/// points and whether the endpoint is included determine the spacing between values.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/iterator
/// import gleeunit/should
/// import gleam_community/maths/elementary
/// import gleam_community/maths/sequences
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// let assert Ok(tol) = elementary.power(10.0, -6.0)
/// let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, True)
/// let assert Ok(result) =
/// predicates.all_close(
/// linspace |> iterator.to_list(),
/// [10.0, 12.5, 15.0, 17.5, 20.0],
/// 0.0,
/// tol,
/// )
///
/// result
/// |> list.all(fn(x) { x == True })
/// |> should.be_true()
///
/// // A negative number of points (-5) does not work
/// sequences.linear_space(10.0, 50.0, -5, True)
/// |> should.be_error()
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn linear_space(
start: Float,
stop: Float,
num: Int,
endpoint: Bool,
) -> Result(iterator.Iterator(Float), Nil) {
let direction = case start <=. stop {
True -> 1.0
False -> -1.0
}
let increment = case endpoint {
True -> {
piecewise.float_absolute_value(start -. stop)
/. conversion.int_to_float(num - 1)
}
False -> {
piecewise.float_absolute_value(start -. stop)
/. conversion.int_to_float(num)
}
}
case num > 0 {
True -> {
iterator.range(0, num - 1)
|> iterator.map(fn(i) {
start +. conversion.int_to_float(i) *. increment *. direction
})
|> Ok
}
False -> Error(Nil)
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns an iterator of logarithmically spaced points over a specified interval.
/// The endpoint of the interval can optionally be included/excluded. The number of points, base,
/// and whether the endpoint is included determine the spacing between values.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/iterator
/// import gleeunit/should
/// import gleam_community/maths/elementary
/// import gleam_community/maths/sequences
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// let assert Ok(tol) = elementary.power(10.0, -6.0)
/// let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, 10.0)
/// let assert Ok(result) =
/// predicates.all_close(
/// logspace |> iterator.to_list(),
/// [10.0, 100.0, 1000.0],
/// 0.0,
/// tol,
/// )
/// result
/// |> list.all(fn(x) { x == True })
/// |> should.be_true()
///
/// // A negative number of points (-3) does not work
/// sequences.logarithmic_space(1.0, 3.0, -3, False, 10.0)
/// |> should.be_error()
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn logarithmic_space(
start: Float,
stop: Float,
num: Int,
endpoint: Bool,
base: Float,
) -> Result(iterator.Iterator(Float), Nil) {
case num > 0 {
True -> {
let assert Ok(linspace) = linear_space(start, stop, num, endpoint)
linspace
|> iterator.map(fn(i) {
let assert Ok(result) = elementary.power(base, i)
result
})
|> Ok
}
False -> Error(Nil)
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The function returns an iterator 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>
///
/// import gleam/iterator
/// import gleeunit/should
/// import gleam_community/maths/elementary
/// import gleam_community/maths/sequences
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// let assert Ok(tol) = elementary.power(10.0, -6.0)
/// let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, True)
/// let assert Ok(result) =
/// predicates.all_close(
/// logspace |> iterator.to_list(),
/// [10.0, 100.0, 1000.0],
/// 0.0,
/// tol,
/// )
/// result
/// |> list.all(fn(x) { x == True })
/// |> should.be_true()
///
/// // Input (start and stop can't be equal to 0.0)
/// sequences.geometric_space(0.0, 1000.0, 3, False)
/// |> should.be_error()
///
/// sequences.geometric_space(-1000.0, 0.0, 3, False)
/// |> should.be_error()
///
/// // A negative number of points (-3) does not work
/// sequences.geometric_space(10.0, 1000.0, -3, False)
/// |> should.be_error()
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn geometric_space(
start: Float,
stop: Float,
num: Int,
endpoint: Bool,
) -> Result(iterator.Iterator(Float), Nil) {
case start == 0.0 || stop == 0.0 {
True -> Error(Nil)
False ->
case num > 0 {
True -> {
let assert Ok(log_start) = elementary.logarithm_10(start)
let assert Ok(log_stop) = elementary.logarithm_10(stop)
logarithmic_space(log_start, log_stop, num, endpoint, 10.0)
}
False -> Error(Nil)
}
}
}

View file

@ -1,195 +0,0 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" integrity="sha384-7zkQWkzuo3B5mTepMUcHkMB5jZaolc2xDwL6VFqjFALcbeS9Ggm/Yr2r3Dy4lfFg" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '$', right: '$', display: false},
//// {left: '\\(', right: '\\)', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : true
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// ---
////
//// Special: A module containing special mathematical functions.
////
//// * **Special mathematical functions**
//// * [`beta`](#beta)
//// * [`erf`](#erf)
//// * [`gamma`](#gamma)
//// * [`incomplete_gamma`](#incomplete_gamma)
////
import gleam/list
import gleam_community/maths/conversion
import gleam_community/maths/elementary
import gleam_community/maths/piecewise
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The beta function over the real numbers:
///
/// \\[
/// \text{B}(x, y) = \frac{\Gamma(x) \cdot \Gamma(y)}{\Gamma(x + y)}
/// \\]
///
/// The beta function is evaluated through the use of the gamma function.
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn beta(x: Float, y: Float) -> Float {
gamma(x) *. gamma(y) /. gamma(x +. y)
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The error function.
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn erf(x: Float) -> Float {
let assert [a1, a2, a3, a4, a5] = [
0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429,
]
let p = 0.3275911
let sign = piecewise.float_sign(x)
let x = piecewise.float_absolute_value(x)
// Formula 7.1.26 given in Abramowitz and Stegun.
let t = 1.0 /. { 1.0 +. p *. x }
let y =
1.0
-. { { { { a5 *. t +. a4 } *. t +. a3 } *. t +. a2 } *. t +. a1 }
*. t
*. elementary.exponential(-1.0 *. x *. x)
sign *. y
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </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 implemented gamma function is approximated through Lanczos approximation
/// using the same coefficients used by the GNU Scientific Library.
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn gamma(x: Float) -> Float {
gamma_lanczos(x)
}
const lanczos_g: Float = 7.0
const lanczos_p: List(Float) = [
0.99999999999980993, 676.5203681218851, -1259.1392167224028,
771.32342877765313, -176.61502916214059, 12.507343278686905,
-0.13857109526572012, 0.0000099843695780195716, 0.00000015056327351493116,
]
fn gamma_lanczos(x: Float) -> Float {
case x <. 0.5 {
True ->
elementary.pi()
/. { elementary.sin(elementary.pi() *. x) *. gamma_lanczos(1.0 -. x) }
False -> {
let z = x -. 1.0
let x =
list.index_fold(lanczos_p, 0.0, fn(acc, v, index) {
case index > 0 {
True -> acc +. v /. { z +. conversion.int_to_float(index) }
False -> v
}
})
let t = z +. lanczos_g +. 0.5
let assert Ok(v1) = elementary.power(2.0 *. elementary.pi(), 0.5)
let assert Ok(v2) = elementary.power(t, z +. 0.5)
v1 *. v2 *. elementary.exponential(-1.0 *. t) *. x
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The lower incomplete gamma function over the real numbers.
///
/// The implemented incomplete gamma function is evaluated through a power series
/// expansion.
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, Nil) {
case a >. 0.0 && x >=. 0.0 {
True -> {
let assert Ok(v) = elementary.power(x, a)
v
*. elementary.exponential(-1.0 *. x)
*. incomplete_gamma_sum(a, x, 1.0 /. a, 0.0, 1.0)
|> Ok
}
False -> Error(Nil)
}
}
fn incomplete_gamma_sum(
a: Float,
x: Float,
t: Float,
s: Float,
n: Float,
) -> Float {
case t {
0.0 -> s
_ -> {
let ns = s +. t
let nt = t *. { x /. { a +. n } }
incomplete_gamma_sum(a, x, nt, ns, n +. 1.0)
}
}
}

View file

@ -0,0 +1,203 @@
import gleam/float
import gleam_community/maths
import gleeunit/should
pub fn int_gcd_test() {
maths.gcd(1, 1)
|> should.equal(1)
maths.gcd(100, 10)
|> should.equal(10)
maths.gcd(10, 100)
|> should.equal(10)
maths.gcd(100, -10)
|> should.equal(10)
maths.gcd(-36, -17)
|> should.equal(1)
maths.gcd(-30, -42)
|> should.equal(6)
}
pub fn int_euclidean_modulo_test() {
// Base Case: Positive x, Positive y
// Note that the truncated, floored, and euclidean
// definitions should agree for this base case
maths.int_euclidean_modulo(15, 4)
|> should.equal(3)
// Case: Positive x, Negative y
maths.int_euclidean_modulo(15, -4)
|> should.equal(3)
// Case: Negative x, Positive y
maths.int_euclidean_modulo(-15, 4)
|> should.equal(1)
// Case: Negative x, Negative y
maths.int_euclidean_modulo(-15, -4)
|> should.equal(1)
// Case: Positive x, Zero y
maths.int_euclidean_modulo(5, 0)
|> should.equal(0)
// Case: Zero x, Negative y
maths.int_euclidean_modulo(0, 5)
|> should.equal(0)
}
pub fn int_lcm_test() {
maths.lcm(1, 1)
|> should.equal(1)
maths.lcm(100, 10)
|> should.equal(100)
maths.lcm(10, 100)
|> should.equal(100)
maths.lcm(100, -10)
|> should.equal(100)
maths.lcm(-36, -17)
|> should.equal(612)
maths.lcm(-30, -42)
|> should.equal(210)
}
pub fn int_proper_divisors_test() {
maths.proper_divisors(2)
|> should.equal([1])
maths.proper_divisors(6)
|> should.equal([1, 2, 3])
maths.proper_divisors(13)
|> should.equal([1])
maths.proper_divisors(18)
|> should.equal([1, 2, 3, 6, 9])
}
pub fn int_divisors_test() {
maths.divisors(2)
|> should.equal([1, 2])
maths.divisors(6)
|> should.equal([1, 2, 3, 6])
maths.divisors(13)
|> should.equal([1, 13])
maths.divisors(18)
|> should.equal([1, 2, 3, 6, 9, 18])
}
pub fn float_list_cumulative_sum_test() {
// An empty lists returns an empty list
[]
|> maths.float_cumulative_sum()
|> should.equal([])
// Valid input returns a result
[1.0, 2.0, 3.0]
|> maths.float_cumulative_sum()
|> should.equal([1.0, 3.0, 6.0])
[-2.0, 4.0, 6.0]
|> maths.float_cumulative_sum()
|> should.equal([-2.0, 2.0, 8.0])
}
pub fn int_list_cumulative_sum_test() {
// An empty lists returns an empty list
[]
|> maths.int_cumulative_sum()
|> should.equal([])
// Valid input returns a result
[1, 2, 3]
|> maths.int_cumulative_sum()
|> should.equal([1, 3, 6])
[-2, 4, 6]
|> maths.int_cumulative_sum()
|> should.equal([-2, 2, 8])
}
pub fn float_list_cumulative_product_test() {
// An empty lists returns an empty list
[]
|> maths.float_cumulative_product()
|> should.equal([])
// Valid input returns a result
[1.0, 2.0, 3.0]
|> maths.float_cumulative_product()
|> should.equal([1.0, 2.0, 6.0])
[-2.0, 4.0, 6.0]
|> maths.float_cumulative_product()
|> should.equal([-2.0, -8.0, -48.0])
}
pub fn int_list_cumulative_product_test() {
// An empty lists returns an empty list
[]
|> maths.int_cumulative_product()
|> should.equal([])
// Valid input returns a result
[1, 2, 3]
|> maths.int_cumulative_product()
|> should.equal([1, 2, 6])
[-2, 4, 6]
|> maths.int_cumulative_product()
|> should.equal([-2, -8, -48])
}
pub fn float_weighted_product_test() {
[]
|> maths.float_weighted_product()
|> should.equal(Ok(1.0))
[#(1.0, 0.0), #(2.0, 0.0), #(3.0, 0.0)]
|> maths.float_weighted_product()
|> should.equal(Ok(1.0))
[#(1.0, 1.0), #(2.0, 1.0), #(3.0, 1.0)]
|> maths.float_weighted_product()
|> should.equal(Ok(6.0))
let assert Ok(tolerance) = float.power(10.0, -6.0)
let assert Ok(result) =
[#(9.0, 0.5), #(10.0, 0.5), #(10.0, 0.5)]
|> maths.float_weighted_product()
result
|> maths.is_close(30.0, 0.0, tolerance)
|> should.be_true()
}
pub fn float_weighted_sum_test() {
[]
|> maths.float_weighted_sum()
|> should.equal(Ok(0.0))
[#(1.0, 0.0), #(2.0, 0.0), #(3.0, 0.0)]
|> maths.float_weighted_sum()
|> should.equal(Ok(0.0))
[#(1.0, 1.0), #(2.0, 1.0), #(3.0, 1.0)]
|> maths.float_weighted_sum()
|> should.equal(Ok(6.0))
[#(9.0, 0.5), #(10.0, 0.5), #(10.0, 0.5)]
|> maths.float_weighted_sum()
|> should.equal(Ok(14.5))
}

View file

@ -0,0 +1,113 @@
import gleam/float
import gleam_community/maths
import gleeunit/should
pub fn float_to_degree_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
maths.radians_to_degrees(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.radians_to_degrees(2.0 *. maths.pi())
|> maths.is_close(360.0, 0.0, tol)
|> should.be_true()
}
pub fn float_to_radian_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
maths.degrees_to_radians(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.degrees_to_radians(360.0)
|> maths.is_close(2.0 *. maths.pi(), 0.0, tol)
|> should.be_true()
}
pub fn float_cartesian_to_polar_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Test: Cartesian (1, 0) -> Polar (1, 0)
let #(r, theta) = maths.cartesian_to_polar(1.0, 0.0)
r
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
theta
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
// Test: Cartesian (0, 1) -> Polar (1, pi/2)
let #(r, theta) = maths.cartesian_to_polar(0.0, 1.0)
r
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
theta
|> maths.is_close(maths.pi() /. 2.0, 0.0, tol)
|> should.be_true()
// Test: Cartesian (-1, 0) -> Polar (1, pi)
let #(r, theta) = maths.cartesian_to_polar(-1.0, 0.0)
r
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
theta
|> maths.is_close(maths.pi(), 0.0, tol)
|> should.be_true()
// Test: Cartesian (0, -1) -> Polar (1, -pi/2)
let #(r, theta) = maths.cartesian_to_polar(0.0, -1.0)
r
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
theta
|> maths.is_close(-1.0 *. maths.pi() /. 2.0, 0.0, tol)
|> should.be_true()
}
pub fn float_polar_to_cartesian_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Test: Polar (1, 0) -> Cartesian (1, 0)
let #(x, y) = maths.polar_to_cartesian(1.0, 0.0)
x
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
y
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
// Test: Polar (1, pi/2) -> Cartesian (0, 1)
let #(x, y) = maths.polar_to_cartesian(1.0, maths.pi() /. 2.0)
x
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
y
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
// Test: Polar (1, pi) -> Cartesian (-1, 0)
let #(x, y) = maths.polar_to_cartesian(1.0, maths.pi())
x
|> maths.is_close(-1.0, 0.0, tol)
|> should.be_true()
y
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
// Test: Polar (1, -pi/2) -> Cartesian (0, -1)
let #(x, y) = maths.polar_to_cartesian(1.0, -1.0 *. maths.pi() /. 2.0)
x
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
y
|> maths.is_close(-1.0, 0.0, tol)
|> should.be_true()
}

View file

@ -0,0 +1,419 @@
import gleam/float
import gleam_community/maths
import gleeunit/should
pub fn float_acos_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = maths.acos(1.0)
result
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = maths.acos(0.5)
result
|> maths.is_close(1.047197, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.acos(1.1)
|> should.be_error()
maths.acos(-1.1)
|> should.be_error()
}
pub fn float_acosh_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = maths.acosh(1.0)
result
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.acosh(0.0)
|> should.be_error()
}
pub fn float_asin_test() {
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.asin(0.0)
|> should.equal(Ok(0.0))
let assert Ok(tol) = float.power(10.0, -6.0)
let assert Ok(result) = maths.asin(0.5)
result
|> maths.is_close(0.523598, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.asin(1.1)
|> should.be_error()
maths.asin(-1.1)
|> should.be_error()
}
pub fn float_asinh_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.asinh(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.asinh(0.5)
|> maths.is_close(0.481211, 0.0, tol)
|> should.be_true()
}
pub fn float_atan_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.atan(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.atan(0.5)
|> maths.is_close(0.463647, 0.0, tol)
|> should.be_true()
}
pub fn math_atan2_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.atan2(0.0, 0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.atan2(0.0, 1.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
// Check atan2(y=1.0, x=0.5)
// Should be equal to atan(y / x) for any x > 0 and any y
let result = maths.atan(1.0 /. 0.5)
maths.atan2(1.0, 0.5)
|> maths.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=2.0, x=-1.5)
// Should be equal to pi + atan(y / x) for any x < 0 and y >= 0
let result = maths.pi() +. maths.atan(2.0 /. -1.5)
maths.atan2(2.0, -1.5)
|> maths.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=-2.0, x=-1.5)
// Should be equal to atan(y / x) - pi for any x < 0 and y < 0
let result = maths.atan(-2.0 /. -1.5) -. maths.pi()
maths.atan2(-2.0, -1.5)
|> maths.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=1.5, x=0.0)
// Should be equal to pi/2 for x = 0 and any y > 0
let result = maths.pi() /. 2.0
maths.atan2(1.5, 0.0)
|> maths.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=-1.5, x=0.0)
// Should be equal to -pi/2 for x = 0 and any y < 0
let result = -1.0 *. maths.pi() /. 2.0
maths.atan2(-1.5, 0.0)
|> maths.is_close(result, 0.0, tol)
|> should.be_true()
}
pub fn float_atanh_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = maths.atanh(0.0)
result
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = maths.atanh(0.5)
result
|> maths.is_close(0.549306, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.atanh(1.0)
|> should.be_error()
maths.atanh(2.0)
|> should.be_error()
maths.atanh(1.0)
|> should.be_error()
maths.atanh(-2.0)
|> should.be_error()
}
pub fn float_cos_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.cos(0.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.cos(maths.pi())
|> maths.is_close(-1.0, 0.0, tol)
|> should.be_true()
maths.cos(0.5)
|> maths.is_close(0.877582, 0.0, tol)
|> should.be_true()
}
pub fn float_cosh_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.cosh(0.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.cosh(0.5)
|> maths.is_close(1.127625, 0.0, tol)
|> should.be_true()
// An (overflow) error might occur when given an input
// value that will result in a too large output value
// e.g. maths.cosh(1000.0) but this is a property of the
// runtime.
}
pub fn float_sin_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.sin(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.sin(0.5 *. maths.pi())
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.sin(0.5)
|> maths.is_close(0.479425, 0.0, tol)
|> should.be_true()
}
pub fn float_sinh_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.sinh(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.sinh(0.5)
|> maths.is_close(0.521095, 0.0, tol)
|> should.be_true()
// An (overflow) error might occur when given an input
// value that will result in a too large output value
// e.g. maths.sinh(1000.0) but this is a property of the
// runtime.
}
pub fn math_tan_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.tan(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.tan(0.5)
|> maths.is_close(0.546302, 0.0, tol)
|> should.be_true()
}
pub fn math_tanh_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.tanh(0.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.tanh(25.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.tanh(-25.0)
|> maths.is_close(-1.0, 0.0, tol)
|> should.be_true()
maths.tanh(0.5)
|> maths.is_close(0.462117, 0.0, tol)
|> should.be_true()
}
pub fn float_exponential_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.exponential(0.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.exponential(0.5)
|> maths.is_close(1.648721, 0.0, tol)
|> should.be_true()
// An (overflow) error might occur when given an input
// value that will result in a too large output value
// e.g. maths.exponential(1000.0) but this is a property of the
// runtime.
}
pub fn float_natural_logarithm_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.natural_logarithm(1.0)
|> should.equal(Ok(0.0))
let assert Ok(result) = maths.natural_logarithm(0.5)
result
|> maths.is_close(-0.693147, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.natural_logarithm(-1.0)
|> should.be_error()
}
pub fn float_logarithm_test() {
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.logarithm(10.0, 10.0)
|> should.equal(Ok(1.0))
maths.logarithm(10.0, 100.0)
|> should.equal(Ok(0.5))
maths.logarithm(1.0, 0.25)
|> should.equal(Ok(0.0))
// Check that we get an error when the function is evaluated
// outside its domain
maths.logarithm(1.0, 1.0)
|> should.be_error()
maths.logarithm(10.0, 1.0)
|> should.be_error()
maths.logarithm(-1.0, 1.0)
|> should.be_error()
maths.logarithm(1.0, 10.0)
|> should.equal(Ok(0.0))
maths.logarithm(maths.e(), maths.e())
|> should.equal(Ok(1.0))
maths.logarithm(-1.0, 2.0)
|> should.be_error()
}
pub fn float_logarithm_2_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
maths.logarithm_2(1.0)
|> should.equal(Ok(0.0))
maths.logarithm_2(2.0)
|> should.equal(Ok(1.0))
let assert Ok(result) = maths.logarithm_2(5.0)
result
|> maths.is_close(2.321928, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.logarithm_2(-1.0)
|> should.be_error()
}
pub fn float_logarithm_10_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = maths.logarithm_10(1.0)
result
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = maths.logarithm_10(10.0)
result
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = maths.logarithm_10(50.0)
result
|> maths.is_close(1.69897, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
maths.logarithm_10(-1.0)
|> should.be_error()
}
pub fn float_nth_root_test() {
maths.nth_root(9.0, 2)
|> should.equal(Ok(3.0))
maths.nth_root(27.0, 3)
|> should.equal(Ok(3.0))
maths.nth_root(1.0, 4)
|> should.equal(Ok(1.0))
maths.nth_root(256.0, 4)
|> should.equal(Ok(4.0))
// An error should be returned as an imaginary number would otherwise
// have to be returned
maths.nth_root(-1.0, 4)
|> should.be_error()
}
pub fn float_constants_test() {
let assert Ok(tolerance) = float.power(10.0, -12.0)
// Test that the constant is approximately equal to 2.7128...
maths.e()
|> maths.is_close(2.7182818284590452353602, 0.0, tolerance)
|> should.be_true()
// Test that the constant is approximately equal to 2.7128...
maths.pi()
|> maths.is_close(3.14159265359, 0.0, tolerance)
|> should.be_true()
// Test that the constant is approximately equal to 1.6180...
maths.golden_ratio()
|> maths.is_close(1.618033988749895, 0.0, tolerance)
|> should.be_true()
}

View file

@ -1,227 +0,0 @@
import gleam/option
import gleam_community/maths/arithmetics
import gleeunit/should
pub fn int_gcd_test() {
arithmetics.gcd(1, 1)
|> should.equal(1)
arithmetics.gcd(100, 10)
|> should.equal(10)
arithmetics.gcd(10, 100)
|> should.equal(10)
arithmetics.gcd(100, -10)
|> should.equal(10)
arithmetics.gcd(-36, -17)
|> should.equal(1)
arithmetics.gcd(-30, -42)
|> should.equal(6)
}
pub fn int_euclidean_modulo_test() {
// Base Case: Positive x, Positive y
// Note that the truncated, floored, and euclidean
// definitions should agree for this base case
arithmetics.int_euclidean_modulo(15, 4)
|> should.equal(3)
// Case: Positive x, Negative y
arithmetics.int_euclidean_modulo(15, -4)
|> should.equal(3)
// Case: Negative x, Positive y
arithmetics.int_euclidean_modulo(-15, 4)
|> should.equal(1)
// Case: Negative x, Negative y
arithmetics.int_euclidean_modulo(-15, -4)
|> should.equal(1)
// Case: Positive x, Zero y
arithmetics.int_euclidean_modulo(5, 0)
|> should.equal(0)
// Case: Zero x, Negative y
arithmetics.int_euclidean_modulo(0, 5)
|> should.equal(0)
}
pub fn int_lcm_test() {
arithmetics.lcm(1, 1)
|> should.equal(1)
arithmetics.lcm(100, 10)
|> should.equal(100)
arithmetics.lcm(10, 100)
|> should.equal(100)
arithmetics.lcm(100, -10)
|> should.equal(100)
arithmetics.lcm(-36, -17)
|> should.equal(612)
arithmetics.lcm(-30, -42)
|> should.equal(210)
}
pub fn int_proper_divisors_test() {
arithmetics.proper_divisors(2)
|> should.equal([1])
arithmetics.proper_divisors(6)
|> should.equal([1, 2, 3])
arithmetics.proper_divisors(13)
|> should.equal([1])
arithmetics.proper_divisors(18)
|> should.equal([1, 2, 3, 6, 9])
}
pub fn int_divisors_test() {
arithmetics.divisors(2)
|> should.equal([1, 2])
arithmetics.divisors(6)
|> should.equal([1, 2, 3, 6])
arithmetics.divisors(13)
|> should.equal([1, 13])
arithmetics.divisors(18)
|> should.equal([1, 2, 3, 6, 9, 18])
}
pub fn float_list_sum_test() {
// An empty list returns 0
[]
|> arithmetics.float_sum(option.None)
|> should.equal(0.0)
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_sum(option.None)
|> should.equal(6.0)
[-2.0, 4.0, 6.0]
|> arithmetics.float_sum(option.None)
|> should.equal(8.0)
}
pub fn int_list_sum_test() {
// An empty list returns 0
[]
|> arithmetics.int_sum()
|> should.equal(0)
// Valid input returns a result
[1, 2, 3]
|> arithmetics.int_sum()
|> should.equal(6)
[-2, 4, 6]
|> arithmetics.int_sum()
|> should.equal(8)
}
pub fn float_list_product_test() {
// An empty list returns 0
[]
|> arithmetics.float_product(option.None)
|> should.equal(Ok(1.0))
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_product(option.None)
|> should.equal(Ok(6.0))
[-2.0, 4.0, 6.0]
|> arithmetics.float_product(option.None)
|> should.equal(Ok(-48.0))
}
pub fn int_list_product_test() {
// An empty list returns 0
[]
|> arithmetics.int_product()
|> should.equal(1)
// Valid input returns a result
[1, 2, 3]
|> arithmetics.int_product()
|> should.equal(6)
[-2, 4, 6]
|> arithmetics.int_product()
|> should.equal(-48)
}
pub fn float_list_cumulative_sum_test() {
// An empty lists returns an empty list
[]
|> arithmetics.float_cumulative_sum()
|> should.equal([])
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_cumulative_sum()
|> should.equal([1.0, 3.0, 6.0])
[-2.0, 4.0, 6.0]
|> arithmetics.float_cumulative_sum()
|> should.equal([-2.0, 2.0, 8.0])
}
pub fn int_list_cumulative_sum_test() {
// An empty lists returns an empty list
[]
|> arithmetics.int_cumulative_sum()
|> should.equal([])
// Valid input returns a result
[1, 2, 3]
|> arithmetics.int_cumulative_sum()
|> should.equal([1, 3, 6])
[-2, 4, 6]
|> arithmetics.int_cumulative_sum()
|> should.equal([-2, 2, 8])
}
pub fn float_list_cumulative_product_test() {
// An empty lists returns an empty list
[]
|> arithmetics.float_cumulative_product()
|> should.equal([])
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_cumulative_product()
|> should.equal([1.0, 2.0, 6.0])
[-2.0, 4.0, 6.0]
|> arithmetics.float_cumulative_product()
|> should.equal([-2.0, -8.0, -48.0])
}
pub fn int_list_cumulative_product_test() {
// An empty lists returns an empty list
[]
|> arithmetics.int_cumulative_product()
|> should.equal([])
// Valid input returns a result
[1, 2, 3]
|> arithmetics.int_cumulative_product()
|> should.equal([1, 2, 6])
[-2, 4, 6]
|> arithmetics.int_cumulative_product()
|> should.equal([-2, -8, -48])
}

View file

@ -1,39 +0,0 @@
import gleam_community/maths/conversion
import gleam_community/maths/elementary
import gleam_community/maths/predicates
import gleeunit/should
pub fn float_to_degree_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
conversion.radians_to_degrees(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
conversion.radians_to_degrees(2.0 *. elementary.pi())
|> predicates.is_close(360.0, 0.0, tol)
|> should.be_true()
}
pub fn float_to_radian_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
conversion.degrees_to_radians(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
conversion.degrees_to_radians(360.0)
|> predicates.is_close(2.0 *. elementary.pi(), 0.0, tol)
|> should.be_true()
}
pub fn float_to_int_test() {
conversion.float_to_int(12.0654)
|> should.equal(12)
}
pub fn int_to_float_test() {
conversion.int_to_float(-1)
|> should.equal(-1.0)
conversion.int_to_float(1)
|> should.equal(1.0)
}

View file

@ -1,486 +0,0 @@
import gleam/option
import gleam_community/maths/elementary
import gleam_community/maths/predicates
import gleeunit/should
pub fn float_acos_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = elementary.acos(1.0)
result
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = elementary.acos(0.5)
result
|> predicates.is_close(1.047197, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.acos(1.1)
|> should.be_error()
elementary.acos(-1.1)
|> should.be_error()
}
pub fn float_acosh_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = elementary.acosh(1.0)
result
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.acosh(0.0)
|> should.be_error()
}
pub fn float_asin_test() {
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.asin(0.0)
|> should.equal(Ok(0.0))
let assert Ok(tol) = elementary.power(10.0, -6.0)
let assert Ok(result) = elementary.asin(0.5)
result
|> predicates.is_close(0.523598, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.asin(1.1)
|> should.be_error()
elementary.asin(-1.1)
|> should.be_error()
}
pub fn float_asinh_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.asinh(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.asinh(0.5)
|> predicates.is_close(0.481211, 0.0, tol)
|> should.be_true()
}
pub fn float_atan_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.atan(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.atan(0.5)
|> predicates.is_close(0.463647, 0.0, tol)
|> should.be_true()
}
pub fn math_atan2_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.atan2(0.0, 0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.atan2(0.0, 1.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
// Check atan2(y=1.0, x=0.5)
// Should be equal to atan(y / x) for any x > 0 and any y
let result = elementary.atan(1.0 /. 0.5)
elementary.atan2(1.0, 0.5)
|> predicates.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=2.0, x=-1.5)
// Should be equal to pi + atan(y / x) for any x < 0 and y >= 0
let result = elementary.pi() +. elementary.atan(2.0 /. -1.5)
elementary.atan2(2.0, -1.5)
|> predicates.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=-2.0, x=-1.5)
// Should be equal to atan(y / x) - pi for any x < 0 and y < 0
let result = elementary.atan(-2.0 /. -1.5) -. elementary.pi()
elementary.atan2(-2.0, -1.5)
|> predicates.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=1.5, x=0.0)
// Should be equal to pi/2 for x = 0 and any y > 0
let result = elementary.pi() /. 2.0
elementary.atan2(1.5, 0.0)
|> predicates.is_close(result, 0.0, tol)
|> should.be_true()
// Check atan2(y=-1.5, x=0.0)
// Should be equal to -pi/2 for x = 0 and any y < 0
let result = -1.0 *. elementary.pi() /. 2.0
elementary.atan2(-1.5, 0.0)
|> predicates.is_close(result, 0.0, tol)
|> should.be_true()
}
pub fn float_atanh_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = elementary.atanh(0.0)
result
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = elementary.atanh(0.5)
result
|> predicates.is_close(0.549306, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.atanh(1.0)
|> should.be_error()
elementary.atanh(2.0)
|> should.be_error()
elementary.atanh(1.0)
|> should.be_error()
elementary.atanh(-2.0)
|> should.be_error()
}
pub fn float_cos_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.cos(0.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
elementary.cos(elementary.pi())
|> predicates.is_close(-1.0, 0.0, tol)
|> should.be_true()
elementary.cos(0.5)
|> predicates.is_close(0.877582, 0.0, tol)
|> should.be_true()
}
pub fn float_cosh_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.cosh(0.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
elementary.cosh(0.5)
|> predicates.is_close(1.127625, 0.0, tol)
|> should.be_true()
// An (overflow) error might occur when given an input
// value that will result in a too large output value
// e.g. elementary.cosh(1000.0) but this is a property of the
// runtime.
}
pub fn float_sin_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.sin(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.sin(0.5 *. elementary.pi())
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
elementary.sin(0.5)
|> predicates.is_close(0.479425, 0.0, tol)
|> should.be_true()
}
pub fn float_sinh_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.sinh(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.sinh(0.5)
|> predicates.is_close(0.521095, 0.0, tol)
|> should.be_true()
// An (overflow) error might occur when given an input
// value that will result in a too large output value
// e.g. elementary.sinh(1000.0) but this is a property of the
// runtime.
}
pub fn math_tan_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.tan(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.tan(0.5)
|> predicates.is_close(0.546302, 0.0, tol)
|> should.be_true()
}
pub fn math_tanh_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.tanh(0.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
elementary.tanh(25.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
elementary.tanh(-25.0)
|> predicates.is_close(-1.0, 0.0, tol)
|> should.be_true()
elementary.tanh(0.5)
|> predicates.is_close(0.462117, 0.0, tol)
|> should.be_true()
}
pub fn float_exponential_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.exponential(0.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
elementary.exponential(0.5)
|> predicates.is_close(1.648721, 0.0, tol)
|> should.be_true()
// An (overflow) error might occur when given an input
// value that will result in a too large output value
// e.g. elementary.exponential(1000.0) but this is a property of the
// runtime.
}
pub fn float_natural_logarithm_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.natural_logarithm(1.0)
|> should.equal(Ok(0.0))
let assert Ok(result) = elementary.natural_logarithm(0.5)
result
|> predicates.is_close(-0.693147, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.natural_logarithm(-1.0)
|> should.be_error()
}
pub fn float_logarithm_test() {
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.logarithm(10.0, option.Some(10.0))
|> should.equal(Ok(1.0))
elementary.logarithm(10.0, option.Some(100.0))
|> should.equal(Ok(0.5))
elementary.logarithm(1.0, option.Some(0.25))
|> should.equal(Ok(0.0))
// Check that we get an error when the function is evaluated
// outside its domain
elementary.logarithm(1.0, option.Some(1.0))
|> should.be_error()
elementary.logarithm(10.0, option.Some(1.0))
|> should.be_error()
elementary.logarithm(-1.0, option.Some(1.0))
|> should.be_error()
elementary.logarithm(1.0, option.Some(10.0))
|> should.equal(Ok(0.0))
elementary.logarithm(elementary.e(), option.Some(elementary.e()))
|> should.equal(Ok(1.0))
elementary.logarithm(-1.0, option.Some(2.0))
|> should.be_error()
}
pub fn float_logarithm_2_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
elementary.logarithm_2(1.0)
|> should.equal(Ok(0.0))
elementary.logarithm_2(2.0)
|> should.equal(Ok(1.0))
let assert Ok(result) = elementary.logarithm_2(5.0)
result
|> predicates.is_close(2.321928, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.logarithm_2(-1.0)
|> should.be_error()
}
pub fn float_logarithm_10_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) = elementary.logarithm_10(1.0)
result
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = elementary.logarithm_10(10.0)
result
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) = elementary.logarithm_10(50.0)
result
|> predicates.is_close(1.69897, 0.0, tol)
|> should.be_true()
// Check that we get an error when the function is evaluated
// outside its domain
elementary.logarithm_10(-1.0)
|> should.be_error()
}
pub fn float_power_test() {
elementary.power(2.0, 2.0)
|> should.equal(Ok(4.0))
elementary.power(-5.0, 3.0)
|> should.equal(Ok(-125.0))
elementary.power(10.5, 0.0)
|> should.equal(Ok(1.0))
elementary.power(16.0, 0.5)
|> should.equal(Ok(4.0))
elementary.power(2.0, -1.0)
|> should.equal(Ok(0.5))
elementary.power(2.0, -1.0)
|> should.equal(Ok(0.5))
// When y = 0, the result should universally be 1, regardless of the value of x
elementary.power(10.0, 0.0)
|> should.equal(Ok(1.0))
elementary.power(-10.0, 0.0)
|> should.equal(Ok(1.0))
elementary.power(2.0, -1.0)
|> should.equal(Ok(0.5))
// elementary.power(-1.0, 0.5) is equivalent to float.square_root(-1.0)
// and should return an error as an imaginary number would otherwise
// have to be returned
elementary.power(-1.0, 0.5)
|> should.be_error()
// Check another case with a negative base and fractional exponent
elementary.power(-1.5, 1.5)
|> should.be_error()
// elementary.power(0.0, -1.0) is equivalent to 1. /. 0 and is expected
// to be an error
elementary.power(0.0, -1.0)
|> should.be_error()
// Check that a negative base and exponent is fine as long as the
// exponent is not fractional
elementary.power(-2.0, -1.0)
|> should.equal(Ok(-0.5))
}
pub fn float_square_root_test() {
elementary.square_root(1.0)
|> should.equal(Ok(1.0))
elementary.square_root(9.0)
|> should.equal(Ok(3.0))
// An error should be returned as an imaginary number would otherwise
// have to be returned
elementary.square_root(-1.0)
|> should.be_error()
}
pub fn float_cube_root_test() {
elementary.cube_root(1.0)
|> should.equal(Ok(1.0))
elementary.cube_root(27.0)
|> should.equal(Ok(3.0))
// An error should be returned as an imaginary number would otherwise
// have to be returned
elementary.cube_root(-1.0)
|> should.be_error()
}
pub fn float_nth_root_test() {
elementary.nth_root(9.0, 2)
|> should.equal(Ok(3.0))
elementary.nth_root(27.0, 3)
|> should.equal(Ok(3.0))
elementary.nth_root(1.0, 4)
|> should.equal(Ok(1.0))
elementary.nth_root(256.0, 4)
|> should.equal(Ok(4.0))
// An error should be returned as an imaginary number would otherwise
// have to be returned
elementary.nth_root(-1.0, 4)
|> should.be_error()
}
pub fn float_constants_test() {
elementary.e()
|> predicates.is_close(2.71828, 0.0, 0.00001)
|> should.be_true()
elementary.pi()
|> predicates.is_close(3.14159, 0.0, 0.00001)
|> should.be_true()
}

View file

@ -1,629 +0,0 @@
import gleam/option
import gleam/set
import gleam_community/maths/elementary
import gleam_community/maths/metrics
import gleam_community/maths/predicates
import gleeunit/should
pub fn float_list_norm_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// An empty lists returns 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, 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, 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, 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, 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, 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, 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, option.None)
result
|> predicates.is_close(3.7416573867739413, 0.0, tol)
|> should.be_true()
}
pub fn float_list_manhattan_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Empty lists returns an error
metrics.manhattan_distance([], [], option.None)
|> should.be_error()
// Differing lengths returns error
metrics.manhattan_distance([], [1.0], option.None)
|> should.be_error()
// 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() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Empty lists returns an error
metrics.minkowski_distance([], [], 1.0, option.None)
|> should.be_error()
// Differing lengths returns error
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, option.None)
|> should.be_error()
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) =
metrics.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0, option.None)
result
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0, option.None)
result
|> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0, option.None)
result
|> predicates.is_close(1.0069555500567189, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0, option.None)
result
|> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true()
// Euclidean distance (p = 2)
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0, option.None)
result
|> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true()
// Manhattan distance (p = 1)
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()
// 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() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Empty lists returns an error
metrics.euclidean_distance([], [], option.None)
|> should.be_error()
// Differing lengths returns error
metrics.euclidean_distance([], [1.0], option.None)
|> should.be_error()
// 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() {
// An empty list returns an error
[]
|> metrics.mean()
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> metrics.mean()
|> should.equal(Ok(2.0))
}
pub fn median_test() {
// An empty list returns an error
[]
|> metrics.median()
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> metrics.median()
|> should.equal(Ok(2.0))
[1.0, 2.0, 3.0, 4.0]
|> metrics.median()
|> should.equal(Ok(2.5))
}
pub fn variance_test() {
// Degrees of freedom
let ddof = 1
// An empty list returns an error
[]
|> metrics.variance(ddof)
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> metrics.variance(ddof)
|> should.equal(Ok(1.0))
}
pub fn standard_deviation_test() {
// Degrees of freedom
let ddof = 1
// An empty list returns an error
[]
|> metrics.standard_deviation(ddof)
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> metrics.standard_deviation(ddof)
|> should.equal(Ok(1.0))
}
pub fn jaccard_index_test() {
metrics.jaccard_index(set.from_list([]), set.from_list([]))
|> should.equal(0.0)
let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9])
let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9])
metrics.jaccard_index(set_a, set_b)
|> should.equal(4.0 /. 10.0)
let set_c = set.from_list([0, 1, 2, 3, 4, 5])
let set_d = set.from_list([6, 7, 8, 9, 10])
metrics.jaccard_index(set_c, set_d)
|> should.equal(0.0 /. 11.0)
let set_e = set.from_list(["cat", "dog", "hippo", "monkey"])
let set_f = set.from_list(["monkey", "rhino", "ostrich", "salmon"])
metrics.jaccard_index(set_e, set_f)
|> should.equal(1.0 /. 7.0)
}
pub fn sorensen_dice_coefficient_test() {
metrics.sorensen_dice_coefficient(set.from_list([]), set.from_list([]))
|> should.equal(0.0)
let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9])
let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9])
metrics.sorensen_dice_coefficient(set_a, set_b)
|> should.equal(2.0 *. 4.0 /. { 7.0 +. 7.0 })
let set_c = set.from_list([0, 1, 2, 3, 4, 5])
let set_d = set.from_list([6, 7, 8, 9, 10])
metrics.sorensen_dice_coefficient(set_c, set_d)
|> should.equal(2.0 *. 0.0 /. { 6.0 +. 5.0 })
let set_e = set.from_list(["cat", "dog", "hippo", "monkey"])
let set_f = set.from_list(["monkey", "rhino", "ostrich", "salmon", "spider"])
metrics.sorensen_dice_coefficient(set_e, set_f)
|> should.equal(2.0 *. 1.0 /. { 4.0 +. 5.0 })
}
pub fn overlap_coefficient_test() {
metrics.overlap_coefficient(set.from_list([]), set.from_list([]))
|> should.equal(0.0)
let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9])
let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9])
metrics.overlap_coefficient(set_a, set_b)
|> should.equal(4.0 /. 7.0)
let set_c = set.from_list([0, 1, 2, 3, 4, 5])
let set_d = set.from_list([6, 7, 8, 9, 10])
metrics.overlap_coefficient(set_c, set_d)
|> should.equal(0.0 /. 5.0)
let set_e = set.from_list(["horse", "dog", "hippo", "monkey", "bird"])
let set_f = set.from_list(["monkey", "bird", "ostrich", "salmon"])
metrics.overlap_coefficient(set_e, set_f)
|> should.equal(2.0 /. 4.0)
}
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()
// One empty list returns an error
metrics.cosine_similarity([1.0, 2.0, 3.0], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.cosine_similarity([], [1.0, 2.0, 3.0], option.None)
|> should.be_error()
// Different sized lists returns an error
metrics.cosine_similarity([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
|> should.be_error()
// Two orthogonal vectors (represented by lists)
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 (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))
// 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([], [])
|> should.be_error()
// One empty list returns an error
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])
|> should.be_error()
// Different sized lists returns an error
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])
|> should.equal(Ok(2.0))
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])
|> should.equal(Ok(3.0))
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])
|> should.equal(Ok(0.0))
}
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()
}

View file

@ -1,753 +0,0 @@
import gleam/float
import gleam/int
import gleam/option
import gleam_community/maths/piecewise
import gleeunit/should
pub fn float_ceiling_test() {
// Round 3. digit AFTER decimal point
piecewise.ceiling(12.0654, option.Some(3))
|> should.equal(12.066)
// Round 2. digit AFTER decimal point
piecewise.ceiling(12.0654, option.Some(2))
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
piecewise.ceiling(12.0654, option.Some(1))
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
piecewise.ceiling(12.0654, option.Some(0))
|> should.equal(13.0)
// Round 1. digit BEFORE decimal point
piecewise.ceiling(12.0654, option.Some(-1))
|> should.equal(20.0)
// Round 2. digit BEFORE decimal point
piecewise.ceiling(12.0654, option.Some(-2))
|> should.equal(100.0)
// Round 3. digit BEFORE decimal point
piecewise.ceiling(12.0654, option.Some(-3))
|> should.equal(1000.0)
}
pub fn float_floor_test() {
// Round 3. digit AFTER decimal point
piecewise.floor(12.0654, option.Some(3))
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
piecewise.floor(12.0654, option.Some(2))
|> should.equal(12.06)
// Round 1. digit AFTER decimal point
piecewise.floor(12.0654, option.Some(1))
|> should.equal(12.0)
// Round 0. digit BEFORE decimal point
piecewise.floor(12.0654, option.Some(0))
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
piecewise.floor(12.0654, option.Some(-1))
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
piecewise.floor(12.0654, option.Some(-2))
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
piecewise.floor(12.0654, option.Some(-3))
|> should.equal(0.0)
}
pub fn float_truncate_test() {
// Round 3. digit AFTER decimal point
piecewise.truncate(12.0654, option.Some(3))
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
piecewise.truncate(12.0654, option.Some(2))
|> should.equal(12.06)
// Round 1. digit AFTER decimal point
piecewise.truncate(12.0654, option.Some(1))
|> should.equal(12.0)
// Round 0. digit BEFORE decimal point
piecewise.truncate(12.0654, option.Some(0))
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
piecewise.truncate(12.0654, option.Some(-1))
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
piecewise.truncate(12.0654, option.Some(-2))
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
piecewise.truncate(12.0654, option.Some(-3))
|> should.equal(0.0)
}
pub fn math_round_to_nearest_test() {
// Try with positive values
piecewise.round(1.5, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(2.0)
piecewise.round(1.75, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(2.0)
piecewise.round(2.0, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(2.0)
piecewise.round(3.5, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(4.0)
piecewise.round(4.5, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(4.0)
// Try with negative values
piecewise.round(-3.5, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(-4.0)
piecewise.round(-4.5, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(-4.0)
// Round 3. digit AFTER decimal point
piecewise.round(12.0654, option.Some(3), option.Some(piecewise.RoundNearest))
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundNearest))
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
piecewise.round(12.0654, option.Some(1), option.Some(piecewise.RoundNearest))
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundNearest))
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(-1), option.Some(piecewise.RoundNearest))
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(-2), option.Some(piecewise.RoundNearest))
|> should.equal(0.0)
// Round 3. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(-3), option.Some(piecewise.RoundNearest))
|> should.equal(0.0)
}
pub fn math_round_up_test() {
// Note: Rounding mode "RoundUp" is an alias for the ceiling function
// Try with positive values
piecewise.round(0.45, option.Some(0), option.Some(piecewise.RoundUp))
|> should.equal(1.0)
piecewise.round(0.5, option.Some(0), option.Some(piecewise.RoundUp))
|> should.equal(1.0)
piecewise.round(0.45, option.Some(1), option.Some(piecewise.RoundUp))
|> should.equal(0.5)
piecewise.round(0.5, option.Some(1), option.Some(piecewise.RoundUp))
|> should.equal(0.5)
piecewise.round(0.455, option.Some(2), option.Some(piecewise.RoundUp))
|> should.equal(0.46)
piecewise.round(0.505, option.Some(2), option.Some(piecewise.RoundUp))
|> should.equal(0.51)
// Try with negative values
piecewise.round(-0.45, option.Some(0), option.Some(piecewise.RoundUp))
|> should.equal(-0.0)
piecewise.round(-0.5, option.Some(0), option.Some(piecewise.RoundUp))
|> should.equal(-0.0)
piecewise.round(-0.45, option.Some(1), option.Some(piecewise.RoundUp))
|> should.equal(-0.4)
piecewise.round(-0.5, option.Some(1), option.Some(piecewise.RoundUp))
|> should.equal(-0.5)
piecewise.round(-0.455, option.Some(2), option.Some(piecewise.RoundUp))
|> should.equal(-0.45)
piecewise.round(-0.505, option.Some(2), option.Some(piecewise.RoundUp))
|> should.equal(-0.5)
}
pub fn math_round_down_test() {
// Note: Rounding mode "RoundDown" is an alias for the floor function
// Try with positive values
piecewise.round(0.45, option.Some(0), option.Some(piecewise.RoundDown))
|> should.equal(0.0)
piecewise.round(0.5, option.Some(0), option.Some(piecewise.RoundDown))
|> should.equal(0.0)
piecewise.round(0.45, option.Some(1), option.Some(piecewise.RoundDown))
|> should.equal(0.4)
piecewise.round(0.5, option.Some(1), option.Some(piecewise.RoundDown))
|> should.equal(0.5)
piecewise.round(0.455, option.Some(2), option.Some(piecewise.RoundDown))
|> should.equal(0.45)
piecewise.round(0.505, option.Some(2), option.Some(piecewise.RoundDown))
|> should.equal(0.5)
// Try with negative values
piecewise.round(-0.45, option.Some(0), option.Some(piecewise.RoundDown))
|> should.equal(-1.0)
piecewise.round(-0.5, option.Some(0), option.Some(piecewise.RoundDown))
|> should.equal(-1.0)
piecewise.round(-0.45, option.Some(1), option.Some(piecewise.RoundDown))
|> should.equal(-0.5)
piecewise.round(-0.5, option.Some(1), option.Some(piecewise.RoundDown))
|> should.equal(-0.5)
piecewise.round(-0.455, option.Some(2), option.Some(piecewise.RoundDown))
|> should.equal(-0.46)
piecewise.round(-0.505, option.Some(2), option.Some(piecewise.RoundDown))
|> should.equal(-0.51)
}
pub fn math_round_to_zero_test() {
// Note: Rounding mode "RoundToZero" is an alias for the truncate function
// Try with positive values
piecewise.round(0.5, option.Some(0), option.Some(piecewise.RoundToZero))
|> should.equal(0.0)
piecewise.round(0.75, option.Some(0), option.Some(piecewise.RoundToZero))
|> should.equal(0.0)
piecewise.round(0.45, option.Some(1), option.Some(piecewise.RoundToZero))
|> should.equal(0.4)
piecewise.round(0.57, option.Some(1), option.Some(piecewise.RoundToZero))
|> should.equal(0.5)
piecewise.round(0.4575, option.Some(2), option.Some(piecewise.RoundToZero))
|> should.equal(0.45)
piecewise.round(0.5075, option.Some(2), option.Some(piecewise.RoundToZero))
|> should.equal(0.5)
// Try with negative values
piecewise.round(-0.5, option.Some(0), option.Some(piecewise.RoundToZero))
|> should.equal(0.0)
piecewise.round(-0.75, option.Some(0), option.Some(piecewise.RoundToZero))
|> should.equal(0.0)
piecewise.round(-0.45, option.Some(1), option.Some(piecewise.RoundToZero))
|> should.equal(-0.4)
piecewise.round(-0.57, option.Some(1), option.Some(piecewise.RoundToZero))
|> should.equal(-0.5)
piecewise.round(-0.4575, option.Some(2), option.Some(piecewise.RoundToZero))
|> should.equal(-0.45)
piecewise.round(-0.5075, option.Some(2), option.Some(piecewise.RoundToZero))
|> should.equal(-0.5)
}
pub fn math_round_ties_away_test() {
// Try with positive values
piecewise.round(1.4, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(1.0)
piecewise.round(1.5, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(2.0)
piecewise.round(2.5, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(3.0)
// Try with negative values
piecewise.round(-1.4, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(-1.0)
piecewise.round(-1.5, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(-2.0)
piecewise.round(-2.0, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(-2.0)
piecewise.round(-2.5, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(-3.0)
// Round 3. digit AFTER decimal point
piecewise.round(12.0654, option.Some(3), option.Some(piecewise.RoundTiesAway))
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundTiesAway))
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
piecewise.round(12.0654, option.Some(1), option.Some(piecewise.RoundTiesAway))
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundTiesAway))
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
piecewise.round(
12.0654,
option.Some(-1),
option.Some(piecewise.RoundTiesAway),
)
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
piecewise.round(
12.0654,
option.Some(-2),
option.Some(piecewise.RoundTiesAway),
)
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
piecewise.round(
12.0654,
option.Some(-3),
option.Some(piecewise.RoundTiesAway),
)
|> should.equal(0.0)
}
pub fn math_round_ties_up_test() {
// Try with positive values
piecewise.round(1.4, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(1.0)
piecewise.round(1.5, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(2.0)
piecewise.round(2.5, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(3.0)
// Try with negative values
piecewise.round(-1.4, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(-1.0)
piecewise.round(-1.5, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(-1.0)
piecewise.round(-2.0, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(-2.0)
piecewise.round(-2.5, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(-2.0)
// Round 3. digit AFTER decimal point
piecewise.round(12.0654, option.Some(3), option.Some(piecewise.RoundTiesUp))
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundTiesUp))
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
piecewise.round(12.0654, option.Some(1), option.Some(piecewise.RoundTiesUp))
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundTiesUp))
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(-1), option.Some(piecewise.RoundTiesUp))
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(-2), option.Some(piecewise.RoundTiesUp))
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
piecewise.round(12.0654, option.Some(-3), option.Some(piecewise.RoundTiesUp))
|> should.equal(0.0)
}
pub fn math_round_edge_cases_test() {
// The default number of digits is 0 if None is provided
piecewise.round(12.0654, option.None, option.Some(piecewise.RoundNearest))
|> should.equal(12.0)
// The default rounding mode is piecewise.RoundNearest if None is provided
piecewise.round(12.0654, option.None, option.None)
|> should.equal(12.0)
}
pub fn float_absolute_value_test() {
piecewise.float_absolute_value(20.0)
|> should.equal(20.0)
piecewise.float_absolute_value(-20.0)
|> should.equal(20.0)
}
pub fn int_absolute_value_test() {
piecewise.int_absolute_value(20)
|> should.equal(20)
piecewise.int_absolute_value(-20)
|> should.equal(20)
}
pub fn float_absolute_difference_test() {
piecewise.float_absolute_difference(20.0, 15.0)
|> should.equal(5.0)
piecewise.float_absolute_difference(-20.0, -15.0)
|> should.equal(5.0)
piecewise.float_absolute_difference(20.0, -15.0)
|> should.equal(35.0)
piecewise.float_absolute_difference(-20.0, 15.0)
|> should.equal(35.0)
piecewise.float_absolute_difference(0.0, 0.0)
|> should.equal(0.0)
piecewise.float_absolute_difference(1.0, 2.0)
|> should.equal(1.0)
piecewise.float_absolute_difference(2.0, 1.0)
|> should.equal(1.0)
piecewise.float_absolute_difference(-1.0, 0.0)
|> should.equal(1.0)
piecewise.float_absolute_difference(0.0, -1.0)
|> should.equal(1.0)
piecewise.float_absolute_difference(10.0, 20.0)
|> should.equal(10.0)
piecewise.float_absolute_difference(-10.0, -20.0)
|> should.equal(10.0)
piecewise.float_absolute_difference(-10.5, 10.5)
|> should.equal(21.0)
}
pub fn int_absolute_difference_test() {
piecewise.int_absolute_difference(20, 15)
|> should.equal(5)
piecewise.int_absolute_difference(-20, -15)
|> should.equal(5)
piecewise.int_absolute_difference(20, -15)
|> should.equal(35)
piecewise.int_absolute_difference(-20, 15)
|> should.equal(35)
}
pub fn float_sign_test() {
piecewise.float_sign(100.0)
|> should.equal(1.0)
piecewise.float_sign(0.0)
|> should.equal(0.0)
piecewise.float_sign(-100.0)
|> should.equal(-1.0)
}
pub fn float_flip_sign_test() {
piecewise.float_flip_sign(100.0)
|> should.equal(-100.0)
piecewise.float_flip_sign(0.0)
|> should.equal(-0.0)
piecewise.float_flip_sign(-100.0)
|> should.equal(100.0)
}
pub fn float_copy_sign_test() {
piecewise.float_copy_sign(100.0, 10.0)
|> should.equal(100.0)
piecewise.float_copy_sign(-100.0, 10.0)
|> should.equal(100.0)
piecewise.float_copy_sign(100.0, -10.0)
|> should.equal(-100.0)
piecewise.float_copy_sign(-100.0, -10.0)
|> should.equal(-100.0)
}
pub fn int_sign_test() {
piecewise.int_sign(100)
|> should.equal(1)
piecewise.int_sign(0)
|> should.equal(0)
piecewise.int_sign(-100)
|> should.equal(-1)
}
pub fn int_flip_sign_test() {
piecewise.int_flip_sign(100)
|> should.equal(-100)
piecewise.int_flip_sign(0)
|> should.equal(-0)
piecewise.int_flip_sign(-100)
|> should.equal(100)
}
pub fn int_copy_sign_test() {
piecewise.int_copy_sign(100, 10)
|> should.equal(100)
piecewise.int_copy_sign(-100, 10)
|> should.equal(100)
piecewise.int_copy_sign(100, -10)
|> should.equal(-100)
piecewise.int_copy_sign(-100, -10)
|> should.equal(-100)
}
pub fn float_minimum_test() {
piecewise.minimum(0.75, 0.5, float.compare)
|> should.equal(0.5)
piecewise.minimum(0.5, 0.75, float.compare)
|> should.equal(0.5)
piecewise.minimum(-0.75, 0.5, float.compare)
|> should.equal(-0.75)
piecewise.minimum(-0.75, 0.5, float.compare)
|> should.equal(-0.75)
}
pub fn int_minimum_test() {
piecewise.minimum(75, 50, int.compare)
|> should.equal(50)
piecewise.minimum(50, 75, int.compare)
|> should.equal(50)
piecewise.minimum(-75, 50, int.compare)
|> should.equal(-75)
piecewise.minimum(-75, 50, int.compare)
|> should.equal(-75)
}
pub fn float_maximum_test() {
piecewise.maximum(0.75, 0.5, float.compare)
|> should.equal(0.75)
piecewise.maximum(0.5, 0.75, float.compare)
|> should.equal(0.75)
piecewise.maximum(-0.75, 0.5, float.compare)
|> should.equal(0.5)
piecewise.maximum(-0.75, 0.5, float.compare)
|> should.equal(0.5)
}
pub fn int_maximum_test() {
piecewise.maximum(75, 50, int.compare)
|> should.equal(75)
piecewise.maximum(50, 75, int.compare)
|> should.equal(75)
piecewise.maximum(-75, 50, int.compare)
|> should.equal(50)
piecewise.maximum(-75, 50, int.compare)
|> should.equal(50)
}
pub fn float_minmax_test() {
piecewise.minmax(0.75, 0.5, float.compare)
|> should.equal(#(0.5, 0.75))
piecewise.minmax(0.5, 0.75, float.compare)
|> should.equal(#(0.5, 0.75))
piecewise.minmax(-0.75, 0.5, float.compare)
|> should.equal(#(-0.75, 0.5))
piecewise.minmax(-0.75, 0.5, float.compare)
|> should.equal(#(-0.75, 0.5))
}
pub fn int_minmax_test() {
piecewise.minmax(75, 50, int.compare)
|> should.equal(#(50, 75))
piecewise.minmax(50, 75, int.compare)
|> should.equal(#(50, 75))
piecewise.minmax(-75, 50, int.compare)
|> should.equal(#(-75, 50))
piecewise.minmax(-75, 50, int.compare)
|> should.equal(#(-75, 50))
}
pub fn float_list_minimum_test() {
// An empty lists returns an error
[]
|> piecewise.list_minimum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> piecewise.list_minimum(float.compare)
|> should.equal(Ok(1.5))
}
pub fn int_list_minimum_test() {
// An empty lists returns an error
[]
|> piecewise.list_minimum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> piecewise.list_minimum(int.compare)
|> should.equal(Ok(1))
}
pub fn float_list_maximum_test() {
// An empty lists returns an error
[]
|> piecewise.list_maximum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> piecewise.list_maximum(float.compare)
|> should.equal(Ok(4.5))
}
pub fn int_list_maximum_test() {
// An empty lists returns an error
[]
|> piecewise.list_maximum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> piecewise.list_maximum(int.compare)
|> should.equal(Ok(4))
}
pub fn float_list_arg_maximum_test() {
// An empty lists returns an error
[]
|> piecewise.arg_maximum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> piecewise.arg_maximum(float.compare)
|> should.equal(Ok([0, 1]))
}
pub fn int_list_arg_maximum_test() {
// An empty lists returns an error
[]
|> piecewise.arg_maximum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> piecewise.arg_maximum(int.compare)
|> should.equal(Ok([0, 1]))
}
pub fn float_list_arg_minimum_test() {
// An empty lists returns an error
[]
|> piecewise.arg_minimum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> piecewise.arg_minimum(float.compare)
|> should.equal(Ok([4]))
}
pub fn int_list_arg_minimum_test() {
// An empty lists returns an error
[]
|> piecewise.arg_minimum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> piecewise.arg_minimum(int.compare)
|> should.equal(Ok([4]))
}
pub fn float_list_extrema_test() {
// An empty lists returns an error
[]
|> piecewise.extrema(float.compare)
|> should.be_error()
// Valid input returns a result
[4.0, 4.0, 3.0, 2.0, 1.0]
|> piecewise.extrema(float.compare)
|> should.equal(Ok(#(1.0, 4.0)))
// Valid input returns a result
[1.0, 4.0, 2.0, 5.0, 0.0]
|> piecewise.extrema(float.compare)
|> should.equal(Ok(#(0.0, 5.0)))
}
pub fn int_list_extrema_test() {
// An empty lists returns an error
[]
|> piecewise.extrema(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> piecewise.extrema(int.compare)
|> should.equal(Ok(#(1, 4)))
// Valid input returns a result
[1, 4, 2, 5, 0]
|> piecewise.extrema(int.compare)
|> should.equal(Ok(#(0, 5)))
}

View file

@ -1,203 +0,0 @@
import gleam/list
import gleam_community/maths/predicates
import gleeunit/should
pub fn float_is_close_test() {
let val = 99.0
let ref_val = 100.0
// We set 'atol' and 'rtol' such that the values are equivalent
// if 'val' is within 1 percent of 'ref_val' +/- 0.1
let rtol = 0.01
let atol = 0.1
predicates.is_close(val, ref_val, rtol, atol)
|> should.be_true()
}
pub fn float_list_all_close_test() {
let val = 99.0
let ref_val = 100.0
let xarr = list.repeat(val, 42)
let yarr = list.repeat(ref_val, 42)
// We set 'atol' and 'rtol' such that the values are equivalent
// if 'val' is within 1 percent of 'ref_val' +/- 0.1
let rtol = 0.01
let atol = 0.1
predicates.all_close(xarr, yarr, rtol, atol)
|> fn(zarr: Result(List(Bool), Nil)) -> Result(Bool, Nil) {
case zarr {
Ok(arr) ->
arr
|> list.all(fn(a) { a })
|> Ok
_ ->
Nil
|> Error
}
}
|> should.equal(Ok(True))
}
pub fn float_is_fractional_test() {
predicates.is_fractional(1.5)
|> should.equal(True)
predicates.is_fractional(0.5)
|> should.equal(True)
predicates.is_fractional(0.3333)
|> should.equal(True)
predicates.is_fractional(0.9999)
|> should.equal(True)
predicates.is_fractional(1.0)
|> should.equal(False)
predicates.is_fractional(999.0)
|> should.equal(False)
}
pub fn int_is_power_test() {
predicates.is_power(10, 10)
|> should.equal(True)
predicates.is_power(11, 10)
|> should.equal(False)
predicates.is_power(4, 2)
|> should.equal(True)
predicates.is_power(5, 2)
|> should.equal(False)
predicates.is_power(27, 3)
|> should.equal(True)
predicates.is_power(28, 3)
|> should.equal(False)
}
pub fn int_is_even_test() {
predicates.is_even(0)
|> should.equal(True)
predicates.is_even(2)
|> should.equal(True)
predicates.is_even(12)
|> should.equal(True)
predicates.is_even(5)
|> should.equal(False)
predicates.is_even(-3)
|> should.equal(False)
predicates.is_even(-4)
|> should.equal(True)
}
pub fn int_is_odd_test() {
predicates.is_odd(0)
|> should.equal(False)
predicates.is_odd(3)
|> should.equal(True)
predicates.is_odd(13)
|> should.equal(True)
predicates.is_odd(4)
|> should.equal(False)
predicates.is_odd(-3)
|> should.equal(True)
predicates.is_odd(-4)
|> should.equal(False)
}
pub fn int_is_perfect_test() {
predicates.is_perfect(6)
|> should.equal(True)
predicates.is_perfect(28)
|> should.equal(True)
predicates.is_perfect(496)
|> should.equal(True)
predicates.is_perfect(1)
|> should.equal(False)
predicates.is_perfect(3)
|> should.equal(False)
predicates.is_perfect(13)
|> should.equal(False)
}
pub fn int_is_prime_test() {
// Test a negative integer, i.e., not a natural number
predicates.is_prime(-7)
|> should.equal(False)
predicates.is_prime(1)
|> should.equal(False)
predicates.is_prime(2)
|> should.equal(True)
predicates.is_prime(3)
|> should.equal(True)
predicates.is_prime(5)
|> should.equal(True)
predicates.is_prime(7)
|> should.equal(True)
predicates.is_prime(11)
|> should.equal(True)
predicates.is_prime(42)
|> should.equal(False)
predicates.is_prime(7919)
|> should.equal(True)
// Test 1st Carmichael number
predicates.is_prime(561)
|> should.equal(False)
// Test 2nd Carmichael number
predicates.is_prime(1105)
|> should.equal(False)
}
pub fn is_between_test() {
predicates.is_between(5.5, 5.0, 6.0)
|> should.equal(True)
predicates.is_between(5.0, 5.0, 6.0)
|> should.equal(False)
predicates.is_between(6.0, 5.0, 6.0)
|> should.equal(False)
}
pub fn is_divisible_test() {
predicates.is_divisible(10, 2)
|> should.equal(True)
predicates.is_divisible(7, 3)
|> should.equal(False)
}
pub fn is_multiple_test() {
predicates.is_multiple(15, 5)
|> should.equal(True)
predicates.is_multiple(14, 5)
|> should.equal(False)
}

View file

@ -1,328 +0,0 @@
import gleam/iterator
import gleam/list
import gleam_community/maths/elementary
import gleam_community/maths/predicates
import gleam_community/maths/sequences
import gleeunit/should
pub fn float_list_linear_space_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, True)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[10.0, 20.0, 30.0, 40.0, 50.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, True)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[10.0, 12.5, 15.0, 17.5, 20.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Try with negative stop
// ----> Without endpoint included
let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, False)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[10.0, 18.0, 26.0, 34.0, 42.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, False)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[10.0, 12.0, 14.0, 16.0, 18.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Try with negative stop
let assert Ok(linspace) = sequences.linear_space(10.0, -50.0, 5, False)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[10.0, -2.0, -14.0, -26.0, -38.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = sequences.linear_space(10.0, -20.0, 5, True)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[10.0, 2.5, -5.0, -12.5, -20.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Try with negative start
let assert Ok(linspace) = sequences.linear_space(-10.0, 50.0, 5, False)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[-10.0, 2.0, 14.0, 26.0, 38.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = sequences.linear_space(-10.0, 20.0, 5, True)
let assert Ok(result) =
predicates.all_close(
linspace |> iterator.to_list(),
[-10.0, -2.5, 5.0, 12.5, 20.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// A negative number of points does not work (-5)
sequences.linear_space(10.0, 50.0, -5, True)
|> should.be_error()
}
pub fn float_list_logarithmic_space_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
// - Positive start, stop, base
let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, 10.0)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[10.0, 100.0, 1000.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, stop, negative base
let assert Ok(logspace) =
sequences.logarithmic_space(1.0, 3.0, 3, True, -10.0)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[-10.0, 100.0, -1000.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, negative stop, base
let assert Ok(logspace) =
sequences.logarithmic_space(1.0, -3.0, 3, True, -10.0)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[-10.0, -0.1, -0.001],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, base, negative stop
let assert Ok(logspace) =
sequences.logarithmic_space(1.0, -3.0, 3, True, 10.0)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[10.0, 0.1, 0.001],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive stop, base, negative start
let assert Ok(logspace) =
sequences.logarithmic_space(-1.0, 3.0, 3, True, 10.0)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[0.1, 10.0, 1000.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// ----> Without endpoint included
// - Positive start, stop, base
let assert Ok(logspace) =
sequences.logarithmic_space(1.0, 3.0, 3, False, 10.0)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[10.0, 46.41588834, 215.443469],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// A negative number of points does not work (-3)
sequences.logarithmic_space(1.0, 3.0, -3, True, 10.0)
|> should.be_error()
}
pub fn float_list_geometric_space_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
// - Positive start, stop
let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, True)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[10.0, 100.0, 1000.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, negative stop
let assert Ok(logspace) = sequences.geometric_space(10.0, 0.001, 3, True)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[10.0, 0.1, 0.001],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive stop, negative start
let assert Ok(logspace) = sequences.geometric_space(0.1, 1000.0, 3, True)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[0.1, 10.0, 1000.0],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// ----> Without endpoint included
// - Positive start, stop
let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, False)
let assert Ok(result) =
predicates.all_close(
logspace |> iterator.to_list(),
[10.0, 46.41588834, 215.443469],
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Test invalid input (start and stop can't be equal to 0.0)
sequences.geometric_space(0.0, 1000.0, 3, False)
|> should.be_error()
sequences.geometric_space(-1000.0, 0.0, 3, False)
|> should.be_error()
// A negative number of points does not work
sequences.geometric_space(-1000.0, 0.0, -3, False)
|> should.be_error()
}
pub fn float_list_arange_test() {
// Positive start, stop, step
sequences.arange(1.0, 5.0, 1.0)
|> iterator.to_list()
|> should.equal([1.0, 2.0, 3.0, 4.0])
sequences.arange(1.0, 5.0, 0.5)
|> iterator.to_list()
|> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5])
sequences.arange(1.0, 2.0, 0.25)
|> iterator.to_list()
|> should.equal([1.0, 1.25, 1.5, 1.75])
// Reverse (switch start/stop largest/smallest value)
sequences.arange(5.0, 1.0, 1.0)
|> iterator.to_list()
|> should.equal([])
// Reverse negative step
sequences.arange(5.0, 1.0, -1.0)
|> iterator.to_list()
|> should.equal([5.0, 4.0, 3.0, 2.0])
// Positive start, negative stop, step
sequences.arange(5.0, -1.0, -1.0)
|> iterator.to_list()
|> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
// Negative start, stop, step
sequences.arange(-5.0, -1.0, -1.0)
|> iterator.to_list()
|> should.equal([])
// Negative start, stop, positive step
sequences.arange(-5.0, -1.0, 1.0)
|> iterator.to_list()
|> should.equal([-5.0, -4.0, -3.0, -2.0])
}

View file

@ -1,114 +0,0 @@
import gleam/result
import gleam_community/maths/elementary
import gleam_community/maths/predicates
import gleam_community/maths/special
import gleeunit/should
pub fn float_beta_function_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Valid input returns a result
special.beta(-0.5, 0.5)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
special.beta(0.5, 0.5)
|> predicates.is_close(3.1415926535897927, 0.0, tol)
|> should.be_true()
special.beta(0.5, -0.5)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
special.beta(5.0, 5.0)
|> predicates.is_close(0.0015873015873015873, 0.0, tol)
|> should.be_true()
}
pub fn float_error_function_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Valid input returns a result
special.erf(-0.5)
|> predicates.is_close(-0.5204998778130465, 0.0, tol)
|> should.be_true()
special.erf(0.5)
|> predicates.is_close(0.5204998778130465, 0.0, tol)
|> should.be_true()
special.erf(1.0)
|> predicates.is_close(0.8427007929497148, 0.0, tol)
|> should.be_true()
special.erf(2.0)
|> predicates.is_close(0.9953222650189527, 0.0, tol)
|> should.be_true()
special.erf(10.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
}
pub fn float_gamma_function_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Valid input returns a result
special.gamma(-0.5)
|> predicates.is_close(-3.5449077018110318, 0.0, tol)
|> should.be_true()
special.gamma(0.5)
|> predicates.is_close(1.7724538509055159, 0.0, tol)
|> should.be_true()
special.gamma(1.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
special.gamma(2.0)
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
special.gamma(3.0)
|> predicates.is_close(2.0, 0.0, tol)
|> should.be_true()
special.gamma(10.0)
|> predicates.is_close(362_880.0, 0.0, tol)
|> should.be_true()
}
pub fn float_incomplete_gamma_function_test() {
let assert Ok(tol) = elementary.power(10.0, -6.0)
// Invalid input gives an error
// 1st arg is invalid
special.incomplete_gamma(-1.0, 1.0)
|> should.be_error()
// 2nd arg is invalid
special.incomplete_gamma(1.0, -1.0)
|> should.be_error()
// Valid input returns a result
special.incomplete_gamma(1.0, 0.0)
|> result.unwrap(-999.0)
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
special.incomplete_gamma(1.0, 2.0)
|> result.unwrap(-999.0)
|> predicates.is_close(0.864664716763387308106, 0.0, tol)
|> should.be_true()
special.incomplete_gamma(2.0, 3.0)
|> result.unwrap(-999.0)
|> predicates.is_close(0.8008517265285442280826, 0.0, tol)
|> should.be_true()
special.incomplete_gamma(3.0, 4.0)
|> result.unwrap(-999.0)
|> predicates.is_close(1.523793388892911312363, 0.0, tol)
|> should.be_true()
}

View file

@ -0,0 +1,549 @@
import gleam/float
import gleam/set
import gleam_community/maths
import gleeunit/should
pub fn float_list_norm_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// An empty lists returns 0.0
[]
|> maths.norm(1.0)
|> 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]
|> maths.norm(1.0)
result
|> maths.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[1.0, 1.0, 1.0]
|> maths.norm(-1.0)
result
|> maths.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -1.0, -1.0]
|> maths.norm(-1.0)
result
|> maths.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -1.0, -1.0]
|> maths.norm(1.0)
result
|> maths.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> maths.norm(-10.0)
result
|> maths.is_close(0.9999007044905545, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> maths.norm(-100.0)
result
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> maths.norm(2.0)
result
|> maths.is_close(3.7416573867739413, 0.0, tol)
|> should.be_true()
}
pub fn float_list_norm_with_weights_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// An empty lists returns 0.0
[]
|> maths.norm_with_weights(1.0)
|> 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, 1.0), #(1.0, 1.0)]
|> maths.norm_with_weights(1.0)
result
|> maths.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[#(1.0, 0.5), #(1.0, 0.5), #(1.0, 0.5)]
|> maths.norm_with_weights(1.0)
result
|> maths.is_close(1.5, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[#(1.0, 0.5), #(2.0, 0.5), #(3.0, 0.5)]
|> maths.norm_with_weights(2.0)
result
|> maths.is_close(2.6457513110645907, 0.0, tol)
|> should.be_true()
}
pub fn float_list_manhattan_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Try with valid input (same as Minkowski distance with p = 1)
let assert Ok(result) = maths.manhattan_distance([#(0.0, 1.0), #(0.0, 2.0)])
result
|> maths.is_close(3.0, 0.0, tol)
|> should.be_true()
maths.manhattan_distance([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)])
|> should.equal(Ok(9.0))
maths.manhattan_distance([#(1.0, 2.0), #(2.0, 3.0)])
|> should.equal(Ok(2.0))
maths.manhattan_distance_with_weights([#(1.0, 2.0, 0.5), #(2.0, 3.0, 1.0)])
|> should.equal(Ok(1.5))
maths.manhattan_distance_with_weights([
#(1.0, 4.0, 1.0),
#(2.0, 5.0, 1.0),
#(3.0, 6.0, 1.0),
])
|> should.equal(Ok(9.0))
maths.manhattan_distance_with_weights([
#(1.0, 4.0, 1.0),
#(2.0, 5.0, 2.0),
#(3.0, 6.0, 3.0),
])
|> should.equal(Ok(18.0))
maths.manhattan_distance_with_weights([
#(1.0, 4.0, -7.0),
#(2.0, 5.0, -8.0),
#(3.0, 6.0, -9.0),
])
|> should.be_error()
}
pub fn float_list_minkowski_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Test order < 1
maths.minkowski_distance([#(0.0, 0.0), #(0.0, 0.0)], -1.0)
|> should.be_error()
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) =
maths.minkowski_distance([#(1.0, 1.0), #(1.0, 1.0)], 1.0)
result
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.minkowski_distance([#(0.0, 1.0), #(0.0, 1.0)], 10.0)
result
|> maths.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.minkowski_distance([#(0.0, 1.0), #(0.0, 1.0)], 100.0)
result
|> maths.is_close(1.0069555500567189, 0.0, tol)
|> should.be_true()
// Euclidean distance (p = 2)
let assert Ok(result) =
maths.minkowski_distance([#(0.0, 1.0), #(0.0, 2.0)], 2.0)
result
|> maths.is_close(2.23606797749979, 0.0, tol)
|> should.be_true()
// Manhattan distance (p = 1)
let assert Ok(result) =
maths.minkowski_distance([#(0.0, 1.0), #(0.0, 2.0)], 1.0)
result
|> maths.is_close(3.0, 0.0, tol)
|> should.be_true()
// Try different valid input
let assert Ok(result) =
maths.minkowski_distance([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)], 4.0)
result
|> maths.is_close(3.9482220388574776, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.minkowski_distance_with_weights(
[#(1.0, 4.0, 1.0), #(2.0, 5.0, 1.0), #(3.0, 6.0, 1.0)],
4.0,
)
result
|> maths.is_close(3.9482220388574776, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.minkowski_distance_with_weights(
[#(1.0, 4.0, 1.0), #(2.0, 5.0, 2.0), #(3.0, 6.0, 3.0)],
4.0,
)
result
|> maths.is_close(4.6952537402198615, 0.0, tol)
|> should.be_true()
// Try invalid input with weights that are negative
maths.minkowski_distance_with_weights(
[#(1.0, 4.0, -7.0), #(2.0, 5.0, -8.0), #(3.0, 6.0, -9.0)],
2.0,
)
|> should.be_error()
}
pub fn float_list_euclidean_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Empty lists returns an error
maths.euclidean_distance([])
|> should.be_error()
// Try with valid input (same as Minkowski distance with p = 2)
let assert Ok(result) = maths.euclidean_distance([#(0.0, 1.0), #(0.0, 2.0)])
result
|> maths.is_close(2.23606797749979, 0.0, tol)
|> should.be_true()
// Try different valid input
let assert Ok(result) =
maths.euclidean_distance([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)])
result
|> maths.is_close(5.196152422706632, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.euclidean_distance_with_weights([
#(1.0, 4.0, 1.0),
#(2.0, 5.0, 1.0),
#(3.0, 6.0, 1.0),
])
result
|> maths.is_close(5.196152422706632, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.euclidean_distance_with_weights([
#(1.0, 4.0, 1.0),
#(2.0, 5.0, 2.0),
#(3.0, 6.0, 3.0),
])
result
|> maths.is_close(7.3484692283495345, 0.0, tol)
|> should.be_true()
// Try invalid input with weights that are negative
maths.euclidean_distance_with_weights([
#(1.0, 4.0, -7.0),
#(2.0, 5.0, -8.0),
#(3.0, 6.0, -9.0),
])
|> should.be_error()
}
pub fn mean_test() {
// An empty list returns an error
[]
|> maths.mean()
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> maths.mean()
|> should.equal(Ok(2.0))
}
pub fn median_test() {
// An empty list returns an error
[]
|> maths.median()
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> maths.median()
|> should.equal(Ok(2.0))
[1.0, 2.0, 3.0, 4.0]
|> maths.median()
|> should.equal(Ok(2.5))
}
pub fn variance_test() {
// Degrees of freedom
let ddof = 1
// An empty list returns an error
[]
|> maths.variance(ddof)
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> maths.variance(ddof)
|> should.equal(Ok(1.0))
}
pub fn standard_deviation_test() {
// Degrees of freedom
let ddof = 1
// An empty list returns an error
[]
|> maths.standard_deviation(ddof)
|> should.be_error()
// Valid input returns a result
[1.0, 2.0, 3.0]
|> maths.standard_deviation(ddof)
|> should.equal(Ok(1.0))
}
pub fn jaccard_index_test() {
maths.jaccard_index(set.from_list([]), set.from_list([]))
|> should.equal(0.0)
let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9])
let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9])
maths.jaccard_index(set_a, set_b)
|> should.equal(4.0 /. 10.0)
let set_c = set.from_list([0, 1, 2, 3, 4, 5])
let set_d = set.from_list([6, 7, 8, 9, 10])
maths.jaccard_index(set_c, set_d)
|> should.equal(0.0 /. 11.0)
let set_e = set.from_list(["cat", "dog", "hippo", "monkey"])
let set_f = set.from_list(["monkey", "rhino", "ostrich", "salmon"])
maths.jaccard_index(set_e, set_f)
|> should.equal(1.0 /. 7.0)
}
pub fn sorensen_dice_coefficient_test() {
maths.sorensen_dice_coefficient(set.from_list([]), set.from_list([]))
|> should.equal(0.0)
let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9])
let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9])
maths.sorensen_dice_coefficient(set_a, set_b)
|> should.equal(2.0 *. 4.0 /. { 7.0 +. 7.0 })
let set_c = set.from_list([0, 1, 2, 3, 4, 5])
let set_d = set.from_list([6, 7, 8, 9, 10])
maths.sorensen_dice_coefficient(set_c, set_d)
|> should.equal(2.0 *. 0.0 /. { 6.0 +. 5.0 })
let set_e = set.from_list(["cat", "dog", "hippo", "monkey"])
let set_f = set.from_list(["monkey", "rhino", "ostrich", "salmon", "spider"])
maths.sorensen_dice_coefficient(set_e, set_f)
|> should.equal(2.0 *. 1.0 /. { 4.0 +. 5.0 })
}
pub fn overlap_coefficient_test() {
maths.overlap_coefficient(set.from_list([]), set.from_list([]))
|> should.equal(0.0)
let set_a = set.from_list([0, 1, 2, 5, 6, 8, 9])
let set_b = set.from_list([0, 2, 3, 4, 5, 7, 9])
maths.overlap_coefficient(set_a, set_b)
|> should.equal(4.0 /. 7.0)
let set_c = set.from_list([0, 1, 2, 3, 4, 5])
let set_d = set.from_list([6, 7, 8, 9, 10])
maths.overlap_coefficient(set_c, set_d)
|> should.equal(0.0 /. 5.0)
let set_e = set.from_list(["horse", "dog", "hippo", "monkey", "bird"])
let set_f = set.from_list(["monkey", "bird", "ostrich", "salmon"])
maths.overlap_coefficient(set_e, set_f)
|> should.equal(2.0 /. 4.0)
}
pub fn cosine_similarity_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Two orthogonal vectors (represented by lists)
maths.cosine_similarity([#(-1.0, 1.0), #(1.0, 1.0), #(0.0, -1.0)])
|> should.equal(Ok(0.0))
// Two identical (parallel) vectors (represented by lists)
maths.cosine_similarity([#(1.0, 1.0), #(2.0, 2.0), #(3.0, 3.0)])
|> should.equal(Ok(1.0))
// Two parallel, but oppositely oriented vectors (represented by lists)
maths.cosine_similarity([#(-1.0, 1.0), #(-2.0, 2.0), #(-3.0, 3.0)])
|> should.equal(Ok(-1.0))
// Try with arbitrary valid input
let assert Ok(result) =
maths.cosine_similarity([#(1.0, 4.0), #(2.0, 5.0), #(3.0, 6.0)])
result
|> maths.is_close(0.9746318461970762, 0.0, tol)
|> should.be_true()
// Try valid input with weights
let assert Ok(result) =
maths.cosine_similarity_with_weights([
#(1.0, 4.0, 1.0),
#(2.0, 5.0, 1.0),
#(3.0, 6.0, 1.0),
])
result
|> maths.is_close(0.9746318461970762, 0.0, tol)
|> should.be_true()
// Try with different weights
let assert Ok(result) =
maths.cosine_similarity_with_weights([
#(1.0, 4.0, 1.0),
#(2.0, 5.0, 2.0),
#(3.0, 6.0, 3.0),
])
result
|> maths.is_close(0.9855274566525745, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.cosine_similarity_with_weights([
#(1.0, 1.0, 2.0),
#(2.0, 2.0, 3.0),
#(3.0, 3.0, 4.0),
])
result
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
maths.cosine_similarity_with_weights([
#(-1.0, 1.0, 1.0),
#(-2.0, 2.0, 0.5),
#(-3.0, 3.0, 0.33),
])
result
|> maths.is_close(-1.0, 0.0, tol)
|> should.be_true()
// Try invalid input with weights that are negative
maths.cosine_similarity_with_weights([
#(1.0, 4.0, -7.0),
#(2.0, 5.0, -8.0),
#(3.0, 6.0, -9.0),
])
|> should.be_error()
}
pub fn chebyshev_distance_test() {
// Empty lists returns an error
maths.chebyshev_distance([])
|> should.be_error()
// Try different types of valid input
maths.chebyshev_distance([#(1.0, 0.0), #(0.0, 2.0)])
|> should.equal(Ok(2.0))
maths.chebyshev_distance([#(1.0, 2.0), #(0.0, 0.0)])
|> should.equal(Ok(1.0))
maths.chebyshev_distance([#(1.0, -2.0), #(0.0, 0.0)])
|> should.equal(Ok(3.0))
maths.chebyshev_distance([#(-5.0, -1.0), #(-10.0, -12.0), #(-3.0, -3.0)])
|> should.equal(Ok(4.0))
maths.chebyshev_distance([#(1.0, 1.0), #(2.0, 2.0), #(3.0, 3.0)])
|> should.equal(Ok(0.0))
maths.chebyshev_distance_with_weights([
#(-5.0, -1.0, 0.5),
#(-10.0, -12.0, 1.0),
#(-3.0, -3.0, 1.0),
])
|> should.equal(Ok(2.0))
}
pub fn canberra_distance_test() {
// Empty lists returns an error
maths.canberra_distance([])
|> should.be_error()
// Try different types of valid input
maths.canberra_distance([#(0.0, 0.0), #(0.0, 0.0)])
|> should.equal(Ok(0.0))
maths.canberra_distance([#(1.0, -2.0), #(2.0, -1.0)])
|> should.equal(Ok(2.0))
maths.canberra_distance([#(1.0, 0.0), #(0.0, 2.0)])
|> should.equal(Ok(2.0))
maths.canberra_distance([#(1.0, 2.0), #(0.0, 0.0)])
|> should.equal(Ok(1.0 /. 3.0))
maths.canberra_distance_with_weights([#(1.0, 0.0, 1.0), #(0.0, 2.0, 1.0)])
|> should.equal(Ok(2.0))
maths.canberra_distance_with_weights([#(1.0, 0.0, 1.0), #(0.0, 2.0, 0.5)])
|> should.equal(Ok(1.5))
maths.canberra_distance_with_weights([#(1.0, 0.0, 0.5), #(0.0, 2.0, 0.5)])
|> should.equal(Ok(1.0))
// Try invalid input with weights that are negative
maths.canberra_distance_with_weights([
#(1.0, 4.0, -7.0),
#(2.0, 5.0, -8.0),
#(3.0, 6.0, -9.0),
])
|> should.be_error()
}
pub fn braycurtis_distance_test() {
// Empty lists returns an error
maths.braycurtis_distance([])
|> should.be_error()
// Try different types of valid input
maths.braycurtis_distance([#(0.0, 0.0), #(0.0, 0.0)])
|> should.equal(Ok(0.0))
maths.braycurtis_distance([#(1.0, -2.0), #(2.0, -1.0)])
|> should.equal(Ok(3.0))
maths.braycurtis_distance([#(1.0, 0.0), #(0.0, 2.0)])
|> should.equal(Ok(1.0))
maths.braycurtis_distance([#(1.0, 3.0), #(2.0, 4.0)])
|> should.equal(Ok(0.4))
maths.braycurtis_distance_with_weights([#(1.0, 3.0, 1.0), #(2.0, 4.0, 1.0)])
|> should.equal(Ok(0.4))
maths.braycurtis_distance_with_weights([#(1.0, 3.0, 0.5), #(2.0, 4.0, 1.0)])
|> should.equal(Ok(0.375))
maths.braycurtis_distance_with_weights([#(1.0, 3.0, 0.25), #(2.0, 4.0, 0.25)])
|> should.equal(Ok(0.4))
// Try invalid input with weights that are negative
maths.braycurtis_distance_with_weights([
#(1.0, 4.0, -7.0),
#(2.0, 5.0, -8.0),
#(3.0, 6.0, -9.0),
])
|> should.be_error()
}

View file

@ -0,0 +1,656 @@
import gleam/float
import gleam/int
import gleam_community/maths
import gleeunit/should
pub fn float_ceiling_test() {
// Round 3. digit AFTER decimal point
maths.round_up(12.0654, 3)
|> should.equal(12.066)
// Round 2. digit AFTER decimal point
maths.round_up(12.0654, 2)
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
maths.round_up(12.0654, 1)
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
maths.round_up(12.0654, 0)
|> should.equal(13.0)
// Round 1. digit BEFORE decimal point
maths.round_up(12.0654, -1)
|> should.equal(20.0)
// Round 2. digit BEFORE decimal point
maths.round_up(12.0654, -2)
|> should.equal(100.0)
// Round 3. digit BEFORE decimal point
maths.round_up(12.0654, -3)
|> should.equal(1000.0)
}
pub fn float_floor_test() {
// Round 3. digit AFTER decimal point
maths.round_down(12.0654, 3)
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
maths.round_down(12.0654, 2)
|> should.equal(12.06)
// Round 1. digit AFTER decimal point
maths.round_down(12.0654, 1)
|> should.equal(12.0)
// Round 0. digit BEFORE decimal point
maths.round_down(12.0654, 0)
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
maths.round_down(12.0654, -1)
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
maths.round_down(12.0654, -2)
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
maths.round_down(12.0654, -3)
|> should.equal(0.0)
}
pub fn float_truncate_test() {
// Round 3. digit AFTER decimal point
maths.round_to_zero(12.0654, 3)
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
maths.round_to_zero(12.0654, 2)
|> should.equal(12.06)
// Round 1. digit AFTER decimal point
maths.round_to_zero(12.0654, 1)
|> should.equal(12.0)
// Round 0. digit BEFORE decimal point
maths.round_to_zero(12.0654, 0)
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
maths.round_to_zero(12.0654, -1)
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
maths.round_to_zero(12.0654, -2)
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
maths.round_to_zero(12.0654, -3)
|> should.equal(0.0)
}
pub fn math_round_to_nearest_test() {
// Try with positive values
maths.round_to_nearest(1.5, 0)
|> should.equal(2.0)
maths.round_to_nearest(1.75, 0)
|> should.equal(2.0)
maths.round_to_nearest(2.0, 0)
|> should.equal(2.0)
maths.round_to_nearest(3.5, 0)
|> should.equal(4.0)
maths.round_to_nearest(4.5, 0)
|> should.equal(4.0)
// Try with negative values
maths.round_to_nearest(-3.5, 0)
|> should.equal(-4.0)
maths.round_to_nearest(-4.5, 0)
|> should.equal(-4.0)
// // Round 3. digit AFTER decimal point
maths.round_to_nearest(12.0654, 3)
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
maths.round_to_nearest(12.0654, 2)
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
maths.round_to_nearest(12.0654, 1)
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
maths.round_to_nearest(12.0654, 0)
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
maths.round_to_nearest(12.0654, -1)
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
maths.round_to_nearest(12.0654, -2)
|> should.equal(0.0)
// Round 3. digit BEFORE decimal point
maths.round_to_nearest(12.0654, -3)
|> should.equal(0.0)
}
pub fn math_round_up_test() {
// Try with positive values
maths.round_up(0.45, 0)
|> should.equal(1.0)
maths.round_up(0.5, 0)
|> should.equal(1.0)
maths.round_up(0.45, 1)
|> should.equal(0.5)
maths.round_up(0.5, 1)
|> should.equal(0.5)
maths.round_up(0.455, 2)
|> should.equal(0.46)
maths.round_up(0.505, 2)
|> should.equal(0.51)
// Try with negative values
maths.round_up(-0.45, 0)
|> should.equal(-0.0)
maths.round_up(-0.5, 0)
|> should.equal(-0.0)
maths.round_up(-0.45, 1)
|> should.equal(-0.4)
maths.round_up(-0.5, 1)
|> should.equal(-0.5)
maths.round_up(-0.455, 2)
|> should.equal(-0.45)
maths.round_up(-0.505, 2)
|> should.equal(-0.5)
}
pub fn math_round_down_test() {
// Try with positive values
maths.round_down(0.45, 0)
|> should.equal(0.0)
maths.round_down(0.5, 0)
|> should.equal(0.0)
maths.round_down(0.45, 1)
|> should.equal(0.4)
maths.round_down(0.5, 1)
|> should.equal(0.5)
maths.round_down(0.455, 2)
|> should.equal(0.45)
maths.round_down(0.505, 2)
|> should.equal(0.5)
// Try with negative values
maths.round_down(-0.45, 0)
|> should.equal(-1.0)
maths.round_down(-0.5, 0)
|> should.equal(-1.0)
maths.round_down(-0.45, 1)
|> should.equal(-0.5)
maths.round_down(-0.5, 1)
|> should.equal(-0.5)
maths.round_down(-0.455, 2)
|> should.equal(-0.46)
maths.round_down(-0.505, 2)
|> should.equal(-0.51)
}
pub fn math_round_to_zero_test() {
// Note: Rounding mode "RoundToZero" is an alias for the truncate function
// Try with positive values
maths.round_to_zero(0.5, 0)
|> should.equal(0.0)
maths.round_to_zero(0.75, 0)
|> should.equal(0.0)
maths.round_to_zero(0.45, 1)
|> should.equal(0.4)
maths.round_to_zero(0.57, 1)
|> should.equal(0.5)
maths.round_to_zero(0.4575, 2)
|> should.equal(0.45)
maths.round_to_zero(0.5075, 2)
|> should.equal(0.5)
// Try with negative values
maths.round_to_zero(-0.5, 0)
|> should.equal(0.0)
maths.round_to_zero(-0.75, 0)
|> should.equal(0.0)
maths.round_to_zero(-0.45, 1)
|> should.equal(-0.4)
maths.round_to_zero(-0.57, 1)
|> should.equal(-0.5)
maths.round_to_zero(-0.4575, 2)
|> should.equal(-0.45)
maths.round_to_zero(-0.5075, 2)
|> should.equal(-0.5)
}
pub fn math_round_ties_away_test() {
// Try with positive values
maths.round_ties_away(1.4, 0)
|> should.equal(1.0)
maths.round_ties_away(1.5, 0)
|> should.equal(2.0)
maths.round_ties_away(2.5, 0)
|> should.equal(3.0)
// Try with negative values
maths.round_ties_away(-1.4, 0)
|> should.equal(-1.0)
maths.round_ties_away(-1.5, 0)
|> should.equal(-2.0)
maths.round_ties_away(-2.0, 0)
|> should.equal(-2.0)
maths.round_ties_away(-2.5, 0)
|> should.equal(-3.0)
// Round 3. digit AFTER decimal point
maths.round_ties_away(12.0654, 3)
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
maths.round_ties_away(12.0654, 2)
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
maths.round_ties_away(12.0654, 1)
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
maths.round_ties_away(12.0654, 0)
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
maths.round_ties_away(12.0654, -1)
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
maths.round_ties_away(12.0654, -2)
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
maths.round_ties_away(12.0654, -3)
|> should.equal(0.0)
}
pub fn math_round_ties_up_test() {
// Try with positive values
maths.round_ties_up(1.4, 0)
|> should.equal(1.0)
maths.round_ties_up(1.5, 0)
|> should.equal(2.0)
maths.round_ties_up(2.5, 0)
|> should.equal(3.0)
// Try with negative values
maths.round_ties_up(-1.4, 0)
|> should.equal(-1.0)
maths.round_ties_up(-1.5, 0)
|> should.equal(-1.0)
maths.round_ties_up(-2.0, 0)
|> should.equal(-2.0)
maths.round_ties_up(-2.5, 0)
|> should.equal(-2.0)
// Round 3. digit AFTER decimal point
maths.round_ties_up(12.0654, 3)
|> should.equal(12.065)
// Round 2. digit AFTER decimal point
maths.round_ties_up(12.0654, 2)
|> should.equal(12.07)
// Round 1. digit AFTER decimal point
maths.round_ties_up(12.0654, 1)
|> should.equal(12.1)
// Round 0. digit BEFORE decimal point
maths.round_ties_up(12.0654, 0)
|> should.equal(12.0)
// Round 1. digit BEFORE decimal point
maths.round_ties_up(12.0654, -1)
|> should.equal(10.0)
// Round 2. digit BEFORE decimal point
maths.round_ties_up(12.0654, -2)
|> should.equal(0.0)
// Round 2. digit BEFORE decimal point
maths.round_ties_up(12.0654, -3)
|> should.equal(0.0)
}
pub fn float_absolute_difference_test() {
maths.float_absolute_difference(20.0, 15.0)
|> should.equal(5.0)
maths.float_absolute_difference(-20.0, -15.0)
|> should.equal(5.0)
maths.float_absolute_difference(20.0, -15.0)
|> should.equal(35.0)
maths.float_absolute_difference(-20.0, 15.0)
|> should.equal(35.0)
maths.float_absolute_difference(0.0, 0.0)
|> should.equal(0.0)
maths.float_absolute_difference(1.0, 2.0)
|> should.equal(1.0)
maths.float_absolute_difference(2.0, 1.0)
|> should.equal(1.0)
maths.float_absolute_difference(-1.0, 0.0)
|> should.equal(1.0)
maths.float_absolute_difference(0.0, -1.0)
|> should.equal(1.0)
maths.float_absolute_difference(10.0, 20.0)
|> should.equal(10.0)
maths.float_absolute_difference(-10.0, -20.0)
|> should.equal(10.0)
maths.float_absolute_difference(-10.5, 10.5)
|> should.equal(21.0)
}
pub fn int_absolute_difference_test() {
maths.int_absolute_difference(20, 15)
|> should.equal(5)
maths.int_absolute_difference(-20, -15)
|> should.equal(5)
maths.int_absolute_difference(20, -15)
|> should.equal(35)
maths.int_absolute_difference(-20, 15)
|> should.equal(35)
}
pub fn float_sign_test() {
maths.float_sign(100.0)
|> should.equal(1.0)
maths.float_sign(0.0)
|> should.equal(0.0)
maths.float_sign(-100.0)
|> should.equal(-1.0)
}
pub fn float_flip_sign_test() {
maths.float_flip_sign(100.0)
|> should.equal(-100.0)
maths.float_flip_sign(0.0)
|> should.equal(-0.0)
maths.float_flip_sign(-100.0)
|> should.equal(100.0)
}
pub fn float_copy_sign_test() {
maths.float_copy_sign(100.0, 10.0)
|> should.equal(100.0)
maths.float_copy_sign(-100.0, 10.0)
|> should.equal(100.0)
maths.float_copy_sign(100.0, -10.0)
|> should.equal(-100.0)
maths.float_copy_sign(-100.0, -10.0)
|> should.equal(-100.0)
}
pub fn int_sign_test() {
maths.int_sign(100)
|> should.equal(1)
maths.int_sign(0)
|> should.equal(0)
maths.int_sign(-100)
|> should.equal(-1)
}
pub fn int_flip_sign_test() {
maths.int_flip_sign(100)
|> should.equal(-100)
maths.int_flip_sign(0)
|> should.equal(-0)
maths.int_flip_sign(-100)
|> should.equal(100)
}
pub fn int_copy_sign_test() {
maths.int_copy_sign(100, 10)
|> should.equal(100)
maths.int_copy_sign(-100, 10)
|> should.equal(100)
maths.int_copy_sign(100, -10)
|> should.equal(-100)
maths.int_copy_sign(-100, -10)
|> should.equal(-100)
}
pub fn float_minmax_test() {
maths.minmax(0.75, 0.5, float.compare)
|> should.equal(#(0.5, 0.75))
maths.minmax(0.5, 0.75, float.compare)
|> should.equal(#(0.5, 0.75))
maths.minmax(-0.75, 0.5, float.compare)
|> should.equal(#(-0.75, 0.5))
maths.minmax(-0.75, 0.5, float.compare)
|> should.equal(#(-0.75, 0.5))
}
pub fn int_minmax_test() {
maths.minmax(75, 50, int.compare)
|> should.equal(#(50, 75))
maths.minmax(50, 75, int.compare)
|> should.equal(#(50, 75))
maths.minmax(-75, 50, int.compare)
|> should.equal(#(-75, 50))
maths.minmax(-75, 50, int.compare)
|> should.equal(#(-75, 50))
}
pub fn float_list_minimum_test() {
// An empty lists returns an error
[]
|> maths.list_minimum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> maths.list_minimum(float.compare)
|> should.equal(Ok(1.5))
}
pub fn int_list_minimum_test() {
// An empty lists returns an error
[]
|> maths.list_minimum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> maths.list_minimum(int.compare)
|> should.equal(Ok(1))
}
pub fn float_list_maximum_test() {
// An empty lists returns an error
[]
|> maths.list_maximum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> maths.list_maximum(float.compare)
|> should.equal(Ok(4.5))
}
pub fn int_list_maximum_test() {
// An empty lists returns an error
[]
|> maths.list_maximum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> maths.list_maximum(int.compare)
|> should.equal(Ok(4))
}
pub fn float_list_arg_maximum_test() {
// An empty lists returns an error
[]
|> maths.arg_maximum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> maths.arg_maximum(float.compare)
|> should.equal(Ok([0, 1]))
}
pub fn int_list_arg_maximum_test() {
// An empty lists returns an error
[]
|> maths.arg_maximum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> maths.arg_maximum(int.compare)
|> should.equal(Ok([0, 1]))
}
pub fn float_list_arg_minimum_test() {
// An empty lists returns an error
[]
|> maths.arg_minimum(float.compare)
|> should.be_error()
// Valid input returns a result
[4.5, 4.5, 3.5, 2.5, 1.5]
|> maths.arg_minimum(float.compare)
|> should.equal(Ok([4]))
}
pub fn int_list_arg_minimum_test() {
// An empty lists returns an error
[]
|> maths.arg_minimum(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> maths.arg_minimum(int.compare)
|> should.equal(Ok([4]))
}
pub fn float_list_extrema_test() {
// An empty lists returns an error
[]
|> maths.extrema(float.compare)
|> should.be_error()
// Valid input returns a result
[4.0, 4.0, 3.0, 2.0, 1.0]
|> maths.extrema(float.compare)
|> should.equal(Ok(#(1.0, 4.0)))
// Valid input returns a result
[1.0, 4.0, 2.0, 5.0, 0.0]
|> maths.extrema(float.compare)
|> should.equal(Ok(#(0.0, 5.0)))
}
pub fn int_list_extrema_test() {
// An empty lists returns an error
[]
|> maths.extrema(int.compare)
|> should.be_error()
// Valid input returns a result
[4, 4, 3, 2, 1]
|> maths.extrema(int.compare)
|> should.equal(Ok(#(1, 4)))
// Valid input returns a result
[1, 4, 2, 5, 0]
|> maths.extrema(int.compare)
|> should.equal(Ok(#(0, 5)))
}

View file

@ -0,0 +1,390 @@
import gleam/float
import gleam/list
import gleam/yielder
import gleam_community/maths
import gleeunit/should
pub fn float_list_linear_space_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
let assert Ok(linspace) = maths.linear_space(10.0, 50.0, 5, True)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([10.0, 20.0, 30.0, 40.0, 50.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = maths.linear_space(10.0, 20.0, 5, True)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([10.0, 12.5, 15.0, 17.5, 20.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Try with negative stop
// ----> Without endpoint included
let assert Ok(linspace) = maths.linear_space(10.0, 50.0, 5, False)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([10.0, 18.0, 26.0, 34.0, 42.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = maths.linear_space(10.0, 20.0, 5, False)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([10.0, 12.0, 14.0, 16.0, 18.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Try with negative stop
let assert Ok(linspace) = maths.linear_space(10.0, -50.0, 5, False)
let assert Ok(result) =
maths.all_close(
linspace
|> yielder.to_list()
|> list.zip([10.0, -2.0, -14.0, -26.0, -38.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = maths.linear_space(10.0, -20.0, 5, True)
let assert Ok(result) =
maths.all_close(
linspace
|> yielder.to_list()
|> list.zip([10.0, 2.5, -5.0, -12.5, -20.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Try with negative start
let assert Ok(linspace) = maths.linear_space(-10.0, 50.0, 5, False)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([-10.0, 2.0, 14.0, 26.0, 38.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = maths.linear_space(-10.0, 20.0, 5, True)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([-10.0, -2.5, 5.0, 12.5, 20.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// A negative number of points does not work (-5)
maths.linear_space(10.0, 50.0, -5, True)
|> should.be_error()
}
pub fn float_list_logarithmic_space_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
// - Positive start, stop, base
let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([10.0, 100.0, 1000.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, stop, negative base
let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, -10.0)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([-10.0, 100.0, -1000.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, negative stop, base
let assert Ok(logspace) = maths.logarithmic_space(1.0, -3.0, 3, True, -10.0)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([-10.0, -0.1, -0.001]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, base, negative stop
let assert Ok(logspace) = maths.logarithmic_space(1.0, -3.0, 3, True, 10.0)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([10.0, 0.1, 0.001]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive stop, base, negative start
let assert Ok(logspace) = maths.logarithmic_space(-1.0, 3.0, 3, True, 10.0)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([0.1, 10.0, 1000.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// ----> Without endpoint included
// - Positive start, stop, base
let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, False, 10.0)
let assert Ok(result) =
maths.all_close(
logspace
|> yielder.to_list()
|> list.zip([10.0, 46.41588834, 215.443469]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// A negative number of points does not work (-3)
maths.logarithmic_space(1.0, 3.0, -3, True, 10.0)
|> should.be_error()
}
pub fn float_list_geometric_space_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
// - Positive start, stop
let assert Ok(logspace) = maths.geometric_space(10.0, 1000.0, 3, True)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([10.0, 100.0, 1000.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive start, negative stop
let assert Ok(logspace) = maths.geometric_space(10.0, 0.001, 3, True)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([10.0, 0.1, 0.001]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive stop, negative start
let assert Ok(logspace) = maths.geometric_space(0.1, 1000.0, 3, True)
let assert Ok(result) =
maths.all_close(
logspace |> yielder.to_list() |> list.zip([0.1, 10.0, 1000.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// ----> Without endpoint included
// - Positive start, stop
let assert Ok(logspace) = maths.geometric_space(10.0, 1000.0, 3, False)
let assert Ok(result) =
maths.all_close(
logspace
|> yielder.to_list()
|> list.zip([10.0, 46.41588834, 215.443469]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Test invalid input (start and stop can't be equal to 0.0)
maths.geometric_space(0.0, 1000.0, 3, False)
|> should.be_error()
maths.geometric_space(-1000.0, 0.0, 3, False)
|> should.be_error()
// A negative number of points does not work
maths.geometric_space(-1000.0, 0.0, -3, False)
|> should.be_error()
}
pub fn float_list_arange_test() {
// Positive start, stop, step
maths.arange(1.0, 5.0, 1.0)
|> yielder.to_list()
|> should.equal([1.0, 2.0, 3.0, 4.0])
maths.arange(1.0, 5.0, 0.5)
|> yielder.to_list()
|> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5])
maths.arange(1.0, 2.0, 0.25)
|> yielder.to_list()
|> should.equal([1.0, 1.25, 1.5, 1.75])
// Reverse (switch start/stop largest/smallest value)
maths.arange(5.0, 1.0, 1.0)
|> yielder.to_list()
|> should.equal([])
// Reverse negative step
maths.arange(5.0, 1.0, -1.0)
|> yielder.to_list()
|> should.equal([5.0, 4.0, 3.0, 2.0])
// Positive start, negative stop, step
maths.arange(5.0, -1.0, -1.0)
|> yielder.to_list()
|> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0])
// Negative start, stop, step
maths.arange(-5.0, -1.0, -1.0)
|> yielder.to_list()
|> should.equal([])
// Negative start, stop, positive step
maths.arange(-5.0, -1.0, 1.0)
|> yielder.to_list()
|> should.equal([-5.0, -4.0, -3.0, -2.0])
}
pub fn float_list_exponential_space_test() {
let assert Ok(tolerance) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
let assert Ok(exp_space) = maths.exponential_space(1.0, 1000.0, 4, True)
let assert Ok(result) =
maths.all_close(
exp_space |> yielder.to_list() |> list.zip([1.0, 10.0, 100.0, 1000.0]),
0.0,
tolerance,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// ---> Without endpoint included
let assert Ok(exp_space) = maths.exponential_space(1.0, 1000.0, 4, False)
let assert Ok(result) =
maths.all_close(
exp_space
|> yielder.to_list()
|> list.zip([
1.0, 5.623413251903491, 31.622776601683793, 177.82794100389228,
]),
0.0,
tolerance,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// A negative number of points does not work (-3)
maths.exponential_space(1.0, 1000.0, -3, True)
|> should.be_error()
}
pub fn float_list_symmetric_space_test() {
let assert Ok(tolerance) = float.power(10.0, -6.0)
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(sym_space) = maths.symmetric_space(0.0, 5.0, 5)
sym_space
|> yielder.to_list()
|> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0])
let assert Ok(sym_space) = maths.symmetric_space(0.0, 5.0, 5)
sym_space
|> yielder.to_list()
|> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0])
// Negative center
let assert Ok(sym_space) = maths.symmetric_space(-10.0, 5.0, 5)
sym_space
|> yielder.to_list()
|> should.equal([-15.0, -12.5, -10.0, -7.5, -5.0])
// Uneven number of points
let assert Ok(sym_space) = maths.symmetric_space(0.0, 2.0, 4)
let assert Ok(result) =
maths.all_close(
sym_space
|> yielder.to_list()
|> list.zip([-2.0, -0.6666666666666667, 0.6666666666666665, 2.0]),
0.0,
tolerance,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// A negative number of points does not work (-5)
maths.symmetric_space(0.0, 5.0, -5)
|> should.be_error()
}

View file

@ -0,0 +1,113 @@
import gleam/float
import gleam/result
import gleam_community/maths
import gleeunit/should
pub fn float_beta_function_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Valid input returns a result
maths.beta(-0.5, 0.5)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.beta(0.5, 0.5)
|> maths.is_close(3.1415926535897927, 0.0, tol)
|> should.be_true()
maths.beta(0.5, -0.5)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.beta(5.0, 5.0)
|> maths.is_close(0.0015873015873015873, 0.0, tol)
|> should.be_true()
}
pub fn float_error_function_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Valid input returns a result
maths.erf(-0.5)
|> maths.is_close(-0.5204998778130465, 0.0, tol)
|> should.be_true()
maths.erf(0.5)
|> maths.is_close(0.5204998778130465, 0.0, tol)
|> should.be_true()
maths.erf(1.0)
|> maths.is_close(0.8427007929497148, 0.0, tol)
|> should.be_true()
maths.erf(2.0)
|> maths.is_close(0.9953222650189527, 0.0, tol)
|> should.be_true()
maths.erf(10.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
}
pub fn float_gamma_function_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Valid input returns a result
maths.gamma(-0.5)
|> maths.is_close(-3.5449077018110318, 0.0, tol)
|> should.be_true()
maths.gamma(0.5)
|> maths.is_close(1.7724538509055159, 0.0, tol)
|> should.be_true()
maths.gamma(1.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.gamma(2.0)
|> maths.is_close(1.0, 0.0, tol)
|> should.be_true()
maths.gamma(3.0)
|> maths.is_close(2.0, 0.0, tol)
|> should.be_true()
maths.gamma(10.0)
|> maths.is_close(362_880.0, 0.0, tol)
|> should.be_true()
}
pub fn float_incomplete_gamma_function_test() {
let assert Ok(tol) = float.power(10.0, -6.0)
// Invalid input gives an error
// 1st arg is invalid
maths.incomplete_gamma(-1.0, 1.0)
|> should.be_error()
// 2nd arg is invalid
maths.incomplete_gamma(1.0, -1.0)
|> should.be_error()
// Valid input returns a result
maths.incomplete_gamma(1.0, 0.0)
|> result.unwrap(-999.0)
|> maths.is_close(0.0, 0.0, tol)
|> should.be_true()
maths.incomplete_gamma(1.0, 2.0)
|> result.unwrap(-999.0)
|> maths.is_close(0.864664716763387308106, 0.0, tol)
|> should.be_true()
maths.incomplete_gamma(2.0, 3.0)
|> result.unwrap(-999.0)
|> maths.is_close(0.8008517265285442280826, 0.0, tol)
|> should.be_true()
maths.incomplete_gamma(3.0, 4.0)
|> result.unwrap(-999.0)
|> maths.is_close(1.523793388892911312363, 0.0, tol)
|> should.be_true()
}