How to multiply a sparse matrix and a dense vector?

Viewed 170

I am trying the following:

Eigen::SparseMatrix<double> bijection(2 * face_count, 2 * vert_count);
/* initialization */
Eigen::VectorXd toggles(2 * vert_count);
toggles.setOnes();
Eigen::SparseMatrix<double> deformed;
deformed = bijection * toggles;

Eigen is returning an error claiming:

 error: static assertion failed: THE_EVAL_EVALTO_FUNCTION_SHOULD_NEVER_BE_CALLED_FOR_DENSE_OBJECTS
  586 |       EIGEN_STATIC_ASSERT((internal::is_same<Dest,void>::value),THE_EVAL_EVALTO_FUNCTION_SHOULD_NEVER_BE_CALLED_FOR_DENSE_OBJECTS);

According to the eigen documentaion

Sparse matrix and vector products are allowed. What am I doing wrong?

2 Answers

The problem is you have the wrong output type for the product.

The Eigen documentation states that the following type of multiplication is defined:

dv2 = sm1 * dv1;

Sparse matrix times dense vector equals dense vector.

If you actually do need a sparse representation, I think there is no better way of getting one than performing the multiplication as above and then converting the product to a sparse matrix with the sparseView member function. e.g.

Eigen::SparseMatrix<double> bijection(2 * face_count, 2 * vert_count);
/* initialization */
Eigen::VectorXd toggles(2 * vert_count);
toggles.setOnes();
Eigen::VectorXd deformedDense = bijection * toggles;
Eigen::SparseMatrix<double> deformedSparse = deformedDense.sparseView();

This can be faster than outputting to a dense vector if it is very sparse. Otherwise, 99/100 times the conventional product is faster.

void sparsem_densev_sparsev(const SparseMatrix<double>& A, const VectorX<double>& x, SparseVector<double>& Ax)
{
    Ax.resize(x.size());

    for (int j = 0; j < A.outerSize(); ++j)
    {
        if (A.outerIndexPtr()[j + 1] - A.outerIndexPtr()[j] > 0)
        {
            Ax.insertBack(j) = 0;
        }
    }

    for (int j_idx = 0; j_idx < Ax.nonZeros(); j_idx++)
    {
        int j = Ax.innerIndexPtr()[j_idx];

        for (int k = A.outerIndexPtr()[j]; k < A.outerIndexPtr()[j + 1]; ++k)
        {
            int i = A.innerIndexPtr()[k];
            Ax.valuePtr()[j_idx] += A.valuePtr()[k] * x.coeff(i);
        }
    }
}

For a (probably not optimal) self-adjoint version (lower triangle), change the j_idx loop to:


for (int j_idx = 0; j_idx < Ax.nonZeros(); j_idx++)
    {
        int j = Ax.innerIndexPtr()[j_idx];
        int i_idx = j_idx;//i>= j, trick to improve binary search

        for (int k = A.outerIndexPtr()[j]; k < A.outerIndexPtr()[j + 1]; ++k)
        {
            int i = A.innerIndexPtr()[k];
            Ax.valuePtr()[j_idx] += A.valuePtr()[k] * x.coeff(i);
            if (i != j)
            {
                i_idx = std::distance(Ax.innerIndexPtr(), std::lower_bound(Ax.innerIndexPtr() + i_idx, Ax.innerIndexPtr() + Ax.nonZeros(), i));
                Ax.valuePtr()[i_idx] += A.valuePtr()[k] * x.coeff(j);
            }
        }
    }
Related