private void incrementOuterProducts(
      int thisOffset, int childOffset0, int childOffset1, double precision0, double precision1) {

    final double[][] outerProduct = wishartStatistics.getScaleMatrix();

    for (int k = 0; k < numData; k++) {

      for (int i = 0; i < dimTrait; i++) {

        // final double wChild0i = meanCache[childOffset0 + k * dimTrait + i] * precision0;
        // final double wChild1i = meanCache[childOffset1 + k * dimTrait + i] * precision1;
        final double wChild0i =
            cacheHelper.getCorrectedMeanCache()[childOffset0 + k * dimTrait + i] * precision0;
        final double wChild1i =
            cacheHelper.getCorrectedMeanCache()[childOffset1 + k * dimTrait + i] * precision1;

        for (int j = 0; j < dimTrait; j++) {

          // final double child0j = meanCache[childOffset0 + k * dimTrait + j];
          // final double child1j = meanCache[childOffset1 + k * dimTrait + j];
          final double child0j =
              cacheHelper.getCorrectedMeanCache()[childOffset0 + k * dimTrait + j];
          final double child1j =
              cacheHelper.getCorrectedMeanCache()[childOffset1 + k * dimTrait + j];

          outerProduct[i][j] += wChild0i * child0j;
          outerProduct[i][j] += wChild1i * child1j;

          // outerProduct[i][j] -= (wChild0i + wChild1i) * meanCache[thisOffset + k * dimTrait + j];
          outerProduct[i][j] -=
              (wChild0i + wChild1i) * cacheHelper.getMeanCache()[thisOffset + k * dimTrait + j];
        }
      }
    }
    wishartStatistics.incrementDf(1); // Peeled one node
  }
  private void incrementRemainderDensities(
      double[][] precisionMatrix,
      double logDetPrecisionMatrix,
      int thisIndex,
      int thisOffset,
      int childOffset0,
      int childOffset1,
      double precision0,
      double precision1,
      double OUFactor0,
      double OUFactor1,
      boolean cacheOuterProducts) {

    final double remainderPrecision = precision0 * precision1 / (precision0 + precision1);

    if (cacheOuterProducts) {
      incrementOuterProducts(thisOffset, childOffset0, childOffset1, precision0, precision1);
    }

    for (int k = 0; k < numData; k++) {

      double childSS0 = 0;
      double childSS1 = 0;
      double crossSS = 0;

      for (int i = 0; i < dimTrait; i++) {

        // In case of no drift, getCorrectedMeanCache() simply returns mean cache
        // final double wChild0i = meanCache[childOffset0 + k * dimTrait + i] * precision0;
        final double wChild0i =
            cacheHelper.getCorrectedMeanCache()[childOffset0 + k * dimTrait + i] * precision0;
        // final double wChild1i = meanCache[childOffset1 + k * dimTrait + i] * precision1;
        final double wChild1i =
            cacheHelper.getCorrectedMeanCache()[childOffset1 + k * dimTrait + i] * precision1;

        for (int j = 0; j < dimTrait; j++) {

          // subtract "correction"
          // final double child0j = meanCache[childOffset0 + k * dimTrait + j];
          final double child0j =
              cacheHelper.getCorrectedMeanCache()[childOffset0 + k * dimTrait + j];
          // subtract "correction"
          // final double child1j = meanCache[childOffset1 + k * dimTrait + j];
          final double child1j =
              cacheHelper.getCorrectedMeanCache()[childOffset1 + k * dimTrait + j];

          childSS0 += wChild0i * precisionMatrix[i][j] * child0j;
          childSS1 += wChild1i * precisionMatrix[i][j] * child1j;

          // make sure meanCache in following is not "corrected"
          // crossSS += (wChild0i + wChild1i) * precisionMatrix[i][j] * meanCache[thisOffset + k *
          // dimTrait + j];
          crossSS +=
              (wChild0i + wChild1i)
                  * precisionMatrix[i][j]
                  * cacheHelper.getMeanCache()[thisOffset + k * dimTrait + j];
        }
      }

      logRemainderDensityCache[thisIndex] +=
          -dimTrait * LOG_SQRT_2_PI
              + 0.5 * (dimTrait * Math.log(remainderPrecision) + logDetPrecisionMatrix)
              - 0.5 * (childSS0 + childSS1 - crossSS)
              - dimTrait * (Math.log(OUFactor0) + Math.log(OUFactor1));
    }
  }
  public double calculateLogLikelihood() {

    double logLikelihood = 0;
    double[][] traitPrecision = diffusionModel.getPrecisionmatrix();
    double logDetTraitPrecision = Math.log(diffusionModel.getDeterminantPrecisionMatrix());
    double[] conditionalRootMean = tmp2;

    final boolean computeWishartStatistics = getComputeWishartSufficientStatistics();

    if (computeWishartStatistics) {
      //            if (wishartStatistics == null) {
      wishartStatistics = new WishartSufficientStatistics(dimTrait);
      //            } else {
      //                wishartStatistics.clear();
      //            }
    }

    // Use dynamic programming to compute conditional likelihoods at each internal node
    postOrderTraverse(
        treeModel,
        treeModel.getRoot(),
        traitPrecision,
        logDetTraitPrecision,
        computeWishartStatistics);

    if (DEBUG) {
      System.err.println("mean: " + new Vector(cacheHelper.getMeanCache()));
      System.err.println("correctedMean: " + new Vector(cacheHelper.getCorrectedMeanCache()));
      System.err.println("upre: " + new Vector(upperPrecisionCache));
      System.err.println("lpre: " + new Vector(lowerPrecisionCache));
      System.err.println("cach: " + new Vector(logRemainderDensityCache));
    }

    // Compute the contribution of each datum at the root
    final int rootIndex = treeModel.getRoot().getNumber();

    // Precision scalar of datum conditional on root
    double conditionalRootPrecision = lowerPrecisionCache[rootIndex];

    for (int datum = 0; datum < numData; datum++) {

      double thisLogLikelihood = 0;

      // Get conditional mean of datum conditional on root
      // System.arraycopy(meanCache, rootIndex * dim + datum * dimTrait, conditionalRootMean, 0,
      // dimTrait);
      System.arraycopy(
          cacheHelper.getMeanCache(),
          rootIndex * dim + datum * dimTrait,
          conditionalRootMean,
          0,
          dimTrait);

      if (DEBUG) {
        System.err.println("Datum #" + datum);
        System.err.println("root mean: " + new Vector(conditionalRootMean));
        System.err.println("root prec: " + conditionalRootPrecision);
        System.err.println("diffusion prec: " + new Matrix(traitPrecision));
      }

      // B = root prior precision
      // z = root prior mean
      // A = likelihood precision
      // y = likelihood mean

      // y'Ay
      double yAy =
          computeWeightedAverageAndSumOfSquares(
              conditionalRootMean,
              Ay,
              traitPrecision,
              dimTrait,
              conditionalRootPrecision); // Also fills in Ay

      if (conditionalRootPrecision != 0) {
        thisLogLikelihood +=
            -LOG_SQRT_2_PI * dimTrait
                + 0.5
                    * (logDetTraitPrecision + dimTrait * Math.log(conditionalRootPrecision) - yAy);
      }

      if (DEBUG) {
        double[][] T = new double[dimTrait][dimTrait];
        for (int i = 0; i < dimTrait; i++) {
          for (int j = 0; j < dimTrait; j++) {
            T[i][j] = traitPrecision[i][j] * conditionalRootPrecision;
          }
        }
        System.err.println("Conditional root MVN precision = \n" + new Matrix(T));
        System.err.println(
            "Conditional root MVN density = "
                + MultivariateNormalDistribution.logPdf(
                    conditionalRootMean,
                    new double[dimTrait],
                    T,
                    Math.log(MultivariateNormalDistribution.calculatePrecisionMatrixDeterminate(T)),
                    1.0));
      }

      if (integrateRoot) {
        // Integrate root trait out against rootPrior
        thisLogLikelihood +=
            integrateLogLikelihoodAtRoot(
                conditionalRootMean,
                Ay,
                tmpM,
                traitPrecision,
                conditionalRootPrecision); // Ay is destroyed
      }

      if (DEBUG) {
        System.err.println("yAy = " + yAy);
        System.err.println(
            "logLikelihood (before remainders) = "
                + thisLogLikelihood
                + " (should match conditional root MVN density when root not integrated out)");
      }

      logLikelihood += thisLogLikelihood;
    }

    logLikelihood += sumLogRemainders();
    if (DEBUG) {
      System.out.println("logLikelihood is " + logLikelihood);
    }

    if (DEBUG) { // Root trait is univariate!!!
      System.err.println("logLikelihood (final) = " + logLikelihood);
      //            checkViaLargeMatrixInversion();
    }

    if (DEBUG_PNAS) {
      checkLogLikelihood(
          logLikelihood,
          sumLogRemainders(),
          conditionalRootMean,
          conditionalRootPrecision,
          traitPrecision);
    }

    areStatesRedrawn = false; // Should redraw internal node states when needed
    return logLikelihood;
  }