Bu entegrasyon kodunu daha hızlı çalışacak şekilde optimize etmek mümkün müdür?


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

Yukarıdaki trapezi kullanarak func()sınırları arasındaki 1D sayısal entegrasyon (genişletilmiş yamuk kuralı kullanılarak) benim C ++ kodum.[a,b]N1

Aslında bu kodun özyinelemeli olarak adlandırıldığı bir 3D entegrasyonu yapıyorum. ile çalışarak bana iyi sonuçlar verdim .N=50

daha da azaltmanın dışında , daha hızlı çalışması için yukarıdaki kodu nasıl optimize edebileceğinizi önerebilen var mı? Hatta daha hızlı bir entegrasyon yöntemi önerebilir mi?N


5
Bu soru ile gerçekten ilgili değil, ama daha iyi değişken isimleri seçmenizi öneririm. Gibi trapezoidal_integrationyerine trap, sumya running_totalyerine s(ve aynı zamanda kullanımı +=yerine s = s +), trapezoid_widthya da dxyerine h(ya da değil, tercih trapez kuralı için gösterimde bağlı olarak), ve değişim func1ve func2onlar değerleri değil işlevleri olduğu gerçeğini yansıtmak için. Örneğin func1-> previous_valueve func2-> current_valueveya bunun gibi bir şey.
David Z

Yanıtlar:


5

Matematiksel olarak, ifadeniz şuna eşittir:

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

Böylece bunu uygulayabilirsiniz. Daha önce de söylendiği gibi, zaman muhtemelen fonksiyon değerlendirme tarafından yönetilir, bu nedenle aynı doğruluğu elde etmek için daha az fonksiyon değerlendirmesi gerektiren daha iyi bir entegrasyon yöntemi kullanabilirsiniz.

Gauss kareleme modern günlerde bir oyuncaktan biraz daha fazladır; yalnızca çok az değerlendirmeye ihtiyacınız varsa yararlıdır . Uygulaması kolay bir şey istiyorsanız, Simpson kuralını kullanabilirsiniz, ancak iyi bir neden olmadan sipariş daha ileri gitmem .1/N3

İşlevin eğriliği çok değişiyorsa, işlev düz olduğunda daha büyük bir adım ve eğrilik daha yüksek olduğunda daha küçük bir adım seçen uyarlamalı bir adım yordamı kullanabilirsiniz.


Gittikten ve soruna geri döndükten sonra, Simpson kuralını uygulamaya karar verdim. Ama aslında kompozit Simpson kuralındaki hatanın 1 / (N ^ 4) ile orantılı olduğunu kontrol edebilir miyim (cevabınızda ima ettiğiniz gibi 1 / (N ^ 3) değil)?
user2970116

1
ve için formülleriniz var . İlki ve ikinci katsayılarını kullanır . 1/N31/N45/12,13/12,1,1...1,1,13/12,15/121/3,4/3,2/3,4/3...
Davidmh

9

Büyük olasılıkla, fonksiyonların değerlendirilmesi bu hesaplamanın en çok zaman alan kısmıdır. Bu durumda, entegrasyon rutinini hızlandırmaya çalışmak yerine func () hızını geliştirmeye odaklanmalısınız.

Func () özelliklerine bağlı olarak, daha karmaşık bir entegrasyon formülü kullanarak daha az fonksiyon değerlendirmeli integralin daha hassas bir değerlendirmesini elde etmeniz de olasıdır.


1
Aslında. İşleviniz düzgünse, yalnızca 5 aralıkta bir Gauss-4 kareleme kuralı kullandıysanız, genellikle 50 işlev değerlendirmesinden daha azıyla kurtulabilirsiniz.
Wolfgang Bangerth

7

Mümkün? Evet. Kullanışlı? Hayır. Burada listeleyeceğim optimizasyonların, çalışma süresindeki yüzde farkının küçük bir kısmını oluşturması pek olası değildir. İyi bir derleyici bunları sizin için zaten yapabilir.

Her neyse, iç döngünüze bakın:

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

Her döngü yinelemesinde dışarıya getirilebilecek üç matematik işlemi gerçekleştirirsiniz: ekleme j + h, çarpma 0.5ve çarpma h. İlk olarak yineleyici değişkeninizi adresinde başlatarak a + hve diğerlerini çarpımları çarpanlarına ayırarak düzeltebilirsiniz :

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

Bunu yaparak, kayan nokta yuvarlama hatası nedeniyle, döngünün son yinelemesini kaçırmanın mümkün olduğunu belirtmek isterim. (Bu, orijinal uygulamanızda da bir sorundu.) Bu sorunu aşmak için bir unsigned intveya size_tsayaç kullanın :

    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;

Brian'ın cevabının dediği gibi, fonksiyonun değerlendirmesini optimize etmek için zamanınız daha iyi harcanır func. Bu yöntemin doğruluğu yeterliyse, bunun için daha hızlı bir şey bulacağınızdan şüpheliyim N. (Yine de, örneğin Runge-Kutta'nın Ngenel entegrasyonun doğruluktan ödün vermeden daha az zaman alacağını yeterince düşürüp düşürmediğini görmek için bazı testler yapabilirsiniz .)


4

Hesaplamayı geliştirmek için önerebileceğim birkaç değişiklik var:

  • Performans ve doğruluk için, sigortalı bir çarpma eklentisistd::fma() gerçekleştiren kullanın .
  • Performans için, her yamuk alanını 0.5 ile çarpmayı erteleyin - sonunda bir kez yapabilirsiniz.
  • Yuvarlama hhataları biriktirebilecek tekrarlanan eklemelerden kaçının .

Ayrıca, netlik için çeşitli değişiklikler yaparım:

  • İşleve daha açıklayıcı bir ad verin.
  • İşlev imzasının sırasını ave sırasını değiştirin b.
  • Nn, hdx, jx2, s→ olarak yeniden adlandırın accumulator.
  • Değişim nbir etmek int.
  • Değişkenleri daha sıkı bir kapsamda bildirin.
#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

İşleviniz bir polinomsa, muhtemelen bazı işlevlerle (örn. Bir gaussian) ağırlıklandırılırsa, doğrudan bir küp formülü ile (örneğin http://people.sc.fsu.edu/~jburkardt/c_src/) doğrudan tam bir entegrasyon yapabilirsiniz. stroud / stroud.html ) veya seyrek bir ızgarayla (ör. http://tasmanian.ornl.gov/ ). Bu yöntemler, işlev değerini çarpmak için bir dizi nokta ve ağırlık belirler, böylece çok hızlıdırlar. Eğer fonksiyonunuz polinomlar tarafından tahmin edilebilecek kadar pürüzsüzse, bu yöntemler hala çok iyi bir cevap verebilir. Formüller, entegre ettiğiniz işlev türü için uzmanlaşmıştır, bu nedenle doğru olanı bulmak için biraz kazma gerekebilir.


3

Bir integrali sayısal olarak hesaplamaya çalıştığınızda, istediğiniz hassasiyeti mümkün olan en küçük çabayla elde etmeye veya alternatif olarak sabit bir çabayla mümkün olan en yüksek hassasiyeti elde etmeye çalışın. Belirli bir algoritma için kodun nasıl olabildiğince hızlı çalışacağını soruyorsunuz.

Bu size biraz kazanç sağlayabilir, ancak çok az olacaktır. Sayısal entegrasyon için çok daha verimli yöntemler vardır. "Simpson kuralı", "Runge-Kutta" ve "Fehlberg" için Google. Hepsi işlevin bazı değerlerini değerlendirerek ve bu değerin katlarını akıllıca ekleyerek, aynı sayıda işlev değerlendirmesi ile çok daha küçük hatalar veya daha az sayıda değerlendirme ile aynı hatayı üreterek oldukça benzer şekilde çalışırlar.


3

Yamuk kuralının en basitiyle ilgili olduğu entegrasyon yapmanın birçok yolu vardır.

Entegre ettiğiniz gerçek işlev hakkında hiçbir şey biliyorsanız, bundan faydalanırsanız daha iyisini yapabilirsiniz. Buradaki fikir, kabul edilebilir hata seviyelerinde ızgara noktası sayısını en aza indirmektir.

Örneğin, trapezoidal ardışık noktalara lineer bir uyum sağlamaktadır. İkinci dereceden bir uyum sağlayabilirsiniz, bu eğri pürüzsüzse daha iyi sığar, bu da daha kaba bir ızgara kullanmanıza izin verebilir.

Yörünge simülasyonları bazen konikler kullanılarak yapılır, çünkü yörüngeler konik bölümlere çok benzer.

Çalışmamda, çan şeklindeki eğrilere yaklaşan şekilleri birleştiriyoruz, bu yüzden onları bu şekilde modellemek etkilidir ( uyarlanabilir Gauss kareleme bu çalışmada "altın standart" olarak kabul edilir).


1

Yani, diğer cevaplarda belirtildiği gibi, bu büyük ölçüde işlevinizin ne kadar pahalı olduğuna bağlıdır. Trapz kodunuzu optimize etmek sadece gerçekten darboğazınız varsa buna değer. Tamamen açık değilse, kodunuzu profilleyerek bunu kontrol etmelisiniz (Intels V-tune, Valgrind veya Visual Studio gibi araçlar bunu yapabilir).

Ancak tamamen farklı bir yaklaşım öneririm: Monte Carlo entegrasyonu . Burada, sonuçları ekleyerek rastgele noktalarda fonksiyonunuzu örnekleyerek integrali basitçe tahmin edersiniz. Bkz bu detaylar için wiki sayfasına ek olarak pdf.

Bu, yüksek boyutlu veriler için son derece iyi çalışır, tipik olarak 1-d entegrasyonda kullanılan kareleme yöntemlerinden çok daha iyidir.

Basit durumun uygulanması çok kolaydır (pdf'ye bakın), sadece c ++ 98'deki standart rastgele fonksiyonun hem performans hem de kalite açısından oldukça kötü olduğuna dikkat edin. C ++ 11'de Mersenne Twister'ı kullanabilirsiniz.

İşlevinizin bazı alanlarda çok fazla ve diğerlerinde daha az varyasyon varsa, tabakalı örnekleme kullanmayı düşünün. Kendi kitaplığınızı yazmak yerine GNU bilimsel kütüphanesini kullanmanızı tavsiye ederim .


1
Aslında bu kodun tekrarlanan olarak adlandırıldığı bir 3D entegrasyonu yapıyorum.

"özyinelemeli" anahtardır. Ya büyük bir veri kümesinden geçiyor ve birden fazla veriyi birden fazla kez değerlendiriyorsunuz ya da aslında veri setinizi (parçalı?) İşlevlerden kendiniz üretiyorsunuz.

Yinelemeli olarak değerlendirilen entegrasyonlar gülünç derecede pahalı olacak ve özyinelemede güçler arttıkça gülünç olarak kesin olmayacaktır.

Veri kümenizi enterpolasyon için bir model oluşturun ve parçalı sembolik bir entegrasyon yapın. Birçok veri daha sonra temel fonksiyonların katsayılarına çöktüğünden, daha derin özyineleme karmaşıklığı katlanarak değil polinom olarak (ve genellikle oldukça düşük güçler) büyür. Ve "kesin" sonuçlar elde edersiniz (makul sayısal performans elde etmek için hala iyi değerlendirme şemaları bulmanız gerekir, ancak yine de trapezoidal entegrasyondan daha iyi olmak daha uygun olmalıdır).

Trapezoidal kurallar için hata tahminlerine bakarsanız, bunların ilgili işlevlerin bazı türevleriyle ilişkili olduğunu görürsünüz ve entegrasyon / tanım tekrarlı olarak yapılırsa, işlevlerin iyi davranılmış türevleri olma eğilimi yoktur .

Tek aracınız bir çekiçse, her sorun bir çiviye benziyor. Açıklamanızdaki soruna zar zor dokunurken, trapezoidal kuralı özyinelemenin kötü bir eşleşme olduğundan şüphe duyuyorum : hem yanlışlık hem de hesaplama gereksinimlerinin patlaması.


1

orijinal kod her N noktasında işlevi değerlendirir, sonra değerleri toplar ve toplamı adım boyutuyla çarpar. tek püf noktası başlangıçtaki ve sondaki değerlerin ağırlıkla eklenmesi , içindeki tüm noktaların ise tam ağırlık ile eklenmesi. aslında, onlar da ama iki kez ağırlık ile eklenir . iki kez eklemek yerine, sadece bir kez tam ağırlıkla ekleyin. döngü dışında adım boyutu çarpma çarpanı. Bunu hızlandırmak için yapılabileceklerin hepsi bu.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
Lütfen değişiklikleriniz ve kodunuz için gerekçe belirtin. Bir kod bloğu çoğu insan için oldukça işe yaramaz.
Godric Seer

Kabul; lütfen cevabınızı açıklayınız.
Geoff Oxberry
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.