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  }