ndarray_interp/interp1d/
mod.rs

1//! A collection of structs and traits to interpolate data along the first axis
2//!
3//! # Interpolator
4//!  - [`Interp1D`] The interpolator used with any strategy
5//!  - [`Interp1DBuilder`] Configure the interpolator
6//!
7//! # Traits
8//!  - [`Interp1DStrategy`] The trait used to specialize [`Interp1D`] with the correct strategy
9//!  - [`Interp1DStrategyBuilder`] The trait used to specialize [`Interp1DBuilder`] to initialize the correct strategy
10//!
11//! # Strategies
12//!  - [`Linear`] Linear interpolation strategy
13//!  - [`cubic_spline`] Cubic spline interpolation strategy
14
15use std::{any::TypeId, fmt::Debug, ops::Sub};
16
17use ndarray::{
18    Array, ArrayBase, ArrayView, ArrayViewMut, ArrayViewMut1, Axis, AxisDescription, Data, DimAdd,
19    Dimension, IntoDimension, Ix1, OwnedRepr, RemoveAxis, Slice, Zip,
20};
21use num_traits::{cast, Num, NumCast};
22
23use crate::{
24    cast_unchecked,
25    dim_extensions::DimExtension,
26    vector_extensions::{Monotonic, VectorExtensions},
27    BuilderError, InterpolateError,
28};
29
30mod aliases;
31mod strategies;
32pub use aliases::*;
33pub use strategies::cubic_spline;
34pub use strategies::linear::Linear;
35pub use strategies::{Interp1DStrategy, Interp1DStrategyBuilder};
36
37/// One dimensional interpolator
38#[derive(Debug)]
39pub struct Interp1D<Sd, Sx, D, Strat>
40where
41    Sd: Data,
42    Sd::Elem: Num + Debug + Send,
43    Sx: Data<Elem = Sd::Elem>,
44    D: Dimension,
45    Strat: Interp1DStrategy<Sd, Sx, D>,
46{
47    /// x values are guaranteed to be strict monotonically rising
48    x: ArrayBase<Sx, Ix1>,
49    data: ArrayBase<Sd, D>,
50    strategy: Strat,
51}
52
53/// Create and configure a [Interp1D] Interpolator.
54///
55/// # Default configuration
56/// In the default configuration the interpolation strategy is [`Linear{extrapolate: false}`].
57/// The data will be interpolated along [`Axis(0)`] (currently this can not be changed).
58/// The index to `Axis(0)` of the data will be used as x values.
59#[derive(Debug)]
60pub struct Interp1DBuilder<Sd, Sx, D, Strat>
61where
62    Sd: Data,
63    Sd::Elem: Num + Debug,
64    Sx: Data<Elem = Sd::Elem>,
65    D: Dimension,
66{
67    x: ArrayBase<Sx, Ix1>,
68    data: ArrayBase<Sd, D>,
69    strategy: Strat,
70}
71
72impl<Sd, D> Interp1D<Sd, OwnedRepr<Sd::Elem>, D, Linear>
73where
74    Sd: Data,
75    Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug + Send,
76    D: Dimension + RemoveAxis,
77{
78    /// Get the [Interp1DBuilder]
79    pub fn builder(data: ArrayBase<Sd, D>) -> Interp1DBuilder<Sd, OwnedRepr<Sd::Elem>, D, Linear> {
80        Interp1DBuilder::new(data)
81    }
82}
83
84impl<Sd, Sx, Strat> Interp1D<Sd, Sx, Ix1, Strat>
85where
86    Sd: Data,
87    Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug + Sub + Send,
88    Sx: Data<Elem = Sd::Elem>,
89    Strat: Interp1DStrategy<Sd, Sx, Ix1>,
90{
91    /// convinient interpolation function for interpolation at one point
92    /// when the data dimension is [`type@Ix1`]
93    ///
94    /// ```rust
95    /// # use ndarray_interp::*;
96    /// # use ndarray_interp::interp1d::*;
97    /// # use ndarray::*;
98    /// # use approx::*;
99    /// let data = array![1.0, 1.5, 2.0];
100    /// let x =    array![1.0, 2.0, 3.0];
101    /// let query = 1.5;
102    /// let expected = 1.25;
103    ///
104    /// let interpolator = Interp1DBuilder::new(data).x(x).build().unwrap();
105    /// let result = interpolator.interp_scalar(query).unwrap();
106    /// # assert_eq!(result, expected);
107    /// ```
108    pub fn interp_scalar(&self, x: Sx::Elem) -> Result<Sd::Elem, InterpolateError> {
109        let mut buffer: [Sd::Elem; 1] = [cast(0.0).unwrap_or_else(|| unimplemented!())];
110        let buf_view = ArrayViewMut1::from(buffer.as_mut_slice()).remove_axis(Axis(0));
111        self.strategy
112            .interp_into(self, buf_view, x)
113            .map(|_| buffer[0])
114    }
115}
116
117impl<Sd, Sx, D, Strat> Interp1D<Sd, Sx, D, Strat>
118where
119    Sd: Data,
120    Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug + Sub + Send,
121    Sx: Data<Elem = Sd::Elem>,
122    D: Dimension + RemoveAxis,
123    Strat: Interp1DStrategy<Sd, Sx, D>,
124{
125    /// Calculate the interpolated values at `x`.
126    /// Returns the interpolated data in an array one dimension smaller than
127    /// the data dimension.
128    ///
129    /// ```rust
130    /// # use ndarray_interp::*;
131    /// # use ndarray_interp::interp1d::*;
132    /// # use ndarray::*;
133    /// # use approx::*;
134    /// // data has 2 dimension:
135    /// let data = array![
136    ///     [0.0, 2.0, 4.0],
137    ///     [0.5, 2.5, 3.5],
138    ///     [1.0, 3.0, 3.0],
139    /// ];
140    /// let query = 0.5;
141    /// let expected = array![0.25, 2.25, 3.75];
142    ///
143    /// let interpolator = Interp1DBuilder::new(data).build().unwrap();
144    /// let result = interpolator.interp(query).unwrap();
145    /// # assert_abs_diff_eq!(result, expected, epsilon=f64::EPSILON);
146    /// ```
147    ///
148    /// Concider using [`interp_scalar(x)`](Interp1D::interp_scalar)
149    /// when the data dimension is [`type@Ix1`]
150    pub fn interp(&self, x: Sx::Elem) -> Result<Array<Sd::Elem, D::Smaller>, InterpolateError> {
151        let dim = self.data.raw_dim().remove_axis(Axis(0));
152        let mut target: Array<Sd::Elem, _> = Array::zeros(dim);
153        self.strategy
154            .interp_into(self, target.view_mut(), x)
155            .map(|_| target)
156    }
157
158    /// Calculate the interpolated values at `x`.
159    /// and stores the result into the provided buffer.
160    ///
161    /// The provided buffer must have the same shape as the interpolation data
162    /// with the first axis removed.
163    ///
164    /// This can improve performance compared to [`interp`](Interp1D::interp)
165    /// because it does not allocate any memory for the result
166    ///
167    /// # Panics
168    /// When the provided buffer is too small or has the wrong shape
169    pub fn interp_into(
170        &self,
171        x: Sx::Elem,
172        buffer: ArrayViewMut<'_, Sd::Elem, D::Smaller>,
173    ) -> Result<(), InterpolateError> {
174        self.strategy.interp_into(self, buffer, x)
175    }
176
177    /// Calculate the interpolated values at all points in `xs`
178    /// See [`interp_array_into`](Interp1D::interp_array_into) for dimension information
179    ///
180    /// ```rust
181    /// # use ndarray_interp::*;
182    /// # use ndarray_interp::interp1d::*;
183    /// # use ndarray::*;
184    /// # use approx::*;
185    /// let data =     array![0.0,  0.5, 1.0 ];
186    /// let x =        array![0.0,  1.0, 2.0 ];
187    /// let query =    array![0.5,  1.0, 1.5 ];
188    /// let expected = array![0.25, 0.5, 0.75];
189    ///
190    /// let interpolator = Interp1DBuilder::new(data)
191    ///     .x(x)
192    ///     .strategy(Linear::new())
193    ///     .build().unwrap();
194    /// let result = interpolator.interp_array(&query).unwrap();
195    /// # assert_abs_diff_eq!(result, expected, epsilon=f64::EPSILON);
196    /// ```
197    pub fn interp_array<Sq, Dq>(
198        &self,
199        xs: &ArrayBase<Sq, Dq>,
200    ) -> Result<Array<Sd::Elem, <Dq as DimAdd<D::Smaller>>::Output>, InterpolateError>
201    where
202        Sq: Data<Elem = Sd::Elem>,
203        Dq: Dimension + DimAdd<D::Smaller> + 'static,
204        <Dq as DimAdd<D::Smaller>>::Output: DimExtension,
205    {
206        let dim = self.get_buffer_shape(xs.raw_dim());
207        debug_assert_eq!(dim.ndim(), self.data.ndim() + xs.ndim() - 1);
208
209        let mut ys = Array::zeros(dim);
210        self.interp_array_into(xs, ys.view_mut()).map(|_| ys)
211    }
212
213    /// Calculate the interpolated values at all points in `xs`
214    /// and stores the result into the provided buffer
215    ///
216    /// This can improve performance compared to [`interp_array`](Interp1D::interp_array)
217    /// because it does not allocate any memory for the result
218    ///
219    /// # Dimensions
220    /// given the data dimension is `N` and the dimension of `xs` is `M`
221    /// the buffer must have dimension `M + N - 1` where the first
222    /// `M` dimensions correspond to the dimensions of `xs`.
223    ///
224    /// Lets assume we hava a data dimension of `N = (2, 3, 4)` and query this data
225    /// with an array of dimension `M = (10)`, the return dimension will be `(10, 3, 4)`
226    /// given a multi dimensional qurey of `M = (10, 20)` the return will be `(10, 20, 3, 4)`
227    ///
228    /// ```rust
229    /// # use ndarray_interp::*;
230    /// # use ndarray_interp::interp1d::*;
231    /// # use ndarray::*;
232    /// # use approx::*;
233    /// // data has 2 dimension:
234    /// let data = array![
235    ///     [0.0, 2.0],
236    ///     [0.5, 2.5],
237    ///     [1.0, 3.0],
238    /// ];
239    /// let x = array![
240    ///     0.0,
241    ///     1.0,
242    ///     2.0,
243    /// ];
244    /// // query with 2 dimensions:
245    /// let query = array![
246    ///     [0.0, 0.5],
247    ///     [1.0, 1.5],
248    /// ];
249    ///
250    /// // we need 3 buffer dimensions
251    /// let mut buffer = array![
252    ///     [[0.0, 0.0], [0.0, 0.0]],
253    ///     [[0.0, 0.0], [0.0, 0.0]],
254    /// ];
255    ///
256    /// // what we expect in the buffer after interpolation
257    /// let expected = array![
258    ///     [[0.0, 2.0], [0.25, 2.25]], // result for x=[0.0, 0.5]
259    ///     [[0.5, 2.5], [0.75, 2.75]], // result for x=[1.0, 1.5]
260    /// ];
261    ///
262    /// let interpolator = Interp1DBuilder::new(data)
263    ///     .x(x)
264    ///     .strategy(Linear::new())
265    ///     .build().unwrap();
266    /// interpolator.interp_array_into(&query, buffer.view_mut()).unwrap();
267    /// # assert_abs_diff_eq!(buffer, expected, epsilon=f64::EPSILON);
268    /// ```
269    ///
270    /// # panics
271    /// When the provided buffer is too small or has the wrong shape
272    pub fn interp_array_into<Sq, Dq>(
273        &self,
274        xs: &ArrayBase<Sq, Dq>,
275        mut buffer: ArrayViewMut<Sd::Elem, <Dq as DimAdd<D::Smaller>>::Output>,
276    ) -> Result<(), InterpolateError>
277    where
278        Sq: Data<Elem = Sd::Elem>,
279        Dq: Dimension + DimAdd<D::Smaller> + 'static,
280        <Dq as DimAdd<D::Smaller>>::Output: DimExtension,
281    {
282        //self.dim_check(xs.raw_dim(), buffer.raw_dim());
283        if TypeId::of::<Dq>() == TypeId::of::<Ix1>() {
284            // Safety: We checked that `Dq` has type `Ix1`.
285            //    Therefor the `&ArrayBase<Sq, Dq>` and `&ArrayBase<Sq, Ix1>` must be the same type.
286            let xs_1d = unsafe { cast_unchecked::<&ArrayBase<Sq, Dq>, &ArrayBase<Sq, Ix1>>(xs) };
287            // Safety: `<Dq as DimAdd<D::Smaller>>::Output>` reducees the dimension of `D` by one,
288            //    and adds the dimension of `Dq`.
289            //    Given that `Dq` has type `Ix1` the resulting dimension will be `D` again.
290            //    `D` might be of type `IxDyn` In that case `IxDyn::Smaller` => `IxDyn` and also `Ix1::DimAdd<IxDyn>::Output` => `IxDyn`
291            let buffer_d = unsafe {
292                cast_unchecked::<
293                    ArrayViewMut<Sd::Elem, <Dq as DimAdd<D::Smaller>>::Output>,
294                    ArrayViewMut<Sd::Elem, D>,
295                >(buffer)
296            };
297            return self.interp_array_into_1d(xs_1d, buffer_d);
298        }
299
300        // Perform interpolation for each index
301        for (index, &x) in xs.indexed_iter() {
302            let current_dim = index.clone().into_dimension();
303            let subview =
304                buffer.slice_each_axis_mut(|AxisDescription { axis: Axis(nr), .. }| {
305                    match current_dim.as_array_view().get(nr) {
306                        Some(idx) => Slice::from(*idx..*idx + 1),
307                        None => Slice::from(..),
308                    }
309                });
310
311            let subview =
312                match subview.into_shape_with_order(self.data.raw_dim().remove_axis(Axis(0))) {
313                    Ok(view) => view,
314                    Err(err) => {
315                        let expect = self.get_buffer_shape(xs.raw_dim()).into_pattern();
316                        let got = buffer.dim();
317                        panic!("{err} expected: {expect:?}, got: {got:?}")
318                    }
319                };
320
321            self.strategy.interp_into(self, subview, x)?;
322        }
323        Ok(())
324    }
325
326    fn interp_array_into_1d<Sq>(
327        &self,
328        xs: &ArrayBase<Sq, Ix1>,
329        mut buffer: ArrayViewMut<'_, Sd::Elem, D>,
330    ) -> Result<(), InterpolateError>
331    where
332        Sq: Data<Elem = Sd::Elem>,
333    {
334        Zip::from(xs)
335            .and(buffer.axis_iter_mut(Axis(0)))
336            .fold_while(Ok(()), |_, &x, buf| {
337                match self.strategy.interp_into(self, buf, x) {
338                    Ok(_) => ndarray::FoldWhile::Continue(Ok(())),
339                    Err(e) => ndarray::FoldWhile::Done(Err(e)),
340                }
341            })
342            .into_inner()
343    }
344
345    /// the required shape of the buffer when calling [`interp_array_into`]
346    fn get_buffer_shape<Dq>(&self, dq: Dq) -> <Dq as DimAdd<D::Smaller>>::Output
347    where
348        Dq: Dimension + DimAdd<D::Smaller>,
349        <Dq as DimAdd<D::Smaller>>::Output: DimExtension,
350    {
351        let binding = dq.as_array_view();
352        let lenghts = binding.iter().chain(self.data.shape()[1..].iter()).copied();
353        <Dq as DimAdd<D::Smaller>>::Output::new(lenghts)
354    }
355
356    /// Create a interpolator without any data validation. This is fast and cheap.
357    ///
358    /// # Safety
359    /// The following data properties are assumed, but not checked:
360    ///  - `x` is stricktly monotonic rising
361    ///  - `data.shape()[0] == x.len()`
362    ///  - the `strategy` is porperly initialized with the data
363    pub fn new_unchecked(x: ArrayBase<Sx, Ix1>, data: ArrayBase<Sd, D>, strategy: Strat) -> Self {
364        Interp1D { x, data, strategy }
365    }
366
367    /// get `(x, data)` coordinate at given index
368    ///
369    /// # panics
370    /// when index out of bounds
371    pub fn index_point(&self, index: usize) -> (Sx::Elem, ArrayView<Sd::Elem, D::Smaller>) {
372        let view = self.data.index_axis(Axis(0), index);
373        (self.x[index], view)
374    }
375
376    /// The index of a known value left of, or at x.
377    ///
378    /// This will never return the right most index,
379    /// so calling [`index_point(idx+1)`](Interp1D::index_point) is always safe.
380    pub fn get_index_left_of(&self, x: Sx::Elem) -> usize {
381        self.x.get_lower_index(x)
382    }
383
384    pub fn is_in_range(&self, x: Sx::Elem) -> bool {
385        self.x[0] <= x && x <= self.x[self.x.len() - 1]
386    }
387}
388
389impl<Sd, D> Interp1DBuilder<Sd, OwnedRepr<Sd::Elem>, D, Linear>
390where
391    Sd: Data,
392    Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug,
393    D: Dimension,
394{
395    /// Create a new [Interp1DBuilder] and provide the data to interpolate.
396    /// When nothing else is configured [Interp1DBuilder::build] will create an Interpolator using
397    /// Linear Interpolation without extrapolation. As x axis the index to the data will be used.
398    /// On multidimensional data interpolation happens along the first axis.
399    pub fn new(data: ArrayBase<Sd, D>) -> Self {
400        let len = data.shape()[0];
401        Interp1DBuilder {
402            x: Array::from_iter((0..len).map(|n| {
403                cast(n).unwrap_or_else(|| {
404                    unimplemented!("casting from usize to a number should always work")
405                })
406            })),
407            data,
408            strategy: Linear::new(),
409        }
410    }
411}
412
413impl<Sd, Sx, D, Strat> Interp1DBuilder<Sd, Sx, D, Strat>
414where
415    Sd: Data,
416    Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug + Send,
417    Sx: Data<Elem = Sd::Elem>,
418    D: Dimension + RemoveAxis,
419    Strat: Interp1DStrategyBuilder<Sd, Sx, D>,
420{
421    /// Add an custom x axis for the data. The axis needs to have the same lenght
422    /// and store the same Type as the data. `x`  must be strict monotonic rising.
423    /// If the x axis is not set the index `0..data.len() - 1` is used
424    pub fn x<NewSx>(self, x: ArrayBase<NewSx, Ix1>) -> Interp1DBuilder<Sd, NewSx, D, Strat>
425    where
426        NewSx: Data<Elem = Sd::Elem>,
427    {
428        let Interp1DBuilder { data, strategy, .. } = self;
429        Interp1DBuilder { x, data, strategy }
430    }
431
432    /// Set the interpolation strategy by providing a [Interp1DStrategyBuilder].
433    /// By default [Linear] with `Linear{extrapolate: false}` is used.
434    pub fn strategy<NewStrat>(self, strategy: NewStrat) -> Interp1DBuilder<Sd, Sx, D, NewStrat>
435    where
436        NewStrat: Interp1DStrategyBuilder<Sd, Sx, D>,
437    {
438        let Interp1DBuilder { x, data, .. } = self;
439        Interp1DBuilder { x, data, strategy }
440    }
441
442    /// Validate input data and create the configured [Interp1D]
443    pub fn build(self) -> Result<Interp1D<Sd, Sx, D, Strat::FinishedStrat>, BuilderError> {
444        use self::Monotonic::*;
445        use BuilderError::*;
446
447        let Interp1DBuilder { x, data, strategy } = self;
448
449        if data.ndim() < 1 {
450            return Err(ShapeError(
451                "data dimension is 0, needs to be at least 1".into(),
452            ));
453        }
454        if data.shape()[0] < Strat::MINIMUM_DATA_LENGHT {
455            return Err(NotEnoughData(format!(
456                "The chosen Interpolation strategy needs at least {} data points",
457                Strat::MINIMUM_DATA_LENGHT
458            )));
459        }
460        if !matches!(x.monotonic_prop(), Rising { strict: true }) {
461            return Err(Monotonic(
462                "Values in the x axis need to be strictly monotonic rising".into(),
463            ));
464        }
465        if x.len() != data.shape()[0] {
466            return Err(BuilderError::ShapeError(format!(
467                "Lengths of x and data axis need to match. Got x: {:}, data: {:}",
468                x.len(),
469                data.shape()[0],
470            )));
471        }
472
473        let strategy = strategy.build(&x, &data)?;
474
475        Ok(Interp1D { x, data, strategy })
476    }
477}
478
479#[cfg(test)]
480mod tests {
481    use approx::assert_abs_diff_eq;
482    use ndarray::{array, Array, Array1, IxDyn};
483    use rand::{
484        distr::{uniform::SampleUniform, Uniform},
485        rngs::StdRng,
486        Rng, SeedableRng,
487    };
488
489    use super::Interp1D;
490
491    fn rand_arr<T: SampleUniform>(size: usize, range: (T, T), seed: u64) -> Array1<T> {
492        Array::from_iter(
493            StdRng::seed_from_u64(seed)
494                .sample_iter(Uniform::new_inclusive(range.0, range.1).unwrap())
495                .take(size),
496        )
497    }
498
499    macro_rules! get_interp {
500        ($dim:expr, $shape:expr) => {{
501            let arr = rand_arr(4usize.pow($dim), (0.0, 1.0), 64)
502                .into_shape_with_order($shape)
503                .unwrap();
504            Interp1D::builder(arr).build().unwrap()
505        }};
506    }
507
508    macro_rules! test_dim {
509        ($name:ident, $dim:expr, $shape:expr) => {
510            #[test]
511            fn $name() {
512                let interp = get_interp!($dim, $shape);
513                let res = interp.interp(2.2).unwrap();
514                assert_eq!(res.ndim(), $dim - 1);
515
516                let mut buf = Array::zeros(res.dim());
517                interp.interp_into(2.2, buf.view_mut()).unwrap();
518                assert_abs_diff_eq!(buf, res, epsilon = f64::EPSILON);
519
520                let query = array![[0.5, 1.0], [1.5, 2.0]];
521                let res = interp.interp_array(&query).unwrap();
522                assert_eq!(res.ndim(), $dim - 1 + query.ndim());
523
524                let mut buf = Array::zeros(res.dim());
525                interp.interp_array_into(&query, buf.view_mut()).unwrap();
526                assert_abs_diff_eq!(buf, res, epsilon = f64::EPSILON);
527            }
528        };
529    }
530
531    test_dim!(interp1d_1d, 1, 4);
532    test_dim!(interp1d_2d, 2, (4, 4));
533    test_dim!(interp1d_3d, 3, (4, 4, 4));
534    test_dim!(interp1d_4d, 4, (4, 4, 4, 4));
535    test_dim!(interp1d_5d, 5, (4, 4, 4, 4, 4));
536    test_dim!(interp1d_6d, 6, (4, 4, 4, 4, 4, 4));
537    test_dim!(interp1d_7d, 7, IxDyn(&[4, 4, 4, 4, 4, 4, 4]));
538
539    #[test]
540    fn interp1d_1d_scalar() {
541        let arr = rand_arr(4, (0.0, 1.0), 64);
542        let _res: f64 = Interp1D::builder(arr) // type check f64 as return
543            .build()
544            .unwrap()
545            .interp_scalar(2.2)
546            .unwrap();
547    }
548
549    #[test]
550    #[should_panic(expected = "expected: [4], got: [3]")]
551    fn interp1d_2d_into_too_small() {
552        let interp = get_interp!(2, (4, 4));
553        let mut buf = Array::zeros(3);
554        let _ = interp.interp_into(2.2, buf.view_mut());
555    }
556
557    #[test]
558    #[should_panic(expected = "expected: [4], got: [5]")]
559    fn interp1d_2d_into_too_big() {
560        let interp = get_interp!(2, (4, 4));
561        let mut buf = Array::zeros(5);
562        let _ = interp.interp_into(2.2, buf.view_mut());
563    }
564
565    #[test]
566    #[should_panic(expected = "expected: [2], got: [1]")] // this is not really a good message
567    fn interp1d_2d_array_into_too_small1() {
568        let arr = rand_arr((4usize).pow(2), (0.0, 1.0), 64)
569            .into_shape_with_order((4, 4))
570            .unwrap();
571        let interp = Interp1D::builder(arr).build().unwrap();
572        let mut buf = Array::zeros((1, 4));
573        let _ = interp.interp_array_into(&array![2.2, 2.4], buf.view_mut());
574    }
575
576    #[test]
577    #[should_panic]
578    fn interp1d_2d_array_into_too_small2() {
579        let arr = rand_arr((4usize).pow(2), (0.0, 1.0), 64)
580            .into_shape_with_order((4, 4))
581            .unwrap();
582        let interp = Interp1D::builder(arr).build().unwrap();
583        let mut buf = Array::zeros((2, 3));
584        let _ = interp.interp_array_into(&array![2.2, 2.4], buf.view_mut());
585    }
586
587    #[test]
588    #[should_panic]
589    fn interp1d_2d_array_into_too_big1() {
590        let arr = rand_arr((4usize).pow(2), (0.0, 1.0), 64)
591            .into_shape_with_order((4, 4))
592            .unwrap();
593        let interp = Interp1D::builder(arr).build().unwrap();
594        let mut buf = Array::zeros((3, 4));
595        let _ = interp.interp_array_into(&array![2.2, 2.4], buf.view_mut());
596    }
597
598    #[test]
599    #[should_panic]
600    fn interp1d_2d_array_into_too_big2() {
601        let arr = rand_arr((4usize).pow(2), (0.0, 1.0), 64)
602            .into_shape_with_order((4, 4))
603            .unwrap();
604        let interp = Interp1D::builder(arr).build().unwrap();
605        let mut buf = Array::zeros((2, 5));
606        let _ = interp.interp_array_into(&array![2.2, 2.4], buf.view_mut());
607    }
608}