GSparseMatrix* GSparseMatrix::subMatrix(int row, int col, int height, int width) { if(row < 0 || col < 0 || row + height >= (int)m_rows || col + width >= (int)m_cols || height < 0 || width < 0) ThrowError("out of range"); GSparseMatrix* pSub = new GSparseMatrix(height, width); for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) pSub->set(y, x, get(row + y, col + x)); } return pSub; }
/***********************************************************************//** * @brief Test matrix copy ***************************************************************************/ void TestGSparseMatrix::copy_matrix(void) { // Copy matrix GSparseMatrix test = m_test; // Test if original and compied matrices are correct test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test), "Test matrix copy operator", "Found:\n"+test.print()+"\nExpected:\n"+m_test.print()); // Return return; }
/***********************************************************************//** * @brief GSparseMatrix to GMatrix storage class convertor * * @param[in] matrix Sparse matrix (GSparseMatrix). * * This constructor converts a sparse matrix (of type GSparseMatrix) into a * generic matrix. As the result is generic, the conversion will succeed in * all cases. ***************************************************************************/ GMatrix::GMatrix(const GSparseMatrix& matrix) : GMatrixBase(matrix) { // Initialise class members for clean destruction init_members(); // Construct matrix alloc_members(matrix.rows(), matrix.cols()); // Fill matrix for (int col = 0; col < matrix.cols(); ++col) { for (int row = 0; row < matrix.rows(); ++row) { (*this)(row, col) = matrix(row, col); } } // Return return; }
// virtual void GNaiveBayes::trainSparse(GSparseMatrix& features, GMatrix& labels) { if(features.rows() != labels.rows()) throw Ex("Expected the features and labels to have the same number of rows"); size_t featureDims = features.cols(); GUniformRelation featureRel(featureDims, 2); beginIncrementalLearning(featureRel, labels.relation()); GVec fullRow(featureDims); for(size_t n = 0; n < features.rows(); n++) { features.fullRow(fullRow, n); for(size_t i = 0; i < featureDims; i++) { if(fullRow[i] < 1e-6) fullRow[i] = 0.0; else fullRow[i] = 1.0; } trainIncremental(fullRow, labels[n]); } }
/***********************************************************************//** * @brief GSparseMatrix to GSymMatrix storage class convertor * * @param[in] matrix Sparse matrix (GSparseMatrix). * * @exception GException::matrix_not_symmetric * Sparse matrix is not symmetric. * * Converts a sparse matrix into the symmetric storage class. If the input * matrix is not symmetric, an exception is thrown. ***************************************************************************/ GSymMatrix::GSymMatrix(const GSparseMatrix& matrix) { // Initialise class members for clean destruction init_members(); // Allocate matrix memory alloc_members(matrix.rows(), matrix.cols()); // Fill matrix for (int col = 0; col < matrix.cols(); ++col) { for (int row = col; row < matrix.rows(); ++row) { double value_ll = matrix(row,col); double value_ur = matrix(col,row); if (value_ll != value_ur) { throw GException::matrix_not_symmetric(G_CAST_SPARSEMATRIX, matrix.rows(), matrix.cols()); } (*this)(row, col) = matrix(row, col); } } // Return return; }
GSparseMatrix* GRecommenderLib::loadSparseData(const char* szFilename) { // Load the dataset by extension PathData pd; GFile::parsePath(szFilename, &pd); if(_stricmp(szFilename + pd.extStart, ".arff") == 0) { // Convert a 3-column dense ARFF file to a sparse matrix GMatrix data; data.loadArff(szFilename); if(data.cols() != 3) throw Ex("Expected 3 columns: 0) user or row-index, 1) item or col-index, 2) value or rating"); double m0 = data.columnMin(0); double r0 = data.columnMax(0) - m0; double m1 = data.columnMin(1); double r1 = data.columnMax(1) - m1; if(m0 < 0 || m0 > 1e10 || r0 < 2 || r0 > 1e10) throw Ex("Invalid row indexes"); if(m1 < 0 || m1 > 1e10 || r1 < 2 || r1 > 1e10) throw Ex("Invalid col indexes"); GSparseMatrix* pMatrix = new GSparseMatrix(size_t(m0 + r0) + 1, size_t(m1 + r1) + 1, UNKNOWN_REAL_VALUE); std::unique_ptr<GSparseMatrix> hMatrix(pMatrix); for(size_t i = 0; i < data.rows(); i++) { GVec& row = data.row(i); pMatrix->set(size_t(row[0]), size_t(row[1]), row[2]); } return hMatrix.release(); } else if(_stricmp(szFilename + pd.extStart, ".sparse") == 0) { GDom doc; doc.loadJson(szFilename); return new GSparseMatrix(doc.root()); } throw Ex("Unsupported file format: ", szFilename + pd.extStart); return NULL; }
GSparseMatrix* loadSparseData(const char* szFilename) { // Load the dataset by extension PathData pd; GFile::parsePath(szFilename, &pd); if(_stricmp(szFilename + pd.extStart, ".arff") == 0) { // Convert a 3-column dense ARFF file to a sparse matrix GMatrix* pData = GMatrix::loadArff(szFilename); if(pData->cols() != 3) ThrowError("Expected 3 columns: 0) user or row-index, 1) item or col-index, 2) value or rating"); double m0, r0, m1, r1; pData->minAndRange(0, &m0, &r0); pData->minAndRange(1, &m1, &r1); if(m0 < 0 || m0 > 1e10 || r0 < 2 || r0 > 1e10) ThrowError("Invalid row indexes"); if(m1 < 0 || m1 > 1e10 || r1 < 2 || r1 > 1e10) ThrowError("Invalid col indexes"); GSparseMatrix* pMatrix = new GSparseMatrix(size_t(m0 + r0) + 1, size_t(m1 + r1) + 1, UNKNOWN_REAL_VALUE); Holder<GSparseMatrix> hMatrix(pMatrix); for(size_t i = 0; i < pData->rows(); i++) { double* pRow = pData->row(i); pMatrix->set(size_t(pRow[0]), size_t(pRow[1]), pRow[2]); } return hMatrix.release(); } else if(_stricmp(szFilename + pd.extStart, ".sparse") == 0) { GDom doc; doc.loadJson(szFilename); return new GSparseMatrix(doc.root()); } ThrowError("Unsupported file format: ", szFilename + pd.extStart); return NULL; }
// static void GSparseMatrix::test() { // Make the data GSparseMatrix sm(4, 4); sm.set(0, 0, 2.0); sm.set(0, 2, 3.0); sm.set(1, 0, 1.0); sm.set(2, 3, -2.0); sm.set(2, 2, 5.0); sm.set(3, 1, -3.0); sm.set(3, 3, -1.0); GData* fm = sm.toFullMatrix(); Holder<GData> hFM(fm); // Do it with the full matrix GData* pU; double* pDiag; GData* pV; fm->singularValueDecomposition(&pU, &pDiag, &pV); Holder<GData> hU(pU); ArrayHolder<double> hDiag(pDiag); Holder<GData> hV(pV); // Do it with the sparse matrix GSparseMatrix* pSU; double* pSDiag; GSparseMatrix* pSV; sm.singularValueDecomposition(&pSU, &pSDiag, &pSV); Holder<GSparseMatrix> hSU(pSU); ArrayHolder<double> hSDiag(pSDiag); Holder<GSparseMatrix> hSV(pSV); // Check the results GData* pV2 = pSV->toFullMatrix(); Holder<GData> hV2(pV2); double err = pV2->sumSquaredDifference(*pV, false); if(err > 1e-6) ThrowError("Failed"); }
/***********************************************************************//** * @brief Test Cholesky decomposition ***************************************************************************/ void TestGSparseMatrix::matrix_cholesky(void) { // Setup matrix for Cholesky decomposition GSparseMatrix chol_test(5,5); chol_test(0,0) = 1.0; chol_test(0,1) = 0.2; chol_test(0,2) = 0.2; chol_test(0,3) = 0.2; chol_test(0,4) = 0.2; chol_test(1,0) = 0.2; chol_test(2,0) = 0.2; chol_test(3,0) = 0.2; chol_test(4,0) = 0.2; chol_test(1,1) = 1.0; chol_test(2,2) = 1.0; chol_test(3,3) = 1.0; chol_test(4,4) = 1.0; // Try to solve now (should not work) test_try("Try Cholesky solver without factorisation"); try { GVector vector(5); vector = chol_test.cholesky_solver(vector); test_try_failure("Expected GException::matrix_not_factorised exception."); } catch (GException::matrix_not_factorised &e) { test_try_success(); } catch (std::exception &e) { test_try_failure(e); } // Perform Cholesky decomposition GSparseMatrix cd = cholesky_decompose(chol_test); // Perform inplace Cholesky decomposition cd = chol_test; cd.cholesky_decompose(); // Test Cholesky solver (first test) GVector e0(5); GVector a0(5); e0[0] = 1.0; a0[0] = 1.0; a0[1] = 0.2; a0[2] = 0.2; a0[3] = 0.2; a0[4] = 0.2; GVector s0 = cd.cholesky_solver(a0); double res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test cholesky_solver() method - 1"); // Test Cholesky solver (second test) e0[0] = 0.0; e0[1] = 1.0; a0[0] = 0.2; a0[1] = 1.0; a0[2] = 0.0; a0[3] = 0.0; a0[4] = 0.0; s0 = cd.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test cholesky_solver() method - 2"); // Test Cholesky solver (third test) e0[1] = 0.0; e0[2] = 1.0; a0[1] = 0.0; a0[2] = 1.0; s0 = cd.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test cholesky_solver() method - 3"); // Test Cholesky solver (forth test) e0[2] = 0.0; e0[3] = 1.0; a0[2] = 0.0; a0[3] = 1.0; s0 = cd.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test cholesky_solver() method - 4"); // Test Cholesky solver (fifth test) e0[3] = 0.0; e0[4] = 1.0; a0[3] = 0.0; a0[4] = 1.0; s0 = cd.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test cholesky_solver() method - 5"); // Setup matrix for Cholesky decomposition with zero row/col GSparseMatrix chol_test_zero(6,6); chol_test_zero(0,0) = 1.0; chol_test_zero(0,1) = 0.2; chol_test_zero(0,2) = 0.2; chol_test_zero(0,4) = 0.2; chol_test_zero(0,5) = 0.2; chol_test_zero(1,0) = 0.2; chol_test_zero(2,0) = 0.2; chol_test_zero(4,0) = 0.2; chol_test_zero(5,0) = 0.2; chol_test_zero(1,1) = 1.0; chol_test_zero(2,2) = 1.0; chol_test_zero(4,4) = 1.0; chol_test_zero(5,5) = 1.0; // Test compressed Cholesky decomposition GSparseMatrix cd_zero = chol_test_zero; cd_zero.cholesky_decompose(); // Test compressed Cholesky solver (first test) e0 = GVector(6); a0 = GVector(6); e0[0] = 1.0; a0[0] = 1.0; a0[1] = 0.2; a0[2] = 0.2; a0[4] = 0.2; a0[5] = 0.2; s0 = cd_zero.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test compressed cholesky_solver() method - 1"); // Test compressed Cholesky solver (second test) e0[0] = 0.0; e0[1] = 1.0; a0[0] = 0.2; a0[1] = 1.0; a0[2] = 0.0; a0[4] = 0.0; a0[5] = 0.0; s0 = cd_zero.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test compressed cholesky_solver() method - 2"); // Test compressed Cholesky solver (third test) e0[1] = 0.0; e0[2] = 1.0; a0[1] = 0.0; a0[2] = 1.0; s0 = cd_zero.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test compressed cholesky_solver() method - 3"); // Test compressed Cholesky solver (forth test) e0[2] = 0.0; e0[4] = 1.0; a0[2] = 0.0; a0[4] = 1.0; s0 = cd_zero.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test compressed cholesky_solver() method - 4"); // Test compressed Cholesky solver (fifth test) e0[4] = 0.0; e0[5] = 1.0; a0[4] = 0.0; a0[5] = 1.0; s0 = cd_zero.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test compressed cholesky_solver() method - 5"); // Setup matrix for Cholesky decomposition with zero row/col (unsymmetric case) GSparseMatrix chol_test_zero2(6,5); chol_test_zero2(0,0) = 1.0; chol_test_zero2(0,1) = 0.2; chol_test_zero2(0,2) = 0.2; chol_test_zero2(0,3) = 0.2; chol_test_zero2(0,4) = 0.2; chol_test_zero2(1,0) = 0.2; chol_test_zero2(2,0) = 0.2; chol_test_zero2(4,0) = 0.2; chol_test_zero2(5,0) = 0.2; chol_test_zero2(1,1) = 1.0; chol_test_zero2(2,2) = 1.0; chol_test_zero2(4,3) = 1.0; chol_test_zero2(5,4) = 1.0; // Test compressed Cholesky decomposition (unsymmetric case) GSparseMatrix cd_zero2 = chol_test_zero2; cd_zero2.cholesky_decompose(); // Test compressed Cholesky solver (unsymmetric case) e0 = GVector(5); a0 = GVector(6); e0[0] = 1.0; a0[0] = 1.0; a0[1] = 0.2; a0[2] = 0.2; a0[4] = 0.2; a0[5] = 0.2; s0 = cd_zero2.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test unsymmetric compressed cholesky_solver() method - 1"); // Test compressed Cholesky solver (unsymmetric case) e0[0] = 0.0; e0[1] = 1.0; a0[0] = 0.2; a0[1] = 1.0; a0[2] = 0.0; a0[4] = 0.0; a0[5] = 0.0; s0 = cd_zero2.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test unsymmetric compressed cholesky_solver() method - 2"); // Test compressed Cholesky solver (unsymmetric case) e0[1] = 0.0; e0[2] = 1.0; a0[1] = 0.0; a0[2] = 1.0; s0 = cd_zero2.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test unsymmetric compressed cholesky_solver() method - 3"); // Test compressed Cholesky solver (unsymmetric case) e0[2] = 0.0; e0[3] = 1.0; a0[2] = 0.0; a0[4] = 1.0; s0 = cd_zero2.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test unsymmetric compressed cholesky_solver() method - 4"); // Test compressed Cholesky solver (unsymmetric case) e0[3] = 0.0; e0[4] = 1.0; a0[4] = 0.0; a0[5] = 1.0; s0 = cd_zero2.cholesky_solver(a0); res = max(abs(s0-e0)); test_value(res, 0.0, 1.0e-15, "Test unsymmetric compressed cholesky_solver() method - 5"); // Test Cholesky inverter (inplace) GSparseMatrix unit(5,5); unit(0,0) = 1.0; unit(1,1) = 1.0; unit(2,2) = 1.0; unit(3,3) = 1.0; unit(4,4) = 1.0; GSparseMatrix chol_test_inv = chol_test; chol_test_inv.cholesky_invert(); GSparseMatrix ci_product = chol_test * chol_test_inv; GSparseMatrix ci_residuals = ci_product - unit; res = (abs(ci_residuals)).max(); test_value(res, 0.0, 1.0e-15, "Test inplace Cholesky inverter"); // Test Cholesky inverter chol_test_inv = cholesky_invert(chol_test); ci_product = chol_test * chol_test_inv; ci_residuals = ci_product - unit; res = (abs(ci_residuals)).max(); test_value(res, 0.0, 1.0e-15, "Test Cholesky inverter"); // Test Cholesky inverter for compressed matrix unit = GSparseMatrix(6,6); unit(0,0) = 1.0; unit(1,1) = 1.0; unit(2,2) = 1.0; unit(4,4) = 1.0; unit(5,5) = 1.0; GSparseMatrix chol_test_zero_inv = chol_test_zero; chol_test_zero_inv.cholesky_invert(); GSparseMatrix ciz_product = chol_test_zero * chol_test_zero_inv; GSparseMatrix ciz_residuals = ciz_product - unit; res = (abs(ciz_residuals)).max(); test_value(res, 0.0, 1.0e-15, "Test compressed matrix Cholesky inverter"); // Return return; }
/***********************************************************************//** * @brief Test matrix arithmetics * * Tests matrix arithmetics. ***************************************************************************/ void TestGSparseMatrix::matrix_arithmetics(void) { // -GSparseMatrix GSparseMatrix test = -m_test; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, -1.0, 0.0), "Test -GSparseMatrix", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix += GSparseMatrix test = m_test; test += m_test; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 2.0, 0.0), "Test GSparseMatrix += GSparseMatrix", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix -= GSparseMatrix test = m_test; test -= m_test; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 0.0, 0.0), "Test GSparseMatrix -= GSparseMatrix", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix *= 3.0 test = m_test; test *= 3.0; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 3.0, 0.0), "Test GSparseMatrix *= 3.0", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix /= 3.0 test = m_test; test /= 3.0; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 1.0/3.0, 0.0), "Test GSparseMatrix /= 3.0", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix + GSparseMatrix test = m_test + m_test; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 2.0, 0.0), "Test GSparseMatrix + GSparseMatrix", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix - GSparseMatrix test = m_test - m_test; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 0.0, 0.0), "Test GSparseMatrix - GSparseMatrix", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix * 3.0 test = m_test * 3.0; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 3.0, 0.0), "Test GSparseMatrix * 3.0", "Unexpected result matrix:\n"+test.print()); // 3.0 * GSparseMatrix test = 3.0 * m_test; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 3.0, 0.0), "Test 3.0 * GSparseMatrix", "Unexpected result matrix:\n"+test.print()); // GSparseMatrix / 3.0 test = m_test / 3.0; test_assert(check_matrix(m_test), "Test source matrix"); test_assert(check_matrix(test, 1.0/3.0, 0.0), "Test GSparseMatrix / 3.0", "Unexpected result matrix:\n"+test.print()); // Test invalid matrix addition test_try("Test invalid matrix addition"); try { test = m_test; test += m_bigger; test_try_failure("Expected GException::matrix_mismatch exception."); } catch (GException::matrix_mismatch &e) { test_try_success(); } catch (std::exception &e) { test_try_failure(e); } // Return return; }
/***********************************************************************//** * @brief Test value assignment ***************************************************************************/ void TestGSparseMatrix::assign_values(void) { // Setup 3x3 matrix GSparseMatrix test(3,3); // Assignment individual values for (int i = 0; i < 3; ++i) { for (int k = 0; k < 3; ++k) { double value = i*2.0 + k*2.0; test(i,k) = value; } } // Check assignment of individual values for (int i = 0; i < 3; ++i) { for (int k = 0; k < 3; ++k) { double value = i*2.0 + k*2.0; test_value(test(i,k), value, 1.0e-10, "Test matrix element assignment"); } } // Verify range checking #ifdef G_RANGE_CHECK test_try("Verify range checking"); try { test(3,3) = 1.0; test_try_failure("Expected GException::out_of_range exception."); } catch (GException::out_of_range &e) { test_try_success(); } catch (std::exception &e) { test_try_failure(e); } #endif // Setup 10x10 matrix and keep for reference GSparseMatrix sparse(10,10); for (int i = 3; i < 5; ++i) { sparse(i,i) = 5.0; } GSparseMatrix initial = sparse; GSparseMatrix reference = sparse; // Insert column into 10 x 10 matrix using large matrix stack and the // add_col(GVector) method sparse.stack_init(100,50); GVector column(10); for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int i_min = (j < 2) ? 0 : j-2; int i_max = (j > 8) ? 10 : j+2; column = 0.0; for (int i = i_min; i < i_max; ++i) { column[i] = (i+1)*1; reference(i,col) += column[i]; } sparse.add_col(column, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test stack fill with large stack using add_col(GVector) method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Insert column into 10 x 10 matrix using small matrix stack and the // add_col(GVector) method sparse = initial; sparse.stack_init(100,3); for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int i_min = (j < 2) ? 0 : j-2; int i_max = (j > 8) ? 10 : j+2; column = 0.0; for (int i = i_min; i < i_max; ++i) { column[i] = (i+1)*1; } sparse.add_col(column, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test stack fill with small stack using add_col(GVector) method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Insert column into 10 x 10 matrix using tiny matrix stack and the // add_col(GVector) method sparse = initial; sparse.stack_init(8,3); for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int i_min = (j < 2) ? 0 : j-2; int i_max = (j > 8) ? 10 : j+2; column = 0.0; for (int i = i_min; i < i_max; ++i) { column[i] = (i+1)*1; } sparse.add_col(column, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test stack fill with tiny stack using add_col(GVector) method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Insert column into 10 x 10 matrix using no matrix stack and the // add_col(GVector) method sparse = initial; for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int i_min = (j < 2) ? 0 : j-2; int i_max = (j > 8) ? 10 : j+2; column = 0.0; for (int i = i_min; i < i_max; ++i) { column[i] = (i+1)*1; } sparse.add_col(column, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test fill using add_col(GVector) method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Set-up workspace for compressed column adding double* wrk_data = new double[10]; int* wrk_row = new int[10]; // Compressed tests test_try("Verify compressed column add_col() method"); try { // Insert column into 10 x 10 matrix using large matrix stack and the // compressed column add_col() method sparse = initial; reference = initial; sparse.stack_init(100,50); for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int inx = 0; int i_min = (j < 3) ? 0 : j-3; int i_max = (j > 8) ? 10 : j+2; for (int i = i_min; i < i_max; ++i) { wrk_data[inx] = (i+1)*3.7; wrk_row[inx] = i; reference(i,col) += wrk_data[inx]; inx++; } sparse.add_col(wrk_data, wrk_row, inx, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test stack fill with large stack using compressed add_col() method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Insert column into 10 x 10 matrix using small matrix stack and the // compressed column add_col() method sparse = initial; sparse.stack_init(100,2); for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int inx = 0; int i_min = (j < 3) ? 0 : j-3; int i_max = (j > 8) ? 10 : j+2; for (int i = i_min; i < i_max; ++i) { wrk_data[inx] = (i+1)*3.7; wrk_row[inx] = i; inx++; } sparse.add_col(wrk_data, wrk_row, inx, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test stack fill with small stack using compressed add_col() method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Insert column into 10 x 10 matrix using tiny matrix stack and the // compressed column add_col() method sparse = initial; sparse.stack_init(3,2); for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int inx = 0; int i_min = (j < 3) ? 0 : j-3; int i_max = (j > 8) ? 10 : j+2; for (int i = i_min; i < i_max; ++i) { wrk_data[inx] = (i+1)*3.7; wrk_row[inx] = i; inx++; } sparse.add_col(wrk_data, wrk_row, inx, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test stack fill with tiny stack using compressed add_col() method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Insert column into 10 x 10 matrix using no matrix stack and the // compressed column add_col() method sparse = initial; for (int j = 0; j < 10; ++j) { int col = int(0.8 * j + 0.5); // This allows that some columns are twice if (col > 9) col -= 10; // This avoids overflow int inx = 0; int i_min = (j < 3) ? 0 : j-3; int i_max = (j > 8) ? 10 : j+2; for (int i = i_min; i < i_max; ++i) { wrk_data[inx] = (i+1)*3.7; wrk_row[inx] = i; inx++; } sparse.add_col(wrk_data, wrk_row, inx, col); } sparse.stack_destroy(); test_assert((sparse == reference), "Test fill using compressed add_col() method", "Found:\n"+sparse.print()+"\nExpected:\n"+reference.print()); // Signal success test_try_success(); } catch (std::exception &e) { test_try_failure(e); } // Free workspace delete [] wrk_data; delete [] wrk_row; // Return return; }
void GSparseMatrix::singularValueDecompositionHelper(GSparseMatrix** ppU, double** ppDiag, GSparseMatrix** ppV, int maxIters) { int m = rows(); int n = cols(); if(m < n) ThrowError("Expected at least as many rows as columns"); int i, j, k; int l = 0; int q, iter; double c, f, h, s, x, y, z; double norm = 0.0; double g = 0.0; double scale = 0.0; GSparseMatrix* pU = new GSparseMatrix(m, m); Holder<GSparseMatrix> hU(pU); pU->copyFrom(this); double* pSigma = new double[n]; ArrayHolder<double> hSigma(pSigma); GSparseMatrix* pV = new GSparseMatrix(n, n); Holder<GSparseMatrix> hV(pV); GTEMPBUF(double, temp, n + m); double* temp2 = temp + n; // Householder reduction to bidiagonal form for(int i = 0; i < n; i++) { // Left-hand reduction temp[i] = scale * g; l = i + 1; g = 0.0; s = 0.0; scale = 0.0; if(i < m) { Iter kend = pU->colEnd(i); for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)i) scale += ABS(kk->second); } if(scale != 0.0) { for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)i) { double t = kk->second / scale; pU->set(kk->first, i, t); s += (t * t); } } f = pU->get(i, i); g = -GSparseMatrix_takeSign(sqrt(s), f); h = f * g - s; pU->set(i, i, f - g); if(i != n - 1) { for(j = l; j < n; j++) { s = 0.0; for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)i) s += kk->second * pU->get(kk->first, j); } f = s / h; for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)i) pU->set(kk->first, j, pU->get(kk->first, j) + f * kk->second); } } } for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)i) pU->set(kk->first, i, pU->get(kk->first, i) * scale); } } } pSigma[i] = scale * g; // Right-hand reduction g = 0.0; s = 0.0; scale = 0.0; if(i < m && i != n - 1) { Iter kend = pU->rowEnd(i); for(Iter kk = pU->rowBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) scale += ABS(kk->second); } if(scale != 0.0) { for(Iter kk = pU->rowBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) { double t = kk->second / scale; pU->set(i, kk->first, t); s += (t * t); } } f = pU->get(i, l); g = -GSparseMatrix_takeSign(sqrt(s), f); h = f * g - s; pU->set(i, l, f - g); for(k = l; k < n; k++) temp[k] = pU->get(i, k) / h; if(i != m - 1) { for(j = l; j < m; j++) { s = 0.0; for(Iter kk = pU->rowBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) s += pU->get(j, kk->first) * kk->second; } Iter kend2 = pU->rowEnd(j); for(Iter kk = pU->rowBegin(j); kk != kend2; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) pU->set(j, kk->first, pU->get(j, kk->first) + s * temp[kk->first]); } } } for(Iter kk = pU->rowBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) pU->set(i, kk->first, kk->second * scale); } } } norm = MAX(norm, ABS(pSigma[i]) + ABS(temp[i])); } // Accumulate right-hand transform for(int i = n - 1; i >= 0; i--) { if(i < n - 1) { if(g != 0.0) { Iter jend = pU->rowEnd(i); for(Iter jj = pU->rowBegin(i); jj != jend; jj++) { if(jj->first >= (unsigned int)n) break; if(jj->first >= (unsigned int)l) pV->set(i, jj->first, (jj->second / pU->get(i, l)) / g); // (double-division to avoid underflow) } for(j = l; j < n; j++) { s = 0.0; Iter kend = pU->rowEnd(i); for(Iter kk = pU->rowBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) s += kk->second * pV->get(j, kk->first); } kend = pV->rowEnd(i); for(Iter kk = pV->rowBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)n) break; if(kk->first >= (unsigned int)l) pV->set(j, kk->first, pV->get(j, kk->first) + s * kk->second); } } } for(j = l; j < n; j++) { pV->set(i, j, 0.0); pV->set(j, i, 0.0); } } pV->set(i, i, 1.0); g = temp[i]; l = i; } // Accumulate left-hand transform for(i = n - 1; i >= 0; i--) { l = i + 1; g = pSigma[i]; if(i < n - 1) { for(j = l; j < n; j++) pU->set(i, j, 0.0); } if(g != 0.0) { g = 1.0 / g; if(i != n - 1) { for(j = l; j < n; j++) { s = 0.0; Iter kend = pU->colEnd(i); for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)l) s += kk->second * pU->get(kk->first, j); } f = (s / pU->get(i, i)) * g; if(f != 0.0) { for(Iter kk = pU->colBegin(i); kk != kend; kk++) { if(kk->first >= (unsigned int)i) pU->set(kk->first, j, pU->get(kk->first, j) + f * kk->second); } } } } for(j = i; j < m; j++) pU->set(j, i, pU->get(j, i) * g); } else { for(j = i; j < m; j++) pU->set(j, i, 0.0); } pU->set(i, i, pU->get(i, i) + 1.0); } // Diagonalize the bidiagonal matrix for(k = n - 1; k >= 0; k--) // For each singular value { for(iter = 1; iter <= maxIters; iter++) { // Test for splitting bool flag = true; for(l = k; l >= 0; l--) { q = l - 1; if(ABS(temp[l]) + norm == norm) { flag = false; break; } if(ABS(pSigma[q]) + norm == norm) break; } if(flag) { c = 0.0; s = 1.0; for(i = l; i <= k; i++) { f = s * temp[i]; temp[i] *= c; if(ABS(f) + norm == norm) break; g = pSigma[i]; h = GSparseMatrix_pythag(f, g); pSigma[i] = h; h = 1.0 / h; c = g * h; s = -f * h; Iter jendi = pU->colEnd(i); Iter jendq = pU->colEnd(q); Iter jji = pU->colBegin(i); Iter jjq = pU->colBegin(q); int tpos; for(tpos = 0; jji != jendi || jjq != jendq; tpos++) { if(jjq == jendq || (jji != jendi && jji->first < jjq->first)) { temp2[tpos] = jji->first; jji++; } else { temp2[tpos] = jjq->first; if(jji != jendi && jjq->first == jji->first) jji++; jjq++; } } for(int tpos2 = 0; tpos2 < tpos; tpos2++) { y = pU->get((unsigned int)temp2[tpos2], q); z = pU->get((unsigned int)temp2[tpos2], i); pU->set((unsigned int)temp2[tpos2], q, y * c + z * s); pU->set((unsigned int)temp2[tpos2], i, z * c - y * s); } } } z = pSigma[k]; if(l == k) { // Detect convergence if(z < 0.0) { // Singular value should be positive pSigma[k] = -z; for(j = 0; j < n; j++) pV->set(k, j, pV->get(k, j) * -1.0); } break; } if(iter >= maxIters) ThrowError("failed to converge"); // Shift from bottom 2x2 minor x = pSigma[l]; q = k - 1; y = pSigma[q]; g = temp[q]; h = temp[k]; f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y); g = GSparseMatrix_pythag(f, 1.0); f = ((x - z) * (x + z) + h * ((y / (f + GSparseMatrix_takeSign(g, f))) - h)) / x; // QR transform c = 1.0; s = 1.0; for(j = l; j <= q; j++) { i = j + 1; g = temp[i]; y = pSigma[i]; h = s * g; g = c * g; z = GSparseMatrix_pythag(f, h); temp[j] = z; c = f / z; s = h / z; f = x * c + g * s; g = g * c - x * s; h = y * s; y = y * c; Iter pendi = pV->rowEnd(i); Iter pendj = pV->rowEnd(j); Iter ppi = pV->rowBegin(i); Iter ppj = pV->rowBegin(j); int tpos; for(tpos = 0; ppi != pendi || ppj != pendj; tpos++) { if(ppj == pendj || (ppi != pendi && ppi->first < ppj->first)) { temp2[tpos] = ppi->first; ppi++; } else { temp2[tpos] = ppj->first; if(ppi != pendi && ppj->first == ppi->first) ppi++; ppj++; } } for(int tpos2 = 0; tpos2 < tpos; tpos2++) { x = pV->get(j, (unsigned int)temp2[tpos2]); z = pV->get(i, (unsigned int)temp2[tpos2]); pV->set(j, (unsigned int)temp2[tpos2], x * c + z * s); pV->set(i, (unsigned int)temp2[tpos2], z * c - x * s); } z = GSparseMatrix_pythag(f, h); pSigma[j] = z; if(z != 0.0) { z = 1.0 / z; c = f * z; s = h * z; } f = c * g + s * y; x = c * y - s * g; pendi = pU->colEnd(i); pendj = pU->colEnd(j); ppi = pU->colBegin(i); ppj = pU->colBegin(j); for(tpos = 0; ppi != pendi || ppj != pendj; tpos++) { if(ppj == pendj || (ppi != pendi && ppi->first < ppj->first)) { temp2[tpos] = ppi->first; ppi++; } else { temp2[tpos] = ppj->first; if(ppi != pendi && ppj->first == ppi->first) ppi++; ppj++; } } for(int tpos2 = 0; tpos2 < tpos; tpos2++) { y = pU->get((unsigned int)temp2[tpos2], j); z = pU->get((unsigned int)temp2[tpos2], i); pU->set((unsigned int)temp2[tpos2], j, y * c + z * s); pU->set((unsigned int)temp2[tpos2], i, z * c - y * s); } } temp[l] = 0.0; temp[k] = f; pSigma[k] = x; } } // Sort the singular values from largest to smallest for(i = 1; i < n; i++) { for(j = i; j > 0; j--) { if(pSigma[j - 1] >= pSigma[j]) break; pU->swapColumns(j - 1, j); pV->swapRows(j - 1, j); std::swap(pSigma[j - 1], pSigma[j]); } } // Return results *ppU = hU.release(); *ppDiag = hSigma.release(); *ppV = hV.release(); }