IBM ILOG Solver User's Manual > More on Solving > Limits and Problem Decomposition: Locating Warehouses > Solve

In this lesson, you will learn how to decompose a problem and how to set limits on the search. Using this approach, Solver will not search the entire search tree and it may miss an optimal solution. However, Solver will explore a more promising part of the search tree more quickly. You will also learn how to use another search strategy--Depth-Bounded Discrepancy Search (DDS).

In Chapter 13, Controlling the Search: Locating Warehouses, you created goals for three decision variables: cost, supplier, and totalCost. These three goals were combined into one goal. You then applied a node evaluator to this combined goal. In the search, you used a search selector created from the totalCost variable.

In this lesson, you do not combine the goals at first. You apply node evaluators to the goals individually. You add a search limit to the first goal you execute. In the first phase, you search the search tree with a goal created from the cost variable. You prune the search tree based on a search limit associated with this goal. In the second phase, you search the remaining part of the search tree with a goal created from the supplier variable. In both phases of the search, you use a search selector created from the totalCost variable.

You will perform eight steps to create a final goal to be used in search:

The member function IloSolver::solve takes finalGoal as a parameter and searches for a solution in two phases. In the first phase, Solver searches using the goal limitgoal1. The search tree is pruned by this goal. In the second phase, the remaining part of the search tree is searched using applygoal2 until a solution is found. The search selector minimizeSearchSelector is used in both phases of the search. Figure 14.1 demonstrates this procedure.

storecpx_goals.gif

Figure 14.1 Creating a final goal

To start, you use the function IloGenerate to return goal1 for the decision variable cost. You use the parameter IloChooseMaxRegretMin for the array of variables cost. This parameter uses the principle of least regret to guide the search strategy. For more information on IloChooseMaxRegretMin, see the section "Solve".

Step 2   -  

Create goal1

Add the following code after the comment //Create goal1

    IloGoal goal1 = IloGenerate(env, cost, IloChooseMaxRegretMin);

Now, you apply a node evaluator to goal1. In this lesson, you apply a node evaluator that implements Depth-Bounded Discrepancy Search (DDS). DDS makes the assumption that mistakes are more likely near the top of the search tree rather than further down. For this reason, this procedure does not count the number of discrepancies but the depth of the last one.

The function IloDDSEvaluator creates and returns an instance of IloNodeEvaluator that implements Depth-Bounded Discrepancy Search. The first parameter is the environment. The second parameter step. Solver will first explore nodes with a depth less than step. After this exploration is complete, it will explore nodes with a depth between step and 2 * step, and so on. The third parameter is width. Solver will look first at paths, starting from the depth level, that have a number of discrepancies, or right moves, less than or equal to width. The fourth parameter is maxDiscrepancy. If this parameter is used, Solver will discard nodes with a number of discrepancies greater than maxDiscrepancy. The default value for maxDiscrepancy is IloIntMax, which means that Solver will not discard any nodes based on this parameter. Here is a constructor:

IloNodeEvaluator IloDDSEvaluator(IloEnv env,
                                 IloInt step = 4,
                                 IloInt width = 2,
                                 IloInt maxDiscrepancy = IloIntMax);

Figure 14.2 shows a search tree that has been traversed with a node evaluator that implements Depth-Bounded Discrepancy Search (DDS) with a depth of 2 and a width of 1. The numbers in the first row represent the order in which paths are evaluated. As you can see, Solver first evaluates paths 1, 2, and 3. It then jumps to paths 4, 5, and 6. It then jumps to paths 7, 8, and 9, and so on. The numbers in the second row represent the number of discrepancies, or right moves, in each path after the level of depth 2. This is called the width. As you can see, Solver first search the paths with 0 or 1 discrepancy and then the paths with 2 discrepancies.

storecpx_DDS2.gif

Figure 14.2 Search tree using a Depth-Bounded Discrepancy Search (DDS) with a depth of 2 and a width of 1.

You now use the function IloApply. This function applies the node evaluator IloDDSEvaluator to goal1 and returns applygoal1. The node evaluator will first search paths in the search tree with a depth of 3 or less nodes. It will search paths deeper than this depth that have a number of discrepancies, or right moves, less than or equal to 1.

Step 3   -  

Create applygoal1

Add the following code after the comment //Create applygoal1

    IloGoal applygoal1 = IloApply(env, goal1, IloDDSEvaluator(env, 3, 1));

Now, you will apply a search limit to the goal applygoal1. You will use the function IloFailLimit to limit the search after maxNbFails failures have occurred. Here is a constructor:

IloSearchLimit IloFailLimit(IloEnv env, IloInt maxNbFails);

Now, you use the function IloLimitSearch. It takes three parameters: the environment, a goal, and a search limit. This function places a fail limit on applygoal1 and returns limitgoal1. The fail limit stops the search after 5 failures have occurred.

Step 4   -  

Create limitgoal1

Add the following code after the comment //Create limitgoal1

    IloGoal limitgoal1 = IloLimitSearch(env, applygoal1, IloFailLimit(env, 5));

You use the function IloGenerate to return goal2 for the decision variable supplier.

Step 5   -  

Create goal2

Add the following code after the comment //Create goal2

    IloGoal goal2 = IloGenerate(env, supplier);

You now use the function IloApply. This function applies the node evaluator IloSBSEvaluator to goal2 and returns applygoal2. This node evaluator will first search paths in the search tree with 1 or less discrepancies, or right moves. It will also discard nodes with a number of discrepancies greater than or equal to 1. For more information on the node evaluator IloSBSEvaluator, see the section "Solve" in Chapter 13, Controlling the Search: Locating Warehouses.

Step 6   -  

Create applygoal2

Add the following code after the comment //Create applygoal2

    IloGoal applygoal2 = IloApply(env, goal2, IloSBSEvaluator(env, 1, 1));

Next, you create combinedGoal by joining the following goals with logical AND operators: limitgoal1, applygoal2, and a goal returned by the function IloInstantiate for the variable totalCost.

Step 7   -  

Create the combinedGoal

Add the following code after the comment //Create the combinedGoal

    IloGoal combinedGoal = limitgoal1 && 
                           applygoal2 && 
                           IloInstantiate(env, totalCost);

Then, you create a search selector, minimizeSearchSelector, from the totalCost variable. This search selector stores the leaf of the search tree that represents a solution with the optimal value of the variable totalCost. After the search tree has been completely explored, it reactivates this value. When a solution is found, it adds a constraint that totalCost must be less than or equal to the solution minus 1.

Step 8   -  

Create the search selector

Add the following code after the comment //Create the search selector

    IloSearchSelector minimizeSearchSelector = IloMinimizeVar(env,
                                                              totalCost,
                                                              1);

Finally, you use the function IloSelectSearch. This function takes combinedGoal and minimizeSearchSelector as parameters and returns finalGoal. It applies the search selector minimizeSearchSelector to the goal combinedGoal.

Step 9   -  

Create the finalGoal

Add the following code after the comment //Create the finalGoal

    IloGoal finalGoal = IloSelectSearch(env,
                                        combinedGoal,
                                        minimizeSearchSelector);

Now, you create an instance of IloSolver to solve the problem expressed in the model. This code is provided for you:

    IloSolver solver(model);

You use the member function IloSolver::solve to search for a solution. You use finalGoal as a parameter of IloSolver::solve.

Step 10   -  

Search for a solution

Add the following code after the comment //Search for a solution

    if (solver.solve(finalGoal))

Step 11   -  

Compile and run the program

Compile and run the program. You should get the following results, though the information displayed by IloSolver::printInformation will vary depending on platform, machine, configuration, and so on:

Using default file
30
[[20, 24, 11, 25, 30], [28, 27, 82, 83, 74], [74, 97, 71, 96, 70], [2, 55, 73, 
6
9, 61], [46, 96, 59, 83, 4], [42, 22, 29, 67, 59], [1, 5, 73, 59, 56], [10, 73,
13, 43, 96], [93, 35, 63, 85, 46], [47, 65, 55, 71, 95]]
[1, 4, 2, 1, 3]
------------------------------
Solution:
Store  0 Warehouse:  Rome
Store  1 Warehouse:  Bordeaux
Store  2 Warehouse:  Rome
Store  3 Warehouse:  Bonn
Store  4 Warehouse:  Rome
Store  5 Warehouse:  Bordeaux
Store  6 Warehouse:  Bordeaux
Store  7 Warehouse:  London
Store  8 Warehouse:  Bordeaux
Store  9 Warehouse:  London

Store  0 Cost:  30
Store  1 Cost:  27
Store  2 Cost:  70
Store  3 Cost:  2
Store  4 Cost:  4
Store  5 Cost:  22
Store  6 Cost:  5
Store  7 Cost:  13
Store  8 Cost:  35
Store  9 Cost:  55

Total cost:  383
------------------------------
Number of fails               : 5
Number of choice points       : 10
Number of variables           : 76
Number of constraints         : 76
Reversible stack (bytes)      : 12084
Solver heap (bytes)           : 52284
Solver global heap (bytes)    : 20124
And stack (bytes)             : 4044
Or stack (bytes)              : 4044
Search Stack (bytes)          : 4044
Constraint queue (bytes)      : 11160
Total memory used (bytes)     : 107784
Elapsed time since creation   : 0.02

As you can see, the solution is the same as that found in Chapter 13, Controlling the Search: Locating Warehouses. However, there are fewer fails and fewer choice points. Using problem limits and decomposition, there are only 5 fails and 10 choice points, as opposed to 25 fails and 27 choice points using slice-based search without problem limits and decomposition.

The complete program is listed in "Complete program". You can also view it online in the file YourSolverHome/examples/src/storecpx.cpp.

IBM® ILOG® Solver Search Strategies

Depth-First Search (DFS)

DFS is the standard search procedure. Depth-First Search explores the search tree in a "left-to-right" fashion. It searches by selecting the left leaf at each open node until the end of the search space is reached. If it does not find a solution, it backtracks and tries again. The function IloDFSEvaluator implements this strategy. See Chapter 13, Controlling the Search: Locating Warehouses.

Best First Search (BFS)

BFS is described in N. J. Nilsson's book, Problem Solving Methods in Artificial Intelligence, published by McGraw-Hill, 1971. The Solver implementation uses a parameter . When selecting an open node, Solver determines the set of open nodes the cost of which is at most (1+) worse than the best open node. If the child of the current node is in the set, Solver goes to this child. If not, Solver chooses the best open node. The function IloBFSEvaluator implements this strategy.

Slice-Based Search (SBS)

SBS is described by J. Christopher Beck and Laurent Perron in their paper, "Discrepancy Bounded Depth First Search," presented at the Second International Workshop on Integration of AI and OR Technologies for Combinatorial Optimization Problems (CP-AI-OR'00), Paderborn, Germany, 2000. The discrepancy of a search node is defined as the number of right moves. Given a parameter step, slice-based search will first explore nodes with a discrepancy less than step. After this exploration is complete, it will explore nodes with a discrepancy between step and 2*step, and so on. This search strategy cuts the search tree into slices. The function IloSBSEvaluator implements this strategy. See Chapter 13, Controlling the Search: Locating Warehouses.

Depth-Bounded Discrepancy Search (DDS)

DDS was introduced by Toby Walsh in his paper, "Depth-Bounded Discrepancy Search," published in the Proceedings of the International Joint Conference on Artificial Intelligence (IJCAI), volume 2, August, 1997, pages 1388-1393. DDS makes the assumption that mistakes are made more likely near the top of the search tree than further down. For this reason, this procedure does not count the number of discrepancies but the depth of the last one. Solver implements an improved version of Walsh's schema. Given a parameter step, DDS will first explore nodes with a depth less than step. After this exploration is complete, it will explore nodes with a depth between step and 2*step, and so on. Solver's implementation of DDS takes a parameter width, that allows width number of discrepancies beyond the depth specified by the parameter step. The function IloDDSEvaluator implements this strategy. See Chapter 14, Limits and Problem Decomposition: Locating Warehouses.

Interleaved Depth-First Search (IDFS)

IDFS was introduced by Pedro Meseguer in his paper, "Interleaved Depth-First Search," published in the Proceedings of the International Joint Conference on Artificial Intelligence (IJCAI), volume 2, August, 1997, pages 1382-1387. IDFS tries to mimic the behavior of an infinite number of threads exploring the search tree. Solver implements a variation that limits the depth of this behavior. The function IloIDFSEvaluator implements this strategy.