/* Copyright (c) 2008 University of North Carolina at Chapel Hill This file is part of SSBA (Simple Sparse Bundle Adjustment). SSBA is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SSBA is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SSBA. If not, see . */ #include "Math/v3d_optimization.h" #if defined(V3DLIB_ENABLE_SUITESPARSE) //# include "COLAMD/Include/colamd.h" # include "colamd.h" extern "C" { //# include "LDL/Include/ldl.h" # include "ldl.h" } #endif #include #include #define USE_BLOCK_REORDERING 1 #define USE_MULTIPLICATIVE_UPDATE 1 using namespace std; namespace { using namespace V3D; inline double squaredResidual(VectorArray const& e) { int const N = e.count(); int const M = e.size(); double res = 0.0; for (int n = 0; n < N; ++n) for (int m = 0; m < M; ++m) res += e[n][m] * e[n][m]; return res; } // end squaredResidual() } // end namespace <> namespace V3D { int optimizerVerbosenessLevel = 0; #if defined(V3DLIB_ENABLE_SUITESPARSE) void SparseLevenbergOptimizer::setupSparseJtJ() { int const nVaryingA = _nParametersA - _nNonvaryingA; int const nVaryingB = _nParametersB - _nNonvaryingB; int const nVaryingC = _paramDimensionC - _nNonvaryingC; int const bColumnStart = nVaryingA*_paramDimensionA; int const cColumnStart = bColumnStart + nVaryingB*_paramDimensionB; int const nColumns = cColumnStart + nVaryingC; _jointNonzerosW.clear(); _jointIndexW.resize(_nMeasurements); #if 1 { map, int> jointNonzeroMap; for (size_t k = 0; k < _nMeasurements; ++k) { int const i = _correspondingParamA[k] - _nNonvaryingA; int const j = _correspondingParamB[k] - _nNonvaryingB; if (i >= 0 && j >= 0) { map, int>::const_iterator p = jointNonzeroMap.find(make_pair(i, j)); if (p == jointNonzeroMap.end()) { jointNonzeroMap.insert(make_pair(make_pair(i, j), _jointNonzerosW.size())); _jointIndexW[k] = _jointNonzerosW.size(); _jointNonzerosW.push_back(make_pair(i, j)); } else { _jointIndexW[k] = (*p).second; } // end if } // end if } // end for (k) } // end scope #else for (size_t k = 0; k < _nMeasurements; ++k) { int const i = _correspondingParamA[k] - _nNonvaryingA; int const j = _correspondingParamB[k] - _nNonvaryingB; if (i >= 0 && j >= 0) { _jointIndexW[k] = _jointNonzerosW.size(); _jointNonzerosW.push_back(make_pair(i, j)); } } // end for (k) #endif #if defined(USE_BLOCK_REORDERING) int const bBlockColumnStart = nVaryingA; int const cBlockColumnStart = bBlockColumnStart + nVaryingB; int const nBlockColumns = cBlockColumnStart + ((nVaryingC > 0) ? 1 : 0); //cout << "nBlockColumns = " << nBlockColumns << endl; // For the column reordering we treat the columns belonging to one set // of parameters as one (logical) column. // Determine non-zeros of JtJ (we forget about the non-zero diagonal for now) // Only consider nonzeros of Ai^t * Bj induced by the measurements. vector > nz_blockJtJ(_jointNonzerosW.size()); for (int k = 0; k < _jointNonzerosW.size(); ++k) { nz_blockJtJ[k].first = _jointNonzerosW[k].second + bBlockColumnStart; nz_blockJtJ[k].second = _jointNonzerosW[k].first; } if (nVaryingC > 0) { // We assume, that the global unknowns are linked to every other variable. for (int i = 0; i < nVaryingA; ++i) nz_blockJtJ.push_back(make_pair(cBlockColumnStart, i)); for (int j = 0; j < nVaryingB; ++j) nz_blockJtJ.push_back(make_pair(cBlockColumnStart, j + bBlockColumnStart)); } // end if int const nnzBlock = nz_blockJtJ.size(); vector permBlockJtJ(nBlockColumns + 1); if (nnzBlock > 0) { // cout << "nnzBlock = " << nnzBlock << endl; CCS_Matrix blockJtJ(nBlockColumns, nBlockColumns, nz_blockJtJ); // cout << " nz_blockJtJ: " << endl; // for (size_t k = 0; k < nz_blockJtJ.size(); ++k) // cout << " " << nz_blockJtJ[k].first << ":" << nz_blockJtJ[k].second << endl; // cout << endl; int * colStarts = (int *)blockJtJ.getColumnStarts(); int * rowIdxs = (int *)blockJtJ.getRowIndices(); // cout << "blockJtJ_colStarts = "; // for (int k = 0; k <= nBlockColumns; ++k) cout << colStarts[k] << " "; // cout << endl; // cout << "blockJtJ_rowIdxs = "; // for (int k = 0; k < nnzBlock; ++k) cout << rowIdxs[k] << " "; // cout << endl; int stats[COLAMD_STATS]; symamd(nBlockColumns, rowIdxs, colStarts, &permBlockJtJ[0], (double *) NULL, stats, &calloc, &free); if (optimizerVerbosenessLevel >= 2) symamd_report(stats); } else { for (int k = 0; k < permBlockJtJ.size(); ++k) permBlockJtJ[k] = k; } // end if // cout << "permBlockJtJ = "; // for (int k = 0; k < permBlockJtJ.size(); ++k) // cout << permBlockJtJ[k] << " "; // cout << endl; // From the determined symbolic permutation with logical variables, determine the actual ordering _perm_JtJ.resize(nVaryingA*_paramDimensionA + nVaryingB*_paramDimensionB + nVaryingC + 1); int curDstCol = 0; for (int k = 0; k < permBlockJtJ.size()-1; ++k) { int const srcCol = permBlockJtJ[k]; if (srcCol < nVaryingA) { for (int n = 0; n < _paramDimensionA; ++n) _perm_JtJ[curDstCol + n] = srcCol*_paramDimensionA + n; curDstCol += _paramDimensionA; } else if (srcCol >= bBlockColumnStart && srcCol < cBlockColumnStart) { int const bStart = nVaryingA*_paramDimensionA; int const j = srcCol - bBlockColumnStart; for (int n = 0; n < _paramDimensionB; ++n) _perm_JtJ[curDstCol + n] = bStart + j*_paramDimensionB + n; curDstCol += _paramDimensionB; } else if (srcCol == cBlockColumnStart) { int const cStart = nVaryingA*_paramDimensionA + nVaryingB*_paramDimensionB; for (int n = 0; n < nVaryingC; ++n) _perm_JtJ[curDstCol + n] = cStart + n; curDstCol += nVaryingC; } else { cerr << "Should not reach " << __LINE__ << ":" << __LINE__ << "!" << endl; assert(false); } } #else vector > nz, nzL; this->serializeNonZerosJtJ(nz); for (int k = 0; k < nz.size(); ++k) { // Swap rows and columns, since serializeNonZerosJtJ() generates the // upper triangular part but symamd wants the lower triangle. nzL.push_back(make_pair(nz[k].second, nz[k].first)); } _perm_JtJ.resize(nColumns+1); if (nzL.size() > 0) { CCS_Matrix symbJtJ(nColumns, nColumns, nzL); int * colStarts = (int *)symbJtJ.getColumnStarts(); int * rowIdxs = (int *)symbJtJ.getRowIndices(); // cout << "symbJtJ_colStarts = "; // for (int k = 0; k <= nColumns; ++k) cout << colStarts[k] << " "; // cout << endl; // cout << "symbJtJ_rowIdxs = "; // for (int k = 0; k < nzL.size(); ++k) cout << rowIdxs[k] << " "; // cout << endl; int stats[COLAMD_STATS]; symamd(nColumns, rowIdxs, colStarts, &_perm_JtJ[0], (double *) NULL, stats, &calloc, &free); if (optimizerVerbosenessLevel >= 2) symamd_report(stats); } else { for (int k = 0; k < _perm_JtJ.size(); ++k) _perm_JtJ[k] = k; } //// end if #endif _perm_JtJ.back() = _perm_JtJ.size() - 1; // cout << "_perm_JtJ = "; // for (int k = 0; k < _perm_JtJ.size(); ++k) cout << _perm_JtJ[k] << " "; // cout << endl; // Finally, compute the inverse of the full permutation. _invPerm_JtJ.resize(_perm_JtJ.size()); for (size_t k = 0; k < _perm_JtJ.size(); ++k) _invPerm_JtJ[_perm_JtJ[k]] = k; vector > nz_JtJ; this->serializeNonZerosJtJ(nz_JtJ); for (int k = 0; k < nz_JtJ.size(); ++k) { int const i = nz_JtJ[k].first; int const j = nz_JtJ[k].second; int pi = _invPerm_JtJ[i]; int pj = _invPerm_JtJ[j]; // Swap values if in lower triangular part if (pi > pj) std::swap(pi, pj); nz_JtJ[k].first = pi; nz_JtJ[k].second = pj; } int const nnz = nz_JtJ.size(); // cout << "nz_JtJ = "; // for (int k = 0; k < nnz; ++k) cout << nz_JtJ[k].first << ":" << nz_JtJ[k].second << " "; // cout << endl; _JtJ.create(nColumns, nColumns, nz_JtJ); // cout << "_colStart_JtJ = "; // for (int k = 0; k < _JtJ.num_cols(); ++k) cout << _JtJ.getColumnStarts()[k] << " "; // cout << endl; // cout << "_rowIdxs_JtJ = "; // for (int k = 0; k < nnz; ++k) cout << _JtJ.getRowIndices()[k] << " "; // cout << endl; vector workFlags(nColumns); _JtJ_Lp.resize(nColumns+1); _JtJ_Parent.resize(nColumns); _JtJ_Lnz.resize(nColumns); ldl_symbolic(nColumns, (int *)_JtJ.getColumnStarts(), (int *)_JtJ.getRowIndices(), &_JtJ_Lp[0], &_JtJ_Parent[0], &_JtJ_Lnz[0], &workFlags[0], NULL, NULL); if (optimizerVerbosenessLevel >= 1) cout << "SparseLevenbergOptimizer: Nonzeros in LDL decomposition: " << _JtJ_Lp[nColumns] << endl; } // end SparseLevenbergOptimizer::setupSparseJtJ() void SparseLevenbergOptimizer::serializeNonZerosJtJ(vector >& dst) const { int const nVaryingA = _nParametersA - _nNonvaryingA; int const nVaryingB = _nParametersB - _nNonvaryingB; int const nVaryingC = _paramDimensionC - _nNonvaryingC; int const bColumnStart = nVaryingA*_paramDimensionA; int const cColumnStart = bColumnStart + nVaryingB*_paramDimensionB; dst.clear(); // Add the diagonal block matrices (only the upper triangular part). // Ui submatrices of JtJ for (int i = 0; i < nVaryingA; ++i) { int const i0 = i * _paramDimensionA; for (int c = 0; c < _paramDimensionA; ++c) for (int r = 0; r <= c; ++r) dst.push_back(make_pair(i0 + r, i0 + c)); } // Vj submatrices of JtJ for (int j = 0; j < nVaryingB; ++j) { int const j0 = j*_paramDimensionB + bColumnStart; for (int c = 0; c < _paramDimensionB; ++c) for (int r = 0; r <= c; ++r) dst.push_back(make_pair(j0 + r, j0 + c)); } // Z submatrix of JtJ for (int c = 0; c < nVaryingC; ++c) for (int r = 0; r <= c; ++r) dst.push_back(make_pair(cColumnStart + r, cColumnStart + c)); // Add the elements i and j linked by an observation k // W submatrix of JtJ for (size_t n = 0; n < _jointNonzerosW.size(); ++n) { int const i0 = _jointNonzerosW[n].first; int const j0 = _jointNonzerosW[n].second; int const r0 = i0*_paramDimensionA; int const c0 = j0*_paramDimensionB + bColumnStart; for (int r = 0; r < _paramDimensionA; ++r) for (int c = 0; c < _paramDimensionB; ++c) dst.push_back(make_pair(r0 + r, c0 + c)); } // end for (n) if (nVaryingC > 0) { // Finally, add the dense columns linking i (resp. j) with the global parameters. // X submatrix of JtJ for (int i = 0; i < nVaryingA; ++i) { int const i0 = i*_paramDimensionA; for (int r = 0; r < _paramDimensionA; ++r) for (int c = 0; c < nVaryingC; ++c) dst.push_back(make_pair(i0 + r, cColumnStart + c)); } // Y submatrix of JtJ for (int j = 0; j < nVaryingB; ++j) { int const j0 = j*_paramDimensionB + bColumnStart; for (int r = 0; r < _paramDimensionB; ++r) for (int c = 0; c < nVaryingC; ++c) dst.push_back(make_pair(j0 + r, cColumnStart + c)); } } // end if } // end SparseLevenbergOptimizer::serializeNonZerosJtJ() void SparseLevenbergOptimizer::fillSparseJtJ(MatrixArray const& Ui, MatrixArray const& Vj, MatrixArray const& Wn, Matrix const& Z, Matrix const& X, Matrix const& Y) { int const nVaryingA = _nParametersA - _nNonvaryingA; int const nVaryingB = _nParametersB - _nNonvaryingB; int const nVaryingC = _paramDimensionC - _nNonvaryingC; int const bColumnStart = nVaryingA*_paramDimensionA; int const cColumnStart = bColumnStart + nVaryingB*_paramDimensionB; int const nCols = _JtJ.num_cols(); int const nnz = _JtJ.getNonzeroCount(); // The following has to replicate the procedure as in serializeNonZerosJtJ() int serial = 0; double * values = _JtJ.getValues(); int const * destIdxs = _JtJ.getDestIndices(); // Add the diagonal block matrices (only the upper triangular part). // Ui submatrices of JtJ for (int i = 0; i < nVaryingA; ++i) { int const i0 = i * _paramDimensionA; for (int c = 0; c < _paramDimensionA; ++c) for (int r = 0; r <= c; ++r, ++serial) values[destIdxs[serial]] = Ui[i][r][c]; } // Vj submatrices of JtJ for (int j = 0; j < nVaryingB; ++j) { int const j0 = j*_paramDimensionB + bColumnStart; for (int c = 0; c < _paramDimensionB; ++c) for (int r = 0; r <= c; ++r, ++serial) values[destIdxs[serial]] = Vj[j][r][c]; } // Z submatrix of JtJ for (int c = 0; c < nVaryingC; ++c) for (int r = 0; r <= c; ++r, ++serial) values[destIdxs[serial]] = Z[r][c]; // Add the elements i and j linked by an observation k // W submatrix of JtJ for (size_t n = 0; n < _jointNonzerosW.size(); ++n) { for (int r = 0; r < _paramDimensionA; ++r) for (int c = 0; c < _paramDimensionB; ++c, ++serial) values[destIdxs[serial]] = Wn[n][r][c]; } // end for (k) if (nVaryingC > 0) { // Finally, add the dense columns linking i (resp. j) with the global parameters. // X submatrix of JtJ for (int i = 0; i < nVaryingA; ++i) { int const r0 = i * _paramDimensionA; for (int r = 0; r < _paramDimensionA; ++r) for (int c = 0; c < nVaryingC; ++c, ++serial) values[destIdxs[serial]] = X[r0+r][c]; } // Y submatrix of JtJ for (int j = 0; j < nVaryingB; ++j) { int const r0 = j * _paramDimensionB; for (int r = 0; r < _paramDimensionB; ++r) for (int c = 0; c < nVaryingC; ++c, ++serial) values[destIdxs[serial]] = Y[r0+r][c]; } } // end if } // end SparseLevenbergOptimizer::fillSparseJtJ() void SparseLevenbergOptimizer::minimize() { status = LEVENBERG_OPTIMIZER_TIMEOUT; bool computeDerivatives = true; int const nVaryingA = _nParametersA - _nNonvaryingA; int const nVaryingB = _nParametersB - _nNonvaryingB; int const nVaryingC = _paramDimensionC - _nNonvaryingC; if (nVaryingA == 0 && nVaryingB == 0 && nVaryingC == 0) { // No degrees of freedom, nothing to optimize. status = LEVENBERG_OPTIMIZER_CONVERGED; return; } this->setupSparseJtJ(); Vector weights(_nMeasurements); MatrixArray Ak(_nMeasurements, _measurementDimension, _paramDimensionA); MatrixArray Bk(_nMeasurements, _measurementDimension, _paramDimensionB); MatrixArray Ck(_nMeasurements, _measurementDimension, _paramDimensionC); MatrixArray Ui(nVaryingA, _paramDimensionA, _paramDimensionA); MatrixArray Vj(nVaryingB, _paramDimensionB, _paramDimensionB); // Wn = Ak^t*Bk MatrixArray Wn(_jointNonzerosW.size(), _paramDimensionA, _paramDimensionB); Matrix Z(nVaryingC, nVaryingC); // X = A^t*C Matrix X(nVaryingA*_paramDimensionA, nVaryingC); // Y = B^t*C Matrix Y(nVaryingB*_paramDimensionB, nVaryingC); VectorArray residuals(_nMeasurements, _measurementDimension); VectorArray residuals2(_nMeasurements, _measurementDimension); VectorArray diagUi(nVaryingA, _paramDimensionA); VectorArray diagVj(nVaryingB, _paramDimensionB); Vector diagZ(nVaryingC); VectorArray At_e(nVaryingA, _paramDimensionA); VectorArray Bt_e(nVaryingB, _paramDimensionB); Vector Ct_e(nVaryingC); Vector Jt_e(nVaryingA*_paramDimensionA + nVaryingB*_paramDimensionB + nVaryingC); Vector delta(nVaryingA*_paramDimensionA + nVaryingB*_paramDimensionB + nVaryingC); Vector deltaPerm(nVaryingA*_paramDimensionA + nVaryingB*_paramDimensionB + nVaryingC); VectorArray deltaAi(_nParametersA, _paramDimensionA); VectorArray deltaBj(_nParametersB, _paramDimensionB); Vector deltaC(_paramDimensionC); double err = 0.0; for (currentIteration = 0; currentIteration < maxIterations; ++currentIteration) { if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: currentIteration: " << currentIteration << endl; if (computeDerivatives) { this->evalResidual(residuals); this->fillWeights(residuals, weights); for (int k = 0; k < _nMeasurements; ++k) scaleVectorIP(weights[k], residuals[k]); err = squaredResidual(residuals); if (optimizerVerbosenessLevel >= 1) cout << "SparseLevenbergOptimizer: |residual|^2 = " << err << endl; if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: lambda = " << lambda << endl; for (int k = 0; k < residuals.count(); ++k) scaleVectorIP(-1.0, residuals[k]); this->setupJacobianGathering(); this->fillAllJacobians(weights, Ak, Bk, Ck); // Compute the different parts of J^t*e if (nVaryingA > 0) { for (int i = 0; i < nVaryingA; ++i) makeZeroVector(At_e[i]); Vector tmp(_paramDimensionA); for (int k = 0; k < _nMeasurements; ++k) { int const i = _correspondingParamA[k] - _nNonvaryingA; if (i < 0) continue; multiply_At_v(Ak[k], residuals[k], tmp); addVectors(tmp, At_e[i], At_e[i]); } // end for (k) } // end if if (nVaryingB > 0) { for (int j = 0; j < nVaryingB; ++j) makeZeroVector(Bt_e[j]); Vector tmp(_paramDimensionB); for (int k = 0; k < _nMeasurements; ++k) { int const j = _correspondingParamB[k] - _nNonvaryingB; if (j < 0) continue; multiply_At_v(Bk[k], residuals[k], tmp); addVectors(tmp, Bt_e[j], Bt_e[j]); } // end for (k) } // end if if (nVaryingC > 0) { makeZeroVector(Ct_e); Vector tmp(_paramDimensionC); for (int k = 0; k < _nMeasurements; ++k) { multiply_At_v(Ck[k], residuals[k], tmp); for (int l = 0; l < nVaryingC; ++l) Ct_e[l] += tmp[_nNonvaryingC + l]; } } // end if int pos = 0; for (int i = 0; i < nVaryingA; ++i) for (int l = 0; l < _paramDimensionA; ++l, ++pos) Jt_e[pos] = At_e[i][l]; for (int j = 0; j < nVaryingB; ++j) for (int l = 0; l < _paramDimensionB; ++l, ++pos) Jt_e[pos] = Bt_e[j][l]; for (int l = 0; l < nVaryingC; ++l, ++pos) Jt_e[pos] = Ct_e[l]; // cout << "Jt_e = "; // for (int k = 0; k < Jt_e.size(); ++k) cout << Jt_e[k] << " "; // cout << endl; if (this->applyGradientStoppingCriteria(norm_Linf(Jt_e))) { status = LEVENBERG_OPTIMIZER_CONVERGED; goto end; } // The lhs J^t*J consists of several parts: // [ U W X ] // J^t*J = [ W^t V Y ] // [ X^t Y^t Z ], // where U, V and W are block-sparse matrices (due to the sparsity of A and B). // X, Y and Z contain only a few columns (the number of global parameters). if (nVaryingA > 0) { // Compute Ui Matrix U(_paramDimensionA, _paramDimensionA); for (int i = 0; i < nVaryingA; ++i) makeZeroMatrix(Ui[i]); for (int k = 0; k < _nMeasurements; ++k) { int const i = _correspondingParamA[k] - _nNonvaryingA; if (i < 0) continue; multiply_At_A(Ak[k], U); addMatricesIP(U, Ui[i]); } // end for (k) } // end if if (nVaryingB > 0) { // Compute Vj Matrix V(_paramDimensionB, _paramDimensionB); for (int j = 0; j < nVaryingB; ++j) makeZeroMatrix(Vj[j]); for (int k = 0; k < _nMeasurements; ++k) { int const j = _correspondingParamB[k] - _nNonvaryingB; if (j < 0) continue; multiply_At_A(Bk[k], V); addMatricesIP(V, Vj[j]); } // end for (k) } // end if if (nVaryingC > 0) { Matrix ZZ(_paramDimensionC, _paramDimensionC); Matrix Zsum(_paramDimensionC, _paramDimensionC); makeZeroMatrix(Zsum); for (int k = 0; k < _nMeasurements; ++k) { multiply_At_A(Ck[k], ZZ); addMatricesIP(ZZ, Zsum); } // end for (k) // Ignore the non-varying parameters for (int i = 0; i < nVaryingC; ++i) for (int j = 0; j < nVaryingC; ++j) Z[i][j] = Zsum[i+_nNonvaryingC][j+_nNonvaryingC]; } // end if if (nVaryingA > 0 && nVaryingB > 0) { for (int n = 0; n < Wn.count(); ++n) makeZeroMatrix(Wn[n]); Matrix W(_paramDimensionA, _paramDimensionB); for (int k = 0; k < _nMeasurements; ++k) { int const n = _jointIndexW[k]; if (n >= 0) { int const i0 = _jointNonzerosW[n].first; int const j0 = _jointNonzerosW[n].second; multiply_At_B(Ak[k], Bk[k], W); addMatricesIP(W, Wn[n]); } // end if } // end for (k) } // end if if (nVaryingA > 0 && nVaryingC > 0) { Matrix XX(_paramDimensionA, _paramDimensionC); makeZeroMatrix(X); for (int k = 0; k < _nMeasurements; ++k) { int const i = _correspondingParamA[k] - _nNonvaryingA; // Ignore the non-varying parameters if (i < 0) continue; multiply_At_B(Ak[k], Ck[k], XX); for (int r = 0; r < _paramDimensionA; ++r) for (int c = 0; c < nVaryingC; ++c) X[r+i*_paramDimensionA][c] += XX[r][c+_nNonvaryingC]; } // end for (k) } // end if if (nVaryingB > 0 && nVaryingC > 0) { Matrix YY(_paramDimensionB, _paramDimensionC); makeZeroMatrix(Y); for (int k = 0; k < _nMeasurements; ++k) { int const j = _correspondingParamB[k] - _nNonvaryingB; // Ignore the non-varying parameters if (j < 0) continue; multiply_At_B(Bk[k], Ck[k], YY); for (int r = 0; r < _paramDimensionB; ++r) for (int c = 0; c < nVaryingC; ++c) Y[r+j*_paramDimensionB][c] += YY[r][c+_nNonvaryingC]; } // end for (k) } // end if if (currentIteration == 0) { // Initialize lambda as tau*max(JtJ[i][i]) double maxEl = -1e30; if (nVaryingA > 0) { for (int i = 0; i < nVaryingA; ++i) for (int l = 0; l < _paramDimensionA; ++l) maxEl = std::max(maxEl, Ui[i][l][l]); } if (nVaryingB > 0) { for (int j = 0; j < nVaryingB; ++j) for (int l = 0; l < _paramDimensionB; ++l) maxEl = std::max(maxEl, Vj[j][l][l]); } if (nVaryingC > 0) { for (int l = 0; l < nVaryingC; ++l) maxEl = std::max(maxEl, Z[l][l]); } lambda = tau * maxEl; if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: initial lambda = " << lambda << endl; } // end if (currentIteration == 0) } // end if (computeDerivatives) for (int i = 0; i < nVaryingA; ++i) { for (int l = 0; l < _paramDimensionA; ++l) diagUi[i][l] = Ui[i][l][l]; } // end for (i) for (int j = 0; j < nVaryingB; ++j) { for (int l = 0; l < _paramDimensionB; ++l) diagVj[j][l] = Vj[j][l][l]; } // end for (j) for (int l = 0; l < nVaryingC; ++l) diagZ[l] = Z[l][l]; // Augment the diagonals with lambda (either by the standard additive update or by multiplication). #if !defined(USE_MULTIPLICATIVE_UPDATE) for (int i = 0; i < nVaryingA; ++i) for (unsigned l = 0; l < _paramDimensionA; ++l) Ui[i][l][l] += lambda; for (int j = 0; j < nVaryingB; ++j) for (unsigned l = 0; l < _paramDimensionB; ++l) Vj[j][l][l] += lambda; for (unsigned l = 0; l < nVaryingC; ++l) Z[l][l] += lambda; #else for (int i = 0; i < nVaryingA; ++i) for (unsigned l = 0; l < _paramDimensionA; ++l) Ui[i][l][l] = std::max(Ui[i][l][l] * (1.0 + lambda), 1e-15); for (int j = 0; j < nVaryingB; ++j) for (unsigned l = 0; l < _paramDimensionB; ++l) Vj[j][l][l] = std::max(Vj[j][l][l] * (1.0 + lambda), 1e-15); for (unsigned l = 0; l < nVaryingC; ++l) Z[l][l] = std::max(Z[l][l] * (1.0 + lambda), 1e-15); #endif this->fillSparseJtJ(Ui, Vj, Wn, Z, X, Y); bool success = true; double rho = 0.0; { int const nCols = _JtJ_Parent.size(); int const nnz = _JtJ.getNonzeroCount(); int const lnz = _JtJ_Lp.back(); vector Li(lnz); vector Lx(lnz); vector D(nCols), Y(nCols); vector workPattern(nCols), workFlag(nCols); int * colStarts = (int *)_JtJ.getColumnStarts(); int * rowIdxs = (int *)_JtJ.getRowIndices(); double * values = _JtJ.getValues(); int const d = ldl_numeric(nCols, colStarts, rowIdxs, values, &_JtJ_Lp[0], &_JtJ_Parent[0], &_JtJ_Lnz[0], &Li[0], &Lx[0], &D[0], &Y[0], &workPattern[0], &workFlag[0], NULL, NULL); if (d == nCols) { ldl_perm(nCols, &deltaPerm[0], &Jt_e[0], &_perm_JtJ[0]); ldl_lsolve(nCols, &deltaPerm[0], &_JtJ_Lp[0], &Li[0], &Lx[0]); ldl_dsolve(nCols, &deltaPerm[0], &D[0]); ldl_ltsolve(nCols, &deltaPerm[0], &_JtJ_Lp[0], &Li[0], &Lx[0]); ldl_permt(nCols, &delta[0], &deltaPerm[0], &_perm_JtJ[0]); } else { if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: LDL decomposition failed. Increasing lambda." << endl; success = false; } } if (success) { double const deltaSqrLength = sqrNorm_L2(delta); if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: ||delta||^2 = " << deltaSqrLength << endl; double const paramLength = this->getParameterLength(); if (this->applyUpdateStoppingCriteria(paramLength, sqrt(deltaSqrLength))) { status = LEVENBERG_OPTIMIZER_SMALL_UPDATE; goto end; } // Copy the updates from delta to the respective arrays int pos = 0; for (int i = 0; i < _nNonvaryingA; ++i) makeZeroVector(deltaAi[i]); for (int i = _nNonvaryingA; i < _nParametersA; ++i) for (int l = 0; l < _paramDimensionA; ++l, ++pos) deltaAi[i][l] = delta[pos]; for (int j = 0; j < _nNonvaryingB; ++j) makeZeroVector(deltaBj[j]); for (int j = _nNonvaryingB; j < _nParametersB; ++j) for (int l = 0; l < _paramDimensionB; ++l, ++pos) deltaBj[j][l] = delta[pos]; makeZeroVector(deltaC); for (int l = _nNonvaryingC; l < _paramDimensionC; ++l, ++pos) deltaC[l] = delta[pos]; saveAllParameters(); if (nVaryingA > 0) updateParametersA(deltaAi); if (nVaryingB > 0) updateParametersB(deltaBj); if (nVaryingC > 0) updateParametersC(deltaC); this->evalResidual(residuals2); for (int k = 0; k < _nMeasurements; ++k) scaleVectorIP(weights[k], residuals2[k]); double const newErr = squaredResidual(residuals2); rho = err - newErr; if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: |new residual|^2 = " << newErr << endl; #if !defined(USE_MULTIPLICATIVE_UPDATE) double const denom1 = lambda * deltaSqrLength; #else double denom1 = 0.0f; for (int i = _nNonvaryingA; i < _nParametersA; ++i) for (int l = 0; l < _paramDimensionA; ++l) denom1 += deltaAi[i][l] * deltaAi[i][l] * diagUi[i-_nNonvaryingA][l]; for (int j = _nNonvaryingB; j < _nParametersB; ++j) for (int l = 0; l < _paramDimensionB; ++l) denom1 += deltaBj[j][l] * deltaBj[j][l] * diagVj[j-_nNonvaryingB][l]; for (int l = _nNonvaryingC; l < _paramDimensionC; ++l) denom1 += deltaC[l] * deltaC[l] * diagZ[l-_nNonvaryingC]; denom1 *= lambda; #endif double const denom2 = innerProduct(delta, Jt_e); rho = rho / (denom1 + denom2); if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: rho = " << rho << " denom1 = " << denom1 << " denom2 = " << denom2 << endl; } // end if (success) if (success && rho > 0) { if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: Improved solution - decreasing lambda." << endl; // Improvement in the new solution decreaseLambda(rho); computeDerivatives = true; } else { if (optimizerVerbosenessLevel >= 2) cout << "SparseLevenbergOptimizer: Inferior solution - increasing lambda." << endl; restoreAllParameters(); increaseLambda(); computeDerivatives = false; // Restore diagonal elements in Ui, Vj and Z. for (int i = 0; i < nVaryingA; ++i) { for (int l = 0; l < _paramDimensionA; ++l) Ui[i][l][l] = diagUi[i][l]; } // end for (i) for (int j = 0; j < nVaryingB; ++j) { for (int l = 0; l < _paramDimensionB; ++l) Vj[j][l][l] = diagVj[j][l]; } // end for (j) for (int l = 0; l < nVaryingC; ++l) Z[l][l] = diagZ[l]; } // end if } // end for end:; if (optimizerVerbosenessLevel >= 2) cout << "Leaving SparseLevenbergOptimizer::minimize()." << endl; } // end SparseLevenbergOptimizer::minimize() #endif // defined(V3DLIB_ENABLE_SUITESPARSE) } // end namespace V3D