Рекурсия Matlab: неэффективный код или сложная рекурсия?

Я изо всех сил пытаюсь получить решение этой проблемы рекурсии в разумные сроки выполнения.

Здесь я показываю рекурсивную функцию, которая в основном вычисляет коэффициенты многочлена.

function [ coeff ] = get_coeff( n, k, tau, x )

if(n == 0) % 1st exit condition
    coeff = 0;
else
    if(k == 0) % 2nd exit condition
        coeff = max(0, n*tau-x)^n;
    else % Else recursion
        total = 0;
        for l = k-1:n-2
            total = total + nchoosek(l, k-1)*tau^(l-k+1)*get_coeff(n-1, l, tau, x);
        end
        coeff = (n/k) * total;            
    end
end

end

 % This symbolic summation solution gives numerical errors, probably due to rounding
 % effects.
 %           syms l;
 %           f = nchoosek(l, k-1)*tau^(l-k+1)*get_coeff(n-1, l, tau, x);
 %           coeff = (n/k) * symsum(f, l, k-1, n-2);

И это основной сценарий, в котором я использую рекурсивную функцию:

Tau = 1;
ns = [3];
%delays = 0:0.25:8;
delays = [0];
F_x = zeros(1, size(delays, 2));
rho = 0.95;
tic
for ns_index = 1: size(ns, 2)

  T = Tau*(ns(ns_index)+1)/rho;

  % Iterate delays (x)
  for delay_index = 1:size(delays, 2)
     total = 0;

     % Iterate polynomial.
     for l = 0:ns(ns_index)-1
        total = total + get_coeff(ns(ns_index), l, Tau, delays(delay_index))*(T - ns(ns_index)*Tau + delays(delay_index))^l;
     end

    F_x(1, delay_index) = T^(-ns(ns_index))*total;

  end

end
toc

Я упростил векторы "ns" и "delays", чтобы они содержали одно значение, чтобы их было легче отслеживать. Таким образом, для фиксированного значения «ns» мне нужно вычислить все коэффициенты многочлена, используя рекурсивную функцию, и вычислить его окончательное значение при «задержках». Увеличивая количество точек в «задержках», я вижу кривую для фиксированного «ns». Мой вопрос: для любых «нс» от 1 до 10 вычисление выполняется очень быстро, порядка 0,069356 секунды (даже для всего вектора «задержек»). И наоборот, для ns = [15] или [20] время вычислений увеличивается НАМНОГО (мне даже не удалось увидеть результат). Я не увлекаюсь оценкой вычислительной сложности, поэтому я не знаю, есть ли проблема в моем коде (может быть, функция nchoosek? Или циклы for?) или, может быть, так и должно быть, имея в виду эту рекурсию. проблема.

EDIT: я вижу, что это действительно факторный рост количества вычислений, как заявил Адриан. Как вы думаете, может ли какая-либо аппроксимация nchoosek быть полезной для решения этой проблемы? Что-то вроде: en.wikipedia.org/wiki/Stirling%27s_closed

Последняя формула в этой статье — это то, что я пытаюсь реализовать (обратите внимание, что я изменил дельту для тау):

введите здесь описание изображения


person Gabriel    schedule 28.02.2018    source источник
comment
Можете ли вы дать формулу коэффициентов полинома? Возможно, вычисление может быть выполнено каким-то другим способом, кроме рекурсивного   -  person Luis Mendo    schedule 28.02.2018
comment
Уточнение: 15! = 1.3077e+12, так что если это количество вычислений, конечно, это займет много времени, даже если время расчета составляет около микросекунды для одной итерации. Напротив: 12! = 4.8e8, что в 10 000 раз меньше, поэтому я не очень удивлен, что для ns>15 требуется очень много времени.   -  person Adriaan    schedule 28.02.2018
comment
К сожалению, формула полиномиальных коэффициентов такова. Так что это рекурсивно по определению.   -  person Gabriel    schedule 28.02.2018
comment
Как вы думаете, будет ли приближение Стрилинга полезным для сокращения времени выполнения? en.wikipedia.org/wiki/Stirling%27s_closed   -  person Gabriel    schedule 28.02.2018
comment
В качестве наброска решения: это рекурсивно, не нужно вычислять ns! раз. Вычислите первую точку, вторую из этой и т. д., сохраните, когда закончите, затем умножьте на правильный префактор (это деление перед суммированием), и это должно быть так. Я не уверен, почему ваш код имеет факторный рост итераций, но этого не должно быть.   -  person Adriaan    schedule 28.02.2018
comment
Разве это не то же самое, что вы обсуждали в ответе ниже? Я могу вычислить и сохранить get_coeff(1, 0, tau, x), чтобы мне не нужно было вычислять его позже, но в любом случае мне придется перебирать его всякий раз, когда я захочу вычислить (18) q_(n, л+1). Единственное отличие теперь в том, что я буду извлекать результат из матрицы, но я не уменьшаю количество итераций, верно? И, конечно же, код не имеет факторного роста в итерациях, потому что я вычислил ns=15 примерно за 1 час.   -  person Gabriel    schedule 28.02.2018
comment
Вы можете создать две матрицы размера [ns+1 x ns+1] и сохранить в них предварительно вычисленные результаты nchoosek и get_coeff.   -  person rahnema1    schedule 01.03.2018


Ответы (2)


Я запустил профиль в вашем коде и получил следующее:

Результат профилировщика

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


Изменить: я попытался предварительно рассчитать nchoosek как таковой:

for i = 0 : ns
    for j = 0 : ns
        if j < i
            nchoosek_(i+1,j+1) = nchoosek(i,j);
        else
            nchoosek_(i+1,j+1) = NaN;
        end
    end
end

И затем внутри функции:

total = total + nchoosek_(l+1, k-1+1)*tau^(l-k+1)*get_coeff(n-1, l, tau, x , nchoosek_);

Кажется, это работает, и я получаю хорошее улучшение с ns = 12:

Результат профилирования с предварительным расчетом

Но я все равно врезался в стену для ns = 15...

person Zep    schedule 28.02.2018
comment
Вызов nchoosek исчез во втором профиле. Поскольку вы ее рассчитываете, пусть и заранее, ее все же следует учитывать. Теперь он закрыт внутри temp>get_coeff? Сокращение времени на 75% — это здорово для меньших значений ns, но, как я прокомментировал вопрос, если я прав, проблема заключается в факторном росте объема вычислений, и в этом случае линейное сокращение времени не идет сократите это (75%-е сокращение годового времени расчета по-прежнему оставляет вам 3 месяца) - person Adriaan; 28.02.2018
comment
Я пропустил начальный цикл для вычисления nchoosek, потому что его время незначительно, и он не будет масштабироваться с помощью ns: 0,008 с против 1,041 с внутри функции. Я обновлю рисунок в ответе. Однако ваш расчет является правильным ответом: очевидно, что факториал быстро увеличивает количество вызовов функций! - person Zep; 28.02.2018

Итак, мне наконец удалось вычислить коэффициенты за разумное время. По сути, я воспользовался предложениями Адриана и rahnema1 и создал матрицу ns на ns для хранения всех коэффициентов, которые я вычисляю рекурсивным способом. Поэтому, когда определенный лист рекурсивного дерева повторяется, я могу обрезать дерево, извлекая значение из матрицы. Обратите внимание, что выигрыш основан не на предварительном вычислении значений (поскольку я вычисляю их на ходу), а на сокращении количества рекурсий. Вот вам несколько цифр:

  • ns = 10 ; delay = 0: количество вызовов старой рекурсивной функции было 23713. Теперь это решается за 175 вызовов.
  • Для ns = 10; delay = [0:0.25:8]: 782529 вызовов со старой функцией и 2,74 секунды времени выполнения, 495 с новой и 0,02, что примерно в 125 раз быстрее.
person Gabriel    schedule 02.03.2018