private int regionToDot(Region r, StringBuilder str, Map<Region, Integer> cache) {
    if (cache.containsKey(r)) { // use same region again
      return cache.get(r);

    } else {
      Triple<Region, Region, Region> triple = delegate.getIfThenElse(r);

      // create node with label
      String predName = regionMap.inverse().get(triple.getFirst());
      nodeCounter += 1; // one more node is created
      int predNum = nodeCounter;
      str.append(predNum).append(" [label=\"").append(predName).append("\"];\n");

      // create arrow for true branch
      Region trueBranch = triple.getSecond();
      int trueTarget = regionToDot(trueBranch, str, cache);
      str.append(predNum).append(" -> ").append(trueTarget).append(" [style=filled];\n");

      // create arrow for false branch
      Region falseBranch = triple.getThird();
      int falseTarget = regionToDot(falseBranch, str, cache);
      str.append(predNum).append(" -> ").append(falseTarget).append(" [style=dotted];\n");

      cache.put(r, predNum);
      return predNum;
    }
  }
  private void dumpRegion(Region r, Appendable out) throws IOException {
    if (regionMap.containsValue(r)) {
      out.append(regionMap.inverse().get(r));

    } else if (r.isFalse()) {
      out.append("FALSE");

    } else if (r.isTrue()) {
      out.append("TRUE");

    } else {
      Triple<Region, Region, Region> triple = delegate.getIfThenElse(r);
      String predName = regionMap.inverse().get(triple.getFirst());

      Region trueBranch = triple.getSecond();
      Region falseBranch = triple.getThird();

      if (trueBranch.isFalse()) {
        assert !falseBranch.isFalse();
        // only falseBranch is present
        out.append("!").append(predName).append(" & ");
        dumpRegion(falseBranch, out);

      } else if (falseBranch.isFalse()) {
        // only trueBranch is present
        out.append(predName).append(" & ");
        dumpRegion(trueBranch, out);

      } else {
        // both branches present
        out.append("((").append(predName).append(" & ");
        dumpRegion(trueBranch, out);
        out.append(") | (").append("!").append(predName).append(" & ");
        dumpRegion(falseBranch, out);
        out.append("))");
      }
    }
  }
  @Override
  public Object getHashCodeForState(AbstractState pElementKey, Precision pPrecisionKey) {

    PredicateAbstractState element = (PredicateAbstractState) pElementKey;
    PredicatePrecision precision = (PredicatePrecision) pPrecisionKey;

    /*
    Normally a getHashCodeForState is just the pair of state and precision.
    For PredicateAnalysis, we need to refine the RelevantPredicatesComputer (in some cases),
    thus the reduce-operator changes and we should clear the cache completely after such a refinement.
    Instead of doing this, we use the RelevantPredicatesComputer as (third) part of the cache-key,
    such that after refining the RelevantPredicatesComputer, each new access results in a cache-miss.
    The benefit is that we keep existing ARGs for statistics or other usage.
    The drawback is the memory-usage through some cached states,
    that will never be visited again, because the reduce-operator has changed.
    */
    // TODO is Object-equality for RelevantPredicatesComputer enough or should we implement
    // RelevantPredicatesComputer.equals()?

    return Triple.of(
        element.getAbstractionFormula().asRegion(), precision, cpa.getRelevantPredicatesComputer());
  }