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 usando o trapézio .
Na verdade, estou fazendo uma integração 3D, onde esse código é chamado recursivamente. Eu trabalho com 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?
c++
performance
user2970116
fonte
fonte
trapezoidal_integration
vez detrap
,sum
ou emrunning_total
vez des
(e também usar em+=
vez des = s +
),trapezoid_width
ou emdx
vez deh
(ou não, dependendo da sua notação preferida para a regra trapezoidal) e alterarfunc1
efunc2
refletir o fato de que são valores, não funções. Por exemplofunc1
->previous_value
efunc2
->current_value
, ou algo assim.Respostas:
Matematicamente, sua expressão é equivalente a:
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.
fonte
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.
fonte
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:
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 por0.5
e multiplicação porh
. O primeiro pode ser corrigido iniciando sua variável iteradora ema + h
e as demais fatorando as multiplicações: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 int
ousize_t
contador: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 mesmoN
. (Embora você possa executar alguns testes para ver se, por exemplo, o Runge-Kutta permite diminuir oN
suficiente para que a integração geral leve menos tempo sem sacrificar a precisão.)fonte
Existem várias mudanças que eu recomendaria para melhorar o cálculo:
std::fma()
, que executa uma adição múltipla combinada .h
, que pode acumular erros de arredondamento.Além disso, eu faria várias alterações para maior clareza:
a
eb
na assinatura da função.N
→n
,h
→dx
,j
→x2
,s
→accumulator
.n
para umint
.fonte
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.
fonte
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.
fonte
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).
fonte
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.
fonte
"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.
fonte
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/2 1/2
fonte