NIntegrate - почему в Mathematica 8 это намного медленнее в данном случае? - PullRequest
10 голосов
/ 10 декабря 2011

У меня есть код Mathematica, где я должен численно оценить тысячи интегралов, подобных этому

NIntegrate[
    (Pi*Cos[(Pi*(-2*x + y))/(1 + y)] + (1 + y)*(-Sin[(2*Pi*x)/(1 + y)] + 
    Sin[(Pi*(-2*x + y))/(1 + y)]))/(E^x*(1 + y)), 
    {x, 0, 100}, {y, 0, 100}
] //AbsoluteTiming

Подынтегральное выражение - это хорошая абсолютно интегрируемая функция без особенностей, которая экспоненциально убывает в одном направлении и равна 1 / y ^ 3 в другом направлении.

Команда NIntegrate работала нормально в Mathematica 7, но в новейшей версии 8.0.4 она замедляется на два порядка. Я предполагаю, что в новой версии он пытается лучше контролировать ошибку, но за счет этого огромного увеличения времени. Могу ли я использовать некоторые настройки, чтобы вычисления выполнялись с той же скоростью, что и в Mathematica 7?

Ответы [ 3 ]

16 голосов
/ 10 декабря 2011

ruebenko ответ и комментарии user1091201 и Leonid вместе объединяются, чтобы дать правильные ответы.

Ответ Edit 1 ruebenko является правильным первым ответом для общих ситуаций, подобных этой, то есть добавьте параметр Method -> {"SymbolicPreprocessing", "OscillatorySelection" -> False}:

expr = (Pi*
      Cos[(Pi*(-2*x + y))/(1 + y)] + (1 + y)*(-Sin[(2*Pi*x)/(1 + y)] +
         Sin[(Pi*(-2*x + y))/(1 + y)]))/(E^x*(1 + y));

NIntegrate[expr, {x, 0, 100}, {y, 0, 100}, 
  Method -> {"SymbolicPreprocessing", 
    "OscillatorySelection" -> False}] // AbsoluteTiming
Комментарий

And user1091201 о том, что Method -> "GaussKronrodRule" близок к самому быстрому решению для этой конкретной проблемы .

Я опишу, что происходит в NIntegrate, в этом конкретном примере и покажу несколько советов по работе с внешне похожими ситуациями в целом.

Выбор метода

В этом примере NIntegrate исследует expr, приходит к выводу, что многомерный метод «LevinRule» является лучшим методом для этого подынтегрального выражения, и применяет его. Однако для этого конкретного примера «LevinRule» медленнее, чем «MultidimensionalRule» (хотя «LevinRule» получает более удовлетворительную оценку ошибки). «LevinRule» также медленнее, чем любое из нескольких одномерных правил типа Гаусса, повторяемых в двух измерениях, таких как «GaussKronrodRule», которое user1091201 найдено.

NIntegrate принимает решение после некоторого символического анализа подынтегрального выражения. Существует несколько типов символической предварительной обработки; настройка Method -> {"SymbolicPreprocessing", "OscillatorySelection" -> False} отключает один тип символьной предварительной обработки.

По сути, с включенным «OscillatorySelection» NIntegrate выбирает «LevinRule». При отключенном «OscillatorySelection» NIntegrate выбирает «MultidimensionalRule», что является более быстрым для этого интеграла, хотя мы можем не доверять результату на основе сообщения NIntegrate :: slwcon, которое указывает на необычно медленную конвергенцию.

Это та часть, в которой Mathematica 8 отличается от Mathematica 7: Mathematica 8 добавляет «LevinRule» и связанные с ним эвристики выбора метода в «OscillatorySelection».

Помимо того, что NIntegrate выбирает другой метод, отключение «OscillatorySelection» также экономит время, затрачиваемое на фактическую символьную обработку, что может быть значительным в некоторых случаях.

Установка Method -> "GaussKronrodRule" переопределяет и пропускает символьную обработку, связанную с выбором метода, и вместо этого использует правило двумерного декартового произведения Method -> {"CartesianRule", Method -> {"GaussKronrodRule", "GaussKronrodRule"}}. Это очень быстрый метод для этого интеграла.

Другая символьная обработка

Оба ruebenko Method -> {"SymbolicPreprocessing", "OscillatorySelection" -> False} и user1091201 Method -> "GaussKronrodRule" не отключают другие формы символьной обработки, и это, как правило, хорошо. См. эту часть расширенной документации NIntegrate для получения списка типов символической предварительной обработки, которые могут быть применены. В частности, «SymbolicPiecewiseSubdivision» очень полезен для интегратов, которые не являются аналитическими в нескольких точках из-за наличия кусочных функций.

Чтобы отключить все символьную обработку и получить только методы по умолчанию с параметрами по умолчанию, используйте Method -> {Automatic, "SymbolicProcessing" -> 0}. Для одномерных интегралов это в настоящее время составляет Method -> {"GlobalAdaptive", Method -> "GaussKronrodRule"} с определенными настройками по умолчанию для всех параметров этих методов (количество точек интерполяции в правиле, тип обработки сингулярностей для глобальной адаптивной стратегии и т. Д.). Для многомерных интегралов оно в настоящее время составляет Method -> {"GlobalAdaptive", Method -> "MultidimensionalRule"}, опять же с определенными значениями параметров по умолчанию. Для многомерных интегралов будет использоваться метод Монте-Карло.

Я не рекомендую переходить прямо к Method -> {Automatic, "SymbolicProcessing" -> 0} в качестве первого шага оптимизации для NIntegrate, но в некоторых случаях это может быть полезно.

Самый быстрый метод

Существует всего лишь всегда некоторый способ ускорить числовую интеграцию хотя бы немного, иногда много, поскольку существует так много параметров, которые выбираются эвристически, что вы можете извлечь выгоду из настройки. (Посмотрите на различные опции и параметры, которые есть у метода "LevinRule" или "GlobalAdaptive" , включая все их под-методы и т. Д.)

Тем не менее, вот самый быстрый метод, который я нашел для этого конкретного интеграла:

NIntegrate[(Pi*
      Cos[(Pi*(-2*x + y))/(1 + y)] + (1 + y)*(-Sin[(2*Pi*x)/(1 + y)] +
         Sin[(Pi*(-2*x + y))/(1 + y)]))/(E^x*(1 + y)), {x, 0, 
   100}, {y, 0, 100}, 
  Method -> {"GlobalAdaptive", Method -> "GaussKronrodRule", 
    "SingularityDepth" -> Infinity}] // AbsoluteTiming

(настройка "SingularityDepth" -> Infinity отключает преобразования обработки сингулярностей.)

Диапазон интеграции

Кстати, действительно ли желаемый диапазон интеграции действительно {x, 0, 100}, {y, 0, 100}, или {x, 0, Infinity}, {y, 0, Infinity} является действительно желаемым диапазоном интеграции для вашего приложения?

Если вам действительно требуется {x, 0, Infinity}, {y, 0, Infinity}, я предлагаю использовать это вместо. Для диапазонов интегрирования бесконечной длины NIntegrate уплотняет подынтегральное выражение до конечного диапазона, эффективно отбирая его геометрически разнесенным способом. Это обычно намного эффективнее, чем линейные (равномерно) разнесенные выборки, которые используются для конечных диапазонов интегрирования.

8 голосов
/ 10 декабря 2011

Вот обходной путь:

NIntegrate[(Pi*
      Cos[(Pi*(-2*x + y))/(1 + y)] + (1 + y)*(-Sin[(2*Pi*x)/(1 + y)] +
         Sin[(Pi*(-2*x + y))/(1 + y)]))/(E^x*(1 + y)), {x, 0, 
   100}, {y, 0, 100}, 
  Method -> "AdaptiveMonteCarlo"] // AbsoluteTiming

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

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

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

Редактировать 1 : Я думаю, что это застряло в LevinRule (новое в V8 для осциллирующих интегратов), так что, я думаю, это должно отключить это.

NIntegrate[(Pi*
      Cos[(Pi*(-2*x + y))/(1 + y)] + (1 + y)*(-Sin[(2*Pi*x)/(1 + y)] +
         Sin[(Pi*(-2*x + y))/(1 + y)]))/(E^x*(1 + y)), {x, 0, 
   100}, {y, 0, 100}, 
  Method -> {"SymbolicPreprocessing", 
    "OscillatorySelection" -> False}] // AbsoluteTiming
2 голосов
/ 10 декабря 2011

Для этого конкретного интеграла основным виновником, по-видимому, является интеграция по x, возможно, из-за наличия как быстро затухающих, так и сильно колеблющихся членов.Кроме того, для этого случая можно аналитически выполнить интеграцию по x:

In[92]:= 
-Integrate[(Pi*Cos[(Pi*(-2*x+y))/(1+y)]+(1+y)*(-Sin[(2*Pi*x)/(1+y)]+Sin[(Pi*(-2*x+y))/(1+y)]))/
 (E^x*  (1+y)),x]/.x->0//FullSimplify

Out[92]= (-\[Pi] (1+y) (2+Cos[(\[Pi] y)/(1+y)])+(2 \[Pi]^2+(1+y)^2) Sin[(\[Pi] y)/(1+y)])/
(4 \[Pi]^2+(1+y)^2)

(я отбросил значение в верхнем пределе, поскольку оно равно очень маленькому для y).Затем можно интегрировать более y численно, чтобы получить правильный результат:

In[94]:= NIntegrate[%,{y,0,100}]
Out[94]= 1.17525

Более общий способ - разделить интеграцию x и y следующим образом:

ClearAll[f];
f[y_?NumericQ, xrange : {_?NumericQ, _?NumericQ}] :=
  NIntegrate[(Pi*
   Cos[(Pi*(-2*x + y))/(1 + y)] + (1 + 
     y)*(-Sin[(2*Pi*x)/(1 + y)] + 
     Sin[(Pi*(-2*x + y))/(1 + y)]))/(E^x*(1 + y)), {x, First@xrange, Last@xrange}];

и тогда у нас есть:

In[105]:= NIntegrate[f[y,{0,100}],{y,0,100}]//Timing
Out[105]= {2.578,1.17525}

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

...