diff --git a/src/main/scala/leon/synthesis/Rules.scala b/src/main/scala/leon/synthesis/Rules.scala
index dfdbe94b80b7e042864ed70b589d38fbdbb66a82..d4d7bab0180076fe387217d04d8a0496e98f43df 100644
--- a/src/main/scala/leon/synthesis/Rules.scala
+++ b/src/main/scala/leon/synthesis/Rules.scala
@@ -2,14 +2,9 @@ package leon
 package synthesis
 
 import purescala.Common._
-import purescala.ScalaPrinter
 import purescala.Trees._
-import purescala.Extractors._
 import purescala.TreeOps._
-import purescala.TypeTrees._
-import purescala.Definitions._
-import LinearEquations.elimVariable
-import ArithmeticNormalization.simplify
+import rules._
 
 object Rules {
   def all = Set[Synthesizer => Rule](
@@ -55,585 +50,3 @@ abstract class Rule(val name: String, val synth: Synthesizer, val priority: Prio
 
   override def toString = "R: "+name
 }
-
-class OnePoint(synth: Synthesizer) extends Rule("One-point", synth, 300) {
-  def applyOn(task: Task): RuleResult = {
-
-    val p = task.problem
-
-    val TopLevelAnds(exprs) = p.phi
-
-    val candidates = exprs.collect {
-      case eq @ Equals(Variable(x), e) if (p.xs contains x) && !(variablesOf(e) contains x) =>
-        (x, e, eq)
-      case eq @ Equals(e, Variable(x)) if (p.xs contains x) && !(variablesOf(e) contains x) =>
-        (x, e, eq)
-    }
-
-    if (!candidates.isEmpty) {
-      val (x, e, eq) = candidates.head
-
-      val others = exprs.filter(_ != eq)
-      val oxs    = p.xs.filter(_ != x)
-
-      val newProblem = Problem(p.as, p.c, subst(x -> e, And(others)), oxs)
-
-      val onSuccess: List[Solution] => Solution = { 
-        case List(Solution(pre, defs, term)) =>
-          if (oxs.isEmpty) {
-            Solution(pre, defs, Tuple(e :: Nil)) 
-          } else {
-            Solution(pre, defs, LetTuple(oxs, term, subst(x -> e, Tuple(p.xs.map(Variable(_)))))) 
-          }
-        case _ => Solution.none
-      }
-
-      RuleStep(List(newProblem), onSuccess)
-    } else {
-      RuleInapplicable
-    }
-  }
-}
-
-class Ground(synth: Synthesizer) extends Rule("Ground", synth, 500) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-
-    if (p.as.isEmpty) {
-
-      val tpe = TupleType(p.xs.map(_.getType))
-
-      synth.solver.solveSAT(p.phi) match {
-        case (Some(true), model) =>
-          RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(model))).setType(tpe)))
-        case (Some(false), model) =>
-          RuleSuccess(Solution(BooleanLiteral(false), Set(), Error(p.phi+" is UNSAT!").setType(tpe)))
-        case _ =>
-          RuleInapplicable
-      }
-    } else {
-      RuleInapplicable
-    }
-  }
-}
-
-class CaseSplit(synth: Synthesizer) extends Rule("Case-Split", synth, 200) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-    p.phi match {
-      case Or(Seq(o1, o2)) =>
-        val sub1 = Problem(p.as, p.c, o1, p.xs)
-        val sub2 = Problem(p.as, p.c, o2, p.xs)
-
-        val onSuccess: List[Solution] => Solution = { 
-          case List(Solution(p1, d1, t1), Solution(p2, d2, t2)) => Solution(Or(p1, p2), d1++d2, IfExpr(p1, t1, t2))
-          case _ => Solution.none
-        }
-
-        RuleStep(List(sub1, sub2), onSuccess)
-      case _ =>
-        RuleInapplicable
-    }
-  }
-}
-
-class Assert(synth: Synthesizer) extends Rule("Assert", synth, 200) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-
-    p.phi match {
-      case TopLevelAnds(exprs) =>
-        val xsSet = p.xs.toSet
-
-        val (exprsA, others) = exprs.partition(e => (variablesOf(e) & xsSet).isEmpty)
-
-        if (!exprsA.isEmpty) {
-          if (others.isEmpty) {
-            RuleSuccess(Solution(And(exprsA), Set(), Tuple(p.xs.map(id => simplestValue(Variable(id))))))
-          } else {
-            val sub = p.copy(c = And(p.c +: exprsA), phi = And(others))
-
-            RuleStep(List(sub), forward)
-          }
-        } else {
-          RuleInapplicable
-        }
-      case _ =>
-        RuleInapplicable
-    }
-  }
-}
-
-class UnusedInput(synth: Synthesizer) extends Rule("UnusedInput", synth, 100) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-    val unused = p.as.toSet -- variablesOf(p.phi) -- variablesOf(p.c)
-
-    if (!unused.isEmpty) {
-      val sub = p.copy(as = p.as.filterNot(unused))
-
-      RuleStep(List(sub), forward)
-    } else {
-      RuleInapplicable
-    }
-  }
-}
-
-class UnconstrainedOutput(synth: Synthesizer) extends Rule("Unconstr.Output", synth, 100) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-    val unconstr = p.xs.toSet -- variablesOf(p.phi)
-
-    if (!unconstr.isEmpty) {
-      val sub = p.copy(xs = p.xs.filterNot(unconstr))
-
-      val onSuccess: List[Solution] => Solution = { 
-        case List(s) =>
-          Solution(s.pre, s.defs, LetTuple(sub.xs, s.term, Tuple(p.xs.map(id => if (unconstr(id)) simplestValue(Variable(id)) else Variable(id)))))
-        case _ =>
-          Solution.none
-      }
-
-      RuleStep(List(sub), onSuccess)
-    } else {
-      RuleInapplicable
-    }
-
-  }
-}
-
-object Unification {
-  class DecompTrivialClash(synth: Synthesizer) extends Rule("Unif Dec./Clash/Triv.", synth, 200) {
-    def applyOn(task: Task): RuleResult = {
-      val p = task.problem
-
-      val TopLevelAnds(exprs) = p.phi
-
-      val (toRemove, toAdd) = exprs.collect {
-        case eq @ Equals(cc1 @ CaseClass(cd1, args1), cc2 @ CaseClass(cd2, args2)) =>
-          if (cc1 == cc2) {
-            (eq, List(BooleanLiteral(true)))
-          } else if (cd1 == cd2) {
-            (eq, (args1 zip args2).map((Equals(_, _)).tupled))
-          } else {
-            (eq, List(BooleanLiteral(false)))
-          }
-      }.unzip
-
-      if (!toRemove.isEmpty) {
-        val sub = p.copy(phi = And((exprs.toSet -- toRemove ++ toAdd.flatten).toSeq))
-
-
-        RuleStep(List(sub), forward)
-      } else {
-        RuleInapplicable
-      }
-    }
-  }
-
-  class OccursCheck(synth: Synthesizer) extends Rule("Unif OccursCheck", synth, 200) {
-    def applyOn(task: Task): RuleResult = {
-      val p = task.problem
-
-      val TopLevelAnds(exprs) = p.phi
-
-      val isImpossible = exprs.exists {
-        case eq @ Equals(cc : CaseClass, Variable(id)) if variablesOf(cc) contains id =>
-          true
-        case eq @ Equals(Variable(id), cc : CaseClass) if variablesOf(cc) contains id =>
-          true
-        case _ =>
-          false
-      }
-
-      if (isImpossible) {
-        val tpe = TupleType(p.xs.map(_.getType))
-
-        RuleSuccess(Solution(BooleanLiteral(false), Set(), Error(p.phi+" is UNSAT!").setType(tpe)))
-      } else {
-        RuleInapplicable
-      }
-    }
-  }
-}
-
-
-class ADTDual(synth: Synthesizer) extends Rule("ADTDual", synth, 200) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-
-    val xs = p.xs.toSet
-    val as = p.as.toSet
-
-    val TopLevelAnds(exprs) = p.phi
-
-
-    val (toRemove, toAdd) = exprs.collect {
-      case eq @ Equals(cc @ CaseClass(cd, args), e) if (variablesOf(e) -- as).isEmpty && (variablesOf(cc) -- xs).isEmpty =>
-        (eq, CaseClassInstanceOf(cd, e) +: (cd.fieldsIds zip args).map{ case (id, ex) => Equals(ex, CaseClassSelector(cd, e, id)) } )
-      case eq @ Equals(e, cc @ CaseClass(cd, args)) if (variablesOf(e) -- as).isEmpty && (variablesOf(cc) -- xs).isEmpty =>
-        (eq, CaseClassInstanceOf(cd, e) +: (cd.fieldsIds zip args).map{ case (id, ex) => Equals(ex, CaseClassSelector(cd, e, id)) } )
-    }.unzip
-
-    if (!toRemove.isEmpty) {
-      val sub = p.copy(phi = And((exprs.toSet -- toRemove ++ toAdd.flatten).toSeq))
-
-      RuleStep(List(sub), forward)
-    } else {
-      RuleInapplicable
-    }
-  }
-}
-
-class CEGIS(synth: Synthesizer) extends Rule("CEGIS", synth, 150) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-
-    case class Generator(tpe: TypeTree, altBuilder: () => List[(Expr, Set[Identifier])]);
-
-    var generators = Map[TypeTree, Generator]()
-    def getGenerator(t: TypeTree): Generator = generators.get(t) match {
-      case Some(g) => g
-      case None =>
-        val alternatives: () => List[(Expr, Set[Identifier])] = t match {
-          case BooleanType =>
-            { () => List((BooleanLiteral(true), Set()), (BooleanLiteral(false), Set())) }
-
-          case Int32Type =>
-            { () => List((IntLiteral(0), Set()), (IntLiteral(1), Set())) }
-
-          case TupleType(tps) =>
-            { () =>
-              val ids = tps.map(t => FreshIdentifier("t", true).setType(t))
-              List((Tuple(ids.map(Variable(_))), ids.toSet))
-            }
-
-          case CaseClassType(cd) =>
-            { () =>
-              val ids = cd.fieldsIds.map(i => FreshIdentifier("c", true).setType(i.getType))
-              List((CaseClass(cd, ids.map(Variable(_))), ids.toSet))
-            }
-
-          case AbstractClassType(cd) =>
-            { () =>
-              val alts: Seq[(Expr, Set[Identifier])] = cd.knownDescendents.flatMap(i => i match {
-                  case acd: AbstractClassDef =>
-                    synth.reporter.error("Unnexpected abstract class in descendants!")
-                    None
-                  case cd: CaseClassDef =>
-                    val ids = cd.fieldsIds.map(i => FreshIdentifier("c", true).setType(i.getType))
-                    Some((CaseClass(cd, ids.map(Variable(_))), ids.toSet))
-              })
-              alts.toList
-            }
-
-          case _ =>
-            synth.reporter.error("Can't construct generator. Unsupported type: "+t+"["+t.getClass+"]");
-            { () => Nil }
-        }
-        val g = Generator(t, alternatives)
-        generators += t -> g
-        g
-    }
-
-    def inputAlternatives(t: TypeTree): List[(Expr, Set[Identifier])] = {
-      p.as.filter(a => isSubtypeOf(a.getType, t)).map(id => (Variable(id) : Expr, Set[Identifier]()))
-    }
-
-    case class TentativeFormula(phi: Expr,
-                                program: Expr,
-                                mappings: Map[Identifier, (Identifier, Expr)],
-                                recTerms: Map[Identifier, Set[Identifier]]) {
-      def unroll: TentativeFormula = {
-        var newProgram  = List[Expr]()
-        var newRecTerms = Map[Identifier, Set[Identifier]]()
-        var newMappings = Map[Identifier, (Identifier, Expr)]()
-
-        for ((_, recIds) <- recTerms; recId <- recIds) {
-          val gen  = getGenerator(recId.getType)
-          val alts = gen.altBuilder() ::: inputAlternatives(recId.getType)
-
-          val altsWithBranches = alts.map(alt => FreshIdentifier("b", true).setType(BooleanType) -> alt)
-
-          val bvs = altsWithBranches.map(alt => Variable(alt._1))
-          val distinct = if (bvs.size > 1) {
-            (for (i <- (1 to bvs.size-1); j <- 0 to i-1) yield {
-              Or(Not(bvs(i)), Not(bvs(j)))
-            }).toList
-          } else {
-            List(BooleanLiteral(true))
-          }
-          val pre = And(Or(bvs) :: distinct) // (b1 OR b2) AND (Not(b1) OR Not(b2))
-          val cases = for((bid, (ex, rec)) <- altsWithBranches.toList) yield { // b1 => E(gen1, gen2)     [b1 -> {gen1, gen2}]
-            if (!rec.isEmpty) {
-              newRecTerms += bid -> rec
-            }
-            newMappings += bid -> (recId -> ex)
-
-            Implies(Variable(bid), Equals(Variable(recId), ex))
-          }
-
-          newProgram = newProgram ::: pre :: cases
-        }
-
-        TentativeFormula(phi, And(program :: newProgram), mappings ++ newMappings, newRecTerms)
-      }
-
-      def bounds = recTerms.keySet.map(id => Not(Variable(id))).toList
-      def bss = mappings.keySet
-
-      def entireFormula = And(phi :: program :: bounds)
-    }
-
-    var result: Option[RuleResult]   = None
-
-    var ass = p.as.toSet
-    var xss = p.xs.toSet
-
-    var lastF     = TentativeFormula(Implies(p.c, p.phi), BooleanLiteral(true), Map(), Map() ++ p.xs.map(x => x -> Set(x)))
-    var currentF  = lastF.unroll
-    var unrolings = 0
-    val maxUnrolings = 2
-    do {
-      //println("Was: "+lastF.entireFormula)
-      //println("Now Trying : "+currentF.entireFormula)
-
-      val tpe = TupleType(p.xs.map(_.getType))
-      val bss = currentF.bss
-
-      var predicates: Seq[Expr]        = Seq()
-      var continue = true
-
-      while (result.isEmpty && continue) {
-        val basePhi = currentF.entireFormula
-        val constrainedPhi = And(basePhi +: predicates)
-        //println("-"*80)
-        //println("To satisfy: "+constrainedPhi)
-        synth.solver.solveSAT(constrainedPhi) match {
-          case (Some(true), satModel) =>
-            //println("Found candidate!: "+satModel.filterKeys(bss))
-
-            //println("Corresponding program: "+simplifyTautologies(synth.solver)(valuateWithModelIn(currentF.program, bss, satModel)))
-            val fixedBss = And(bss.map(b => Equals(Variable(b), satModel(b))).toSeq)
-            //println("Phi with fixed sat bss: "+fixedBss)
-
-            val counterPhi = Implies(And(fixedBss, currentF.program), currentF.phi)
-            //println("Formula to validate: "+counterPhi)
-
-            synth.solver.solveSAT(Not(counterPhi)) match {
-              case (Some(true), invalidModel) =>
-                // Found as such as the xs break, refine predicates
-                //println("Found counter EX: "+invalidModel)
-                predicates = Not(And(bss.map(b => Equals(Variable(b), satModel(b))).toSeq)) +: predicates
-                //println("Let's avoid this case: "+bss.map(b => Equals(Variable(b), satModel(b))).mkString(" "))
-
-              case (Some(false), _) =>
-                //println("Sat model: "+satModel.toSeq.sortBy(_._1.toString).map{ case (id, v) => id+" -> "+v }.mkString(", "))
-                var mapping = currentF.mappings.filterKeys(satModel.mapValues(_ == BooleanLiteral(true))).values.toMap
-
-                //println("Mapping: "+mapping)
-
-                // Resolve mapping
-                for ((c, e) <- mapping) {
-                  mapping += c -> substAll(mapping, e)
-                }
-
-                result = Some(RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(mapping))).setType(tpe))))
-
-              case _ =>
-                synth.reporter.warning("Solver returned 'UNKNOWN' in a CEGIS iteration.")
-                continue = false
-            }
-
-          case (Some(false), _) =>
-            //println("%%%% UNSAT")
-            continue = false
-          case _ =>
-            //println("%%%% WOOPS")
-            continue = false
-        }
-      }
-
-      lastF = currentF
-      currentF = currentF.unroll
-      unrolings += 1
-    } while(unrolings < maxUnrolings && lastF != currentF && result.isEmpty)
-
-    result.getOrElse(RuleInapplicable)
-  }
-}
-
-class OptimisticGround(synth: Synthesizer) extends Rule("Optimistic Ground", synth, 150) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-
-    if (!p.as.isEmpty && !p.xs.isEmpty) {
-      val xss = p.xs.toSet
-      val ass = p.as.toSet
-
-      val tpe = TupleType(p.xs.map(_.getType))
-
-      var i = 0;
-      var maxTries = 3;
-
-      var result: Option[RuleResult]   = None
-      var predicates: Seq[Expr]        = Seq()
-
-      while (result.isEmpty && i < maxTries) {
-        val phi = And(p.phi +: predicates)
-        //println("SOLVING " + phi + " ...")
-        synth.solver.solveSAT(phi) match {
-          case (Some(true), satModel) =>
-            val satXsModel = satModel.filterKeys(xss) 
-
-            val newPhi = valuateWithModelIn(phi, xss, satModel)
-
-            //println("REFUTING " + Not(newPhi) + "...")
-            synth.solver.solveSAT(Not(newPhi)) match {
-              case (Some(true), invalidModel) =>
-                // Found as such as the xs break, refine predicates
-                predicates = valuateWithModelIn(phi, ass, invalidModel) +: predicates
-
-              case (Some(false), _) =>
-                result = Some(RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(satModel))).setType(tpe))))
-
-              case _ =>
-                result = Some(RuleInapplicable)
-            }
-
-          case (Some(false), _) =>
-            if (predicates.isEmpty) {
-              result = Some(RuleSuccess(Solution(BooleanLiteral(false), Set(), Error(p.phi+" is UNSAT!").setType(tpe))))
-            } else {
-              result = Some(RuleInapplicable)
-            }
-          case _ =>
-            result = Some(RuleInapplicable)
-        }
-
-        i += 1 
-      }
-
-      result.getOrElse(RuleInapplicable)
-    } else {
-      RuleInapplicable
-    }
-  }
-}
-
-class EqualitySplit(synth: Synthesizer) extends Rule("Eq. Split.", synth, 90) {
-  def applyOn(task: Task): RuleResult = {
-    val p = task.problem
-
-    val TopLevelAnds(presSeq) = p.c
-    val pres = presSeq.toSet
-
-    def combinations(a1: Identifier, a2: Identifier): Set[Expr] = {
-      val v1 = Variable(a1)
-      val v2 = Variable(a2)
-      Set(
-        Equals(v1, v2),
-        Equals(v2, v1),
-        Not(Equals(v1, v2)),
-        Not(Equals(v2, v1))
-      )
-    }
-
-    val candidate = p.as.groupBy(_.getType).map(_._2.toList).find {
-      case List(a1, a2) => (pres & combinations(a1, a2)).isEmpty
-      case _ => false
-    }
-
-
-    candidate match {
-      case Some(List(a1, a2)) =>
-
-        val sub1 = p.copy(c = And(Equals(Variable(a1), Variable(a2)), p.c))
-        val sub2 = p.copy(c = And(Not(Equals(Variable(a1), Variable(a2))), p.c))
-
-        val onSuccess: List[Solution] => Solution = { 
-          case List(s1, s2) =>
-            Solution(Or(s1.pre, s2.pre), s1.defs++s2.defs, IfExpr(Equals(Variable(a1), Variable(a2)), s1.term, s2.term))
-          case _ =>
-            Solution.none
-        }
-
-        RuleStep(List(sub1, sub2), onSuccess)
-      case _ =>
-        RuleInapplicable
-    }
-  }
-}
-
-class IntegerEquation(synth: Synthesizer) extends Rule("Integer Equation", synth, 300) {
-  def applyOn(task: Task): RuleResult = {
-
-    val p = task.problem
-
-    val TopLevelAnds(exprs) = p.phi
-    val xs = p.xs
-    val as = p.as
-    val formula = p.phi
-
-    val (eqs, others) = exprs.partition(_.isInstanceOf[Equals])
-    var candidates: Seq[Expr] = eqs
-    var allOthers: Seq[Expr] = others
-
-    var vars: Set[Identifier] = Set()
-    var eqas: Set[Identifier] = Set()
-    var eqxs: List[Identifier] = List()
-    var ys: Set[Identifier] = Set()
-
-    var optionNormalizedEq: Option[List[Expr]] = None
-    while(!candidates.isEmpty && optionNormalizedEq == None) {
-      val eq@Equals(_,_) = candidates.head
-      candidates = candidates.tail
-      
-      vars = variablesOf(eq)
-      eqas = as.toSet.intersect(vars)
-
-      eqxs = xs.toSet.intersect(vars).toList
-      ys = xs.toSet.diff(vars)
-
-      try {
-        optionNormalizedEq = Some(ArithmeticNormalization(Minus(eq.left, eq.right), eqxs.toArray).toList)
-      } catch {
-        case ArithmeticNormalization.NonLinearExpressionException(_) =>
-      }
-    }
-
-    optionNormalizedEq match {
-      case None => RuleInapplicable
-      case Some(normalizedEq0) => {
-        val (neqxs, normalizedEq) = eqxs.zip(normalizedEq0.tail).filterNot{ case (_, IntLiteral(0)) => true case _ => false}.unzip
-
-        //if(normalizedEq.size == 1) {
-
-
-        //} else {
-
-        val (eqPre, eqWitness, eqFreshVars) = elimVariable(eqas, normalizedEq)
-
-        val eqSubstMap: Map[Expr, Expr] = neqxs.zip(eqWitness).map{case (id, e) => (Variable(id), simplify(e))}.toMap
-        val freshFormula = simplify(replace(eqSubstMap, And(allOthers)))
-        //}
-        //(eqPre, freshFormula)
-
-        val newProblem = Problem(as, And(eqPre, p.c), freshFormula, eqFreshVars)
-
-        val onSuccess: List[Solution] => Solution = { 
-          case List(Solution(pre, defs, term)) =>
-            if (eqFreshVars.isEmpty) {
-              Solution(pre, defs, replace(eqSubstMap, Tuple(neqxs.map(Variable(_)))))
-            } else {
-              Solution(pre, defs, LetTuple(eqFreshVars, term, replace(eqSubstMap, Tuple(neqxs.map(Variable(_))))))
-            }
-          case _ => Solution.none
-        }
-
-        RuleStep(List(newProblem), onSuccess)
-      }
-    }
-
-  }
-}
diff --git a/src/main/scala/leon/synthesis/rules/ADTDual.scala b/src/main/scala/leon/synthesis/rules/ADTDual.scala
new file mode 100644
index 0000000000000000000000000000000000000000..5cf2bdb3b9f1647ffe9a0fed3d3919862e5bc78b
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/ADTDual.scala
@@ -0,0 +1,35 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class ADTDual(synth: Synthesizer) extends Rule("ADTDual", synth, 200) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+
+    val xs = p.xs.toSet
+    val as = p.as.toSet
+
+    val TopLevelAnds(exprs) = p.phi
+
+
+    val (toRemove, toAdd) = exprs.collect {
+      case eq @ Equals(cc @ CaseClass(cd, args), e) if (variablesOf(e) -- as).isEmpty && (variablesOf(cc) -- xs).isEmpty =>
+        (eq, CaseClassInstanceOf(cd, e) +: (cd.fieldsIds zip args).map{ case (id, ex) => Equals(ex, CaseClassSelector(cd, e, id)) } )
+      case eq @ Equals(e, cc @ CaseClass(cd, args)) if (variablesOf(e) -- as).isEmpty && (variablesOf(cc) -- xs).isEmpty =>
+        (eq, CaseClassInstanceOf(cd, e) +: (cd.fieldsIds zip args).map{ case (id, ex) => Equals(ex, CaseClassSelector(cd, e, id)) } )
+    }.unzip
+
+    if (!toRemove.isEmpty) {
+      val sub = p.copy(phi = And((exprs.toSet -- toRemove ++ toAdd.flatten).toSeq))
+
+      RuleStep(List(sub), forward)
+    } else {
+      RuleInapplicable
+    }
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/Assert.scala b/src/main/scala/leon/synthesis/rules/Assert.scala
new file mode 100644
index 0000000000000000000000000000000000000000..cfd5f5a92a1be2e73e6f36c8be8e51cf6a71e9bf
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/Assert.scala
@@ -0,0 +1,35 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class Assert(synth: Synthesizer) extends Rule("Assert", synth, 200) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+
+    p.phi match {
+      case TopLevelAnds(exprs) =>
+        val xsSet = p.xs.toSet
+
+        val (exprsA, others) = exprs.partition(e => (variablesOf(e) & xsSet).isEmpty)
+
+        if (!exprsA.isEmpty) {
+          if (others.isEmpty) {
+            RuleSuccess(Solution(And(exprsA), Set(), Tuple(p.xs.map(id => simplestValue(Variable(id))))))
+          } else {
+            val sub = p.copy(c = And(p.c +: exprsA), phi = And(others))
+
+            RuleStep(List(sub), forward)
+          }
+        } else {
+          RuleInapplicable
+        }
+      case _ =>
+        RuleInapplicable
+    }
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/CaseSplit.scala b/src/main/scala/leon/synthesis/rules/CaseSplit.scala
new file mode 100644
index 0000000000000000000000000000000000000000..a99b7a361dced3e19d8e79703d9cb90ba2483779
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/CaseSplit.scala
@@ -0,0 +1,28 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class CaseSplit(synth: Synthesizer) extends Rule("Case-Split", synth, 200) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+    p.phi match {
+      case Or(Seq(o1, o2)) =>
+        val sub1 = Problem(p.as, p.c, o1, p.xs)
+        val sub2 = Problem(p.as, p.c, o2, p.xs)
+
+        val onSuccess: List[Solution] => Solution = { 
+          case List(Solution(p1, d1, t1), Solution(p2, d2, t2)) => Solution(Or(p1, p2), d1++d2, IfExpr(p1, t1, t2))
+          case _ => Solution.none
+        }
+
+        RuleStep(List(sub1, sub2), onSuccess)
+      case _ =>
+        RuleInapplicable
+    }
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/Cegis.scala b/src/main/scala/leon/synthesis/rules/Cegis.scala
new file mode 100644
index 0000000000000000000000000000000000000000..6894a035344d5904137e7cc540d342f769e7fe8e
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/Cegis.scala
@@ -0,0 +1,188 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.Common._
+import purescala.Definitions._
+import purescala.TypeTrees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class CEGIS(synth: Synthesizer) extends Rule("CEGIS", synth, 150) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+
+    case class Generator(tpe: TypeTree, altBuilder: () => List[(Expr, Set[Identifier])]);
+
+    var generators = Map[TypeTree, Generator]()
+    def getGenerator(t: TypeTree): Generator = generators.get(t) match {
+      case Some(g) => g
+      case None =>
+        val alternatives: () => List[(Expr, Set[Identifier])] = t match {
+          case BooleanType =>
+            { () => List((BooleanLiteral(true), Set()), (BooleanLiteral(false), Set())) }
+
+          case Int32Type =>
+            { () => List((IntLiteral(0), Set()), (IntLiteral(1), Set())) }
+
+          case TupleType(tps) =>
+            { () =>
+              val ids = tps.map(t => FreshIdentifier("t", true).setType(t))
+              List((Tuple(ids.map(Variable(_))), ids.toSet))
+            }
+
+          case CaseClassType(cd) =>
+            { () =>
+              val ids = cd.fieldsIds.map(i => FreshIdentifier("c", true).setType(i.getType))
+              List((CaseClass(cd, ids.map(Variable(_))), ids.toSet))
+            }
+
+          case AbstractClassType(cd) =>
+            { () =>
+              val alts: Seq[(Expr, Set[Identifier])] = cd.knownDescendents.flatMap(i => i match {
+                  case acd: AbstractClassDef =>
+                    synth.reporter.error("Unnexpected abstract class in descendants!")
+                    None
+                  case cd: CaseClassDef =>
+                    val ids = cd.fieldsIds.map(i => FreshIdentifier("c", true).setType(i.getType))
+                    Some((CaseClass(cd, ids.map(Variable(_))), ids.toSet))
+              })
+              alts.toList
+            }
+
+          case _ =>
+            synth.reporter.error("Can't construct generator. Unsupported type: "+t+"["+t.getClass+"]");
+            { () => Nil }
+        }
+        val g = Generator(t, alternatives)
+        generators += t -> g
+        g
+    }
+
+    def inputAlternatives(t: TypeTree): List[(Expr, Set[Identifier])] = {
+      p.as.filter(a => isSubtypeOf(a.getType, t)).map(id => (Variable(id) : Expr, Set[Identifier]()))
+    }
+
+    case class TentativeFormula(phi: Expr,
+                                program: Expr,
+                                mappings: Map[Identifier, (Identifier, Expr)],
+                                recTerms: Map[Identifier, Set[Identifier]]) {
+      def unroll: TentativeFormula = {
+        var newProgram  = List[Expr]()
+        var newRecTerms = Map[Identifier, Set[Identifier]]()
+        var newMappings = Map[Identifier, (Identifier, Expr)]()
+
+        for ((_, recIds) <- recTerms; recId <- recIds) {
+          val gen  = getGenerator(recId.getType)
+          val alts = gen.altBuilder() ::: inputAlternatives(recId.getType)
+
+          val altsWithBranches = alts.map(alt => FreshIdentifier("b", true).setType(BooleanType) -> alt)
+
+          val bvs = altsWithBranches.map(alt => Variable(alt._1))
+          val distinct = if (bvs.size > 1) {
+            (for (i <- (1 to bvs.size-1); j <- 0 to i-1) yield {
+              Or(Not(bvs(i)), Not(bvs(j)))
+            }).toList
+          } else {
+            List(BooleanLiteral(true))
+          }
+          val pre = And(Or(bvs) :: distinct) // (b1 OR b2) AND (Not(b1) OR Not(b2))
+          val cases = for((bid, (ex, rec)) <- altsWithBranches.toList) yield { // b1 => E(gen1, gen2)     [b1 -> {gen1, gen2}]
+            if (!rec.isEmpty) {
+              newRecTerms += bid -> rec
+            }
+            newMappings += bid -> (recId -> ex)
+
+            Implies(Variable(bid), Equals(Variable(recId), ex))
+          }
+
+          newProgram = newProgram ::: pre :: cases
+        }
+
+        TentativeFormula(phi, And(program :: newProgram), mappings ++ newMappings, newRecTerms)
+      }
+
+      def bounds = recTerms.keySet.map(id => Not(Variable(id))).toList
+      def bss = mappings.keySet
+
+      def entireFormula = And(phi :: program :: bounds)
+    }
+
+    var result: Option[RuleResult]   = None
+
+    var ass = p.as.toSet
+    var xss = p.xs.toSet
+
+    var lastF     = TentativeFormula(Implies(p.c, p.phi), BooleanLiteral(true), Map(), Map() ++ p.xs.map(x => x -> Set(x)))
+    var currentF  = lastF.unroll
+    var unrolings = 0
+    val maxUnrolings = 2
+    do {
+      //println("Was: "+lastF.entireFormula)
+      //println("Now Trying : "+currentF.entireFormula)
+
+      val tpe = TupleType(p.xs.map(_.getType))
+      val bss = currentF.bss
+
+      var predicates: Seq[Expr]        = Seq()
+      var continue = true
+
+      while (result.isEmpty && continue) {
+        val basePhi = currentF.entireFormula
+        val constrainedPhi = And(basePhi +: predicates)
+        //println("-"*80)
+        //println("To satisfy: "+constrainedPhi)
+        synth.solver.solveSAT(constrainedPhi) match {
+          case (Some(true), satModel) =>
+            //println("Found candidate!: "+satModel.filterKeys(bss))
+
+            //println("Corresponding program: "+simplifyTautologies(synth.solver)(valuateWithModelIn(currentF.program, bss, satModel)))
+            val fixedBss = And(bss.map(b => Equals(Variable(b), satModel(b))).toSeq)
+            //println("Phi with fixed sat bss: "+fixedBss)
+
+            val counterPhi = Implies(And(fixedBss, currentF.program), currentF.phi)
+            //println("Formula to validate: "+counterPhi)
+
+            synth.solver.solveSAT(Not(counterPhi)) match {
+              case (Some(true), invalidModel) =>
+                // Found as such as the xs break, refine predicates
+                //println("Found counter EX: "+invalidModel)
+                predicates = Not(And(bss.map(b => Equals(Variable(b), satModel(b))).toSeq)) +: predicates
+                //println("Let's avoid this case: "+bss.map(b => Equals(Variable(b), satModel(b))).mkString(" "))
+
+              case (Some(false), _) =>
+                //println("Sat model: "+satModel.toSeq.sortBy(_._1.toString).map{ case (id, v) => id+" -> "+v }.mkString(", "))
+                var mapping = currentF.mappings.filterKeys(satModel.mapValues(_ == BooleanLiteral(true))).values.toMap
+
+                //println("Mapping: "+mapping)
+
+                // Resolve mapping
+                for ((c, e) <- mapping) {
+                  mapping += c -> substAll(mapping, e)
+                }
+
+                result = Some(RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(mapping))).setType(tpe))))
+
+              case _ =>
+                synth.reporter.warning("Solver returned 'UNKNOWN' in a CEGIS iteration.")
+                continue = false
+            }
+
+          case (Some(false), _) =>
+            //println("%%%% UNSAT")
+            continue = false
+          case _ =>
+            //println("%%%% WOOPS")
+            continue = false
+        }
+      }
+
+      lastF = currentF
+      currentF = currentF.unroll
+      unrolings += 1
+    } while(unrolings < maxUnrolings && lastF != currentF && result.isEmpty)
+
+    result.getOrElse(RuleInapplicable)
+  }
+}
diff --git a/src/main/scala/leon/synthesis/rules/EqualitySplit.scala b/src/main/scala/leon/synthesis/rules/EqualitySplit.scala
new file mode 100644
index 0000000000000000000000000000000000000000..2fb7bd19a8e57e46dd46714b1eec1485b5324426
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/EqualitySplit.scala
@@ -0,0 +1,53 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.Common._
+import purescala.TypeTrees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class EqualitySplit(synth: Synthesizer) extends Rule("Eq. Split.", synth, 90) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+
+    val TopLevelAnds(presSeq) = p.c
+    val pres = presSeq.toSet
+
+    def combinations(a1: Identifier, a2: Identifier): Set[Expr] = {
+      val v1 = Variable(a1)
+      val v2 = Variable(a2)
+      Set(
+        Equals(v1, v2),
+        Equals(v2, v1),
+        Not(Equals(v1, v2)),
+        Not(Equals(v2, v1))
+      )
+    }
+
+    val candidate = p.as.groupBy(_.getType).map(_._2.toList).find {
+      case List(a1, a2) => (pres & combinations(a1, a2)).isEmpty
+      case _ => false
+    }
+
+
+    candidate match {
+      case Some(List(a1, a2)) =>
+
+        val sub1 = p.copy(c = And(Equals(Variable(a1), Variable(a2)), p.c))
+        val sub2 = p.copy(c = And(Not(Equals(Variable(a1), Variable(a2))), p.c))
+
+        val onSuccess: List[Solution] => Solution = { 
+          case List(s1, s2) =>
+            Solution(Or(s1.pre, s2.pre), s1.defs++s2.defs, IfExpr(Equals(Variable(a1), Variable(a2)), s1.term, s2.term))
+          case _ =>
+            Solution.none
+        }
+
+        RuleStep(List(sub1, sub2), onSuccess)
+      case _ =>
+        RuleInapplicable
+    }
+  }
+}
diff --git a/src/main/scala/leon/synthesis/rules/Ground.scala b/src/main/scala/leon/synthesis/rules/Ground.scala
new file mode 100644
index 0000000000000000000000000000000000000000..122829442033c802a5fd0ec82647a0b86f0c1f05
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/Ground.scala
@@ -0,0 +1,31 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TypeTrees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class Ground(synth: Synthesizer) extends Rule("Ground", synth, 500) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+
+    if (p.as.isEmpty) {
+
+      val tpe = TupleType(p.xs.map(_.getType))
+
+      synth.solver.solveSAT(p.phi) match {
+        case (Some(true), model) =>
+          RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(model))).setType(tpe)))
+        case (Some(false), model) =>
+          RuleSuccess(Solution(BooleanLiteral(false), Set(), Error(p.phi+" is UNSAT!").setType(tpe)))
+        case _ =>
+          RuleInapplicable
+      }
+    } else {
+      RuleInapplicable
+    }
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/IntegerEquation.scala b/src/main/scala/leon/synthesis/rules/IntegerEquation.scala
new file mode 100644
index 0000000000000000000000000000000000000000..47246152dd73ba0584f48cb127a2dfc95feaf2ae
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/IntegerEquation.scala
@@ -0,0 +1,85 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Common._
+import purescala.Trees._
+import purescala.Extractors._
+import purescala.TreeOps._
+import purescala.TypeTrees._
+import purescala.Definitions._
+import LinearEquations.elimVariable
+import ArithmeticNormalization.simplify
+
+class IntegerEquation(synth: Synthesizer) extends Rule("Integer Equation", synth, 300) {
+  def applyOn(task: Task): RuleResult = {
+
+    val p = task.problem
+
+    val TopLevelAnds(exprs) = p.phi
+    val xs = p.xs
+    val as = p.as
+    val formula = p.phi
+
+    val (eqs, others) = exprs.partition(_.isInstanceOf[Equals])
+    var candidates: Seq[Expr] = eqs
+    var allOthers: Seq[Expr] = others
+
+    var vars: Set[Identifier] = Set()
+    var eqas: Set[Identifier] = Set()
+    var eqxs: List[Identifier] = List()
+    var ys: Set[Identifier] = Set()
+
+    var optionNormalizedEq: Option[List[Expr]] = None
+    while(!candidates.isEmpty && optionNormalizedEq == None) {
+      val eq@Equals(_,_) = candidates.head
+      candidates = candidates.tail
+      
+      vars = variablesOf(eq)
+      eqas = as.toSet.intersect(vars)
+
+      eqxs = xs.toSet.intersect(vars).toList
+      ys = xs.toSet.diff(vars)
+
+      try {
+        optionNormalizedEq = Some(ArithmeticNormalization(Minus(eq.left, eq.right), eqxs.toArray).toList)
+      } catch {
+        case ArithmeticNormalization.NonLinearExpressionException(_) =>
+      }
+    }
+
+    optionNormalizedEq match {
+      case None => RuleInapplicable
+      case Some(normalizedEq0) => {
+        val (neqxs, normalizedEq) = eqxs.zip(normalizedEq0.tail).filterNot{ case (_, IntLiteral(0)) => true case _ => false}.unzip
+
+        //if(normalizedEq.size == 1) {
+
+
+        //} else {
+
+        val (eqPre, eqWitness, eqFreshVars) = elimVariable(eqas, normalizedEq)
+
+        val eqSubstMap: Map[Expr, Expr] = neqxs.zip(eqWitness).map{case (id, e) => (Variable(id), simplify(e))}.toMap
+        val freshFormula = simplify(replace(eqSubstMap, And(allOthers)))
+        //}
+        //(eqPre, freshFormula)
+
+        val newProblem = Problem(as, And(eqPre, p.c), freshFormula, eqFreshVars)
+
+        val onSuccess: List[Solution] => Solution = { 
+          case List(Solution(pre, defs, term)) =>
+            if (eqFreshVars.isEmpty) {
+              Solution(pre, defs, replace(eqSubstMap, Tuple(neqxs.map(Variable(_)))))
+            } else {
+              Solution(pre, defs, LetTuple(eqFreshVars, term, replace(eqSubstMap, Tuple(neqxs.map(Variable(_))))))
+            }
+          case _ => Solution.none
+        }
+
+        RuleStep(List(newProblem), onSuccess)
+      }
+    }
+
+  }
+}
diff --git a/src/main/scala/leon/synthesis/rules/OnePoint.scala b/src/main/scala/leon/synthesis/rules/OnePoint.scala
new file mode 100644
index 0000000000000000000000000000000000000000..af047f3cb4bf7952ef629bd530cc1c2c3e6830e1
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/OnePoint.scala
@@ -0,0 +1,47 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class OnePoint(synth: Synthesizer) extends Rule("One-point", synth, 300) {
+  def applyOn(task: Task): RuleResult = {
+
+    val p = task.problem
+
+    val TopLevelAnds(exprs) = p.phi
+
+    val candidates = exprs.collect {
+      case eq @ Equals(Variable(x), e) if (p.xs contains x) && !(variablesOf(e) contains x) =>
+        (x, e, eq)
+      case eq @ Equals(e, Variable(x)) if (p.xs contains x) && !(variablesOf(e) contains x) =>
+        (x, e, eq)
+    }
+
+    if (!candidates.isEmpty) {
+      val (x, e, eq) = candidates.head
+
+      val others = exprs.filter(_ != eq)
+      val oxs    = p.xs.filter(_ != x)
+
+      val newProblem = Problem(p.as, p.c, subst(x -> e, And(others)), oxs)
+
+      val onSuccess: List[Solution] => Solution = { 
+        case List(Solution(pre, defs, term)) =>
+          if (oxs.isEmpty) {
+            Solution(pre, defs, Tuple(e :: Nil)) 
+          } else {
+            Solution(pre, defs, LetTuple(oxs, term, subst(x -> e, Tuple(p.xs.map(Variable(_)))))) 
+          }
+        case _ => Solution.none
+      }
+
+      RuleStep(List(newProblem), onSuccess)
+    } else {
+      RuleInapplicable
+    }
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/OptimisticGround.scala b/src/main/scala/leon/synthesis/rules/OptimisticGround.scala
new file mode 100644
index 0000000000000000000000000000000000000000..1933807202ab826e82ea1e3407d54b6b9b2c9ac8
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/OptimisticGround.scala
@@ -0,0 +1,66 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TypeTrees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class OptimisticGround(synth: Synthesizer) extends Rule("Optimistic Ground", synth, 150) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+
+    if (!p.as.isEmpty && !p.xs.isEmpty) {
+      val xss = p.xs.toSet
+      val ass = p.as.toSet
+
+      val tpe = TupleType(p.xs.map(_.getType))
+
+      var i = 0;
+      var maxTries = 3;
+
+      var result: Option[RuleResult]   = None
+      var predicates: Seq[Expr]        = Seq()
+
+      while (result.isEmpty && i < maxTries) {
+        val phi = And(p.phi +: predicates)
+        //println("SOLVING " + phi + " ...")
+        synth.solver.solveSAT(phi) match {
+          case (Some(true), satModel) =>
+            val satXsModel = satModel.filterKeys(xss) 
+
+            val newPhi = valuateWithModelIn(phi, xss, satModel)
+
+            //println("REFUTING " + Not(newPhi) + "...")
+            synth.solver.solveSAT(Not(newPhi)) match {
+              case (Some(true), invalidModel) =>
+                // Found as such as the xs break, refine predicates
+                predicates = valuateWithModelIn(phi, ass, invalidModel) +: predicates
+
+              case (Some(false), _) =>
+                result = Some(RuleSuccess(Solution(BooleanLiteral(true), Set(), Tuple(p.xs.map(valuateWithModel(satModel))).setType(tpe))))
+
+              case _ =>
+                result = Some(RuleInapplicable)
+            }
+
+          case (Some(false), _) =>
+            if (predicates.isEmpty) {
+              result = Some(RuleSuccess(Solution(BooleanLiteral(false), Set(), Error(p.phi+" is UNSAT!").setType(tpe))))
+            } else {
+              result = Some(RuleInapplicable)
+            }
+          case _ =>
+            result = Some(RuleInapplicable)
+        }
+
+        i += 1 
+      }
+
+      result.getOrElse(RuleInapplicable)
+    } else {
+      RuleInapplicable
+    }
+  }
+}
diff --git a/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala b/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala
new file mode 100644
index 0000000000000000000000000000000000000000..0a24dff40cb757b288eea1fb4cb83b846c73507a
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala
@@ -0,0 +1,31 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class UnconstrainedOutput(synth: Synthesizer) extends Rule("Unconstr.Output", synth, 100) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+    val unconstr = p.xs.toSet -- variablesOf(p.phi)
+
+    if (!unconstr.isEmpty) {
+      val sub = p.copy(xs = p.xs.filterNot(unconstr))
+
+      val onSuccess: List[Solution] => Solution = { 
+        case List(s) =>
+          Solution(s.pre, s.defs, LetTuple(sub.xs, s.term, Tuple(p.xs.map(id => if (unconstr(id)) simplestValue(Variable(id)) else Variable(id)))))
+        case _ =>
+          Solution.none
+      }
+
+      RuleStep(List(sub), onSuccess)
+    } else {
+      RuleInapplicable
+    }
+
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/Unification.scala b/src/main/scala/leon/synthesis/rules/Unification.scala
new file mode 100644
index 0000000000000000000000000000000000000000..7817d63d93bae7ff59bbdb00455ce9cc29aecc7c
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/Unification.scala
@@ -0,0 +1,64 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TypeTrees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+object Unification {
+  class DecompTrivialClash(synth: Synthesizer) extends Rule("Unif Dec./Clash/Triv.", synth, 200) {
+    def applyOn(task: Task): RuleResult = {
+      val p = task.problem
+
+      val TopLevelAnds(exprs) = p.phi
+
+      val (toRemove, toAdd) = exprs.collect {
+        case eq @ Equals(cc1 @ CaseClass(cd1, args1), cc2 @ CaseClass(cd2, args2)) =>
+          if (cc1 == cc2) {
+            (eq, List(BooleanLiteral(true)))
+          } else if (cd1 == cd2) {
+            (eq, (args1 zip args2).map((Equals(_, _)).tupled))
+          } else {
+            (eq, List(BooleanLiteral(false)))
+          }
+      }.unzip
+
+      if (!toRemove.isEmpty) {
+        val sub = p.copy(phi = And((exprs.toSet -- toRemove ++ toAdd.flatten).toSeq))
+
+
+        RuleStep(List(sub), forward)
+      } else {
+        RuleInapplicable
+      }
+    }
+  }
+
+  class OccursCheck(synth: Synthesizer) extends Rule("Unif OccursCheck", synth, 200) {
+    def applyOn(task: Task): RuleResult = {
+      val p = task.problem
+
+      val TopLevelAnds(exprs) = p.phi
+
+      val isImpossible = exprs.exists {
+        case eq @ Equals(cc : CaseClass, Variable(id)) if variablesOf(cc) contains id =>
+          true
+        case eq @ Equals(Variable(id), cc : CaseClass) if variablesOf(cc) contains id =>
+          true
+        case _ =>
+          false
+      }
+
+      if (isImpossible) {
+        val tpe = TupleType(p.xs.map(_.getType))
+
+        RuleSuccess(Solution(BooleanLiteral(false), Set(), Error(p.phi+" is UNSAT!").setType(tpe)))
+      } else {
+        RuleInapplicable
+      }
+    }
+  }
+}
+
diff --git a/src/main/scala/leon/synthesis/rules/UnusedInput.scala b/src/main/scala/leon/synthesis/rules/UnusedInput.scala
new file mode 100644
index 0000000000000000000000000000000000000000..cc9c35d6def0a11d37b5663642a7e19d411904f7
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/UnusedInput.scala
@@ -0,0 +1,22 @@
+package leon
+package synthesis
+package rules
+
+import purescala.Trees._
+import purescala.TreeOps._
+import purescala.Extractors._
+
+class UnusedInput(synth: Synthesizer) extends Rule("UnusedInput", synth, 100) {
+  def applyOn(task: Task): RuleResult = {
+    val p = task.problem
+    val unused = p.as.toSet -- variablesOf(p.phi) -- variablesOf(p.c)
+
+    if (!unused.isEmpty) {
+      val sub = p.copy(as = p.as.filterNot(unused))
+
+      RuleStep(List(sub), forward)
+    } else {
+      RuleInapplicable
+    }
+  }
+}