Skip to content

Commit cedbdf9

Browse files
authored
Merge pull request #630 from jturner314/improve-space-constructors
Improve linspace, range, logspace, geomspace
2 parents 6b56adf + 0295d46 commit cedbdf9

File tree

4 files changed

+85
-67
lines changed

4 files changed

+85
-67
lines changed

src/geomspace.rs

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,41 +66,40 @@ impl<F> ExactSizeIterator for Geomspace<F> where Geomspace<F>: Iterator {}
6666

6767
/// An iterator of a sequence of geometrically spaced values.
6868
///
69-
/// The `Geomspace` has `n` elements, where the first element is `a` and the
70-
/// last element is `b`.
69+
/// The `Geomspace` has `n` geometrically spaced elements from `start` to `end`
70+
/// (inclusive).
7171
///
72-
/// Iterator element type is `F`, where `F` must be either `f32` or `f64`.
72+
/// The iterator element type is `F`, where `F` must implement `Float`, e.g.
73+
/// `f32` or `f64`.
7374
///
74-
/// **Panics** if the interval `[a, b]` contains zero (including the end points).
75+
/// Returns `None` if `start` and `end` have different signs or if either one
76+
/// is zero. Conceptually, this means that in order to obtain a `Some` result,
77+
/// `end / start` must be positive.
78+
///
79+
/// **Panics** if converting `n - 1` to type `F` fails.
7580
#[inline]
76-
pub fn geomspace<F>(a: F, b: F, n: usize) -> Geomspace<F>
81+
pub fn geomspace<F>(a: F, b: F, n: usize) -> Option<Geomspace<F>>
7782
where
7883
F: Float,
7984
{
80-
assert!(
81-
a != F::zero() && b != F::zero(),
82-
"Start and/or end of geomspace cannot be zero.",
83-
);
84-
assert!(
85-
a.is_sign_negative() == b.is_sign_negative(),
86-
"Logarithmic interval cannot cross 0."
87-
);
88-
85+
if a == F::zero() || b == F::zero() || a.is_sign_negative() != b.is_sign_negative() {
86+
return None;
87+
}
8988
let log_a = a.abs().ln();
9089
let log_b = b.abs().ln();
9190
let step = if n > 1 {
92-
let nf: F = F::from(n).unwrap();
93-
(log_b - log_a) / (nf - F::one())
91+
let num_steps = F::from(n - 1).expect("Converting number of steps to `A` must not fail.");
92+
(log_b - log_a) / num_steps
9493
} else {
9594
F::zero()
9695
};
97-
Geomspace {
96+
Some(Geomspace {
9897
sign: a.signum(),
9998
start: log_a,
10099
step: step,
101100
index: 0,
102101
len: n,
103-
}
102+
})
104103
}
105104

106105
#[cfg(test)]
@@ -113,22 +112,22 @@ mod tests {
113112
use approx::assert_abs_diff_eq;
114113
use crate::{arr1, Array1};
115114

116-
let array: Array1<_> = geomspace(1e0, 1e3, 4).collect();
115+
let array: Array1<_> = geomspace(1e0, 1e3, 4).unwrap().collect();
117116
assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12);
118117

119-
let array: Array1<_> = geomspace(1e3, 1e0, 4).collect();
118+
let array: Array1<_> = geomspace(1e3, 1e0, 4).unwrap().collect();
120119
assert_abs_diff_eq!(array, arr1(&[1e3, 1e2, 1e1, 1e0]), epsilon = 1e-12);
121120

122-
let array: Array1<_> = geomspace(-1e3, -1e0, 4).collect();
121+
let array: Array1<_> = geomspace(-1e3, -1e0, 4).unwrap().collect();
123122
assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12);
124123

125-
let array: Array1<_> = geomspace(-1e0, -1e3, 4).collect();
124+
let array: Array1<_> = geomspace(-1e0, -1e3, 4).unwrap().collect();
126125
assert_abs_diff_eq!(array, arr1(&[-1e0, -1e1, -1e2, -1e3]), epsilon = 1e-12);
127126
}
128127

129128
#[test]
130129
fn iter_forward() {
131-
let mut iter = geomspace(1.0f64, 1e3, 4);
130+
let mut iter = geomspace(1.0f64, 1e3, 4).unwrap();
132131

133132
assert!(iter.size_hint() == (4, Some(4)));
134133

@@ -143,7 +142,7 @@ mod tests {
143142

144143
#[test]
145144
fn iter_backward() {
146-
let mut iter = geomspace(1.0f64, 1e3, 4);
145+
let mut iter = geomspace(1.0f64, 1e3, 4).unwrap();
147146

148147
assert!(iter.size_hint() == (4, Some(4)));
149148

@@ -157,20 +156,17 @@ mod tests {
157156
}
158157

159158
#[test]
160-
#[should_panic]
161159
fn zero_lower() {
162-
geomspace(0.0, 1.0, 4);
160+
assert!(geomspace(0.0, 1.0, 4).is_none());
163161
}
164162

165163
#[test]
166-
#[should_panic]
167164
fn zero_upper() {
168-
geomspace(1.0, 0.0, 4);
165+
assert!(geomspace(1.0, 0.0, 4).is_none());
169166
}
170167

171168
#[test]
172-
#[should_panic]
173169
fn zero_included() {
174-
geomspace(-1.0, 1.0, 4);
170+
assert!(geomspace(-1.0, 1.0, 4).is_none());
175171
}
176172
}

src/impl_constructors.rs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,16 @@ impl<S, A> ArrayBase<S, Ix1>
6767
Self::from_vec(iterable.into_iter().collect())
6868
}
6969

70-
/// Create a one-dimensional array from the inclusive interval
71-
/// `[start, end]` with `n` elements. `A` must be a floating point type.
70+
/// Create a one-dimensional array with `n` evenly spaced elements from
71+
/// `start` to `end` (inclusive). `A` must be a floating point type.
7272
///
73-
/// **Panics** if `n` is greater than `isize::MAX`.
73+
/// Note that if `start > end`, the first element will still be `start`,
74+
/// and the following elements will be decreasing. This is different from
75+
/// the behavior of `std::ops::RangeInclusive`, which interprets `start >
76+
/// end` to mean that the range is empty.
77+
///
78+
/// **Panics** if `n` is greater than `isize::MAX` or if converting `n - 1`
79+
/// to type `A` fails.
7480
///
7581
/// ```rust
7682
/// use ndarray::{Array, arr1};
@@ -84,9 +90,8 @@ impl<S, A> ArrayBase<S, Ix1>
8490
Self::from_vec(to_vec(linspace::linspace(start, end, n)))
8591
}
8692

87-
/// Create a one-dimensional array from the half-open interval
88-
/// `[start, end)` with elements spaced by `step`. `A` must be a floating
89-
/// point type.
93+
/// Create a one-dimensional array with elements from `start` to `end`
94+
/// (exclusive), incrementing by `step`. `A` must be a floating point type.
9095
///
9196
/// **Panics** if the length is greater than `isize::MAX`.
9297
///
@@ -102,13 +107,14 @@ impl<S, A> ArrayBase<S, Ix1>
102107
Self::from_vec(to_vec(linspace::range(start, end, step)))
103108
}
104109

105-
/// Create a one-dimensional array with `n` elements logarithmically spaced,
106-
/// with the starting value being `base.powf(start)` and the final one being
107-
/// `base.powf(end)`. `A` must be a floating point type.
110+
/// Create a one-dimensional array with `n` logarithmically spaced
111+
/// elements, with the starting value being `base.powf(start)` and the
112+
/// final one being `base.powf(end)`. `A` must be a floating point type.
108113
///
109114
/// If `base` is negative, all values will be negative.
110115
///
111-
/// **Panics** if the length is greater than `isize::MAX`.
116+
/// **Panics** if `n` is greater than `isize::MAX` or if converting `n - 1`
117+
/// to type `A` fails.
112118
///
113119
/// ```rust
114120
/// use approx::assert_abs_diff_eq;
@@ -129,32 +135,38 @@ impl<S, A> ArrayBase<S, Ix1>
129135
Self::from_vec(to_vec(logspace::logspace(base, start, end, n)))
130136
}
131137

132-
/// Create a one-dimensional array from the inclusive interval `[start,
133-
/// end]` with `n` elements geometrically spaced. `A` must be a floating
134-
/// point type.
138+
/// Create a one-dimensional array with `n` geometrically spaced elements
139+
/// from `start` to `end` (inclusive). `A` must be a floating point type.
135140
///
136-
/// The interval can be either all positive or all negative; however, it
137-
/// cannot contain 0 (including the end points).
141+
/// Returns `None` if `start` and `end` have different signs or if either
142+
/// one is zero. Conceptually, this means that in order to obtain a `Some`
143+
/// result, `end / start` must be positive.
138144
///
139-
/// **Panics** if `n` is greater than `isize::MAX`.
145+
/// **Panics** if `n` is greater than `isize::MAX` or if converting `n - 1`
146+
/// to type `A` fails.
140147
///
141148
/// ```rust
142149
/// use approx::assert_abs_diff_eq;
143150
/// use ndarray::{Array, arr1};
144151
///
152+
/// # fn example() -> Option<()> {
145153
/// # #[cfg(feature = "approx")] {
146-
/// let array = Array::geomspace(1e0, 1e3, 4);
154+
/// let array = Array::geomspace(1e0, 1e3, 4)?;
147155
/// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12);
148156
///
149-
/// let array = Array::geomspace(-1e3, -1e0, 4);
157+
/// let array = Array::geomspace(-1e3, -1e0, 4)?;
150158
/// assert_abs_diff_eq!(array, arr1(&[-1e3, -1e2, -1e1, -1e0]), epsilon = 1e-12);
151159
/// # }
160+
/// # Some(())
161+
/// # }
162+
/// #
163+
/// # fn main() { example().unwrap() }
152164
/// ```
153-
pub fn geomspace(start: A, end: A, n: usize) -> Self
165+
pub fn geomspace(start: A, end: A, n: usize) -> Option<Self>
154166
where
155167
A: Float,
156168
{
157-
Self::from_vec(to_vec(geomspace::geomspace(start, end, n)))
169+
Some(Self::from_vec(to_vec(geomspace::geomspace(start, end, n)?)))
158170
}
159171
}
160172

src/linspace.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,19 @@ impl<F> ExactSizeIterator for Linspace<F>
6363

6464
/// Return an iterator of evenly spaced floats.
6565
///
66-
/// The `Linspace` has `n` elements, where the first
67-
/// element is `a` and the last element is `b`.
66+
/// The `Linspace` has `n` elements from `a` to `b` (inclusive).
6867
///
69-
/// Iterator element type is `F`, where `F` must be
70-
/// either `f32` or `f64`.
68+
/// The iterator element type is `F`, where `F` must implement `Float`, e.g.
69+
/// `f32` or `f64`.
70+
///
71+
/// **Panics** if converting `n - 1` to type `F` fails.
7172
#[inline]
7273
pub fn linspace<F>(a: F, b: F, n: usize) -> Linspace<F>
7374
where F: Float
7475
{
7576
let step = if n > 1 {
76-
let nf: F = F::from(n).unwrap();
77-
(b - a) / (nf - F::one())
77+
let num_steps = F::from(n - 1).expect("Converting number of steps to `A` must not fail.");
78+
(b - a) / num_steps
7879
} else {
7980
F::zero()
8081
};
@@ -86,13 +87,15 @@ pub fn linspace<F>(a: F, b: F, n: usize) -> Linspace<F>
8687
}
8788
}
8889

89-
/// Return an iterator of floats spaced by `step`, from
90-
/// the half-open interval [a, b).
91-
/// Numerical reasons can result in `b` being included
92-
/// in the result.
90+
/// Return an iterator of floats from `start` to `end` (exclusive),
91+
/// incrementing by `step`.
92+
///
93+
/// Numerical reasons can result in `b` being included in the result.
94+
///
95+
/// The iterator element type is `F`, where `F` must implement `Float`, e.g.
96+
/// `f32` or `f64`.
9397
///
94-
/// Iterator element type is `F`, where `F` must be
95-
/// either `f32` or `f64`.
98+
/// **Panics** if converting `((b - a) / step).ceil()` to type `F` fails.
9699
#[inline]
97100
pub fn range<F>(a: F, b: F, step: F) -> Linspace<F>
98101
where F: Float
@@ -102,7 +105,11 @@ pub fn range<F>(a: F, b: F, step: F) -> Linspace<F>
102105
Linspace {
103106
start: a,
104107
step: step,
105-
len: steps.to_usize().unwrap(),
108+
len: steps.to_usize().expect(
109+
"Converting the length to `usize` must not fail. The most likely \
110+
cause of this failure is if the sign of `end - start` is \
111+
different from the sign of `step`.",
112+
),
106113
index: 0,
107114
}
108115
}

src/logspace.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,24 @@ where
6565

6666
impl<F> ExactSizeIterator for Logspace<F> where Logspace<F>: Iterator {}
6767

68-
/// An iterator of a sequence of logarithmically spaced number.
68+
/// An iterator of a sequence of logarithmically spaced numbers.
6969
///
7070
/// The `Logspace` has `n` elements, where the first element is `base.powf(a)`
7171
/// and the last element is `base.powf(b)`. If `base` is negative, this
7272
/// iterator will return all negative values.
7373
///
74-
/// Iterator element type is `F`, where `F` must be either `f32` or `f64`.
74+
/// The iterator element type is `F`, where `F` must implement `Float`, e.g.
75+
/// `f32` or `f64`.
76+
///
77+
/// **Panics** if converting `n - 1` to type `F` fails.
7578
#[inline]
7679
pub fn logspace<F>(base: F, a: F, b: F, n: usize) -> Logspace<F>
7780
where
7881
F: Float,
7982
{
8083
let step = if n > 1 {
81-
let nf: F = F::from(n).unwrap();
82-
(b - a) / (nf - F::one())
84+
let num_steps = F::from(n - 1).expect("Converting number of steps to `A` must not fail.");
85+
(b - a) / num_steps
8386
} else {
8487
F::zero()
8588
};

0 commit comments

Comments
 (0)