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}