bacon_sci/interp/
spline.rs1use crate::polynomial::Polynomial;
8use nalgebra::{ComplexField, RealField};
9use num_traits::FromPrimitive;
10
11#[derive(Debug, Clone)]
12pub struct CubicSpline<N: ComplexField + FromPrimitive + Copy>
13where
14 <N as ComplexField>::RealField: FromPrimitive + Copy,
15{
16 cubics: Vec<Polynomial<N>>,
17 ranges: Vec<(N::RealField, N::RealField)>,
18}
19
20impl<N: ComplexField + FromPrimitive + Copy> CubicSpline<N>
21where
22 <N as ComplexField>::RealField: FromPrimitive + Copy,
23{
24 pub fn evaluate(&self, x: N::RealField) -> Result<N, String> {
25 if self.cubics.is_empty() {
26 return Err("CubicSpline evaluate: Empty spline".to_owned());
27 }
28
29 for (ind, range) in self.ranges.iter().enumerate() {
30 if x >= range.0 && x <= range.1 {
31 return Ok(self.cubics[ind].evaluate(N::from_real(x)));
32 }
33 }
34
35 Err(format!("CubicSpline evaluate: {} outside of range", x))
36 }
37
38 pub fn evaluate_derivative(&self, x: N::RealField) -> Result<(N, N), String> {
39 if self.cubics.is_empty() {
40 return Err("CubicSpline evaluate: Empty spline".to_owned());
41 }
42
43 for (ind, range) in self.ranges.iter().enumerate() {
44 if x >= range.0 && x <= range.1 {
45 return Ok(self.cubics[ind].evaluate_derivative(N::from_real(x)));
46 }
47 }
48
49 Err(format!("CubicSpline evaluate: {} outside of range", x))
50 }
51}
52
53pub fn spline_free<N: ComplexField + FromPrimitive + Copy>(
83 xs: &[N::RealField],
84 ys: &[N],
85 tol: N::RealField,
86) -> Result<CubicSpline<N>, String>
87where
88 <N as ComplexField>::RealField: FromPrimitive + Copy,
89{
90 if xs.len() != ys.len() {
91 return Err("spline_free: xs and ys must be same length".to_owned());
92 }
93 if xs.len() < 2 {
94 return Err("spline_free: need at least two points".to_owned());
95 }
96
97 let mut hs = Vec::with_capacity(xs.len() - 1);
98 for i in 0..xs.len() - 1 {
99 hs.push(xs[i + 1] - xs[i]);
100 }
101
102 if hs.iter().any(|h| !h.is_sign_positive()) {
103 return Err("spline_free: xs must be sorted".to_owned());
104 }
105
106 let third = N::from_f64(1.0 / 3.0).unwrap();
107 let two = N::from_i32(2).unwrap();
108 let three = N::from_i32(3).unwrap();
109
110 let mut alphas = Vec::with_capacity(xs.len() - 1);
111 alphas.push(N::zero());
112 for i in 1..xs.len() - 1 {
113 alphas.push(
114 (three / N::from_real(hs[i])) * (ys[i + 1] - ys[i])
115 - (three / N::from_real(hs[i - 1])) * (ys[i] - ys[i - 1]),
116 );
117 }
118
119 let mut l = Vec::with_capacity(xs.len() - 1);
120 let mut mu = Vec::with_capacity(xs.len() - 1);
121 let mut z = Vec::with_capacity(xs.len() - 1);
122
123 l.push(N::one());
124 mu.push(N::zero());
125 z.push(N::zero());
126
127 for i in 1..xs.len() - 1 {
128 l.push(two * N::from_real(xs[i + 1] - xs[i - 1]) - N::from_real(hs[i - 1]) * mu[i - 1]);
129 mu.push(N::from_real(hs[i]) / l[i]);
130 z.push((alphas[i] - N::from_real(hs[i - 1]) * z[i - 1]) / l[i]);
131 }
132
133 l.push(N::one());
134 z.push(N::zero());
135
136 let mut c_coefficient = vec![N::zero(); xs.len()];
137 let mut b_coefficient = vec![N::zero(); xs.len()];
138 let mut d_coefficient = vec![N::zero(); xs.len()];
139 for i in (0..xs.len() - 1).rev() {
140 c_coefficient[i] = z[i] - mu[i] * c_coefficient[i + 1];
141 b_coefficient[i] = (ys[i + 1] - ys[i]) / N::from_real(hs[i])
142 - N::from_real(hs[i]) * (c_coefficient[i + 1] + two * c_coefficient[i]) * third;
143 d_coefficient[i] =
144 (c_coefficient[i + 1] - c_coefficient[i]) / (three * N::from_real(hs[i]));
145 }
146
147 let mut polynomials = Vec::with_capacity(xs.len() - 1);
148 let mut ranges = Vec::with_capacity(xs.len() - 1);
149
150 for i in 0..xs.len() - 1 {
151 let term = polynomial![N::one(), N::from_real(-xs[i])];
153 let mut poly = &term * d_coefficient[i];
154 poly.set_tolerance(tol)?;
155 poly += c_coefficient[i];
156 poly *= &term;
157 poly += b_coefficient[i];
158 poly *= term;
159 poly += ys[i];
160 polynomials.push(poly);
161 ranges.push((xs[i], xs[i + 1]));
162 }
163
164 Ok(CubicSpline {
165 cubics: polynomials,
166 ranges,
167 })
168}
169
170pub fn spline_clamped<N: ComplexField + FromPrimitive + Copy>(
202 xs: &[N::RealField],
203 ys: &[N],
204 (f_0, f_n): (N, N),
205 tol: N::RealField,
206) -> Result<CubicSpline<N>, String>
207where
208 <N as ComplexField>::RealField: FromPrimitive + Copy,
209{
210 if xs.len() != ys.len() {
211 return Err("spline_clamped: xs and ys must be same length".to_owned());
212 }
213 if xs.len() < 2 {
214 return Err("spline_clamped: need at least two points".to_owned());
215 }
216
217 let mut hs = Vec::with_capacity(xs.len() - 1);
218 for i in 0..xs.len() - 1 {
219 hs.push(xs[i + 1] - xs[i]);
220 }
221
222 if hs.iter().any(|h| !h.is_sign_positive()) {
223 return Err("spline_clamped: xs must be sorted".to_owned());
224 }
225
226 let third = N::from_f64(1.0 / 3.0).unwrap();
227 let half = N::from_f64(0.5).unwrap();
228 let two = N::from_i32(2).unwrap();
229 let three = N::from_i32(3).unwrap();
230
231 let mut alphas = vec![N::zero(); xs.len()];
232 alphas[0] = three * ((ys[1] - ys[0]) / N::from_real(hs[0]) - f_0);
233 alphas[xs.len() - 1] =
234 three * (f_n - (ys[xs.len() - 1] - ys[xs.len() - 2]) / N::from_real(hs[xs.len() - 2]));
235
236 for i in 1..xs.len() - 1 {
237 alphas[i] = three
238 * ((ys[i + 1] - ys[i]) / N::from_real(hs[i])
239 - (ys[i] - ys[i - 1]) / N::from_real(hs[i - 1]));
240 }
241
242 let mut l = Vec::with_capacity(xs.len() - 1);
243 let mut mu = Vec::with_capacity(xs.len() - 1);
244 let mut z = Vec::with_capacity(xs.len() - 1);
245
246 l.push(two * N::from_real(hs[0]));
247 mu.push(half);
248 z.push(alphas[0] / l[0]);
249
250 for i in 1..xs.len() - 1 {
251 l.push(two * N::from_real(xs[i + 1] - xs[i - 1]) - N::from_real(hs[i - 1]) * mu[i - 1]);
252 mu.push(N::from_real(hs[i]) / l[i]);
253 z.push((alphas[i] - N::from_real(hs[i - 1]) * z[i - 1]) / l[i]);
254 }
255
256 l.push(N::from_real(hs[xs.len() - 2]) * (two - mu[xs.len() - 2]));
257 z.push(
258 (alphas[xs.len() - 1] - N::from_real(hs[xs.len() - 2]) * z[xs.len() - 2]) / l[xs.len() - 1],
259 );
260
261 let mut b_coefficient = vec![N::zero(); xs.len()];
262 let mut c_coefficient = vec![N::zero(); xs.len()];
263 let mut d_coefficient = vec![N::zero(); xs.len()];
264
265 c_coefficient[xs.len() - 1] = z[xs.len() - 1];
266
267 for i in (0..xs.len() - 1).rev() {
268 c_coefficient[i] = z[i] - mu[i] * c_coefficient[i + 1];
269 b_coefficient[i] = (ys[i + 1] - ys[i]) / N::from_real(hs[i])
270 - N::from_real(hs[i]) * third * (c_coefficient[i + 1] + two * c_coefficient[i]);
271 d_coefficient[i] =
272 (c_coefficient[i + 1] - c_coefficient[i]) / (three * N::from_real(hs[i]));
273 }
274
275 let mut polynomials = Vec::with_capacity(xs.len() - 1);
276 let mut ranges = Vec::with_capacity(xs.len() - 1);
277
278 for i in 0..xs.len() - 1 {
279 let term = polynomial![N::one(), N::from_real(-xs[i])];
281 let mut poly = &term * d_coefficient[i];
282 poly.set_tolerance(tol)?;
283 poly += c_coefficient[i];
284 poly *= &term;
285 poly += b_coefficient[i];
286 poly *= term;
287 poly += ys[i];
288 polynomials.push(poly);
289 ranges.push((xs[i], xs[i + 1]));
290 }
291
292 Ok(CubicSpline {
293 cubics: polynomials,
294 ranges,
295 })
296}