use smallvec::SmallVec;
use crate::{
curves::{hilbert2, hilbertn},
error, point,
spacecurve::SpaceCurve,
spec::GridSpec,
};
#[derive(Debug, Clone, Copy)]
enum HilbertImpl {
TwoD,
Nd,
}
impl HilbertImpl {
fn index(&self, dimension: u32, order: u32, point: &[u32]) -> u32 {
match self {
Self::TwoD => hilbert2::hilbert_index(order, point),
Self::Nd => hilbertn::hilbert_index(dimension, order, point),
}
}
fn point(&self, dimension: u32, order: u32, index: u32) -> SmallVec<[u32; 8]> {
match self {
Self::TwoD => hilbert2::hilbert_point(order, index),
Self::Nd => hilbertn::hilbert_point(dimension, order, index),
}
}
}
#[derive(Debug)]
pub struct Hilbert {
pub order: u32,
pub dimension: u32,
length: u32,
mapper: HilbertImpl,
}
impl Hilbert {
pub fn from_dimensions(dimension: u32, size: u32) -> error::Result<Self> {
let spec = GridSpec::power_of_two(dimension, size)?;
spec.require_index_bits_lt(32)?;
Ok(Self {
dimension: spec.dimension(),
order: spec.order().unwrap(),
length: spec.length(),
mapper: if spec.dimension() == 2 {
HilbertImpl::TwoD
} else {
HilbertImpl::Nd
},
})
}
}
impl SpaceCurve for Hilbert {
fn name(&self) -> &'static str {
"Hilbert"
}
fn info(&self) -> &'static str {
"Classic continuous space-filling curve with excellent locality.\n\
Defined recursively via rotations/reflections; widely used in GIS,\n\
image storage, and indexing; typically clusters better than Z-order."
}
fn length(&self) -> u32 {
self.length
}
fn dimensions(&self) -> u32 {
self.dimension
}
fn index(&self, p: &point::Point) -> u32 {
debug_assert_eq!(p.len(), self.dimension as usize, "point dimension mismatch");
let side = 1u32 << self.order;
debug_assert!(
p.iter().all(|&c| c < side),
"point coordinate out of bounds"
);
self.mapper.index(self.dimension, self.order, p)
}
fn point(&self, index: u32) -> point::Point {
let len = self.length;
debug_assert!(index < len, "index out of bounds");
point::Point::new_with_dimension(
self.dimension,
self.mapper.point(self.dimension, self.order, index % len),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_dimensions() -> error::Result<()> {
let h = &Hilbert::from_dimensions(2, 2)?;
assert_eq!(h.order, 1);
assert_eq!(h.length(), 4);
let h = &Hilbert::from_dimensions(3, 2)?;
assert_eq!(h.order, 1);
assert_eq!(h.length(), 8);
if Hilbert::from_dimensions(2, 3).is_ok() {
panic!("expected error")
}
assert!(Hilbert::from_dimensions(2, 1u32 << 16).is_err());
assert!(Hilbert::from_dimensions(2, 1u32 << 15).is_ok());
Ok(())
}
}