Adaptacyjna metoda Simpsona
Adaptacyjna metoda Simpsona , zwana także adaptacyjną regułą Simpsona , to metoda całkowania numerycznego zaproponowana przez GF Kuncira w 1962 r. Jest to prawdopodobnie pierwszy rekurencyjny algorytm adaptacyjny całkowania numerycznego, który pojawił się w druku, chociaż bardziej nowoczesne metody adaptacyjne oparte na kwadraturze Gaussa – Kronroda i kwadratura Clenshawa – Curtisa są obecnie ogólnie preferowane. Metoda Adaptive Simpsona wykorzystuje oszacowanie błędu, jaki otrzymujemy przy obliczaniu całki oznaczonej za pomocą reguły Simpsona . Jeśli błąd przekracza tolerancję określoną przez użytkownika, algorytm wzywa do podzielenia przedziału całkowania na dwie części i zastosowania adaptacyjnej metody Simpsona do każdego podprzedziału w sposób rekurencyjny. Technika ta jest zwykle znacznie bardziej wydajna niż złożona reguła Simpsona, ponieważ wykorzystuje mniej ocen funkcji w miejscach, w których funkcja jest dobrze aproksymowana przez funkcję sześcienną .
Reguła Simpsona jest interpolacyjną regułą kwadraturową, która jest dokładna, gdy całka jest wielomianem stopnia trzeciego lub niższego. Korzystając z ekstrapolacji Richardsona dokładniejsze oszacowanie Simpsona dla sześciu wartości funkcji jest połączone z mniej dokładnym oszacowaniem dla trzech wartości funkcji przez zastosowanie poprawki . Uzyskane oszacowanie jest więc dokładne dla wielomianów stopnia piątego lub mniejszego.
Procedura matematyczna
Definiowanie warunków
Kryterium określania, kiedy przestać dzielić przedział, sugerowane przez JN Lynessa, jest
gdzie jest przedziałem z punktem środkowym , podczas gdy , i podane przez regułę Simpsona to oszacowania , ∫ odpowiednio, a maksymalną tolerancją błędu dla przedziału.
Uwaga, .
Kroki proceduralne
Aby wykonać adaptacyjną metodę Simpsona, wykonaj następujące czynności: if S reguł które są używane do przybliżenia całki, w przeciwnym razie wykonaj tę samą i zamiast .
Rozważanie liczbowe
Niektóre dane wejściowe nie zostaną szybko zbieżne w adaptacyjnej metodzie Simpsona, co spowoduje niedopełnienie tolerancji i utworzenie nieskończonej pętli. Proste metody ochrony przed tym problemem obejmują dodanie ograniczenia głębokości (jak w próbce C i McKeeman), sprawdzenie, czy ε /2 ≠ ε w arytmetyce zmiennoprzecinkowej lub jedno i drugie (jak Kuncir). Rozmiar interwału może również zbliżyć się do epsilon maszyny lokalnej , dając a = b .
Artykuł Lychee z 1969 roku zawiera „Modyfikację 4”, która odnosi się do tego problemu w bardziej konkretny sposób:
- Niech początkowy przedział będzie [ A , B ] . Niech pierwotną tolerancją będzie ε 0 .
- Dla każdego podprzedziału [ a , b ] zdefiniuj D ( a , b ) , oszacowanie błędu jako . Zdefiniujmy 0 E = 180 ε / ( B - A ) . Pierwotne kryteria zakończenia miałyby wówczas postać D ≤ E .
-
0 Jeśli D ( a , m ) ≥ D ( a , b ) , albo osiągnięto poziom zaokrąglenia, albo w przedziale znaleziono zero dla f (4) . Konieczna jest zmiana tolerancji ε na ε′ . 0
- Podprogramy rekurencyjne muszą teraz zwracać poziom D dla bieżącego interwału. Zmienna rutynowo-statyczna 0 E' = 180 ε' / ( B - A ) jest zdefiniowana i zainicjowana jako E .
- (Modyfikacja 4 i, ii) Jeśli dalsza rekurencja jest używana w przedziale:
- Jeśli wydaje się, że zaokrąglenie zostało osiągnięte, zmień E' na D ( a , m ) .
- W przeciwnym razie dostosuj E' do max( E , D ( a , m )) .
- Konieczna jest pewna kontrola regulacji. Należy powstrzymać znaczne wzrosty i niewielkie spadki tolerancji.
-
0 Aby obliczyć efektywną ε′ w całym przedziale:
- Zarejestruj każde x i , przy którym E' zmienia się w tablicę par ( x i , ε i ' ) . Pierwszym wpisem powinno być 0 ( a , ε′ ) .
- 0 Rzeczywisty ε eff jest średnią arytmetyczną wszystkich ε′ ważoną szerokością przedziałów.
- Jeżeli bieżące E' dla przedziału jest większe niż E , to przyspieszenie/poprawka piątego rzędu nie miałaby zastosowania:
- Współczynnik „15” w kryteriach zakończenia jest wyłączony.
- Nie należy stosować terminu korygującego.
Manewr podnoszenia epsilon pozwala na użycie procedury w trybie „najlepszego wysiłku”: przy zerowej początkowej tolerancji, procedura spróbuje uzyskać najbardziej precyzyjną odpowiedź i zwróci rzeczywisty poziom błędu.
Przykładowe implementacje kodu
Powszechną techniką implementacji pokazaną poniżej jest przekazywanie w dół f( a ), f( b ), f( m ) wraz z przedziałem [ a , b ] . Te wartości, użyte do oceny S ( a , b ) na poziomie nadrzędnym, zostaną ponownie użyte dla podprzedziałów. W ten sposób zmniejsza się koszt każdego wywołania rekurencyjnego z 6 do 2 ocen funkcji wejściowej. Rozmiar używanej przestrzeni stosu pozostaje liniowy w stosunku do warstwy rekurencji.
Pyton
Oto implementacja adaptacyjnej metody Simpsona w Pythonie .
0 from __future__ import Division # python 2 compat # "structured" adaptacyjna wersja, przetłumaczona z Racket def _quad_simpsons_mem ( f , a , fa , b , fb ): """Ocenia regułę Simpsona, zwracając również m i f(m) do ponownego użycia """ m = ( za + b ) / 2 fm = fa ( m ) powrót ( m , fm , abs ( b - za ) / 6 * ( fa + 4 * fm + fb )) def _quad_asr ( fa , za , fa , b , fb , eps , whole , m , fm ): """ Efektywna rekurencyjna implementacja adaptacyjnej reguły Simpsona. Zachowane są wartości funkcji na początku, w środku, na końcu przedziału. """ lm , flm , left = _quad_simpsons_mem ( f , a , fa , m , fm ) rm , frm , right = _quad_simpsons_mem ( f , m , fm , b , fb ) delta = lewa + prawa - całość jeśli abs ( delta ) <= 15 * eps : powrót w lewo + prawo + delta / 15 powrót _quad_asr ( f , a , fa , m , fm , eps / 2 , lewo , lm , flm ) + \ _quad_asr ( f , m , fm , b , fb , eps / 2 , prawo , rm , frm ) def quad_asr ( f , a , b , eps ): """Całkuj f od a do b za pomocą adaptacyjnej reguły Simpsona z maksymalnym błędem eps.""" fa , fb = f ( a ), f ( b ) m , fm , whole = _quad_simpsons_mem ( f , a , fa , b , fb ) return _quad_asr ( f , a , fa , b , fb , eps , całość , m , fm ) z matematyki import sin print ( quad_asr ( sin , , 1 , 1e-09 ))
C
Oto implementacja adaptacyjnej metody Simpsona w C99, która pozwala uniknąć zbędnych ocen f i obliczeń kwadraturowych. Obejmuje wszystkie trzy „proste” sposoby obrony przed problemami numerycznymi.
0
0
0
0 0
0
0
0 0
0
0
#include <math.h> // plik dołączania dla fabs i sin #include <stdio.h> // plik dołączany dla printf i perror #include <errno.h> /** Adaptacyjna reguła Simpsona, rdzeń rekurencyjny */ float adaptiveSimpsonsAux ( float ( * f )( float ), float a , float b , float eps , float całość , float fa , float fb , float fm , int rec ) { float m = ( a + b ) / 2 , h = ( b - a ) / 2 ; float lm = ( za + m ) / 2 , rm = ( m + b ) / 2 ; // poważny problem liczbowy: nie będzie zbieżny if (( eps / 2 == eps ) || ( a == lm )) { errno = EDOM ; zwrócić w całości ; } float flm = fa ( lm ), frm = fa ( rm ); pływak w lewo = ( h / 6 ) * ( fa + 4 * flm + fm ); pływak w prawo = ( h / 6 ) * ( fm + 4 * frm + fb ); float delta = lewa + prawa - całość ; if ( rec <= && errno != EDOM ) errno = ERANGE ; // granica głębokości zbyt płytka // Lyness 1969 + ekstrapolacja Richardsona; zobacz artykuł if ( rec <= || fabs ( delta ) <= 15 * eps ) return left + right + ( delta ) / 15 ; return adaptiveSimpsonsAux ( f , a , m , eps / 2 , left , fa , fm , flm , rec -1 ) + adaptiveSimpsonsAux ( f , m , b , eps / 2 , right , fm , fb , frm , rec -1 ) ; } /** Adaptacyjne opakowanie reguły Simpsona * (wypełnia oceny funkcji w pamięci podręcznej) */ float adaptiveSimpsons ( float ( * f )( float ), // funkcja ptr do całkowania float a , float b , // interwał [a,b] float epsilon , // tolerancja błędu int maxRecDepth ) { // limit rekurencji errno = ; liczba zmiennoprzecinkowa h = b - a ; jeśli ( h == ) powrót ; float fa = fa ( za ), fb = fa ( b ), fm = fa (( a + b ) / 2 ); float S = ( godz / 6 ) * ( fa + 4 * fm + fb ); return adaptiveSimpsonsAux ( f , a , b , epsilon , S , fa , fb , fm , maxRecDepth ); } /** przykład użycia */ #include <stdlib.h> // dla wrogiego przykładu (funkcja Rand) static int callcnt = ; static float sinfc ( float x ) { callcnt ++ ; zwróć sinf ( x ); } static float frand48c ( float x ) { callcnt ++ ; zwróć drand48 (); } int main () { // Niech I będzie całką sin(x) od 0 do 2 float I = adaptiveSimpsons ( sinfc , , 2 , 1e-5 , 3 ); printf ( "integrate(sinf, 0, 2) = %lf \n " , I ); // wypisz wynik perror ( "adaptiveSimpsons" ); // Czy to się udało? (głębokość = 1 jest zbyt płytka) printf ( "(%d ocen) \n " , callcnt ); wywołanie = ; srand48 ( ); I = adaptacyjne Simpsony ( frand48c , , 0.25 , 1e-5 , 25 ); // losowa funkcja printf ( "integrate(frand48, 0, 0.25) = %lf \n " , I ); perror ( "adaptacyjne Simpsony" ); // nie będzie zbieżny printf ( "(%d ocen) \n " , callcnt ); powrót ; }
Ta implementacja została włączona między innymi do śledzenia promieni C++ przeznaczonego do symulacji lasera rentgenowskiego w Oak Ridge National Laboratory . Wersja ORNL została wzbogacona o licznik połączeń, szablony dla różnych typów danych i opakowania do integracji w wielu wymiarach.
Rakieta
Oto implementacja adaptacyjnej metody Simpsona w Racket z behawioralnym kontraktem na oprogramowanie. Wyeksportowana funkcja oblicza całkę nieoznaczoną dla danej funkcji f .
;; -------------------------------------------------- --------------------------- ;; interfejs, z kontraktem ( dostarcz/umowa [adaptive-simpson ( ->i (( f ( -> real? real? )) ( L real? ) ( R ( L ) ( and/c real? ( >/c L ) ))) ( #:epsilon ( ε real? )) ( r real? )) ] ) ;; -------------------------------------------------- --------------------------- ;; implementacja ( zdefiniuj ( adaptive-simpson f L R #:epsilon [ε . 000000001] ) ( zdefiniuj f@L ( f L )) ( zdefiniuj f@R ( f R )) ( zdefiniuj-wartości ( M f@M całość ) ( simpson-1call-to-f f L f@L R f@R )) ( asr f L f@L R f@R ε całe M f@M )) ;; „wydajna” implementacja ( zdefiniuj ( asr f L f@L R f@R ε całe M f@M ) ( zdefiniuj wartości ( leftM f@leftM left* ) ( simpson-1call-to-f f L f@L M f@M )) ( zdefiniuj wartości ( prawyM f@prawyM prawy* ) ( simpson-1call-to-f f M f@M R f@R )) ( zdefiniuj delta* ( - ( + lewy* prawy* ) całość )) ( warunek [ ( <= ( abs delta* ) ( * 15 ε )) ( + lewy* prawy* ( / delta* 15 )) ] [else ( zdefiniuj epsilon1 ( / ε 2 )) ( + ( asr f L f@L M f@M epsilon1 left* leftM f@leftM ) ( asr f M f@M R f@R epsilon1 right* rightM f@rightM )) ] )) ;; oceń połowę przedziału (1 func eval) ( zdefiniuj ( simpson-1call-to-f f L f@L R f@R ) ( zdefiniuj M ( mid L R )) ( zdefiniuj f@M ( f M )) ( wartości M f@M ( * ( / ( abs ( - R L )) 6 ) ( + f@ L ( * 4 f@M ) f@R )))) ( zdefiniuj ( mid L R ) ( / ( + L R ) 2. ))
Powiązane algorytmy
- Henriksson (1961) to nierekurencyjny wariant reguły Simpsona. „Dostosowuje się”, całkując od lewej do prawej i dostosowując szerokość interwału w razie potrzeby.
- Kuncir's Algorithm 103 (1962) jest oryginalnym rekurencyjnym, dwusiecznym, adaptacyjnym integratorem. Algorytm 103 składa się z większej procedury z zagnieżdżonym podprogramem (pętla AA), która stała się rekurencyjna za pomocą goto . Zabezpiecza przed niedopełnieniem szerokości interwału (pętla BB) i przerywa, gdy tylko zostanie przekroczony określony przez użytkownika eps. Kryterium zakończenia to a \ } a S (2) jest dokładniejszym oszacowaniem.
- Algorytm McKeemana 145 (1962) jest podobnie rekurencyjnym integratorem, który dzieli przedział na trzy zamiast na dwie części. Rekurencja jest napisana w bardziej znany sposób. Algorytm z 1962 roku, który okazał się zbyt ostrożny, używa Wykorzystuje zamiast.
- Lyness (1969) jest prawie obecnym integratorem. Stworzony jako zestaw czterech modyfikacji McKeemana 1962, zastępuje trysekcję bisekcją w celu obniżenia kosztów obliczeniowych (Modyfikacje 1 + 2, zbieżne z integratorem Kuncir) i poprawia oszacowania błędów McKeemana 1962/63 do piątego rzędu (Modyfikacja 3), w sposób związany z regułą Boole'a i metodą Romberga . Modyfikacja 4, nie zaimplementowana tutaj, zawiera zapisy na błąd zaokrąglenia, który pozwala na podniesienie ε do minimum dozwolonego przez obecną precyzję i zwrócenie nowego błędu.