É possível otimizar esse código de integração para que ele seja executado mais rapidamente?

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;
}

O código acima é o meu código C ++ para uma integração numérica 1D (usando a regra do trapézio estendido) func()entre os limites [a,b] usando o trapézio N1 .

Na verdade, estou fazendo uma integração 3D, onde esse código é chamado recursivamente. Eu trabalho com N=50 me dando resultados decentes.

Além de reduzir ainda mais o , alguém pode sugerir como otimizar o código acima para que ele seja executado mais rapidamente? Ou, ainda, pode sugerir um método de integração mais rápido?N

user2970116
fonte
5
Isso não é realmente relevante para a pergunta, mas eu sugiro escolher nomes de variáveis ​​melhores. Como em trapezoidal_integrationvez de trap, sumou em running_totalvez de s(e também usar em +=vez de s = s +), trapezoid_widthou em dxvez de h(ou não, dependendo da sua notação preferida para a regra trapezoidal) e alterar func1e func2refletir o fato de que são valores, não funções. Por exemplo func1-> previous_valuee func2-> current_value, ou algo assim.
David Z

Respostas:

5

Matematicamente, sua expressão é equivalente a:

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

Então você pode implementar isso. Como foi dito, o tempo provavelmente é dominado pela avaliação da função; portanto, para obter a mesma precisão, você pode usar um método de integração melhor que requer menos avaliações da função.

A quadratura gaussiana é, nos dias modernos, um pouco mais que um brinquedo; útil apenas se você precisar de muito poucas avaliações. Se você quer algo fácil de implementar, pode usar a regra de Simpson, mas eu não iria além da ordem sem uma boa razão.1/N3

Se a curvatura da função mudar muito, você poderá usar uma rotina de etapas adaptativa, que selecionaria uma etapa maior quando a função for plana e uma menor e mais precisa quando a curvatura for maior.

Davidmh
fonte
Depois de sair e voltar ao problema, decidi implementar uma regra de Simpson. Mas posso verificar se, de fato, o erro na regra composta de Simpson é proporcional a 1 / (N ^ 4) (não a 1 / (N ^ 3), como você sugere na sua resposta)?
user2970116
11
Você tem fórmulas para e . O primeiro usa os coeficientes e o segundo . 1/N31/N45/12,13/12,1,1...1,1,13/12,15/121/3,4/3,2/3,4/3...
21414 Davidmh
9

As chances são de que a avaliação das funções seja a parte mais demorada desse cálculo. Se for esse o caso, concentre-se em melhorar a velocidade de func () em vez de tentar acelerar a própria rotina de integração.

Dependendo das propriedades de func (), também é provável que você possa obter uma avaliação mais precisa da integral com menos avaliações de função usando uma fórmula de integração mais sofisticada.

Brian Borchers
fonte
11
De fato. Se sua função for suave, você poderá obter menos do que as 50 avaliações de funções se tiver usado, digamos, uma regra de quadratura de Gauss-4 em apenas 5 intervalos.
Wolfgang Bangerth 24/03
7

Possível? Sim. Útil? Não. As otimizações que vou listar aqui provavelmente não farão mais do que uma pequena fração de uma diferença percentual no tempo de execução. Um bom compilador já pode fazer isso por você.

Enfim, olhando para o seu loop interno:

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

Em cada iteração de loop, você executa três operações matemáticas que podem ser trazidas para fora: adição j + h, multiplicação por 0.5e multiplicação por h. O primeiro pode ser corrigido iniciando sua variável iteradora em a + he as demais fatorando as multiplicações:

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

Embora eu aponte isso, devido ao erro de arredondamento do ponto flutuante, é possível perder a última iteração do loop. (Esse também foi um problema na sua implementação original.) Para contornar isso, use um unsigned intou 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 a resposta de Brian diz, é melhor gastar seu tempo otimizando a avaliação da função func. Se a precisão desse método for suficiente, duvido que você encontre algo mais rápido para o mesmo N. (Embora você possa executar alguns testes para ver se, por exemplo, o Runge-Kutta permite diminuir o Nsuficiente para que a integração geral leve menos tempo sem sacrificar a precisão.)

David Z
fonte
4

Existem várias mudanças que eu recomendaria para melhorar o cálculo:

  • Para desempenho e precisão, use std::fma(), que executa uma adição múltipla combinada .
  • Para obter desempenho, adie a multiplicação da área de cada trapézio por 0,5 - você pode fazê-lo uma vez no final.
  • Evite a adição repetida de h, que pode acumular erros de arredondamento.

Além disso, eu faria várias alterações para maior clareza:

  • Atribua à função um nome mais descritivo.
  • Troque a ordem ae bna assinatura da função.
  • Renomeie Nn, hdx, jx2, saccumulator.
  • Mude npara um int.
  • Declarar variáveis ​​em um escopo mais restrito.
#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;
}
200_success
fonte
3

Se sua função for um polinômio, possivelmente ponderado por alguma função (por exemplo, um gaussiano), você poderá fazer uma integração exata em 3d diretamente com uma fórmula de cubagem (por exemplo, http://people.sc.fsu.edu/~jburkardt/c_src/ stroud / stroud.html ) ou com uma grade esparsa (por exemplo, http://tasmanian.ornl.gov/ ). Esses métodos simplesmente especificam um conjunto de pontos e pesos para multiplicar o valor da função, para que sejam muito rápidos. Se sua função é suave o suficiente para ser aproximada por polinômios, esses métodos ainda podem dar uma resposta muito boa. As fórmulas são especializadas no tipo de função que você está integrando, por isso pode levar algumas escavações para encontrar a correta.

Ronaldo Carpio
fonte
3

Ao tentar calcular uma integral numericamente, você tenta obter a precisão desejada com o menor esforço possível ou, alternativamente, tenta obter a maior precisão possível com um esforço fixo. Você parece perguntar como fazer o código de um algoritmo específico rodar o mais rápido possível.

Isso pode lhe dar um pequeno ganho, mas será pouco. Existem métodos muito mais eficientes para integração numérica. Google para "regra de Simpson", "Runge-Kutta" e "Fehlberg". Todos eles funcionam de maneira bastante semelhante, avaliando alguns valores da função e adicionando inteligentemente múltiplos desses valores, produzindo erros muito menores com o mesmo número de avaliações de função ou o mesmo erro com um número muito menor de avaliações.

gnasher729
fonte
3

Existem várias maneiras de fazer a integração, das quais a regra trapezoidal é a mais simples.

Se você souber alguma coisa sobre a função real que está integrando, poderá fazer melhor se explorar isso. A idéia é minimizar o número de pontos da grade dentro de níveis aceitáveis ​​de erro.

Por exemplo, o trapezoidal está ajustando linearmente a pontos consecutivos. Você poderia fazer um ajuste quadrático, que, se a curva for suave, se ajustaria melhor, o que permitiria o uso de uma grade mais grossa.

Às vezes, simulações orbitais são feitas usando cônicas, porque as órbitas são muito parecidas com seções cônicas.

No meu trabalho, estamos integrando formas que se aproximam de curvas em forma de sino, por isso é eficaz modelá-las dessa forma ( a quadratura gaussiana adaptativa é considerada o "padrão ouro" neste trabalho).

Mike Dunlavey
fonte
1

Portanto, como foi apontado em outras respostas, isso depende muito de quão cara sua função é. A otimização do seu código trapz só vale a pena se for realmente o seu gargalo. Se não for completamente óbvio, verifique isso criando um perfil do seu código (ferramentas como Intels V-tune, Valgrind ou Visual Studio podem fazer isso).

No entanto, eu sugeriria uma abordagem completamente diferente: integração de Monte Carlo . Aqui você simplesmente aproxima a integral amostrando sua função em pontos aleatórios adicionando os resultados. Veja este pdf além da página wiki para detalhes.

Isso funciona muito bem para dados de alta dimensão, geralmente muito melhores do que os métodos de quadratura usados ​​na integração 1-d.

O caso simples é muito fácil de implementar (consulte o pdf), mas tenha cuidado para que a função aleatória padrão no c ++ 98 seja bastante ruim, tanto no desempenho quanto na qualidade. No c ++ 11, você pode usar o Mersenne Twister no.

Se sua função tem muita variação em algumas áreas e menos em outras, considere o uso de amostragem estratificada. Eu recomendaria usar a biblioteca científica GNU , em vez de escrever a sua própria.

LKlevin
fonte
1
Na verdade, estou fazendo uma integração 3D, onde esse código é chamado recursivamente.

"recursivamente" é a chave. Você está passando por um grande conjunto de dados e considerando muitos dados mais de uma vez ou está gerando seu próprio conjunto de dados a partir de funções (por partes?).

As integrações avaliadas recursivamente serão ridiculamente caras e ridiculamente imprecisas à medida que os poderes aumentam em recursão.

Crie um modelo para interpolar seu conjunto de dados e faça uma integração simbólica por partes. Como muitos dados estão colapsando em coeficientes de funções básicas, a complexidade para uma recursão mais profunda cresce polinomialmente (e geralmente potências bastante baixas) em vez de exponencialmente. E você obtém resultados "exatos" (você ainda precisa descobrir bons esquemas de avaliação para obter um desempenho numérico razoável, mas ainda deve ser bastante viável obter melhor do que a integração trapezoidal).

Se você der uma olhada nas estimativas de erro para regras trapezoidais, descobrirá que elas estão relacionadas a alguma derivada das funções envolvidas e, se a integração / definição for feita recursivamente, as funções não tenderão a ter derivadas bem comportadas. .

Se sua única ferramenta é um martelo, todo problema parece um prego. Enquanto você mal aborda o problema em sua descrição, desconfio que a aplicação recursiva da regra trapezoidal seja uma má combinação: você obtém uma explosão de imprecisão e requisitos computacionais.

David
fonte
1

o código original avalia a função em cada N ponto, adiciona os valores e multiplica a soma pelo tamanho da etapa. o único truque é que os valores no início e no final sejam adicionados com peso , enquanto todos os pontos internos são adicionados com peso total. na verdade, eles também são adicionados com peso mas duas vezes. em vez de adicioná-los duas vezes, adicione-os apenas uma vez com peso total. fatorar a multiplicação pelo tamanho da etapa fora do loop. isso é tudo o que pode ser feito para acelerar isso, realmente.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;
}
Aksakal quase certamente binário
fonte
11
Forneça um raciocínio para suas alterações e código. Um bloco de código é bastante inútil para a maioria das pessoas.
Godric Seer
Acordado; por favor, explique sua resposta.
Geoff Oxberry 26/03