diff --git a/src/geomspace.rs b/src/geomspace.rs new file mode 100644 index 000000000..f04d44889 --- /dev/null +++ b/src/geomspace.rs @@ -0,0 +1,173 @@ +// Copyright 2014-2016 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use num_traits::Float; + +/// An iterator of a sequence of geometrically spaced floats. +/// +/// Iterator element type is `F`. +pub struct Geomspace { + sign: F, + start: F, + step: F, + index: usize, + len: usize, +} + +impl Iterator for Geomspace +where + F: Float, +{ + type Item = F; + + #[inline] + fn next(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + let i = self.index; + self.index += 1; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * exponent.exp()) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let n = self.len - self.index; + (n, Some(n)) + } +} + +impl DoubleEndedIterator for Geomspace +where + F: Float, +{ + #[inline] + fn next_back(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + self.len -= 1; + let i = self.len; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * exponent.exp()) + } + } +} + +impl ExactSizeIterator for Geomspace where Geomspace: Iterator {} + +/// An iterator of a sequence of geometrically spaced values. +/// +/// The `Geomspace` has `n` elements, where the first element is `a` and the +/// last element is `b`. +/// +/// Iterator element type is `F`, where `F` must be either `f32` or `f64`. +/// +/// **Panics** if the interval `[a, b]` contains zero (including the end points). +#[inline] +pub fn geomspace(a: F, b: F, n: usize) -> Geomspace +where + F: Float, +{ + assert!( + a != F::zero() && b != F::zero(), + "Start and/or end of geomspace cannot be zero.", + ); + assert!( + a.is_sign_negative() == b.is_sign_negative(), + "Logarithmic interval cannot cross 0." + ); + + let log_a = a.abs().ln(); + let log_b = b.abs().ln(); + let step = if n > 1 { + let nf: F = F::from(n).unwrap(); + (log_b - log_a) / (nf - F::one()) + } else { + F::zero() + }; + Geomspace { + sign: a.signum(), + start: log_a, + step: step, + index: 0, + len: n, + } +} + +#[cfg(test)] +mod tests { + use super::geomspace; + use crate::{arr1, Array1}; + + #[test] + fn valid() { + let array: Array1<_> = geomspace(1e0, 1e3, 4).collect(); + assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + + let array: Array1<_> = geomspace(1e3, 1e0, 4).collect(); + assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); + + let array: Array1<_> = geomspace(-1e3, -1e0, 4).collect(); + assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + + let array: Array1<_> = geomspace(-1e0, -1e3, 4).collect(); + assert!(array.all_close(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); + } + + #[test] + fn iter_forward() { + let mut iter = geomspace(1.0f64, 1e3, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next().unwrap() - 1e0).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e3).abs() < 1e-5); + assert!(iter.next().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } + + #[test] + fn iter_backward() { + let mut iter = geomspace(1.0f64, 1e3, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next_back().unwrap() - 1e3).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e0).abs() < 1e-5); + assert!(iter.next_back().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } + + #[test] + #[should_panic] + fn zero_lower() { + geomspace(0.0, 1.0, 4); + } + + #[test] + #[should_panic] + fn zero_upper() { + geomspace(1.0, 0.0, 4); + } + + #[test] + #[should_panic] + fn zero_included() { + geomspace(-1.0, 1.0, 4); + } +} diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index 1c2b84439..a57dcfff8 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -10,18 +10,18 @@ //! //! -use num_traits::{Zero, One, Float}; +use num_traits::{Float, One, Zero}; use std::isize; use std::mem; -use crate::imp_prelude::*; -use crate::StrideShape; use crate::dimension; -use crate::linspace; use crate::error::{self, ShapeError}; -use crate::indices; +use crate::imp_prelude::*; use crate::indexes; +use crate::indices; use crate::iterators::{to_vec, to_vec_mapped}; +use crate::StrideShape; +use crate::{linspace, geomspace, logspace}; /// # Constructor Methods for Owned Arrays /// @@ -101,6 +101,55 @@ impl ArrayBase { Self::from_vec(to_vec(linspace::range(start, end, step))) } + + /// Create a one-dimensional array with `n` elements logarithmically spaced, + /// with the starting value being `base.powf(start)` and the final one being + /// `base.powf(end)`. `A` must be a floating point type. + /// + /// If `base` is negative, all values will be negative. + /// + /// **Panics** if the length is greater than `isize::MAX`. + /// + /// ```rust + /// use ndarray::{Array, arr1}; + /// + /// let array = Array::logspace(10.0, 0.0, 3.0, 4); + /// assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + /// + /// let array = Array::logspace(-10.0, 3.0, 0.0, 4); + /// assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// ``` + pub fn logspace(base: A, start: A, end: A, n: usize) -> Self + where + A: Float, + { + Self::from_vec(to_vec(logspace::logspace(base, start, end, n))) + } + + /// Create a one-dimensional array from the inclusive interval `[start, + /// end]` with `n` elements geometrically spaced. `A` must be a floating + /// point type. + /// + /// The interval can be either all positive or all negative; however, it + /// cannot contain 0 (including the end points). + /// + /// **Panics** if `n` is greater than `isize::MAX`. + /// + /// ```rust + /// use ndarray::{Array, arr1}; + /// + /// let array = Array::geomspace(1e0, 1e3, 4); + /// assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + /// + /// let array = Array::geomspace(-1e3, -1e0, 4); + /// assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + /// ``` + pub fn geomspace(start: A, end: A, n: usize) -> Self + where + A: Float, + { + Self::from_vec(to_vec(geomspace::geomspace(start, end, n))) + } } /// ## Constructor methods for two-dimensional arrays. diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index b9aab1aa9..17e137543 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -1214,25 +1214,25 @@ send_sync_read_write!(ElementsBaseMut); /// (Trait used internally) An iterator that we trust /// to deliver exactly as many items as it said it would. -pub unsafe trait TrustedIterator { } +pub unsafe trait TrustedIterator {} -use std; -use crate::linspace::Linspace; -use crate::iter::IndicesIter; use crate::indexes::IndicesIterF; +use crate::iter::IndicesIter; +use crate::{geomspace::Geomspace, linspace::Linspace, logspace::Logspace}; +use std; -unsafe impl TrustedIterator for Linspace { } -unsafe impl<'a, A, D> TrustedIterator for Iter<'a, A, D> { } -unsafe impl<'a, A, D> TrustedIterator for IterMut<'a, A, D> { } -unsafe impl TrustedIterator for std::iter::Map - where I: TrustedIterator { } -unsafe impl<'a, A> TrustedIterator for slice::Iter<'a, A> { } -unsafe impl<'a, A> TrustedIterator for slice::IterMut<'a, A> { } -unsafe impl TrustedIterator for ::std::ops::Range { } +unsafe impl TrustedIterator for Geomspace {} +unsafe impl TrustedIterator for Linspace {} +unsafe impl TrustedIterator for Logspace {} +unsafe impl<'a, A, D> TrustedIterator for Iter<'a, A, D> {} +unsafe impl<'a, A, D> TrustedIterator for IterMut<'a, A, D> {} +unsafe impl TrustedIterator for std::iter::Map where I: TrustedIterator {} +unsafe impl<'a, A> TrustedIterator for slice::Iter<'a, A> {} +unsafe impl<'a, A> TrustedIterator for slice::IterMut<'a, A> {} +unsafe impl TrustedIterator for ::std::ops::Range {} // FIXME: These indices iter are dubious -- size needs to be checked up front. -unsafe impl TrustedIterator for IndicesIter where D: Dimension { } -unsafe impl TrustedIterator for IndicesIterF where D: Dimension { } - +unsafe impl TrustedIterator for IndicesIter where D: Dimension {} +unsafe impl TrustedIterator for IndicesIterF where D: Dimension {} /// Like Iterator::collect, but only for trusted length iterators pub fn to_vec(iter: I) -> Vec diff --git a/src/lib.rs b/src/lib.rs index 8b44c21d2..5cb59ae5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,15 +167,18 @@ mod free_functions; pub use crate::free_functions::*; pub use crate::iterators::iter; -#[macro_use] mod slice; -mod layout; +mod error; +mod geomspace; mod indexes; mod iterators; +mod layout; mod linalg_traits; mod linspace; +mod logspace; mod numeric_util; -mod error; mod shape_builder; +#[macro_use] +mod slice; mod stacking; #[macro_use] mod zip; diff --git a/src/logspace.rs b/src/logspace.rs new file mode 100644 index 000000000..fe62e9990 --- /dev/null +++ b/src/logspace.rs @@ -0,0 +1,145 @@ +// Copyright 2014-2016 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use num_traits::Float; + +/// An iterator of a sequence of logarithmically spaced number. +/// +/// Iterator element type is `F`. +pub struct Logspace { + sign: F, + base: F, + start: F, + step: F, + index: usize, + len: usize, +} + +impl Iterator for Logspace +where + F: Float, +{ + type Item = F; + + #[inline] + fn next(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + let i = self.index; + self.index += 1; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * self.base.powf(exponent)) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let n = self.len - self.index; + (n, Some(n)) + } +} + +impl DoubleEndedIterator for Logspace +where + F: Float, +{ + #[inline] + fn next_back(&mut self) -> Option { + if self.index >= self.len { + None + } else { + // Calculate the value just like numpy.linspace does + self.len -= 1; + let i = self.len; + let exponent = self.start + self.step * F::from(i).unwrap(); + Some(self.sign * self.base.powf(exponent)) + } + } +} + +impl ExactSizeIterator for Logspace where Logspace: Iterator {} + +/// An iterator of a sequence of logarithmically spaced number. +/// +/// The `Logspace` has `n` elements, where the first element is `base.powf(a)` +/// and the last element is `base.powf(b)`. If `base` is negative, this +/// iterator will return all negative values. +/// +/// Iterator element type is `F`, where `F` must be either `f32` or `f64`. +#[inline] +pub fn logspace(base: F, a: F, b: F, n: usize) -> Logspace +where + F: Float, +{ + let step = if n > 1 { + let nf: F = F::from(n).unwrap(); + (b - a) / (nf - F::one()) + } else { + F::zero() + }; + Logspace { + sign: base.signum(), + base: base.abs(), + start: a, + step: step, + index: 0, + len: n, + } +} + +#[cfg(test)] +mod tests { + use super::logspace; + use crate::{arr1, Array1}; + + #[test] + fn valid() { + let array: Array1<_> = logspace(10.0, 0.0, 3.0, 4).collect(); + assert!(array.all_close(&arr1(&[1e0, 1e1, 1e2, 1e3]), 1e-5)); + + let array: Array1<_> = logspace(10.0, 3.0, 0.0, 4).collect(); + assert!(array.all_close(&arr1(&[1e3, 1e2, 1e1, 1e0]), 1e-5)); + + let array: Array1<_> = logspace(-10.0, 3.0, 0.0, 4).collect(); + assert!(array.all_close(&arr1(&[-1e3, -1e2, -1e1, -1e0]), 1e-5)); + + let array: Array1<_> = logspace(-10.0, 0.0, 3.0, 4).collect(); + assert!(array.all_close(&arr1(&[-1e0, -1e1, -1e2, -1e3]), 1e-5)); + } + + #[test] + fn iter_forward() { + let mut iter = logspace(10.0f64, 0.0, 3.0, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next().unwrap() - 1e0).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next().unwrap() - 1e3).abs() < 1e-5); + assert!(iter.next().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } + + #[test] + fn iter_backward() { + let mut iter = logspace(10.0f64, 0.0, 3.0, 4); + + assert!(iter.size_hint() == (4, Some(4))); + + assert!((iter.next_back().unwrap() - 1e3).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e2).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e1).abs() < 1e-5); + assert!((iter.next_back().unwrap() - 1e0).abs() < 1e-5); + assert!(iter.next_back().is_none()); + + assert!(iter.size_hint() == (0, Some(0))); + } +}