1use serde::{Deserialize, Serialize};
2use std::fs::File;
3use std::io::{BufReader, BufWriter};
4use std::path::Path;
5
6#[derive(Clone, Debug)]
8pub struct AdaptiveSegments {
9 pub points: Vec<f64>,
11}
12
13impl AdaptiveSegments {
14 pub fn from_points(mut points: Vec<f64>) -> Self {
16 points.sort_by(|a, b| a.partial_cmp(b).unwrap());
17 points.dedup_by(|a, b| (*a - *b).abs() < 1e-14);
18 Self { points }
19 }
20
21 pub fn len(&self) -> usize {
23 self.points.len()
24 }
25
26 pub fn is_empty(&self) -> bool {
28 self.points.is_empty()
29 }
30}
31
32pub struct AdaptiveGrid1D {
47 start: f64,
48 end: f64,
49 tolerance: f64,
50 max_depth: usize,
51}
52
53impl AdaptiveGrid1D {
54 pub fn new(start: f64, end: f64, tolerance: f64, max_depth: usize) -> Self {
62 assert!(start < end, "start must be less than end");
63 assert!(tolerance > 0.0, "tolerance must be positive");
64 Self {
65 start,
66 end,
67 tolerance,
68 max_depth,
69 }
70 }
71
72 pub fn generate<F>(&self, f: F) -> AdaptiveSegments
76 where
77 F: Fn(f64) -> f64,
78 {
79 let mut points = Vec::new();
80 self.bisect(self.start, self.end, &f, 0, &mut points);
81 points.push(self.end); AdaptiveSegments::from_points(points)
83 }
84
85 fn bisect<F>(&self, a: f64, b: f64, f: &F, depth: usize, points: &mut Vec<f64>)
87 where
88 F: Fn(f64) -> f64,
89 {
90 if points.is_empty() || (points.last().unwrap() - a).abs() > 1e-14 {
92 points.push(a);
93 }
94
95 let m = (a + b) / 2.0;
96 let fa = f(a);
97 let fm = f(m);
98 let fb = f(b);
99
100 let fm_interp = (fa + fb) / 2.0;
102 let error = (fm - fm_interp).abs();
103
104 if error > self.tolerance && depth < self.max_depth {
105 self.bisect(a, m, f, depth + 1, points);
107 self.bisect(m, b, f, depth + 1, points);
108 } else {
109 if points.is_empty() || (points.last().unwrap() - m).abs() > 1e-14 {
111 points.push(m);
112 }
113 }
114 }
115}
116
117#[derive(Clone, Serialize, Deserialize, Debug)]
129pub struct IndirectionTable {
130 coarse: Vec<(f64, bool)>,
133 fine: Vec<f64>,
135 min: f64,
137 max: f64,
138 coarse_resolution: usize,
140}
141
142impl IndirectionTable {
143 pub fn new(
152 coarse: Vec<(f64, bool)>,
153 fine: Vec<f64>,
154 min: f64,
155 max: f64,
156 coarse_resolution: usize,
157 ) -> Self {
158 assert_eq!(
159 coarse.len(),
160 coarse_resolution,
161 "coarse length must match coarse_resolution"
162 );
163 assert!(min < max, "min must be less than max");
164 Self {
165 coarse,
166 fine,
167 min,
168 max,
169 coarse_resolution,
170 }
171 }
172
173 pub fn lookup(&self, x: f64) -> f64 {
177 if x <= self.min {
178 return self.coarse[0].0;
179 }
180 if x >= self.max {
181 return self.coarse[self.coarse_resolution - 1].0;
182 }
183
184 let normalized = (x - self.min) / (self.max - self.min);
186 let coarse_idx =
187 ((normalized * self.coarse_resolution as f64) as usize).min(self.coarse_resolution - 1);
188
189 let (payload, is_pointer) = self.coarse[coarse_idx];
190
191 if !is_pointer {
192 payload
194 } else {
195 let offset = payload as usize;
197 if offset < self.fine.len() {
198 self.fine[offset]
199 } else {
200 payload }
202 }
203 }
204
205 pub fn save(&self, path: &Path) -> bincode::Result<()> {
207 let file = File::create(path)?;
208 let writer = BufWriter::new(file);
209 bincode::serialize_into(writer, self)
210 }
211
212 pub fn load(path: &Path) -> bincode::Result<Self> {
214 let file = File::open(path)?;
215 let reader = BufReader::new(file);
216 bincode::deserialize_from(reader)
217 }
218
219 pub fn stats(&self) -> IndirectionStats {
221 let num_direct = self.coarse.iter().filter(|(_, is_ptr)| !is_ptr).count();
222 let num_pointers = self.coarse.iter().filter(|(_, is_ptr)| *is_ptr).count();
223 let coarse_bytes = std::mem::size_of::<f64>() * 2 * self.coarse.len(); let fine_bytes = std::mem::size_of::<f64>() * self.fine.len();
225
226 IndirectionStats {
227 num_direct_values: num_direct,
228 num_pointers,
229 fine_array_size: self.fine.len(),
230 coarse_bytes,
231 fine_bytes,
232 total_bytes: coarse_bytes + fine_bytes,
233 }
234 }
235}
236
237#[derive(Debug, Clone)]
239pub struct IndirectionStats {
240 pub num_direct_values: usize,
241 pub num_pointers: usize,
242 pub fine_array_size: usize,
243 pub coarse_bytes: usize,
244 pub fine_bytes: usize,
245 pub total_bytes: usize,
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_adaptive_grid_1d_linear() {
254 let grid_gen = AdaptiveGrid1D::new(0.0, 1.0, 0.1, 10);
255 let grid = grid_gen.generate(|x| x); assert!(grid.len() >= 2);
259 assert!((grid.points[0] - 0.0).abs() < 1e-10);
260 assert!((grid.points[grid.len() - 1] - 1.0).abs() < 1e-10);
261
262 assert!(grid.len() <= 10, "Linear function should not over-refine");
264 }
265
266 #[test]
267 fn test_adaptive_grid_1d_quadratic() {
268 let grid_gen = AdaptiveGrid1D::new(0.0, 1.0, 0.01, 10);
270 let grid = grid_gen.generate(|x| x * x);
271
272 assert!(grid.len() >= 2);
273 assert!((grid.points[0] - 0.0).abs() < 1e-10);
274 assert!((grid.points[grid.len() - 1] - 1.0).abs() < 1e-10);
275 }
276
277 #[test]
278 fn test_adaptive_grid_1d_sharp_feature() {
279 let grid_gen = AdaptiveGrid1D::new(0.0, 1.0, 0.01, 12);
282 let grid = grid_gen.generate(|x| {
283 let dx = x - 0.5;
284 1.0 / (1.0 + 100.0 * dx * dx)
285 });
286
287 assert!(
289 grid.len() >= 5,
290 "Sharp features should trigger some refinement"
291 );
292 }
293
294 #[test]
295 fn test_adaptive_grid_monotonicity() {
296 let grid_gen = AdaptiveGrid1D::new(0.0, 1.0, 0.01, 10);
297 let grid = grid_gen.generate(|x| x.exp());
298
299 for i in 0..grid.len() - 1 {
301 assert!(
302 grid.points[i] < grid.points[i + 1],
303 "Grid points must be strictly increasing"
304 );
305 }
306 }
307
308 #[test]
309 fn test_indirection_table_direct_values() {
310 let coarse = vec![(1.0, false), (2.0, false), (3.0, false), (4.0, false)];
312 let fine = vec![];
313 let table = IndirectionTable::new(coarse, fine, 0.0, 4.0, 4);
314
315 assert_eq!(table.lookup(0.0), 1.0); assert_eq!(table.lookup(0.5), 1.0); assert_eq!(table.lookup(1.0), 2.0); assert_eq!(table.lookup(2.5), 3.0); assert_eq!(table.lookup(4.0), 4.0); }
322
323 #[test]
324 fn test_indirection_table_with_pointers() {
325 let coarse = vec![
327 (1.0, false), (0.0, true), (3.0, false), (1.0, true), ];
332 let fine = vec![1.5, 2.0, 3.5]; let table = IndirectionTable::new(coarse, fine, 0.0, 4.0, 4);
334
335 assert_eq!(table.lookup(0.1), 1.0);
337 assert_eq!(table.lookup(2.1), 3.0);
338
339 assert_eq!(table.lookup(1.1), 1.5); assert_eq!(table.lookup(3.1), 2.0); }
343
344 #[test]
345 fn test_indirection_stats() {
346 let coarse = vec![(1.0, false), (0.0, true), (3.0, false), (1.0, true)];
347 let fine = vec![1.5, 2.0, 3.5];
348 let table = IndirectionTable::new(coarse, fine, 0.0, 4.0, 4);
349
350 let stats = table.stats();
351 assert_eq!(stats.num_direct_values, 2);
352 assert_eq!(stats.num_pointers, 2);
353 assert_eq!(stats.fine_array_size, 3);
354 assert!(stats.total_bytes > 0);
355 }
356
357 #[test]
358 fn test_adaptive_grid_max_depth_limit() {
359 let grid_gen = AdaptiveGrid1D::new(0.0, 1.0, 0.0001, 5); let grid = grid_gen.generate(|x| x.sin());
362
363 assert!(
365 grid.len() < 1000,
366 "max_depth should prevent pathological refinement"
367 );
368 }
369
370 #[test]
371 fn test_adaptive_grid_constant_function() {
372 let grid_gen = AdaptiveGrid1D::new(0.0, 1.0, 0.1, 10);
374 let grid = grid_gen.generate(|_x| 5.0);
375
376 assert!(grid.len() <= 3, "Constant function should refine minimally");
378 }
379
380 #[test]
381 fn test_indirection_table_boundary_clamp() {
382 let coarse = vec![(1.0, false), (2.0, false), (3.0, false)];
383 let fine = vec![];
384 let table = IndirectionTable::new(coarse, fine, 1.0, 3.0, 3);
385
386 assert_eq!(table.lookup(-10.0), 1.0);
388 assert_eq!(table.lookup(100.0), 3.0);
390 }
391}