32 #include <Eigen/Eigenvalues>
52 double optimalityThreshold,
double alpha,
double beta,
double gamma)
54 optimalityThreshold(optimalityThreshold),
59 certifyOptimality(true) {
71 if (method ==
"SUBGRAPH") {
73 std::make_shared<SubgraphSolverParameters>(builderParameters);
74 }
else if (method ==
"SGPC") {
76 std::make_shared<SubgraphPreconditionerParameters>(builderParameters));
77 }
else if (method ==
"JACOBI") {
78 lm.
iterativeParams = std::make_shared<PCGSolverParameters>(std::make_shared<BlockJacobiPreconditionerParameters>());
79 }
else if (method ==
"QR") {
81 }
else if (method ==
"CHOLESKY") {
84 throw std::invalid_argument(
"ShonanAveragingParameters: unknown method");
107 size_t N =
keys.size();
109 throw std::invalid_argument(
110 "As of now, ShonanAveraging expects keys numbered 0..N-1");
125 measurement.print(
"Factor with incorrect noise model:\n");
126 throw std::invalid_argument(
127 "ShonanAveraging: measurements passed to "
128 "constructor have incorrect dimension.");
149 if (parameters_.beta > 0) {
150 const size_t dim = SOn::Dimension(
p);
154 if (parameters_.gamma > 0 &&
p >
d + 1) {
170 std::shared_ptr<LevenbergMarquardtOptimizer>
176 if (parameters_.alpha > 0) {
177 const size_t dim = SOn::Dimension(
p);
178 const auto [
i,
value] = parameters_.anchor;
179 auto model = noiseModel::Isotropic::Precision(dim, parameters_.alpha);
184 return std::make_shared<LevenbergMarquardtOptimizer>(
graph,
initial,
192 auto lm = createOptimizerAt(
p,
initial);
193 return lm->optimize();
200 const size_t N =
values.size();
203 for (
const auto& it :
values.extract<
SOn>()) {
204 S.middleCols<
d>(it.first *
d) =
205 it.second.matrix().leftCols<
d>();
214 for (
const auto& it :
values.extract<
SOn>()) {
215 assert(it.second.rows() ==
p);
216 const auto &
M = it.second.matrix();
226 for (
const auto& it :
values.extract<
SOn>()) {
227 assert(it.second.rows() ==
p);
228 const auto &
M = it.second.matrix();
229 const Rot3 R = Rot3::ClosestTo(
M.topLeftCorner<3, 3>());
238 const size_t N =
S.cols() /
d;
245 DiagonalMatrix Sigma_d;
249 Matrix R = Sigma_d *
svd.matrixV().leftCols<
d>().transpose();
252 size_t numPositiveBlocks = 0;
253 for (
size_t i = 0;
i <
N; ++
i) {
259 if (numPositiveBlocks <
N / 2) {
262 DiagonalMatrix reflector;
263 reflector.setIdentity();
264 reflector.diagonal()(
d - 1) = -1;
279 for (
size_t j = 0;
j < nrUnknowns(); ++
j) {
293 for (
size_t j = 0;
j < nrUnknowns(); ++
j) {
294 const Rot3 Ri = Rot3::ClosestTo(
R.middleCols<3>(3 *
j));
306 return roundSolutionS(
S);
322 for (
const auto& it :
values.extract<
Rot>()) {
323 result.insert(it.first,
SO<d>(it.second.matrix()));
330 template <
typename T,
size_t d>
333 const auto &isotropic = std::dynamic_pointer_cast<noiseModel::Isotropic>(
337 sigma = isotropic->sigma();
339 const auto &robust = std::dynamic_pointer_cast<noiseModel::Robust>(
345 throw std::invalid_argument(
346 "Certification of optimality does not work with robust cost.");
352 throw std::invalid_argument(
353 "Shonan averaging noise models must be isotropic (but robust losses "
365 static constexpr
size_t stride = 2 *
d;
368 std::vector<Eigen::Triplet<double>> triplets;
369 triplets.reserve(stride * measurements_.size());
376 double kappa = Kappa<Rot, d>(
measurement, parameters_);
378 const size_t di =
d *
keys[0], dj =
d *
keys[1];
379 for (
size_t k = 0; k <
d; k++) {
381 triplets.emplace_back(di + k, di + k, kappa);
383 triplets.emplace_back(dj + k, dj + k, kappa);
388 const size_t dN =
d * nrUnknowns();
390 D.setFromTriplets(triplets.begin(), triplets.end());
400 static constexpr
size_t stride = 2 *
d *
d;
403 std::vector<Eigen::Triplet<double>> triplets;
404 triplets.reserve(stride * measurements_.size());
414 double kappa = Kappa<Rot, d>(
measurement, parameters_);
416 const size_t di =
d *
keys[0], dj =
d *
keys[1];
417 for (
size_t r = 0; r <
d; r++) {
418 for (
size_t c = 0;
c <
d;
c++) {
420 triplets.emplace_back(di + r, dj +
c, kappa * Rij(r,
c));
422 triplets.emplace_back(dj + r, di +
c, kappa * Rij(
c, r));
428 const size_t dN =
d * nrUnknowns();
430 Q.setFromTriplets(triplets.begin(), triplets.end());
439 static constexpr
size_t stride =
d *
d;
442 const size_t N = nrUnknowns();
443 std::vector<Eigen::Triplet<double>> triplets;
444 triplets.reserve(stride *
N);
447 auto QSt = Q_ *
S.transpose();
449 for (
size_t j = 0;
j <
N;
j++) {
451 const size_t dj =
d *
j;
452 Matrix B = QSt.middleRows(dj,
d) *
S.middleCols<
d>(dj);
455 for (
size_t r = 0; r <
d; r++)
456 for (
size_t c = 0;
c <
d;
c++)
457 triplets.emplace_back(dj + r, dj +
c, 0.5 * (
B(r,
c) +
B(
c, r)));
472 return computeLambda(
S);
478 assert(
values.size() == nrUnknowns());
480 auto Lambda = computeLambda(
S);
487 auto Lambda = computeLambda(
S);
500 int n = initialVector.rows();
501 Vector disturb = Vector::Random(
n);
504 double magnitude = initialVector.norm();
505 Vector perturbedVector = initialVector + 0.03 * magnitude * disturb;
506 perturbedVector.normalize();
507 return perturbedVector;
523 Vector *minEigenVector = 0,
size_t *numIterations = 0,
524 size_t maxIterations = 1000,
525 double minEigenvalueNonnegativityTolerance = 10
e-4) {
530 const bool pmConverged = pmOperator.
compute(
531 maxIterations, 1
e-5);
534 if (!pmConverged)
return false;
536 const double pmEigenValue = pmOperator.
eigenvalue();
538 if (pmEigenValue < 0) {
541 minEigenValue = pmEigenValue;
542 if (minEigenVector) {
544 minEigenVector->normalize();
549 const Sparse C = pmEigenValue * Matrix::Identity(
A.rows(),
A.cols()).sparseView() -
A;
553 const bool minConverged = apmShiftedOperator.
compute(
554 maxIterations, minEigenvalueNonnegativityTolerance / pmEigenValue);
556 if (!minConverged)
return false;
558 minEigenValue = pmEigenValue - apmShiftedOperator.
eigenvalue();
559 if (minEigenVector) {
560 *minEigenVector = apmShiftedOperator.
eigenvector();
561 minEigenVector->normalize();
563 if (numIterations) *numIterations = apmShiftedOperator.
nrIterations();
593 Y = A_ *
X + sigma_ *
X;
626 Vector *minEigenVector = 0,
size_t *numIterations = 0,
627 size_t maxIterations = 1000,
628 double minEigenvalueNonnegativityTolerance = 10
e-4,
634 lmEigenValueSolver(&lmOperator, 1,
std::min(numLanczosVectors,
A.rows()));
635 lmEigenValueSolver.init();
637 const int lmConverged = lmEigenValueSolver.compute(
641 if (lmConverged != 1)
return false;
643 const double lmEigenValue = lmEigenValueSolver.eigenvalues()(0);
645 if (lmEigenValue < 0) {
648 *minEigenValue = lmEigenValue;
649 if (minEigenVector) {
650 *minEigenVector = lmEigenValueSolver.eigenvectors(1).col(0);
651 minEigenVector->normalize();
668 minEigenValueSolver(&minShiftedOperator, 1,
686 perturbation.setRandom();
687 perturbation.normalize();
688 Vector xinit =
v0 + (.03 *
v0.norm()) * perturbation;
691 minEigenValueSolver.init(xinit.data());
696 const int minConverged = minEigenValueSolver.compute(
697 maxIterations, minEigenvalueNonnegativityTolerance / lmEigenValue,
700 if (minConverged != 1)
return false;
702 *minEigenValue = minEigenValueSolver.eigenvalues()(0) + 2 * lmEigenValue;
703 if (minEigenVector) {
704 *minEigenVector = minEigenValueSolver.eigenvectors(1).col(0);
705 minEigenVector->normalize();
707 if (numIterations) *numIterations = minEigenValueSolver.num_iterations();
714 Vector *minEigenVector)
const {
715 assert(
values.size() == nrUnknowns());
717 auto A = computeA(
S);
719 double minEigenValue;
722 throw std::runtime_error(
723 "SparseMinimumEigenValue failed to compute minimum eigenvalue.");
725 return minEigenValue;
731 Vector *minEigenVector)
const {
732 assert(
values.size() == nrUnknowns());
734 auto A = computeA(
S);
736 double minEigenValue = 0;
739 throw std::runtime_error(
740 "PowerMinimumEigenValue failed to compute minimum eigenvalue.");
742 return minEigenValue;
750 double minEigenValue = computeMinEigenValue(
values, &minEigenVector);
751 return {minEigenValue, minEigenVector};
757 double minEigenValue = computeMinEigenValue(
values);
758 return minEigenValue > parameters_.optimalityThreshold;
767 const size_t dimension = SOn::Dimension(
p);
768 double sign0 =
pow(-1.0,
round((
p + 1) / 2) + 1);
769 for (
size_t i = 0;
i <
v.size() /
d;
i++) {
771 const auto v_i =
v.segment<
d>(
d *
i);
772 Vector xi = Vector::Zero(dimension);
774 for (
size_t j = 0;
j <
d;
j++) {
789 Matrix euclideanGradient = 2 * (L_ * (S_dot.transpose())).transpose();
794 Matrix symBlockDiagProduct(
p,
d * nrUnknowns());
795 for (
size_t i = 0;
i < nrUnknowns();
i++) {
797 const size_t di =
d *
i;
798 const Matrix P = S_dot.middleCols<
d>(di).transpose() *
799 euclideanGradient.middleCols<
d>(di);
803 symBlockDiagProduct.middleCols<
d>(di) = S_dot.middleCols<
d>(di) *
S;
805 Matrix riemannianGradient = euclideanGradient - symBlockDiagProduct;
806 return riemannianGradient;
812 const Vector &minEigenVector) {
822 double minEigenValue,
double gradienTolerance,
823 double preconditionedGradNormTolerance)
const {
824 double funcVal = costAt(
p - 1,
values);
825 double alphaMin = 1
e-2;
827 std::max(1024 * alphaMin, 10 * gradienTolerance /
fabs(minEigenValue));
828 vector<double> alphas;
829 vector<double> fvals;
831 while ((
alpha >= alphaMin)) {
833 double funcValTest = costAt(
p, Qplus);
834 Matrix gradTest = riemannianGradient(
p, Qplus);
835 double gradTestNorm = gradTest.norm();
837 alphas.push_back(
alpha);
838 fvals.push_back(funcValTest);
839 if ((funcVal > funcValTest) && (gradTestNorm > gradienTolerance)) {
845 auto fminIter = min_element(fvals.begin(), fvals.end());
846 auto minIdx =
distance(fvals.begin(), fminIter);
847 double fMin = fvals[minIdx];
848 double aMin = alphas[minIdx];
849 if (fMin < funcVal) {
850 Values Qplus = LiftwithDescent(
p,
values, aMin * minEigenVector);
854 return LiftwithDescent(
p,
values,
alpha * minEigenVector);
861 for (
size_t j = 0;
j < nrUnknowns();
j++) {
876 std::mt19937 &
rng)
const {
877 const Values randomRotations = initializeRandomly(
rng);
878 return LiftTo<Rot3>(
p, randomRotations);
893 throw std::runtime_error(
"pMin is smaller than the base dimension d");
896 Values initialSOp = LiftTo<Rot>(pMin, initialEstimate);
897 for (
size_t p = pMin;
p <= pMax;
p++) {
899 Qstar = tryOptimizingAt(
p, initialSOp);
900 if (parameters_.getUseHuber() || !parameters_.getCertifyOptimality()) {
903 throw std::runtime_error(
904 "When using robust norm, Shonan only tests a single rank. Set pMin = pMax");
906 const Values SO3Values = roundSolution(Qstar);
907 return {SO3Values, 0};
911 double minEigenValue = computeMinEigenValue(Qstar, &minEigenVector);
912 if (minEigenValue > parameters_.optimalityThreshold) {
914 const Values SO3Values = roundSolution(Qstar);
915 return {SO3Values, minEigenValue};
922 initializeWithDescent(
p + 1, Qstar, minEigenVector, minEigenValue);
926 throw std::runtime_error(
"Shonan::run did not converge for given pMax");
948 std::dynamic_pointer_cast<noiseModel::Gaussian>(
f->noiseModel());
950 throw std::invalid_argument(
951 "parseMeasurements<Rot2> can only convert Pose2 measurements "
952 "with Gaussian noise models.");
953 const Matrix3
M = gaussian->covariance();
996 std::dynamic_pointer_cast<noiseModel::Gaussian>(
f->noiseModel());
998 throw std::invalid_argument(
999 "parseMeasurements<Rot3> can only convert Pose3 measurements "
1000 "with Gaussian noise models.");
1001 const Matrix6
M = gaussian->covariance();