|
|
Exponential Curve Fitting : source listing |
|
|
This is the source code of the Graphics-Explorer unit ExpFuncUnit, that takes care of exponential curve fitting.
Click to go back to the article.
unit expfuncUnit;
{
calculates exponential functions
of type:
1 : y = ab^x+c
2 : y = ae^(bx)+c
3 : y = e^(ax^2+bx+c)
4: ab^x
5: ae^(bx)
}
interface
uses sysutils,xlateUnit,puntenUnit,formUnit,scaleUnit,dialogs;
type puntarray = array[1..40] of TPunt; //punt = point , coordinates of points
procedure expfunc(n : byte); //n= exp function type requested
implementation
uses controlUnit,klkwUnit;
const FlString : string = '####0.0#####';
var Epoints : puntarray;
a,b,c : double; //y = a*e(bx)+c
ePower : boolean; //use power of e
dx,dy : array[1..40] of double;
dxy : array[1..40] of TPunt;
procedure ExpPlot(s : string);
begin
controlform.formedit.text := s;
addform(formNr);
end;
procedure Getpoints;
//sort points by x value, ascending order
var i,j : byte;
h : Tpunt;
begin
for i := 1 to maxpunt do //1 to max. point , maxpunt in other unit
begin
Epoints[i].x := punten[i].x; //punten = points, from other unit
Epoints[i].y := punten[i].y;
end;
for i := 1 to maxpunt-1 do
for j := i+1 to maxpunt do
if Epoints[j].x < Epoints[i].x then
begin
h.x := Epoints[i].x;
h.y := Epoints[i].y;
Epoints[i].x := Epoints[j].x;
Epoints[i].y := Epoints[j].y;
Epoints[j].x := h.x;
Epoints[j].y := h.y;
end;
end;
function InsufficientPoints(n : byte) : boolean;
//error if < n points supplied
begin
if maxpunt < n then
begin
controlform.msgtext.caption := 'minimal '+inttostr(n)+' points needed';
result := true;
end
else result := false;
end;
procedure TwoPointsExp;
//calculate a and b, if 2 points supplied
var sign1,sign2 : boolean;
s : string;
i : byte;
begin
sign1 := Epoints[1].y < 0;
sign2 := Epoints[2].y < 0;
if sign1 <> sign2 then
begin
controlform.msgtext.caption := 'no exp. function possible: signs unlike';
exit;
end;
if sign1 then
for i := 1 to 2 do Epoints[i].y := -Epoints[i].y;//a negative, invert
for i := 1 to 2 do Epoints[i].y := ln(Epoints[i].y);
b := (Epoints[2].y - Epoints[1].y)/(Epoints[2].x - Epoints[1].x);
a := Epoints[1].y - b*Epoints[1].x;
a := exp(a);
if not Epower then b := exp(b);
if sign1 then a := -a;
//a,b calculated
s := 'y = ';
s := s + formatfloat(flstring,a) + ' * ';
if Epower then s := s + 'e^(' + formatfloat(Flstring,b) +'x)'
else s := s + formatfloat(Flstring,b) + '^x';
expPlot(s); //to other unit to be plotted
end;
function Deviation(cc : double; var af : boolean) : double;
//calculate variation in growth factors after c asymptote correction
//called for fine tuning c in ae^(bx)+c formula
//cc: y value (asymptote); af : abort flag...abort if intersection
var i : byte;
avG,ee : double;
growthfactor : array[1..40] of double;
begin
avG := 0;
for i := 1 to maxpunt-1 do
begin
ee := (Epoints[i].y-cc);//denominator
if ee <> 0 then ee := (Epoints[i+1].y-cc)/ee;
if ee <= 0 then //test for intersection of asymptote and function
begin
af := true;//abort flag
exit;
end;
ee := ln(ee);
avG := avG + ee;
growthfactor[i] := exp(ee/dx[i]);//save
end;
af := false;
avG := exp(avG /(Epoints[maxpunt].x-Epoints[1].x));//exp added
ee := 0;
for i := 1 to maxpunt-1 do
ee := ee + abs(avG-growthFactor[i]);
result := ee;
end;
procedure makeXYarrays;
var i : byte;
begin
for i := 1 to maxpunt-1 do
begin
dx[i] := Epoints[i+1].x - epoints[i].x;
dy[i] := epoints[i+1].y - epoints[i].y;
dxy[i].x := (epoints[i].x + epoints[i+1].x)/2;
dxy[i].y := dy[i] / dx[i];
end;
end;
procedure ExpAbort(m : byte);
const abortmessage : array[1..2] of string =
('no exp. function',
'numbers out of reach');
begin
controlform.msgtext.caption := abortmessage[m];
controlform.formedit.text := '';
end;
procedure morepointsExp;
var Sum,deltaC,Bvar1,Bvar2 : double;
i : byte;
w : word;
s : string;
abort : boolean;
begin
makeXYarrays;
//approximate value of b ....growthfactor e^b
try
sum := 0;
for i := 1 to maxpunt-2 do
Sum := Sum + ln(dxy[i+1].ydxy[i].y)/(dxy[i+1].x-dxy[i].x);
b := Sum / (maxpunt - 2);
//approximate value of a
Sum := 0;
for i := 1 to maxpunt-1 do
Sum := Sum + dy[i]/(exp(b*epoints[i+1].x) - exp(b*epoints[i].x));
a := Sum / (maxpunt - 1);
//approximate value of c
sum := 0;
for i := 1 to maxpunt do
sum := sum + epoints[i].y-a*exp(b*epoints[i].x);
c := sum / maxpunt;
except
expAbort(1);
exit;
end;
if a > 0 then deltaC := -scaledY //move away from asymptote first
else deltaC := scaledY; //scaledY = vertical distance between 2 pixels
repeat //.. defined in other unit
Bvar1 := Deviation(c,abort);
if abort then c := c + deltaC;
until not abort;
w := 0;
deltaC := -deltaC;//towards asymptote first
repeat
inc(w);
Bvar2 := Deviation(c + deltaC,abort);
if abort then deltaC := deltaC2 //half distance in same direction
else
if Bvar2 < Bvar1 then
begin
Bvar1 := Bvar2; //if better result
c := c + deltaC;
end
else deltaC := -deltaC2;
until (abs(deltaC) <= 1e-8) or (w > 100);
if abort then
begin
expAbort(1);
exit;
end;
if w > 100 then
begin
expAbort(2);
exit;
end;
//call least square procedure to make best a,b
for i := 1 to maxpunt do
epoints[i].y := ln(abs(epoints[i].y-c));
buildexp12(epoints); //this is call to least square unit
if a < 0 then a := -exp(epoints[1].y)
else a := exp(epoints[1].y);
b := epoints[2].y;
//make formula
s := 'y = ' + formatfloat(Flstring,a)+' * ';
if Epower then s := s + 'e^(' + formatfloat(Flstring,b) + 'x)'
else begin
b := exp(b);
s := s + formatfloat(Flstring,b) + '^x';
end;
if c < 0 then begin
c := abs(c);
s := s + ' - ';
end
else s := s + ' + ';
s := s + formatfloat(Flstring,c);
expPlot(s);
end;
procedure makeExpFunction;
begin
if Insufficientpoints(2) then exit;//msg al gepost
GetPoints;
if maxpunt = 2 then
begin
c := 0;
TwoPointsExp;
exit;
end;
morePointsExp;
end;
procedure exp3;
var i : byte;
s : string;
begin
if Insufficientpoints(3) then exit;
getPoints;
for i := 1 to maxpunt do
if epoints[i].y <= 0 then
begin
controlform.msgtext.caption := 'fout: y <= 0';
exit;
end
else epoints[i].y := ln(epoints[i].y);
buildexp3(epoints); //call least squares unit,return a,b,c at epoints[1..3].y
s := 'y = e^(';
s := s + formatfloat(Flstring,epoints[3].y) +'x^2';
if epoints[2].y < 0 then
begin
s := s + ' - ';
epoints[2].y := abs(epoints[2].y);
end
else s := s + ' + ';
s := s + formatfloat(Flstring,epoints[2].y) +'x';
if epoints[1].y < 0 then
begin
s := s + ' - ';
epoints[1].y := abs(epoints[1].y);
end
else s := s + ' + ';
s := s + formatfloat('####0.0#####',epoints[1].y);
s := s + ')';
ExpPlot(s);
end;
procedure exp45;
var i : byte;
s : string;
begin
if InsufficientPoints(2) then exit;
getPoints;
for i := 1 to maxpunt do
if epoints[i].y <= 0 then
begin
controlform.msgtext.caption := 'error: y <= 0';
exit;
end
else epoints[i].y := ln(epoints[i].y);
buildexp12(epoints);//call least squares unit, return a,b at epoints[1,2].y
a := exp(epoints[1].y);
b := epoints[2].y;
if not Epower then b := exp(b);
//a,b,c calculated
s := 'y = ';
s := s + formatfloat(flstring,a) + ' * ';
if Epower then s := s + 'e^(' + formatfloat(Flstring,b) +'x)'
else s := s + formatfloat(Flstring,b) + '^x';
expPlot(s); //call to plot the function, outside this unit
end;
//--- central call to make exponential function of type n
procedure expFunc(n : byte);
begin
case n of
1 : begin
Epower := false; //no power of e
makeExpfunction;
end;
2 : begin
Epower := true; //is power of e
makeExpfunction;
end;
3 : exp3; //build function type 3
4 : begin
Epower := false;
exp45; //build function type 4 or 5
end;
5 : begin
Epower := true;
exp45; //build function type 4 or 5
end;
end;//case
end;
end.
|
|