Q:. | + What is CBC? + |
A:. | + (JF 04/01/05) The COIN-OR Branch and Cut code + is designed to be a high quality mixed integer code provided under the terms of the + Common Public License. + CBC is written in C++, and is primarily intended to be used as a callable + library (though a rudimentary stand-alone executable exists). + The first documented release was .90.0 The current release is version .90.0. + |
Q:. | + What are some of the features of CBC? + |
A:. | + (JF 04/01/05) CBC allows the use of any Cgl cuts and the use of heuristics and + specialized branching methods. + |
Q:. | + How do I obtain and install CBC? + |
A:. | + (JF 04/01/05) Please see the + COIN-OR FAQ + for details on how to + obtain + and + install + COIN-OR modules. + |
Q:. | + Is CBC reliable? + |
A:. | + (JF 04/01/05) CBC has been tested on many problems, + but more testing and improvement is needed before it can get to version 1.0. + |
Q:. | + Is there any documentation for CBC? + |
A:. | + (JF 04/01/05) If you can see this you have the best there is:-) + Also available is a list of + CBC class descriptions generated + by Doxygen. + |
Q:. | + Is CBC as fast as Cplex or Xpress? + |
A:. | + (JF 04/01/05) No. However its design is much more flexible so advanced users + will be able to tailor CBC to their needs. + |
Q:. | + When will version 1.0 of CBC be available? + |
A:. | + (JF 04/01/05) It is expected that version 1.0 will be released in time for the 2005 + INFORMS + |
Q:. | + What can the community do to help? + |
A:. | + (JF 04/01/05) People from all around the world are already helping. There are + probably ten people who are do not always post to discussions but are constantly + "improving" the code by demanding performance or bug fixes or enhancements. And there + are others posting questions to discussion groups. + |
+There is Doxygen content for CBC available online at + +http://www.coin-or.org/Doxygen/Cbc/index.html. A local version of the +Doxygen content can be generated from the CBC distribution. To do so, in the +directory COIN/Cbc, enter make doc. +The Doxygen content will be created in the directory +COIN/Cbc/Doc/html. The same can be done for +the COIN core, from the COIN/Coin directory. +
Table of Contents
+ COIN Branch and Cut or CBC is an open-source mixed integer solver written + in C++. It is primarily meant to be used as a callable library, but a + basic, stand-alone do link **** link linkend="cbcexe"executable version is also + available. This Branch and Cut solver relies on many other parts of the COIN + repository. So it relies on Cgl for cut generators and any cut generator written to + CGL standards may be used in CBC. Again some of these cut generators e.g. Gomory cuts + rely on the factorization functionality of CoinFactorization. CBC needs a linear solver + and uses the Osi (Open Solver Interface) interface to access the linaer solver so + many solvers may be used. However the most common use is expected to be when + using COIN's native linear Solver - CLP. +
+ Before examining CBC in more detail it may be helpful to give a very brief description + of Branch and Cut (which should really be called Branch and Cut and Bound). If some + variables in the model must take on integer values e.g. 0,1 or 2 then the integrality + requirement is relaxed and a lower bound of 0.0 and an upper bound of 2.0 put on the + variable(s). This linear model can be solved using a solver. If all "integer" + variables take integer values then we are finished; if not we choose one non-integral + variable e.g. with value 1.3 (A) (B) and create two linear models - one with the variable + having an upper bound of 1.0 and the other with a lower bound of 2.0. We then put + these two models on our tree of models and solve one of them. We repeat the process + taking one model off our tree (C) (D) and repeating the process. As every time we + branch we tighten the problem so the objective value can not improve. So if we + obtain a valid solution we can use that as a bound to prune the tree. If we + try and make the linear models more integral by using Cuts then it is termed + Branch and Cut (E) (F). +
Table 1.1. Associated classes
+ Note + | + Class name + | + Description + |
---|---|---|
+ (A) + | + CbcBranch... + | + These classes define what is the nature of discontinuity. The simplest + are variables which must take an integral value but there others + which will be described later e.g. lotsizing variables. + |
+ (B) + | + CbcNode + | + This is the class that decides which variable/entity to branch on next. + Even advanced users will probably only interact with this by setting + CbcModel parameters e.g. priorities. + |
+ (C) + | + CbcTree + | + All unsolved models can be thought of as being on a tree where each + model can branch two or more times. The user should not need to be + concerned with this class. + |
+ (D) + | + CbcCompare... + | + All unsolved models are in a tree but which leaf do we choose. These + classes are very small simple ones which can be tailored to suit the problem. + |
+ (E) + | + CglCutGenerators + | + Any cut generator from Cgl can be given to the model to be used with parameters + which modify when each generator will be tried. Few people will write their + own cut generators but all should see which are effective. + |
+ (F) + | + CbcHeuristics + | + Heuristics are very important for obtaining valid solutions quickly. Some + are available but this is an area where it is useful and interesting to + write specialized ones. + |
+ There are a number of resources available to help new CBC users get started. + This document is designed to be used in conjunction with the files in the + Samples subdirectory of the main CBC directory (COIN/Cbc/Samples). + The Samples illustrate how to use CBC and may also serve as useful starting points + for user projects. In the event that either this document or the available + Doxygen content conflicts with the observed + behavior of the source code, the comments in the header files, found in + COIN/Cbc/include, are the ultimate reference. +
+ CBC is written in C++, so it is expected that users of CBC will be writing + C++ programs which use CBC as a library. Thus a working knowledge of + C++, including basic + object-oriented programming terminology is assumed in this document. In + addition, the user should be familiar with the fundamental concepts of + + Linear Programming and + + Mixed Integer Programming (may need better linke) . +
Table of Contents
+ The class that controls Coin Branch and Cut is CbcModel. This is where most + of the parameter setting is done. CbcModel uses other classes some of + which are virtual and may have multiple instances. More details on the more + useful classes will be given later. + The absolute minimum number of actions for CbcModel is: + CbcModel(OsiSolverInterface & linearSolver) as constructor, + and branchAndBound() for solving the problem. +
Table 2.1. Classes used by CbcModel - Most useful
+ Class name + | + Description + | + Notes + |
---|---|---|
+ CbcCompareBase + | + Controls choice of next node on tree + | + Default is CbcCompareDefault, others in CbcCompareActual.hpp include + CbcCompareDepth and CbcCompareObjective. Very easy for user to + experiment. + |
+ CbcCutGenerator + | + Is a CglCutGenerator with data to decide when to use. + | + Need to know how to add generator to CbcModel. + Not much need to know about details of class. + |
+ CbcHeuristic + | + Heuristic to try and get valid solutions + | + User can get a lot of value out of coding this. There can be + as many as you like. + |
+ CbcObject + | + Definition of what it means for a variable to be satisfied. + | + Virtual - instances include simple integer, simple integer with pseudocosts, + SOS (type 1 and 2) and lotsizing. (found in CbcBranch..hpp). An object has to + have a method of generating a branching object which defines an up and down + branch. + |
+ OsiSolverInterface + | + Defines the solver being used and the LP model. Is normally + passed across to CbcModel before branch and cut. + | + Virtual class - the user would instantiate a particular solver e.g. + OsiClpSolverInterface or OsiXprSolverInterface. + |
Table 2.2. Classes used by CbcModel - Least useful
+ Class name + | + Description + | + Notes + |
---|---|---|
+ CbcBranchDecision + | + Part of code for choosing which variable to branch on. Most of + work is done by definitions in CbcObject + | + Defaults to CbcBranchDefaultDecision + Not much need to know about. + |
+ CbcCountRowCut + | + Interface to OsiRowCut but counts use so can gracefully vanish. + | + See OsiRowCut for extra information. + Not much need to know about. + |
+ CbcNode + | + Controls choice of variable/entity to branch on + | + Controlled via CbcModel parameters. + Not much need to know about. + |
+ CbcNodeInfo + | + Contains data for bounds, basis etc for one node of tree + | + Not much need to know about (header in CbcNode.hpp). + |
+ CbcTree + | + How tree is stored + | + Can be changed but unlikely. + Not much need to know about. + |
+ CoinMessageHandler + | + Deals with message handling + | + User can inherit from to specialize message handling. + Not much need to know about. + |
+ CoinWarmStartBasis + | + Representation of a basis to be used by solver + | + Not much need to know about. + |
+ Below is our first CBC sample program. It is short enough to present in full + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). Most of the remaining examples in this Guide + will take the form of small code fragments. +
Example 2.1. minimum.cpp
+ +// Copyright (C) 2005, International Business Machines +// Corporation and others. All Rights Reserved. + +#include "CbcModel.hpp" + +// Using as solver +#include "OsiClpSolverInterface.hpp" + +int main (int argc, const char *argv[]) +{ + OsiClpSolverInterface solver1; + // Read in example model + // and assert that it is a clean model + int numMpsReadErrors = solver1.readMps("../../Mps/Sample/p0033.mps",""); + assert(numMpsReadErrors==0); + + // Pass data and solver to CbcModel + CbcModel model(solver1); + + // Do complete search + model.branchAndBound(); + /* Print solution. CbcModel clones solver so we + need to get current copy */ + int numberColumns = model.solver()->getNumCols(); + + const double * solution = model.solver()->getColSolution(); + + for (int iColumn=0;iColumn<numberColumns;iColumn++) { + double value=solution[iColumn]; + if (fabs(value)>1.0e-7&&model.solver()->isInteger(iColumn)) + printf("%d has value %g\n",iColumn,value); + } + return 0; +} + +
+ This sample program creates a OsiClpSolverInterface solver, + reads an MPS file, and if there are no errors, passes it to CbcModel + which solves it + using the Branch and Bound algorithm. The part of the program which solves the program + is very small but before that the linear solver had to be created with data and + after that the results were printed out. So the user can see that often knowledge + of the OsiSolverInterface methods will be necessary. + In this case CbcModel has the identical methods so we could have used those but not + always. For instance the program produces a lot of output so one might add + + model.solver()->setHintParam(OsiDoReducePrint,true,OsiHintTry); + + to reduce the amount. That improves things a lot but we still get one message per node + so we could add + + model.setLogLevel(1); + + The following section gives many of the ways of getting information from the + model or underlying solver. +
+ The OSI way to check for optimality is to call model.isProvenOptimal(). Also + available are isProvenInfeasible(), + isSolutionLimitReached(), + isNodeLimitReached() or the feared + isAbandoned(). You can also pick up + int status() which returns 0 if finished, + 1 if stopped by user and 2 if difficulties. (status of 0 even if proved + infeasible) +
+ Similarly, we can pick up the solution values. The OSI methods pick up + the current solution. This will match the best solution found so far if + called after branchAndBound and if a solution was found. +
Table 2.3. + Methods for getting solution information from OSI solver +
+ Purpose + | + Name + | + Notes + |
---|---|---|
+ Primal column solution + | const double * getColSolution() | + Outside CBC will be best solution unless none found. Safer to use + CbcModel::bestSolution() + |
+ Dual row solution + | const double * getRowPrice() | + CbcModel:: version available and identical + |
+ Primal row solution + | const double * getRowActivity() | + CbcModel:: version available and identical + |
+ Dual column solution + | const double * getReducedCost() | |
+ Number of rows in model + | int getNumRows() | + CbcModel:: version available and identical + (but note that number of rows may change due to cuts) + |
+ Number of columns in model + | int getNumCols() | + CbcModel:: version available and identical + |
+ The remainder of this chapter will show more of the basic CBC tasks a user + might wish to perform. +
Table 2.4. Some Useful Set and Get Methods
+ Method(s) + | + Description + |
---|---|
setMaximumNodes(int value) int maximumNodes() setMaximumSeconds(double value) double maximumSeconds()setMaximumSolutions(double value) double maximumSolutions() | + These methods tell CBC to stop after a given number of nodes or + seconds or solutions (and returns these values). + |
setIntegerTolerance(double) double getIntegerTolerance() | + An integer variable + is deemed to be at an integral value if it is no further than this tolerance + away. + |
setAllowableGap(double) double getAllowableGap() setAllowablePercentageGap(double) double getAllowablePercentageGap() setAllowableFractionGap(double) double getAllowableFractionGap() | + CbcModel returns if the gap between the best known solution and the best + possible solution is less than this (or as a percentage or fraction). + |
setNumberStrong(double) int numberStrong() | + Get or set the maximum number of candidates at a node to + be evaluated for strong branching. + |
setPrintFrequency(int) int printFrequency() | + Controls the number of nodes evaluated between status prints. + Print frequency has very slight overhead if small. + |
int getNodeCount() | + Returns number of nodes search took + |
int numberRowsAtContinuous() | + Returns number of rows at continuous + |
int numberIntegers() const int * integerVariable() | + Returns number of integers and an array giving which ones + |
bool isBinary(int) bool isContinuous(int) const bool isInteger(int) | + Returns information on a variable. You can use Osi methods + to set these attributes (before handing to CbcModel) + |
double getObjValue() | + This method returns the best objective value.so far + |
double getCurrentObjValue() | + This method returns the current objective value. + |
const double * getObjCoefficients() double * objective() | + These methods return the objective coefficients. + |
const double * getRowLower() double * rowLower() const double * getRowUpper() double * rowUpper() const double * getColLower() double * columnLower() const double * getColUpper() double * columnUpper() | + These methods give lower and upper bounds on row and column activities. + |
const CoinPackMatrix * getMatrixByRow() | + This method returns a pointer to a row copy of matrix + CoinPackedMatrix which can be further examined. + |
const CoinPackMatrix * getMatrixByCol() | + This method returns a pointer to a column copy of matrix + CoinPackedMatrix which can be further examined. + |
CoinBigIndex getNumElements()^{[a]} | + Returns the number of elements in the problem matrix. + |
void setObjSense(double value) double objSense() | + These methods set and get the objective sense. The parameter + value should be +1 to minimize and -1 to maximize. + |
^{[a] } + CoinBigIndex is a typedef which in + most cases is the same as int. + |
+ These are important methods which will impact search e.g. adding a cut + generator. The description here may not give all parameters if they are exotic. + You will also find more on some of them under the description + of that class: +
Table 2.5. Major methods
+ Method(s) + | + Description + |
---|---|
passInPriorities(const int * priorities, bool ifNotSimpleIntegers, + int defaultValue=1000) | + Normally this is a list of priorities (1 being highest) and the other + ifNotSimpleIntegers being false which set priorities for all integer + variables. If two variables are unsatisfied and one has a higher priority + then it is always preferred whatever its value. This can be very powerful + but also see PseudoCosts in CbcObject discussion. + |
addCutGenerator(CglCutGenerator *,int howOften, const char * name) | + This is used to add a cut generator to CbcModel. Any cut generator in Cgl + can be used. If howOften >0 then the cut generator will be called at root + node and every howOften nodes (There is an option to override and do at + depth 0,k,2k..). If -1 then the code sees how effective it was at root + node and sets howOften dynamically. If -99 then just does at root node. + There is also a redundant -100 setting which switches off which can be useful + for testing. For usage see sample2.cpp in the CBC Samples + directoryChapter 5, +More Samples + which uses the most common + cut generators. + |
addHeuristic(CbcHeuristic *) | + This adds one heuristic to CbcModel.See the section on CbcHeuristic to obtain + a list of available heuristics and a guide as to building new ones. + |
addObjects(int number,CbcObject ** objects) | + This adds members of the base class CbcObject to CbcModel. See the section + on CbcObject for detailed nformation. The objects are cloned by code so + user should delete after addObjects. There are two + main cases. The first is when the originally created objects are left and + new ones added (maybe with a higher priority); this might be used when + simple integer objects exist and Special Ordered Sets of type 1 are going + to be added, which while not necessary will give extra power in branching. + The second case is when the old ones are being deleted. + For usage see sos.cpp in the CBC Samples + directoryChapter 5, +More Samples +. + |
CglPreProcess::preProcess(OsiSolverInterface &) | + This is not really part of Cbc and can be used by other mixed integer + solvers but it can be a useful tool. It tries to fix variables and + strengthen coefficients and also do a normal presolve. Owing to the + odd nature of integer programming it may not always reduce the time + taken but is definitely worth trying. For an example of using + preProcess and postProcess see sample2.cpp in the CBC Samples + directoryChapter 5, +More Samples +. + |
setNodeComparison(CbcCompareBase *) | + This is used to use a non-default node comparison function + to see which is the next node on tree to explore. This + can make a large difference and specialized ones are easy to program. + See the section on CbcCompare. + |
Table of Contents
+ Although the unexplored nodes of the search are organized in a tree, the + order of solution is not predetermined and can be influenced by the user. + Cbc provides a abstract base class CbcCompareBase + and then instances of each. It is relatively simple for an advanced user to + create new instances and an explanation of an example will be given later. +
Table 3.1. Compare Classes provided
+ Class name + | + Description + |
---|---|
+ CbcCompareDepth + | + This will always choose the node deepest in tree. It gives minimum + tree size but may take a long time to find best solution. + |
+ CbcCompareObjective + | + This will always choose the node with the best objective value. This may + give a very large tree. It is likely that the first solution found + will be the best and the search should finish soon after the first solution + is found. + |
+ CbcCompareDefault + | + This is designed to do a mostly depth first search until a solution has + been found and then use estimates designed to give a slightly better solution. + If a reasonable number of nodes have been done or a reasonable number of + solutions found then it will go breadth first (i.e. on objective) unless + the tree is very large when it will revert to depth first. Probably + CbcCompareUser described below is better. + |
+ CbcCompareEstimate + | + If pseudocosts are being used then they can be used to guess a solution. + This just uses guessed solution. + |
+ This describes how to build a new comparison class and the reasoning + behind it. This is CbcCompareUser.hpp and + CbcCompareUser.cpp + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). + The key CbcCompare method is test which returns true if node y is better + than node x. In this method the user can easily use +
Table 3.2. Information available from CbcModel
+ objectiveValue() + | + Value of objective at that node. + |
+ numberUnsatisfied() + | + Number of unsatisfied integers (assuming branching + object is an integer - otherwise might be number of unsatsified sets) + |
+ depth() + | + depth in tree of node + |
+ guessedObjectiveValue() + | |
+ way() + | + which way would be next from this node + (for more advanced use) + |
+ variable() + | + which "variable" would be branched on. + (for more advanced use) + |
+
+ There is no information on the state of the tree. If you wanted you could + keep a pointer to the CbcModel but the way it is meant to work is that + newSolution is called whenever a solution is found and every1000Nodes is + called every 1000 nodes. When these are called the user can modify the + behavior of test. Because the model is passed in the user can also do other things + such as changing the maximum time of Branch and Cut once a solution has been found. + +So in CbcCompareUser in Samples +the four items of data are: +1) The number of solutions found so far +2) The size of the tree (number of active nodes) +3) A weight which is initialized to -1.0 +4) A saved value of weight (for when we set weight back to -1.0 for special reason) + +The full code for test is: +
Example 3.1. test
+ +// Returns true if y better than x +bool +CbcCompareUser::test (CbcNode * x, CbcNode * y) +{ + if (weight_==-1.0) { + // before solution + if (x->numberUnsatisfied() > y->numberUnsatisfied()) + return true; + else if (x->numberUnsatisfied() < y->numberUnsatisfied()) + return false; + else + return x->depth() < y->depth(); + } else { + // after solution + double weight = CoinMax(weight_,0.0); + return x->objectiveValue()+ weight*x->numberUnsatisfied() > + y->objectiveValue() + weight*y->numberUnsatisfied(); + } +} + +
+So initially as weight is < 0.0 we are biased towards depth first. In +fact it prefers y is y has fewer unsatisfied variables - if there is a tie +then it prefers the one with greater depth in tree. + +Once we get a solution newSolution is called. If it was a solution +achieved by branching we work out how much it cost per unsatisfied integer +variable to go from continuous solution to integer solution. We then set +the weight to aim at a slightly better solution. From then on test +returns true if it looks as if y will lead to a better solution than x. +This is done by newSolution +
Example 3.2. newSolution
+ +// This allows method to change behavior as it is called +// after each solution +void +CbcCompareUser::newSolution(CbcModel * model, + double objectiveAtContinuous, + int numberInfeasibilitiesAtContinuous) +{ + if (model->getSolutionCount()==model->getNumberHeuristicSolutions()) + return; // solution was got by rounding so we ignore + // set to get close to this solution + double costPerInteger = + (model->getObjValue()-objectiveAtContinuous)/ + ((double) numberInfeasibilitiesAtContinuous); + weight_ = 0.98*costPerInteger; + saveWeight_=weight_; + numberSolutions_++; + if (numberSolutions_>5) + weight_ =0.0; // this searches on objective +} + +
+ +But as the search goes on this may be modified. + +If we have done a lot of nodes or got a lot of solutions then weight is +set to 0.0 so we are doing breadth first search. This can lead to an +enormous tree so if the tree size is >10000 then we go may go back to one biased +towards depth first. This is done by every1000Nodes. +
Example 3.3. newSolution
+ +// This allows method to change behavior +bool +CbcCompareUser::every1000Nodes(CbcModel * model, int numberNodes) +{ + if (numberNodes>10000) + weight_ =0.0; // this searches on objective + else if (numberNodes==1000&&weight_==-2.0) + weight_=-1.0; // Go to depth first + // get size of tree + treeSize_ = model->tree()->size(); + if (treeSize_>10000) { + // set weight to reduce size most of time + if (treeSize_>20000) + weight_=-1.0; + else if ((numberNodes%4000)!=0) + weight_=-1.0; + else + weight_=saveWeight_; + } + return numberNodes==11000; // resort if first time +} + +
+ For practical use it is very useful to be able to get a good solution reasonably fast. + A good bound will greatly reduce the run time and good solutions can satisfy the user + on very large problems where a complete search is impossible. Heuristics are obviously + problem dependant although some have more general use. I hope to increase the number. + At present there is only one in Cbc itself although there are others in the Samples + directory. The heuristic is trying to obtain a solution to the original + problem so it need only consider original rows and does not have to use the + current bounds. + One to use a greedy heuristic designed for use in the miplib problem + fast0507 will be developed later in this section. + Cbc provides a abstract base class CbcHeuristic and a rounding heuristic in Cbc. +
+ This describes how to build a greedy heuristic for a set covering problem. + A more general version is in CbcHeuristicGreedy.hpp and + CbcHeuristicGreedy.cpp + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). + + The heuristic we will code will leave all variables which are at one at this node of the + tree to that value and will + initially set all others to zero. We then sort all variables in order of their cost + divided by the number of entries in rows which are not yet covered. We may randomize that + value a bit so that ties will be broken in different ways on different runs of the heuristic. + We then choose the best one and set it to one and repeat the exercise. Because this is + a set covering problem ≥ we are guaranteed to find a solution (not necessarily a + better one though). We could + improve the speed by just redoing those affected but in this text we will keep it simple. + Also if all elements are 1.0 then we could do faster. + The key CbcHeuristic method is int solution(double & solutionValue, + double * betterSolution) + which returns 0 if no solution found and 1 if found when it fills in the objective value + and primal solution. The actual code in CbcHeuristicGreedy.cpp is + a little more complicated but this will work and gives the basic idea. For instance + the code here assumes all variables are integer. + The important bit of data is a copy of the matrix (stored by column) + before any cuts have been made. The data used are bounds, objective and the matrix + plus two work arrays. +
Example 3.4. Data
+ + OsiSolverInterface * solver = model_->solver(); // Get solver from CbcModel + const double * columnLower = solver->getColLower(); // Column Bounds + const double * columnUpper = solver->getColUpper(); + const double * rowLower = solver->getRowLower(); // We know we only need lower bounds + const double * solution = solver->getColSolution(); + const double * objective = solver->getObjCoefficients(); // In code we also use min/max + double integerTolerance = model_->getDblParam(CbcModel::CbcIntegerTolerance); + double primalTolerance; + solver->getDblParam(OsiPrimalTolerance,primalTolerance); + int numberRows = originalNumberRows_; // This is number of rows when matrix was passed in + // Column copy of matrix (before cuts) + const double * element = matrix_.getElements(); + const int * row = matrix_.getIndices(); + const CoinBigIndex * columnStart = matrix_.getVectorStarts(); + const int * columnLength = matrix_.getVectorLengths(); + + // Get solution array for heuristic solution + int numberColumns = solver->getNumCols(); + double * newSolution = new double [numberColumns]; + // And to sum row activities + double * rowActivity = new double[numberRows]; + +
+Then we initialize newSolution as rounded down solution. +
Example 3.5. initialize newSolution
+ + for (iColumn=0;iColumn<numberColumns;iColumn++) { + CoinBigIndex j; + double value = solution[iColumn]; + // Round down integer + if (fabs(floor(value+0.5)-value)<integerTolerance) + value=floor(CoinMax(value+1.0e-3,columnLower[iColumn])); + // make sure clean + value = CoinMin(value,columnUpper[iColumn]); + value = CoinMax(value,columnLower[iColumn]); + newSolution[iColumn]=value; + if (value) { + double cost = direction * objective[iColumn]; + newSolutionValue += value*cost; + for (j=columnStart[iColumn]; + j<columnStart[iColumn]+columnLength[iColumn];j++) { + int iRow=row[j]; + rowActivity[iRow] += value*element[j]; + } + } + } + +
+Now some row activities will be below their lower bound so +we then find the variable which is cheapest in reducing the sum of +infeasibilities. We then repeat. This is a finite process and could be coded +to be faster but this is simplest. +
Example 3.6. Create feasible new solution
+ + while (true) { + // Get column with best ratio + int bestColumn=-1; + double bestRatio=COIN_DBL_MAX; + for (int iColumn=0;iColumn<numberColumns;iColumn++) { + CoinBigIndex j; + double value = newSolution[iColumn]; + double cost = direction * objective[iColumn]; + // we could use original upper rather than current + if (value+0.99<columnUpper[iColumn]) { + double sum=0.0; // Compute how much we will reduce infeasibility by + for (j=columnStart[iColumn]; + j<columnStart[iColumn]+columnLength[iColumn];j++) { + int iRow=row[j]; + double gap = rowLower[iRow]-rowActivity[iRow]; + if (gap>1.0e-7) { + sum += CoinMin(element[j],gap); + if (element[j]+rowActivity[iRow]<rowLower[iRow]+1.0e-7) { + sum += element[j]; + } + } + if (sum>0.0) { + double ratio = (cost/sum)*(1.0+0.1*CoinDrand48()); + if (ratio<bestRatio) { + bestRatio=ratio; + bestColumn=iColumn; + } + } + } + } + if (bestColumn<0) + break; // we have finished + // Increase chosen column + newSolution[bestColumn] += 1.0; + double cost = direction * objective[bestColumn]; + newSolutionValue += cost; + for (CoinBigIndex j=columnStart[bestColumn]; + j<columnStart[bestColumn]+columnLength[bestColumn];j++) { + int iRow = row[j]; + rowActivity[iRow] += element[j]; + } + } + +
+We have finished so now we need to see if solution is better and doublecheck +we are feasible. +
Example 3.7. Check good solution
+ + returnCode=0; // 0 means no good solution + if (newSolutionValue<solutionValue) { + // check feasible + memset(rowActivity,0,numberRows*sizeof(double)); + for (iColumn=0;iColumn<numberColumns;iColumn++) { + CoinBigIndex j; + double value = newSolution[iColumn]; + if (value) { + for (j=columnStart[iColumn]; + j<columnStart[iColumn]+columnLength[iColumn];j++) { + int iRow=row[j]; + rowActivity[iRow] += value*element[j]; + } + } + } + // check was approximately feasible + bool feasible=true; + for (iRow=0;iRow<numberRows;iRow++) { + if(rowActivity[iRow]<rowLower[iRow]) { + if (rowActivity[iRow]<rowLower[iRow]-10.0*primalTolerance) + feasible = false; + } + } + if (feasible) { + // new solution + memcpy(betterSolution,newSolution,numberColumns*sizeof(double)); + solutionValue = newSolutionValue; + // We have good solution + returnCode=1; + } + } + +
+If the user declares variables as integer but does no more, then Cbc will treat them +as simple integer variables. In many cases the user would like to do some more fine tuning. This shows how to create integer variables with pseudo costs. When pseudo costs are given then +it is assumed that if a variable is at 1.3 then the cost of branching that variable down will be 0.3 times the down pseudo cost and the cost of branching up would be 0.7 times the up pseudo cost. This can be used both for branching and for choosing a node. + The full code is in longthin.cpp + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). + The idea is simple for set covering problems. + Branching up gets us much closer to an integer solution so we want + to encourage up - so we will branch up if variable value > 0.333333. + The expected cost of going up obviously depends on the cost of the + variable so we just choose pseudo costs to reflect that. +
Example 3.8. Pseudo costs
+ + int iColumn; + int numberColumns = solver3->getNumCols(); + // do pseudo costs + CbcObject ** objects = new CbcObject * [numberColumns]; + // Point to objective + const double * objective = model.getObjCoefficients(); + int numberIntegers=0; + for (iColumn=0;iColumn<numberColumns;iColumn++) { + if (solver3->isInteger(iColumn)) { + double cost = objective[iColumn]; + CbcSimpleIntegerPseudoCost * newObject = + new CbcSimpleIntegerPseudoCost(&model,numberIntegers,iColumn, + 2.0*cost,cost); + newObject->setMethod(3); + objects[numberIntegers++]= newObject; + } + } + // Now add in objects (they will replace simple integers) + model.addObjects(numberIntegers,objects); + for (iColumn=0;iColumn<numberIntegers;iColumn++) + delete objects[iColumn]; + delete [] objects; + +
+The actual coding in the example also tries to give more importance to variables with more +coefficients. Whether this sort of thing is worthwhile should be the subject of experimentation. +Here is another example which is for crew scheduling problems. In this case the problem has +few rows but many thousands of variables. Branching a variable to 1 is very powerful as it +fixes many other variables to zero, but branching to zero is very weak as thousands of variables +can increase from zero. But in crew scheduling each constraint is a flight leg e.g. JFK to DFW. +From DFW (Dallas) there may be several flights the crew could take next - suppose one flight is +the 9:30 flight from DFW to LAX (Los Angeles). Then a binary branch is that the crew arriving +at DFW either take the 9:30 flight to LAX or they don't. This follow-on branching does not +fix individual variables but instead divides all the variables with entries in the JFK-DFW +constraint into two groups - those with entries in the DFW-LAX constraint and those without +entries. + The full code is in crew.cpp + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). In this case we may as well leave the simple integer +variables and we may have to if there are other sorts of constraints. But we want to +branch on the follow-on rules first so we use priorities to say that those are the +important ones. +
Example 3.9. Follow-on branching
+ + int iColumn; + int numberColumns = solver3->getNumCols(); + /* We are going to add a single follow on object but we + want to give low priority to existing integers + As the default priority is 1000 we don't actually need to give + integer priorities but it is here to show how. + */ + // Normal integer priorities + int * priority = new int [numberColumns]; + int numberIntegers=0; + for (iColumn=0;iColumn<numberColumns;iColumn++) { + if (solver3->isInteger(iColumn)) { + priority[numberIntegers++]= 100; // low priority + } + } + /* Second parameter is true if we are adding objects, + false if integers. So this does integers */ + model.passInPriorities(priority,false); + delete [] priority; + /* Add in objects before we can give priority. + In this case just one - but this shows general method + */ + CbcObject ** objects = new CbcObject * [1]; + objects[0]=new CbcFollowOn(&model); + model.addObjects(1,objects); + delete objects[0]; + delete [] objects; + // High priority + int followPriority=1; + model.passInPriorities(&followPriority,true); + +
+ Coin Branch and Cut uses a generic OsiSolverInterface and its resolve capability. + This does not give much flexibility so advanced users can inherit from the interface + of choice. This describes such a solver for a long thin problem e.g. fast0507 again. + As with all these examples it is not guaranteed that this is the fastest way to solve + any of these problems - they are to illustrate techniques. + The full code is in CbcSolver2.hpp and + CbcSolver2.cpp + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). + initialSolve is called a few times so although we will not gain much + this is a simpler place to start. The example derives from OsiClpSolverInterface and the code + is: +
Example 3.10. initialSolve
+ + // modelPtr_ is of type ClpSimplex * + modelPtr_->setLogLevel(1); // switch on a bit of printout + modelPtr_->scaling(0); // We don't want scaling for fast0507 + setBasis(basis_,modelPtr_); // Put basis into ClpSimplex + // Do long thin by sprint + ClpSolve options; + options.setSolveType(ClpSolve::usePrimalorSprint); + options.setPresolveType(ClpSolve::presolveOff); + options.setSpecialOption(1,3,15); // Do 15 sprint iterations + modelPtr_->initialSolve(options); // solve problem + basis_ = getBasis(modelPtr_); // save basis + modelPtr_->setLogLevel(0); // switch off printout + +
+The resolve method is more complicated. The main pieces of data are +a counter count_ which is incremented each solve and an int array node_ which stores the last time +a variable was active in a solution. For the first few times normal dual is called and +node_ array is updated. +
Example 3.11. First few solves
+ + if (count_<10) { + OsiClpSolverInterface::resolve(); // Normal resolve + if (modelPtr_->status()==0) { + count_++; // feasible - save any nonzero or basic + const double * solution = modelPtr_->primalColumnSolution(); + for (int i=0;i<numberColumns;i++) { + if (solution[i]>1.0e-6||modelPtr_->getStatus(i)==ClpSimplex::basic) { + node_[i]=CoinMax(count_,node_[i]); + howMany_[i]++; + } + } + } else { + printf("infeasible early on\n"); + } + } + +
+After the first few solves we only use those which took part in a solution in the last so many +solves. As fast0507 is a set covering problem we can also take out any rows which are +already covered. +
Example 3.12. Create small problem
+ + int * whichRow = new int[numberRows]; // Array to say which rows used + int * whichColumn = new int [numberColumns]; // Array to say which columns used + int i; + const double * lower = modelPtr_->columnLower(); + const double * upper = modelPtr_->columnUpper(); + setBasis(basis_,modelPtr_); // Set basis + int nNewCol=0; // Number of columns in small model + // Column copy of matrix + const double * element = modelPtr_->matrix()->getElements(); + const int * row = modelPtr_->matrix()->getIndices(); + const CoinBigIndex * columnStart = modelPtr_->matrix()->getVectorStarts(); + const int * columnLength = modelPtr_->matrix()->getVectorLengths(); + + int * rowActivity = new int[numberRows]; // Number of columns with entries in each row + memset(rowActivity,0,numberRows*sizeof(int)); + int * rowActivity2 = new int[numberRows]; // Lower bound on row activity for each row + memset(rowActivity2,0,numberRows*sizeof(int)); + char * mark = (char *) modelPtr_->dualColumnSolution(); // Get some space to mark columns + memset(mark,0,numberColumns); + for (i=0;i<numberColumns;i++) { + bool choose = (node_[i]>count_-memory_&&node_[i]>0); // Choose if used recently + // Take if used recently or active in some sense + if ((choose&&upper[i]) + ||(modelPtr_->getStatus(i)!=ClpSimplex::atLowerBound&& + modelPtr_->getStatus(i)!=ClpSimplex::isFixed) + ||lower[i]>0.0) { + mark[i]=1; // mark as used + whichColumn[nNewCol++]=i; // add to list + CoinBigIndex j; + double value = upper[i]; + if (value) { + for (j=columnStart[i]; + j<columnStart[i]+columnLength[i];j++) { + int iRow=row[j]; + assert (element[j]==1.0); + rowActivity[iRow] ++; // This variable can cover this row + } + if (lower[i]>0.0) { + for (j=columnStart[i]; + j<columnStart[i]+columnLength[i];j++) { + int iRow=row[j]; + rowActivity2[iRow] ++; // This row redundant + } + } + } + } + } + int nOK=0; // Use to count rows which can be covered + int nNewRow=0; // Use to make list of rows needed + for (i=0;i<numberRows;i++) { + if (rowActivity[i]) + nOK++; + if (!rowActivity2[i]) + whichRow[nNewRow++]=i; // not satisfied + else + modelPtr_->setRowStatus(i,ClpSimplex::basic); // make slack basic + } + if (nOK<numberRows) { + // The variables we have do not cover rows - see if we can find any that do + for (i=0;i<numberColumns;i++) { + if (!mark[i]&&upper[i]) { + CoinBigIndex j; + int good=0; + for (j=columnStart[i]; + j<columnStart[i]+columnLength[i];j++) { + int iRow=row[j]; + if (!rowActivity[iRow]) { + rowActivity[iRow] ++; + good++; + } + } + if (good) { + nOK+=good; // This covers - put in list + whichColumn[nNewCol++]=i; + } + } + } + } + delete [] rowActivity; + delete [] rowActivity2; + if (nOK<numberRows) { + // By inspection the problem is infeasible - no need to solve + modelPtr_->setProblemStatus(1); + delete [] whichRow; + delete [] whichColumn; + printf("infeasible by inspection\n"); + return; + } + // Now make up a small model with the right rows and columns + ClpSimplex * temp = new ClpSimplex(modelPtr_,nNewRow,whichRow,nNewCol,whichColumn); + +
+If the variables cover the rows then we know that the problem is feasible (We are not using +cuts). If the rows +were E rows then this might not be the case and we would have to do more work. When we have solved +then we see if there are any negative reduced costs and if there are then we have to go to the +full problem and use primal to clean up. +
Example 3.13. Check optimal solution
+ + temp->setDualObjectiveLimit(1.0e50); // Switch off dual cutoff as problem is restricted + temp->dual(); // solve + double * solution = modelPtr_->primalColumnSolution(); // put back solution + const double * solution2 = temp->primalColumnSolution(); + memset(solution,0,numberColumns*sizeof(double)); + for (i=0;i<nNewCol;i++) { + int iColumn = whichColumn[i]; + solution[iColumn]=solution2[i]; + modelPtr_->setStatus(iColumn,temp->getStatus(i)); + } + double * rowSolution = modelPtr_->primalRowSolution(); + const double * rowSolution2 = temp->primalRowSolution(); + double * dual = modelPtr_->dualRowSolution(); + const double * dual2 = temp->dualRowSolution(); + memset(dual,0,numberRows*sizeof(double)); + for (i=0;i<nNewRow;i++) { + int iRow=whichRow[i]; + modelPtr_->setRowStatus(iRow,temp->getRowStatus(i)); + rowSolution[iRow]=rowSolution2[i]; + dual[iRow]=dual2[i]; + } + // See if optimal + double * dj = modelPtr_->dualColumnSolution(); + // get reduced cost for large problem + // this assumes minimization + memcpy(dj,modelPtr_->objective(),numberColumns*sizeof(double)); + modelPtr_->transposeTimes(-1.0,dual,dj); + modelPtr_->setObjectiveValue(temp->objectiveValue()); + modelPtr_->setProblemStatus(0); + int nBad=0; + + for (i=0;i<numberColumns;i++) { + if (modelPtr_->getStatus(i)==ClpSimplex::atLowerBound + &&upper[i]>lower[i]&&dj[i]<-1.0e-5) + nBad++; + } + // If necessary claen up with primal (and save some statistics) + if (nBad) { + timesBad_++; + modelPtr_->primal(1); + iterationsBad_ += modelPtr_->numberIterations(); + } + +
+We then update node_ array as for the first few solves. To give some idea of the effect of this +tactic fast0507 has 63,009 variables and the small problem never has more than 4,000 variables. +In just over ten percent of solves did we have to resolve and then the average number of iterations +on full problem was less than 20. +To give another example - again only for illustrative purposes it is possible to do quadratic +mip. In this case we make resolve the same as +initialSolve. + The full code is in ClpQuadInterface.hpp and + ClpQuadInterface.cpp + (this code can be found in the CBC Samples directory, see + Chapter 5, +More Samples +). +
Example 3.14. Solve a quadratic mip
+ + // save cutoff + double cutoff = modelPtr_->dualObjectiveLimit(); + modelPtr_->setDualObjectiveLimit(1.0e50); + modelPtr_->scaling(0); + modelPtr_->setLogLevel(0); + // solve with no objective to get feasible solution + setBasis(basis_,modelPtr_); + modelPtr_->dual(); + basis_ = getBasis(modelPtr_); + modelPtr_->setDualObjectiveLimit(cutoff); + if (modelPtr_->problemStatus()) + return; // problem was infeasible + // Now pass in quadratic objective + ClpObjective * saveObjective = modelPtr_->objectiveAsObject(); + modelPtr_->setObjectivePointer(quadraticObjective_); + modelPtr_->primal(); + modelPtr_->setDualObjectiveLimit(cutoff); + if (modelPtr_->objectiveValue()>cutoff) + modelPtr_->setProblemStatus(1); + modelPtr_->setObjectivePointer(saveObjective); + +
Table of Contents
+The CBC distribution includes a number of .cpp sample files. +Users are encouraged to use them as starting points for their own CBC projects. +The files can be found in the COIN/Cbc/Samples/ directory. +For the latest information on compiling and running these samples, please see +the file COIN/Cbc/Samples/INSTALL. Most of them can be built +by
make DRIVER=name
which produces an executable testit. Below is a list of +some of the most useful sample files with a short description for each file. +
Table 5.1. Basic Samples
+ Source file + | + Description + |
---|---|
minimum.cpp | + This is a CBC "Hello, world" program. It reads a problem + from an MPS file, and solves the problem. + |
sample2.cpp | + This is designed to be a file that a user could modify to get a useful + driver program for his or her project. In particular, it demonstrates + the use of Cgl's preprocess functionality. + It uses CbcBranchUser.cpp, + CbcCompareUser.cpp and + CbcHeuristicUser.cpp + with corresponding *.hpp files. + |
Table 5.2. Advanced Samples
+ Source file + | + Description + |
---|---|
crew.cpp | + This sample, shows the use of advanced branching and a use of priorities. + It uses CbcCompareUser.cpp + with corresponding *.hpp files. + |
longthin.cpp | + This sample shows the advanced use of a solver. It also has coding for + a greedy heuristic. + The solver is given in CbcSolver2.hpp and + CbcSolver2.cpp. + The heuristic is given in CbcHeuristicGreedy.hpp and + CbcHeuristicGreedy.cpp. + It uses CbcBranchUser.cpp and + CbcCompareUser.cpp + with corresponding *.hpp files. + |
qmip.cpp | + This solves a quadratic mip. It is to show advanced use of a solver. + The solver is given in ClpQuadInterface.hpp and + ClpQuadInterface.cpp. + It uses CbcBranchUser.cpp and + CbcCompareUser.cpp + with corresponding *.hpp files. + |
sos.cpp | + This artificially creates a Special Ordered set problem. + |
lotsize.cpp | + This artificially creates a lot sizing problem. + |
+ Some of the more common messages and codes passed by CLP are listed in the + tables below. This is list is not meant to exhaustive. The notation is as + for printf from "C": +
Table 6.1. + COIN Messages passed at or above logging level 1 +
+ Code + | + Area + | + Text and notes + | |
---|---|---|---|
+ 1 + | + MPSREAD + | At line %d %s | |
+ This just prints out NAME line, ROW line, etc + | |||
+ 2 + | + MPSREAD + | Problem %s has %d rows, %d columns and %d elements + | |
+ This gives statistics after reading an MPS file + | |||
+ 8 + | + MPSREAD + | %s read with %d errors + | |
+ This gives error statistics for file + | |||
+ 505 + | + PRESOLVE + | + Presolved poblem not optimal, resolve after postsolve + | |
+ This could be because it was not feasible or because of maximum + iterations. If this message occurs then consider using primal clean up + | |||
+ 506 + | + PRESOLVE + | + Presolve %d (%d) rows, %d (%d) columns and %d (%d) elements + | |
+ The first number is the number after presolve and the number + in parentheses is amount of reduction + | |||
+ 510 + | + PRESOLVE + | + Presolve is modifying %d integer bounds and re-presolving + | |
+ If presolve determines at the end that an integer variable have its bounds + changed then it will repeat the entrire presolve + | |||
+ 511 + | + PRESOLVE + | + After Postsolve, objective %g, infeasibilities - dual %g (%d), + primal %g (%d) + | |
+ This gives the state after postsolve - this gives the objective value + and the sum of dual and primal infeasibilities with the number of + infeasibilities in parentheses. Hopefully these should be zero + | |||
+ 512 + | + PRESOLVE + | + Presolved model was optimal, full model needs cleaning up + | |
+ If the numbers in previous message (511) were large then maybe we need to + know, if small then that's life + |
Table 6.2. + CLP Messages passed at or above logging level 1 +
+ Code + | + Area + | + Text and notes + | |
---|---|---|---|
+ 1 + | + SIMPLEX + | + Primal infeasible - objective value %g + | |
+ You may need to look at previous messages or use methods. Such as + sumPrimalInfeasibilities() to find cause + | |||
+ 2 + | + SIMPLEX + | + Dual infeasible - objective value %g + | |
+ You may need to look at previous messages or use methods. Such as + sumDualInfeasibilities() to find cause + | |||
+ 3 + | + SIMPLEX + | + Stopped - objective value %g + | |
+ The algorithm stopped as requested by the user. + | |||
+ 4 + | + SIMPLEX + | + Stopped due to errors - objective value %g + | |
+ Switch on log level 2 to see information on size of elements etc. If they + look reasonable then maybe we need to know. + | |||
+ 5 + | + SIMPLEX + | + %d Obj %g Primal inf %g (%d) Dual inf %g (%d) + | |
+ At each re-factorization this gives the number of iterations and the value + of the objective function. If there are primal infeasibilities then the + sum and number are given and similarly for dual infeasibilities. + (This is a simplified form of message.) + | |||
+ 14 + | + SIMPLEX + | + Perturbing problem by %g % of %g + | |
+ There is more to this message but if the user sees this then s/he has + chosen to perturb the problem or the algorithm has decided to do so. + If the numbers look too large the user may wish to think again. + | |||
+ 19 + | + SIMPLEX + | + %d variables/rows fixed as scaled bounds too close + | |
+ If this occurs look carefully at your input data + | |||
+ 24 + | + SIMPLEX + | + Matrix will be packed to eliminate small elements + | |
+ If this occurs the user should look carefully at data. + | |||
+ 26 + | + SIMPLEX + | + Matrix will be packed to eliminate %d duplicate elements + | |
+ If this occurs the user should look carefully at data. + | |||
+ 28 + | + SIMPLEX + | + Crash put %d variables in basis, %d dual infeasibilities + | |
+ + | |||
+ 29 + | + SIMPLEX + | + End of values pass after %d iterations + | |
+ ??? If primal(1) or dual(1) the a sweep through model is made and this + signals end of pass. + |
Table 6.3. + COIN Messages passed at or above logging level 0 +
+ Code + | + Area + | + Text and notes + | |
---|---|---|---|
+ 3001 + | + MPSREAD + | + Illegal value for %s of %g + | |
+ String will be "infinity" if setInfinity passed bad value, + or "default integer bound" if setDefaultBound passed bad value. + | |||
+ 3002 + | + MPSREAD + | + Bad image at line %d < %s > + | |
+ This gives line number and the offending line + | |||
+ 3003 + | + MPSREAD + | + Duplicate objective at line %d < %s > + | |
+ An objective row appears twice in one column + | |||
+ 3004 + | + MPSREAD + | + Duplicate row %s at line %d %s + | |
+ The named row appears twice in one column. + | |||
+ 3005 + | + MPSREAD + | + No match for row %s at line %d < %s > + | |
+ The named row did not appear in ROWS section. + | |||
+ 3006 + | + MPSREAD + | + No match for column at line %d < %s > + | |
+ The named column (in BOUNDS section) did not appear in COLUMNS section. + | |||
+ 6001 + | + MPSREAD + | + Unable to open mps input file %s + | |
+ + | |||
+ 6002 + | + MPSREAD + | + Unknown image %s at line %d of file %s + | |
+ The Mps reader could not make sense of the image file specified. + | |||
+ 6003 + | + MPSREAD + | + Consider the possibility of a compressed file which zlib is unable to read. + | |
+ Some .gz files can not be read by zlib. Using gunzip and then gzip + normally cures problem. + | |||
+ 6004 + | + MPSREAD + | + EOF on file %s + | |
+ The Mps reader did not find expected section marker. + | |||
+ 6005 + | + MPSREAD + | + Returning as too many errors + | |
+ The reader has put out 100 messages and is giving up. + | |||
+ 507 + | + PRESOLVE + | + Presolve determined that the problem is infeasible with tolerance of %g + | |
+ If you want you can try with a larger tolerance + | |||
+ 508 + | + PRESOLVE + | + Presolve thinks problem is unbounded + | |
+ Perhaps the user should maximize if initially minimizing or vice versa. + | |||
+ 509 + | + PRESOLVE + | + Presolve thinks problem is infeasible AND unbounded??? + | |
+ If you get this message we want to know + |
Table 6.4. + CLP Messages passed at or above logging level 0 +
+ Code + | + Area + | + Text and notes + | |
---|---|---|---|
+ 3002 + | + SIMPLEX + | + Not solving empty problem - %d rows, %d columns and %d elements + | |
+ Test problem size before solving. + | |||
+ 6002 + | + SIMPLEX + | + %d bad bound pairs or bad objectives were found + | |
+ Either the value in the objective was too large or a lower bound was + greater than an upper bound. + | |||
+ 6003 + | + SIMPLEX + | + Matrix has %d large values, first at column %d, row %d is %g + | |
+ Some of the values in matrix are ridiculous. + | |||
+ 6004 + | + SIMPLEX + | + Can't get out of loop ... + | |
+ + |
+ There are also messages available at log level 2 (the most likely useful relate + to scaling), and will be addressed in a future version of this User Guide. +
Copyright © 2005 IBM Coportation
Table of Contents
List of Tables
List of Examples