Search code examples
c++armadillo

More efficient way to access lower triangular elements using Armadillo


Is there a more efficient way to create a vector containing the lower triangular elements of a matrix? For certain algorithms it is useful to just have these elements in a vector.

However, the code below obviously creates copies, when I'd prefer pointers. This is probably not possible given the non-contigous nature of the element positions.

One alternative I thought about is to create an index matrix via find(trimatu(inmat)!=0) or so, but I can't imagine that to be more efficient. Finding exact zeroes in a matrix of doubles is usually not very fast, and also there may be actual 0s in within the triangular I am trying to extract. Something along those lines has been discussed here (C++ Armadillo Access Triangular Matrix Elements), however, the question is 5 years old and armadillo has improved a lot since then.

vec trimat2vec(mat const& inmat){

  int K = inmat.n_rows;
  int p = K*(K-1)/2;
  vec out(p);

  int counter=0;
  for(int i=0; i<K; i++){
    for(int j=1; j<K; j++){
      if(i<j){
        out(counter)=inmat(j,i);
        counter+=1;
      }
    }
  }
  return(out);
}

Solution

  • Ok, with the new version Armadillo 9.870, there is now a better way to do this. The speed improvement is small, especially on small matrices, but it appears to be consistent. More importantly, the code is much shorter.

    I call the new function trimat2vec_new.

    vec trimat2vec_new(mat const& inmat){
      return(inmat.elem(trimatl_ind(size(inmat),-1)));
    }
    

    Simple benchmark in R:

    a=matrix(1:900,30,30)
    
    all(trimat2vec_new(a) == trimat2vec(a))
    
    library(microbenchmark)
    microbenchmark(trimat2vec_new(a),
                   trimat2vec(a),
                   times = 1000)
    

    yields:

    Unit: microseconds
                  expr   min     lq     mean median    uq    max neval cld
     trimat2vec_new(a) 3.026 3.4060 3.633960  3.557 3.727 20.549  1000  a 
         trimat2vec(a) 3.116 3.6515 3.955116  3.787 3.958 42.981  1000   b
    

    I especially like the convenience of the new trimatl_ind function. And the small speed improvement.