1 /*
2 * Copyright (c) 2011-2021, The DART development contributors
3 * All rights reserved.
4 *
5 * The list of contributors can be found at:
6 * https://github.com/dartsim/dart/blob/master/LICENSE
7 *
8 * This file is provided under the following "BSD-style" License:
9 * Redistribution and use in source and binary forms, with or
10 * without modification, are permitted provided that the following
11 * conditions are met:
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * * Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
19 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
20 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include <iostream>
34
35 #include "dart/common/Console.hpp"
36 #include "dart/math/Helpers.hpp"
37 #include "dart/optimizer/GradientDescentSolver.hpp"
38 #include "dart/optimizer/Problem.hpp"
39
40 namespace dart {
41 namespace optimizer {
42
43 //==============================================================================
44 const std::string GradientDescentSolver::Type = "GradientDescentSolver";
45
46 //==============================================================================
UniqueProperties(double _stepMultiplier,std::size_t _maxAttempts,std::size_t _perturbationStep,double _maxPerturbationFactor,double _maxRandomizationStep,double _defaultConstraintWeight,Eigen::VectorXd _eqConstraintWeights,Eigen::VectorXd _ineqConstraintWeights)47 GradientDescentSolver::UniqueProperties::UniqueProperties(
48 double _stepMultiplier,
49 std::size_t _maxAttempts,
50 std::size_t _perturbationStep,
51 double _maxPerturbationFactor,
52 double _maxRandomizationStep,
53 double _defaultConstraintWeight,
54 Eigen::VectorXd _eqConstraintWeights,
55 Eigen::VectorXd _ineqConstraintWeights)
56 : mStepSize(_stepMultiplier),
57 mMaxAttempts(_maxAttempts),
58 mPerturbationStep(_perturbationStep),
59 mMaxPerturbationFactor(_maxPerturbationFactor),
60 mMaxRandomizationStep(_maxRandomizationStep),
61 mDefaultConstraintWeight(_defaultConstraintWeight),
62 mEqConstraintWeights(_eqConstraintWeights),
63 mIneqConstraintWeights(_ineqConstraintWeights)
64 {
65 // Do nothing
66 }
67
68 //==============================================================================
Properties(const Solver::Properties & _solverProperties,const UniqueProperties & _descentProperties)69 GradientDescentSolver::Properties::Properties(
70 const Solver::Properties& _solverProperties,
71 const UniqueProperties& _descentProperties)
72 : Solver::Properties(_solverProperties), UniqueProperties(_descentProperties)
73 {
74 // Do nothing
75 }
76
77 //==============================================================================
GradientDescentSolver(const Properties & _properties)78 GradientDescentSolver::GradientDescentSolver(const Properties& _properties)
79 : Solver(_properties),
80 mGradientP(_properties),
81 mRD(),
82 mMT(mRD()),
83 mDistribution(
84 0.0, std::nextafter(1.0, 2.0)) // This allows mDistrubtion to produce
85 // numbers in the range [0,1] inclusive
86 {
87 // Do nothing
88 }
89
90 //==============================================================================
GradientDescentSolver(std::shared_ptr<Problem> _problem)91 GradientDescentSolver::GradientDescentSolver(std::shared_ptr<Problem> _problem)
92 : Solver(_problem),
93 mRD(),
94 mMT(mRD()),
95 mDistribution(0.0, std::nextafter(1.0, 2.0))
96 {
97 // Do nothing
98 }
99
100 //==============================================================================
~GradientDescentSolver()101 GradientDescentSolver::~GradientDescentSolver()
102 {
103 // Do nothing
104 }
105
106 //==============================================================================
solve()107 bool GradientDescentSolver::solve()
108 {
109 bool minimized = false;
110 bool satisfied = false;
111
112 std::shared_ptr<Problem> problem = mProperties.mProblem;
113 if (nullptr == problem)
114 {
115 dtwarn << "[GradientDescentSolver::solve] Attempting to solve a nullptr "
116 << "problem! We will return false.\n";
117 return false;
118 }
119
120 double tol = std::abs(mProperties.mTolerance);
121 double gamma = mGradientP.mStepSize;
122 std::size_t dim = problem->getDimension();
123
124 if (dim == 0)
125 {
126 problem->setOptimalSolution(Eigen::VectorXd());
127 problem->setOptimumValue(0.0);
128 return true;
129 }
130
131 Eigen::VectorXd x = problem->getInitialGuess();
132 assert(x.size() == static_cast<int>(dim));
133
134 Eigen::VectorXd lastx = x;
135 Eigen::VectorXd dx(x.size());
136 Eigen::VectorXd grad(x.size());
137
138 mEqConstraintCostCache.resize(problem->getNumEqConstraints());
139 mIneqConstraintCostCache.resize(problem->getNumIneqConstraints());
140
141 mLastNumIterations = 0;
142 std::size_t attemptCount = 0;
143 do
144 {
145 std::size_t stepCount = 0;
146 do
147 {
148 ++mLastNumIterations;
149
150 // Perturb the configuration if we have reached an iteration where we are
151 // supposed to perturb it.
152 if (mGradientP.mPerturbationStep > 0 && stepCount > 0
153 && stepCount % mGradientP.mPerturbationStep == 0)
154 {
155 dx = x; // Seed the configuration randomizer with the current
156 // configuration
157 randomizeConfiguration(dx);
158
159 // Step the current configuration towards the randomized configuration
160 // proportionally to a randomized scaling factor
161 double scale = mGradientP.mMaxPerturbationFactor * mDistribution(mMT);
162 x += scale * (dx - x);
163 }
164
165 // Check if the equality constraints are satsified
166 satisfied = true;
167 for (std::size_t i = 0; i < problem->getNumEqConstraints(); ++i)
168 {
169 mEqConstraintCostCache[i] = problem->getEqConstraint(i)->eval(x);
170 if (std::abs(mEqConstraintCostCache[i]) > tol)
171 satisfied = false;
172 }
173
174 // Check if the inequality constraints are satisfied
175 for (std::size_t i = 0; i < problem->getNumIneqConstraints(); ++i)
176 {
177 mIneqConstraintCostCache[i] = problem->getIneqConstraint(i)->eval(x);
178 if (mIneqConstraintCostCache[i] > std::abs(tol))
179 satisfied = false;
180 }
181
182 dx.setZero();
183 Eigen::Map<Eigen::VectorXd> dxMap(dx.data(), dim);
184 Eigen::Map<Eigen::VectorXd> gradMap(grad.data(), dim);
185 // Compute the gradient of the objective, combined with the weighted
186 // gradients of the softened constraints
187 const FunctionPtr& objective = problem->getObjective();
188 if (objective)
189 objective->evalGradient(x, dxMap);
190 for (int i = 0; i < static_cast<int>(problem->getNumEqConstraints()); ++i)
191 {
192 if (std::abs(mEqConstraintCostCache[i]) < tol)
193 continue;
194
195 problem->getEqConstraint(i)->evalGradient(x, gradMap);
196
197 // Get the user-specified weight if available, otherwise use the default
198 // weight value
199 double weight = mGradientP.mEqConstraintWeights.size() > i
200 ? mGradientP.mEqConstraintWeights[i]
201 : mGradientP.mDefaultConstraintWeight;
202
203 // We treat the constraint function as though we are minimizing its
204 // absolute value. We do not want to treat it as though we are
205 // minimizing its square, because that could adversely affect the
206 // curvature of its derivative.
207 dx += weight * grad * math::sign(mEqConstraintCostCache[i]);
208 }
209
210 for (int i = 0; i < static_cast<int>(problem->getNumIneqConstraints());
211 ++i)
212 {
213 if (mIneqConstraintCostCache[i] < tol)
214 continue;
215
216 problem->getIneqConstraint(i)->evalGradient(x, gradMap);
217
218 // Get the user-specified weight if available, otherwise use the
219 // default weight value
220 double weight = mGradientP.mIneqConstraintWeights.size() > i
221 ? mGradientP.mIneqConstraintWeights[i]
222 : mGradientP.mDefaultConstraintWeight;
223
224 dx += weight * grad;
225 }
226
227 x -= gamma * dx;
228 clampToBoundary(x);
229
230 if ((x - lastx).norm() < tol)
231 minimized = true;
232 else
233 minimized = false;
234
235 lastx = x;
236 ++stepCount;
237
238 if (nullptr != mProperties.mOutStream
239 && mProperties.mIterationsPerPrint > 0
240 && stepCount % mProperties.mIterationsPerPrint == 0)
241 {
242 *mProperties.mOutStream
243 << "[GradientDescentSolver] Progress (attempt #" << attemptCount
244 << " | iteration #" << stepCount << ")\n"
245 << "cost: " << problem->getObjective()->eval(x) << " | "
246 << (minimized ? "minimized | " : "not minimized | ")
247 << (satisfied ? "constraints satisfied | "
248 : "constraints unsatisfied | ")
249 << "x: " << x.transpose() << "\n"
250 << "grad: " << dx.transpose() << std::endl;
251 }
252
253 if (stepCount > mProperties.mNumMaxIterations)
254 break;
255
256 } while (!minimized || !satisfied);
257
258 if (!minimized || !satisfied)
259 {
260 ++attemptCount;
261
262 if (mGradientP.mMaxAttempts > 0
263 && attemptCount >= mGradientP.mMaxAttempts)
264 break;
265
266 if (attemptCount - 1 < problem->getSeeds().size())
267 {
268 x = problem->getSeed(attemptCount - 1);
269 }
270 else
271 {
272 randomizeConfiguration(x);
273 }
274 }
275
276 } while (!minimized || !satisfied);
277
278 mLastConfig = x;
279 problem->setOptimalSolution(x);
280 if (problem->getObjective())
281 problem->setOptimumValue(problem->getObjective()->eval(x));
282 else
283 problem->setOptimumValue(0.0);
284
285 return minimized && satisfied;
286 }
287
288 //==============================================================================
getLastConfiguration() const289 Eigen::VectorXd GradientDescentSolver::getLastConfiguration() const
290 {
291 return mLastConfig;
292 }
293
294 //==============================================================================
getType() const295 std::string GradientDescentSolver::getType() const
296 {
297 return Type;
298 }
299
300 //==============================================================================
clone() const301 std::shared_ptr<Solver> GradientDescentSolver::clone() const
302 {
303 return std::make_shared<GradientDescentSolver>(
304 getGradientDescentProperties());
305 }
306
307 //==============================================================================
setProperties(const Properties & _properties)308 void GradientDescentSolver::setProperties(const Properties& _properties)
309 {
310 Solver::setProperties(_properties);
311 setProperties(static_cast<const UniqueProperties&>(_properties));
312 }
313
314 //==============================================================================
setProperties(const UniqueProperties & _properties)315 void GradientDescentSolver::setProperties(const UniqueProperties& _properties)
316 {
317 setStepSize(_properties.mStepSize);
318 setMaxAttempts(_properties.mMaxAttempts);
319 setPerturbationStep(_properties.mPerturbationStep);
320 setMaxPerturbationFactor(_properties.mMaxPerturbationFactor);
321 setDefaultConstraintWeight(_properties.mDefaultConstraintWeight);
322 getEqConstraintWeights() = _properties.mEqConstraintWeights;
323 }
324
325 //==============================================================================
326 GradientDescentSolver::Properties
getGradientDescentProperties() const327 GradientDescentSolver::getGradientDescentProperties() const
328 {
329 return GradientDescentSolver::Properties(getSolverProperties(), mGradientP);
330 }
331
332 //==============================================================================
copy(const GradientDescentSolver & _other)333 void GradientDescentSolver::copy(const GradientDescentSolver& _other)
334 {
335 if (this == &_other)
336 return;
337
338 setProperties(_other.getGradientDescentProperties());
339 }
340
341 //==============================================================================
operator =(const GradientDescentSolver & _other)342 GradientDescentSolver& GradientDescentSolver::operator=(
343 const GradientDescentSolver& _other)
344 {
345 copy(_other);
346 return *this;
347 }
348
349 //==============================================================================
setStepSize(double _newMultiplier)350 void GradientDescentSolver::setStepSize(double _newMultiplier)
351 {
352 mGradientP.mStepSize = _newMultiplier;
353 }
354
355 //==============================================================================
getStepSize() const356 double GradientDescentSolver::getStepSize() const
357 {
358 return mGradientP.mStepSize;
359 }
360
361 //==============================================================================
setMaxAttempts(std::size_t _maxAttempts)362 void GradientDescentSolver::setMaxAttempts(std::size_t _maxAttempts)
363 {
364 mGradientP.mMaxAttempts = _maxAttempts;
365 }
366
367 //==============================================================================
getMaxAttempts() const368 std::size_t GradientDescentSolver::getMaxAttempts() const
369 {
370 return mGradientP.mMaxAttempts;
371 }
372
373 //==============================================================================
setPerturbationStep(std::size_t _step)374 void GradientDescentSolver::setPerturbationStep(std::size_t _step)
375 {
376 mGradientP.mPerturbationStep = _step;
377 }
378
379 //==============================================================================
getPerturbationStep() const380 std::size_t GradientDescentSolver::getPerturbationStep() const
381 {
382 return mGradientP.mPerturbationStep;
383 }
384
385 //==============================================================================
setMaxPerturbationFactor(double _factor)386 void GradientDescentSolver::setMaxPerturbationFactor(double _factor)
387 {
388 mGradientP.mMaxPerturbationFactor = _factor;
389 }
390
391 //==============================================================================
getMaxPerturbationFactor() const392 double GradientDescentSolver::getMaxPerturbationFactor() const
393 {
394 return mGradientP.mMaxPerturbationFactor;
395 }
396
397 //==============================================================================
setDefaultConstraintWeight(double _newDefault)398 void GradientDescentSolver::setDefaultConstraintWeight(double _newDefault)
399 {
400 mGradientP.mDefaultConstraintWeight = _newDefault;
401 }
402
403 //==============================================================================
getDefaultConstraintWeight() const404 double GradientDescentSolver::getDefaultConstraintWeight() const
405 {
406 return mGradientP.mDefaultConstraintWeight;
407 }
408
409 //==============================================================================
getEqConstraintWeights()410 Eigen::VectorXd& GradientDescentSolver::getEqConstraintWeights()
411 {
412 return mGradientP.mEqConstraintWeights;
413 }
414
415 //==============================================================================
getEqConstraintWeights() const416 const Eigen::VectorXd& GradientDescentSolver::getEqConstraintWeights() const
417 {
418 return mGradientP.mEqConstraintWeights;
419 }
420
421 //==============================================================================
getIneqConstraintWeights()422 Eigen::VectorXd& GradientDescentSolver::getIneqConstraintWeights()
423 {
424 return mGradientP.mIneqConstraintWeights;
425 }
426
427 //==============================================================================
getIneqConstraintWeights() const428 const Eigen::VectorXd& GradientDescentSolver::getIneqConstraintWeights() const
429 {
430 return mGradientP.mIneqConstraintWeights;
431 }
432
433 //==============================================================================
randomizeConfiguration(Eigen::VectorXd & _x)434 void GradientDescentSolver::randomizeConfiguration(Eigen::VectorXd& _x)
435 {
436 if (nullptr == mProperties.mProblem)
437 return;
438
439 if (_x.size() < static_cast<int>(mProperties.mProblem->getDimension()))
440 _x = Eigen::VectorXd::Zero(mProperties.mProblem->getDimension());
441
442 for (int i = 0; i < _x.size(); ++i)
443 {
444 double lower = mProperties.mProblem->getLowerBounds()[i];
445 double upper = mProperties.mProblem->getUpperBounds()[i];
446 double step = upper - lower;
447 if (step > mGradientP.mMaxRandomizationStep)
448 {
449 step = 2 * mGradientP.mMaxRandomizationStep;
450 lower = _x[i] - step / 2.0;
451 }
452
453 _x[i] = step * mDistribution(mMT) + lower;
454 }
455 }
456
457 //==============================================================================
clampToBoundary(Eigen::VectorXd & _x)458 void GradientDescentSolver::clampToBoundary(Eigen::VectorXd& _x)
459 {
460 if (nullptr == mProperties.mProblem)
461 return;
462
463 if (_x.size() != static_cast<int>(mProperties.mProblem->getDimension()))
464 {
465 dterr << "[GradientDescentSolver::clampToBoundary] Mismatch between "
466 << "configuration size [" << _x.size() << "] and the dimension of "
467 << "the Problem [" << mProperties.mProblem->getDimension() << "]\n";
468 assert(false);
469 return;
470 }
471
472 assert(mProperties.mProblem->getLowerBounds().size() == _x.size());
473 assert(mProperties.mProblem->getUpperBounds().size() == _x.size());
474
475 for (int i = 0; i < _x.size(); ++i)
476 {
477 _x[i] = math::clip(
478 _x[i],
479 mProperties.mProblem->getLowerBounds()[i],
480 mProperties.mProblem->getUpperBounds()[i]);
481 }
482 }
483
484 //==============================================================================
getLastNumIterations() const485 std::size_t GradientDescentSolver::getLastNumIterations() const
486 {
487 return mLastNumIterations;
488 }
489
490 } // namespace optimizer
491 } // namespace dart
492