diff --git a/javawlp.cabal b/javawlp.cabal
index b2eb9801f0edf395677ea2f0a0d4458ab1556d87..ddf236691ec192c5d4ac56ec43d864af12822337 100644
--- a/javawlp.cabal
+++ b/javawlp.cabal
@@ -86,6 +86,7 @@ test-suite javawlp-tests
                      , TIRParser
                      , TEquivalenceClasses
                      , TModelParser
+                     , TFeedback
   build-depends:       base
                      , javawlp
                      , test-framework
diff --git a/src/API.hs b/src/API.hs
index 81a3f59fb5e1deed5ca29ffbfbfd04d58e3ebf7c..a577fda4fc985d24f8b7c35726ae51f8f6571d35 100644
--- a/src/API.hs
+++ b/src/API.hs
@@ -31,10 +31,10 @@ data ParseMode = Raw | File
 
 type Source = String
 
-type EquivImpl = LExpr -> LExpr -> IO Response
-
 type MethodDef = ([TypeDecl], Stmt, TypeEnv)
 
+type EquivImpl = LExpr -> LExpr -> IO Response
+
 -- | Calls proveSpec and testSpec on different threads and returns
 --   their response. If the function is called in debug mode, it
 --   compares the result of testSpec and proveSpec. Otherwise, it
@@ -71,15 +71,15 @@ compareSpec m pMode methodA methodB = do
 
             mv1 <- newEmptyMVar
             mv2 <- if m == Debug then newEmptyMVar else return mv1
-            mapM_ compareSpecHelper [ (mv1, "Z3", Z3.equivalentTo)
+            mapM_ compareSpecHelper [ (mv1, "Z3", Test.equivalentTo)
                                     , (mv2, "Test", Test.equivalentTo)
                                     ]
             res1 <- readMVar mv1
             res2 <- readMVar mv2 -- if Release, this won't block
             return $ getRes m res1 res2
             where -- | Runs f on a separate thread and stores the result in mv.
-            compareSpecHelper (mv, name, impl) = forkIO $ do
-                    res <- checkSpec name impl (preL, preL') (postL, postL') 
+                  compareSpecHelper (mv, name, impl) = forkIO $ do
+                    res <- checkEquiv name impl (preL, preL') (postL, postL')
                     res `seq` putMVar mv res
 
 -- | Makes sure that both Responses are the same, otherwise, if we
@@ -90,16 +90,17 @@ getRes :: Mode     -- ^ True if debug mode, False otherwise
        -> Response -- ^ The response from proveSpec
        -> Response -- ^ The response from testSpec
        -> Response -- ^ The final response.
-getRes _        Timeout           testRes           = testRes
-getRes _        Undefined         testRes           = testRes
-getRes _        Equivalent        Equivalent        = Equivalent
-getRes _        (NotEquivalent m) (NotEquivalent _) = NotEquivalent m
-getRes Release  resp              _                 = resp
-getRes Debug    resp              resp'             =
-    error $ "proveSpec says " ++ show resp ++ ", testSpec says " ++ show resp'
-
-checkSpec :: String -> EquivImpl -> (LExpr, LExpr) -> (LExpr, LExpr) -> IO Response
-checkSpec name equivTo (preL, preL') (postL, postL') = do
+getRes _        Timeout             testRes             = testRes
+getRes _        Undefined           testRes             = testRes
+getRes _        Equivalent          Equivalent          = Equivalent
+getRes _        (NotEquivalent m f) (NotEquivalent _ _) = NotEquivalent m f
+getRes Release  resp                _                   = resp
+getRes Debug    resp                resp'               =
+  error $ "proveSpec says " ++ show resp ++ ", testSpec says " ++ show resp'
+
+-- | Check if two logic statements are equivalent.
+checkEquiv :: String -> EquivImpl -> (LExpr, LExpr) -> (LExpr, LExpr) -> IO Response
+checkEquiv name equivTo (preL, preL') (postL, postL') = do
     preRes <- preL `equivTo` preL'
     log $ "PreResponse (" ++ name ++ "):\n" ++ show preRes ++ "\n"
     postRes <- postL `equivTo` postL'
diff --git a/src/LogicIR/Backend/QuickCheck/API.hs b/src/LogicIR/Backend/QuickCheck/API.hs
index 7e7d233a547b9742933679cdb9b9aa949b28a65a..e2fcc8bfa821dfd2d6c9f4810ac5451f7ed63357 100644
--- a/src/LogicIR/Backend/QuickCheck/API.hs
+++ b/src/LogicIR/Backend/QuickCheck/API.hs
@@ -14,9 +14,10 @@ import Model
 equivalentTo :: LExpr -> LExpr -> IO Response
 equivalentTo lexpr lexpr' = do
     (eq, testModel) <- testEquality 1000 lexpr lexpr'
+    let feedback = NoFeedback -- TODO check implication (stronger + weaker)
     if eq
     then return Equivalent
-    else return $ NotEquivalent $ toZ3Model testModel
+    else return $ NotEquivalent (toZ3Model testModel) (feedback, NoFeedback)
 
 toZ3Model :: (QC.Model, QC.ArrayModel) -> Model
 toZ3Model (m, arrayM) =
diff --git a/src/LogicIR/Backend/Z3/API.hs b/src/LogicIR/Backend/Z3/API.hs
index f761068873902f7cf6d893f09d9316f13df5442c..0fe5a166555f16ab0497cbce83f41e9c302c3078 100644
--- a/src/LogicIR/Backend/Z3/API.hs
+++ b/src/LogicIR/Backend/Z3/API.hs
@@ -1,27 +1,95 @@
-module LogicIR.Backend.Z3.API where
+module LogicIR.Backend.Z3.API
+       ( equivalentTo
+       , showZ3AST
+       )
+       where
 
-import qualified Z3.Base as Z3
-import Z3.Monad hiding (Model)
+import qualified Z3.Base  as Z3
+import           Z3.Monad hiding (Model)
 
-import Control.Exception.Base (tryJust)
-import Control.Monad (forM)
-import Data.String
-import Data.Maybe (fromJust)
-import qualified Data.Map as M
+import           Control.Exception.Base (tryJust)
+import           Control.Monad          (forM, when)
+import qualified Data.Map               as M
+import           Data.Maybe             (fromJust)
+import           Data.String
 
+import Control.Monad.Trans   (liftIO)
 import LogicIR.Backend.Z3.Z3
-import LogicIR.Expr (LExpr)
+import LogicIR.Expr          (LExpr, lnot, (.<==>), (.==>))
 import Model
 
+data Z3Response = Satisfiable Model | Unsatisfiable | Undecidable
+                  deriving Show
+
 -- | Determine the equality of two method's pre/post conditions.
 equivalentTo :: LExpr -> LExpr -> IO Response
-equivalentTo lexpr lexpr' = do
-    let fv = freeVars2 lexpr lexpr'
-    let (ast, ast') = (lExprToZ3Ast lexpr, lExprToZ3Ast lexpr')
-    res <- tryJust errorSelector $ equivalentToZ3 fv ast ast'
-    case res of
-      Left () -> return Timeout
-      Right r -> return r
+equivalentTo l l' = do
+  equiv <- checkZ3 $ lnot (l .<==> l')
+  case equiv of
+    Satisfiable m -> do
+      feedback <- do
+        stronger <- checkZ3 $ lnot (l .==> l')
+        case stronger of
+          Unsatisfiable -> return Stronger
+          _ -> do
+            weaker <- checkZ3 $ lnot (l' .==> l)
+            case weaker of
+              Unsatisfiable -> return Weaker
+              _             -> return NoFeedback
+      return $ NotEquivalent m (feedback, NoFeedback)
+    Unsatisfiable -> return Equivalent
+    Undecidable -> return Undefined
+
+-- | Checks the validity of a logical statement.
+checkZ3 :: LExpr -> IO Z3Response
+checkZ3 l = catchTimeout $ tryZ3 $ do
+  -- solverReset
+  fv <- freeVars l
+  ast <- lExprToZ3Ast l
+  -- Get model
+  (r, model) <- local $ assert ast >> solverCheckAndGetModel
+  response <- case r of
+    Sat   -> do
+      -- Construct counter-model
+      ms <- local (sanitize . M.fromList <$>
+         forM (M.toList fv) (evalAST fv (fromJust model)))
+      return $ Satisfiable ms
+    Unsat -> return Unsatisfiable
+    _ -> return Undecidable
+  solverReset
+  return response
+  where
+    -- Construct model values
+    evalAST :: FreeVars -> Z3.Model -> (String, AST) -> Z3 (String, ModelVal)
+    evalAST fv m (k, ast) = do
+      v <- fromJust <$> modelEval m ast True
+      sortKind <- getSort v >>= getSortKind
+      if sortKind == Z3_ARRAY_SORT then do
+        -- Retrieve array's length
+        lenName <- mkStringSymbol (k ++ "?length") >>= mkIntVar
+        f <- snd <$> evalAST fv m (k ++ "?length", lenName)
+        let len = case f of
+              (IntVal i) -> i
+              _          -> error "non-int length"
+        -- Iterate array "points"
+        modelVals <- forM [0..(len-1)] (\i -> do
+          indexAST <- mkInteger $ toInteger i
+          pointAST <- mkSelect v indexAST
+          snd <$> evalAST fv m ("", pointAST)
+          )
+        return (k, ManyVal modelVals)
+      else do
+        v' <- astToString v
+        return (k, fromString v' :: ModelVal)
+
+
+-- | Run a Z3 action in 'IO', but catch timeouts.
+catchTimeout :: IO Z3Response -> IO Z3Response
+catchTimeout prog = do
+  res <- tryJust errorSelector prog
+  return $ case res of
+    Left () -> Undecidable
+    Right r -> r
     where
       errorSelector :: Z3Error -> Maybe ()
       errorSelector err =
@@ -29,75 +97,16 @@ equivalentTo lexpr lexpr' = do
           InvalidUsage -> Just ()
           _            -> Nothing
 
--- | Check if two Z3 AST's are equivalent.
-equivalentToZ3 :: Z3 FreeVars -> Z3 AST -> Z3 AST -> IO Response
-equivalentToZ3 fVars ast1' ast2' =
-  tryZ3 $ do
-    -- Setup
-    fv <- fVars
-    ast1 <- ast1'
-    ast2 <- ast2'
-    astEq <- mkEq ast1 ast2
-    astNeq <- mkNot astEq -- negate the question to get a model
-
-    -- Tactics
-    g <- mkGoal True True False
-    goalAssert g astNeq
-    t <- "qe" --> "aig"
-    a <- applyTactic t g
-    asts <- getGoalFormulas =<< getApplyResultSubgoal a 0
-    g' <- mkAnd asts
-    assert g'
-
-    -- Get model
-    (r, model) <- solverCheckAndGetModel
-    response <- case r of
-      Unsat -> return Equivalent
-      Undef -> return Undefined
-      Sat   -> do
-        ms <- M.fromList <$> forM (M.toList fv) (evalAST fv (fromJust model))
-        return $ NotEquivalent (sanitize ms)
-    solverReset
-    return response
-    where
-      -- Construct model values
-      evalAST :: FreeVars -> Z3.Model -> (String, AST) -> Z3 (String, ModelVal)
-      evalAST fv m (k, ast) = do
-        v <- fromJust <$> modelEval m ast True
-        sortKind <- getSort v >>= getSortKind
-        if sortKind == Z3_ARRAY_SORT then do
-          -- Retrieve array's length
-          lenName <- mkStringSymbol (k ++ "?length") >>= mkIntVar
-          f <- snd <$> evalAST fv m (k ++ "?length", lenName)
-          let len = case f of
-                (IntVal i) -> i
-                _          -> error "non-int length"
-          -- Iterate array "points"
-          modelVals <- forM [0..(len-1)] (\i -> do
-            indexAST <- mkInteger $ toInteger i
-            pointAST <- mkSelect v indexAST
-            snd <$> evalAST fv m ("", pointAST)
-            )
-          return (k, ManyVal modelVals)
-        else do
-          v' <- astToString v
-          return (k, fromString v' :: ModelVal)
-
 -- | Z3 try evaluation with timeout.
 tryZ3 :: Z3 a -> IO a
-tryZ3 = evalZ3With Nothing (  opt "timeout" (5000 :: Integer)
-                           +? opt "model_validate" True
-                           +? opt "well_sorted_check" True
-                           +? opt "auto_config" True
-                           -- +? opt "unsat_core" True
-                           )
-
--- | Sequence tactics.
-(-->) :: String -> String -> Z3 Z3.Tactic
-(-->) t t' = do
-  tt <- mkTactic t
-  tt' <- mkTactic t'
-  andThenTactic tt tt'
+tryZ3 prog = do
+  env <- newEnv Nothing (  opt "timeout" (5000 :: Integer)
+                        +? opt "model_validate" True
+                        +? opt "well_sorted_check" True
+                        +? opt "auto_config" True
+                        -- +? opt "unsat_core" True
+                        )
+  evalZ3WithEnv prog env
 
 -- | Pretty-print Z3 AST.
 showZ3AST :: Z3 AST -> IO String
diff --git a/src/LogicIR/Backend/Z3/Z3.hs b/src/LogicIR/Backend/Z3/Z3.hs
index f77c05534a6093182af2e7c9236ae944d4313a43..6440998f1c1e266e0871b740f2bb7d5984e306d3 100644
--- a/src/LogicIR/Backend/Z3/Z3.hs
+++ b/src/LogicIR/Backend/Z3/Z3.hs
@@ -18,7 +18,7 @@ lExprToZ3AstAlgebra =
     CBool b -> mkBool b
     CInt n -> mkInteger $ toInteger n
     CReal f -> mkRealNum f
-    CNil -> error "null constants cannot be used directly with Z3 (use LogicIR.Backend.Null)"
+    CNil -> error "null constants cannot be used directly with Z3 (see LogicIR.Null)"
   fUnop o a' = do
     a <- a'
     case o of
diff --git a/src/LogicIR/Expr.hs b/src/LogicIR/Expr.hs
index 86459355b8d69d6490613104e2a615bb951d95bc..51394d2319d71f08bc9fc77161bfb8fb2b01c432 100644
--- a/src/LogicIR/Expr.hs
+++ b/src/LogicIR/Expr.hs
@@ -86,7 +86,7 @@ infix 1 .: ; infix 1 .!
 (.!) = LArray
 
 binOp op e = LBinop e op
-infix 2 .==> ; infix 3 .&& ; infix 3 .||
+infix 2 .<==> ; infix 2 .==> ; infix 3 .&& ; infix 3 .||
 infix 4 .== ; infix 4 .!= ; infix 4 .> ; infix 4 .< ; infix 4 .<= ; infix 4 .>=
 infix 5 .* ; infix 5 ./ ; infix 5 .%
 infix 6 .+ ; infix 6 .-
@@ -95,4 +95,5 @@ infix 6 .+ ; infix 6 .-
 (.==) = binOp CEqual ; (.!=) x y = lnot $ x .== y
 (.<) = binOp CLess ; (.<=) x y = x .< y .|| x.== y
 (.>) = binOp CGreater ; (.>=) x y = x .> y .|| x.== y
-(.&&) = binOp LAnd ; (.||) = binOp LOr ; (.==>) = binOp LImpl
+(.&&) = binOp LAnd ; (.||) = binOp LOr
+(.==>) = binOp LImpl ; (.<==>) = (.==)
diff --git a/src/Model.hs b/src/Model.hs
index 59d9658bf46c72f35de672e5b77d870606f85f9e..f23e7172869bd87c3aafbf2878b41fbdc2e5ee36 100644
--- a/src/Model.hs
+++ b/src/Model.hs
@@ -4,36 +4,39 @@
 {-# LANGUAGE TypeSynonymInstances #-}
 module Model where
 
-import Data.List (isSuffixOf)
-import qualified Data.Map as M
-import Data.String
-import GHC.Generics
-import Text.Parsec hiding (runP)
-import Text.Parsec.Language
-import Text.Parsec.String
-import qualified Text.Parsec.Token as Tokens
+import           Data.List            (isSuffixOf)
+import qualified Data.Map             as M
+import           Data.String
+import           GHC.Generics
+import           Text.Parsec          hiding (runP)
+import           Text.Parsec.Language
+import           Text.Parsec.String
+import qualified Text.Parsec.Token    as Tokens
 
 import LogicIR.ParserUtils
 
+-- | Feedback type.
+data Feedback = Stronger   -- A ==> B
+              | Weaker     -- A <== B
+              | NoFeedback -- A ??? B
+                deriving (Eq, Show, Generic)
+
 -- | Response type.
-data Response = Equivalent | NotEquivalent Model | ErrorResponse String | Undefined | Timeout
-                deriving (Eq)
+data Response = Equivalent
+              | NotEquivalent Model (Feedback, Feedback)
+              | ErrorResponse String
+              | Undefined
+              | Timeout
+                deriving (Eq, Show)
 
 (<>) :: Response -> Response -> Response
-ErrorResponse e <> _ = ErrorResponse e
-_ <> ErrorResponse e = ErrorResponse e
-Equivalent <> r = r
-NotEquivalent s <> _ = NotEquivalent s
-Timeout <> _ = Timeout
-Undefined <> _ = Undefined
-
-instance Show Response where
-  show Equivalent = "Formulas are equivalent"
-  show (NotEquivalent model) = "Not equivalent: " ++ show model
-  show (ErrorResponse e) = "*** Error: " ++ show e
-  show Undefined = "Oops... could not determine if formulas are equivalent"
-  show Timeout = "Timeout occured"
-
+ErrorResponse e        <> _                       = ErrorResponse e
+_                      <> ErrorResponse e         = ErrorResponse e
+Timeout                <> _                       = Timeout
+Undefined              <> _                       = Undefined
+Equivalent             <> r                       = r
+NotEquivalent s (f, _) <> NotEquivalent _ (f', _) = NotEquivalent s (f, f')
+r                      <> _                       = r
 
 -- | Model type.
 data ModelVal = BoolVal Bool
@@ -46,6 +49,8 @@ type Model = M.Map String ModelVal
 
 emptyModel :: Model
 emptyModel = M.empty
+emptyFeedback :: (Feedback, Feedback)
+emptyFeedback = (NoFeedback, NoFeedback)
 
 -- | Pretty-printing.
 instance Show ModelVal where
diff --git a/src/Server.hs b/src/Server.hs
index 6fbb0e39d767abaa9bd5236f46e03b96c9ccef65..ddacd7d2f463cc8e8980eb0e4dae44c9cf4fb06b 100644
--- a/src/Server.hs
+++ b/src/Server.hs
@@ -52,8 +52,10 @@ data ApiResponse = ApiResponse
   { responseType :: ApiResponseType
   , model        :: Maybe Model
   , err          :: Maybe String
+  , feedback     :: Maybe (Feedback, Feedback)
   }
   deriving (Eq, Show, Generic)
+instance ToJSON Feedback
 instance ToJSON ApiResponseType
 instance ToJSON ApiResponse
 instance ToJSON ModelVal where
@@ -62,6 +64,13 @@ instance ToJSON ModelVal where
   toJSON (RealVal n)  = toJSON n
   toJSON (ManyVal vs) = Array $ fromList $ map toJSON vs
 
+-- | Default API response.
+defResp = ApiResponse { responseType = Undef
+                      , model = Nothing
+                      , err = Nothing
+                      , feedback = Nothing
+                      }
+
 type CompareApi = "compare"
   :> ReqBody '[JSON] ApiReqBody
   :> Post '[JSON] ApiResponse
@@ -74,13 +83,16 @@ getCompareResponse ApiReqBody {sourceA = srcA, sourceB = srcB} = do
     resp <- liftIO $ compareSpec Release Raw (wrap srcA) (wrap srcB)
     return $ case resp of
                   Equivalent ->
-                    ApiResponse { responseType = Equiv, model = Nothing, err = Nothing }
-                  NotEquivalent m ->
-                    ApiResponse { responseType = NotEquiv, model = Just m, err = Nothing }
+                    defResp { responseType = Equiv }
+                  NotEquivalent m f ->
+                    defResp { responseType = NotEquiv
+                            , model = Just m
+                            , feedback = Just f
+                            }
                   ErrorResponse e ->
-                    ApiResponse { responseType = ResponseErr, model = Nothing, err = Just e }
+                    defResp { responseType = ResponseErr, err = Just e }
                   _ ->
-                    ApiResponse { responseType = Undef, model = Nothing, err = Nothing }
+                    defResp { responseType = Undef }
     where
       wrap s = ( "public class Main {" ++ s ++ "}"
                , last $ splitOn " " $ head $ splitOn "(" s
@@ -106,6 +118,7 @@ instance ToSample ApiResponse where
       , ("i", IntVal 10)
       ]
     , err = Nothing
+    , feedback = Just (NoFeedback, Stronger)
     }
 
 docsBS :: ByteString
@@ -117,6 +130,7 @@ docsBS = encodeUtf8
         opts = DocOptions 1
 
 -- | API documentation (Swagger).
+instance ToSchema Feedback
 instance ToSchema ApiReqBody
 instance ToSchema ApiResponseType
 instance ToSchema ApiResponse
diff --git a/test/Spec.hs b/test/Spec.hs
index 4baa53534870c3a914274de4f758b085538c0adf..d8f8ac956c98c7604770c6981d8f2ac14ba274df 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -5,6 +5,7 @@ import TExamples
 import TIRParser
 import TEquivalenceClasses
 import TModelParser
+import TFeedback
 
 main = defaultMain
   [ constructTestSuite testName testSuite
@@ -14,8 +15,9 @@ main = defaultMain
       , ("EXAMPLES", examples)
       , ("EQUIV_REAL", genEquivTests "examples/test_equiv/Reals.java")
       , ("EQUIV_ARRAY", genEquivTests "examples/test_equiv/Arrays.java")
+      , ("FEEDBACK", feedbackTests)
       ]
   ]
-
-constructTestSuite s suite =
-  testGroup s [testCase (s ++ "_" ++ show i) t | (i, t) <- zip [1..] suite]
+  where
+    constructTestSuite s suite =
+      testGroup s [testCase (s ++ "_" ++ show i) t | (i, t) <- zip [1..] suite]
diff --git a/test/TEquivalenceClasses.hs b/test/TEquivalenceClasses.hs
index 855d1f2f9f8dab1be9321856367a8827853ee168..7d4ac12628c3f4ffa38cf8f7b2c9501150a97323 100644
--- a/test/TEquivalenceClasses.hs
+++ b/test/TEquivalenceClasses.hs
@@ -17,10 +17,10 @@ testEquiv :: Response -> String -> String -> String -> Assertion
 testEquiv b src s s' = do
   res <- hSilence [stdout, stderr] $ compareSpec Debug File (src, s) (src, s')
   (case res of
-    NotEquivalent x -> NotEquivalent emptyModel
+    NotEquivalent _ _ -> NotEquivalent emptyModel emptyFeedback
     x               -> x) @?= b
 (.==) = testEquiv Equivalent
-(.!=) = testEquiv $ NotEquivalent emptyModel
+(.!=) = testEquiv $ NotEquivalent emptyModel emptyFeedback
 
 genEquivTests edslSrc =
   let methodIds = unsafePerformIO (hSilence [stdout, stderr] $ parseMethodIds edslSrc)
diff --git a/test/TExamples.hs b/test/TExamples.hs
index 6a0303266afdff48c7e793da16bba2c9bd4f3cef..5afa55b3c0895bad8cd3377ec574baabae037e7a 100644
--- a/test/TExamples.hs
+++ b/test/TExamples.hs
@@ -11,12 +11,12 @@ src = "examples/javawlp_edsl/src/nl/uu/javawlp_edsl/Main.java"
 
 testEquiv :: Response -> String -> String -> Assertion
 testEquiv b s s' = do
-  res <- hSilence [stdout, stderr] $ compareSpec Debug File (src, s) (src, s')
+  res <- hSilence [stdout, stderr] $ compareSpec Release File (src, s) (src, s')
   (case res of
-    NotEquivalent _ -> NotEquivalent emptyModel
-    x               -> x) @?= b
+    NotEquivalent _ _ -> NotEquivalent emptyModel emptyFeedback
+    x                 -> x) @?= b
 (.==) = testEquiv Equivalent
-(.!=) = testEquiv $ NotEquivalent emptyModel
+(.!=) = testEquiv $ NotEquivalent emptyModel emptyFeedback
 (.??) = testEquiv Timeout
 
 examples =
diff --git a/test/TFeedback.hs b/test/TFeedback.hs
new file mode 100644
index 0000000000000000000000000000000000000000..0065748f2d8c608eb5384c53c494f6dca7736a1a
--- /dev/null
+++ b/test/TFeedback.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE OverloadedStrings #-}
+module TFeedback where
+import System.IO          (stderr, stdout)
+import System.IO.Silently (hSilence)
+import System.IO.Unsafe   (unsafePerformIO)
+import Test.HUnit
+
+import LogicIR.Expr
+import LogicIR.Parser
+import LogicIR.Backend.Z3.API
+import Model
+
+testEquiv :: Response -> LExpr -> LExpr -> Assertion
+testEquiv b s s' = do
+  res <- hSilence [stdout, stderr] $ equivalentTo s s'
+  (case res of
+    NotEquivalent _ f -> NotEquivalent emptyModel f
+    x                 -> x) @?= b
+(!!=) s s' f = testEquiv (NotEquivalent emptyModel (f, NoFeedback)) s s'
+
+feedbackTests =
+  [ ("true /\\ x:int == 1" !!= "false \\/ x:int >= 1") Stronger
+  , ("true /\\ x:int >= 1" !!= "false \\/ x:int == 1") Weaker
+  , ("x:int == 1 /\\ y:int == 1" !!= "x:int == 2 /\\ y:int == 1") NoFeedback
+  , ("x:[int][0] != 0 /\\ x:[int][1] != 0"
+      !!= "x:[int][0] != 0 \\/ x:[int][1] != 0") Stronger
+  ]