@Override
  protected double computeNormalizationEnergy() {
    final double alpha = _alphaMinusOne + 1;
    final double logBeta = _beta != 0 ? Math.log(_beta) : 0.0;

    return -(org.apache.commons.math3.special.Gamma.logGamma(alpha) - alpha * logBeta);
  }
 /**
  * Compute the error of Stirling's series at the given value.
  *
  * <p>References:
  *
  * <ol>
  *   <li>Eric W. Weisstein. "Stirling's Series." From MathWorld--A Wolfram Web Resource. <a
  *       target="_blank" href="http://mathworld.wolfram.com/StirlingsSeries.html">
  *       http://mathworld.wolfram.com/StirlingsSeries.html</a>
  * </ol>
  *
  * @param z the value.
  * @return the Striling's series error.
  */
 static double getStirlingError(double z) {
   double ret;
   if (z < 15.0) {
     double z2 = 2.0 * z;
     if (FastMath.floor(z2) == z2) {
       ret = EXACT_STIRLING_ERRORS[(int) z2];
     } else {
       ret = Gamma.logGamma(z + 1.0) - (z + 0.5) * FastMath.log(z) + z - HALF_LOG_2_PI;
     }
   } else {
     double z2 = z * z;
     ret =
         (0.083333333333333333333
                 - (0.00277777777777777777778
                         - (0.00079365079365079365079365
                                 - (0.000595238095238095238095238
                                         - 0.0008417508417508417508417508 / z2)
                                     / z2)
                             / z2)
                     / z2)
             / z;
   }
   return ret;
 }
 /** Recompute the normalization factor. */
 private void recomputeZ() {
   if (Double.isNaN(z)) {
     z = Gamma.logGamma(alpha) + Gamma.logGamma(beta) - Gamma.logGamma(alpha + beta);
   }
 }
  // Make proposal
  @Override
  public BlockProposal next(Value[] currentValue, Domain[] variableDomain) {
    final DimpleRandom rand = activeRandom();

    double proposalForwardEnergy = 0;
    double proposalReverseEnergy = 0;
    int argumentIndex = 0;
    int argumentLength = currentValue.length;
    Value[] newValue = new Value[argumentLength];
    for (int i = 0; i < argumentLength; i++) newValue[i] = Value.create(variableDomain[i]);

    // Get the current alpha values
    double[] alpha;
    double[] alphaEnergy;
    double alphaSum = 0;
    if (_customFactor.isAlphaEnergyRepresentation()) {
      alphaEnergy = _customFactor.getCurrentAlpha();
      alpha = new double[alphaEnergy.length];
      for (int i = 0; i < alphaEnergy.length; i++) {
        alpha[i] = Math.exp(-alphaEnergy[i]);
        alphaSum += alpha[i];
      }
    } else {
      alpha = _customFactor.getCurrentAlpha();
      alphaEnergy = new double[alpha.length];
      for (int i = 0; i < alpha.length; i++) {
        alphaEnergy[i] = -Math.log(alpha[i]);
        alphaSum += alpha[i];
      }
    }
    if (alphaSum == 0) // Shouldn't happen, but can during initialization
    {
      Arrays.fill(alpha, 1);
      Arrays.fill(alphaEnergy, 0);
      alphaSum = alpha.length;
    }

    int nextN = _constantN;
    if (!_hasConstantN) {
      // If N is variable, sample N uniformly
      int previousN = currentValue[argumentIndex].getIndex();
      int NDomainSize = requireNonNull(variableDomain[0].asDiscrete()).size();
      nextN = rand.nextInt(NDomainSize);
      newValue[argumentIndex].setIndex(nextN);
      argumentIndex++;

      // Add this portion of -log p(x_proposed -> x_previous)
      proposalReverseEnergy +=
          -org.apache.commons.math3.special.Gamma.logGamma(previousN + 1)
              + previousN * Math.log(alphaSum);

      // Add this portion of -log p(x_previous -> x_proposed)
      proposalForwardEnergy +=
          -org.apache.commons.math3.special.Gamma.logGamma(nextN + 1) + nextN * Math.log(alphaSum);
    }

    // Given N and alpha, resample the outputs
    // Multinomial formed by successively sampling from a binomial and subtracting each count from
    // the total
    // FIXME: Assumes all outputs are variable (no constant outputs)
    int remainingN = nextN;
    int alphaIndex = 0;
    for (; argumentIndex < argumentLength; argumentIndex++, alphaIndex++) {
      double alphai = alpha[alphaIndex];
      double alphaEnergyi = alphaEnergy[alphaIndex];
      int previousX = currentValue[argumentIndex].getIndex();
      int nextX;
      if (argumentIndex < argumentLength - 1)
        nextX = rand.nextBinomial(remainingN, alphai / alphaSum);
      else // Last value
      nextX = remainingN;
      newValue[argumentIndex].setIndex(nextX);
      remainingN -= nextX; // Subtract the sample value from the remaining total count
      alphaSum -= alphai; // Subtract this alpha value from the sum used for normalization

      double previousXNegativeLogAlphai;
      double nextXNegativeLogAlphai;
      if (alphai == 0 && previousX == 0) previousXNegativeLogAlphai = 0;
      else previousXNegativeLogAlphai = previousX * alphaEnergyi;
      if (alphai == 0 && nextX == 0) nextXNegativeLogAlphai = 0;
      else nextXNegativeLogAlphai = nextX * alphaEnergyi;

      // Add this portion of -log p(x_proposed -> x_previous)
      proposalReverseEnergy +=
          previousXNegativeLogAlphai
              + org.apache.commons.math3.special.Gamma.logGamma(previousX + 1);

      // Add this portion of -log p(x_previous -> x_proposed)
      proposalForwardEnergy +=
          nextXNegativeLogAlphai + org.apache.commons.math3.special.Gamma.logGamma(nextX + 1);
    }

    return new BlockProposal(newValue, proposalForwardEnergy, proposalReverseEnergy);
  }