Ett exempel på hur man använder lsqnonlin

Antag att vi har mätt upp utslag hos en partikel som utför en dämpad  harmonisk svängningsrörelse. Vår modell är y = a exp(d t) cos(w t). y är utslaget (avvikelsen från jämviktsläget) hos partikeln och t är tiden. Modellen har tre parametrar, a, d och w, som vi vill bestämma. a står för amplituden, d för dämpningen och w för vinkelfrekvensen.

Vi har en uppsättning mätpunkter (tk, yk), k = 1, ..., n och vill anpassa modellen till dessa mätvärden. Funktionen  lsqnonlin försöker minimera följande kvadratsumma:

[y1 - a exp(d t1) cos(w t1)]2 + [y2 - a exp(d t2) cos(w t2)]2 + ... + [yn - a exp(d tn) cos(w tn)]2

med avseende på de tre parametrarna a, d och w. För att använda lsqnonlin måste vi tillhandahålla en funktion, kalla den residual, som räknar ut residualvektorn (rutinen skall alltså inte räkna ut kvadratsumman) givet de aktuella uppskattningarna av parametrarna. lsqnonlin prövar sig fram med olika parametervärden och vill, för varje uppsättning, få tillbaks residualvektorn. Funktionen residual tar bara en inparameter, nämligen en vektor som innehåller de aktuella uppskattningarna av parametrarna i modellen, så vi måste alltså förpacka a, d och w i en vektor. Det är vanligt att numerisk programvara utformas med standardiserade parameterlistor på detta sätt (man har alltså inte olika antal inparametrar beroende på antalet parametrar i modellen).

Observera att alla variabler i funktionen är lokala till funktion (så att om jag har definierat t- och y-värden enbart i funktionen, så kommer man inte åt dessa variabler "utifrån" och vice versa). Detta problem (att ge både huvudprogram och funktion tillgång till t- och y-värden) kan lösas på minst tre olika sätt:

  1. man kan definiera variablerna både i huvudprogram och i funktionen
     
  2. författarna av lsqnonlin har tänkt på detta problem och har gjort det möjligt att skicka med extra variabler (help lsqnonlin för detaljer)
     
  3. man kan använda global (global-deklarationen måste finnas både i funktionen och i "huvudprogrammet").

I följande exempel har jag utnyttjat global eftersom det är standardlösningen. Så här ser t- respektive y-värdena ut:

t            y
0            1.3787e+01
1.0871e+00   2.4637e+00
2.1971e+00  -8.2296e+00
3.0095e+00  -7.0862e+00
4.3971e+00   7.2126e+00
5.2744e+00   7.8584e+00
6.2714e+00   1.1796e+00
7.3574e+00  -5.4583e+00
8.3811e+00  -2.5818e+00
9.1610e+00   8.9438e-01
1.0589e+01   4.4598e+00
1.1582e+01   3.0895e-02
1.2956e+01  -2.9151e+00
1.3504e+01  -1.4731e+00
1.5205e+01   3.2932e+00
1.6419e+01   8.1525e-01

och jag förutsätter att vektorn t innehåller t-värdena och att vektorn y innehåller y-värdena.

function r = residual(params)

% Vi använder global för att få tillgång till mätdata. 
% Denna deklaration måste också finnas i huvudprogrammet.

global t y

% Här packar vi upp params. Detta är inte nödvändigt
% vi kan givetvis referera till params(1) etc. direkt.

a = params(1);  % Aktuell uppskattning av amplituden
d = params(2);  % av dämpfaktorn
w = params(3);  % och av vinkelfrekvensen

% Här beräknar vi residualen och tilldelar
% den till resultatvariabeln r.
% OBS: rutinen skall returnera en vektor av residualer
% och INTE kvadrater av residualer eller en kvadratsumma.

r = y - a * exp(d * t) .* cos(w * t);

Jag har också skapat en fil main.m där jag anropar lsqnonlin plottar mätdata och modellen. Den svåraste delen i main.m är att hitta på bra startvärden (gissningar). Vi har ju ett ickelinjärt problem. Dåliga startvärden kan medföra att vi inte får konvergens eller att vi hamnar i en minpunkt med större fel i anpassningen. Även om det kan konvergera med dåliga startvärden så brukar det gå snabbare om man startar nära minimum.
 

Så här ser mätpunkterna ut (röda *) där jag har förbundit punkterna med räta linjesegment.

Mätpunkter


Som gissning på amplituden kan vi ta y(1) eftersom t(1) = 0.Från figuren ser jag att kurvan svänger ungefär tre hela perioder mellan första och näst sista mätvärdet. Jag uppskattar därför w till 6 pi / t(end-1). (end är en funktion som står för indexet för sista elementet). Det är svårare att uppskatta dämpningen. Ett sätt är att betrakta de värden vi gissat för amplitud och vinkelfrekvens som givna, sedan logaritmera och lösa det linjära minstakvadratproblemet. Man får se till att bara plocka ut de tidsvärden där argumenten till log-funktionen blir positiva. Detta ger oss approximationen -8.7622e-02.

Ett enklare sätt är att välja en tidpunkt tk och lösa ut dämpningen ur sambandet yk = a exp(d tk) cos(w tk), där vi betraktar de gissade värdena på amplitud och vinkelfrekvens som givna. Man skall inte ta ett tidsvärde där yk är för nära noll eftersom man kan anta att mätbruset är stort jämfört med själva y-värdet. Jag har, lite godtyckligt, valt (t5, y5).

Här kommer main.m (notera att jag har samma ordning på parametrarna i startvektorn som jag har i funktionen residual):

% En matchande global-deklaration.
global t y % Här ger jag t och y värden. Jag har inte skrivit ut alla värdena. t = [ 0 1.0871 ... ]'; y = [13.787 24.637 ... ]'; % Bestäm startvärden, gissningar på parametrarna. a_gissning = y(1) w_gissning = 6 * pi / t(end - 1) d_gissning = log(y(5) / (a_gissning * cos(w_gissning * t(5)))) / t(5) % Stoppa in gissningarna på rätt platser i startvektorn. start_gissning = [a_gissning; d_gissning; w_gissning]; % Anropa lsqnonlin. I params hamnar de optimala värdena, % förutsatt att allt fungerar som det ska. params = lsqnonlin(@residual, start_gissning) figure(1) hold off plot(t, y, 'r*') % plotta mätdata xlabel('tid') ylabel('y') title('Mätdata och anpassning') hold on % Skapa en t-vektor, med fin uppdelning, för att plotta % modellen som ju beror kontinuerligt av t. t_fin = linspace(t(1), t(end)); plot(t_fin, params(1) * exp(params(2) * t_fin) .* cos(params(3) * t_fin)) grid on

Här är körningen (något redigerad). Vi kan notera att jag gissade väldigt bra i detta fall (snälla mätvärden).

>> main               
a_gissning =  1.3787e+01
w_gissning =  1.2397e+00
d_gissning = -1.9390e-01

Optimization terminated successfully:
 Relative function value changing by less than OPTIONS.TolFun

params =
   1.3099e+01   % optimala a
  -1.0447e-01   % d
   1.2330e+00   % w

Så här blev plotten:

Mätpunkter och modell


 

Back