#include "R_api.h" #include "ttm.h" int mlm( /* options */ const int* trans, const int* modes, const int nrhs, /* dims */ const int* dimA, const int* dimC, const int ord, /* scalars */ const double alpha, /* tensor */ const double* A, /* matrices */ const double** Bs, const int* ldBs, /* scalar */ const double beta, /* tensor */ double* C, double* work_mem ) { // Compute total size of `A`, `C` and required working memory int sizeA = 1; // `prod(dim(A))` int sizeC = 1; // `prod(dim(C))` int work_size = 2; // `2 * prod(pmax(dim(A), dim(C)))` (`+ ord` NOT included) for (int i = 0; i < ord; ++i) { sizeA *= dimA[i]; sizeC *= dimC[i]; work_size *= dimA[i] < dimC[i] ? dimC[i] : dimA[i]; } // In requesting working memory size stop here and return size if (work_mem == NULL) { return work_size + ord; } // Copy dimensions of A to intermediate temp. dim int* dimT = (int*)(work_mem + work_size); // `work_mem` is `ord` longer memcpy(dimT, dimA, ord * sizeof(int)); // Setup work memory hooks (two swapable blocks) double* tmp1 = work_mem; double* tmp2 = work_mem + (work_size >> 1); // Multi Linear Multiplication is an iterated application of TTM for (int i = 0; i < nrhs; ++i) { // Get current `B` dimensions (only implicitly given) int nrowB = dimC[modes[i]]; int ncolB = dimA[modes[i]]; if (trans && trans[i]) { nrowB = dimA[modes[i]]; ncolB = dimC[modes[i]]; } // Tensor Times Matrix (`modes[i]` mode products) ttm(trans ? trans[i] : 0, modes[i], dimT, ord, nrowB, ncolB, 1.0, i ? tmp2 : A, Bs[i], ldBs ? ldBs[i] : dimC[modes[i]], 0.0, tmp1); // Update intermediate temp dim dimT[modes[i]] = dimC[modes[i]]; // Swap tmp1 <-> tmp2 double* tmp3 = tmp1; tmp1 = tmp2; tmp2 = tmp3; } // dAXPY (a x + y) with `x = tmp2` and `y = beta C` if (beta != 0.0) { for (int i = 0; i < sizeC; ++i) { C[i] *= beta; } } else { memset(C, 0, sizeC * sizeof(double)); } axpy(sizeC, alpha, tmp2, 1, C, 1); return 0; } /** * Multi Linear Multiplication (`R` binding) */ extern SEXP R_mlm(SEXP A, SEXP Bs, SEXP ms, SEXP ops) { // get dimension attribute of A SEXP dimA = Rf_getAttrib(A, R_DimSymbol); int ord = Rf_length(dimA); // Check if `A` is a real valued tensor if (!Rf_isReal(A) || Rf_isNull(dimA)) { Rf_error("Param. `A` need to be a real valued array"); } // Validate that `Bs` is a list if (!Rf_isNewList(Bs)) { Rf_error("Param. `Bs` need to be a list of matrices"); } if (!Rf_isInteger(ms) || !Rf_isLogical(ops)) { Rf_error("Param. type missmatch, expected modes as ints and ops logical"); } // Number of `B` matrices (Nr of Right Hand Side objects) int nrhs = Rf_length(Bs); // further parameter validations if ((nrhs != Rf_length(ms)) || (nrhs != Rf_length(ops))) { Rf_error("Dimension missmatch (Params length differ)"); } // In case of an empty RHS (nr. of B's) is zero, this reduces to the identity if (nrhs == 0) { return A; } // Get modes and operations for Bs (transposed or not) int* modes = (int*)R_alloc(nrhs, sizeof(int)); // 0-indexed version of `ms` int* trans = INTEGER(ops); // Compute result dimensions `dim(C)` while extracting `B` data pointers SEXP dimC = PROTECT(Rf_duplicate(dimA)); const double** bs = (const double**)R_alloc(nrhs, sizeof(double*)); for (int i = 0; i < nrhs; ++i) { // Extract right hand side matrices (`B` matrices from list) SEXP B = VECTOR_ELT(Bs, i); if (!(Rf_isMatrix(B) && Rf_isReal(B))) { UNPROTECT(1); Rf_error("Param. `Bs` need to be a list of real matrices"); } int* dimB = INTEGER(Rf_getAttrib(B, R_DimSymbol)); bs[i] = REAL(B); // Convert 1-indexed modes to be 0-indexed modes[i] = INTEGER(ms)[i] - 1; // Check if mode is out or range (indexing a non-existing mode of `A`) if (!((0 <= modes[i]) && (modes[i] < ord))) { UNPROTECT(1); Rf_error("%d'th mode (%d) out of range", i + 1, modes[i] + 1); } // Check if `i`th mode of `A` matches corresponding `B` dimension if (INTEGER(dimA)[modes[i]] != dimB[!trans[i]]) { UNPROTECT(1); Rf_error("%d'th mode (%d) dimension missmatch", i + 1, modes[i] + 1); } INTEGER(dimC)[modes[i]] = dimB[!!trans[i]]; } // Now, compute `C`s size `prod(dim(C))` int sizeC = 1; for (int i = 0; i < ord; ++i) { sizeC *= INTEGER(dimC)[i]; } // Allocate response `C` SEXP C = PROTECT(Rf_allocVector(REALSXP, sizeC)); Rf_setAttrib(C, R_DimSymbol, dimC); // allocate working memory size int work_size = mlm(trans, modes, nrhs, INTEGER(dimA), INTEGER(dimC), ord, 1.0, REAL(A), bs, NULL, 0.0, REAL(C), NULL); double* work_memory = (double*)R_alloc(work_size, sizeof(double)); // Compute Multi-Linear Multiplication (call subroutine) (void)mlm(trans, modes, nrhs, INTEGER(dimA), INTEGER(dimC), ord, 1.0, REAL(A), bs, NULL, 0.0, REAL(C), work_memory); // release C to the hands of the garbage collector UNPROTECT(2); return C; }