1 /*
2   Copyright (C) 2007, Lou Hafer, International Business Machines Corporation
3   and others.  All Rights Reserved.
4 
5   This code is licensed under the terms of the Eclipse Public License (EPL).
6 
7   $Id$
8 */
9 /*
10   This file is part of cbc-generic.
11 */
12 
13 #if defined(_MSC_VER)
14 // Turn off compiler warning about long names
15 #pragma warning(disable : 4786)
16 #endif
17 
18 #include <string>
19 #include <cassert>
20 
21 #include "CoinFinite.hpp"
22 #include "CoinParam.hpp"
23 
24 #include "OsiSolverInterface.hpp"
25 
26 #include "CbcModel.hpp"
27 #include "CbcGenCtlBlk.hpp"
28 
29 #include "CbcGenParam.hpp"
30 #include "CbcGenCbcParam.hpp"
31 #include "CbcGenOsiParam.hpp"
32 
33 /*! \file CbcOsiParamUtils
34     \brief Implementation functions for CbcOsiParam parameters.
35 */
36 
37 namespace {
38 
39 char svnid[] = "$Id$";
40 
41 }
42 
43 namespace CbcOsiParamUtils {
44 
45 /*
46   Function to set up OSI parameters. Does not include solver-specific
47   parameters.
48 
49   ALGORITHM is commented out in CoinSolve.
50 */
51 
addCbcOsiParams(int & numberParameters,CoinParamVec & parameters,OsiSolverInterface * osi)52 void addCbcOsiParams(int &numberParameters, CoinParamVec &parameters,
53   OsiSolverInterface *osi)
54 {
55   CbcOsiParam *param;
56   OsiHintParam key;
57   bool sense;
58   OsiHintStrength strength;
59   int ival;
60   double dval;
61 
62   param = new CbcOsiParam(CbcOsiParam::KEEPNAMES,
63     "keepN!ames", "Whether to keep row and column names on import.",
64     "off", 1);
65   param->appendKwd("on");
66   param->setPushFunc(pushCbcOsiKwd);
67   param->setObj(osi);
68   param->setLongHelp(
69     "Row and column names are human-friendly, but maintaining names takes up space and time. Specifying -keepnames off >before< importing a problem will discard any name information.");
70   parameters.push_back(param);
71 
72   (void)osi->getIntParam(OsiMaxNumIteration, ival);
73   param = new CbcOsiParam(CbcOsiParam::MAXITERATION,
74     "maxIt!erations", "Iteration limit for OSI solver.",
75     0, COIN_INT_MAX, ival);
76   param->setPushFunc(pushCbcOsiInt);
77   param->setObj(osi);
78   param->setLongHelp(
79     "Limits the number of iterations the OSI solver can perform when solving a problem.");
80   parameters.push_back(param);
81 
82   (void)osi->getIntParam(OsiMaxNumIterationHotStart, ival);
83   param = new CbcOsiParam(CbcOsiParam::MAXHOTITS,
84     "hot!StartMaxIts", "Iteration limit for OSI solver hot start.",
85     0, COIN_INT_MAX, ival);
86   param->setPushFunc(pushCbcOsiInt);
87   param->setObj(osi);
88   param->setLongHelp(
89     "Limits the number of iterations the OSI solver can perform when solving a problem from a hot start. In the context of cbc, this limits the number of iterations expended on each LP during strong branching.");
90   parameters.push_back(param);
91 
92   /*
93       Simplified to on/off for OsiSolverInterface, where it goes in as a hint.
94     */
95   (void)osi->getHintParam(OsiDoPresolveInInitial, sense, strength);
96   if (sense == true) {
97     ival = 1;
98   } else {
99     ival = 0;
100   }
101   param = new CbcOsiParam(CbcOsiParam::PRESOLVE,
102     "presolve", "Whether to presolve problem", "off", ival);
103   param->appendKwd("on");
104   param->setPushFunc(pushCbcOsiHint);
105   param->setObj(osi);
106   param->setLongHelp(
107     "Presolve analyzes the model to find such things as redundant constraints, constraints which fix some variables, constraints which can be transformed into bounds, etc.  For the initial solve of any problem this is worth doing unless you know that it will have no effect.");
108   parameters.push_back(param);
109 
110   param = new CbcOsiParam(CbcOsiParam::PRIMALTOLERANCE,
111     "primalT!olerance",
112     "For an optimal solution no primal infeasibility may exceed this value",
113     1.0e-20, 1.0e12);
114   param->setPushFunc(pushCbcOsiDbl);
115   param->setObj(osi);
116   param->setLongHelp(
117     "Normally the default tolerance is fine, but you may want to increase it a bit if a primal run seems to be having a hard time");
118   parameters.push_back(param);
119 
120   /*
121       Simplified for OsiSolverInterface, which just takes a hint.
122     */
123   (void)osi->getHintParam(OsiDoScale, sense, strength);
124   if (sense == true) {
125     ival = 1;
126   } else {
127     ival = 0;
128   }
129   param = new CbcOsiParam(CbcOsiParam::SCALING,
130     "scal!ing", "Whether to scale problem", "off", ival);
131   param->appendKwd("on");
132   param->setPushFunc(pushCbcOsiHint);
133   param->setObj(osi);
134   param->setLongHelp(
135     "Scaling can help in solving problems which might otherwise fail because of lack of accuracy.  It can also reduce the number of iterations.  It is not applied if the range of elements is small.  When unscaled it is possible that there may be small primal and/or infeasibilities.");
136   parameters.push_back(param);
137 
138   ival = osi->messageHandler()->logLevel();
139   param = new CbcOsiParam(CbcOsiParam::SOLVERLOGLEVEL,
140     "slog!Level", "Level of detail in Solver output", -1, 63, ival);
141   param->setPushFunc(pushCbcOsiLogLevel);
142   param->setObj(osi);
143   param->setLongHelp(
144     "If 0 then there should be no output in normal circumstances. 1 is probably the best value for most uses, while 2 and 3 give more information.");
145   parameters.push_back(param);
146 
147   numberParameters = parameters.size();
148   assert(numberParameters <= parameters.capacity());
149 }
150 
loadOsiParamObj(const CoinParamVec paramVec,int first,int last,OsiSolverInterface * obj)151 void loadOsiParamObj(const CoinParamVec paramVec, int first, int last,
152   OsiSolverInterface *obj)
153 
154 {
155   int i;
156   /*
157       Load the OsiSolverInterface object into the parameters
158     */
159   for (i = first; i <= last; i++) {
160     CbcOsiParam *osiParam = dynamic_cast< CbcOsiParam * >(paramVec[i]);
161     assert(osiParam != 0);
162     osiParam->setObj(obj);
163   }
164 
165   return;
166 }
167 
168 /*
169   Function to set default values for solver appropriate for cbc-generic.
170 */
171 
setOsiSolverInterfaceDefaults(OsiSolverInterface * osi)172 void setOsiSolverInterfaceDefaults(OsiSolverInterface *osi)
173 
174 {
175   bool result;
176 
177   /*
178       OsiNameDiscipline isn't supported by all solvers, so check to see that it
179       worked. If not, fall back to zero.
180     */
181   osi->setIntParam(OsiMaxNumIteration, 1000000);
182   osi->setIntParam(OsiMaxNumIterationHotStart, 1000);
183   result = osi->setIntParam(OsiNameDiscipline, 1);
184   if (!result) {
185     result = osi->setIntParam(OsiNameDiscipline, 0);
186   }
187 
188   /*
189       Primal and dual feasibility tolerances (OsiPrimalTolerance and
190       OsiDualTolerance, respectively)  are left to the discretion of the solver.
191     */
192   osi->setDblParam(OsiDualObjectiveLimit, 1.0e100);
193   osi->setDblParam(OsiPrimalObjectiveLimit, 1.0e100);
194   osi->setDblParam(OsiObjOffset, 0.0);
195 
196   osi->setHintParam(OsiDoPresolveInInitial, true, OsiHintDo);
197   osi->setHintParam(OsiDoDualInInitial, true, OsiHintIgnore);
198   osi->setHintParam(OsiDoPresolveInResolve, false, OsiHintTry);
199   osi->setHintParam(OsiDoDualInInitial, true, OsiHintTry);
200   osi->setHintParam(OsiDoScale, true, OsiHintDo);
201   osi->setHintParam(OsiDoCrash, true, OsiHintIgnore);
202   osi->setHintParam(OsiDoReducePrint, true, OsiHintDo);
203   osi->setHintParam(OsiDoInBranchAndCut, true, OsiHintTry);
204 
205   return;
206 }
207 
208 /*
209   Function to push an integer parameter.
210 */
211 
pushCbcOsiInt(CoinParam * param)212 int pushCbcOsiInt(CoinParam *param)
213 
214 {
215   assert(param != 0);
216 
217   CbcOsiParam *osiParam = dynamic_cast< CbcOsiParam * >(param);
218   assert(osiParam != 0);
219 
220   OsiSolverInterface *osi = osiParam->obj();
221   assert(osi != 0);
222   int val = osiParam->intVal();
223   CbcOsiParam::CbcOsiParamCode code = osiParam->paramCode();
224 
225   assert(osi != 0);
226   /*
227       Setup to return nonfatal/fatal error (1/-1) by default, so that all we need
228       to do is correct to 0 (no error) if we're successful.
229     */
230   int retval;
231   if (CoinParamUtils::isInteractive()) {
232     retval = 1;
233   } else {
234     retval = -1;
235   }
236   /*
237       Translate the parameter code from CbcOsiParamCode into the correct key for
238       CbcIntParam.
239     */
240   OsiIntParam key;
241   switch (code) {
242   case CbcOsiParam::MAXITERATION: {
243     key = OsiMaxNumIteration;
244     break;
245   }
246   case CbcOsiParam::MAXHOTITS: {
247     key = OsiMaxNumIterationHotStart;
248     break;
249   }
250   default: {
251     std::cerr << "pushCbcOsiIntParam: no equivalent OsiIntParam for "
252               << "parameter code `" << code << "'." << std::endl;
253     retval = -1;
254     break;
255   }
256   }
257 
258   bool setOK = osi->setIntParam(key, val);
259   if (setOK == false) {
260     retval = -1;
261   }
262 
263   return (retval);
264 }
265 /*
266   Function to push a double parameter.
267 */
268 
pushCbcOsiDbl(CoinParam * param)269 int pushCbcOsiDbl(CoinParam *param)
270 
271 {
272   assert(param != 0);
273 
274   CbcOsiParam *osiParam = dynamic_cast< CbcOsiParam * >(param);
275   assert(osiParam != 0);
276 
277   OsiSolverInterface *osi = osiParam->obj();
278   assert(osi != 0);
279   double val = osiParam->dblVal();
280   CbcOsiParam::CbcOsiParamCode code = osiParam->paramCode();
281 
282   assert(osi != 0);
283   /*
284       Setup to return nonfatal/fatal error (1/-1) by default, so that all we need
285       to do is correct to 0 (no error) if we're successful.
286     */
287   int retval;
288   if (CoinParamUtils::isInteractive()) {
289     retval = 1;
290   } else {
291     retval = -1;
292   }
293   /*
294       Translate the parameter code from CbcOsiParamCode into the correct key for
295       CbcDblParam.
296     */
297   OsiDblParam key;
298   switch (code) {
299   case CbcOsiParam::PRIMALTOLERANCE: {
300     key = OsiPrimalTolerance;
301     break;
302   }
303   case CbcOsiParam::DUALTOLERANCE: {
304     key = OsiDualTolerance;
305     ;
306     break;
307   }
308   case CbcOsiParam::DUALBOUND: {
309     key = OsiDualObjectiveLimit;
310     break;
311   }
312   default: {
313     std::cerr << "pushCbcOsiDblParam: no equivalent OsiDblParam for "
314               << "parameter code `" << code << "'." << std::endl;
315     retval = -1;
316     break;
317   }
318   }
319 
320   bool setOK = osi->setDblParam(key, val);
321   if (setOK == false) {
322     retval = -1;
323   }
324 
325   return (retval);
326 }
327 
328 /*
329   Function to push a keyword-valued parameter. This can translate into integer
330   as well as string-valued parameters.
331 */
332 
pushCbcOsiKwd(CoinParam * param)333 int pushCbcOsiKwd(CoinParam *param)
334 
335 {
336   assert(param != 0);
337   CbcOsiParam *osiParam = dynamic_cast< CbcOsiParam * >(param);
338   assert(osiParam != 0);
339   OsiSolverInterface *osi = osiParam->obj();
340   assert(osi != 0);
341 
342   std::string str = osiParam->kwdVal();
343   CbcOsiParam::CbcOsiParamCode code = osiParam->paramCode();
344 
345   int retval = 0;
346   /*
347       Figure out what we're doing and set the relevant field.
348     */
349   OsiIntParam key;
350 
351   switch (code) {
352   case CbcOsiParam::KEEPNAMES: {
353     if (str == "on" || str == "off") {
354       int discipline;
355       if (str == "on") {
356         discipline = 1;
357       } else {
358         discipline = 0;
359       }
360       bool recog = osi->setIntParam(OsiNameDiscipline, discipline);
361       if (recog == false) {
362         std::cerr
363           << "pushCbcOsiKwdParam(KEEPNAMES): underlying solver does not "
364           << "recognise name discipline " << discipline << "."
365           << std::endl;
366         retval = +1;
367       }
368     } else {
369       std::cerr
370         << "pushCbcOsiKwdParam(KEEPNAMES): unrecognised keyword `"
371         << str << "'." << std::endl;
372       retval = -1;
373     }
374     break;
375   }
376   default: {
377     std::cerr
378       << "pushCbcGenKwdParam: unrecognised parameter code `"
379       << code << "'." << std::endl;
380     retval = -1;
381     break;
382   }
383   }
384 
385   return (retval);
386 }
387 
388 /*
389   Function to set the solver's output level. To cover all the bases, adjust
390   the message handler and set the hint. Nothing can go fatally wrong here,
391   but we'll return non-fatal error if the solver rejects the hint. The
392   implementor of an OSI has wide latitude with hints, and may elect to set a
393   log level as part of handling the hint. Do that first and then explicitly
394   set the message handler log level to be sure the new value isn't
395   overridden.
396 */
397 
pushCbcOsiLogLevel(CoinParam * param)398 int pushCbcOsiLogLevel(CoinParam *param)
399 
400 {
401   assert(param != 0);
402   CbcOsiParam *osiParam = dynamic_cast< CbcOsiParam * >(param);
403   assert(osiParam != 0);
404   OsiSolverInterface *osi = osiParam->obj();
405   assert(osi != 0);
406 
407   int lvl = param->intVal();
408   /*
409       Setup to return nonfatal/fatal error (1/-1) by default, so that all we need
410       to do is correct to 0 (no error) if we're successful.
411     */
412   int retval;
413   if (CoinParamUtils::isInteractive()) {
414     retval = 1;
415   } else {
416     retval = -1;
417   }
418   /*
419       Now try to do the right thing with a hint. Harder to say -- assume that log
420       level 1 is `normal'.
421     */
422   OsiHintStrength strength;
423   bool sense;
424   if (lvl < 1) {
425     strength = OsiHintDo;
426     sense = true;
427   } else if (lvl == 1) {
428     strength = OsiHintIgnore;
429     sense = true;
430   } else if (lvl == 2) {
431     strength = OsiHintTry;
432     sense = false;
433   } else {
434     strength = OsiHintDo;
435     sense = false;
436   }
437 
438   bool setOK = osi->setHintParam(OsiDoReducePrint, sense, strength);
439 
440   /*
441       Recover the message handler and set the log level directly.
442     */
443   CoinMessageHandler *hndl = osi->messageHandler();
444   assert(hndl != 0);
445   hndl->setLogLevel(lvl);
446 
447   if (setOK) {
448     return (0);
449   } else {
450     return (retval);
451   }
452 }
453 
454 /*
455   Function for parameters that are enabled/disabled with a hint.
456 */
pushCbcOsiHint(CoinParam * param)457 int pushCbcOsiHint(CoinParam *param)
458 
459 {
460   assert(param != 0);
461   CbcOsiParam *osiParam = dynamic_cast< CbcOsiParam * >(param);
462   assert(osiParam != 0);
463   OsiSolverInterface *osi = osiParam->obj();
464   assert(osi != 0);
465   /*
466       Setup to return nonfatal/fatal error (1/-1) by default, so that all we need
467       to do is correct to 0 (no error) if we're successful.
468     */
469   int retval;
470   if (CoinParamUtils::isInteractive()) {
471     retval = 1;
472   } else {
473     retval = -1;
474   }
475   /*
476       Set the sense for the hint.
477     */
478   std::string kwd = param->kwdVal();
479   bool sense;
480   if (kwd == "off") {
481     sense = false;
482   } else {
483     sense = true;
484   }
485   /*
486       Grab the parameter code and translate to an OSI parameter key.
487     */
488   CbcOsiParam::CbcOsiParamCode code = osiParam->paramCode();
489   OsiHintParam key;
490   switch (code) {
491   case CbcOsiParam::PRESOLVE: {
492     key = OsiDoPresolveInInitial;
493     break;
494   }
495   case CbcOsiParam::SCALING: {
496     key = OsiDoScale;
497     break;
498   }
499   default: {
500     std::cerr << "pushCbcOsiHint: no equivalent OsiHintParam for "
501               << "parameter code `" << code << "'." << std::endl;
502     retval = -1;
503     break;
504   }
505   }
506 
507   bool setOK = osi->setHintParam(key, sense, OsiHintDo);
508 
509   if (setOK) {
510     return (0);
511   } else {
512     return (retval);
513   }
514 }
515 
516 } // end namespace CbcOsiParamUtils
517 
518 /* vi: softtabstop=2 shiftwidth=2 expandtab tabstop=2
519 */
520