¿Es posible optimizar este código de integración para que se ejecute más rápido?


9
double trap(double func(double), double b, double a, double N) {
  double j;
  double s;
  double h = (b-a)/(N-1.0); //Width of trapezia

  double func1 = func(a);
  double func2;

  for (s=0,j=a;j<b;j+=h){
    func2 = func(j+h);
    s = s + 0.5*(func1+func2)*h;
    func1 = func2;
  }

  return s;
}

Lo anterior es mi código C ++ para una integración numérica 1D (usando la regla de trapecio extendida) de func()entre límites [a,b] usando trapecia .N1

Realmente estoy haciendo una integración 3D, donde este código se llama recursivamente. Trabajo con dándome resultados decentes.N=50

Además de reducir aún más , ¿alguien puede sugerir cómo optimizar el código anterior para que se ejecute más rápido? ¿O incluso puede sugerir un método de integración más rápido?N


55
Esto no es realmente relevante para la pregunta, pero sugeriría elegir mejores nombres de variables. Me gusta en trapezoidal_integrationlugar de trap, sumo en running_totallugar de s(y también usar en +=lugar de s = s +), trapezoid_widtho en dxlugar de h(o no, dependiendo de su notación preferida para la regla trapezoidal), y cambiar func1y func2reflejar el hecho de que son valores, no funciones. Por ejemplo, func1-> previous_valuey func2-> current_value, o algo así.
David Z

Respuestas:


5

Matemáticamente, su expresión es equivalente a:

I=h(12f1+f2+f3+...+fn1+12fn)+O((ba)3fn2)

Entonces podrías implementar eso. Como se dijo, el tiempo probablemente esté dominado por la evaluación de la función, por lo que para obtener la misma precisión, puede usar un mejor método de integración que requiera menos evaluaciones de la función.

La cuadratura gaussiana es, en la actualidad, poco más que un juguete; solo es útil si requiere muy pocas evaluaciones. Si quieres algo fácil de implementar, puedes usar la regla de Simpson, pero no iría más allá del orden sin una buena razón.1/N3

Si la curvatura de la función cambia mucho, podría usar una rutina de pasos adaptativos, que seleccionaría un paso más grande cuando la función es plana y uno más pequeño y más preciso cuando la curvatura es más alta.


Después de irme y volver al problema, he decidido implementar una regla de Simpson. Pero, ¿puedo verificar que, de hecho, el error en la regla compuesta de Simpson es proporcional a 1 / (N ^ 4) (no 1 / (N ^ 3) como implica en su respuesta)?
user2970116

1
1/N31/N45/12,13/12,1,1...1,1,13/12,15/121/3,4/3,2/3,4/3...

9

Lo más probable es que la evaluación de las funciones sea la parte más lenta de este cálculo. Si ese es el caso, entonces debería enfocarse en mejorar la velocidad de func () en lugar de tratar de acelerar la rutina de integración en sí.

Dependiendo de las propiedades de func (), también es probable que pueda obtener una evaluación más precisa de la integral con menos evaluaciones de funciones utilizando una fórmula de integración más sofisticada.


1
En efecto. Si su función es fluida, generalmente puede salirse con menos de sus 50 evaluaciones de funciones si usó, por ejemplo, una regla de cuadratura Gauss-4 en solo 5 intervalos.
Wolfgang Bangerth

7

¿Posible? Si. ¿Útil? No. Es poco probable que las optimizaciones que voy a enumerar aquí hagan más que una pequeña fracción de un porcentaje de diferencia en el tiempo de ejecución. Un buen compilador ya puede hacer esto por usted.

De todos modos, mirando tu circuito interno:

    for (s=0,j=a;j<b;j+=h){
        func2 = func(j+h);
        s = s + 0.5*(func1+func2)*h;
        func1 = func2;
    }

En cada iteración de bucle, realiza tres operaciones matemáticas que pueden llevarse al exterior: sumar j + h, multiplicar por 0.5y multiplicar por h. Lo primero que puede solucionar iniciando su variable iterador en a + h, y los otros factorizando las multiplicaciones:

    for (s=0, j=a+h; j<=b; j+=h){
        func2 = func(j);
        s += func1+func2;
        func1 = func2;
    }
    s *= 0.5 * h;

Aunque señalaría que al hacer esto, debido al error de redondeo de punto flotante, es posible perder la última iteración del bucle. (Esto también fue un problema en su implementación original). Para solucionarlo, use un unsigned into size_tcontador:

    size_t n;
    for (s=0, n=0, j=a+h; n<N; n++, j+=h){
        func2 = func(j);
        s += func1+func2;
        func1 = func2;
    }
    s *= 0.5 * h;

Como dice la respuesta de Brian, es mejor dedicar su tiempo a optimizar la evaluación de la función func. Si la precisión de este método es suficiente, dudo que encuentre algo más rápido para lo mismo N. (Aunque podría ejecutar algunas pruebas para ver si, por ejemplo, Runge-Kutta le permite bajar lo Nsuficiente como para que la integración general lleve menos tiempo sin sacrificar la precisión).


4

Hay varios cambios que recomendaría para mejorar el cálculo:

  • Para obtener rendimiento y precisión, use std::fma(), que realiza una suma múltiple fusionada .
  • Para el rendimiento, difiere multiplicando el área de cada trapecio por 0.5; puedes hacerlo una vez al final.
  • Evite la adición repetida de h, que podría acumular errores de redondeo.

Además, haría varios cambios para mayor claridad:

  • Dele a la función un nombre más descriptivo.
  • Cambie el orden de ay ben la firma de la función.
  • Renombrar Nn, hdx, jx2, saccumulator.
  • Cambiar na un int.
  • Declarar variables en un ámbito más estricto.
#include <cmath>

double trapezoidal_integration(double func(double), double a, double b, int n) {
    double dx = (b - a) / (n - 1);   // Width of trapezoids

    double func_x1 = func(a);
    double accumulator = 0;

    for (int i = 1; i <= n; i++) {
        double x2 = a + i * dx;      // Avoid repeated floating-point addition
        double func_x2 = func(x2);
        accumulator = std::fma(func_x1 + func_x2, dx, accumulator); // Fused multiply-add
        func_x1 = func_x2;
    }

    return 0.5 * accumulator;
}

3

Si su función es un polinomio, posiblemente ponderado por alguna función (por ejemplo, una gaussiana), puede hacer una integración exacta en 3d directamente con una fórmula de cubicación (por ejemplo, http://people.sc.fsu.edu/~jburkardt/c_src/ stroud / stroud.html ) o con una cuadrícula escasa (por ejemplo, http://tasmanian.ornl.gov/ ). Estos métodos simplemente especifican un conjunto de puntos y pesos para multiplicar el valor de la función, por lo que son muy rápidos. Si su función es lo suficientemente suave como para ser aproximada por polinomios, entonces estos métodos aún pueden dar una muy buena respuesta. Las fórmulas están especializadas para el tipo de función que está integrando, por lo que puede llevar un poco de investigación encontrar la correcta.


3

Cuando intenta calcular una integral numéricamente, intenta obtener la precisión que desea con el menor esfuerzo posible, o, alternativamente, intenta obtener la mayor precisión posible con un esfuerzo fijo. Parece que se pregunta cómo hacer que el código de un algoritmo en particular se ejecute lo más rápido posible.

Eso puede darle un poco de ganancia, pero será poco. Existen métodos mucho más eficientes para la integración numérica. Google para "la regla de Simpson", "Runge-Kutta" y "Fehlberg". Todos funcionan de manera bastante similar al evaluar algunos valores de la función y agregar inteligentemente múltiplos de esos valores, produciendo errores mucho más pequeños con el mismo número de evaluaciones de funciones, o el mismo error con un número mucho más pequeño de evaluaciones.


3

Hay muchas maneras de hacer la integración, de las cuales la regla trapezoidal es la más simple.

Si sabes algo sobre la función real que estás integrando, puedes hacerlo mejor si explotas eso. La idea es minimizar el número de puntos de cuadrícula dentro de niveles aceptables de error.

Por ejemplo, trapezoidal está haciendo un ajuste lineal a puntos consecutivos. Podría hacer un ajuste cuadrático, que si la curva es suave, se ajustaría mejor, lo que podría permitirle usar una cuadrícula más gruesa.

Las simulaciones orbitales a veces se realizan utilizando cónicas, porque las órbitas se parecen mucho a las secciones cónicas.

En mi trabajo, estamos integrando formas que se aproximan a las curvas en forma de campana, por lo que es efectivo modelarlas de esa manera ( la cuadratura gaussiana adaptativa se considera el "estándar de oro" en este trabajo).


1

Entonces, como se ha señalado en otras respuestas, esto depende en gran medida de lo costosa que sea su función. La optimización de su código trapz solo vale la pena si realmente es su cuello de botella. Si no es completamente obvio, debe verificar esto perfilando su código (herramientas como Intels V-tune, Valgrind o Visual Studio pueden hacer esto).

Sin embargo, sugeriría un enfoque completamente diferente: la integración de Monte Carlo . Aquí simplemente aproxima la integral muestreando su función en puntos aleatorios agregando los resultados. Vea este pdf además de la página wiki para más detalles.

Esto funciona extremadamente bien para datos de alta dimensión, típicamente mucho mejor que los métodos de cuadratura utilizados en la integración 1-d.

El caso simple es muy fácil de implementar (vea el pdf), solo tenga cuidado de que la función aleatoria estándar en c ++ 98 sea bastante mala tanto en rendimiento como en calidad. En c ++ 11, puede usar el Mersenne Twister en.

Si su función tiene mucha variación en algunas áreas y menos en otras, considere el uso de muestreo estratificado. Sin embargo, recomendaría usar la biblioteca científica GNU , en lugar de escribir la suya propia.


1
Realmente estoy haciendo una integración 3D, donde este código se llama recursivamente.

"recursivamente" es la clave. Está pasando por un gran conjunto de datos y está considerando una gran cantidad de datos más de una vez, o en realidad está generando su conjunto de datos usted mismo a partir de funciones (por partes).

Las integraciones evaluadas recursivamente serán ridículamente caras y ridículamente imprecisas a medida que aumentan los poderes en la recursividad.

Cree un modelo para interpolar su conjunto de datos y realice una integración simbólica por partes. Dado que muchos datos se están colapsando en coeficientes de funciones básicas, la complejidad para una recursión más profunda crece polinomialmente (y generalmente con potencias más bien bajas) en lugar de exponencialmente. Y obtienes resultados "exactos" (aún debes encontrar buenos esquemas de evaluación para obtener un rendimiento numérico razonable, pero aún así debería ser bastante factible mejorar la integración trapezoidal).

Si observa las estimaciones de error para las reglas trapezoidales, encontrará que están relacionadas con alguna derivada de las funciones involucradas, y si la integración / definición se realiza de forma recursiva, las funciones no tenderán a tener derivadas con buen comportamiento .

Si su única herramienta es un martillo, cada problema parece un clavo. Si bien apenas toca el problema en su descripción, tengo la sospecha de que aplicar la regla trapezoidal de forma recursiva es una mala coincidencia: obtiene una explosión de inexactitud y requisitos computacionales.


1

1/21/2

    double trap(double func(double), double b, double a, double N){
double j, s;
double h = (b-a)/(N-1.0); //Width of trapezia

double s = 0;
j = a;
for(i=1; i<N-1; i++){
  j += h;
  s += func(j);
}
s += (func(a)+func(b))/2;

return s*h;
}

1
Indique el razonamiento de sus cambios y código. Un bloque de código es bastante inútil para la mayoría de las personas.
Godric Seer

Convenido; por favor explique su respuesta
Geoff Oxberry
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.