private void preOrderTraverseSample(
      MultivariateTraitTree treeModel,
      NodeRef node,
      int parentIndex,
      double[][] treePrecision,
      double[][] treeVariance) {

    final int thisIndex = node.getNumber();

    if (treeModel.isRoot(node)) {
      // draw root

      double[] rootMean = new double[dimTrait];
      final int rootIndex = treeModel.getRoot().getNumber();
      double rootPrecision = lowerPrecisionCache[rootIndex];

      for (int datum = 0; datum < numData; datum++) {
        // System.arraycopy(meanCache, thisIndex * dim + datum * dimTrait, rootMean, 0, dimTrait);
        System.arraycopy(
            cacheHelper.getMeanCache(), thisIndex * dim + datum * dimTrait, rootMean, 0, dimTrait);

        double[][] variance =
            computeMarginalRootMeanAndVariance(
                rootMean, treePrecision, treeVariance, rootPrecision);

        double[] draw =
            MultivariateNormalDistribution.nextMultivariateNormalVariance(rootMean, variance);

        if (DEBUG_PREORDER) {
          Arrays.fill(draw, 1.0);
        }

        System.arraycopy(draw, 0, drawnStates, rootIndex * dim + datum * dimTrait, dimTrait);

        if (DEBUG) {
          System.err.println("Root mean: " + new Vector(rootMean));
          System.err.println("Root var : " + new Matrix(variance));
          System.err.println("Root draw: " + new Vector(draw));
        }
      }
    } else { // draw conditional on parentState

      if (!missingTraits.isCompletelyMissing(thisIndex)
          && !missingTraits.isPartiallyMissing(thisIndex)) {

        // System.arraycopy(meanCache, thisIndex * dim, drawnStates, thisIndex * dim, dim);
        System.arraycopy(
            cacheHelper.getMeanCache(), thisIndex * dim, drawnStates, thisIndex * dim, dim);

      } else {

        if (missingTraits.isPartiallyMissing(thisIndex)) {
          throw new RuntimeException("Partially missing values are not yet implemented");
        }
        // This code should work for sampling a missing tip trait as well, but needs testing

        // parent trait at drawnStates[parentOffset]
        double precisionToParent = 1.0 / getRescaledBranchLengthForPrecision(node);
        double precisionOfNode = lowerPrecisionCache[thisIndex];
        double totalPrecision = precisionOfNode + precisionToParent;

        double[] mean = Ay; // temporary storage
        double[][] var = tmpM; // temporary storage

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

          int parentOffset = parentIndex * dim + datum * dimTrait;
          int thisOffset = thisIndex * dim + datum * dimTrait;

          if (DEBUG) {
            double[] parentValue = new double[dimTrait];
            System.arraycopy(drawnStates, parentOffset, parentValue, 0, dimTrait);
            System.err.println("Parent draw: " + new Vector(parentValue));
            if (parentValue[0] != drawnStates[parentOffset]) {
              throw new RuntimeException("Error in setting indices");
            }
          }

          for (int i = 0; i < dimTrait; i++) {
            mean[i] =
                (drawnStates[parentOffset + i] * precisionToParent
                        //  + meanCache[thisOffset + i] * precisionOfNode) / totalPrecision;
                        + cacheHelper.getMeanCache()[thisOffset + i] * precisionOfNode)
                    / totalPrecision;
            for (int j = 0; j < dimTrait; j++) {
              var[i][j] = treeVariance[i][j] / totalPrecision;
            }
          }
          double[] draw = MultivariateNormalDistribution.nextMultivariateNormalVariance(mean, var);
          System.arraycopy(draw, 0, drawnStates, thisOffset, dimTrait);

          if (DEBUG) {
            System.err.println("Int prec: " + totalPrecision);
            System.err.println("Int mean: " + new Vector(mean));
            System.err.println("Int var : " + new Matrix(var));
            System.err.println("Int draw: " + new Vector(draw));
            System.err.println("");
          }
        }
      }
    }

    if (peel() && !treeModel.isExternal(node)) {
      preOrderTraverseSample(
          treeModel, treeModel.getChild(node, 0), thisIndex, treePrecision, treeVariance);
      preOrderTraverseSample(
          treeModel, treeModel.getChild(node, 1), thisIndex, treePrecision, treeVariance);
    }
  }
  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;
  }