/** Resets the markov chain */
  public void reset() {
    currentLength = 0;

    // reset operator acceptance levels
    for (int i = 0; i < schedule.getOperatorCount(); i++) {
      schedule.getOperator(i).reset();
    }
  }
  /**
   * Updates the proposal parameter, based on the target acceptance probability This method relies
   * on the proposal parameter being a decreasing function of acceptance probability.
   *
   * @param op The operator
   * @param logr
   */
  private void coerceAcceptanceProbability(CoercableMCMCOperator op, double logr) {

    if (isCoercable(op)) {
      final double p = op.getCoercableParameter();

      final double i = schedule.getOptimizationTransform(MCMCOperator.Utils.getOperationCount(op));

      final double target = op.getTargetAcceptanceProbability();

      final double newp = p + ((1.0 / (i + 1.0)) * (Math.exp(logr) - target));

      if (newp > -Double.MAX_VALUE && newp < Double.MAX_VALUE) {
        op.setCoercableParameter(newp);
      }
    }
  }
  /**
   * Run the chain for a given number of states.
   *
   * @param length number of states to run the chain.
   *     <p>param onTheFlyOperatorWeights
   */
  public long runChain(long length, boolean disableCoerce /*,int onTheFlyOperatorWeights*/) {

    likelihood.makeDirty();
    currentScore = evaluate(likelihood, prior);

    long currentState = currentLength;

    final Model currentModel = likelihood.getModel();

    if (currentState == 0) {
      initialScore = currentScore;
      bestScore = currentScore;
      fireBestModel(currentState, currentModel);
    }

    if (currentScore == Double.NEGATIVE_INFINITY) {

      // identify which component of the score is zero...
      if (prior != null) {
        double logPrior = prior.getLogPrior(likelihood.getModel());

        if (logPrior == Double.NEGATIVE_INFINITY) {
          throw new IllegalArgumentException(
              "The initial model is invalid because one of the priors has zero probability.");
        }
      }

      String message = "The initial likelihood is zero";
      if (likelihood instanceof CompoundLikelihood) {
        message += ": " + ((CompoundLikelihood) likelihood).getDiagnosis();
      } else {
        message += "!";
      }
      throw new IllegalArgumentException(message);
    }

    pleaseStop = false;
    isStopped = false;

    String diagnostic = "";

    // int otfcounter = onTheFlyOperatorWeights > 0 ? onTheFlyOperatorWeights : 0;

    double[] logr = {0.0};

    boolean usingFullEvaluation = true;
    // set ops count in mcmc element instead
    if (fullEvaluationCount == 0) // Temporary solution until full code review
    usingFullEvaluation = false;
    boolean fullEvaluationError = false;

    while (!pleaseStop && (currentState < (currentLength + length))) {

      // periodically log states
      fireCurrentModel(currentState, currentModel);

      if (pleaseStop) {
        isStopped = true;
        break;
      }

      // Get the operator
      final int op = schedule.getNextOperatorIndex();
      final MCMCOperator mcmcOperator = schedule.getOperator(op);

      double oldScore = currentScore;

      // assert Profiler.startProfile("Store");

      // The current model is stored here in case the proposal fails
      if (currentModel != null) {
        currentModel.storeModelState();
      }

      // assert Profiler.stopProfile("Store");

      boolean operatorSucceeded = true;
      double hastingsRatio = 1.0;
      boolean accept = false;

      logr[0] = -Double.MAX_VALUE;

      try {
        // The new model is proposed
        // assert Profiler.startProfile("Operate");

        if (DEBUG) {
          System.out.println("\n&& Operator: " + mcmcOperator.getOperatorName());
        }

        if (mcmcOperator instanceof GeneralOperator) {
          hastingsRatio = ((GeneralOperator) mcmcOperator).operate(prior, likelihood);
        } else {
          hastingsRatio = mcmcOperator.operate();
        }

        // assert Profiler.stopProfile("Operate");
      } catch (OperatorFailedException e) {
        operatorSucceeded = false;
      }

      double score = 0.0;
      double deviation = 0.0;

      //    System.err.print("" + currentState + ": ");
      if (operatorSucceeded) {

        // The new model is proposed
        // assert Profiler.startProfile("Evaluate");

        if (DEBUG) {
          System.out.println("** Evaluate");
        }

        long elapsedTime = 0;
        if (PROFILE) {
          elapsedTime = System.currentTimeMillis();
        }

        // The new model is evaluated
        score = evaluate(likelihood, prior);

        if (PROFILE) {
          mcmcOperator.addEvaluationTime(System.currentTimeMillis() - elapsedTime);
        }

        // assert Profiler.stopProfile("Evaluate");

        if (usingFullEvaluation) {

          // This is a test that the state is correctly restored. The
          // restored state is fully evaluated and the likelihood compared with
          // that before the operation was made.
          likelihood.makeDirty();
          final double testScore = evaluate(likelihood, prior);

          if (Math.abs(testScore - score) > EVALUATION_TEST_THRESHOLD) {
            Logger.getLogger("error")
                .severe(
                    "State was not correctly calculated after an operator move.\n"
                        + "Likelihood evaluation: "
                        + score
                        + "\nFull Likelihood evaluation: "
                        + testScore
                        + "\n"
                        + "Operator: "
                        + mcmcOperator
                        + " "
                        + mcmcOperator.getOperatorName());
            fullEvaluationError = true;
          }
        }

        if (score > bestScore) {
          bestScore = score;
          fireBestModel(currentState, currentModel);
        }

        accept =
            mcmcOperator instanceof GibbsOperator
                || acceptor.accept(oldScore, score, hastingsRatio, logr);

        deviation = score - oldScore;
      }

      // The new model is accepted or rejected
      if (accept) {
        if (DEBUG) {
          System.out.println(
              "** Move accepted: new score = " + score + ", old score = " + oldScore);
        }

        mcmcOperator.accept(deviation);
        currentModel.acceptModelState();
        currentScore = score;

        //                if( otfcounter > 0 ) {
        //                    --otfcounter;
        //                    if( otfcounter == 0 ) {
        //                        adjustOpWeights(currentState);
        //                        otfcounter = onTheFlyOperatorWeights;
        //                    }
        //                }

        if (usingFullEvaluation) {
          oldScore = score; // for the usingFullEvaluation test
          diagnostic =
              likelihood instanceof CompoundLikelihood
                  ? ((CompoundLikelihood) likelihood).getDiagnosis()
                  : "";
        }
      } else {
        if (DEBUG) {
          System.out.println(
              "** Move rejected: new score = " + score + ", old score = " + oldScore);
        }

        mcmcOperator.reject();

        // assert Profiler.startProfile("Restore");

        currentModel.restoreModelState();
      }
      // assert Profiler.stopProfile("Restore");

      if (usingFullEvaluation) {
        // This is a test that the state is correctly restored. The
        // restored state is fully evaluated and the likelihood compared with
        // that before the operation was made.

        likelihood.makeDirty();
        final double testScore = evaluate(likelihood, prior);

        final String d2 =
            likelihood instanceof CompoundLikelihood
                ? ((CompoundLikelihood) likelihood).getDiagnosis()
                : "";

        if (Math.abs(testScore - oldScore) > EVALUATION_TEST_THRESHOLD) {

          final Logger logger = Logger.getLogger("error");
          logger.severe(
              "State was not correctly restored after reject step.\n"
                  + "Likelihood before: "
                  + oldScore
                  + " Likelihood after: "
                  + testScore
                  + "\n"
                  + "Operator: "
                  + mcmcOperator
                  + " "
                  + mcmcOperator.getOperatorName()
                  + (diagnostic.length() > 0
                      ? "\n\nDetails\nBefore: " + diagnostic + "\nAfter: " + d2
                      : ""));
          fullEvaluationError = true;
        }
      }

      if (!disableCoerce && mcmcOperator instanceof CoercableMCMCOperator) {
        coerceAcceptanceProbability((CoercableMCMCOperator) mcmcOperator, logr[0]);
      }

      if (usingFullEvaluation) {
        if (schedule.getMinimumAcceptAndRejectCount() >= minOperatorCountForFullEvaluation
            && currentState >= fullEvaluationCount) {
          // full evaluation is only switched off when each operator has done a
          // minimum number of operations (currently 1) and fullEvalationCount
          // operations in total.

          usingFullEvaluation = false;
          if (fullEvaluationError) {
            // If there has been an error then stop with an error
            throw new RuntimeException(
                "One or more evaluation errors occurred during the test phase of this\n"
                    + "run. These errors imply critical errors which may produce incorrect\n"
                    + "results.");
          }
        }
      }

      fireEndCurrentIteration(currentState);

      currentState += 1;
    }

    currentLength = currentState;

    return currentLength;
  }