一尘不染

将N维值映射到希尔伯特曲线上的一个点

algorithm

我有大量的N维点(数千万; N接近100)。

我需要将这些点映射到一个维度,同时保留空间局部性。我想使用希尔伯特空间填充曲线来做到这一点。

对于每个点,我想选择曲线上最接近的点。该点的希尔伯特值(从曲线起点到拾取点的曲线长度)是我要寻找的单个尺寸值。

计算不一定必须是即时的,但我希望它在体面的现代家用PC硬件上不会超过几个小时。

对实施有什么建议吗?有没有什么图书馆对我有帮助?(语言无关紧要。)


阅读 250

收藏
2020-07-28

共1个答案

一尘不染

我终于崩溃了,掏出一些钱。AIP(美国物理研究所)在C上有一篇很好的简短文章,带有源代码。John Skilling的“ Programming
Hilbert curve”(来自AIP
Conf。Proc。707,381(2004))有一个附录,其中包含用于双向映射。它适用于任何大于1的维数,不是递归的,不使用状态转换查找表,该表占用大量内存,并且主要使用位操作。因此,它相当快并且具有良好的内存占用量。

如果您选择购买该文章,我发现源代码中有错误。

下面的代码行(在TransposetoAxes函数中找到)具有错误:

for(i = n-1; i> = 0; i–)X [i] ^ = X [i-1];

纠正方法是将大于或等于(> =)更改为大于(>)。如果不进行此更正,则当变量“ i”变为零时,将使用负索引访问X数组,从而导致程序失败。

我建议阅读该文章(包括代码在内,长达七页),因为它解释了算法的工作原理,这显然并不明显。

我将他的代码翻译成C#供我自己使用。代码如下。Skilling进行适当的转换,覆盖您传入的向量。我选择对输入向量进行克隆,然后返回一个新副本。另外,我将这些方法实现为扩展方法。

Skilling的代码将希尔伯特索引表示为转置,存储为数组。我发现交错这些位并形成一个BigInteger更方便(在Dictionary中更有用,更容易在循环中进行迭代等),但是我使用魔术数字,位操作等优化了该操作及其逆运算,并且代码很长,所以我省略了。

namespace HilbertExtensions
{
    /// <summary>
    /// Convert between Hilbert index and N-dimensional points.
    /// 
    /// The Hilbert index is expressed as an array of transposed bits. 
    /// 
    /// Example: 5 bits for each of n=3 coordinates.
    /// 15-bit Hilbert integer = A B C D E F G H I J K L M N O is stored
    /// as its Transpose                        ^
    /// X[0] = A D G J M                    X[2]|  7
    /// X[1] = B E H K N        <------->       | /X[1]
    /// X[2] = C F I L O                   axes |/
    ///        high low                         0------> X[0]
    ///        
    /// NOTE: This algorithm is derived from work done by John Skilling and published in "Programming the Hilbert curve".
    /// (c) 2004 American Institute of Physics.
    /// 
    /// </summary>
    public static class HilbertCurveTransform
    {
        /// <summary>
        /// Convert the Hilbert index into an N-dimensional point expressed as a vector of uints.
        ///
        /// Note: In Skilling's paper, this function is named TransposetoAxes.
        /// </summary>
        /// <param name="transposedIndex">The Hilbert index stored in transposed form.</param>
        /// <param name="bits">Number of bits per coordinate.</param>
        /// <returns>Coordinate vector.</returns>
        public static uint[] HilbertAxes(this uint[] transposedIndex, int bits)
        {
            var X = (uint[])transposedIndex.Clone();
            int n = X.Length; // n: Number of dimensions
            uint N = 2U << (bits - 1), P, Q, t;
            int i;
            // Gray decode by H ^ (H/2)
            t = X[n - 1] >> 1;
            // Corrected error in Skilling's paper on the following line. The appendix had i >= 0 leading to negative array index.
            for (i = n - 1; i > 0; i--) 
                X[i] ^= X[i - 1];
            X[0] ^= t;
            // Undo excess work
            for (Q = 2; Q != N; Q <<= 1)
            {
                P = Q - 1;
                for (i = n - 1; i >= 0; i--)
                    if ((X[i] & Q) != 0U)
                        X[0] ^= P; // invert
                    else
                    {
                        t = (X[0] ^ X[i]) & P;
                        X[0] ^= t;
                        X[i] ^= t;
                    }
            } // exchange
            return X;
        }

        /// <summary>
        /// Given the axes (coordinates) of a point in N-Dimensional space, find the distance to that point along the Hilbert curve.
        /// That distance will be transposed; broken into pieces and distributed into an array.
        /// 
        /// The number of dimensions is the length of the hilbertAxes array.
        ///
        /// Note: In Skilling's paper, this function is called AxestoTranspose.
        /// </summary>
        /// <param name="hilbertAxes">Point in N-space.</param>
        /// <param name="bits">Depth of the Hilbert curve. If bits is one, this is the top-level Hilbert curve.</param>
        /// <returns>The Hilbert distance (or index) as a transposed Hilbert index.</returns>
        public static uint[] HilbertIndexTransposed(this uint[] hilbertAxes, int bits)
        {
            var X = (uint[])hilbertAxes.Clone();
            var n = hilbertAxes.Length; // n: Number of dimensions
            uint M = 1U << (bits - 1), P, Q, t;
            int i;
            // Inverse undo
            for (Q = M; Q > 1; Q >>= 1)
            {
                P = Q - 1;
                for (i = 0; i < n; i++)
                    if ((X[i] & Q) != 0)
                        X[0] ^= P; // invert
                    else
                    {
                        t = (X[0] ^ X[i]) & P;
                        X[0] ^= t;
                        X[i] ^= t;
                    }
            } // exchange
            // Gray encode
            for (i = 1; i < n; i++)
                X[i] ^= X[i - 1];
            t = 0;
            for (Q = M; Q > 1; Q >>= 1)
                if ((X[n - 1] & Q)!=0)
                    t ^= Q - 1;
            for (i = 0; i < n; i++)
                X[i] ^= t;

            return X;
        }

    }
}

我已经在C#中将工作代码发布到了github。

参见https://github.com/paulchernoch/HilbertTransformation

更新:我刚刚(2019年秋季)在crates.io上发布了一个名为“
hilbert”的Rust板条箱。它还使用了斯基林算法。参见https://crates.io/crates/hilbert

2020-07-28