M-tiedostot, skriptit ja funktiot

Avataan MATLAB-editori (New m-file) ja kirjoitetaan seuraavat komennot:
pisteet=[10 11 15 3 29 7 0 9 30 2 1 8 20 22 5 9 23 24];
jarjestyksessa=sort(pisteet)
keskiarvo=sum(pisteet)/length(pisteet) % tai mean(pisteet)
hajonta=std(pisteet)
Talletetaan tiedostoon tenttipisteet.m .
MATLAB-istunnossa suoritetaan komento tenttipisteet .
Näin käy:
>> tenttipisteet
pisteet =
  Columns 1 through 14
    10    11    15     3    29     7     0     9    30     2     1     8    20    22
  Columns 15 through 18
     5     9    23    24
jarjestyksessa =
  Columns 1 through 14
     0     1     2     3     5     7     8     9     9    10    11    15    20    22
  Columns 15 through 18
    23    24    29    30
keskiarvo =
   12.6667
hajonta =
    9.7075
Tulos on sama kuin jos komennot olisi kirjoitettu Matlab-istuntoon.

Skripti-m-tiedosto toimii täsmälleen samoin, kuin jos kirjoittaisimme komennot Matlab-ikkunaan. Kaikki m-skripti-tiedoston ajossa luodut muuttujat pysyvät voimassa ajon jälkeen. (Funktio-m-tiedostojen laita on toisin.)

Matlab-työn dokumentointi (publish) (Seuraava tekstikappale leikattiin mini/tutoriaalista)

Matlab-oppaissa mainitaan yleensä komento diary. Se alkaa mm. yllä mainittujen mahdollisuuksien valossa edustaa mennyttä aikaa.

Tehtävä: Edellä oleva data on näppäilty aivan "näppimutikassa" ja jakautuma on sen mukainen "mutikka". Teoreettisesti ihanteellisempi jakauma voitaisiin saada jotenkin tähän tapaan:

>> P=12+5*randn(1,200);  % Normaalijakautuneita, keskiarvo=12, hajonta=5.
>> P=round(P);           % Pyöristetään kokonaisluvuiksi.
>> P(P<0)=0;             % Negatiiviset pisteet nollataan
>> P(P>30)=30;           % Suuremmat kuin 30 saavat arvon 30,
>> hist(P,6);shg         % Histogrammi, arvoalue 6:een yhtäsuureen osaan.
Selvitä itsellesi, kokeile erilaisilla parametreilla, tee m-tiedosto ja aja html-dokumentiksi.

Matlab-työn ja dokumentin yhdistelmä

Uusien mahdollisuuksien myötä Matlab-työn suoritus kannattaa siirtää enenevässä määrin komentoikkunasta m-ajotiedostoon. Tiedoston rakenne on tällainen:

% Tiedosto ajo.m
%% Kappaleen 1 otsikko
%
% Tähän väliin selostusta...
% .. ja lisää ...

A=rand(3,3);   % Muodostetaan 3x3-satunnaislukumatriisi
b=ones(3,1);   % Ykköspystyvektori
x=A\b;             % Ratkaistaan yhtälöryhmä: A x = b
...
%% Kappaleen 2 otsikko
...

Kun hiiriosoitin viedään jonkin kappaleen sisään, voidaan tämän kappaleen Matlab-komennot suorittaa CTR-ENTER-painalluksella.

Työn kuluessa on kätevää edetä aluksi pienin kappalein. Se, mikä ennen piti tehdä cut/paste-tyylillä, voidaan nyt hoitaa CTR-ENTER:llä.

Ja tosiaan, komentoikkunaa tarvitaan tulosten ja virheilmoitusten seuraamiseen.

Lopuksi voidaan tehdä mahd. väljempi kappalejako otsikoineen ja ajaa html-dokumentiksi. Myös pdf- ja LaTex-tulokset saadaan komentamalla komentoikkunassa esim

   >> publish('omafile','pdf')
Työstä syntyy kaunis html/pdf/Latex-dokumentti, joka sijaitsee työhakemiston html-nimisessä alihakemistossa.

Funktio-m-tiedostot

MATLAB on funktionaalinen kieli. Ohjelmointi tarkoittaa omien funktioiden kirjoittamista tällä kielellä. Funktiot kirjoitetaan tekstitiedostoon, ns. m-tiedostoon. Lisäksi on versiosta 5.3 lähtien tullut mahdolliseksi määritellä lyhyitä "kertakäyttöluonteisia" funktioita suoraan komentotilassa. Käsittelemme ensin lyhyesti näitä (pitkästi niitä ei voikaan käsitellä).
Versiosta 7 lähtien Matlabissa on ns. funktio-osoittimet, "function handles". Se tekee merkkijonona annetuista funktiomäärittelyistä ja feval- käytöstä vanhanaikaista ja tehotonta kamaa. Pidetään ne tässä vielä mukana, mutta kaikissa uusissa esimerkeissä käytetään yksinomaan tätä uutta osoitin(kahva)tapaa. (Oppaan rakenne hiukan hypähtelee, kun Matlabiin tulee uusia ominaisuuksia.)

Komentorivifunktiot, funktiokahvat (function handles)

Lyhyitä funktiomäärityksiä voidaan tehdä suoraan komentorivillä. Jos haluaisimme määritellä vaikkapa funktion f(x)=e-x2, voisimme toimia näin:
>> f=@(x) exp(-x.^2)     % Funktio-kahva (handle)
   tai "vanhanaikaisesti":
>> f=inline('exp(-x.^2)','x')  % Ei aktiivikäyttöön, vain vanhan koodin ymmärrykseen.
MATLAB tuntee nyt funktion f, ja voimme kutsua sitä tähän tapaan:
>> y=f(-1:0.2:1) 
y =
  Columns 1 through 7 
    0.3679    0.5273    0.6977    0.8521    0.9608    1.0000    0.9608
  Columns 8 through 11 
    0.8521    0.6977    0.5273    0.3679
Huomaa taas piste funktion määrittelyssä potenssin yhteydessä, sen ansiosta funktiomme toimii samoin kuin muutkin MATLABin matemaattiset funktiot, eli palauttaa syöteargumentin kokoisen matriisin "pisteittäin" laskettuja funktion arvoja. %(Skalaarifunktio) Kts ...

Funktiomäärittely on itse asiassa käsitteellisesti aivan sama kuin vaikkapa Maplessa: f:=x->exp(-x^2);, eli määritellään funktio, joka argumentilla x saa arvon exp(-x2) . Usean muuttujan funktio määriteltäisiin näin:

**
>> g=@(x,y,a,b) a*x.^2 + b*y.^2
g = 
    @(x,y,a,b)a*x.^2+b*y.^2
>> g(1,0,2,3)
ans =
     2
**

Funktiokahvan eräs hienous on, että voidaan näppärästi määritellä parametreista riippuvia funktioita. Edellisessä esimerkissä voitaisiin ajatella a ja b parametreiksi. Meillä voisi olla käytössämme Matlab-funktio "ratkaise", joka kutsuu kahden muuttujan funktiota (muiden argumenttien ohella). Tällöin voitaisiin toimia tähän tapaan:

**
>> a=1; b=2;
>> g=@(x,y) a*x.^2 + b*y.^2
g = 
    @(x,y)a*x.^2+b*y.^2
>> g(2,3)
ans =
    22
>> ratkaise(g,...)

>> a=-1; b=0;                % Muutetaan parametreja.
>> g=@(x,y) a*x.^2 + b*y.^2  % Huom! funktiomääritys on uusittava näillä.
g = 
    @(x,y)a*x.^2+b*y.^2
>> g(2,3)
ans =
    -4
>> ratkaise(g,...)
** Palaa katsomaan tätä vaikkapa sitten, kun käsitellään diffyhtälöratkaisijoita.

M-tiedostot

Jokaisen vakavasti MATLABilla työskentelevän on syytä tutustua m-tiedostoihin ja ottaa ne aktiiviseen käyttöön.

Jos yllä oleva funktio kirjoitettaisiin m-tiedostosyntaksilla, se kirjoitettaisin tiedostoon f.m ja koodi olisi:

function y=f(x)
% Kutsu y=f(x) tuottaa samanmuotoisen matriisin kuin x. 
% Tulos y koostuu funktion exp(-x.^2) arvoista x:n alkioissa.
y=exp(-x.^2);                           

Jotta MATLAB löytäisi tiedoston f.m, sen on oltava matlabpolun path varrella. Helpointa lienee käyttää addpath- komentoa. Huomaa, että tiedoston nimi on ratkaiseva. Suositus: Käytä aina samaa tiedoston nimeä kuin sen määrittelemä funktion nimi.

Ongelmaksi voi muodostua, että polun varrella saattaa olla monta f.m-nimistä tiedostoa. Kannattaa varmistaa which-komennolla, missä hakemistossa olevaa f.m-funktiota MATLAB soveltaa. Yleensä kannattaa käyttää hieman "eksoottisempia" funktionnimiä kuin f.

On hyvä tietää, missä järjestyksessä MATLAB hakee sille annettuja symboleja.

Kun MATLABille kirjoitetaan nimi, esimerkiksi foo, MATLAB-tulkki toimii seuraavassa järjestyksessä:

  1. Tarkistaa onko foo muuttuja
  2. Tarkistaa onko foo sisäänrakennettu funktio
  3. Etsii nykyhakemistosta foo.mex tai foo.m-nimistä tiedostoa.
  4. Etsii MATLABin hakupolun osoittamista hakemistoista em. tiedostoja. Katso komento path.

MEX-tiedostot liittyvät käyttäjän tekemiin C- tai fortran-kielisiin aliohjelmiin. Käsittelemme niitä myöhemmin.

M-tiedostot: skriptejä tai funktioita

Mikä tahansa tekstitiedosto tiedosto.m, joka sisältää MATLAB-komentoja, on m-tiedosto. Jos m-tiedosto on MATLAB-polun varrella, ja kirjoitamme sen nimen, niin sen sisältämät komennot tulevat suoritetuiksi. Kyseessä on komentotiedosto eli skripti. Esimerkiksi MATLABin mukana tulevat demot ovat tällaisia skriptejä.

Jos M-tiedoston ensimmäinen rivi alkaa sanalla function on kyseessä funktion määrittely. Funktio eroaa skriptistä siinä, että funktiolle voi antaa parametreja ja funktionmäärittelyn sisällä olevat muuttujanmäärittelyt ja suoritetut laskutoimitukset ovat lokaaleja ts. ne eivät näy varsinaisessa MATLAB-työtilassa. Suuri osa MATLABin mukana tulevista funktiosta on toteutettu M-tiedostoina ja niiden sisältö on siten vapaasti tutkittavissa (esimerkiksi MATLAB-komennon type avulla).

Lisää esimerkkejä M-funktiosta

Edellä oli jo esillä funktiotiedosto f.m . Otamme joukon erilaisia esimerkkejä.

Ajatellaanpa, että haluaisimme laskea annettujen lukujen keskiarvon. Luvut olisi sopivaa tallettaa vektoriin x. Keskiarvo saataisiin yksinkertaisesti lausekkeella sum(x)/length(x). Kokeillaan vaikka näin:

>>  x=1:10
x =
     1     2     3     4     5     6     7     8     9    10
>>  sum(x)/length(x)
ans =
    5.5000
Voimme haluta kirjoittaa funktion, jolle annetaan argumentiksi datavektori x ja joka palauttaa vektorin alkioiden keskiarvon. Kirjoitamme tiedostoon keskiarvo.m seuraavan koodin (Matlabin omalla editorilla tai millä tahansa muulla tekstieditorilla.)
function y = keskiarvo1(x)
% Funktio palauttaa syötevektorin x alkioiden keskiarvon.
% 
y = sum(x)/length(x);
Funktio tulee sijoittaa hakemistoon, joka on Matlab-polun varrella. Ellei näin ole tehty, pitää ennen funktion kutsua antaa sopiva addpath-komento.

Esimerkiksi

>> help keskiarvo1
  Funktio palauttaa syötevektorin x alkioiden keskiarvon.
>> addpath /home/apiola/matlab/opas/marko/matopas/mfiles
>> keskiarvo1([1,2,3])
ans =
     2
>> ka=keskiarvo1(1:10)   
ka =
    5.5000
Esimerkki havainnollistaa seuraavia periaatteita:
  1. Otsikkorivin jälkeisten kommenttirivien teksti tulostuu help- komennolla (kätevää).
  2. Otsikkorivillä esiintyy muuttujan nimi (tässä y). Funktion koodissa viimeksi tälle muuttujalle sijoitettu arvo palautetaan funktion arvona.
  3. Koodissa on syytä lopettaa kaikki sijoituslauseet puolipisteeseen, muussa tapauksessa funktion kutsu aiheuttaa ylimääräisiä, usein hämmentäviä tulostuksia. (Testausvaiheessa ne voivat olla hyödyksi.)

Jos haluaisimme sallia myös datan antamisen matriisina, ja haluaisimme sen toimivan samaan tyyliin kuin funktiot sum, min, max, ... , voisimme täydentää koodia näin:

function y = keskiarvo2(x)
% keskiarvo(x) palauttaa 
%   -  x:n alkioiden keskiarvon, jos x on vektori.
%   -  x:n sarakkeiden keskiarvojen muodostaman vektorin, jos x on matriisi.
%
[m,n] = size(x);
if m == 1         % Vaakavektorin (1 x n-matriisin) tapauksessa komponenttien
        m = n;    % lukumäärä on n, muuten m .
end
y = sum(x)/m;
Laajemmassa oppaassamme puhumme "vektorifunktioista". Nämä toimivat juuri funktioiden sum, max, min, ... tavoin, eli palauttavat vektoriargumentilla skalaarituloksen ja matriisiargumentilla operoivat sarakkeittain.

Jos haluaisimme laskea matriisin A kaikkien alkioiden keskiarvon, olisi kutsu: keskiarvo2(keskiarvo2(A)) tai keskiarvo2(A(:)).

Toinen ajattelutapa olisi kirjoittaa funtio, joka palauttaa aina kaikkien alkioiden keskiarvon. Se olisi yksinkertaisempaa:

function y = keskiarvo3(x)
% Funktio palauttaa syötteenä annetun vektorin tai matriisin x 
% kaikkien alkioiden keskiarvon.
% 
x=x(:);    % Jos x on matriisi, se "jonoutetaan" pitkäksi vektoriksi.
           % (Vektoriargumenttia jonoutus ei haittaa.)
y = sum(x)/length(x);

Käyttötarkoituksesta riippuu, kummanlainen funktio on tarkoituksenmukaisempi. Tärkeää on kirjoittaa alkukommentteihin, mitkä ovat funktion toimintaperiaatteet.

Useita tulosarvoja

Toisinaan on kätevää kirjoittaa funktio, joka palauttaa useamman tulosarvon. Tässä esimerkki, joka palauttaa sekä keskiarvon että keskihajonnan. Kirjoitetaan funktio nyt pelkästään vektoriargumentille.
function [ka,haj] = tilasto1(x)
   % Palautetaan keskiarvo ja keskihajonta.
   % x - vektori
   % esim: [karvo,kh]=tilasto1(1:10)
   n = length(x);
   ka = sum(x)/n;
   haj = sqrt(sum((x - ka).^2)/n);
Kutsuesimerkki:
>> [karvo,kh]=tilasto1(1:10)
karvo =
    5.5000
kh =
    2.8723
Jos tällaista funktiota kutsutaan yhdellä paluumuuttujan nimellä, palautetaan ensimmäinen, tässä tapauksessa keskiarvo.

Funktiomäärittelyn sisällä on käytettävissä hyödylliset muuttujat nargin ja nargout, jotka ilmaisevat funktion kutsussa esiintyvien syöte- tai tulosargumenttien lukumäärän.

Esimerkki nargin:n käytöstä

Tyypillinen käyttötapa on sellainen, jossa halutaan antaa osalle argumenteista jokin oletusarvo. Tällaisia ovat usein esimerkiksi toleranssit, iteraatioiden maksimimäärä, piirtodatapisteiden lukumäärä, ym. Otetaan esimerkiksi neliöjuuren $\sqrt a$ laskeminen Newtonin iteraatiolla. Iteraatiokaava on $$ x_{k+1}=\frac{1}{2}(x_k+\frac{a}{x_k})$$ Koodissa esiintyy while-ohjausrakenne. Iteratiivista algoritmia ei voi "vektoroida", joten ohjausrakenteen käyttö ei ole vältettävissä.
function x=itersqrt(a,tol,maxiter)
% Palautetaan iteraatiovektori, joka suppenee kohti sqrt(a):ta.
% Oletustoleranssi = eps.
% Oletusarvo maksimaaliselle iteraatiomäärälle = 50.

if nargin < 3, maxiter=50; end
if nargin < 2, tol=eps; end

x(1)=a;
k=1;
suhtero=inf; % Näppärä tapa alustaa

while(suhtero > tol)
     x(k+1)=(x(k)+a./x(k))/2;
     k=k+1;
     suhtero=abs((x(k)-x(k-1)))/x(k-1);
  if k > maxiter
    error(['Ei suppene ',num2str(maxiter),':lla iteraatiolla'])
  end
end
Kutsuesimerkkejä:
>> format long
>> itersqrt(2)
ans =
  Columns 1 through 4 
   2.00000000000000   1.50000000000000   1.41666666666667   1.41421568627451
  Columns 5 through 7 
   1.41421356237469   1.41421356237309   1.41421356237309

>> x=itersqrt(20,.0001,6)
??? Error using ==> itersqrt
Ei suppene 6:lla iteraatiolla

>> x=itersqrt(20,.0001,7)
x =
  Columns 1 through 4 
  20.00000000000000  10.50000000000000   6.20238095238095   4.71347454528837
  Columns 5 through 7 
   4.47831444547438   4.47214021706570   4.47213595500161

Esimerkki nargout:n käytöstä

Kuten edellä todettiin, tulosargumenttien lukumäärän testaaminen ei "peruskäytössä" ole useinkaan tarpeen. Erityisen hyödyllistä se on esimerkiksi kahden tulosargumentin tapauksessa, jossa pelkästään toisen tuloksen pyytäminen on tavallista, ja toisen laskeminen on raskasta. Hyvä esimerkki tällaisesta on MATLAB:n funktio eig. Se on ns. "builtin-function" ja sen koodi ei ole luettavissa. Periaatteessa toteutus voisi olla seuraavanlainen:
function [OA,OV]=eig(A)
% Lasketaan ominaisarvot ja pyydettäessä myös ominaisvektorit. 
% Kutsut:  lambda=eig(A); [lambda,V]=eig(A);
%
OA=laskeominaisarvot(A);
if nargout == 2, OV=laskeominaisvektorit(A); end

Ohjausrakenteet

MATLAB sisältää muista ohjelmointikielistä tutut ohjausrakenteet, kuten for ja while loopit sekä if .. else lauseen.

Edellä näimme jo esimerkkejä while- ja if-rakenteista. Kenties tavallisin on for-lause, jota demonstroimme heti.

Muistutamme ensin, että ohjausrakenteita harkittaessa on syytä muistaa vektoriajattelu. Monet asiat voidaan hoitaa suoraan vektorioperaatioilla ilman ohjausrakenteita. Tietenkään tämä ei ole aina mahdollista, kuten iteratiivisissa algoritmeissa. (Vrt. edellä oleva neliöjuuri-iteraatio.)

Seuraava toimii, mutta edustaa "vääräoppista" skalaariajattelua, joka tekee koodista sekä tarpeettoman mutkikasta että tehotonta. Esimerkki toimikoon tässä for-rakenteen opetustarkoituksessa.

t=0:.01:200;
for i=1:length(t);
   y(i) = sin(t(i));
end
Oikeaoppinen tapa tehdä sama suoraan vektorioperaationa on yksinkertaisesti:
t = 0:.01:200;
y = sin(t);
tai suoraan
y = sin(0:.01:200);
Tällaisella vektorilla (pituus = 20001) näkyy hyvin selkeä ero suoritusajassa.

Tehtävä: Tee pieni vertaileva tutkielma edellä olevan for-silmukan ja oikeaoppisen vektorioperaation välillä. Sen saat tic,toc-yhdistelmällä:

>> tic  % Kello käyntiin
>> komento1 
>> komento2
>> ...
>> toc  % Kellon pysäytys

Esimerkki "oikeaoppisesta" for-silmukasta

Lasketaan vektoreita for-silmukassa (vaikka Gaussin eliminaatio)

Funktion välittäminen argumenttina

Monissa tilanteissa on tarpeellista välittää jokin funktio argumenttina toiselle funktiolle. Esimerkin tästä näimme Funktion fplot yhteydessä.

Numeerinen integrointi, nollakohta, minimi

Oman funktion tekeminen on välttämätöntä mm. yllä mainituissa tehtävissä. Otetaan esimerkiksi numeerinen integrointi, johon MATLAB:ssa on mm. funktio quad ja muita, komennoilla help quad, doc quad löydettäviä.
(doc quad-tekstin saat suoraan klikkamalla quad-sanaa edellä, näinhän on yleisesti oppaan "kuumissa" sanoissa.) Kokeile esim.

>>g=@(x)x.*sin(x)
>> format long                       % Täysi tulostustarkkuus
>> arvo1=quad(g,0,pi)                % Oletustoleranssi
     arvo1 =
       3.141592657032484
>> arvo2=quad(g,0,pi,10^(-10))       % Käyttäjän vaatima toleranssi 10^(-10)
    arvo2 =
       3.141592653589818
>> syms x                            % Symbolic toolbox
>> int(x*sin(x))                     % Integroidaan symbolisesti
    ans
    pi                               % Tarkka arvo on pi.
>> arvo1-pi
   ans =
        3.442691021149358e-09 

>> arvo2-pi  
        2.531308496145357e-14
Lisää numeerisesta integroinnista

Muita otsikossa olevia (ja olemattomiakin) "funktiofunktioita":

>> help funfun
Tämä on hiukan vanhentunut lista Function functions - nonlinear numerical methods. ode23 - Solve differential equations, low order method. ode45 - Solve differential equations, higher order method. quad - Numerically evaluate integral, low order method. fzero - Find zero of function of one variable. fplot - Plot function. See also The Optimization Toolbox, which has a comprehensive set of function functions for optimizing and minimizing functions.

Tutki helpin avulla ja testaa.

Ulkoisten ohjelmien käyttö

MATLABista käsin voidaan kutsua käyttäjän kirjoittamia C- tai fortran aliohjelmia. Nämä näkyvät MATLAB-istunnossa tavallisina MATLAB-funktioina. Näin voidaan lisätä huomattavasti käytettävissä olevien funktioiden määrää. Ulkoisia ohjelmia käytetään MEX-tiedostojen avulla.

Tiedostovälitteinen datan siirto ulkoiseen ohjelmaan

Yksinkertaisin tapa käyttää ulkoista ohjelmaa perustuu parametrien ja datan välitykseen tiedostojen avulla. Olkoon meillä ohjelma myprog, joka luettuaan datan määrätyn nimisestä tiedostosta, käsittelee sitä ja lopuksi kirjoittaa tulokset toiseen tiedostoon. Välittävä MATLAB-funktio voisi olla vaikka seuraavanlainen

function y=myprog(x)
save indata.dat x -ascii
!run myprog
load outdata.dat
y=outdata;
MATLAB-funktioilla save ja load talletetaan ja luetaan ASCII-muodossa olevaa dataa. Huutomerkki ! komentorivin alussa luo käyttöjärjestelmän aliprosessin, jossa annettu ohjelma suoritetaan. Katso myös esimerkkinä vaikka print.m-funktion toteutusta.

Kehittyneempi tapa omien ohjelmien käyttöön on tehdä MEX-tiedostoja, so. ohjelmia joihin on suoraan linkitetty oma aliohjelma ja jotka dynaamisesti ladataan muistiin ja joissa parametrit välitetään MATLABin sisäisesti.


[Edellinen] [Seuraava] [Alkusivu]