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 
33 bool LevenbergMarquardtSparse::initialize(OptimizationProblemInterface* problem)
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
57  _obj_dim = problem.getLsqObjectiveDimension();
58  _eq_dim = problem.getEqualityDimension();
59  _ineq_dim = problem.getInequalityDimension();
60  _finite_bounds_dim = problem.finiteCombinedBoundsDimension();
62  _param_dim = problem.getParameterDimension();
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
70  int nnz = problem.computeSparseJacobianLsqObjectiveNNZ() + problem.computeSparseJacobianEqualitiesNNZ() +
71  problem.computeSparseJacobianInequalitiesNNZ() + problem.computeSparseJacobianFiniteCombinedBoundsNNZ();
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
97  _hessian = _jacobian.transpose() * _jacobian;
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
188  _hessian = _jacobian.transpose() * _jacobian;
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 
222 void LevenbergMarquardtSparse::computeValues(OptimizationProblemInterface& problem)
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  {
238  problem.computeValuesActiveInequality(_values.segment(idx, _ineq_dim), _weight_ineq);
239  idx += _ineq_dim;
240  }
241  if (_finite_bounds_dim > 0)
242  {
243  problem.computeDistanceFiniteCombinedBounds(_values.segment(idx, _finite_bounds_dim));
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
Eigen::SparseMatrix::resize
void resize(Index rows, Index cols)
Definition: SparseMatrix.h:621
corbo::LevenbergMarquardtSparse::_weight_adapt_factor_eq
double _weight_adapt_factor_eq
Definition: levenberg_marquardt_sparse.h:162
sqrt
const EIGEN_DEVICE_FUNC SqrtReturnType sqrt() const
Definition: ArrayCwiseUnaryOps.h:152
corbo::LevenbergMarquardtSparse::computeValues
void computeValues(OptimizationProblemInterface &problem)
Compute overall value vector including constraint approximation.
Definition: levenberg_marquardt_sparse.cpp:244
levenberg_marquardt_sparse.h
corbo::LevenbergMarquardtSparse::_weight_adapt_factor_ineq
double _weight_adapt_factor_ineq
Definition: levenberg_marquardt_sparse.h:163
corbo::LevenbergMarquardtSparse::clear
void clear() override
Clear internal caches.
Definition: levenberg_marquardt_sparse.cpp:311
alpha
RealScalar alpha
Definition: level1_cplx_impl.h:125
Eigen::half_impl::pow
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC half pow(const half &a, const half &b)
Definition: Half.h:477
corbo::LevenbergMarquardtSparse::_ineq_dim
int _ineq_dim
Definition: levenberg_marquardt_sparse.h:174
corbo
Definition: communication/include/corbo-communication/utilities.h:37
corbo::SolverStatus::Converged
@ Converged
corbo::LevenbergMarquardtSparse::resetWeights
void resetWeights()
Reset weights to their original values.
Definition: levenberg_marquardt_sparse.cpp:292
Eigen::SimplicialLLT::analyzePattern
void analyzePattern(const MatrixType &a)
Definition: SimplicialCholesky.h:376
Eigen::half_impl::isinf
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC bool() isinf(const half &a)
Definition: Half.h:431
corbo::LevenbergMarquardtSparse::_weight_eq
double _weight_eq
Definition: levenberg_marquardt_sparse.h:184
console.h
corbo::SolverStatus
SolverStatus
Definition: optimization/include/corbo-optimization/types.h:52
corbo::SolverStatus::EarlyTerminated
@ EarlyTerminated
corbo::LevenbergMarquardtSparse::_jacobian
Eigen::SparseMatrix< double > _jacobian
Definition: levenberg_marquardt_sparse.h:179
corbo::LevenbergMarquardtSparse::adaptWeights
void adaptWeights()
Perform single weight adapation step.
Definition: levenberg_marquardt_sparse.cpp:299
corbo::SolverStatus::Error
@ Error
corbo::LevenbergMarquardtSparse::_param_dim
int _param_dim
Definition: levenberg_marquardt_sparse.h:171
Eigen::SparseMatrix::diagonal
const ConstDiagonalReturnType diagonal() const
Definition: SparseMatrix.h:650
corbo::LevenbergMarquardtSparse::_delta
Eigen::VectorXd _delta
Definition: levenberg_marquardt_sparse.h:181
corbo::LevenbergMarquardtSparse::setPenaltyWeights
void setPenaltyWeights(double weight_eq, double weight_ineq, double weight_bounds)
Define penalty weights (equality constraints, inequality constraints, bounds)
Definition: levenberg_marquardt_sparse.cpp:270
corbo::LevenbergMarquardtSparse::_rhs
Eigen::VectorXd _rhs
Definition: levenberg_marquardt_sparse.h:182
corbo::LevenbergMarquardtSparse::_weight_adapt_max_bounds
double _weight_adapt_max_bounds
Definition: levenberg_marquardt_sparse.h:168
PRINT_WARNING_COND
#define PRINT_WARNING_COND(cond, msg)
Print msg-stream only if cond == true.
Definition: console.h:159
corbo::LevenbergMarquardtSparse::_weight_adapt_factor_bounds
double _weight_adapt_factor_bounds
Definition: levenberg_marquardt_sparse.h:164
corbo::LevenbergMarquardtSparse::_weight_adapt_max_eq
double _weight_adapt_max_eq
Definition: levenberg_marquardt_sparse.h:166
corbo::LevenbergMarquardtSparse::initialize
bool initialize(OptimizationProblemInterface *problem=nullptr) override
Initialize the solver w.r.t. a given optimization problem.
Definition: levenberg_marquardt_sparse.cpp:55
corbo::LevenbergMarquardtSparse::_weight_adapt_max_ineq
double _weight_adapt_max_ineq
Definition: levenberg_marquardt_sparse.h:167
Eigen::SparseMatrix::reserve
void reserve(Index reserveSize)
Definition: SparseMatrix.h:262
corbo::LevenbergMarquardtSparse::_weight_init_ineq
double _weight_init_ineq
Definition: levenberg_marquardt_sparse.h:159
corbo::LevenbergMarquardtSparse::solve
SolverStatus solve(OptimizationProblemInterface &problem, bool new_structure=true, bool new_run=true, double *obj_value=nullptr) override
Solve the provided optimization problem.
Definition: levenberg_marquardt_sparse.cpp:66
corbo::LevenbergMarquardtSparse::_iterations
int _iterations
Definition: levenberg_marquardt_sparse.h:156
corbo::LevenbergMarquardtSparse::setWeightAdapation
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...
Definition: levenberg_marquardt_sparse.cpp:281
corbo::LevenbergMarquardtSparse::_values
Eigen::VectorXd _values
Definition: levenberg_marquardt_sparse.h:178
Eigen::SparseMatrix::setZero
void setZero()
Definition: SparseMatrix.h:251
min
#define min(a, b)
Definition: datatypes.h:19
corbo::LevenbergMarquardtSparse::_obj_dim
int _obj_dim
Definition: levenberg_marquardt_sparse.h:172
corbo::LevenbergMarquardtSparse::_eq_dim
int _eq_dim
Definition: levenberg_marquardt_sparse.h:173
corbo::LevenbergMarquardtSparse::_finite_bounds_dim
int _finite_bounds_dim
Definition: levenberg_marquardt_sparse.h:175
corbo::LevenbergMarquardtSparse::_val_dim
int _val_dim
Definition: levenberg_marquardt_sparse.h:176
corbo::LevenbergMarquardtSparse::_sparse_solver
Eigen::SimplicialLLT< Eigen::SparseMatrix< double >, Eigen::Upper > _sparse_solver
Eigens sparse solver wrapper. Check http://eigen.tuxfamily.org/dox/group__TopicSparseSystems....
Definition: levenberg_marquardt_sparse.h:199
corbo::LevenbergMarquardtSparse::_weight_bounds
double _weight_bounds
Definition: levenberg_marquardt_sparse.h:186
Eigen::SparseMatrix::coeffRef
Scalar & coeffRef(Index row, Index col)
Definition: SparseMatrix.h:206
max
#define max(a, b)
Definition: datatypes.h:20
corbo::LevenbergMarquardtSparse::_hessian
Eigen::SparseMatrix< double > _hessian
Definition: levenberg_marquardt_sparse.h:180
Eigen::Infinity
const int Infinity
Definition: Constants.h:31
corbo::LevenbergMarquardtSparse::_weight_ineq
double _weight_ineq
Definition: levenberg_marquardt_sparse.h:185
Eigen::SimplicialLLT::factorize
void factorize(const MatrixType &a)
Definition: SimplicialCholesky.h:387
PRINT_ERROR
#define PRINT_ERROR(msg)
Print msg-stream as error msg.
Definition: console.h:173
Eigen::half_impl::isnan
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC bool() isnan(const half &a)
Definition: Half.h:434
corbo::LevenbergMarquardtSparse::_weight_init_eq
double _weight_init_eq
Definition: levenberg_marquardt_sparse.h:158
corbo::LevenbergMarquardtSparse::_weight_init_bounds
double _weight_init_bounds
Definition: levenberg_marquardt_sparse.h:160


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Wed Mar 2 2022 00:05:52