29#include <gtsam/nonlinear/GncParams.h>
31#include <boost/math/distributions/chi_squared.hpp>
38static double Chi2inv(
const double alpha,
const size_t dofs) {
39 boost::math::chi_squared_distribution<double> chi2(dofs);
40 return boost::math::quantile(chi2, alpha);
44template<
class GncParameters>
53 GncParameters params_;
60 const GncParameters& params = GncParameters())
61 : state_(initialValues),
66 for (
size_t i = 0; i < graph.size(); i++) {
70 auto robust = boost::dynamic_pointer_cast<
73 nfg_[i] = robust ? factor-> cloneWithNewNoiseModel(robust->noise()) : factor;
78 std::vector<size_t> inconsistentlySpecifiedWeights;
80 std::set_intersection(params.knownInliers.begin(),params.knownInliers.end(),
81 params.knownOutliers.begin(),params.knownOutliers.end(),
82 std::inserter(inconsistentlySpecifiedWeights, inconsistentlySpecifiedWeights.begin()));
83 if(inconsistentlySpecifiedWeights.size() > 0){
84 params.print(
"params\n");
85 throw std::runtime_error(
"GncOptimizer::constructor: the user has selected one or more measurements"
86 " to be BOTH a known inlier and a known outlier.");
89 for (
size_t i = 0; i < params.knownInliers.size(); i++){
90 if( params.knownInliers[i] > nfg_.
size()-1 ){
91 throw std::runtime_error(
"GncOptimizer::constructor: the user has selected one or more measurements"
92 "that are not in the factor graph to be known inliers.");
96 for (
size_t i = 0; i < params.knownOutliers.size(); i++){
97 if( params.knownOutliers[i] > nfg_.
size()-1 ){
98 throw std::runtime_error(
"GncOptimizer::constructor: the user has selected one or more measurements"
99 "that are not in the factor graph to be known outliers.");
104 weights_ = initializeWeightsFromKnownInliersAndOutliers();
108 setInlierCostThresholdsAtProbability(alpha);
118 barcSq_ = inth * Vector::Ones(nfg_.
size());
133 barcSq_ = Vector::Ones(nfg_.
size());
134 for (
size_t k = 0; k < nfg_.
size(); k++) {
136 barcSq_[k] = 0.5 * Chi2inv(alpha, nfg_[k]->dim());
145 if (
size_t(w.size()) != nfg_.
size()) {
146 throw std::runtime_error(
147 "GncOptimizer::setWeights: the number of specified weights"
148 " does not match the size of the factor graph.");
160 const GncParameters&
getParams()
const {
return params_;}
176 Vector initializeWeightsFromKnownInliersAndOutliers()
const{
177 Vector weights = Vector::Ones(nfg_.
size());
178 for (
size_t i = 0; i < params_.knownOutliers.size(); i++){
179 weights[ params_.knownOutliers[i] ] = 0.0;
188 Values result = baseOptimizer.optimize();
189 double mu = initializeMu();
190 double prev_cost = graph_initial.
error(result);
197 int nrUnknownInOrOut = nfg_.
size() - ( params_.knownInliers.size() + params_.knownOutliers.size() );
199 if (mu <= 0 || nrUnknownInOrOut == 0) {
200 if (mu <= 0 && params_.verbosity >= GncParameters::Verbosity::SUMMARY) {
201 std::cout <<
"GNC Optimizer stopped because maximum residual at "
202 "initialization is small."
205 if (nrUnknownInOrOut==0 && params_.verbosity >= GncParameters::Verbosity::SUMMARY) {
206 std::cout <<
"GNC Optimizer stopped because all measurements are already known to be inliers or outliers"
209 if (params_.verbosity >= GncParameters::Verbosity::VALUES) {
210 result.print(
"result\n");
211 std::cout <<
"mu: " << mu << std::endl;
217 for (iter = 0; iter < params_.maxIterations; iter++) {
220 if (params_.verbosity >= GncParameters::Verbosity::VALUES) {
221 std::cout <<
"iter: " << iter << std::endl;
222 result.print(
"result\n");
223 std::cout <<
"mu: " << mu << std::endl;
224 std::cout <<
"weights: " << weights_ << std::endl;
227 weights_ = calculateWeights(result, mu);
232 result = baseOptimizer_iter.optimize();
235 cost = graph_iter.
error(result);
247 if (params_.verbosity >= GncParameters::Verbosity::VALUES) {
248 std::cout <<
"previous cost: " << prev_cost << std::endl;
249 std::cout <<
"current cost: " << cost << std::endl;
253 if (params_.verbosity >= GncParameters::Verbosity::SUMMARY) {
254 std::cout <<
"final iterations: " << iter << std::endl;
255 std::cout <<
"final mu: " << mu << std::endl;
256 std::cout <<
"final weights: " << weights_ << std::endl;
257 std::cout <<
"previous cost: " << prev_cost << std::endl;
258 std::cout <<
"current cost: " << cost << std::endl;
266 double mu_init = 0.0;
268 switch (params_.lossType) {
269 case GncLossType::GM:
273 for (
size_t k = 0; k < nfg_.
size(); k++) {
275 mu_init = std::max(mu_init, 2 * nfg_[k]->error(state_) / barcSq_[k]);
279 case GncLossType::TLS:
286 mu_init = std::numeric_limits<double>::infinity();
287 for (
size_t k = 0; k < nfg_.
size(); k++) {
289 double rk = nfg_[k]->
error(state_);
290 mu_init = (2 * rk - barcSq_[k]) > 0 ?
291 std::min(mu_init, barcSq_[k] / (2 * rk - barcSq_[k]) ) : mu_init;
294 return mu_init > 0 && !std::isinf(mu_init) ? mu_init : -1;
298 throw std::runtime_error(
299 "GncOptimizer::initializeMu: called with unknown loss type.");
305 switch (params_.lossType) {
306 case GncLossType::GM:
308 return std::max(1.0, mu / params_.muStep);
309 case GncLossType::TLS:
311 return mu * params_.muStep;
313 throw std::runtime_error(
314 "GncOptimizer::updateMu: called with unknown loss type.");
320 bool muConverged =
false;
321 switch (params_.lossType) {
322 case GncLossType::GM:
323 muConverged = std::fabs(mu - 1.0) < 1e-9;
325 case GncLossType::TLS:
329 throw std::runtime_error(
330 "GncOptimizer::checkMuConvergence: called with unknown loss type.");
332 if (muConverged && params_.verbosity >= GncParameters::Verbosity::SUMMARY)
333 std::cout <<
"muConverged = true " << std::endl;
339 bool costConverged = std::fabs(cost - prev_cost) / std::max(prev_cost, 1e-7)
340 < params_.relativeCostTol;
341 if (costConverged && params_.verbosity >= GncParameters::Verbosity::SUMMARY)
342 std::cout <<
"checkCostConvergence = true " << std::endl;
343 return costConverged;
348 bool weightsConverged =
false;
349 switch (params_.lossType) {
350 case GncLossType::GM:
351 weightsConverged =
false;
353 case GncLossType::TLS:
354 weightsConverged =
true;
355 for (
int i = 0; i < weights.size(); i++) {
356 if (std::fabs(weights[i] - std::round(weights[i]))
357 > params_.weightsTol) {
358 weightsConverged =
false;
364 throw std::runtime_error(
365 "GncOptimizer::checkWeightsConvergence: called with unknown loss type.");
368 && params_.verbosity >= GncParameters::Verbosity::SUMMARY)
369 std::cout <<
"weightsConverged = true " << std::endl;
370 return weightsConverged;
375 const double cost,
const double prev_cost)
const {
376 return checkCostConvergence(cost, prev_cost)
377 || checkWeightsConvergence(weights) || checkMuConvergence(mu);
385 for (
size_t i = 0; i < nfg_.
size(); i++) {
387 auto factor = boost::dynamic_pointer_cast<
390 boost::dynamic_pointer_cast<noiseModel::Gaussian>(
391 factor->noiseModel());
393 Matrix newInfo = weights[i] * noiseModel->information();
395 newGraph[i] = factor->cloneWithNewNoiseModel(newNoiseModel);
397 throw std::runtime_error(
398 "GncOptimizer::makeWeightedGraph: unexpected non-Gaussian noise model.");
407 Vector weights = initializeWeightsFromKnownInliersAndOutliers();
410 std::vector<size_t> allWeights;
411 for (
size_t k = 0; k < nfg_.
size(); k++) {
412 allWeights.push_back(k);
414 std::vector<size_t> knownWeights;
415 std::set_union(params_.knownInliers.begin(), params_.knownInliers.end(),
416 params_.knownOutliers.begin(), params_.knownOutliers.end(),
417 std::inserter(knownWeights, knownWeights.begin()));
419 std::vector<size_t> unknownWeights;
420 std::set_difference(allWeights.begin(), allWeights.end(),
421 knownWeights.begin(), knownWeights.end(),
422 std::inserter(unknownWeights, unknownWeights.begin()));
425 switch (params_.lossType) {
426 case GncLossType::GM: {
427 for (
size_t k : unknownWeights) {
429 double u2_k = nfg_[k]->
error(currentEstimate);
430 weights[k] = std::pow(
431 (mu * barcSq_[k]) / (u2_k + mu * barcSq_[k]), 2);
436 case GncLossType::TLS: {
437 double upperbound = (mu + 1) / mu * barcSq_.maxCoeff();
438 double lowerbound = mu / (mu + 1) * barcSq_.minCoeff();
439 for (
size_t k : unknownWeights) {
441 double u2_k = nfg_[k]->
error(currentEstimate);
442 if (u2_k >= upperbound) {
444 }
else if (u2_k <= lowerbound) {
447 weights[k] = std::sqrt(barcSq_[k] * mu * (mu + 1) / u2_k)
455 throw std::runtime_error(
456 "GncOptimizer::calculateWeights: called with unknown loss type.");
Factor Graph consisting of non-linear factors.
Global functions in a separate testing namespace.
Definition: chartTesting.h:28
bool checkConvergence(double relativeErrorTreshold, double absoluteErrorTreshold, double errorThreshold, double currentError, double newError, NonlinearOptimizerParams::Verbosity verbosity)
Check whether the relative error decrease is less than relativeErrorTreshold, the absolute error decr...
Definition: NonlinearOptimizer.cpp:185
bool equal(const T &obj1, const T &obj2, double tol)
Call equal on the object.
Definition: Testable.h:84
void resize(size_t size)
Directly resize the number of factors in the graph.
Definition: FactorGraph.h:357
size_t size() const
return the number of factors (including any null factors set by remove() ).
Definition: FactorGraph.h:305
static shared_ptr Information(const Matrix &M, bool smart=true)
A Gaussian noise model created by specifying an information matrix.
Definition: NoiseModel.cpp:99
Base class for robust error models The robust M-estimators above simply tell us how to re-weight the ...
Definition: NoiseModel.h:656
Definition: GncOptimizer.h:45
bool checkWeightsConvergence(const Vector &weights) const
Check convergence of weights to binary values.
Definition: GncOptimizer.h:347
void setWeights(const Vector w)
Set weights for each factor.
Definition: GncOptimizer.h:144
bool checkMuConvergence(const double mu) const
Check if we have reached the value of mu for which the surrogate loss matches the original loss.
Definition: GncOptimizer.h:319
GncParameters::OptimizerType BaseOptimizer
For each parameter, specify the corresponding optimizer: e.g., GaussNewtonParams -> GaussNewtonOptimi...
Definition: GncOptimizer.h:48
Values optimize()
Compute optimal solution using graduated non-convexity.
Definition: GncOptimizer.h:185
const Vector & getInlierCostThresholds() const
Get the inlier threshold.
Definition: GncOptimizer.h:166
bool checkCostConvergence(const double cost, const double prev_cost) const
Check convergence of relative cost differences.
Definition: GncOptimizer.h:338
const Values & getState() const
Access a copy of the internal values.
Definition: GncOptimizer.h:157
void setInlierCostThresholds(const Vector &inthVec)
Set the maximum weighted residual error for an inlier (one for each factor).
Definition: GncOptimizer.h:125
Vector calculateWeights(const Values ¤tEstimate, const double mu)
Calculate gnc weights.
Definition: GncOptimizer.h:406
double initializeMu() const
Initialize the gnc parameter mu such that loss is approximately convex (remark 5 in GNC paper).
Definition: GncOptimizer.h:264
double updateMu(const double mu) const
Update the gnc parameter mu to gradually increase nonconvexity.
Definition: GncOptimizer.h:304
bool equals(const GncOptimizer &other, double tol=1e-9) const
Equals.
Definition: GncOptimizer.h:169
bool checkConvergence(const double mu, const Vector &weights, const double cost, const double prev_cost) const
Check for convergence between consecutive GNC iterations.
Definition: GncOptimizer.h:374
void setInlierCostThresholds(const double inth)
Set the maximum weighted residual error for an inlier (same for all factors).
Definition: GncOptimizer.h:117
void setInlierCostThresholdsAtProbability(const double alpha)
Set the maximum weighted residual error threshold by specifying the probability alpha that the inlier...
Definition: GncOptimizer.h:132
NonlinearFactorGraph makeWeightedGraph(const Vector &weights) const
Create a graph where each factor is weighted by the gnc weights.
Definition: GncOptimizer.h:381
const NonlinearFactorGraph & getFactors() const
Access a copy of the internal factor graph.
Definition: GncOptimizer.h:154
const Vector & getWeights() const
Access a copy of the GNC weights.
Definition: GncOptimizer.h:163
const GncParameters & getParams() const
Access a copy of the parameters.
Definition: GncOptimizer.h:160
GncOptimizer(const NonlinearFactorGraph &graph, const Values &initialValues, const GncParameters ¶ms=GncParameters())
Constructor.
Definition: GncOptimizer.h:59
A nonlinear sum-of-squares factor with a zero-mean noise model implementing the density Templated on...
Definition: NonlinearFactor.h:162
boost::shared_ptr< This > shared_ptr
Noise model.
Definition: NonlinearFactor.h:174
A non-linear factor graph is a graph of non-Gaussian, i.e.
Definition: NonlinearFactorGraph.h:78
bool equals(const NonlinearFactorGraph &other, double tol=1e-9) const
Test equality.
Definition: NonlinearFactorGraph.cpp:89
double error(const Values &values) const
unnormalized error, in the most common case
Definition: NonlinearFactorGraph.cpp:271
A non-templated config holding any types of Manifold-group elements.
Definition: Values.h:63