nodedb_array/coord/
hilbert.rs1use super::normalize::MAX_DIMS;
10use crate::error::{ArrayError, ArrayResult};
11
12pub fn encode(coords: &[u64], bits: u32) -> ArrayResult<u64> {
17 check_shape(coords.len(), bits)?;
18 let n = coords.len();
19 if n == 0 {
20 return Ok(0);
21 }
22 let mut x: [u64; MAX_DIMS] = [0; MAX_DIMS];
23 x[..n].copy_from_slice(coords);
24
25 let m: u64 = 1u64 << (bits - 1);
27 let mut q = m;
28 while q > 0 {
29 let p = q - 1;
30 for i in 0..n {
31 if x[i] & q != 0 {
32 x[0] ^= p;
33 } else {
34 let t = (x[0] ^ x[i]) & p;
35 x[0] ^= t;
36 x[i] ^= t;
37 }
38 }
39 q >>= 1;
40 }
41 for i in 1..n {
43 x[i] ^= x[i - 1];
44 }
45 let mut t: u64 = 0;
46 let mut q = m;
47 while q > 1 {
48 if x[n - 1] & q != 0 {
49 t ^= q - 1;
50 }
51 q >>= 1;
52 }
53 for xi in x.iter_mut().take(n) {
54 *xi ^= t;
55 }
56
57 let mut idx: u64 = 0;
59 for b in (0..bits).rev() {
60 for xi in x.iter().take(n) {
61 let bit = (*xi >> b) & 1;
62 idx = (idx << 1) | bit;
63 }
64 }
65 Ok(idx)
66}
67
68pub fn decode(idx: u64, n: usize, bits: u32) -> ArrayResult<Vec<u64>> {
71 check_shape(n, bits)?;
72 if n == 0 {
73 return Ok(Vec::new());
74 }
75 let mut x: [u64; MAX_DIMS] = [0; MAX_DIMS];
77 for b in (0..bits).rev() {
78 for (i, slot) in x.iter_mut().enumerate().take(n) {
79 let shift = b * n as u32 + (n as u32 - 1 - i as u32);
80 let bit = (idx >> shift) & 1;
81 *slot = (*slot << 1) | bit;
82 }
83 }
84 let t = x[n - 1] >> 1;
86 for i in (1..n).rev() {
87 x[i] ^= x[i - 1];
88 }
89 x[0] ^= t;
90 let n_lim: u128 = 1u128 << bits;
92 let mut q: u128 = 2;
93 while q != n_lim {
94 let p = (q - 1) as u64;
95 let qb = q as u64;
96 for i in (0..n).rev() {
97 if x[i] & qb != 0 {
98 x[0] ^= p;
99 } else {
100 let t = (x[0] ^ x[i]) & p;
101 x[0] ^= t;
102 x[i] ^= t;
103 }
104 }
105 q <<= 1;
106 }
107 Ok(x[..n].to_vec())
108}
109
110fn check_shape(n: usize, bits: u32) -> ArrayResult<()> {
111 if n > MAX_DIMS {
112 return Err(ArrayError::InvalidSchema {
113 array: String::new(),
114 detail: format!("hilbert: arity {n} exceeds MAX_DIMS={MAX_DIMS}"),
115 });
116 }
117 if bits == 0 || (n as u32) * bits > 64 {
118 return Err(ArrayError::InvalidSchema {
119 array: String::new(),
120 detail: format!("hilbert: {n} dims × {bits} bits exceeds 64-bit prefix"),
121 });
122 }
123 Ok(())
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn hilbert_round_trip_2d_4bit() {
132 for x in 0..16u64 {
133 for y in 0..16u64 {
134 let idx = encode(&[x, y], 4).unwrap();
135 let back = decode(idx, 2, 4).unwrap();
136 assert_eq!(back, vec![x, y], "mismatch at ({x},{y}) idx={idx}");
137 }
138 }
139 }
140
141 #[test]
142 fn hilbert_round_trip_3d_4bit() {
143 for x in 0..16u64 {
144 for y in 0..16u64 {
145 for z in 0..16u64 {
146 let idx = encode(&[x, y, z], 4).unwrap();
147 let back = decode(idx, 3, 4).unwrap();
148 assert_eq!(back, vec![x, y, z]);
149 }
150 }
151 }
152 }
153
154 #[test]
155 fn hilbert_indices_are_unique_2d() {
156 use std::collections::HashSet;
157 let mut seen = HashSet::new();
158 for x in 0..8u64 {
159 for y in 0..8u64 {
160 assert!(seen.insert(encode(&[x, y], 3).unwrap()));
161 }
162 }
163 assert_eq!(seen.len(), 64);
164 }
165
166 #[test]
167 fn hilbert_index_adjacent_cells_are_spatial_neighbors() {
168 let mut by_idx = vec![[0u64; 2]; 64];
173 for x in 0..8u64 {
174 for y in 0..8u64 {
175 let i = encode(&[x, y], 3).unwrap() as usize;
176 by_idx[i] = [x, y];
177 }
178 }
179 for w in by_idx.windows(2) {
180 let dx = (w[0][0] as i64 - w[1][0] as i64).abs();
181 let dy = (w[0][1] as i64 - w[1][1] as i64).abs();
182 assert!(
183 dx.max(dy) == 1,
184 "index-adjacent cells {:?}, {:?} not spatial neighbors",
185 w[0],
186 w[1]
187 );
188 }
189 }
190
191 #[test]
192 fn hilbert_rejects_too_many_dims() {
193 let coords = vec![0u64; MAX_DIMS + 1];
194 assert!(encode(&coords, 1).is_err());
195 }
196
197 #[test]
198 fn hilbert_rejects_overflowing_prefix() {
199 let coords = vec![0u64; 8];
201 assert!(encode(&coords, 9).is_err());
202 }
203}