double trap(double func(double), double b, double a, double N) {
double j;
double s;
double h = (b-a)/(N-1.0); //Width of trapezia
double func1 = func(a);
double func2;
for (s=0,j=a;j<b;j+=h){
func2 = func(j+h);
s = s + 0.5*(func1+func2)*h;
func1 = func2;
}
return s;
}
Выше приведен мой код C ++ для одномерного численного интегрирования (с использованием расширенного правила трапеции) func()
между пределами с использованием трапеции .
Я на самом деле делаю 3D-интеграцию, где этот код называется рекурсивно. Я работаю с давая мне достойные результаты.
Кроме дальнейшего сокращения , кто-нибудь может предложить, как оптимизировать приведенный выше код, чтобы он работал быстрее? Или даже можете предложить более быстрый способ интеграции?
c++
performance
user2970116
источник
источник
trapezoidal_integration
вместоtrap
,sum
илиrunning_total
вместо того , чтобыs
(а также использовать+=
вместоs = s +
),trapezoid_width
илиdx
вместоh
(или нет, в зависимости от предпочитаемого обозначения трапеций), а также измененийfunc1
иfunc2
отразить тот факт , что они являются ценностями, а не функции. Напримерfunc1
->previous_value
иfunc2
->current_value
или что-то в этом роде.Ответы:
Математически ваше выражение эквивалентно:
Таким образом, вы могли бы реализовать это. Как уже было сказано, время, вероятно, зависит от оценки функции, поэтому, чтобы получить ту же точность, вы можете использовать лучший метод интеграции, который требует меньше оценок функций.
Квадратура Гаусса в наши дни немного больше, чем игрушка; полезно только если вам требуется очень мало оценок. Если вы хотите что-то простое в реализации, вы можете использовать правило Симпсона, но я бы не пошел дальше, чем заказать без веской причины.1 /N3
Если кривизна функции сильно меняется, вы можете использовать процедуру адаптивного шага, которая выберет больший шаг, когда функция плоская, и меньший, более точный, когда кривизна выше.
источник
Скорее всего, оценка функций является наиболее трудоемкой частью этого вычисления. Если это так, то вам следует сосредоточиться на улучшении скорости func (), а не пытаться ускорить саму процедуру интеграции.
В зависимости от свойств func (), также возможно, что вы могли бы получить более точную оценку интеграла с меньшим количеством оценок функций, используя более сложную формулу интеграции.
источник
Возможный? Да. Полезно? Нет. Оптимизации, которые я собираюсь перечислить здесь, вряд ли дадут более чем незначительную долю процента разницы во времени выполнения. Хороший компилятор может уже сделать это для вас.
Во всяком случае, глядя на ваш внутренний цикл:
На каждой итерации цикла вы выполняете три математические операции, которые можно вывести за пределы: сложение
j + h
, умножение на0.5
и умножение наh
. Первое, что вы можете исправить, запустив переменную итератора вa + h
, а другие, вычленив умножения:Хотя я хотел бы отметить, что при этом из-за ошибки округления с плавающей запятой можно пропустить последнюю итерацию цикла. (Это было также проблемой в вашей первоначальной реализации.) Чтобы обойти это, используйте
unsigned int
илиsize_t
счетчик:Как говорит ответ Брайана, ваше время лучше потратить на оптимизацию оценки функции
func
. Если точность этого метода достаточна, я сомневаюсь, что вы найдете что-то быстрее для того жеN
. (Хотя вы могли бы запустить некоторые тесты, чтобы увидеть, например, позволяет ли Runge-Kutta снизить уровеньN
шума, чтобы полная интеграция занимала меньше времени, не жертвуя точностью.)источник
Есть несколько изменений, которые я бы порекомендовал улучшить для вычисления:
std::fma()
, который выполняет слитое умножение-сложение .h
, которое может накапливать ошибки округления.Кроме того, я бы внес несколько изменений для ясности:
a
иb
в сигнатуре функции.N
→n
,h
→dx
,j
→x2
,s
→accumulator
.n
наint
.источник
Если ваша функция является полиномом, возможно взвешенным какой-либо функцией (например, гауссианом), вы можете выполнить точное интегрирование в 3d непосредственно с кубатурной формулой (например, http://people.sc.fsu.edu/~jburkardt/c_src/ stroud / stroud.html ) или с разреженной сеткой (например, http://tasmanian.ornl.gov/ ). Эти методы просто задают набор точек и весов, на которые умножается значение функции, поэтому они очень быстрые. Если ваша функция достаточно гладкая, чтобы ее можно было аппроксимировать полиномами, тогда эти методы могут дать очень хороший ответ. Формулы специализируются на типе функции, которую вы интегрируете, поэтому может потребоваться некоторое копание, чтобы найти правильную.
источник
Когда вы пытаетесь вычислить интеграл численно, вы пытаетесь получить желаемую точность с наименьшим возможным усилием или, альтернативно, пытаетесь получить максимально возможную точность с фиксированным усилием. Вы, кажется, спрашиваете, как заставить код для одного конкретного алгоритма работать максимально быстро.
Это может дать вам небольшой выигрыш, но это будет мало. Существуют гораздо более эффективные методы численного интегрирования. Google для "правила Симпсона", "Рунге-Кутта" и "Фельберг". Все они работают примерно одинаково, оценивая некоторые значения функции и умно добавляя кратные значения этих значений, создавая гораздо меньшие ошибки с тем же числом оценок функции или ту же ошибку с гораздо меньшим числом оценок.
источник
Есть много способов сделать интеграцию, из которых правило трапеции является самым простым.
Если вы вообще что-то знаете о фактической функции, которую вы интегрируете, вы можете добиться большего успеха, если будете ее использовать. Идея состоит в том, чтобы минимизировать количество точек сетки в пределах допустимых уровней ошибок.
Например, трапецеидальная линейная подгонка к последовательным точкам. Вы можете сделать квадратичное приближение, которое, если кривая будет гладкой, будет соответствовать лучше, что может позволить вам использовать более грубую сетку.
Орбитальное моделирование иногда выполняется с использованием коник, потому что орбиты очень похожи на конические сечения.
В моей работе мы интегрируем формы, которые приближаются к колоколообразным кривым, поэтому эффективно моделировать их так ( адаптивная квадратура Гаусса считается «золотым стандартом» в этой работе).
источник
Итак, как было указано в других ответах, это сильно зависит от того, насколько дорогая ваша функция. Оптимизация вашего кода trapz того стоит, только если это действительно ваше узкое место. Если это не совсем очевидно, вы должны проверить это путем профилирования вашего кода (такие инструменты, как Intel V-tune, Valgrind или Visual Studio могут это сделать).
Однако я бы предложил совершенно другой подход: интеграция Монте-Карло . Здесь вы просто аппроксимируете интеграл путем выборки вашей функции в случайных точках, добавляя результаты. Смотрите этот PDF в дополнение к странице вики для деталей.
Это работает очень хорошо для данных больших размеров, как правило, намного лучше, чем квадратурные методы, используемые в 1-й интеграции.
Простой случай очень прост в реализации (см. Pdf), но будьте осторожны, так как стандартная случайная функция в c ++ 98 довольно плоха как по производительности, так и по качеству. В C ++ 11 вы можете использовать Mersenne Twister в.
Если ваша функция имеет большие различия в одних областях и меньше в других, рассмотрите возможность использования стратифицированной выборки. Я бы порекомендовал использовать научную библиотеку GNU , а не писать свою.
источник
«рекурсивно» является ключом. Вы либо просматриваете большой набор данных и рассматриваете множество данных более одного раза, либо фактически генерируете свой набор данных из (кусочно?) Функций.
Рекурсивно оцениваемые интеграции будут смехотворно дорогими и смехотворно неточными, поскольку полномочия в рекурсии увеличиваются.
Создайте модель для интерполяции вашего набора данных и сделайте кусочную символическую интеграцию. Так как большая часть данных затем сворачивается в коэффициенты базовых функций, сложность для более глубокой рекурсии возрастает полиномиально (и, как правило, довольно низко), а не экспоненциально. И вы получите «точные» результаты (вам все еще нужно найти хорошие схемы оценки, чтобы получить разумную числовую производительность, но все же должно быть достаточно выполнимо, чтобы добиться лучшего, чем трапецеидальная интеграция).
Если вы посмотрите на оценки ошибок для трапециевидных правил, вы обнаружите, что они связаны с некоторой производной участвующих функций, и если интегрирование / определение выполняется рекурсивно, функции не будут иметь производных с хорошим поведением ,
Если ваш единственный инструмент - молоток, каждая проблема выглядит как гвоздь. Хотя вы едва затрагиваете проблему в своем описании, у меня есть подозрение, что рекурсивное применение правила трапеции является плохим совпадением: вы получаете взрыв как неточностей, так и вычислительных требований.
источник
исходный код оценивает функцию в каждой из N точек, затем складывает значения и умножает сумму на размер шага. Единственная хитрость в том, что значения в начале и конце добавляются с весом1 / 2 , в то время как все точки внутри добавлены с полным весом. на самом деле, они также добавляются с весом1 / 2 но дважды. вместо того, чтобы добавлять их дважды, добавляйте их только один раз с полным весом. вычленить умножение на размер шага за пределами цикла. это все, что можно сделать, чтобы ускорить это, правда.
источник