/**
   * {@inheritDoc}
   *
   * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace)
   */
  public IntSet next(ResolutionTrace trace) {
    if (hits.isEmpty()) return Ints.EMPTY_SET; // tried everything
    final IntSet relevantVars = StrategyUtils.coreTailUnits(trace);

    final long[] byRelevance = sortByRelevance(trace, relevantVars);
    if (DBG) printRelevant(byRelevance);
    for (int i = byRelevance.length - 1; i >= 0; i--) {
      final int var = (int) byRelevance[i];
      if (hits.remove(var) != null) {
        // remove maxVar from the set of relevant variables
        relevantVars.remove(var);
        if (relevantVars.isEmpty()) break; // there was only one root formula left
        // get all axioms and resolvents corresponding to the clauses that
        // form the translations of formulas identified by relevant vars
        final IntSet relevantClauses = clausesFor(trace, relevantVars);
        assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size() - 1);

        if (DBG)
          System.out.println("relevant clauses: " + relevantClauses.size() + ", removed " + var);

        return relevantClauses;
      }
    }

    hits.clear();
    return Ints.EMPTY_SET;
  }
  /**
   * Returns the indices of all axioms and resolvents in the given trace that form the translations
   * of the formulas identified by the given variables. This method assumes that the axioms in the
   * given trace were generated by the Kodkod {@linkplain Translator}.
   *
   * @return let C = { c: trace.prover.clauses | c.maxVariable() in relevantVars }, T = { c1, c2: C
   *     | c2.maxVariable() in abs(c1.literals) }, A = C.*T | trace.backwardReachable(A) -
   *     trace.backwardReachable(trace.axioms() - A)
   */
  private IntSet clausesFor(ResolutionTrace trace, IntSet relevantVars) {
    final double hardness = (double) trace.size() / (double) trace.axioms().size();
    final double coreRatio = ((double) trace.core().size() / (double) trace.axioms().size());

    if (DBG)
      System.out.println(
          "trace size: "
              + trace.size()
              + ", axioms: "
              + trace.axioms().size()
              + ", core: "
              + trace.core().size()
              + ", resolvents: "
              + trace.resolvents().size());
    if (DBG) System.out.println("hardness: " + hardness + ", coreRatio: " + coreRatio);

    final IntSet relevantAxioms = StrategyUtils.clausesFor(trace, relevantVars);
    if (DBG) System.out.println("relevant axioms:  " + relevantAxioms.size());

    if (coreRatio < noRecycleRatio) {
      return relevantAxioms;
    } else if (hardness < hardnessCutOff) {
      return trace.learnable(relevantAxioms);
    } else {
      IntSet current = relevantAxioms, last;
      final int maxRelevant = (int) Math.rint(relevantAxioms.size() * recycleLimit);
      do {
        last = current;
        current = trace.directlyLearnable(current);
      } while (last.size() < current.size() && current.size() < maxRelevant);

      if (DBG)
        System.out.println(
            "last: "
                + last.size()
                + ", current: "
                + current.size()
                + ", maxRelevant: "
                + maxRelevant);

      return current.size() < maxRelevant ? current : last;
    }
  }