Search code examples
c++matrixeigen

Efficient Eigen Matrix From Function


I'm trying to build a matrix from a kernel, such that A(i,j) = f(i,j) where i,j are both vectors (hence I build A from two matrices x,y which each row corresponds to a point/vector). My current function looks similar to this:

Eigen::MatrixXd get_kernel_matrix(const Eigen::MatrixXd& x, const Eigen::MatrixXd& y, double(&kernel)(const Eigen::VectorXd&)) {
    Eigen::MatrixXd res (x.rows(), y.rows());
    for(int i = 0; i < res.rows() ; i++) {
        for(int j = 0; j < res.cols(); j++) {
            res(i, j) = kernel(x.row(i), y.row(j));
            }
        }
    }
    return res;
}

Along with some logic for the diagonal (which would in my case likely cause division by zero).

Is there a more efficient/idiometric way to do this? In some of my tests it appears that Matlab code beats the speed of my C++/Eigen implementation (I'm guessing due to vectorization).

I've looked through a considerable amount of documentation (such as the unaryExpr function), but can't seem to find what I'm looking for.

Thanks for any help.


Solution

  • You can use NullaryExpr with an appropriate lambda to remove your for loops:

    MatrixXd res = MatrixXd::NullaryExpr(x.rows(), y.rows(),
                   [&x,&y,&kernel](int i,int j) { return kernel(x.row(i), y.row(j)); });
    

    Here is a working self-contained example reproducing a matrix product:

    #include <iostream>
    #include <Eigen/Dense>
    using namespace Eigen;
    using namespace std;
    
    double my_kernel(const MatrixXd::ConstRowXpr &x, const MatrixXd::ConstRowXpr &y) {
      return x.dot(y);
    }
    
    template<typename Kernel>
    MatrixXd apply_kernel(const MatrixXd& x, const MatrixXd& y, Kernel kernel) {
      return MatrixXd::NullaryExpr(x.rows(), y.rows(),
                    [&x,&y,&kernel](int i,int j) { return kernel(x.row(i), y.row(j)); });
    }
    
    int main()
    {
      int n = 10;
      MatrixXd X = MatrixXd::Random(n,n);
      MatrixXd Y = MatrixXd::Random(n,n);
      MatrixXd R = apply_kernel(X,Y,std::ptr_fun(my_kernel));
      std::cout << R << "\n\n";
      std::cout << X*Y.transpose() << "\n\n";
    }
    

    If you don't want to make apply_kernel a template function, you can use std::function to pass the kernel.