
/**
    Linear statistics for conditional inference based on Strasser & Weber (1999)
    *\file LinearStatistic.c
    *\author $Author: thothorn $
    *\date $Date: 2019-03-04 11:23:08 +0100 (Mo, 04 Mär 2019) $
*/
    
#include "party.h"


/**
    Computes the linear statistic, formula (1) in the paper\n
    *\param x values of the transformation
    *\param p dimension of the transformation
    *\param y values of the influence function
    *\param q dimension of the influence function
    *\param weights case weights
    *\param n number of observations
    *\param ans return value; a pointer to a REALSXP-vector of length pq
*/
  
void C_LinearStatistic (const double *x, const int p,
                        const double *y, const int q,
                        const double *weights, const int n,
                        double *ans) {
              
    int i, j, k, kp, kn;
    double tmp;

    for (k = 0; k < q; k++) {

        kn = k * n;
        kp = k * p;
        for (j = 0; j < p; j++) ans[kp + j] = 0.0;
            
        for (i = 0; i < n; i++) {
                
            /* optimization: weights are often zero */
            if (weights[i] == 0.0) continue;
                
            tmp = y[kn + i] * weights[i];
                
            for (j = 0; j < p; j++)
                 ans[kp + j] += x[j*n + i] * tmp;
        }
    }
}


/**
    R-interface to C_LinearStatistic \n
    *\param x values of the transformation
    *\param y values of the influence function
    *\param weights case weights
*/

SEXP R_LinearStatistic(SEXP x, SEXP y, SEXP weights) {

    /* the return value; a vector of type REALSXP */
    SEXP ans;

    /* dimensions */
    int n, p, q;

    /* 
     *    only a basic check: we do not coerce objects since this
     *    function is for internal use only
     */
    
    if (!isReal(x) || !isReal(y) || !isReal(weights))
        error("LinStat: arguments are not of type REALSXP");
    
    n = nrow(y);
    if (nrow(x) != n || LENGTH(weights) != n)
        error("LinStat: dimensions don't match");

    q    = ncol(y);
    p    = ncol(x);
           
    PROTECT(ans = allocVector(REALSXP, p*q));
 
    C_LinearStatistic(REAL(x), p, REAL(y), q, REAL(weights), n, 
                      REAL(ans));

    UNPROTECT(1);
    return(ans);
}


/**
    Conditional expectation and covariance of the influence function\n
    *\param y values of the influence function
    *\param q dimension of the influence function
    *\param weights case weights
    *\param n number of observations
    *\param ans return value; an object of class `ExpectCovarInfluence'
*/

void C_ExpectCovarInfluence(const double* y, const int q,
                            const double* weights, const int n, 
                            SEXP ans) {

    int i, j, k, jq;
    
    /* pointers to the slots of object ans */
    double *dExp_y, *dCov_y, *dsweights, tmp;
    
    /*  return values: set to zero initially */
    dExp_y = REAL(GET_SLOT(ans, PL2_expectationSym));
    for (j = 0; j < q; j++) dExp_y[j] = 0.0;
    
    dCov_y = REAL(GET_SLOT(ans, PL2_covarianceSym));
    for (j = 0; j < q*q; j++) dCov_y[j] = 0.0;
    
    dsweights = REAL(GET_SLOT(ans, PL2_sumweightsSym));

    /*  compute the sum of the weights */
        
    dsweights[0] = 0;
    for (i = 0; i < n; i++) dsweights[0] += weights[i];
    if (dsweights[0] <= 1) 
        error("C_ExpectCovarInfluence: sum of weights is less than one");

    /*
     *    Expectation of the influence function
     */

    for (i = 0; i < n; i++) {

        /*  observations with zero case weights do not contribute */
    
        if (weights[i] == 0.0) continue;
    
        for (j = 0; j < q; j++)
            dExp_y[j] += weights[i] * y[j * n + i];
    }

    for (j = 0; j < q; j++)
        dExp_y[j] = dExp_y[j] / dsweights[0];


    /*
     *    Covariance of the influence function
     */

    for (i = 0; i < n; i++) {

        if (weights[i] == 0.0) continue;
     
        for (j = 0; j < q; j++) {
            tmp = weights[i] * (y[j * n + i] - dExp_y[j]);
            jq = j * q;
            for (k = 0; k < q; k++)
                dCov_y[jq + k] += tmp * (y[k * n + i] - dExp_y[k]);
        }
    }

    for (j = 0; j < q*q; j++)
        dCov_y[j] = dCov_y[j] / dsweights[0];
}


/**
    R-interface to C_ExpectCovarInfluence\n
    *\param y values of the influence function
    *\param weights case weights
*/

SEXP R_ExpectCovarInfluence(SEXP y, SEXP weights) {

    SEXP ans;
    int q, n;
    
    if (!isReal(y) || !isReal(weights))
        error("R_ExpectCovarInfluence: arguments are not of type REALSXP");
    
    n = nrow(y);
    q = ncol(y);
    
    if (LENGTH(weights) != n) 
        error("R_ExpectCovarInfluence: vector of case weights does not have %d elements", n);

    /*  allocate storage for return values */
    PROTECT(ans = party_NEW_OBJECT("ExpectCovarInfluence"));
    SET_SLOT(ans, PL2_expectationSym, 
             PROTECT(allocVector(REALSXP, q)));
    SET_SLOT(ans, PL2_covarianceSym, 
             PROTECT(allocMatrix(REALSXP, q, q)));
    SET_SLOT(ans, PL2_sumweightsSym, 
             PROTECT(allocVector(REALSXP, 1)));

    C_ExpectCovarInfluence(REAL(y), q, REAL(weights), n, ans);
    
    UNPROTECT(4);
    return(ans);
}


/**
    Conditional expectation and covariance of the a linear statistic\n
    *\param x values of the transformation
    *\param p dimension of the transformation
    *\param y values of the influence function
    *\param q dimension of the influence function
    *\param weights case weights
    *\param n number of observations
    *\param expcovinf an object of class `ExpectCovarInfluence'
    *\param ans return value; an object of class `ExpectCovar'
*/

void C_ExpectCovarLinearStatistic(const double* x, const int p, 
                                  const double* y, const int q,
                                  const double* weights, const int n,
                                  const SEXP expcovinf, SEXP ans) {

    int i, j, k, pq;
    double sweights = 0.0, f1, f2, tmp;
    double *swx, *CT1, *CT2, *Covy_x_swx, 
           *dExp_y, *dCov_y, *dExp_T, *dCov_T;
    
    pq   = p * q;
    
    /* the expectation and covariance of the influence function */
    dExp_y = REAL(GET_SLOT(expcovinf, PL2_expectationSym));
    dCov_y = REAL(GET_SLOT(expcovinf, PL2_covarianceSym));
    sweights = REAL(GET_SLOT(expcovinf, PL2_sumweightsSym))[0];

    if (sweights <= 1.0) 
        error("C_ExpectCovarLinearStatistic: sum of weights is less than one");

    /* prepare for storing the results */
    dExp_T = REAL(GET_SLOT(ans, PL2_expectationSym));
    dCov_T = REAL(GET_SLOT(ans, PL2_covarianceSym));

    /* allocate storage: all helpers, initially zero */
    swx = Calloc(p, double);               /* p x 1  */
    CT1 = Calloc(p * p, double);           /* p x p  */

    for (i = 0; i < n; i++) {

        /*  observations with zero case weights do not contribute */
        if (weights[i] == 0.0) continue;
    
        for (k = 0; k < p; k++) {
            tmp = weights[i] * x[k * n + i];
            swx[k] += tmp;

            /* covariance part */
            for (j = 0; j < p; j++) {
                CT1[j * p + k] += tmp * x[j * n + i];
            }
        }
    }

    /*
    *   dExp_T: expectation of the linear statistic T
    */

    for (k = 0; k < p; k++) {
        for (j = 0; j < q; j++)
            dExp_T[j * p + k] = swx[k] * dExp_y[j];
    }

    /* 
    *   dCov_T:  covariance of the linear statistic T
    */

    f1 = sweights/(sweights - 1);
    f2 = (1/(sweights - 1));

    if (pq == 1) {
        dCov_T[0] = f1 * dCov_y[0] * CT1[0];
        dCov_T[0] -= f2 * dCov_y[0] * swx[0] * swx[0];
    } else {
        /* two more helpers needed */
        CT2 = Calloc(pq * pq, double);            /* pq x pq */
        Covy_x_swx = Calloc(pq * q, double);      /* pq x q  */
        
        C_kronecker(dCov_y, q, q, CT1, p, p, dCov_T);
        C_kronecker(dCov_y, q, q, swx, p, 1, Covy_x_swx);
        C_kronecker(Covy_x_swx, pq, q, swx, 1, p, CT2);

        for (k = 0; k < (pq * pq); k++)
            dCov_T[k] = f1 * dCov_T[k] - f2 * CT2[k];

        /* clean up */
        Free(CT2); Free(Covy_x_swx);
    }

    /* clean up */
    Free(swx); Free(CT1); 
}


/**
    R-interface to C_ExpectCovarLinearStatistic\n
    *\param x values of the transformation
    *\param y values of the influence function
    *\param weights case weights
    *\param expcovinf an object of class `ExpectCovarInfluence'
*/

SEXP R_ExpectCovarLinearStatistic(SEXP x, SEXP y, SEXP weights, 
                                  SEXP expcovinf) {
    
    SEXP ans;
    int n, p, q, pq;

    /* determine the dimensions and some checks */

    n  = nrow(x);
    p  = ncol(x);
    q  = ncol(y);
    pq = p * q;
    
    if (nrow(y) != n)
        error("y does not have %d rows", n);
    if (LENGTH(weights) != n) 
        error("vector of case weights does not have %d elements", n);

    PROTECT(ans = party_NEW_OBJECT("ExpectCovar"));
    SET_SLOT(ans, PL2_expectationSym, 
             PROTECT(allocVector(REALSXP, pq)));
    SET_SLOT(ans, PL2_covarianceSym, 
             PROTECT(allocMatrix(REALSXP, pq, pq)));

    C_ExpectCovarLinearStatistic(REAL(x), p, REAL(y), q, 
        REAL(weights), n, expcovinf, ans);
    
    UNPROTECT(3);
    return(ans);
}


/**
    Linear Statistic with permuted indices\n
    *\param x values of the transformation
    *\param p dimension of the transformation
    *\param y values of the influence function
    *\param q dimension of the influence function
    *\param n number of observations
    *\param nperm number of permutations
    *\param indx indices for the x-part
    *\param perm (permuted) indices for the y-part
    *\param ans return value; a pointer to a REALSXP-vector of length pq
*/

void C_PermutedLinearStatistic(const double *x, const int p,
                               const double *y, const int q,
                               const int n, const int nperm,
                               const int *indx, const int *perm, 
                               double *ans) {

    int i, j, k, kp, kn, knpi;

    for (k = 0; k < q; k++) {

        kn = k * n;
        kp = k * p;
        for (j = 0; j < p; j++) ans[kp + j] = 0.0;
            
        for (i = 0; i < nperm; i++) {
                
            knpi = kn + perm[i];

            for (j = 0; j < p; j++)
                ans[kp + j] += x[j*n + indx[i]] * y[knpi];
        }
    }
}


/**
    Linear Statistic with permuted indices\n
    *\param x values of the transformation
    *\param y values of the influence function
    *\param indx indices for the x-part
    *\param perm (permuted) indices for the y-part
*/

SEXP R_PermutedLinearStatistic(SEXP x, SEXP y, SEXP indx, SEXP perm) {

    SEXP ans;
    int n, nperm, p, q, i, *iperm, *iindx;

    /* 
       only a basic check
    */

    if (!isReal(x) || !isReal(y))
        error("R_PermutedLinearStatistic: arguments are not of type REALSXP");
    
    if (!isInteger(perm))
        error("R_PermutedLinearStatistic: perm is not of type INTSXP");
    if (!isInteger(indx))
        error("R_PermutedLinearStatistic: indx is not of type INTSXP");
    
    n = nrow(y);
    nperm = LENGTH(perm);
    iperm = INTEGER(perm);
    if (LENGTH(indx)  != nperm)
        error("R_PermutedLinearStatistic: dimensions don't match");
    iindx = INTEGER(indx);

    if (nrow(x) != n)
        error("R_PermutedLinearStatistic: dimensions don't match");

    for (i = 0; i < nperm; i++) {
        if (iperm[i] < 0 || iperm[i] > (n - 1) )
            error("R_PermutedLinearStatistic: perm is not between 1 and nobs");
        if (iindx[i] < 0 || iindx[i] > (n - 1) )
            error("R_PermutedLinearStatistic: indx is not between 1 and nobs");
    }

    q    = ncol(y);
    p    = ncol(x);
           
    PROTECT(ans = allocVector(REALSXP, p*q));
    
    C_PermutedLinearStatistic(REAL(x), p, REAL(y), q, n, nperm,
                 iindx, iperm, REAL(ans));
    
    UNPROTECT(1);
    return(ans);
}
