gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/spatial/curve/hilbert.go (about) 1 // Copyright ©2024 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package curve 6 7 import ( 8 "errors" 9 "fmt" 10 ) 11 12 // ErrUnderflow is returned by Hilbert curve constructors when the power is less 13 // than 1. 14 var ErrUnderflow = errors.New("order is less than 1") 15 16 // ErrOverflow is returned (wrapped) by Hilbert curve constructors when the 17 // power would cause Len and Pos to overflow. 18 var ErrOverflow = errors.New("overflow int") 19 20 // The size of an int. Taken from src/math/const.go. 21 const intSize = 32 << (^uint(0) >> 63) // 32 or 64 22 23 // Hilbert2D is a 2-dimensional Hilbert curve. 24 type Hilbert2D struct{ order int } 25 26 // NewHilbert2D constructs a [Hilbert2D] of the given order. NewHilbert2D 27 // returns [ErrOverflow] (wrapped) if the order would cause Len and Pos to 28 // overflow. 29 func NewHilbert2D(order int) (Hilbert2D, error) { 30 v := Hilbert2D{order: order} 31 32 // The order must be greater than zero. 33 if order < 1 { 34 return v, ErrUnderflow 35 } 36 37 // The product of the order and number of dimensions must not exceed or 38 // equal the size of an int. 39 if order*2 >= intSize { 40 return v, fmt.Errorf("a 2-dimensional, %d-order Hilbert curve will %w", order, ErrOverflow) 41 } 42 43 return v, nil 44 } 45 46 // Dims returns the spatial dimensions of the curve, which is {2ᵏ, 2ᵏ}, where k 47 // is the order. 48 func (h Hilbert2D) Dims() []int { return []int{1 << h.order, 1 << h.order} } 49 50 // Len returns the length of the curve, which is 2ⁿᵏ, where n is the dimension 51 // (2) and k is the order. 52 // 53 // Len will overflow if order is ≥ 16 on architectures where [int] is 32-bits, 54 // or ≥ 32 on architectures where [int] is 64-bits. 55 func (h Hilbert2D) Len() int { return 1 << (2 * h.order) } 56 57 func (h Hilbert2D) rot(n int, v []int, d int) { 58 switch d { 59 case 0: 60 swap{0, 1}.do(n, v) 61 case 3: 62 flip{0, 1}.do(n, v) 63 } 64 } 65 66 // Pos returns the linear position of the 3-spatial coordinate v along the 67 // curve. Pos modifies v. 68 // 69 // Pos will overflow if order is ≥ 16 on architectures where [int] is 32-bits, 70 // or ≥ 32 on architectures where [int] is 64-bits. 71 func (h Hilbert2D) Pos(v []int) int { 72 var d int 73 const dims = 2 74 for n := h.order - 1; n >= 0; n-- { 75 rx := (v[0] >> n) & 1 76 ry := (v[1] >> n) & 1 77 rd := ry<<1 | (ry ^ rx) 78 d += rd << (dims * n) 79 h.rot(h.order, v, rd) 80 } 81 return d 82 } 83 84 // Coord writes the spatial coordinates of pos to dst and returns it. If dst is 85 // nil, Coord allocates a new slice of length 2; otherwise Coord is 86 // allocation-free. 87 // 88 // Coord panics if dst is not nil and len(dst) is not equal to 2. 89 func (h Hilbert2D) Coord(dst []int, pos int) []int { 90 if dst == nil { 91 dst = make([]int, 2) 92 } else if len(dst) != 2 { 93 panic("len(dst) must equal 2") 94 } 95 for n := 0; n < h.order; n++ { 96 e := pos & 3 97 h.rot(n, dst[:], e) 98 99 ry := e >> 1 100 rx := (e>>0 ^ e>>1) & 1 101 dst[0] += rx << n 102 dst[1] += ry << n 103 pos >>= 2 104 } 105 return dst 106 } 107 108 // Hilbert3D is a 3-dimensional Hilbert curve. 109 type Hilbert3D struct{ order int } 110 111 // NewHilbert3D constructs a [Hilbert3D] of the given order. NewHilbert3D 112 // returns [ErrOverflow] (wrapped) if the order would cause Len and Pos to 113 // overflow. 114 func NewHilbert3D(order int) (Hilbert3D, error) { 115 v := Hilbert3D{order: order} 116 117 // The order must be greater than zero. 118 if order < 1 { 119 return v, ErrUnderflow 120 } 121 122 // The product of the order and number of dimensions must not exceed or 123 // equal the size of an int. 124 if order*3 >= intSize { 125 return v, fmt.Errorf("a 3-dimensional, %d-order Hilbert curve will %w", order, ErrOverflow) 126 } 127 128 return v, nil 129 } 130 131 // Dims returns the spatial dimensions of the curve, which is {2ᵏ, 2ᵏ, 2ᵏ}, where 132 // k is the order. 133 func (h Hilbert3D) Dims() []int { return []int{1 << h.order, 1 << h.order, 1 << h.order} } 134 135 // Len returns the length of the curve, which is 2ⁿᵏ, where n is the dimension 136 // (3) and k is the order. 137 // 138 // Len will overflow if order is ≥ 11 on architectures where [int] is 32-bits, 139 // or ≥ 21 on architectures where [int] is 64-bits. 140 func (h Hilbert3D) Len() int { return 1 << (3 * h.order) } 141 142 func (h Hilbert3D) rot(reverse bool, n int, v []int, d int) { 143 switch d { 144 case 0: 145 do2(reverse, n, v, swap{1, 2}, swap{0, 2}) 146 case 1, 2: 147 do2(reverse, n, v, swap{0, 2}, swap{1, 2}) 148 case 3, 4: 149 invert{0, 1}.do(n, v) 150 case 5, 6: 151 do2(reverse, n, v, flip{0, 2}, flip{1, 2}) 152 case 7: 153 do2(reverse, n, v, flip{1, 2}, flip{0, 2}) 154 } 155 } 156 157 // Pos returns the linear position of the 4-spatial coordinate v along the 158 // curve. Pos modifies v. 159 // 160 // Pos will overflow if order is ≥ 11 on architectures where [int] is 32-bits, 161 // or ≥ 21 on architectures where [int] is 64-bits. 162 func (h Hilbert3D) Pos(v []int) int { 163 var d int 164 const dims = 3 165 for n := h.order - 1; n >= 0; n-- { 166 rx := (v[0] >> n) & 1 167 ry := (v[1] >> n) & 1 168 rz := (v[2] >> n) & 1 169 rd := rz<<2 | (rz^ry)<<1 | (rz ^ ry ^ rx) 170 d += rd << (dims * n) 171 h.rot(false, h.order, v, rd) 172 } 173 return d 174 } 175 176 // Coord writes the spatial coordinates of pos to dst and returns it. If dst is 177 // nil, Coord allocates a new slice of length 3; otherwise Coord is 178 // allocation-free. 179 // 180 // Coord panics if dst is not nil and len(dst) is not equal to 3. 181 func (h Hilbert3D) Coord(dst []int, pos int) []int { 182 if dst == nil { 183 dst = make([]int, 3) 184 } else if len(dst) != 3 { 185 panic("len(dst) must equal 3") 186 } 187 for n := 0; n < h.order; n++ { 188 e := pos & 7 189 h.rot(true, n, dst[:], e) 190 191 rz := e >> 2 192 ry := (e>>1 ^ e>>2) & 1 193 rx := (e>>0 ^ e>>1) & 1 194 dst[0] += rx << n 195 dst[1] += ry << n 196 dst[2] += rz << n 197 pos >>= 3 198 } 199 return dst 200 } 201 202 // Hilbert4D is a 4-dimensional Hilbert curve. 203 type Hilbert4D struct{ order int } 204 205 // NewHilbert4D constructs a [Hilbert4D] of the given order. NewHilbert4D 206 // returns [ErrOverflow] (wrapped) if the order would cause Len and Pos to 207 // overflow. 208 func NewHilbert4D(order int) (Hilbert4D, error) { 209 v := Hilbert4D{order: order} 210 211 // The order must be greater than zero. 212 if order < 1 { 213 return v, ErrUnderflow 214 } 215 216 // The product of the order and number of dimensions must not exceed or 217 // equal the size of an int. 218 if order*4 >= intSize { 219 return v, fmt.Errorf("a 4-dimensional, %d-order Hilbert curve will %w", order, ErrOverflow) 220 } 221 222 return v, nil 223 } 224 225 // Dims returns the spatial dimensions of the curve, which is {2ᵏ, 2ᵏ, 2ᵏ, 2ᵏ}, 226 // where k is the order. 227 func (h Hilbert4D) Dims() []int { return []int{1 << h.order, 1 << h.order, 1 << h.order, 1 << h.order} } 228 229 // Len returns the length of the curve, which is 2ⁿᵏ, where n is the dimension 230 // (4) and k is the order. 231 // 232 // Len will overflow if order is ≥ 8 on architectures where [int] is 32-bits, or 233 // ≥ 16 on architectures where [int] is 64-bits. 234 func (h Hilbert4D) Len() int { return 1 << (4 * h.order) } 235 236 func (h Hilbert4D) rot(reverse bool, n int, v []int, d int) { 237 switch d { 238 case 0: 239 do2(reverse, n, v, swap{1, 3}, swap{0, 3}) 240 case 1, 2: 241 do2(reverse, n, v, swap{0, 3}, swap{1, 3}) 242 case 3, 4: 243 do2(reverse, n, v, flip{0, 1}, swap{2, 3}) 244 case 5, 6: 245 do2(reverse, n, v, flip{1, 2}, swap{2, 3}) 246 case 7, 8: 247 invert{0, 2}.do(n, v) 248 case 9, 10: 249 do2(reverse, n, v, flip{1, 2}, flip{2, 3}) 250 case 11, 12: 251 do2(reverse, n, v, flip{0, 1}, flip{2, 3}) 252 case 13, 14: 253 do2(reverse, n, v, flip{0, 3}, flip{1, 3}) 254 case 15: 255 do2(reverse, n, v, flip{1, 3}, flip{0, 3}) 256 } 257 } 258 259 // Pos returns the linear position of the 2-spatial coordinate v along the 260 // curve. Pos modifies v. 261 // 262 // Pos will overflow if order is ≥ 8 on architectures where [int] is 32-bits, or 263 // ≥ 16 on architectures where [int] is 64-bits. 264 func (h Hilbert4D) Pos(v []int) int { 265 var d int 266 const dims = 4 267 for n := h.order - 1; n >= 0; n-- { 268 var e int 269 for i := dims - 1; i >= 0; i-- { 270 v := v[i] >> n & 1 271 e = e<<1 | (e^v)&1 272 } 273 274 d += e << (dims * n) 275 h.rot(false, h.order, v, e) 276 } 277 return d 278 } 279 280 // Coord writes the spatial coordinates of pos to dst and returns it. If dst is 281 // nil, Coord allocates a new slice of length 4; otherwise Coord is 282 // allocation-free. 283 // 284 // Coord panics if dst is not nil and len(dst) is not equal to 4. 285 func (h Hilbert4D) Coord(dst []int, pos int) []int { 286 if dst == nil { 287 dst = make([]int, 4) 288 } else if len(dst) != 4 { 289 panic("len(dst) must equal 4") 290 } 291 N := 4 292 for n := 0; n < h.order; n++ { 293 e := pos & (1<<N - 1) 294 h.rot(true, n, dst[:], e) 295 296 for i, e := 0, e; i < N; i++ { 297 dst[i] += (e ^ e>>1) & 1 << n 298 e >>= 1 299 } 300 pos >>= N 301 } 302 return dst 303 } 304 305 type op interface{ do(int, []int) } 306 307 // invert I and J 308 type invert struct{ i, j int } 309 310 func (c invert) do(n int, v []int) { v[c.i], v[c.j] = v[c.i]^(1<<n-1), v[c.j]^(1<<n-1) } 311 312 // swap I and J 313 type swap struct{ i, j int } 314 315 func (c swap) do(n int, v []int) { v[c.i], v[c.j] = v[c.j], v[c.i] } 316 317 // swap and invert I and J 318 type flip struct{ i, j int } 319 320 func (c flip) do(n int, v []int) { v[c.i], v[c.j] = v[c.j]^(1<<n-1), v[c.i]^(1<<n-1) } 321 322 // do2 executes the given operations, optionally in reverse. 323 // 324 // Generic specialization reduces allocation (because it can eliminate interface 325 // value boxing) and improves performance 326 func do2[A, B op](reverse bool, n int, v []int, a A, b B) { 327 if reverse { 328 b.do(n, v) 329 a.do(n, v) 330 } else { 331 a.do(n, v) 332 b.do(n, v) 333 } 334 }