Overview | Group | Tree | Graph | Index | Concepts |
Description |
Local Search versus Large Neighborhood Search |
Scheduler Large Neighborhood |
Extending the Library of Large Neighborhoods |
The main idea behind Large Neighborhood Search is to focus the search on a sub-part of the decision variables of the problem. In this approach, we start from a known solution (current solution) and try to improve it by restoring (freezing) the values subset of some decision variables stored in this solution, while leaving other decision variables free. A goal is then used to try fix these remaining decision variables.
The set of decision variables is partitioned into two sets: the set of selected decision variables, and the other variables. Different restore policies are then applied, depending if the variable belongs or not to the set of selected decision variables.
To define the set of selected decision variables, use the pure virtual
member function defineSelected
of the class
IloLargeNHoodI
. Its signature is:
IloSolution IloLargeNHoodI::defineSelected(IloSolver solver, IloInt index)
The instance of class IloSolution
returned must contain
the set of selected decision variables corresponding to the neighbor with
index index
.
At first glance, a large neighborhood and a neighborhood look very similar. But there are also some differences:
define
). The size of the neighborhood is the number
of neighbors. A goal may be used to validate the neighbor: to check
for an instance where all the constraints are fulfilled.
Suppose there is a scheduling problem made of three activities
requiring a unary resource. Further, we have found a first solution and
wish to iteratively select an activity and try to relocate it on
the resource. In a local search approach, the size of the
neighborhood is nine: each activity can be either first,
last or in the middle on the resource. A sub-goal may
be used to ensure that all other constraints are fulfilled (side
constraints). In a large neighborhood search approach, the
number of sub-problems to solve is the number of activities (three in
this case). For each sub-problem, we can use, for instance, the goal
IloRankForward
to find a neighbor.
Large neighborhoods dedicated to scheduling problems derive from
the class IloSchedulerLargeNHoodI
.
Two predefined neighborhoods dedicated to scheduling problems are available:
IloRelocateActivityNHood
which iteratively selects an
activity and tries to reschedule it somewhere else.
IloTimeWindowNHood
which iteratively selects a time
window and tries to reschedule the activities within the time window.
The class IloSchedulerLargeNHoodI
provides specialization of
functionalities available in class IloLargeNHoodI
for activities and
resource constraints. For instance, in class IloLargeNHood
,
there is a member function isSelected
used to check if an
extractable is selected or not:
IloBool isSelected(IloExtractable extr) const;
A similar member function exists on class IloLargeNHood
to check
if activities and resource constraints are selected:
IloBool isSelected(IloActivity activity) const;
IloBool isSelected(IloResourceConstraint rc) const;
In addition to the above functionalities, the main difference between class
IloSchedulerLargeNHoodI
and class IloLargeNHoodI
is that class
IloSchedulerLargeNHoodI
overloads the method
finalizeDelta
. To ease the definition of neighborhoods
dedicated to scheduling problems, this method removes from any resource
constraint that is not selected the next, the previous, the direct
successors and the direct predecessors, in case these are
selected. These are then replaced by new appropriate direct predecessors and
direct successors.
As an example, suppose a set of activities are scheduled on a unary resource, and a time window neighborhood is applied. In the following equation, R is the resource, [Ai] represents an activity and | represents the time window boundary.
R: [A0][A1]..[Ai-1] | A[i]..[Aj] | [Aj+1]..[Ak]
Here, the time window contains all activities from Ai to Aj. In the current solution Ai is the next (and also the direct successor) of Ai-1. Similarly in the current solution, activity Aj is the previous (and also the direct predecessors) of activity Aj+1. Both the next of Ai-1, the successor of Ai-1, the previous of Aj+1 and the direct predecessors of Aj+1 are removed. Also, Aj+1 is set as the successor of Ai-1, and Ai-1 is set as the predecessor of Aj+1.
There are two ways to extend the library with new large neighborhoods:
IloLargeNHoodI
.
There are two ways to combine large neighborhoods:
The size of the resulting neighborhood is the product of the sizes of the two neighborhoods: size(n1)*size(n2).
The set of selected extractables of the union for index i is the union of the sets of selected extractables for index i1 for neighborhood n1, and for index i2 for neighborhood n2 such that i = i1*size(n2) + i2.
For any extractable, if it belongs to the union of the selected sets, it will be restored only if both neighborhoods specify that it must be restored. In case it does not belong to the union of the selected sets, then it is restored if at least one of the two neighborhoods specifies that it must be restored.
For example, a neighborhood that relocates two activities at once can be defined as the union of two relocate activity neighborhoods:
IloSchedulerLargeNHood n1 = IloRelocateActivityNHood(env); IloSchedulerLargeNHood n2 = IloRelocateActivityNHood(env); IloLargeNHood nhood = IloUnionNHood(env, n1, n2);
As for the union, the size of the resulting neighborhood is the product of the sizes of the two neighborhoods: size(n1)*size(n2).
The set of selected extractables of the intersection for index i is the intersection of the sets of selected extractables for index i1 for neighborhood n1 and for index i2 for neighborhood n2 such that i = i1*size(n2) + i2.
For any extractable, if it belongs to the intersection of the selected sets, it will be restored only if both neighborhoods specify that it must be restored. In case it does not belong to the intersection of the selected sets, then it is restored if at least one of the two neighborhoods specify that it must be restored.
For instance, a neighborhood that relocates activities within a time window with a window size 20 and a window step 10 can be defined as:
IloSchedulerLargeNHood n1 = IloRelocateActivityNHood(env); IloSchedulerLargeNHood n2 = IloTimeWindowNHood(env, 20, 10); IloLargeNHood nhood = IloIntersectNHood(env, n1, n2);
When deriving from class IloLargeNHoodI
(or IloSchedulerLargeNHoodI
),
you have to overload at least the three following virtual member
functions:
IloSolution defineSelected(IloSolver solver, IloInt index); void start(IloSolver solver, IloSolution); IlcInt getSize(IloSolver solver, IloInt index);
The method defineSelected
is a pure virtual member function and must be
overloaded to define the selected extractables corresponding to a
neighbor with index.
Methods start
and getSize
are pure virtual
member functions of class IloNHoodI
and must be overloaded as for any
neighborhood.
Other virtual member functions on class IloLargeNHoodI
are:
void defineRestoreInfo(IloSolver solver, IloSolution delta); void finalizeDelta(IloSolver solver, IloSolution delta);
Method defineRestoreInfo
is used to specify for each
extractable the restore information; that is, either the restore fields
for activities and resource constraints, or the restore status for
other extractables. If this information differs from the current solution, then this
information is added to the delta solution provided as
argument. Default implementation uses the predicates.
Method finalizeDelta
can be used to complete the
definition delta. By default it doesn't do anything.
Note that class IloLargeHNoodI
derives from class
IloNHoodI
and redefines the virtual method
IloNHoodI::define
. It calls method
defineSelected
, and then if not empty, calls method
defineRestoreInfo
and finalizeDelta
. So when
deriving class IloLargeHNoodI
, you do not need to overload virtual
method define
.