levenberg_marquardt_sparse.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement
4  *
5  * Copyright (c) 2020,
6  * TU Dortmund - Institute of Control Theory and Systems Engineering.
7  * All rights reserved.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  *
22  * Authors: Christoph Rösmann
23  *********************************************************************/
24 
26 
27 #include <corbo-core/console.h>
28 
29 #include <Eigen/Cholesky>
30 
31 namespace corbo {
32 
34 {
35  if (problem && !problem->isLeastSquaresProblem())
36  {
37  PRINT_ERROR("LevenbergMarquardtSparse(): cannot handle non-least-squares objectives or LS objectives in non-LS form.");
38  return false;
39  }
40 
41  return true;
42 }
43 
44 SolverStatus LevenbergMarquardtSparse::solve(OptimizationProblemInterface& problem, bool new_structure, bool new_run, double* obj_value)
45 {
46  if (obj_value) *obj_value = -1; // set to invalid first
47 
48  if (new_structure)
49  {
50  if (!problem.isLeastSquaresProblem())
51  {
52  PRINT_ERROR("LevenbergMarquardtSparse(): cannot handle non-least-squares objectives or LS objectives in non-LS form.");
53  return SolverStatus::Error;
54  }
55 
56  // get dimension for the value/cost vector
58  _eq_dim = problem.getEqualityDimension();
63  _values.resize(_val_dim);
64 
65  _delta.resize(_param_dim);
66  _rhs.resize(_param_dim);
67 
68  // initialize sparse jacobian
69  // int count potential non-zeros of the Jacobian
72 
75  // _jacobian.makeCompressed(); // TODO(roesmann): required?
76  _jacobian.reserve(nnz);
77 
78  // hessian
80  }
81 
82  // adapt weights
83  if (new_run)
84  resetWeights();
85  else
86  adaptWeights();
87 
88  // compute complete value vector
89  computeValues(problem);
90 
91  // compute jacobian
92  problem.computeCombinedSparseJacobian(_jacobian, true, true, true, true, true, _weight_eq, _weight_ineq, _weight_bounds, &_values);
93 
94  // construct quasi-newton hessian approximation
95  // hessian.selfadjointView<Eigen::Upper>() = (_jacobian.transpose() * _jacobian).selfadjointView<Eigen::Upper>(); // just copy upper
96  // triangular part, but is the last cast necessary? it does not compile without it
98 
99  // construct right-hand-side of the approxiamted linear system
100  _rhs.noalias() = _jacobian.transpose() * -_values;
101 
102  // convergence consts
103  constexpr const double eps1 = 1e-5;
104  constexpr const double eps2 = 1e-5;
105  constexpr const double eps3 = 1e-5;
106  constexpr const double eps4 = 0;
107 
108  // lm variables
109  unsigned int v = 2;
110  double tau = 1e-5;
111 
112  constexpr const double goodStepUpperScale = 2. / 3.;
113  constexpr const double goodStepLowerScale = 1. / 3.;
114 
115  bool stop = (_rhs.lpNorm<Eigen::Infinity>() <= eps1);
116 
117  double mu = tau * _hessian.diagonal().maxCoeff();
118  if (mu < 0) mu = 0;
119 
120  double rho = 0;
121 
122  bool analyze_pattern = new_structure;
123 
124  // get old chi2
125  double chi2_old = _values.squaredNorm();
126  if (obj_value) *obj_value = chi2_old;
127 
128  // start levenberg marquardt optimization loop
129  for (int k = 0; k < _iterations; ++k)
130  {
131  do
132  {
133  // augment hessian diagonal with damping factor
134  // iterate diagonal elements
135  for (int i = 0; i < _param_dim; ++i)
136  {
137  _hessian.coeffRef(i, i) += mu;
138  }
139  // solve linear system
140  if (analyze_pattern)
141  {
142  // calc structural (non-zero) pattern only if graph (structure) was changed
144  analyze_pattern = false;
145  }
146 
147  _sparse_solver.factorize(_hessian); // calculate factors with actual values
148  _delta = _sparse_solver.solve(_rhs); // solve linear system
149 
150  // if (delta.norm() <= eps2 * (x.norm() + eps2) stop = true; // x -> values of vertices, not constructed until now. modify check
151  if (_delta.norm() <= eps2)
152  {
153  stop = true;
154  }
155  else
156  {
157  // backup current parameter values
158  problem.backupParameters();
159 
160  // apply new delta/increment to vertices
161  problem.applyIncrement(_delta);
162 
163  // calculate new costs/values
164  computeValues(problem);
165 
166  // get new chi2
167  double chi2_new = _values.squaredNorm();
168 
169  rho = (chi2_old - chi2_new) / (_delta.transpose() * (mu * _delta + _rhs));
170 
171  if (rho > 0 && !std::isnan(chi2_new) && !std::isinf(chi2_new)) // adaptation of mu to find a sufficient descent step
172  {
173  stop = (std::sqrt(chi2_old) - std::sqrt(chi2_new) < eps4 * std::sqrt(chi2_old));
174 
175  // accept update and discard backup
176  problem.discardBackupParameters();
177 
178  if (!stop && k < _iterations - 1) // do not recalculate jacobian and hessian in the last iteration
179  {
180  // get new cost/value vector
181  // computeValues(problem); // redundant
182 
183  // calculate new jacobian
184  problem.computeCombinedSparseJacobian(_jacobian, true, true, true, true, true, _weight_eq, _weight_ineq, _weight_bounds,
185  &_values);
186 
187  // get new hessian
189 
190  // calculate new right hand side
191  _rhs.noalias() = _jacobian.transpose() * -_values;
192 
193  stop = stop || (_rhs.lpNorm<Eigen::Infinity>() <= eps1);
194 
195  double alpha = std::min(goodStepUpperScale, 1 - std::pow((2 * rho - 1), 3));
196  double scaleFactor = std::max(goodStepLowerScale, alpha);
197  mu *= scaleFactor;
198  v = 2;
199  }
200 
201  chi2_old = chi2_new;
202  if (obj_value) *obj_value = chi2_old;
203  }
204  else
205  {
206  // restore from backup
207  problem.restoreBackupParameters(false);
208  // hessian.diagonal().array() -= mu; // this should be done here, but it works in the matlab version without for some
209  // time now.
210 
211  mu = mu * v;
212  v = 2 * v;
213  }
214  }
215  } while (rho <= 0 && !stop);
216  stop = (_values.norm() <= eps3);
217  }
218  return (stop || rho <= 0) ? SolverStatus::Converged
219  : SolverStatus::EarlyTerminated; // TODO(roesmann): proper solver status (especially w.r.t. rho)
220 }
221 
223 {
224  int idx = 0;
225  if (_obj_dim > 0)
226  {
227  problem.computeValuesLsqObjective(_values.segment(idx, _obj_dim));
228  idx += _obj_dim;
229  }
230  if (_eq_dim > 0)
231  {
232  problem.computeValuesEquality(_values.segment(idx, _eq_dim));
233  _values.segment(idx, _eq_dim) *= _weight_eq;
234  idx += _eq_dim;
235  }
236  if (_ineq_dim > 0)
237  {
239  idx += _ineq_dim;
240  }
241  if (_finite_bounds_dim > 0)
242  {
244  _values.segment(idx, _finite_bounds_dim) *= _weight_bounds;
245  }
246 }
247 
248 void LevenbergMarquardtSparse::setPenaltyWeights(double weight_eq, double weight_ineq, double weight_bounds)
249 {
250  _weight_init_eq = weight_eq;
251  _weight_init_ineq = weight_ineq;
252  _weight_init_bounds = weight_bounds;
253 
254  PRINT_WARNING_COND(_weight_init_eq > _weight_adapt_max_eq, "LevenbergMarquardtSparse(): weight_eq > max_eq");
255  PRINT_WARNING_COND(_weight_init_ineq > _weight_adapt_max_ineq, "LevenbergMarquardtSparse(): weight_ineq > max_ineq");
256  PRINT_WARNING_COND(_weight_init_bounds > _weight_adapt_max_bounds, "LevenbergMarquardtSparse(): weight_bounds > max_bounds");
257 }
258 
259 void LevenbergMarquardtSparse::setWeightAdapation(double factor_eq, double factor_ineq, double factor_bounds, double max_eq, double max_ineq,
260  double max_bounds)
261 {
262  _weight_adapt_factor_eq = factor_eq;
263  _weight_adapt_factor_ineq = factor_ineq;
264  _weight_adapt_factor_bounds = factor_bounds;
265  _weight_adapt_max_eq = max_eq;
266  _weight_adapt_max_ineq = max_ineq;
267  _weight_adapt_max_bounds = max_bounds;
268 }
269 
271 {
275 }
276 
278 {
281 
284 
287 }
288 
290 {
291  _param_dim = 0;
292  _obj_dim = 0;
293  _eq_dim = 0;
294  _ineq_dim = 0;
295  _finite_bounds_dim = 0;
296  _val_dim = 0;
297 
301 }
302 
303 #ifdef MESSAGE_SUPPORT
304 void LevenbergMarquardtSparse::toMessage(corbo::messages::NlpSolver& message) const
305 {
306  message.mutable_levenberg_marquardt_sparse()->set_iterations(_iterations);
307  message.mutable_levenberg_marquardt_sparse()->set_weight_eq(_weight_init_eq);
308  message.mutable_levenberg_marquardt_sparse()->set_weight_ineq(_weight_init_ineq);
309  message.mutable_levenberg_marquardt_sparse()->set_weight_bounds(_weight_init_bounds);
310  message.mutable_levenberg_marquardt_sparse()->set_weight_eq_factor(_weight_adapt_factor_eq);
311  message.mutable_levenberg_marquardt_sparse()->set_weight_ineq_factor(_weight_adapt_factor_ineq);
312  message.mutable_levenberg_marquardt_sparse()->set_weight_bounds_factor(_weight_adapt_factor_bounds);
313  message.mutable_levenberg_marquardt_sparse()->set_weight_eq_max(_weight_adapt_max_eq);
314  message.mutable_levenberg_marquardt_sparse()->set_weight_ineq_max(_weight_adapt_max_ineq);
315  message.mutable_levenberg_marquardt_sparse()->set_weight_bounds_max(_weight_adapt_max_bounds);
316 }
317 
318 void LevenbergMarquardtSparse::fromMessage(const corbo::messages::NlpSolver& message, std::stringstream* issues)
319 {
320  _iterations = message.levenberg_marquardt_sparse().iterations();
321  _weight_init_eq = message.levenberg_marquardt_sparse().weight_eq();
322  _weight_init_ineq = message.levenberg_marquardt_sparse().weight_ineq();
323  _weight_init_bounds = message.levenberg_marquardt_sparse().weight_bounds();
324  _weight_adapt_factor_eq = message.levenberg_marquardt_sparse().weight_eq_factor();
325  _weight_adapt_factor_ineq = message.levenberg_marquardt_sparse().weight_ineq_factor();
326  _weight_adapt_factor_bounds = message.levenberg_marquardt_sparse().weight_bounds_factor();
327  _weight_adapt_max_eq = message.levenberg_marquardt_sparse().weight_eq_max();
328  _weight_adapt_max_ineq = message.levenberg_marquardt_sparse().weight_ineq_max();
329  _weight_adapt_max_bounds = message.levenberg_marquardt_sparse().weight_bounds_max();
330 }
331 #endif
332 
333 } // namespace corbo
virtual int finiteCombinedBoundsDimension()
Dimension of the set of finite bounds (combined such that each ub and lb component define a single di...
#define max(a, b)
Definition: datatypes.h:20
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC half pow(const half &a, const half &b)
Definition: Half.h:477
virtual void backupParameters()=0
Restore parameter set from the last backup and keep backup if desired.
const ConstDiagonalReturnType diagonal() const
Definition: SparseMatrix.h:650
Eigen::SparseMatrix< double > _hessian
virtual int getInequalityDimension()=0
Total dimension of general inequality constraints.
#define min(a, b)
Definition: datatypes.h:19
void resize(Index rows, Index cols)
Definition: SparseMatrix.h:621
const Solve< Derived, Rhs > solve(const MatrixBase< Rhs > &b) const
virtual void computeDistanceFiniteCombinedBounds(Eigen::Ref< Eigen::VectorXd > values)
Compute the distance to finite bound values (combined lower and upper)
EIGEN_DEVICE_FUNC const SqrtReturnType sqrt() const
void resetWeights()
Reset weights to their original values.
virtual void restoreBackupParameters(bool keep_backup)=0
Discard last backup (or all)
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC bool() isinf(const half &a)
Definition: Half.h:431
virtual int getEqualityDimension()=0
Total dimension of equality constraints.
Eigen::SimplicialLLT< Eigen::SparseMatrix< double >, Eigen::Upper > _sparse_solver
Eigens sparse solver wrapper. Check http://eigen.tuxfamily.org/dox/group__TopicSparseSystems.html for further information and different solvers. The second template parameter specifies, whether the upper or lower triangular part should be used. If CHOLMOD was found by CMake, the cholmod supernodal llt version is used rathar than Eigens simplicial ldlt. Define FORCE_EIGEN_SOLVER if the default LDLT should sill be used even if CHOLMOD is found.
void factorize(const MatrixType &a)
virtual void applyIncrement(const Eigen::Ref< const Eigen::VectorXd > &increment)
Apply increment to the current parameter set.
virtual int getLsqObjectiveDimension()=0
Total dimension of least-squares objective function terms.
void setWeightAdapation(double factor_eq, double factor_ineq, double factor_bounds, double max_eq, double max_ineq, double max_bounds)
Set parameters for weight adaptation (refer to the class description); set factors to 1 in order to d...
TransposeReturnType transpose()
virtual void computeValuesActiveInequality(Eigen::Ref< Eigen::VectorXd > values, double weight=1.0)
Compute the values of the active inequality constraints (elementwise max(0, c(x))) ...
Generic interface for optimization problem definitions.
void clear() override
Clear internal caches.
void adaptWeights()
Perform single weight adapation step.
virtual void computeValuesEquality(Eigen::Ref< Eigen::VectorXd > values)=0
Compute the equality constraint values ceq(x) for the current parameter set.
RealScalar alpha
#define PRINT_WARNING_COND(cond, msg)
Print msg-stream only if cond == true.
Definition: console.h:159
Scalar & coeffRef(Index row, Index col)
Definition: SparseMatrix.h:206
void computeValues(OptimizationProblemInterface &problem)
Compute overall value vector including constraint approximation.
virtual bool isLeastSquaresProblem() const =0
Check if the underlying problem is defined in the least squares form.
virtual void computeValuesLsqObjective(Eigen::Ref< Eigen::VectorXd > values)=0
Compute the objective function values f(x) for the current parameter set.
virtual void discardBackupParameters(bool all=false)=0
void reserve(Index reserveSize)
Definition: SparseMatrix.h:262
virtual int getParameterDimension()=0
Effictive dimension of the optimization parameter set (changeable, non-fixed part) ...
void analyzePattern(const MatrixType &a)
Eigen::SparseMatrix< double > _jacobian
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC bool() isnan(const half &a)
Definition: Half.h:434
SolverStatus solve(OptimizationProblemInterface &problem, bool new_structure=true, bool new_run=true, double *obj_value=nullptr) override
Solve the provided optimization problem.
void setPenaltyWeights(double weight_eq, double weight_ineq, double weight_bounds)
Define penalty weights (equality constraints, inequality constraints, bounds)
const int Infinity
Definition: Constants.h:31
bool initialize(OptimizationProblemInterface *problem=nullptr) override
Initialize the solver w.r.t. a given optimization problem.
#define PRINT_ERROR(msg)
Print msg-stream as error msg.
Definition: console.h:173
virtual void computeCombinedSparseJacobian(Eigen::SparseMatrix< double > &jacobian, bool objective_lsq, bool equality, bool inequality, bool finite_combined_bounds, bool active_ineq=false, double weight_eq=1.0, double weight_ineq=1.0, double weight_bounds=1.0, const Eigen::VectorXd *values=nullptr, const Eigen::VectorXi *col_nnz=nullptr)


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Mon Feb 28 2022 22:06:59