// The need for `USE_FC_LEN_T` and `FCONE` is due to a Fortran character string // to C incompatibility. See: Writing R Extentions: 6.6.1 Fortran character strings #define USE_FC_LEN_T #include #include #include #ifndef FCONE #define FCONE #endif void mcrossprod( const int rank, const double* A, const int* dimA, const double* B, const int* dimB, const int mode, double* C ) { // the strides // `stride[0] <- prod(dim(A)[seq_len(mode - 1)])` // `stride[1] <- dim(A)[mode]` // `stride[2] <- prod(dim(A)[-seq_len(mode)])` // Note: Middle stride is ignored (to be consistent with sym version) int stride[3] = {1, 0, 1}; for (int i = 0; i < rank; ++i) { int size = dimA[i]; stride[0] *= (i < mode) ? size : 1; stride[2] *= (i > mode) ? size : 1; } // employ BLAS dgemm (Double GEneralized Matrix Matrix) operation // (C = alpha op(A) op(A) + beta C, op is the identity of transposition) const double zero = 0.0; const double one = 1.0; if (mode == 0) { // mode 1: special case C = A_(1) B_(1)^T // C = 1 A B^T + 0 C F77_CALL(dgemm)("N", "T", &dimA[mode], &dimB[mode], &stride[2], &one, A, &dimA[mode], B, &dimB[mode], &zero, C, &dimA[mode] FCONE FCONE); } else { // Other modes writen as accumulated sum of matrix products // initialize C to zero memset(C, 0, dimA[mode] * dimB[mode] * sizeof(double)); // Sum over all modes > mode for (int i2 = 0; i2 < stride[2]; ++i2) { // C = 1 A^T B + 1 C F77_CALL(dgemm)("T", "N", &dimA[mode], &dimB[mode], &stride[0], &one, &A[i2 * stride[0] * dimA[mode]], &stride[0], &B[i2 * stride[0] * dimB[mode]], &stride[0], &one, C, &dimA[mode] FCONE FCONE); } } } /** * Tensor Mode Crossproduct * * C = A_(m) t(B_(m)) * * For a matrices `A` and `B`, the first mode `mcrossprod(A, B, 1)` is equivalent * to `A %*% t(B)` (`tcrossprod`). On the other hand for mode two * `mcrossprod(A, B, 2)` the equivalence is `t(A) %*% B` (`crossprod`). * * @param A multi-dimensional array * @param B multi-dimensional array * @param m mode index (1-indexed) */ extern SEXP R_mcrossprod(SEXP A, SEXP B, SEXP m) { // get zero indexed mode int mode = asInteger(m) - 1; // Check if both `A` and `B` are real-valued if (!isReal(A) || !isReal(B)) { error("Type missmatch, both `A` and `B` must be real-valued"); } // get dimension attributes SEXP dimA_sexp = getAttrib(A, R_DimSymbol); SEXP dimB_sexp = getAttrib(B, R_DimSymbol); // dimension type validation if (!isInteger(dimA_sexp) || !isInteger(dimB_sexp)) { error("Dimensions must be integer vectors"); } // validate dimensions int rank = length(dimA_sexp); if (rank != length(dimB_sexp)) { error("Dimension mismatch"); } // validate mode (0-indexed, must be smaller than the tensor order) if (mode < 0 || rank <= mode) { error("Illegal mode"); } // get raw pointers to dimensions int* dimA = INTEGER(coerceVector(dimA_sexp, INTSXP)); int* dimB = INTEGER(coerceVector(dimB_sexp, INTSXP)); // finaly, check for `A` and `B` dimensions to match for (int i = 0; i < rank; ++i) { if (i != mode && dimA[i] != dimB[i]) { error("Dimension mismatch"); } } // create response matrix C SEXP C = PROTECT(allocMatrix(REALSXP, dimA[mode], dimB[mode])); // Call C mode crossprod subroutine mcrossprod( rank, // tensor rank of both `A` and `B` REAL(A), dimA, // mem. addr. of A, dim(A) REAL(B), dimB, // mem. addr. of B, dim(B) mode, // the crossproduct mode to compute REAL(C) // return value memory addr. ); // release C to garbage collector UNPROTECT(1); return C; } /** * Symmetric Tensor Mode Crossproduct * * C = A_(m) t(A_(m)) * * For a matrix `A`, the first mode is `mcrossprod_sym(A, 1)` equivalent to * `A %*% t(A)` (`tcrossprod`). On the other hand for mode two * `mcrossprod(A, 2)` the equivalence is `t(A) %*% A` (`crossprod`). * * @param A multi-dimensional array * @param m mode index (1-indexed) */ extern SEXP R_mcrossprod_sym(SEXP A, SEXP m) { // get zero indexed mode int mode = asInteger(m) - 1; // get dimension attribute of A SEXP dim = getAttrib(A, R_DimSymbol); // validate mode (0-indexed, must be smaller than the tensor order) if (mode < 0 || length(dim) <= mode) { error("Illegal mode"); } // the strides // `stride[0] <- prod(dim(X)[seq_len(mode - 1)])` // `stride[1] <- dim(X)[mode]` // `stride[2] <- prod(dim(X)[-seq_len(mode)])` int stride[3] = {1, INTEGER(dim)[mode], 1}; for (int i = 0; i < length(dim); ++i) { int size = INTEGER(dim)[i]; stride[0] *= (i < mode) ? size : 1; stride[2] *= (i > mode) ? size : 1; } // create response matrix C SEXP C = PROTECT(allocMatrix(REALSXP, stride[1], stride[1])); // raw data access pointers double* a = REAL(A); double* c = REAL(C); // employ BLAS dsyrk (Double SYmmeric Rank K) operation // (C = alpha A A^T + beta C or C = alpha A^T A + beta C) const double zero = 0.0; const double one = 1.0; if (mode == 0) { // mode 1: special case C = A_(1) A_(1)^T // C = 1 A A^T + 0 C F77_CALL(dsyrk)("U", "N", &stride[1], &stride[2], &one, a, &stride[1], &zero, c, &stride[1] FCONE FCONE); } else { // Other modes writen as accumulated sum of matrix products // initialize C to zero memset(c, 0, stride[1] * stride[1] * sizeof(double)); // Sum over all modes > mode for (int i2 = 0; i2 < stride[2]; ++i2) { // C = 1 A^T A + 1 C F77_CALL(dsyrk)("U", "T", &stride[1], &stride[0], &one, &a[i2 * stride[0] * stride[1]], &stride[0], &one, c, &stride[1] FCONE FCONE); } } // Symmetric matrix result is stored in upper triangular part only // Copy upper triangular part to lower for (int j = 0; j + 1 < stride[1]; j++) { for (int i = j + 1; i < stride[1]; ++i) { c[i + j * stride[1]] = c[j + i * stride[1]]; } } // release C to grabage collector UNPROTECT(1); return C; }