Model
Once the house building with state incompatibilities problem has been described using natural language, you then use Concert Technology classes to model the constraint programming problem.
After you have written a description of your problem, you can use IBM® ILOG® Concert Technology classes to model it.
Step 2: Open the example file
Open the
example file <Install_dir>/cpoptimizer/examples/tutorial/cpp/sched_state_partial.cpp in
your development environment. This file is a program that is only
partially completed. You will enter the missing code in each step
in this lesson. At the end, you will have completed the program code,
and you can compile and run the program.
In
this lesson, you include the header file <ilcp/cp.h>.
To catch exceptions that may be thrown, you use a try/catch block.
The code for creating the environment and model and for printing out
the solution found by the CP Optimizer engine is provided.
In addition, the data related to the tasks, such as the tasks
(Tasks), the number of tasks (NbTasks),
the names of the tasks (TaskNames) and sizes
of the tasks (TaskDurations) and constants
representing the number of houses (NbHouses),
the number of workers (NbWorkers) and the
two cleanliness states (Clean and Dirty) are provided.
After you create
an environment and a model, you need to define the decision variables
and add the constraints and objective to the model. Since the requirements
for each of the five houses are similar, you use a function MakeHouse. to create the decision variables, constraints
and objective associated with each house. Information about individual
houses that must be shared with the main function includes the expressions
needed to create the objective function and information about worker
usage. In order to display the results of the optimization, it is
also useful to maintain an array of all the interval variables.
To access this information, you create objects that will
be updated in the MakeHouse function. The
cost expression involves the date at which moving is completed for
each house; the integer expression array ends is
used to store this information. An array of task interval variables, allTasks, stores all the interval variables that
are created. In addition, the expression used to represent worker
usage is included in the global information that is updated in each
call to the MakeHouse function.
Since the workers are equivalent in this problem, it is better to represent them as one pool of workers instead of as individual workers with no overlap constraints as was done in the earlier examples. This representation removes symmetry. The cumulative function expression representing usage of the pool of workers can be modified by the interval variables that require a worker.
Step 3: Declare the objects needed for MakeHouse
Add the following code after the comment //Declare
the objects needed for MakeHouse
IloInt i;
IloModel model(env);
IloIntExprArray ends(env);
IloIntervalVarArray allTasks(env);
IloCumulFunctionExpr workers(env);
The transition time from a dirty state to
a clean state is the same value for all houses. Thus you can create
one IloTransitionDistance object that can
be shared by all houses. Here there are two transition types.
Step 4: Create the transition times
Add
the following code after the comment //Create the
transition times
IloTransitionDistance ttime(env, 2);
ttime.setValue(Dirty, Clean, 1);
You need to pass the model, the house identifier,
the array of expressions representing the completion dates of the
houses, the array of all tasks, the cumulative function expression
for the worker usage and the transition object as arguments to the MakeHouse function.
Step 5: Create the MakeHouse function
Add
the following code after the comment //Create the
MakeHouse function
void MakeHouse(IloModel model,
IloInt id,
IloIntExprArray ends,
IloIntervalVarArray allTasks,
IloCumulFunctionExpr& workers,
IloTransitionDistance ttime) {
Each house has a list of NbTasks that
must be scheduled. Task i, where i is in 0..NbTasks-1,
has a size of TaskDurations[i] and the name TaskNames[i]. Using these, you build an array tasks of interval variables.
Each
task also requires one worker from the start to the end of the task
interval. To represent the fact that a worker is required for the
task, you modify the cumulative function expression, workerUsage,
using the function IloPulse.
You
also add each interval variable to the array allTasks that
will be used to display the solution once the schedule has been determined.
Step 6: Create the interval variables
Add
the following code after the comment //Create the
interval variables
char name[128];
IloIntervalVarArray tasks(env, NbTasks);
for (IloInt i=0; i<NbTasks; ++i) {
sprintf(name, "H%ld-%s", id, TaskNames[i]);
tasks[i] = IloIntervalVar(env, TaskDurations[i], name);
workers += IloPulse(tasks[i], 1);
allTasks.add(tasks[i]);
}
The tasks in the model have precedence constraints that are added to the model.
Step 7: Add the temporal constraints
Add the following code after the comment //Add
the temporal constraints
model.add(IloEndBeforeStart(env, tasks[masonry], tasks[carpentry]));
model.add(IloEndBeforeStart(env, tasks[masonry], tasks[plumbing]));
model.add(IloEndBeforeStart(env, tasks[masonry], tasks[ceiling]));
model.add(IloEndBeforeStart(env, tasks[carpentry], tasks[roofing]));
model.add(IloEndBeforeStart(env, tasks[ceiling], tasks[painting]));
model.add(IloEndBeforeStart(env, tasks[roofing], tasks[windows]));
model.add(IloEndBeforeStart(env, tasks[roofing], tasks[facade]));
model.add(IloEndBeforeStart(env, tasks[plumbing], tasks[facade]));
model.add(IloEndBeforeStart(env, tasks[roofing], tasks[garden]));
model.add(IloEndBeforeStart(env, tasks[plumbing], tasks[garden]));
model.add(IloEndBeforeStart(env, tasks[windows], tasks[moving]));
model.add(IloEndBeforeStart(env, tasks[facade], tasks[moving]));
model.add(IloEndBeforeStart(env, tasks[garden], tasks[moving]));
model.add(IloEndBeforeStart(env, tasks[painting], tasks[moving]));
Certain tasks require the house to be clean,
and other tasks cause the house to be dirty. To model the possible
states of the house, Concert Technology provides the class IloStateFunction to represent the disjoint states
through time.
State function
A state function, represented in IBM ILOG Concert Technology by IloStateFunction, is a decision variable whose value
is a set of non-overlapping intervals over which the function maintains
a particular non-negative integer state. In between those intervals,
the state of the function is not defined, typically because of an
ongoing transition between two states.
You
create one state function object in each execution of MakeHouse,
one for each house.
The first argument passed to the
constructor of the class IloStateFunction is
the environment. The second argument is the transition time object.
The final argument is an optional name used for debug and trace purposes.
Here is a constructor:
IloStateFunction(const IloEnv env,
const IloTransitionDistance tdist,
const char* name =0);
To model the state required or imposed by a task, you create a constraint that specifies the state of the house throughout the interval variable representing that task.
Constraint on cumul function expression
With the specialized constraint IloAlwaysEqual,
you can create a constraint that specifies the value of a state function
over the interval variable.
The constraint takes a state function, an interval variable and a state value. Whenever the interval variable is present, then the state function is defined everywhere between the start and the end of the interval variable and remains equal to the specified state value over this interval.
The first argument passed to the function IloAlwaysEqual is
the environment. The second argument is the state function. The third
argument is the interval variable on which you want to place the constraint.
The fourth argument is the state value that the state function must
take during the interval.
Here is a function signature:
IloConstraint IloAlwaysEqual(const IloEnv env,
const IloStateFunction f,
const IloIntervalVar a,
IloInt v);
You create a state function and constrain the state function to take the appropriate values during the tasks that require the house to be in a specific state.
Step 8: Add the state constraints
Add the following
code after the comment //Add the state constraints
IloStateFunction houseState(env, ttime);
model.add(IloAlwaysEqual(env, houseState, tasks[masonry], Dirty));
model.add(IloAlwaysEqual(env, houseState, tasks[carpentry], Dirty));
model.add(IloAlwaysEqual(env, houseState, tasks[plumbing], Clean));
model.add(IloAlwaysEqual(env, houseState, tasks[ceiling], Clean));
model.add(IloAlwaysEqual(env, houseState, tasks[roofing], Dirty));
model.add(IloAlwaysEqual(env, houseState, tasks[painting], Clean));
model.add(IloAlwaysEqual(env, houseState, tasks[windows], Dirty));
To model the cost of building the houses,
you will need to determine the maximum completion date among the individual
house projects. To access the expression representing the completion
date of the house currently in consideration, you use the function IloEndOf on the last task in building a house (here,
it is the moving task) and store this expression in the array ends.
Step 9: Add the cost expression
Add the
following code after the comment //Add the cost expression
ends.add(IloEndOf(tasks[moving]));
This completes the MakeHouse function.
In the main function, you now call the MakeHouse function,
once for each house. At each call, the cumulative expression, workers, is updated and additional elements are
appended to the arrays ends and allTasks. The model, house identifier and transition
object are also passed to the MakeHouse function.
Step 10: Create the houses
Add
the following code after the comment //Create the
houses
for (i=0; i<NbHouses; ++i) {
MakeHouse(model, i, ends, allTasks, workers, ttime);
}
To add the constraint that there can be only
two workers working at a given time, you constrain the cumulative
function expression representing worker usage to be no greater than
the value NbWorkers.
Step 11: Add the cumulative constraints
Add
the following code after the comment //Add the cumulative
constraints
model.add(workers <= NbWorkers);
The objective of this problem is to minimize
the overall completion date (the completion date of the house that
is completed last). To do this, you minimize the maximal expression
in the array ends.
Step 12: Add the objective
Add the following
code after the comment //Add the objective
model.add(IloMinimize(env, IloMax(ends)));