bool NnlsHals(const MatrixType<T>& A, DenseMatrix<T>& W, DenseMatrix<T>& H, const T tol, const bool verbose, const unsigned int max_iter) { unsigned int n = A.Width(); unsigned int k = W.Width(); if (static_cast<unsigned int>(W.Height()) != static_cast<unsigned int>(A.Height())) throw std::logic_error("NnlsHals: W and A must have identical height"); if (static_cast<unsigned int>(H.Width()) != static_cast<unsigned int>(A.Width())) throw std::logic_error("NnlsHals: H and A must have identical width"); if (H.Height() != W.Width()) throw std::logic_error("NnlsHals: non-conformant W and H"); DenseMatrix<T> WtW(k, k), WtA(k, n), WtWH_r(1, n), gradH(k, n); if (verbose) std::cout << "\nRunning NNLS solver..." << std::endl; // compute W'W and W'A for the normal equations Gemm(TRANSPOSE, NORMAL, T(1.0), W, W, T(0.0), WtW); Gemm(TRANSPOSE, NORMAL, T(1.0), W, A, T(0.0), WtA); bool success = false; T pg0 = T(0), pg; for (unsigned int i=0; i<max_iter; ++i) { // compute the new matrix H UpdateH_Hals(H, WtWH_r, WtW, WtA); // compute gradH = WtW*H - WtA Gemm(NORMAL, NORMAL, T(1.0), WtW, H, T(0.0), gradH); Axpy( T(-1.0), WtA, gradH); // compute progress metric if (0 == i) { pg0 = ProjectedGradientNorm(gradH, H); if (verbose) ReportProgress(i+1, T(1.0)); continue; } else { pg = ProjectedGradientNorm(gradH, H); } if (verbose) ReportProgress(i+1, pg/pg0); // check progress vs. desired tolerance if (pg < tol * pg0) { success = true; NormalizeAndScale<T>(W, H); break; } } if (!success) std::cerr << "NNLS solver reached iteration limit." << std::endl; return success; }
mat nnls_solver_with_missing(const mat & A, const mat & W, const mat & W1, const mat & H2, const umat & mask, const double & eta, const double & beta, int max_iter, double rel_tol, int n_threads) { // A = [W, W1, W2] [H, H1, H2]^T. // Where A may have missing values // Note that here in the input W = [W, W2] // compute x = [H, H1]^T given W, W2 // A0 = W2*H2 is empty when H2 is empty (no partial info in H) // Return: x = [H, H1] int n = A.n_rows, m = A.n_cols; int k = W.n_cols - H2.n_cols; int kW = W1.n_cols; int nH = k+kW; mat x(nH, m, fill::zeros); if (n_threads < 0) n_threads = 0; bool is_masked = !mask.empty(); #pragma omp parallel for num_threads(n_threads) schedule(dynamic) for (int j = 0; j < m; j++) { // break if all entries of col_j are masked if (is_masked && arma::all(mask.col(j))) continue; uvec non_missing = find_finite(A.col(j)); mat WtW(nH, nH); // WtW update_WtW(WtW, W.rows(non_missing), W1.rows(non_missing), H2); if (beta > 0) WtW += beta; if (eta > 0) WtW.diag() += eta; mat mu(nH, 1); // -WtA uvec jv(1); jv(0) = j; //non_missing.t().print("non_missing = "); //std::cout << "1.1" << std::endl; if (H2.empty()) update_WtA(mu, W.rows(non_missing), W1.rows(non_missing), H2, A.submat(non_missing, jv)); else update_WtA(mu, W.rows(non_missing), W1.rows(non_missing), H2.rows(j, j), A.submat(non_missing, jv)); //std::cout << "1.5" << std::endl; vec x0(nH); double tmp; int i = 0; double err1, err2 = 9999; do { x0 = x.col(j); err1 = err2; err2 = 0; for (int l = 0; l < nH; l++) { if (is_masked && mask(l,j) > 0) continue; tmp = x(l,j) - mu(l,0) / WtW(l,l); if (tmp < 0) tmp = 0; if (tmp != x(l,j)) { mu.col(0) += (tmp - x(l,j)) * WtW.col(l); } x(l,j) = tmp; tmp = std::abs(x(l,j) - x0(l)); if (tmp > err2) err2 = tmp; } } while(++i < max_iter && std::abs(err1 - err2) / (err1 + 1e-9) > rel_tol); } return x; }