1#![expect(
11 clippy::manual_midpoint,
12 reason = "must match vanilla's truncate-toward-zero behavior"
13)]
14
15use std::cmp::Ordering;
16
17use super::PARAMETER_COUNT;
18use super::types::{Parameter, ParameterPoint, TargetPoint};
19use rayon::iter::{IntoParallelIterator, ParallelIterator};
20
21const CHILDREN_PER_NODE: usize = 19;
23
24enum RTreeNode {
27 Leaf {
29 parameter_space: [Parameter; PARAMETER_COUNT],
30 value_index: usize,
31 },
32 SubTree {
34 parameter_space: [Parameter; PARAMETER_COUNT],
35 children: Vec<RTreeNode>,
36 },
37}
38
39impl RTreeNode {
40 const fn parameter_space(&self) -> &[Parameter; PARAMETER_COUNT] {
42 match self {
43 Self::Leaf {
44 parameter_space, ..
45 }
46 | Self::SubTree {
47 parameter_space, ..
48 } => parameter_space,
49 }
50 }
51}
52
53#[derive(Clone)]
55struct BuildEntry {
56 parameter_space: [Parameter; PARAMETER_COUNT],
57 index: usize,
58}
59
60fn build_parameter_space(children: &[RTreeNode]) -> [Parameter; PARAMETER_COUNT] {
62 let mut bounds: [Option<Parameter>; PARAMETER_COUNT] = [None; PARAMETER_COUNT];
63 for child in children {
64 let ps = child.parameter_space();
65 for d in 0..PARAMETER_COUNT {
66 bounds[d] = Some(ps[d].span_with(bounds[d].as_ref()));
67 }
68 }
69 bounds.map(|b| b.expect("bounds should be initialized"))
70}
71
72fn cost(parameter_space: &[Parameter; PARAMETER_COUNT]) -> i64 {
74 let mut result = 0i64;
75 for p in parameter_space {
76 result += (p.max - p.min).abs();
77 }
78 result
79}
80
81fn build_tree(entries: &mut [BuildEntry]) -> RTreeNode {
83 assert!(!entries.is_empty());
84
85 if entries.len() == 1 {
86 return RTreeNode::Leaf {
87 parameter_space: entries[0].parameter_space,
88 value_index: entries[0].index,
89 };
90 }
91
92 if entries.len() <= CHILDREN_PER_NODE {
93 entries.sort_by_key(|e| {
95 e.parameter_space
96 .iter()
97 .map(|p| ((p.min + p.max) / 2).abs())
98 .sum::<i64>()
99 });
100
101 let children: Vec<RTreeNode> = entries
102 .iter()
103 .map(|e| RTreeNode::Leaf {
104 parameter_space: e.parameter_space,
105 value_index: e.index,
106 })
107 .collect();
108 let ps = build_parameter_space(&children);
109 return RTreeNode::SubTree {
110 parameter_space: ps,
111 children,
112 };
113 }
114
115 let mut min_cost = i64::MAX;
121 let mut best_dim = 0;
122 let mut best_buckets: Option<Vec<Vec<BuildEntry>>> = None;
123
124 for d in 0..PARAMETER_COUNT {
125 sort_entries(entries, d);
126 let (bucket_cost, buckets) = snapshot_buckets(entries);
127 if min_cost > bucket_cost {
128 min_cost = bucket_cost;
129 best_dim = d;
130 best_buckets = Some(buckets);
131 }
132 }
133
134 let buckets = best_buckets.expect("should have found at least one dimension");
136
137 let mut bucket_subtrees: Vec<([Parameter; PARAMETER_COUNT], Vec<BuildEntry>)> = buckets
139 .into_iter()
140 .map(|bucket_entries| {
141 let mut bounds: [Option<Parameter>; PARAMETER_COUNT] = [None; PARAMETER_COUNT];
142 for e in &bucket_entries {
143 for dim in 0..PARAMETER_COUNT {
144 bounds[dim] = Some(e.parameter_space[dim].span_with(bounds[dim].as_ref()));
145 }
146 }
147 let ps = bounds.map(|b| b.expect("bounds should be initialized"));
148 (ps, bucket_entries)
149 })
150 .collect();
151
152 sort_bucket_subtrees(&mut bucket_subtrees, best_dim);
154 let final_children: Vec<RTreeNode> = bucket_subtrees
156 .into_par_iter()
157 .map(|(_, mut child_entries)| build_tree(&mut child_entries))
158 .collect();
159
160 let ps = build_parameter_space(&final_children);
161 RTreeNode::SubTree {
162 parameter_space: ps,
163 children: final_children,
164 }
165}
166
167fn sort_entries(entries: &mut [BuildEntry], dimension: usize) {
169 entries.sort_by(|a, b| {
170 for offset in 0..PARAMETER_COUNT {
171 let d = (dimension + offset) % PARAMETER_COUNT;
172 let center_a = (a.parameter_space[d].min + a.parameter_space[d].max) / 2;
173 let center_b = (b.parameter_space[d].min + b.parameter_space[d].max) / 2;
174 let cmp = center_a.cmp(¢er_b);
175 if cmp != Ordering::Equal {
176 return cmp;
177 }
178 }
179 Ordering::Equal
180 });
181}
182
183fn sort_bucket_subtrees(
186 subtrees: &mut [([Parameter; PARAMETER_COUNT], Vec<BuildEntry>)],
187 dimension: usize,
188) {
189 subtrees.sort_by(|a, b| {
190 for offset in 0..PARAMETER_COUNT {
191 let d = (dimension + offset) % PARAMETER_COUNT;
192 let center_a = (a.0[d].min + a.0[d].max) / 2;
193 let center_b = (b.0[d].min + b.0[d].max) / 2;
194 let cmp = center_a.abs().cmp(¢er_b.abs());
195 if cmp != Ordering::Equal {
196 return cmp;
197 }
198 }
199 Ordering::Equal
200 });
201}
202
203fn expected_children_count(total: usize) -> usize {
205 let log_base_children = ((total as f64) - 0.01).ln() / (CHILDREN_PER_NODE as f64).ln();
206 (CHILDREN_PER_NODE as f64).powf(log_base_children.floor()) as usize
207}
208
209fn snapshot_buckets(entries: &[BuildEntry]) -> (i64, Vec<Vec<BuildEntry>>) {
215 let expected = expected_children_count(entries.len());
216 let mut buckets = Vec::new();
217 let mut total_cost = 0i64;
218 let mut start = 0;
219 while start < entries.len() {
220 let end = (start + expected).min(entries.len());
221 let bucket = entries[start..end].to_vec();
222 let mut bounds: [Option<Parameter>; PARAMETER_COUNT] = [None; PARAMETER_COUNT];
224 for e in &bucket {
225 for d in 0..PARAMETER_COUNT {
226 bounds[d] = Some(e.parameter_space[d].span_with(bounds[d].as_ref()));
227 }
228 }
229 let ps = bounds.map(|b| b.expect("bounds should be initialized"));
230 total_cost += cost(&ps);
231 buckets.push(bucket);
232 start = end;
233 }
234 (total_cost, buckets)
235}
236
237struct FlatNode {
244 mins: [i64; PARAMETER_COUNT],
246 maxs: [i64; PARAMETER_COUNT],
248 value_index: u32,
251 children_start: u32,
253 children_count: u8,
255}
256
257impl FlatNode {
258 #[inline]
263 fn distance(&self, target: &[i64; PARAMETER_COUNT]) -> i64 {
264 let mut d = 0i64;
265 for i in 0..PARAMETER_COUNT {
266 let di = (target[i] - self.maxs[i])
267 .max(self.mins[i] - target[i])
268 .max(0);
269 d += di * di;
270 }
271 d
272 }
273
274 #[inline]
275 const fn is_leaf(&self) -> bool {
276 self.children_count == 0
277 }
278}
279
280fn flatten_tree(root: RTreeNode) -> Vec<FlatNode> {
285 use std::collections::VecDeque;
286
287 let mut nodes: Vec<FlatNode> = Vec::new();
288 let mut queue: VecDeque<(Vec<RTreeNode>, Option<u32>)> = VecDeque::new();
291 queue.push_back((vec![root], None));
292
293 while let Some((batch, parent_idx)) = queue.pop_front() {
294 let batch_start = nodes.len() as u32;
295
296 if let Some(pidx) = parent_idx {
298 nodes[pidx as usize].children_start = batch_start;
299 }
300
301 for node in batch {
302 let flat_idx = nodes.len() as u32;
303 match node {
304 RTreeNode::Leaf {
305 parameter_space,
306 value_index,
307 } => {
308 nodes.push(FlatNode {
309 mins: parameter_space.map(|p| p.min),
310 maxs: parameter_space.map(|p| p.max),
311 value_index: value_index as u32,
312 children_start: 0,
313 children_count: 0,
314 });
315 }
316 RTreeNode::SubTree {
317 parameter_space,
318 children,
319 } => {
320 let children_count = children.len() as u8;
321 nodes.push(FlatNode {
322 mins: parameter_space.map(|p| p.min),
323 maxs: parameter_space.map(|p| p.max),
324 value_index: u32::MAX,
325 children_start: 0, children_count,
327 });
328 queue.push_back((children, Some(flat_idx)));
329 }
330 }
331 }
332 }
333
334 nodes
335}
336
337fn search_nearest(
342 nodes: &[FlatNode],
343 node: &FlatNode,
344 target: &[i64; PARAMETER_COUNT],
345 best_dist: &mut i64,
346 best_idx: &mut Option<u32>,
347) {
348 let start = node.children_start as usize;
349 let end = start + node.children_count as usize;
350 let children = &nodes[start..end];
351
352 for child in children {
353 let child_dist = child.distance(target);
354 if *best_dist > child_dist {
356 if child.is_leaf() {
357 *best_dist = child_dist;
359 *best_idx = Some(child.value_index);
360 } else {
361 search_nearest(nodes, child, target, best_dist, best_idx);
363 }
364 }
365 }
366}
367
368pub struct ParameterList<T> {
372 values: Vec<(ParameterPoint, T)>,
374 param_spaces: Vec<[Parameter; PARAMETER_COUNT]>,
376 nodes: Vec<FlatNode>,
378}
379
380impl<T> ParameterList<T> {
381 #[must_use]
387 pub fn new(values: Vec<(ParameterPoint, T)>) -> Self {
388 assert!(!values.is_empty(), "Need at least one value");
389
390 let param_spaces: Vec<[Parameter; PARAMETER_COUNT]> =
391 values.iter().map(|(pp, _)| pp.parameter_space()).collect();
392
393 let mut entries: Vec<BuildEntry> = values
395 .iter()
396 .enumerate()
397 .map(|(i, (pp, _))| BuildEntry {
398 parameter_space: pp.parameter_space(),
399 index: i,
400 })
401 .collect();
402
403 let root = build_tree(&mut entries);
404 let nodes = flatten_tree(root);
405
406 Self {
407 values,
408 param_spaces,
409 nodes,
410 }
411 }
412
413 #[must_use]
415 pub fn values(&self) -> &[(ParameterPoint, T)] {
416 &self.values
417 }
418
419 #[must_use]
432 pub fn find_value(&self, target: &TargetPoint) -> &T {
433 let target_array = target.to_parameter_array();
434 let root = &self.nodes[0];
435 if root.is_leaf() {
436 return &self.values[root.value_index as usize].1;
437 }
438 let mut best_dist = i64::MAX;
439 let mut best_idx = None;
440 search_nearest(
441 &self.nodes,
442 root,
443 &target_array,
444 &mut best_dist,
445 &mut best_idx,
446 );
447 &self.values[best_idx.expect("R-Tree search should always find a value") as usize].1
448 }
449
450 #[must_use]
461 pub fn find_value_cached(&self, target: &TargetPoint, cache: &mut Option<usize>) -> &T {
462 let target_array = target.to_parameter_array();
463
464 let root = &self.nodes[0];
465 if root.is_leaf() {
466 let idx = root.value_index as usize;
467 *cache = Some(idx);
468 return &self.values[idx].1;
469 }
470
471 let (mut best_dist, init_idx) = match *cache {
473 Some(idx) => {
474 let ps = &self.param_spaces[idx];
475 let mut d = 0i64;
476 for i in 0..PARAMETER_COUNT {
477 let di = ps[i].distance(target_array[i]);
478 d += di * di;
479 }
480 (d, Some(idx as u32))
481 }
482 None => (i64::MAX, None),
483 };
484
485 let mut best_idx = init_idx;
486 search_nearest(
487 &self.nodes,
488 root,
489 &target_array,
490 &mut best_dist,
491 &mut best_idx,
492 );
493 let result_idx = best_idx.expect("R-Tree search should always find a value") as usize;
494
495 *cache = Some(result_idx);
496 &self.values[result_idx].1
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_parameter_list_find_value() {
506 let values = vec![
507 (
508 ParameterPoint::new(
509 Parameter::new(-10000, 0),
510 Parameter::new(0, 0),
511 Parameter::new(0, 0),
512 Parameter::new(0, 0),
513 Parameter::new(0, 0),
514 Parameter::new(0, 0),
515 0,
516 ),
517 "cold",
518 ),
519 (
520 ParameterPoint::new(
521 Parameter::new(0, 10000),
522 Parameter::new(0, 0),
523 Parameter::new(0, 0),
524 Parameter::new(0, 0),
525 Parameter::new(0, 0),
526 Parameter::new(0, 0),
527 0,
528 ),
529 "hot",
530 ),
531 ];
532
533 let list = ParameterList::new(values);
534
535 let cold_target = TargetPoint::new(-5000, 0, 0, 0, 0, 0);
537 assert_eq!(*list.find_value(&cold_target), "cold");
538
539 let hot_target = TargetPoint::new(5000, 0, 0, 0, 0, 0);
541 assert_eq!(*list.find_value(&hot_target), "hot");
542 }
543}