MatrixXd MultivariateFNormalSufficient::compute_PW_direct() const
{
    //Eigen::LLT<MatrixXd, Eigen::Upper> ldlt(get_ldlt());
    Eigen::LDLT<MatrixXd, Eigen::Upper> ldlt(get_ldlt());
    MatrixXd tmp(ldlt.solve(get_W()));
    return tmp;
}
std::vector<double> MultivariateFNormalSufficient::get_norms() const
{
    if (!flag_norms_)
    {
        //Eigen::LLT<MatrixXd, Eigen::Upper> ldlt(get_ldlt());
        Eigen::LDLT<MatrixXd, Eigen::Upper> ldlt(get_ldlt());
        // determinant and derived constants
        //MatrixXd L(ldlt.matrixU());
        //std::cout << "L: " << L << std::endl << std::endl;
        //std::cout << "D: " << ldlt.vectorD() << std::endl << std::endl;
        //double logDetSigma=2*L.diagonal().array().log().sum() ;
        double logDetSigma = ldlt.vectorD().array().abs().log().sum();
        LOG( "MVN:   det(Sigma) = " <<exp(logDetSigma) << std::endl);
        double norm=pow(2*PI*SQUARE(factor_), -double(N_*M_)/2.0)
                    * exp(-double(N_)/2.0*logDetSigma);
        double lnorm=double(N_*M_)/2 * log(2*PI*SQUARE(factor_))
            + double(N_)/2 * logDetSigma;
        LOG( "MVN:   norm = " << norm << " lnorm = "
                << lnorm << std::endl);
        const_cast<MultivariateFNormalSufficient *>(this)
            ->set_norms(norm,lnorm);
    }
    std::vector<double> norms;
    norms.push_back(norm_);
    norms.push_back(lnorm_);
    return norms;
}
IMP_Eigen::MatrixXd GaussianProcessInterpolation::get_d2cov_dOm_dOm(
    Floats q, unsigned m, unsigned n) const {
  IMP_Eigen::VectorXd wq(get_wx_vector(q));
  IMP_Eigen::VectorXd L(get_ldlt().solve(wq));
  IMP_Eigen::MatrixXd Omi(get_Omi());
  IMP_Eigen::MatrixXd tmp(Omi.col(m) * L.transpose());
  return -L(n) * (tmp + tmp.transpose());
}
IMP_Eigen::MatrixXd GaussianProcessInterpolation::get_d2cov_dwq_dOm(
    Floats q, unsigned m) const {
  IMP_Eigen::VectorXd wq(get_wx_vector(q));
  IMP_Eigen::VectorXd L(get_ldlt().solve(wq));
  IMP_Eigen::MatrixXd Omi(get_Omi());
  IMP_Eigen::MatrixXd ret(L * Omi.col(m).transpose());
  return ret + ret.transpose();
}
IMP_Eigen::MatrixXd
GaussianProcessInterpolation::get_posterior_covariance_matrix(FloatsList x)
    const {
  unsigned N(x.size());
  IMP_Eigen::MatrixXd Wpri(M_, N);
  for (unsigned i = 0; i < N; i++) Wpri.col(i) = get_wx_vector(x[i]);
  IMP_Eigen::LDLT<IMP_Eigen::MatrixXd, IMP_Eigen::Upper> ldlt(get_ldlt());
  // we can now use covariance_function_ because it is up to date
  IMP_Eigen::MatrixXd Wpost((*covariance_function_)(x));
  return Wpost - Wpri.transpose() * ldlt.solve(Wpri);
}
VectorXd MultivariateFNormalSufficient::get_Peps() const
{
    if (!flag_Peps_)
    {
        ////Peps
        LOG( "MVN:   solving for P*epsilon" << std::endl);
        const_cast<MultivariateFNormalSufficient *>(this)
            ->set_Peps(get_ldlt().solve(get_epsilon()));
    }
    return Peps_;
}
MatrixXd MultivariateFNormalSufficient::get_P() const
{
    if (!flag_P_)
    {
        //inverse
        //Eigen::LLT<MatrixXd, Eigen::Upper> ldlt(get_ldlt());
        Eigen::LDLT<MatrixXd, Eigen::Upper> ldlt(get_ldlt());
        LOG( "MVN:   solving for inverse" << std::endl);
        const_cast<MultivariateFNormalSufficient *>(this)
            ->set_P(ldlt.solve(MatrixXd::Identity(M_,M_)));
    }
    return P_;
}
VectorXd MultivariateFNormalSufficient::get_Peps() const
{
    if (!flag_Peps_)
    {
        ////Peps
        timer_.start(SOLVE);
        IMP_LOG(TERSE, "MVN:   solving for P*epsilon" << std::endl);
        const_cast<MultivariateFNormalSufficient *>(this)
            ->set_Peps(get_ldlt().solve(get_epsilon()));
        timer_.stop(SOLVE);
    }
    return Peps_;
}
double GaussianProcessInterpolation::get_posterior_covariance(Floats x1,
                                                              Floats x2) const {
  // std::cerr << "posterior covariance at q=" << x1(0) << std::endl;
  IMP_Eigen::VectorXd wx2(get_wx_vector(x2));
  IMP_Eigen::VectorXd wx1;
  if (x1 != x2) {
    wx1 = get_wx_vector(x1);
  } else {
    wx1 = wx2;
  }
  double ret = wx1.transpose() * get_ldlt().solve(wx2);
  return (*covariance_function_)(x1, x2)[0] - ret;  // licit because Omi
                                                    // is up to date
}
IMP_Eigen::MatrixXd GaussianProcessInterpolation::get_dcov_dOm(Floats q) const {
  IMP_Eigen::VectorXd wq(get_wx_vector(q));
  IMP_Eigen::LDLT<IMP_Eigen::MatrixXd, IMP_Eigen::Upper> ldlt(get_ldlt());
  IMP_Eigen::VectorXd ret(ldlt.solve(wq));
  return ret * ret.transpose();
}
void GaussianProcessInterpolation::compute_Omi() {
  // get inverse
  IMP_LOG_TERSE("  compute_Omi: inverse" << std::endl);
  IMP_Eigen::LDLT<IMP_Eigen::MatrixXd, IMP_Eigen::Upper> ldlt(get_ldlt());
  Omi_ = ldlt.solve(IMP_Eigen::MatrixXd::Identity(M_, M_));
}
MatrixXd MultivariateFNormalSufficient::solve(MatrixXd B) const
{
    return get_ldlt().solve(B);
}