csaps/ndg.rs
1mod validate;
2mod make;
3mod evaluate;
4mod util;
5
6use ndarray::{
7    Dimension,
8    AsArray,
9    Array,
10    ArrayView,
11    ArrayView1,
12};
13
14use crate::{Real, Result};
15
16
17/// N-d grid spline PP-form representation
18///
19/// `NdGridSpline` represents n-dimensional splines for n-dimensional grid data. In n-d grid case
20/// the spline is represented as tensor-product of univariate spline coefficients along every
21/// diemension.
22///
23/// Also `evaluate` method is implemented for `NdGridSpline` for evaluating the values
24/// for given data sites.
25///
26#[derive(Debug)]
27pub struct NdGridSpline<'a, T, D>
28    where
29        T: Real,
30        D: Dimension
31{
32    /// The grid dimensionality
33    ndim: usize,
34
35    /// The vector of the spline orders for each grid dimension
36    order: Vec<usize>,
37
38    /// The vector of the number of pieces of the spline for each grid dimension
39    pieces: Vec<usize>,
40
41    /// The breaks (data sites for each grid dimension) which have been used for computing spline
42    breaks: Vec<ArrayView1<'a, T>>,
43
44    /// N-d array of the tensor-product univariate spline coefficients as
45    /// representation of n-d grid spline coefficients
46    coeffs: Array<T, D>,
47}
48
49
50impl<'a, T, D> NdGridSpline<'a, T, D>
51    where
52        T: Real,
53        D: Dimension
54{
55    /// Creates `NdGridSpline` struct from given `breaks` and `coeffs`
56    ///
57    /// # Arguments
58    ///
59    /// - `breaks` -- The vector of the breaks (data sites) which have been used for computing spline
60    /// - `coeffs` -- The n-d array of tensor-product spline coefficients
61    ///
62    /// # Notes
63    ///
64    /// - `NdGridSpline` struct should not be created directly by a user in most cases.
65    ///
66    pub fn new(breaks: Vec<ArrayView1<'a, T>>, coeffs: Array<T, D>) -> Self {
67        let ndim = breaks.len();
68        let pieces: Vec<usize> = breaks.iter().map(|x| x.len() - 1).collect();
69        let order: Vec<usize> = pieces.iter().zip(coeffs.shape().iter()).map(|(p, s)| s / p).collect();
70
71        NdGridSpline {
72            ndim,
73            order,
74            pieces,
75            breaks,
76            coeffs,
77        }
78    }
79
80    /// Returns the n-d grid spline dimensionality
81    pub fn ndim(&self) -> usize { self.ndim }
82
83    /// Returns the vector of the spline order for each dimension
84    pub fn order(&self) -> &Vec<usize> { &self.order }
85
86    /// Returns the vector of the number of pieces of the spline for each dimension
87    pub fn pieces(&self) -> &Vec<usize> { &self.pieces }
88
89    /// Returns the vector of views to the breaks for each dimension
90    pub fn breaks(&self) -> &Vec<ArrayView1<'a, T>> { &self.breaks }
91
92    /// Returns the view to the spline coefficients array
93    pub fn coeffs(&self) -> ArrayView<'_, T, D> { self.coeffs.view() }
94
95    /// Evaluates the spline on the given data sites
96    pub fn evaluate(&self, xi: &'a [ArrayView1<'a, T>]) -> Array<T, D> {
97        self.evaluate_spline(xi)
98    }
99}
100
101
102/// N-dimensional grid cubic smoothing spline calculator/evaluator
103///
104/// The struct represents n-dimensional grid smoothing cubic spline and allows you to make
105/// and evaluate the splines for given n-dimensional grid data.
106///
107/// `GridCubicSmoothingSpline` struct is parametrized by data type (`f64` or `f32`)
108/// and data dimension.
109///
110/// The methods API of `GridCubicSmoothingSpline` is implemented as builder-like pattern or in other
111/// words as chained API (also as `CubicSmoothingSpline` struct).
112///
113/// # Examples
114///
115/// ```
116/// use ndarray::array;
117/// use csaps::GridCubicSmoothingSpline;
118///
119/// let x0 = array![1.0, 2.0, 3.0, 4.0];
120/// let x1 = array![1.0, 2.0, 3.0, 4.0];
121/// let x = vec![x0.view(), x1.view()];
122///
123/// let y = array![
124///     [0.5, 1.2, 3.4, 2.5],
125///     [1.5, 2.2, 4.4, 3.5],
126///     [2.5, 3.2, 5.4, 4.5],
127///     [3.5, 4.2, 6.4, 5.5],
128/// ];
129///
130/// let ys = GridCubicSmoothingSpline::new(&x, &y)
131///     .make().unwrap()
132///     .evaluate(&x).unwrap();
133/// ```
134///
135pub struct GridCubicSmoothingSpline<'a, T, D>
136    where
137        T: Real,
138        D: Dimension
139{
140    /// X data sites (also breaks)
141    x: Vec<ArrayView1<'a, T>>,
142
143    /// Y data values
144    y: ArrayView<'a, T, D>,
145
146    /// The optional data weights
147    weights: Vec<Option<ArrayView1<'a, T>>>,
148
149    /// The optional smoothing parameter
150    smooth: Vec<Option<T>>,
151
152    /// `NdGridSpline` struct with computed spline
153    spline: Option<NdGridSpline<'a, T, D>>
154}
155
156
157impl<'a, T, D> GridCubicSmoothingSpline<'a, T, D>
158    where
159        T: Real,
160        D: Dimension
161{
162    /// Creates `NdGridCubicSmoothingSpline` struct from the given `X` data sites and `Y` data values
163    ///
164    /// # Arguments
165    ///
166    /// - `x` -- the slice of X-data sites 1-d array view for each dimension.
167    ///   Each data sites must strictly increasing: `x1 < x2 < x3 < ... < xN`.
168    /// - `y` -- The Y-data n-d grid values array-like. `ndim` can be from 1 to N.
169    ///
170    pub fn new<Y>(x: &[ArrayView1<'a, T>], y: Y) -> Self
171        where
172            Y: AsArray<'a, T, D>
173    {
174        let ndim = x.len();
175
176        GridCubicSmoothingSpline {
177            x: x.to_vec(),
178            y: y.into(),
179            weights: vec![None; ndim],
180            smooth: vec![None; ndim],
181            spline: None,
182        }
183    }
184
185    /// Sets the weights data vectors for each dimension
186    ///
187    /// # Arguments
188    ///
189    /// - `weights` -- the slice of optional weights arrays (array-like) for each dimension
190    ///
191    /// # Notes
192    ///
193    /// `weights` vectors sizes must be equal to `x` data site sizes for each dimension.
194    ///
195    pub fn with_weights(mut self, weights: &[Option<ArrayView1<'a, T>>]) -> Self {
196        self.invalidate();
197        self.weights = weights.to_vec();
198        self
199    }
200
201    /// Sets the smoothing parameters for each dimension
202    ///
203    /// # Arguments
204    ///
205    /// - `smooth` - the slice of optional smoothing parameters for each dimension
206    ///
207    /// # Notes
208    ///
209    /// The smoothing parameters should be in range `[0, 1]` or `None`,
210    /// where bounds are:
211    ///
212    ///  - 0: The smoothing spline is the least-squares straight line fit to the data
213    ///  - 1: The cubic spline interpolant with natural boundary condition
214    ///
215    /// If the smoothing parameter value is None, it will be computed automatically.
216    ///
217    pub fn with_smooth(mut self, smooth: &[Option<T>]) -> Self {
218        self.invalidate();
219        self.smooth = smooth.to_vec();
220        self
221    }
222
223    /// Sets the smoothing parameter for all dimensions
224    ///
225    /// # Arguments
226    ///
227    /// - `smooth` - the smoothing parameter value that the same for all dimensions
228    ///
229    /// # Notes
230    ///
231    /// The smoothing parameter should be in range `[0, 1]`,
232    /// where bounds are:
233    ///
234    ///  - 0: The smoothing spline is the least-squares straight line fit to the data
235    ///  - 1: The cubic spline interpolant with natural boundary condition
236    ///
237    pub fn with_smooth_fill(mut self, smooth: T) -> Self {
238        self.invalidate();
239        self.smooth = vec![Some(smooth); self.x.len()];
240        self
241    }
242
243    /// Makes (computes) the n-dimensional grid spline for given data and parameters
244    ///
245    /// # Errors
246    ///
247    /// - If the data or parameters are invalid
248    ///
249    pub fn make(mut self) -> Result<Self> {
250        self.invalidate();
251        self.make_validate()?;
252        self.make_spline()?;
253
254        Ok(self)
255    }
256
257    /// Evaluates the computed n-dimensional grid spline on the given data sites
258    ///
259    /// # Errors
260    ///
261    /// - If the `xi` data is invalid
262    /// - If the spline yet has not been computed
263    ///
264    pub fn evaluate(&self, xi: &[ArrayView1<'a, T>]) -> Result<Array<T, D>> {
265        self.evaluate_validate(&xi)?;
266        let yi = self.evaluate_spline(&xi);
267
268        Ok(yi)
269    }
270
271    /// Returns the ref to smoothing parameters vector or None
272    pub fn smooth(&self) -> &Vec<Option<T>> {
273        &self.smooth
274    }
275
276    /// Returns ref to `NdGridSpline` struct with data of computed spline or None
277    pub fn spline(&self) -> Option<&NdGridSpline<'a, T, D>> {
278        self.spline.as_ref()
279    }
280
281    /// Invalidate computed spline
282    fn invalidate(&mut self) {
283        self.spline = None;
284    }
285}