/**
  * Logically ands the provided terms with the terms in the formula, replacing this formula with
  * the result
  *
  * @param other the terms to and with this formula
  * @return this object.
  */
 public BooleanFormula andWith(BooleanFormula other) {
   if (other.isTrue()) {
     return this;
   }
   if (other.isFalse() || isFalse()) {
     if (booleanTerms == null) {
       booleanTerms = new HashSet<BooleanTerm>();
     } else {
       booleanTerms.clear();
     }
     isSimplified = true;
     return this;
   }
   if (isTrue()) {
     booleanTerms = new HashSet<BooleanTerm>(other.booleanTerms);
     return this;
   }
   BooleanFormula newTerms = new BooleanFormula();
   for (BooleanTerm otherTerm : other) {
     for (BooleanTerm term : booleanTerms) {
       BooleanTerm newTerm = term.andWith(otherTerm);
       if (newTerm != null) {
         newTerms.add(newTerm);
       }
     }
   }
   booleanTerms = newTerms.booleanTerms;
   isSimplified = newTerms.isSimplified;
   return this;
 }
 /**
  * Resolves the formula using the boolean values specified in <code>features</code>. If there are
  * no more un-resolved variables in the forumula, the resulting formula will be either TRUE or
  * FALSE.
  *
  * @param features the values to assign to variables in the formula
  * @return this object
  */
 public BooleanFormula resolveWith(Features features) {
   if (isTrue() || isFalse()) {
     return this;
   }
   BooleanFormula formula = new BooleanFormula();
   for (BooleanTerm term : this) {
     BooleanTerm evaluated = term.resolveWith(features);
     if (evaluated.isFalse()) {
       // term is false.  Don't add to result
     } else if (evaluated.isTrue()) {
       // term is true so formula is true
       booleanTerms = null;
       isSimplified = true;
       return this;
     } else {
       formula.add(evaluated);
     }
   }
   booleanTerms = formula.booleanTerms;
   isSimplified = formula.isSimplified;
   return this;
 }
  /**
   * Returns an instance of this class which is the simplified representation of the formula
   * represented by this object. May return this object if the formula is already simplified.
   *
   * <p>This method uses the Quine-McCluskey algorithm and code described at
   * http://en.literateprograms.org/Quine-McCluskey_algorithm_%28Java%29
   *
   * @return The simplified representation of this formula, or this object, if already simplified.
   */
  public BooleanFormula simplify() {
    if (isSimplified) {
      return this;
    }
    if (booleanTerms == null || booleanTerms.size() < 2) {
      isSimplified = true;
      return this;
    }

    // Remove true and false terms.  False terms are discarded.  True terms
    // make the entire formula evaluate to true.
    Set<BooleanTerm> trimmed = new HashSet<BooleanTerm>();
    for (BooleanTerm term : booleanTerms) {
      if (term.isTrue()) {
        booleanTerms = null;
        isSimplified = true;
        return this;
      } else if (term.isFalse()) {
        continue; // omitting a false term
      } else {
        trimmed.add(term);
      }
    }
    if (trimmed.size() != booleanTerms.size()) {
      booleanTerms = trimmed;
    }

    // Determine number of unique variable names in the formula
    Map<String, Integer> names = new TreeMap<String, Integer>();
    for (Set<BooleanVar> term : booleanTerms) {
      for (BooleanVar state : term) {
        names.put(state.name, -1);
      }
    }
    int i = 0;
    for (Map.Entry<String, Integer> entry : names.entrySet()) {
      entry.setValue(i++);
    }
    int count = names.size();
    if (count >= Integer.SIZE) {
      throw new IllegalArgumentException(Integer.toString(count));
    }
    List<Term> terms = new ArrayList<Term>(booleanTerms.size());
    for (Set<BooleanVar> term : booleanTerms) {
      byte[] bytes = new byte[count];
      for (i = 0; i < count; i++) bytes[i] = Term.DontCare;
      boolean ignoreTerm = false;
      for (BooleanVar state : term) {
        int index = names.get(state.name);
        byte val = state.state ? (byte) 1 : (byte) 0;
        if (bytes[index] == Term.DontCare) bytes[index] = val;
        else if (bytes[index] != val) {
          // Mutually exclusive states.  Don't consider this term
          ignoreTerm = true;
          break;
        }
      }
      if (!ignoreTerm) {
        terms.add(new Term(bytes));
      }
    }
    Set<BooleanTerm> result = new HashSet<BooleanTerm>();
    if (terms.size() == 0) {
      return new BooleanFormula(false);
    }

    terms = expandDontCares(count, terms);

    Formula formula = new Formula(terms);
    formula.reduceToPrimeImplicants();
    formula.reducePrimeImplicantsToSubset();

    // now convert back to featureExpression form
    List<Term> termList = formula.getTermList();
    if (termList.size() == 0) {
      return new BooleanFormula(false);
    }
    for (Term term : termList) {
      byte[] varValues = term.getVarValues();
      Set<BooleanVar> states = new HashSet<BooleanVar>();
      for (Map.Entry<String, Integer> entry : names.entrySet()) {
        if (varValues[entry.getValue()] == (byte) 1) {
          states.add(new BooleanVar(entry.getKey(), true));
        } else if (varValues[entry.getValue()] == (byte) 0) {
          states.add(new BooleanVar(entry.getKey(), false));
        }
      }
      if (states.size() > 0) {
        result.add(new BooleanTerm(states));
      }
    }
    if (result.size() == 0) {
      return new BooleanFormula(true);
    }
    BooleanFormula newFormula = new BooleanFormula();
    newFormula.booleanTerms = result;
    newFormula.isSimplified = true;
    return newFormula;
  }