После большого перерыва хочу опять попросить вашей помощи. Указания Мат-Ламера мне очень помогли, я сделал как он сказал - воспользовался методом комбинирования минимизируемой функции с множителями Лагранжа и с штрафной функцией. Я написал код, который решает задачу. Но есть проблема. Искомое решение с заданной точностью ищется слишком медленно. На моем компьютере поиск занимает 35 секунд, в то время как требуется решать задачу за 0.1 секунды. Пока код не оптимизирован, но дело не в этом, а в математике (пробовал заняться оптимизацией, но выйти из 30 секунд все равно не получается). За первые 2 цикла приближение идет очень быстро (точность 1е-4 (значение штрафной функции)). А вот потом код работает практически в холостую и требуемая точность (1е-10) достигается примерно за 1000 циклов. Для минимизации целевой функции я использую метод сопряженных градиентов Fletcher and Reeves (Bazaraa, 2006, страница 423). У меня не получилось сделать, как там написано, выход из цикла если градиент целевой функции меньше критического значения. У меня градиент не уменьшается, а колеблется (то больше, то меньше, но почти никогда не бывает очень маленьким). Косвенное подтверждение этому феномену я нашел в этой книге (Бертсекас, 1982, страница 66). Но может быть я просто что-то делаю неправильно или можно применить какой-то другой более эффективный метод для минимизации по направлению. В моем алгоритме выход происходит, когда целевая функция перестает существенно изменяться. Я использую довольно странный метод одномерной минимизации, который я взял из (Capitani and Brown, 1987), но для этой задачи он в несколько раз эффективнее метода золотого сечения. В общем можно сказать, что я много чего перепробовал и уже даже и не знаю, что можно еще сделать, чтобы повысить эффективность вычислений.
Вот собственно код, написанный на Delphi, выход из которого нужно сделать более эффективным:
Код:
repeat
dk_max:=-1e+100;
for i := 0 to n-1 do
if abs(dk[i])>dk_max then dk_max:=abs(dk[i]);
step:=1/dk_max;
Gbulk_old:=Gbulk;
k:=0;
repeat //One-dimentional minimization (Capitani and Brown, 1987)
for i := 0 to n-1 do
x_1[i]:=x[i]+step*dk[i];
Gbulk_1:=GibbsDuhem(x_1,G0,n)+Lagrange(x_1,Lag,aij,b,n,m)+Penalty(x_1,aij,b,rk,n,m)+Lagrange_pH(x_1,Lag,aij,b,n,m)+Penalty_pH(x_1,aij,b,rk,n,m);
if Gbulk_1<Gbulk then begin
step:=step+step;
for i := 0 to n-1 do
x[i]:=x_1[i];
Gbulk:=Gbulk_1;
end else begin
f:=1;
repeat
step:=step/2/f;
for i := 0 to n-1 do
x_1[i]:=x[i]+step*dk[i];
Gbulk_1:=GibbsDuhem(x_1,G0,n)+Lagrange(x_1,Lag,aij,b,n,m)+Penalty(x_1,aij,b,rk,n,m)+Lagrange_pH(x_1,Lag,aij,b,n,m)+Penalty_pH(x_1,aij,b,rk,n,m);
Inc(f);
until (Gbulk_1<Gbulk) or (f>100) or (abs(step*dk_max)<eps);
end;
Inc(k);
until (Gbulk_1>Gbulk) or (k>100); //Finish of one-dimentional minimization
if Gbulk_old-Gbulk<eps*abs(Gbulk) then begin //Exit from the cycle if the function doesn't change
if lll=0 then
for i := 0 to n-1 do
x_bu[i]:=x[i];
Inc(lll);
if lll>10 then break;
end else lll:=0;
gradx_old:=gradx;
gradx:=0;
for i:=0 to n-1 do begin //Calculate new gradient
g[i]:=GibbsDuhemDerivative(x,G0,i,n)+LagrangeDerivative(Lag,aij,i,m)+PenaltyDerivative(x,aij,b,rk,i,n,m)+PenaltyDerivative_pH(x,aij,b,rk,i,n,m)+LagrangeDerivative_pH(Lag,aij,i,m);
gradx:=gradx+g[i]*g[i];
end;
Betta:= gradx/gradx_old; //Calculate new conjugate directions
if fff>20 then begin //Refresh of conjugate directions every 20 steps
for i:=0 to n-1 do
dk[i]:=-g[i];
fff:=0; end else begin
for i:=0 to n-1 do
dk[i]:=-g[i]+Betta*dk[i];
Inc(fff);
end;
Inc(o);
until o>1000;
Я не знаю, могу ли я вставлять сюда такие большие куски кода, но возможно, что неэффективная процедура зашита в другом месте. Поэтому рискну вставить сюда весь текст програмы. Также исходники можно скачать здесь (сама програма в папке /Win32/Debug, она берет исходные параметры для вычислений из файла ini):
https://drive.google.com/file/d/0B08REaASOrMBSVd3RDRqWk1hOUk/edit?usp=sharingКод:
unit Lagrange_Multipliers;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, Grids, StdCtrls, IniFiles, Math, DateUtils;
type
TA = array of array of extended;
TForm1 = class(TForm)
StringGrid1: TStringGrid;
StringGrid2: TStringGrid;
EPSEdit: TEdit;
TimeEdit: TEdit;
Label1: TLabel;
Label2: TLabel;
Button14: TButton;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormPaint(Sender: TObject);
procedure Button14Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
IniFileName: string;
IniFile: TIniFile;
IniFormSaveName: string;
IniFormSaveFile: TIniFile;
const
R: extended = 1.985877534;
T: extended = 298.15;
implementation
{$R *.dfm}
procedure ReadIni; //Read data from ini
var
IniPath: string;
k, m: integer;
begin
GetDir(0,IniPath);
IniFileName:=IniPath+'\Crono.ini';
if FileExists(IniFileName) then
begin
IniFile:=TIniFile.Create(IniFileName);
Form1.Left:=IniFile.ReadInteger('position','left',100);
Form1.Top:=IniFile.ReadInteger('position','top',100);
Form1.StringGrid1.ColWidths[0]:=20;
Form1.StringGrid1.Cells[1,0]:='Phase';
Form1.StringGrid1.ColWidths[1]:=60;
Form1.StringGrid1.Cells[2,0]:='G';
Form1.StringGrid1.ColWidths[2]:=60;
Form1.StringGrid1.Cells[3,0]:='O';
Form1.StringGrid1.ColWidths[3]:=20;
Form1.StringGrid1.Cells[4,0]:='H';
Form1.StringGrid1.ColWidths[4]:=20;
Form1.StringGrid1.Cells[5,0]:='Si';
Form1.StringGrid1.ColWidths[5]:=20;
Form1.StringGrid1.Cells[6,0]:='z';
Form1.StringGrid1.ColWidths[6]:=20;
Form1.StringGrid1.Cells[7,0]:='pH';
Form1.StringGrid1.ColWidths[7]:=20;
Form1.StringGrid1.Cells[8,0]:='x';
Form1.StringGrid2.Cells[0,0]:='Phase';
Form1.StringGrid2.ColWidths[0]:=35;
Form1.StringGrid2.Cells[0,1]:='O';
Form1.StringGrid2.Cells[0,2]:='H';
Form1.StringGrid2.Cells[0,3]:='Si';
Form1.StringGrid2.Cells[0,4]:='z';
Form1.StringGrid2.Cells[0,5]:='pH';
Form1.StringGrid2.Cells[0,6]:='G';
Form1.StringGrid2.Cells[0,7]:='P';
Form1.StringGrid2.Cells[1,0]:='a*x';
Form1.StringGrid2.Cells[2,0]:='g';
Form1.StringGrid2.Cells[3,0]:='Lag';
Form1.StringGrid2.Cells[4,0]:='InSum';
Form1.StringGrid2.ColWidths[4]:=60;
m:=1;
for k := 1 to Form1.StringGrid1.RowCount-1 do
begin
Form1.StringGrid1.Cells[0,k]:=IntToStr(m);
Form1.StringGrid1.Cells[1,k]:=IniFile.ReadString('Name of phase',IntToStr(m),'');
Form1.StringGrid1.Cells[2,k]:=IniFile.ReadString('G',IntToStr(m),'');
Form1.StringGrid1.Cells[3,k]:=IniFile.ReadString('O',IntToStr(m),'');
Form1.StringGrid1.Cells[4,k]:=IniFile.ReadString('H',IntToStr(m),'');
Form1.StringGrid1.Cells[5,k]:=IniFile.ReadString('Si',IntToStr(m),'');
Form1.StringGrid1.Cells[6,k]:=IniFile.ReadString('z',IntToStr(m),'');
Form1.StringGrid1.Cells[7,k]:=IniFile.ReadString('pH',IntToStr(m),'');
Inc(m);
end;
m:=1;
for k := 1 to Form1.StringGrid2.RowCount-1 do
begin
Form1.StringGrid2.Cells[4,k]:=IniFile.ReadString('InSum',IntToStr(m),'');
Inc(m);
end;
IniFile.Free;
end else
Form1.Position:=poScreenCenter;
end;
procedure WriteIni; //Save data in ini
var
k, m: integer;
begin
if FileExists(IniFileName) then
if not(DeleteFile(IniFileName)) then
ShowMessage('Crono did not delete ini file');
IniFile:=TIniFile.Create(IniFileName);
IniFile.WriteInteger('position','left',Form1.Left);
IniFile.WriteInteger('position','top',Form1.Top);
m:=1;
for k := 1 to Form1.StringGrid1.RowCount-1 do
begin
IniFile.WriteString('Name of phase',IntToStr(m),Form1.StringGrid1.Cells[1,k]);
IniFile.WriteString('G',IntToStr(m),Form1.StringGrid1.Cells[2,k]);
IniFile.WriteString('O',IntToStr(m),Form1.StringGrid1.Cells[3,k]);
IniFile.WriteString('H',IntToStr(m),Form1.StringGrid1.Cells[4,k]);
IniFile.WriteString('Si',IntToStr(m),Form1.StringGrid1.Cells[5,k]);
IniFile.WriteString('z',IntToStr(m),Form1.StringGrid1.Cells[6,k]);
IniFile.WriteString('pH',IntToStr(m),Form1.StringGrid1.Cells[7,k]);
Inc(m);
end;
m:=1;
for k := 1 to Form1.StringGrid2.RowCount-1 do
begin
IniFile.WriteString('InSum',IntToStr(m),Form1.StringGrid2.Cells[4,k]);
Inc(m);
end;
IniFile.Free;
end;
function GibbsDuhem(x:array of extended; G0:array of extended; n: integer): extended;
var
xsum, Gbulk: extended;
i: integer;
begin
xsum:=0;
for i := 0 to n-1 do
xsum:=xsum+exp(x[i]);
Gbulk:=0;
for i := 0 to n-1 do
Gbulk:=Gbulk+exp(x[i])*(G0[i]+x[i]-ln(xsum));
Result:=Gbulk;
end;
function Lagrange(x: array of extended; Lag: array of extended; aij: TA; b: array of extended; n: integer; m: integer): extended;
var
P, ggg: extended;
i, k: integer;
begin
P:=0;
for k := 0 to m-2 do begin
ggg:=0;
for i := 0 to n-1 do
ggg:=ggg+exp(x[i])*aij[k,i];
P:=P+Lag[k]*(ggg-b[k]);
end;
Result:=P;
end;
function Lagrange_pH(x: array of extended; Lag: array of extended; aij: TA; b: array of extended; n: integer; m: integer): extended;
var
xmult: extended;
l: integer;
begin
xmult:=0;
for l := 0 to n-1 do
xmult:=xmult+aij[m-1,l]*x[l];
Result:=Lag[m-1]*(xmult/ln(10)+b[m-1]); //ln(10)=2.30258509299405
end;
function Penalty(x:array of extended; aij: TA; b: array of extended; rk: extended; n: integer; m: integer): extended;
var
P, ggg: extended;
i, k: integer;
begin
P:=0;
for k := 0 to m-2 do begin
ggg:=0;
for i := 0 to n-1 do
ggg:=ggg+exp(x[i])*aij[k,i];
P:=P+(ggg-b[k])*(ggg-b[k]);
end;
Result:=0.5*rk*P;
end;
function Penalty_pH(x:array of extended; aij: TA; b: array of extended; rk: extended; n: integer; m: integer): extended;
var
xmult: extended;
l: integer;
begin
xmult:=0;
for l := 0 to n-1 do
xmult:=xmult+aij[m-1,l]*x[l];
Result:=0.5*rk*(xmult/ln(10)+b[m-1])*(xmult/ln(10)+b[m-1]); //ln(10)=2.30258509299405
end;
function GibbsDuhemDerivative(x:array of extended; G0:array of extended; i: integer; n:integer): extended;
var
xsum: extended;
l: integer;
begin
xsum:=0;
for l := 0 to n-1 do
xsum:=xsum+exp(x[l]);
Result:=G0[i]+x[i]-ln(xsum);
end;
function LagrangeDerivative(Lag: array of extended; aij: TA; i: integer; m:integer): extended;
var
aaa: extended;
l: integer;
begin
aaa:=0;
for l := 0 to m-2 do
aaa:=aaa+aij[l,i]*Lag[l];
Result:=aaa;
end;
function LagrangeDerivative_pH(Lag: array of extended; aij: TA; i: integer; m:integer): extended;
begin
Result:=aij[m-1,i]*Lag[m-1]/ln(10); //ln(10)=2.30258509299405
end;
function PenaltyDerivative(x:array of extended; aij: TA; b: array of extended; rk: extended; i: integer; n:integer; m:integer): extended;
var
aaa, ggg: extended;
l, k: integer;
begin
aaa:=0;
for l := 0 to m-2 do begin
ggg:=0;
for k := 0 to n-1 do
ggg:=ggg+exp(x[k])*aij[l,k];
aaa:=aaa+aij[l,i]*(ggg-b[l]);
end;
Result:=rk*aaa;
end;
function PenaltyDerivative_pH(x:array of extended; aij: TA; b: array of extended; rk: extended; i: integer; n:integer; m:integer): extended;
var
xmult: extended;
l: integer;
begin
xmult:=0;
for l := 0 to n-1 do
xmult:=xmult+aij[m-1,l]*x[l];
Result:=aij[m-1,i]*rk/ln(10)*(xmult/ln(10)+b[m-1]); //ln(10)=2.30258509299405
end;
procedure TForm1.Button14Click(Sender: TObject);
var
i, k, l, m, n, o, ii, lll, f, fff: integer;
eps, step, Gbulk, Gbulk_old, Gbulk_1, rk, gradx, gradx_old, Betta, P_new, P_old, ggg, dk_max: extended;
aij: TA;
G0, x, b, g, dk, x_1, x_bu, Lag: array of extended;
TimeStart: TDateTime;
begin
try
TimeStart:=Now;
n:=0;
for i := 1 to Form1.StringGrid1.RowCount-1 do
if Form1.StringGrid1.Cells[6,i]<>'' then Inc(n);
SetLength(G0,n);
m:=Form1.StringGrid1.ColCount-4;
SetLength(aij,n+1,m+1);
k:=0;
for i := 1 to Form1.StringGrid1.RowCount-1 do
if Form1.StringGrid1.Cells[6,i]<>'' then begin
G0[k]:=StrToFloat(Form1.StringGrid1.Cells[2,i])/(R*T); //Standard Gibbs energies
Inc(k);
end;
l:=0;
repeat
k:=0;
repeat
aij[l,k]:=StrToFloat(StringGrid1.Cells[l+3,k+3]); //Stoichiometric coefficients
Inc(k);
until k=n;
Inc(l);
until l=m;
SetLength(x,n+1); //Variables
SetLength(x_1,n+1);
SetLength(x_bu,n+1);
for i:=0 to n-1 do
x[i]:=0;
SetLength(Lag, m+1); //Lagrange Multipliers
for i:=0 to m-1 do
Lag[i]:=0;
eps:=StrToFloat(EPSEdit.Text); //Precision parameter
rk:=100; //The initial rk value (100) was taken from (Lantagne et al., 1988)
SetLength(b,m); //Constraints
for i := 1 to m do
b[i-1]:=StrToFloat(StringGrid2.Cells[4,i]);
SetLength(g,n); //Gradient
SetLength(dk,n); //Conjugate directions
l:=0;
repeat //Start of the minimization cycle
o:=0;
lll:=0;
fff:=0;
P_new:=Penalty(x,aij,b,rk,n,m)+Penalty_pH(x,aij,b,rk,n,m);
Gbulk:=GibbsDuhem(x,G0,n)+Lagrange(x,Lag,aij,b,n,m)+Lagrange_pH(x,Lag,aij,b,n,m)+P_new;
gradx:=0;
for i:=0 to n-1 do begin
g[i]:=GibbsDuhemDerivative(x,G0,i,n)+LagrangeDerivative(Lag,aij,i,m)+PenaltyDerivative(x,aij,b,rk,i,n,m)+PenaltyDerivative_pH(x,aij,b,rk,i,n,m)+LagrangeDerivative_pH(Lag,aij,i,m);
dk[i]:=-g[i];
gradx:=gradx+g[i]*g[i];
end;
repeat
dk_max:=-1e+100;
for i := 0 to n-1 do
if abs(dk[i])>dk_max then dk_max:=abs(dk[i]);
step:=1/dk_max;
Gbulk_old:=Gbulk;
k:=0;
repeat //One-dimentional minimization (Capitani and Brown, 1987)
for i := 0 to n-1 do
x_1[i]:=x[i]+step*dk[i];
Gbulk_1:=GibbsDuhem(x_1,G0,n)+Lagrange(x_1,Lag,aij,b,n,m)+Penalty(x_1,aij,b,rk,n,m)+Lagrange_pH(x_1,Lag,aij,b,n,m)+Penalty_pH(x_1,aij,b,rk,n,m);
if Gbulk_1<Gbulk then begin
step:=step+step;
for i := 0 to n-1 do
x[i]:=x_1[i];
Gbulk:=Gbulk_1;
end else begin
f:=1;
repeat
step:=step/2/f;
for i := 0 to n-1 do
x_1[i]:=x[i]+step*dk[i];
Gbulk_1:=GibbsDuhem(x_1,G0,n)+Lagrange(x_1,Lag,aij,b,n,m)+Penalty(x_1,aij,b,rk,n,m)+Lagrange_pH(x_1,Lag,aij,b,n,m)+Penalty_pH(x_1,aij,b,rk,n,m);
Inc(f);
until (Gbulk_1<Gbulk) or (f>100) or (abs(step*dk_max)<eps);
end;
Inc(k);
until (Gbulk_1>Gbulk) or (k>100); //Finish of one-dimentional minimization
if Gbulk_old-Gbulk<eps*abs(Gbulk) then begin //Exit from the cycle if the function doesn't change
if lll=0 then
for i := 0 to n-1 do
x_bu[i]:=x[i];
Inc(lll);
if lll>10 then break;
end else lll:=0;
gradx_old:=gradx;
gradx:=0;
for i:=0 to n-1 do begin //Calculate new gradient
g[i]:=GibbsDuhemDerivative(x,G0,i,n)+LagrangeDerivative(Lag,aij,i,m)+PenaltyDerivative(x,aij,b,rk,i,n,m)+PenaltyDerivative_pH(x,aij,b,rk,i,n,m)+LagrangeDerivative_pH(Lag,aij,i,m);
gradx:=gradx+g[i]*g[i];
end;
Betta:= gradx/gradx_old; //Calculate new conjugate directions
if fff>20 then begin //Refresh of conjugate directions every 20 steps
for i:=0 to n-1 do
dk[i]:=-g[i];
fff:=0; end else begin
for i:=0 to n-1 do
dk[i]:=-g[i]+Betta*dk[i];
Inc(fff);
end;
Inc(o);
until o>1000;
if lll>0 then
for i := 0 to n-1 do
x[i]:=x_bu[i];
P_old:=P_new;
P_new:=Penalty(x,aij,b,rk,n,m)+Penalty_pH(x,aij,b,rk,n,m);
if (P_old>P_new) and (P_new>P_old/4) then
rk:=rk*32; //The multiplier value (32) was taken from (Lantagne et al., 1988)
for i := 0 to m-2 do begin //Calculate Lagrange multipliers
ggg:=0;
for ii := 0 to n-1 do
ggg:=ggg+exp(x[ii])*aij[i,ii];
Lag[i]:=Lag[i]+rk*(ggg-b[i]);
end;
i:=m-1;
ggg:=0;
for ii := 0 to n-1 do
ggg:=ggg+aij[i,ii]*x[ii];
Lag[i]:=Lag[i]+rk*(ggg/ln(10)+b[i]);
Inc(l);
until (l>1000) or (sqrt(2*P_new/rk)<eps); //Finish of the minimization cycle
for i := 0 to n-1 do //Write the obtained result in the table
Form1.StringGrid1.Cells[8,i+3]:=FloatToStr(exp(x[i]));
Form1.StringGrid2.Cells[1,1]:=FloatToStr(exp(x[0])+exp(x[3])*3+exp(x[4])*2+exp(x[5])+exp(x[6])*2);
Form1.StringGrid2.Cells[1,2]:=FloatToStr(exp(x[0])*2+exp(x[1])+exp(x[2])*2+exp(x[3])+exp(x[5]));
Form1.StringGrid2.Cells[1,3]:=FloatToStr(exp(x[3])+exp(x[6]));
Form1.StringGrid2.Cells[1,4]:=FloatToStr(exp(x[1])-exp(x[3])-exp(x[5]));
Form1.StringGrid2.Cells[1,5]:=FloatToStr(-log10(exp(x[1])));
Form1.StringGrid2.Cells[1,6]:=FloatToStr(GibbsDuhem(x,G0,n)*R*T);
Form1.StringGrid2.Cells[1,7]:=FloatToStr(sqrt(2*P_new/rk));
Form1.StringGrid2.Cells[2,1]:=FloatToStr(g[0]);
Form1.StringGrid2.Cells[2,2]:=FloatToStr(g[1]);
Form1.StringGrid2.Cells[2,3]:=FloatToStr(g[2]);
Form1.StringGrid2.Cells[2,4]:=FloatToStr(g[3]);
Form1.StringGrid2.Cells[2,5]:=FloatToStr(g[4]);
Form1.StringGrid2.Cells[2,6]:=FloatToStr(g[5]);
Form1.StringGrid2.Cells[2,7]:=FloatToStr(g[6]);
Form1.StringGrid2.Cells[3,1]:=FloatToStr(Lag[0]);
Form1.StringGrid2.Cells[3,2]:=FloatToStr(Lag[1]);
Form1.StringGrid2.Cells[3,3]:=FloatToStr(Lag[2]);
Form1.StringGrid2.Cells[3,4]:=FloatToStr(Lag[3]);
Form1.StringGrid2.Cells[3,5]:=FloatToStr(Lag[4]);
TimeEdit.Text:=FormatDateTime('hh:nn:ss:zzz', Now-TimeStart); //Calculate the duration of calculation
except
Application.MessageBox('Fatal error #0001 happened! ','Lagrange Multipliers',MB_OK);
Abort;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
WriteIni;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
ReadIni;
end;
end.