Base<F> Norm( const Matrix<F>& A, NormType type ) { DEBUG_CSE Base<F> norm = 0; switch( type ) { // The following norms are rather cheap to compute case ENTRYWISE_ONE_NORM: norm = EntrywiseNorm( A, Base<F>(1) ); break; case FROBENIUS_NORM: norm = FrobeniusNorm( A ); break; case INFINITY_NORM: norm = InfinityNorm( A ); break; case MAX_NORM: norm = MaxNorm( A ); break; case ONE_NORM: norm = OneNorm( A ); break; // The following two norms make use of an SVD case NUCLEAR_NORM: norm = NuclearNorm( A ); break; case TWO_NORM: norm = TwoNorm( A ); break; } return norm; }
void FrobeniusProx( AbstractDistMatrix<Field>& A, const Base<Field>& tau ) { EL_DEBUG_CSE const Base<Field> frobNorm = FrobeniusNorm( A ); if( frobNorm > 1/tau ) A *= 1-1/(tau*frobNorm); else Zero( A ); }
Base<Field> TransposedCoordinatesToNorm ( const Matrix<Base<Field>>& d, const Matrix<Field>& NTrans, const Matrix<Field>& v ) { EL_DEBUG_CSE Matrix<Field> z( v ); Trmv( LOWER, TRANSPOSE, UNIT, NTrans, z ); DiagonalScale( LEFT, NORMAL, d, z ); return FrobeniusNorm( z ); }
Base<Field> CoordinatesToNorm ( const Matrix<Base<Field>>& d, const Matrix<Field>& N, const Matrix<Field>& v ) { EL_DEBUG_CSE Matrix<Field> z( v ); Trmv( UPPER, NORMAL, UNIT, N, z ); DiagonalScale( LEFT, NORMAL, d, z ); return FrobeniusNorm( z ); }
int main(int argc, char *argv[]) { // tests(); // srand(time(10)); srand(10); if(argc < 4) { std::cout << "Input arguments:\n\t1: Filename for A matrix (as in A ~= WH)\n\t2: New desired dimension\n\t3: Max NMF iterations\n\t4: Max NNLS iterations\n\t5 (optional): delimiter (space is default)\n"; } else { std::string filename = argv[1]; int newDimension = atoi(argv[2]); int max_iter_nmf = atoi(argv[3]); int max_iter_nnls = atoi(argv[4]); char delimiter = (argc > 5) ? *argv[5] : ' '; DenseMatrix* A = readMatrix(filename,delimiter); A->copyColumnToRow(); printf("Sparsity of A: %f\n",sparsity(A)); DenseMatrix W = DenseMatrix(A->rows,newDimension); DenseMatrix H = DenseMatrix(newDimension,A->cols); NMF_Input input = NMF_Input(&W,&H,A,max_iter_nmf,max_iter_nnls); std::cout << "Starting NMF computation." << std::endl; std::clock_t start = std::clock(); double duration; // nmf_cpu(input); nmf_cpu_profile(input); duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC; std::cout << "NMF computation complete. Time: " << duration << " s." << std::endl; W.copyColumnToRow(); dtype AF = FrobeniusNorm(A); dtype WH_AF = Fnorm(W,H,*A); printf("Objective value: %f\n",WH_AF/AF); // DenseMatrix z1 = DenseMatrix(A->rows,newDimension); // DenseMatrix z2 = DenseMatrix(newDimension,A->cols); // printf("Calculated solution approximate Frobenius norm: %f\n",Fnorm(z1,z2,*A)); // printcolmajor(H.colmajor,H.rows,H.cols); if(A) delete A; } return 0; }
/* Returns the Matrix A diagonalized. The Matrix V is such that * * V'*A*V is diagonal */ void Jacobi(Matrix *A, Matrix *V, double tolerance) { register int i,j,N ; h_schur S ; int itmax=1000; int iter = 0 ; register double eps,off,old_off ; N = A->N ; /* Initialize V to the unit matrix */ for(i=0;i<N;i++) { V->M[i][i].real = 1.0 ; V->M[i][i].imag = 0.0 ; for(j=i+1;j<N;j++) V->M[i][j].real=V->M[i][j].imag=V->M[j][i].real=V->M[j][i].imag = 0.0 ; } eps = FrobeniusNorm(A)*tolerance ; #ifdef DEBUG printf("In jacobi::Jacobi -- convergence eps: %g\n",eps) ; printf("%i off(A): %g\n", iter, OffDiag(A) ) ; #endif old_off = 1.0e+32 ; off = OffDiag(A) ; while((off>eps)&&(fabs(old_off-off)>eps)&&(iter<itmax)) { iter++ ; for(i=0;i<N;i++) for(j=i+1;j<N;j++) { HermitianSchur(A, i, j, &S) ; rightMultiplySchur(A, &S) ; leftMultiplySchur(A, &S) ; rightMultiplySchur(V, &S) ; } old_off = off ; off = OffDiag(A) ; #ifdef DEBUG printf("%i off(A): %g\n", iter, off ) ; #endif } if(iter==itmax)printf("Jacobi didn't converge in %d steps\n",iter); }
which can be found in the LICENSE file in the root directory, or at http://opensource.org/licenses/BSD-2-Clause */ #include "El.hpp" // The Frobenius norm prox returns the solution to // arg min || A ||_F + tau/2 || A - A0 ||_F^2 // A namespace El { template<typename F> void FrobeniusProx( Matrix<F>& A, Base<F> tau ) { DEBUG_ONLY(CSE cse("FrobeniusProx")) const Base<F> frobNorm = FrobeniusNorm( A ); if( frobNorm > 1/tau ) A *= 1-1/(tau*frobNorm); else Zero( A ); } template<typename F> void FrobeniusProx( AbstractDistMatrix<F>& A, Base<F> tau ) { DEBUG_ONLY(CSE cse("FrobeniusProx")) const Base<F> frobNorm = FrobeniusNorm( A ); if( frobNorm > 1/tau ) A *= 1-1/(tau*frobNorm); else Zero( A );
which can be found in the LICENSE file in the root directory, or at http://opensource.org/licenses/BSD-2-Clause */ #pragma once #ifndef EL_CONDITION_FROBENIUS_HPP #define EL_CONDITION_FROBENIUS_HPP namespace El { template<typename F> Base<F> FrobeniusCondition( const Matrix<F>& A ) { DEBUG_ONLY(CallStackEntry cse("FrobeniusCondition")) typedef Base<F> Real; Matrix<F> B( A ); const Real oneNorm = FrobeniusNorm( B ); try { Inverse( B ); } catch( SingularMatrixException& e ) { return std::numeric_limits<Real>::infinity(); } const Real oneNormInv = FrobeniusNorm( B ); return oneNorm*oneNormInv; } template<typename F> Base<F> FrobeniusCondition( const AbstractDistMatrix<F>& A ) { DEBUG_ONLY(CallStackEntry cse("FrobeniusCondition")) typedef Base<F> Real; DistMatrix<F> B( A ); const Real oneNorm = FrobeniusNorm( B ); try { Inverse( B ); }
bool LatticeCoordinates ( const Matrix<F>& B, const Matrix<F>& y, Matrix<F>& x ) { DEBUG_CSE typedef Base<F> Real; const Int m = B.Height(); const Int n = B.Width(); if( y.Height() != m || y.Width() != 1 ) LogicError("y should have been an ",m," x 1 vector"); if( FrobeniusNorm(y) == Real(0) ) { Zeros( x, n, 1 ); return true; } Matrix<F> BRed( B ); Matrix<F> UB, RB; auto infoB = LLL( BRed, UB, RB ); auto MB = BRed( ALL, IR(0,infoB.rank) ); Matrix<F> A; Zeros( A, m, infoB.rank+1 ); { auto AL = A( ALL, IR(0,infoB.rank) ); auto aR = A( ALL, IR(infoB.rank) ); AL = MB; aR = y; } // Reduce A in-place Matrix<F> UA, RA; auto infoA = LLL( A, UA, RA ); if( infoA.nullity != 1 ) return false; // Solve for x_M such that M_B x_M = y // NOTE: The last column of U_A should hold the coordinates of the single // member of the null-space of (the original) A Matrix<F> xM; xM = UA( IR(0,infoA.rank), IR(infoB.rank) ); const F gamma = UA(infoA.rank,infoB.rank); if( Abs(gamma) != Real(1) ) LogicError("Invalid member of null space"); else xM *= -Conj(gamma); // Map xM back to the original coordinates using the portion of the // unimodular transformation of B (U_B) which produced the image of B auto UBM = UB( ALL, IR(0,infoB.rank) ); Zeros( x, n, 1 ); Gemv( NORMAL, F(1), UBM, xM, F(0), x ); /* if( infoB.nullity != 0 ) { Matrix<F> C; Zeros( C, m, infoB.nullity+1 ); auto cL = C( ALL, IR(infoB.rank-1) ); auto CR = C( ALL, IR(infoB.rank,END) ); // Reduce the kernel of B CR = UB( ALL, IR(infoB.rank,END) ); LLL( CR ); // Attempt to reduce the (reduced) kernel out of the coordinates // TODO: Which column to grab from the result?!? cL = x; LLL( C ); x = cL; } */ return true; }
Int ADMM ( const Matrix<Real>& A, const Matrix<Real>& b, const Matrix<Real>& c, Matrix<Real>& z, const ADMMCtrl<Real>& ctrl ) { EL_DEBUG_CSE // Cache a custom partially-pivoted LU factorization of // | rho*I A^H | = | B11 B12 | // | A 0 | | B21 B22 | // by (justifiably) avoiding pivoting in the first n steps of // the factorization, so that // [I,rho*I] = lu(rho*I). // The factorization would then proceed with // B21 := B21 U11^{-1} = A (rho*I)^{-1} = A/rho // B12 := L11^{-1} B12 = I A^H = A^H. // The Schur complement would then be // B22 := B22 - B21 B12 = 0 - (A*A^H)/rho. // We then factor said matrix with LU with partial pivoting and // swap the necessary rows of B21 in order to implicitly commute // the row pivots with the Gauss transforms in the manner standard // for GEPP. Unless A A' is singular, pivoting should not be needed, // as Cholesky factorization of the negative matrix should be valid. // // The result is the factorization // | I 0 | | rho*I A^H | = | I 0 | | rho*I U12 |, // | 0 P22 | | A 0 | | L21 L22 | | 0 U22 | // where [L22,U22] are stored within B22. Matrix<Real> U12, L21, B22, bPiv; Adjoint( A, U12 ); L21 = A; L21 *= 1/ctrl.rho; Herk( LOWER, NORMAL, -1/ctrl.rho, A, B22 ); MakeHermitian( LOWER, B22 ); // TODO: Replace with sparse-direct Cholesky version? Permutation P2; LU( B22, P2 ); P2.PermuteRows( L21 ); bPiv = b; P2.PermuteRows( bPiv ); // Possibly form the inverse of L22 U22 Matrix<Real> X22; if( ctrl.inv ) { X22 = B22; MakeTrapezoidal( LOWER, X22 ); FillDiagonal( X22, Real(1) ); TriangularInverse( LOWER, UNIT, X22 ); Trsm( LEFT, UPPER, NORMAL, NON_UNIT, Real(1), B22, X22 ); } Int numIter=0; const Int m = A.Height(); const Int n = A.Width(); Matrix<Real> g, xTmp, y, t; Zeros( g, m+n, 1 ); PartitionDown( g, xTmp, y, n ); Matrix<Real> x, u, zOld, xHat; Zeros( z, n, 1 ); Zeros( u, n, 1 ); Zeros( t, n, 1 ); while( numIter < ctrl.maxIter ) { zOld = z; // Find x from // | rho*I A^H | | x | = | rho*(z-u)-c | // | A 0 | | y | | b | // via our cached custom factorization: // // |x| = inv(U) inv(L) P' |rho*(z-u)-c| // |y| |b | // = |rho*I U12|^{-1} |I 0 | |I 0 | |rho*(z-u)-c| // = |0 U22| |L21 L22| |0 P22'| |b | // = " " |rho*(z-u)-c| // | P22' b | xTmp = z; xTmp -= u; xTmp *= ctrl.rho; xTmp -= c; y = bPiv; Gemv( NORMAL, Real(-1), L21, xTmp, Real(1), y ); if( ctrl.inv ) { Gemv( NORMAL, Real(1), X22, y, t ); y = t; } else { Trsv( LOWER, NORMAL, UNIT, B22, y ); Trsv( UPPER, NORMAL, NON_UNIT, B22, y ); } Gemv( NORMAL, Real(-1), U12, y, Real(1), xTmp ); xTmp *= 1/ctrl.rho; // xHat := alpha*x + (1-alpha)*zOld xHat = xTmp; xHat *= ctrl.alpha; Axpy( 1-ctrl.alpha, zOld, xHat ); // z := pos(xHat+u) z = xHat; z += u; LowerClip( z, Real(0) ); // u := u + (xHat-z) u += xHat; u -= z; const Real objective = Dot( c, xTmp ); // rNorm := || x - z ||_2 t = xTmp; t -= z; const Real rNorm = FrobeniusNorm( t ); // sNorm := |rho| || z - zOld ||_2 t = z; t -= zOld; const Real sNorm = Abs(ctrl.rho)*FrobeniusNorm( t ); const Real epsPri = Sqrt(Real(n))*ctrl.absTol + ctrl.relTol*Max(FrobeniusNorm(xTmp),FrobeniusNorm(z)); const Real epsDual = Sqrt(Real(n))*ctrl.absTol + ctrl.relTol*Abs(ctrl.rho)*FrobeniusNorm(u); if( ctrl.print ) { t = xTmp; LowerClip( t, Real(0) ); t -= xTmp; const Real clipDist = FrobeniusNorm( t ); cout << numIter << ": " << "||x-z||_2=" << rNorm << ", " << "epsPri=" << epsPri << ", " << "|rho| ||z-zOld||_2=" << sNorm << ", " << "epsDual=" << epsDual << ", " << "||x-Pos(x)||_2=" << clipDist << ", " << "c'x=" << objective << endl; } if( rNorm < epsPri && sNorm < epsDual ) break; ++numIter; } if( ctrl.maxIter == numIter ) cout << "ADMM failed to converge" << endl; x = xTmp; return numIter; }
void Flush() { if( numQueued_ == 0 ) return; auto YActive = Y_( ALL, IR(0,numQueued_) ); Matrix<Base<Field>> colNorms; if( useTranspose_ ) { // TODO(poulson): Add this as an option /* Timer timer; timer.Start(); BatchTransposedSparseToCoordinates ( NTrans_, YActive, VCand_, blocksize_ ); const double transformTime = timer.Stop(); const double n = YActive.Height(); const double transformGflops = double(numQueued_)*n*n/(1.e9*transformTime); Output (numQueued_," transforms: ",timer.Stop()," seconds (", transformGflops," GFlop/s"); timer.Start(); colNorms = BatchTransposedCoordinatesToNorms ( d_, NTrans_, VCand_, insertionBound_ ); const double normTime = timer.Stop(); const double normGflops = double(numQueued_)*n*n/(1.e9*normTime); Output (numQueued_," norms: ",timer.Stop()," seconds (", normGflops," GFlop/s"); */ BatchTransposedSparseToCoordinates ( NTrans_, YActive, VCand_, blocksize_ ); colNorms = BatchTransposedCoordinatesToNorms ( d_, NTrans_, VCand_, insertionBound_ ); } else { BatchSparseToCoordinates( N_, YActive, VCand_, blocksize_ ); colNorms = BatchCoordinatesToNorms( d_, N_, VCand_, insertionBound_ ); } for( Int j=0; j<numQueued_; ++j ) { for( Int k=0; k<insertionBound_; ++k ) { const Base<Field> bNorm = colNorms(j,k); if( bNorm < normUpperBounds_(k) && bNorm != Base<Field>(0) ) { const Range<Int> subInd(k,END); auto y = YActive(subInd,IR(j)); auto vCand = VCand_(subInd,IR(j)); Output ("normUpperBound=",normUpperBounds_(k), ", bNorm=",bNorm,", k=",k); Print( y, "y" ); // Check that the reverse transformation holds Matrix<Field> yCheck; CoordinatesToSparse( N_(subInd,subInd), vCand, yCheck ); yCheck -= y; if( FrobeniusNorm(yCheck) != Base<Field>(0) ) { Print( B_(ALL,subInd), "B" ); Print( d_(subInd,ALL), "d" ); Print( N_(subInd,subInd), "N" ); Print( vCand, "vCand" ); Print( yCheck, "eCheck" ); LogicError("Invalid sparse transformation"); } Copy( vCand, v_ ); Print( v_, "v" ); Matrix<Field> b; Zeros( b, B_.Height(), 1 ); Gemv( NORMAL, Field(1), B_(ALL,subInd), v_, Field(0), b ); Print( b, "b" ); normUpperBounds_(k) = bNorm; foundVector_ = true; insertionBound_ = k+1; } // TODO(poulson): Keep track of 'stock' vectors? } } numQueued_ = 0; Zero( Y_ ); }
QDWHInfo QDWHInner( Matrix<F>& A, Base<F> sMinUpper, const QDWHCtrl& ctrl ) { EL_DEBUG_CSE typedef Base<F> Real; typedef Complex<Real> Cpx; const Int m = A.Height(); const Int n = A.Width(); const Real oneThird = Real(1)/Real(3); if( m < n ) LogicError("Height cannot be less than width"); QDWHInfo info; QRCtrl<Base<F>> qrCtrl; qrCtrl.colPiv = ctrl.colPiv; const Real eps = limits::Epsilon<Real>(); const Real tol = 5*eps; const Real cubeRootTol = Pow(tol,oneThird); Real L = sMinUpper / Sqrt(Real(n)); Real frobNormADiff; Matrix<F> ALast, ATemp, C; Matrix<F> Q( m+n, n ); auto QT = Q( IR(0,m ), ALL ); auto QB = Q( IR(m,END), ALL ); while( info.numIts < ctrl.maxIts ) { ALast = A; Real L2; Cpx dd, sqd; if( Abs(1-L) < tol ) { L2 = 1; dd = 0; sqd = 1; } else { L2 = L*L; dd = Pow( 4*(1-L2)/(L2*L2), oneThird ); sqd = Sqrt( Real(1)+dd ); } const Cpx arg = Real(8) - Real(4)*dd + Real(8)*(2-L2)/(L2*sqd); const Real a = (sqd + Sqrt(arg)/Real(2)).real(); const Real b = (a-1)*(a-1)/4; const Real c = a+b-1; const Real alpha = a-b/c; const Real beta = b/c; L = L*(a+b*L2)/(1+c*L2); if( c > 100 ) { // // The standard QR-based algorithm // QT = A; QT *= Sqrt(c); MakeIdentity( QB ); qr::ExplicitUnitary( Q, true, qrCtrl ); Gemm( NORMAL, ADJOINT, F(alpha/Sqrt(c)), QT, QB, F(beta), A ); ++info.numQRIts; } else { // // Use faster Cholesky-based algorithm since A is well-conditioned // Identity( C, n, n ); Herk( LOWER, ADJOINT, c, A, Real(1), C ); Cholesky( LOWER, C ); ATemp = A; Trsm( RIGHT, LOWER, ADJOINT, NON_UNIT, F(1), C, ATemp ); Trsm( RIGHT, LOWER, NORMAL, NON_UNIT, F(1), C, ATemp ); A *= beta; Axpy( alpha, ATemp, A ); ++info.numCholIts; } ++info.numIts; ALast -= A; frobNormADiff = FrobeniusNorm( ALast ); if( frobNormADiff <= cubeRootTol && Abs(1-L) <= tol ) break; } return info; }
inline void ADMM ( const Matrix<F>& M, Matrix<F>& L, Matrix<F>& S, const RPCACtrl<Base<F>>& ctrl ) { typedef Base<F> Real; const Int m = M.Height(); const Int n = M.Width(); // If tau is not specified, then set it to 1/sqrt(max(m,n)) const Base<F> tau = ( ctrl.tau <= Real(0) ? Real(1)/sqrt(Real(Max(m,n))) : ctrl.tau ); if( ctrl.beta <= Real(0) ) LogicError("beta cannot be non-positive"); if( ctrl.tol <= Real(0) ) LogicError("tol cannot be non-positive"); const Base<F> beta = ctrl.beta; const Base<F> tol = ctrl.tol; const double startTime = mpi::Time(); Matrix<F> E, Y; Zeros( Y, m, n ); const Real frobM = FrobeniusNorm( M ); const Real maxM = MaxNorm( M ); if( ctrl.progress ) cout << "|| M ||_F = " << frobM << "\n" << "|| M ||_max = " << maxM << endl; Zeros( L, m, n ); Zeros( S, m, n ); Int numIts = 0; while( true ) { ++numIts; // ST_{tau/beta}(M - L + Y/beta) S = M; S -= L; Axpy( F(1)/beta, Y, S ); SoftThreshold( S, tau/beta ); const Int numNonzeros = ZeroNorm( S ); // SVT_{1/beta}(M - S + Y/beta) L = M; L -= S; Axpy( F(1)/beta, Y, L ); Int rank; if( ctrl.usePivQR ) rank = SVT( L, Real(1)/beta, ctrl.numPivSteps ); else rank = SVT( L, Real(1)/beta ); // E := M - (L + S) E = M; E -= L; E -= S; const Real frobE = FrobeniusNorm( E ); if( frobE/frobM <= tol ) { if( ctrl.progress ) cout << "Converged after " << numIts << " iterations " << " with rank=" << rank << ", numNonzeros=" << numNonzeros << " and " << "|| E ||_F / || M ||_F = " << frobE/frobM << ", and " << mpi::Time()-startTime << " total secs" << endl; break; } else if( numIts >= ctrl.maxIts ) { if( ctrl.progress ) cout << "Aborting after " << numIts << " iterations and " << mpi::Time()-startTime << " total secs" << endl; break; } else { if( ctrl.progress ) cout << numIts << ": || E ||_F / || M ||_F = " << frobE/frobM << ", rank=" << rank << ", numNonzeros=" << numNonzeros << ", " << mpi::Time()-startTime << " total secs" << endl; } // Y := Y + beta E Axpy( beta, E, Y ); } }
inline void ALM ( const ElementalMatrix<F>& MPre, ElementalMatrix<F>& LPre, ElementalMatrix<F>& SPre, const RPCACtrl<Base<F>>& ctrl ) { DistMatrixReadProxy<F,F,MC,MR> MProx( MPre ); DistMatrixWriteProxy<F,F,MC,MR> LProx( LPre ), SProx( SPre ); auto& M = MProx.GetLocked(); auto& L = LProx.Get(); auto& S = SProx.Get(); typedef Base<F> Real; const Int m = M.Height(); const Int n = M.Width(); const int commRank = mpi::Rank( M.Grid().Comm() ); // If tau is unspecified, set it to 1/sqrt(max(m,n)) const Base<F> tau = ( ctrl.tau <= Real(0) ? Real(1) / sqrt(Real(Max(m,n))) : ctrl.tau ); if( ctrl.tol <= Real(0) ) LogicError("tol cannot be non-positive"); const Base<F> tol = ctrl.tol; const double startTime = mpi::Time(); DistMatrix<F> Y( M ); NormalizeEntries( Y ); const Real twoNorm = TwoNorm( Y ); const Real maxNorm = MaxNorm( Y ); const Real infNorm = maxNorm / tau; const Real dualNorm = Max( twoNorm, infNorm ); Y *= F(1)/dualNorm; // If beta is unspecified, set it to 1 / 2 || sign(M) ||_2 Base<F> beta = ( ctrl.beta <= Real(0) ? Real(1) / (2*twoNorm) : ctrl.beta ); const Real frobM = FrobeniusNorm( M ); const Real maxM = MaxNorm( M ); if( ctrl.progress && commRank == 0 ) cout << "|| M ||_F = " << frobM << "\n" << "|| M ||_max = " << maxM << endl; Zeros( L, m, n ); Zeros( S, m, n ); Int numIts=0, numPrimalIts=0; DistMatrix<F> LLast( M.Grid() ), SLast( M.Grid() ), E( M.Grid() ); while( true ) { ++numIts; Int rank, numNonzeros; while( true ) { ++numPrimalIts; LLast = L; SLast = S; // ST_{tau/beta}(M - L + Y/beta) S = M; S -= L; Axpy( F(1)/beta, Y, S ); SoftThreshold( S, tau/beta ); numNonzeros = ZeroNorm( S ); // SVT_{1/beta}(M - S + Y/beta) L = M; L -= S; Axpy( F(1)/beta, Y, L ); if( ctrl.usePivQR ) rank = SVT( L, Real(1)/beta, ctrl.numPivSteps ); else rank = SVT( L, Real(1)/beta ); LLast -= L; SLast -= S; const Real frobLDiff = FrobeniusNorm( LLast ); const Real frobSDiff = FrobeniusNorm( SLast ); if( frobLDiff/frobM < tol && frobSDiff/frobM < tol ) { if( ctrl.progress && commRank == 0 ) cout << "Primal loop converged: " << mpi::Time()-startTime << " total secs" << endl; break; } else { if( ctrl.progress && commRank == 0 ) cout << " " << numPrimalIts << ": \n" << " || Delta L ||_F / || M ||_F = " << frobLDiff/frobM << "\n" << " || Delta S ||_F / || M ||_F = " << frobSDiff/frobM << "\n" << " rank=" << rank << ", numNonzeros=" << numNonzeros << ", " << mpi::Time()-startTime << " total secs" << endl; } } // E := M - (L + S) E = M; E -= L; E -= S; const Real frobE = FrobeniusNorm( E ); if( frobE/frobM <= tol ) { if( ctrl.progress && commRank == 0 ) cout << "Converged after " << numIts << " iterations and " << numPrimalIts << " primal iterations with rank=" << rank << ", numNonzeros=" << numNonzeros << " and " << "|| E ||_F / || M ||_F = " << frobE/frobM << ", " << mpi::Time()-startTime << " total secs" << endl; break; } else if( numIts >= ctrl.maxIts ) { if( ctrl.progress && commRank == 0 ) cout << "Aborting after " << numIts << " iterations and " << mpi::Time()-startTime << " total secs" << endl; break; } else { if( ctrl.progress && commRank == 0 ) cout << numPrimalIts << ": || E ||_F / || M ||_F = " << frobE/frobM << ", rank=" << rank << ", numNonzeros=" << numNonzeros << ", " << mpi::Time()-startTime << " total secs" << endl; } // Y := Y + beta E Axpy( beta, E, Y ); beta *= ctrl.rho; } }
inline void ADMM ( const AbstractDistMatrix<F>& MPre, AbstractDistMatrix<F>& LPre, AbstractDistMatrix<F>& SPre, const RPCACtrl<Base<F>>& ctrl ) { auto MPtr = ReadProxy<F,MC,MR>( &MPre ); auto& M = *MPtr; auto LPtr = WriteProxy<F,MC,MR>( &LPre ); auto& L = *LPtr; auto SPtr = WriteProxy<F,MC,MR>( &SPre ); auto& S = *SPtr; typedef Base<F> Real; const Int m = M.Height(); const Int n = M.Width(); const Int commRank = mpi::Rank( M.Grid().Comm() ); // If tau is not specified, then set it to 1/sqrt(max(m,n)) const Base<F> tau = ( ctrl.tau <= Real(0) ? Real(1)/sqrt(Real(Max(m,n))) : ctrl.tau ); if( ctrl.beta <= Real(0) ) LogicError("beta cannot be non-positive"); if( ctrl.tol <= Real(0) ) LogicError("tol cannot be non-positive"); const Base<F> beta = ctrl.beta; const Base<F> tol = ctrl.tol; const double startTime = mpi::Time(); DistMatrix<F> E( M.Grid() ), Y( M.Grid() ); Zeros( Y, m, n ); const Real frobM = FrobeniusNorm( M ); const Real maxM = MaxNorm( M ); if( ctrl.progress && commRank == 0 ) cout << "|| M ||_F = " << frobM << "\n" << "|| M ||_max = " << maxM << endl; Zeros( L, m, n ); Zeros( S, m, n ); Int numIts = 0; while( true ) { ++numIts; // ST_{tau/beta}(M - L + Y/beta) S = M; Axpy( F(-1), L, S ); Axpy( F(1)/beta, Y, S ); SoftThreshold( S, tau/beta ); const Int numNonzeros = ZeroNorm( S ); // SVT_{1/beta}(M - S + Y/beta) L = M; Axpy( F(-1), S, L ); Axpy( F(1)/beta, Y, L ); Int rank; if( ctrl.usePivQR ) rank = SVT( L, Real(1)/beta, ctrl.numPivSteps ); else rank = SVT( L, Real(1)/beta ); // E := M - (L + S) E = M; Axpy( F(-1), L, E ); Axpy( F(-1), S, E ); const Real frobE = FrobeniusNorm( E ); if( frobE/frobM <= tol ) { if( ctrl.progress && commRank == 0 ) cout << "Converged after " << numIts << " iterations " << " with rank=" << rank << ", numNonzeros=" << numNonzeros << " and " << "|| E ||_F / || M ||_F = " << frobE/frobM << ", and " << mpi::Time()-startTime << " total secs" << endl; break; } else if( numIts >= ctrl.maxIts ) { if( ctrl.progress && commRank == 0 ) cout << "Aborting after " << numIts << " iterations and " << mpi::Time()-startTime << " total secs" << endl; break; } else { if( ctrl.progress && commRank == 0 ) cout << numIts << ": || E ||_F / || M ||_F = " << frobE/frobM << ", rank=" << rank << ", numNonzeros=" << numNonzeros << ", " << mpi::Time()-startTime << " total secs" << endl; } // Y := Y + beta E Axpy( beta, E, Y ); } }