Генетические алгоритмы
01.01.2007
{ **** UBPFD *********** by kladovka.net.ru **** >> Генетические алгоритмы Класс, реализующий генетический алгоритм. Зависимости: Classes, SysUtils, Windows, Math Автор: Mystic, mystic2000@newmail.ru, ICQ:125905046, Харьков Copyright: Mystic Дата: 25 апреля 2002 г. ********************************************** } unit Genes; interface uses {Fuzzy,} Classes, SysUtils, Windows, Math; type TGeneAlgorithm = class; TExtendedArray = array of Extended; TEstimateEvent = procedure (Sender: TObject; const X: TExtendedArray; var Y: Extended) of object; TIterationEvent = procedure (Sender: TObject; Iteration: Integer); TBestChangeEvent = procedure (Sender: TObject; BestEstimate: Extended); EGeneError = class(Exception) end; TCardinalArray = array of Cardinal; TGeneRecord = record Bits: TCardinalArray; Values: TExtendedArray; Estimate: Extended; end; TGeneRecords = array of TGeneRecord; TSolutionThread = class(TThread) private FOwner: TGeneAlgorithm; protected procedure Execute; override; property Owner: TGeneAlgorithm read FOwner; public constructor Create(AOwner: TGeneAlgorithm); end; TGeneState = (gsExecute, gsSuspend, gsTune); TGeneAlgorithm = class private FData: array of TGeneRecord; // Algorithm data FLock: TRTLCriticalSection; FLowValues: TExtendedArray; FHighValues: TExtendedArray; FSolutionThread: TSolutionThread; FMutation: Extended; FInversion: Extended; FCrossover: Extended; FMaxPopulation: Integer; FBitPerNumber: Integer; FMinPopulation: Integer; FDimCount: Integer; FOnBestChange: TBestChangeEvent; FOnEstimate: TEstimateEvent; FOnIteration: TIterationEvent; FIteration: Integer; // FBestEstimate: Extended; FState: TGeneState; BitSize: Integer; function GetBestEstimate: Extended; function GetHighValues(I: Integer): Extended; function GetIteration: Integer; function GetLowValues(I: Integer): Extended; procedure SetBitPerNumber(const Value: Integer); procedure SetCrossover(const Value: Extended); procedure SetDimCount(const Value: Integer); procedure SetHighValues(I: Integer; const Value: Extended); procedure SetInversion(const Value: Extended); procedure SetLowValues(I: Integer; const Value: Extended); procedure SetMaxPopulation(const Value: Integer); procedure SetMinPopulation(const Value: Integer); procedure SetMutation(const Value: Extended); procedure SetOnBestChange(const Value: TBestChangeEvent); procedure SetOnEstimate(const Value: TEstimateEvent); procedure SetOnIteration(const Value: TIterationEvent); procedure Lock; procedure Unlock; function GetBestX(I: Integer): Extended; function GetState: TGeneState; procedure DoCrossover(N: Integer); procedure DoMutation(N: Integer); procedure DoInversion(N: Integer); procedure EstimatePopulation(StartIndex: Integer); procedure SortPopulation; procedure MakeChild; public // Creation & destroying constructor Create; destructor Destroy; override; // Running / stopping procedure Run; procedure Abort; procedure Suspend; procedure Resume; // Saving / opening procedure LoadFromStream(S: TStream); procedure SaveToStream(S: TStream); // Algorithm param property BitPerNumber: Integer read FBitPerNumber write SetBitPerNumber; property MaxPopulation: Integer read FMaxPopulation write SetMaxPopulation; property MinPopulation: Integer read FMinPopulation write SetMinPopulation; property Crossover: Extended read FCrossover write SetCrossover; property Mutation: Extended read FMutation write SetMutation; property Inversion: Extended read FInversion write SetInversion; property DimCount: Integer read FDimCount write SetDimCount; property LowValues[I: Integer]: Extended read GetLowValues write SetLowValues; property HighValues[I: Integer]: Extended read GetHighValues write SetHighValues; // Info property property Iteration: Integer read GetIteration; property BestX[I: Integer]: Extended read GetBestX; property BestEstimate: Extended read GetBestEstimate; property State: TGeneState read GetState; // Events property OnEstimate: TEstimateEvent read FOnEstimate write SetOnEstimate; property OnIteration: TIterationEvent read FOnIteration write SetOnIteration; property OnBestChange: TBestChangeEvent read FOnBestChange write SetOnBestChange; end; implementation resourcestring SCannotSetParam = 'Невозможно установить параметр %s в состоянии %s'; SCannotGetParam = 'Невозможно прочитать параметр %s в состоянии %s'; SInvalidParam = 'Параметр %s не может быть %s (%d).'; SNonPositive = 'отрицательным или нулевым'; SInvalidProbality = 'вероятность %s должна быть в диапазоне 0..1 (%f).'; SLess2 = 'меньше двух'; SEmpty = 'Неправильный индекс при обращении к %s (%d) при нулевом количества элементов.'; SInvalidIndex = 'Неправильный индекс при обращении к %s (%d). Индекс должен лежать в диапазоне от %d до %d'; SNonEstimate = 'Не задана функция оценки.'; const SState: array[TGeneState] of string = ( 'настройки параметров алгоритма', 'работы алгоритма', 'остановки алгоритма'); { TGeneAlgorithm } procedure TGeneAlgorithm.Abort; var I: Integer; begin if FState=gsExecute then begin FSolutionThread.Terminate; FSolutionThread.WaitFor; end; Lock; try for I:=0 to Length(FData)-1 do begin SetLength(FData[I].Bits, 0); SetLength(FData[I].Values, 0); end; SetLength(FData, 0); FState := gsTune; finally Unlock; end; end; constructor TGeneAlgorithm.Create; begin InitializeCriticalSection(FLock); FBitPerNumber := 8; FMinPopulation := 5000; FMaxPopulation := 10000; FMutation := 0.1; FCrossover := 0.89; FInversion := 0.01; FDimCount := 0; FState := gsTune; end; destructor TGeneAlgorithm.Destroy; begin Abort; DeleteCriticalSection(FLock); SetLength(FLowValues, 0); SetLength(FHighValues, 0); inherited; end; procedure TGeneAlgorithm.DoCrossover(N: Integer); var I: Integer; Parent1, Parent2: Integer; Bit, ByteCount: Integer; BitPos: Byte; Mask: Integer; begin Parent1 := Random(FMinPopulation); Parent2 := Random(FMinPopulation); Bit := Random(FDimCount*FBitPerNumber-1); ByteCount := Bit div 32; for I:=0 to ByteCount-1 do FData[N].Bits[I] := FData[Parent1].Bits[I]; for I:=ByteCount+1 to BitSize-1 do FData[N].Bits[I] := FData[Parent2].Bits[I]; BitPos := Bit - 32*ByteCount; asm MOV CL, BitPos MOV EAX, -1 SHL EAX, CL MOV Mask, EAX end; FData[N].Bits[ByteCount] := (FData[Parent1].Bits[ByteCount] and not Mask) or (FData[Parent2].Bits[ByteCount] and Mask); end; procedure TGeneAlgorithm.DoInversion(N: Integer); function GetBit(Addr: Pointer; No: Integer): Byte; assembler; asm MOV EAX, Addr MOV ECX, No BT [EAX], ECX SBB EAX, EAX AND EAX, 1 end; procedure SetBit(Addr: Pointer; No: Integer; Value: Byte); assembler; asm MOV EAX, Addr OR Value,Value JZ @@1 BTS [EAX], No RET @@1: BTR [EAX], No RET end; var Parent, Bit, I: Integer; B: Byte; begin Parent := Random(FMinPopulation); Bit := Random(FDimCount*FBitPerNumber-1); FData[N].Bits := FData[Parent].Bits; repeat B := GetBit(FData[N].Bits, 0); for I:=0 to FDimCount*FBitPerNumber-2 do SetBit(FData[N].Bits, I, GetBit(FData[N].Bits, I+1)); SetBit(FData[N].Bits, FDimCount*FBitPerNumber-1, B); if Bit=0 then Break; Bit := Bit - 1; until False; end; procedure TGeneAlgorithm.DoMutation(N: Integer); var Parent: Integer; Bit, BitPos, ByteCount: Integer; Mask: Cardinal; begin Parent := Random(FMinPopulation); Bit := Random(FDimCount*FBitPerNumber); ByteCount := Bit div 32; BitPos := Bit - 32 * ByteCount; Mask := 1 shl BitPos; FData[N].Bits := FData[Parent].Bits; FData[N].Bits[ByteCount] := FData[N].Bits[ByteCount] xor Mask; end; procedure TGeneAlgorithm.EstimatePopulation(StartIndex: Integer); var I, J, K, Index: Integer; P, Q, Y: Extended; MaxWeight, Weight: Extended; Addr: Pointer; GrayBit, BinBit: Cardinal; begin MaxWeight := Power(2, FBitPerNumber); for I:=StartIndex to Length(FData)-1 do begin Index := 0; Addr := FData[I].Bits; for J:=0 to FDimCount-1 do begin Weight := 0.5 * MaxWeight; P := 0.0; BinBit := 0; for K:=0 to FBitPerNumber-1 do begin asm MOV EAX, Addr MOV ECX, Index BT [EAX], ECX SBB EAX, EAX AND EAX, 1 MOV GrayBit, EAX INC Index end; BinBit := BinBit xor GrayBit; if BinBit=1 then P := P + Weight; Weight := 0.5 * Weight; end; P := P / MaxWeight; Q := 1 - P; FData[I].Values[J] := P * FHighValues[J] + Q * FLowValues[J]; end; Y := 0; FOnEstimate(Self, FData[I].Values, Y); FData[I].Estimate := Y; end; end; function TGeneAlgorithm.GetBestEstimate: Extended; begin Lock; try Result := 0.0; //Kill warning if FState=gsTune then raise EGeneError.CreateFmt(SCannotGetParam, ['BestEstimate', SState[FState]]); Result := FData[0].Estimate; finally Unlock; end; end; function TGeneAlgorithm.GetBestX(I: Integer): Extended; begin Lock; try Result := 0.0; // Kill warning if FState=gsTune then raise EGeneError.CreateFmt(SCannotGetParam, ['BestX', SState[FState]]); if (FDimCount=0) then raise EGeneError.CreateFmt(SEmpty, ['BestX', I]); if (I<0) or (I>=FDimCount) then raise EGeneError.CreateFmt(SInvalidIndex, ['BestX', I, 0, DimCount]); Result := FData[0].Values[I]; finally Unlock; end; end; function TGeneAlgorithm.GetHighValues(I: Integer): Extended; begin Lock; try Result := 0.0; // Kill warning if FState <> gsTune then raise EGeneError.CreateFmt(SCannotGetParam, ['HighValues', SState[FState]]); if (FDimCount=0) then raise EGeneError.CreateFmt(SEmpty, ['HighValues', I]); if (I<0) or (I>=FDimCount) then raise EGeneError.CreateFmt(SInvalidIndex, ['HighValues', I, 0, DimCount]); Result := FHighValues[I]; finally Unlock; end; end; function TGeneAlgorithm.GetIteration: Integer; begin Lock; try Result := 0; // Kill warning if FState=gsTune then raise EGeneError.CreateFmt(SCannotGetParam, ['Iteration', SState[FState]]); Result := FIteration; finally Unlock; end; end; function TGeneAlgorithm.GetLowValues(I: Integer): Extended; begin Lock; try Result := 0.0; // Kill warning if FState <> gsTune then raise EGeneError.CreateFmt(SCannotGetParam, ['LowValues', SState[FState]]); if (FDimCount=0) then raise EGeneError.CreateFmt(SEmpty, ['LowValues', I]); if (I<0) or (I>=FDimCount) then raise EGeneError.CreateFmt(SInvalidIndex, ['LowValues', I, 0, DimCount]); Result := FLowValues[I]; finally Unlock; end; end; function TGeneAlgorithm.GetState: TGeneState; begin Lock; try Result := FState; finally Unlock; end; end; procedure TGeneAlgorithm.LoadFromStream(S: TStream); begin end; procedure TGeneAlgorithm.Lock; begin EnterCriticalSection(FLock); end; procedure TGeneAlgorithm.MakeChild; var I: Integer; RandomValue: Extended; begin for I:=FMinPopulation to FMaxPopulation-1 do begin RandomValue := Random; if RandomValue<FCrossover then DoCrossover(I) else if RandomValue<FCrossover+FMutation then DoMutation(I) else DoInversion(I); end; end; procedure TGeneAlgorithm.Resume; begin if FState <> gsSuspend then raise EGeneError.Create('Прежде чем возобновить, надо начать!'); FSolutionThread.Create(Self); FState := gsExecute; end; procedure TGeneAlgorithm.Run; var I, J: Integer; b1, b2: Cardinal; begin Lock; try if not Assigned(FOnEstimate) then raise EGeneError.Create(SNonEstimate); Abort; try // Getting memory SetLength(FData, FMaxPopulation); for I:=0 to Length(FData)-1 do begin FData[I].Values := nil; FData[I].bits := nil; end; BitSize := FDimCount * FBitPerNumber + 31; BitSize := BitSize and not 31; BitSize := BitSize div 32; for I:=0 to Length(FData)-1 do begin SetLength(FData[I].Values, DimCount); SetLength(FData[I].Bits, BitSize); end; // Initializing Population for I:=0 to Length(FData)-1 do begin for J:=0 to BitSize-1 do begin b1 := Random(35536); b2 := Random(35536); FData[I].Bits[J] := b1 shl 16 + b2; end; end; EstimatePopulation(0); SortPopulation; FIteration := 0; FState := gsExecute; FSolutionThread := TSolutionThread.Create(Self); except Abort; end; finally Unlock; end; end; procedure TGeneAlgorithm.SaveToStream(S: TStream); begin end; procedure TGeneAlgorithm.SetBitPerNumber(const Value: Integer); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['BitPerNumber', SState[FState]]); if Value<=0 then raise EGeneError.CreateFmt(SInvalidParam, ['BitPerNumber', SNonPositive, Value]); FBitPerNumber := Value; finally Unlock; end; end; procedure TGeneAlgorithm.SetCrossover(const Value: Extended); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['Crossover', SState[FState]]); if (Value<0) or (Value>1) then raise EGeneError.CreateFmt(SInvalidProbality, ['кроссовера', Value]); FCrossover := Value; if FCrossover + FMutation > 1.0 then begin FMutation := 1.0 - FCrossover; FInversion := 0.0; end else begin FInversion := 1.0 - FMutation - FCrossover; end; finally Unlock; end; end; procedure TGeneAlgorithm.SetDimCount(const Value: Integer); var I: Integer; begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['DimCount', SState[FState]]); if FDimCount=Value then Exit; if Value<=0 then raise EGeneError.CreateFmt(SInvalidParam, ['DimCount', SNonPositive, Value]); SetLength(FLowValues, Value); SetLength(FHighValues, Value); for I:=FDimCount to Value-1 do begin FLowValues[I] := 0.0; FHighValues[I] := 1.0; end; FDimCount := Value; finally Unlock; end; end; procedure TGeneAlgorithm.SetHighValues(I: Integer; const Value: Extended); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['HighValues', SState[FState]]); if (FDimCount=0) then raise EGeneError.CreateFmt(SEmpty, ['HighValues', Value]); if (I<0) or (I>=FDimCount) then raise EGeneError.CreateFmt(SInvalidIndex, ['HighValues', Value, 0, DimCount]); FHighValues[I] := Value; if FLowValues[I] > FHighValues[I] then FLowValues[I] := FHighValues[I]; finally Unlock; end; end; procedure TGeneAlgorithm.SetInversion(const Value: Extended); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['Crossover', SState[FState]]); if (Value<0) or (Value>1) then raise EGeneError.CreateFmt(SInvalidProbality, ['инверсии', Value]); FInversion := Value; if FCrossover + FInversion > 1.0 then begin FCrossover := 1.0 - FInversion; FMutation := 0.0; end else begin FMutation := 1.0 - FInversion - FCrossover; end; finally Unlock; end; end; procedure TGeneAlgorithm.SetLowValues(I: Integer; const Value: Extended); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['LowValues', SState[FState]]); if (FDimCount=0) then raise EGeneError.CreateFmt(SEmpty, ['LowValues', Value]); if (I<0) or (I>=FDimCount) then raise EGeneError.CreateFmt(SInvalidIndex, ['LowValues', Value, 0, DimCount]); FLowValues[I] := Value; if FHighValues[I] < FLowValues[I] then FHighValues[I] := FLowValues[I]; finally Unlock; end; end; procedure TGeneAlgorithm.SetMaxPopulation(const Value: Integer); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['MaxPopulation', SState[FState]]); if Value<2 then raise EGeneError.CreateFmt(SInvalidParam, ['MaxPopulation', SLess2, Value]); FMaxPopulation := Value; if FMinPopulation >= FMaxPopulation then FMinPopulation := FMaxPopulation - 1; finally Unlock; end; end; procedure TGeneAlgorithm.SetMinPopulation(const Value: Integer); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['MinPopulation', SState[FState]]); if Value<=0 then raise EGeneError.CreateFmt(SInvalidParam, ['MinPopulation', SNonPositive, Value]); FMinPopulation := Value; if FMinPopulation >= FMaxPopulation then FMaxPopulation := FMinPopulation + 1; finally Unlock; end; end; procedure TGeneAlgorithm.SetMutation(const Value: Extended); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['Crossover', SState[FState]]); if (Value<0) or (Value>1) then raise EGeneError.CreateFmt(SInvalidProbality, ['мутации', Value]); FMutation := Value; if FCrossover + FMutation > 1.0 then begin FCrossover := 1.0 - FMutation; FInversion := 0.0; end else begin FInversion := 1.0 - FMutation - FCrossover; end; finally Unlock; end; end; procedure TGeneAlgorithm.SetOnBestChange(const Value: TBestChangeEvent); begin Lock; try FOnBestChange := Value; finally Unlock; end; end; procedure TGeneAlgorithm.SetOnEstimate(const Value: TEstimateEvent); begin Lock; try if FState <> gsTune then raise EGeneError.CreateFmt(SCannotSetParam, ['OnEstimate', SState[FState]]); FOnEstimate := Value; finally Unlock; end; end; procedure TGeneAlgorithm.SetOnIteration(const Value: TIterationEvent); begin Lock; try FOnIteration := Value; finally Unlock; end; end; procedure TGeneAlgorithm.SortPopulation; procedure QuickSort(L, R: Integer); var I, J: Integer; P: Extended; T: TGeneRecord; begin repeat I := L; J := R; P := FData[(L + R) shr 1].Estimate; repeat while FData[I].Estimate > P do Inc(I); while FData[J].Estimate < P do Dec(J); if I <= J then begin if (I=0) or (J=0) then Lock; try T := FData[I]; FData[I] := FData[J]; FData[J] := T; finally if (I=0) or (J=0) then UnLock; end; Inc(I); Dec(J); end; until I > J; if L < J then QuickSort(L, J); L := I; until I >= R; end; begin QuickSort(0, Length(FData) - 1); end; procedure TGeneAlgorithm.Suspend; begin if FState<>gsExecute then raise EGeneError.Create('Прежде чем остановить, надо запустить!'); FSolutionThread.Terminate; // FSolutionThread.WaitFor; FState := gsSuspend; end; procedure TGeneAlgorithm.Unlock; begin LeaveCriticalSection(FLock); end; { TSolutionThread } constructor TSolutionThread.Create(AOwner: TGeneAlgorithm); begin FOwner := AOwner; FreeOnTerminate := True; inherited Create(False); end; procedure TSolutionThread.Execute; begin repeat Owner.MakeChild; Owner.EstimatePopulation(Owner.FMinPopulation); Owner.SortPopulation; Inc(Owner.FIteration); until Terminated; Sleep(10); end; end.
Пример использования:
unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Genes, ExtCtrls, Grids; type TForm1 = class(TForm) Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Button1: TButton; Button2: TButton; Button3: TButton; Edit4: TEdit; Button4: TButton; Button5: TButton; Timer1: TTimer; Button7: TButton; Label1: TLabel; Grid: TStringGrid; Label2: TLabel; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure Button5Click(Sender: TObject); procedure Button7Click(Sender: TObject); procedure Timer1Timer(Sender: TObject); private procedure Refresh; procedure GeneEstimate(Sender: TObject; const X: TExtendedArray; var Y: Extended); public FGene: TGeneAlgorithm; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin DecimalSeparator := '.'; FGene := TGeneAlgorithm.Create; Refresh; end; procedure TForm1.FormDestroy(Sender: TObject); begin FGene.Free; end; procedure TForm1.Refresh; begin Edit1.Text := FloaTtoStr(FGene.Crossover); Edit2.Text := FloatToStr(FGene.Mutation); Edit3.Text := FloatToStr(FGene.Inversion); end; procedure TForm1.Button1Click(Sender: TObject); begin FGene.Crossover := StrTofloat(Edit1.Text); Refresh; end; procedure TForm1.Button2Click(Sender: TObject); begin FGene.Mutation := StrTofloat(Edit2.Text); Refresh; end; procedure TForm1.Button3Click(Sender: TObject); begin FGene.Inversion := StrTofloat(Edit3.Text); Refresh; end; procedure TForm1.Button4Click(Sender: TObject); begin FGene.BitPerNumber := StrToInt(Edit4.Text); Edit4.Text := IntToStr(FGene.BitPerNumber); end; procedure TForm1.Button5Click(Sender: TObject); var I: Integer; begin Randomize; FGene.DimCount := 5; FGene.MaxPopulation := 10000; FGene.MinPopulation := 5000; FGene.OnEstimate := GeneEstimate; for I:=0 to 4 do begin FGene.LowValues[I] := 0; FGene.HighValues[I] := 10; end; FGene.Run; Timer1.Enabled := True; end; procedure TForm1.GeneEstimate(Sender: TObject; const X: TExtendedArray; var Y: Extended); var I: Integer; begin Y := 0; for I:=Low(X) to High(X) do Y := Y + Sqr(X[I]-I); Y := -Y; end; procedure TForm1.Button7Click(Sender: TObject); var I: Integer; begin Timer1.Enabled := False; Label1.Caption := ''; FGene.Suspend; Grid.RowCount := FGene.DimCount + 1; for I:=0 to FGene.DimCount-1 do Grid.Cells[0,I+1] := FloattoStr(FGene.BestX[I]); FGene.Abort; end; procedure TForm1.Timer1Timer(Sender: TObject); begin Label1.Caption := FloatToStr(FGene.BestEstimate); end; end.