Fix SparseLU and SparseQR on row-major and uncompressed sparse inputs libeigen/eigen!2433 Closes #1964 and #798
diff --git a/Eigen/src/SparseLU/SparseLU.h b/Eigen/src/SparseLU/SparseLU.h index 9b12f49..124becc 100644 --- a/Eigen/src/SparseLU/SparseLU.h +++ b/Eigen/src/SparseLU/SparseLU.h
@@ -535,19 +535,21 @@ OrderingType ord; ord(m_mat, m_perm_c); - // Apply the permutation to the column of the input matrix + // Apply the permutation to the column of the input matrix if (m_perm_c.size()) { - m_mat.uncompress(); // NOTE: The effect of this command is only to create the InnerNonzeros pointers. FIXME : This - // vector is filled but not subsequently used. - // Then, permute only the column pointers + // Switch to uncompressed mode so innerNonZeroPtr() exists and can be + // permuted consistently with outerIndexPtr(). + // Downstream sparse traversals may also rely on these per-column counts + // while m_mat remains uncompressed. + m_mat.uncompress(); + // A compressed column-major input already exposes valid column pointers. + // Otherwise snapshot the internal column-major structure before permuting in place. + const bool useInputOuterIndex = !MatrixType::IsRowMajor && mat.isCompressed(); ei_declare_aligned_stack_constructed_variable( - StorageIndex, outerIndexPtr, mat.cols() + 1, - mat.isCompressed() ? const_cast<StorageIndex*>(mat.outerIndexPtr()) : 0); - - // If the input matrix 'mat' is uncompressed, then the outer-indices do not match the ones of m_mat, and a copy is - // thus needed. - if (!mat.isCompressed()) - IndexVector::Map(outerIndexPtr, mat.cols() + 1) = IndexVector::Map(m_mat.outerIndexPtr(), mat.cols() + 1); + StorageIndex, outerIndexPtr, m_mat.cols() + 1, + useInputOuterIndex ? const_cast<StorageIndex*>(mat.outerIndexPtr()) : 0); + if (!useInputOuterIndex) + IndexVector::Map(outerIndexPtr, m_mat.cols() + 1) = IndexVector::Map(m_mat.outerIndexPtr(), m_mat.cols() + 1); // Apply the permutation and compute the nnz per column. for (Index i = 0; i < mat.cols(); i++) { @@ -618,21 +620,19 @@ // Apply the column permutation computed in analyzepattern() m_mat = matrix; if (m_perm_c.size()) { - m_mat.uncompress(); // NOTE: The effect of this command is only to create the InnerNonzeros pointers. - // Then, permute only the column pointers - const StorageIndex* outerIndexPtr; - if (matrix.isCompressed()) - outerIndexPtr = matrix.outerIndexPtr(); - else { - StorageIndex* outerIndexPtr_t = new StorageIndex[matrix.cols() + 1]; - for (Index i = 0; i <= matrix.cols(); i++) outerIndexPtr_t[i] = m_mat.outerIndexPtr()[i]; - outerIndexPtr = outerIndexPtr_t; - } + // Switch to uncompressed mode so innerNonZeroPtr() exists and can be + // permuted consistently with outerIndexPtr(). + m_mat.uncompress(); + const bool useInputOuterIndex = !MatrixType::IsRowMajor && matrix.isCompressed(); + ei_declare_aligned_stack_constructed_variable( + StorageIndex, outerIndexPtr, m_mat.cols() + 1, + useInputOuterIndex ? const_cast<StorageIndex*>(matrix.outerIndexPtr()) : 0); + if (!useInputOuterIndex) + IndexVector::Map(outerIndexPtr, m_mat.cols() + 1) = IndexVector::Map(m_mat.outerIndexPtr(), m_mat.cols() + 1); for (Index i = 0; i < matrix.cols(); i++) { m_mat.outerIndexPtr()[m_perm_c.indices()(i)] = outerIndexPtr[i]; m_mat.innerNonZeroPtr()[m_perm_c.indices()(i)] = outerIndexPtr[i + 1] - outerIndexPtr[i]; } - if (!matrix.isCompressed()) delete[] outerIndexPtr; } else { // FIXME This should not be needed if the empty permutation is handled transparently m_perm_c.resize(matrix.cols()); for (StorageIndex i = 0; i < matrix.cols(); ++i) m_perm_c.indices()(i) = i;
diff --git a/Eigen/src/SparseQR/SparseQR.h b/Eigen/src/SparseQR/SparseQR.h index cd2697c..a866595 100644 --- a/Eigen/src/SparseQR/SparseQR.h +++ b/Eigen/src/SparseQR/SparseQR.h
@@ -374,16 +374,18 @@ m_isEtreeOk = true; } - m_pmat.uncompress(); // To have the innerNonZeroPtr allocated + // Switch to uncompressed mode so innerNonZeroPtr() exists and can be + // permuted consistently with outerIndexPtr(). + m_pmat.uncompress(); // Apply the fill-in reducing permutation lazily: { - // If the input is row major, copy the original column indices, - // otherwise directly use the input matrix - // + // A compressed column-major input already exposes valid column pointers. + // Otherwise snapshot the internal column-major structure before permuting in place. IndexVector originalOuterIndicesCpy; - const StorageIndex* originalOuterIndices = mat.outerIndexPtr(); - if (MatrixType::IsRowMajor) { + const bool useInputOuterIndices = !MatrixType::IsRowMajor && mat.isCompressed(); + const StorageIndex* originalOuterIndices = useInputOuterIndices ? mat.outerIndexPtr() : nullptr; + if (!useInputOuterIndices) { originalOuterIndicesCpy = IndexVector::Map(m_pmat.outerIndexPtr(), n + 1); originalOuterIndices = originalOuterIndicesCpy.data(); }
diff --git a/test/sparselu.cpp b/test/sparselu.cpp index d933136..fc2de47 100644 --- a/test/sparselu.cpp +++ b/test/sparselu.cpp
@@ -35,9 +35,74 @@ check_sparse_square_determinant(sparselu_amd); } +template <typename T> +void test_sparselu_rowmajor_compressed_input() { + typedef SparseMatrix<T, RowMajor> RowMajorSparseMatrix; + typedef Matrix<T, Dynamic, 1> Vector; + + Vector b(2); + b << T(1.1), T(3.14); + + Vector expected(2); + expected << T(1.1 - 0.0001 * 3.14), T(3.14); + + RowMajorSparseMatrix compressed(2, 2); + compressed.insert(0, 0) = T(1.0); + compressed.insert(0, 1) = T(0.0001); + compressed.insert(1, 1) = T(1.0); + compressed.makeCompressed(); + + RowMajorSparseMatrix uncompressed(2, 2); + uncompressed.insert(0, 0) = T(1.0); + uncompressed.insert(0, 1) = T(0.0001); + uncompressed.insert(1, 1) = T(1.0); + + SparseLU<RowMajorSparseMatrix> compressed_solver; + compressed_solver.compute(compressed); + VERIFY_IS_EQUAL(compressed_solver.info(), Success); + VERIFY_IS_APPROX(compressed_solver.solve(b), expected); + + SparseLU<RowMajorSparseMatrix> two_step_solver; + two_step_solver.analyzePattern(compressed); + two_step_solver.factorize(compressed); + VERIFY_IS_EQUAL(two_step_solver.info(), Success); + VERIFY_IS_APPROX(two_step_solver.solve(b), expected); + + SparseLU<RowMajorSparseMatrix> uncompressed_solver; + uncompressed_solver.compute(uncompressed); + VERIFY_IS_EQUAL(uncompressed_solver.info(), Success); + VERIFY_IS_APPROX(uncompressed_solver.solve(b), expected); +} + +template <typename T> +void test_sparselu_colmajor_uncompressed_input() { + typedef SparseMatrix<T, ColMajor> ColMajorSparseMatrix; + typedef Matrix<T, Dynamic, 1> Vector; + + Vector b(2); + b << T(1.1), T(3.14); + + Vector expected(2); + expected << T(1.1 - 0.0001 * 3.14), T(3.14); + + ColMajorSparseMatrix uncompressed(2, 2); + uncompressed.insert(0, 0) = T(1.0); + uncompressed.insert(0, 1) = T(0.0001); + uncompressed.insert(1, 1) = T(1.0); + + SparseLU<ColMajorSparseMatrix> uncompressed_solver; + uncompressed_solver.compute(uncompressed); + VERIFY_IS_EQUAL(uncompressed_solver.info(), Success); + VERIFY_IS_APPROX(uncompressed_solver.solve(b), expected); +} + EIGEN_DECLARE_TEST(sparselu) { CALL_SUBTEST_1(test_sparselu_T<float>()); CALL_SUBTEST_2(test_sparselu_T<double>()); CALL_SUBTEST_3(test_sparselu_T<std::complex<float> >()); CALL_SUBTEST_4(test_sparselu_T<std::complex<double> >()); + CALL_SUBTEST_5(test_sparselu_rowmajor_compressed_input<float>()); + CALL_SUBTEST_6(test_sparselu_rowmajor_compressed_input<double>()); + CALL_SUBTEST_7(test_sparselu_colmajor_uncompressed_input<float>()); + CALL_SUBTEST_8(test_sparselu_colmajor_uncompressed_input<double>()); }
diff --git a/test/sparseqr.cpp b/test/sparseqr.cpp index 51ed398..897f426 100644 --- a/test/sparseqr.cpp +++ b/test/sparseqr.cpp
@@ -132,9 +132,49 @@ dQ = solver.matrixQ(); VERIFY_IS_APPROX(Q, dQ); } + +void test_sparseqr_factorize_uncompressed_input() { + typedef SparseMatrix<double, ColMajor> MatrixType; + typedef VectorXd Vector; + typedef SparseQR<MatrixType, NaturalOrdering<int> > Solver; + + MatrixType uncompressed(2, 2); + VectorXi reserve(2); + reserve << 2, 3; + uncompressed.reserve(reserve); + uncompressed.insert(0, 0) = 1.0; + uncompressed.insert(0, 1) = 0.5; + uncompressed.insert(1, 1) = 1.0; + + // Poison inactive capacity so factorize() must ignore unused slots in the + // uncompressed input instead of treating reserved space as structural nnz. + uncompressed.innerIndexPtr()[1] = 1; + uncompressed.valuePtr()[1] = 7.0; + + MatrixType compressed = uncompressed; + compressed.makeCompressed(); + + Vector b(2); + b << 1.0, 2.0; + Vector expected(2); + expected << 0.0, 2.0; + + Solver compressed_solver; + compressed_solver.compute(compressed); + VERIFY_IS_EQUAL(compressed_solver.info(), Success); + VERIFY_IS_APPROX(compressed_solver.solve(b), expected); + + Solver two_step_solver; + two_step_solver.analyzePattern(compressed); + two_step_solver.factorize(uncompressed); + VERIFY_IS_EQUAL(two_step_solver.info(), Success); + VERIFY_IS_APPROX(two_step_solver.solve(b), expected); +} + EIGEN_DECLARE_TEST(sparseqr) { for (int i = 0; i < g_repeat; ++i) { CALL_SUBTEST_1(test_sparseqr_scalar<double>()); CALL_SUBTEST_2(test_sparseqr_scalar<std::complex<double> >()); } + CALL_SUBTEST_3(test_sparseqr_factorize_uncompressed_input()); }