<< Click to Display Table of Contents >> MAXI Program Code |
|
In this section, we will explain the sample code. The sample is the Visual Basic code for the MAXI problem. When you installed GeneHunter, it was installed in the GENEHUNTER\EXAMPLES\VB6 subdirectory of your GeneHunter directory. You should probably run the problem to familiarize yourself with it before trying to understand the code.
Before writing a genetic algorithm application, copy GALIB32.BAS from the GENEHUNTER\EXAMPLES\VB6 subdirectory to the Visual Basic project file you are using for your application.
GALIB32.BAS contains the declarations and prototype declarations for the GeneHunter Dynamic Link Library functions. Once you have copied GALIB32.BAS to your project file, the prototype declarations and constants become available to you. Declarations Section First, examine the declarations section of MAXI.FRM. The code declares the variables that are needed as global. They include the population number, generation counter, and the flag GoGener that is used to control the program flow.
Dim PopulN%, NGener% Dim GoGener% Dim i%
The size of the population and parameters of the evolutionary process are defined in this section as constants. If all parameters are in one place, you can easily experiment with different values.
Const Siz% = 20 Const GenerGap! = .9
Const ScaleFit! =3 Const CrossRate! = .9 Const MutRate! = .01 Form Load A good place to build a population is in the load procedure of the form. First, we initialize the variables setting the generation counter at zero and turn off the flag allowing evolution. Then, we get a population number from GetNextPopulation and store it in PopulN%.
Note that we check the value of the return code from GetNextPopulation. All GeneHunter functions return a zero if there was no error. Otherwise, a non-zero error code is returned.
Sub Form_Load ( ) NGener% = 0 GoGener% = False i% = GetNextPopulation(PopulN%) If i% GoTo err_form
Now we are creating the population with the associated number stored in PopulN%. The number of individuals in the population is given by the Siz% constant (=20) and the number 1000 stands for your GeneHunter serial number that is printed on your distribution CD. Change the 1000 to your serial number.
i% = MakePopulation(PopulN%, Siz%, 1000) If i% GoTo err_form
Now we will make the first chromosome, with the number 0. Since we are going to find the maximum of a function of a single variable, we need only one chromosome. Multidimensional problems will require multi-chromosome individuals.
At the beginning of a chromosome creation, we specify the resolution of the chromosome (BIT16 is the constant defined in GALIB32.BAS). We can choose from 3 resolutions: 8, 16, and 32 bits. They correspond to 256, 65,536, and 4,294,967,296 search points per interval respectively. The search interval is set by the next two arguments of the MakeChromosome function. We will consider the interval from 0.0 to 2.0. The last argument, which is equal to 0, indicates that we are creating a continuous non-integer chromosome.
i% = MakeChromosome(PopulN%, 0, BIT16, 2!, 0!, 0) If i% GoTo err_form
Next, we set the selection strategy for the evolutionary process. We will choose the elitist method (again the constant is defined in GALIB32.BAS). Constants for the generation gap and for the fitness scale are defined in the declarations section.
i% = SetStrategy(PopulN%, ELITIST_SELECTION,GenerGap!,ScaleFit!) If i% GoTo err_form
Now we can set the parameters of the evolutionary operators. SIMPLE_CONT_CROSS is again the constant from GALIB32.BAS. The last two constants were defined in the declarations section.
i%=SetOperators(PopulN%, SIMPLE_CONT_CROSS, CrossRate!, MutRate!) If i% GoTo err_form
The rates are displayed on the screen:
label9.Caption = "Crossover: " & Format$(CrossRate!, "0.00") label10.Caption = " Mutation: " & Format$(MutRate!, "0.000")
Next, we start the loop in which we calculate the fitnesses for all of the individuals. To do this, we retrieve the chromosome value and call the fitness function that is written in Visual Basic. The calculated fitness is then passed to GeneHunter. Each of these procedures should be done before the first call of the Reproduce function. Note that the individuals are numbered from 0 to Siz%-1.
For k% = 0 To Siz% - 1 i% = GetChromosome(PopulN%, k%, 0, A!) If i% GoTo err_form fit! = Fitness!(A!) i% = PutFitness(PopulN%, k%, fit!) If i% GoTo err_form Next k%
We display the shape of the fitness function and the initial distribution of individuals with the help of the next function:
DrawFitness
The remaining code is related to error handling. The first line is executed in the case of no error, and the subroutine is finished at this point. If an error occurs, the program goes to the label err_form:, the corresponding error message is displayed, and the program terminates. All GeneHunter functions return an error number; zero means that no error was detected. The error messages are defined in GALIB32.BAS as an array of strings with indexes corresponding to error numbers. The error messages are listed in Chapter 9 .
Exit Sub err_form: MsgBox Ga_error(i%), 16, "Error" End End Sub
The module GALIB32.BAS contains a special function called GA_error (which is written in Visual Basic), that returns a string error message for the corresponding error code. This error message is displayed by the MsgBox subroutine call. Fitness Function The fitness function is defined by the problem to be solved. In the MAXI example, we are searching for the maximum of the function that is given by the formula:
F(x) = A1 * exp (- (x - x1)^2 / w1^2) + A2 * exp (- (x - x2)^2 / w2^2) + A3 * exp (- (x - x3)^2 / w3^2)
This is a sum of three Gaussian functions with amplitudes A1, A2, and A3, with the maxima located at x1, x2, and x3. The widths of the Gaussian functions are determined by w1, w2, and w3.
In this simple example, coding the fitness function is straightforward. We are searching for the maximum value of the fitness function.
It is possible to change the parameters of the function and test the algorithm on different versions of the function. At this point, it is also possible to write any other function you like, but it would be better if it will change in the internal 0.0...1.0 limits while its argument is changing from 0.0 to 2.0. Otherwise, you will have to change the DrawFitness function. Please remember that if you would like to change the search interval, you should change the search interval in the chromosome description given by the MakeChromosome function.
Function Fitness! (A!) Const Amp1! = .5 Const Amp2! = 1 Const Amp3! = .7 Const wid1! = .3 Const wid2! = .5 Const wid3! = .2 Const x01! = .5 Const x02! = 1.15 Const x03! = 1.5 Gauss1! = Amp1 * Exp(-(A! - x01!)^2 / (wid1! / 2!)^2) Gauss2! = Amp2 * Exp(-(A! - x02!)^2 / (wid2! / 2!)^2) Gauss3! = Amp3 * Exp(-(A! - x03!)^2 / (wid3! / 2!)^2) Fitness! = Gauss1! + Gauss2! + Gauss3! End Function The Go Button When the "GO" button is pressed, the cmdGoGener_Click subroutine is called. In this subroutine, the main program loop is executed. At first, the flag GoGener is set indicating that iterations should continue.
Sub cmdGoGener_Click ( ) GoGener% = True
The main loop starts with the Reproduce function which creates the new population. Note that at this moment the fitnesses of all the individuals are calculated and passed to GeneHunter.
Do i% = Reproduce( PopulN% ) If i% GoTo err_GA
After calling Reproduce, the values of the fitness functions are calculated once again.
For k% = 0 To Siz% - 1 i% = GetChromosome(PopulN%, k%, 0, A!) If i% GoTo err_GA fit! = Fitness!(A!) i% = PutFitness(PopulN%, k%, fit!) If i% GoTo err_GA Next k%
Next, the generation counter is incremented and displayed. The DrawFitness function displays the function and the distribution of individuals. The DoEvents function from the Visual Basic library is called to check for the user commands "STOP" and "EXIT". If the corresponding buttons are pressed, the main loop terminates since the GoGener flag is set to FALSE by these buttons.
NGener% = NGener% + 1 lblNgener.Caption = NGener% DrawFitness DoEvents Loop While GoGener% Exit Sub Figure 6.1 View of Maxi main form
All errors are processed at the label err_GA. Error number 12 (see definition of the equal_fitnesses constant in GALIB32.BAS) is not a real error, but an indication of the fact that the fitnesses of all individuals in the population have become equal. Usually, this happens when the algorithm has already converged and there is little sense in searching further. You may ignore this error. In this example, we draw the fitness function and inform the user about the situation. "Real" errors are processed as in the FormLoad function, already described. err_GA: If i% = equal_fitnesses Then DrawFitness MsgBox Ga_error(i%), 48, "Message" Exit Sub Else MsgBox Ga_error(i%), 16, "Error" End End If End Sub The Stop Button The Stop button simply sets the flag variable GoGener to False. This terminates the main program loop, and the results of the evolutionary process are displayed.
Sub cmdStopGen_Click () GoGener% = False DrawFitness End Sub The Exit Button When exiting, the current population should be killed. Otherwise, calling programs with inactive populations may create too many populations to be simultaneously processed by GeneHunter, or may fill the entire available memory with unnecessary data.
Sub CmdExit_Click () i% = KillPopulation(PopulN%) If i% GoTo err_kill End err_kill: MsgBox Ga_error(i%), 16, "Error" End End Sub Form Unload There are two ways to exit the program. You can click the exit button or close the program window with the help of standard Windows procedures, such as Alt-F4, or by choosing the corresponding menu option. That is why the same code used for the EXIT button is executed for the form unload.
Sub Form_Unload (Cancel As Integer) i% = KillPopulation(PopulN%) If i% GoTo err_load End err_load: MsgBox Ga_error(i%), 16, "Error" End End Sub Draw Fitness This code is not important to the understanding of how GALIB works, and most users will want to skip it unless they are interested in graphics. This subroutine draws the fitness function and marks the positions of individuals on the graph. In the first loop, the fitnesses of all individuals are calculated and the average, maximum, and minimum fitnesses are found. These values are displayed.
Sub DrawFitness ( ) Dim NumDot% ReDim X(0 To Siz%) As Integer screen.MousePointer = 11 sumfit! = 0 sumX! = 0 Maxft! = -1E+38 Minft! = 1E+38 For k% = 0 To Siz% - 1 i% = GetChromosome(PopulN%, k%, 0, A!) fit! = Fitness!(A!) If Maxft! < fit! Then Maxft! = fit! MaxX! = A! End If if Minft! > fit! Then Minft! = fit! MinX! = A! End If sumfit! = sumfit! + fit! sumX! = sumX! + A! Next k% avrfit! = sumfit! / Siz% avrX! = sumX! / Siz% Maxfit.Caption = " Max fitness:" & Format$(Maxft!, "0.000") & " X: " & Format$(MaxX!, "0.000") Minfit.Caption = " Min fitness:" & Format$(Minft!, "0.000") & " X: " & Format$(MinX!, "0.000") averfit.Caption = "Average fitness:" & Format$(avrfit!, "0.000") & " X: " & Format$(avrX!, "0.000")
The next portion of the code draws the fitness function: X is varied in the interval 0.0...2.0 and it is assumed that the fitness function values lie within the interval 0.0...1.0. The horizontal resolution of the drawing is set by the NumDot% variable and is 100 points per the graph window.
NumDot% = 100 WidSt = picture1.ScaleWidth / NumDot% picture1.Cls picture1.DrawWidth = 1 x0% = 0 y0% = picture1.ScaleHeight / 1.05 * (1# - Fitness(0#)) + 20 For j% = 1 To NumDot x1% = j% * WidSt x2! = j% * 2# / NumDot y1% = picture1.ScaleHeight / 1.05 * (1# - Fitness(x2!)) + 20 picture1.Line (x0%, y0%)-(x1%, y1%), RGB(0, 0, 0) x0% = x1% y0% = y1% Next j%
The population is displayed as a set of points representing individuals. The X coordinate is the value of the chromosome and the Y coordinate is the value of the fitness function. In the next loop, we get the value of the chromosome into the variable A! and calculate the X-coordinate of the individual.
For k% = 0 To Siz% - 1 i% = GetChromosome(PopulN%, k%, 0, A!) X(k%) = A! / 2# * picture1.ScaleWidth Next k%
The graph displays the individuals. If a number of individuals have X-coordinates which are so close that the coordinate values differ by less than the horizontal resolution, they are displayed as a bar with the height proportional to the number of close individuals.
picture1.DrawWidth = 3 For j% = 0 To NumDot% x0% = j% * WidSt x2! = j% * 2# / NumDot y0% = picture1.ScaleHeight / 1.05 * (1# - Fitness(x2!)) + 20 cnt% = 0 For k% = 0 To Siz% - 1 If (X(k%) >= x0%) And (X(k%) < x0% + WidSt) Then cnt% = cnt% + 1 Next k% If cnt% > 0 Then picture1.Line (x0%, y0%)-(x0%, y0% + 40 * cnt%), RGB(0, 50, 150) End If Next j% screen.MousePointer = 0 End Sub
Use this example to explore the GeneHunter functions. You may change the parameters, choose your own fitness function, and even make a fitness function that is gradually modified while it is called. For example, you may make the parameters of the current fitness function dependent upon the number of calls of the function. This will simulate a real-life situation, where the environment is slowly changing. |