Remove duplicate function. Improve function input validation + tests

This commit is contained in:
NicklasXYZ 2024-12-08 23:39:25 +01:00
parent 3b7629bd3e
commit 803abd516e
2 changed files with 135 additions and 146 deletions

View file

@ -5419,6 +5419,11 @@ pub fn linear_space(
/// 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.
///
/// The values in the sequence are computed as powers of the given base, where the exponents are
/// evenly spaced between `start` and `stop`. The `base` parameter must be positive, as negative
/// bases lead to undefined behavior when computing fractional exponents. Similarly, the number of
/// points (`steps`) must be positive; specifying zero or a negative value will result in an error.
///
/// <details>
/// <summary>Example:</summary>
///
@ -5454,7 +5459,7 @@ pub fn logarithmic_space(
endpoint: Bool,
base: Float,
) -> Result(Yielder(Float), Nil) {
case steps > 0 {
case steps > 0 && base >=. 0.0 {
True -> {
let assert Ok(linspace) = linear_space(start, stop, steps, endpoint)
@ -5475,10 +5480,19 @@ pub fn logarithmic_space(
/// </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.
/// The function returns an iterator for generating a geometric progression between two specified
/// values, where each value is a constant multiple of the previous one. Unlike
/// [`logarithmic_space`](#logarithmic_space), this function allows specifying the starting
/// and ending values (`start` and `stop`) directly, without requiring them to be transformed
/// into exponents.
///
/// Internally, the function computes the logarithms of `start` and `stop` and generates evenly
/// spaced points in the logarithmic domain (using base 10). These points are then transformed back
/// into their original scale to create a sequence of values that grow multiplicatively.
///
/// The `start` and `stop` values must be positive, as logarithms are undefined for non-positive
/// values. The number of points (`steps`) must also be positive; specifying zero or a negative
/// value will result in an error.
///
/// <details>
/// <summary>Example:</summary>
@ -5496,7 +5510,7 @@ pub fn logarithmic_space(
/// |> list.all(fn(x) { x == True })
/// |> should.be_true()
///
/// // Input (start and stop can't be equal to 0.0)
/// // Input (start and stop can't be less than or equal to 0.0)
/// maths.geometric_space(0.0, 1000.0, 3, False)
/// |> should.be_error()
///
@ -5521,17 +5535,13 @@ pub fn geometric_space(
steps: Int,
endpoint: Bool,
) -> Result(Yielder(Float), Nil) {
case start == 0.0 || stop == 0.0 {
case start <=. 0.0 || stop <=. 0.0 || steps < 0 {
True -> Error(Nil)
False ->
case steps > 0 {
True -> {
False -> {
let assert Ok(log_start) = logarithm_10(start)
let assert Ok(log_stop) = logarithm_10(stop)
logarithmic_space(log_start, log_stop, steps, endpoint, 10.0)
}
False -> Error(Nil)
}
}
}
@ -5541,66 +5551,8 @@ pub fn geometric_space(
/// </a>
/// </div>
///
/// The function returns an iterator of exponentially 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. The sequence is generated by
/// computing intermediate values in a logarithmic domain and transforming them into the exponential
/// domain.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleam/yielder
/// import gleeunit/should
/// import gleam_community/maths
///
/// pub fn example() {
/// let assert Ok(tolerance) = float.power(10.0, -6.0)
/// let assert Ok(exp_space) = maths.exponential_space(1.0, 1000.0, 4, True)
/// let expected = [1.0, 10.0, 100.0, 1000.0]
/// let pairs = exp_space |> yielder.to_list() |> list.zip(expected)
/// let assert Ok(result) = maths.all_close(pairs, 0.0, tolerance)
/// result |> list.all(fn(x) { x == True }) |> should.be_true()
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn exponential_space(
start: Float,
stop: Float,
steps: Int,
endpoint: Bool,
) -> Result(Yielder(Float), Nil) {
case steps > 0 {
True -> {
let assert Ok(log_start) = logarithm_10(start)
let assert Ok(log_stop) = logarithm_10(stop)
let assert Ok(log_space) =
linear_space(log_start, log_stop, steps, endpoint)
Ok(
yielder.map(log_space, fn(value) {
let assert Ok(exp_value) = float.power(10.0, value)
exp_value
}),
)
}
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>
///
/// Generates evenly spaced points around a center value. The total span is determined by
/// the radius argument of the function.
/// Generates evenly spaced points around a center value. The total span (around the center value)
/// is determined by the `radius` argument of the function.
///
/// <details>
/// <summary>Example:</summary>
@ -5614,6 +5566,12 @@ pub fn exponential_space(
/// sym_space
/// |> yielder.to_list()
/// |> should.equal([-5.0, -2.5, 0.0, 2.5, 5.0])
///
/// // A negative radius reverses the order of the 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])
/// }
/// </details>
///

View file

@ -28,7 +28,6 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -42,7 +41,6 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -54,7 +52,6 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -69,7 +66,6 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -83,7 +79,6 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -96,7 +91,6 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -108,7 +102,31 @@ pub fn list_linear_space_test() {
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Check that when start == stop and steps > 0, then
// the value start/stop value is just repeated, since the
// step increment will be 0
let assert Ok(linspace) = maths.linear_space(10.0, 10.0, 5, True)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(linspace) = maths.linear_space(10.0, 10.0, 5, False)
let assert Ok(result) =
maths.all_close(
linspace |> yielder.to_list() |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
@ -123,7 +141,7 @@ pub fn list_logarithmic_space_test() {
// Check that the function agrees, at some arbitrary input
// points, with known function values
// ---> With endpoint included
// - Positive start, stop, base
// - Positive start, stop
let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0)
let assert Ok(result) =
maths.all_close(
@ -135,31 +153,7 @@ pub fn list_logarithmic_space_test() {
|> 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
// - Positive start, negative stop
let assert Ok(logspace) = maths.logarithmic_space(1.0, -3.0, 3, True, 10.0)
let assert Ok(result) =
maths.all_close(
@ -171,7 +165,7 @@ pub fn list_logarithmic_space_test() {
|> list.all(fn(x) { x == True })
|> should.be_true()
// - Positive stop, base, negative start
// - Positive stop, negative start
let assert Ok(logspace) = maths.logarithmic_space(-1.0, 3.0, 3, True, 10.0)
let assert Ok(result) =
maths.all_close(
@ -184,7 +178,7 @@ pub fn list_logarithmic_space_test() {
|> should.be_true()
// ----> Without endpoint included
// - Positive start, stop, base
// - Positive start, stop
let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, False, 10.0)
let assert Ok(result) =
maths.all_close(
@ -198,9 +192,41 @@ pub fn list_logarithmic_space_test() {
|> list.all(fn(x) { x == True })
|> should.be_true()
// Check that when start == stop and steps > 0, then
// the value start/stop value is just repeated, since the
// step increment will be 0
let assert Ok(logspace) = maths.logarithmic_space(5.0, 5.0, 5, True, 5.0)
let assert Ok(result) =
maths.all_close(
logspace
|> yielder.to_list()
|> list.zip([3125.0, 3125.0, 3125.0, 3125.0, 3125.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(logspace) = maths.logarithmic_space(5.0, 5.0, 5, False, 5.0)
let assert Ok(result) =
maths.all_close(
logspace
|> yielder.to_list()
|> list.zip([3125.0, 3125.0, 3125.0, 3125.0, 3125.0]),
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()
// A negative base does not work (-10)
maths.logarithmic_space(1.0, 3.0, 3, True, -10.0)
|> should.be_error()
}
pub fn list_geometric_space_test() {
@ -259,7 +285,36 @@ pub fn list_geometric_space_test() {
|> list.all(fn(x) { x == True })
|> should.be_true()
// Test invalid input (start and stop can't be equal to 0.0)
// Check that when start == stop and steps > 0, then
// the value start/stop value is just repeated, since the
// step increment will be 0
let assert Ok(logspace) = maths.geometric_space(5.0, 5.0, 5, True)
let assert Ok(result) =
maths.all_close(
logspace
|> yielder.to_list()
|> list.zip([5.0, 5.0, 5.0, 5.0, 5.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
let assert Ok(logspace) = maths.geometric_space(5.0, 5.0, 5, False)
let assert Ok(result) =
maths.all_close(
logspace
|> yielder.to_list()
|> list.zip([5.0, 5.0, 5.0, 5.0, 5.0]),
0.0,
tol,
)
result
|> list.all(fn(x) { x == True })
|> should.be_true()
// Test invalid input (start and stop can't be less than or equal to 0.0)
maths.geometric_space(0.0, 1000.0, 3, False)
|> should.be_error()
@ -311,44 +366,6 @@ pub fn list_arange_test() {
|> should.equal([-5.0, -4.0, -3.0, -2.0])
}
pub fn 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 list_symmetric_space_test() {
let assert Ok(tolerance) = float.power(10.0, -6.0)
@ -370,6 +387,12 @@ pub fn list_symmetric_space_test() {
|> yielder.to_list()
|> should.equal([-15.0, -12.5, -10.0, -7.5, -5.0])
// Negative Radius (simply reverses the order of the 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])
// Uneven number of points
let assert Ok(sym_space) = maths.symmetric_space(0.0, 2.0, 4)
let assert Ok(result) =
@ -384,6 +407,14 @@ pub fn list_symmetric_space_test() {
|> list.all(fn(x) { x == True })
|> should.be_true()
// Check that when radius == 0 and steps > 0, then
// the value center value is just repeated, since the
// step increment will be 0
let assert Ok(sym_space) = maths.symmetric_space(10.0, 0.0, 4)
sym_space
|> yielder.to_list()
|> should.equal([10.0, 10.0, 10.0, 10.0])
// A negative number of points does not work (-5)
maths.symmetric_space(0.0, 5.0, -5)
|> should.be_error()