Jordi
Jordi

Reputation: 1

How to reshape sparse matrices in Eigen?

I would like to know if there is a function or an optimized way to reshape sparse matrices in Eigen.

In the documentation there is no reshape method for such matrices, so I implemented a function myself, but I don't know if it is optimized (i need it to be as fast as possible). Here is my approach:

Eigen::SparseMatrix<double> reshape_sp(const Eigen::SparseMatrix<double>& x,
    lint a, lint b) {
    Eigen::SparseMatrix<double> y(a, b);
    for (int k=0; k<x.outerSize(); ++k) {
        for (Eigen::SparseMatrix<double>::InnerIterator it(x,k); it; ++it) {
            int pos = it.col()*x.rows()+it.row();
            int col = int(pos/a);
            int row = pos%a;
            y.insert(row, col) = it.value();
        }
    }
    y.makeCompressed();
    return y;
}

Upvotes: 0

Views: 254

Answers (1)

Homer512
Homer512

Reputation: 13463

For performance, it is absolutely crucial that you call reserve on your matrix. I've tested with a 100,000 x 100,000 matrix population 1%. Your version (after fixing the 32 bit overflow in pos computation), took 3 minutes. This fixed version a few seconds:

Eigen::SparseMatrix<double>
reshape(const Eigen::SparseMatrix<double>& orig,
    int rows, int cols)
{
  Eigen::SparseMatrix<double> rtrn(rows, cols);
  rtrn.reserve(orig.nonZeros());
  using InnerIterator = Eigen::SparseMatrix<double>::InnerIterator;
  for(int k = 0; k < orig.outerSize(); ++k) {
    for(InnerIterator it(orig, k); it; ++it) {
      std::int64_t pos = std::int64_t(it.col()) * orig.rows() + it.row();
      int col = int(pos / rows);
      int row = int(pos % rows);
      rtrn.insert(row, col) = it.value();
    }
  }
  rtrn.makeCompressed();
  return rtrn;
}

An alternative is to work with triplets again. This is a bit slower but less likely to explode in your face the same way insert does. This is particularly helpful for more complex operations like transposing where you cannot guarantee that the insert appends at the end.

Eigen::SparseMatrix<double>
reshape(const Eigen::SparseMatrix<double>& orig,
        int rows, int cols)
{
  using InnerIterator = Eigen::SparseMatrix<double>::InnerIterator;
  using Triplet = Eigen::Triplet<double>;
  std::vector<Triplet> triplets;
  triplets.reserve(std::size_t(orig.nonZeros()));
  for(int k = 0; k < orig.outerSize(); ++k) {
    for(InnerIterator it(orig, k); it; ++it) {
      std::int64_t pos = std::int64_t(it.col()) * orig.rows() + it.row();
      int col = int(pos / rows);
      int row = int(pos % rows);
      triplets.emplace_back(row, col, it.value());
    }
  }
  Eigen::SparseMatrix<double> rtrn(rows, cols);
  rtrn.setFromTriplets(triplets.begin(), triplets.end());
  return rtrn;
}

Things I tested that did not work:

  1. Using FXDiv to replace the division with a cheaper operation
  2. Computing maximum distance from one index to the next within a single column to skip dividing if both values are in the same output column (may still be worth it for sparse matrices with suitable inner structure)
  3. Parallelizing the loop with OpenMP, using a final std::sort(std::execution::par, ...) for the triplets.

Upvotes: 1

Related Questions