使三角形(或通常为正方形)矩阵对称

时间:2018-09-29 18:02:10

标签: c r performance matrix

我正在尝试从R中的下三角矩阵创建对称矩阵。

在先前的问与答(Convert upper triangular part of a matrix to symmetric matrix)用户李哲源中,对于大型矩阵,这不应该在R中完成,而在C中提出解决方案。但是我不理解C和以前从未使用过Rccp这样的例子,所以不知道如何解释答案。但是很明显,那里的C代码会生成我不想要的随机数(rnorm)。 我想放入一个正方形矩阵并得出一个对称矩阵。

对于给定的方阵A,其下部三角形中有数据,我如何在C中高效创建对称矩阵并在R中使用对称矩阵?

2 个答案:

答案 0 :(得分:5)

快速适应as.matrix on a distance object is extremely slow; how to make it faster?中的dist2mat功能。

library(Rcpp)

cppFunction('NumericMatrix Mat2Sym(NumericMatrix A, bool up2lo, int bf) {

  IntegerVector dim = A.attr("dim");
  size_t n = (size_t)dim[0], m = (size_t)dim[1];
  if (n != m) stop("A is not a square matrix!");

  /* use pointers */
  size_t j, i, jj, ni, nj;
  double *A_jj, *A_ij, *A_ji, *col, *row, *end;

  /* cache blocking factor */
  size_t b = (size_t)bf;

  /* copy lower triangular to upper triangular; cache blocking applied */
  for (j = 0; j < n; j += b) {
    nj = n - j; if (nj > b) nj = b;
    /* diagonal block has size nj x nj */
    A_jj = &A(j, j);
    for (jj = nj - 1; jj > 0; jj--, A_jj += n + 1) {
      /* copy a column segment to a row segment (or vise versa) */
      col = A_jj + 1; row = A_jj + n;
      for (end = col + jj; col < end; col++, row += n) {
        if (up2lo) *col = *row; else *row = *col;
        }
      }
    /* off-diagonal blocks */
    for (i = j + nj; i < n; i += b) {
      ni = n - i; if (ni > b) ni = b;
      /* off-diagonal block has size ni x nj */
      A_ij = &A(i, j); A_ji = &A(j, i);
      for (jj = 0; jj < nj; jj++) {
        /* copy a column segment to a row segment (or vise versa) */
        col = A_ij + jj * n; row = A_ji + jj;
        for (end = col + ni; col < end; col++, row += n) {
          if (up2lo) *col = *row; else *row = *col;
          }
        }
      }
    }

  return A;
  }')

对于方阵A,此函数Mat2Sym将其下三角部分(带有换位)复制到其上三角部分,以使其在up2lo = FALSE下对称,而在{ {1}}。

请注意,该功能覆盖 up2lo = TRUE,而不会占用额外的内存。要保留输入矩阵并创建新的输出矩阵,请将A而不是A + 0传递到函数中。

A

## an arbitrary asymmetric square matrix
set.seed(0)
A <- matrix(runif(25), 5)
#          [,1]      [,2]       [,3]      [,4]      [,5]
#[1,] 0.8966972 0.2016819 0.06178627 0.7698414 0.7774452
#[2,] 0.2655087 0.8983897 0.20597457 0.4976992 0.9347052
#[3,] 0.3721239 0.9446753 0.17655675 0.7176185 0.2121425
#[4,] 0.5728534 0.6607978 0.68702285 0.9919061 0.6516738
#[5,] 0.9082078 0.6291140 0.38410372 0.3800352 0.1255551

## lower triangular to upper triangular; don't overwrite
B <- Mat2Sym(A + 0, up2lo = FALSE, 128)
#          [,1]      [,2]      [,3]      [,4]      [,5]
#[1,] 0.8966972 0.2655087 0.3721239 0.5728534 0.9082078
#[2,] 0.2655087 0.8983897 0.9446753 0.6607978 0.6291140
#[3,] 0.3721239 0.9446753 0.1765568 0.6870228 0.3841037
#[4,] 0.5728534 0.6607978 0.6870228 0.9919061 0.3800352
#[5,] 0.9082078 0.6291140 0.3841037 0.3800352 0.1255551

## A is unchanged
A
#          [,1]      [,2]       [,3]      [,4]      [,5]
#[1,] 0.8966972 0.2016819 0.06178627 0.7698414 0.7774452
#[2,] 0.2655087 0.8983897 0.20597457 0.4976992 0.9347052
#[3,] 0.3721239 0.9446753 0.17655675 0.7176185 0.2121425
#[4,] 0.5728534 0.6607978 0.68702285 0.9919061 0.6516738
#[5,] 0.9082078 0.6291140 0.38410372 0.3800352 0.1255551

使用## upper triangular to lower triangular; overwrite D <- Mat2Sym(A, up2lo = TRUE, 128) # [,1] [,2] [,3] [,4] [,5] #[1,] 0.89669720 0.2016819 0.06178627 0.7698414 0.7774452 #[2,] 0.20168193 0.8983897 0.20597457 0.4976992 0.9347052 #[3,] 0.06178627 0.2059746 0.17655675 0.7176185 0.2121425 #[4,] 0.76984142 0.4976992 0.71761851 0.9919061 0.6516738 #[5,] 0.77744522 0.9347052 0.21214252 0.6516738 0.1255551 ## A has been changed; D and A are aliased in memory A # [,1] [,2] [,3] [,4] [,5] #[1,] 0.89669720 0.2016819 0.06178627 0.7698414 0.7774452 #[2,] 0.20168193 0.8983897 0.20597457 0.4976992 0.9347052 #[3,] 0.06178627 0.2059746 0.17655675 0.7176185 0.2121425 #[4,] 0.76984142 0.4976992 0.71761851 0.9919061 0.6516738 #[5,] 0.77744522 0.9347052 0.21214252 0.6516738 0.1255551 软件包

Matrix对于稀疏矩阵特别有用。为了兼容性,它还为密集矩阵定义了一些类,例如“ dgeMatrix”,“ dtrMatrix”,“ dtpMatrix”,“ dsyMatrix”,“ dspMatrix”。

鉴于正方形Matrix,使A对称的方法如下。

Matrix

set.seed(0) A <- matrix(runif(25), 5) # [,1] [,2] [,3] [,4] [,5] #[1,] 0.8966972 0.2016819 0.06178627 0.7698414 0.7774452 #[2,] 0.2655087 0.8983897 0.20597457 0.4976992 0.9347052 #[3,] 0.3721239 0.9446753 0.17655675 0.7176185 0.2121425 #[4,] 0.5728534 0.6607978 0.68702285 0.9919061 0.6516738 #[5,] 0.9082078 0.6291140 0.38410372 0.3800352 0.1255551 ## equivalent to: Mat2Sym(A + 0, TRUE, 128) new("dsyMatrix", x = base::c(A), Dim = dim(A), uplo = "U") #5 x 5 Matrix of class "dsyMatrix" # [,1] [,2] [,3] [,4] [,5] #[1,] 0.89669720 0.2016819 0.06178627 0.7698414 0.7774452 #[2,] 0.20168193 0.8983897 0.20597457 0.4976992 0.9347052 #[3,] 0.06178627 0.2059746 0.17655675 0.7176185 0.2121425 #[4,] 0.76984142 0.4976992 0.71761851 0.9919061 0.6516738 #[5,] 0.77744522 0.9347052 0.21214252 0.6516738 0.1255551 ## equivalent to: Mat2Sym(A + 0, FALSE, 128) new("dsyMatrix", x = base::c(A), Dim = dim(A), uplo = "L") #5 x 5 Matrix of class "dsyMatrix" # [,1] [,2] [,3] [,4] [,5] #[1,] 0.8966972 0.2655087 0.3721239 0.5728534 0.9082078 #[2,] 0.2655087 0.8983897 0.9446753 0.6607978 0.6291140 #[3,] 0.3721239 0.9446753 0.1765568 0.6870228 0.3841037 #[4,] 0.5728534 0.6607978 0.6870228 0.9919061 0.3800352 #[5,] 0.9082078 0.6291140 0.3841037 0.3800352 0.1255551 方法不理想的原因有三个:

  1. 它需要传递Matrix槽作为数值矢量,因此我们必须做x,这实际上在RAM中创建了矩阵的副本;
  2. 它不能进行就地修改,因此将创建一个新的矩阵副本作为输出矩阵;
  3. 进行转置复制时,它不会进行缓存阻止。

这里是一个快速的比较:

base::c(A)

请注意library(bench) A <- matrix(runif(5000 * 5000), 5000) bench::mark("Mat2Sym" = Mat2Sym(A, FALSE, 128), "Matrix" = new("dsyMatrix", x = base::c(A), Dim = dim(A), uplo = "L"), check = FALSE) # expression min mean median max `itr/sec` mem_alloc n_gc n_itr # <chr> <bch:tm> <bch:tm> <bch:tm> <bch:t> <dbl> <bch:byt> <dbl> <int> #1 Mat2Sym 56.8ms 57.7ms 57.4ms 59.4ms 17.3 2.48KB 0 9 #2 Matrix 334.3ms 337.4ms 337.4ms 340.6ms 2.96 190.74MB 2 2 有多快。同样,在“覆盖”模式下不会进行内存分配。

As G. Grothendieck mentions,我们也可以使用“ dspMatrix”。

Mat2Sym

同样,由于使用set.seed(0) A <- matrix(runif(25), 5) # [,1] [,2] [,3] [,4] [,5] #[1,] 0.8966972 0.2016819 0.06178627 0.7698414 0.7774452 #[2,] 0.2655087 0.8983897 0.20597457 0.4976992 0.9347052 #[3,] 0.3721239 0.9446753 0.17655675 0.7176185 0.2121425 #[4,] 0.5728534 0.6607978 0.68702285 0.9919061 0.6516738 #[5,] 0.9082078 0.6291140 0.38410372 0.3800352 0.1255551 ## equivalent to: Mat2Sym(A + 0, TRUE, 128) new("dspMatrix", x = A[upper.tri(A, TRUE)], Dim = dim(A), uplo = "U") #5 x 5 Matrix of class "dspMatrix" # [,1] [,2] [,3] [,4] [,5] #[1,] 0.89669720 0.2016819 0.06178627 0.7698414 0.7774452 #[2,] 0.20168193 0.8983897 0.20597457 0.4976992 0.9347052 #[3,] 0.06178627 0.2059746 0.17655675 0.7176185 0.2121425 #[4,] 0.76984142 0.4976992 0.71761851 0.9919061 0.6516738 #[5,] 0.77744522 0.9347052 0.21214252 0.6516738 0.1255551 ## equivalent to: Mat2Sym(A + 0, FALSE, 128) new("dspMatrix", x = A[lower.tri(A, TRUE)], Dim = dim(A), uplo = "L") #5 x 5 Matrix of class "dspMatrix" # [,1] [,2] [,3] [,4] [,5] #[1,] 0.8966972 0.2655087 0.3721239 0.5728534 0.9082078 #[2,] 0.2655087 0.8983897 0.9446753 0.6607978 0.6291140 #[3,] 0.3721239 0.9446753 0.1765568 0.6870228 0.3841037 #[4,] 0.5728534 0.6607978 0.6870228 0.9919061 0.3800352 #[5,] 0.9082078 0.6291140 0.3841037 0.3800352 0.1255551 Matrixupper.tri是次优的方法。

lower.tri

特别是,我们看到使用“ dspMatrix”的效率甚至比使用“ dsyMatrix”的效率低。

答案 1 :(得分:2)

在使用C / C ++进行可能的过早优化之前,请检查是否有密集矩阵

A + t(A)

就足够了(假设A的唯一非零元素在对角线以下或对角线上方。

此外,如果存在内存问题,那么Matrix程序包具有打包的对称类dspMatrix,可以这样创建:

library(Matrix)

A <- matrix(c(0, 2, 3, 0, 0, 4, 0, 0, 0), 3) # dense lower triangular test input
dspA <- as(A + t(A), "dspMatrix")

给予:

> str(dspA)
Formal class 'dspMatrix' [package "Matrix"] with 5 slots
  ..@ x       : num [1:6] 0 2 0 3 4 0   <- only 6 elements stored, not 9
  ..@ Dim     : int [1:2] 3 3
  ..@ Dimnames:List of 2
  .. ..$ : NULL
  .. ..$ : NULL
  ..@ uplo    : chr "U"
  ..@ factors : list()

或者可以直接从上三角部分创建它:

# use upper triangular part since we already created dspA that way
tA <- t(A)
dspA2 <- new("dspMatrix", Dim = as.integer(c(3,3)), 
  x = tA[upper.tri(tA, diag = TRUE)])

identical(dspA, dspA2)
## [1] TRUE