Алгоритм быстрой диффузии интенсивности

Во-первых, извините за название, я не знаю, описывает ли оно то, чего я пытаюсь достичь, но это лучшее, что у меня есть.

В основном у меня есть массив, описывающий интенсивность в 2D-пространстве. Затем я хочу распределить эту интенсивность среди соседей по заданному набору итераций, т.е. Допустим, у меня есть следующий массив:

intensity = [ 0, 0, 0, 0, 0, 
              0, 0, 0, 0, 0, 
              0, 0, 0, 0, 0, 
              0, 0, 100, 0, 0, 
              0, 0, 0, 0, 0, 
              0, 0, 0, 0, 0,
              0, 0, 0, 0, 0 ]

Затем я делаю один проход по моему алгоритму распределенияIntensity (распределяя 50% интенсивности соседям). Я бы тогда:

            [ 0,  0,   0,  0, 0, 
              0,  0,   0,  0, 0, 
              0, 50,  50, 50, 0, 
              0, 50, 100, 50, 0, 
              0, 50,  50, 50, 0, 
              0,  0,   0,  0, 0,
              0,  0,   0,  0, 0 ]

Если я сделаю 2 прохода по исходному массиву, мой результирующий массив будет:

          [ 0,   0,   0,   0, 0, 
           25,  50,  75,  50, 25, 
           50, 150, 200, 150, 50, 
          75, 200, 300, 200, 75, 
           50, 150, 200, 150, 50, 
           25,  50,  75,  50, 25,
            0,   0,   0,   0, 0 ]

Мой текущий код:

this.distributeIntensities = function(passes, shareRatio) {     
    for (var i = 0; i < passes; i++) { this.distributeIntensity(shareRatio); }
}

this.distributeIntensity = function(shareRatio) {       
    var tmp = hm.intensity.slice(0); // copy array
    for (var i = 0; i < tmp.length; i++) {          
        if (hm.intensity[i] <= 0) { continue; }
        var current = hm.intensity[i];
        var shareAmount = current * shareRatio;                     
        this.shareIntensityWithNeighbours(tmp, shareAmount, i);                                                             
    }       
    hm.intensity = tmp;
}

this.shareIntensityWithNeighbours = function(arr, heat, i) {                    
    // This should be var x = Math.floor(...) however 
    // this is slower and without gives satisfactory results
    var x = i % hm.columnCount; 
    var y = i / hm.columnCount; 

    if (x > 0) {
        if (y > 0) arr[i - hm.columnCount - 1] += heat;
        arr[i - 1] += heat;
        if (y < (hm.rowCount - 1)) arr[i + hm.columnCount - 1] += heat;
    }               

    if (y > 0) arr[i - hm.columnCount] += heat;     
    if (y < (hm.rowCount - 1)) arr[i + hm.columnCount] += heat;

    if (x < (hm.columnCount - 1)) {
        if (y > 0) arr[i - hm.columnCount + 1] += heat;
        arr[i + 1] += heat;
        if (y < (hm.rowCount - 1)) arr[i + hm.columnCount + 1] += heat;
    }               
}

Теперь это работает, но очень медленно (я работаю с огромным массивом и 8 проходами). Я знаю, что есть более быстрый/лучший/чистый способ сделать это, но это выходит за рамки моих возможностей, поэтому я разместил его там в надежде, что кто-то может указать мне правильное направление (Примечание: я не говорю бегло по математике, на самом деле Я довольно математически неграмотен).

заранее спасибо

Гвидо


person gatapia    schedule 28.12.2009    source источник
comment
сейчас хорошее время для использования параллелизма, но со стандартным Javascript это не вариант...   -  person Willi Ballenthin    schedule 28.12.2009


Ответы (4)


Convolution – это распространенный прием обработки изображений (теперь у вас есть ключевое слово для поиска!).

[[ 0.5, 0.5, 0.5 ],
 [ 0.5, 1.0, 0.5 ],
 [ 0.5, 0.5, 0.5 ]]

Похоже, вы реализовали свертки с этим ядром вручную.

Чтобы ускорить процесс: поскольку свертка является ассоциативной, вы можете предварительно вычислить один фильтр вместо многократного применения оригинала. Например, если количество проходов = 2,

once = [[ 0.5, 0.5, 0.5 ], [ 0.5, 1.0, 0.5 ], [ 0.5, 0.5, 0.5 ]]
twice = once ⊗ once =
    [[ 0.25, 0.50, 0.75, 0.50, 0.25 ],
     [ 0.50, 1.50, 2.00, 1.50, 0.50 ],
     [ 0.75, 2.00, 3.00, 2.00, 0.75 ], 
     [ 0.50, 1.50, 2.00, 1.50, 0.50 ], 
     [ 0.25, 0.50, 0.75, 0.50, 0.25 ]]

distribute(hm) = hm ⊗ once ⊗ once
               = hm ⊗ twice

Если вы будете делать это неоднократно, возможно, стоит изучить ФурьеПреобразование; есть теорема о том, что

FT(X ⊗ Y) = FT(X) ⋅ FT(Y)

или после применения обратного преобразования Фурье,

X ⊗ Y = IFT(FT(X) ⋅ FT(Y))

Другими словами, сложные свертки можно заменить простыми умножениями.

person ephemient    schedule 28.12.2009

Что ж, думаю, в вашем цикле for для функции distributeIntensity вы могли бы внести небольшие изменения:

  • Сохраните массив length.
  • Инвертируйте оператор if, чтобы избежать оператора continue.


this.distributeIntensity = function(shareRatio) {       
    var tmp = hm.intensity.slice(0); // copy array
    for (var i = 0, n = tmp.length; i < n; i++) {                      
        if (hm.intensity[i] > 0) {
          var current = hm.intensity[i];
          var shareAmount = current * shareRatio;
          this.shareIntensityWithNeighbours(tmp, shareAmount, i);
        }
    }
    hm.intensity = tmp;
};

Если порядок итерации не важен для вашего алгоритма, вы можете выполнить обратную итерацию своего массива, который равен известен как более быстрый:

this.distributeIntensity = function(shareRatio) {       
    var tmp = hm.intensity.slice(0); // copy array
    var i = tmp.length;
    while (i--) {
        if (hm.intensity[i] > 0) {
          var current = hm.intensity[i];
          var shareAmount = current * shareRatio;
          this.shareIntensityWithNeighbours(tmp, shareAmount, i);
        }
    }
    hm.intensity = tmp;
};

Вы также можете рассмотреть возможность интеграции функции shareIntensityWithNeighbours в цикл, вызов функции может быть несколько дорогим.

Однако я настоятельно рекомендую вам использовать профилировщик (встроенный в Firebug действительно хорош), чтобы действительно измерить производительность и быстро найти узкие места.

person Christian C. Salvadó    schedule 28.12.2009

Следует отметить, что в относительно редком случае (таком как первая установка, где имеется только одна запись с интенсивностью > 0) вам нужно заниматься только некоторыми записями.

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

Редактировать

Я пошел нарыть пример такого подхода. Моделирование игры Конвея «Жизнь / Клеточные автоматы» представляет аналогичные трудности в оптимизации. На каждом этапе состояние каждой ячейки должно быть рассчитано на основе окружающих ячеек.

Чрезвычайно мощной реализацией является Hashlife. По сути, он использует умный хэш для отслеживания того, какие ячейки действительно необходимо обновить, и для запоминания (кэширования шаблонов) вычислений. Вы можете найти вдохновение в его стратегии.

person Willi Ballenthin    schedule 28.12.2009

Благодаря ответу ephemient выше я смог получить информацию, необходимую для исправления этого алгоритма. В итоге я получил алгоритм, который на 50-55% быстрее. Также спасибо CMS за лайфхаки с производительностью javascript (на самом деле они имеют большое значение).

Для полноты я включаю код:

Примечание. Код оптимизирован, поэтому он не самый аккуратный (развернутые продолжения, обратные итерации массивов и т. д.), много локального кэширования переменных и т. д.

    this.distributeHeatMapIntensities = function(passes, shareRatio) {              
        var tmp = hm.intensity.slice(0);

        var distances = {};
        for (var i = -passes; i <= passes; i++) { distances[i] = Math.abs(i); }
        var shares = [];
        for (var i = 0; i <= passes; i++) { shares.push(i === 0 ? 0 : Math.pow(shareRatio, i)); }
        var len = tmp.length - 1;

        for(var i = len; i >= 0; i--) {     
            var intens = hm.intensity[i];
            if (intens > 0) {                       
                var x = Math.floor(i % hm.columnCount);
                var y = Math.floor(i / hm.columnCount);                     

                for (var tx = -passes; tx <= passes; tx++) { 
                    var nx = x + tx;
                    if (nx >= 0 && nx < hm.columnCount) {               
                        var dx = distances[tx];
                        for (var ty = -passes; ty <= passes; ty++) { 
                            var ny = y + ty;
                            if (ny >= 0 && ny < hm.rowCount) {                      
                                var dy = distances[ty];
                                var distance = dx >= dy ? dx : dy;
                                var share = shares[distance] * intens;
                                var i2 = (ny * hm.columnCount) + nx;
                                tmp[i2] += share;
                            }
                        }
                    }
                }
            }
        }   
        hm.intensity = tmp; 
    }

Спасибо всем

Гвидо

person gatapia    schedule 28.12.2009