IBM ILOG Solver User's Manual > Evolutionary Algorithms > Modeling and Solving a Basic EA Problem > Writing a simple generation loop

In this section you will learn how to:

Make a copy of the example file YourSolverHome/examples/src/tutorial/ea1max_gen_partial.cpp and open the copy in your development environment. As in the first section of this lesson, you will fill in the blanks in each step. At the end of the section, you will have completed the example and you can compile and run the program.

As in the previous example you parse command line arguments. In this example, there is another command line argument: the maximum number of generations. This code is provided for you:

    IloInt problemSize = 50;     // Bits per solution
    IloInt populationSize = 50;  // Number of solutions in population
    IloInt maxGeneration = 60;   // Generation count limit
    if (argc > 1)
      problemSize = atoi(argv[1]);
    if (argc > 2)
      populationSize = atoi(argv[2]);
    if (argc > 3)
      maxGeneration = atoi(argv[3]);
    env.out() << "Problem size: " << problemSize << endl;
    env.out() << "Population size: " << populationSize << endl;
    env.out() << "Max generation: " << maxGeneration << endl;

To keep track of population evolution you write a function to display some statistical information:

This function is used after the population initialization and after the creation of each new generation.

Step 1   -  

Display the generation statistics

Add the following code after the comment //Display the generation statistics

void DisplayGenerationStatistics(ostream& stream,
                                 IloInt generation,
                                 IloSolutionPool population) {
  IloNum sum = 0.0;
  for (IloSolutionPool::Iterator it(population); it.ok(); ++it) 
    sum += (*it).getObjectiveValue();
  stream << "GENERATION " << generation 
         << " WORST " 
         << population.getWorstSolution().getObjectiveValue()
         << " BEST " 
         << population.getBestSolution().getObjectiveValue()
         << " AVERAGE " << (sum / population.getSize())
         << endl;
}

Solutions processed by the operators will be chosen using tournament selection. The selector IloTournamentSelector will be used. A tournament selector operates by randomly selecting n (the tournament size) solutions from its input pool, and placing the best of these resulting solutions in the output pool. In this way, better solutions are favored for selection over poorer ones. Here a tournament size of 2 is used: two random solutions are chosen from the population, and the better is placed in the parent pool. You also specify via a Boolean parameter that when more than one solution is selected by the selector, the same solution cannot be selected more than once.

Step 2   -  

Use tournament selection to choose parents

Add the following code after the comment
//Use tournament selection to choose parents

    IloTournamentSelector<IloSolution, IloSolutionPool> tsel(
      env, 2, IloBestSolutionComparator(env)
    );
    IloPoolProc selector = IloSelectSolutions(env, tsel, IloTrue);

In this example, the solution will be processed using a single mutation operator. The mutation operator simply changes the value of each element of the solution to another of its values with a constant probability. Here, you choose this probability to be 1.0/problemSize to ensure that, on average, one site is mutated each time. This is simply parameterization and not a necessary condition. The operator is supplied by a factory which returns an object of type IloPoolOperator. An operator takes an input pool of solutions and instantiates Solver's constrained variables according to its function. An object of type IloPoolOperator does not produce a solution object, and so cannot be used directly with pool processors. The operator is converted to a pool processor (type IloPoolProc) automatically. This conversion creates a pool processor which invokes the operator, and creates an output solution from the state of the Solver.

Step 3   -  

Use mutation as a single operator

Add the following code after the comment //Use mutation as a single operator

    IloPoolProc applyOp = factory.mutate(1.0 / problemSize, "mutate");

In this example, you will replace the entire population by the offspring at each generation. Thus, at each generation, populationSize offspring must be created. The reproduction goal accumulates solutions in the offspring pool until the pool's size reaches populationSize.

Step 4   -  

Create the reproduction goal

Add the following code after the comment //Create the reproduction goal

    IloSolutionPool offspring(env, "offspring");
    IloGoal reproduceGoal = IloExecuteProcessor(
      env, population >> selector >> applyOp(populationSize) >> offspring
    );

Then you create a replacement goal which:

Step 5   -  

Replace the entire population with offspring

Add the following code after the comment
//Replace the entire population with offspring

    IloGoal replacementGoal =
      IloExecuteProcessor(env, population >> IloDestroyAll(env)) &&
      IloExecuteProcessor(env, offspring >> population);

A single generation is created by chaining reproduction and replacement goals.

Step 6   -  

Each generation reproduces and replaces solutions

Add the following code after the comment
//Each generation reproduces and replaces solutions

    IloGoal generationGoal = reproduceGoal && replacementGoal;

You define the optimum value which will be used to stop the generation loop. Note that, for this example, since the optimal is known, such a determination is possible. In most real world cases, the optimal will not be known and the more usual terminating condition is on the number of iterations or a time limit.

Step 7   -  

Set up the optimum value

Add the following code after the comment //Set up the optimum value

    IloInt optimumValue = problemSize;
    env.out() << "Optimum value: " << optimumValue << endl;

Finally, you create the loop to search for the best solution.

Step 8   -  

The main generational loop

Add the following code after the comment //The main generational loop

    for (generation++; generation < maxGeneration; generation++) {
      solver.solve(generationGoal);
      DisplayGenerationStatistics(env.out(), generation, population);
      if (population.getBestSolution().getObjectiveValue() == optimumValue) {
        env.out() << "OPTIMUM FOUND " << endl;
        break;
      }
    }

You have now learned how to write a simple generation loop.

Step 9   -  

Compile and run the program

Compile and run the program. You should get the following results:

Problem size: 50
Population size: 50
Max generation: 60
GENERATION 0 WORST 17 BEST 34 AVERAGE 25.06
Optimum value: 50
GENERATION 1 WORST 20 BEST 35 AVERAGE 27.4
GENERATION 2 WORST 24 BEST 35 AVERAGE 29.08
GENERATION 3 WORST 26 BEST 37 AVERAGE 31.02
GENERATION 4 WORST 29 BEST 38 AVERAGE 32.18
GENERATION 5 WORST 28 BEST 38 AVERAGE 32.68
GENERATION 6 WORST 29 BEST 39 AVERAGE 33.58
GENERATION 7 WORST 31 BEST 39 AVERAGE 34.86
GENERATION 8 WORST 31 BEST 38 AVERAGE 35.04
GENERATION 9 WORST 32 BEST 39 AVERAGE 35.38
GENERATION 10 WORST 32 BEST 39 AVERAGE 35.66
GENERATION 11 WORST 33 BEST 39 AVERAGE 36.2
GENERATION 12 WORST 33 BEST 39 AVERAGE 36.02
GENERATION 13 WORST 34 BEST 39 AVERAGE 36.2
GENERATION 14 WORST 33 BEST 38 AVERAGE 35.84
GENERATION 15 WORST 32 BEST 39 AVERAGE 36.18

                (abridged)

The complete program is available in the YourSolverHome/examples/src/ea1max_gen.cpp file.