package cecj.eval;
import cecj.interaction.InteractionResult;
import cecj.problems.SymmetricCompetitionProblem;
import ec.EvolutionState;
import ec.Individual;
import ec.simple.SimpleFitness;
import ec.util.Parameter;
/**
* Single elimination tournament competitive evaluator. It is different from the other
* coevolutionary evaluators because interactions between individuals must be simulated in strict
* order. It depends on the outcome of previous interaction if the individual can compete further.
*
* Assumes that individuals use SimpleFitness
. The fitness assigned by this method is
* equal to the height of the tournament subtree that particular individual has traversed - the
* number of games won. If a series of tournaments is organized (i.e. repeats
parameter
* is greater than 1), the fitness is summed over all the tournaments.
*
* This evaluator can be used if problem being solved implements
* SymmetricCompetitionProblem
interface.
*
* It cannot employ any other mechanisms like archiving or fitness sharing because the interactions
* scheme is very different and hard to generalize.
*
* @author Marcin Szubert
*
*/
public class TournamentCoevolutionaryEvaluator extends CoevolutionaryEvaluator {
private static final String P_REPEATS = "repeats";
/**
* Specifies how many times the tournament should be repeated during single evaluation process.
* More repeats can reduce the noise of this evaluation scheme.
*/
private int tournamentRepeats;
private SymmetricCompetitionProblem problem;
/**
* Represents competing individuals.
*/
private Individual[] competitors;
/**
* Number of competitors - size of the particular subpopulation.
*/
private int numCompetitors;
/**
* Points gathered during the course of competition.
*/
private int[] points;
/**
* An array used as a tournament tree representation. It stores indices of competing
* individuals. Neighbouring indices copmete with each other in certain round.
*/
private int[] competition;
/**
* Stores active competitors ready to be divided into pairs.
*/
private int[] activeCompetitors;
/**
* Indicates if particular competitor is still in game.
*/
private boolean[] active;
@Override
public void setup(final EvolutionState state, final Parameter base) {
super.setup(state, base);
if (!(p_problem instanceof SymmetricCompetitionProblem)) {
state.output.fatal("Tournament evaluator can be used only with symmetric problems");
} else {
problem = (SymmetricCompetitionProblem) p_problem;
}
Parameter repeatsParameter = base.push(P_REPEATS);
tournamentRepeats = state.parameters.getIntWithDefault(repeatsParameter, null, 1);
if (tournamentRepeats <= 0) {
state.output.fatal("Tournament repeats parameter can not be negative.",
repeatsParameter);
}
}
@Override
public void evaluatePopulation(EvolutionState state) {
for (int subpop = 0; subpop < numSubpopulations; subpop++) {
prepareTournament(state, subpop);
for (int r = 0; r < tournamentRepeats; r++) {
makeTournament(state);
}
assignFitness(state);
}
}
/**
* Initializes structures used in the tournament series.
*
* @param state
* @param subpop
*/
private void prepareTournament(EvolutionState state, int subpop) {
competitors = state.population.subpops[subpop].individuals;
numCompetitors = competitors.length;
points = new int[numCompetitors];
active = new boolean[numCompetitors];
competition = new int[numCompetitors];
activeCompetitors = new int[numCompetitors];
}
/**
* Plays a single tournament between earlier selected competitors from particular subpopulation.
* Each tournament consists of a sequence of rounds. In each round number of active competitors
* is reduced by half according to the results of their games (approximately - if at the start
* of the round number of players is odd, one player is given a "bye" and advances to the next
* round directly). At the beginning of each round there is a drawing which assigns competitors
* in pairs.
*
* @param state
*/
private void makeTournament(EvolutionState state) {
int numActiveCompetitors;
for (int c = 0; c < numCompetitors; c++) {
active[c] = true;
}
while ((numActiveCompetitors = findActiveCompetitors()) > 1) {
shuffleCompetitors(state, numActiveCompetitors);
playTournamentRound(state, numActiveCompetitors);
}
}
/**
* Assign fitness value to each competing individual according to overall points which it has
* gathered during the series of tournaments.
*
* @param state
*/
private void assignFitness(EvolutionState state) {
for (int c = 0; c < numCompetitors; c++) {
Individual competitor = competitors[c];
((SimpleFitness) competitor.fitness).setFitness(state, points[c], false);
}
}
/**
* Finds still active competitors according to active
array. Found competitor
* indices are stored in activeCompetitors
array and their number is returned.
*
* @return number of still active competitors
*/
private int findActiveCompetitors() {
int competitors = 0;
for (int c = 0; c < numCompetitors; c++) {
if (active[c]) {
activeCompetitors[competitors++] = c;
}
}
return competitors;
}
/**
* Randomly shuffles competitors indices taken from <
*
* @param state
* @param source
* @param target
* @param count
*/
private void shuffleCompetitors(EvolutionState state, int count) {
int left = count;
for (int i = 0; i < count; i++) {
int rand = state.random[0].nextInt(left);
competition[i] = activeCompetitors[rand];
activeCompetitors[rand] = activeCompetitors[--left];
}
}
/**
* Arranges a competition between neighbours in competition
array.
*
* @param state
* @param numCompetitors
*/
private void playTournamentRound(EvolutionState state, int numCompetitors) {
for (int i = 0; i + 1 < numCompetitors; i += 2) {
Individual c1 = competitors[competition[i]];
Individual c2 = competitors[competition[i + 1]];
// TODO: consider if it is needed to call compete method twice
// maybe it should use internal individual's fitness or return both results at once?
InteractionResult score1 = problem.compete(state, c1, c2).first;
InteractionResult score2 = problem.compete(state, c2, c1).first;
if (score1.betterThan(score2)) {
points[competition[i]]++;
active[competition[i + 1]] = false;
} else {
points[competition[i + 1]]++;
active[competition[i]] = false;
}
}
// TODO: in case of odd number of competitors, should the one given a "bye" achieve a point
// in this round?
if (numCompetitors % 2 != 0) {
points[competition[numCompetitors - 1]]++;
}
}
}