use smallvec::SmallVec;
use crate::{error, ops, point, spacecurve::SpaceCurve, spec::GridSpec};
fn grey(d: u32, val: u32) -> u32 {
let mask = (1 << d) - 1;
let val2 = val & mask;
val2 ^ (val2 >> 1)
}
fn parity(val: u32) -> u32 {
val.count_ones() % 2
}
fn cached_grey(_d: u32, val: u32, corners: &[Vec<u32>]) -> u32 {
corners[0][val as usize]
}
fn cached_inv_grey(d: u32, val: u32, corners: &[Vec<u32>]) -> u32 {
corners[0][(val + (1 << d)) as usize]
}
fn corner_indexes(d: u32, n: u32) -> Vec<Vec<u32>> {
let size = 1u32 << d;
let mut v = vec![vec![0u32; (size * 2) as usize]; (n + 1) as usize];
for i in 0..size {
let g = grey(d, i);
v[0][(g + size) as usize] = i;
v[0][i as usize] = g;
}
for n1 in 1..=n {
for r in 0..size {
let mut alphas = vec![r; n1 as usize];
v[n1 as usize][r as usize] = h_index_alphas(d, n1, &alphas[..], &v);
alphas[(n1 - 1) as usize] ^= 1;
v[n1 as usize][(r + size) as usize] = h_index_alphas(d, n1, &alphas[..], &v);
}
}
v
}
fn h_index_alphas(d: u32, n: u32, alphas: &[u32], corners: &[Vec<u32>]) -> u32 {
debug_assert_eq!(alphas.len(), n as usize);
if alphas.len() != n as usize {
return 0;
}
let mut r: u64 = 0;
let two_power_d = 1u32 << d;
let two_power_d_64 = 1u64 << d;
for i in (0..n).rev() {
let alpha = alphas[i as usize] % two_power_d;
let alpha_inv = alpha ^ (two_power_d - 1);
let need_to_change_last = 1 ^ parity(alpha_inv);
let index = (alpha_inv + two_power_d * need_to_change_last) as usize;
let k = (n - 1 - i) as usize;
let r_shift = corners[k][index] as u64;
let mut current_r_shift = r_shift;
if (d % 2 == 1) && (n == 1) {
current_r_shift = (current_r_shift ^ (two_power_d_64 - 1)).wrapping_add(1);
}
let shift = (d as u64) * (k as u64);
let sub_cell_size = 1u64 << shift;
r = r.wrapping_sub(current_r_shift);
r %= sub_cell_size;
let r0 = cached_inv_grey(d, alpha, corners) as u64;
r += r0 * sub_cell_size;
}
r as u32
}
fn h_index(d: u32, n: u32, p: &[u32], corners: &[Vec<u32>]) -> u32 {
debug_assert_eq!(p.len(), d as usize);
if p.len() != d as usize {
return 0;
}
h_index_alphas(d, n, &ops::bit_transpose(n, p), corners)
}
fn h_point(d: u32, n: u32, idx: u32, corners: &[Vec<u32>]) -> SmallVec<[u32; 8]> {
let mut alphas = vec![0; n as usize];
let two_power_d = 1u32 << d;
let two_power_d_64 = 1u64 << d;
let mut r: u64 = idx as u64;
for i in 0..n {
let k = n - 1 - i;
let shift = k as u64 * d as u64;
let r0 = (r >> shift) % two_power_d_64;
let r0_u32 = r0 as u32;
let alpha = cached_grey(d, r0_u32, corners);
alphas[i as usize] = alpha;
let alpha_inv = alpha ^ (two_power_d - 1);
let need_to_change_last = 1 ^ parity(alpha_inv);
let index = (alpha_inv + two_power_d * need_to_change_last) as usize;
let mut r_shift = corners[k as usize][index] as u64;
if d % 2 == 1 && n == 1 {
r_shift ^= two_power_d_64 - 1;
r_shift = r_shift.wrapping_add(1);
}
r = r.wrapping_add(r_shift);
}
ops::bit_transpose(d, &alphas)
}
#[derive(Debug)]
pub struct HCurve {
pub order: u32,
pub dimension: u32,
corners: Vec<Vec<u32>>,
}
impl HCurve {
pub fn from_dimensions(dimension: u32, size: u32) -> error::Result<Self> {
if dimension < 2 {
return Err(error::Error::Shape("Dimension must be >= 2".to_string()));
}
let spec = GridSpec::power_of_two(dimension, size)?;
let order = spec.order().unwrap();
if dimension >= 32 {
return Err(error::Error::Shape("Dimension must be < 32".to_string()));
}
if (order as u64) * (dimension as u64) >= 32 {
return Err(error::Error::Size(
"Curve size exceeds u32 limits (D*O must be < 32)".to_string(),
));
}
let corners = corner_indexes(dimension, order);
Ok(Self {
dimension,
order,
corners,
})
}
}
impl SpaceCurve for HCurve {
fn name(&self) -> &'static str {
"H-curve"
}
fn info(&self) -> &'static str {
"Hilbert-like family based on Binary Reflected Gray Code with\n\
orientation transforms (Niedermeier–Reinhardt–Sanders; Netay).\n\
Continuous on 2^n grids and often offering strong locality with\n\
relatively simple bit operations."
}
fn length(&self) -> u32 {
1u32 << (self.order * self.dimension)
}
fn dimensions(&self) -> u32 {
self.dimension
}
fn point(&self, index: u32) -> point::Point {
let d = self.dimension;
let n = self.order;
let hpoint = h_point(d, n, index, &self.corners);
point::Point::new_with_dimension(self.dimension, hpoint)
}
fn index(&self, p: &point::Point) -> u32 {
let d = self.dimension;
let n = self.order;
h_index(d, n, &p[..], &self.corners)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_2d_order3() -> error::Result<()> {
let curve = HCurve::from_dimensions(2, 8)?;
for idx in 0..curve.length() {
let point = curve.point(idx);
assert_eq!(curve.index(&point), idx, "roundtrip failed at {idx}");
}
Ok(())
}
#[test]
fn roundtrip_3d_order1() -> error::Result<()> {
let curve = HCurve::from_dimensions(3, 2)?;
for idx in 0..curve.length() {
let point = curve.point(idx);
assert_eq!(curve.index(&point), idx, "3D order1 mismatch at {idx}");
}
Ok(())
}
#[test]
fn roundtrip_3d_order2() -> error::Result<()> {
let curve = HCurve::from_dimensions(3, 4)?;
for idx in 0..curve.length() {
let point = curve.point(idx);
assert_eq!(curve.index(&point), idx, "3D order2 mismatch at {idx}");
}
Ok(())
}
}