Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 93 additions & 19 deletions src/internals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<B, ID>(
pub fn one_way_scan<'a, B, ID>(
intervals: &BBoxSet<B, ID>,
points: &BBoxSet<B, ID>,
max_dim_check: usize,
out: &mut Vec<(ID, ID)>,
mut out: AnswerFormat<'a, ID>,
) where
B: BBox,
ID: Copy + PartialOrd,
Expand All @@ -25,7 +25,7 @@ pub fn one_way_scan<B, ID>(
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);
Expand Down Expand Up @@ -62,18 +62,28 @@ pub fn one_way_scan<B, ID>(
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<B, ID>(
pub fn simulated_one_way_scan<'a, B, ID>(
intervals: &BBoxSet<B, ID>,
points: &BBoxSet<B, ID>,
max_dim_check: usize,
out: &mut Vec<(ID, ID)>,
out: AnswerFormat<'a, ID>,
) where
B: BBox,
ID: Copy + PartialOrd,
Expand All @@ -86,7 +96,7 @@ pub fn simulated_one_way_scan<B, ID>(
/// 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<B, ID>(a: &BBoxSet<B, ID>, b: &BBoxSet<B, ID>, out: &mut Vec<(ID, ID)>)
pub fn two_way_scan<'a, B, ID>(a: &BBoxSet<B, ID>, b: &BBoxSet<B, ID>, out: AnswerFormat<'a, ID>)
where
B: BBox,
ID: Copy,
Expand All @@ -96,11 +106,11 @@ where
_two_way_scan::<B, ID, false>(a, b, B::DIM - 1, out);
}

fn _two_way_scan<B, ID, const SIMULATE_ONE_WAY: bool>(
fn _two_way_scan<'a, B, ID, const SIMULATE_ONE_WAY: bool>(
intervals: &BBoxSet<B, ID>,
points: &BBoxSet<B, ID>,
max_dim_check: usize,
out: &mut Vec<(ID, ID)>,
mut out: AnswerFormat<'a, ID>,
) where
B: BBox,
ID: Copy + PartialOrd,
Expand Down Expand Up @@ -144,8 +154,18 @@ fn _two_way_scan<B, ID, const SIMULATE_ONE_WAY: bool>(
{
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;
Expand Down Expand Up @@ -174,9 +194,18 @@ fn _two_way_scan<B, ID, const SIMULATE_ONE_WAY: bool>(
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;
}
}
Expand All @@ -203,6 +232,33 @@ pub fn hybrid<B, ID, R, const CUTOFF: usize>(
B::Num: PartialOrd + HasInfinity,
R: Rng,
{
hybrid_flex::<B, ID, R, CUTOFF>(
intervals,
points,
lo,
hi,
dim,
AnswerFormat::Ident(out),
rand,
);
}

pub fn hybrid_flex<'a, B, ID, R, const CUTOFF: usize>(
intervals: &BBoxSet<B, ID>,
points: &BBoxSet<B, ID>,
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

Expand Down Expand Up @@ -231,15 +287,33 @@ pub fn hybrid<B, ID, R, const CUTOFF: usize>(
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::<B, ID, R, CUTOFF>(&intervals_m, points, ninfty, infty, dim - 1, out, rand);
hybrid::<B, ID, R, CUTOFF>(points, &intervals_m, ninfty, infty, dim - 1, out, rand);

hybrid_flex::<B, ID, R, CUTOFF>(
&intervals_m,
points,
ninfty,
infty,
dim - 1,
out.reborrow(),
rand,
);

hybrid_flex::<B, ID, R, CUTOFF>(
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;
}

Expand All @@ -263,7 +337,7 @@ pub fn hybrid<B, ID, R, const CUTOFF: usize>(
}
}

hybrid::<B, ID, R, CUTOFF>(&intervals_l, &points_l, lo, mi, dim, out, rand); // Step 6: left subtree
hybrid::<B, ID, R, CUTOFF>(&intervals_r, &points_r, mi, hi, dim, out, rand);
hybrid_flex::<B, ID, R, CUTOFF>(&intervals_l, &points_l, lo, mi, dim, out.reborrow(), rand); // Step 6: left subtree
hybrid_flex::<B, ID, R, CUTOFF>(&intervals_r, &points_r, mi, hi, dim, out.reborrow(), rand);
// Step 7: right subtree
}
104 changes: 90 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
//! ```
Expand Down Expand Up @@ -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<B, ID>(
a: &BBoxSet<B, ID>,
b: &BBoxSet<B, ID>,
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, ID>, b: &BBoxSet<B, ID>, 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),
}
}
}

Expand All @@ -135,25 +177,59 @@ pub fn intersect_brute_force<B, ID>(a: &BBoxSet<B, ID>, b: &BBoxSet<B, ID>, 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<B, ID>(
a: &BBoxSet<B, ID>,
b: &BBoxSet<B, ID>,
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, ID>,
b: &BBoxSet<B, ID>,
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))),
}
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ struct TestData {
}

static TEST_DATA: Lazy<TestData> = 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();

Expand All @@ -84,23 +85,22 @@ 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));
}

#[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));
}
Expand Down