The TPopulation class is the last and most important piece of the puzzle. It is responsible for bringing together the functionality provided by the classes detailed in the last seven sections to implement the evolutionary algorithm.
Those familiar with design patterns will notice the heavy use that I have made of the Strategy (or Policy) pattern in the design of the TPopulation. This is done, essentially, to decouple any problem-specific behaviour from the Population component. The TPopulation has no knowledge of the travelling salesman problem, or any other problem. It simply implements the evolutionary algorithm shown in figure 4.
In order to apply the evolutionary algorithm to a problem, such as the TSP, descendants of the base strategy components must be created to implement the required functionality. The population then applies the evolutionary algorithm using the strategy components. Figure 7 illustrates this.
fig. 7: The EA implemented using the strategy pattern
The algorithm shown in figure 7 is implemented in the "Initialise" and "Generation" methods. These are shown in listing 16.
procedure TPopulation.Initialise(Size: Integer);
var
i : Integer;
New : TIndividual;
begin
// Clear out the old stuff
Clear;
// Set the capacity first to save about 12 nanoseconds ;o)
FPop.Capacity := Size;
// Create the appropriate number of individuals
for i := 1 to Size do
begin
// Create the individual
New := FCreator.CreateIndividual;
// Get the fitness of the new individual
New.Fitness := FExaminer.GetFitness(New);
// Add to the population
Add(New);
end;
SortPopulation;
Change;
end;
procedure TPopulation.Generation;
var
Replace, i : Integer;
New : TIndividual;
begin
// Kill some of the population
Replace := FKiller.Kill(Self);
for i := 1 to Replace do
begin
// Breed a new individual
New := FBreeder.BreedOffspring(FParentSelector, Self);
// Perform some mutation on the individual
FMutator.Mutate(New);
// Get the fitness of the new individual
New.Fitness := FExaminer.GetFitness(New);
// Add it to the population
Add(New);
end;
// Sort the population into fitness order where first <==> best
SortPopulation;
Change;
end;
|
listing 16: The "Initialise" and "Generation" methods, used to implement the EA
Note how the use of the strategy pattern allows us to delegate all problem specific functionality to our strategy classes and keep the code for the EA clean and simple.
Listing 17 shows the class declaration for the population component. Note the properties which allow the strategy components to be assigned at design time. Figure 8 shows this taking place.
TPopulation = class(TComponent)
private
// The population
FPop : TObjectList;
// Worker for breeding
FBreeder: TBreeder;
// Worker for killing
FKiller: TKiller;
// Worker for parent selection
FParentSelector: TParentSelector;
// Worker for mutation
FMutator: TMutator;
// Worker for initial creation
FCreator: TCreator;
// Worker for fitness calculation
FExaminer: TExaminer;
// On Change event
FOnChange: TNotifyEvent;
procedure Change;
// Getters...
function GetIndividual(I: Integer): TIndividual;
function GetCount: Integer;
protected
// Comparison function for Sort()
function CompareIndividuals(I1 : Pointer; I2 : Pointer) : Integer;
// Sort the population
procedure SortPopulation;
public
// The constructor
constructor Create(Owner : TComponent); override;
// The destructor
destructor Destroy; override;
// Adds an individual to the population
procedure Add(New : TIndividual);
// Deletes an individual from the population
procedure Delete(I : Integer);
// Runs a single generation
procedure Generation;
// Initialise the population
procedure Initialise(Size : Integer);
// Clear ourselves out
procedure Clear;
// Get the fitness of an individual
function FitnessOf(I : Integer) : Double;
// Access to the population members
property Pop[I : Integer] : TIndividual read GetIndividual; default;
// The size of the population
property Count : Integer read GetCount;
published
// Allow these to be assigned in the IDE...
property ParentSelector : TParentSelector read FParentSelector
write FParentSelector;
property Breeder : TBreeder read FBreeder write FBreeder;
property Killer : TKiller read FKiller write FKiller;
property Mutator : TMutator read FMutator write FMutator;
property Creator : TCreator read FCreator write FCreator;
property Examiner : TExaminer read FExaminer write FExaminer;
// An event
property OnChange : TNotifyEvent read FOnChange write FOnChange;
end;
|
listing 17: Class declaration for the TPopulation component
   
fig. 8: Setting up the EA to solve the TSP at design time
The Observer pattern is also implemented (somewhat crudely) using the "OnChange" event. This can be assigned at design time to allow the user interface to monitor the population. Thus, displays and suchlike can be updated as the EA progresses. This is not the true observer pattern, because we are not maintaining lists of observer classes in our observed component, but it provides much of the functionality with a fraction of the overhead.
| Back |
|
|
| Contents | ||
| Next | ||