¿Cómo reducir el tiempo de integración para la integración sobre 2D conectada dominios

SMA.D:

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.nquadhacer 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 nquadfunció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.00029segundos, mientras que el dominio circular requiere 2.07061segundos 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)
Jacques Gaudin:

Una forma de hacer el cálculo más rápido es utilizar numba, un compilador justo a tiempo para Python.

el @jitdecorador

Numba proporciona un @jitdecorador 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 LowLevelCallableclase 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 nquadla firma de la cfuncpasó a LowerLevelCallabledebe ser uno de:

double func(int n, double *xx)
double func(int n, double *xx, void *user_data)

donde el intes el número de argumentos y los valores para los argumentos están en el segundo argumento. user_datase 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 LowLevelCallableobjeto 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 carrayfunció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)

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=283040&siteId=1
Recomendado
Clasificación