1use alloc::vec::Vec;
97
98use crate::bounds::{Bounds, union_all};
99
100pub trait SplitParameters {
109 const MAX: usize;
111 const MIN: usize;
113 const LEAF_MAX: usize = Self::MAX;
115 const LEAF_MIN: usize = Self::MIN;
117 const BRANCH_MAX: usize = Self::MAX;
119 const BRANCH_MIN: usize = Self::MIN;
121 const BULK_LEAF_MAX: usize = Self::LEAF_MAX;
123 const BULK_BRANCH_MAX: usize = Self::BRANCH_MAX;
125
126 fn split(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>);
129
130 #[must_use]
132 fn split_leaf(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
133 Self::split(entries)
134 }
135
136 #[must_use]
138 fn split_branch(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
139 Self::split(entries)
140 }
141}
142
143#[derive(Debug, Clone, Copy, Default)]
154pub struct Quadratic<const MAX: usize = 32, const MIN: usize = 9>;
155
156impl<const MAX: usize, const MIN: usize> SplitParameters for Quadratic<MAX, MIN> {
157 const MAX: usize = MAX;
158 const MIN: usize = MIN;
159
160 fn split(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
161 quadratic_partition(entries, MIN)
162 }
163}
164
165#[derive(Debug, Clone, Copy, Default)]
172pub struct AsymmetricQuadratic<
173 const BRANCH_MAX: usize,
174 const BRANCH_MIN: usize,
175 const LEAF_MAX: usize,
176 const LEAF_MIN: usize,
177>;
178
179impl<const BRANCH_MAX: usize, const BRANCH_MIN: usize, const LEAF_MAX: usize, const LEAF_MIN: usize>
180 SplitParameters for AsymmetricQuadratic<BRANCH_MAX, BRANCH_MIN, LEAF_MAX, LEAF_MIN>
181{
182 const MAX: usize = BRANCH_MAX;
183 const MIN: usize = BRANCH_MIN;
184 const LEAF_MAX: usize = LEAF_MAX;
185 const LEAF_MIN: usize = LEAF_MIN;
186 const BRANCH_MAX: usize = BRANCH_MAX;
187 const BRANCH_MIN: usize = BRANCH_MIN;
188
189 fn split(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
190 quadratic_partition(entries, BRANCH_MIN)
191 }
192
193 fn split_leaf(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
194 quadratic_partition(entries, LEAF_MIN)
195 }
196}
197
198#[derive(Debug, Clone, Copy, Default)]
206pub struct RStarSplit<const MAX: usize = 32, const MIN: usize = 9>;
207
208impl<const MAX: usize, const MIN: usize> SplitParameters for RStarSplit<MAX, MIN> {
209 const MAX: usize = MAX;
210 const MIN: usize = MIN;
211
212 fn split(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
213 rstar_partition(entries, MIN)
214 }
215}
216
217#[derive(Debug, Clone, Copy, Default)]
225pub struct AsymmetricRStarSplit<
226 const BRANCH_MAX: usize,
227 const BRANCH_MIN: usize,
228 const LEAF_MAX: usize,
229 const LEAF_MIN: usize,
230 const PACKED_BRANCH_MAX: usize = 0,
231 const PACKED_LEAF_MAX: usize = 0,
232>;
233
234impl<
235 const BRANCH_MAX: usize,
236 const BRANCH_MIN: usize,
237 const LEAF_MAX: usize,
238 const LEAF_MIN: usize,
239 const PACKED_BRANCH_MAX: usize,
240 const PACKED_LEAF_MAX: usize,
241> SplitParameters
242 for AsymmetricRStarSplit<
243 BRANCH_MAX,
244 BRANCH_MIN,
245 LEAF_MAX,
246 LEAF_MIN,
247 PACKED_BRANCH_MAX,
248 PACKED_LEAF_MAX,
249 >
250{
251 const MAX: usize = BRANCH_MAX;
252 const MIN: usize = BRANCH_MIN;
253 const LEAF_MAX: usize = LEAF_MAX;
254 const LEAF_MIN: usize = LEAF_MIN;
255 const BRANCH_MAX: usize = BRANCH_MAX;
256 const BRANCH_MIN: usize = BRANCH_MIN;
257 const BULK_LEAF_MAX: usize = if PACKED_LEAF_MAX == 0 {
258 LEAF_MAX
259 } else {
260 PACKED_LEAF_MAX
261 };
262 const BULK_BRANCH_MAX: usize = if PACKED_BRANCH_MAX == 0 {
263 BRANCH_MAX
264 } else {
265 PACKED_BRANCH_MAX
266 };
267
268 fn split(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
269 rstar_partition(entries, BRANCH_MIN)
270 }
271
272 fn split_leaf(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
273 rstar_partition(entries, LEAF_MIN)
274 }
275}
276
277fn quadratic_partition(entries: &[Bounds], min: usize) -> (Vec<usize>, Vec<usize>) {
278 let n = entries.len();
279 let (s1, s2) = quadratic_seeds(entries);
280
281 let mut g1 = Vec::from([s1]);
282 let mut g2 = Vec::from([s2]);
283 let mut b1 = entries[s1];
284 let mut b2 = entries[s2];
285
286 let mut remaining: Vec<usize> = (0..n).filter(|&i| i != s1 && i != s2).collect();
287
288 while !remaining.is_empty() {
289 if g1.len() + remaining.len() == min {
292 for idx in remaining.drain(..) {
293 g1.push(idx);
294 b1 = b1.union(&entries[idx]);
295 }
296 break;
297 }
298 if g2.len() + remaining.len() == min {
299 for idx in remaining.drain(..) {
300 g2.push(idx);
301 b2 = b2.union(&entries[idx]);
302 }
303 break;
304 }
305
306 let mut next_pos = 0;
309 let mut best_difference = f64::NEG_INFINITY;
310 for (pos, &idx) in remaining.iter().enumerate() {
311 let e1 = b1.enlargement(&entries[idx]);
312 let e2 = b2.enlargement(&entries[idx]);
313 let difference = (e1 - e2).abs();
314 if difference > best_difference {
315 next_pos = pos;
316 best_difference = difference;
317 }
318 }
319 let idx = remaining.swap_remove(next_pos);
320 let e1 = b1.enlargement(&entries[idx]);
321 let e2 = b2.enlargement(&entries[idx]);
322 let assign_first = match e1.total_cmp(&e2) {
323 core::cmp::Ordering::Less => true,
324 core::cmp::Ordering::Greater => false,
325 core::cmp::Ordering::Equal => match b1.area().total_cmp(&b2.area()) {
326 core::cmp::Ordering::Less => true,
327 core::cmp::Ordering::Greater => false,
328 core::cmp::Ordering::Equal => g1.len() <= g2.len(),
329 },
330 };
331 if assign_first {
332 g1.push(idx);
333 b1 = b1.union(&entries[idx]);
334 } else {
335 g2.push(idx);
336 b2 = b2.union(&entries[idx]);
337 }
338 }
339
340 (g1, g2)
341}
342
343#[allow(
344 clippy::float_cmp,
345 reason = "exact R*-split tie-break between equal overlap and area"
346)]
347fn rstar_partition(entries: &[Bounds], min: usize) -> (Vec<usize>, Vec<usize>) {
348 let mut best_axis = 0;
349 let mut best_margin = f64::INFINITY;
350 for axis in 0..2 {
351 let ordered = sorted_indices(entries, axis);
352 let suffixes = suffix_bounds(entries, &ordered);
353 let mut left = partition_bounds(entries, &ordered[..min]);
354 let mut margin = left.half_perimeter() + suffixes[min].half_perimeter();
355 for split in (min + 1)..=entries.len() - min {
356 left = left.union(&entries[ordered[split - 1]]);
357 margin += left.half_perimeter() + suffixes[split].half_perimeter();
358 }
359 if margin < best_margin {
360 best_axis = axis;
361 best_margin = margin;
362 }
363 }
364
365 let ordered = sorted_indices(entries, best_axis);
366 let suffixes = suffix_bounds(entries, &ordered);
367 let mut best_split = min;
368 let mut best_overlap = f64::INFINITY;
369 let mut best_area = f64::INFINITY;
370 let mut left = partition_bounds(entries, &ordered[..min]);
371 for split in min..=entries.len() - min {
372 if split > min {
373 left = left.union(&entries[ordered[split - 1]]);
374 }
375 let right = suffixes[split];
376 let overlap = intersection_area(&left, &right);
377 let area = left.area() + right.area();
378 if overlap < best_overlap || (overlap == best_overlap && area < best_area) {
379 best_split = split;
380 best_overlap = overlap;
381 best_area = area;
382 }
383 }
384
385 (
386 ordered[..best_split].to_vec(),
387 ordered[best_split..].to_vec(),
388 )
389}
390
391fn sorted_indices(entries: &[Bounds], axis: usize) -> Vec<usize> {
392 let mut indices: Vec<usize> = (0..entries.len()).collect();
393 indices.sort_unstable_by(|&left, &right| {
394 entries[left].min[axis].total_cmp(&entries[right].min[axis])
395 });
396 indices
397}
398
399fn partition_bounds(entries: &[Bounds], indices: &[usize]) -> Bounds {
400 let mut bounds = entries[indices[0]];
401 for &index in &indices[1..] {
402 bounds = bounds.union(&entries[index]);
403 }
404 bounds
405}
406
407fn suffix_bounds(entries: &[Bounds], indices: &[usize]) -> Vec<Bounds> {
408 let mut suffixes = Vec::with_capacity(indices.len());
409 let mut bounds = entries[*indices.last().expect("a split has at least two entries")];
410 suffixes.push(bounds);
411 for &index in indices[..indices.len() - 1].iter().rev() {
412 bounds = bounds.union(&entries[index]);
413 suffixes.push(bounds);
414 }
415 suffixes.reverse();
416 suffixes
417}
418
419fn intersection_area(left: &Bounds, right: &Bounds) -> f64 {
420 let width = left.max[0].min(right.max[0]) - left.min[0].max(right.min[0]);
421 let height = left.max[1].min(right.max[1]) - left.min[1].max(right.min[1]);
422 width.max(0.0) * height.max(0.0)
423}
424
425#[derive(Debug, Clone, Copy, Default)]
435pub struct Linear<const MAX: usize = 32, const MIN: usize = 9>;
436
437impl<const MAX: usize, const MIN: usize> SplitParameters for Linear<MAX, MIN> {
438 const MAX: usize = MAX;
439 const MIN: usize = MIN;
440
441 fn split(entries: &[Bounds]) -> (Vec<usize>, Vec<usize>) {
442 let n = entries.len();
443 let (s1, s2) = linear_seeds(entries);
444
445 let mut g1 = Vec::from([s1]);
446 let mut g2 = Vec::from([s2]);
447 let mut b1 = entries[s1];
448 let mut b2 = entries[s2];
449
450 let mut remaining: Vec<usize> = (0..n).filter(|&i| i != s1 && i != s2).collect();
451 while let Some(idx) = remaining.pop() {
452 if g1.len() + remaining.len() + 1 == MIN {
453 g1.push(idx);
454 b1 = b1.union(&entries[idx]);
455 continue;
456 }
457 if g2.len() + remaining.len() + 1 == MIN {
458 g2.push(idx);
459 b2 = b2.union(&entries[idx]);
460 continue;
461 }
462 let e1 = b1.enlargement(&entries[idx]);
463 let e2 = b2.enlargement(&entries[idx]);
464 if e1 <= e2 {
465 g1.push(idx);
466 b1 = b1.union(&entries[idx]);
467 } else {
468 g2.push(idx);
469 b2 = b2.union(&entries[idx]);
470 }
471 }
472 (g1, g2)
473 }
474}
475
476fn quadratic_seeds(entries: &[Bounds]) -> (usize, usize) {
479 let mut worst = (0, 1, f64::NEG_INFINITY);
480 for i in 0..entries.len() {
481 for j in (i + 1)..entries.len() {
482 let combined = entries[i].union(&entries[j]).area();
483 let waste = combined - entries[i].area() - entries[j].area();
484 if waste > worst.2 {
485 worst = (i, j, waste);
486 }
487 }
488 }
489 (worst.0, worst.1)
490}
491
492fn linear_seeds(entries: &[Bounds]) -> (usize, usize) {
495 let all = union_all(entries);
496 let width = [all.max[0] - all.min[0], all.max[1] - all.min[1]];
497 let axis = usize::from(width[1] > width[0]);
498
499 let mut lo_idx = 0;
501 let mut hi_idx = 0;
502 let mut max_low = f64::NEG_INFINITY;
503 let mut min_high = f64::INFINITY;
504 for (i, b) in entries.iter().enumerate() {
505 if b.min[axis] > max_low {
506 max_low = b.min[axis];
507 hi_idx = i;
508 }
509 if b.max[axis] < min_high {
510 min_high = b.max[axis];
511 lo_idx = i;
512 }
513 }
514 if lo_idx == hi_idx {
515 (0, usize::from(entries.len() > 1))
517 } else {
518 (lo_idx, hi_idx)
519 }
520}
521
522#[cfg(test)]
523#[allow(
524 clippy::cast_precision_loss,
525 reason = "small test indices convert exactly to f64"
526)]
527mod tests {
528 use super::{
529 AsymmetricQuadratic, AsymmetricRStarSplit, Linear, Quadratic, RStarSplit, SplitParameters,
530 };
531 use crate::bounds::Bounds;
532
533 fn line_of_boxes(n: usize) -> Vec<Bounds> {
534 (0..n)
535 .map(|i| Bounds::point([i as f64, 0.0]))
536 .collect::<Vec<_>>()
537 }
538
539 #[test]
540 fn quadratic_splits_into_two_min_sized_groups() {
541 let entries = line_of_boxes(9);
542 let (g1, g2) = <Quadratic<8, 3>>::split(&entries);
543 assert!(g1.len() >= 3 && g2.len() >= 3);
544 assert_eq!(g1.len() + g2.len(), 9);
545 }
546
547 #[test]
548 fn linear_splits_into_two_min_sized_groups() {
549 let entries = line_of_boxes(9);
550 let (g1, g2) = <Linear<8, 3>>::split(&entries);
551 assert!(g1.len() >= 3 && g2.len() >= 3);
552 assert_eq!(g1.len() + g2.len(), 9);
553 }
554
555 #[test]
556 fn rstar_splits_into_two_min_sized_groups() {
557 let entries = line_of_boxes(9);
558 let (g1, g2) = <RStarSplit<8, 3>>::split(&entries);
559 assert!(g1.len() >= 3 && g2.len() >= 3);
560 assert_eq!(g1.len() + g2.len(), 9);
561 }
562
563 #[test]
564 fn every_index_assigned_exactly_once() {
565 let entries = line_of_boxes(9);
566 let (mut g1, g2) = <Quadratic<8, 3>>::split(&entries);
567 g1.extend(g2);
568 g1.sort_unstable();
569 assert_eq!(g1, (0..9).collect::<Vec<_>>());
570 }
571
572 #[test]
573 fn asymmetric_leaf_and_branch_minimums_are_independent() {
574 type Params = AsymmetricQuadratic<8, 3, 32, 9>;
575 let branch_entries = line_of_boxes(9);
576 let (b1, b2) = Params::split_branch(&branch_entries);
577 assert!(b1.len() >= Params::BRANCH_MIN && b2.len() >= Params::BRANCH_MIN);
578
579 let leaf_entries = line_of_boxes(33);
580 let (l1, l2) = Params::split_leaf(&leaf_entries);
581 assert!(l1.len() >= Params::LEAF_MIN && l2.len() >= Params::LEAF_MIN);
582 }
583
584 #[test]
585 fn asymmetric_rstar_leaf_and_branch_minimums_are_independent() {
586 type Params = AsymmetricRStarSplit<8, 3, 32, 9>;
587 let branch_entries = line_of_boxes(9);
588 let (b1, b2) = Params::split_branch(&branch_entries);
589 assert!(b1.len() >= Params::BRANCH_MIN && b2.len() >= Params::BRANCH_MIN);
590
591 let leaf_entries = line_of_boxes(33);
592 let (l1, l2) = Params::split_leaf(&leaf_entries);
593 assert!(l1.len() >= Params::LEAF_MIN && l2.len() >= Params::LEAF_MIN);
594 }
595
596 #[test]
597 fn asymmetric_rstar_bulk_capacities_are_independent_and_optional() {
598 type Inherited = AsymmetricRStarSplit<6, 2, 12, 4>;
599 type Tuned = AsymmetricRStarSplit<6, 2, 12, 4, 4, 4>;
600
601 assert_eq!(Inherited::BULK_BRANCH_MAX, 6);
602 assert_eq!(Inherited::BULK_LEAF_MAX, 12);
603
604 assert_eq!(Tuned::BRANCH_MAX, 6);
605 assert_eq!(Tuned::LEAF_MAX, 12);
606 assert_eq!(Tuned::BULK_BRANCH_MAX, 4);
607 assert_eq!(Tuned::BULK_LEAF_MAX, 4);
608 }
609}