tensor_predictors/tensorPredictors/src/mtvk.c

102 lines
3.6 KiB
C

// Suppress stripping R API prefixes, all API functions have the form `Rf_<name>`
// and public variables are called `R_<name>`.
#define R_NO_REMAP
#include <R.h>
#include <Rinternals.h>
/**
* Matrix Times a Vectorized Kronecker product
*
* C = A vec(B_1 %x% ... %x% B_r)
*
* Note the reverse order of the `B_k` in the Kronecker product.
*
* @param A matrix of dimensions `n` by `pp * qq`
* @param Bs list of matrices such that the product there Kronecker product has
* dimensions `pp` by `qq`.
*/
extern SEXP mtvk(SEXP A, SEXP Bs) {
if ((TYPEOF(A) != REALSXP) || !Rf_isMatrix(A)) {
Rf_error("First argument must be a numeric matrices");
}
// extract dimensions from `A` and `Bs`
size_t nrow = Rf_nrows(A);
size_t ncol = Rf_ncols(A);
size_t r = Rf_length(Bs);
size_t pp = 1; // `nrow(Reduce("%x%", Bs))` see below
size_t qq = 1; // `ncol(Reduce("%x%", Bs))` see below
// retrieve dimensions and direct memory access for each component in
// reverse order
// (Allocated memory freed at function exit and errors)
double** bs = (double**)R_alloc(r, sizeof(double*));
// dimensions of reshaped `A` columns, we treat the matrix `A` as an
// `nrow` by `p[1]` by ... by `p[2 r]` tensor where the `p[1:r]` dimensions
// as the row and `p[(r + 1):(2 r)]` the column dimensions of the Kronecker
// product components.
size_t* p = (size_t*)R_alloc(2 * r + 1, sizeof(size_t));
// Multi-Index into the reshaped `A`:
// `A[i, j] == do.call(array(A, dim = c(nrow, p), c(i, J)))`
size_t* J = (size_t*)R_alloc(2 * r + 1, sizeof(size_t));
// reversed order of Kronecker components
for (size_t k = 0; k < r; ++k) {
SEXP Bk = VECTOR_ELT(Bs, k);
if ((TYPEOF(Bk) == REALSXP) && Rf_isMatrix(Bk)) {
bs[r - 1 - k] = REAL(Bk);
pp *= (p[ r - 1 - k] = Rf_nrows(Bk));
qq *= (p[2 * r - 1 - k] = Rf_ncols(Bk));
// set all zero
J[k] = J[k + r] = 0;
} else {
Rf_error("Second argument must be a list of numeric matrices");
}
}
// Additional last element such that the condition `p[2 * r] <= (++J[2 * r])`
// (evaluated exactly once) is false. Allows to skip the check `k < 2 * r`
// in the loop for updating the Multi-Index `J`.
p[2 * r] = 127; // biggest value of smallest signed type
J[2 * r] = 0;
// check if dimensions match
if ((ncol != pp * qq) || (r < 1)) {
Rf_error("Dimension Missmatch");
}
// Create new R result vector
SEXP C = PROTECT(Rf_allocVector(REALSXP, nrow));
double* c = REAL(C);
memset(c, 0, nrow * sizeof(double));
// main operation (single iteration over `A`)
double* a = REAL(A);
for (size_t j = 0; j < ncol; ++j) {
// Compute `prod_k (B_k)_{J_k, J_k+r}` (identical for each row)
double prod_BJ = 1.0;
for (size_t k = 0; k < r; ++k) {
prod_BJ *= bs[k][J[k] + p[k] * J[k + r]];
}
// Add `j`th summand `A_i,J prod_k (B_k)_{J_k, J_k+r}` to each component
for (size_t i = 0; i < nrow; ++i) {
c[i] += a[i + j * nrow] * prod_BJ;
}
// Compute matching Multi-Index `J` for reshaped `A` columns
// In `R` the relation between `j` and `J` is
// `A[i, j] == do.call(array(A, dim = c(nrow, p), c(i, J)))`
// Note: the check `k < 2 * r` is skipped as described above.
for (size_t k = 0; p[k] <= (++J[k]); ++k) {
J[k] = 0;
}
}
// Onle the result object `C` needs to be unprotected
UNPROTECT(1);
return C;
}