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