Polynomanpassningsprogram Den här uppgiften skall göra en polynomanpassning av en tvåkolumners tabell enligt minstakvadrat kriteriet och presentera resultatet grafiskt. Uppgiftens tygndpunkt ligger på hanteringen av den grafiska presentationen. I ditt program skall man kunna välja datafil. Det gör man genom att lägga in klassen OpenDialog som ligger i klassbiblioteket Dialogs. Låt filtypen vara *.txt men möjliggör även att browsa efter filtypen *.* I uppgifterna kryptering och koppla till annat program... finns i detalj beskrivet hur man använder OpenDialogen. När man har valt datafil skall knappen Editera aktiveras. Genom att klicka på den knappen skall Window s inbyggda editor Notepad startas och den valda datafilen öppnas. Man skall då kunna skriva in sin tvåkolumners tabell och spara den. Vidare skall det finnas möjlighet att välja gradtalet hos polynomet. Det skall man välja med en ComboBox och valen skall vara heltalen från noll till sex. När fil och gradtal är valt skall knappen Poly Fit aktiveras. Klickar man på den skall programmet öppna datafilen och läsa in tvåkolumners tabellen och visa hur många datapunkter den innehöll. Därefter skall programmet göra en minstakvadratanpassning av talen till ett polynom av det valda gradtalet. De framräknade koefficienterna i polynomet samt RMS (Root Mean Square) skall visas i ett RichEdit-fönster, som finns i klassbiblioteket Win32 respektive i ett Edit-fönster som finns i Standardbiblioteket. För att visa grafiskt hur anpassningen gick skall ditt program innehålla två diagram. Använd dig av klassen Chart som ligger i klassbiblioteket Additional. Denna klass har väldigt många egenskaper så för Chart kan man öppna en Objektinspektor genom att dubbelklicka i diagrammet. En kurva i ett diagram kallas för Series. Det vänstra diagrammet skall innehålla två Series: primärdata som skall visas som points och en line som visar det framräknade polynomet. Varje datapunkt i datafilen skall alltså visas som en röd rektangel och polynomet visas som en sammanhängande linje. Per Nylén - 25 november 2003 1(5)
I det högra diagrammet skall felen mellan datapunkterna och det värde som polynomet ger, visas i form av ett stapeldiagram, Bar. Sätt också ut titlar på diagrammen. Felen mellan datapunkterna och polynomet brukar kallas för residuals, dvs. restfel. Själva polynomanpassningen kräver en del räknande. Enklast uttrycker man det på matrisformalism. Vi ställer upp ekvationssystemet Y = X * A (1) där Y är vektorn y 1,y 2,y 3 osv., A är den obekanta vektorn som bildas av koefficienterna A 0, A 1,A 2 osv i polynomet, och X är den matris som bildas av x-värdenas potenser upp till j polynomets gradtal, dvs: x ij =x i där i=1..n och j=0..p, där n är antalet datapunkter och p är polynomets gradtal. För att lösa equ.1 förlänger man ekvation 1. med matrisen X s transponat ( X T ) och får då X T *Y = X T *X*A (2) Detta kallas för normalekvationerna och är p+1 ekvationer i p+1 obekanta. Den kan alltså ha en exakt lösning. Lösningen får man genom att förlänga med inversen till den kvadratiska matrisen X T *X, dvs: (X T *X) -1 *X T *Y = A (3) Har man tillgång till matrisoperationer behövs således bara lite matrismultiplikationer och en invertering. Man kan också lösa problemet med vanligt räknande, enligt följande recept: Börja med att öppna datafilen och läsa in mätvärdena till två arrayer X och Y. N:=1; While not EOF(Fil) do Readln(Fil, X[N], Y[N] ); if IOResult=0 then N:=N+1; Antal:=N-1; där X och Y är två arrayer [1..50] av typen Extended. Jag har begränsat antalet datapar till 50. För att minimera avrundningsfelen i beräkningarna utnyttjar jag processorn maximalt genom att använda datatypen Extended som representerar ett tal på potensform i 80 bitar. När du är klar med inläsningen skall du stänga filen. Vi kommer att behöva veta intervallet hos X-värdena, därför bestämmer du X max och X min med satserna: Xmin:=X[1]; Xmax:=X[1]; for j:=1 to Antal do Per Nylén - 25 november 2003 2(5)
if Xmin>X[j] then Xmin:=X[j]; if Xmax<X[j] then Xmax:=X[j]; Nu är det dags att formulera normalekvationerna. Det gör du med satserna: Gradtal := boxorder.itemindex; //Hämta gradtalet For j:=0 to Gradtal do // Sätt upp normalekvationerna for i:=1 to Antal do begin Z[i]:=Y[i]*Power(X[i],j); for k:=0 to Gradtal do B[i,k]:=Power(X[i], j+k); end; R[j]:=0; for i:= 1 to Antal do R[j]:=R[j]+Z[i]; for i:=0 to Gradtal do M[j,i]:=0; for i:=0 to Gradtal do for k:=1 to Antal do M[j,i]:=M[j,i]+B[k,i]; där Z är en array [1..50] av Extended, B är en array [1..50, 0..6] av Extended, R är en array [0..6] av Extended och slutligen M är en array [0..6, 0..6] av typen Extended som slutligen innehåller koefficienterna i normalekvationerna. Slutligen skall vi lösa normalekvationerna och beräkna koefficienterna. Det gör vi med s.k. Gauss-elimination och återsubstitution. For i:=0 to Gradtal-1 do // Gausselimination for j:=i+1 to Gradtal do begin F:=M[j,i]/M[i,i]; R[j]:=R[j]-R[i]*F; for k:=i to Gradtal do M[j,k]:=M[j,k]-M[i,k]*F; end; For i:=0 to 6 do A[i]:=0; For i:= Gradtal downto 0 do // Beräkna koefficienterna A[i]:=R[i]/M[i,i]; for j:=i-1 downto 0 do R[j]:=R[j]-M[j,i]*A[i]; Vi har nu koefficienterna liggande i A[0] till A[Gradtal] och kan skriva ut dem i Koefficientfönstret. Jag använder mig av klassen RichEdit som ligger i klassbiblioteket Win32. För att få utskriften snygg bör du använda funktionen FloatToStrF som ger möjlighet till formatterad utskrift. Dessutom kollar jag om talet är positivt eller negativt. Per Nylén - 25 november 2003 3(5)
reresult.text:=''; for i := 0 to Gradtal do if A[i]<0 then TalText:=': ' +FloatToStrF(A[i], ffexponent, 4,2) else TalText:=': '+FloatToStrF(A[i], ffexponent, 4,2); reresult.text:=reresult.text+' '+IntToStr(i)+TalText + #10; Nu skall du också presentera resultatet i de två diagrammen. I det vänstra diagrammet visas primärdata som punkter och det återberäknade polynomet som en kurva. Det vänstra diagrammet innehåller alltså två Series, där det första är av typen point och det andra av typen line. För att rita en kurva i ett diagram, börjar du med att radera den med satsen Series1.Clear; Därefter ritar du kurvan genom att anropa seriens metod AddXY for i:=1 to Antal do Series1.AddXY( X[i], Y[i],'', clred); På liknande sätt ritar du det återberäknade polynomet som en linje. För att få en snygg kurva har jag valt att rita kurvan med 10 gånger fler punkter än mina primärdata, och jag ritar den från X min till X max, som jag har beräknat tidigare. Series2.Clear; for i:=0 to Antal*10 do XX:=Xmin +(Xmax-Xmin)*i/(Antal*10); Series2.AddXY( XX, Poly(XX, A), '', clnavy); För att kunna studera detaljer i det här diagrammet vill jag att man skall kunna zooma in ett område och kunna gå tillbaka till den ursprungliga grafen. Zoomningen ställer du in i diagrammets General-flik, och för att gå tillbaka till den ursprungliga grafen när man klickar i grafen anropar man metoden: Chart1.UndoZoom; I det högra diagrammet skall du visa felet i varje mätpunkt, dvs skillnaden mellan primärdatas Y-värde och det värde som polynomet ger om man sätter in motsvarande X-värde. Samtidigt som du räknar ut felen (residualerna) passar du på att beräkna felkvadratsumman och därur RMS (Root Mean Square). RMS:=0; For i:=1 to Antal do Err[i]:=Y[i]-Poly(X[i], A); RMS:=RMS+SQR(Err[i]); // Err := Obs - Calc Per Nylén - 25 november 2003 4(5)
if Antal>Gradtal then RMS:=SQRT(RMS/(Antal-Gradtal)) else RMS:=0; Om ekvationssystemet inte är överbestämt utan antalet ekvationer är lika med antalet obekanta får jag en exakt lösning och RMS=0. Jag skulle då dividera med noll när jag beräknar RMS. Därför gör jag en särskild koll av det. De beräknade residualerna skall jag nu visa som ett stapeldiagram (bar graph) i det högra diagrammet. Series3.Clear; for i:=1 to Antal do Series3.AddXY( X[i], Err[i], '', clred); Slutligen kan jag skriva ut värdet på RMS och antalet datapunkter i respektive fönster: wrms.text:=' '+FloatToStrF(RMS, ffexponent, 4,2); wpoints.text:=' '+IntToStr(Antal); Avsluta med att provköra programmet på en känd datamängd, t.ex. heltalen och deras kvadrater. Per Nylén - 25 november 2003 5(5)