1 //                                               -*- C++ -*-
2 /**
3  *  @brief Bonmin allows to describe a MINLP optimization algorithm
4  *
5  *  Copyright 2005-2021 Airbus-EDF-IMACS-ONERA-Phimeca
6  *
7  *  This library is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 #include "openturns/Bonmin.hxx"
22 #include "openturns/ResourceMap.hxx"
23 #include "openturns/SpecFunc.hxx"
24 #include "openturns/OSS.hxx"
25 #include "openturns/SymbolicFunction.hxx"
26 #include "openturns/PersistentObjectFactory.hxx"
27 #ifdef OPENTURNS_HAVE_BONMIN
28 #include "openturns/BonminProblem.hxx"
29 #include <BonBonminSetup.hpp>
30 #include <BonCbc.hpp>
31 using namespace Bonmin;
32 using namespace Ipopt;
33 #endif
34 
35 BEGIN_NAMESPACE_OPENTURNS
36 
37 CLASSNAMEINIT(Bonmin)
38 
39 static const Factory<Bonmin> Factory_Bonmin;
40 
41 /** Constructors */
Bonmin(const String & algoName)42 Bonmin::Bonmin(const String & algoName)
43   : OptimizationAlgorithmImplementation()
44   , algoName_()
45 {
46   setAlgorithmName(algoName);
47 }
48 
Bonmin(OptimizationProblem & problem,const String & algoName)49 Bonmin::Bonmin( OptimizationProblem & problem,
50                 const String & algoName)
51   : OptimizationAlgorithmImplementation(problem)
52   , algoName_()
53 {
54   setAlgorithmName(algoName);
55 }
56 
57 /* Virtual constructor */
clone() const58 Bonmin * Bonmin::clone() const
59 {
60   return new Bonmin(*this);
61 }
62 
63 
64 
65 /** Bonmin static methods */
IsAvailable()66 Bool Bonmin::IsAvailable()
67 {
68 #ifdef OPENTURNS_HAVE_BONMIN
69   return true;
70 #else
71   return false;
72 #endif
73 }
74 
GetAlgorithmNames()75 Description Bonmin::GetAlgorithmNames()
76 {
77   Description algoNames(5);
78   algoNames[0] = "B-BB";
79   algoNames[1] = "B-OA";
80   algoNames[2] = "B-QG";
81   algoNames[3] = "B-Hyb";
82   algoNames[4] = "B-iFP";
83 
84   return algoNames;
85 }
86 
87 /** Accessors to Bonmin attributes */
setAlgorithmName(const String & algoName)88 void Bonmin::setAlgorithmName(const String & algoName)
89 {
90   // Check algoName
91   if (!GetAlgorithmNames().contains(algoName))
92     throw InvalidArgumentException(HERE) << "Unknown solver " << algoName;
93   algoName_ = algoName;
94 }
95 
getAlgorithmName() const96 String Bonmin::getAlgorithmName() const
97 {
98   return algoName_;
99 }
100 
101 
102 /** Check whether this problem can be solved by this solver. */
checkProblem(const OptimizationProblem & problem) const103 void Bonmin::checkProblem(const OptimizationProblem & problem) const
104 {
105   // Cannot solve multi-objective problems
106   if (problem.hasMultipleObjective()) throw InvalidArgumentException(HERE) << "Bonmin does not support multi-objective optimization";
107 
108   // No LeastSquaresProblem / NearestPointProblem
109   if (problem.hasResidualFunction() || problem.hasLevelFunction())
110     throw InvalidArgumentException(HERE) << "Bonmin does not support least squares or nearest point problems";
111 }
112 
113 
114 #ifdef OPENTURNS_HAVE_BONMIN
115 
116 /** Accessors to Bonmin options */
GetOptionsFromResourceMap(SmartPtr<OptionsList> options)117 static void GetOptionsFromResourceMap(SmartPtr<OptionsList> options)
118 {
119 //   Get options for Bonmin setup from ResourceMap
120 //   See Bonmin/Ipopt user manuals for more details.
121 
122   std::vector<String> keys(ResourceMap::GetKeys());
123   const UnsignedInteger nbKeys = keys.size();
124 
125   for (UnsignedInteger i = 0; i < nbKeys; ++i)
126     if (keys[i].substr(0, 7) == "Bonmin-")
127     {
128       String optionName(keys[i].substr(7));
129       String type(ResourceMap::GetType(keys[i]));
130       Bool ok = false;
131       if (type == "str")
132         ok = options->SetStringValue(optionName, ResourceMap::GetAsString(keys[i]));
133       else if (type == "float")
134         ok = options->SetNumericValue(optionName, ResourceMap::GetAsScalar(keys[i]));
135       else if (type == "int")
136         ok = options->SetIntegerValue(optionName, ResourceMap::GetAsUnsignedInteger(keys[i]));
137       else if (type == "bool")
138         ok = options->SetStringValue(optionName, ResourceMap::GetAsBool(keys[i]) ? "yes" : "no");
139       if (!ok)
140         throw InvalidArgumentException(HERE) << "Invalid Bonmin option " << optionName;
141     }
142 }
143 
144 #endif
145 
146 /** Performs the actual computation. */
run()147 void Bonmin::run()
148 {
149 #ifdef OPENTURNS_HAVE_BONMIN
150   // Check problem
151   checkProblem(getProblem());
152 
153   // Check starting point
154   if (getStartingPoint().getDimension() != getProblem().getDimension())
155     throw InvalidArgumentException(HERE) << "Invalid starting point dimension (" << getStartingPoint().getDimension() << "), expected " << getProblem().getDimension();
156 
157   // Create BonminProblem
158   Ipopt::SmartPtr<BonminProblem> tminlp = new BonminProblem(getProblem(), getStartingPoint(), getMaximumEvaluationNumber());
159   tminlp->setProgressCallback(progressCallback_.first, progressCallback_.second);
160   tminlp->setStopCallback(stopCallback_.first, stopCallback_.second);
161 
162   // Create setup, initialize options
163   BonminSetup app;
164   app.initializeOptionsAndJournalist();
165   app.options()->SetStringValue("bonmin.algorithm", algoName_);
166   app.options()->SetIntegerValue("max_iter", getMaximumIterationNumber());
167   app.options()->SetStringValue("sb", "yes"); // skip ipopt banner
168   app.options()->SetIntegerValue("print_level", 0);
169   app.options()->SetStringValue("honor_original_bounds", "yes");// disabled in ipopt 3.14
170   app.options()->SetIntegerValue("bonmin.bb_log_level", 0);
171   app.options()->SetIntegerValue("bonmin.nlp_log_level", 0);
172   app.options()->SetIntegerValue("bonmin.lp_log_level", 0);
173   app.options()->SetIntegerValue("bonmin.oa_log_level", 0);
174   app.options()->SetIntegerValue("bonmin.fp_log_level", 0);
175   app.options()->SetIntegerValue("bonmin.milp_log_level", 0);
176   GetOptionsFromResourceMap(app.options());
177   String optlist;
178   app.options()->PrintList(optlist);
179   LOGDEBUG(optlist);
180 
181   // Update setup with BonminProblem
182   app.initialize(GetRawPtr(tminlp));
183 
184   // Solve problem
185   Bab solver;
186   solver(app);
187 
188   // Retrieve input/output history
189   Sample inputHistory(tminlp->getInputHistory());
190   Sample outputHistory(tminlp->getOutputHistory());
191 
192   // Create OptimizationResult, initialize error values
193   OptimizationResult optimResult(getProblem());
194   Scalar absoluteError = -1.0;
195   Scalar relativeError = -1.0;
196   Scalar residualError = -1.0;
197   Scalar constraintError = -1.0;
198 
199   /* Populate OptimizationResult from history */
200 
201   for (UnsignedInteger i = 0; i < inputHistory.getSize(); ++ i)
202   {
203     const Point inP(inputHistory[i]);
204     const Point outP(outputHistory[i]);
205     constraintError = 0.0;
206 
207     // Constraint error on bounds
208     if (getProblem().hasBounds())
209     {
210       Interval bounds(getProblem().getBounds());
211       for (UnsignedInteger j = 0; j < getProblem().getDimension(); ++ j)
212       {
213         if (bounds.getFiniteLowerBound()[j])
214           constraintError = std::max(constraintError, bounds.getLowerBound()[j] - inP[j]);
215         if (bounds.getFiniteUpperBound()[j])
216           constraintError = std::max(constraintError, inP[j] - bounds.getUpperBound()[j]);
217       }
218     }
219 
220     // Constraint error on equality constraints
221     if (getProblem().hasEqualityConstraint())
222     {
223       const Point g(getProblem().getEqualityConstraint()(inP));
224       constraintError = std::max(constraintError, g.normInf());
225     }
226 
227     // Constraint error on inequality constraints
228     if (getProblem().hasInequalityConstraint())
229     {
230       Point h(getProblem().getInequalityConstraint()(inP));
231       for (UnsignedInteger k = 0; k < getProblem().getInequalityConstraint().getOutputDimension(); ++ k)
232       {
233         h[k] = std::min(h[k], 0.0); // convention h(x)>=0 <=> admissibility
234       }
235       constraintError = std::max(constraintError, h.normInf());
236     }
237 
238     // Computing errors, storing into OptimizationResult
239     if (i > 0)
240     {
241       const Point inPM(inputHistory[i - 1]);
242       const Point outPM(outputHistory[i - 1]);
243       absoluteError = (inP - inPM).normInf();
244       relativeError = (inP.normInf() > 0.0) ? (absoluteError / inP.normInf()) : -1.0;
245       residualError = (std::abs(outP[0]) > 0.0) ? (std::abs(outP[0] - outPM[0]) / std::abs(outP[0])) : -1.0;
246     }
247 
248     optimResult.store(inP,
249                       outP,
250                       absoluteError,
251                       relativeError,
252                       residualError,
253                       constraintError);
254   }
255 
256   // Optimum is not the last call to objective function
257   optimResult.setOptimalPoint(tminlp->getOptimalPoint());
258   optimResult.setOptimalValue(tminlp->getOptimalValue());
259 
260   setResult(optimResult);
261 
262   String allOptions;
263   app.options()->PrintList(allOptions);
264   LOGINFO(allOptions);
265 
266 #else
267 
268   throw NotYetImplementedException(HERE) << "No Bonmin support";
269 
270 #endif
271 }
272 
273 
274 /** Description of object */
__str__(const String &) const275 String Bonmin::__str__(const String &) const
276 {
277   OSS oss(false);
278   oss << "class=" << getClassName()
279       << "\nalgorithm=" << algoName_;
280   return oss;
281 }
282 
__repr__() const283 String Bonmin::__repr__() const
284 {
285   OSS oss(false);
286   oss << __str__();
287   oss << "\noptions=\n";
288 
289   // List user-defined options
290   std::vector<String> keys(ResourceMap::GetKeys());
291   UnsignedInteger nbKeys = keys.size();
292 
293   for (UnsignedInteger i = 0; i < nbKeys; ++i)
294     if (keys[i].substr(0, 7) == "Bonmin-")
295     {
296       String optionName(keys[i].substr(7));
297       String type(ResourceMap::GetType(keys[i]));
298       if (type == "str")
299         oss << optionName << "=" << ResourceMap::GetAsString(keys[i]) << "\n";
300       else if (type == "float")
301         oss << optionName << "=" << ResourceMap::GetAsScalar(keys[i]) << "\n";
302       else if (type == "int")
303         oss << optionName << "=" << ResourceMap::GetAsUnsignedInteger(keys[i]) << "\n";
304       else if (type == "bool")
305         oss << optionName << "=" << ResourceMap::GetAsBool(keys[i]) << "\n";
306       else
307         throw InvalidArgumentException(HERE) << "Unsupported type " << type << " for Bonmin option " << optionName;
308     }
309 
310   return oss;
311 }
312 
313 END_NAMESPACE_OPENTURNS
314