gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/spatial/curve/hilbert_test.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 "reflect" 11 "slices" 12 "strings" 13 "testing" 14 15 "golang.org/x/exp/rand" 16 ) 17 18 func ExampleHilbert2D_Pos() { 19 h := Hilbert2D{order: 3} 20 21 for y := 0; y < 1<<h.order; y++ { 22 for x := 0; x < 1<<h.order; x++ { 23 if x > 0 { 24 fmt.Print(" ") 25 } 26 fmt.Printf("%02x", h.Pos([]int{x, y})) 27 } 28 fmt.Println() 29 } 30 // Output: 31 // 00 01 0e 0f 10 13 14 15 32 // 03 02 0d 0c 11 12 17 16 33 // 04 07 08 0b 1e 1d 18 19 34 // 05 06 09 0a 1f 1c 1b 1a 35 // 3a 39 36 35 20 23 24 25 36 // 3b 38 37 34 21 22 27 26 37 // 3c 3d 32 33 2e 2d 28 29 38 // 3f 3e 31 30 2f 2c 2b 2a 39 } 40 41 func ExampleHilbert3D_Pos() { 42 h := Hilbert3D{order: 2} 43 44 for z := 0; z < 1<<h.order; z++ { 45 for y := 0; y < 1<<h.order; y++ { 46 for x := 0; x < 1<<h.order; x++ { 47 if x > 0 { 48 fmt.Print(" ") 49 } 50 fmt.Printf("%02x", h.Pos([]int{x, y, z})) 51 } 52 fmt.Println() 53 } 54 fmt.Println() 55 } 56 // Output: 57 // 00 07 08 0b 58 // 01 06 0f 0c 59 // 1a 1b 10 13 60 // 19 18 17 14 61 // 62 // 03 04 09 0a 63 // 02 05 0e 0d 64 // 1d 1c 11 12 65 // 1e 1f 16 15 66 // 67 // 3c 3b 36 35 68 // 3d 3a 31 32 69 // 22 23 2e 2d 70 // 21 20 29 2a 71 // 72 // 3f 38 37 34 73 // 3e 39 30 33 74 // 25 24 2f 2c 75 // 26 27 28 2b 76 } 77 78 func ExampleHilbert4D_Pos() { 79 h := Hilbert4D{order: 2} 80 N := 1 << h.order 81 for z := 0; z < N; z++ { 82 if z > 0 { 83 s := strings.Repeat("═", N*4-2) 84 s = s + strings.Repeat("═╬═"+s, N-1) 85 fmt.Println(s) 86 } 87 for y := 0; y < N; y++ { 88 for w := 0; w < N; w++ { 89 if w > 0 { 90 fmt.Print(" ║ ") 91 } 92 for x := 0; x < N; x++ { 93 if x > 0 { 94 fmt.Print(" ") 95 } 96 fmt.Printf("%02x", h.Pos([]int{x, y, z, w})) 97 } 98 } 99 fmt.Println() 100 } 101 } 102 // Output: 103 // 00 0f 10 13 ║ 03 0c 11 12 ║ fc f3 ee ed ║ ff f0 ef ec 104 // 01 0e 1f 1c ║ 02 0d 1e 1d ║ fd f2 e1 e2 ║ fe f1 e0 e3 105 // 32 31 20 23 ║ 35 36 21 22 ║ ca c9 de dd ║ cd ce df dc 106 // 33 30 2f 2c ║ 34 37 2e 2d ║ cb c8 d1 d2 ║ cc cf d0 d3 107 // ═══════════════╬════════════════╬════════════════╬═══════════════ 108 // 07 08 17 14 ║ 04 0b 16 15 ║ fb f4 e9 ea ║ f8 f7 e8 eb 109 // 06 09 18 1b ║ 05 0a 19 1a ║ fa f5 e6 e5 ║ f9 f6 e7 e4 110 // 3d 3e 27 24 ║ 3a 39 26 25 ║ c5 c6 d9 da ║ c2 c1 d8 db 111 // 3c 3f 28 2b ║ 3b 38 29 2a ║ c4 c7 d6 d5 ║ c3 c0 d7 d4 112 // ═══════════════╬════════════════╬════════════════╬═══════════════ 113 // 76 77 6c 6d ║ 79 78 6b 6a ║ 86 87 94 95 ║ 89 88 93 92 114 // 75 74 63 62 ║ 7a 7b 64 65 ║ 85 84 9b 9a ║ 8a 8b 9c 9d 115 // 42 41 5c 5d ║ 45 46 5b 5a ║ ba b9 a4 a5 ║ bd be a3 a2 116 // 43 40 53 52 ║ 44 47 54 55 ║ bb b8 ab aa ║ bc bf ac ad 117 // ═══════════════╬════════════════╬════════════════╬═══════════════ 118 // 71 70 6f 6e ║ 7e 7f 68 69 ║ 81 80 97 96 ║ 8e 8f 90 91 119 // 72 73 60 61 ║ 7d 7c 67 66 ║ 82 83 98 99 ║ 8d 8c 9f 9e 120 // 4d 4e 5f 5e ║ 4a 49 58 59 ║ b5 b6 a7 a6 ║ b2 b1 a0 a1 121 // 4c 4f 50 51 ║ 4b 48 57 56 ║ b4 b7 a8 a9 ║ b3 b0 af ae 122 } 123 124 func TestConstructors(t *testing.T) { 125 const intSize = 32 << (^uint(0) >> 63) // 32 or 64 126 127 t.Run("2D/Ok", func(t *testing.T) { 128 _, err := NewHilbert2D(intSize/2 - 1) 129 noError(t, err) 130 }) 131 132 t.Run("3D/Ok", func(t *testing.T) { 133 _, err := NewHilbert3D(intSize / 3) 134 noError(t, err) 135 }) 136 137 t.Run("4D/Ok", func(t *testing.T) { 138 _, err := NewHilbert4D(intSize/4 - 1) 139 noError(t, err) 140 }) 141 142 t.Run("2D/Underflow", func(t *testing.T) { 143 _, err := NewHilbert2D(0) 144 errorIs(t, err, ErrUnderflow) 145 }) 146 147 t.Run("3D/Underflow", func(t *testing.T) { 148 _, err := NewHilbert3D(0) 149 errorIs(t, err, ErrUnderflow) 150 }) 151 152 t.Run("4D/Underflow", func(t *testing.T) { 153 _, err := NewHilbert4D(0) 154 errorIs(t, err, ErrUnderflow) 155 }) 156 157 t.Run("2D/Overflow", func(t *testing.T) { 158 _, err := NewHilbert2D(intSize / 2) 159 errorIs(t, err, ErrOverflow) 160 }) 161 162 t.Run("3D/Overflow", func(t *testing.T) { 163 _, err := NewHilbert3D(intSize/3 + 1) 164 errorIs(t, err, ErrOverflow) 165 }) 166 167 t.Run("4D/Overflow", func(t *testing.T) { 168 _, err := NewHilbert4D(intSize / 4) 169 errorIs(t, err, ErrOverflow) 170 }) 171 } 172 173 func TestHilbert2D(t *testing.T) { 174 for ord := 1; ord <= 4; ord++ { 175 t.Run(fmt.Sprintf("Order/%d", ord), func(t *testing.T) { 176 testCurve(t, Hilbert2D{order: ord}) 177 }) 178 } 179 180 cases := map[int][]int{ 181 1: { 182 0, 1, 183 3, 2, 184 }, 185 2: { 186 0x0, 0x3, 0x4, 0x5, 187 0x1, 0x2, 0x7, 0x6, 188 0xE, 0xD, 0x8, 0x9, 189 0xF, 0xC, 0xB, 0xA, 190 }, 191 3: { 192 0x00, 0x01, 0x0E, 0x0F, 0x10, 0x13, 0x14, 0x15, 193 0x03, 0x02, 0x0D, 0x0C, 0x11, 0x12, 0x17, 0x16, 194 0x04, 0x07, 0x08, 0x0B, 0x1E, 0x1D, 0x18, 0x19, 195 0x05, 0x06, 0x09, 0x0A, 0x1F, 0x1C, 0x1B, 0x1A, 196 0x3A, 0x39, 0x36, 0x35, 0x20, 0x23, 0x24, 0x25, 197 0x3B, 0x38, 0x37, 0x34, 0x21, 0x22, 0x27, 0x26, 198 0x3C, 0x3D, 0x32, 0x33, 0x2E, 0x2D, 0x28, 0x29, 199 0x3F, 0x3E, 0x31, 0x30, 0x2F, 0x2C, 0x2B, 0x2A, 200 }, 201 } 202 203 for order, expected := range cases { 204 t.Run(fmt.Sprintf("Case/%d", order), func(t *testing.T) { 205 testCurveCase(t, Hilbert2D{order: order}, order, expected) 206 }) 207 } 208 } 209 210 func TestHilbert3D(t *testing.T) { 211 for ord := 1; ord <= 4; ord++ { 212 t.Run(fmt.Sprintf("Order/%d", ord), func(t *testing.T) { 213 testCurve(t, Hilbert3D{order: ord}) 214 }) 215 } 216 217 cases := map[int][]int{ 218 1: { 219 0, 1, 220 3, 2, 221 222 7, 6, 223 4, 5, 224 }, 225 2: { 226 0x00, 0x07, 0x08, 0x0B, 227 0x01, 0x06, 0x0F, 0x0C, 228 0x1A, 0x1B, 0x10, 0x13, 229 0x19, 0x18, 0x17, 0x14, 230 231 0x03, 0x04, 0x09, 0x0A, 232 0x02, 0x05, 0x0E, 0x0D, 233 0x1D, 0x1C, 0x11, 0x12, 234 0x1E, 0x1F, 0x16, 0x15, 235 236 0x3C, 0x3B, 0x36, 0x35, 237 0x3D, 0x3A, 0x31, 0x32, 238 0x22, 0x23, 0x2E, 0x2D, 239 0x21, 0x20, 0x29, 0x2A, 240 241 0x3F, 0x38, 0x37, 0x34, 242 0x3E, 0x39, 0x30, 0x33, 243 0x25, 0x24, 0x2F, 0x2C, 244 0x26, 0x27, 0x28, 0x2B, 245 }, 246 } 247 248 for order, expected := range cases { 249 t.Run(fmt.Sprintf("Case/%d", order), func(t *testing.T) { 250 testCurveCase(t, Hilbert3D{order: order}, order, expected) 251 }) 252 } 253 } 254 255 func TestHilbert4D(t *testing.T) { 256 for ord := 1; ord <= 4; ord++ { 257 t.Run(fmt.Sprintf("Order %d", ord), func(t *testing.T) { 258 testCurve(t, Hilbert4D{order: ord}) 259 }) 260 } 261 } 262 263 func BenchmarkHilbert(b *testing.B) { 264 const O = 10 265 for N := 2; N <= 4; N++ { 266 b.Run(fmt.Sprintf("%dD/Pos", N), func(b *testing.B) { 267 for ord := 1; ord <= O; ord++ { 268 b.Run(fmt.Sprintf("Order %d", ord), func(b *testing.B) { 269 h := newCurve(ord, N) 270 v := make([]int, N) 271 for i := range v { 272 v[i] = rand.Intn(1 << ord) 273 } 274 u := make([]int, N) 275 for n := 0; n < b.N; n++ { 276 copy(u, v) 277 h.Pos(u) 278 } 279 }) 280 } 281 }) 282 283 b.Run(fmt.Sprintf("%dD/Coord", N), func(b *testing.B) { 284 for ord := 1; ord <= O; ord++ { 285 b.Run(fmt.Sprintf("Order %d", ord), func(b *testing.B) { 286 h := newCurve(ord, N) 287 d := rand.Intn(1 << (ord * N)) 288 v := make([]int, N) 289 for n := 0; n < b.N; n++ { 290 h.Coord(v, d) 291 } 292 }) 293 } 294 }) 295 } 296 } 297 298 type curve interface { 299 Dims() []int 300 Len() int 301 Pos(v []int) int 302 Coord(dst []int, pos int) []int 303 } 304 305 func newCurve(order, dim int) curve { 306 switch dim { 307 case 2: 308 return Hilbert2D{order: order} 309 case 3: 310 return Hilbert3D{order: order} 311 case 4: 312 return Hilbert4D{order: order} 313 } 314 panic("invalid dimension") 315 } 316 317 // testCurve verifies that Pos and Coord (of C) are inverses of each other and 318 // that the spatial coordinates V and U - corresponding to linear coordinates D 319 // and D+1 - are exactly one unit (euclidean) distant from each other. 320 func testCurve(t *testing.T, c curve) { 321 t.Helper() 322 323 // Stop if the error count reaches 10 324 var errc int 325 fail := func() { 326 if errc < 10 { 327 errc++ 328 t.Fail() 329 } else { 330 t.FailNow() 331 } 332 } 333 334 // Map between linear and spatial coordinates, and verify that Pos and Coord 335 // are inverses of each other 336 m := map[int][]int{} 337 curveRange(c, func(v []int) { 338 d := c.Pos(slices.Clone(v)) 339 u := c.Coord(nil, d) 340 if !reflect.DeepEqual(v, u) { 341 t.Logf("Space is not the inverse of Curve for d=%d %v", d, v) 342 fail() 343 } 344 345 m[d] = slices.Clone(v) 346 }) 347 348 D := 1 349 for _, v := range c.Dims() { 350 D *= v 351 } 352 353 // For each possible pairs of linear coordinates D and D+1, verify that the 354 // corresponding spatial coordinates V and U are exactly one unit apart 355 // (euclidean distance) 356 for d := 0; d < D-1; d++ { 357 v, u := m[d], m[d+1] 358 if !adjacent(v, u) { 359 t.Logf("points %x and %x are not adjacent\n %v -> %v", d, d+1, v, u) 360 fail() 361 } 362 } 363 } 364 365 // curveRange ranges over the n-dimensional coordinate space of the curve, 366 // calling fn on each element of the space. 367 func curveRange(c curve, fn func([]int)) { 368 size := c.Dims() 369 dimRange(len(size), size, make([]int, len(size)), fn) 370 } 371 372 // dimRange ranges over the coordinate space defined by size, calling fn on each 373 // element of the space. Call dimRange with dim = len(size). 374 func dimRange(dim int, size []int, v []int, fn func([]int)) { 375 if dim == 0 { 376 fn(v) 377 return 378 } 379 380 for i := 0; i < size[dim-1]; i++ { 381 v[dim-1] = i 382 dimRange(dim-1, size, v, fn) 383 } 384 } 385 386 // adjacent returns true if the euclidean distance between v and u is 387 // exactly one. v and u must be the same length. 388 // 389 // In other words, given d(i) = abs(v[i] - u[i]), adjacent returns false if 390 // d(i) > 1 for any i or if d(i) == 1 for more than a single i, or if d(i) 391 // == 0 for all i. 392 func adjacent(v, u []int) bool { 393 n := 0 394 for i := range v { 395 x := v[i] - u[i] 396 if x == 0 { 397 continue 398 } 399 if x < -1 || 1 < x { 400 return false 401 } 402 n++ 403 } 404 405 return n == 1 406 } 407 408 // testCurveCase verifies that the curve produces the expected sequence of 409 // values. 410 func testCurveCase(t *testing.T, c curve, order int, expected []int) { 411 t.Helper() 412 413 dim := len(c.Dims()) 414 actual := make([]int, len(expected)) 415 for i := range expected { 416 v := coord(i, order, dim) 417 actual[i] = c.Pos(slices.Clone(v)) 418 } 419 if !reflect.DeepEqual(expected, actual) { 420 t.Fatalf("unexpected result:\ngot: %v\nwant: %v", actual, expected) 421 } 422 423 for i, d := range expected { 424 v := coord(i, order, dim) 425 if !reflect.DeepEqual(v, c.Coord(nil, d)) { 426 t.Fatalf("[%d] expected %v for d = %d", i, v, d) 427 } 428 } 429 } 430 431 // coord returns the nth spatial coordinates for a dim-dimensional space with 432 // sides 2^ord in row-major order. 433 func coord(n, ord, dim int) []int { 434 v := make([]int, dim) 435 for i := 0; i < dim; i++ { 436 v[i] = n % (1 << ord) 437 n /= (1 << ord) 438 } 439 return v 440 } 441 442 func noError(t testing.TB, err error) { 443 t.Helper() 444 if err != nil { 445 t.Fatal(err) 446 } 447 } 448 449 func errorIs(t testing.TB, err, target error) { 450 t.Helper() 451 if err == nil { 452 t.Fatal("Expected an error") 453 } 454 if !errors.Is(err, target) { 455 t.Fatalf("Expected %v to be %v", err, target) 456 } 457 }