Necesito calcular muchos 2D integraciones más dominios que están conectados simplemente (y convexas mayor parte del tiempo). Estoy usando la función pitón scipy.integrate.nquad
hacer esta integración. Sin embargo, el tiempo requerido por esta operación es significativamente más grande en comparación con la integración sobre un dominio rectangular. ¿Hay alguna aplicación más rápido posible?
Aquí hay un ejemplo; Integro una función constante en primer lugar sobre un dominio circular (usando una restricción dentro de la función) y luego en un dominio rectangular (dominio predeterminado de la nquad
función).
from scipy import integrate
import time
def circular(x,y,a):
if x**2 + y**2 < a**2/4:
return 1
else:
return 0
def rectangular(x,y,a):
return 1
a = 4
start = time.time()
result = integrate.nquad(circular, [[-a/2, a/2],[-a/2, a/2]], args=(a,))
now = time.time()
print(now-start)
start = time.time()
result = integrate.nquad(rectangular, [[-a/2, a/2],[-a/2, a/2]], args=(a,))
now = time.time()
print(now-start)
El dominio rectangular, toma sólo 0.00029
segundos, mientras que el dominio circular requiere 2.07061
segundos para completar.
También la integración circular da la siguiente advertencia:
IntegrationWarning: The maximum number of subdivisions (50) has been achieved.
If increasing the limit yields no improvement it is advised to analyze
the integrand in order to determine the difficulties. If the position of a
local difficulty can be determined (singularity, discontinuity) one will
probably gain from splitting up the interval and calling the integrator
on the subranges. Perhaps a special-purpose integrator should be used.
**opt)
Una forma de hacer el cálculo más rápido es utilizar numba
, un compilador justo a tiempo para Python.
el @jit
decorador
Numba proporciona un @jit
decorador para compilar algo de código y de salida optimizado código de máquina Python que se puede ejecutar en paralelo en varias CPU. Jitting la función integrando sólo toma poco esfuerzo y alcanzar un cierto ahorro de tiempo que el código está optimizado para correr más rápido. Uno ni siquiera tiene que preocuparse con los tipos, Numba significa todo esto bajo el capó.
from scipy import integrate
from numba import jit
@jit
def circular_jit(x, y, a):
if x**2 + y**2 < a**2 / 4:
return 1
else:
return 0
a = 4
result = integrate.nquad(circular_jit, [[-a/2, a/2],[-a/2, a/2]], args=(a,))
Este hecho se ejecuta más rápido y adecuado cuando se miden en mi máquina, me sale:
Original circular function: 1.599048376083374
Jitted circular function: 0.8280022144317627
Eso es una reducción ~ 50% de tiempo de cálculo.
scipy de LowLevelCallable
Las llamadas a funciones en Python están consumiendo debido a la naturaleza de la lengua bastante tiempo. La sobrecarga puede a veces hacer que el código Python lenta en comparación con los lenguajes compilados como C.
Con el fin de mitigar este, Scipy proporciona una LowLevelCallable
clase que puede ser utilizada para proporcionar acceso a una función de devolución de llamada compilado bajo nivel. A través de este mecanismo, la función de llamada sobrecarga de Python se pasa por alto y más ahorro de tiempo se pueden hacer.
Tenga en cuenta que en el caso de nquad
la firma de la cfunc
pasó a LowerLevelCallable
debe ser uno de:
double func(int n, double *xx)
double func(int n, double *xx, void *user_data)
donde el int
es el número de argumentos y los valores para los argumentos están en el segundo argumento. user_data
se utiliza para las devoluciones de llamada que necesitan para operar contexto.
Por tanto, podemos cambiar ligeramente la firma de la función circular en Python para que sea compatible.
from scipy import integrate, LowLevelCallable
from numba import cfunc
from numba.types import intc, CPointer, float64
@cfunc(float64(intc, CPointer(float64)))
def circular_cfunc(n, args):
x, y, a = (args[0], args[1], args[2]) # Cannot do `(args[i] for i in range(n))` as `yield` is not supported
if x**2 + y**2 < a**2/4:
return 1
else:
return 0
circular_LLC = LowLevelCallable(circular_cfunc.ctypes)
a = 4
result = integrate.nquad(circular_LLC, [[-a/2, a/2],[-a/2, a/2]], args=(a,))
Con este método get me
LowLevelCallable circular function: 0.07962369918823242
Esta es una reducción del 95% en comparación con el original y 90% cuando se compara con la versión compilados JIT de la función.
A medida decorador
Con el fin de hacer que el código sea más ordenado y para mantener la firma de la función integrando flexible, un decorador medida la función se pueden crear. Será JIT la función integrando y se envuelve en un LowLevelCallable
objeto que puede ser utilizado con nquad
.
from scipy import integrate, LowLevelCallable
from numba import cfunc, jit
from numba.types import intc, CPointer, float64
def jit_integrand_function(integrand_function):
jitted_function = jit(integrand_function, nopython=True)
@cfunc(float64(intc, CPointer(float64)))
def wrapped(n, xx):
return jitted_function(xx[0], xx[1], xx[2])
return LowLevelCallable(wrapped.ctypes)
@jit_integrand_function
def circular(x, y, a):
if x**2 + y**2 < a**2 / 4:
return 1
else:
return 0
a = 4
result = integrate.nquad(circular, [[-a/2, a/2],[-a/2, a/2]], args=(a,))
número arbitrario de argumentos
Si el número de argumentos es desconocido, entonces podemos utilizar la conveniente carray
función proporcionada por Numba para convertir la CPointer(float64)
a una matriz Numpy.
import numpy as np
from scipy import integrate, LowLevelCallable
from numba import cfunc, carray, jit
from numba.types import intc, CPointer, float64
def jit_integrand_function(integrand_function):
jitted_function = jit(integrand_function, nopython=True)
@cfunc(float64(intc, CPointer(float64)))
def wrapped(n, xx):
ar = carray(xx, n)
return jitted_function(ar[0], ar[1], ar[2:])
return LowLevelCallable(wrapped.ctypes)
@jit_integrand_function
def circular(x, y, a):
if x**2 + y**2 < a[-1]**2 / 4:
return 1
else:
return 0
ar = np.array([1, 2, 3, 4])
a = ar[-1]
result = integrate.nquad(circular, [[-a/2, a/2],[-a/2, a/2]], args=ar)