From 87a1f19ba495cdea4c4bab3222101182532c68b5 Mon Sep 17 00:00:00 2001 From: Slaven Peles Date: Thu, 4 Jul 2024 18:21:09 -0400 Subject: [PATCH 1/3] First draft of refactored coo2csr function. --- resolve/matrix/Utilities.cpp | 227 ++++++++- resolve/matrix/Utilities.hpp | 3 + tests/unit/matrix/CMakeLists.txt | 7 +- tests/unit/matrix/MatrixConversionTests.hpp | 450 ++++++++++++++++++ .../unit/matrix/runMatrixConversionTests.cpp | 21 + 5 files changed, 705 insertions(+), 3 deletions(-) create mode 100644 tests/unit/matrix/MatrixConversionTests.hpp create mode 100644 tests/unit/matrix/runMatrixConversionTests.cpp diff --git a/resolve/matrix/Utilities.cpp b/resolve/matrix/Utilities.cpp index bd5a6f9b..cc086a5a 100644 --- a/resolve/matrix/Utilities.cpp +++ b/resolve/matrix/Utilities.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,6 +8,8 @@ namespace ReSolve { + using out = io::Logger; + /// @brief Helper class for COO matrix sorting class IndexValuePair { @@ -36,16 +39,236 @@ namespace ReSolve bool operator < (const IndexValuePair& str) const { return (idx_ < str.idx_); - } + } private: index_type idx_; real_type value_; }; - using out = io::Logger; + /// @brief Helper class for COO matrix sorting + class CooTriplet + { + public: + CooTriplet() : rowidx_(0), colidx_(0), value_(0.0) + {} + ~CooTriplet() + {} + void setColIdx (index_type new_idx) + { + colidx_ = new_idx; + } + void setValue (real_type new_value) + { + value_ = new_value; + } + void set(index_type rowidx, index_type colidx, real_type value) + { + rowidx_ = rowidx; + colidx_ = colidx; + value_ = value; + } + + index_type getRowIdx() + { + return rowidx_; + } + index_type getColIdx() + { + return colidx_; + } + real_type getValue() + { + return value_; + } + + bool operator < (const CooTriplet& str) const + { + if (rowidx_ < str.rowidx_) + return true; + + if ((rowidx_ == str.rowidx_) && (colidx_ < str.colidx_)) + return true; + + return false; + } + + bool operator == (const CooTriplet& str) const + { + return (rowidx_ == str.rowidx_) && (colidx_ == str.colidx_); + } + + CooTriplet& operator += (const CooTriplet t) + { + if ((rowidx_ != t.rowidx_) || (colidx_ != t.colidx_)) { + out::error() << "Adding values into non-matching triplet.\n"; + } + value_ += t.value_; + return *this; + } + + void print() const + { + // Add 1 to indices to restore indexing from MM format + std::cout << rowidx_ + 1 << " " << colidx_ + 1 << " " << value_ << "\n"; + } + + private: + index_type rowidx_{0}; + index_type colidx_{0}; + real_type value_{0.0}; + }; + + void print_list(std::list& l) + { + // Print out the list + std::cout << "tmp list:\n"; + for (CooTriplet& n : l) + n.print(); + std::cout << "\n"; + } + + namespace matrix { + /** + * @brief + * + * @param A_coo + * @param A_csr + * @param memspace + * @return int + * + * @pre A_coo and A_csr matrix sizes must match. + */ + int coo2csr_new(matrix::Coo* A_coo, matrix::Csr* A_csr, memory::MemorySpace memspace) + { + index_type n = A_coo->getNumRows(); + index_type m = A_coo->getNumColumns(); + index_type nnz = A_coo->getNnz(); + const index_type* rows_coo = A_coo->getRowData(memory::HOST); + const index_type* cols_coo = A_coo->getColData(memory::HOST); + const real_type* vals_coo = A_coo->getValues(memory::HOST); + index_type n_diagonal = 0; + bool is_symmetric = A_coo->symmetric(); + bool is_expanded = A_coo->expanded(); + bool is_upper_triangular = false; + bool is_lower_triangular = false; + + // Compute size of expanded matrix if it is stored as symmetric. + // Also check if matrix is upper- or lower-triangular, if it is + // defined as such. + // Complexity O(NNZ) + index_type nnz_expanded = nnz; + if (is_symmetric && !is_expanded) { + for (index_type i = 0; i cols_coo[i]); + is_upper_triangular += (rows_coo[i] < cols_coo[i]); + if (is_lower_triangular && is_upper_triangular) { + out::error() << "Input COO matrix supposed to be storred as symmetric " + << "but is neither upper- nor lower-triangular.\n" + << "Now exiting coo2csr ...\n"; + return 1; + } + } + nnz_expanded = 2*nnz - n_diagonal; + } + + // Create temporary workspace for COO to CSR conversion + // Store COO data in the workspace and expand, if needed. + // Complexity O(NNZ) + std::list tmp(nnz_expanded); + std::list::iterator it = tmp.begin(); + for (index_type i = 0; i < nnz; ++i) { + index_type row = rows_coo[i]; + index_type col = cols_coo[i]; + real_type val = vals_coo[i]; + it->set(row, col, val); + it++; + + if (is_symmetric && !is_expanded) { + if (row != col) { + it->set(col, row, val); + it++; + } + } + } + if (it != tmp.end()) { + out::error() << "NNZ computed inaccurately!\n"; + } + print_list(tmp); + std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + + // Sort tmp: Complexity NNZ*log(NNZ) + tmp.sort(); + print_list(tmp); + std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + + // Deduplicate tmp + it = tmp.begin(); + while (it != tmp.end()) + { + std::list::iterator it_tmp = it; + it++; + if (*it == *it_tmp) { + *it += *it_tmp; + tmp.erase(it_tmp); + } + } + print_list(tmp); + index_type nnz_expanded_no_duplicates = tmp.size(); + std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + + + // Convert to general CSR + A_csr->setExpanded(true); + A_csr->setNnz(nnz_expanded_no_duplicates); + A_csr->setNnzExpanded(nnz_expanded_no_duplicates); + A_csr->allocateMatrixData(memory::HOST); + index_type* rows_csr = A_csr->getRowData(memory::HOST); + index_type* cols_csr = A_csr->getColData(memory::HOST); + real_type* vals_csr = A_csr->getValues(memory::HOST); + + index_type csr_row_idx = 0; + index_type csr_row_counter = 0; + index_type csr_val_counter = 0; + + // Set first row pointer here + rows_csr[0] = csr_row_idx; + ++csr_row_counter; + + it = tmp.begin(); + while (it != tmp.end()) + { + cols_csr[csr_val_counter] = it->getColIdx(); + vals_csr[csr_val_counter] = it->getValue(); + + if (csr_row_idx != it->getRowIdx()) { + csr_row_idx = it->getRowIdx(); + ++csr_row_counter; + rows_csr[csr_row_idx] = csr_val_counter; + } + + csr_val_counter++; + it++; + } + // Set last row pointer + rows_csr[csr_row_counter] = nnz_expanded_no_duplicates; + + std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + std::cout << "Rows counted = " << csr_row_counter << "\n"; + std::cout << "NNZs counted = " << csr_val_counter << "\n\n"; + A_csr->print(); + + return 0; + } + + + /** * @brief Creates a CSR from a COO matrix. * diff --git a/resolve/matrix/Utilities.hpp b/resolve/matrix/Utilities.hpp index ecaee79e..58971972 100644 --- a/resolve/matrix/Utilities.hpp +++ b/resolve/matrix/Utilities.hpp @@ -12,5 +12,8 @@ namespace ReSolve /// @brief Converts symmetric or general COO to general CSR matrix int coo2csr(matrix::Coo* A_coo, matrix::Csr* A_csr, memory::MemorySpace memspace); + + /// @brief Converts symmetric or general COO to general CSR matrix + int coo2csr_new(matrix::Coo* A_coo, matrix::Csr* A_csr, memory::MemorySpace memspace); } } diff --git a/tests/unit/matrix/CMakeLists.txt b/tests/unit/matrix/CMakeLists.txt index 5b939056..76110271 100644 --- a/tests/unit/matrix/CMakeLists.txt +++ b/tests/unit/matrix/CMakeLists.txt @@ -10,6 +10,10 @@ add_executable(runMatrixIoTests.exe runMatrixIoTests.cpp) target_link_libraries(runMatrixIoTests.exe PRIVATE ReSolve resolve_matrix) +# Build matrix Conversion tests +add_executable(runMatrixConversionTests.exe runMatrixConversionTests.cpp) +target_link_libraries(runMatrixConversionTests.exe PRIVATE ReSolve resolve_matrix) + # Build matrix handler tests add_executable(runMatrixHandlerTests.exe runMatrixHandlerTests.cpp) target_link_libraries(runMatrixHandlerTests.exe PRIVATE ReSolve resolve_matrix) @@ -25,7 +29,7 @@ if(RESOLVE_USE_LUSOL) endif() # Install tests -set(installable_tests runMatrixIoTests.exe runMatrixHandlerTests.exe runMatrixFactorizationTests.exe) +set(installable_tests runMatrixIoTests.exe runMatrixConversionTests.exe runMatrixHandlerTests.exe runMatrixFactorizationTests.exe) if(RESOLVE_USE_LUSOL) list(APPEND installable_tests runLUSOLTests.exe) endif() @@ -33,6 +37,7 @@ install(TARGETS ${installable_tests} RUNTIME DESTINATION bin/resolve/tests/unit) add_test(NAME matrix_test COMMAND $) +add_test(NAME matrix_conversion_test COMMAND $) add_test(NAME matrix_handler_test COMMAND $) add_test(NAME matrix_factorization_test COMMAND $) if(RESOLVE_USE_LUSOL) diff --git a/tests/unit/matrix/MatrixConversionTests.hpp b/tests/unit/matrix/MatrixConversionTests.hpp new file mode 100644 index 00000000..988704d7 --- /dev/null +++ b/tests/unit/matrix/MatrixConversionTests.hpp @@ -0,0 +1,450 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace ReSolve { namespace tests { + +class MatrixConversionTests : TestBase +{ +public: + MatrixConversionTests(){} + virtual ~MatrixConversionTests(){} + + TestOutcome cooToCsr() + { + TestStatus status; + status = true; + + index_type n_answer = symmetric_expanded_csr_matrix_rows_.size() - 1; + + std::istringstream file2(symmetric_coo_matrix_file_); + matrix::Coo* A = ReSolve::io::readMatrixFromFile(file2); + ReSolve::matrix::Csr* A_csr = new matrix::Csr(n_answer, n_answer, 10); + + index_type nnz_coo = 10; + if (A->getNnz() != nnz_coo) { + std::cout << "Incorrect NNZ read from the file ...\n"; + status = false; + } + + if (!A->symmetric()) { + std::cout << "Incorrect matrix type, matrix is symmetric ...\n"; + status = false; + } + + if (A->expanded()) { + std::cout << "Incorrect matrix type, matrix not expanded ...\n"; + status = false; + } + + int retval = coo2csr_new(A, A_csr, memory::HOST); + + status *= verifyAnswer(*A_csr, symmetric_expanded_csr_matrix_rows_, symmetric_expanded_csr_matrix_cols_, symmetric_expanded_csr_matrix_vals_); + + delete A; + delete A_csr; + + return status.report(__func__); + } + + TestOutcome cooMatrixImport() + { + TestStatus status; + + // Read string into istream and status it to `readMatrixFromFile` function. + std::istringstream file(general_coo_matrix_file_); + ReSolve::matrix::Coo* A = ReSolve::io::readMatrixFromFile(file); + + // Check if the matrix data was correctly loaded + status = true; + + index_type nnz_answer = static_cast(general_coo_matrix_vals_.size()); + if (A->getNnz() != nnz_answer) { + std::cout << "Incorrect NNZ read from the file ...\n"; + status = false; + } + + if (A->symmetric()) { + std::cout << "Incorrect matrix type, matrix not symmetric ...\n"; + status = false; + } + + if (!A->expanded()) { + std::cout << "Incorrect matrix type, matrix is general (expanded) ...\n"; + status = false; + } + + status *= verifyAnswer(*A, general_coo_matrix_rows_, general_coo_matrix_cols_, general_coo_matrix_vals_); + + // A->print(); + delete A; + A = nullptr; + + std::istringstream file2(symmetric_coo_matrix_file_); + A = ReSolve::io::readMatrixFromFile(file2); + + nnz_answer = static_cast(symmetric_coo_matrix_vals_.size()); + if (A->getNnz() != nnz_answer) { + std::cout << "Incorrect NNZ read from the file ...\n"; + status = false; + } + + if (!A->symmetric()) { + std::cout << "Incorrect matrix type, matrix is symmetric ...\n"; + status = false; + } + + if (A->expanded()) { + std::cout << "Incorrect matrix type, matrix not expanded ...\n"; + status = false; + } + + status *= verifyAnswer(*A, symmetric_coo_matrix_rows_, symmetric_coo_matrix_cols_, symmetric_coo_matrix_vals_); + + return status.report(__func__); + } + + + TestOutcome cooMatrixExport() + { + TestStatus status; + status = true; + + // Read string into istream and status it to `readMatrixFromFile` function. + std::ostringstream buffer; + + // Deep copy constant test vectors with matrix data to nonconstant ones + std::vector rows = general_coo_matrix_rows_; + std::vector cols = general_coo_matrix_cols_; + std::vector vals = general_coo_matrix_vals_; + + // Get number of matrix rows + const index_type N = 1 + *(std::max_element(rows.begin(), rows.end())); + + // Get number of matrix columns + const index_type M = 1 + *(std::max_element(cols.begin(), cols.end())); + + // Get number of nonzeros + const index_type NNZ = static_cast(general_coo_matrix_vals_.size()); + + // Create the test COO matrix + ReSolve::matrix::Coo A(N, M, NNZ, false, false); + A.setMatrixData(&rows[0], + &cols[0], + &vals[0], + memory::HOST); + + // Write the matrix to an ostream + ReSolve::io::writeMatrixToFile(&A, buffer); + status *= (buffer.str() == resolve_general_coo_matrix_file_); + buffer.str(""); + + // Create the test CSR matrix + ReSolve::matrix::Csr B(&A, ReSolve::memory::HOST); + + // Write the matrix to an ostream + ReSolve::io::writeMatrixToFile(&B, buffer); + status *= (buffer.str() == resolve_row_sorted_general_coo_matrix_file_); + + return status.report(__func__); + } + + + TestOutcome cooMatrixReadAndUpdate() + { + TestStatus status; + + // Create a 5x5 COO matrix with 10 nonzeros + ReSolve::matrix::Coo A(5, 5, 10); + A.allocateMatrixData(memory::HOST); + + // Read string into istream and status it to `readMatrixFromFile` function. + std::istringstream file2(symmetric_coo_matrix_file_); + + // Update matrix A with data from the matrix market file + ReSolve::io::readAndUpdateMatrix(file2, &A); + + // Check if the matrix data was correctly loaded + status = true; + + index_type nnz_answer = static_cast(symmetric_coo_matrix_vals_.size()); + if (A.getNnz() != nnz_answer) { + std::cout << "Incorrect NNZ read from the file ...\n"; + status = false; + } + + status *= verifyAnswer(A, symmetric_coo_matrix_rows_, symmetric_coo_matrix_cols_, symmetric_coo_matrix_vals_); + + // Read string into istream and status it to `readMatrixFromFile` function. + std::istringstream file(general_coo_matrix_file_); + + // Update matrix A with data from the matrix market file + ReSolve::io::readAndUpdateMatrix(file, &A); + + nnz_answer = static_cast(general_coo_matrix_vals_.size()); + if (A.getNnz() != nnz_answer) { + std::cout << "Incorrect NNZ read from the file ...\n"; + status = false; + } + + status *= verifyAnswer(A, general_coo_matrix_rows_, general_coo_matrix_cols_, general_coo_matrix_vals_); + + return status.report(__func__); + } + + TestOutcome rhsVectorReadFromFile() + { + TestStatus status; + + // Read string into istream and status it to `readMatrixFromFile` function. + std::istringstream file(general_vector_file_); + + // Create rhs vector and load its data from the input file + real_type* rhs = ReSolve::io::readRhsFromFile(file); + + // Check if the matrix data was correctly loaded + status = true; + + for (size_t i = 0; i < general_vector_vals_.size(); ++i) { + if (!isEqual(rhs[i], general_vector_vals_[i])) + { + std::cout << "Incorrect vector value at storage element " << i << ".\n"; + status = false; + break; + } + // std::cout << i << ": " << rhs[i] << "\n"; + } + + return status.report(__func__); + } + + TestOutcome rhsVectorReadAndUpdate() + { + TestStatus status; + + // Read string into istream and status it to `readMatrixFromFile` function. + std::istringstream file(general_vector_file_); + + // For now let's test only the case when `readAndUpdateRhs` does not allocate rhs + real_type* rhs = new real_type[5]; //nullptr; + + // Update matrix A with data from the matrix market file + ReSolve::io::readAndUpdateRhs(file, &rhs); + + // Check if the matrix data was correctly loaded + status = true; + + for (size_t i = 0; i < general_vector_vals_.size(); ++i) { + if (!isEqual(rhs[i], general_vector_vals_[i])) + { + std::cout << "Incorrect vector value at storage element " << i << ".\n"; + status = false; + break; + } + // std::cout << i << ": " << rhs[i] << "\n"; + } + + return status.report(__func__); + } + +private: + bool verifyAnswer(/* const */ ReSolve::matrix::Coo& answer, + const std::vector& row_data, + const std::vector& col_data, + const std::vector& val_data) + { + for (size_t i = 0; i < val_data.size(); ++i) { + if ((answer.getRowData(memory::HOST)[i] != row_data[i]) || + (answer.getColData(memory::HOST)[i] != col_data[i]) || + (!isEqual(answer.getValues(memory::HOST)[i], val_data[i]))) + { + std::cout << "Incorrect matrix value at storage element " << i << ".\n"; + return false; + } + } + return true; + } + + bool verifyAnswer(/* const */ ReSolve::matrix::Csr& answer, + const std::vector& row_data, + const std::vector& col_data, + const std::vector& val_data) + { + for (size_t i = 0; i < val_data.size(); ++i) { + if ((answer.getColData(memory::HOST)[i] != col_data[i]) || + (!isEqual(answer.getValues(memory::HOST)[i], val_data[i]))) { + std::cout << "Incorrect matrix value at storage element " << i << ".\n"; + return false; + } + } + + for (size_t i = 0; i < row_data.size(); ++i) { + if(answer.getRowData(memory::HOST)[i] != row_data[i]) { + std::cout << "Incorrect row pointer value at storage element " << i << ".\n"; + return false; + } + } + + return true; + } + +private: + // + // Test examples + // + + /// String pretending to be matrix market file + /// Same stored in file `matrix_general_coo_ordered.mtx` + const std::string general_coo_matrix_file_ = +R"(% This ASCII file represents a sparse MxN matrix with L +% nonzeros in the following Matrix Market format: +% +% +----------------------------------------------+ +% |%%MatrixMarket matrix coordinate real general | <--- header line +% |% | <--+ +% |% comments | |-- 0 or more comment lines +% |% | <--+ +% | M N L | <--- rows, columns, entries +% | I1 J1 A(I1, J1) | <--+ +% | I2 J2 A(I2, J2) | | +% | I3 J3 A(I3, J3) | |-- L lines +% | . . . | | +% | IL JL A(IL, JL) | <--+ +% +----------------------------------------------+ +% +% Indices are 1-based, i.e. A(1,1) is the first element. +% +%================================================================================= + 5 5 8 + 1 1 1.000e+00 + 2 2 1.050e+01 + 3 3 1.500e-02 + 1 4 6.000e+00 + 4 2 2.505e+02 + 4 4 -2.800e+02 + 4 5 3.332e+01 + 5 5 1.200e+01 +)"; + + /// String pretending to be matrix market file. + /// Generated by ReSolve's `writeMatrixToFile` function. + const std::string resolve_general_coo_matrix_file_ = +R"(%%MatrixMarket matrix coordinate real general +% Generated by Re::Solve +5 5 8 +0 0 1.000000000000000e+00 +1 1 1.050000000000000e+01 +2 2 1.500000000000000e-02 +0 3 6.000000000000000e+00 +3 1 2.505000000000000e+02 +3 3 -2.800000000000000e+02 +3 4 3.332000000000000e+01 +4 4 1.200000000000000e+01 +)"; + + /// String pretending to be matrix market file. + /// Generated by ReSolve's `writeMatrixToFile` function. + const std::string resolve_row_sorted_general_coo_matrix_file_ = +R"(%%MatrixMarket matrix coordinate real general +% Generated by Re::Solve +5 5 8 +0 0 1.000000000000000e+00 +0 3 6.000000000000000e+00 +1 1 1.050000000000000e+01 +2 2 1.500000000000000e-02 +3 1 2.505000000000000e+02 +3 3 -2.800000000000000e+02 +3 4 3.332000000000000e+01 +4 4 1.200000000000000e+01 +)"; + + /// Matching COO matrix data as it is supposed to be read from the file + const std::vector general_coo_matrix_rows_ = {0,1,2,0,3,3,3,4}; + const std::vector general_coo_matrix_cols_ = {0,1,2,3,1,3,4,4}; + const std::vector general_coo_matrix_vals_ = { 1.000e+00, + 1.050e+01, + 1.500e-02, + 6.000e+00, + 2.505e+02, + -2.800e+02, + 3.332e+01, + 1.200e+01 }; + + const std::string symmetric_coo_matrix_file_ = +R"(%%MatrixMarket matrix coordinate real symmetric +% + 5 5 10 + 1 1 11.0 + 1 5 15.0 + 2 4 12.0 + 2 2 22.0 + 2 3 23.0 + 3 5 35.0 + 3 3 33.0 + 4 4 44.0 + 5 5 55.0 + 2 4 12.0 + )"; + + + /// Matching COO matrix data as it is supposed to be read from the file + const std::vector symmetric_coo_matrix_rows_ = {0,0,1,1,1,2,2,3,4}; + const std::vector symmetric_coo_matrix_cols_ = {0,4,1,2,3,2,4,3,4}; + const std::vector symmetric_coo_matrix_vals_ = { 11.0, + 15.0, + 22.0, + 23.0, + 24.0, + 33.0, + 35.0, + 44.0, + 55.0 }; + + /// Matching expanded CSR matrix data as it is supposed to be read from the file + const std::vector symmetric_expanded_csr_matrix_rows_ = {0,2,5,8,10,13}; + const std::vector symmetric_expanded_csr_matrix_cols_ = {0,4,1,2,3,1,2,4,1,3,0,2,4}; + const std::vector symmetric_expanded_csr_matrix_vals_ = { 11.0, + 15.0, + 22.0, + 23.0, + 24.0, + 23.0, + 33.0, + 35.0, + 24.0, + 44.0, + 15.0, + 35.0, + 55.0 }; + + + const std::string general_vector_file_ = +R"(% This ASCII file represents a sparse MxN matrix with L +% nonzeros in the following Matrix Market format: +% +% +%================================================================================= + 5 1 + 1.000e+00 + 2.000e+01 + 3.000e-02 + 4.000e+00 + 5.505e+02 +)"; + + const std::vector general_vector_vals_ = { 1.000e+00, + 2.000e+01, + 3.000e-02, + 4.000e+00, + 5.505e+02 }; + + /// Location of other test data + std::string datafiles_folder_; +}; // class MatrixConversionTests + +}} // namespace ReSolve::tests diff --git a/tests/unit/matrix/runMatrixConversionTests.cpp b/tests/unit/matrix/runMatrixConversionTests.cpp new file mode 100644 index 00000000..b1eb6df3 --- /dev/null +++ b/tests/unit/matrix/runMatrixConversionTests.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include +#include +#include "MatrixConversionTests.hpp" + +int main(int, char**) +{ + ReSolve::tests::MatrixConversionTests test; + + ReSolve::tests::TestingResults result; + result += test.cooToCsr(); + // result += test.cooMatrixExport(); + // result += test.cooMatrixReadAndUpdate(); + // result += test.rhsVectorReadFromFile(); + // result += test.rhsVectorReadAndUpdate(); + + return result.summary(); +} \ No newline at end of file From 563940c18e606113134e3c0d981ab378e3e12e4a Mon Sep 17 00:00:00 2001 From: Slaven Peles Date: Thu, 4 Jul 2024 22:24:56 -0400 Subject: [PATCH 2/3] Clean up code. --- resolve/matrix/Utilities.cpp | 36 +- tests/unit/matrix/MatrixConversionTests.hpp | 530 ++++-------------- .../unit/matrix/runMatrixConversionTests.cpp | 4 - 3 files changed, 119 insertions(+), 451 deletions(-) diff --git a/resolve/matrix/Utilities.cpp b/resolve/matrix/Utilities.cpp index cc086a5a..2bf530ee 100644 --- a/resolve/matrix/Utilities.cpp +++ b/resolve/matrix/Utilities.cpp @@ -110,7 +110,7 @@ namespace ReSolve void print() const { // Add 1 to indices to restore indexing from MM format - std::cout << rowidx_ + 1 << " " << colidx_ + 1 << " " << value_ << "\n"; + std::cout << rowidx_ << " " << colidx_ << " " << value_ << "\n"; } private: @@ -155,9 +155,9 @@ namespace ReSolve bool is_upper_triangular = false; bool is_lower_triangular = false; - // Compute size of expanded matrix if it is stored as symmetric. - // Also check if matrix is upper- or lower-triangular, if it is - // defined as such. + // Compute size of the symmetric matrix when expanded as general. + // Check if matrix is upper- or lower-triangular, if it is + // defined as symmetric and not expanded. // Complexity O(NNZ) index_type nnz_expanded = nnz; if (is_symmetric && !is_expanded) { @@ -200,15 +200,17 @@ namespace ReSolve if (it != tmp.end()) { out::error() << "NNZ computed inaccurately!\n"; } - print_list(tmp); - std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + // print_list(tmp); + // std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; - // Sort tmp: Complexity NNZ*log(NNZ) + // Sort tmp + // Complexity NNZ*log(NNZ) tmp.sort(); - print_list(tmp); - std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + // print_list(tmp); + // std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; // Deduplicate tmp + // Complexity O(NNZ) it = tmp.begin(); while (it != tmp.end()) { @@ -219,12 +221,13 @@ namespace ReSolve tmp.erase(it_tmp); } } - print_list(tmp); + // print_list(tmp); index_type nnz_expanded_no_duplicates = tmp.size(); - std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + // std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; // Convert to general CSR + // Complexity O(NNZ) A_csr->setExpanded(true); A_csr->setNnz(nnz_expanded_no_duplicates); A_csr->setNnzExpanded(nnz_expanded_no_duplicates); @@ -241,12 +244,15 @@ namespace ReSolve rows_csr[0] = csr_row_idx; ++csr_row_counter; + // Loop throught the list of COO triplets it = tmp.begin(); while (it != tmp.end()) { + // Set column indices and matrix values cols_csr[csr_val_counter] = it->getColIdx(); vals_csr[csr_val_counter] = it->getValue(); + // When row index changes, set next row pointer if (csr_row_idx != it->getRowIdx()) { csr_row_idx = it->getRowIdx(); ++csr_row_counter; @@ -259,10 +265,10 @@ namespace ReSolve // Set last row pointer rows_csr[csr_row_counter] = nnz_expanded_no_duplicates; - std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; - std::cout << "Rows counted = " << csr_row_counter << "\n"; - std::cout << "NNZs counted = " << csr_val_counter << "\n\n"; - A_csr->print(); + // std::cout << "Size of tmp list = " << tmp.size() << "\n\n"; + // std::cout << "Rows counted = " << csr_row_counter << "\n"; + // std::cout << "NNZs counted = " << csr_val_counter << "\n\n"; + // A_csr->print(); return 0; } diff --git a/tests/unit/matrix/MatrixConversionTests.hpp b/tests/unit/matrix/MatrixConversionTests.hpp index 988704d7..cbe3b35c 100644 --- a/tests/unit/matrix/MatrixConversionTests.hpp +++ b/tests/unit/matrix/MatrixConversionTests.hpp @@ -11,440 +11,106 @@ namespace ReSolve { namespace tests { class MatrixConversionTests : TestBase { -public: - MatrixConversionTests(){} - virtual ~MatrixConversionTests(){} - - TestOutcome cooToCsr() - { - TestStatus status; - status = true; - - index_type n_answer = symmetric_expanded_csr_matrix_rows_.size() - 1; - - std::istringstream file2(symmetric_coo_matrix_file_); - matrix::Coo* A = ReSolve::io::readMatrixFromFile(file2); - ReSolve::matrix::Csr* A_csr = new matrix::Csr(n_answer, n_answer, 10); - - index_type nnz_coo = 10; - if (A->getNnz() != nnz_coo) { - std::cout << "Incorrect NNZ read from the file ...\n"; - status = false; - } - - if (!A->symmetric()) { - std::cout << "Incorrect matrix type, matrix is symmetric ...\n"; - status = false; - } - - if (A->expanded()) { - std::cout << "Incorrect matrix type, matrix not expanded ...\n"; - status = false; - } - - int retval = coo2csr_new(A, A_csr, memory::HOST); - - status *= verifyAnswer(*A_csr, symmetric_expanded_csr_matrix_rows_, symmetric_expanded_csr_matrix_cols_, symmetric_expanded_csr_matrix_vals_); - - delete A; - delete A_csr; - - return status.report(__func__); - } - - TestOutcome cooMatrixImport() - { - TestStatus status; - - // Read string into istream and status it to `readMatrixFromFile` function. - std::istringstream file(general_coo_matrix_file_); - ReSolve::matrix::Coo* A = ReSolve::io::readMatrixFromFile(file); - - // Check if the matrix data was correctly loaded - status = true; - - index_type nnz_answer = static_cast(general_coo_matrix_vals_.size()); - if (A->getNnz() != nnz_answer) { - std::cout << "Incorrect NNZ read from the file ...\n"; - status = false; - } - - if (A->symmetric()) { - std::cout << "Incorrect matrix type, matrix not symmetric ...\n"; - status = false; - } - - if (!A->expanded()) { - std::cout << "Incorrect matrix type, matrix is general (expanded) ...\n"; - status = false; - } - - status *= verifyAnswer(*A, general_coo_matrix_rows_, general_coo_matrix_cols_, general_coo_matrix_vals_); - - // A->print(); - delete A; - A = nullptr; - - std::istringstream file2(symmetric_coo_matrix_file_); - A = ReSolve::io::readMatrixFromFile(file2); - - nnz_answer = static_cast(symmetric_coo_matrix_vals_.size()); - if (A->getNnz() != nnz_answer) { - std::cout << "Incorrect NNZ read from the file ...\n"; - status = false; - } - - if (!A->symmetric()) { - std::cout << "Incorrect matrix type, matrix is symmetric ...\n"; - status = false; - } - - if (A->expanded()) { - std::cout << "Incorrect matrix type, matrix not expanded ...\n"; - status = false; - } - - status *= verifyAnswer(*A, symmetric_coo_matrix_rows_, symmetric_coo_matrix_cols_, symmetric_coo_matrix_vals_); - - return status.report(__func__); - } - - - TestOutcome cooMatrixExport() - { - TestStatus status; - status = true; - - // Read string into istream and status it to `readMatrixFromFile` function. - std::ostringstream buffer; - - // Deep copy constant test vectors with matrix data to nonconstant ones - std::vector rows = general_coo_matrix_rows_; - std::vector cols = general_coo_matrix_cols_; - std::vector vals = general_coo_matrix_vals_; - - // Get number of matrix rows - const index_type N = 1 + *(std::max_element(rows.begin(), rows.end())); - - // Get number of matrix columns - const index_type M = 1 + *(std::max_element(cols.begin(), cols.end())); - - // Get number of nonzeros - const index_type NNZ = static_cast(general_coo_matrix_vals_.size()); - - // Create the test COO matrix - ReSolve::matrix::Coo A(N, M, NNZ, false, false); - A.setMatrixData(&rows[0], - &cols[0], - &vals[0], - memory::HOST); - - // Write the matrix to an ostream - ReSolve::io::writeMatrixToFile(&A, buffer); - status *= (buffer.str() == resolve_general_coo_matrix_file_); - buffer.str(""); - - // Create the test CSR matrix - ReSolve::matrix::Csr B(&A, ReSolve::memory::HOST); - - // Write the matrix to an ostream - ReSolve::io::writeMatrixToFile(&B, buffer); - status *= (buffer.str() == resolve_row_sorted_general_coo_matrix_file_); - - return status.report(__func__); - } - - - TestOutcome cooMatrixReadAndUpdate() - { - TestStatus status; - - // Create a 5x5 COO matrix with 10 nonzeros - ReSolve::matrix::Coo A(5, 5, 10); - A.allocateMatrixData(memory::HOST); - - // Read string into istream and status it to `readMatrixFromFile` function. - std::istringstream file2(symmetric_coo_matrix_file_); - - // Update matrix A with data from the matrix market file - ReSolve::io::readAndUpdateMatrix(file2, &A); - - // Check if the matrix data was correctly loaded - status = true; - - index_type nnz_answer = static_cast(symmetric_coo_matrix_vals_.size()); - if (A.getNnz() != nnz_answer) { - std::cout << "Incorrect NNZ read from the file ...\n"; - status = false; - } - - status *= verifyAnswer(A, symmetric_coo_matrix_rows_, symmetric_coo_matrix_cols_, symmetric_coo_matrix_vals_); - - // Read string into istream and status it to `readMatrixFromFile` function. - std::istringstream file(general_coo_matrix_file_); - - // Update matrix A with data from the matrix market file - ReSolve::io::readAndUpdateMatrix(file, &A); - - nnz_answer = static_cast(general_coo_matrix_vals_.size()); - if (A.getNnz() != nnz_answer) { - std::cout << "Incorrect NNZ read from the file ...\n"; - status = false; - } - - status *= verifyAnswer(A, general_coo_matrix_rows_, general_coo_matrix_cols_, general_coo_matrix_vals_); - - return status.report(__func__); - } - - TestOutcome rhsVectorReadFromFile() - { - TestStatus status; - - // Read string into istream and status it to `readMatrixFromFile` function. - std::istringstream file(general_vector_file_); - - // Create rhs vector and load its data from the input file - real_type* rhs = ReSolve::io::readRhsFromFile(file); - - // Check if the matrix data was correctly loaded - status = true; - - for (size_t i = 0; i < general_vector_vals_.size(); ++i) { - if (!isEqual(rhs[i], general_vector_vals_[i])) - { - std::cout << "Incorrect vector value at storage element " << i << ".\n"; - status = false; - break; - } - // std::cout << i << ": " << rhs[i] << "\n"; - } - - return status.report(__func__); - } - - TestOutcome rhsVectorReadAndUpdate() - { - TestStatus status; - - // Read string into istream and status it to `readMatrixFromFile` function. - std::istringstream file(general_vector_file_); - - // For now let's test only the case when `readAndUpdateRhs` does not allocate rhs - real_type* rhs = new real_type[5]; //nullptr; - - // Update matrix A with data from the matrix market file - ReSolve::io::readAndUpdateRhs(file, &rhs); - - // Check if the matrix data was correctly loaded - status = true; - - for (size_t i = 0; i < general_vector_vals_.size(); ++i) { - if (!isEqual(rhs[i], general_vector_vals_[i])) - { - std::cout << "Incorrect vector value at storage element " << i << ".\n"; - status = false; - break; - } - // std::cout << i << ": " << rhs[i] << "\n"; - } - - return status.report(__func__); - } - -private: - bool verifyAnswer(/* const */ ReSolve::matrix::Coo& answer, - const std::vector& row_data, - const std::vector& col_data, - const std::vector& val_data) - { - for (size_t i = 0; i < val_data.size(); ++i) { - if ((answer.getRowData(memory::HOST)[i] != row_data[i]) || - (answer.getColData(memory::HOST)[i] != col_data[i]) || - (!isEqual(answer.getValues(memory::HOST)[i], val_data[i]))) - { - std::cout << "Incorrect matrix value at storage element " << i << ".\n"; - return false; + public: + MatrixConversionTests(){} + virtual ~MatrixConversionTests(){} + + TestOutcome cooToCsr() + { + TestStatus status; + status = true; + + matrix::Coo* A = createSymmetricCooMatrix(); + ReSolve::matrix::Csr* A_csr = new matrix::Csr(A->getNumRows(), A->getNumColumns(), 0); + + int retval = coo2csr_new(A, A_csr, memory::HOST); + + status *= verifyAnswer(*A_csr, symmetric_expanded_csr_matrix_rows_, symmetric_expanded_csr_matrix_cols_, symmetric_expanded_csr_matrix_vals_); + + delete A; + delete A_csr; + + return status.report(__func__); + } + + private: + + bool verifyAnswer(/* const */ ReSolve::matrix::Csr& answer, + const std::vector& row_data, + const std::vector& col_data, + const std::vector& val_data) + { + for (size_t i = 0; i < val_data.size(); ++i) { + if ((answer.getColData(memory::HOST)[i] != col_data[i]) || + (!isEqual(answer.getValues(memory::HOST)[i], val_data[i]))) { + std::cout << "Incorrect matrix value at storage element " << i << ".\n"; + return false; + } } - } - return true; - } - - bool verifyAnswer(/* const */ ReSolve::matrix::Csr& answer, - const std::vector& row_data, - const std::vector& col_data, - const std::vector& val_data) - { - for (size_t i = 0; i < val_data.size(); ++i) { - if ((answer.getColData(memory::HOST)[i] != col_data[i]) || - (!isEqual(answer.getValues(memory::HOST)[i], val_data[i]))) { - std::cout << "Incorrect matrix value at storage element " << i << ".\n"; - return false; - } - } - - for (size_t i = 0; i < row_data.size(); ++i) { - if(answer.getRowData(memory::HOST)[i] != row_data[i]) { - std::cout << "Incorrect row pointer value at storage element " << i << ".\n"; - return false; + + for (size_t i = 0; i < row_data.size(); ++i) { + if(answer.getRowData(memory::HOST)[i] != row_data[i]) { + std::cout << "Incorrect row pointer value at storage element " << i << ".\n"; + return false; + } } - } - - return true; - } - -private: - // - // Test examples - // - - /// String pretending to be matrix market file - /// Same stored in file `matrix_general_coo_ordered.mtx` - const std::string general_coo_matrix_file_ = -R"(% This ASCII file represents a sparse MxN matrix with L -% nonzeros in the following Matrix Market format: -% -% +----------------------------------------------+ -% |%%MatrixMarket matrix coordinate real general | <--- header line -% |% | <--+ -% |% comments | |-- 0 or more comment lines -% |% | <--+ -% | M N L | <--- rows, columns, entries -% | I1 J1 A(I1, J1) | <--+ -% | I2 J2 A(I2, J2) | | -% | I3 J3 A(I3, J3) | |-- L lines -% | . . . | | -% | IL JL A(IL, JL) | <--+ -% +----------------------------------------------+ -% -% Indices are 1-based, i.e. A(1,1) is the first element. -% -%================================================================================= - 5 5 8 - 1 1 1.000e+00 - 2 2 1.050e+01 - 3 3 1.500e-02 - 1 4 6.000e+00 - 4 2 2.505e+02 - 4 4 -2.800e+02 - 4 5 3.332e+01 - 5 5 1.200e+01 -)"; - - /// String pretending to be matrix market file. - /// Generated by ReSolve's `writeMatrixToFile` function. - const std::string resolve_general_coo_matrix_file_ = -R"(%%MatrixMarket matrix coordinate real general -% Generated by Re::Solve -5 5 8 -0 0 1.000000000000000e+00 -1 1 1.050000000000000e+01 -2 2 1.500000000000000e-02 -0 3 6.000000000000000e+00 -3 1 2.505000000000000e+02 -3 3 -2.800000000000000e+02 -3 4 3.332000000000000e+01 -4 4 1.200000000000000e+01 -)"; - - /// String pretending to be matrix market file. - /// Generated by ReSolve's `writeMatrixToFile` function. - const std::string resolve_row_sorted_general_coo_matrix_file_ = -R"(%%MatrixMarket matrix coordinate real general -% Generated by Re::Solve -5 5 8 -0 0 1.000000000000000e+00 -0 3 6.000000000000000e+00 -1 1 1.050000000000000e+01 -2 2 1.500000000000000e-02 -3 1 2.505000000000000e+02 -3 3 -2.800000000000000e+02 -3 4 3.332000000000000e+01 -4 4 1.200000000000000e+01 -)"; - - /// Matching COO matrix data as it is supposed to be read from the file - const std::vector general_coo_matrix_rows_ = {0,1,2,0,3,3,3,4}; - const std::vector general_coo_matrix_cols_ = {0,1,2,3,1,3,4,4}; - const std::vector general_coo_matrix_vals_ = { 1.000e+00, - 1.050e+01, - 1.500e-02, - 6.000e+00, - 2.505e+02, - -2.800e+02, - 3.332e+01, - 1.200e+01 }; - - const std::string symmetric_coo_matrix_file_ = -R"(%%MatrixMarket matrix coordinate real symmetric -% - 5 5 10 - 1 1 11.0 - 1 5 15.0 - 2 4 12.0 - 2 2 22.0 - 2 3 23.0 - 3 5 35.0 - 3 3 33.0 - 4 4 44.0 - 5 5 55.0 - 2 4 12.0 - )"; - - - /// Matching COO matrix data as it is supposed to be read from the file - const std::vector symmetric_coo_matrix_rows_ = {0,0,1,1,1,2,2,3,4}; - const std::vector symmetric_coo_matrix_cols_ = {0,4,1,2,3,2,4,3,4}; - const std::vector symmetric_coo_matrix_vals_ = { 11.0, - 15.0, - 22.0, - 23.0, - 24.0, - 33.0, - 35.0, - 44.0, - 55.0 }; - - /// Matching expanded CSR matrix data as it is supposed to be read from the file - const std::vector symmetric_expanded_csr_matrix_rows_ = {0,2,5,8,10,13}; - const std::vector symmetric_expanded_csr_matrix_cols_ = {0,4,1,2,3,1,2,4,1,3,0,2,4}; - const std::vector symmetric_expanded_csr_matrix_vals_ = { 11.0, - 15.0, - 22.0, - 23.0, - 24.0, - 23.0, - 33.0, - 35.0, - 24.0, - 44.0, - 15.0, - 35.0, - 55.0 }; - - - const std::string general_vector_file_ = -R"(% This ASCII file represents a sparse MxN matrix with L -% nonzeros in the following Matrix Market format: -% -% -%================================================================================= - 5 1 - 1.000e+00 - 2.000e+01 - 3.000e-02 - 4.000e+00 - 5.505e+02 -)"; - - const std::vector general_vector_vals_ = { 1.000e+00, - 2.000e+01, - 3.000e-02, - 4.000e+00, - 5.505e+02 }; - - /// Location of other test data - std::string datafiles_folder_; + + return true; + } + + // + // Test examples + // + + + // + // [11 15] + // [ 22 23 24 ] + // A = [ 33 35] + // [ 44 ] + // [ 55] + // + // Symmetric matrix in COO unordered format + // Only upper triangular matrix is stored + // A(2,4) is stored in two duplicate entries + // + matrix::Coo* createSymmetricCooMatrix() + { + matrix::Coo* A = new matrix::Coo(5, 5, 10, true, false); + index_type rows[10] = {0, 0, 1, 1, 1, 2, 2, 3, 4, 1}; + index_type cols[10] = {0, 4, 3, 1, 2, 4, 2, 3, 4, 3}; + real_type vals[10] = {11.0, 15.0,12.0, 22.0, 23.0, 35.0, 33.0, 44.0, 55.0, 12.0}; + + A->allocateMatrixData(memory::HOST); + A->updateData(rows, cols, vals, memory::HOST, memory::HOST); + return A; + } + + // Matching expanded CSR matrix data as it is supposed to be converted + // + // [11 15] + // [ 22 23 24 ] + // A = [ 23 33 35] + // [ 24 44 ] + // [15 35 55] + // + // Symmetric matrix in CSR general format + // + const std::vector symmetric_expanded_csr_matrix_rows_ = {0,2,5,8,10,13}; + const std::vector symmetric_expanded_csr_matrix_cols_ = {0,4,1,2,3,1,2,4,1,3,0,2,4}; + const std::vector symmetric_expanded_csr_matrix_vals_ = { 11.0, + 15.0, + 22.0, + 23.0, + 24.0, + 23.0, + 33.0, + 35.0, + 24.0, + 44.0, + 15.0, + 35.0, + 55.0 }; }; // class MatrixConversionTests }} // namespace ReSolve::tests diff --git a/tests/unit/matrix/runMatrixConversionTests.cpp b/tests/unit/matrix/runMatrixConversionTests.cpp index b1eb6df3..0ed80b76 100644 --- a/tests/unit/matrix/runMatrixConversionTests.cpp +++ b/tests/unit/matrix/runMatrixConversionTests.cpp @@ -12,10 +12,6 @@ int main(int, char**) ReSolve::tests::TestingResults result; result += test.cooToCsr(); - // result += test.cooMatrixExport(); - // result += test.cooMatrixReadAndUpdate(); - // result += test.rhsVectorReadFromFile(); - // result += test.rhsVectorReadAndUpdate(); return result.summary(); } \ No newline at end of file From ee813cef6bdfa976db8a8658bdbb8fcd4ad347a9 Mon Sep 17 00:00:00 2001 From: Slaven Peles Date: Thu, 4 Jul 2024 22:36:17 -0400 Subject: [PATCH 3/3] Add test for old coo2csr. --- tests/unit/matrix/MatrixConversionTests.hpp | 20 ++++++++++++++++++- .../unit/matrix/runMatrixConversionTests.cpp | 3 ++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/unit/matrix/MatrixConversionTests.hpp b/tests/unit/matrix/MatrixConversionTests.hpp index cbe3b35c..4489851c 100644 --- a/tests/unit/matrix/MatrixConversionTests.hpp +++ b/tests/unit/matrix/MatrixConversionTests.hpp @@ -15,7 +15,7 @@ class MatrixConversionTests : TestBase MatrixConversionTests(){} virtual ~MatrixConversionTests(){} - TestOutcome cooToCsr() + TestOutcome newCooToCsr() { TestStatus status; status = true; @@ -33,6 +33,24 @@ class MatrixConversionTests : TestBase return status.report(__func__); } + TestOutcome oldCooToCsr() + { + TestStatus status; + status.expectFailure(); + + matrix::Coo* A = createSymmetricCooMatrix(); + ReSolve::matrix::Csr* A_csr = new matrix::Csr(A->getNumRows(), A->getNumColumns(), 0); + + int retval = coo2csr(A, A_csr, memory::HOST); + + status *= verifyAnswer(*A_csr, symmetric_expanded_csr_matrix_rows_, symmetric_expanded_csr_matrix_cols_, symmetric_expanded_csr_matrix_vals_); + + delete A; + delete A_csr; + + return status.report(__func__); + } + private: bool verifyAnswer(/* const */ ReSolve::matrix::Csr& answer, diff --git a/tests/unit/matrix/runMatrixConversionTests.cpp b/tests/unit/matrix/runMatrixConversionTests.cpp index 0ed80b76..882e3edc 100644 --- a/tests/unit/matrix/runMatrixConversionTests.cpp +++ b/tests/unit/matrix/runMatrixConversionTests.cpp @@ -11,7 +11,8 @@ int main(int, char**) ReSolve::tests::MatrixConversionTests test; ReSolve::tests::TestingResults result; - result += test.cooToCsr(); + result += test.newCooToCsr(); + result += test.oldCooToCsr(); return result.summary(); } \ No newline at end of file