private static MultiMap<Variable, Variable> surelyDifferent(DefaultRule rule) {
   Clause body = rule.antecedent();
   Clause head = rule.consequent();
   final MultiMap<Variable, Variable> different = new MultiMap<Variable, Variable>();
   for (Literal literal :
       Sugar.union(
           body.getLiteralsByPredicate(SpecialBinaryPredicates.NEQ),
           body.getLiteralsByPredicate(SpecialBinaryPredicates.GT),
           body.getLiteralsByPredicate(SpecialBinaryPredicates.LT),
           body.getLiteralsByPredicate(SpecialVarargPredicates.ALLDIFF),
           head.getLiteralsByPredicate(SpecialBinaryPredicates.NEQ),
           head.getLiteralsByPredicate(SpecialBinaryPredicates.GT),
           head.getLiteralsByPredicate(SpecialBinaryPredicates.LT),
           head.getLiteralsByPredicate(SpecialVarargPredicates.ALLDIFF))) {
     for (Term a : literal.terms()) {
       if (a instanceof Variable) {
         Variable v1 = (Variable) a;
         for (Term b : literal.terms()) {
           if (b instanceof Variable) {
             Variable v2 = (Variable) b;
             if (v1 != v2) {
               different.put(v1, v2);
             }
           }
         }
       }
     }
   }
   return different;
 }
 public static List<Set<Constant>> partitionInterchangeableConstants(
     Collection<DefaultRule> rules, Collection<Clause> hardRules) {
   List<Set<Constant>> retVal = new ArrayList<Set<Constant>>();
   Collection<DefaultRule> dummyRules =
       Sugar.<Clause, DefaultRule>funcall(
           hardRules,
           new Sugar.Fun<Clause, DefaultRule>() {
             @Override
             public DefaultRule apply(Clause clause) {
               return new DefaultRule(new Clause(Sugar.<Literal>list()), clause);
             }
           });
   for (List<Set<Constant>> list :
       partitionExchangeable_impl(Sugar.union(rules, dummyRules)).values()) {
     retVal.addAll(list);
   }
   return retVal;
 }
  public static MultiMap<DefaultRule, DefaultRule> representativeBodySpecializations(
      DefaultRule rule, List<Set<Constant>> interchangeableConstants) {
    Clause body = rule.antecedent();
    Clause head = rule.consequent();
    final MultiMap<Variable, Variable> different = surelyDifferent(rule);

    final List<Variable> variables = Sugar.<Variable>listFromCollections(body.variables());
    List<Integer> indices = VectorUtils.toList(VectorUtils.sequence(0, variables.size() - 1));
    List<Tuple<Integer>> unifications =
        Combinatorics.<Integer>cartesianPower(
            indices,
            indices.size(),
            new Sugar.Fun<Tuple<Integer>, Boolean>() {
              @Override
              public Boolean apply(Tuple<Integer> integerTuple) {
                for (int i = 0; i < integerTuple.size(); i++) {
                  if (integerTuple.get(i) > i
                      || !integerTuple.get(i).equals(integerTuple.get(integerTuple.get(i)))
                      || different
                          .get(variables.get(integerTuple.get(i)))
                          .contains(variables.get(i))
                      || !sameType(variables.get(integerTuple.get(i)), variables.get(i))) {
                    return Boolean.FALSE;
                  }
                }
                return Boolean.TRUE;
              }
            });

    Set<DefaultRule> nonIsomorphicUnifications = new HashSet<DefaultRule>();
    for (Tuple<Integer> unification : unifications) {
      Map<Term, Term> substitution = new HashMap<Term, Term>();
      for (int i = 0; i < unification.size(); i++) {
        substitution.put(variables.get(i), variables.get(unification.get(i)));
      }
      Clause newBody = LogicUtils.substitute(rule.antecedent(), substitution);
      Clause newHead = LogicUtils.substitute(rule.consequent(), substitution);
      nonIsomorphicUnifications.add(
          new DefaultRule(
              newBody.variables().size() > 1
                  ? new Clause(Sugar.union(newBody.literals(), allDiffLiteral(newBody)))
                  : newBody,
              newHead));
    }

    nonIsomorphicUnifications =
        DefaultTransformationUtils.selectNonisomorphicDefaultRules(nonIsomorphicUnifications);

    MultiMap<DefaultRule, DefaultRule> retVal = new MultiMap<DefaultRule, DefaultRule>();

    // this needs to be improved... e.g. using typing information...
    MultiList<Integer, Constant> consts = new MultiList<Integer, Constant>();
    int index = 0;
    for (Set<Constant> interch : interchangeableConstants) {
      consts.putAll(index++, interch);
    }
    for (DefaultRule unifiedRule : nonIsomorphicUnifications) {
      List<Variable> unifsVariables = Sugar.listFromCollections(unifiedRule.variables());
      if (unifsVariables.isEmpty()) {
        retVal.put(unifiedRule, unifiedRule);
      } else {
        Set<DefaultRule> substituted = new HashSet<DefaultRule>();
        middleLoop:
        for (Tuple<Integer> tuple :
            Combinatorics.cartesianPower(
                new NaturalNumbersList(0, interchangeableConstants.size()),
                unifsVariables.size())) {
          Counters<Integer> used = new Counters<Integer>();
          Map<Term, Term> substitution = new HashMap<Term, Term>();
          for (int i = 0; i < unifsVariables.size(); i++) {
            int j = used.incrementPost(tuple.get(i));
            if (j >= consts.get(tuple.get(i)).size()) {
              continue middleLoop;
            } else {
              if (unifsVariables.get(i).type() == null) {
                substitution.put(
                    unifsVariables.get(i),
                    Variable.construct(
                        unifsVariables.get(i).name(), consts.get(tuple.get(i)).get(j).type()));
              } else {
                if (unifsVariables.get(i).type().equals(consts.get(tuple.get(i)).get(j).type())) {
                  substitution.put(
                      unifsVariables.get(i),
                      Variable.construct(
                          unifsVariables.get(i).name(), consts.get(tuple.get(i)).get(j).type()));
                } else {
                  continue middleLoop;
                }
              }
            }
          }
          substituted.add(DefaultTransformationUtils.substitute(unifiedRule, substitution));
        }
        retVal.putAll(
            unifiedRule, DefaultTransformationUtils.selectNonisomorphicDefaultRules(substituted));
      }
    }
    return retVal;
  }