libreda_interp/
interp1d.rs1use crate::{find_closest_neighbours_indices, is_monotonic};
21use std::ops::{Add, Div, Mul, Sub};
22
23#[derive(Debug, Clone)]
27pub struct Interp1D<C, Z> {
28 x: Vec<C>,
30 z: Vec<Z>,
32}
33
34fn interpolate1d<C, Z>(z0: Z, z1: Z, alpha: C) -> Z
38where
39 C: Copy + Sub<Output = C>,
40 Z: Copy + Mul<C, Output = Z> + Add<Output = Z> + Sub<Output = Z>,
41{
42 z0 - z0 * alpha + z1 * alpha
43}
44
45#[test]
46fn test_interpolate1d() {
47 assert!((interpolate1d(1.0f64, 2., 0.) - 1.).abs() < 1e-6);
48 assert!((interpolate1d(1.0f64, 2., 1.) - 2.).abs() < 1e-6);
49 assert!((interpolate1d(1.0f64, 2., 0.5) - 1.5).abs() < 1e-6);
50}
51
52fn interp1d<C, Z>(x: C, (x0, x1): (C, C), (v0, v1): (Z, Z)) -> Z
55where
56 C: Copy + Sub<Output = C> + Div,
57 Z: Copy + Mul<<C as Div>::Output, Output = Z> + Add<Output = Z> + Sub<Output = Z>,
58 <C as Div>::Output: Copy + Add<Output = <C as Div>::Output> + Sub<Output = <C as Div>::Output>,
59{
60 let dx = x1 - x0;
61
62 let alpha = (x - x0) / dx;
63
64 interpolate1d(v0, v1, alpha)
65}
66
67impl<C, Z> Interp1D<C, Z> {
68 pub fn new(x: Vec<C>, z: Vec<Z>) -> Self
82 where
83 C: PartialOrd,
84 {
85 assert_eq!(z.len(), x.len(), "x-axis length mismatch.");
86 assert!(!x.is_empty());
87
88 assert!(is_monotonic(&x), "x values must be monotonic.");
89
90 Self { x, z }
91 }
92
93 pub fn bounds(&self) -> (C, C)
96 where
97 C: Copy,
98 {
99 (self.x[0], self.x[self.x.len() - 1])
100 }
101
102 pub fn is_within_bounds(&self, x: C) -> bool
104 where
105 C: PartialOrd + Copy,
106 {
107 let (x0, x1) = self.bounds();
108 x0 <= x && x <= x1
109 }
110
111 pub fn xs(&self) -> &Vec<C> {
113 &self.x
114 }
115
116 pub fn z(&self) -> &Vec<Z> {
118 &self.z
119 }
120
121 pub fn map_values<Z2>(&self, f: impl Fn(&Z) -> Z2) -> Interp1D<C, Z2>
123 where
124 C: PartialOrd + Clone,
125 {
126 Interp1D {
127 x: self.x.clone(),
128 z: self.z.iter().map(f).collect(),
129 }
130 }
131
132 pub fn map_axis<Xnew>(self, f: impl Fn(C) -> Xnew) -> Interp1D<Xnew, Z>
135 where
136 Xnew: PartialOrd,
137 {
138 let xnew = self.x.into_iter().map(f).collect();
139 assert!(is_monotonic(&xnew));
140 Interp1D { x: xnew, z: self.z }
141 }
142}
143
144impl<C, Z> Interp1D<C, Z>
145where
146 C: Copy + Sub<Output = C> + Div + PartialOrd,
147 Z: Copy + Mul<<C as Div>::Output, Output = Z> + Add<Output = Z> + Sub<Output = Z>,
148 <C as Div>::Output: Copy + Add<Output = <C as Div>::Output> + Sub<Output = <C as Div>::Output>,
149{
150 pub fn eval(&self, x: C) -> Z {
155 let (x0, x1) = find_closest_neighbours_indices(&self.x, x);
158
159 interp1d(x, (self.x[x0], self.x[x1]), (self.z[x0], self.z[x1]))
160 }
161
162 pub fn eval_no_extrapolation(&self, x: C) -> Option<Z> {
165 if self.is_within_bounds(x) {
166 Some(self.eval(x))
167 } else {
168 None
169 }
170 }
171}
172
173#[test]
174fn test_interp1d() {
175 let xs = vec![0.0f64, 1.0, 2.0];
176 let zs = vec![0.0, 1.0, 0.0];
177
178 let interp = Interp1D::new(xs, zs);
179
180 assert!((interp.eval_no_extrapolation(1.0).unwrap() - 1.0).abs() < 1e-6);
181 assert!((interp.eval_no_extrapolation(2.0).unwrap() - 0.0).abs() < 1e-6);
182 assert!((interp.eval_no_extrapolation(1.5).unwrap() - 0.5).abs() < 1e-6);
183}
184
185#[test]
186fn test_interp1d_map_values() {
187 let xs = vec![0.0f64, 1.0, 2.0];
188 let zs = vec![0.0, 1.0, 0.0];
189
190 let interp = Interp1D::new(xs, zs);
191 let interp = interp.map_values(|x| 2. * x);
192
193 assert!((interp.eval_no_extrapolation(1.0).unwrap() - 2.0).abs() < 1e-6);
194}
195
196#[test]
197fn test_interp1d_map_axis() {
198 let xs = vec![0.0f64, 1.0, 2.0];
199 let zs = vec![0.0, 1.0, 0.0];
200
201 let interp = Interp1D::new(xs, zs);
202 let interp = interp.map_axis(|x| 2. * x);
203
204 assert!((interp.eval_no_extrapolation(2.0).unwrap() - 1.0).abs() < 1e-6);
205}