NIntegrate - почему в данном случае Mathematica 8 намного медленнее?

У меня есть код 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?


person user1091201    schedule 10.12.2011    source источник
comment
Здесь (на 8.0.1) он жалуется на катастрофическую потерю точности в оценке глобальной ошибки и возвращает невычисленное значение.   -  person acl    schedule 10.12.2011
comment
На 8.0.4 не жалуется, но занимает больше 300 секунд.   -  person user1091201    schedule 10.12.2011


Ответы (3)


Ответ ruebenko и комментарии от user1091201 и Leonid вместе дают правильные ответы.

Ответ Редактировать 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

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

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

Выбор метода

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

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

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

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

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

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

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

И Method -> {"SymbolicPreprocessing", "OscillatorySelection" -> False} ruebenko, и Method -> "GaussKronrodRule" user1091201 не отключают другие формы символьной обработки, и в целом это хорошо. Список типов символьных предварительная обработка, которая может быть применена. В частности, «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 компактизирует подынтегральную функцию до конечного диапазона, эффективно выполняя выборку с геометрическим интервалом. Обычно это намного эффективнее, чем линейные (равномерно расположенные) выборки, которые используются для конечных диапазонов интегрирования.

person Andrew Moylan    schedule 10.12.2011
comment
Да, действительно {x, 0, Infinity}, {y, 0, Infinity} - требуемый диапазон. Спасибо за указание на это. Я включил вклад от 100 лет за счет расширения серии в сочетании с аналитической интеграцией. Я думал, что это более надежно. - person user1091201; 11.12.2011
comment
Ах да, это может быть хорошим маневром. Он также может хорошо работать в другом направлении: если вы действительно хотите интегрировать с конечным диапазоном, сформируйте его как разность двух интегрирований с бесконечным диапазоном или как разность интегрирования с бесконечным диапазоном и последовательного разложения хвоста. - person Andrew Moylan; 11.12.2011
comment
Здравствуйте, Эндрю, у нас есть предложение по созданию отдельного сайта Mathematica в сети SE для всего, что связано to mma (не только вопросы программирования, как на SO). Мы очень близки к запуску (осталось 24 пользователя), и было бы здорово, если бы вы согласились на это предложение :) - person abcd; 10.01.2012

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

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
person Community    schedule 10.12.2011
comment
Ситуация более сложная. Ваш код был первым, что я тоже попробовал, но, к сожалению, он не дает правильного результата (по крайней мере, на M8.0, который я для него использовал), и он даже не дает того же результата при многократном запуске. Проблема в том, что подынтегральная функция не так безобидна — она очень быстро затухает в одном измерении, а также сильно осциллирует. - person Leonid Shifrin; 10.12.2011
comment
Я думаю, что это застревает в LevinRule. Поэтому редактирование 1 должно помочь обойти это. - person ; 10.12.2011
comment
Да, ты прав. Заставив его использовать определенный метод, такой как Method -> GaussKronrodRule, это может занять всего 0,2 секунды (на моем ноутбуке), в то время как использование Method -> LevinRule занимает 343 секунды при получении того же результата. Вероятно, не указав метод, пробует несколько из них. - person user1091201; 10.12.2011
comment
Будьте осторожны с конкретным методом - как и моя первая попытка выше, она пошла не так. Второй подход лучше, поскольку он отключает конкретный подход. Но опять же, LevinRule создан для таких проблем и может дать лучшие результаты. Всегда важнее дать правильный результат, чем очень быстро получить неправильный результат. - person ; 10.12.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 достаточно "развязаны".

person Leonid Shifrin    schedule 10.12.2011