IBM ILOG Solver User's Manual > Evolutionary Algorithms > Using More Advanced EA Features > Using listeners and comparators |
Using listeners and comparators |
INDEX
![]() |
In this section, you will learn how to:
You can monitor operator improvements by attaching listeners to operators. Such monitoring can be done in two ways:
Make a copy of the example file YourSolverHome/examples/src/tutorial/ea1max_listen_partial.cpp
and open this copy in your development environment. After filling in the blanks in each step in this section, you will have completed the example and you can compile and run the program.
In this example, you will show operator statistics; you define a small structure to hold these statistics for a particular operator.
Step 1 - | Stores invocation and improvement statistics for operators |
Add the following code after the comment //Stores invocation and improvement statistics for operators
struct OperatorStatistics {
IloInt invocations;
IloInt improvements;
IloInt successes;
OperatorStatistics() : invocations(0), improvements(0), successes(0) { }
};
The following procedure checks if the operator statistics record already exists and creates it if required. Each operator's statistics object are stored and retrieved using setObject
and getObject
respectively.
Step 2 - | Retrieve operator statistics (and create if necessary) |
Add the following code after the comment //Retrieve operator statistics (and create if necessary)
Objects of type IloPoolOperator
define the basic genetic operators that will be used to modify the population, and it is upon these objects that monitoring will be done, and statistics gathered. However, to execute an operator it must be cast to a pool processor (IloPoolProc
), albeit automatically most of the time. It is thus convenient to be able to have access to an operator which was used to create a processor. You perform this using a wrapping function and setObject
. We also introduce a function which retrieves operator statistics give a processor.
Step 3 - | Set up processor to operator mapping |
Add the following code after the comment // Setup the processor to operator mapping
You are now in a position to define a listener object which will be called when a new solution is produced by an operator. In the listener, you update operator statistics:
Listener objects can be defined via macros. You should pass dynamic information by reference or pointer so that you have the up-to-date information available when the listener is invoked; here the current generation.
Step 4 - | Defines a listener which records solution improvements |
Add the following code after the comment //Defines a listener which records solution improvements
The previous listener was to be called whenever a new solution was produced. You now create a listener which keeps track of when a particular genetic operator is invoked.
Step 5 - | Defines a listener which records operator invocations |
Add the following code after the comment //Defines a listener which records operator invocations
ILOIIMLISTENER0(OperatorListener, IloPoolOperator::InvocationEvent, event) { IloPoolOperator op = event.getOperator(); OperatorStatistics * stat = GetOperatorStatistics(op); stat->invocations++; } |
It can be useful to rank or score genetic operators according to their past performance so that they can be more intelligently invoked in the future. For example, this can mean choosing more often operators which seem to work well. In this example, to sort genetic operators depending on their performance, you use two criteria:
To do so, you define a first an IloComparator
which will compare operator improvement counts, followed by one to compare invocations. The final idea is to combine the two comparisons such that operators with higher improvement counts and lower invocation counts are preferred. You use the ILOCOMPARATOR
macro to perform custom comparison. Two parameters, left
and right
, are always present and are the operators to be compared. This macro should return a true value if and only if left
is strictly better than right
. Here you see that an operator with more improvements is better than one with less.
Step 6 - | Define the operator improvement comparator |
Add the following code after the comment //Define the operator improvement comparator
You also define a second IloComparator
devoted to comparing the numbers of operator invocations. Here, an operator which has been invoked less is deemed better than one which has been invoked more.
Step 7 - | Define the operator invocation comparator |
Add the following code after the comment
//Define the operator invocation comparator
In the main routine you use the predefined tournament selector instead of the customized one.
Step 8 - | 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);
You attach the listeners before starting the generation loop. It is possible to attach listeners directly to the operator objects themselves. However, often a more convenient method is to add the listeners to the operator factory. These listeners will then be added to all operators produced by the factory after this point.
Step 9 - | Add the operator invocation listener |
Add the following code after the comment // Add the operator invocation listener
factory.addListener(OperatorListener(env));
To record generated solutions, you add the improvement listener to the factory.
Step 10 - | Add the improvement listener |
Add the following code after the comment // Add the improvement listener
factory.addListener(ImprovementListener(env, best, generation));
This last listener will display a message each time the best solution found is improved upon.
Creation of the operators to use is done as before, with the exception that you make use of the BuildGAProcessor
function to maintain a link from the processor at the operator on which it is based. This code is provided for you:
ops.add(BuildGAProcessor(factory.mutate(1.0 / problemSize, "mutate"))); ops.add(BuildGAProcessor(factory.uniformXover(0.5, "uXover"))); |
Once the generational loop exits, you display the goal statistics. The idea is to compare the performance of the operators according to two criteria. First, you compare the number of improvements which each operator provided. If the number of improvements is different for each operator, we know definitively which operator is considered better. If the number of improvements is the same, however, the number of invocations of each operator is examined. The operator which was invoked less is preferred. This type of hierarchical comparison is termed a lexicographical comparison and you will use it below.
Step 11 - | Create composite operator comparator |
Add the following code after the comment //Create composite operator comparator
IloComparator<IloPoolProc> opComparator = IloComposeLexical(
OperatorImprovementComparator(env),
OperatorInvocationComparator(env)
);
Using this comparator, you sort the operator pool.
Step 12 - | Sort the operator pool |
Add the following code after the comment //Sort the operator pool
Finally, you loop over the sorted processors in best-first manner.
Step 13 - | Loop over the processor pool and display statistics |
Add the following code after the comment //Loop over the processor pool and display statistics
You have now learned how to use listeners and comparators. In the next section, you will learn how to use pool evaluators.
Step 14 - | Compile and run the program |
Compile and run the program. You should get the following results:
The complete program is available in the YourSolverHome/examples/src/ea1max_listen.cpp
file.
© Copyright IBM Corp. 1987, 2009. Legal terms. | PREVIOUS NEXT |