2

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.

Aerys.L
  • 59
  • 2
  • 8

1 Answers1

1

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.

ggael
  • 28,425
  • 2
  • 65
  • 71
  • Thanks! Looks like it should do what I want it to do. I'm pretty new to C++ lambda expressions, and it's having trouble using the kernel function, but once I can modify it and confirm it's working I'll accept the answer. – Aerys.L Nov 09 '17 at 02:37
  • Thank you very much, this fulfills my need perfectly. – Aerys.L Nov 10 '17 at 18:33
  • In case anybody is looking for additional information on NullaryExpr, the documentation page is https://eigen.tuxfamily.org/dox/TopicCustomizing_NullaryExpr.html – Aerys.L Nov 14 '17 at 18:42