IBM ILOG Solver User's Manual > Evolutionary Algorithms > Car Sequencing Using EA > Complete Program

The complete program follows and can be viewed on-line in YourSolverHome\examples\src\eacarseq.cpp.

// -------------------------------------------------------------- -*- C++ -*-
// File: examples/src/eacarseq.cpp

#include <ilsolver/iim.h>

ILOSTLBEGIN

// A structure to hold instance data
struct CarSeqProblem {
  IloEnv _env;
  IloInt _numberOfCars;
  IloInt _numberOfOptions;
  IloInt _numberOfConfigurations;
  IloInt _slack;

  IloIntArray _optionCapacity;
  IloIntArray _optionSequenceLength;

  IloIntArray            _numberOfCarsPerConfiguration;
  IloArray<IloBoolArray> _configurationNeedsOption;

  void read(istream& stream, IloInt slack);
  CarSeqProblem(IloEnv env) : _env(env) { }
};

// Read in a problem from a file
void CarSeqProblem::read(istream & stream, IloInt slack) {
  _slack = slack;

  // Dimension problem
  stream >> _numberOfCars >> _numberOfOptions >> _numberOfConfigurations;

  // setup data structures
  _optionCapacity = IloIntArray(_env, _numberOfOptions);
  _optionSequenceLength = IloIntArray(_env, _numberOfOptions);
  _numberOfCarsPerConfiguration =
    IloIntArray(_env, _numberOfConfigurations + 1);
  _configurationNeedsOption =
    IloArray<IloBoolArray>(_env, _numberOfConfigurations + 1);
  IloInt i;
  for (i = 0; i < _numberOfConfigurations + 1; i++)
    _configurationNeedsOption[i] = IloBoolArray(_env, _numberOfOptions);

  // read problem body
  for (i = 0; i < _numberOfOptions; i++)
    stream >> _optionCapacity[i];
  for (i = 0; i < _numberOfOptions; i++)
    stream >> _optionSequenceLength[i];
  for (i = 0; i < _numberOfConfigurations; i++) {
    IloInt id, ncars;
    stream >> id >> ncars;
    id++; // ids to begin at 1 (0 is empty configuration)
    _numberOfCarsPerConfiguration[id] = ncars;
    for (IloInt j = 0; j < _numberOfOptions; j++)
      stream >> _configurationNeedsOption[id][j];
  }

  // Setup empty configuration.
  _numberOfCarsPerConfiguration[0] = _slack;
  assert(IloSum(_numberOfCarsPerConfiguration) == _numberOfCars + _slack);
  for (i = 0; i < _numberOfOptions; i++)
    _configurationNeedsOption[0][i] = IloFalse;
}

// Build the Concert model
IloModel BuildModel(CarSeqProblem problem, IloIntVarArray seq) {
  IloEnv env = problem._env;
  IloModel model(env);

  // Global distribute.  Ensures that the correct number of cars
  // with each configration is built.
  IloIntVarArray cards(env, 1 + problem._numberOfConfigurations);
  IloInt i;
  for (i = 0; i < problem._numberOfConfigurations + 1; i++) {
    IloInt nb = problem._numberOfCarsPerConfiguration[i];
    cards[i] = IloIntVar(env, nb, nb);
  }
  model.add(IloDistribute(env, cards, seq));

  // Sequence constraints.  Ensure that no sub-sequence contains
  // more of a given options that allowed
  for (IloInt opt = 0; opt < problem._numberOfOptions; opt++) {
    // Build an array of integers which represent configurations
    // which require option opt.
    IloIntArray pertinentConfigs(env);
    for (i = 0; i <= problem._numberOfConfigurations; i++) {
      if (problem._configurationNeedsOption[i][opt])
        pertinentConfigs.add(i);
    }

    // use us a boolean array for which use[i] = true iff
    // seq[i] has a configuration which requires opt
    IloBoolVarArray use(env, seq.getSize());
    model.add(IloBoolAbstraction(env, use, seq, pertinentConfigs));

    // Limit each subsequence to the correct number of occurrences
    // Loop over all subsequences of the correct sequence length
    // for option opt.  Add a sum to limit the maximum number of
    // occurrences in each sub-sequence.
    IloInt sl = problem._optionSequenceLength[opt];
    IloInt max = seq.getSize() - sl;
    IloInt x;
    for (x = 0; x <= max; x++) {
      IloBoolVarArray subUse(env, sl);
      for (i = 0; i < sl; i++)
        subUse[i] = use[x + i];
      model.add(IloSum(subUse) <= problem._optionCapacity[opt]);
    }
  }
  return model;
}

// Build the cost variable
IloIntVar BuildCostVar(IloModel model, IloIntVarArray seq, IloInt slack) {
  IloEnv env(model.getEnv());
  IloIntVar cost(env, 0, slack);
  IloConstraintArray carAfter(env, slack);
  for (IloInt i = slack - 1; i >= 0; i--) {
    IlcInt j = seq.getSize() - slack + i;
    carAfter[i] = seq[j] != 0;
    if (i < slack - 1)
      carAfter[i] = carAfter[i] || carAfter[i + 1];
    model.add(carAfter[i] == cost > i);
  }
  return cost;
}

// Useful type definitions
typedef IlcRevInt * IlcRevIntPtr;
ILCARRAY(IlcRevIntPtr)
typedef IlcRevIntPtrArray IlcRevIntArray;

ILCARRAY(IlcIntArray)
typedef IlcIntArrayArray IlcIntArray2;

// Class to produce configs in priority order
class ConfigurationPriorities {
private:
  IlcIntArray2   _priorities;
  IlcRevIntArray _index;

  static void Sort(IlcIntArray a);
public:
  ConfigurationPriorities(CarSeqProblem problem, IlcIntVarArray prio);
  IlcInt getHighestPriority(IlcInt conf) const {
    return _priorities[conf][_index[conf]->getValue()];
  }
  void markScheduled(IlcInt conf) {
    IloSolver solver(_priorities.getSolver());
    _index[conf]->setValue(solver, _index[conf]->getValue() + 1);
  }
};

// Build configuration priorities
ConfigurationPriorities
::ConfigurationPriorities(CarSeqProblem problem, IlcIntVarArray prio) {
  IloSolver solver(prio.getSolver());
  IlcInt numConf = problem._numberOfConfigurations;

  // Create array of sets
  _priorities = IlcIntArray2(solver, 1 + numConf);
  _index = IlcRevIntArray(solver, 1 + numConf);

  IlcInt k = 0;
  for (IlcInt conf = 1; conf <= numConf; conf++) {
    IlcInt numCars = problem._numberOfCarsPerConfiguration[conf];
    _priorities[conf] = IlcIntArray(solver, numCars);
    for (IlcInt j = 0; j < numCars; j++)
      _priorities[conf][j] = prio[k++].getValue();
    Sort(_priorities[conf]);
    _index[conf] = new (solver.getHeap()) IlcRevInt(solver, 0);
  }
}

// Sort integers in decreasing order
void ConfigurationPriorities::Sort(IlcIntArray a) {
  for (IlcInt i = 0; i < a.getSize() - 1; i++) {
    IlcInt big = i;
    for (IlcInt j = i + 1; j < a.getSize(); j++)
      if (a[j] > a[big]) big = j;
    IlcInt tmp = a[i]; a[i] = a[big]; a[big] = tmp;
  }
}

// Decode priorities to a sequence
ILCGOAL3(DecodeSlave, ConfigurationPriorities *, priorities,
                      IlcIntVarArray, seq,
                      IlcInt, idx) {
  IlcInt i;
  for (i = idx; i < seq.getSize() && seq[i].isBound(); i++)
    if (seq[i].getValue() != 0)
      priorities->markScheduled(seq[i].getValue());

  // No unbound slots, found a solution
  if (i == seq.getSize())
    return 0;

  // Decide on a configuration
  IloInt bestPrio = -1;
  IloInt bestConf = 0;
  for (IlcIntExpIterator it(seq[i]); it.ok(); ++it) {
    IlcInt conf = *it;
    if (conf != 0) {
      IlcInt p = priorities->getHighestPriority(conf);
      if (p > bestPrio) {
        bestPrio = p;
        bestConf = conf;
      }
    }
  }

  // Instantiate remainder of sequence
  IlcGoal loop = DecodeSlave(getSolver(), priorities, seq, i);
  return IlcAnd(IlcOr(seq[i] == bestConf, seq[i] != bestConf), loop);
}

// Build the priority structure
ILCGOAL3(Decode,
         CarSeqProblem, problem,
         IlcIntVarArray, prio,
         IlcIntVarArray, seq) {
  IloSolver solver(getSolver());
  ConfigurationPriorities * cp = new (solver.getHeap())
    ConfigurationPriorities(problem, prio);
  return DecodeSlave(solver, cp, seq, 0);
}

// An ILO wrapper for the decoding goal
ILOCPGOALWRAPPER3(Decode, solver,
                  CarSeqProblem, problem,
                  IloIntVarArray, prio,
                  IloIntVarArray, seq) {
  IlcIntVarArray solverPrio = solver.getIntVarArray(prio);
  IlcIntVarArray solverSeq = solver.getIntVarArray(seq);
  return Decode(solver, problem, solverPrio, solverSeq);
}

// Force improvement of offspring
ILOIIMOP2(ImproveOn, AnySize, IlcFloat, p, IlcFloat, q) {
  IloSolutionPool pool = getInputPool();
  IloIntVar var = pool.getSolution(0).getObjectiveVar();
  IlcInt limit = IlcIntMax;
  if (getSolver().getRandom().getFloat() < p)
    limit = (IloInt)pool.getBestSolution().getObjectiveValue() - 1;
  else if (getSolver().getRandom().getFloat() < q)
    limit = (IloInt)pool.getWorstSolution().getObjectiveValue() - 1;
  return getSolver().getIntVar(var) <= limit;
}

// The replacement selector
ILOSELECTOR2(ReplaceWorstParent, IloSolution,
             IloSolutionPool, in,
             IloSolutionPool, pop,
             IloSolutionPool, parents) {
  IloSolution candidate = parents.getWorstSolution();
  IloSolution child = in.getSolution(0);
  pop.sort();
  if (pop.getSize() <= 1 || !candidate.isBetterThan(pop.getSolution(1))) {
    pop.remove(candidate);
    pop.add(child);
  }
  else
    candidate = child;
  select(candidate);
}

// Display the costs of solutions
void DisplayCosts(IloSolutionPool pool) {
  IloEnv env(pool.getEnv());
  IloNum sum = 0;
  for (IloSolutionPool::Iterator it(pool); it.ok(); ++it) {
    IloNum cost = (*it).getObjectiveValue();
    sum += cost;
    env.out() << cost << " ";
  }
  env.out() << "(Mean = " << sum / pool.getSize() << ")" << endl;
}

// Main GA solving function
void SolveWithGA(IloSolver solver,
                 IloModel model,
                 CarSeqProblem problem,
                 IloIntVarArray sequence,
                 IloIntVar cost) {

  // Indirect representation
  IloEnv env(solver.getEnv());
  IloIntVarArray prio(problem._env, problem._numberOfCars, 0, 1000);
  model.add(prio);

  // Create the solution prototype
  IloSolution prototype(env);
  prototype.add(prio);
  prototype.add(sequence);
  prototype.add(IloMinimize(env, cost));

  // Create the decoding goal
  IloGoal decode = Decode(env, problem, prio, sequence);

  // Set up an operator factory
  IloEAOperatorFactory factory(env, prio);
  factory.setAfterOperate(decode);
  factory.setSearchLimit(IloFailLimit(env, 100));
  factory.setPrototype(prototype);

  // Start timing
  IloTimer timer(env);
  timer.start();

  // Create initial population
  const IloInt popSize = 30;
  IloSolutionPool pop(env);
  IloPoolProc create = factory.randomize();
  solver.solve(IloExecuteProcessor(env, create(popSize) >> pop));

  // Sort the population wrt. quality and display.
  pop.sort();
  env.out() << "Initial population: ";
  DisplayCosts(pop);

  // Enforce probabilistic improvement
  factory.setBeforeOperate(ImproveOn(env, 0.5, 0.9));

  // Create genetic operators
  IloPoolProcArray ops(env);
  ops.add(factory.mutate(2.0 / problem._numberOfCars, "mutation"));
  ops.add(factory.uniformXover("uxover"));
  ops.add(factory.onePointXover("1ptxo"));

  // Make a processor that will perform a breeding cycle
  IloRandomSelector<IloSolution, IloSolutionPool> randomSelector(env);
  IloPoolProc rndSel = IloSelectSolutions(env, randomSelector, IloTrue);
  IloPoolProc breed = IloSelectProcessor(
    env,
    ops,
    IloRandomSelector<IloPoolProc,IloPoolProcArray>(env)
  );


  // Child replaces worst parent
  IloSolutionPool parents(env);
  IloPoolProc replace =
    IloSelectSolutions(env, ReplaceWorstParent(env, pop, parents));
  IloPoolProc destroy = IloDestroyAll(env);
  IloPoolProc replaceAndDestroy = replace >> destroy;

  // Create single-generation processor
  IloPoolProc cycle = pop >> rndSel >> parents >> breed(1) >> replaceAndDestroy;

  // The main genetic algorithm loop
  const IloInt maxGen = 10000;
  IloInt gen;
  IloSolution best = pop.getBestSolution();
  for (gen = 1; best.getObjectiveValue() != 0 && gen <= maxGen; gen++) {
    solver.solve(IloExecuteProcessor(env, cycle));
    best = pop.getBestSolution();
    pop.sort();
    if ((gen % 10) == 0) {
      env.out() << "Generation " << gen << ": ";
      DisplayCosts(pop);
    }
  }

  // Display best solution and statistics
  solver.solve(IloRestoreSolution(env, best));
  env.out() << "Time taken = " << timer.getTime() << endl;
  env.out() << "Terminated at generation " << gen << endl;
  env.out() << "Solution of cost " << solver.getValue(cost) << " is: " << endl;
  for (IloInt i = 0; i < sequence.getSize(); i++)
    env.out() << solver.getValue(sequence[i]) << " ";
  env.out() << endl;
}

// Solve car sequencing problems using a GA
int main(int argc, const char * argv[]) {
  IloEnv env;
  try {
    // Default input file
    const char * fname = "../../../examples/data/carseq1.dat";

    // Number of empty configurations to add by default
    IloInt slack = 20;

    // Read command line arguments
    if (argc > 1)
      fname = argv[1];
    if (argc > 2)
      slack = atoi(argv[2]);

    // Open input file and read problem data
    ifstream stream(fname);
    if (!stream.good()) {
      env.out() << "Cannot open " << fname << endl;
      return 0;
    }
    CarSeqProblem problem(env);
    problem.read(stream, slack);

    // Create the decision variables and the model.
    IloIntVarArray seq(env, problem._numberOfCars + problem._slack,
                       0, problem._numberOfConfigurations);
    IloModel model = BuildModel(problem, seq);
    IloIntVar costVar = BuildCostVar(model, seq, problem._slack);

    // Create the solver and solve.
    IloSolver solver(model);
    SolveWithGA(solver, model, problem, seq, costVar);
  } catch (IloException & ex) {
    env.out() << "Caught: " << ex << endl;
  }
  env.end();
  return 0;
}