32#include <gtsam/linear/LossFunctions.h>
33#include <gtsam/nonlinear/GncParams.h>
41GTSAM_EXPORT
double Chi2inv(
const double alpha,
const size_t dofs);
69 double totalElapsed = 0.0;
75 double totalElapsed = 0.0;
76 std::vector<GncIterationTiming> iterations;
78 double sumWeightsUpdate()
const {
80 for (
const auto& it : iterations) sum += it.weightsUpdateElapsed;
83 double sumMakeGraph()
const {
85 for (
const auto& it : iterations) sum += it.makeGraphElapsed;
88 double sumBaseOptimize()
const {
90 for (
const auto& it : iterations) sum += it.baseOptimizeElapsed;
93 double sumCostEvaluation()
const {
95 for (
const auto& it : iterations) sum += it.costEvaluationElapsed;
101template<
class GncParameters>
115 GncParameters params_;
125 std::vector<GncFactorType> factorTypes_;
133 const GncParameters& params = GncParameters())
134 : state_(initialValues),
138 nfg_.resize(graph.
size());
140 for (
size_t i = 0; i < graph.
size(); i++) {
147 if (!params.allowNonNoiseModelFactors) {
148 throw std::runtime_error(
"GncOptimizer::constructor: the user must set allowNonNoiseModelFactors as"
149 " true if the factor graph contains factors without noise model.");
156 std::dynamic_pointer_cast<noiseModel::Robust>(factor->noiseModel());
158 nfg_[i] = robust ? factor-> cloneWithNewNoiseModel(robust->noise()) : factor;
163 for (
size_t i = 0; i < params.knownInliers.size(); i++){
164 if( params.knownInliers[i] > nfg_.size()-1 || isNullType(factorTypes_[params.knownInliers[i]])) {
165 throw std::runtime_error(
"GncOptimizer::constructor: the user has selected one or more measurements"
166 "that are not in the factor graph to be known inliers.");
168 if (!isNonNoiseModelType(factorTypes_[params.knownInliers[i]])) {
173 for (
size_t i = 0; i < params.knownOutliers.size(); i++){
174 if( params.knownOutliers[i] > nfg_.size()-1 || isNullType(factorTypes_[params.knownOutliers[i]])) {
175 throw std::runtime_error(
"GncOptimizer::constructor: the user has selected one or more measurements"
176 "that are not in the factor graph to be known outliers.");
178 if (!needsWeightUpdate(factorTypes_[params.knownOutliers[i]])) {
180 throw std::runtime_error(
"GncOptimizer::constructor: the user has selected one or more measurements"
181 " to be an outlier that is either an inlier or a non noise model factor.");
188 weights_ = initializeWeightsFromKnownInliersAndOutliers();
202 barcSq_ = inth * Vector::Ones(nfg_.size());
217 barcSq_ = Vector::Ones(nfg_.size());
218 for (
size_t k = 0; k < nfg_.size(); k++) {
219 if (hasNoise(factorTypes_[k])) {
220 barcSq_[k] = 0.5 * Chi2inv(alpha, nfg_[k]->dim());
229 if (
size_t(w.size()) != nfg_.size()) {
230 throw std::runtime_error(
231 "GncOptimizer::setWeights: the number of specified weights"
232 " does not match the size of the factor graph.");
244 const GncParameters&
getParams()
const {
return params_;}
263 Vector initializeWeightsFromKnownInliersAndOutliers()
const{
264 Vector weights = Vector::Ones(nfg_.
size());
266 for (
size_t i = 0; i < params_.knownOutliers.size(); i++){
267 weights[ params_.knownOutliers[i] ] = 0.0;
274 using Clock = std::chrono::steady_clock;
275 const auto elapsedSince = [](Clock::time_point start) {
276 return std::chrono::duration<double>(Clock::now() - start).count();
279 const auto totalStart = Clock::now();
281 validateLossSchedulerCombination();
284 graph_initial, state_, params_.baseOptimizerParams);
285 Values result = baseOptimizer.optimize();
286 timing_.initialOptimizeElapsed = elapsedSince(totalStart);
288 double prev_cost = graph_initial.
error(result);
295 int nrUnknownInOrOut = 0;
297 if (needsWeightUpdate(t)) {
302 if (lambda <= 0 || nrUnknownInOrOut == 0) {
303 if (lambda <= 0 && params_.verbosity >= GncParameters::Verbosity::SUMMARY) {
304 std::cout <<
"GNC Optimizer stopped because maximum residual at "
305 "initialization is small."
308 if (nrUnknownInOrOut==0 && params_.verbosity >= GncParameters::Verbosity::SUMMARY) {
309 std::cout <<
"GNC Optimizer stopped because all measurements are already known to be inliers or outliers"
312 if (params_.verbosity >= GncParameters::Verbosity::LAMBDA) {
313 std::cout <<
"lambda: " << lambda << std::endl;
315 if (params_.verbosity >= GncParameters::Verbosity::VALUES) {
316 result.
print(
"result\n");
318 timing_.totalElapsed = elapsedSince(totalStart);
323 for (iter = 0; iter < params_.maxIterations; iter++) {
324 const auto iterationStart = Clock::now();
328 if (params_.verbosity >= GncParameters::Verbosity::LAMBDA) {
329 std::cout <<
"iter: " << iter << std::endl;
330 std::cout <<
"lambda: " << lambda << std::endl;
332 if (params_.verbosity >= GncParameters::Verbosity::WEIGHTS) {
333 std::cout <<
"weights: " << weights_ << std::endl;
335 if (params_.verbosity >= GncParameters::Verbosity::VALUES) {
336 result.
print(
"result\n");
339 auto stageStart = Clock::now();
344 stageStart = Clock::now();
347 stageStart = Clock::now();
349 graph_iter, state_, params_.baseOptimizerParams);
350 result = baseOptimizer_iter.optimize();
354 stageStart = Clock::now();
355 cost = graph_iter.
error(result);
357 iterationTiming.totalElapsed = elapsedSince(iterationStart);
358 timing_.iterations.push_back(iterationTiming);
370 if (params_.verbosity >= GncParameters::Verbosity::VALUES) {
371 std::cout <<
"previous cost: " << prev_cost << std::endl;
372 std::cout <<
"current cost: " << cost << std::endl;
376 if (params_.verbosity >= GncParameters::Verbosity::SUMMARY) {
377 std::cout <<
"final iterations: " << iter << std::endl;
378 std::cout <<
"final lambda: " << lambda << std::endl;
379 std::cout <<
"previous cost: " << prev_cost << std::endl;
380 std::cout <<
"current cost: " << cost << std::endl;
382 if (params_.verbosity >= GncParameters::Verbosity::WEIGHTS) {
383 std::cout <<
"final weights: " << weights_ << std::endl;
385 timing_.totalElapsed = elapsedSince(totalStart);
389 void validateLossSchedulerCombination()
const {
390 if (params_.lossType == GncLossType::GM &&
391 params_.scheduler != GncScheduler::Linear) {
392 throw std::runtime_error(
393 "GncOptimizer::optimize: scheduler must be Linear for GM.");
395 if (params_.lossType == GncLossType::TLS) {
404 double lambdaInit = 0.0;
406 switch (params_.lossType) {
407 case GncLossType::GM:
411 for (
size_t k = 0; k < nfg_.size(); k++) {
412 if (hasNoise(factorTypes_[k])) {
413 lambdaInit = std::max(lambdaInit, 2 * nfg_[k]->error(state_) / barcSq_[k]);
417 case GncLossType::TLS:
424 lambdaInit = std::numeric_limits<double>::infinity();
425 for (
size_t k = 0; k < nfg_.size(); k++) {
426 if (hasNoise(factorTypes_[k])) {
427 double rk = nfg_[k]->error(state_);
428 lambdaInit = (2 * rk - barcSq_[k]) > 0 ?
429 std::min(lambdaInit, barcSq_[k] / (2 * rk - barcSq_[k]) ) : lambdaInit;
432 if (lambdaInit >= 0 && lambdaInit < 1e-6){
437 return lambdaInit > 0 && !std::isinf(lambdaInit) ? lambdaInit : -1;
441 throw std::runtime_error(
442 "GncOptimizer::initializeLambda: called with unknown loss type.");
448 switch (params_.lossType) {
449 case GncLossType::GM:
451 return std::max(1.0, lambda / params_.lambdaStep);
452 case GncLossType::TLS:
454 switch (params_.scheduler) {
455 case GncScheduler::SuperLinear: {
456 if (lambda < 1)
return std::min(std::sqrt(lambda) * params_.lambdaStep, params_.lambdaMax);
457 return std::min(lambda * params_.lambdaStep, params_.lambdaMax);
459 case GncScheduler::Linear: {
460 return lambda * params_.lambdaStep;
463 throw std::runtime_error(
"GncOptimizer::updateLambda: unknown scheduler type.");
466 throw std::runtime_error(
467 "GncOptimizer::updateLambda: called with unknown loss type.");
473 bool lambdaConverged =
false;
474 switch (params_.lossType) {
475 case GncLossType::GM:
476 lambdaConverged = std::fabs(lambda - 1.0) < 1e-9;
478 case GncLossType::TLS:
479 lambdaConverged =
false;
482 throw std::runtime_error(
483 "GncOptimizer::checkLambdaConvergence: called with unknown loss type.");
485 if (lambdaConverged && params_.verbosity >= GncParameters::Verbosity::SUMMARY)
486 std::cout <<
"lambdaConverged = true " << std::endl;
487 return lambdaConverged;
492 bool costConverged = std::fabs(cost - prev_cost) / std::max(prev_cost, 1e-7)
493 < params_.relativeCostTol;
494 if (costConverged && params_.verbosity >= GncParameters::Verbosity::SUMMARY){
495 std::cout <<
"checkCostConvergence = true (prev. cost = " << prev_cost
496 <<
", curr. cost = " << cost <<
")" << std::endl;
498 return costConverged;
503 bool weightsConverged =
false;
504 switch (params_.lossType) {
505 case GncLossType::GM:
506 weightsConverged =
false;
508 case GncLossType::TLS:
509 weightsConverged =
true;
510 for (
int i = 0; i < weights.size(); i++) {
511 if (std::fabs(weights[i] - std::round(weights[i]))
512 > params_.weightsTol) {
513 weightsConverged =
false;
519 throw std::runtime_error(
520 "GncOptimizer::checkWeightsConvergence: called with unknown loss type.");
523 && params_.verbosity >= GncParameters::Verbosity::SUMMARY)
524 std::cout <<
"weightsConverged = true " << std::endl;
525 return weightsConverged;
530 const double cost,
const double prev_cost)
const {
539 newGraph.
resize(nfg_.size());
540 for (
size_t i = 0; i < nfg_.size(); i++) {
541 if (!isNullType(factorTypes_[i])) {
542 if (!hasNoise(factorTypes_[i])) {
544 newGraph[i] = nfg_[i];
547 auto factor = std::static_pointer_cast<NoiseModelFactor>(nfg_[i]);
548 auto noiseModel = std::dynamic_pointer_cast<noiseModel::Gaussian>(
549 factor->noiseModel());
551 Matrix newInfo = weights[i] *
noiseModel->information();
553 newGraph[i] = factor->cloneWithNewNoiseModel(newNoiseModel);
555 throw std::runtime_error(
556 "GncOptimizer::makeWeightedGraph: unexpected non-Gaussian noise model.");
577 case GncLossType::GM:
579 return std::min(1.0, 1.0 / lambda);
580 case GncLossType::TLS:
581 return lambda / (1.0 + lambda);
583 throw std::runtime_error(
584 "GncOptimizer::NormalizedMu: called with unknown loss type.");
590 Vector weights = initializeWeightsFromKnownInliersAndOutliers();
591 const double mu =
NormalizedMu(params_.lossType, lambda);
594 switch (params_.lossType) {
595 case GncLossType::GM: {
596 for (
size_t k = 0; k < nfg_.size(); k++) {
597 if (needsWeightUpdate(factorTypes_[k])) {
599 double u2_k = nfg_[k]->error(currentEstimate);
601 u2_k, barcSq_[k], mu,
602 noiseModel::mEstimator::GemanMcClure::GradScheme::STANDARD);
607 case GncLossType::TLS: {
608 for (
size_t k = 0; k < nfg_.size(); k++) {
609 if (needsWeightUpdate(factorTypes_[k])) {
610 double u2_k = nfg_[k]->error(
612 switch (params_.scheduler) {
613 case GncScheduler::SuperLinear: {
616 u2_k, barcSq_[k], mu,
618 GradScheme::GNC_SUPERLINEAR);
621 case GncScheduler::Linear: {
624 u2_k, barcSq_[k], mu,
626 GradScheme::GNC_LINEAR);
630 throw std::runtime_error(
631 "GncOptimizer::calculateWeights: unknown scheduler type.");
638 throw std::runtime_error(
639 "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
GncLossType
Choice of robust loss function for GNC.
Definition GncParams.h:43
bool equal(const T &obj1, const T &obj2, double tol)
Call equal on the object.
Definition Testable.h:85
GncFactorType
Enum to classify factor types in GNC optimization.
Definition GncOptimizer.h:47
@ NullPointer
Factor pointer is null.
Definition GncOptimizer.h:52
@ NonNoiseModel
Factor does not have a noise model.
Definition GncOptimizer.h:51
@ Outlier
Factor is a known outlier.
Definition GncOptimizer.h:50
@ Normal
Normal case.
Definition GncOptimizer.h:48
@ Inlier
Factor is a known inlier.
Definition GncOptimizer.h:49
All noise models live in the noiseModel namespace.
Definition LossFunctions.cpp:33
virtual void resize(size_t size)
Directly resize the number of factors in the graph.
Definition FactorGraph.h:367
size_t size() const
return the number of factors (including any null factors set by remove() ).
Definition FactorGraph.h:297
const sharedFactor at(size_t i) const
Get a specific factor by index (this checks array bounds and may throw an exception,...
Definition FactorGraph.h:306
static double GraduatedWeight(double distance2, double c2, double mu, GradScheme graduation)
Static implementation of GemanMcClure Graduated Weight.
Definition LossFunctions.cpp:419
Truncated Least Squares (TLS) robust error model.
Definition LossFunctions.h:573
static double GraduatedWeight(double distance2, double c2, double mu, GradScheme graduation)
Static implementation of TLS Graduated Weight.
Definition LossFunctions.cpp:543
static shared_ptr Information(const Matrix &M, bool smart=true)
A Gaussian noise model created by specifying an information matrix.
Definition NoiseModel.cpp:99
Timing of one GNC outer iteration (all in seconds).
Definition GncOptimizer.h:64
double costEvaluationElapsed
weighted graph error for convergence check
Definition GncOptimizer.h:68
double baseOptimizeElapsed
inner optimizer construction + optimize()
Definition GncOptimizer.h:67
double makeGraphElapsed
makeWeightedGraph (factor cloning)
Definition GncOptimizer.h:66
double weightsUpdateElapsed
calculateWeights (per-factor errors)
Definition GncOptimizer.h:65
Timing of a full GncOptimizer::optimize() call.
Definition GncOptimizer.h:73
double initialOptimizeElapsed
optimize before the GNC loop
Definition GncOptimizer.h:74
const GncTiming & getTiming() const
Get the timing of the last optimize() call.
Definition GncOptimizer.h:253
bool checkWeightsConvergence(const Vector &weights) const
Check convergence of weights to binary values.
Definition GncOptimizer.h:502
bool checkLambdaConvergence(const double lambda) const
Check if we have reached the value of lambda for which the surrogate loss matches the original loss.
Definition GncOptimizer.h:472
void setWeights(const Vector w)
Set weights for each factor.
Definition GncOptimizer.h:228
GncParameters::OptimizerType BaseOptimizer
For each parameter, specify the corresponding optimizer: e.g., GaussNewtonParams -> GaussNewtonOptimi...
Definition GncOptimizer.h:105
double updateLambda(const double lambda) const
Update the gnc parameter lambda to gradually increase nonconvexity.
Definition GncOptimizer.h:447
Values optimize()
Compute optimal solution using graduated non-convexity.
Definition GncOptimizer.h:273
const Vector & getInlierCostThresholds() const
Get the inlier threshold.
Definition GncOptimizer.h:250
double initializeLambda() const
Initialize the gnc parameter lambda such that loss is approximately convex (remark 5 in GNC paper).
Definition GncOptimizer.h:402
Vector calculateWeights(const Values ¤tEstimate, const double lambda)
Calculate gnc weights.
Definition GncOptimizer.h:589
bool checkCostConvergence(const double cost, const double prev_cost) const
Check convergence of relative cost differences.
Definition GncOptimizer.h:491
const Values & getState() const
Access a copy of the internal values.
Definition GncOptimizer.h:241
void setInlierCostThresholds(const Vector &inthVec)
Set the maximum weighted residual error for an inlier (one for each factor).
Definition GncOptimizer.h:209
bool equals(const GncOptimizer &other, double tol=1e-9) const
Equals.
Definition GncOptimizer.h:256
void setInlierCostThresholds(const double inth)
Set the maximum weighted residual error for an inlier (same for all factors).
Definition GncOptimizer.h:201
void setInlierCostThresholdsAtProbability(const double alpha)
Set the maximum weighted residual error threshold by specifying the probability alpha that the inlier...
Definition GncOptimizer.h:216
NonlinearFactorGraph makeWeightedGraph(const Vector &weights) const
Create a graph where each factor is weighted by the gnc weights.
Definition GncOptimizer.h:536
static double NormalizedMu(GncLossType lossType, const double lambda)
Map this optimizer's historical graduation parameter to the normalized \mu in [0,1] expected by the r...
Definition GncOptimizer.h:575
const NonlinearFactorGraph & getFactors() const
Access a copy of the internal factor graph.
Definition GncOptimizer.h:238
const Vector & getWeights() const
Access a copy of the GNC weights.
Definition GncOptimizer.h:247
const GncParameters & getParams() const
Access a copy of the parameters.
Definition GncOptimizer.h:244
bool checkConvergence(const double lambda, const Vector &weights, const double cost, const double prev_cost) const
Check for convergence between consecutive GNC iterations.
Definition GncOptimizer.h:529
GncOptimizer(const NonlinearFactorGraph &graph, const Values &initialValues, const GncParameters ¶ms=GncParameters())
Constructor.
Definition GncOptimizer.h:132
A nonlinear sum-of-squares factor with a zero-mean noise model implementing the density Templated on...
Definition NonlinearFactor.h:208
std::shared_ptr< This > shared_ptr
Noise model.
Definition NonlinearFactor.h:220
Definition NonlinearFactorGraph.h:57
virtual double error(const Values &values) const
unnormalized error, in the most common case
Definition NonlinearFactorGraph.cpp:171
A non-templated config holding any types of Manifold-group elements.
Definition Values.h:65
void print(const std::string &str="", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const
print method for testing and debugging
Definition Values.cpp:68