use std::collections::HashMap;
use ndarray::ArrayD;
use rstar::{AABB, RTree, RTreeObject};
use rustc_hash::FxHashMap;
pub type Envelope = [f64; 4];
#[inline]
fn inflate(e: &Envelope, eps: f64) -> Envelope {
[e[0] - eps, e[1] - eps, e[2] + eps, e[3] + eps]
}
#[inline]
fn envelopes_intersect(a: &Envelope, b: &Envelope) -> bool {
a[0] <= b[2] && b[0] <= a[2] && a[1] <= b[3] && b[1] <= a[3]
}
pub fn broad_phase_candidates_bruteforce(
query_envs: &[Envelope],
cell_envs: &[Envelope],
eps: f64,
) -> Vec<(usize, usize)> {
let mut out: Vec<(usize, usize)> = Vec::new();
if query_envs.is_empty() || cell_envs.is_empty() {
return out;
}
for (qi, q) in query_envs.iter().enumerate() {
let qi_env = inflate(q, eps);
for (cj, c) in cell_envs.iter().enumerate() {
let cj_env = inflate(c, eps);
if envelopes_intersect(&qi_env, &cj_env) {
out.push((qi, cj));
}
}
}
out.sort_unstable();
out
}
struct CellEnv {
idx: usize,
aabb: AABB<[f64; 2]>,
}
impl RTreeObject for CellEnv {
type Envelope = AABB<[f64; 2]>;
fn envelope(&self) -> Self::Envelope {
self.aabb
}
}
pub fn broad_phase_candidates(
query_envs: &[Envelope],
cell_envs: &[Envelope],
eps: f64,
) -> Vec<(usize, usize)> {
let mut out: Vec<(usize, usize)> = Vec::new();
if query_envs.is_empty() || cell_envs.is_empty() {
return out;
}
let objects: Vec<CellEnv> = cell_envs
.iter()
.enumerate()
.map(|(idx, c)| {
let e = inflate(c, eps);
CellEnv {
idx,
aabb: AABB::from_corners([e[0], e[1]], [e[2], e[3]]),
}
})
.collect();
let tree = RTree::bulk_load(objects);
for (qi, q) in query_envs.iter().enumerate() {
let e = inflate(q, eps);
let query_aabb = AABB::from_corners([e[0], e[1]], [e[2], e[3]]);
for hit in tree.locate_in_envelope_intersecting(query_aabb) {
out.push((qi, hit.idx));
}
}
out.sort_unstable();
out
}
pub fn envelope_vectors(
env_names: &[String],
arrays: &HashMap<String, ArrayD<f64>>,
) -> Result<Vec<Envelope>, String> {
let k = env_names.len();
let col = |name: &str| -> Result<&ArrayD<f64>, String> {
arrays
.get(name)
.ok_or_else(|| format!("overlap-join env factor {name:?} not supplied in const arrays"))
};
match k {
4 => {
let (a, b, c, d) = (
col(&env_names[0])?,
col(&env_names[1])?,
col(&env_names[2])?,
col(&env_names[3])?,
);
let n = a.len();
if b.len() != n || c.len() != n || d.len() != n {
return Err(format!(
"overlap-join 4-factor rect envelope factors must share a length; got \
{}, {}, {}, {}",
a.len(),
b.len(),
c.len(),
d.len()
));
}
let (av, bv, cv, dv) = (flat(a), flat(b), flat(c), flat(d));
Ok((0..n).map(|p| [av[p], bv[p], cv[p], dv[p]]).collect())
}
2 => {
let (x, y) = (col(&env_names[0])?, col(&env_names[1])?);
let n = x.len();
if y.len() != n {
return Err(format!(
"overlap-join 2-factor point envelope factors must share a length; got {}, {}",
x.len(),
y.len()
));
}
let (xv, yv) = (flat(x), flat(y));
Ok((0..n).map(|p| [xv[p], yv[p], xv[p], yv[p]]).collect())
}
1 => ring_envelopes(col(&env_names[0])?),
other => Err(format!(
"overlap-join env must name 1 (rings), 2 (point [x,y]), or 4 \
(rect [xmin,ymin,xmax,ymax]) const-array factors; got {other}"
)),
}
}
fn flat(a: &ArrayD<f64>) -> Vec<f64> {
a.iter().copied().collect()
}
fn ring_envelopes(rings: &ArrayD<f64>) -> Result<Vec<Envelope>, String> {
let shape = rings.shape();
if shape.len() != 3 {
return Err(format!(
"overlap-join single-factor env expects a [pos, verts, coord] 3-D ring array; \
got a {}-D factor",
shape.len()
));
}
let (npos, nverts, ncoord) = (shape[0], shape[1], shape[2]);
if ncoord < 2 || nverts == 0 {
return Err(format!(
"overlap-join ring factor must be [pos, verts>=1, coord>=2]; got shape {shape:?}"
));
}
let mut out: Vec<Envelope> = Vec::with_capacity(npos);
for p in 0..npos {
let mut xmin = rings[ndarray::IxDyn(&[p, 0, 0])];
let mut xmax = xmin;
let mut ymin = rings[ndarray::IxDyn(&[p, 0, 1])];
let mut ymax = ymin;
for v in 1..nverts {
let x = rings[ndarray::IxDyn(&[p, v, 0])];
let y = rings[ndarray::IxDyn(&[p, v, 1])];
if x < xmin {
xmin = x;
}
if x > xmax {
xmax = x;
}
if y < ymin {
ymin = y;
}
if y > ymax {
ymax = y;
}
}
out.push([xmin, ymin, xmax, ymax]);
}
Ok(out)
}
use std::cell::Cell;
use std::sync::OnceLock;
thread_local! {
static ENUM_VISITS: Cell<u64> = const { Cell::new(0) };
}
#[inline]
pub(crate) fn bump_overlap_enum_visits(n: u64) {
ENUM_VISITS.with(|c| c.set(c.get().wrapping_add(n)));
}
pub fn overlap_enum_visits() -> u64 {
ENUM_VISITS.with(Cell::get)
}
pub fn reset_overlap_enum_visits() {
ENUM_VISITS.with(|c| c.set(0));
}
thread_local! {
static GATE_INDEX_BUILDS: Cell<u64> = const { Cell::new(0) };
}
#[inline]
pub(crate) fn bump_gate_index_builds() {
GATE_INDEX_BUILDS.with(|c| c.set(c.get().wrapping_add(1)));
}
pub fn gate_index_builds() -> u64 {
GATE_INDEX_BUILDS.with(Cell::get)
}
pub fn reset_gate_index_builds() {
GATE_INDEX_BUILDS.with(|c| c.set(0));
}
thread_local! {
static PLAN_DECLINES: Cell<u64> = const { Cell::new(0) };
}
#[inline]
pub(crate) fn bump_gate_plan_declines() {
PLAN_DECLINES.with(|c| c.set(c.get().wrapping_add(1)));
}
#[cfg(test)]
pub(crate) fn gate_plan_declines() -> u64 {
PLAN_DECLINES.with(Cell::get)
}
#[cfg(test)]
pub(crate) fn reset_gate_plan_declines() {
PLAN_DECLINES.with(|c| c.set(0));
}
thread_local! {
static GATE_ENABLED: Cell<bool> = const { Cell::new(true) };
}
pub fn join_gate_enabled() -> bool {
GATE_ENABLED.with(Cell::get)
}
pub fn set_join_gate_enabled(on: bool) -> bool {
GATE_ENABLED.with(|c| c.replace(on))
}
pub fn gate_cache_pair_budget() -> usize {
GATE_CACHE_PAIRS.with(|c| match c.get() {
Some(v) => v,
None => {
let v = gate_cache_budget_env();
c.set(Some(v));
v
}
})
}
pub const GATE_PAIR_BYTES: usize = 2 * size_of::<i64>();
pub const DEFAULT_GATE_CACHE_PAIRS: usize = 4_000_000;
fn gate_cache_budget_env() -> usize {
static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*N.get_or_init(|| {
std::env::var("ESS_GATE_CACHE_PAIRS")
.ok()
.and_then(|v| v.trim().parse::<usize>().ok())
.unwrap_or(DEFAULT_GATE_CACHE_PAIRS)
})
}
thread_local! {
static GATE_CACHE_PAIRS: Cell<Option<usize>> = const { Cell::new(None) };
}
pub(crate) fn gate_plan_ratio() -> u128 {
GATE_PLAN_RATIO.with(|c| match c.get() {
Some(v) => v,
None => {
let v = *gate_plan_ratio_env();
c.set(Some(v));
v
}
})
}
fn gate_plan_ratio_env() -> &'static u128 {
static RATIO: std::sync::OnceLock<u128> = std::sync::OnceLock::new();
RATIO.get_or_init(|| env_u128("ESS_GATE_PLAN_RATIO", DEFAULT_GATE_PLAN_RATIO))
}
pub(crate) fn gate_plan_floor() -> usize {
GATE_PLAN_FLOOR.with(|c| match c.get() {
Some(v) => v,
None => {
let v = *gate_plan_floor_env();
c.set(Some(v));
v
}
})
}
fn gate_plan_floor_env() -> &'static usize {
static FLOOR: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
FLOOR.get_or_init(|| {
usize::try_from(env_u128(
"ESS_GATE_PLAN_FLOOR",
DEFAULT_GATE_PLAN_FLOOR as u128,
))
.unwrap_or(usize::MAX)
})
}
pub(crate) const DEFAULT_GATE_PLAN_RATIO: u128 = 4;
pub(crate) const DEFAULT_GATE_PLAN_FLOOR: usize = 1_000_000;
fn env_u128(name: &str, default: u128) -> u128 {
std::env::var(name)
.ok()
.and_then(|v| v.trim().parse::<u128>().ok())
.unwrap_or(default)
}
thread_local! {
static GATE_PLAN_RATIO: Cell<Option<u128>> = const { Cell::new(None) };
static GATE_PLAN_FLOOR: Cell<Option<usize>> = const { Cell::new(None) };
}
#[cfg(test)]
pub(crate) fn set_gate_plan(ratio: u128, floor: usize) -> (u128, usize) {
let prev = (gate_plan_ratio(), gate_plan_floor());
GATE_PLAN_RATIO.with(|c| c.set(Some(ratio)));
GATE_PLAN_FLOOR.with(|c| c.set(Some(floor)));
prev
}
pub fn set_gate_cache_pair_budget(pairs: usize) -> usize {
let prev = gate_cache_pair_budget();
GATE_CACHE_PAIRS.with(|c| c.set(Some(pairs)));
prev
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Side {
Src,
Tgt,
}
#[derive(Debug, Clone, Default)]
pub struct OverlapIndex {
src: Vec<i64>,
tgt: Vec<i64>,
src_runs: Option<SrcRuns>,
by_tgt: OnceLock<Csr>,
}
#[derive(Debug, Clone)]
enum SrcRuns {
Dense { lo: i64, starts: Vec<u32> },
Sparse(FxHashMap<i64, (u32, u32)>),
}
#[derive(Debug, Clone)]
struct Csr {
keys: Vec<i64>,
offsets: Vec<usize>,
partners: Vec<i64>,
}
const NO_PARTNERS: &[i64] = &[];
impl SrcRuns {
const MAX_SPAN_FACTOR: usize = 4;
fn build(src: &[i64]) -> Option<SrcRuns> {
let (&lo, &hi) = (src.first()?, src.last()?);
if src.len() > u32::MAX as usize {
return None;
}
let span = hi
.checked_sub(lo)
.and_then(|d| d.checked_add(1))
.and_then(|s| usize::try_from(s).ok());
match span {
Some(span) if span <= Self::MAX_SPAN_FACTOR * src.len() + 1024 => {
let mut starts = vec![0u32; span + 1];
for &p in src {
starts[(p - lo) as usize + 1] += 1;
}
for k in 0..span {
starts[k + 1] += starts[k];
}
Some(SrcRuns::Dense { lo, starts })
}
_ => {
let mut runs: FxHashMap<i64, (u32, u32)> = FxHashMap::default();
let mut i = 0usize;
while i < src.len() {
let p = src[i];
let mut j = i + 1;
while j < src.len() && src[j] == p {
j += 1;
}
runs.insert(p, (i as u32, j as u32));
i = j;
}
runs.shrink_to_fit();
Some(SrcRuns::Sparse(runs))
}
}
}
#[inline]
fn run(&self, pos: i64) -> (usize, usize) {
match self {
SrcRuns::Dense { lo, starts } => {
let Some(k) = pos.checked_sub(*lo) else {
return (0, 0);
};
if k < 0 || k as usize + 1 >= starts.len() {
return (0, 0);
}
let k = k as usize;
(starts[k] as usize, starts[k + 1] as usize)
}
SrcRuns::Sparse(runs) => runs
.get(&pos)
.map_or((0, 0), |&(a, b)| (a as usize, b as usize)),
}
}
fn resident_bytes(&self) -> usize {
match self {
SrcRuns::Dense { starts, .. } => starts.capacity() * size_of::<u32>(),
SrcRuns::Sparse(runs) => runs.capacity() * (size_of::<(i64, (u32, u32))>() + 1),
}
}
}
impl Csr {
fn resident_bytes(&self) -> usize {
self.keys.capacity() * size_of::<i64>()
+ self.offsets.capacity() * size_of::<usize>()
+ self.partners.capacity() * size_of::<i64>()
}
#[inline]
fn partners_of(&self, key: i64) -> &[i64] {
match self.keys.binary_search(&key) {
Ok(k) => &self.partners[self.offsets[k]..self.offsets[k + 1]],
Err(_) => NO_PARTNERS,
}
}
}
impl OverlapIndex {
pub fn from_zero_based(pairs: &[(usize, usize)]) -> Self {
Self::from_owned_pairs(
pairs
.iter()
.map(|&(q, c)| (q as i64 + 1, c as i64 + 1))
.collect(),
)
}
pub fn from_pairs(pairs: &[(i64, i64)]) -> Self {
Self::from_owned_pairs(pairs.to_vec())
}
pub fn from_owned_pairs(mut pairs: Vec<(i64, i64)>) -> Self {
pairs.sort_unstable();
pairs.dedup();
let mut src = Vec::with_capacity(pairs.len());
let mut tgt = Vec::with_capacity(pairs.len());
for (l, r) in pairs {
src.push(l);
tgt.push(r);
}
let src_runs = SrcRuns::build(&src);
OverlapIndex {
src,
tgt,
src_runs,
by_tgt: OnceLock::new(),
}
}
#[inline]
fn src_run(&self, pos: i64) -> (usize, usize) {
if let Some(r) = &self.src_runs {
return r.run(pos);
}
let start = self.src.partition_point(|&p| p < pos);
let end = self.src[start..].partition_point(|&p| p == pos) + start;
(start, end)
}
#[inline]
pub fn contains(&self, src: i64, tgt: i64) -> bool {
let (a, b) = self.src_run(src);
self.tgt[a..b].binary_search(&tgt).is_ok()
}
pub fn len(&self) -> usize {
self.src.len()
}
pub fn resident_pairs(&self) -> usize {
self.resident_bytes().div_ceil(GATE_PAIR_BYTES)
}
fn resident_bytes(&self) -> usize {
(self.src.capacity() + self.tgt.capacity()) * size_of::<i64>()
+ self.src_runs.as_ref().map_or(0, SrcRuns::resident_bytes)
+ self.by_tgt.get().map_or(0, Csr::resident_bytes)
}
pub fn is_empty(&self) -> bool {
self.src.is_empty()
}
pub fn pairs(&self) -> impl ExactSizeIterator<Item = (i64, i64)> + '_ {
self.src.iter().copied().zip(self.tgt.iter().copied())
}
fn build_by_tgt(&self) -> Csr {
let mut keys = self.tgt.clone();
keys.sort_unstable();
keys.dedup();
keys.shrink_to_fit(); let mut offsets = vec![0usize; keys.len() + 1];
for &r in &self.tgt {
if let Ok(k) = keys.binary_search(&r) {
offsets[k + 1] += 1;
}
}
for k in 0..keys.len() {
offsets[k + 1] += offsets[k];
}
let mut partners = vec![0i64; self.tgt.len()];
let mut fill = offsets.clone();
for (l, r) in self.pairs() {
if let Ok(k) = keys.binary_search(&r) {
partners[fill[k]] = l;
fill[k] += 1;
}
}
Csr {
keys,
offsets,
partners,
}
}
pub fn partners(&self, side: Side, pos: i64) -> &[i64] {
match side {
Side::Src => {
let (a, b) = self.src_run(pos);
&self.tgt[a..b]
}
Side::Tgt => self
.by_tgt
.get_or_init(|| self.build_by_tgt())
.partners_of(pos),
}
}
pub fn partners_in(&self, side: Side, pos: i64, lo: i64, hi: i64) -> &[i64] {
restrict_to_range(self.partners(side, pos), lo, hi)
}
#[cfg(test)]
pub(crate) fn tgt_adjacency_built(&self) -> bool {
self.by_tgt.get().is_some()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DrivePlan<'a> {
Full,
Reject,
Pairs,
Restrict {
free_is_src: bool,
vals: &'a [i64],
},
}
fn restrict_to_range(parts: &[i64], lo: i64, hi: i64) -> &[i64] {
let start = parts.partition_point(|&p| p < lo);
let end = parts.partition_point(|&p| p <= hi).max(start);
&parts[start..end]
}
pub fn overlap_drive_plan(
index: &OverlapIndex,
src_bound: Option<i64>,
tgt_bound: Option<i64>,
free_range: Option<(i64, i64)>,
) -> DrivePlan<'_> {
match (src_bound, tgt_bound) {
(None, None) => DrivePlan::Pairs,
(Some(l), Some(r)) => {
if index.contains(l, r) {
DrivePlan::Full
} else {
DrivePlan::Reject
}
}
(Some(l), None) => match free_range {
Some((lo, hi)) => DrivePlan::Restrict {
free_is_src: false,
vals: restrict_to_range(index.partners(Side::Src, l), lo, hi),
},
None => DrivePlan::Full,
},
(None, Some(r)) => match free_range {
Some((lo, hi)) => DrivePlan::Restrict {
free_is_src: true,
vals: restrict_to_range(index.partners(Side::Tgt, r), lo, hi),
},
None => DrivePlan::Full,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::IxDyn;
use std::collections::BTreeMap;
fn arr(shape: &[usize], data: Vec<f64>) -> ArrayD<f64> {
ArrayD::from_shape_vec(IxDyn(shape), data).expect("shape matches data")
}
fn ca(pairs: Vec<(&str, ArrayD<f64>)>) -> HashMap<String, ArrayD<f64>> {
pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
}
fn names(ns: &[&str]) -> Vec<String> {
ns.iter().map(|s| s.to_string()).collect()
}
fn pir_envelopes() -> (Vec<Envelope>, Vec<Envelope>) {
let arrays = ca(vec![
("X", arr(&[5], vec![1.0, 3.0, 2.0, 10.0, 6.0])),
("Y", arr(&[5], vec![1.0, 3.0, 2.0, 10.0, 6.0])),
("W", arr(&[4], vec![0.0, 2.0, 4.0, 6.0])),
("S", arr(&[4], vec![0.0, 2.0, 4.0, 6.0])),
("E", arr(&[4], vec![2.0, 4.0, 6.0, 8.0])),
("N", arr(&[4], vec![2.0, 4.0, 6.0, 8.0])),
]);
let src = envelope_vectors(&names(&["X", "Y"]), &arrays).unwrap();
let tgt = envelope_vectors(&names(&["W", "S", "E", "N"]), &arrays).unwrap();
(src, tgt)
}
#[test]
fn point_in_rect_candidate_set_matches_julia_golden() {
let (src, tgt) = pir_envelopes();
let got = broad_phase_candidates(&src, &tgt, 0.0);
assert_eq!(got, vec![(0, 0), (1, 1), (2, 0), (2, 1), (4, 2), (4, 3)]);
}
#[test]
fn rstar_equals_bruteforce_on_pir() {
let (src, tgt) = pir_envelopes();
for &eps in &[0.0, 0.5, 1.0, 2.5] {
assert_eq!(
broad_phase_candidates(&src, &tgt, eps),
broad_phase_candidates_bruteforce(&src, &tgt, eps),
"rstar != brute-force at eps={eps}"
);
}
}
#[test]
fn conservativeness_and_monotonicity_in_eps() {
let (src, tgt) = pir_envelopes();
let c0 = broad_phase_candidates(&src, &tgt, 0.0);
let c1 = broad_phase_candidates(&src, &tgt, 1.0);
for pair in &c0 {
assert!(
c1.contains(pair),
"eps=1 dropped a eps=0 candidate {pair:?}"
);
}
assert!(c1.len() >= c0.len());
assert!(c0.contains(&(0, 0)));
assert!(c0.contains(&(1, 1)));
}
#[test]
fn rstar_equals_bruteforce_random_stress() {
let mut state: u64 = 0x9E3779B97F4A7C15;
let mut next = || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((state >> 33) as f64) / (1u64 << 31) as f64 };
let mk = |n: usize, next: &mut dyn FnMut() -> f64| -> Vec<Envelope> {
(0..n)
.map(|_| {
let x0 = next() * 10.0;
let y0 = next() * 10.0;
let w = next() * 2.0;
let h = next() * 2.0;
[x0, y0, x0 + w, y0 + h]
})
.collect()
};
let query = mk(40, &mut next);
let cells = mk(60, &mut next);
for &eps in &[0.0, 0.1, 0.75, 3.0] {
assert_eq!(
broad_phase_candidates(&query, &cells, eps),
broad_phase_candidates_bruteforce(&query, &cells, eps),
"rstar != brute-force at eps={eps}"
);
}
}
#[test]
fn empty_inputs_yield_empty() {
let some = vec![[0.0, 0.0, 1.0, 1.0]];
assert!(broad_phase_candidates(&[], &some, 0.0).is_empty());
assert!(broad_phase_candidates(&some, &[], 0.0).is_empty());
assert!(broad_phase_candidates_bruteforce(&[], &some, 0.0).is_empty());
}
#[test]
fn ring_envelope_arm_builds_aabb() {
let rings = arr(
&[2, 3, 2],
vec![
0.0, 0.0, 2.0, 0.0, 1.0, 1.0, 3.0, 2.0, 5.0, 2.0, 4.0, 4.0, ],
);
let arrays = ca(vec![("rings", rings)]);
let envs = envelope_vectors(&names(&["rings"]), &arrays).unwrap();
assert_eq!(envs, vec![[0.0, 0.0, 2.0, 1.0], [3.0, 2.0, 5.0, 4.0]]);
}
fn stress_pairs() -> Vec<(i64, i64)> {
let mut state: u64 = 0x2545_F491_4F6C_DD1D;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
(0..4000)
.map(|_| {
(2 * ((next() % 48) as i64 + 1), (next() % 53) as i64 + 1)
})
.collect()
}
#[allow(clippy::type_complexity)]
fn naive(
pairs: &[(i64, i64)],
) -> (
std::collections::HashSet<(i64, i64)>,
BTreeMap<i64, Vec<i64>>,
BTreeMap<i64, Vec<i64>>,
) {
let set: std::collections::HashSet<(i64, i64)> = pairs.iter().copied().collect();
let mut by_src: BTreeMap<i64, Vec<i64>> = BTreeMap::new();
let mut by_tgt: BTreeMap<i64, Vec<i64>> = BTreeMap::new();
let mut sorted: Vec<(i64, i64)> = set.iter().copied().collect();
sorted.sort_unstable();
for (l, r) in sorted {
by_src.entry(l).or_default().push(r);
by_tgt.entry(r).or_default().push(l);
}
(set, by_src, by_tgt)
}
#[test]
fn overlap_index_views_match_the_naive_pair_set() {
let raw = stress_pairs();
let (set, by_src, by_tgt) = naive(&raw);
let ix = OverlapIndex::from_pairs(&raw);
let mut want: Vec<(i64, i64)> = set.iter().copied().collect();
want.sort_unstable();
assert_eq!(ix.pairs().collect::<Vec<_>>(), want);
assert_eq!(ix.len(), want.len());
assert!(!ix.is_empty());
for l in 0..=100 {
for r in 0..=60 {
assert_eq!(
ix.contains(l, r),
set.contains(&(l, r)),
"membership disagrees at ({l}, {r})"
);
}
}
const NONE: &[i64] = &[];
for l in 0..=100 {
assert_eq!(
ix.partners(Side::Src, l),
by_src.get(&l).map(Vec::as_slice).unwrap_or(NONE),
"src partners disagree at {l}"
);
}
for r in 0..=60 {
assert_eq!(
ix.partners(Side::Tgt, r),
by_tgt.get(&r).map(Vec::as_slice).unwrap_or(NONE),
"tgt partners disagree at {r}"
);
}
}
#[test]
fn partners_in_is_the_contiguous_restriction_of_partners() {
let ix = OverlapIndex::from_pairs(&stress_pairs());
for pos in [2i64, 4, 50, 96] {
for side in [Side::Src, Side::Tgt] {
let all = ix.partners(side, pos);
for (lo, hi) in [(0i64, 100i64), (10, 20), (30, 30), (40, 5)] {
let want: Vec<i64> = all
.iter()
.copied()
.filter(|&v| v >= lo && v <= hi)
.collect();
assert_eq!(ix.partners_in(side, pos, lo, hi), want.as_slice());
}
}
}
}
#[test]
fn a_src_side_walk_never_builds_the_tgt_adjacency() {
let ix = OverlapIndex::from_pairs(&stress_pairs());
assert!(!ix.tgt_adjacency_built(), "built eagerly at construction");
for l in 0..=100 {
let _ = ix.partners(Side::Src, l);
let _ = ix.partners_in(Side::Src, l, 1, 20);
let _ = ix.contains(l, 3);
}
let _ = ix.pairs().count();
assert!(
!ix.tgt_adjacency_built(),
"a src-only walk paid for the tgt direction"
);
let _ = ix.partners(Side::Tgt, 3);
assert!(ix.tgt_adjacency_built());
}
#[test]
fn the_three_constructors_agree() {
let raw = stress_pairs();
let from_ref = OverlapIndex::from_pairs(&raw);
let owned = OverlapIndex::from_owned_pairs(raw.clone());
assert_eq!(
from_ref.pairs().collect::<Vec<_>>(),
owned.pairs().collect::<Vec<_>>()
);
let zb: Vec<(usize, usize)> = vec![(0, 0), (2, 1), (0, 3)];
assert_eq!(
OverlapIndex::from_zero_based(&zb)
.pairs()
.collect::<Vec<_>>(),
vec![(1, 1), (1, 4), (3, 2)]
);
}
#[test]
fn an_empty_index_has_no_partners_and_admits_nothing() {
let ix = OverlapIndex::from_pairs(&[]);
assert!(ix.is_empty());
assert_eq!(ix.len(), 0);
assert!(!ix.contains(1, 1));
assert!(ix.partners(Side::Src, 1).is_empty());
assert!(ix.partners(Side::Tgt, 1).is_empty());
assert_eq!(ix.pairs().count(), 0);
}
#[test]
fn a_sparse_left_column_is_hashed_and_answers_the_same() {
let sparse: Vec<(i64, i64)> = vec![(1, 2), (1, 5), (10_000_000, 3), (10_000_000, 5)];
let ix = OverlapIndex::from_pairs(&sparse);
assert!(
matches!(ix.src_runs, Some(SrcRuns::Sparse(_))),
"a 10-million-wide span over 4 pairs must not be densified, and must not \
be left without a constant-time run lookup either"
);
let dense = OverlapIndex::from_pairs(&[(1, 2), (1, 5), (3, 3), (3, 5)]);
assert!(
matches!(dense.src_runs, Some(SrcRuns::Dense { .. })),
"a tight span should be densified"
);
for (ix, hit, miss) in [(&ix, 10_000_000i64, 4_999_999i64), (&dense, 3i64, 2i64)] {
assert!(ix.contains(1, 2) && ix.contains(hit, 5));
assert!(!ix.contains(1, 3) && !ix.contains(miss, 5));
assert!(!ix.contains(0, 2) && !ix.contains(i64::MAX, 2));
assert_eq!(ix.partners(Side::Src, 1), &[2, 5]);
assert_eq!(ix.partners(Side::Src, hit), &[3, 5]);
assert!(ix.partners(Side::Src, miss).is_empty());
assert_eq!(ix.partners(Side::Tgt, 5), &[1, hit]);
}
}
#[test]
fn a_hashed_run_table_costs_by_the_pairs_not_by_the_span() {
const N: i64 = 1000;
let pairs: Vec<(i64, i64)> = (0..N).map(|i| (i * 1_000_000, i)).collect();
let ix = OverlapIndex::from_owned_pairs(pairs);
assert!(matches!(ix.src_runs, Some(SrcRuns::Sparse(_))));
assert!(
ix.resident_pairs() <= 4 * N as usize,
"a sparse index must stay within a small constant of its PAIR count, \
not of its span; it costs {} pair-equivalents for {N} pairs",
ix.resident_pairs()
);
for i in 0..N {
assert!(ix.contains(i * 1_000_000, i));
assert!(!ix.contains(i * 1_000_000 + 1, i));
}
}
}