MN14

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania

Całkowanie

Zajmiemy się teraz zadaniem całkowania numerycznego. Polega ono na obliczeniu (a raczej przybliżeniu) całki oznaczonej

S(f)=abf(x)dx,

gdzie <a<b<+, a f należy do pewnej klasy F funkcji rzeczywistych określonych i całkowalnych w sensie Riemanna na całym przedziale [a,b].

Będziemy zakładać, że mamy możliwość obliczania wartości funkcji f, a w niektórych przypadkach również jej pochodnych o ile istnieją. Dokładna całka S(f) będzie więc w ogólności przybliżana wartością A(f), która zależy tylko od wartości f i ewentualnie jej pochodnych w skończonej liczbie punktów.

Kwadratury

Kwadraturami nazywamy funkcjonały liniowe Parser nie mógł rozpoznać (nieznana funkcja „\toR”): {\displaystyle \displaystyle Q:F\toR} postaci

Q(f)=i=0naif(xi),

albo ogólniej

Q(f)=i=0kj=0ni1ai,jf(j)(xi),

gdzie xi są punktami z [a,b], a ai (albo ai,j) są pewnymi współczynnikami rzeczywistymi. Zauważmy, że obliczenia kwadratur są dopuszczalne w naszym modelu obliczeniowym, mogą więc służyć jako sposób przybliżania całki.

Jeden z możliwych sposobów konstrukcji kwadratur jest następujący. Najpierw wybieramy węzły xj (pojedyncze lub wielokrotne), budujemy wielomian interpolacyjny odpowiadający tym węzłom, a następnie całkujemy go. Ponieważ postać wielomianu interpolacyjnego zależy tylko od danej informacji o f, otrzymana w ten sposób wartość też będzie zależeć tylko od tej informacji, a w konsekwencji funkcjonał wynikowy będzie takiej postaci, jak wyżej. Są to tzw. kwadratury interpolacyjne.

Definicja

Kwadraturę QI opartą na węzłach o łącznej krotności n+1 nazywamy interpolacyjną, jeśli

QI(f)=abwf(x)dx,

gdzie wf jest wielomianem interpolacyjnym funkcji f stopnia co najwyżej n opartym na tych węzłach.

Współczynniki kwadratur interpolacyjnych można łatwo wyliczyć. Rozpatrzmy dla uproszczenia przypadek, gdy węzły są jednokrotne. Zapisując wielomian interpolacyjny w postaci jego rozwinięcia w bazie kanonicznej Lagrange'a li (zob. (Uzupelnic: Lagrbaza )), otrzymujemy

QI(f)=abi=0nf(xi)li(x)dx=i=0nf(xi)abli(x)dx,

a stąd i z postaci li,

ai=ab(xx0)(xxi1)(xxi+1)(xxn)(xix0)(xixi1)(xixi+1)(xixn)dx,

0in.

Podamy teraz kilka przykładów.

\noindent Kwadratura prostokątów jest oparta na jednym węźle x0=(a+b)//2,

Q0I(f)=(ba)f(a+b2).

\noindent Kwadratura trapezów jest oparta na jednokrotnych węzłach x0=a, x1=b i jest równa polu odpowiedniego trapezu,

Q1I(f)=T(f)=ba2(f(a)+f(b)).

\noindent Kwadratura parabol (Simpsona) jest oparta na jednokrotnych węzłach x0=a, x1=b, x2=(a+b)//2, i jest równa polu pod parabolą interpolującą f w tych węzłach,

Q2I(f)=P(f)=ba6(f(a)+4f(a+b2)+f(b)).

Zauważmy, że kwadratury trapezów i parabol są oparte na węzłach jednokrotnych i równoodległych, przy czym x0=a i xn=b. Ogólnie, kwadratury interpolacyjne oparte na węzłach równoodległych Parser nie mógł rozpoznać (błąd składni): {\displaystyle \displaystyle x_i=a+(b-a)i/ń} , 0in, nazywamy kwadraturami Newtona--Cotesa.

Błąd kwadratur interpolacyjnych

Zajmiemy się teraz błędem kwadratur interpolacyjnych. Przypomnijmy, że FMn([a,b]) oznacza klasę funkcji (n+1) razy różniczkowalnych w sposób ciągły i takich, że |f(n+1)(x)|M, x.

Twierdzenie

Niech QI będzie kwadraturą 

interpolacyjną opartą na (jednokrotnych lub wielokrotnych) węzłach xi, 0in. Jeśli fFMn([a,b]), to

|S(f)QI(f)|M(n+1)!(ba)n+2.

W klasie FMn([a,b]) maksymalny błąd kwadratury QI wynosi

supfFMn([a,b])|S(f)QI(f)|=M(n+1)!ab|(xx0)(xx1)(xxn)|dx.

\noindent Dowód\quad Korzystając ze znanego nam już wzoru na błąd interpolacji wielomianowej z Lematu Uzupelnic: leblint , mamy

S(f)QI(f)=ab(xx0)(xx1)(xxn)f(x0,x1,,xn,x)dx.

Stąd, jeśli fFMn([a,b]) to

|S(f)QI(f)|ab(ba)n+1M(n+1)!dx=(ba)n+2M(n+1)!.

Ograniczenie górne w dokładnej formule na błąd w klasie FMn([a,b]) wynika bezpośrednio z oszacowania (Uzupelnic: blkwad ). Aby pokazać ograniczenie dolne zauważmy, że dla funkcji g takiej, że g(n+1) przyjmuje na przedziałach (a,x0), (x0,x1), , (xn,b) naprzemiennie wartości M i M mamy

|S(g)QI(g)|=M(n+1)!ab|(xx0)(xx1)(xxn)|dx.

Co prawda, g nie jest w FMn[a,b], ale może być dla dowolnego ϵ>0 przybliżana funkcjami fϵFMn([a,b]) w ten sposób, że całka

ab|(xx0)(xxn)(fg)(n+1)(x)|dxϵ(n+1)!.

Zapisując fϵ=g+(fϵg) mamy

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned |S(f_\epsilon)\,-\,Q^I(f_\epsilon)| &\le & |S(g)\,-\,Q^I(g)|\,+\, |S(f_\epsilon-g)-Q^I(f_\epsilon-g)| \\ &\le & \frac M{(n+1)!}\int_a^b |(x-x_0)\cdots(x-x_n)| \,dx\,+\,\epsilon, \endaligned}

co wobec dowolności ϵ daje dowód twierdzenia.

W szczególnych przypadkach kwadratur trapezów T i parabol P możemy otrzymać innego rodzaju formuły na błąd.

Twierdzenie

(i) Jeśli fC(2)([a,b]),

to dla kwadratury trapezów mamy

S(f)T(f)=(ba)312f(2)(ξ1).

(ii) Jeśli fC(4)([a,b]), to dla kwadratury parabol mamy

S(f)P(f)=(ba)52280f(4)(ξ2).

(ξ1,ξ2[a,b]).

\noindent Dowód\quad (i) Ze wzoru (Uzupelnic: blkwad )

S(f)T(f)=ab(xa)(xb)f(a,b,x)dx.

Ponieważ funkcja xf(a,b,x) jest ciągła, a wielomian (xa)(xb) przyjmuje jedynie wartości nieujemne, można zastosować twierdzenie o wartości średniej dla całki aby otrzymać

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned S(f)\,-\,T(f) &= f(a,b,c)\int_a^b (x-a)(x-b)\,dx \\ &= -\frac{f^{(2)}(\xi_1)}{2!}\frac{(b-a)^3}6, \endaligned}

dla pewnych c,ξ1[a,b].

\noindent (ii) Niech wf,2Π2 i wf,3Π3 będą wielomianami interpolacyjnymi funkcji f odpowiednio dla węzłów a,b,(a+b)//2 oraz a,b,(a+b)//2,(a+b)//2. Wtedy

wf,3(x)=wf,2(x)+f(a,b,a+b2,a+b2)(xa)(xa+b2)(xb).

Wobec

ab(xa)(a+b2)(xb)dx=0

mamy

P(f)=abwf,2(x)dx=abwf,3(x)dx.

Stąd i ze wzoru na błąd interpolacji Hermite'a otrzymujemy

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned \lefteqn{ S(f)\,-\,P(f)\;=\;\int_a^b (f-w_{f,3})(x)\,dx } \\ && =\; \int_a^b (x-a)\Big(x-\frac{a+b}2\Big)^2(x-b) f\Big(a,b,\frac{a+b}2,\frac{a+b}2,x\Big)\,dx. \endaligned}

Ponieważ wielomian (xa)(x(a+b)//2)2(xb) jest niedodatni na [a,b], możemy znów zastosować twierdzenie o wartości średniej. Mamy

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned S(f)\,-\,P(f) &= f\Big(a,b,\frac{a+b}2,\frac{a+b}2,c\Big) \\ && \qquad\qquad\qquad \int_a^b (x-a)\Big(x-\frac{a+b}2\Big)^2(x-b)\,dx \\ &= -\frac{f^{(4)}(\xi_2)}{4!}\frac{(b-a)^5}{120}, \endaligned}

co kończy dowód.

Kwadratury złożone

Podobnie jak w przypadku zadania interpolacji chcielibyśmy, aby błąd kwadratur malał do zera, gdy liczba węzłów rośnie do nieskończoności. Można to osiągnąć stosując np. kwadratury złożone. Są to kwadratury, które powstają przez scałkowanie funkcji kawałkami wielomianowej interpolującej f.

Prostym przykładem kwadratury złożonej jest suma Riemanna,

Q¯(f)=i=0n(ti+1ti)f(xi),

gdzie a=t0<t1<<tn+1=b oraz xi[ti,ti+1]. Jeśli średnica podziału, max0in(titi1), maleje do zera to limnQ¯(f)=S(f).

Będziemy rozpatrywać kwadratury złożone postaci

Q¯(f)=abw¯f(x)dx,

gdzie w¯f jest kawałkami wielomianem z Rozdziału Uzupelnic: kawwiel . To znaczy, dla danego n kładziemy ti=a+(ba)i//k, 0ik, a następnie dla każdego i wybieramy dowolne węzły xi,j[ti1,ti], 0jr. Wtedy w¯f jest na każdym przedziale wielomianem interpolacyjnym funkcji f stopnia co najwyżej r opartym na węzłach xi,j. Kwadratura Q¯ korzysta z węzłów o łącznej krotności nk(r+1).

Twierdzenie

Błąd kwadratury złożonej 

Q¯(f) w klasie FMr([a,b]) jest ograniczony przez

supfFMr([a,b])|S(f)Q¯(f)|(ba)r+2kr+1M(r+1)!C(1n)r+1,

gdzie

C=M(r+1)r+1(ba)r+2(r+1)!.

\noindent Dowód\quad Twierdzenie to jest bezpośrednim wnioskiem z Twierdzenia Uzupelnic: twblkw . Mamy bowiem

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned |S(f)-\bar Q(f)| &\le & \sum_{i=1}^k \int_{t_{i-1}}^{t_i} |f(x)-\bar w_f(x)|\,dx \\ &\le & \sum_{i=1}^k \Big(\frac{b-a}{k}\Big)^{r+2} \frac M{(r+1)!} \,=\, \frac{(b-a)^{r+2}}{k^{r+1}}\frac M{(r+1)!}, \endaligned}

co kończy dowód.

W klasie FMr([a,b]) błąd kwadratur złożonych jest rzędu n(r+1), czyli jest taki sam jak błąd interpolacji kawałkami wielomianowej. I tak jak przy interpolacji można pokazać, że błąd każdej innej metody całkowania korzystającej jedynie z wartości funkcji w n punktach nie może w klasie FMr([a,b]) maleć szybciej niż n(r+1), zob. U. Uzupelnic: optzlo . Podane kwadratury złożone mają więc optymalny rząd zbieżności.

Zajmiemy się teraz błędem szczególnych kwadratur złożonych, mianowicie złożonych kwadratur trapezów T¯k i parabol P¯k. Powstają one przez zastosowanie na każdym przedziale [ti1,ti] odpowiednio kwadratur trapezów T i parabol P. Jak łatwo się przekonać,

T¯k(f)=bak(f(a)+f(b)2+j=1k1f(jk)),

oraz

P¯k(f)=ba3k(f(a)+f(b)2+j=1k1f(jk)+2j=1kf(2j12k)).

Twierdzenie

  • Jeśli fC(2)([a,b]), to
S(f)T¯k(f)=(ba)212k2f(2)(ξ1).
  • Jeśli fC(4)([a,b]), to
S(f)P¯k(f)=(ba)42280k4f(4)(ξ2).

Dowód

Dla kwadratury trapezów mamy

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned \lefteqn{ S(f)\,-\,\bar T_k(f) \;=\; -\sum_{i=1}^k \frac{(b-a)^3}{12 k^3}f^{(2)}(\alpha_i) } \\ && =\;-\frac{(b-a)^2}{12 k^2}\sum_{i=1}^k \frac{b-a}k f^{(2)}(\alpha_i) \,=\, -\frac{(b-a)^2}{12 k^2} f^{(2)}(\xi_1), \endaligned}

a dla kwadratury parabol podobnie

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned \lefteqn{ S(f)\,-\,\bar P_k(f) \;=\; -\sum_{i=1}^k \frac{(b-a)^5}{2280 k^5}f^{(4)}(\beta_i) } \\ && =\; -\frac{(b-a)^4}{2280 k^4}\sum_{i=1}^k \frac{b-a}k f^{(4)}(\beta_i) \,=\, -\frac{(b-a)^4}{2280 k^4} f^{(4)}(\xi_2). \endaligned}

Kwadratura parabol ma więc optymalny rząd zbieżności nie tylko w klasie FM2([a,b]), ale też w FM3([a,b]).

Przyspieszanie zbieżności kwadratur

W praktyce często stosuje się obliczanie kwadratur poprzez zagęszczanie podziału przedziału [a,b]. Na przykład, dla złożonej kwadratury trapezów zachodzi następujący wygodny wzór rekurencyjny:

T¯2k=12(T¯k(f)+baki=1kf(2i12k)).

Pozwala on obliczyć T¯2k(f) na podstawie T¯k(f) poprzez "doliczenie" wartości funkcji w punktach "gęstszej" siatki. W ten sposób możemy obserwować zachowanie się kolejnych przybliżeń T¯2s(f) (s0) całki S(f). Jest to szczególnie istotne wtedy, gdy nie mamy żadnej informacji a priori o fC([a,b]), a przez to nie potrafimy oszacować liczby n węzłów, dla której osiągniemy pożądaną dokładność, zob. U. Uzupelnic: kwas .

Jeśli funkcja jest więcej niż dwa razy różniczkowalna, użycie złożonych kwadratur trapezów zdaje się tracić sens. Wtedy istnieją przecież kwadratury, których błąd maleje do zera szybciej niż n2. Okazuje się jednak, że kwadratury T¯k mogą być podstawą dla prostej rekurencyjnej konstrukcji innych kwadratur posiadających już optymalną zbieżność. Konstrukcja ta bazuje na następującym ważnym lemacie.

Leonhard Euler
Zobacz biografię
Maclaurin
Zobacz biografię

Lemat Formuła Eulera-Maclaurina

Dla funkcji fC(2m+2)([a,b]) błąd złożonej kwadratury trapezów T¯k wyraża się wzorem

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned S(f)\,-\,\bar T_k(f) &= \sum_{i=1}^{m} c_ih^{2i} \Big(f^{(2i-1)}(b)-f^{(2i-1)}(a)\Big) \\ &&\qquad\qquad\qquad \,+\,c_{m+1}h^{2m+2}(b-a)f^{(2m+2)}(\xi_{m,k}), \endaligned}

gdzie h=(ba)//k, ξm,k[a,b], a ci są pewnymi stałymi liczbowymi. Mamy c1=1//12, c2=1//720 i, ogólnie, ci=Bi//(2i)!, gdzie Bi są tzw. liczbami Bernoulliego.

Dowód tego lematu pominiemy.

Formułę Eulera-Maclaurina można przepisać w postaci

S(f)T¯k(f)=i=1mci(0)(f)k2i+cm+1,k(0)(f)k(2m+2),

gdzie ci(0)(f)=ci(ba)2i(f(2i1)(b)f(2i1)(a)), 1im, oraz cm+1,k(0)(f)=cm+1(ba)2m+2f(2m+2)(ξm+1,k). Zauważmy przy tym, że jeśli fFM2m+1([a,b]) to współczynniki cm+1,k(0)(f) są wspólnie ograniczone przez cm+1(ba)2m+2M.

Definiując teraz kwadraturę

T¯k1(f)=4T¯2k(f)T¯k(f)3,

dla fC(4)([a,b]) mamy

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned S(f)\,-\,\bar T^1_k(f) &= \frac{4\,(S(f)-\bar T_{2k}(f)- (S(f)-\bar T_k(f))}{3} \\ &= \frac 43\left(\frac{c^{(0)}_1(f)}{4k^2}+ \frac{c^{(0)}_{2,2k}(f)}{4^2k^4}\right)\,-\, \frac 13\left(\frac{c^{(0)}_1(f)}{k^2}+ \frac{c^{(0)}_{2,k}(f)}{k^4}\right) \\ &= \frac{c^{(1)}_{2,k}(f)}{k^4}, \endaligned}

gdzie c2,k(1)(f)=(1//12)c2,2k(0)(f)(1//3)c2,k(0)(f) i jest wspólnie ograniczone dla fFM3([a,b]). Kwadratura Tk1 ma więc optymalny w FM3([a,b]) rząd zbieżności k4. Proces ten można kontynuować dalej tworząc kolejne kwadratury o coraz to wyższym rzędzie zbieżności. Dokładniej, połóżmy T¯k0(f)=T¯k(f) oraz, dla s1,

T¯ks(f)=4sT¯2ks1(f)T¯ks1(f)4s1.

Wtedy, dla fFM2m+1([a,b]), rząd zbieżności kwadratury T¯km wynosi k(2m+2). Rzeczywiście sprawdziliśmy, że jest to prawdą dla m=0,1. Niech m2. Postępując indukcyjnie ze względu na s=1,2,,m mamy

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned \lefteqn{ S(f)\,-\,\bar T^s_k(f) \;=\; \frac{ 4^s(S(f)-\bar T^{s-1}_{2k}(f))- (S(f)-\bar T^{s-1}_k(f)) }{ 4^s\,-\,1 } } \\ &&=\; \left( 4^s\,\left(\sum_{i=s}^{m}c_i^{(s-1)}(f)(2k)^{-2i}+ c_{m+1,2k}^{(s-1)}(f)(2k)^{-(2m+2)}\right)\right. \\ && \left.\quad \,-\,\left( \sum_{i=s}^mc_i^{(s-1)}(f)k^{-2i}+ c_{m+1,k}^{(s-1)}(f)k^{-(2m+2)} \right) \right)\,\frac{1}{4^s\,-\,1}\\ &&=\; \sum_{i=s+1}^m c_i^{(s)}(f)k^{-2i}\,+\, c_{m+1,k}^{(s)}(f)k^{-(2m+2)}, \endaligned}

ponieważ współczynniki przy k2s redukują się. ci(s)(f) są tutaj pewnymi nowymi stałymi, a cm+1,k(s)(f) może być w klasie FM2m+1([a,b]) ograniczona przez stałą niezależną od f. Ostatecznie, dla s=m mamy więc

S(f)T¯km(f)=cm+1,k(m)(f)k(2m+2)

i w klasie FM2m+1([a,b])

|S(f)T¯km(f)|cmk(2m+2)

dla pewnej stałej cm niezależnej od f.

Zauważmy jeszcze, że T¯km wykorzystuje n=k2m+1 wartości f w punktach równoodległych na [a,b], co oznacza, że w terminach n rząd zbieżności wynosi też n(2m+2), a więc jest optymalny w klasie FM2m+1([a,b]).

Kwadratury T¯ks nazywane są kwadraturami Romberga. Dla danej funkcji f można je łatwo konstruować budując następującą tablicę trójkątną:

T¯10(f)T¯20(f)T¯11(f)T¯40(f)T¯21(f)T¯12(f)T¯80(f)T¯41(f)T¯22(f)T¯13(f)T¯2s0(f)T¯2s11(f)T¯2s22(f)T¯2s33(f)T¯1s(f),

w której pierwsza kolumna jest tworzona indukcyjnie zgodnie ze wzorem (Uzupelnic: indtrap ), a kolejne zgodnie z (Uzupelnic: indRom ).

Biblioteki

W Octave dostępne są jedynie procedury całkujące funkcje skalarne jednej zmiennej na odcinku:

I=abf(x)dx.

Robi to funkcja DQAGP ze znakomitego pakietu QUADPACK. Najlepiej od razu posłużmy się przykładem.

Przykład: Prosta całka funkcji jednej zmiennej

Przypuśćmy, że chcemy obliczyć całkę I=01F(x)dx, gdzie np. F(x)=sin(23x)+(1x2)1/2. W tym celu najpierw implementujemy F w Octave:

 
function y = F(x)
	y = sin(23*x)+1/sqrt(1-x^2);
endfunction

Aby teraz obliczyć całkę I=01F(x)dx, wystarczy wywołać

 
I = quad("F", 0, 1);

W rzeczywistości, podobnie jak w przypadku funkcji fsolve, funkcja quad zwraca więcej informacji, można jej także przekazać dodatkowe parametry. I tak, jeśli chcemy ustawić poziom tolerancji błędu obliczenia całki:

Parser nie mógł rozpoznać (błąd składni): {\displaystyle \displaystyle |I - \text{\lstoct{quad(...)}}| \leq \max\{\text{ATOL}, \text{RTOL}\cdot |I|\} }

z wartościami ATOL = 1e-3 i RTOL = 1e-6, to wywołamy funkcję przekazując jej te parametry następująco:

 
quad("F", 0, 1, [1e-3, 1e-6]);

Musimy jednak pamiętać, by pojęcia tolerancji "błędu" nie traktować zbyt dosłownie: tym, co naprawdę kontroluje quad podczas wyznaczania wartości całki, jest jedynie pewien estymator błędu, dlatego wartość tolerancji należy zawsze wybierać w sposób konserwatywny, czyli z pewnym zapasem bezpieczeństwa, np.

Jeśli chcemy wyznaczyć wartość całki z błędem bezwzględnym na

poziomie 106, ustawimy -- na wszelki wypadek -- ATOL = 1e-7, a nie, prostodusznie, ATOL = 1e-6... Musimy także pamiętać, że choć są bardzo mało prawdopodobne do spotkania w praktyce, to jednak istnieją wyuzdane funkcje, dla których estymator błędu może dać całkowicie fałszywe wartości,

przez co i obliczona całka może być obarczona dowolnie wielkim błędem.

Dodajmy, że quad doskonale radzi sobie także z funkcjami z osobliwościami (o ile tylko ją o nich uprzedzimy). Przykładowo, scałkujmy funkcję ze złośliwą nieciągłością w zerze:

Parser nie mógł rozpoznać (nieznana funkcja „\begincases”): {\displaystyle \displaystyle f(x) = \begincases \frac{1}{x}, \qquad x\neq 0,\\ 10^6, \qquad x=0. \endcases }

Oczywiście 11f(x)dx=0, tymczasem definiując

 
function y = osobliwa(x)
if (x != 0.0)
	y = 1/x;
else
	y = 1e6;
endif
endfunction

quad("osobliwa", -1, 1)

daje w rezultacie

 
 ABNORMAL RETURN FROM DQAGP
ans = -81.098

-- błędny wynik i (na szczęście!) także komunikat o jakichś problemach procedury całkującej. Jeśli jednak wspomożemy quad informacją o tym, że w x=0 czai się osobliwość, wszystko przebiegnie już gładko:

 
quad("osobliwa", -1, 1, [1e-8 1e-8], 0)
ans = 0

(1e-8 jest żądaną tolerancją błędu, równą wartości domyślnej).

QUADPACK

Właściwie jedynym klasycznym pakietem, jaki mamy do dyspozycji jest ponaddwudziestoletni QUADPACK . Jest to zestaw kilkunastu procedur fortranowskich, służących obliczaniu typowych całek jednowymiarowych, w tym kilka ogólnego stosowania:

\begintable [ht]

Typ całki || Procedura QUADPACKa

 abf(x)  ||  DQNG, DQAG, DQAGS, DQAGP 

f(x) || DQAGI

abf(x)cos(ωx) || DQAWO

abf(x)xc || DQAWC

\caption{Tabela najważniejszych procedur QUADPACKa i ich zastosowań.} \endtable

oraz spora liczba podstawowych kwadratur, na których oparto te ogólne. Nazwy procedur rozszyfrowuje się podobnie jak nazwy procedur LAPACKa, zatem

  • przedrostek D w nazwie każdej procedury wymienionej w

Tabeli Uzupelnic: table:quadpack (np. DQAGI) oznacza, że będzie działać na liczbach typu double (całkując funkcję f zwracającą wartości tego samego typu). Gdybyśmy chcieli użyć pojedynczej precyzji, użylibyśmy nazwy procedury bez przedrostka.

  • Kolejna litera, Q, oczywiście oznacza kwadraturę

(Quadrature ).

  • Trzecia litera - A lub N - oznacza, odpowiednio,

kwadraturę adaptacyjną lub nieadaptacyjną. Jak wiadomo, w praktyce lepiej sprawdzają się kwadratury adaptacyjne, potrafiące w jakiejś mierze dostosować się do przebiegu funkcji podcałkowej. Kwadratury nieadaptacyjne nie mają tej własności, są natomiast tańsze, warto więc je stosować w szczególnych przypadkach, gdy wiemy a priori, że adaptacja niewiele pomoże: np. do wolno zmiennych funkcji.

  • Pozostałe litery precyzują typ liczonej całki i zakres ingerencji

użytkownika; G --- "zwykła" całka, bez wagi, W --- całka z wagą, O --- oscylacyjną, C --- wartość główna całki (tzw. całka Cauchy'ego), I --- przedział nieskończony, S --- możliwe osobliwości, P --- użytkownik poda listę punktów, gdzie są osobliwości.

GSL

Biblioteka GSL reimplementuje podstawowe procedury QUADPACKa w języku C, zob. {GSL-reference}, gdzie zostały one bardzo przystępnie i szczegółowo opisane. Procedury GSL mają nazwy analogiczne, jak procedury QUADPACKa, ale z przedrostkiem gsl_integration, jak w poniższym przykładzie, gdzie wywołamy odpowiednik procedury DQAG: funkcję gsl_integration_qag.

#include <stdio.h>
#include <math.h>
#include <gsl/gsl_integration.h>

double F(double X, void * param) /* wrapper dla funkcji sin(x)/x */
{
	return(sin(X)/X);
}

int main(void)
{
	gsl_function f; /* argument z funkcją podcałkową */

	double A,ABSERR,B, EPSABS,EPSREL,RESULT;
	int IER,NEVAL;

	gsl_integration_workspace *workspace;

	int KEY, LIMIT; 

	/* przygotowujemy argument z funkcją podcałkową */
	f.function = &F;
	
	A = 0.0E0; B = 10*M_PI; /* przedział całkowania */
	EPSABS = 0.0E0; EPSREL = 1.0E-3; /* tolerancja błędu */

	/* parametry specyficzne dla QAG */
	KEY = 1; /* tzn. użyj minimalnej liczby punktów kwadratury bazowej */
	LIMIT = 100; /* maksymalny podział przedziału całkowania */
	workspace  = gsl_integration_workspace_alloc(LIMIT);
	
	/* całkujemy: QAG! */

	IER = gsl_integration_qag(&f, A, B, EPSABS, EPSREL, 
					LIMIT, KEY, workspace, &RESULT, &ABSERR);

	if (IER != 0)
		fprintf(stderr,"GSL_QAG: Kłopoty z całkowaniem\n");
	fprintf(stderr,"Całka: 
	gsl_integration_workspace_free(workspace);
	
	return(0);
}

W powyższym przykładzie specjalnie pozostawiliśmy oznaczenia wykorzystywane w poprzednim programie. Jak widać, funkcje całkujące GSL mają bardzo podobną składnię do odpowiadających im funkcji QUADPACKa.

Miłym rozszerzeniem funkcjonalności jest możliwość przekazywania parametrów do wnętrza funkcji podcałkowej.


Numeryczne równania różniczkowe

Dla rozwiązywania równań różniczkowych zwyczajnych,

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned y'(t) &= f(y(t),t), \qquad t\in (t_0,T]\\ y(t_0) &= y_0, \endaligned}

korzystamy w Octave z funkcji lsode. Jak już zdążyliśmy się przyzwyczaić, także ta funkcja jest jedynie zgrabnym interfejsem dla procedury DLSODE z pakietu ODEPACK autorstwa A.Hindmarscha. Domyślnie wybierana jest metoda BDF (dobra dla równań trudniejszych, tzw. sztywnych równań różniczkowych, lecz względnie kosztowna). Należy dodać, że solver kontroluje zachowanie się rozwiązania i sam w trakcie obliczeń może zmienić wartości pewnych swoich parametrów w zależności od bieżących potrzeb.

Wprowadzenie do numerycznych metod rozwiązywania RRZ

LSODE jest pakietem, którego jądrem obliczeniowym są tzw. schematy różnicowe. Nie miejsce tu na dokładną analizę numeryczną metod rozwiązywania równań różniczkowych zwyczajnych (zainteresowanych odsyłam do doskonałej monografii Hairer et. al , bądź do książki Palczewskiego), ograniczymy się tutaj do pewnych intuicji. Będą nam one potrzebne, by móc świadomie wpływać na pewne parametry metody w celu uzyskania lepszego (lub szybszego) rozwiązania (zazwyczaj te dwie własności stoją w opozycji względem siebie).

Jawne schematy różnicowe

Najprostszym schematem aproksymacji równania różniczkowego zwyczajnego jest schemat Eulera: gdy pochodną przybliżymy ilorazem różnicowym, dostajemy (przybliżone) równanie

Leonhard Euler
Zobacz biografię
yh(t+h)yh(t)h=f(yh(t),t).

(dla podkreślenia, że spełniająca je funkcja jest jedynie przybliżeniem dokładnego rozwiązania y, oznaczyliśmy je yh).

Oznaczając dalej t1=t0+h, t2=t1+h, ... tn=tn1+h zauważamy, że
z powyższej równości wynika wzór rekurencyjny

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned y_{n+1} - y_{n} = h\cdot f_n, \endaligned}

gdzie yn=yh(tn)y(tn) oraz fn=f(yn,tn). Naturalnie, warunkiem początkowym pozostaje y0. Jest to schemat jawny, gdyż yn+1 daje się wyznaczyć wprost z równania -- jest zadane w postaci nieuwikłanej.

Uzasadnieniem dla schematu Eulera jest to, że iloraz yn+1ynh dość dobrze przybliża pochodną, gdy h jest małe:

yn+1ynh=y(tn)+O(h).

Oczywiście pochodną y(tn) możemy przybliżać na różne sposoby, korzystając np. z wielomianu interpolacyjnego dla y. Takie podejście prowadzi do klasy metod wielokrokowych, ogólnej postaci

j=0kαkyn+k=hj=0kβkfn+k,

(metoda k-krokowa). Inną ważną klasą metod są metody jednokrokowe tzw. Rungego--Kutty.

Niejawne schematy różnicowe

Bardzo podobnie wyglądający niejawny schemat Eulera,

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned y_{n+1} - y_{n} = h\cdot f_{n+1}, \endaligned}

ma znacząco różne własności. Pierwsza, która rzuca się w oczy to konieczność rozwiązywania równania nieliniowego na kazdym kroku schematu (co znacząco podnosi jego koszt), bo tym razem yn+1 zadaje się wzorem niejawnym: yn+1yn=hf(yn+1,tn+1). Za chwilę pokażemy, że czasem opłaca się zapłacić tę cenę, gdyż schematy niejawne mają pewne bardzo pożądane własności.

Lokalna dokładność schematu

Każdy schemat rozwiązywania

równania zwyczajnego jest schematem przybliżonym, a jakość lokalnego przybliżenia równania schematem można ściśle określić przy użyciu wielkości całkowitej zwanej rzędem schematu: im wyższy, tym większa może być wartość h, przy której schemat dostatecznie dobrze przybliżać będzie równanie. Przykładowo, schemat Eulera stosunkowo mało dokładnie aproksymuje równanie dla niezbyt małych h: aby uzyskać rozwiązanie obarczone możliwie małym błędem, należy zazwyczaj wybierać dostatecznie mały krok schematu h, a to znaczy, że w końcowym efekcie będziemy musieli wykonać bardzo wiele kroków schematu, co podniesie jego całkowity koszt.

Należy pamiętać, że schematy wysokiego rzędu nie muszą prowadzić do uzyskania dokładniejszego rozwiązania. Więcej szczegółów na ten temat Czytelnik odnajdzie w fachowym monografiach, pamiętając, że metody użyte w Octave potrafią do pewnego stopnia automatycznie dopasować krok całkowania i rząd schematu tak, by uzyskać możliwie dobre przybliżenie prawdziwego rozwiązania równania.

Równania sztywne

Schematy jawne okazują się niewystarczająco skuteczne dla pewnych ważnych klas równań, tzw. równań sztywnych. Niektórzy autorzy wręcz definiują równania sztywne jako te, dla których schematy jawne nie są skuteczne tzn. np. schemat jawny z adaptacyjnym wyborem długości kroku musi stosować patologicznie krótki krok.

Rozważmy sprawę sztywności na przykładzie układu równań różniczkowych liniowych

dYdt=ΛY

(gdzie Y(t)=(y1(t),,yK(t))TRK, a Λ jest macierzą K×K).

Przypuśćmy, że macierz Λ jest diagonalizowalna, a liczby λ1,,λK są jej wartościami własnymi. Gdy wszystkie λi mają ujemne części rzeczywiste, składowe yi rozwiązania gasną do zera, gdy t. Rozsądne jest, aby od schematu wymagać podobnej właściwości, tzn. by składowe yi,nh rozwiązania przybliżonego miały własność yi,nh0, gdy n. Okazuje się, że dla jawnej metody Eulera tak będzie, gdy

h2maxiλi.

A więc długość kroku czasowego jawnej metody Eulera nie może być zbyt wielka, co jest szokujące, bo dla dużych wartości t rozwiązania stabilizują się wokół zera i wydawać by się mogło, że to pozwoliłoby zwiększyć wówczas krok schematu. Niestety tak nie jest i krok schematu musimy dostosować do najmniejszej skali czasowej, która objawia się jedynie na początku ewolucji układu. I to jest cecha charakterystyczna dla ogólnie nazywanych równań sztywnych, nie tylko liniowych jak w powyższym przykładzie.

Tymczasem, dla niejawnego schematu Eulera nie ma w omawianym przykładzie żadnego ograniczenia na krok schematu! Ogólniej, schematy niejawne, (czy to wielokrokowe typu Adamsa--Bashfortha lub BDF, czy niejawne metody Rungego--Kutty) lepiej nadają się do rozwiązywania równań sztywnych, niż odpowiadające im analogiczne schematy jawne. Ceną, jaką płacimy za stosowanie schematu niejawnego, a tym samym za uzyskanie mniejszych ograniczeń stabilnościowych na długość użytego kroku schematu, jest większy (być może nawet znacznie większy) koszt wykonania jednego kroku schematu, bo tym razem na każdym kroku schematu musimy rozwiązywać równanie algebraiczne, w ogólnym przypadku nieliniowe. lsode domyślnie używa właśnie schematu BDF, by użytkownik był z góry zabezpieczony na wypadek sztywności rozwiązywanego zadania.

Niestety równania sztywne pojawiają się dość często w zastosowaniach, co generalnie wiąże się z obecnością różnych skal czasowych ewolucji poszczególnych składników rozważanego modelu. Sztywne są m.in. układy równań różniczkowych zwyczajnych powstałe po dyskretyzacji (przestrzennej) równań różniczkowych cząstkowych typu parabolicznego. Również wiele układów równań różniczkowych zwyczajnych spotykanych w zastosowaniach, np. w modelach reakcji chemicznych, jest z natury sztywnych. Podkreślmy raz jeszcze, że z analizy teoretycznej i z praktycznych doświadczeń wynika, że dla tej klasy zadań niezbędne jest stosowanie schematów niejawnych, np. typu BDF, w których yn+1 będzie zadane w sposób uwikłany.

Biblioteki

Działanie instrukcji Octave'a lsode najlepiej prześledzić na konkretnym przykładzie.

Rozwiązujemy równanie Van der Pola

Rozważmy równanie różniczkowe zwyczajne rzędu drugiego, opisujące tak zwany oscylator Van der Pola:

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned y'' + \epsilon (y^2-1)y' + y = 0. \endaligned}

Ponieważ metody dla równań zwyczajnych zazwyczaj pisze się dla równań pierwszego rzędu (i takie są procedury Octave), zapiszemy najpierw to równanie w postaci układu równań pierwszego rzędu:

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned y_1' &= y_2\\ y_2' &= - \epsilon (y_1^2 - 1) y_2 - y_1. \endaligned}

Jest to równanie różniczkowe postaci

y(t)=f(y(t),t),t[t0,T],

gdzie u nas y jest wektorem o dwóch współrzędnych, y=(y1,y2)T. Aby znaleźć numeryczne rozwiązanie tego równania w Octave, najpierw zdefiniujemy M-funkcję, która oblicza prawą stronę f(y,t):

 
function ydot = vanderpol(y,t)
global epsilon;

ydot = [y(2);
        epsilon*(1-y(1)^2)*y(2) - y(1)];
endfunction

Zwróćmy uwagę na wykorzystanie zmiennej globalnej epsilon, dzięki której będzie możliwe przekazanie wartości parametru ϵ do wnętrza funkcji vanderpol(y,t). Procedura rozwiązująca równanie wymaga funkcji, której argumentami są tylko i wyłącznie liczba t i wektor y o tylu współrzędnych ile jest równań w układzie.

Choć trudno w to uwierzyć, to jednak w zasadzie najtrudniejszy krok mamy za sobą! Teraz, aby rozwiązać równanie, wystarczy wywołać funkcję

lsode("vanderpol",y0,t),

gdzie y0 będzie wektorem wartości początkowych rozwiązania: y(t0)=y0, a wektor t - zestawem kolejnych punktów (pierwszym punktem w zestawie musi być oczywiście chwila początkowa t0) w przedziale czasowym [t0,T], w których zechcemy poznać wartości rozwiązania: naturalnie, aby dobrze przedstawić rozwiązanie graficznie, musimy wyznaczyć je w dostatecznie wielu punktach.

CVODE

Z dobrych bibliotek napisanych w C należy wymienić pakiet CVODE. Omówienie jego możliwości wykracza jednak poza zakres niniejszego wykładu.