Ohjelmointi ja numeeriset menetelmät, luento 6, 22.2.

Omat muuttujatyypit

Vrt. Pascalin record ja C:n struct.

Muuttujatyypin äärittely:

        type kaupunki
          real :: pituus, leveys
          character (len=20) :: nimi
        end type
Tyyppiä kaupunki olevien muuttujien määrittely:
        type (kaupunki) :: turku, k1, k2
        type (kaupunki), dimension(1000) :: luettelo
Komponentteihin voi viitata operaattorilla %:
        real :: x

        turku%pituus = 22.3
        turku%leveys = 60.45
        turku%nimi = 'Turku'

        x = luettelo(i)%pituus
Omia tyyppejä voi käyttää sijoituslauseissa:
        k1 = k2
        k2 = luettelo(327)
Sijoituslauseessa voidaan luetella kaikki komponentit:
        k1 = kaupunki(25.0, 60.0, 'Helsinki')
Myös muita operaattoreita voi määritellä (käsitellään myöhemmin).

Moduuli

Voidaan paketoida yhteen tiedostoon samaan asiaan liittyviä muuttujien ja proseduurien määrittelyjä
        module m
        ! tyyppien ja muuttujien maarittelyt                
        ...
        contains
        subroutine sub 
        ! aliohjelma

        end
        ...
        end module
Myös moduulin proseduureilla voi olla omia sisäisiä proseduureja.

Moduulin muuttujamäärittelyt ja aliohjelmat saadaan käyttöön use-lauseella.

Moduuli käännetään erikseen ja linkitetään sitä käyttävään ohjelmaan.

Esimerkki: vakioiden määrittely

     module realnum
       implicit none
       integer, parameter :: single=selected_real_kind(5)
       integer, parameter :: double=selected_real_kind(10)
     end module
Käyttö:
     program realtest
        use realnum
        implicit none
        real (kind=single) :: x
        real (kind=double) :: y
        ...
    end program


Use-lause
Oltava ennen kaikkia muita määrittelyjä.

Moduulin tunnuksiin voidaan viitata myös eri nimillä:

     program realtest
        use realnum, myreal=>double
        implicit none
        real (kind=myreal) :: x
        ...
    end program
Oletusarvo on, että käyttöön saadaan kaikki moduulin määrittelyt. Käyttöön voidaan ottaa myös vain halutut määrittelyt:
     program realtest
        use realnum, only: double
         ...
     end program
tai
     program realtest
        use realnum, only: myreal=>double
         ...
     end program
- Nähdään, mitä määrittelyjä on käytössä.
- Vältetään ristiriita omien tunnusten ja mahdollisesti samannimisten moduulin tunnusten välillä.


Esimerkki: rationaalilukuja käsittelevä moduuli.

Moduulia käyttävä ohjelma voisi olla esimerkiksi:

     program koe
        use ratpack
        implicit none
        type (rational) :: p, q, r
        p=rat(1,2)
        q=rat(2,3)
        r=p+q
        q=q+1
        write(*,*) nomin(r),denom(r)
        write(*,*) nomin(q),denom(q)
        p=5
        p= .inv. p
        write(*,*) nomin(p),denom(p)
     end program
Ohjelman tulostus:
        7 6
        5 3
        1 5
Yksinkertainen versio:
        module ratpack
        type rational
           integer :: nominator, denominator
        end type
        contains

        function mul (p, q)
        ! lasketaan rationaalilukujen p ja q tulo
          type (rational) mul
          type (rational), intent(in):: p, q
          integer a,b,c,d
          type (rational) r
          a=p%nominator; b=p%denominator
          c=q%nominator; d=q%denominator
          r%nominator = a*c
          r%denominator = b*d
          mul = simplify (r)
       end function

       function div (p, q)
       ! lasketaan rationaalilukujen p ja q osamaara
       ..
       end function

       function add (p, q)
       ! lasketaan rationaalilukujen p ja q summa
       ..
       end function

       function sub (p, q)
       ! lasketaan rationaalilukujen p ja q erotus
       ..
       end function

       function simplify (q)
       ! supistetaan rationaaliluku q yksinkertaisimpaan 
       ! muotoon Eukleideen algoritmilla
       ! funktion arvo on supistettu rationaaliluku
       type (rational) simplify
         type (rational), intent(in):: q
         integer  a, b
         a=q%nominator
         b=q%denominator
         do
           if (a < b) then
              c=a; a=b; b=c; 
           end if
           do while (a >= b) 
             a = a-b 
           end do
           if (a == 0) exit
         end do
         ! b on nyt osoittajan ja nimittajan
         ! suurin yhteinen tekija
         simplify%nominator = q%nominator/b
         simplify%denominator = q%denominator/b
      end function

      end module
Pakkauksen käyttö:
     program koe
        use ratpack
        implicit none
        type (rational) :: p, q, r

        p%nominator=1;  p%denominator=2
        q%nominator=3;  q%denominator=4
        r=add(p,q)
        write(*,*) r%nominator, r%denominator
     end program
  f95 -o koe koe.f90 ratpack.o 
Kun moduuli on otettu käyttöön use-lauseella, kaikki moduulissa määritellyt tyypit, muuttujat ja proseduurit ovat käytettävissä. Määrittelyt ovat julkisia (public).

Moduulissa voi olla myös yksityisiä (private) määrittelyjä, jotka eivät näy käyttäjälle.

Myös julkisen tietotyypin komponentit voivat olla yksityisiä.

        real, private, dimension(100) :: table
        
        type rational
           integer, private :: nominator, denominator
        end type
Käyttäjä ei pääse suoraan käsiksi tyypin rational komponentteihin. Komponenttien arvojen asettaminen ja tutkiminen on mahdollista vain moduulissa määriteltyjen proseduurien avulla.

Tyypin sisäistä määrittelyä voidaan muuttaa ilman, että se vaikuttaa käyttäjän ohjelmiin. Nimiä voidaan muuttaa tai koko esitystapa vaihtaa.

Käyttäjä ei voi vahingossa sotkea tietorakenteita.

Rationaalilukumoduuliin täytyy lisätä proseduuri, jolla muodostetaan rationaalilukuja:

        function rat (n, d)
        ! muodostetaan rationaaliluku, jonka 
        ! osoittaja=n nimittaja=d
          type (rational) rat
          integer, intent(in):: n, d
          rat%nominator=n
          rat%denominator=d
       end function
Lisäksi tarvitaan proseduurit, joilla rationaaliluvusta poimitaan osoittaja ja nimittäjä:
        function nomin (p)
        ! palautetaan rationaaliluvun osoittaja
          integer nomin
          type (rational), intent(in):: p
          nomin=p%nominator
        end function

        function denom (p)
        ! palautetaan rationaaliluvun nimittaja
          integer denom
          type (rational), intent(in):: p
          denom=p%denominator
       end function
Nyt rationaaliluvun osoittajaan ja nimittäjään ei voi viitata suoraan, vaan on käytettävä moduulin proseduureja:
     program koe
        use ratpack
        implicit none
        type (rational) :: p, q, r

        p=rat(1, 2)
        p=rat(3, 4)
        r=add(p,q)
        write(*,*) nomin(p), denom(p)
    end program
Aritmeettiset operaattorit voidaan määritellä niin, että ne kohdistuvat mielivaltaisiin muuttujatyyppeihin:
        type (rational) :: p, q, r
        ...
        r=p+q
Tätä käsitellään myöhemmin.
(Seuraavassa \d tarkoittaa osittaisderivaattaa; muut merkinnät lienevät arvattavissa)

Pienimmän neliösumman sovitus

Havaintopisteet (xi, yi), i=1, ... , n. Halutaan esittää pistejoukko käyränä y=f(x). Pisteessä xi käyrän pystysuora poikkeama havaitusta arvosta on yi - f(xi). Koko sovituksen virhe R on virheiden neliöiden summa:

R2 = \sum | yi - f(xi) | 2

Sovitettava funktio f sisältää muuttujan lisäksi joukon vakioita (parametreja) ak, k=1, ..., K.

Residuaali R on sovitettavan funktion parametrien funktio: R=R(a1, ... , aK). Etsitään sellaiset parametrien arvot, joilla R tulee mahdollisimman pieneksi.

Oletetaan, että f on derivoituva parametrien suhteen. Residuaalin R minimissä on

\d R/ \d a1=0, ... \d R/ \d aK=0.

Tästä saadaan yhtälöryhmä sovitettavassa funktiossa esiintyville parametreille.

Jos funktio f voidaan esittää joidenkin kantafunktioiden fi lineaarikombinaationa

f(x)= \sum ai fi(x),

saadaan lineaarinen yhtälöryhmä riippumatta siitä, mitä muotoa kantafunktiot fi ovat.

Esimerkiksi

f(x) = a0 + a1 x + a2 x2,
f(x) = a1 sin x + a2 cos x,
f(x) = a0 + a1 ex + a2 e3x.

Johdetaan ratkaisu, kun sovitettava funktio on muotoa

f(x) = a + b x,

eli kantafunktiot ovat 1 ja x.

Minimoitava suure on

R=\sum (yi - a - b xi)2.

Osittaisderivaatat ovat

\dR / \da = -2\sum (yi - a - bxi),
\dR / \db = -2\sum (yi - a - bxi) xi.

Saadaan normaaliyhtälöt:

\sum yi - a \sum 1 - b \sum xi = 0,
\sum xi yi - a \sum xi - b \sum xi2 = 0.

eli

a N + b Sx = Sy,
a Sx + b Sxx = Sxy

missä

Sx = \sum xi,
Sy = \sum yi,
Sxx = \sum xi2,
Sxy = \sum xi yi.

Datapisteiden koordinaatit ovat yleensä mittaustuloksia, joilla kullakin on oma virheensä.

Kirjoitetaan minimoitava suure muotoon

R2 = \sum ((yi - f(xi) \sigmai)2,

missä \sigmai on havainnon yi virhe.

Jos kaikki virheet \sigmai ovat samoja, tämä poikkeaa aikaisemmasta versiosta vain vakiokertoimella, jolloin normaaliyhtälöiden ratkaisut eivät muutu.

Kun y:n virheet otetaan huomioon, pienimmän neliösumman suoran normaaliyhtälöt ovat:

a S + b Sx = b Sy,
a Sx + b Sxx = Sxy,

missä

S = \sum (1 / \sigmai2),
Sx = \sum (xi / \sigmai2),
Sy= \sum (yi / \sigmai2),
Sxx = \sum (xi2 / \sigmai2),
Sxy = \sum (xi yi / \sigmai2).

Yleisessä tapauksessa normaaliyhtälöt ovat

\sum (yi - f(xi) / \sigmai2) (\d f(xi) / \d ak=0, k=1,...,K.

Suoran tapauksessa normaaliyhtälöiden ratkaisu on

a = (Sxx Sy - Sx Sxy / D),
b = (S Sxy - Sx Sy / D),

missä

D = S Sxy - (Sx)2.

Suoran parametrien hajonnat ovat

\sigmaa = \sqrt( Sxx / D),
\sigmab = \sqrt( S / D).


Matriisiformalismi

Olkoot kantafunktiot \phi1, ... ,\phiK, jolloin sovitettava funktio on

y(x) = a1\phi1(\x) + ... + aK\phiK(x).

Selitettävän muuttujan y arvoista muodostetaan pystyvektori

y = ( y1, y2, ... yn) T

Muuttujan x arvojen xi avulla lasketaan matriisi

   A =  \phi1(x1) \phi2(x1)  ... \phiK(x1)
        \phi1(x2) \phi2(x2)  ... \phiK(x2)
        ...         ...               ...
        \phi1(xn) \phi2(xn)  ... \phiK(xn)
Ratkaistavat kertoimet muodostavat pystyvektorin

a = (a1, a2, ... aK)T.

Jotta sovitettava funktio kuvaisi dataa, täytyy olla

A a = y.

Tämä yhtälö voidaan ratkaista täsmällisesti vain, jos A on neliömatriisi, eli havaintoja on yhtä monta kuin kantafunktioita.

Yleisessä tapauksessa voidaan etsiä vektori a, joka antaa minimiarvon normille || A a - y ||.

Pienimmän neliösumman ratkaisu on

Ca = d,

missä

C = AT A

ja

d= AT y

Tämä K:n yhtälön ryhmä on normaaliyhtälöiden matriisimuoto.

Jos mittausten virheet otetaan huomioon

C = AT \Sigma-1 A

d = AT \Sigma-1 y,

missä \Sigma-1 on kovarianssimatriisin käänteismatriisi.

Jos mittaukset ovat riippumattomia, kovarianssimatriisi on

  \sigma12     0               ...    0 
  0              \sigma22      ...    0 
  ...            ...             ...    0
  0              0               ...    \sigman2
ja sen käänteismatriisi
  1/\sigma12     0               ...    0 
  0                1/\sigma22    ...    0 
  ...              ...             ...    0
  0                0               ...    1/\sigman2
Jos mittaukset ovat riippumattomia, on

Cij = \suml (\phii(xl) \phij(xl) / \sigmal2).

di = \suml (\phii (xl) yl / \sigmal 2).

Käänteismatriisi C-1 on kovarianssimatriisi, jonka lävistäjältä löytyvät kertoimien varianssit:

\sigma_ai = \sqrt(C-1ii).

Jos C-1 on lävistäjämatriisi, parametrit ovat riippumattomia.


Esimerkki: Alkuperäinen aineisto (x, y, \sigma):
 1  1  0.5
 3  1  1.0
 4  3  0.5
 6  4  0.5
Sovitetaan tähän suora, jolloin \phi1(x)=1, \phi2(x)=x.
  A  =    1  1 
          1  3 
          1  4 
          1  6 

  b  =    1 
          1 
          3 
          4
 
 \Sigma =       0.25  0    0     0 
                0     1    0     0 
                0     0    0.25  0 
                0     0    0     0.25

 \Sigma-1 =  4     0    0     0 
                0     1    0     0 
                0     0    4     0 
                0     0    0     4

C = AT \Sigma-1 A =

     13  
     47 
     47 
    221

d = AT \Sigma-1 y =

     33 
    151
Saadaan yhtälöryhmä
    | 13   47 |  | a |   |  33 |
    |         |  |   | = |     |
    | 47  221 |  | b |   | 151 |
jonka ratkaisu on a=0.295, b=0.620.

Kerroinmatriisin käänteismatriisi on

C-1 =

    0.333,  -0.071 
   -0.071    0.020
josta

\sigmaa = \sqrt(0.333) = 0.577,
\sigmab = \sqrt(0.020) = 0.140.


(Ruma mutta helppo tapa laskea käänteismatriisi)

On etsittävä matriisi X, jolle

AX = I,

missä I on yksikkömatriisi. Tämä voidaan kirjoittaa n2 lineaarisen yhtälön ryhmäksi.

Jos esimerkiksi n=2, on

 | a11  a12 |  | x11  x12 |     | 1  0 |
 |                |  |                |  =  |      |
 | a21  a22 |  | x21  x22 |     | 0  1 |
eli
  a11 x11 + a12 x21 = 1,
  a11 x12 + a12 x22 = 0,
  a21 x11 + a22 x21 = 0,
  a21 x12 + a22 x22 = 1.
Tästä voidaan ratkaista käänteismatriisin alkiot aikaisemmin esitetyllä lineaarisen yhtälöryhmän ratkaisijalla.


Polynomit

Kantafunktioina muuttujan x potenssien joukko, jolloin sovitettava funktio kokonaisuudessaan on polynomi

f(x) = a0 + a1x + a2x + ... + anxn.

Polynomi voi käyttäytyä huonosti havaintopisteiden ulkopuolella.

Kun astelukua lisätään, polynomi saadaan kulkemaan kaikkien haluttujen pisteiden kautta, mutta niiden välillä se voi heilahdella voimakkaasti. Usein korkein järkevä asteluku on noin 4--5.

Jos aineistossa on pitkiä aukkoja, sovitettuun käyrään saattaa ilmaantua asiaankuulumattomia mutkia.

Regularisointimenetelmät "jäykistävät" sovitettavaa funktiota ja estävät sen liialliset heilahtelut. Esimerkiksi voidaan rajoittaa korkeimmanasteisten termien kertoimia, mikä merkitsee korkeampien derivaattojen pitämistä pieninä.

Regularisointi voidaan toteuttaa esimerkiksi korvaamalla minimoitava residuaali lausekkeella

\sum (yi - P(xi))2 + \lambda\sum P''(xi)2,

missä \lambda on jokin vakio. Tämän lausekkeen minimointi johtaa ratkaisuun, jossa polynomien toiset derivaatat ovat pieniä, joten käyrässä ei esiinny jyrkkiä mutkia.

Esimerkki: Kuvataan Rungen funktiota 9 pisteellä ja sovitetaan niihin eri asteisia polynomeja:


Fourier'n sarjat

Mikäli havaittu ilmiö on jaksollinen, tulokset on usein kätevää esittää Fourier'n sarjana:

f(x) = A0 + \sum Ak cos (2\pi kx/P) + \sum Bk sin (2\pi kx/P),

missä P on jakson pituus.

Jos aineisto ei ole tasavälistä, kertoimien laskeminen on hankalaa. Helpoin tapa on ratkaista ongelma pienimmän neliösumman sovituksena. Kantafunktiona ovat sin kx ja cos kx.

Mahdollisia ongelmia:

1) Lineaarisessa sovituksessa jakso on tunnettava etukäteen. Mikäli sekin halutaan sovittaa samanaikaisesti, joudutaan ratkaisemaan epälineaarinen tehtävä.

2) Jos aineisto ei ole tasavälinen, kantafunktiot eivät ole ortogonaalisia. Myös sarjan alkupään kertoimet muuttuvat, jos sarjaan lisätään uusia termejä. Jos sarjalle käytetään aineistosta riippuvaa katkaisukriteeriä (esimerkiksi lisätään termejä, kunnes R ei enää oleellisesti pienene), eri aineistoja ei enää voi luotettavasti vertailla keskenään. Mikäli eri aineistoja halutaan verrata, on jokainen aineisto esitettävä yhtä monella termillä.

3) Aineiston tulisi kattaa ainakin yksi kokonainen jakso, eikä siinä saisi olla pitkiä katkoja. Muuten kertoimien virheet voivat tulla hyvin suuriksi.

4) Jos mitattavassa suureessa esiintyy Nyquistin taajuutta korkeampia taajuuksia, ne voivat aiheuttaa mitattuihin arvoihin matalia taajuuksia, joita todellisuudessa ei ole olemassakaan.

5) Jos aineistossa esiintyy jyrkkiä hyppäyksiä, sarja suppenee niiden lähellä varsin hitaasti.

Epälineaariset sovitukset

Joissakin erikoistapauksissa tehtävä voidaan muuntaa lineaariseen muotoon.

f(x) = a e-bx,

Sovitetaankin funktion f logaritmi

ln f(x) = a' - bx,

missä a'=ln a. Tuloksena on lineaarinen tehtävä.

Mikäli sovitettavan funktion derivaatat parametrien suhteen pystytään laskemaan, voidaan parametreille johtaa yhtälöryhmä, kuten lineaarisessakin tapauksessa. Yhtälöryhmä on kuitenkin epälineaarinen.

Jos derivaattoja ei pystytä laskemaan analyyttisesti, tai jos tuloksena on kovin mutkikas yhtälöryhmä, on helpompaa käyttää jotakin minimointiohjelmaa.