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