Гаус-Лежандр с интервалами -x → бесконечность: адаптивный алгоритм для эффективного преобразования весов и узлов

Хорошо, я знаю, что это было задано раньше с ограниченным примером масштабирования [-1, 1] интервалов [a, b] Различные интервалы для квадратурности Гаусса-Лежандра в numpy, но никто опубликовал, как обобщить это для [-a, Infinity] (как это сделано ниже, но пока (пока) не быстро). Также это показывает, как вызвать сложную функцию (в количественном определении цены) в любом случае с несколькими реализациями. Существует контрольный quad код, за которым следует leggauss, со ссылками на примеры кода о том, как реализовать адаптивный алгоритм. Я работал над большинством связанных трудностей adaptive algorithm - он в настоящее время печатает сумму разделенного интеграла, чтобы показать, что он работает правильно. Здесь вы найдете функции для преобразования диапазона от [-1, 1] до [0, 1] до [a, Infinity] (спасибо @AlexisClarembeau). Чтобы использовать адаптивный алгоритм, мне пришлось создать еще одну функцию для преобразования из [-1, 1] в [a, b], которая возвращается обратно в функцию [a, Infinity].

import numpy as np
from scipy.stats import norm, lognorm
from scipy.integrate import quad

a = 0
degrees = 50

flag=-1.0000
F = 1.2075
K = 0.1251
vol = 0.43
T2 = 0.0411
T1 = 0.0047

def integrand(x, flag, F, K, vol, T2, T1):
    d1 = (np.log(x / (x+K)) + 0.5 * (vol**2) * (T2-T1)) / (vol * np.sqrt(T2 - T1))
    d2 = d1 - vol*np.sqrt(T2 - T1)
    mu = np.log(F) - 0.5 *vol **2 * T1
    sigma = vol * np.sqrt(T1)
    return lognorm.pdf(x, mu, sigma) * (flag * x*norm.cdf(flag * d1) - flag * (x+K)*norm.cdf(flag * d2))

def transform_integral_0_1_to_Infinity(x, a): 
    return integrand(a+(x/(1-x)), flag, F, K, vol, T2, T1) *(1/(1-x)**2); 

def transform_integral_negative1_1_to_0_1(x, a): 
    return 0.5 * transform_integral_0_1_to_Infinity((x+1)/2, a)

def transform_integral_negative1_1_to_a_b(x, w, a, b):
    return np.sum(w*(0.5 * transform_integral_0_1_to_Infinity(((x+1)/2*(b-a)+a), a)))

def adaptive_integration(x, w, a=-1, b=1, lastsplit=False, precision=1e-10):
    #split the integral in half assuming [-1, 1] range
    midpoint = (a+b)/2
    interval1 = transform_integral_negative1_1_to_a_b(x, w, a, midpoint)
    interval2 = transform_integral_negative1_1_to_a_b(x, w, midpoint, b)
    return interval1+interval2 #just shows this is correct for splitting the interval

def integrate(x, w, a): 
    return np.sum(w*transform_integral_negative1_1_to_0_1(x, a))

x, w = np.polynomial.legendre.leggauss(degrees) 
quadresult = quad(integrand, a, np.Inf, args=(flag, F, K, vol, T2, T1), epsabs=1e-1000)[0]
GL = integrate(x, w, a)
print("Adaptive Sum Result:")
print(adaptive_integration(x, w))
print("GL result"); 
print(GL)
print("QUAD result")
print(quadresult)

По-прежнему необходимо увеличить скорость и точность с меньшими размерами, так как я не могу вручную отрегулировать диапазон degrees для -a, чтобы получить сходимость. Чтобы проиллюстрировать, почему это проблема, введите следующие значения: a=-20, F=50, затем запустите. Вы можете увеличить degrees=1000 и увидеть, что для этого алгоритма Гаусса-Лежандра нет никакой пользы, если он не применяется разумно. Мое требование скорости - добраться до 0.0004s за цикл, тогда как последний алгоритм я Cythonized занял около 0.75s, поэтому я пытаюсь использовать алгоритм с низкой степенью точности с Gauss-Legendre. С Cython и многопоточным это требование от полностью оптимизированной реализации Python составляет примерно 0,007 с за цикл (не-векторизованная циклическая, неэффективная процедура может быть 0,1 с за цикл с degrees=20, то есть %timeit adaptive_integration(x,w).

Возможное решение, которое я реализовал наполовину, находится здесь http://online.sfsu.edu/meredith/Numerical_Analysis/improper_integrals на страницах 5/6, adaptive integration, тогда как интервал a-b (в этом случае я написал функцию transform_integral_negative1_1_to_a_b), где интервал делится на 2 (@0.5), тогда функция оценивается на этих 1/2 интервалах, а сумма двух 0->0.5 + 0.5->1 сравниваются с результатами функции для всего диапазона 0->1. Если точность не находится в пределах допуска, диапазон далее подразделяется на 0.25 и 0.75, функция снова оценивается для каждого интервала и сравнивается с предыдущими 1/2 интервальными суммами @0.5. Если 1 сторона находится в пределах допуска (например, abs(0->0.5 - (0->0.25 + 0.25->0.5)) < precision), но на другой стороне нет, расщепление останавливается сбоку в пределах допуска, но продолжается с другой стороны, пока не будет достигнуто значение precision. В этот момент результаты для каждого среза интервала суммируются для получения полного интеграла с большей точностью.

Скорее всего, существуют более быстрые и лучшие способы решения этой проблемы. Мне все равно, пока это быстро и точно. Вот лучшее описание интеграционных подпрограмм, которые я нашел для справки http://orion.math.iastate.edu/keinert/computation_notes/chapter5.pdf. Награда - 100pts bounty + 15pts для принятия ответа. Благодарим вас за помощь в создании этого кода FAST и ACCURATE!

EDIT:

Вот мое изменение кода adaptive_integration - если кто-то может быстро выполнить эту работу, я могу принять ответ и наградить наградой. Этот код Mathematica на стр. 7 http://online.sfsu.edu/meredith/Numerical_Analysis/improper_integrals выполняет обычную попытку. Он работает над рутиной, которая не сходится хорошо, см. Приведенные ниже переменные. В настоящий момент мой код ошибки: RecursionError: maximum recursion depth exceeded in comparison на некоторых входах или если параметр degrees установлен слишком высоким или не приближается к результату quad, когда он работает, значит, что-то явно не так.

def adaptive_integration(x, w, a, b, integralA2B, remainingIterations, firstIteration, precision=1e-9):
    #split the integral in half assuming [-1, 1] range
    if remainingIterations == 0:
        print('Adaptive integration failed on the interval',a,'->',b)
    if np.isnan(integralA2B): return np.nan

    midpoint = (a+b)/2
    interval1 = transform_integral_negative1_1_to_a_b(x, w, a, midpoint)
    interval2 = transform_integral_negative1_1_to_a_b(x, w, midpoint, b)
    if np.abs(integralA2B - (interval1 + interval2))  < precision : 
        return(interval1 + interval2)       
    else:
        return adaptive_integration(x, w, a, midpoint, interval1, (remainingIterations-1), False) + adaptive_integration(x, w, midpoint, b, interval2, (remainingIterations-1), False) 

#This example doesn't converge to Quad

# non-converging interval inputs
a = 0 # AND a = -250
degrees = 10

flag= 1
F = 50
K = 0.1251
vol = 0.43
T2 = 0.0411
T1 = 0.0047

print(adaptive_integration(x, w, -1, 1, GL, 500, False))

Выход с degrees=100 (после вычисления GL с degrees=10000 для лучшей начальной оценки, в противном случае алгоритм всегда согласен с его собственной точностью, по-видимому, и не вызывает адаптивный путь, который терпит неудачу каждый раз):

GL result:
60.065205169286379
Adaptive Sum Result:
RecursionError: maximum recursion depth exceeded in comparison
QUAD result:
68.72069173210338

Ответ 1

Я думаю, что код выполняет свою работу:

import numpy as np 
import math

deg = 10
x, w = np.polynomial.legendre.leggauss(deg)

def function(x): 
    # the function to integrate
    return math.exp(-x)

def function2(x, a): 
    return function(a+x/(1-x))/((1-x)**2); 

def anotherOne(x, a): 
    return 0.5 * function2(x/2 + 1/2, a)

def integrate(deg, a): 
    sum = 0 
    x, w = np.polynomial.legendre.leggauss(deg)
    for i in range(deg): 
        print("sum({}) += {} * {} (eval in {})".format(sum, w[i], anotherOne(x[i], a), x[i]))
        sum += w[i]*anotherOne(x[i], a)
    return sum; 

print("result"); 
print(integrate(10, 1))

Он объединяет ваше уравнение для интегрирования из a в inf и уравнение для изменения границ интеграла.

Я надеюсь, что это решит вашу проблему (это работает как минимум для exp (-x)) :)

Если вы хотите встроенные вычисления, программа делает сумму: enter image description here

Это сочетание:

enter image description here

И:

enter image description here

И:

enter image description here

Ответ 2

В "Численное программирование: практическое руководство для ученых и инженеров, использующих Python и C/С++" Титусом А. Бью, вы можете найти методы в примерах кода integral.py и specfunc.py здесь: http://phys.ubbcluj.ro/~tbeu/INP/libraries.html Вы вызываете функцию xGaussLag(a, deg), которая вызывает Laguerre из другого .py файла и возвращает отрегулированный (x,w) между a и infinity. Здесь, как установить это (примечание выше deg=80, это очень медленно, я просто показываю вам, как применять его, изменяя строки выше):

x, w = np.array(xGaussLag(a,deg))
gauss = sum(w * integrand(x, flag, F, K, vol, T2, T1))

Получает довольно близкую сходимость на deg=80 (быстрее), но я просто положил eps=1e-13 в xGaussLag и нажал deg=150 с этими результатами, тем не менее быстрее, чем quad на 33%:

Решение QUADPACK: 0.149221620346 с ошибкой: 1.49870924498e-12 Решение Гаусса-Лежандра: 0.149238273747 Разница между QUADPACK и Gauss-Legendre: 1.66534003601e-05

В Cython это на 6 раз быстрее, чем прямой Python BTW, все еще слишком медленный, поэтому я собираюсь попробовать пакет "FastGL" с ответом от @Alexis на данный момент, просто разместив, так как я думаю, что это будет полезно для других пользователей SO в будущем.

Ответ 3

Интегралы, простирающиеся по бесконечной области, всегда должны вызывать у вас подозрения. В конце концов, большинство "простых" функций даже не могут быть интегрированы там! Вот почему обычно есть демпфирующий термин, например, exp(-x) или exp(-x**2). И, конечно же, для этих двух случаев у вас есть особые правила интеграции:

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

Обычно это можно сделать вручную.