diff --git a/src/internals.rs b/src/internals.rs index 80ca0c8..7f2fab8 100644 --- a/src/internals.rs +++ b/src/internals.rs @@ -3,19 +3,19 @@ use crate::boxes::BBox; use crate::set::BBoxSet; +use crate::AnswerFormat; use crate::{HasInfinity, Rng}; - /// Reports intersections between `intervals` and `points` by scanning in dimension 0, /// treating boxes in `points` as points: intersections are only reported when the low /// endpoint in dimension 0 of a box in `points` is inside the projection of a box in `intervals`. /// * `intervals` and `points` must be sorted before calling /// * `max_dim_check`: highest dimension that should be checked for intersection /// * `out` will contain pairs of `ID`s of intersecting boxes. -pub fn one_way_scan( +pub fn one_way_scan<'a, B, ID>( intervals: &BBoxSet, points: &BBoxSet, max_dim_check: usize, - out: &mut Vec<(ID, ID)>, + mut out: AnswerFormat<'a, ID>, ) where B: BBox, ID: Copy + PartialOrd, @@ -25,7 +25,7 @@ pub fn one_way_scan( let mut p_min_idx = 0; // iterate through (sorted) intervals - for i in &intervals.boxes { + for (i_idx, i) in intervals.boxes.iter().enumerate() { let &(i, i_id) = i; let i_min = i.lo(0); let i_max = i.hi(0); @@ -62,18 +62,28 @@ pub fn one_way_scan( continue 'points; } - out.push((i_id, p_id)); + match out { + AnswerFormat::Index(ref mut out) => { + out.push((i_idx, p_idx)); + } + AnswerFormat::Ident(ref mut out) => { + out.push((i_id, p_id)); + } + AnswerFormat::Both(ref mut out) => { + out.push(((p_idx, p_min_idx), (i_id, p_id))); + } + } } } } /// Reports intersections between `intervals` and `points` by scanning in dimension 0 (because that's where boxes are sorted), /// but pretends it was scanning in dimension `max_dim_check` by treating `points` as points there, as in [`one_way_scan`] -pub fn simulated_one_way_scan( +pub fn simulated_one_way_scan<'a, B, ID>( intervals: &BBoxSet, points: &BBoxSet, max_dim_check: usize, - out: &mut Vec<(ID, ID)>, + out: AnswerFormat<'a, ID>, ) where B: BBox, ID: Copy + PartialOrd, @@ -86,7 +96,7 @@ pub fn simulated_one_way_scan( /// as intervals and points in turn, as if [`one_way_scan`] was called twice, once with intervals and points switched /// * `a` and `b` must be distinct [`BBoxSet`]s and must be sorted before calling. /// * `out` will contain pairs of `ID`s of intersecting boxes. -pub fn two_way_scan(a: &BBoxSet, b: &BBoxSet, out: &mut Vec<(ID, ID)>) +pub fn two_way_scan<'a, B, ID>(a: &BBoxSet, b: &BBoxSet, out: AnswerFormat<'a, ID>) where B: BBox, ID: Copy, @@ -96,11 +106,11 @@ where _two_way_scan::(a, b, B::DIM - 1, out); } -fn _two_way_scan( +fn _two_way_scan<'a, B, ID, const SIMULATE_ONE_WAY: bool>( intervals: &BBoxSet, points: &BBoxSet, max_dim_check: usize, - out: &mut Vec<(ID, ID)>, + mut out: AnswerFormat<'a, ID>, ) where B: BBox, ID: Copy + PartialOrd, @@ -144,8 +154,18 @@ fn _two_way_scan( { continue 'points; } - - out.push((p_id, i_min_id)); + match out { + AnswerFormat::Index(ref mut out) => { + out.push((i_min_idx, p_idx)); + } + AnswerFormat::Ident(ref mut out) => { + out.push((i_min_id, p_id)); + } + AnswerFormat::Both(ref mut out) => { + out.push(((i_min_idx, p_idx), (i_min_id, p_id))); + } + } + //out.push((p_id, i_min_id)); } i_min_idx += 1; @@ -174,9 +194,18 @@ fn _two_way_scan( continue 'intervals; } - out.push((p_min_id, i_id)); + match out { + AnswerFormat::Index(ref mut out) => { + out.push((i_idx, p_min_idx)); + } + AnswerFormat::Ident(ref mut out) => { + out.push((i_id, p_min_id)); + } + AnswerFormat::Both(ref mut out) => { + out.push(((i_idx, p_min_idx), (p_min_id, i_id))); + } + } } - p_min_idx += 1; } } @@ -203,6 +232,33 @@ pub fn hybrid( B::Num: PartialOrd + HasInfinity, R: Rng, { + hybrid_flex::( + intervals, + points, + lo, + hi, + dim, + AnswerFormat::Ident(out), + rand, + ); +} + +pub fn hybrid_flex<'a, B, ID, R, const CUTOFF: usize>( + intervals: &BBoxSet, + points: &BBoxSet, + lo: B::Num, + hi: B::Num, + dim: usize, + mut out: AnswerFormat<'a, ID>, + rand: &mut R, +) where + B: BBox, + ID: PartialOrd + Copy, + B::Num: PartialOrd + HasInfinity, + R: Rng, +{ + //use reborrow::ReborrowMut; + //impl <'a, ID> Copy for AnswerFormat<'a, ID>{} // The steps of the algorithm are numbered as in the paper "Fast software for box intersections": // https://dl.acm.org/doi/10.1145/336154.336192 @@ -231,15 +287,33 @@ pub fn hybrid( let (ninfty, infty) = (B::Num::NINFTY, B::Num::INFTY); // Step 4: stream two segment trees in the next dimension for the intervals stored at this node - hybrid::(&intervals_m, points, ninfty, infty, dim - 1, out, rand); - hybrid::(points, &intervals_m, ninfty, infty, dim - 1, out, rand); + + hybrid_flex::( + &intervals_m, + points, + ninfty, + infty, + dim - 1, + out.reborrow(), + rand, + ); + + hybrid_flex::( + points, + &intervals_m, + ninfty, + infty, + dim - 1, + out.reborrow(), + rand, + ); // Step 5: divide the segment [lo, hi) into segments [lo, mi) and [mi, hi) by computing an approximate median let mi = points.approx_median(dim, rand); // if we failed to divide the segment into subsegments, just scan instead if mi == hi || mi == lo { - simulated_one_way_scan(&intervals_lr, points, dim, out); + simulated_one_way_scan(&intervals_lr, points, dim, out.reborrow()); return; } @@ -263,7 +337,7 @@ pub fn hybrid( } } - hybrid::(&intervals_l, &points_l, lo, mi, dim, out, rand); // Step 6: left subtree - hybrid::(&intervals_r, &points_r, mi, hi, dim, out, rand); + hybrid_flex::(&intervals_l, &points_l, lo, mi, dim, out.reborrow(), rand); // Step 6: left subtree + hybrid_flex::(&intervals_r, &points_r, mi, hi, dim, out.reborrow(), rand); // Step 7: right subtree } diff --git a/src/lib.rs b/src/lib.rs index b114ec1..094250d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,8 +25,8 @@ //! let mut result = Vec::with_capacity(2); // set capacity according to expected number of intersections to avoid resizing //! box_intersect_ze::intersect_ze(&boxes, &boxes, &mut result, &mut ChaCha8Rng::seed_from_u64(1234)); // get the intersections //! -//! assert!(result.contains(&(1,0))); -//! assert!(result.contains(&(2,1))); +//! assert!(result.contains(&(0,1))); +//! assert!(result.contains(&(1,2))); //! assert!(!result.contains(&(2,0))); //! assert!(!result.contains(&(0,2))); //! ``` @@ -119,11 +119,53 @@ where B: BBox, ID: Copy + PartialOrd, { - let same = a as *const _ == b as *const _; // check if a and b refer to the same BBoxSet - if same { - one_way_scan(a, b, B::DIM - 1, out); - } else { - two_way_scan(a, b, out); + intersect_scan_flex(a, b, AnswerFormat::Ident(out)); +} + +pub fn intersect_scan_idx( + a: &BBoxSet, + b: &BBoxSet, + out: &mut Vec<(usize, usize)>, +) where + B: BBox, + ID: Copy + PartialOrd, +{ + intersect_scan_flex(a, b, AnswerFormat::Index(out)); +} + +fn intersect_scan_flex<'a, B, ID>(a: &BBoxSet, b: &BBoxSet, out: AnswerFormat<'a, ID>) +where + B: BBox, + ID: Copy + PartialOrd, +{ + let same = a as *const _ == b as *const _; + // check if a and b refer to the same BBoxSet + match same { + true => { + one_way_scan(a, b, B::DIM - 1, out); + } + false => { + two_way_scan(a, b, out); + } + } +} + +#[derive(Debug)] +pub enum AnswerFormat<'a, ID> { + Index(&'a mut Vec<(usize, usize)>), + Ident(&'a mut Vec<(ID, ID)>), + Both(&'a mut Vec<((usize, usize), (ID, ID))>), +} + +// Allow AnswerFormat to be reborrowed (see reborrow crate for why this is useful) +impl<'__reborrow_lifetime, 'a, ID> AnswerFormat<'a, ID> { + #[inline] + pub fn reborrow(&'__reborrow_lifetime mut self) -> AnswerFormat<'__reborrow_lifetime, ID> { + match self { + AnswerFormat::Index(ref mut s) => AnswerFormat::<'__reborrow_lifetime>::Index(s), + AnswerFormat::Ident(ref mut s) => AnswerFormat::<'__reborrow_lifetime>::Ident(s), + AnswerFormat::Both(ref mut s) => AnswerFormat::<'__reborrow_lifetime>::Both(s), + } } } @@ -135,25 +177,59 @@ pub fn intersect_brute_force(a: &BBoxSet, b: &BBoxSet, out: where B: BBox, ID: Copy, +{ + intersect_brute_force_flex(a, b, AnswerFormat::Ident(out)); +} + +/// Finds box intersections by checking every box in `a` against every box in `b`. +/// Performs well for on the order of 100 boxes. *O*(*n^2*) +/// * `a` and `b` may be either the same or distinct [`BBoxSet`]s +/// * `out` will contain pairs of `ID`s of intersecting boxes. +pub fn intersect_brute_force_idx( + a: &BBoxSet, + b: &BBoxSet, + out: &mut Vec<(usize, usize)>, +) where + B: BBox, + ID: Copy, +{ + intersect_brute_force_flex(a, b, AnswerFormat::Index(out)); +} + +pub fn intersect_brute_force_flex<'a, B, ID>( + a: &BBoxSet, + b: &BBoxSet, + mut out: AnswerFormat<'a, ID>, +) where + B: BBox, + ID: Copy, { let same = a as *const _ == b as *const _; // check if a and b refer to the same BBoxSet if same { // avoid duplicate intersections let mut start = 1; - for &(bbox, id) in &a.boxes { - for idx in start..a.boxes.len() { - let (bbox2, id2) = a.boxes[idx]; + for (aidx, &(bbox, id)) in a.boxes.iter().enumerate() { + for bidx in start..a.boxes.len() { + let (bbox2, id2) = a.boxes[bidx]; if bbox.intersects(&bbox2) { - out.push((id, id2)); + match out { + AnswerFormat::Index(ref mut out) => out.push((aidx, bidx)), + AnswerFormat::Ident(ref mut out) => out.push((id, id2)), + AnswerFormat::Both(ref mut out) => out.push(((aidx, bidx), (id, id2))), + } } } start += 1; } } else { - for &(bbox, id) in &a.boxes { - for &(bbox2, id2) in &b.boxes { + for (aidx, &(bbox, id)) in a.boxes.iter().enumerate() { + for (bidx, &(bbox2, id2)) in b.boxes.iter().enumerate() { if bbox.intersects(&bbox2) { - out.push((id, id2)); + match out { + AnswerFormat::Index(ref mut out) => out.push((aidx, bidx)), + AnswerFormat::Ident(ref mut out) => out.push((id, id2)), + AnswerFormat::Both(ref mut out) => out.push(((aidx, bidx), (id, id2))), + } } } } diff --git a/src/tests.rs b/src/tests.rs index 836d530..ebfcb57 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -55,10 +55,11 @@ struct TestData { } static TEST_DATA: Lazy = Lazy::new(|| test_data()); +const N:usize = 1234; /// Generates some random boxes and finds their intersections using brute force, as a reference to validate against fn test_data() -> TestData { - let mut boxes1 = random_boxes(150, 0, 12345); - let mut boxes2 = random_boxes(150, boxes1.len(), 54321); + let mut boxes1 = random_boxes(N, 0, 12345); + let mut boxes2 = random_boxes(N, boxes1.len(), 54321); boxes1.sort(); boxes2.sort(); @@ -84,15 +85,14 @@ fn test_data() -> TestData { #[test] fn one_way_scan() { let mut res = Vec::<(usize, usize)>::with_capacity(80); - crate::internals::one_way_scan(&TEST_DATA.boxes1, &TEST_DATA.boxes1, 2, &mut res); - + crate::internals::one_way_scan(&TEST_DATA.boxes1, &TEST_DATA.boxes1, 2, crate::AnswerFormat::Ident(&mut res)); assert!(same(&TEST_DATA.complete, &res)); } #[test] fn simulated_one_way_scan() { let mut res = Vec::<(usize, usize)>::with_capacity(80); - crate::internals::simulated_one_way_scan(&TEST_DATA.boxes1, &TEST_DATA.boxes1, 2, &mut res); + crate::internals::simulated_one_way_scan(&TEST_DATA.boxes1, &TEST_DATA.boxes1, 2, crate::AnswerFormat::Ident(&mut res)); assert!(same(&TEST_DATA.complete, &res)); } @@ -100,7 +100,7 @@ fn simulated_one_way_scan() { #[test] fn two_way_scan() { let mut res = Vec::<(usize, usize)>::with_capacity(80); - crate::internals::two_way_scan(&TEST_DATA.boxes1, &TEST_DATA.boxes2, &mut res); + crate::internals::two_way_scan(&TEST_DATA.boxes1, &TEST_DATA.boxes2, crate::AnswerFormat::Ident(&mut res)); assert!(same(&TEST_DATA.bipartite, &res)); }