Skip to content

Commit

Permalink
Add logspace and geomspace constructors (#617)
Browse files Browse the repository at this point in the history
* Add `logspace` constructor for Array1

Signed-off-by: JP-Ellis <josh@jpellis.me>

* Add logspace tests

Signed-off-by: JP-Ellis <josh@jpellis.me>

* More extensive logspace tests

Signed-off-by: JP-Ellis <josh@jpellis.me>

* Remove print statements in doc tests

Signed-off-by: JP-Ellis <josh@jpellis.me>

* Add `geomspace` and make `logspace` compatible with NumPy

Signed-off-by: JP-Ellis <josh@jpellis.me>
  • Loading branch information
JP-Ellis authored and LukeMathWalker committed May 4, 2019
1 parent 61b4835 commit 3656ddf
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 23 deletions.
173 changes: 173 additions & 0 deletions src/geomspace.rs
@@ -0,0 +1,173 @@
// Copyright 2014-2016 bluss and ndarray developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<F> {
sign: F,
start: F,
step: F,
index: usize,
len: usize,
}

impl<F> Iterator for Geomspace<F>
where
F: Float,
{
type Item = F;

#[inline]
fn next(&mut self) -> Option<F> {
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<usize>) {
let n = self.len - self.index;
(n, Some(n))
}
}

impl<F> DoubleEndedIterator for Geomspace<F>
where
F: Float,
{
#[inline]
fn next_back(&mut self) -> Option<F> {
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<F> ExactSizeIterator for Geomspace<F> where Geomspace<F>: 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<F>(a: F, b: F, n: usize) -> Geomspace<F>
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);
}
}
59 changes: 54 additions & 5 deletions src/impl_constructors.rs
Expand Up @@ -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
///
Expand Down Expand Up @@ -101,6 +101,55 @@ impl<S, A> ArrayBase<S, Ix1>
{
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.
Expand Down
30 changes: 15 additions & 15 deletions src/iterators/mod.rs
Expand Up @@ -1311,25 +1311,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<F> TrustedIterator for Linspace<F> { }
unsafe impl<'a, A, D> TrustedIterator for Iter<'a, A, D> { }
unsafe impl<'a, A, D> TrustedIterator for IterMut<'a, A, D> { }
unsafe impl<I, F> TrustedIterator for std::iter::Map<I, F>
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<usize> { }
unsafe impl<F> TrustedIterator for Geomspace<F> {}
unsafe impl<F> TrustedIterator for Linspace<F> {}
unsafe impl<F> TrustedIterator for Logspace<F> {}
unsafe impl<'a, A, D> TrustedIterator for Iter<'a, A, D> {}
unsafe impl<'a, A, D> TrustedIterator for IterMut<'a, A, D> {}
unsafe impl<I, F> TrustedIterator for std::iter::Map<I, F> 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<usize> {}
// FIXME: These indices iter are dubious -- size needs to be checked up front.
unsafe impl<D> TrustedIterator for IndicesIter<D> where D: Dimension { }
unsafe impl<D> TrustedIterator for IndicesIterF<D> where D: Dimension { }

unsafe impl<D> TrustedIterator for IndicesIter<D> where D: Dimension {}
unsafe impl<D> TrustedIterator for IndicesIterF<D> where D: Dimension {}

/// Like Iterator::collect, but only for trusted length iterators
pub fn to_vec<I>(iter: I) -> Vec<I::Item>
Expand Down
9 changes: 6 additions & 3 deletions src/lib.rs
Expand Up @@ -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;
Expand Down

0 comments on commit 3656ddf

Please sign in to comment.