/////////////////////////////////////////////////////////////////////////////
// Compute variance with the diagonal of the Hessian
/////////////////////////////////////////////////////////////////////////////
void CBradleyTerry::GetVariance(double *pdVariance) const
{
 ConvertEloToGamma();

 const double x = std::log(10.0) / 400;
 const double xx = x * x;

 for (int Player = crs.GetPlayers(); --Player >= 0;)
 {
  double Diag = 0;
  double PlayerGamma = pGamma[Player];

  for (int j = crs.GetOpponents(Player); --j >= 0;)
  {
   const CCondensedResult &cr = crs.GetCondensedResult(Player, j);
   double OpponentGamma = pGamma[cr.Opponent];
 
   double h = 0;

   {
    double d = ThetaW * PlayerGamma + ThetaD * OpponentGamma;
    h += (cr.w_ij + cr.d_ij) / (d * d);
   }
   {
    double d = ThetaD * ThetaW * PlayerGamma + OpponentGamma;
    h += (cr.l_ij + cr.d_ij) / (d * d);
   }
   {
    double d = ThetaW * OpponentGamma + ThetaD * PlayerGamma;
    h += (cr.w_ji + cr.d_ji) / (d * d);
   }
   {
    double d = ThetaD * ThetaW * OpponentGamma + PlayerGamma;
    h += (cr.l_ji + cr.d_ji) / (d * d);
   }

   h *= PlayerGamma * OpponentGamma * ThetaD * ThetaW;
   Diag -= h;
  }

  pdVariance[Player] = -1.0 / (Diag * xx);
 }
}
/////////////////////////////////////////////////////////////////////////////
// Compute the covariance matrix
// This function assumes that ratings are maximum-likelihood ratings
/////////////////////////////////////////////////////////////////////////////
void CBradleyTerry::ComputeCovariance() {
  //
  // Compute the truncated opposite of the Hessian
  //
  CMatrix mTruncatedHessian(crs.GetPlayers() - 1, crs.GetPlayers() - 1);
  {
    ConvertEloToGamma();

    const double x = std::log(10.0) / 400;
    const double xx = -x * x;

    mTruncatedHessian.Zero();

    for (int Player = crs.GetPlayers() - 1; --Player >= 0;) {
      double Diag = 0;
      double PlayerGamma = pGamma[Player];

      for (int j = crs.GetOpponents(Player); --j >= 0;) {
        const CCondensedResult &cr = crs.GetCondensedResult(Player, j);
        double OpponentGamma = pGamma[cr.Opponent];

        double h = 0;

        {
          double d = ThetaW * PlayerGamma + ThetaD * OpponentGamma;
          h += (cr.w_ij + cr.d_ij) / (d * d);
        }
        {
          double d = ThetaD * ThetaW * PlayerGamma + OpponentGamma;
          h += (cr.l_ij + cr.d_ij) / (d * d);
        }
        {
          double d = ThetaW * OpponentGamma + ThetaD * PlayerGamma;
          h += (cr.w_ji + cr.d_ji) / (d * d);
        }
        {
          double d = ThetaD * ThetaW * OpponentGamma + PlayerGamma;
          h += (cr.l_ji + cr.d_ji) / (d * d);
        }

        h *= PlayerGamma * OpponentGamma * ThetaD * ThetaW;
        Diag -= h;
        if (cr.Opponent != crs.GetPlayers() - 1)
          mTruncatedHessian.SetElement(Player, cr.Opponent, h * xx);
      }

      mTruncatedHessian.SetElement(Player, Player, Diag * xx);
    }
  }

  //
  // LU-Decompose it
  //
  CLUDecomposition lud(crs.GetPlayers() - 1);
  std::vector<int> vIndex(crs.GetPlayers() - 1);
  lud.Decompose(mTruncatedHessian, &vIndex[0]);

  //
  // Fill A
  //
  CMatrix mA(crs.GetPlayers(), crs.GetPlayers() - 1);
  {
    double x = -1.0 / crs.GetPlayers();
    for (int i = crs.GetPlayers() * (crs.GetPlayers() - 1); --i >= 0;)
      mA[i] = x;
    for (int i = crs.GetPlayers() - 1; --i >= 0;)
      mA.SetElement(i, i, 1.0 + x);
  }

  //
  // Compute AC
  //
  CMatrix mAC(crs.GetPlayers(), crs.GetPlayers() - 1);
  for (int i = crs.GetPlayers(); --i >= 0;) {
    int Index = i * (crs.GetPlayers() - 1);
    lud.Solve(mTruncatedHessian, &vIndex[0], mA + Index, mAC + Index);
  }

  //
  // Compute the covariance
  //
  mCovariance.SetProductByTranspose(mAC, mA);
}