1#[must_use]
13pub fn value_at(x: &[f64], y: &[f64], at: f64) -> f64 {
14 let length = x.len().min(y.len());
15 match length {
16 0 => return 0.0,
17 1 => return y[0],
18 _ => {}
19 }
20
21 if at <= x[0] {
22 return y[0];
23 }
24 if at >= x[length - 1] {
25 return y[length - 1];
26 }
27
28 let above = x[..length]
29 .partition_point(|sample| *sample < at)
30 .clamp(1, length - 1);
31 let below = above - 1;
32 let span = x[above] - x[below];
33
34 if span.abs() < f64::EPSILON {
35 return (y[below] + y[above]) / 2.0;
36 }
37
38 y[below] + (at - x[below]) / span * (y[above] - y[below])
39}
40
41#[must_use]
43pub fn resample(x: &[f64], y: &[f64], at: &[f64]) -> Vec<f64> {
44 at.iter().map(|at| value_at(x, y, *at)).collect()
45}
46
47#[must_use]
53pub fn resample_uniform(x: &[f64], y: &[f64], step: f64) -> Vec<f64> {
54 let length = x.len().min(y.len());
55 if length == 0 || step <= 0.0 {
56 return Vec::new();
57 }
58
59 let span = x[length - 1] - x[0];
60 let count = (span / step).floor() as usize + 1;
61
62 (0..count)
63 .map(|index| value_at(x, y, x[0] + index as f64 * step))
64 .collect()
65}
66
67#[must_use]
74pub fn resample_count(values: &[f64], count: usize) -> Vec<f64> {
75 if values.is_empty() || count == 0 {
76 return Vec::new();
77 }
78 if values.len() == 1 || count == 1 {
79 return vec![values[0]; count];
80 }
81
82 let step = (values.len() - 1) as f64 / (count - 1) as f64;
83 let axis: Vec<f64> = (0..values.len()).map(|index| index as f64).collect();
84
85 (0..count)
86 .map(|index| value_at(&axis, values, index as f64 * step))
87 .collect()
88}
89
90#[must_use]
95pub fn nearest(x: &[f64], at: f64) -> Option<usize> {
96 if x.is_empty() {
97 return None;
98 }
99
100 let above = x.partition_point(|sample| *sample < at);
101 if above == 0 {
102 return Some(0);
103 }
104 if above >= x.len() {
105 return Some(x.len() - 1);
106 }
107
108 let below = above - 1;
109 Some(if at - x[below] <= x[above] - at {
110 below
111 } else {
112 above
113 })
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn a_midpoint_reads_the_average_of_its_neighbours() {
122 let x = vec![0.0, 1.0, 2.0];
123 let y = vec![0.0, 10.0, 20.0];
124
125 assert!((value_at(&x, &y, 0.5) - 5.0).abs() < 1e-12);
126 assert!((value_at(&x, &y, 1.5) - 15.0).abs() < 1e-12);
127 assert!((value_at(&x, &y, 1.0) - 10.0).abs() < 1e-12);
128 }
129
130 #[test]
131 fn a_quarter_of_the_way_reads_a_quarter_of_the_rise() {
132 assert!((value_at(&[0.0, 4.0], &[0.0, 100.0], 1.0) - 25.0).abs() < 1e-12);
133 assert!((value_at(&[0.0, 4.0], &[0.0, 100.0], 3.0) - 75.0).abs() < 1e-12);
134 }
135
136 #[test]
137 fn outside_the_span_the_nearest_end_is_the_answer() {
138 let x = vec![0.0, 1.0, 2.0];
139 let y = vec![0.0, 10.0, 20.0];
140
141 assert_eq!(value_at(&x, &y, -5.0), 0.0);
142 assert_eq!(value_at(&x, &y, 10.0), 20.0);
143 }
144
145 #[test]
146 fn a_run_too_short_to_interpolate_answers_what_it_has() {
147 assert_eq!(value_at(&[], &[], 5.0), 0.0);
148 assert_eq!(value_at(&[1.0], &[10.0], 5.0), 10.0);
149 }
150
151 #[test]
152 fn resampling_onto_an_axis_reads_it_point_by_point() {
153 let x = vec![0.0, 1.0, 2.0];
154 let y = vec![0.0, 10.0, 20.0];
155
156 assert_eq!(resample(&x, &y, &[0.0, 0.5, 2.0]), vec![0.0, 5.0, 20.0]);
157 assert!(resample(&x, &y, &[]).is_empty());
158 }
159
160 #[test]
161 fn a_uniform_grid_covers_the_span_it_was_given() {
162 let x: Vec<f64> = (0..10).map(f64::from).collect();
164 let y: Vec<f64> = x.iter().map(|at| at * 2.0).collect();
165
166 let resampled = resample_uniform(&x, &y, 0.5);
167
168 assert_eq!(resampled.len(), 19);
169 for (index, value) in resampled.iter().enumerate() {
171 assert!(
172 (value - index as f64).abs() < 1e-12,
173 "sample {index} read {value}"
174 );
175 }
176 }
177
178 #[test]
179 fn a_grid_with_no_step_resamples_to_nothing() {
180 assert!(resample_uniform(&[0.0, 1.0], &[0.0, 1.0], 0.0).is_empty());
181 assert!(resample_uniform(&[], &[], 0.5).is_empty());
182 }
183
184 #[test]
185 fn a_count_resample_keeps_both_ends() {
186 assert_eq!(
187 resample_count(&[0.0, 100.0], 5),
188 vec![0.0, 25.0, 50.0, 75.0, 100.0]
189 );
190 assert_eq!(
191 resample_count(&[0.0, 25.0, 50.0, 75.0, 100.0], 3),
192 vec![0.0, 50.0, 100.0]
193 );
194 assert_eq!(resample_count(&[0.0, 10.0, 20.0], 3), vec![0.0, 10.0, 20.0]);
195 }
196
197 #[test]
198 fn a_count_resample_of_nothing_is_nothing() {
199 assert!(resample_count(&[], 10).is_empty());
200 assert!(resample_count(&[1.0, 2.0], 0).is_empty());
201 assert_eq!(resample_count(&[5.0], 3), vec![5.0, 5.0, 5.0]);
202 assert_eq!(resample_count(&[1.0, 2.0, 3.0], 1), vec![1.0]);
203 }
204
205 #[test]
206 fn the_nearest_sample_is_the_one_the_query_is_closest_to() {
207 let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
208
209 assert_eq!(nearest(&x, 0.0), Some(0));
210 assert_eq!(nearest(&x, 1.3), Some(1));
211 assert_eq!(nearest(&x, 1.7), Some(2));
212 assert_eq!(nearest(&x, 2.5), Some(2));
213 assert_eq!(nearest(&x, -1.0), Some(0));
214 assert_eq!(nearest(&x, 10.0), Some(4));
215 assert_eq!(nearest(&[], 1.0), None);
216 }
217
218 #[test]
219 fn the_nearest_sample_of_an_uneven_axis_is_still_the_closest() {
220 let x = vec![0.0, 0.3, 1.0];
221
222 assert_eq!(nearest(&x, 0.1), Some(0));
223 assert_eq!(nearest(&x, 0.2), Some(1));
224 assert_eq!(nearest(&x, 0.5), Some(1));
225 assert_eq!(nearest(&x, 0.8), Some(2));
226 }
227}