Ensimmäisen kertaluvun yhtälön numeerinen ratkaiseminen

Eulerin menetelmä alkaurvoprobleeman y'  =  f(x, y), y((Subscript[x, 0]))  =  (Subscript[y, 0]) ratkaisemiseksi voidaan ohjelmoida Mathematicalle euler-nimiseksi funktioksi seuraavalla tavalla (solu on ajettava, jotta määritelmä tulee voimaan):

euler[f_, x0_, y0_, h_, xend_] := 
      Module[{X, Y, kend}, 
X[0] = 
          x0; 
X[k_] := X[k] = 
            X[k - 1] + h; 
Y[0] = 
          y0; 
Y[k_] := Y[k] = 
            Y[k - 1] + 
              h f[X[k - 1], Y[k - 1]]; 
kend = 
          Round[(xend - x0)/h]; 
Table[{X[k], 
            Y[k]}, {k, 0, kend}]]

Funktion ensimmäinen argumentti on differentiaaliyhtälön oikean puolen funktio f Mathematican funktioksi määriteltynä, kaksi seuraavaa määrittävät alkuehdon, neljäs on askelpituus. Laskenta tapahtuu välillä [x0,xend].

Funktion runko muodostuu Module-komennosta, jonka sisällä on ensin aaltosuluissa lueteltu paikalliset muuttujat, jotka eivät näy funktion ulkopuolelle. Seuraavilla neljällä rivillä on kaavat, joilla paikallisten funktioiden X ja Y arvoiksi määritellään numeerisen ratkaisun hilapisteiden koordinaatit. Toiseksi viimeisellä rivillä lasketaan tarvittavien askeleiden lukumäärä. Viimeisellä rivillä tapahtuu varsinainen laskenta: Hilapisteiden koordinaatit todella lasketaan ja sijoitetaan taulukkoon, joka tulee Module-kutsun arvoksi ja myös euler-funktion arvoksi.

Rivit

X[k_]:=X[k]=X[k-1]+h; ja
Y[k_]:=Y[k]=Y[k-1]+h f[X[k-1],Y[k-1]];

ovat hieman erikoiset: niissä näyttäisi olevan kaksi sijoituskäskyä tehtynä symboleilla := ja = . Kyseessä on rerkursiivinen määrittely (symboli :=), jossa esimerkiksi arvon X[k] laskeminen palautetaan arvon X[k-1] laskemiseen, tämä edelleen arvon X[k-2] laskemiseen jne., kunnes päädytään arvoon X[0], joka on aiemmin asetettu. Jotta jokaista arvoa — esimerkiksi seuraavaa arvoa X[k+1] — laskettaessa ei uudelleen tarvitsisi laskea kaikkia arvoja alkuarvoon X[0] saakka, laskettu arvo X[k] talletetaan funktion X arvoksi operaattorilla = . (Vrt. arvon antamiseen komennossa X[0]=x0; .)

euler-funktion käyttö tapahtuu antamalla sille tarvittavat argumentit, jolloin funktio palauttaa taulukon, jossa on hilapisteiden koordinaatit. Argumentit voidaan tallettaa muillekin nimille kuin funktion määrittelyssä käytetyille tai antaa myös suoraan numeroina (esimerkiksi askel= 0.1, euler[Function[{x,y},2 x y],0,1,askel,2]).

f[x_, y_] := 2 x y

h = 0.1

x0 =  0

y0  =  1

xend =  2

eulerdata =  euler[f, x0, y0, h, xend]

Datan perusteella voidaan piirtää kuva ratkaisua approksimoivista pisteistä.

eulerkuva =  ListPlot[eulerdata]

Samaan tapaan voidaan ohjelmoida parannettu Eulerin menetelmä, Rungen – Kuttan menetelmä ja Adamsin – Bashforthin menetelmä:

pareuler[f_, x0_, y0_, h_, xend_] := 
      Module[{X, Y, kend}, 
X[0] = 
          x0; 
X[k_] := X[k] = 
            X[k - 1] + h; 
Y[0] = 
          y0; 
Y[k_] := Y[k] = 
            Y[k - 1] + 
              h/2 (f[X[k - 1], Y[k - 1]] + 
                    f[X[k], 
                      Y[k - 1] + 
                        h f[X[k - 1], 
                            Y[k - 
                                1]]]); 
kend = 
          Round[(xend - x0)/h]; 
Table[{X[k], 
            Y[k]}, {k, 0, kend}]]

rungekutta[f_, x0_, y0_, h_, xend_] := 
      Module[{X, Y, kend}, 
X[0] = 
          x0; 
X[k_] := X[k] = 
            X[k - 1] + h; 
Y[0] = 
          y0; 
Y[k_] := Y[k] = 
            Y[k - 1] + 
              1/6 Module[{k1, k2, k3, k4}, 
                  k1 = h f[X[k - 1], 
                        Y[k - 1]]; 
k2 = 
                    h f[X[k - 1] + h/2, 
                        Y[k - 1] + k1/2]; 
k3 = 
                    h f[X[k - 1] + h/2, 
                        Y[k - 1] + k2/2]; 
k4 = 
                    h f[X[k - 1] + h, 
                        Y[k - 1] + k3]; 
k1 + 
                    2 k2 + 2 k3 + k4]; 
kend = 
          Round[(xend - x0)/h]; 
Table[{X[k], 
            Y[k]}, {k, 0, kend}]]

adamsbashforth[f_, x0_, y0_, h_, xend_] := 
      Module[{X, Y, kend}, 
X[0] = 
          x0; 
X[k_] := X[k] = 
            X[k - 1] + h; 
Y[0] = 
          y0; 
Y[k_ /; k <= 3] := Y[k] = 
            Y[k - 1] + 
              1/6 Module[{k1, k2, k3, k4}, 
                  k1 = h f[X[k - 1], 
                        Y[k - 1]]; 
k2 = 
                    h f[X[k - 1] + h/2, 
                        Y[k - 1] + k1/2]; 
k3 = 
                    h f[X[k - 1] + h/2, 
                        Y[k - 1] + k2/2]; 
k4 = 
                    h f[X[k - 1] + h, 
                        Y[k - 1] + k3]; 
k1 + 
                    2 k2 + 2 k3 + k4]; 
Y[
            k_ /; k > 3] := Y[k] = 
            Y[k - 1] + 
              h/24 (55 f[X[k - 1], Y[k - 1]] - 
                    59 f[X[k - 2], Y[k - 2]] + 
                    37 f[X[k - 3], Y[k - 3]] - 
                    9 f[X[k - 4], 
                        Y[k - 4]]); 
kend = 
          Round[(xend - x0)/h]; 
Table[{X[k], 
            Y[k]}, {k, 0, kend}]]

Näiden avulla muodostetut ratkaisut ja vastaavat kuvat:

pareulerdata =  pareuler[f, x0, y0, h, xend]

pareulerkuva =  ListPlot[pareulerdata]

rungekuttadata =  rungekutta[f, x0, y0, h, xend]

rungekuttakuva =  ListPlot[rungekuttadata]

adamsbashforthdata =  
      adamsbashforth[f, x0, y0, h, xend]

adamsbashforthkuva =  
      ListPlot[adamsbashforthdata]

Esimerkkinä oleva alkuarvoprobleema on myös ratkaistavissa algebrallisesti, jolloin voidaan verrata eri menetelmien tarkkuutta toisiinsa ja myös tarkkaan ratkaisuun:

tarkkaratkaisu =  
      y /. First[
          DSolve[{y'[x] == f[x, y[x]], y[x0] == y0}, y, 
            x]]

tarkkakuva =  
      Plot[tarkkaratkaisu[x], {x, 0, xend}]

Kaikki ratkaisut samassa kuvassa:

kuva =  
      Show[eulerkuva, pareulerkuva, rungekuttakuva, 
        adamsbashforthkuva, tarkkakuva, 
        PlotRange -> All]

Lopuksi eri menetelmillä saadut arvot tarkasteluvälin loppupisteessä:

vertailu =  {Last[eulerdata][[2]], Last[
            pareulerdata][[2]], Last[
            rungekuttadata][[2]], Last[
            adamsbashforthdata][[2]], 
        tarkkaratkaisu[xend] // N}

Edellä olevia syötteitä voi muuntaa ja tämän jälkeen ajaa koko muistikirjan yhdellä kerralla (valikko Kernel / Evaluation).

SKK 30.04.2001