使用C ++在R中乘以复数矩阵

时间:2018-02-05 10:32:28

标签: c++ r matrix eigen complex-numbers

假设A是复杂矩阵。我有兴趣有效地计算A%*%Conj(t(A))中的产品R。据我所知,使用C ++会显着加快速度,所以这就是我想要做的。

我在R中可以使用以下真实矩阵代码。

library(Rcpp); 
library(inline); 
library(RcppEigen);

crossprodCpp <- '
using Eigen::Map;
using Eigen::MatrixXd;
using Eigen::Lower;

const Map<MatrixXd> A(as<Map<MatrixXd> >(AA));
const int   m(A.rows());
MatrixXd    AAt(MatrixXd(m, m).setZero().selfadjointView<Lower>().rankUpdate(A));
return wrap(AAt);
'

fcprd <- cxxfunction(signature(AA = "matrix"), crossprodCpp, "RcppEigen")
A<-matrix(rnorm(100^2),100)
all.equal(fcprd(A),tcrossprod(A))
我的笔记本电脑上的

fcprd(A)运行速度比tcrossprod(A)快得多。这就是我为A<-matrix(rnorm(1000^2),1000)获得的:

 microbenchmark::microbenchmark('tcrossprod(A)'=tcrossprod(A),'A%*%t(A)'=A%*%t(A),fcprd=fcprd(A))
 Unit: milliseconds
          expr       min       lq     mean   median       uq      max neval
 tcrossprod(A) 428.06452 435.9700 468.9323 448.8168 504.2628 618.7681   100
      A%*%t(A) 722.24053 736.6197 775.4814 767.7668 809.8356 903.8592   100
         fcprd  95.04678 100.0733 111.5021 103.6616 107.2551 197.4479   100

但是,此代码仅适用于具有双精度条目的矩阵。如何修改此代码以使其适用于复杂矩阵?

我对编程知识非常有限,但我正在努力学习。 非常感谢任何帮助!

2 个答案:

答案 0 :(得分:2)

Eigen库还通过Eigen::MatrixXcd支持复杂的条目。因此,原则上,如果您将MatrixXd替换为MatrixXcd,它应该有效。但是,这可能无法编译,因为使用as(c.f。https://github.com/RcppCore/RcppEigen/blob/master/inst/unitTests/runit.RcppEigen.R#L205)的复杂矩阵没有Map - 函数。需要as - 函数来转换R数据类型和C ++ / Eigen数据类型(c.f。http://dirk.eddelbuettel.com/code/rcpp/Rcpp-extending.pdf)。如果您不使用Map,那么您可以使用:

crossprodCpp <- '
using Eigen::MatrixXcd;
using Eigen::Lower;

const MatrixXcd A(as<MatrixXcd>(AA));
const int   m(A.rows());
MatrixXcd    AAt(MatrixXcd(m, m).setZero().selfadjointView<Lower>().rankUpdate(A));
return wrap(AAt);
'

fcprd <- inline::cxxfunction(signature(AA = "matrix"), crossprodCpp, "RcppEigen")
N <- 100
A <- matrix(complex(real = rnorm(N), imaginary = rnorm(N)), N)
all.equal(fcprd(A), A %*% Conj(t(A)))

但是,这比我的测试中的基本R版本要慢:

N <- 1000
A <- matrix(complex(real = rnorm(N * N), imaginary = rnorm(N * N)), N)
all.equal(fcprd(A), A %*% Conj(t(A)))
#> [1] TRUE
microbenchmark::microbenchmark(base = A %*% Conj(t(A)), eigen = fcprd(A))
#> Unit: milliseconds
#>   expr      min       lq     mean   median       uq      max neval
#>   base 111.6512 124.4490 145.7583 140.9199 160.3420 241.8986   100
#>  eigen 453.6702 501.5419 535.0192 537.2925 564.8746 628.4999   100

注意R中的矩阵乘法是通过BLAS完成的。但是,R使用的默认BLAS实现不是很快。提高R性能的一种方法是使用优化的BLAS库,c.f。 https://csgillespie.github.io/efficientR/set-up.html#blas-and-alternative-r-interpreters

或者,如果您有完整的BLAS,则可以使用BLAS函数zherk非常粗糙:

dyn.load("/usr/lib/libblas.so")

zherk <- function(a, uplo = 'u', trans = 'n') {
    n <- nrow(a)
    k <- ncol(a)
    c <- matrix(complex(real = 0, imaginary = 0), nrow = n, ncol = n)
    z <- .Fortran("zherk",
             uplo = as.character(uplo),
             trans = as.character(trans),
             n = as.integer(n),
             k = as.integer(k),
             alpha = as.double(1),
             a = as.complex(a),
             lda = as.integer(n),
             beta = as.double(0),
             c = as.complex(c),
             ldc = as.integer(n))
    matrix(z$c, nrow = n, ncol = n)
}

N <- 2
A <- matrix(complex(real = rnorm(N * N), imaginary = rnorm(N * N)), N)
zherk(A, uplo = "l") - A %*% Conj(t(A))

请注意,这仅填充上部(或下部)三角形部分,但速度非常快:

microbenchmark::microbenchmark(base = A %*% Conj(t(A)), blas = zherk(A))
#> Unit: milliseconds
#>  expr      min        lq      mean    median       uq      max neval
#>  base 112.5588 117.12531 146.10026 138.37565 167.6811 282.3564   100
#>  blas  66.9541  70.12438  91.44617  82.74522 108.4979 188.3728   100

答案 1 :(得分:1)

这是在Rcpp中绑定grails set-proxy client 对象的一种方法。该解决方案可以在R包设置中使用,但是我不确定使用Eigen::Map<Eigen::MatrixXcd>库将其组合在一起的简便方法。

首先,您需要在inline中提供以下专业化条件,以便将此标头包含在inst/include/mylib.h中:

RcppExports.cpp

与RcppEigenWrap.h中可用的非专用导出程序的唯一区别是最后一行上的#include <complex> #include <Eigen/Core> #include <Eigen/Dense> #include <Rcpp.h> namespace Rcpp { namespace traits { template<> class Exporter<Eigen::Map<Eigen::Matrix<std::complex<double>, Eigen::Dynamic, Eigen::Dynamic> > > { using OUT = typename Eigen::Map<Eigen::Matrix<std::complex<double>, Eigen::Dynamic, Eigen::Dynamic> >; const static int RTYPE = ::Rcpp::traits::r_sexptype_traits<std::complex<double>>::rtype; Rcpp::Vector<RTYPE> vec; int d_ncol, d_nrow; public: Exporter(SEXP x) : vec(x), d_ncol(1) , d_nrow(Rf_xlength(x)) { if (TYPEOF(x) != RTYPE) throw std::invalid_argument("Wrong R type for mapped matrix"); if (::Rf_isMatrix(x)) { int* dims = INTEGER(::Rf_getAttrib(x, R_DimSymbol)); d_nrow = dims[0]; d_ncol = dims[1]; } } OUT get() { return OUT(reinterpret_cast<std::complex<double>*>(vec.begin()), d_nrow, d_ncol); } }; }} 。具有C99复杂兼容类型的std :: complex和Rcomplex都应该具有相同的内存布局,而与实现无关。

将其包装起来,您现在可以将函数创建为:

reinterpret_cast

,然后在R中以以下方式调用该函数:

// [[Rcpp::export]]
Eigen::MatrixXd selfadj_mult(const Eigen::Map<Eigen::MatrixXcd>& mat) {
  Eigen::MatrixXd result = (mat * mat.adjoint()).real();
  return result;
}

代码使用library(mylib) library(microbenchmark) N <- 1000 A <- matrix(complex(real = rnorm(N * N), imaginary = rnorm(N * N)), N) microbenchmark::microbenchmark( base = A %*% Conj(t(A)) , eigen = mylib::selfadj_mult(A) , times = 100L ) 在centos7 / gcc83上编译。 R是使用完全相同的编译器/标志(使用默认的BLAS绑定)从源代码构建的。结果是:

-O3 -DNDEBUG -flto -march=generic