- Speed: Able to compute
IsToVs()at2.93 GiB/sas indicated by runningbenchmarkinReleasemode withclang++on an i5-2500K system. - Small code size: Currently the implementation is only about 200 lines. The functions are non-recursive and do not call other functions. They should be trivially portable to most other languages.
- Compile-time evaluation: All functions are
constexpr. See example usage inexample.cpp. - No dependencies.
hilbert.hpphas 0includes. - Functions are also defined for
N = 0andK = 0. - Full git history: Everything has been left in, no matter how embarassing my mistakes may be.
Simply add hilbert.hpp to your project.
There are 4 functions for manipulating Hilbert curves:
void IsToVs(ITy N, ITy K, ViTy curve[]): Computes theK'th step of anNdimensional Hilbert curve. The result will be stored incurve, which must have space for2^(N*K)N-dimensional vectors, for a total ofN*2^(N*K)integers.void VsToIs(STy N, STy K, ITy is[]): Computes the inverse of theK'th step of anNdimensional Hilbert curve. The result will be stored inis, which must have space for2^(N*K)integers.ismaps vectors to indices. The vector is encoded in the index intois. For example, ifN=3andK=2, theniscould be an array of typeViTy[2][2][2], and to get the index for a vectorvof typeITy[3], you would useis[v[2], v[1], v[0]]. Ifisis a flat array, you wold useis[(v[0] << 0*2) + (v[1] << 1*2) + (v[2] << 2*2)].void IToV(ITy N, ITy K, ITy i, ViTy v[]): Computes thei'th vector inIsToVs(N, K). The result will be stored inv. Requiresi < 2^(N*K).ITy VToI(ITy N, ITy K, ViTy v[]): Computes the index thatvwould have in theK'th step of anNdimensional Hilbert curve. Behavior is undefined ifvis not a point on the curve.vwill be zeroed when this function returns.
The Hilbert class itself has three tempalte parameters: ITy,
ViTy, and STy which are all defaulted to unsigned int.
ViTy is the type of the component of a vector. If you know that K
is small, you can use a narrower integer for ViTy like uint16_t if
K <= 16. This will increase performance of IsToVs() since it's
mostly limited by memory.
ITy is the type of an index of a vector in the curve. If you known
N*K is small, you can use a narrower integer for ITy like
uint32_t if N*K <= 32. This will also increase performance of
VsToIs() since it's limited by memory.
STy is used as the type of the variables N and K. There's not
many situations where you'd want to change it from the default.
The above functions live in a static class called Hilbert. To call
IsToVs(), for example, you might use:
// Compute the K'th iteration of an N-dimensional Hilbert curve.
unsigned int curve[1U << N * K][N];
Hilbert<>::IsToVs(N, K, curve[0]);
Each function takes the number of dimensions N and the iteration K
as parameters. If N and/or K are known at compile time, template
functions are available which can help the compiler generate more
optimized code. For example, the 4 versions of IsToVs() are listed
below.
// Compute the K'th iteration of an N-dimensional Hilbert curve.
constexpr int N = 2;
constexpr int K = 3;
unsigned int curve[1U << N * K][N];
// Untemplated version.
// Use this version if N and K are not known at compile time.
Hilbert<>::IsToVs(N, K, curve[0]);
// Use this version if N is known at compile time.
Hilbert<>::IsToVsN<N>(K, curve[0]);
// Use this version if K is known at compile time.
Hilbert<>::IsToVsK<K>(N, curve[0]);
// Use this version if N and K are known at compile time.
Hilbert<>::IsToVs<N, K>(curve[0]);
If N is known at compile time, the compiler can generate much faster
code. There's a much smaller marginal speedup if K is known at
compile time, and only when N is large. If binary size is more
important than runtime, you might want to stick to using the
non-templated versions.
See example.cpp.
The tests and example can be built with cmake:
cmake .
make
./hilbert_test
./example
A compiler with C++17 support is needed for constexpr usage. If
constexpr is removed, the code should be portable to C++98.