Отображение N-мерного значения в точку на кривой Гильберта - PullRequest
36 голосов
/ 31 января 2009

У меня огромный набор N-мерных точек (десятки миллионов; N близко к 100).

Мне нужно отобразить эти точки в одном измерении, сохраняя пространственную локализацию. Я хочу использовать Кривая заполнения пространства Гильберта , чтобы сделать это.

Для каждой точки я хочу выбрать ближайшую точку на кривой. Значение Гильберта этой точки (длина кривой от начала кривой до выбранной точки) - это одно значение измерения, которое я ищу.

Вычисление не должно быть мгновенным, но я ожидаю, что оно будет не более нескольких часов на приличном современном домашнем ПК.

Есть предложения по внедрению? Есть ли библиотеки, которые бы мне помогли? (Язык не имеет большого значения.)

Ответы [ 6 ]

45 голосов
/ 30 апреля 2012

Я наконец сломался и выложил немного денег. В AIP (Американский институт физики) есть хорошая короткая статья с исходным кодом на языке C. «Программирование кривой Гильберта» Джона Скиллинга (из AIP Conf. Proc. 707, 381 (2004)), а также приложение с кодом для отображения в обоих направлениях. Он работает для любого числа измерений> 1, не является рекурсивным, не использует таблицы поиска с изменением состояния, которые поглощают огромные объемы памяти, и в основном использует битовые операции. Таким образом, он достаточно быстрый и имеет хороший объем памяти.

Если вы решили приобрести статью, я обнаружил ошибку в исходном коде.

Следующая строка кода (находится в функции TransposetoAxes) имеет ошибку:

для (i = n-1; i> = 0; i--) X [i] ^ = X [i-1];

Корректировка заключается в изменении значения больше или равно (> =) на значение больше (>). Без этого исправления доступ к массиву X осуществляется с использованием отрицательного индекса, когда переменная «i» становится равной нулю, что приводит к сбою программы.

Я рекомендую прочитать статью (которая состоит из семи страниц, включая код), поскольку она объясняет, как работает алгоритм, что далеко не очевидно.

Я перевел его код на C # для собственного использования. Код следует. Skilling выполняет преобразование на месте, перезаписывая вектор, который вы передаете. Я решил сделать клон входного вектора и вернуть новую копию. Также я реализовал методы как методы расширения.

Код Скиллинга представляет индекс Гильберта в виде транспонирования, сохраненного в виде массива. Я считаю более удобным чередовать биты и формировать один BigInteger (более полезный в словарях, легче перебирать циклы и т. Д.), Но я оптимизировал эту операцию и ее инверсию с помощью магических чисел, битовых операций и т. код длинный, поэтому я его опустил.

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

8 голосов
/ 15 августа 2009

Алгоритм отображения из n-> 1 и 1-> n приведен здесь "Расчет отображений между одномерными и n-мерными значениями с использованием кривой заполнения пространства Гильберта" Дж. К. Лоудер

Если вы используете Google для «SFC module and Kademlia overlay», вы найдете группу, которая утверждает, что использует ее как часть своей системы. Если вы просматриваете источник, вы можете извлечь соответствующую функцию.

4 голосов
/ 31 января 2009

Мне не ясно, как это будет делать то, что вы хотите. Рассмотрим этот случай 3D-случая:

001 ------ 101
 |\         |\
 | \        | \
 |  011 ------ 111
 |   |      |   |
 |   |      |   |
000 -|---- 100  |
  \  |       \  |
   \ |        \ |
    010 ------ 110

, который может быть "скомпонован" следующим путем:

001 -----> 101
  \          \
   \          \
    011        111
     ^          |
     |          |
000  |     100  |
  \  |       \  |
   \ |        \ V
    010        110

в 1D заказ:

000 -> 010 -> 011 -> 001 -> 101 -> 111 -> 110 -> 100

Вот неприятный момент. Рассмотрим список пар и одномерные расстояния ниже:

000 : 100 -> 7
010 : 110 -> 5
011 : 111 -> 3
001 : 101 -> 1

Во всех случаях левые и правые значения находятся на одном и том же трехмерном расстоянии друг от друга (+/- 1 в первой позиции), что, по-видимому, подразумевает сходную «пространственную локализацию». Но линеаризация любого выбора размерного порядка (y, затем z, затем z, в приведенном выше примере) нарушает эту локальность.

Еще один способ сказать, что если взять начальную точку и упорядочить оставшиеся точки по их расстоянию от этой начальной точки, то результаты будут существенно отличаться. Принимая 000 в качестве начала, например:

1D ordering : distance    3D ordering : distance
----------------------    ----------------------
        010 : 1           001,010,100 : 1
                          011,101,110 : sqrt(2)
                              111     : sqrt(3)
        011 : 2
        001 : 3
        101 : 4
        111 : 5
        110 : 6
        100 : 7

Этот эффект растет экспоненциально с увеличением количества измерений (при условии, что каждое измерение имеет одинаковый «размер»).

2 голосов
/ 13 июля 2016

Я потратил немного времени на перевод кода Пола Черноха на Java и его очистку. Возможно, в моем коде есть ошибка, особенно потому, что у меня нет доступа к статье, из которой она изначально. Тем не менее, он проходит, какие юнит-тесты мне удалось написать. Это ниже.

Обратите внимание, что я оценил кривые Z-порядка и Гильберта для пространственной индексации на больших наборах данных. Я должен сказать, что Z-Order обеспечивает гораздо лучшее качество. Но не стесняйтесь попробовать сами.

    /**
     * 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.
     * @param transposedIndex The Hilbert index stored in transposed form.
     * @param bits Number of bits per coordinate.
     * @return Point in N-space.
     */
    static long[] HilbertAxes(final long[] transposedIndex, final int bits) {
        final long[] result = transposedIndex.clone();
        final int dims = result.length;
        grayDecode(result, dims);
        undoExcessWork(result, dims, bits);
        return result;
    }

    static void grayDecode(final long[] result, final int dims) {
        final long swap = result[dims - 1] >>> 1;
        // Corrected error in Skilling's paper on the following line. The appendix had i >= 0 leading to negative array index.
        for (int i = dims - 1; i > 0; i--)
            result[i] ^= result[i - 1];
        result[0] ^= swap;
    }

    static void undoExcessWork(final long[] result, final int dims, final int bits) {
        for (long bit = 2, n = 1; n != bits; bit <<= 1, ++n) {
            final long mask = bit - 1;
            for (int i = dims - 1; i >= 0; i--)
                if ((result[i] & bit) != 0)
                    result[0] ^= mask; // invert
                else
                    swapBits(result, mask, i);
        }
    }

    /**
     * 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.
     * @param hilbertAxes Point in N-space.
     * @param bits Depth of the Hilbert curve. If bits is one, this is the top-level Hilbert curve.
     * @return The Hilbert distance (or index) as a transposed Hilbert index.
     */
    static long[] HilbertIndexTransposed(final long[] hilbertAxes, final int bits) {
        final long[] result = hilbertAxes.clone();
        final int dims = hilbertAxes.length;
        final long maxBit = 1L << (bits - 1);
        inverseUndo(result, dims, maxBit);
        grayEncode(result, dims, maxBit);
        return result;
    }

    static void inverseUndo(final long[] result, final int dims, final long maxBit) {
        for (long bit = maxBit; bit != 0; bit >>>= 1) {
            final long mask = bit - 1;
            for (int i = 0; i < dims; i++)
                if ((result[i] & bit) != 0)
                    result[0] ^= mask; // invert
                else
                    swapBits(result, mask, i);
        } // exchange
    }

    static void grayEncode(final long[] result, final int dims, final long maxBit) {
        for (int i = 1; i < dims; i++)
            result[i] ^= result[i - 1];
        long mask = 0;
        for (long bit = maxBit; bit != 0; bit >>>= 1)
            if ((result[dims - 1] & bit) != 0)
                mask ^= bit - 1;
        for (int i = 0; i < dims; i++)
            result[i] ^= mask;
    }

    static void swapBits(final long[] array, final long mask, final int index) {
        final long swap = (array[0] ^ array[index]) & mask;
        array[0] ^= swap;
        array[index] ^= swap;
    }
2 голосов
/ 31 января 2009

Другой возможностью было бы построить kd-дерево на ваших данных, а затем выполнить обход дерева по порядку, чтобы получить порядок. Для построения дерева kd требуется только хороший алгоритм поиска медианы, которых много.

1 голос
/ 31 января 2009

Я не понимаю, как можно использовать кривую Гильберта в одном измерении.

Если вас интересует отображение точек на более низкое измерение при сохранении расстояний (с минимальной ошибкой), вы можете обратиться к алгоритмам «Многомерного масштабирования».

Имитация отжига - один из подходов.

Редактировать: Спасибо за комментарий. Теперь я понимаю, что вы подразумевали под подходом к кривой Гильберта. Тем не менее, это сложная проблема, и, учитывая N = 100 и 10 миллионов точек данных, я не думаю, что какой-либо подход хорошо сохранит локальность и будет работать в разумные сроки. Я не думаю, что здесь будут работать kd-деревья.

Если поиск общего порядка не важен для вас, то вы можете изучить хэширование на основе локальных условий и другие приблизительные схемы ближайших соседей. Иерархическое многомерное масштабирование с набором точек для уменьшения входного размера может дать вам хороший порядок, но опять же сомнительно в таком высоком измерении.

...