diff --git a/src/gleam_community/maths.gleam b/src/gleam_community/maths.gleam index 1e95fbd..2fcfc1c 100644 --- a/src/gleam_community/maths.gleam +++ b/src/gleam_community/maths.gleam @@ -21,8 +21,6 @@ //// .katex { font-size: 1.10em; } //// //// -//// --- -//// import gleam/bool import gleam/float @@ -5278,7 +5276,7 @@ fn incomplete_gamma_sum( /// /// /// -/// The function returns an iterator generating evenly spaced values within a specified interval +/// The function returns a list of evenly spaced values within a specified interval /// `[start, stop)` based on a given increment size. /// /// Note that if `increment > 0`, the sequence progresses from `start` towards `stop`, while if @@ -5287,25 +5285,21 @@ fn incomplete_gamma_sum( ///
/// Example: /// -/// import gleam/yielder /// import gleeunit/should /// import gleam_community/maths /// /// pub fn example () { -/// maths.arange(1.0, 5.0, 1.0) -/// |> yielder.to_list() +/// maths.step_range(1.0, 5.0, 1.0) /// |> should.equal([1.0, 2.0, 3.0, 4.0]) -/// +/// /// // No points returned since /// // start is smaller than stop and the step is positive -/// maths.arange(5.0, 1.0, 1.0) -/// |> yielder.to_list() +/// maths.step_range(5.0, 1.0, 1.0) /// |> should.equal([]) -/// +/// /// // Points returned since /// // start smaller than stop but negative step -/// maths.arange(5.0, 1.0, -1.0) -/// |> yielder.to_list() +/// maths.step_range(5.0, 1.0, -1.0) /// |> should.equal([5.0, 4.0, 3.0, 2.0]) /// } ///
@@ -5316,7 +5310,91 @@ fn incomplete_gamma_sum( /// /// /// -pub fn arange(start: Float, stop: Float, increment: Float) -> Yielder(Float) { +pub fn step_range(start: Float, stop: Float, increment: Float) -> List(Float) { + case + { start >=. stop && increment >. 0.0 } + || { start <=. stop && increment <. 0.0 } + { + True -> [] + False -> { + let direction = case start <=. stop { + True -> 1.0 + False -> -1.0 + } + + let increment_abs = float.absolute_value(increment) + let distance = float.absolute_value(start -. stop) + let steps = float.round(distance /. increment_abs) + let adjusted_stop = stop -. increment_abs *. direction + + // Generate the sequence from 'adjusted_stop' towards 'start' + do_step_range(adjusted_stop, increment_abs *. direction, steps, []) + } + } +} + +fn do_step_range( + current: Float, + increment: Float, + remaining_steps: Int, + acc: List(Float), +) -> List(Float) { + case remaining_steps { + 0 -> acc + _ -> + do_step_range(current -. increment, increment, remaining_steps - 1, [ + current, + ..acc + ]) + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function is similar to [`step_range`](#step_range) but instead returns a yielder +/// (lazily evaluated sequence of elements). This function can be used whenever there is a need +/// to generate a larger-than-usual sequence of elements. +/// +///
+/// Example: +/// +/// import gleam/yielder.{Next, Done} +/// import gleeunit/should +/// import gleam_community/maths +/// +/// pub fn example () { +/// let range = maths.yield_step_range(1.0, 2.5, 0.5) +/// +/// let assert Next(element, rest) = yielder.step(range) +/// should.equal(element, 1.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 1.5) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 2.0) +/// +/// // We have generated 3 values over the interval [1.0, 2.5) +/// // in increments of 0.5, so the 4th will be 'Done' +/// should.equal(yielder.step(rest), Done) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn yield_step_range( + start: Float, + stop: Float, + increment: Float, +) -> Yielder(Float) { // Check if the range would be empty due to direction and increment case { start >=. stop && increment >. 0.0 } @@ -5345,22 +5423,20 @@ pub fn arange(start: Float, stop: Float, increment: Float) -> Yielder(Float) { /// /// /// -/// The function returns an iterator for generating linearly spaced points over a specified +/// The function returns a list of 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. /// ///
/// Example: /// -/// 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(linspace) = maths.linear_space(10.0, 20.0, 5, True) -/// let pairs = -/// linspace |> yielder.to_list() |> list.zip([10.0, 12.5, 15.0, 17.5, 20.0]) +/// let pairs = linspace |> list.zip([10.0, 12.5, 15.0, 17.5, 20.0]) /// let assert Ok(result) = maths.all_close(pairs, 0.0, tolerance) /// result /// |> list.all(fn(x) { x == True }) @@ -5383,6 +5459,98 @@ pub fn linear_space( stop: Float, steps: Int, endpoint: Bool, +) -> Result(List(Float), Nil) { + let direction = case start <=. stop { + True -> 1.0 + False -> -1.0 + } + + let increment_abs = case endpoint { + True -> float.absolute_value(start -. stop) /. int.to_float(steps - 1) + False -> float.absolute_value(start -. stop) /. int.to_float(steps) + } + + let adjusted_stop = case endpoint { + True -> stop + False -> stop -. increment_abs *. direction + } + + // Generate the sequence from 'adjusted_stop' towards 'start' + case steps > 0 { + True -> { + Ok(do_linear_space(adjusted_stop, increment_abs *. direction, steps, [])) + } + False -> Error(Nil) + } +} + +fn do_linear_space( + current: Float, + increment: Float, + remaining_steps: Int, + acc: List(Float), +) -> List(Float) { + case remaining_steps { + 0 -> acc + _ -> + do_linear_space(current -. increment, increment, remaining_steps - 1, [ + current, + ..acc + ]) + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function is similar to [`linear_space`](#linear_space) but instead returns a yielder +/// (lazily evaluated sequence of elements). This function can be used whenever there is a need +/// to generate a larger-than-usual sequence of elements. +/// +///
+/// Example: +/// +/// import gleam/yielder.{Next, Done} +/// import gleeunit/should +/// import gleam_community/maths +/// +/// pub fn example () { +/// let assert Ok(linspace) = maths.yield_linear_space(10.0, 20.0, 5, True) +/// +/// let assert Next(element, rest) = yielder.step(linspace) +/// should.equal(element, 10.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 12.5) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 15.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 17.5) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 20.0) +/// +/// // We have generated 5 values, so the 6th will be 'Done' +/// should.equal(yielder.step(rest), Done) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn yield_linear_space( + start: Float, + stop: Float, + steps: Int, + endpoint: Bool, ) -> Result(Yielder(Float), Nil) { let direction = case start <=. stop { True -> 1.0 @@ -5415,7 +5583,7 @@ pub fn linear_space( /// /// /// -/// The function returns an iterator for generating logarithmically spaced points over a specified +/// The function returns a list 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. /// @@ -5427,14 +5595,13 @@ pub fn linear_space( ///
/// Example: /// -/// 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(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0) -/// let pairs = logspace |> yielder.to_list() |> list.zip([10.0, 100.0, 1000.0]) +/// let pairs = logspace |> list.zip([10.0, 100.0, 1000.0]) /// let assert Ok(result) = maths.all_close(pairs, 0.0, tolerance) /// result /// |> list.all(fn(x) { x == True }) @@ -5458,11 +5625,74 @@ pub fn logarithmic_space( steps: Int, endpoint: Bool, base: Float, -) -> Result(Yielder(Float), Nil) { +) -> Result(List(Float), Nil) { case steps > 0 && base >=. 0.0 { True -> { let assert Ok(linspace) = linear_space(start, stop, steps, endpoint) + Ok( + list.map(linspace, fn(value) { + let assert Ok(result) = float.power(base, value) + result + }), + ) + } + False -> Error(Nil) + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function is similar to [`logarithmic_space`](#logarithmic_space) but instead returns a yielder +/// (lazily evaluated sequence of elements). This function can be used whenever there is a need +/// to generate a larger-than-usual sequence of elements. +/// +///
+/// Example: +/// +/// import gleam/yielder.{Next, Done} +/// import gleeunit/should +/// import gleam_community/maths +/// +/// pub fn example () { +/// let assert Ok(logspace) = +/// maths.yield_logarithmic_space(1.0, 3.0, 3, True, 10.0) +/// +/// let assert Next(element, rest) = yielder.step(logspace) +/// should.equal(element, 10.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 100.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 1000.0) +/// +/// // We have generated 3 values, so the 4th will be 'Done' +/// should.equal(yielder.step(rest), Done) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn yield_logarithmic_space( + start: Float, + stop: Float, + steps: Int, + endpoint: Bool, + base: Float, +) -> Result(Yielder(Float), Nil) { + case steps > 0 && base >=. 0.0 { + True -> { + let assert Ok(linspace) = yield_linear_space(start, stop, steps, endpoint) + Ok( yielder.map(linspace, fn(value) { let assert Ok(result) = float.power(base, value) @@ -5480,7 +5710,7 @@ pub fn logarithmic_space( /// /// /// -/// The function returns an iterator for generating a geometric progression between two specified +/// The function returns a list of 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 @@ -5504,7 +5734,7 @@ pub fn logarithmic_space( /// pub fn example () { /// let assert Ok(tolerance) = float.power(10.0, -6.0) /// let assert Ok(logspace) = maths.geometric_space(10.0, 1000.0, 3, True) -/// let pairs = logspace |> yielder.to_list() |> list.zip([10.0, 100.0, 1000.0]) +/// let pairs = logspace |> list.zip([10.0, 100.0, 1000.0]) /// let assert Ok(result) = maths.all_close(pairs, 0.0, tolerance) /// result /// |> list.all(fn(x) { x == True }) @@ -5534,7 +5764,7 @@ pub fn geometric_space( stop: Float, steps: Int, endpoint: Bool, -) -> Result(Yielder(Float), Nil) { +) -> Result(List(Float), Nil) { case start <=. 0.0 || stop <=. 0.0 || steps < 0 { True -> Error(Nil) False -> { @@ -5545,6 +5775,62 @@ pub fn geometric_space( } } +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function is similar to [`geometric_space`](#geometric_space) but instead returns a yielder +/// (lazily evaluated sequence of elements). This function can be used whenever there is a need +/// to generate a larger-than-usual sequence of elements. +/// +///
+/// Example: +/// +/// import gleam/yielder.{Next, Done} +/// import gleeunit/should +/// import gleam_community/maths +/// +/// pub fn example () { +/// let assert Ok(logspace) = maths.yield_geometric_space(10.0, 1000.0, 3, True) +/// +/// let assert Next(element, rest) = yielder.step(logspace) +/// should.equal(element, 10.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 100.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 1000.0) +/// +/// // We have generated 3 values, so the 4th will be 'Done' +/// should.equal(yielder.step(rest), Done) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn yield_geometric_space( + start: Float, + stop: Float, + steps: Int, + endpoint: Bool, +) -> Result(Yielder(Float), Nil) { + case start <=. 0.0 || stop <=. 0.0 || steps < 0 { + True -> Error(Nil) + False -> { + let assert Ok(log_start) = logarithm_10(start) + let assert Ok(log_stop) = logarithm_10(stop) + yield_logarithmic_space(log_start, log_stop, steps, endpoint, 10.0) + } + } +} + ///
/// /// Spot a typo? Open an issue! @@ -5557,20 +5843,17 @@ pub fn geometric_space( ///
/// Example: /// -/// import gleam/yielder /// import gleeunit/should /// import gleam_community/maths /// /// pub fn example() { -/// let assert Ok(sym_space) = maths.symmetric_space(0.0, 5.0, 5) -/// sym_space -/// |> yielder.to_list() +/// let assert Ok(symspace) = maths.symmetric_space(0.0, 5.0, 5) +/// symspace /// |> 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() +/// let assert Ok(symspace) = maths.symmetric_space(0.0, -5.0, 5) +/// symspace /// |> should.equal([5.0, 2.5, 0.0, -2.5, -5.0]) /// } ///
@@ -5585,7 +5868,7 @@ pub fn symmetric_space( center: Float, radius: Float, steps: Int, -) -> Result(Yielder(Float), Nil) { +) -> Result(List(Float), Nil) { case steps > 0 { False -> Error(Nil) True -> { @@ -5595,3 +5878,64 @@ pub fn symmetric_space( } } } + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function is similar to [`symmetric_space`](#symmetric_space) but instead returns a yielder +/// (lazily evaluated sequence of elements). This function can be used whenever there is a need +/// to generate a larger-than-usual sequence of elements. +/// +///
+/// Example: +/// +/// import gleam/yielder.{Next, Done} +/// import gleeunit/should +/// import gleam_community/maths +/// +/// pub fn example() { +/// let assert Ok(symspace) = maths.yield_symmetric_space(0.0, 5.0, 5) +/// +/// let assert Next(element, rest) = yielder.step(symspace) +/// should.equal(element, -5.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, -2.5) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 0.0) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 2.5) +/// +/// let assert Next(element, rest) = yielder.step(rest) +/// should.equal(element, 5.0) +/// +/// // We have generated 5 values, so the 6th will be 'Done' +/// should.equal(yielder.step(rest), Done) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn yield_symmetric_space( + center: Float, + radius: Float, + steps: Int, +) -> Result(Yielder(Float), Nil) { + case steps > 0 { + False -> Error(Nil) + True -> { + let start = center -. radius + let stop = center +. radius + yield_linear_space(start, stop, steps, True) + } + } +} diff --git a/test/gleam_community/sequences_test.gleam b/test/gleam_community/sequences_test.gleam index 2e4ff20..bb05a17 100644 --- a/test/gleam_community/sequences_test.gleam +++ b/test/gleam_community/sequences_test.gleam @@ -4,6 +4,138 @@ import gleam/yielder import gleam_community/maths import gleeunit/should +pub fn yield_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.yield_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.yield_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.yield_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.yield_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.yield_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.yield_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.yield_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.yield_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() + + // Check that when start == stop and steps > 0, then + // the value (start/stop) is just repeated, since the + // step increment will be 0 + let assert Ok(linspace) = maths.yield_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.yield_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() + + // A negative number of points does not work (-5) + maths.yield_linear_space(10.0, 50.0, -5, True) + |> should.be_error() +} + pub fn list_linear_space_test() { let assert Ok(tol) = float.power(10.0, -6.0) @@ -13,7 +145,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([10.0, 20.0, 30.0, 40.0, 50.0]), 0.0, tol, ) @@ -24,7 +156,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([10.0, 12.5, 15.0, 17.5, 20.0]), 0.0, tol, ) @@ -37,7 +169,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([10.0, 18.0, 26.0, 34.0, 42.0]), 0.0, tol, ) @@ -48,7 +180,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([10.0, 12.0, 14.0, 16.0, 18.0]), 0.0, tol, ) @@ -61,7 +193,6 @@ pub fn list_linear_space_test() { 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, @@ -74,7 +205,6 @@ pub fn list_linear_space_test() { 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, @@ -87,7 +217,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([-10.0, 2.0, 14.0, 26.0, 38.0]), 0.0, tol, ) @@ -98,7 +228,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([-10.0, -2.5, 5.0, 12.5, 20.0]), 0.0, tol, ) @@ -112,7 +242,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]), 0.0, tol, ) @@ -123,7 +253,7 @@ pub fn list_linear_space_test() { 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]), + linspace |> list.zip([10.0, 10.0, 10.0, 10.0, 10.0]), 0.0, tol, ) @@ -136,13 +266,14 @@ pub fn list_linear_space_test() { |> should.be_error() } -pub fn list_logarithmic_space_test() { +pub fn yield_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 - let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0) + let assert Ok(logspace) = + maths.yield_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]), @@ -154,7 +285,8 @@ pub fn list_logarithmic_space_test() { |> should.be_true() // - Positive start, negative stop - let assert Ok(logspace) = maths.logarithmic_space(1.0, -3.0, 3, True, 10.0) + let assert Ok(logspace) = + maths.yield_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]), @@ -166,7 +298,8 @@ pub fn list_logarithmic_space_test() { |> should.be_true() // - Positive stop, negative start - let assert Ok(logspace) = maths.logarithmic_space(-1.0, 3.0, 3, True, 10.0) + let assert Ok(logspace) = + maths.yield_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]), @@ -179,7 +312,8 @@ pub fn list_logarithmic_space_test() { // ----> Without endpoint included // - Positive start, stop - let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, False, 10.0) + let assert Ok(logspace) = + maths.yield_logarithmic_space(1.0, 3.0, 3, False, 10.0) let assert Ok(result) = maths.all_close( logspace @@ -195,7 +329,8 @@ pub fn list_logarithmic_space_test() { // Check that when start == stop and steps > 0, then // the value (start/stop) 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(logspace) = + maths.yield_logarithmic_space(5.0, 5.0, 5, True, 5.0) let assert Ok(result) = maths.all_close( logspace @@ -207,7 +342,8 @@ pub fn list_logarithmic_space_test() { 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(logspace) = + maths.yield_logarithmic_space(5.0, 5.0, 5, False, 5.0) let assert Ok(result) = maths.all_close( logspace @@ -220,6 +356,84 @@ pub fn list_logarithmic_space_test() { |> list.all(fn(x) { x == True }) |> should.be_true() + // A negative number of points does not work (-3) + maths.yield_logarithmic_space(1.0, 3.0, -3, True, 10.0) + |> should.be_error() + + // A negative base does not work (-10) + maths.yield_logarithmic_space(1.0, 3.0, 3, True, -10.0) + |> should.be_error() +} + +pub fn 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 + let assert Ok(logspace) = maths.logarithmic_space(1.0, 3.0, 3, True, 10.0) + let assert Ok(result) = + maths.all_close(logspace |> 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.logarithmic_space(1.0, -3.0, 3, True, 10.0) + let assert Ok(result) = + maths.all_close(logspace |> 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.logarithmic_space(-1.0, 3.0, 3, True, 10.0) + let assert Ok(result) = + maths.all_close(logspace |> 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.logarithmic_space(1.0, 3.0, 3, False, 10.0) + let assert Ok(result) = + maths.all_close( + logspace + |> list.zip([10.0, 46.41588834, 215.443469]), + 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) 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 + |> 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 + |> 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() @@ -229,13 +443,13 @@ pub fn list_logarithmic_space_test() { |> should.be_error() } -pub fn list_geometric_space_test() { +pub fn yield_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(logspace) = maths.yield_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]), @@ -247,7 +461,7 @@ pub fn list_geometric_space_test() { |> should.be_true() // - Positive start, negative stop - let assert Ok(logspace) = maths.geometric_space(10.0, 0.001, 3, True) + let assert Ok(logspace) = maths.yield_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]), @@ -259,7 +473,7 @@ pub fn list_geometric_space_test() { |> should.be_true() // - Positive stop, negative start - let assert Ok(logspace) = maths.geometric_space(0.1, 1000.0, 3, True) + let assert Ok(logspace) = maths.yield_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]), @@ -272,7 +486,7 @@ pub fn list_geometric_space_test() { // ----> Without endpoint included // - Positive start, stop - let assert Ok(logspace) = maths.geometric_space(10.0, 1000.0, 3, False) + let assert Ok(logspace) = maths.yield_geometric_space(10.0, 1000.0, 3, False) let assert Ok(result) = maths.all_close( logspace @@ -288,7 +502,7 @@ pub fn list_geometric_space_test() { // Check that when start == stop and steps > 0, then // the value (start/stop) 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(logspace) = maths.yield_geometric_space(5.0, 5.0, 5, True) let assert Ok(result) = maths.all_close( logspace @@ -301,7 +515,7 @@ pub fn list_geometric_space_test() { |> 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(logspace) = maths.yield_geometric_space(5.0, 5.0, 5, False) let assert Ok(result) = maths.all_close( logspace @@ -314,6 +528,88 @@ pub fn list_geometric_space_test() { |> 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.yield_geometric_space(0.0, 1000.0, 3, False) + |> should.be_error() + + maths.yield_geometric_space(-1000.0, 0.0, 3, False) + |> should.be_error() + + // A negative number of points does not work + maths.yield_geometric_space(-1000.0, 0.0, -3, False) + |> should.be_error() +} + +pub fn 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 |> 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 |> 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 |> 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 + |> list.zip([10.0, 46.41588834, 215.443469]), + 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) 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 + |> 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 + |> 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() @@ -326,46 +622,132 @@ pub fn list_geometric_space_test() { |> should.be_error() } -pub fn list_arange_test() { +pub fn list_step_range_test() { + // Positive start, stop, step + maths.step_range(1.0, 5.0, 1.0) + |> should.equal([1.0, 2.0, 3.0, 4.0]) + + maths.step_range(1.0, 5.0, 0.5) + |> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]) + + maths.step_range(1.0, 2.0, 0.25) + |> should.equal([1.0, 1.25, 1.5, 1.75]) + + // Reverse (switch start/stop largest/smallest value) + maths.step_range(5.0, 1.0, 1.0) + |> should.equal([]) + + // Reverse negative step + maths.step_range(5.0, 1.0, -1.0) + |> should.equal([5.0, 4.0, 3.0, 2.0]) + + // Positive start, negative stop, step + maths.step_range(5.0, -1.0, -1.0) + |> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) + + // Negative start, stop, step + maths.step_range(-5.0, -1.0, -1.0) + |> should.equal([]) + + // Negative start, stop, positive step + maths.step_range(-5.0, -1.0, 1.0) + |> should.equal([-5.0, -4.0, -3.0, -2.0]) +} + +pub fn yield_step_range_test() { // Positive start, stop, step - maths.arange(1.0, 5.0, 1.0) + maths.yield_step_range(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) + maths.yield_step_range(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) + maths.yield_step_range(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) + maths.yield_step_range(5.0, 1.0, 1.0) |> yielder.to_list() |> should.equal([]) // Reverse negative step - maths.arange(5.0, 1.0, -1.0) + maths.yield_step_range(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) + maths.yield_step_range(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) + maths.yield_step_range(-5.0, -1.0, -1.0) |> yielder.to_list() |> should.equal([]) // Negative start, stop, positive step - maths.arange(-5.0, -1.0, 1.0) + maths.yield_step_range(-5.0, -1.0, 1.0) |> yielder.to_list() |> should.equal([-5.0, -4.0, -3.0, -2.0]) } +pub fn yield_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.yield_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.yield_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.yield_symmetric_space(-10.0, 5.0, 5) + sym_space + |> 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.yield_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.yield_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() + + // 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.yield_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.yield_symmetric_space(0.0, 5.0, -5) + |> should.be_error() +} + pub fn list_symmetric_space_test() { let assert Ok(tolerance) = float.power(10.0, -6.0) @@ -373,24 +755,20 @@ pub fn list_symmetric_space_test() { // 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]) // 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 @@ -398,7 +776,6 @@ pub fn list_symmetric_space_test() { 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, @@ -412,7 +789,6 @@ pub fn list_symmetric_space_test() { // 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)