diff --git a/Data/ChurchList.hs b/Data/ChurchList.hs
new file mode 100644
--- /dev/null
+++ b/Data/ChurchList.hs
@@ -0,0 +1,99 @@
+-- Church-encoded lists. Used in Twee.CP to make sure that fusion happens.
+{-# LANGUAGE Rank2Types, BangPatterns #-}
+module Data.ChurchList where
+
+import Prelude(Functor(..), Applicative(..), Monad(..), Bool(..), Maybe(..), (.), ($), id)
+import qualified Prelude
+import GHC.Magic(oneShot)
+import GHC.Exts(build)
+import Control.Monad(MonadPlus(..), liftM2)
+import Control.Applicative(Alternative(..))
+
+newtype ChurchList a =
+  ChurchList (forall b. (a -> b -> b) -> b -> b)
+
+{-# INLINE foldr #-}
+foldr :: (a -> b -> b) -> b -> ChurchList a -> b
+foldr op e (ChurchList f) = eta (f op (eta e))
+  -- Using eta here seems to help with eta-expanding foldl'
+
+{-# INLINE[0] eta #-}
+eta :: a -> a
+eta x = x
+{-# RULES "eta" forall f. eta f = \x -> f x #-}
+
+{-# INLINE nil #-}
+nil :: ChurchList a
+nil = ChurchList (\_ n -> n)
+
+{-# INLINE unit #-}
+unit :: a -> ChurchList a
+unit x = ChurchList (\c n -> c x n)
+
+{-# INLINE cons #-}
+cons :: a -> ChurchList a -> ChurchList a
+cons x xs = ChurchList (\c n -> c x (foldr c n xs))
+
+{-# INLINE append #-}
+append :: ChurchList a -> ChurchList a -> ChurchList a
+append xs ys = ChurchList (\c n -> foldr c (foldr c n ys) xs)
+
+{-# INLINE join #-}
+join :: ChurchList (ChurchList a) -> ChurchList a
+join xss = ChurchList (\c n -> foldr (\xs ys -> foldr c ys xs) n xss)
+
+instance Functor ChurchList where
+  {-# INLINE fmap #-}
+  fmap f xs = ChurchList (\c n -> foldr (c . f) n xs)
+
+instance Applicative ChurchList where
+  {-# INLINE pure #-}
+  pure = return
+  {-# INLINE (<*>) #-}
+  (<*>) = liftM2 ($)
+
+instance Monad ChurchList where
+  {-# INLINE return #-}
+  return = unit
+  {-# INLINE (>>=) #-}
+  xs >>= f = join (fmap f xs)
+
+instance Alternative ChurchList where
+  {-# INLINE empty #-}
+  empty = nil
+  {-# INLINE (<|>) #-}
+  (<|>) = append
+
+instance MonadPlus ChurchList where
+  {-# INLINE mzero #-}
+  mzero = empty
+  {-# INLINE mplus #-}
+  mplus = (<|>)
+
+{-# INLINE fromList #-}
+fromList :: [a] -> ChurchList a
+fromList xs = ChurchList (\c n -> Prelude.foldr c n xs)
+
+{-# INLINE toList #-}
+toList :: ChurchList a -> [a]
+toList (ChurchList f) = build f
+
+{-# INLINE foldl' #-}
+foldl' :: (b -> a -> b) -> b -> ChurchList a -> b
+foldl' op e xs =
+  foldr (\x f -> oneShot (\ (!acc) -> f (op acc x))) id xs e
+
+{-# INLINE filter #-}
+filter :: (a -> Bool) -> ChurchList a -> ChurchList a
+filter p xs =
+  ChurchList $ \c n ->
+    let            
+      {-# INLINE op #-}
+      op x xs = if p x then c x xs else xs
+    in
+      foldr op n xs
+
+{-# INLINE fromMaybe #-}
+fromMaybe :: Maybe a -> ChurchList a
+fromMaybe Nothing = nil
+fromMaybe (Just x) = unit x
diff --git a/Data/DynamicArray.hs b/Data/DynamicArray.hs
new file mode 100644
--- /dev/null
+++ b/Data/DynamicArray.hs
@@ -0,0 +1,67 @@
+-- | Zero-indexed dynamic arrays, optimised for lookup.
+-- Modification is slow. Uninitialised indices have a default value.
+{-# LANGUAGE CPP #-}
+module Data.DynamicArray where
+
+#ifdef BOUNDS_CHECKS
+import qualified Data.Primitive.SmallArray.Checked as P
+#else
+import qualified Data.Primitive.SmallArray as P
+#endif
+import Control.Monad.ST
+import Data.List
+
+-- | A type which has a default value.
+class Default a where
+  -- | The default value.
+  def :: a
+
+-- | An array.
+data Array a =
+  Array {
+    -- | The size of the array.
+    arraySize     :: {-# UNPACK #-} !Int,
+    -- | The contents of the array.
+    arrayContents :: {-# UNPACK #-} !(P.SmallArray a) }
+
+-- | Convert an array to a list of (index, value) pairs.
+{-# INLINE toList #-}
+toList :: Array a -> [(Int, a)]
+toList arr =
+  [ (i, x)
+  | i <- [0..arraySize arr-1],
+    let x = P.indexSmallArray (arrayContents arr) i ]
+
+instance Show a => Show (Array a) where
+  show arr =
+    "{" ++
+    intercalate ", "
+      [ show i ++ "->" ++ show x
+      | (i, x) <- toList arr ] ++
+    "}"
+
+-- | Create an empty array.
+newArray :: Default a => Array a
+newArray = runST $ do
+  marr <- P.newSmallArray 0 def
+  arr  <- P.unsafeFreezeSmallArray marr
+  return (Array 0 arr)
+
+-- | Index into an array. O(1) time.
+{-# INLINE (!) #-}
+(!) :: Default a => Array a -> Int -> a
+arr ! n
+  | 0 <= n && n < arraySize arr =
+    P.indexSmallArray (arrayContents arr) n
+  | otherwise = def
+
+-- | Update the array. O(n) time.
+{-# INLINEABLE update #-}
+update :: Default a => Int -> a -> Array a -> Array a
+update n x arr = runST $ do
+  let size = arraySize arr `max` (n+1)
+  marr <- P.newSmallArray size def
+  P.copySmallArray marr 0 (arrayContents arr) 0 (arraySize arr)
+  P.writeSmallArray marr n $! x
+  arr' <- P.unsafeFreezeSmallArray marr
+  return (Array size arr')
diff --git a/Data/Heap.hs b/Data/Heap.hs
new file mode 100644
--- /dev/null
+++ b/Data/Heap.hs
@@ -0,0 +1,154 @@
+-- | Skew heaps.
+
+{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
+module Data.Heap(
+  Heap, empty, singleton, insert, removeMin, union, mapMaybe, size) where
+
+-- | A heap.
+
+-- Representation: the size of the heap, and the heap itself.
+data Heap a = Heap {-# UNPACK #-} !Int !(Heap1 a) deriving Show
+-- N.B.: arguments are not strict so code has to take care
+-- to force stuff appropriately.
+data Heap1 a = Nil | Node a (Heap1 a) (Heap1 a) deriving Show
+
+-- | Take the union of two heaps.
+{-# INLINEABLE union #-}
+union :: Ord a => Heap a -> Heap a -> Heap a
+union (Heap n1 h1) (Heap n2 h2) = Heap (n1+n2) (union1 h1 h2)
+
+{-# INLINEABLE union1 #-}
+union1 :: forall a. Ord a => Heap1 a -> Heap1 a -> Heap1 a
+union1 = u1
+  where
+    -- The generated code is better when we do everything
+    -- through this u1 function instead of union1...
+    -- This is because u1 has no Ord constraint in its type.
+    u1 :: Heap1 a -> Heap1 a -> Heap1 a
+    u1 Nil h = h
+    u1 h Nil = h
+    u1 h1@(Node x1 l1 r1) h2@(Node x2 l2 r2)
+      | x1 <= x2 = (Node x1 $! u1 r1 h2) l1
+      | otherwise = (Node x2 $! u1 r2 h1) l2
+
+-- | A singleton heap.
+{-# INLINE singleton #-}
+singleton :: a -> Heap a
+singleton !x = Heap 1 (Node x Nil Nil)
+
+-- | The empty heap.
+{-# INLINE empty #-}
+empty :: Heap a
+empty = Heap 0 Nil
+
+-- | Insert an element.
+{-# INLINEABLE insert #-}
+insert :: Ord a => a -> Heap a -> Heap a
+insert x h = union (singleton x) h
+
+-- | Find and remove the minimum element.
+{-# INLINEABLE removeMin #-}
+removeMin :: Ord a => Heap a -> Maybe (a, Heap a)
+removeMin (Heap _ Nil) = Nothing
+removeMin (Heap n (Node x l r)) = Just (x, Heap (n-1) (union1 l r))
+
+-- | Map a function over a heap, removing all values which
+-- map to 'Nothing'. May be more efficient when the function
+-- being mapped is mostly monotonic.
+{-# INLINEABLE mapMaybe #-}
+mapMaybe :: Ord b => (a -> Maybe b) -> Heap a -> Heap b
+mapMaybe f (Heap _ h) = Heap (sz 0 h') h'
+  where
+    -- Compute the size fairly efficiently.
+    sz !n Nil = n
+    sz !n (Node _ l r) = sz (sz (n+1) l) r
+
+    h' = mm h
+
+    mm Nil = Nil
+    mm (Node x l r) =
+      case f x of
+        -- If the value maps to Nothing, get rid of it.
+        Nothing -> union1 l' r'
+        -- Otherwise, check if the heap invariant still holds
+        -- and sift downwards to restore it.
+        Just !y -> down y l' r'
+      where
+        !l' = mm l
+        !r' = mm r
+
+    down x l@(Node y ll lr) r@(Node z rl rr)
+      -- Put the smallest of x, y and z at the root.
+      | y < x && y <= z =
+        (Node y $! down x ll lr) r
+      | z < x && z <= y =
+        Node z l $! down x rl rr
+    down x Nil (Node y l r)
+      -- Put the smallest of x and y at the root.
+      | y < x =
+        Node y Nil $! down x l r
+    down x (Node y l r) Nil
+      -- Put the smallest of x and y at the root.
+      | y < x =
+        (Node y $! down x l r) Nil
+    down x l r = Node x l r
+
+-- | Return the number of elements in the heap.
+{-# INLINE size #-}
+size :: Heap a -> Int
+size (Heap n _) = n
+
+-- Testing code:
+-- import Test.QuickCheck
+-- import qualified Data.List as List
+-- import qualified Data.Maybe as Maybe
+
+-- instance (Arbitrary a, Ord a) => Arbitrary (Heap a) where
+--   arbitrary = sized arb
+--     where
+--       arb 0 = return empty
+--       arb n =
+--         frequency
+--           [(1, singleton <$> arbitrary),
+--            (n-1, union <$> arb' <*> arb')]
+--         where
+--           arb' = arb (n `div` 2)
+
+-- toList :: Ord a => Heap a -> [a]
+-- toList = List.unfoldr removeMin
+
+-- invariant :: Ord a => Heap a -> Bool
+-- invariant h@(Heap n h1) =
+--   n == length (toList h) && ord h1
+--   where
+--     ord Nil = True
+--     ord (Node x l r) = ord1 x l && ord1 x r
+
+--     ord1 _ Nil = True
+--     ord1 x h@(Node y _ _) = x <= y && ord h
+
+-- prop_1 h = withMaxSuccess 10000 $ invariant h
+-- prop_2 x h = withMaxSuccess 10000 $ invariant (insert x h)
+-- prop_3 h =
+--   withMaxSuccess 1000 $
+--   case removeMin h of
+--     Nothing -> discard
+--     Just (_, h) -> invariant h
+-- prop_4 h = withMaxSuccess 10000 $ List.sort (toList h) == toList h
+-- prop_5 x h = withMaxSuccess 10000 $ toList (insert x h) == List.insert x (toList h)
+-- prop_6 x h =
+--   withMaxSuccess 1000 $
+--   case removeMin h of
+--     Nothing -> discard
+--     Just (x, h') -> toList h == List.insert x (toList h')
+-- prop_7 h1 h2 = withMaxSuccess 10000 $
+--   invariant (union h1 h2)
+-- prop_8 h1 h2 = withMaxSuccess 10000 $
+--   toList (union h1 h2) == List.sort (toList h1 ++ toList h2)
+-- prop_9 (Blind f) h = withMaxSuccess 10000 $
+--   invariant (mapMaybe f h)
+-- prop_10 (Blind f) h = withMaxSuccess 1000000 $
+--   toList (mapMaybe f h) == List.sort (Maybe.mapMaybe f (toList h))
+
+-- return []
+-- main = $quickCheckAll
diff --git a/Data/Primitive/ByteArray/Checked.hs b/Data/Primitive/ByteArray/Checked.hs
new file mode 100644
--- /dev/null
+++ b/Data/Primitive/ByteArray/Checked.hs
@@ -0,0 +1,74 @@
+-- | A bounds-checked version of 'Data.Primitive.ByteArray'.
+-- See that module for documentation.
+
+{-# LANGUAGE ScopedTypeVariables #-}
+module Data.Primitive.ByteArray.Checked(
+  module Data.Primitive.ByteArray,
+  module Data.Primitive.ByteArray.Checked) where
+
+import Control.Monad.Primitive
+import qualified Data.Primitive.ByteArray as P
+import Data.Primitive(Prim)
+import Data.Primitive.ByteArray(
+  ByteArray(..), MutableByteArray(..),
+  newByteArray, newPinnedByteArray, newAlignedPinnedByteArray,
+  byteArrayContents, mutableByteArrayContents,
+  sameMutableByteArray,
+  unsafeFreezeByteArray, unsafeThawByteArray,
+  sizeofByteArray, sizeofMutableByteArray)
+import Data.Primitive.Checked
+import Data.Word
+
+instance Sized ByteArray where
+  size = sizeofByteArray
+instance Sized (MutableByteArray m) where
+  size = sizeofMutableByteArray
+
+{-# INLINE readByteArray #-}
+readByteArray :: forall m a. (PrimMonad m, Prim a) => MutableByteArray (PrimState m) -> Int -> m a
+readByteArray arr n =
+  checkPrim (undefined :: a) arr n $
+  P.readByteArray arr n
+
+{-# INLINE writeByteArray #-}
+writeByteArray :: (PrimMonad m, Prim a) => MutableByteArray (PrimState m) -> Int -> a -> m ()
+writeByteArray arr n x =
+  checkPrim x arr n $
+  P.writeByteArray arr n x
+
+{-# INLINE indexByteArray #-}
+indexByteArray :: forall a. Prim a => ByteArray -> Int -> a
+indexByteArray arr n =
+  checkPrim (undefined :: a) arr n $
+  P.indexByteArray arr n
+
+{-# INLINE copyByteArray #-}
+copyByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> ByteArray -> Int -> Int -> m ()
+copyByteArray arr1 n1 arr2 n2 len =
+  range arr1 n1 len $
+  range arr2 n2 len $
+  P.copyByteArray arr1 n1 arr2 n2 len
+
+{-# INLINE moveByteArray #-}
+moveByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> MutableByteArray (PrimState m) -> Int -> Int -> m ()
+moveByteArray arr1 n1 arr2 n2 len =
+  range arr1 n1 len $
+  range arr2 n2 len $
+  P.moveByteArray arr1 n1 arr2 n2 len
+
+{-# INLINE copyMutableByteArray #-}
+copyMutableByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> MutableByteArray (PrimState m) -> Int -> Int -> m ()
+copyMutableByteArray arr1 n1 arr2 n2 len =
+  range arr1 n1 len $
+  range arr2 n2 len $
+  P.copyMutableByteArray arr1 n1 arr2 n2 len
+
+{-# INLINE setByteArray #-}
+setByteArray :: (Prim a, PrimMonad m) => MutableByteArray (PrimState m) -> Int -> Int -> a -> m ()
+setByteArray arr n len x =
+  rangePrim x arr n len $
+  P.setByteArray arr n len x
+
+{-# INLINE fillByteArray #-}
+fillByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> Int -> Word8 -> m ()
+fillByteArray = setByteArray
diff --git a/Data/Primitive/Checked.hs b/Data/Primitive/Checked.hs
new file mode 100644
--- /dev/null
+++ b/Data/Primitive/Checked.hs
@@ -0,0 +1,46 @@
+-- | A helper module for array bounds checking.
+
+module Data.Primitive.Checked where
+
+import Data.Primitive(Prim, sizeOf)
+
+-- | A type class of things which have a size (e.g., arrays).
+class Sized a where
+  -- | Read the size of the thing.
+  size :: a -> Int
+
+-- | Check that a single access is in bounds.
+{-# INLINE check #-}
+check :: Sized a => a -> Int -> b -> b
+check arr n x
+  | n >= 0 && n < size arr = x
+  | otherwise = error "out-of-bounds array access"
+
+-- | Check that a range of accesses is in bounds.
+-- The range is inclusive.
+{-# INLINE range #-}
+range :: Sized a => a -> Int -> Int -> b -> b
+range arr n len x
+  | len < 0 = error "array slice has negative length"
+  | len == 0 = x
+  | otherwise =
+    check arr n $
+    check arr (n+len-1) $ x
+
+-- | Check that a single access is in bounds.
+-- The index accessed is computed by multiplying by the size
+-- of the first argument.
+{-# INLINE checkPrim #-}
+checkPrim :: (Sized a, Prim b) => b -> a -> Int -> c -> c
+checkPrim x arr n res =
+  range arr (n*sizeOf x) (sizeOf x) res
+  
+-- | Check that a range of accesses is in bounds.
+-- The range is inclusive.
+-- The index accessed is computed by multiplying by the size
+-- of the first argument.
+{-# INLINE rangePrim #-}
+rangePrim :: (Sized a, Prim b) => b -> a -> Int -> Int -> c -> c
+rangePrim x arr n len res =
+  range arr (n*sizeOf x) (len*sizeOf x) res
+  
diff --git a/Data/Primitive/SmallArray/Checked.hs b/Data/Primitive/SmallArray/Checked.hs
new file mode 100644
--- /dev/null
+++ b/Data/Primitive/SmallArray/Checked.hs
@@ -0,0 +1,80 @@
+-- | A bounds-checked version of 'Data.Primitive.SmallArray'.
+-- See that module for documentation.
+
+module Data.Primitive.SmallArray.Checked(
+  module Data.Primitive.SmallArray,
+  module Data.Primitive.SmallArray.Checked) where
+
+import Control.Monad.Primitive
+import qualified Data.Primitive.SmallArray as P
+import Data.Primitive.SmallArray(
+  SmallArray(..), SmallMutableArray(..), newSmallArray, unsafeFreezeSmallArray,
+  unsafeThawSmallArray, sizeofSmallArray, sizeofSmallMutableArray)
+import Data.Primitive.Checked
+
+instance Sized (SmallArray a) where
+  size = sizeofSmallArray
+instance Sized (SmallMutableArray m a) where
+  size = sizeofSmallMutableArray
+
+{-# INLINE readSmallArray #-}
+readSmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> m a
+readSmallArray arr n =
+  check arr n $
+  P.readSmallArray arr n
+
+{-# INLINE writeSmallArray #-}
+writeSmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> a -> m ()
+writeSmallArray arr n x =
+  check arr n $
+  P.writeSmallArray arr n x
+
+{-# INLINE indexSmallArrayM #-}
+indexSmallArrayM :: Monad m => SmallArray a -> Int -> m a
+indexSmallArrayM arr n =
+  check arr n $
+  P.indexSmallArrayM arr n
+
+{-# INLINE indexSmallArray #-}
+indexSmallArray :: SmallArray a -> Int -> a
+indexSmallArray arr n =
+  check arr n $
+  P.indexSmallArray arr n
+
+{-# INLINE cloneSmallArray #-}
+cloneSmallArray :: SmallArray a -> Int -> Int -> SmallArray a
+cloneSmallArray arr n len =
+  range arr n len $
+  P.cloneSmallArray arr n len
+
+{-# INLINE cloneSmallMutableArray #-}
+cloneSmallMutableArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> Int -> m (SmallMutableArray (PrimState m) a)
+cloneSmallMutableArray arr n len =
+  range arr n len $
+  P.cloneSmallMutableArray arr n len
+
+{-# INLINE freezeSmallArray #-}
+freezeSmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> Int -> m (SmallArray a)
+freezeSmallArray arr n len =
+  range arr n len $
+  P.freezeSmallArray arr n len
+
+{-# INLINE thawSmallArray #-}
+thawSmallArray :: PrimMonad m => SmallArray a -> Int -> Int -> m (SmallMutableArray (PrimState m) a)
+thawSmallArray arr n len =
+  range arr n len $
+  P.thawSmallArray arr n len
+
+{-# INLINE copySmallArray #-}
+copySmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> SmallArray a -> Int -> Int -> m ()
+copySmallArray arr1 n1 arr2 n2 len =
+  range arr1 n1 len $
+  range arr2 n2 len $
+  P.copySmallArray arr1 n1 arr2 n2 len
+
+{-# INLINE copySmallMutableArray #-}
+copySmallMutableArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> SmallMutableArray (PrimState m) a -> Int -> Int -> m ()
+copySmallMutableArray arr1 n1 arr2 n2 len =
+  range arr1 n1 len $
+  range arr2 n2 len $
+  P.copySmallMutableArray arr1 n1 arr2 n2 len
diff --git a/README.md b/README.md
deleted file mode 100644
--- a/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-This is twee, an equational theorem prover.
-
-The version in this git repository is likely to be unstable!
-To install the latest stable version, run:
-
-    cabal install twee
-
-If you have LLVM installed, you can get a slightly faster version by
-running:
-
-    cabal install twee -fllvm
-
-If you really want the latest unstable version, run `cabal install` in
-this repository, and then in the `executable` subdirectory.
-You will most likely need the latest git version of Jukebox, from
-https://github.com/nick8325/jukebox, too - and things may break from
-time to time.
-
-Afterwards, run `twee nameofproblem.p`. The problem should be in TPTP
-format (http://www.tptp.org). You can find a few examples in the
-`tests` directory. All axioms and conjectures must be equations, but
-you can freely use quantifiers. If it succeeds in proving your
-problem, twee will print a human-readable proof.
-
-For the official manual, see http://nick8325.github.io/twee.
diff --git a/Twee.hs b/Twee.hs
new file mode 100644
--- /dev/null
+++ b/Twee.hs
@@ -0,0 +1,616 @@
+-- | The main prover loop.
+{-# LANGUAGE RecordWildCards, MultiParamTypeClasses, GADTs, BangPatterns, OverloadedStrings, ScopedTypeVariables, GeneralizedNewtypeDeriving, PatternGuards, TypeFamilies #-}
+module Twee where
+
+import Twee.Base
+import Twee.Rule hiding (normalForms)
+import qualified Twee.Rule as Rule
+import Twee.Equation
+import qualified Twee.Proof as Proof
+import Twee.Proof(Proof, Axiom(..), Lemma(..), ProvedGoal(..), provedGoal, certify, derivation, symm)
+import Twee.CP hiding (Config)
+import qualified Twee.CP as CP
+import Twee.Join hiding (Config, defaultConfig)
+import qualified Twee.Join as Join
+import qualified Twee.Rule.Index as RuleIndex
+import Twee.Rule.Index(RuleIndex(..))
+import qualified Twee.Index as Index
+import Twee.Index(Index)
+import Twee.Constraints
+import Twee.Utils
+import Twee.Task
+import qualified Twee.PassiveQueue as Queue
+import Twee.PassiveQueue(Queue, Passive(..))
+import qualified Data.IntMap.Strict as IntMap
+import Data.IntMap(IntMap)
+import Data.Maybe
+import Data.List
+import Data.Function
+import qualified Data.Set as Set
+import Data.Set(Set)
+import Data.Int
+import Data.Ord
+import Control.Monad
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Class
+import qualified Control.Monad.Trans.State.Strict as StateM
+
+----------------------------------------------------------------------
+-- * Configuration and prover state.
+----------------------------------------------------------------------
+
+-- | The prover configuration.
+data Config =
+  Config {
+    cfg_max_term_size          :: Int,
+    cfg_max_critical_pairs     :: Int64,
+    cfg_max_cp_depth           :: Int,
+    cfg_simplify               :: Bool,
+    cfg_renormalise_percent    :: Int,
+    cfg_critical_pairs         :: CP.Config,
+    cfg_join                   :: Join.Config,
+    cfg_proof_presentation     :: Proof.Config }
+
+-- | The prover state.
+data State f =
+  State {
+    st_rules          :: !(RuleIndex f (ActiveRule f)),
+    st_active_ids     :: !(IntMap (Active f)),
+    st_rule_ids       :: !(IntMap (ActiveRule f)),
+    st_joinable       :: !(Index f (Equation f)),
+    st_goals          :: ![Goal f],
+    st_queue          :: !(Queue Params),
+    st_next_active    :: {-# UNPACK #-} !Id,
+    st_next_rule      :: {-# UNPACK #-} !RuleId,
+    st_considered     :: {-# UNPACK #-} !Int64,
+    st_messages_rev   :: ![Message f] }
+
+-- | The default prover configuration.
+defaultConfig :: Config
+defaultConfig =
+  Config {
+    cfg_max_term_size = maxBound,
+    cfg_max_critical_pairs = maxBound,
+    cfg_max_cp_depth = maxBound,
+    cfg_simplify = True,
+    cfg_renormalise_percent = 5,
+    cfg_critical_pairs = CP.defaultConfig,
+    cfg_join = Join.defaultConfig,
+    cfg_proof_presentation = Proof.defaultConfig }
+
+-- | Does this configuration run the prover in a complete mode?
+configIsComplete :: Config -> Bool
+configIsComplete Config{..} =
+  cfg_max_term_size == maxBound &&
+  cfg_max_critical_pairs == maxBound &&
+  cfg_max_cp_depth == maxBound
+
+-- | The initial state.
+initialState :: State f
+initialState =
+  State {
+    st_rules = RuleIndex.empty,
+    st_active_ids = IntMap.empty,
+    st_rule_ids = IntMap.empty,
+    st_joinable = Index.empty,
+    st_goals = [],
+    st_queue = Queue.empty,
+    st_next_active = 1,
+    st_next_rule = 0,
+    st_considered = 0,
+    st_messages_rev = [] }
+
+----------------------------------------------------------------------
+-- * Messages.
+----------------------------------------------------------------------
+
+-- | A message which is produced by the prover when something interesting happens.
+data Message f =
+    -- | A new rule.
+    NewActive !(Active f)
+    -- | A new joinable equation.
+  | NewEquation !(Equation f)
+    -- | A rule was deleted.
+  | DeleteActive !(Active f)
+    -- | The CP queue was simplified.
+  | SimplifyQueue
+    -- | The rules were reduced wrt each other.
+  | Interreduce
+
+instance Function f => Pretty (Message f) where
+  pPrint (NewActive rule) = pPrint rule
+  pPrint (NewEquation eqn) =
+    text "  (hard)" <+> pPrint eqn
+  pPrint (DeleteActive rule) =
+    text "  (delete rule " <> pPrint (active_id rule) <> text ")"
+  pPrint SimplifyQueue =
+    text "  (simplifying queued critical pairs...)"
+  pPrint Interreduce =
+    text "  (simplifying rules with respect to one another...)"
+
+-- | Emit a message.
+message :: PrettyTerm f => Message f -> State f -> State f
+message !msg state@State{..} =
+  state { st_messages_rev = msg:st_messages_rev }
+
+-- | Forget about all emitted messages.
+clearMessages :: State f -> State f
+clearMessages state@State{..} =
+  state { st_messages_rev = [] }
+
+-- | Get all emitted messages.
+messages :: State f -> [Message f]
+messages state = reverse (st_messages_rev state)
+
+----------------------------------------------------------------------
+-- * The CP queue.
+----------------------------------------------------------------------
+
+data Params
+instance Queue.Params Params where
+  type Score Params = Int
+  type Id Params = RuleId
+  type PackedId Params = Int32
+  type PackedScore Params = Int32
+  packScore _ = fromIntegral
+  unpackScore _ = fromIntegral
+  packId _ = fromIntegral
+  unpackId _ = fromIntegral
+
+-- | Compute all critical pairs from a rule.
+{-# INLINEABLE makePassives #-}
+makePassives :: Function f => Config -> State f -> ActiveRule f -> [Passive Params]
+makePassives Config{..} State{..} rule =
+  {-# SCC makePassive #-}
+  [ Passive (fromIntegral (score cfg_critical_pairs o)) (rule_rid rule1) (rule_rid rule2) (fromIntegral (overlap_pos o))
+  | (rule1, rule2, o) <- overlaps (Depth cfg_max_cp_depth) (index_oriented st_rules) rules rule ]
+  where
+    rules = IntMap.elems st_rule_ids
+
+-- | Turn a Passive back into an overlap.
+-- Doesn't try to simplify it.
+{-# INLINEABLE findPassive #-}
+findPassive :: forall f. Function f => Config -> State f -> Passive Params -> Maybe (ActiveRule f, ActiveRule f, Overlap f)
+findPassive Config{..} State{..} Passive{..} = {-# SCC findPassive #-} do
+  rule1 <- IntMap.lookup (fromIntegral passive_rule1) st_rule_ids
+  rule2 <- IntMap.lookup (fromIntegral passive_rule2) st_rule_ids
+  let !depth = 1 + max (the rule1) (the rule2)
+  overlap <-
+    overlapAt (fromIntegral passive_pos) depth
+      (renameAvoiding (the rule2 :: Rule f) (the rule1)) (the rule2)
+  return (rule1, rule2, overlap)
+
+-- | Renormalise a queued Passive.
+{-# INLINEABLE simplifyPassive #-}
+simplifyPassive :: Function f => Config -> State f -> Passive Params -> Maybe (Passive Params)
+simplifyPassive config@Config{..} state@State{..} passive = {-# SCC simplifyPassive #-} do
+  (_, _, overlap) <- findPassive config state passive
+  overlap <- simplifyOverlap (index_oriented st_rules) overlap
+  return passive {
+    passive_score = fromIntegral $
+      fromIntegral (passive_score passive) `intMin`
+      score cfg_critical_pairs overlap }
+
+-- | Renormalise the entire queue.
+{-# INLINEABLE simplifyQueue #-}
+simplifyQueue :: Function f => Config -> State f -> State f
+simplifyQueue config state =
+  {-# SCC simplifyQueue #-}
+  state { st_queue = simp (st_queue state) }
+  where
+    simp =
+      Queue.mapMaybe (simplifyPassive config state)
+
+-- | Enqueue a set of critical pairs.
+{-# INLINEABLE enqueue #-}
+enqueue :: Function f => State f -> RuleId -> [Passive Params] -> State f
+enqueue state rule passives =
+  {-# SCC enqueue #-}
+  state { st_queue = Queue.insert rule passives (st_queue state) }
+
+-- | Dequeue a critical pair.
+--
+-- Also takes care of:
+--
+--   * removing any orphans from the head of the queue
+--   * ignoring CPs that are too big
+{-# INLINEABLE dequeue #-}
+dequeue :: Function f => Config -> State f -> (Maybe (CriticalPair f, ActiveRule f, ActiveRule f), State f)
+dequeue config@Config{..} state@State{..} =
+  {-# SCC dequeue #-}
+  case deq 0 st_queue of
+    -- Explicitly make the queue empty, in case it e.g. contained a
+    -- lot of orphans
+    Nothing -> (Nothing, state { st_queue = Queue.empty })
+    Just (overlap, n, queue) ->
+      (Just overlap,
+       state { st_queue = queue, st_considered = st_considered + n })
+  where
+    deq !n queue = do
+      (passive, queue) <- Queue.removeMin queue
+      case findPassive config state passive of
+        Just (rule1, rule2, overlap)
+          | passive_score passive >= 0,
+            Just Overlap{overlap_eqn = t :=: u} <-
+              simplifyOverlap (index_oriented st_rules) overlap,
+            size t <= cfg_max_term_size,
+            size u <= cfg_max_term_size,
+            Just cp <- makeCriticalPair rule1 rule2 overlap ->
+              return ((cp, rule1, rule2), n+1, queue)
+        _ -> deq (n+1) queue
+
+----------------------------------------------------------------------
+-- * Active rewrite rules.
+----------------------------------------------------------------------
+
+data Active f =
+  Active {
+    active_id    :: {-# UNPACK #-} !Id,
+    active_depth :: {-# UNPACK #-} !Depth,
+    active_rule  :: {-# UNPACK #-} !(Rule f),
+    active_top   :: !(Maybe (Term f)),
+    active_proof :: {-# UNPACK #-} !(Proof f),
+    -- A model in which the rule is false (used when reorienting)
+    active_model :: !(Model f),
+    active_rules :: ![ActiveRule f] }
+
+active_cp :: Active f -> CriticalPair f
+active_cp Active{..} =
+  CriticalPair {
+    cp_eqn = unorient active_rule,
+    cp_depth = active_depth,
+    cp_top = active_top,
+    cp_proof = derivation active_proof }
+
+-- An active oriented in a particular direction.
+data ActiveRule f =
+  ActiveRule {
+    rule_active    :: {-# UNPACK #-} !Id,
+    rule_rid       :: {-# UNPACK #-} !RuleId,
+    rule_depth     :: {-# UNPACK #-} !Depth,
+    rule_rule      :: {-# UNPACK #-} !(Rule f),
+    rule_proof     :: {-# UNPACK #-} !(Proof f),
+    rule_positions :: !(Positions f) }
+
+instance PrettyTerm f => Symbolic (ActiveRule f) where
+  type ConstantOf (ActiveRule f) = f
+  termsDL ActiveRule{..} =
+    termsDL rule_rule `mplus`
+    termsDL (derivation rule_proof)
+  subst_ sub r@ActiveRule{..} =
+    r {
+      rule_rule = rule',
+      rule_proof = certify (subst_ sub (derivation rule_proof)),
+      rule_positions = positions (lhs rule') }
+    where
+      rule' = subst_ sub rule_rule
+
+instance Eq (Active f) where
+  (==) = (==) `on` active_id
+
+instance Eq (ActiveRule f) where
+  (==) = (==) `on` rule_rid
+
+instance Function f => Pretty (Active f) where
+  pPrint Active{..} =
+    pPrint active_id <> text "." <+> pPrint (canonicalise active_rule)
+
+instance Has (ActiveRule f) Id where the = rule_active
+instance Has (ActiveRule f) RuleId where the = rule_rid
+instance Has (ActiveRule f) Depth where the = rule_depth
+instance f ~ g => Has (ActiveRule f) (Rule g) where the = rule_rule
+instance f ~ g => Has (ActiveRule f) (Proof g) where the = rule_proof
+instance f ~ g => Has (ActiveRule f) (Lemma g) where the x = Lemma (the x) (the x)
+instance f ~ g => Has (ActiveRule f) (Positions g) where the = rule_positions
+
+newtype RuleId = RuleId Id deriving (Eq, Ord, Show, Num, Real, Integral, Enum)
+
+-- Add a new active.
+{-# INLINEABLE addActive #-}
+addActive :: Function f => Config -> State f -> (Id -> RuleId -> RuleId -> Active f) -> State f
+addActive config state@State{..} active0 =
+  {-# SCC addActive #-}
+  let
+    active@Active{..} = active0 st_next_active st_next_rule (succ st_next_rule)
+    state' =
+      message (NewActive active) $
+      addActiveOnly state{st_next_active = st_next_active+1, st_next_rule = st_next_rule+2} active
+  in if subsumed st_joinable st_rules (unorient active_rule) then
+    state
+  else
+    normaliseGoals $
+    foldl' (uncurry . enqueue) state'
+      [ (the rule, makePassives config state' rule)
+      | rule <- active_rules ]
+
+-- Add an active without generating critical pairs. Used in interreduction.
+{-# INLINEABLE addActiveOnly #-}
+addActiveOnly :: Function f => State f -> Active f -> State f
+addActiveOnly state@State{..} active@Active{..} =
+  state {
+    st_rules = foldl' insertRule st_rules active_rules,
+    st_active_ids = IntMap.insert (fromIntegral active_id) active st_active_ids,
+    st_rule_ids = foldl' insertRuleId st_rule_ids active_rules }
+  where
+    insertRule rules rule@ActiveRule{..} =
+      RuleIndex.insert (lhs rule_rule) rule rules
+    insertRuleId rules rule@ActiveRule{..} =
+      IntMap.insert (fromIntegral rule_rid) rule rules
+
+-- Delete an active. Used in interreduction, not suitable for general use.
+{-# INLINE deleteActive #-}
+deleteActive :: Function f => State f -> Active f -> State f
+deleteActive state@State{..} Active{..} =
+  state {
+    st_rules = foldl' deleteRule st_rules active_rules,
+    st_active_ids = IntMap.delete (fromIntegral active_id) st_active_ids,
+    st_rule_ids = foldl' deleteRuleId st_rule_ids active_rules }
+  where
+    deleteRule rules rule =
+      RuleIndex.delete (lhs (rule_rule rule)) rule rules
+    deleteRuleId rules ActiveRule{..} =
+      IntMap.delete (fromIntegral rule_rid) rules
+
+-- Try to join a critical pair.
+{-# INLINEABLE consider #-}
+consider :: Function f => Config -> State f -> CriticalPair f -> State f
+consider config state cp =
+  considerUsing (st_rules state) config state cp
+
+-- Try to join a critical pair, but using a different set of critical
+-- pairs for normalisation.
+{-# INLINEABLE considerUsing #-}
+considerUsing ::
+  Function f =>
+  RuleIndex f (ActiveRule f) -> Config -> State f -> CriticalPair f -> State f
+considerUsing rules config@Config{..} state@State{..} cp0 =
+  {-# SCC consider #-}
+  -- Important to canonicalise the rule so that we don't get
+  -- bigger and bigger variable indices over time
+  let cp = canonicalise cp0 in
+  case joinCriticalPair cfg_join st_joinable rules Nothing cp of
+    Right (mcp, cps) ->
+      let
+        state' = foldl' (considerUsing rules config) state cps
+      in case mcp of
+        Just cp -> addJoinable state' (cp_eqn cp)
+        Nothing -> state'
+
+    Left (cp, model) ->
+      foldl' (addCP config model) state (split cp)
+
+{-# INLINEABLE addCP #-}
+addCP :: Function f => Config -> Model f -> State f -> CriticalPair f -> State f
+addCP config model state@State{..} CriticalPair{..} =
+  addActive config state $ \n k1 k2 ->
+  let
+    pf = certify cp_proof
+    rule = orient cp_eqn
+
+    makeRule k r p =
+      ActiveRule {
+        rule_active = n,
+        rule_rid = k,
+        rule_depth = cp_depth,
+        rule_rule = r rule,
+        rule_proof = p pf,
+        rule_positions = positions (lhs (r rule)) }
+  in
+  Active {
+    active_id = n,
+    active_depth = cp_depth,
+    active_rule = rule,
+    active_model = model,
+    active_top = cp_top,
+    active_proof = pf,
+    active_rules =
+      usortBy (comparing (canonicalise . rule_rule)) $
+        makeRule k1 id id:
+        [ makeRule k2 backwards (certify . symm . derivation)
+        | not (oriented (orientation rule)) ] }
+
+-- Add a new equation.
+{-# INLINEABLE addAxiom #-}
+addAxiom :: Function f => Config -> State f -> Axiom f -> State f
+addAxiom config state axiom =
+  consider config state $
+    CriticalPair {
+      cp_eqn = axiom_eqn axiom,
+      cp_depth = 0,
+      cp_top = Nothing,
+      cp_proof = Proof.axiom axiom }
+
+-- Record an equation as being joinable.
+{-# INLINEABLE addJoinable #-}
+addJoinable :: Function f => State f -> Equation f -> State f
+addJoinable state eqn@(t :=: u) =
+  message (NewEquation eqn) $
+  state {
+    st_joinable =
+      Index.insert t (t :=: u) $
+      Index.insert u (u :=: t) (st_joinable state) }
+
+-- For goal terms we store the set of all their normal forms.
+-- Name and number are for information only.
+data Goal f =
+  Goal {
+    goal_name   :: String,
+    goal_number :: Int,
+    goal_eqn    :: Equation f,
+    goal_lhs    :: Set (Resulting f),
+    goal_rhs    :: Set (Resulting f) }
+
+-- Add a new goal.
+{-# INLINEABLE addGoal #-}
+addGoal :: Function f => Config -> State f -> Goal f -> State f
+addGoal _config state@State{..} goal =
+  normaliseGoals state { st_goals = goal:st_goals }
+
+-- Normalise all goals.
+{-# INLINEABLE normaliseGoals #-}
+normaliseGoals :: Function f => State f -> State f
+normaliseGoals state@State{..} =
+  {-# SCC normaliseGoals #-}
+  state {
+    st_goals =
+      map (goalMap (successors (rewrite reduces (index_all st_rules)) . Set.toList)) st_goals }
+  where
+    goalMap f goal@Goal{..} =
+      goal { goal_lhs = f goal_lhs, goal_rhs = f goal_rhs }
+
+-- Create a goal.
+{-# INLINE goal #-}
+goal :: Int -> String -> Equation f -> Goal f
+goal n name (t :=: u) =
+  Goal {
+    goal_name = name,
+    goal_number = n,
+    goal_eqn = t :=: u,
+    goal_lhs = Set.singleton (reduce (Refl t)),
+    goal_rhs = Set.singleton (reduce (Refl u)) }
+
+----------------------------------------------------------------------
+-- Interreduction.
+----------------------------------------------------------------------
+
+-- Simplify all rules.
+{-# INLINEABLE interreduce #-}
+interreduce :: Function f => Config -> State f -> State f
+interreduce config@Config{..} state =
+  {-# SCC interreduce #-}
+  let
+    state' =
+      foldl' (interreduce1 config)
+        -- Clear out st_joinable, since we don't know which
+        -- equations have made use of each active.
+        state { st_joinable = Index.empty }
+        (IntMap.elems (st_active_ids state))
+    in state' { st_joinable = st_joinable state }
+
+{-# INLINEABLE interreduce1 #-}
+interreduce1 :: Function f => Config -> State f -> Active f -> State f
+interreduce1 config@Config{..} state active =
+  -- Exclude the active from the rewrite rules when testing
+  -- joinability, otherwise it will be trivially joinable.
+  case
+    joinCriticalPair cfg_join
+      (st_joinable state)
+      (st_rules (deleteActive state active))
+      (Just (active_model active)) (active_cp active)
+  of
+    Right (_, cps) ->
+      flip (foldl' (consider config)) cps $
+      message (DeleteActive active) $
+      deleteActive state active
+    Left (cp, model)
+      | not (cp_eqn cp `isInstanceOf` cp_eqn (active_cp active)) ->
+        flip (foldl' (addCP config model)) (split cp) $
+        message (DeleteActive active) $
+        deleteActive state active
+      | model /= active_model active ->
+        flip addActiveOnly active { active_model = model } $
+        deleteActive state active
+      | otherwise ->
+        state
+  where
+    (t :=: u) `isInstanceOf` (t' :=: u') = isJust $ do
+      sub <- match t' t
+      matchIn sub u' u
+
+
+----------------------------------------------------------------------
+-- The main loop.
+----------------------------------------------------------------------
+
+data Output m f =
+  Output {
+    output_message :: Message f -> m () }
+
+{-# INLINE complete #-}
+complete :: (Function f, MonadIO m) => Output m f -> Config -> State f -> m (State f)
+complete Output{..} config@Config{..} state =
+  flip StateM.execStateT state $ do
+    tasks <- sequence
+      [newTask 1 (fromIntegral cfg_renormalise_percent / 100) $ do
+         lift $ output_message SimplifyQueue
+         state <- StateM.get
+         StateM.put $! simplifyQueue config state,
+       newTask 0.25 0.05 $ do
+         when cfg_simplify $ do
+           lift $ output_message Interreduce
+           state <- StateM.get
+           StateM.put $! interreduce config state]
+
+    let
+      loop = do
+        progress <- StateM.state (complete1 config)
+        state <- StateM.get
+        lift $ mapM_ output_message (messages state)
+        StateM.put (clearMessages state)
+        mapM_ checkTask tasks
+        when progress loop
+
+    loop
+
+{-# INLINEABLE complete1 #-}
+complete1 :: Function f => Config -> State f -> (Bool, State f)
+complete1 config@Config{..} state
+  | st_considered state >= cfg_max_critical_pairs =
+    (False, state)
+  | solved state = (False, state)
+  | otherwise =
+    case dequeue config state of
+      (Nothing, state) -> (False, state)
+      (Just (overlap, _, _), state) ->
+        (True, consider config state overlap)
+
+{-# INLINEABLE solved #-}
+solved :: Function f => State f -> Bool
+solved = not . null . solutions
+
+-- Return whatever goals we have proved and their proofs.
+{-# INLINEABLE solutions #-}
+solutions :: Function f => State f -> [ProvedGoal f]
+solutions State{..} = {-# SCC solutions #-} do
+  Goal{goal_lhs = ts, goal_rhs = us, ..} <- st_goals
+  guard (not (null (Set.intersection ts us)))
+  let t:_ = filter (`Set.member` us) (Set.toList ts)
+      u:_ = filter (== t) (Set.toList us)
+      -- Strict so that we check the proof before returning a solution
+      !p =
+        Proof.certify $
+          reductionProof (reduction t) `Proof.trans`
+          Proof.symm (reductionProof (reduction u))
+  return (provedGoal goal_number goal_name p)
+
+-- Return all current rewrite rules.
+{-# INLINEABLE rules #-}
+rules :: Function f => State f -> [Rule f]
+rules = map active_rule . IntMap.elems . st_active_ids
+
+----------------------------------------------------------------------
+-- For code which uses twee as a library.
+----------------------------------------------------------------------
+
+{-# INLINEABLE completePure #-}
+completePure :: Function f => Config -> State f -> State f
+completePure cfg state
+  | progress = completePure cfg (clearMessages state')
+  | otherwise = state'
+  where
+    (progress, state') = complete1 cfg state
+
+{-# INLINEABLE normaliseTerm #-}
+normaliseTerm :: Function f => State f -> Term f -> Resulting f
+normaliseTerm State{..} t =
+  normaliseWith (const True) (rewrite reduces (index_all st_rules)) t
+
+{-# INLINEABLE normalForms #-}
+normalForms :: Function f => State f -> Term f -> Set (Resulting f)
+normalForms State{..} t =
+  Rule.normalForms (rewrite reduces (index_all st_rules)) [reduce (Refl t)]
+
+{-# INLINEABLE simplifyTerm #-}
+simplifyTerm :: Function f => State f -> Term f -> Term f
+simplifyTerm State{..} t =
+  simplify (index_oriented st_rules) t
diff --git a/Twee/Base.hs b/Twee/Base.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Base.hs
@@ -0,0 +1,285 @@
+-- | Useful operations on terms and similar. Also re-exports some generally
+-- useful modules such as 'Twee.Term' and 'Twee.Pretty'.
+
+{-# LANGUAGE TypeFamilies, FlexibleInstances, UndecidableInstances, DeriveFunctor, DefaultSignatures, FlexibleContexts, TypeOperators, MultiParamTypeClasses, GeneralizedNewtypeDeriving, ConstraintKinds, RecordWildCards #-}
+module Twee.Base(
+  -- * Re-exported functionality
+  module Twee.Term, module Twee.Pretty,
+  -- * The 'Symbolic' typeclass
+  Symbolic(..), subst, terms,
+  TermOf, TermListOf, SubstOf, TriangleSubstOf, BuilderOf, FunOf,
+  vars, isGround, funs, occ, occVar, canonicalise, renameAvoiding,
+  -- * General-purpose functionality
+  Id(..), Has(..),
+  -- * Typeclasses
+  Minimal(..), minimalTerm, isMinimal, erase,
+  Skolem(..), Arity(..), Sized(..), Ordered(..), lessThan, orientTerms, EqualsBonus(..), Strictness(..), Function, Extended(..)) where
+
+import Prelude hiding (lookup)
+import Control.Monad
+import qualified Data.DList as DList
+import Twee.Term hiding (subst, canonicalise)
+import qualified Twee.Term as Term
+import Twee.Pretty
+import Twee.Constraints hiding (funs)
+import Data.DList(DList)
+import Data.Typeable
+import Data.Int
+import Data.Maybe
+import qualified Data.IntMap.Strict as IntMap
+
+-- | Represents a unique identifier (e.g., for a rule).
+newtype Id = Id { unId :: Int32 }
+  deriving (Eq, Ord, Show, Enum, Bounded, Num, Real, Integral)
+
+instance Pretty Id where
+  pPrint = text . show . unId
+
+-- | Generalisation of term functionality to things that contain terms (e.g.,
+-- rewrite rules and equations).
+class Symbolic a where
+  type ConstantOf a
+
+  -- | Compute a 'DList' of all terms which appear in the argument
+  -- (used for e.g. computing free variables).
+  -- See also 'terms'.
+  termsDL :: a -> DList (TermListOf a)
+
+  -- | Apply a substitution.
+  -- When using the 'Symbolic' type class, you can use 'subst' instead.
+  subst_ :: (Var -> BuilderOf a) -> a -> a
+
+-- | Apply a substitution.
+subst :: (Symbolic a, Substitution s, SubstFun s ~ ConstantOf a) => s -> a -> a
+subst sub x = subst_ (evalSubst sub) x
+
+-- | Find all terms occuring in the argument.
+terms :: Symbolic a => a -> [TermListOf a]
+terms = DList.toList . termsDL
+
+-- | A term compatible with a given 'Symbolic'.
+type TermOf a = Term (ConstantOf a)
+-- | A termlist compatible with a given 'Symbolic'.
+type TermListOf a = TermList (ConstantOf a)
+-- | A substitution compatible with a given 'Symbolic'.
+type SubstOf a = Subst (ConstantOf a)
+-- | A triangle substitution compatible with a given 'Symbolic'.
+type TriangleSubstOf a = TriangleSubst (ConstantOf a)
+-- | A builder compatible with a given 'Symbolic'.
+type BuilderOf a = Builder (ConstantOf a)
+-- | The underlying type of function symbols of a given 'Symbolic'.
+type FunOf a = Fun (ConstantOf a)
+
+instance Symbolic (Term f) where
+  type ConstantOf (Term f) = f
+  termsDL = return . singleton
+  subst_ sub = build . Term.subst sub
+
+instance Symbolic (TermList f) where
+  type ConstantOf (TermList f) = f
+  termsDL = return
+  subst_ sub = buildList . Term.substList sub
+
+instance Symbolic (Subst f) where
+  type ConstantOf (Subst f) = f
+  termsDL (Subst sub) = termsDL (IntMap.elems sub)
+  subst_ sub (Subst s) = Subst (fmap (subst_ sub) s)
+
+instance (ConstantOf a ~ ConstantOf b, Symbolic a, Symbolic b) => Symbolic (a, b) where
+  type ConstantOf (a, b) = ConstantOf a
+  termsDL (x, y) = termsDL x `mplus` termsDL y
+  subst_ sub (x, y) = (subst_ sub x, subst_ sub y)
+
+instance (ConstantOf a ~ ConstantOf b,
+          ConstantOf a ~ ConstantOf c,
+          Symbolic a, Symbolic b, Symbolic c) => Symbolic (a, b, c) where
+  type ConstantOf (a, b, c) = ConstantOf a
+  termsDL (x, y, z) = termsDL x `mplus` termsDL y `mplus` termsDL z
+  subst_ sub (x, y, z) = (subst_ sub x, subst_ sub y, subst_ sub z)
+
+instance Symbolic a => Symbolic [a] where
+  type ConstantOf [a] = ConstantOf a
+  termsDL xs = msum (map termsDL xs)
+  subst_ sub xs = map (subst_ sub) xs
+
+instance Symbolic a => Symbolic (Maybe a) where
+  type ConstantOf (Maybe a) = ConstantOf a
+  termsDL Nothing = mzero
+  termsDL (Just x) = termsDL x
+  subst_ sub x = fmap (subst_ sub) x
+
+-- | An instance @'Has' a b@ indicates that a value of type @a@ contains a value
+-- of type @b@ which is somehow part of the meaning of the @a@.
+--
+-- A number of functions use 'Has' constraints to work in a more general setting.
+-- For example, the functions in 'Twee.CP' operate on rewrite rules, but actually
+-- accept any @a@ satisfying @'Has' a ('Twee.Rule.Rule' f)@.
+--
+-- Use taste when definining 'Has' instances; don't do it willy-nilly.
+class Has a b where
+  -- | Get at the thing.
+  the :: a -> b
+
+instance Has a a where
+  the = id
+
+-- | Find the variables occurring in the argument.
+{-# INLINE vars #-}
+vars :: Symbolic a => a -> [Var]
+vars x = [ v | t <- DList.toList (termsDL x), Var v <- subtermsList t ]
+
+-- | Test if the argument is ground.
+{-# INLINE isGround #-}
+isGround :: Symbolic a => a -> Bool
+isGround = null . vars
+
+-- | Find the function symbols occurring in the argument.
+{-# INLINE funs #-}
+funs :: Symbolic a => a -> [FunOf a]
+funs x = [ f | t <- DList.toList (termsDL x), App f _ <- subtermsList t ]
+
+-- | Count how many times a function symbol occurs in the argument.
+{-# INLINE occ #-}
+occ :: Symbolic a => FunOf a -> a -> Int
+occ x t = length (filter (== x) (funs t))
+
+-- | Count how many times a variable occurs in the argument.
+{-# INLINE occVar #-}
+occVar :: Symbolic a => Var -> a -> Int
+occVar x t = length (filter (== x) (vars t))
+
+-- | Rename the argument so that variables are introduced in a canonical order
+-- (starting with V0, then V1 and so on).
+{-# INLINEABLE canonicalise #-}
+canonicalise :: Symbolic a => a -> a
+canonicalise t = subst sub t
+  where
+    sub = Term.canonicalise (DList.toList (termsDL t))
+
+-- | Rename the second argument so that it does not mention any variable which
+-- occurs in the first.
+{-# INLINEABLE renameAvoiding #-}
+renameAvoiding :: (Symbolic a, Symbolic b) => a -> b -> b
+renameAvoiding x y
+  | x2 < y1 || y2 < x1 =
+    -- No overlap. Important in the case when x is ground,
+    -- in which case x2 == minBound and the calculation below doesn't work.
+    y
+  | otherwise =
+    -- Map y1 to x2+1
+    subst (\(V x) -> var (V (x-y1+x2+1))) y
+  where
+    (V x1, V x2) = boundLists (terms x)
+    (V y1, V y2) = boundLists (terms y)
+
+-- | Check if a term is the minimal constant.
+isMinimal :: Minimal f => Term f -> Bool
+isMinimal (App f Empty) | f == minimal = True
+isMinimal _ = False
+
+-- | Build the minimal constant as a term.
+minimalTerm :: Minimal f => Term f
+minimalTerm = build (con minimal)
+
+-- | Erase a given set of variables from the argument, replacing them with the
+-- minimal constant.
+erase :: (Symbolic a, ConstantOf a ~ f, Minimal f) => [Var] -> a -> a
+erase [] t = t
+erase xs t = subst sub t
+  where
+    sub = fromMaybe undefined $ listToSubst [(x, minimalTerm) | x <- xs]
+
+-- | Construction of Skolem constants.
+class Skolem f where
+  -- | Turn a variable into a Skolem constant.
+  skolem  :: Var -> Fun f
+
+-- | For types which have a notion of arity.
+class Arity f where
+  -- | Measure the arity.
+  arity :: f -> Int
+
+instance Arity f => Arity (Fun f) where
+  arity = arity . fun_value
+
+-- | For types which have a notion of size.
+class Sized a where
+  -- | Compute the size.
+  size  :: a -> Int
+
+instance Sized f => Sized (Fun f) where
+  size = size . fun_value
+
+instance Sized f => Sized (TermList f) where
+  size = aux 0
+    where
+      aux n Empty = n
+      aux n (ConsSym (App f _) t) = aux (n+size f) t
+      aux n (Cons (Var _) t) = aux (n+1) t
+
+instance Sized f => Sized (Term f) where
+  size = size . singleton
+
+-- | The collection of constraints which the type of function symbols must
+-- satisfy in order to be used by twee.
+type Function f = (Ordered f, Arity f, Sized f, Minimal f, Skolem f, PrettyTerm f, EqualsBonus f)
+
+-- | A hack for encoding Horn clauses. See 'Twee.CP.Score'.
+-- The default implementation of 'hasEqualsBonus' should work OK.
+class EqualsBonus f where
+  hasEqualsBonus :: f -> Bool
+  hasEqualsBonus _ = False
+  isEquals, isTrue, isFalse :: f -> Bool
+  isEquals _ = False
+  isTrue _ = False
+  isFalse _ = False
+
+instance EqualsBonus f => EqualsBonus (Fun f) where
+  hasEqualsBonus = hasEqualsBonus . fun_value
+  isEquals = isEquals . fun_value
+  isTrue = isTrue . fun_value
+  isFalse = isFalse . fun_value
+
+-- | A function symbol extended with a minimal constant and Skolem functions.
+-- Comes equipped with 'Minimal' and 'Skolem' instances.
+data Extended f =
+    -- | The minimal constant.
+    Minimal
+    -- | A Skolem function.
+  | Skolem Var
+    -- | An ordinary function symbol.
+  | Function f
+  deriving (Eq, Ord, Show, Functor)
+
+instance Pretty f => Pretty (Extended f) where
+  pPrintPrec _ _ Minimal = text "?"
+  pPrintPrec _ _ (Skolem (V n)) = text "sk" <> pPrint n
+  pPrintPrec l p (Function f) = pPrintPrec l p f
+
+instance PrettyTerm f => PrettyTerm (Extended f) where
+  termStyle (Function f) = termStyle f
+  termStyle _ = uncurried
+
+instance Sized f => Sized (Extended f) where
+  size (Function f) = size f
+  size _ = 1
+
+instance Arity f => Arity (Extended f) where
+  arity (Function f) = arity f
+  arity _ = 0
+
+instance (Typeable f, Ord f) => Minimal (Extended f) where
+  minimal = fun Minimal
+
+instance (Typeable f, Ord f) => Skolem (Extended f) where
+  skolem x = fun (Skolem x)
+
+instance EqualsBonus f => EqualsBonus (Extended f) where
+  hasEqualsBonus (Function f) = hasEqualsBonus f
+  hasEqualsBonus _ = False
+  isEquals (Function f) = isEquals f
+  isEquals _ = False
+  isTrue (Function f) = isTrue f
+  isTrue _ = False
+  isFalse (Function f) = isFalse f
+  isFalse _ = False
diff --git a/Twee/CP.hs b/Twee/CP.hs
new file mode 100644
--- /dev/null
+++ b/Twee/CP.hs
@@ -0,0 +1,330 @@
+-- | Critical pair generation.
+{-# LANGUAGE BangPatterns, FlexibleContexts, ScopedTypeVariables, MultiParamTypeClasses, RecordWildCards, OverloadedStrings, TypeFamilies, GeneralizedNewtypeDeriving #-}
+module Twee.CP where
+
+import qualified Twee.Term as Term
+import Twee.Base
+import Twee.Rule
+import Twee.Index(Index)
+import qualified Data.Set as Set
+import Control.Monad
+import Data.Maybe
+import Data.List
+import qualified Data.ChurchList as ChurchList
+import Data.ChurchList (ChurchList(..))
+import Twee.Utils
+import Twee.Equation
+import qualified Twee.Proof as Proof
+import Twee.Proof(Derivation, Lemma, congPath)
+
+-- | The set of positions at which a term can have critical overlaps.
+data Positions f = NilP | ConsP {-# UNPACK #-} !Int !(Positions f)
+type PositionsOf a = Positions (ConstantOf a)
+
+instance Show (Positions f) where
+  show = show . ChurchList.toList . positionsChurch
+
+-- | Calculate the set of positions for a term.
+positions :: Term f -> Positions f
+positions t = aux 0 Set.empty (singleton t)
+  where
+    -- Consider only general superpositions.
+    aux !_ !_ Empty = NilP
+    aux n m (Cons (Var _) t) = aux (n+1) m t
+    aux n m (ConsSym t@App{} u)
+      | t `Set.member` m = aux (n+1) m u
+      | otherwise = ConsP n (aux (n+1) (Set.insert t m) u)
+
+{-# INLINE positionsChurch #-}
+positionsChurch :: Positions f -> ChurchList Int
+positionsChurch posns =
+  ChurchList $ \c n ->
+    let
+      pos NilP = n
+      pos (ConsP x posns) = c x (pos posns)
+    in
+      pos posns
+
+-- | A critical overlap of one rule with another.
+data Overlap f =
+  Overlap {
+    -- | The depth (1 for CPs of axioms, 2 for CPs whose rules have depth 1, etc.)
+    overlap_depth :: {-# UNPACK #-} !Depth,
+    -- | The critical term.
+    overlap_top   :: {-# UNPACK #-} !(Term f),
+    -- | The part of the critical term which the inner rule rewrites.
+    overlap_inner :: {-# UNPACK #-} !(Term f),
+    -- | The position in the critical term which is rewritten.
+    overlap_pos   :: {-# UNPACK #-} !Int,
+    -- | The critical pair itself.
+    overlap_eqn   :: {-# UNPACK #-} !(Equation f) }
+  deriving Show
+type OverlapOf a = Overlap (ConstantOf a)
+
+-- | Represents the depth of a critical pair.
+newtype Depth = Depth Int deriving (Eq, Ord, Num, Real, Enum, Integral, Show)
+
+-- | Compute all overlaps of a rule with a set of rules.
+{-# INLINEABLE overlaps #-}
+overlaps ::
+  (Function f, Has a (Rule f), Has a (Positions f), Has a Depth) =>
+  Depth -> Index f a -> [a] -> a -> [(a, a, Overlap f)]
+overlaps max_depth idx rules r =
+  ChurchList.toList (overlapsChurch max_depth idx rules r)
+
+{-# INLINE overlapsChurch #-}
+overlapsChurch :: forall f a.
+  (Function f, Has a (Rule f), Has a (Positions f), Has a Depth) =>
+  Depth -> Index f a -> [a] -> a -> ChurchList (a, a, Overlap f)
+overlapsChurch max_depth idx rules r1 = do
+  guard (the r1 < max_depth)
+  r2 <- ChurchList.fromList rules
+  guard (the r2 < max_depth)
+  let !depth = 1 + max (the r1) (the r2)
+  do { o <- asymmetricOverlaps idx depth (the r1) r1' (the r2); return (r1, r2, o) } `mplus`
+    do { o <- asymmetricOverlaps idx depth (the r2) (the r2) r1'; return (r2, r1, o) }
+  where
+    !r1' = renameAvoiding (map the rules :: [Rule f]) (the r1)
+
+{-# INLINE asymmetricOverlaps #-}
+asymmetricOverlaps ::
+  (Function f, Has a (Rule f), Has a Depth) =>
+  Index f a -> Depth -> Positions f -> Rule f -> Rule f -> ChurchList (Overlap f)
+asymmetricOverlaps idx depth posns r1 r2 = do
+  n <- positionsChurch posns
+  ChurchList.fromMaybe $
+    overlapAt n depth r1 r2 >>=
+    simplifyOverlap idx
+
+-- | Create an overlap at a particular position in a term.
+-- Doesn't simplify the overlap.
+{-# INLINE overlapAt #-}
+overlapAt :: Int -> Depth -> Rule f -> Rule f -> Maybe (Overlap f)
+overlapAt !n !depth (Rule _ !outer !outer') (Rule _ !inner !inner') = do
+  let t = at n (singleton outer)
+  sub <- unifyTri inner t
+  let
+    top = {-# SCC overlap_top #-} termSubst sub outer
+    innerTerm = {-# SCC overlap_inner #-} termSubst sub inner
+    -- Make sure to keep in sync with overlapProof
+    lhs = {-# SCC overlap_eqn_1 #-} termSubst sub outer'
+    rhs = {-# SCC overlap_eqn_2 #-}
+      buildReplacePositionSub sub n (singleton inner') (singleton outer)
+
+  guard (lhs /= rhs)
+  return Overlap {
+    overlap_depth = depth,
+    overlap_top = top,
+    overlap_inner = innerTerm,
+    overlap_pos = n,
+    overlap_eqn = lhs :=: rhs }
+
+-- | Simplify an overlap and remove it if it's trivial.
+{-# INLINE simplifyOverlap #-}
+simplifyOverlap :: (Function f, Has a (Rule f)) => Index f a -> Overlap f -> Maybe (Overlap f)
+simplifyOverlap idx overlap@Overlap{overlap_eqn = lhs :=: rhs, ..}
+  | lhs == rhs'  = Nothing
+  | lhs' == rhs' = Nothing
+  | otherwise = Just overlap{overlap_eqn = lhs' :=: rhs'}
+  where
+    lhs' = simplify idx lhs
+    rhs' = simplify idx rhs
+
+-- Put these in separate functions to avoid code blowup
+buildReplacePositionSub :: TriangleSubst f -> Int -> TermList f -> TermList f -> Term f
+buildReplacePositionSub !sub !n !inner' !outer =
+  build (replacePositionSub sub n inner' outer)
+
+termSubst :: TriangleSubst f -> Term f -> Term f
+termSubst sub t = build (Term.subst sub t)
+
+-- | The configuration for the critical pair weighting heuristic.
+data Config =
+  Config {
+    cfg_lhsweight :: !Int,
+    cfg_rhsweight :: !Int,
+    cfg_funweight :: !Int,
+    cfg_varweight :: !Int,
+    cfg_depthweight :: !Int,
+    cfg_dupcost :: !Int,
+    cfg_dupfactor :: !Int }
+
+-- | The default heuristic configuration.
+defaultConfig :: Config
+defaultConfig =
+  Config {
+    cfg_lhsweight = 3,
+    cfg_rhsweight = 1,
+    cfg_funweight = 7,
+    cfg_varweight = 6,
+    cfg_depthweight = 16,
+    cfg_dupcost = 7,
+    cfg_dupfactor = 0 }
+
+-- | Compute a score for a critical pair.
+
+-- We compute:
+--   cfg_lhsweight * size l + cfg_rhsweight * size r
+-- where l is the biggest term and r is the smallest,
+-- and variables have weight 1 and functions have weight cfg_funweight.
+{-# INLINEABLE score #-}
+score :: Function f => Config -> Overlap f -> Int
+score Config{..} Overlap{..} =
+  fromIntegral overlap_depth * cfg_depthweight +
+  (m + n) * cfg_rhsweight +
+  intMax m n * (cfg_lhsweight - cfg_rhsweight)
+  where
+    l :=: r = overlap_eqn
+    m = size' 0 (singleton l)
+    n = size' 0 (singleton r)
+
+    size' !n Empty = n
+    size' n (Cons t ts)
+      | len t > 1, t `isSubtermOfList` ts =
+        size' (n+cfg_dupcost+cfg_dupfactor*size t) ts
+    size' n ts
+      | Cons (App f ws@(Cons a (Cons b us))) vs <- ts,
+        hasEqualsBonus (fun_value f),
+        Just sub <- unify a b =
+        size' (n+cfg_funweight*size f) ws `min`
+        size' (size' (n+1) (subst sub us)) (subst sub vs)
+    size' n (Cons (Var _) ts) =
+      size' (n+cfg_varweight) ts
+    size' n (ConsSym (App f _) ts) =
+      size' (n+cfg_funweight*size f) ts
+
+----------------------------------------------------------------------
+-- * Higher-level handling of critical pairs.
+----------------------------------------------------------------------
+
+-- | A critical pair together with information about how it was derived
+data CriticalPair f =
+  CriticalPair {
+    -- | The critical pair itself.
+    cp_eqn   :: {-# UNPACK #-} !(Equation f),
+    -- | The depth of the critical pair.
+    cp_depth :: {-# UNPACK #-} !Depth,
+    -- | The critical term, if there is one.
+    -- (Axioms do not have a critical term.)
+    cp_top   :: !(Maybe (Term f)),
+    -- | A derivation of the critical pair from the axioms.
+    cp_proof :: !(Derivation f) }
+
+instance Symbolic (CriticalPair f) where
+  type ConstantOf (CriticalPair f) = f
+  termsDL CriticalPair{..} =
+    termsDL cp_eqn `mplus` termsDL cp_top `mplus` termsDL cp_proof
+  subst_ sub CriticalPair{..} =
+    CriticalPair {
+      cp_eqn = subst_ sub cp_eqn,
+      cp_depth = cp_depth,
+      cp_top = subst_ sub cp_top,
+      cp_proof = subst_ sub cp_proof }
+
+instance PrettyTerm f => Pretty (CriticalPair f) where
+  pPrint CriticalPair{..} =
+    vcat [
+      pPrint cp_eqn,
+      nest 2 (text "top:" <+> pPrint cp_top) ]
+
+-- | Split a critical pair so that it can be turned into rules.
+--
+-- The resulting critical pairs have the property that no variable appears on
+-- the right that is not on the left.
+
+-- See the comment below.
+split :: Function f => CriticalPair f -> [CriticalPair f]
+split CriticalPair{cp_eqn = l :=: r, ..}
+  | l == r = []
+  | otherwise =
+    -- If we have something which is almost a rule, except that some
+    -- variables appear only on the right-hand side, e.g.:
+    --   f x y -> g x z
+    -- then we replace it with the following two rules:
+    --   f x y -> g x ?
+    --   g x z -> g x ?
+    -- where the second rule is weakly oriented and ? is the minimal
+    -- constant.
+    --
+    -- If we have an unoriented equation with a similar problem, e.g.:
+    --   f x y = g x z
+    -- then we replace it with potentially three rules:
+    --   f x ? = g x ?
+    --   f x y -> f x ?
+    --   g x z -> g x ?
+
+    -- The main rule l -> r' or r -> l' or l' = r'
+    [ CriticalPair {
+        cp_eqn   = l :=: r',
+        cp_depth = cp_depth,
+        cp_top   = eraseExcept (vars l) cp_top,
+        cp_proof = eraseExcept (vars l) cp_proof }
+    | ord == Just GT ] ++
+    [ CriticalPair {
+        cp_eqn   = r :=: l',
+        cp_depth = cp_depth,
+        cp_top   = eraseExcept (vars r) cp_top,
+        cp_proof = Proof.symm (eraseExcept (vars r) cp_proof) }
+    | ord == Just LT ] ++
+    [ CriticalPair {
+        cp_eqn   = l' :=: r',
+        cp_depth = cp_depth,
+        cp_top   = eraseExcept (vars l) $ eraseExcept (vars r) cp_top,
+        cp_proof = eraseExcept (vars l) $ eraseExcept (vars r) cp_proof }
+    | ord == Nothing ] ++
+
+    -- Weak rules l -> l' or r -> r'
+    [ CriticalPair {
+        cp_eqn   = l :=: l',
+        cp_depth = cp_depth + 1,
+        cp_top   = Nothing,
+        cp_proof = cp_proof `Proof.trans` Proof.symm (erase ls cp_proof) }
+    | not (null ls), ord /= Just GT ] ++
+    [ CriticalPair {
+        cp_eqn   = r :=: r',
+        cp_depth = cp_depth + 1,
+        cp_top   = Nothing,
+        cp_proof = Proof.symm cp_proof `Proof.trans` erase rs cp_proof }
+    | not (null rs), ord /= Just LT ]
+    where
+      ord = orientTerms l' r'
+      l' = erase ls l
+      r' = erase rs r
+      ls = usort (vars l) \\ usort (vars r)
+      rs = usort (vars r) \\ usort (vars l)
+
+      eraseExcept vs t =
+        erase (usort (vars t) \\ usort vs) t
+
+-- | Make a critical pair from two rules and an overlap.
+{-# INLINEABLE makeCriticalPair #-}
+makeCriticalPair ::
+  (Has a (Rule f), Has a (Lemma f), Has a Id, Function f) =>
+  a -> a -> Overlap f -> Maybe (CriticalPair f)
+makeCriticalPair r1 r2 overlap@Overlap{..}
+  | lessEq overlap_top t = Nothing
+  | lessEq overlap_top u = Nothing
+  | otherwise =
+    Just $
+      CriticalPair overlap_eqn
+        overlap_depth
+        (Just overlap_top)
+        (overlapProof r1 r2 overlap)
+  where
+    t :=: u = overlap_eqn
+
+-- | Return a proof for a critical pair.
+{-# INLINEABLE overlapProof #-}
+overlapProof ::
+  forall a f.
+  (Has a (Rule f), Has a (Lemma f), Has a Id) =>
+  a -> a -> Overlap f -> Derivation f
+overlapProof left right Overlap{..} =
+  Proof.symm (reductionProof (step left leftSub))
+  `Proof.trans`
+  congPath path overlap_top (reductionProof (step right rightSub))
+  where
+    Just leftSub = match (lhs (the left)) overlap_top
+    Just rightSub = match (lhs (the right)) overlap_inner
+
+    path = positionToPath (lhs (the left) :: Term f) overlap_pos
diff --git a/Twee/Constraints.hs b/Twee/Constraints.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Constraints.hs
@@ -0,0 +1,312 @@
+{-# LANGUAGE FlexibleContexts, UndecidableInstances, RecordWildCards #-}
+-- | Solving constraints on variable ordering.
+module Twee.Constraints where
+
+--import Twee.Base hiding (equals, Term, pattern Fun, pattern Var, lookup, funs)
+import qualified Twee.Term as Flat
+import qualified Data.Map.Strict as Map
+import Twee.Pretty hiding (equals)
+import Twee.Utils
+import Data.Maybe
+import Data.List
+import Data.Function
+import Data.Graph
+import Data.Map.Strict(Map)
+import Data.Ord
+import Twee.Term hiding (lookup)
+
+data Atom f = Constant (Fun f) | Variable Var deriving (Show, Eq, Ord)
+
+{-# INLINE atoms #-}
+atoms :: Term f -> [Atom f]
+atoms t = aux (singleton t)
+  where
+    aux Empty = []
+    aux (Cons (App f Empty) t) = Constant f:aux t
+    aux (Cons (Var x) t) = Variable x:aux t
+    aux (ConsSym _ t) = aux t
+
+toTerm :: Atom f -> Term f
+toTerm (Constant f) = build (con f)
+toTerm (Variable x) = build (var x)
+
+fromTerm :: Flat.Term f -> Maybe (Atom f)
+fromTerm (App f Empty) = Just (Constant f)
+fromTerm (Var x) = Just (Variable x)
+fromTerm _ = Nothing
+
+instance PrettyTerm f => Pretty (Atom f) where
+  pPrint = pPrint . toTerm
+
+data Formula f =
+    Less   (Atom f) (Atom f)
+  | LessEq (Atom f) (Atom f)
+  | And [Formula f]
+  | Or  [Formula f]
+  deriving (Eq, Ord, Show)
+
+instance PrettyTerm f => Pretty (Formula f) where
+  pPrintPrec _ _ (Less t u) = hang (pPrint t <+> text "<") 2 (pPrint u)
+  pPrintPrec _ _ (LessEq t u) = hang (pPrint t <+> text "<=") 2 (pPrint u)
+  pPrintPrec _ _ (And []) = text "true"
+  pPrintPrec _ _ (Or []) = text "false"
+  pPrintPrec l p (And xs) =
+    maybeParens (p > 10)
+      (fsep (punctuate (text " &") (nest_ (map (pPrintPrec l 11) xs))))
+    where
+      nest_ (x:xs) = x:map (nest 2) xs
+      nest_ [] = undefined
+  pPrintPrec l p (Or xs) =
+    maybeParens (p > 10)
+      (fsep (punctuate (text " |") (nest_ (map (pPrintPrec l 11) xs))))
+    where
+      nest_ (x:xs) = x:map (nest 2) xs
+      nest_ [] = undefined
+
+negateFormula :: Formula f -> Formula f
+negateFormula (Less t u) = LessEq u t
+negateFormula (LessEq t u) = Less u t
+negateFormula (And ts) = Or (map negateFormula ts)
+negateFormula (Or ts) = And (map negateFormula ts)
+
+conj forms
+  | false `elem` forms' = false
+  | otherwise =
+    case forms' of
+      [x] -> x
+      xs  -> And xs
+  where
+    flatten (And xs) = xs
+    flatten x = [x]
+    forms' = filter (/= true) (usort (concatMap flatten forms))
+disj forms
+  | true `elem` forms' = true
+  | otherwise =
+    case forms' of
+      [x] -> x
+      xs  -> Or xs
+  where
+    flatten (Or xs) = xs
+    flatten x = [x]
+    forms' = filter (/= false) (usort (concatMap flatten forms))
+
+x &&& y = conj [x, y]
+x ||| y = disj [x, y]
+true  = And []
+false = Or []
+
+data Branch f =
+  -- Branches are kept normalised wrt equals
+  Branch {
+    funs        :: [Fun f],
+    less        :: [(Atom f, Atom f)],  -- sorted
+    equals      :: [(Atom f, Atom f)] } -- sorted, greatest atom first in each pair
+  deriving (Eq, Ord)
+
+instance PrettyTerm f => Pretty (Branch f) where
+  pPrint Branch{..} =
+    braces $ fsep $ punctuate (text ",") $
+      [pPrint x <+> text "<" <+> pPrint y | (x, y) <- less ] ++
+      [pPrint x <+> text "=" <+> pPrint y | (x, y) <- equals ]
+
+trueBranch :: Branch f
+trueBranch = Branch [] [] []
+
+norm :: Eq f => Branch f -> Atom f -> Atom f
+norm Branch{..} x = fromMaybe x (lookup x equals)
+
+contradictory :: (Minimal f, Ord f) => Branch f -> Bool
+contradictory Branch{..} =
+  or [f == minimal | (_, Constant f) <- less] ||
+  or [f /= g | (Constant f, Constant g) <- equals] ||
+  any cyclic (stronglyConnComp
+    [(x, x, [y | (x', y) <- less, x == x']) | x <- usort (map fst less)])
+  where
+    cyclic (AcyclicSCC _) = False
+    cyclic (CyclicSCC _) = True
+
+formAnd :: (Minimal f, Ordered f) => Formula f -> [Branch f] -> [Branch f]
+formAnd f bs = usort (bs >>= add f)
+  where
+    add (Less t u) b = addLess t u b
+    add (LessEq t u) b = addLess t u b ++ addEquals t u b
+    add (And []) b = [b]
+    add (And (f:fs)) b = add f b >>= add (And fs)
+    add (Or fs) b = usort (concat [ add f b | f <- fs ])
+
+branches :: (Minimal f, Ordered f) => Formula f -> [Branch f]
+branches x = aux [x]
+  where
+    aux [] = [Branch [] [] []]
+    aux (And xs:ys) = aux (xs ++ ys)
+    aux (Or xs:ys) = usort $ concat [aux (x:ys) | x <- xs]
+    aux (Less t u:xs) = usort $ concatMap (addLess t u) (aux xs)
+    aux (LessEq t u:xs) =
+      usort $
+      concatMap (addLess t u) (aux xs) ++
+      concatMap (addEquals u t) (aux xs)
+
+addLess :: (Minimal f, Ordered f) => Atom f -> Atom f -> Branch f -> [Branch f]
+addLess _ (Constant min) _ | min == minimal = []
+addLess (Constant min) _ b | min == minimal = [b]
+addLess t0 u0 b@Branch{..} =
+  filter (not . contradictory)
+    [addTerm t (addTerm u b{less = usort ((t, u):less)})]
+  where
+    t = norm b t0
+    u = norm b u0
+
+addEquals :: (Minimal f, Ordered f) => Atom f -> Atom f -> Branch f -> [Branch f]
+addEquals t0 u0 b@Branch{..}
+  | t == u || (t, u) `elem` equals = [b]
+  | otherwise =
+    filter (not . contradictory)
+      [addTerm t (addTerm u b {
+         equals      = usort $ (t, u):[(x', y') | (x, y) <- equals, let (y', x') = sort2 (sub x, sub y), x' /= y'],
+         less        = usort $ [(sub x, sub y) | (x, y) <- less] })]
+  where
+    sort2 (x, y) = (min x y, max x y)
+    (u, t) = sort2 (norm b t0, norm b u0)
+
+    sub x
+      | x == t = u
+      | otherwise = x
+
+addTerm :: (Minimal f, Ordered f) => Atom f -> Branch f -> Branch f
+addTerm (Constant f) b
+  | f `notElem` funs b =
+    b {
+      funs = f:funs b,
+      less =
+        usort $
+          [ (Constant f, Constant g) | g <- funs b, f << g ] ++
+          [ (Constant g, Constant f) | g <- funs b, g << f ] ++ less b }
+addTerm _ b = b
+
+newtype Model f = Model (Map (Atom f) (Int, Int))
+  deriving (Eq, Show)
+-- Representation: map from atom to (major, minor)
+-- x <  y if major x < major y
+-- x <= y if major x = major y and minor x < minor y
+
+instance PrettyTerm f => Pretty (Model f) where
+  pPrint (Model m)
+    | Map.size m <= 1 = text "empty"
+    | otherwise = fsep (go (sortBy (comparing snd) (Map.toList m)))
+      where
+        go [(x, _)] = [pPrint x]
+        go ((x, (i, _)):xs@((_, (j, _)):_)) =
+          (pPrint x <+> text rel):go xs
+          where
+            rel = if i == j then "<=" else "<"
+
+modelToLiterals :: Model f -> [Formula f]
+modelToLiterals (Model m) = go (sortBy (comparing snd) (Map.toList m))
+  where
+    go []  = []
+    go [_] = []
+    go ((x, (i, _)):xs@((y, (j, _)):_)) =
+      rel x y:go xs
+      where
+        rel = if i == j then LessEq else Less
+
+modelFromOrder :: (Minimal f, Ord f) => [Atom f] -> Model f
+modelFromOrder xs =
+  Model (Map.fromList [(x, (i, i)) | (x, i) <- zip xs [0..]])
+
+weakenModel :: Model f -> [Model f]
+weakenModel (Model m) =
+  [ Model (Map.delete x m) | x <- Map.keys m ] ++
+  [ Model (Map.fromList xs)
+  | xs <- glue (sortBy (comparing snd) (Map.toList m)),
+    all ok (groupBy ((==) `on` (fst . snd)) xs) ]
+  where
+    glue [] = []
+    glue [_] = []
+    glue (a@(_x, (i1, j1)):b@(y, (i2, _)):xs) =
+      [ (a:(y, (i1, j1+1)):xs) | i1 < i2 ] ++
+      map (a:) (glue (b:xs))
+
+    -- We must never make two constants equal
+    ok xs = length [x | (Constant x, _) <- xs] <= 1
+
+varInModel :: (Minimal f, Ord f) => Model f -> Var -> Bool
+varInModel (Model m) x = Variable x `Map.member` m
+
+varGroups :: (Minimal f, Ord f) => Model f -> [(Fun f, [Var], Maybe (Fun f))]
+varGroups (Model m) = filter nonempty (go minimal (map fst (sortBy (comparing snd) (Map.toList m))))
+  where
+    go f xs =
+      case span isVariable xs of
+        (_, []) -> [(f, map unVariable xs, Nothing)]
+        (ys, Constant g:zs) ->
+          (f, map unVariable ys, Just g):go g zs
+    isVariable (Constant _) = False
+    isVariable (Variable _) = True
+    unVariable (Variable x) = x
+    nonempty (_, [], _) = False
+    nonempty _ = True
+
+class Minimal f where
+  minimal :: Fun f
+
+{-# INLINE lessEqInModel #-}
+lessEqInModel :: (Minimal f, Ordered f) => Model f -> Atom f -> Atom f -> Maybe Strictness
+lessEqInModel (Model m) x y
+  | Just (a, _) <- Map.lookup x m,
+    Just (b, _) <- Map.lookup y m,
+    a < b = Just Strict
+  | Just a <- Map.lookup x m,
+    Just b <- Map.lookup y m,
+    a < b = Just Nonstrict
+  | x == y = Just Nonstrict
+  | Constant a <- x, Constant b <- y, a << b = Just Strict
+  | Constant a <- x, a == minimal = Just Nonstrict
+  | otherwise = Nothing
+
+solve :: (Minimal f, Ordered f, PrettyTerm f) => [Atom f] -> Branch f -> Either (Model f) (Subst f)
+solve xs branch@Branch{..}
+  | null equals && not (all true less) =
+    error $ "Model " ++ prettyShow model ++ " is not a model of " ++ prettyShow branch ++ " (edges = " ++ prettyShow edges ++ ", vs = " ++ prettyShow vs ++ ")"
+  | null equals = Left model
+  | otherwise = Right sub
+    where
+      sub = fromMaybe undefined . listToSubst $
+        [(x, toTerm y) | (Variable x, y) <- equals] ++
+        [(y, toTerm x) | (x@Constant{}, Variable y) <- equals]
+      vs = Constant minimal:reverse (flattenSCCs (stronglyConnComp edges))
+      edges = [(x, x, [y | (x', y) <- less', x == x']) | x <- as, x /= Constant minimal]
+      less' = less ++ [(Constant x, Constant y) | Constant x <- as, Constant y <- as, x << y]
+      as = usort $ xs ++ map fst less ++ map snd less
+      model = modelFromOrder vs
+      true (t, u) = lessEqInModel model t u == Just Strict
+
+class Ord f => Ordered f where
+  -- | Return 'True' if the first term is less than or equal to the second,
+  -- in the term ordering.
+  lessEq :: Term f -> Term f -> Bool
+  -- | Check if the first term is less than or equal to the second in the given model,
+  -- and decide whether the inequality is strict or nonstrict.
+  lessIn :: Model f -> Term f -> Term f -> Maybe Strictness
+
+-- | Describes whether an inequality is strict or nonstrict.
+data Strictness =
+    -- | The first term is strictly less than the second.
+    Strict
+    -- | The first term is less than or equal to the second.
+  | Nonstrict deriving (Eq, Show)
+
+-- | Return 'True' if the first argument is strictly less than the second,
+-- in the term ordering.
+lessThan :: Ordered f => Term f -> Term f -> Bool
+lessThan t u = lessEq t u && isNothing (unify t u)
+
+-- | Return the direction in which the terms are oriented according to the term
+-- ordering, or 'Nothing' if they cannot be oriented. A result of @'Just' 'LT'@
+-- means that the first term is less than /or equal to/ the second.
+orientTerms :: Ordered f => Term f -> Term f -> Maybe Ordering
+orientTerms t u
+  | t == u = Just EQ
+  | lessEq t u = Just LT
+  | lessEq u t = Just GT
+  | otherwise = Nothing
diff --git a/Twee/Equation.hs b/Twee/Equation.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Equation.hs
@@ -0,0 +1,58 @@
+-- | Equations.
+{-# LANGUAGE TypeFamilies #-}
+module Twee.Equation where
+
+import Twee.Base
+import Data.Maybe
+import Control.Monad
+
+--------------------------------------------------------------------------------
+-- * Equations.
+--------------------------------------------------------------------------------
+
+data Equation f =
+  (:=:) {
+    eqn_lhs :: {-# UNPACK #-} !(Term f),
+    eqn_rhs :: {-# UNPACK #-} !(Term f) }
+  deriving (Eq, Ord, Show)
+type EquationOf a = Equation (ConstantOf a)
+
+instance Symbolic (Equation f) where
+  type ConstantOf (Equation f) = f
+  termsDL (t :=: u) = termsDL t `mplus` termsDL u
+  subst_ sub (t :=: u) = subst_ sub t :=: subst_ sub u
+
+instance PrettyTerm f => Pretty (Equation f) where
+  pPrint (x :=: y) = pPrint x <+> text "=" <+> pPrint y
+
+instance Sized f => Sized (Equation f) where
+  size (x :=: y) = size x + size y
+
+-- | Order an equation roughly left-to-right.
+-- However, there is no guarantee that the result is oriented.
+order :: Function f => Equation f -> Equation f
+order (l :=: r)
+  | l == r = l :=: r
+  | otherwise =
+    case compare (size l) (size r) of
+      LT -> r :=: l
+      GT -> l :=: r
+      EQ -> if lessEq l r then r :=: l else l :=: r
+
+-- | Apply a function to both sides of an equation.
+bothSides :: (Term f -> Term f') -> Equation f -> Equation f'
+bothSides f (t :=: u) = f t :=: f u
+
+-- | Is an equation of the form t = t?
+trivial :: Eq f => Equation f -> Bool
+trivial (t :=: u) = t == u
+
+simplerThan :: Function f => Equation f -> Equation f -> Bool
+eq1 `simplerThan` eq2 =
+  t1 `lessEq` t2 &&
+  (isNothing (unify t1 t2) || (u1 `lessEq` u2))
+  where
+    t1 :=: u1 = skolemise eq1
+    t2 :=: u2 = skolemise eq2
+
+    skolemise = subst (con . skolem)
diff --git a/Twee/Index.hs b/Twee/Index.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Index.hs
@@ -0,0 +1,310 @@
+-- | A term index to accelerate matching.
+-- An index is a multimap from terms to arbitrary values.
+--
+-- The type of query supported is: given a search term, find all keys such that
+-- the search term is an instance of the key, and return the corresponding
+-- values.
+
+{-# LANGUAGE BangPatterns, RecordWildCards, OverloadedStrings, FlexibleContexts #-}
+-- We get some bogus warnings because of pattern synonyms.
+{-# OPTIONS_GHC -fno-warn-overlapping-patterns #-}
+module Twee.Index(
+  Index,
+  empty,
+  null,
+  singleton,
+  insert,
+  delete,
+  lookup,
+  matches,
+  approxMatches,
+  elems) where
+
+import qualified Prelude
+import Prelude hiding (null, lookup)
+import Data.Maybe
+import Twee.Base hiding (var, fun, empty, size, singleton, prefix, funs, lookupList, lookup)
+import qualified Twee.Term as Term
+import Twee.Term.Core(TermList(..))
+import Data.DynamicArray
+import qualified Data.List as List
+
+-- The term index in this module is an _imperfect discrimination tree_.
+-- This is a trie whose keys are terms, represented as flat lists of symbols,
+-- but where all variables have been replaced by a single don't-care variable '_'.
+-- That is, the edges of the trie can be either function symbols or '_'.
+-- To insert a key-value pair into the discrimination tree, we first replace all
+-- variables in the key with '_', and then do ordinary trie insertion.
+--
+-- Lookup maintains a term list, which is initially the search term.
+-- It proceeds down the trie, consuming bits of the term list as it goes.
+--
+-- If the current trie node has an edge for a function symbol f, and the term at
+-- the head of the term list is f(t1..tn), we can follow the f edge. We then
+-- delete f from the term list, but keep t1..tn at the front of the term list.
+-- (In other words we delete only the symbol f and not its arguments.)
+--
+-- If the current trie node has an edge for '_', we can always follow that edge.
+-- We then remove the head term from the term list, as the '_' represents a
+-- variable that should match that whole term.
+--
+-- If the term list ever becomes empty, we have a possible match. We then
+-- do matching on the values stored at the current node to see if they are
+-- genuine matches.
+--
+-- Often there are two edges we can follow (function symbol and '_'), and in
+-- that case the algorithm uses backtracking.
+
+-- | A term index: a multimap from @'Term' f@ to @a@.
+data Index f a =
+  -- A non-empty index.
+  Index {
+    -- Size of smallest term in index.
+    size   :: {-# UNPACK #-} !Int,
+    -- When all keys in the index start with the same sequence of symbols, we
+    -- compress them into this prefix; the "fun" and "var" fields below refer to
+    -- the first symbol _after_ the prefix, and the "here" field contains values
+    -- whose remaining key is exactly this prefix.
+    prefix :: {-# UNPACK #-} !(TermList f),
+    -- The values that are found at this node.
+    here   :: [a],
+    -- Function symbol edges.
+    -- The array is indexed by function number.
+    fun    :: {-# UNPACK #-} !(Array (Index f a)),
+    -- Variable edge.
+    var    :: !(Index f a) } |
+  -- An empty index.
+  Nil
+  deriving Show
+
+instance Default (Index f a) where def = Nil
+
+-- To get predictable performance, the lookup function uses an explicit stack
+-- instead of recursion to control backtracking.
+data Stack f a =
+  -- A normal stack frame: records the current index node and term.
+  Frame {
+    frame_term  :: {-# UNPACK #-} !(TermList f),
+    frame_index :: !(Index f a),
+    frame_rest  :: !(Stack f a) }
+  -- A stack frame which is used when we have found a match.
+  | Yield {
+    yield_found :: [a],
+    yield_rest  :: !(Stack f a) }
+  -- End of stack.
+  | Stop
+
+-- Turn a stack into a list of results.
+run :: Stack f a -> [a]
+run Stop = []
+run Frame{..} = run ({-# SCC run_inner #-} step frame_term frame_index frame_rest)
+run Yield{..} = {-# SCC run_found #-} yield_found ++ run yield_rest
+
+-- Execute a single stack frame.
+{-# INLINE step #-}
+step :: TermList f -> Index f a -> Stack f a -> Stack f a
+step !_ _ _ | False = undefined
+step t idx rest =
+  case idx of
+    Nil -> rest
+    Index{..}
+      | lenList t < size ->
+        rest -- the search term is smaller than any in this index
+      | otherwise ->
+        pref t prefix here fun var rest
+
+-- The main work of 'step' goes on here.
+-- It is carefully tweaked to generate nice code,
+-- including using UnsafeCons and only casing on each
+-- term list exactly once.
+pref :: TermList f -> TermList f -> [a] -> Array (Index f a) -> Index f a -> Stack f a -> Stack f a
+pref !_ !_ _ !_ !_ _ | False = undefined
+pref search prefix here fun var rest =
+  case search of
+    Empty ->
+      case prefix of
+        Empty ->
+          -- The search term matches this node.
+          case here of
+            [] -> rest
+            _ -> Yield here rest
+        _ ->
+          -- We've run out of search term - it doesn't match this node.
+          rest
+    UnsafeCons t ts ->
+      case prefix of
+        Cons u us ->
+          -- Check the search term against the prefix.
+          case (t, u) of
+            (_, Var _) ->
+              -- Prefix contains a variable - if there is a match, the
+              -- variable will be bound to t.
+              pref ts us here fun var rest
+            (App f _, App g _) | f == g ->
+              -- Term and prefix start with same symbol, chop them off.
+               let
+                 UnsafeConsSym _ ts' = search
+                 UnsafeConsSym _ us' = prefix
+               in pref ts' us' here fun var rest
+            _ ->
+              -- Term and prefix don't match.
+              rest
+        _ ->
+          -- We've exhausted the prefix, so let's descend into the tree.
+          -- Seems to work better to explore the function node first.
+          let
+            tryVar =
+              case var of
+                Nil -> rest
+                Index{} -> Frame ts var rest
+              where
+                UnsafeCons _ ts = search
+
+            tryFun =
+              case t of
+                App f _ ->
+                  case fun ! fun_id f of
+                    Nil -> tryVar
+                    idx -> Frame ts idx $! tryVar
+                _ ->
+                  tryVar
+              where
+                UnsafeConsSym t ts = search
+          in
+            tryFun
+
+-- | An empty index.
+empty :: Index f a
+empty = Nil
+
+-- | Is the index empty?
+null :: Index f a -> Bool
+null Nil = True
+null _ = False
+
+-- | An index with one entry.
+singleton :: Term f -> a -> Index f a
+singleton !t x = singletonList (Term.singleton t) x
+
+-- An index with one entry, giving a termlist instead of a term.
+{-# INLINE singletonList #-}
+singletonList :: TermList f -> a -> Index f a
+singletonList t x = Index 0 t [x] newArray Nil
+
+-- | Insert an entry into the index.
+insert :: Term f -> a -> Index f a -> Index f a
+insert !t x !idx = {-# SCC insert #-} aux (Term.singleton t) idx
+  where
+    aux t Nil = singletonList t x
+    aux (Cons t ts) idx@Index{prefix = Cons u us} | t == u =
+      withPrefix (Term.singleton t) (aux ts idx{prefix = us})
+    aux t idx@Index{prefix = Cons{}} = aux t (expand idx)
+
+    aux Empty idx =
+      idx { size = 0, here = x:here idx }
+    aux t@(ConsSym (App f _) u) idx =
+      idx {
+        size = lenList t `min` size idx,
+        fun  = update (fun_id f) idx' (fun idx) }
+      where
+        idx' = aux u (fun idx ! fun_id f)
+    aux t@(ConsSym (Var _) u) idx =
+      idx {
+        size = lenList t `min` size idx,
+        var  = aux u (var idx) }
+
+-- Add a prefix to an index.
+-- Does not update the size field.
+{-# INLINE withPrefix #-}
+withPrefix :: TermList f -> Index f a -> Index f a
+withPrefix Empty idx = idx
+withPrefix _ Nil = Nil
+withPrefix t idx@Index{..} =
+  idx{prefix = buildList (builder t `mappend` builder prefix)}
+
+-- Take an index with a prefix and pull out the first symbol of the prefix,
+-- giving an index which doesn't start with a prefix.
+{-# INLINE expand #-}
+expand :: Index f a -> Index f a
+expand idx@Index{size = size, prefix = ConsSym t ts} =
+  case t of
+    Var _ ->
+      Index {
+        size = size,
+        prefix = Term.empty,
+        here = [],
+        fun = newArray,
+        var = idx { prefix = ts, size = size - 1 } }
+    App f _ ->
+      Index {
+        size = size,
+        prefix = Term.empty,
+        here = [],
+        fun = update (fun_id f) idx { prefix = ts, size = size - 1 } newArray,
+        var = Nil }
+
+-- | Delete an entry from the index.
+{-# INLINEABLE delete #-}
+delete :: Eq a => Term f -> a -> Index f a -> Index f a
+delete !t x !idx = {-# SCC delete #-} aux (Term.singleton t) idx
+  where
+    aux _ Nil = Nil
+    aux (Cons t ts) idx@Index{prefix = Cons u us} | t == u =
+      withPrefix (Term.singleton t) (aux ts idx{prefix = us})
+    aux _ idx@Index{prefix = Cons{}} = idx
+
+    aux Empty idx
+      | x `List.elem` here idx =
+        idx { here = List.delete x (here idx) }
+      | otherwise =
+        error "deleted term not found in index"
+    aux (ConsSym (App f _) t) idx =
+      idx { fun = update (fun_id f) (aux t (fun idx ! fun_id f)) (fun idx) }
+    aux (ConsSym (Var _) t) idx =
+      idx { var = aux t (var idx) }
+
+-- | Look up a term in the index. Finds all key-value such that the search term
+-- is an instance of the key, and returns an instance of the the value which
+-- makes the search term exactly equal to the key.
+{-# INLINE lookup #-}
+lookup :: (Has a b, Symbolic b, Has b (TermOf b)) => TermOf b -> Index (ConstantOf b) a -> [b]
+lookup t idx = lookupList (Term.singleton t) idx
+
+{-# INLINEABLE lookupList #-}
+lookupList :: (Has a b, Symbolic b, Has b (TermOf b)) => TermListOf b -> Index (ConstantOf b) a -> [b]
+lookupList t idx =
+  [ subst sub x
+  | x <- List.map the (approxMatchesList t idx),
+    sub <- maybeToList (matchList (Term.singleton (the x)) t)]
+
+-- | Look up a term in the index. Like 'lookup', but returns the exact value
+-- that was inserted into the index, not an instance. Also returns a substitution
+-- which when applied to the value gives you the matching instance.
+{-# INLINE matches #-}
+matches :: Has a (Term f) => Term f -> Index f a -> [(Subst f, a)]
+matches t idx = matchesList (Term.singleton t) idx
+
+{-# INLINEABLE matchesList #-}
+matchesList :: Has a (Term f) => TermList f -> Index f a -> [(Subst f, a)]
+matchesList t idx =
+  [ (sub, x)
+  | x <- approxMatchesList t idx,
+    sub <- maybeToList (matchList (Term.singleton (the x)) t)]
+
+-- | Look up a term in the index, possibly returning spurious extra results.
+{-# INLINE approxMatches #-}
+approxMatches :: Term f -> Index f a -> [a]
+approxMatches t idx = approxMatchesList (Term.singleton t) idx
+
+approxMatchesList :: TermList f -> Index f a -> [a]
+approxMatchesList t idx =
+  {-# SCC approxMatchesList #-}
+  run (Frame t idx Stop)
+
+-- | Return all elements of the index.
+elems :: Index f a -> [a]
+elems Nil = []
+elems idx =
+  here idx ++
+  concatMap elems (Prelude.map snd (toList (fun idx))) ++
+  elems (var idx)
diff --git a/Twee/Join.hs b/Twee/Join.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Join.hs
@@ -0,0 +1,212 @@
+-- | Tactics for joining critical pairs.
+{-# LANGUAGE FlexibleContexts, BangPatterns, RecordWildCards, TypeFamilies #-}
+module Twee.Join where
+
+import Twee.Base
+import Twee.Rule
+import Twee.Equation
+import Twee.Proof(Lemma)
+import qualified Twee.Proof as Proof
+import Twee.CP hiding (Config)
+import Twee.Constraints
+import qualified Twee.Index as Index
+import Twee.Index(Index)
+import Twee.Rule.Index(RuleIndex(..))
+import Twee.Utils
+import Data.Maybe
+import Data.Either
+import Data.Ord
+import qualified Data.Set as Set
+
+data Config =
+  Config {
+    cfg_ground_join :: !Bool,
+    cfg_use_connectedness :: !Bool,
+    cfg_set_join :: !Bool }
+
+defaultConfig :: Config
+defaultConfig =
+  Config {
+    cfg_ground_join = True,
+    cfg_use_connectedness = True,
+    cfg_set_join = False }
+
+{-# INLINEABLE joinCriticalPair #-}
+joinCriticalPair ::
+  (Function f, Has a (Rule f), Has a (Lemma f)) =>
+  Config ->
+  Index f (Equation f) -> RuleIndex f a ->
+  Maybe (Model f) -> -- A model to try before checking ground joinability
+  CriticalPair f ->
+  Either
+    -- Failed to join critical pair.
+    -- Returns simplified critical pair and model in which it failed to hold.
+    (CriticalPair f, Model f)
+    -- Split critical pair into several instances.
+    -- Returns list of instances which must be joined,
+    -- and an optional equation which can be added to the joinable set
+    -- after successfully joining all instances.
+    (Maybe (CriticalPair f), [CriticalPair f])
+joinCriticalPair config eqns idx mmodel cp@CriticalPair{cp_eqn = t :=: u} =
+  {-# SCC joinCriticalPair #-}
+  case allSteps config eqns idx cp of
+    Nothing ->
+      Right (Nothing, [])
+    _ | cfg_set_join config &&
+        not (null $ Set.intersection
+          (normalForms (rewrite reduces (index_all idx)) [reduce (Refl t)])
+          (normalForms (rewrite reduces (index_all idx)) [reduce (Refl u)])) ->
+      Right (Just cp, [])
+    Just cp ->
+      case groundJoinFromMaybe config eqns idx mmodel (branches (And [])) cp of
+        Left model -> Left (cp, model)
+        Right cps -> Right (Just cp, cps)
+
+{-# INLINEABLE step1 #-}
+{-# INLINEABLE step2 #-}
+{-# INLINEABLE step3 #-}
+{-# INLINEABLE allSteps #-}
+step1, step2, step3, allSteps ::
+  (Function f, Has a (Rule f), Has a (Lemma f)) =>
+  Config -> Index f (Equation f) -> RuleIndex f a -> CriticalPair f -> Maybe (CriticalPair f)
+allSteps config eqns idx cp =
+  step1 config eqns idx cp >>=
+  step2 config eqns idx >>=
+  step3 config eqns idx
+step1 _ eqns idx = joinWith eqns idx (\t _ -> normaliseWith (const True) (rewrite reducesOriented (index_oriented idx)) t)
+step2 _ eqns idx = joinWith eqns idx (\t _ -> normaliseWith (const True) (rewrite reduces (index_all idx)) t)
+step3 Config{..} eqns idx cp
+  | not cfg_use_connectedness = Just cp
+  | otherwise =
+    case cp_top cp of
+      Just top ->
+        case (join (cp, top), join (flipCP (cp, top))) of
+          (Just _, Just _) -> Just cp
+          _ -> Nothing
+      _ -> Just cp
+  where
+    join (cp, top) =
+      joinWith eqns idx (\t u -> normaliseWith (`lessThan` top) (rewrite (ok t u) (index_all idx)) t) cp
+
+    ok t u rule sub =
+      unorient rule `simplerThan` (t :=: u) &&
+      reducesSkolem rule sub
+
+    flipCP :: Symbolic a => a -> a
+    flipCP t = subst sub t
+      where
+        n = maximum (0:map fromEnum (vars t))
+        sub (V x) = var (V (n - x))
+
+
+{-# INLINEABLE joinWith #-}
+joinWith ::
+  (Has a (Rule f), Has a (Lemma f)) =>
+  Index f (Equation f) -> RuleIndex f a -> (Term f -> Term f -> Resulting f) -> CriticalPair f -> Maybe (CriticalPair f)
+joinWith eqns idx reduce cp@CriticalPair{cp_eqn = lhs :=: rhs, ..}
+  | subsumed eqns idx eqn = Nothing
+  | otherwise =
+    Just cp {
+      cp_eqn = eqn,
+      cp_proof =
+        Proof.symm (reductionProof (reduction lred)) `Proof.trans`
+        cp_proof `Proof.trans`
+        reductionProof (reduction rred) }
+  where
+    lred = reduce lhs rhs
+    rred = reduce rhs lhs
+    eqn = result lred :=: result rred
+
+{-# INLINEABLE subsumed #-}
+subsumed ::
+  (Has a (Rule f), Has a (Lemma f)) =>
+  Index f (Equation f) -> RuleIndex f a -> Equation f -> Bool
+subsumed eqns idx (t :=: u)
+  | t == u = True
+  | or [ rhs rule == u | rule <- Index.lookup t (index_all idx) ] = True
+  | or [ rhs rule == t | rule <- Index.lookup u (index_all idx) ] = True
+    -- No need to do this symmetrically because addJoinable adds
+    -- both orientations of each equation
+  | or [ u == subst sub u'
+       | t' :=: u' <- Index.approxMatches t eqns,
+         sub <- maybeToList (match t' t) ] = True
+subsumed eqns idx (App f ts :=: App g us)
+  | f == g =
+    let
+      sub Empty Empty = True
+      sub (Cons t ts) (Cons u us) =
+        subsumed eqns idx (t :=: u) && sub ts us
+      sub _ _ = error "Function used with multiple arities"
+    in
+      sub ts us
+subsumed _ _ _ = False
+
+{-# INLINEABLE groundJoin #-}
+groundJoin ::
+  (Function f, Has a (Rule f), Has a (Lemma f)) =>
+  Config -> Index f (Equation f) -> RuleIndex f a -> [Branch f] -> CriticalPair f -> Either (Model f) [CriticalPair f]
+groundJoin config eqns idx ctx cp@CriticalPair{cp_eqn = t :=: u, ..} =
+  case partitionEithers (map (solve (usort (atoms t ++ atoms u))) ctx) of
+    ([], instances) ->
+      let cps = [ subst sub cp | sub <- instances ] in
+      Right (usortBy (comparing (canonicalise . order . cp_eqn)) cps)
+    (model:_, _) ->
+      groundJoinFrom config eqns idx model ctx cp
+
+{-# INLINEABLE groundJoinFrom #-}
+groundJoinFrom ::
+  (Function f, Has a (Rule f), Has a (Lemma f)) =>
+  Config -> Index f (Equation f) -> RuleIndex f a -> Model f -> [Branch f] -> CriticalPair f -> Either (Model f) [CriticalPair f]
+groundJoinFrom config@Config{..} eqns idx model ctx cp@CriticalPair{cp_eqn = t :=: u, ..}
+  | not cfg_ground_join ||
+    (modelOK model && isJust (allSteps config eqns idx cp { cp_eqn = t' :=: u' })) = Left model
+  | otherwise =
+      let model1 = optimise model weakenModel (\m -> not (modelOK m) || (valid m (reduction nt) && valid m (reduction nu)))
+          model2 = optimise model1 weakenModel (\m -> not (modelOK m) || isNothing (allSteps config eqns idx cp { cp_eqn = result (normaliseIn m t u) :=: result (normaliseIn m u t) }))
+
+          diag [] = Or []
+          diag (r:rs) = negateFormula r ||| (weaken r &&& diag rs)
+          weaken (LessEq t u) = Less t u
+          weaken x = x
+          ctx' = formAnd (diag (modelToLiterals model2)) ctx in
+
+      groundJoin config eqns idx ctx' cp
+  where
+    normaliseIn m t u = normaliseWith (const True) (rewrite (ok t u m) (index_all idx)) t
+    ok t u m rule sub =
+      reducesInModel m rule sub &&
+      unorient rule `simplerThan` (t :=: u)
+
+    nt = normaliseIn model t u
+    nu = normaliseIn model u t
+    t' = result nt
+    u' = result nu
+
+    -- XXX not safe to exploit the top term if we then add the equation to
+    -- the joinable set. (It might then be used to join a CP with an entirely
+    -- different top term.)
+    modelOK _ = True
+{-    modelOK m =
+      case cp_top of
+        Nothing -> True
+        Just top ->
+          isNothing (lessIn m top t) && isNothing (lessIn m top u)-}
+
+{-# INLINEABLE groundJoinFromMaybe #-}
+groundJoinFromMaybe ::
+  (Function f, Has a (Rule f), Has a (Lemma f)) =>
+  Config -> Index f (Equation f) -> RuleIndex f a -> Maybe (Model f) -> [Branch f] -> CriticalPair f -> Either (Model f) [CriticalPair f]
+groundJoinFromMaybe config eqns idx Nothing = groundJoin config eqns idx
+groundJoinFromMaybe config eqns idx (Just model) = groundJoinFrom config eqns idx model
+
+{-# INLINEABLE valid #-}
+valid :: Function f => Model f -> Reduction f -> Bool
+valid model red =
+  and [ reducesInModel model rule sub
+      | Step _ rule sub <- steps red ]
+
+optimise :: a -> (a -> [a]) -> (a -> Bool) -> a
+optimise x f p =
+  case filter p (f x) of
+    y:_ -> optimise y f p
+    _   -> x
diff --git a/Twee/KBO.hs b/Twee/KBO.hs
new file mode 100644
--- /dev/null
+++ b/Twee/KBO.hs
@@ -0,0 +1,121 @@
+-- | An implementation of Knuth-Bendix ordering.
+
+{-# LANGUAGE PatternGuards #-}
+module Twee.KBO(lessEq, lessIn) where
+
+import Twee.Base hiding (lessEq, lessIn)
+import Data.List
+import Twee.Constraints hiding (lessEq, lessIn)
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict(Map)
+import Data.Maybe
+import Control.Monad
+
+-- | Check if one term is less than another in KBO.
+lessEq :: Function f => Term f -> Term f -> Bool
+lessEq (App f Empty) _ | f == minimal = True
+lessEq (Var x) (Var y) | x == y = True
+lessEq _ (Var _) = False
+lessEq (Var x) t = x `elem` vars t
+lessEq t@(App f ts) u@(App g us) =
+  (st < su ||
+   (st == su && f << g) ||
+   (st == su && f == g && lexLess ts us)) &&
+  xs `isSubsequenceOf` ys
+  where
+    lexLess Empty Empty = True
+    lexLess (Cons t ts) (Cons u us)
+      | t == u = lexLess ts us
+      | otherwise =
+        lessEq t u &&
+        case unify t u of
+          Nothing -> True
+          Just sub
+            | not (allSubst (\_ (Cons t Empty) -> isMinimal t) sub) -> error "weird term inequality"
+            | otherwise -> lexLess (subst sub ts) (subst sub us)
+    lexLess _ _ = error "incorrect function arity"
+    xs = sort (vars t)
+    ys = sort (vars u)
+    st = size t
+    su = size u
+
+-- | Check if one term is less than another in a given model.
+
+-- See "notes/kbo under assumptions" for how this works.
+
+lessIn :: Function f => Model f -> Term f -> Term f -> Maybe Strictness
+lessIn model t u =
+  case sizeLessIn model t u of
+    Nothing -> Nothing
+    Just Strict -> Just Strict
+    Just Nonstrict -> lexLessIn model t u
+
+sizeLessIn :: Function f => Model f -> Term f -> Term f -> Maybe Strictness
+sizeLessIn model t u =
+  case minimumIn model m of
+    Just l
+      | l >  -k -> Just Strict
+      | l == -k -> Just Nonstrict
+    _ -> Nothing
+  where
+    (k, m) =
+      foldr (addSize id)
+        (foldr (addSize negate) (0, Map.empty) (subterms t))
+        (subterms u)
+    addSize op (App f _) (k, m) = (k + op (size f), m)
+    addSize op (Var x) (k, m) = (k, Map.insertWith (+) x (op 1) m)
+
+minimumIn :: Function f => Model f -> Map Var Int -> Maybe Int
+minimumIn model t =
+  liftM2 (+)
+    (fmap sum (mapM minGroup (varGroups model)))
+    (fmap sum (mapM minOrphan (Map.toList t)))
+  where
+    minGroup (lo, xs, mhi)
+      | all (>= 0) sums = Just (sum coeffs * size lo)
+      | otherwise =
+        case mhi of
+          Nothing -> Nothing
+          Just hi ->
+            let coeff = negate (minimum coeffs) in
+            Just $
+              sum coeffs * size lo +
+              coeff * (size lo - size hi)
+      where
+        coeffs = map (\x -> Map.findWithDefault 0 x t) xs
+        sums = scanr1 (+) coeffs
+
+    minOrphan (x, k)
+      | varInModel model x = Just 0
+      | k < 0 = Nothing
+      | otherwise = Just k
+
+lexLessIn :: Function f => Model f -> Term f -> Term f -> Maybe Strictness
+lexLessIn _ t u | t == u = Just Nonstrict
+lexLessIn cond t u
+  | Just a <- fromTerm t,
+    Just b <- fromTerm u,
+    Just x <- lessEqInModel cond a b = Just x
+  | Just a <- fromTerm t,
+    any isJust
+      [ lessEqInModel cond a b
+      | v <- properSubterms u, Just b <- [fromTerm v]] =
+        Just Strict
+lexLessIn cond (App f ts) (App g us)
+  | f == g = loop ts us
+  | f << g = Just Strict
+  | otherwise = Nothing
+  where
+    loop Empty Empty = Just Nonstrict
+    loop (Cons t ts) (Cons u us)
+      | t == u = loop ts us
+      | otherwise =
+        case lessIn cond t u of
+          Nothing -> Nothing
+          Just Strict -> Just Strict
+          Just Nonstrict ->
+            let Just sub = unify t u in
+            loop (subst sub ts) (subst sub us)
+    loop _ _ = error "incorrect function arity"
+lexLessIn _ t _ | isMinimal t = Just Nonstrict
+lexLessIn _ _ _ = Nothing
diff --git a/Twee/Label.hs b/Twee/Label.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Label.hs
@@ -0,0 +1,125 @@
+-- | Assignment of unique IDs to values.
+-- Inspired by the 'intern' package.
+
+{-# LANGUAGE RecordWildCards, ScopedTypeVariables, BangPatterns #-}
+module Twee.Label(Label, unsafeMkLabel, labelNum, label, find) where
+
+import Data.IORef
+import System.IO.Unsafe
+import qualified Data.Map.Strict as Map
+import Data.Map.Strict(Map)
+import qualified Data.IntMap.Strict as IntMap
+import Data.IntMap.Strict(IntMap)
+import Data.Typeable
+import GHC.Exts
+import Unsafe.Coerce
+import Data.Int
+
+-- | A value of type @a@ which has been given a unique ID.
+newtype Label a =
+  Label {
+    -- | The unique ID of a label.
+    labelNum :: Int32 }
+  deriving (Eq, Ord, Show)
+
+-- | Construct a @'Label' a@ from its unique ID, which must be the 'labelNum' of
+-- an already existing 'Label'. Extremely unsafe!
+unsafeMkLabel :: Int32 -> Label a
+unsafeMkLabel = Label
+
+-- The global cache of labels.
+{-# NOINLINE cachesRef #-}
+cachesRef :: IORef Caches
+cachesRef = unsafePerformIO (newIORef (Caches 0 Map.empty IntMap.empty))
+
+data Caches =
+  Caches {
+    -- The next id number to assign.
+    caches_nextId :: {-# UNPACK #-} !Int32,
+    -- A map from values to labels.
+    caches_from   :: !(Map TypeRep (Cache Any)),
+    -- The reverse map from labels to values.
+    caches_to     :: !(IntMap Any) }
+
+type Cache a = Map a Int32
+
+atomicModifyCaches :: (Caches -> (Caches, a)) -> IO a
+atomicModifyCaches f = do
+  -- N.B. atomicModifyIORef' ref f evaluates f ref *after* doing the
+  -- compare-and-swap. This causes bad things to happen when 'label'
+  -- is used reentrantly (i.e. the Ord instance itself calls label).
+  -- This function only lets the swap happen if caches_nextId didn't
+  -- change (i.e., no new values were inserted).
+  !caches <- readIORef cachesRef
+  -- First compute the update.
+  let !(!caches', !x) = f caches
+  -- Now see if anyone else updated the cache in between
+  -- (can happen if f called 'label', or in a concurrent setting).
+  ok <- atomicModifyIORef' cachesRef $ \cachesNow ->
+    if caches_nextId caches == caches_nextId cachesNow
+    then (caches', True)
+    else (cachesNow, False)
+  if ok then return x else atomicModifyCaches f
+
+-- Versions of unsafeCoerce with slightly more type checking
+toAnyCache :: Cache a -> Cache Any
+toAnyCache = unsafeCoerce
+
+fromAnyCache :: Cache Any -> Cache a
+fromAnyCache = unsafeCoerce
+
+toAny :: a -> Any
+toAny = unsafeCoerce
+
+fromAny :: Any -> a
+fromAny = unsafeCoerce
+
+-- | Assign a label to a value.
+{-# NOINLINE label #-}
+label :: forall a. (Typeable a, Ord a) => a -> Label a
+label x =
+  unsafeDupablePerformIO $ do
+    -- Common case: label is already there.
+    caches <- readIORef cachesRef
+    case tryFind caches of
+      Just l -> return l
+      Nothing -> do
+        -- Rare case: label was not there.
+        x <- atomicModifyCaches $ \caches ->
+          case tryFind caches of
+            Just l -> (caches, l)
+            Nothing ->
+              insert caches
+        return x
+
+  where
+    ty = typeOf x
+
+    tryFind :: Caches -> Maybe (Label a)
+    tryFind Caches{..} =
+      Label <$> (Map.lookup ty caches_from >>= Map.lookup x . fromAnyCache)
+
+    insert :: Caches -> (Caches, Label a)
+    insert caches@Caches{..} =
+      if n < 0 then error "label overflow" else
+      (caches {
+         caches_nextId = n+1,
+         caches_from = Map.insert ty (toAnyCache (Map.insert x n cache)) caches_from,
+         caches_to = IntMap.insert (fromIntegral n) (toAny x) caches_to },
+       Label n)
+      where
+        n = caches_nextId
+        cache =
+          fromAnyCache $
+          Map.findWithDefault Map.empty ty caches_from
+
+-- | Recover the underlying value from a label.
+find :: Label a -> a
+-- N.B. must force n before calling readIORef, otherwise a call of
+-- the form
+--   find (label x)
+-- doesn't work.
+find (Label !n) = unsafeDupablePerformIO $ do
+  Caches{..} <- readIORef cachesRef
+  x <- return $! fromAny (IntMap.findWithDefault undefined (fromIntegral n) caches_to)
+  return x
diff --git a/Twee/PassiveQueue.hs b/Twee/PassiveQueue.hs
new file mode 100644
--- /dev/null
+++ b/Twee/PassiveQueue.hs
@@ -0,0 +1,183 @@
+-- | A queue of passive critical pairs, using a memory-efficient representation.
+{-# LANGUAGE TypeFamilies, RecordWildCards, FlexibleContexts, ScopedTypeVariables, StandaloneDeriving #-}
+module Twee.PassiveQueue(
+  Params(..),
+  Queue,
+  Passive(..),
+  empty, insert, removeMin, mapMaybe) where
+
+import qualified Data.Heap as Heap
+import qualified Data.Vector.Unboxed as Vector
+import Data.Int
+import Data.List hiding (insert)
+import qualified Data.Maybe
+import Data.Ord
+import Data.Proxy
+import Twee.Utils
+
+-- | A datatype representing all the type parameters of the queue.
+class (Eq (Id params), Integral (Id params), Ord (Score params), Vector.Unbox (PackedScore params), Vector.Unbox (PackedId params)) => Params params where
+  -- | The score assigned to critical pairs. Smaller scores are better.
+  type Score params
+  -- | The type of ID numbers used to name rules.
+  type Id params
+
+  -- | A 'Score' packed for storage into a 'Vector.Vector'. Must be an instance of 'Vector.Unbox'.
+  type PackedScore params
+  -- | An 'Id' packed for storage into a 'Vector.Vector'. Must be an instance of 'Vector.Unbox'.
+  type PackedId params
+
+  -- | Pack a 'Score'.
+  packScore :: proxy params -> Score params -> PackedScore params
+  -- | Unpack a 'PackedScore'.
+  unpackScore :: proxy params -> PackedScore params -> Score params
+  -- | Pack an 'Id'.
+  packId :: proxy params -> Id params -> PackedId params
+  -- | Unpack a 'PackedId'.
+  unpackId :: proxy params -> PackedId params -> Id params
+
+-- | A critical pair queue.
+newtype Queue params =
+  Queue (Heap.Heap (PassiveSet params))
+
+-- All passive CPs generated from one given rule.
+data PassiveSet params =
+  PassiveSet {
+    passiveset_best  :: {-# UNPACK #-} !(Passive params),
+    passiveset_rule  :: !(Id params),
+    -- CPs where the rule is the left-hand rule
+    passiveset_left  :: {-# UNPACK #-} !(Vector.Vector (PackedScore params, PackedId params, Int32)),
+    -- CPs where the rule is the right-hand rule
+    passiveset_right :: {-# UNPACK #-} !(Vector.Vector (PackedScore params, PackedId params, Int32)) }
+instance Params params => Eq (PassiveSet params) where
+  x == y = compare x y == EQ
+instance Params params => Ord (PassiveSet params) where
+  compare = comparing passiveset_best
+
+-- A smart-ish constructor.
+{-# INLINEABLE mkPassiveSet #-}
+mkPassiveSet ::
+  Params params =>
+  Proxy params ->
+  Id params ->
+  Vector.Vector (PackedScore params, PackedId params, Int32) ->
+  Vector.Vector (PackedScore params, PackedId params, Int32) ->
+  Maybe (PassiveSet params)
+mkPassiveSet proxy rule left right
+  | Vector.null left && Vector.null right = Nothing
+  | not (Vector.null left) &&
+    (Vector.null right || l <= r) =
+    Just PassiveSet {
+      passiveset_best  = l,
+      passiveset_rule  = rule,
+      passiveset_left  = Vector.tail left,
+      passiveset_right = right }
+    -- In this case we must have not (Vector.null right).
+  | otherwise =
+    Just PassiveSet {
+      passiveset_best  = r,
+      passiveset_rule  = rule,
+      passiveset_left  = left,
+      passiveset_right = Vector.tail right }
+  where
+    l = unpack proxy rule True (Vector.head left)
+    r = unpack proxy rule False (Vector.head right)
+
+-- Unpack a triple into a Passive.
+{-# INLINEABLE unpack #-}
+unpack :: Params params => Proxy params -> Id params -> Bool -> (PackedScore params, PackedId params, Int32) -> Passive params
+unpack proxy rule isLeft (score, id, pos) =
+  Passive {
+    passive_score = unpackScore proxy score,
+    passive_rule1 = if isLeft then rule else rule',
+    passive_rule2 = if isLeft then rule' else rule,
+    passive_pos = fromIntegral pos }
+  where
+    rule' = unpackId proxy id
+
+-- Make a PassiveSet from a list of Passives.
+{-# INLINEABLE makePassiveSet #-}
+makePassiveSet :: forall params. Params params => Id params -> [Passive params] -> Maybe (PassiveSet params)
+makePassiveSet _ [] = Nothing
+makePassiveSet rule ps
+  | and [passive_rule2 p == rule | p <- right] =
+    mkPassiveSet proxy rule
+      (Vector.fromList (map (pack True) (sort left)))
+      (Vector.fromList (map (pack False) (sort right)))
+  | otherwise = error "rule id does not occur in passive"
+  where
+    proxy :: Proxy params
+    proxy = Proxy
+    
+    (left, right) = partition (\p -> passive_rule1 p == rule) ps
+    pack isLeft Passive{..} =
+      (packScore proxy passive_score,
+       packId proxy (if isLeft then passive_rule2 else passive_rule1),
+       fromIntegral passive_pos)
+
+-- Find and remove the best element from a PassiveSet.
+{-# INLINEABLE unconsPassiveSet #-}
+unconsPassiveSet :: forall params. Params params => PassiveSet params -> (Passive params, Maybe (PassiveSet params))
+unconsPassiveSet PassiveSet{..} =
+  (passiveset_best, mkPassiveSet (Proxy :: Proxy params) passiveset_rule passiveset_left passiveset_right)
+
+-- | A queued critical pair.
+data Passive params =
+  Passive {
+    -- | The score of this critical pair.
+    passive_score :: !(Score params),
+    -- | The rule which does the outermost rewrite in this critical pair.
+    passive_rule1 :: !(Id params),
+    -- | The rule which does the innermost rewrite in this critical pair.
+    passive_rule2 :: !(Id params),
+    -- | The position of the overlap. See 'Twee.CP.overlap_pos'.
+    passive_pos   :: {-# UNPACK #-} !Int }
+
+instance Params params => Eq (Passive params) where
+  x == y = compare x y == EQ
+
+instance Params params => Ord (Passive params) where
+  compare = comparing f
+    where
+      f Passive{..} =
+        (passive_score,
+         intMax (fromIntegral passive_rule1) (fromIntegral passive_rule2),
+         intMin (fromIntegral passive_rule1) (fromIntegral passive_rule2),
+         passive_pos)
+
+-- | The empty queue.
+empty :: Queue params
+empty = Queue Heap.empty
+
+-- | Add a set of 'Passive's to the queue.
+{-# INLINEABLE insert #-}
+insert :: Params params => Id params -> [Passive params] -> Queue params -> Queue params
+insert rule passives (Queue q) =
+  Queue $
+  case makePassiveSet rule passives of
+    Nothing -> q
+    Just p -> Heap.insert p q
+
+-- | Remove the minimum 'Passive' from the queue.
+{-# INLINEABLE removeMin #-}
+removeMin :: Params params => Queue params -> Maybe (Passive params, Queue params)
+removeMin (Queue q) = do
+  (passiveset, q) <- Heap.removeMin q
+  case unconsPassiveSet passiveset of
+    (passive, Just passiveset') ->
+      Just (passive, Queue (Heap.insert passiveset' q))
+    (passive, Nothing) ->
+      Just (passive, Queue q)
+
+-- | Map a function over all 'Passive's.
+{-# INLINEABLE mapMaybe #-}
+mapMaybe :: Params params => (Passive params -> Maybe (Passive params)) -> Queue params -> Queue params
+mapMaybe f (Queue q) = Queue (Heap.mapMaybe g q)
+  where
+    g PassiveSet{..} =
+      makePassiveSet passiveset_rule $ Data.Maybe.mapMaybe f $
+        passiveset_best:
+        map (unpack proxy passiveset_rule True) (Vector.toList passiveset_left) ++
+        map (unpack proxy passiveset_rule False) (Vector.toList passiveset_right)
+    proxy :: Proxy params
+    proxy = Proxy
diff --git a/Twee/Pretty.hs b/Twee/Pretty.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Pretty.hs
@@ -0,0 +1,182 @@
+-- | Pretty-printing of terms and assorted other values.
+
+{-# LANGUAGE Rank2Types #-}
+module Twee.Pretty(module Twee.Pretty, module Text.PrettyPrint.HughesPJClass, Pretty(..)) where
+
+import Text.PrettyPrint.HughesPJClass hiding (empty)
+import qualified Text.PrettyPrint.HughesPJClass as PP
+import qualified Data.Map as Map
+import Data.Map(Map)
+import qualified Data.Set as Set
+import Data.Set(Set)
+import Data.Ratio
+import Twee.Term
+
+-- * Miscellaneous 'Pretty' instances and utilities.
+
+-- | Print a value to the console.
+prettyPrint :: Pretty a => a -> IO ()
+prettyPrint x = putStrLn (prettyShow x)
+
+-- | The empty document. Used to avoid name clashes with 'Twee.Term.empty'.
+pPrintEmpty :: Doc
+pPrintEmpty = PP.empty
+
+instance Pretty Doc where pPrint = id
+
+-- | Print a tuple of values.
+pPrintTuple :: [Doc] -> Doc
+pPrintTuple = parens . fsep . punctuate comma
+
+instance Pretty a => Pretty (Set a) where
+  pPrint = pPrintSet . map pPrint . Set.toList
+
+-- | Print a set of vlaues.
+pPrintSet :: [Doc] -> Doc
+pPrintSet = braces . fsep . punctuate comma
+
+instance Pretty Var where
+  pPrint (V n) =
+    text $
+      vars !! (n `mod` length vars):
+      case n `div` length vars of
+        0 -> ""
+        m -> show (m+1)
+    where
+      vars = "XYZWVUTS"
+
+instance (Pretty k, Pretty v) => Pretty (Map k v) where
+  pPrint = pPrintSet . map binding . Map.toList
+    where
+      binding (x, v) = hang (pPrint x <+> text "=>") 2 (pPrint v)
+
+instance (Eq a, Integral a, Pretty a) => Pretty (Ratio a) where
+  pPrint a
+    | denominator a == 1 = pPrint (numerator a)
+    | otherwise = text "(" <+> pPrint (numerator a) <> text "/" <> pPrint (denominator a) <+> text ")"
+
+-- | Generate a list of candidate names for pretty-printing.
+supply :: [String] -> [String]
+supply names =
+  names ++
+  [ name ++ show i | i <- [2..], name <- names ]
+
+-- * Pretty-printing of terms.
+
+instance Pretty f => Pretty (Fun f) where
+  pPrintPrec l p = pPrintPrec l p . fun_value
+
+instance PrettyTerm f => PrettyTerm (Fun f) where
+  termStyle f = termStyle (fun_value f)
+
+instance PrettyTerm f => Pretty (Term f) where
+  pPrintPrec l p (Var x) = pPrintPrec l p x
+  pPrintPrec l p (App f xs) =
+    pPrintTerm (termStyle f) l p (pPrint f) (unpack xs)
+
+instance PrettyTerm f => Pretty (TermList f) where
+  pPrintPrec _ _ = pPrint . unpack
+
+instance PrettyTerm f => Pretty (Subst f) where
+  pPrint sub = text "{" <> fsep (punctuate (text ",") docs) <> text "}"
+    where
+      docs =
+        [ hang (pPrint x <+> text "->") 2 (pPrint t)
+        | (x, t) <- substToList sub ]
+
+-- | A class for customising the printing of function symbols.
+class Pretty f => PrettyTerm f where
+  -- | The style of the function symbol. Defaults to 'curried'.
+  termStyle :: f -> TermStyle
+  termStyle _ = curried
+
+-- | Defines how to print out a function symbol.
+newtype TermStyle =
+  TermStyle {
+    -- | Renders a function application.
+    -- Takes the following arguments in this order:
+    -- Pretty-printing level, current precedence,
+    -- pretty-printed function symbol and list of arguments to the function.
+    pPrintTerm :: forall a. Pretty a => PrettyLevel -> Rational -> Doc -> [a] -> Doc }
+
+invisible, curried, uncurried, prefix, postfix :: TermStyle
+
+-- | For operators like @$@ that should be printed as a blank space.
+invisible =
+  TermStyle $ \l p d ->
+    let
+      f [] = d
+      f [t] = pPrintPrec l p t
+      f (t:ts) =
+        maybeParens (p > 10) $
+          pPrint t <+>
+            (hsep (map (pPrintPrec l 11) ts))
+    in f
+
+-- | For functions that should be printed curried.
+curried =
+  TermStyle $ \l p d ->
+    let
+      f [] = d
+      f xs =
+        maybeParens (p > 10) $
+          d <+>
+            (hsep (map (pPrintPrec l 11) xs))
+    in f
+
+-- | For functions that should be printed uncurried.
+uncurried =
+  TermStyle $ \l _ d ->
+    let
+      f [] = d
+      f xs =
+        d <> parens (hsep (punctuate comma (map (pPrintPrec l 0) xs)))
+    in f
+
+-- | A helper function that deals with under- and oversaturated applications.
+fixedArity :: Int -> TermStyle -> TermStyle
+fixedArity arity style =
+  TermStyle $ \l p d ->
+    let
+      f xs
+        | length xs < arity = pPrintTerm curried l p (parens d) xs
+        | length xs > arity =
+            maybeParens (p > 10) $
+              hsep (pPrintTerm style l 11 d ys:
+                    map (pPrintPrec l 11) zs)
+        | otherwise = pPrintTerm style l p d xs
+        where
+          (ys, zs) = splitAt arity xs
+    in f
+
+-- | A helper function that drops a certain number of arguments.
+implicitArguments :: Int -> TermStyle -> TermStyle
+implicitArguments n (TermStyle pp) =
+  TermStyle $ \l p d xs -> pp l p d (drop n xs)
+
+-- | For prefix operators.
+prefix =
+  fixedArity 1 $
+  TermStyle $ \l _ d [x] ->
+    d <> pPrintPrec l 11 x
+
+-- | For postfix operators.
+postfix =
+  fixedArity 1 $
+  TermStyle $ \l _ d [x] ->
+    pPrintPrec l 11 x <> d
+
+-- | For infix operators.
+infixStyle :: Int -> TermStyle
+infixStyle pOp =
+  fixedArity 2 $
+  TermStyle $ \l p d [x, y] ->
+    maybeParens (p > fromIntegral pOp) $
+      pPrintPrec l (fromIntegral pOp+1) x <+> d <+>
+      pPrintPrec l (fromIntegral pOp+1) y
+
+-- | For tuples.
+tupleStyle :: TermStyle
+tupleStyle =
+  TermStyle $ \l _ _ xs ->
+    parens (hsep (punctuate comma (map (pPrintPrec l 0) xs)))
diff --git a/Twee/Proof.hs b/Twee/Proof.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Proof.hs
@@ -0,0 +1,723 @@
+-- | Equational proofs which are checked for correctedness.
+{-# LANGUAGE TypeFamilies, PatternGuards, RecordWildCards, ScopedTypeVariables #-}
+module Twee.Proof(
+  -- * Constructing proofs
+  Proof, Derivation(..), Lemma(..), Axiom(..),
+  certify, equation, derivation,
+  -- ** Smart constructors for derivations
+  lemma, axiom, symm, trans, cong, congPath,
+
+  -- * Analysing proofs
+  simplify, usedLemmas, usedAxioms, usedLemmasAndSubsts, usedAxiomsAndSubsts,
+
+  -- * Pretty-printing proofs
+  Config(..), defaultConfig, Presentation(..),
+  ProvedGoal(..), provedGoal, checkProvedGoal,
+  pPrintPresentation, present, describeEquation) where
+
+import Twee.Base hiding (invisible)
+import Twee.Equation
+import Twee.Utils
+import Control.Monad
+import Data.Maybe
+import Data.List
+import Data.Ord
+import qualified Data.Set as Set
+import qualified Data.Map.Strict as Map
+
+----------------------------------------------------------------------
+-- Equational proofs. Only valid proofs can be constructed.
+----------------------------------------------------------------------
+
+-- | A checked proof. If you have a value of type @Proof f@,
+-- it should jolly well represent a valid proof!
+--
+-- The only way to construct a @Proof f@ is by using 'certify'.
+data Proof f =
+  Proof {
+    equation   :: !(Equation f),
+    derivation :: !(Derivation f) }
+  deriving (Eq, Show)
+
+-- | A derivation is an unchecked proof. It might be wrong!
+-- The way to check it is to call 'certify' to turn it into a 'Proof'.
+data Derivation f =
+    -- | Apply an existing rule (with proof!) to the root of a term
+    UseLemma {-# UNPACK #-} !(Lemma f) !(Subst f)
+    -- | Apply an axiom to the root of a term
+  | UseAxiom {-# UNPACK #-} !(Axiom f) !(Subst f)
+    -- | Reflexivity. @'Refl' t@ proves @t = t@.
+  | Refl !(Term f)
+    -- | Symmetry
+  | Symm !(Derivation f)
+    -- | Transivitity
+  | Trans !(Derivation f) !(Derivation f)
+    -- | Congruence.
+    -- Parallel, i.e., takes a function symbol and one derivation for each
+    -- argument of that function.
+  | Cong {-# UNPACK #-} !(Fun f) ![Derivation f]
+  deriving (Eq, Show)
+
+-- | A lemma, which includes a proof.
+data Lemma f =
+  Lemma {
+    -- | The id number of the lemma.
+    -- Has no semantic meaning; for convenience only.
+    lemma_id :: {-# UNPACK #-} !Id,
+    -- | A proof of the lemma.
+    lemma_proof :: !(Proof f) }
+  deriving Show
+
+--  | An axiom, which comes without proof.
+data Axiom f =
+  Axiom {
+    -- | The number of the axiom.
+    -- Has no semantic meaning; for convenience only.
+    axiom_number :: {-# UNPACK #-} !Int,
+    -- | A description of the axiom.
+    -- Has no semantic meaning; for convenience only.
+    axiom_name :: !String,
+    -- | The equation which the axiom asserts.
+    axiom_eqn :: !(Equation f) }
+  deriving (Eq, Ord, Show)
+
+-- | Checks a 'Derivation' and, if it is correct, returns a
+-- certified 'Proof'.
+--
+-- If the 'Derivation' is incorrect, throws an exception.
+
+-- This is the trusted core of the module.
+{-# INLINEABLE certify #-}
+certify :: PrettyTerm f => Derivation f -> Proof f
+certify p =
+  {-# SCC certify #-}
+  case check p of
+    Nothing -> error ("Invalid proof created!\n" ++ prettyShow p)
+    Just eqn -> Proof eqn p
+  where
+    check (UseLemma Lemma{..} sub) =
+      return (subst sub (equation lemma_proof))
+    check (UseAxiom Axiom{..} sub) =
+      return (subst sub axiom_eqn)
+    check (Refl t) =
+      return (t :=: t)
+    check (Symm p) = do
+      t :=: u <- check p
+      return (u :=: t)
+    check (Trans p q) = do
+      t :=: u1 <- check p
+      u2 :=: v <- check q
+      guard (u1 == u2)
+      return (t :=: v)
+    check (Cong f ps) = do
+      eqns <- mapM check ps
+      return
+        (build (app f (map eqn_lhs eqns)) :=:
+         build (app f (map eqn_rhs eqns)))
+
+----------------------------------------------------------------------
+-- Everything below this point need not be trusted, since all proof
+-- construction goes through the "proof" function.
+----------------------------------------------------------------------
+
+-- Typeclass instances.
+instance Eq (Lemma f) where
+  x == y = compare x y == EQ
+instance Ord (Lemma f) where
+  compare =
+    comparing (\x ->
+      -- Don't look into lemma proofs when comparing derivations,
+      -- to avoid exponential blowup
+      (lemma_id x, equation (lemma_proof x)))
+
+instance Symbolic (Derivation f) where
+  type ConstantOf (Derivation f) = f
+  termsDL (UseLemma _ sub) = termsDL sub
+  termsDL (UseAxiom _ sub) = termsDL sub
+  termsDL (Refl t) = termsDL t
+  termsDL (Symm p) = termsDL p
+  termsDL (Trans p q) = termsDL p `mplus` termsDL q
+  termsDL (Cong _ ps) = termsDL ps
+
+  subst_ sub (UseLemma lemma s) = UseLemma lemma (subst_ sub s)
+  subst_ sub (UseAxiom axiom s) = UseAxiom axiom (subst_ sub s)
+  subst_ sub (Refl t) = Refl (subst_ sub t)
+  subst_ sub (Symm p) = symm (subst_ sub p)
+  subst_ sub (Trans p q) = trans (subst_ sub p) (subst_ sub q)
+  subst_ sub (Cong f ps) = cong f (subst_ sub ps)
+
+instance Function f => Pretty (Proof f) where
+  pPrint = pPrintLemma defaultConfig prettyShow
+instance PrettyTerm f => Pretty (Derivation f) where
+  pPrint (UseLemma lemma sub) =
+    text "subst" <> pPrintTuple [pPrint lemma, pPrint sub]
+  pPrint (UseAxiom axiom sub) =
+    text "subst" <> pPrintTuple [pPrint axiom, pPrint sub]
+  pPrint (Refl t) =
+    text "refl" <> pPrintTuple [pPrint t]
+  pPrint (Symm p) =
+    text "symm" <> pPrintTuple [pPrint p]
+  pPrint (Trans p q) =
+    text "trans" <> pPrintTuple [pPrint p, pPrint q]
+  pPrint (Cong f ps) =
+    text "cong" <> pPrintTuple (pPrint f:map pPrint ps)
+
+instance PrettyTerm f => Pretty (Axiom f) where
+  pPrint Axiom{..} =
+    text "axiom" <>
+    pPrintTuple [pPrint axiom_number, text axiom_name, pPrint axiom_eqn]
+
+instance PrettyTerm f => Pretty (Lemma f) where
+  pPrint Lemma{..} =
+    text "lemma" <>
+    pPrintTuple [pPrint lemma_id, pPrint (equation lemma_proof)]
+
+-- | Simplify a derivation.
+--
+-- After simplification, a derivation has the following properties:
+--
+--   * 'Symm' is pushed down next to 'Lemma' and 'Axiom'
+--   * 'Refl' only occurs inside 'Cong' or at the top level
+--   * 'Trans' is right-associated and is pushed inside 'Cong' if possible
+simplify :: Minimal f => (Lemma f -> Maybe (Derivation f)) -> Derivation f -> Derivation f
+simplify lem p = simp p
+  where
+    simp p@(UseLemma lemma sub) =
+      case lem lemma of
+        Nothing -> p
+        Just q ->
+          let
+            -- Get rid of any variables that are not bound by sub
+            -- (e.g., ones which only occur internally in q)
+            dead = usort (vars q) \\ substDomain sub
+          in simp (subst sub (erase dead q))
+    simp (Symm p) = symm (simp p)
+    simp (Trans p q) = trans (simp p) (simp q)
+    simp (Cong f ps) = cong f (map simp ps)
+    simp p = p
+
+lemma :: Lemma f -> Subst f -> Derivation f
+lemma lem@Lemma{..} sub = UseLemma lem sub
+
+axiom :: Axiom f -> Derivation f
+axiom ax@Axiom{..} =
+  UseAxiom ax $
+    fromJust $
+    listToSubst [(x, build (var x)) | x <- vars axiom_eqn]
+
+symm :: Derivation f -> Derivation f
+symm (Refl t) = Refl t
+symm (Symm p) = p
+symm (Trans p q) = trans (symm q) (symm p)
+symm (Cong f ps) = cong f (map symm ps)
+symm p = Symm p
+
+trans :: Derivation f -> Derivation f -> Derivation f
+trans Refl{} p = p
+trans p Refl{} = p
+trans (Trans p q) r =
+  -- Right-associate uses of transitivity.
+  -- p cannot be a Trans (if it was created with the smart
+  -- constructors) but q could be.
+  Trans p (trans q r)
+-- Collect adjacent uses of congruence.
+trans (Cong f ps) (Cong g qs) | f == g =
+  transCong f ps qs
+trans (Cong f ps) (Trans (Cong g qs) r) | f == g =
+  trans (transCong f ps qs) r
+trans p q = Trans p q
+
+transCong :: Fun f -> [Derivation f] -> [Derivation f] -> Derivation f
+transCong f ps qs =
+  cong f (zipWith trans ps qs)
+
+cong :: Fun f -> [Derivation f] -> Derivation f
+cong f ps =
+  case sequence (map unRefl ps) of
+    Nothing -> Cong f ps
+    Just ts -> Refl (build (app f ts))
+  where
+    unRefl (Refl t) = Just t
+    unRefl _ = Nothing
+
+-- | Find all lemmas which are used in a derivation.
+usedLemmas :: Derivation f -> [Lemma f]
+usedLemmas p = map fst (usedLemmasAndSubsts p)
+
+-- | Find all lemmas which are used in a derivation,
+-- together with the substitutions used.
+usedLemmasAndSubsts :: Derivation f -> [(Lemma f, Subst f)]
+usedLemmasAndSubsts p = lem p []
+  where
+    lem (UseLemma lemma sub) = ((lemma, sub):)
+    lem (Symm p) = lem p
+    lem (Trans p q) = lem p . lem q
+    lem (Cong _ ps) = foldr (.) id (map lem ps)
+    lem _ = id
+
+-- | Find all axioms which are used in a derivation.
+usedAxioms :: Derivation f -> [Axiom f]
+usedAxioms p = map fst (usedAxiomsAndSubsts p)
+
+-- | Find all axioms which are used in a derivation,
+-- together with the substitutions used.
+usedAxiomsAndSubsts :: Derivation f -> [(Axiom f, Subst f)]
+usedAxiomsAndSubsts p = ax p []
+  where
+    ax (UseAxiom axiom sub) = ((axiom, sub):)
+    ax (Symm p) = ax p
+    ax (Trans p q) = ax p . ax q
+    ax (Cong _ ps) = foldr (.) id (map ax ps)
+    ax _ = id
+
+-- | Applies a derivation at a particular path in a term.
+congPath :: [Int] -> Term f -> Derivation f -> Derivation f
+congPath [] _ p = p
+congPath (n:ns) (App f t) p | n <= length ts =
+  cong f $
+    map Refl (take n ts) ++
+    [congPath ns (ts !! n) p] ++
+    map Refl (drop (n+1) ts)
+  where
+    ts = unpack t
+congPath _ _ _ = error "bad path"
+
+----------------------------------------------------------------------
+-- Pretty-printing of proofs.
+----------------------------------------------------------------------
+
+-- | Options for proof presentation.
+data Config =
+  Config {
+    -- | Never inline lemmas.
+    cfg_all_lemmas :: !Bool,
+    -- | Inline all lemmas.
+    cfg_no_lemmas :: !Bool,
+    -- | Print out explicit substitutions.
+    cfg_show_instances :: !Bool }
+
+-- | The default configuration.
+defaultConfig :: Config
+defaultConfig =
+  Config {
+    cfg_all_lemmas = False,
+    cfg_no_lemmas = False,
+    cfg_show_instances = False }
+
+-- | A proof, with all axioms and lemmas explicitly listed.
+data Presentation f =
+  Presentation {
+    -- | The used axioms.
+    pres_axioms :: [Axiom f],
+    -- | The used lemmas.
+    pres_lemmas :: [Lemma f],
+    -- | The goals proved.
+    pres_goals  :: [ProvedGoal f] }
+  deriving Show
+
+-- Note: only the pg_proof field should be trusted!
+-- The remaining fields are for information only.
+data ProvedGoal f =
+  ProvedGoal {
+    pg_number  :: Int,
+    pg_name    :: String,
+    pg_proof   :: Proof f,
+
+    -- Extra fields for existentially-quantified goals, giving the original goal
+    -- and the existential witness. These fields are not verified. If you want
+    -- to check them, use checkProvedGoal.
+    --
+    -- In general, subst pg_witness_hint pg_goal_hint == equation pg_proof.
+    -- For non-existential goals, pg_goal_hint == equation pg_proof
+    -- and pg_witness_hint is the empty substitution.
+    pg_goal_hint    :: Equation f,
+    pg_witness_hint :: Subst f }
+  deriving Show
+
+-- | Construct a @ProvedGoal@.
+provedGoal :: Int -> String -> Proof f -> ProvedGoal f
+provedGoal number name proof =
+  ProvedGoal {
+    pg_number = number,
+    pg_name = name,
+    pg_proof = proof,
+    pg_goal_hint = equation proof,
+    pg_witness_hint = emptySubst }
+
+-- | Check that pg_goal/pg_witness match up with pg_proof.
+checkProvedGoal :: Function f => ProvedGoal f -> ProvedGoal f
+checkProvedGoal pg@ProvedGoal{..}
+  | subst pg_witness_hint pg_goal_hint == equation pg_proof =
+    pg
+  | otherwise =
+    error $ show $
+      text "Invalid ProvedGoal!" $$
+      text "Claims to prove" <+> pPrint pg_goal_hint $$
+      text "with witness" <+> pPrint pg_witness_hint <> text "," $$
+      text "but actually proves" <+> pPrint (equation pg_proof)
+
+instance Function f => Pretty (Presentation f) where
+  pPrint = pPrintPresentation defaultConfig
+
+-- | Simplify and present a proof.
+present :: Function f => Config -> [ProvedGoal f] -> Presentation f
+present config goals =
+  -- First find all the used lemmas, then hand off to presentWithGoals
+  presentWithGoals config goals
+    (used Set.empty (concatMap (usedLemmas . derivation . pg_proof) goals))
+  where
+    used lems [] = Set.elems lems
+    used lems (x:xs)
+      | x `Set.member` lems = used lems xs
+      | otherwise =
+        used (Set.insert x lems)
+          (usedLemmas (derivation (lemma_proof x)) ++ xs)
+
+presentWithGoals ::
+  Function f =>
+  Config -> [ProvedGoal f] -> [Lemma f] -> Presentation f
+presentWithGoals config@Config{..} goals lemmas
+  -- We inline a lemma if one of the following holds:
+  --   * It only has one step
+  --   * It is subsumed by an earlier lemma
+  --   * It is only used once
+  --   * It has to do with $equals (for printing of the goal proof)
+  --   * The option cfg_no_lemmas is true
+  -- First we compute all inlinings, then apply simplify to remove them,
+  -- then repeat if any lemma was inlined
+  | Map.null inlinings =
+    let
+      axioms = usort $
+        concatMap (usedAxioms . derivation . pg_proof) goals ++
+        concatMap (usedAxioms . derivation . lemma_proof) lemmas
+    in
+      Presentation axioms
+        [ lemma { lemma_proof = flattenProof lemma_proof }
+        | lemma@Lemma{..} <- lemmas ]
+        [ decodeGoal (goal { pg_proof = flattenProof pg_proof })
+        | goal@ProvedGoal{..} <- goals ]
+
+  | otherwise =
+    let
+      inline lemma = Map.lookup lemma inlinings
+
+      goals' =
+        [ decodeGoal (goal { pg_proof = certify $ simplify inline (derivation pg_proof) })
+        | goal@ProvedGoal{..} <- goals ]
+      lemmas' =
+        [ Lemma n (certify $ simplify inline (derivation p))
+        | lemma@(Lemma n p) <- lemmas, not (lemma `Map.member` inlinings) ]
+    in
+      presentWithGoals config goals' lemmas'
+
+  where
+    inlinings =
+      Map.fromList
+        [ (lemma, p)
+        | lemma <- lemmas, Just p <- [tryInline lemma]]
+
+    tryInline (Lemma n p)
+      | shouldInline n p = Just (derivation p)
+    tryInline (Lemma n p)
+      -- Check for subsumption by an earlier lemma
+      | Just (Lemma m q) <- Map.lookup (canonicalise (t :=: u)) equations, m < n =
+        Just (subsume p (derivation q))
+      | Just (Lemma m q) <- Map.lookup (canonicalise (u :=: t)) equations, m < n =
+        Just (subsume p (Symm (derivation q)))
+      where
+        t :=: u = equation p
+    tryInline _ = Nothing
+
+    shouldInline n p =
+      cfg_no_lemmas ||
+      oneStep (derivation p) ||
+      (not cfg_all_lemmas &&
+       (isJust (decodeEquality (eqn_lhs (equation p))) ||
+        isJust (decodeEquality (eqn_rhs (equation p))) ||
+        Map.lookup n uses == Just 1))
+  
+    subsume p q =
+      -- Rename q so its variables match p's
+      subst sub q
+      where
+        t  :=: u  = equation p
+        t' :=: u' = equation (certify q)
+        Just sub  = matchList (buildList [t', u']) (buildList [t, u])
+
+    -- Record which lemma proves each equation
+    equations =
+      Map.fromList
+        [ (canonicalise (equation lemma_proof), lemma)
+        | lemma@Lemma{..} <- lemmas]
+
+    -- Count how many times each lemma is used
+    uses =
+      Map.fromListWith (+)
+        [ (lemma_id, 1)
+        | Lemma{..} <-
+            concatMap usedLemmas
+              (map (derivation . pg_proof) goals ++
+               map (derivation . lemma_proof) lemmas) ]
+
+    -- Check if a proof only has one step.
+    -- Trans only occurs at the top level by this point.
+    oneStep Trans{} = False
+    oneStep _ = True
+
+invisible :: Function f => Equation f -> Bool
+invisible (t :=: u) = show (pPrint t) == show (pPrint u)
+
+-- Pretty-print the proof of a single lemma.
+pPrintLemma :: Function f => Config -> (Id -> String) -> Proof f -> Doc
+pPrintLemma Config{..} lemmaName p =
+  ppTerm (eqn_lhs (equation q)) $$ pp (derivation q)
+  where
+    q = flattenProof p
+
+    pp (Trans p q) = pp p $$ pp q
+    pp p | invisible (equation (certify p)) = pPrintEmpty
+    pp p =
+      (text "= { by" <+>
+       ppStep
+         (nub (map (show . ppLemma) (usedLemmasAndSubsts p)) ++
+          nub (map (show . ppAxiom) (usedAxiomsAndSubsts p))) <+>
+       text "}" $$
+       ppTerm (eqn_rhs (equation (certify p))))
+
+    ppTerm t = text "  " <> pPrint t
+
+    ppStep [] = text "reflexivity" -- ??
+    ppStep [x] = text x
+    ppStep xs =
+      hcat (punctuate (text ", ") (map text (init xs))) <+>
+      text "and" <+>
+      text (last xs)
+
+    ppLemma (Lemma{..}, sub) =
+      text "lemma" <+> text (lemmaName lemma_id) <> showSubst sub
+    ppAxiom (Axiom{..}, sub) =
+      text "axiom" <+> pPrint axiom_number <+> parens (text axiom_name) <> showSubst sub
+
+    showSubst sub
+      | cfg_show_instances && not (null (substToList sub)) =
+        text " with " <>
+        fsep (punctuate comma
+          [ pPrint x <+> text "->" <+> pPrint t
+          | (x, t) <- substToList sub ])
+      | otherwise = pPrintEmpty
+
+-- Transform a proof so that each step uses exactly one axiom
+-- or lemma. The proof will have the following form afterwards:
+--   * Trans only occurs at the outermost level and is right-associated
+--   * Each Cong has exactly one non-Refl argument (no parallel rewriting)
+--   * Symm only occurs innermost, i.e., next to UseLemma or UseAxiom
+--   * Refl only occurs as an argument to Cong, or outermost if the
+--     whole proof is a single reflexivity step
+flattenProof :: Function f => Proof f -> Proof f
+flattenProof =
+  certify . flat . simplify (const Nothing) . derivation
+  where
+    flat (Trans p q) = trans (flat p) (flat q)
+    flat p@(Cong f ps) =
+      foldr trans (reflAfter p)
+        [ Cong f $
+            map reflAfter (take i ps) ++
+            [p] ++
+            map reflBefore (drop (i+1) ps)
+        | (i, q) <- zip [0..] qs,
+          p <- steps q ]
+      where
+        qs = map flat ps
+    flat p = p
+
+    reflBefore p = Refl (eqn_lhs (equation (certify p)))
+    reflAfter p  = Refl (eqn_rhs (equation (certify p)))
+
+    steps Refl{} = []
+    steps (Trans p q) = steps p ++ steps q
+    steps p = [p]
+
+    trans (Trans p q) r = trans p (trans q r)
+    trans Refl{} p = p
+    trans p Refl{} = p
+    trans p q =
+      case strip q of
+        Nothing -> Trans p q
+        Just q' -> trans p q'
+
+    strip p
+      | t == u = Just (Refl t)
+      | otherwise = strip' t p
+      where
+        t :=: u = equation (certify p)
+    strip' t (Trans _ q)
+      | eqn_lhs (equation (certify q)) == t = Just q
+      | otherwise = strip' t q
+    strip' _ _ = Nothing
+
+-- Transform a derivation into a list of single steps.
+-- Each step has the following form:
+--   * Trans does not occur
+--   * Symm only occurs innermost, i.e., next to UseLemma or UseAxiom
+--   * Each Cong has exactly one non-Refl argument (no parallel rewriting)
+--   * Refl only occurs as an argument to Cong
+derivSteps :: Function f => Derivation f -> [Derivation f]
+derivSteps = steps . derivation . flattenProof . certify
+  where
+    steps Refl{} = []
+    steps (Trans p q) = steps p ++ steps q
+    steps p = [p]
+
+-- | Print a presented proof.
+pPrintPresentation :: forall f. Function f => Config -> Presentation f -> Doc
+pPrintPresentation config (Presentation axioms lemmas goals) =
+  vcat $ intersperse (text "") $
+    vcat [ describeEquation "Axiom" (show n) (Just name) eqn
+         | Axiom n name eqn <- axioms,
+           not (invisible eqn) ]:
+    [ pp "Lemma" (num n) Nothing (equation p) emptySubst p
+    | Lemma n p <- lemmas,
+      not (invisible (equation p)) ] ++
+    [ pp "Goal" (show num) (Just pg_name) pg_goal_hint pg_witness_hint pg_proof
+    | (num, ProvedGoal{..}) <- zip [1..] goals ]
+  where
+    pp kind n mname eqn witness p =
+      describeEquation kind n mname eqn $$
+      ppWitness witness $$
+      text "Proof:" $$
+      pPrintLemma config num p
+
+    num x = show (fromJust (Map.lookup x nums))
+    nums = Map.fromList (zip (map lemma_id lemmas) [n+1 ..])
+    n = maximum $ 0:map axiom_number axioms
+
+    ppWitness sub
+      | sub == emptySubst = pPrintEmpty
+      | otherwise =
+          vcat [
+            text "The goal is true when:",
+            nest 2 $ vcat
+              [ pPrint x <+> text "=" <+> pPrint t
+              | (x, t) <- substToList sub ],
+            if minimal `elem` funs sub then
+              text "where" <+> doubleQuotes (pPrint (minimal :: Fun f)) <+>
+              text "stands for an arbitrary term of your choice."
+            else pPrintEmpty,
+            text ""]
+
+-- | Format an equation nicely.
+--
+-- Used both here and in the main file.
+describeEquation ::
+  PrettyTerm f =>
+  String -> String -> Maybe String -> Equation f -> Doc
+describeEquation kind num mname eqn =
+  text kind <+> text num <>
+  (case mname of
+     Nothing -> text ""
+     Just name -> text (" (" ++ name ++ ")")) <>
+  text ":" <+> pPrint eqn <> text "."
+
+----------------------------------------------------------------------
+-- Making proofs of existential goals more readable.
+----------------------------------------------------------------------
+
+-- The idea: the only axioms which mention $equals, $true and $false
+-- are:
+--   * $equals(x,x) = $true  (reflexivity)
+--   * $equals(t,u) = $false (conjecture)
+-- This implies that a proof $true = $false must have the following
+-- structure, if we expand out all lemmas:
+--   $true = $equals(s,s) = ... = $equals(t,u) = $false.
+--
+-- The substitution in the last step $equals(t,u) = $false is in fact the
+-- witness to the existential.
+--
+-- Furthermore, we can make it so that the inner "..." doesn't use the $equals
+-- axioms. If it does, one of the "..." steps results in either $true or $false,
+-- and we can chop off everything before the $true or after the $false.
+--
+-- Once we have done that, every proof step in the "..." must be a congruence
+-- step of the shape
+--   $equals(t, u) = $equals(v, w).
+-- This is because there are no other axioms which mention $equals. Hence we can
+-- split the proof of $equals(s,s) = $equals(t,u) into separate proofs of s=t
+-- and s=u.
+--
+-- What we have got out is:
+--   * the witness to the existential
+--   * a proof that both sides of the conjecture are equal
+-- and we can present that to the user.
+
+-- Decode $equals(t,u) into an equation t=u.
+decodeEquality :: Function f => Term f -> Maybe (Equation f)
+decodeEquality (App equals (Cons t (Cons u Empty)))
+  | isEquals equals = Just (t :=: u)
+decodeEquality _ = Nothing
+
+-- Tries to transform a proof of $true = $false into a proof of
+-- the original existentially-quantified formula.
+decodeGoal :: Function f => ProvedGoal f -> ProvedGoal f
+decodeGoal pg =
+  case maybeDecodeGoal pg of
+    Nothing -> pg
+    Just (name, witness, goal, deriv) ->
+      checkProvedGoal $
+      pg {
+        pg_name = name,
+        pg_proof = certify deriv,
+        pg_goal_hint = goal,
+        pg_witness_hint = witness }
+
+maybeDecodeGoal :: forall f. Function f =>
+  ProvedGoal f -> Maybe (String, Subst f, Equation f, Derivation f)
+maybeDecodeGoal ProvedGoal{..}
+  --  N.B. presentWithGoals takes care of expanding any lemma which mentions
+  --  $equals, and flattening the proof.
+  | isFalseTerm u = extract (derivSteps deriv)
+    -- Orient the equation so that $false is the RHS.
+  | isFalseTerm t = extract (derivSteps (symm deriv))
+  | otherwise = Nothing
+  where
+    isFalseTerm, isTrueTerm :: Term f -> Bool
+    isFalseTerm (App false _) = isFalse false
+    isFalseTerm _ = False
+    isTrueTerm (App true _) = isTrue true
+    isTrueTerm _ = False
+
+    t :=: u = equation pg_proof
+    deriv = derivation pg_proof
+
+    -- Detect $true = $equals(t, t).
+    decodeReflexivity :: Derivation f -> Maybe (Term f)
+    decodeReflexivity (Symm (UseAxiom Axiom{..} sub)) = do
+      guard (isTrueTerm (eqn_rhs axiom_eqn))
+      (t :=: u) <- decodeEquality (eqn_lhs axiom_eqn)
+      guard (t == u)
+      return (subst sub t)
+    decodeReflexivity _ = Nothing
+
+    -- Detect $equals(t, u) = $false.
+    decodeConjecture :: Derivation f -> Maybe (String, Equation f, Subst f)
+    decodeConjecture (UseAxiom Axiom{..} sub) = do
+      guard (isFalseTerm (eqn_rhs axiom_eqn))
+      eqn <- decodeEquality (eqn_lhs axiom_eqn)
+      return (axiom_name, eqn, sub)
+    decodeConjecture _ = Nothing
+
+    extract (p:ps) = do
+      -- Start by finding $true = $equals(t,u).
+      t <- decodeReflexivity p
+      cont (Refl t) (Refl t) ps
+    extract [] = Nothing
+
+    cont p1 p2 (p:ps)
+      | Just t <- decodeReflexivity p =
+        cont (Refl t) (Refl t) ps
+      | Just (name, eqn, sub) <- decodeConjecture p =
+        -- If p1: s=t and p2: s=u
+        -- then symm p1 `trans` p2: t=u.
+        return (name, sub, eqn, symm p1 `trans` p2)
+      | Cong eq [p1', p2'] <- p, isEquals eq =
+        cont (p1 `trans` p1') (p2 `trans` p2') ps
+    cont _ _ _ = Nothing
diff --git a/Twee/Rule.hs b/Twee/Rule.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Rule.hs
@@ -0,0 +1,488 @@
+-- | Term rewriting.
+{-# LANGUAGE TypeFamilies, FlexibleContexts, RecordWildCards, BangPatterns, OverloadedStrings, MultiParamTypeClasses, ScopedTypeVariables, GeneralizedNewtypeDeriving #-}
+module Twee.Rule where
+
+import Twee.Base
+import Twee.Constraints
+import qualified Twee.Index as Index
+import Twee.Index(Index)
+import Control.Monad
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.State.Strict
+import Data.Maybe
+import Data.List
+import Twee.Utils
+import qualified Data.Set as Set
+import Data.Set(Set)
+import qualified Twee.Term as Term
+import Data.Ord
+import Twee.Equation
+import qualified Twee.Proof as Proof
+import Twee.Proof(Derivation, Lemma(..))
+import Data.Tuple
+
+--------------------------------------------------------------------------------
+-- * Rewrite rules.
+--------------------------------------------------------------------------------
+
+-- | A rewrite rule.
+data Rule f =
+  Rule {
+    -- | Information about whether and how the rule is oriented.
+    orientation :: !(Orientation f),
+    -- Invariant:
+    -- For oriented rules: vars rhs `isSubsetOf` vars lhs
+    -- For unoriented rules: vars lhs == vars rhs
+
+    -- | The left-hand side of the rule.
+    lhs :: {-# UNPACK #-} !(Term f),
+    -- | The right-hand side of the rule.
+    rhs :: {-# UNPACK #-} !(Term f) }
+  deriving (Eq, Ord, Show)
+type RuleOf a = Rule (ConstantOf a)
+
+-- | A rule's orientation.
+--
+-- 'Oriented' and 'WeaklyOriented' rules are used only left-to-right.
+-- 'Permutative' and 'Unoriented' rules are used bidirectionally.
+data Orientation f =
+    -- | An oriented rule.
+    Oriented
+    -- | A weakly oriented rule.
+    -- The first argument is the minimal constant, the second argument is a list
+    -- of terms which are weakly oriented in the rule.
+    -- 
+    -- A rule with orientation @'WeaklyOriented' k ts@ can be used unless
+    -- all terms in @ts@ are equal to @k@.
+  | WeaklyOriented {-# UNPACK #-} !(Fun f) [Term f]
+    -- | A permutative rule.
+    --
+    -- A rule with orientation @'Permutative' ts@ can be used if
+    -- @map fst ts@ is lexicographically greater than @map snd ts@.
+  | Permutative [(Term f, Term f)]
+    -- | An unoriented rule.
+  | Unoriented
+  deriving Show
+
+instance Eq (Orientation f) where _ == _ = True
+instance Ord (Orientation f) where compare _ _ = EQ
+
+-- | Is a rule oriented or weakly oriented?
+oriented :: Orientation f -> Bool
+oriented Oriented{} = True
+oriented WeaklyOriented{} = True
+oriented _ = False
+
+-- | Is a rule weakly oriented?
+weaklyOriented :: Orientation f -> Bool
+weaklyOriented WeaklyOriented{} = True
+weaklyOriented _ = False
+
+instance Symbolic (Rule f) where
+  type ConstantOf (Rule f) = f
+  termsDL (Rule or t u) = termsDL or `mplus` termsDL t `mplus` termsDL u
+  subst_ sub (Rule or t u) = Rule (subst_ sub or) (subst_ sub t) (subst_ sub u)
+
+instance f ~ g => Has (Rule f) (Term g) where
+  the = lhs
+
+instance Symbolic (Orientation f) where
+  type ConstantOf (Orientation f) = f
+
+  termsDL Oriented = mzero
+  termsDL (WeaklyOriented _ ts) = termsDL ts
+  termsDL (Permutative ts) = termsDL ts
+  termsDL Unoriented = mzero
+
+  subst_ _   Oriented = Oriented
+  subst_ sub (WeaklyOriented min ts) = WeaklyOriented min (subst_ sub ts)
+  subst_ sub (Permutative ts) = Permutative (subst_ sub ts)
+  subst_ _   Unoriented = Unoriented
+
+instance PrettyTerm f => Pretty (Rule f) where
+  pPrint (Rule or l r) =
+    pPrint l <+> text (showOrientation or) <+> pPrint r
+    where
+      showOrientation Oriented = "->"
+      showOrientation WeaklyOriented{} = "~>"
+      showOrientation Permutative{} = "<->"
+      showOrientation Unoriented = "="
+
+-- | Turn a rule into an equation.
+unorient :: Rule f -> Equation f
+unorient (Rule _ l r) = l :=: r
+
+-- | Turn an equation t :=: u into a rule t -> u by computing the
+-- orientation info (e.g. oriented, permutative or unoriented).
+--
+-- Crashes if t -> u is not a valid rule, for example if there is
+-- a variable in @u@ which is not in @t@. To prevent this happening,
+-- combine with 'Twee.CP.split'.
+orient :: Function f => Equation f -> Rule f
+orient (t :=: u) = Rule o t u
+  where
+    o | lessEq u t =
+        case unify t u of
+          Nothing -> Oriented
+          Just sub
+            | allSubst (\_ (Cons t Empty) -> isMinimal t) sub ->
+              WeaklyOriented minimal (map (build . var . fst) (substToList sub))
+            | otherwise -> Unoriented
+      | lessEq t u = error "wrongly-oriented rule"
+      | not (null (usort (vars u) \\ usort (vars t))) =
+        error "unbound variables in rule"
+      | Just ts <- evalStateT (makePermutative t u) [],
+        permutativeOK t u ts =
+        Permutative ts
+      | otherwise = Unoriented
+
+    permutativeOK _ _ [] = True
+    permutativeOK t u ((Var x, Var y):xs) =
+      lessIn model u t == Just Strict &&
+      permutativeOK t' u' xs
+      where
+        model = modelFromOrder [Variable y, Variable x]
+        sub x' = if x == x' then var y else var x'
+        t' = subst sub t
+        u' = subst sub u
+
+    makePermutative t u = do
+      msub <- gets listToSubst
+      sub  <- lift msub
+      aux (subst sub t) (subst sub u)
+        where
+          aux (Var x) (Var y)
+            | x == y = return []
+            | otherwise = do
+              modify ((x, build $ var y):)
+              return [(build $ var x, build $ var y)]
+
+          aux (App f ts) (App g us)
+            | f == g =
+              fmap concat (zipWithM makePermutative (unpack ts) (unpack us))
+
+          aux _ _ = mzero
+
+-- | Flip an unoriented rule so that it goes right-to-left.
+backwards :: Rule f -> Rule f
+backwards (Rule or t u) = Rule (back or) u t
+  where
+    back (Permutative xs) = Permutative (map swap xs)
+    back Unoriented = Unoriented
+    back _ = error "Can't turn oriented rule backwards"
+
+--------------------------------------------------------------------------------
+-- * Extra-fast rewriting, without proof output or unorientable rules.
+--------------------------------------------------------------------------------
+
+-- | Compute the normal form of a term wrt only oriented rules.
+{-# INLINEABLE simplify #-}
+simplify :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Term f
+simplify !idx !t = {-# SCC simplify #-} simplify1 idx t
+
+{-# INLINEABLE simplify1 #-}
+simplify1 :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Term f
+simplify1 idx t
+  | t == u = t
+  | otherwise = simplify idx u
+  where
+    u = build (simp (singleton t))
+
+    simp Empty = mempty
+    simp (Cons (Var x) t) = var x `mappend` simp t
+    simp (Cons t u)
+      | Just (rule, sub) <- simpleRewrite idx t =
+        Term.subst sub (rhs rule) `mappend` simp u
+    simp (Cons (App f ts) us) =
+      app f (simp ts) `mappend` simp us
+
+-- | Check if a term can be simplified.
+{-# INLINEABLE canSimplify #-}
+canSimplify :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Bool
+canSimplify idx t = canSimplifyList idx (singleton t)
+
+{-# INLINEABLE canSimplifyList #-}
+canSimplifyList :: (Function f, Has a (Rule f)) => Index f a -> TermList f -> Bool
+canSimplifyList idx t =
+  {-# SCC canSimplifyList #-}
+  any (isJust . simpleRewrite idx) (filter isApp (subtermsList t))
+
+-- | Find a simplification step that applies to a term.
+{-# INLINEABLE simpleRewrite #-}
+simpleRewrite :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Maybe (Rule f, Subst f)
+simpleRewrite idx t =
+  -- Use instead of maybeToList to make fusion work
+  foldr (\x _ -> Just x) Nothing $ do
+    rule <- the <$> Index.approxMatches t idx
+    guard (oriented (orientation rule))
+    sub <- maybeToList (match (lhs rule) t)
+    guard (reducesOriented rule sub)
+    return (rule, sub)
+
+--------------------------------------------------------------------------------
+-- * Rewriting, with proof output.
+--------------------------------------------------------------------------------
+
+-- | A strategy gives a set of possible reductions for a term.
+type Strategy f = Term f -> [Reduction f]
+
+-- | A multi-step rewrite proof @t ->* u@
+data Reduction f =
+    -- | Apply a single rewrite rule to the root of a term
+    Step {-# UNPACK #-} !(Lemma f) !(Rule f) !(Subst f)
+    -- | Reflexivity
+  | Refl {-# UNPACK #-} !(Term f)
+    -- | Transivitity
+  | Trans !(Reduction f) !(Reduction f)
+    -- | Congruence
+  | Cong {-# UNPACK #-} !(Fun f) ![Reduction f]
+  deriving Show
+
+instance Symbolic (Reduction f) where
+  type ConstantOf (Reduction f) = f
+  termsDL (Step _ _ sub) = termsDL sub
+  termsDL (Refl t) = termsDL t
+  termsDL (Trans p q) = termsDL p `mplus` termsDL q
+  termsDL (Cong _ ps) = termsDL ps
+
+  subst_ sub (Step lemma rule s) = Step lemma rule (subst_ sub s)
+  subst_ sub (Refl t) = Refl (subst_ sub t)
+  subst_ sub (Trans p q) = Trans (subst_ sub p) (subst_ sub q)
+  subst_ sub (Cong f ps) = Cong f (subst_ sub ps)
+
+instance Function f => Pretty (Reduction f) where
+  pPrint = pPrint . reductionProof
+
+-- | A smart constructor for Trans which simplifies Refl.
+trans :: Reduction f -> Reduction f -> Reduction f
+trans Refl{} p = p
+trans p Refl{} = p
+-- Make right-associative to improve performance of 'result'
+trans p (Trans q r) = Trans (Trans p q) r
+trans p q = Trans p q
+
+-- | A smart constructor for Cong which simplifies Refl.
+cong :: Fun f -> [Reduction f] -> Reduction f
+cong f ps
+  | all isRefl ps = Refl (result (reduce (Cong f ps)))
+  | otherwise = Cong f ps
+  where
+    isRefl Refl{} = True
+    isRefl _ = False
+
+-- | The list of all rewrite rules used in a rewrite proof.
+steps :: Reduction f -> [Reduction f]
+steps r = aux r []
+  where
+    aux step@Step{} = (step:)
+    aux (Refl _) = id
+    aux (Trans p q) = aux p . aux q
+    aux (Cong _ ps) = foldr (.) id (map aux ps)
+
+-- | Turn a reduction into a proof.
+reductionProof :: Reduction f -> Derivation f
+reductionProof (Step lemma _ sub) =
+  Proof.lemma lemma sub
+reductionProof (Refl t) = Proof.Refl t
+reductionProof (Trans p q) =
+  Proof.trans (reductionProof p) (reductionProof q)
+reductionProof (Cong f ps) = Proof.cong f (map reductionProof ps)
+
+-- | Construct a basic rewrite step.
+{-# INLINE step #-}
+step :: (Has a (Rule f), Has a (Lemma f)) => a -> Subst f -> Reduction f
+step x sub = Step (the x) (the x) sub
+
+----------------------------------------------------------------------
+-- | A rewrite proof with the final term attached.
+-- Has an @Ord@ instance which compares the final term.
+----------------------------------------------------------------------
+
+data Resulting f =
+  Resulting {
+    result :: {-# UNPACK #-} !(Term f),
+    reduction :: !(Reduction f) }
+  deriving Show
+
+instance Eq (Resulting f) where x == y = compare x y == EQ
+instance Ord (Resulting f) where compare = comparing result
+
+instance Symbolic (Resulting f) where
+  type ConstantOf (Resulting f) = f
+  termsDL (Resulting t red) =
+    termsDL t `mplus` termsDL red
+  subst_ sub (Resulting t red) =
+    Resulting (subst_ sub t) (subst_ sub red)
+
+instance Function f => Pretty (Resulting f) where
+  pPrint = pPrint . reduction
+
+-- | Construct a 'Resulting' from a 'Reduction'.
+reduce :: Reduction f -> Resulting f
+reduce p =
+  Resulting (res p) p
+  where
+    res (Trans _ q) = res q
+    res (Refl t) = t
+    res p = {-# SCC res_emitRes #-} build (emitResult p)
+
+    emitResult (Step _ r sub) = Term.subst sub (rhs r)
+    emitResult (Refl t) = builder t
+    emitResult (Trans _ q) = emitResult q
+    emitResult (Cong f ps) = app f (map emitResult ps)
+
+--------------------------------------------------------------------------------
+-- * Strategy combinators.
+--------------------------------------------------------------------------------
+
+-- | Normalise a term wrt a particular strategy.
+{-# INLINE normaliseWith #-}
+normaliseWith :: Function f => (Term f -> Bool) -> Strategy f -> Term f -> Resulting f
+normaliseWith ok strat t = {-# SCC normaliseWith #-} res
+  where
+    res = aux 0 (Refl t) t
+    aux 1000 p _ =
+      error $
+        "Possibly nonterminating rewrite:\n" ++ prettyShow p
+    aux n p t =
+      case parallel strat t of
+        (q:_) | u <- result (reduce q), ok u ->
+          aux (n+1) (p `trans` q) u
+        _ -> Resulting t p
+
+-- | Compute all normal forms of a set of terms wrt a particular strategy.
+normalForms :: Function f => Strategy f -> [Resulting f] -> Set (Resulting f)
+normalForms strat ps = snd (successorsAndNormalForms strat ps)
+
+-- | Compute all successors of a set of terms (a successor of a term @t@
+-- is a term @u@ such that @t ->* u@).
+successors :: Function f => Strategy f -> [Resulting f] -> Set (Resulting f)
+successors strat ps = Set.union qs rs
+  where
+    (qs, rs) = successorsAndNormalForms strat ps
+
+{-# INLINEABLE successorsAndNormalForms #-}
+successorsAndNormalForms :: Function f => Strategy f -> [Resulting f] ->
+  (Set (Resulting f), Set (Resulting f))
+successorsAndNormalForms strat ps =
+  {-# SCC successorsAndNormalForms #-} go Set.empty Set.empty ps
+  where
+    go dead norm [] = (dead, norm)
+    go dead norm (p:ps)
+      | p `Set.member` dead = go dead norm ps
+      | p `Set.member` norm = go dead norm ps
+      | null qs = go dead (Set.insert p norm) ps
+      | otherwise =
+        go (Set.insert p dead) norm (qs ++ ps)
+      where
+        qs =
+          [ reduce (reduction p `Trans` q)
+          | q <- anywhere strat (result p) ]
+
+-- | Apply a strategy anywhere in a term.
+anywhere :: Strategy f -> Strategy f
+anywhere strat t = strat t ++ nested (anywhere strat) t
+
+-- | Apply a strategy to some child of the root function.
+nested :: Strategy f -> Strategy f
+nested _ Var{} = []
+nested strat (App f ts) =
+  cong f <$> inner [] ts
+  where
+    inner _ Empty = []
+    inner before (Cons t u) =
+      [ reverse before ++ [p] ++ map Refl (unpack u)
+      | p <- strat t ] ++
+      inner (Refl t:before) u
+
+-- | Apply a strategy in parallel in as many places as possible.
+-- Takes only the first rewrite of each strategy.
+{-# INLINE parallel #-}
+parallel :: PrettyTerm f => Strategy f -> Strategy f
+parallel strat t =
+  case par t of
+    Refl{} -> []
+    p -> [p]
+  where
+    par t | p:_ <- strat t = p
+    par (App f ts) = cong f (inner [] ts)
+    par t = Refl t
+
+    inner before Empty = reverse before
+    inner before (Cons t u) = inner (par t:before) u
+
+--------------------------------------------------------------------------------
+-- * Basic strategies. These only apply at the root of the term.
+--------------------------------------------------------------------------------
+
+-- | A strategy which rewrites using an index.
+{-# INLINE rewrite #-}
+rewrite :: (Function f, Has a (Rule f), Has a (Lemma f)) => (Rule f -> Subst f -> Bool) -> Index f a -> Strategy f
+rewrite p rules t = do
+  rule <- Index.approxMatches t rules
+  tryRule p rule t
+
+-- | A strategy which applies one rule only.
+{-# INLINEABLE tryRule #-}
+tryRule :: (Function f, Has a (Rule f), Has a (Lemma f)) => (Rule f -> Subst f -> Bool) -> a -> Strategy f
+tryRule p rule t = do
+  sub <- maybeToList (match (lhs (the rule)) t)
+  guard (p (the rule) sub)
+  return (step rule sub)
+
+-- | Check if a rule can be applied, given an ordering <= on terms.
+{-# INLINEABLE reducesWith #-}
+reducesWith :: Function f => (Term f -> Term f -> Bool) -> Rule f -> Subst f -> Bool
+reducesWith _ (Rule Oriented _ _) _ = True
+reducesWith _ (Rule (WeaklyOriented min ts) _ _) sub =
+  -- Be a bit careful here not to build new terms
+  -- (reducesWith is used in simplify).
+  -- This is the same as:
+  --   any (not . isMinimal) (subst sub ts)
+  any (not . isMinimal . expand) ts
+  where
+    expand t@(Var x) = fromMaybe t (Term.lookup x sub)
+    expand t = t
+
+    isMinimal (App f Empty) = f == min
+    isMinimal _ = False
+reducesWith p (Rule (Permutative ts) _ _) sub =
+  aux ts
+  where
+    aux [] = False
+    aux ((t, u):ts)
+      | t' == u' = aux ts
+      | otherwise = p u' t'
+      where
+        t' = subst sub t
+        u' = subst sub u
+reducesWith p (Rule Unoriented t u) sub =
+  p u' t' && u' /= t'
+  where
+    t' = subst sub t
+    u' = subst sub u
+
+-- | Check if a rule can be applied normally.
+{-# INLINEABLE reduces #-}
+reduces :: Function f => Rule f -> Subst f -> Bool
+reduces rule sub = reducesWith lessEq rule sub
+
+-- | Check if a rule can be applied and is oriented.
+{-# INLINEABLE reducesOriented #-}
+reducesOriented :: Function f => Rule f -> Subst f -> Bool
+reducesOriented rule sub =
+  oriented (orientation rule) && reducesWith undefined rule sub
+
+-- | Check if a rule can be applied in a particular model.
+{-# INLINEABLE reducesInModel #-}
+reducesInModel :: Function f => Model f -> Rule f -> Subst f -> Bool
+reducesInModel cond rule sub =
+  reducesWith (\t u -> isJust (lessIn cond t u)) rule sub
+
+-- | Check if a rule can be applied to the Skolemised version of a term.
+{-# INLINEABLE reducesSkolem #-}
+reducesSkolem :: Function f => Rule f -> Subst f -> Bool
+reducesSkolem rule sub =
+  reducesWith (\t u -> lessEq (subst skolemise t) (subst skolemise u)) rule sub
+  where
+    skolemise = con . skolem
diff --git a/Twee/Rule/Index.hs b/Twee/Rule/Index.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Rule/Index.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE RecordWildCards, ScopedTypeVariables, FlexibleContexts #-}
+module Twee.Rule.Index(
+  RuleIndex(..),
+  empty, insert, delete,
+  approxMatches, matches, lookup) where
+
+import Prelude hiding (lookup)
+import Twee.Base hiding (lookup, empty)
+import Twee.Rule
+import Twee.Index hiding (insert, delete, empty)
+import qualified Twee.Index as Index
+
+data RuleIndex f a =
+  RuleIndex {
+    index_oriented :: !(Index f a),
+    index_weak     :: !(Index f a),
+    index_all      :: !(Index f a) }
+  deriving Show
+
+empty :: RuleIndex f a
+empty = RuleIndex Index.empty Index.empty Index.empty
+
+insert :: forall f a. Has a (Rule f) => Term f -> a -> RuleIndex f a -> RuleIndex f a
+insert t x RuleIndex{..} =
+  RuleIndex {
+    index_oriented = insertWhen (oriented or) index_oriented,
+    index_weak = insertWhen (weaklyOriented or) index_weak,
+    index_all = insertWhen True index_all }
+  where
+    Rule or _ _ = the x :: Rule f
+
+    insertWhen False idx = idx
+    insertWhen True idx = Index.insert t x idx
+
+delete :: forall f a. (Eq a, Has a (Rule f)) => Term f -> a -> RuleIndex f a -> RuleIndex f a
+delete t x RuleIndex{..} =
+  RuleIndex {
+    index_oriented = deleteWhen (oriented or) index_oriented,
+    index_weak = deleteWhen (weaklyOriented or) index_weak,
+    index_all = deleteWhen True index_all }
+  where
+    Rule or _ _ = the x :: Rule f
+
+    deleteWhen False idx = idx
+    deleteWhen True idx = Index.delete t x idx
diff --git a/Twee/Task.hs b/Twee/Task.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Task.hs
@@ -0,0 +1,56 @@
+-- | A module which can run housekeeping tasks every so often.
+{-# LANGUAGE RecordWildCards #-}
+module Twee.Task(Task, newTask, checkTask) where
+
+import System.CPUTime
+import Data.IORef
+import Control.Monad.IO.Class
+
+data TaskData m a =
+  TaskData {
+    -- When was the task created?
+    task_start :: !Integer,
+    -- When was the task last run?
+    task_last :: !Integer,
+    -- How long have we spent on this task so far?
+    task_spent :: !Integer,
+    -- How often should we run this task at most, in seconds?
+    task_frequency :: !Double,
+    -- What proportion of our time should we spend on the task?
+    task_budget :: !Double,
+    -- The task itself
+    task_what :: m a }
+
+-- | A task which runs in the monad @m@ and produces a value of type @a@.
+newtype Task m a = Task (IORef (TaskData m a))
+
+-- | Create a new task that should be run a certain proportion
+-- of the time. The first argument is how often in seconds the
+-- task should run, at most. The second argument is the maximum
+-- percentage of time that should be spent on the task.
+newTask :: MonadIO m => Double -> Double -> m a -> m (Task m a)
+newTask freq budget what = liftIO $ do
+  now <- getCPUTime
+  Task <$> newIORef (TaskData now now 0 freq budget what)
+
+-- | Run a task if it's time to run it.
+checkTask :: MonadIO m => Task m a -> m (Maybe a)
+checkTask (Task ref) = do
+  task@TaskData{..} <- liftIO $ readIORef ref
+  now <- liftIO getCPUTime
+  if not (taskDue now task) then return Nothing else do
+    res <- task_what
+    after <- liftIO getCPUTime
+    liftIO $ writeIORef ref task {
+      task_last = after,
+      task_spent = task_spent + (after-now) }
+    return (Just res)
+
+-- Check if a task should be run now.
+taskDue :: Integer -> TaskData m a -> Bool
+taskDue now TaskData{..} =
+  -- Don't run more than the frequency says.
+  fromInteger (now - task_last) >= task_frequency * 10^12 &&
+  -- Run if we spent less than task_budget proportion of the total time so far.
+  -- Use > rather than >= so that tasks with zero budget never get run.
+  fromInteger (now - task_start) * task_budget > fromInteger task_spent
diff --git a/Twee/Term.hs b/Twee/Term.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Term.hs
@@ -0,0 +1,647 @@
+-- | Terms and substitutions.
+--
+-- Terms in twee are represented as arrays rather than as an algebraic data
+-- type. This module defines pattern synonyms ('App', 'Var', 'Cons', 'Empty')
+-- which means that pattern matching on terms works just as normal.
+-- The pattern synonyms can not be used to create new terms; for that you
+-- have to use a builder interface ('Build').
+--
+-- This module also provides:
+--
+--   * pattern synonyms for iterating through a term one symbol at a time
+--     ('ConsSym');
+--   * substitutions ('Substitution', 'Subst', 'subst');
+--   * unification ('unify') and matching ('match');
+--   * miscellaneous useful functions on terms.
+{-# LANGUAGE BangPatterns, PatternSynonyms, ViewPatterns, TypeFamilies, OverloadedStrings, ScopedTypeVariables #-}
+module Twee.Term(
+  -- * Terms
+  Term, pattern Var, pattern App, isApp, isVar, singleton, len,
+  -- * Termlists
+  TermList, pattern Empty, pattern Cons, pattern ConsSym,
+  pattern UnsafeCons, pattern UnsafeConsSym,
+  empty, unpack, lenList,
+  -- * Function symbols and variables
+  Fun, fun, fun_id, fun_value, pattern F, Var(..), 
+  -- * Building terms
+  Build(..),
+  Builder,
+  build, buildList,
+  con, app, var,
+  -- * Access to subterms
+  children, properSubterms, subtermsList, subterms, occurs, isSubtermOf, isSubtermOfList, at,
+  -- * Substitutions
+  Substitution(..),
+  subst,
+  Subst(..),
+  -- ** Constructing and querying substitutions
+  emptySubst, listToSubst, substToList,
+  lookup, lookupList,
+  extend, extendList, unsafeExtendList,
+  retract,
+  -- ** Other operations on substitutions
+  foldSubst, allSubst, substDomain,
+  substSize,
+  substCompose, substCompatible, substUnion, idempotent, idempotentOn,
+  canonicalise,
+  -- * Matching
+  match, matchIn, matchList, matchListIn, isInstanceOf, isVariantOf,
+  -- * Unification
+  unify, unifyList,
+  unifyTri, unifyListTri,
+  TriangleSubst(..),
+  close,
+  -- * Positions in terms
+  positionToPath, pathToPosition,
+  replacePosition,
+  replacePositionSub,
+  -- * Miscellaneous functions
+  bound, boundList, boundLists, mapFun, mapFunList, (<<)) where
+
+import Prelude hiding (lookup)
+import Twee.Term.Core hiding (F)
+import Data.List hiding (lookup, find)
+import Data.Maybe
+import Data.Monoid
+import Data.IntMap.Strict(IntMap)
+import qualified Data.IntMap.Strict as IntMap
+
+--------------------------------------------------------------------------------
+-- * A type class for builders
+--------------------------------------------------------------------------------
+
+-- | Instances of 'Build' can be turned into terms using 'build' or 'buildList',
+-- and turned into term builders using 'builder'. Has instances for terms,
+-- termlists, builders, and Haskell lists.
+class Build a where
+  -- | The underlying type of function symbols.
+  type BuildFun a
+  -- | Convert a value into a 'Builder'.
+  builder :: a -> Builder (BuildFun a)
+
+instance Build (Builder f) where
+  type BuildFun (Builder f) = f
+  builder = id
+
+instance Build (Term f) where
+  type BuildFun (Term f) = f
+  builder = emitTermList . singleton
+
+instance Build (TermList f) where
+  type BuildFun (TermList f) = f
+  builder = emitTermList
+
+instance Build a => Build [a] where
+  type BuildFun [a] = BuildFun a
+  {-# INLINE builder #-}
+  builder = mconcat . map builder
+
+-- | Build a term. The given builder must produce exactly one term.
+{-# INLINE build #-}
+build :: Build a => a -> Term (BuildFun a)
+build x =
+  case buildList x of
+    Cons t Empty -> t
+
+-- | Build a termlist.
+{-# INLINE buildList #-}
+buildList :: Build a => a -> TermList (BuildFun a)
+buildList x = {-# SCC buildList #-} buildTermList (builder x)
+
+-- | Build a constant (a function with no arguments).
+{-# INLINE con #-}
+con :: Fun f -> Builder f
+con x = emitApp x mempty
+
+-- | Build a function application.
+{-# INLINE app #-}
+app :: Build a => Fun (BuildFun a) -> a -> Builder (BuildFun a)
+app f ts = emitApp f (builder ts)
+
+-- | Build a variable.
+var :: Var -> Builder f
+var = emitVar
+
+--------------------------------------------------------------------------------
+-- Functions for substitutions.
+--------------------------------------------------------------------------------
+
+{-# INLINE substToList' #-}
+substToList' :: Subst f -> [(Var, TermList f)]
+substToList' (Subst sub) = [(V x, t) | (x, t) <- IntMap.toList sub]
+
+-- | Convert a substitution to a list of bindings.
+{-# INLINE substToList #-}
+substToList :: Subst f -> [(Var, Term f)]
+substToList sub =
+  [(x, t) | (x, Cons t Empty) <- substToList' sub]
+
+-- | Fold a function over a substitution.
+{-# INLINE foldSubst #-}
+foldSubst :: (Var -> TermList f -> b -> b) -> b -> Subst f -> b
+foldSubst op e !sub = foldr (uncurry op) e (substToList' sub)
+
+-- | Check if all bindings of a substitution satisfy a given property.
+{-# INLINE allSubst #-}
+allSubst :: (Var -> TermList f -> Bool) -> Subst f -> Bool
+allSubst p = foldSubst (\x t y -> p x t && y) True
+
+-- | Compute the set of variables bound by a substitution.
+{-# INLINE substDomain #-}
+substDomain :: Subst f -> [Var]
+substDomain (Subst sub) = map V (IntMap.keys sub)
+
+--------------------------------------------------------------------------------
+-- Substitution.
+--------------------------------------------------------------------------------
+
+-- | A class for values which act as substitutions.
+--
+-- Instances include 'Subst' as well as functions from variables to terms.
+class Substitution s where
+  -- | The underlying type of function symbols.
+  type SubstFun s
+
+  -- | Apply the substitution to a variable.
+  evalSubst :: s -> Var -> Builder (SubstFun s)
+
+  -- | Apply the substitution to a termlist.
+  {-# INLINE substList #-}
+  substList :: s -> TermList (SubstFun s) -> Builder (SubstFun s)
+  substList sub ts = aux ts
+    where
+      aux Empty = mempty
+      aux (Cons (Var x) ts) = evalSubst sub x <> aux ts
+      aux (Cons (App f ts) us) = app f (aux ts) <> aux us
+
+instance (Build a, v ~ Var) => Substitution (v -> a) where
+  type SubstFun (v -> a) = BuildFun a
+
+  {-# INLINE evalSubst #-}
+  evalSubst sub x = builder (sub x)
+
+instance Substitution (Subst f) where
+  type SubstFun (Subst f) = f
+
+  {-# INLINE evalSubst #-}
+  evalSubst sub x =
+    case lookupList x sub of
+      Nothing -> var x
+      Just ts -> builder ts
+
+-- | Apply a substitution to a term.
+{-# INLINE subst #-}
+subst :: Substitution s => s -> Term (SubstFun s) -> Builder (SubstFun s)
+subst sub t = substList sub (singleton t)
+
+-- | A substitution which maps variables to terms of type @'Term' f@.
+newtype Subst f =
+  Subst {
+    unSubst :: IntMap (TermList f) }
+  deriving Eq
+
+-- | Return the highest-number variable in a substitution plus 1.
+{-# INLINE substSize #-}
+substSize :: Subst f -> Int
+substSize (Subst sub)
+  | IntMap.null sub = 0
+  | otherwise = fst (IntMap.findMax sub) + 1
+
+-- | Look up a variable in a substitution, returning a termlist.
+{-# INLINE lookupList #-}
+lookupList :: Var -> Subst f -> Maybe (TermList f)
+lookupList x (Subst sub) = IntMap.lookup (var_id x) sub
+
+-- | Add a new binding to a substitution, giving a termlist.
+{-# INLINE extendList #-}
+extendList :: Var -> TermList f -> Subst f -> Maybe (Subst f)
+extendList x !t (Subst sub) =
+  case IntMap.lookup (var_id x) sub of
+    Nothing -> Just $! Subst (IntMap.insert (var_id x) t sub)
+    Just u
+      | t == u    -> Just (Subst sub)
+      | otherwise -> Nothing
+
+-- | Remove a binding from a substitution.
+{-# INLINE retract #-}
+retract :: Var -> Subst f -> Subst f
+retract x (Subst sub) = Subst (IntMap.delete (var_id x) sub)
+
+-- | Add a new binding to a substitution.
+-- Overwrites any existing binding.
+{-# INLINE unsafeExtendList #-}
+unsafeExtendList :: Var -> TermList f -> Subst f -> Subst f
+unsafeExtendList x !t (Subst sub) = Subst (IntMap.insert (var_id x) t sub)
+
+-- | Compose two substitutions.
+substCompose :: Substitution s => Subst (SubstFun s) -> s -> Subst (SubstFun s)
+substCompose (Subst !sub1) !sub2 =
+  Subst (IntMap.map (buildList . substList sub2) sub1)
+
+-- | Check if two substitutions are compatible (they do not send the same
+-- variable to different terms).
+substCompatible :: Subst f -> Subst f -> Bool
+substCompatible (Subst !sub1) (Subst !sub2) =
+  IntMap.null (IntMap.mergeWithKey f g h sub1 sub2)
+  where
+    f _ t u
+      | t == u = Nothing
+      | otherwise = Just t
+    g _ = IntMap.empty
+    h _ = IntMap.empty
+
+-- | Take the union of two substitutions.
+-- The substitutions must be compatible, which is not checked.
+substUnion :: Subst f -> Subst f -> Subst f
+substUnion (Subst !sub1) (Subst !sub2) =
+  Subst (IntMap.union sub1 sub2)
+
+-- | Check if a substitution is idempotent (applying it twice has the same
+-- effect as applying it once).
+{-# INLINE idempotent #-}
+idempotent :: Subst f -> Bool
+idempotent !sub = allSubst (\_ t -> sub `idempotentOn` t) sub
+
+-- | Check if a substitution has no effect on a given term.
+{-# INLINE idempotentOn #-}
+idempotentOn :: Subst f -> TermList f -> Bool
+idempotentOn !sub = aux
+  where
+    aux Empty = True
+    aux (ConsSym App{} t) = aux t
+    aux (Cons (Var x) t) = isNothing (lookupList x sub) && aux t
+
+-- | Iterate a triangle substitution to make it idempotent.
+close :: TriangleSubst f -> Subst f
+close (Triangle sub)
+  | idempotent sub = sub
+  | otherwise      = close (Triangle (substCompose sub sub))
+
+-- | Return a substitution which renames the variables of a list of terms to put
+-- them in a canonical order.
+canonicalise :: [TermList f] -> Subst f
+canonicalise [] = emptySubst
+canonicalise (t:ts) = loop emptySubst vars t ts
+  where
+    (V m, V n) = boundLists (t:ts)
+    vars =
+      buildTermList $
+        -- Produces two variables when the term is ground
+        -- (n = minBound, m = maxBound), which is OK.
+        mconcat [emitVar (V x) | x <- [0..n-m+1]]
+
+    loop !_ !_ !_ !_ | False = undefined
+    loop sub _ Empty [] = sub
+    loop sub Empty _ _ = sub
+    loop sub vs Empty (t:ts) = loop sub vs t ts
+    loop sub vs (ConsSym App{} t) ts = loop sub vs t ts
+    loop sub vs0@(Cons v vs) (Cons (Var x) t) ts =
+      case extend x v sub of
+        Just sub -> loop sub vs  t ts
+        Nothing  -> loop sub vs0 t ts
+
+-- | The empty substitution.
+{-# NOINLINE emptySubst #-}
+emptySubst = Subst IntMap.empty
+
+-- | Construct a substitution from a list.
+-- Returns @Nothing@ if a variable is bound to several different terms.
+listToSubst :: [(Var, Term f)] -> Maybe (Subst f)
+listToSubst sub = matchList pat t
+  where
+    pat = buildList (map (var . fst) sub)
+    t   = buildList (map snd sub)
+
+--------------------------------------------------------------------------------
+-- Matching.
+--------------------------------------------------------------------------------
+
+-- | @'match' pat t@ matches the term @t@ against the pattern @pat@.
+{-# INLINE match #-}
+match :: Term f -> Term f -> Maybe (Subst f)
+match pat t = matchList (singleton pat) (singleton t)
+
+-- | A variant of 'match' which extends an existing substitution.
+{-# INLINE matchIn #-}
+matchIn :: Subst f -> Term f -> Term f -> Maybe (Subst f)
+matchIn sub pat t = matchListIn sub (singleton pat) (singleton t)
+
+-- | A variant of 'match' which works on termlists.
+{-# INLINE matchList #-}
+matchList :: TermList f -> TermList f -> Maybe (Subst f)
+matchList pat t = matchListIn emptySubst pat t
+
+-- | A variant of 'match' which works on termlists
+-- and extends an existing substitution.
+matchListIn :: Subst f -> TermList f -> TermList f -> Maybe (Subst f)
+matchListIn !sub !pat !t
+  | lenList t < lenList pat = Nothing
+  | otherwise =
+    let loop !_ !_ !_ | False = undefined
+        loop sub Empty Empty = Just sub
+        loop sub (ConsSym (App f _) pat) (ConsSym (App g _) t)
+          | f == g = loop sub pat t
+        loop sub (Cons (Var x) pat) (Cons t u) = do
+          sub <- extend x t sub
+          loop sub pat u
+        loop _ _ _ = Nothing
+    in {-# SCC match #-} loop sub pat t
+
+--------------------------------------------------------------------------------
+-- Unification.
+--------------------------------------------------------------------------------
+
+-- | A triangle substitution is one in which variables can be defined in terms
+-- of each other, though not in a circular way.
+--
+-- The main use of triangle substitutions is in unification; 'unifyTri' returns
+-- one. A triangle substitution can be converted to an ordinary substitution
+-- with 'close', or used directly using its 'Substitution' instance.
+newtype TriangleSubst f = Triangle { unTriangle :: Subst f }
+  deriving Show
+
+instance Substitution (TriangleSubst f) where
+  type SubstFun (TriangleSubst f) = f
+
+  {-# INLINE evalSubst #-}
+  evalSubst (Triangle sub) x =
+    case lookupList x sub of
+      Nothing  -> var x
+      Just ts  -> substList (Triangle sub) ts
+
+  -- Redefine substList to get better inlining behaviour
+  {-# INLINE substList #-}
+  substList (Triangle sub) ts = aux ts
+    where
+      aux Empty = mempty
+      aux (Cons (Var x) ts) = auxVar x <> aux ts
+      aux (Cons (App f ts) us) = app f (aux ts) <> aux us
+
+      auxVar x =
+        case lookupList x sub of
+          Nothing -> var x
+          Just ts -> aux ts
+
+-- | Unify two terms.
+unify :: Term f -> Term f -> Maybe (Subst f)
+unify t u = unifyList (singleton t) (singleton u)
+
+-- | Unify two termlists.
+unifyList :: TermList f -> TermList f -> Maybe (Subst f)
+unifyList t u = do
+  sub <- unifyListTri t u
+  -- Not strict so that isJust (unify t u) doesn't force the substitution
+  return (close sub)
+
+-- | Unify two terms, returning a triangle substitution.
+-- This is slightly faster than 'unify'.
+unifyTri :: Term f -> Term f -> Maybe (TriangleSubst f)
+unifyTri t u = unifyListTri (singleton t) (singleton u)
+
+-- | Unify two termlists, returning a triangle substitution.
+-- This is slightly faster than 'unify'.
+unifyListTri :: TermList f -> TermList f -> Maybe (TriangleSubst f)
+unifyListTri !t !u = fmap Triangle ({-# SCC unify #-} loop emptySubst t u)
+  where
+    loop !_ !_ !_ | False = undefined
+    loop sub Empty Empty = Just sub
+    loop sub (ConsSym (App f _) t) (ConsSym (App g _) u)
+      | f == g = loop sub t u
+    loop sub (Cons (Var x) t) (Cons u v) = do
+      sub <- var sub x u
+      loop sub t v
+    loop sub (Cons t u) (Cons (Var x) v) = do
+      sub <- var sub x t
+      loop sub u v
+    loop _ _ _ = Nothing
+
+    var sub x t =
+      case lookupList x sub of
+        Just u -> loop sub u (singleton t)
+        Nothing -> var1 sub x t
+
+    var1 sub x t@(Var y)
+      | x == y = return sub
+      | otherwise =
+        case lookup y sub of
+          Just t  -> var1 sub x t
+          Nothing -> extend x t sub
+
+    var1 sub x t = do
+      occurs sub x (singleton t)
+      extend x t sub
+
+    occurs !_ !_ Empty = Just ()
+    occurs sub x (ConsSym App{} t) = occurs sub x t
+    occurs sub x (ConsSym (Var y) t)
+      | x == y = Nothing
+      | otherwise = do
+          occurs sub x t
+          case lookupList y sub of
+            Nothing -> Just ()
+            Just u  -> occurs sub x u
+
+--------------------------------------------------------------------------------
+-- Miscellaneous stuff.
+--------------------------------------------------------------------------------
+
+-- | The empty termlist.
+{-# NOINLINE empty #-}
+empty :: forall f. TermList f
+empty = buildList (mempty :: Builder f)
+
+-- | Get the children (direct subterms) of a term.
+children :: Term f -> TermList f
+children t =
+  case singleton t of
+    UnsafeConsSym _ ts -> ts
+
+-- | Convert a termlist into an ordinary list of terms.
+unpack :: TermList f -> [Term f]
+unpack t = unfoldr op t
+  where
+    op Empty = Nothing
+    op (Cons t ts) = Just (t, ts)
+
+instance Show (Term f) where
+  show (Var x) = show x
+  show (App f Empty) = show f
+  show (App f ts) = show f ++ "(" ++ intercalate "," (map show (unpack ts)) ++ ")"
+
+instance Show (TermList f) where
+  show = show . unpack
+
+instance Show (Subst f) where
+  show subst =
+    show
+      [ (i, t)
+      | i <- [0..substSize subst-1],
+        Just t <- [lookup (V i) subst] ]
+
+-- | Look up a variable in a substitution.
+{-# INLINE lookup #-}
+lookup :: Var -> Subst f -> Maybe (Term f)
+lookup x s = do
+  Cons t Empty <- lookupList x s
+  return t
+
+-- | Add a new binding to a substitution.
+{-# INLINE extend #-}
+extend :: Var -> Term f -> Subst f -> Maybe (Subst f)
+extend x t sub = extendList x (singleton t) sub
+
+-- | Find the length of a term.
+{-# INLINE len #-}
+len :: Term f -> Int
+len = lenList . singleton
+
+-- | Return the lowest- and highest-numbered variables in a term.
+{-# INLINE bound #-}
+bound :: Term f -> (Var, Var)
+bound t = boundList (singleton t)
+
+-- | Return the lowest- and highest-numbered variables in a termlist.
+{-# INLINE boundList #-}
+boundList :: TermList f -> (Var, Var)
+boundList t = boundListFrom (V maxBound) (V minBound) t
+
+boundListFrom :: Var -> Var -> TermList f -> (Var, Var)
+boundListFrom !m !n Empty = (m, n)
+boundListFrom m n (ConsSym App{} t) = boundListFrom m n t
+boundListFrom m n (ConsSym (Var x) t) =
+  boundListFrom (m `min` x) (n `max` x) t
+
+-- | Return the lowest- and highest-numbered variables in a list of termlists.
+boundLists :: [TermList f] -> (Var, Var)
+boundLists t = boundListsFrom (V maxBound) (V minBound) t
+
+boundListsFrom :: Var -> Var -> [TermList f] -> (Var, Var)
+boundListsFrom !m !n [] = (m, n)
+boundListsFrom m n (t:ts) =
+  let
+    (m', n') = boundListFrom m n t
+  in
+    boundListsFrom m' n' ts
+
+-- | Check if a variable occurs in a term.
+{-# INLINE occurs #-}
+occurs :: Var -> Term f -> Bool
+occurs x t = occursList x (singleton t)
+
+-- | Find all subterms of a termlist.
+{-# INLINE subtermsList #-}
+subtermsList :: TermList f -> [Term f]
+subtermsList t = unfoldr op t
+  where
+    op Empty = Nothing
+    op (ConsSym t u) = Just (t, u)
+
+-- | Find all subterms of a term.
+{-# INLINE subterms #-}
+subterms :: Term f -> [Term f]
+subterms = subtermsList . singleton
+
+-- | Find all proper subterms of a term.
+{-# INLINE properSubterms #-}
+properSubterms :: Term f -> [Term f]
+properSubterms = subtermsList . children
+
+-- | Check if a term is a function application.
+isApp :: Term f -> Bool
+isApp App{} = True
+isApp _     = False
+
+-- | Check if a term is a variable
+isVar :: Term f -> Bool
+isVar Var{} = True
+isVar _     = False
+
+-- | @t \`'isInstanceOf'\` pat@ checks if @t@ is an instance of @pat@.
+isInstanceOf :: Term f -> Term f -> Bool
+t `isInstanceOf` pat = isJust (match pat t)
+
+-- | Check if two terms are renamings of one another.
+isVariantOf :: Term f -> Term f -> Bool
+t `isVariantOf` u = t `isInstanceOf` u && u `isInstanceOf` t
+
+-- | Is a term a subterm of another one?
+isSubtermOf :: Term f -> Term f -> Bool
+t `isSubtermOf` u = t `isSubtermOfList` singleton u
+
+-- | Map a function over the function symbols in a term.
+mapFun :: (Fun f -> Fun g) -> Term f -> Builder g
+mapFun f = mapFunList f . singleton
+
+-- | Map a function over the function symbols in a termlist.
+mapFunList :: (Fun f -> Fun g) -> TermList f -> Builder g
+mapFunList f ts = aux ts
+  where
+    aux Empty = mempty
+    aux (Cons (Var x) ts) = var x `mappend` aux ts
+    aux (Cons (App ff ts) us) = app (f ff) (aux ts) `mappend` aux us
+
+-- | Replace the term at a given position in a term with a different term.
+{-# INLINE replacePosition #-}
+replacePosition :: (Build a, BuildFun a ~ f) => Int -> a -> TermList f -> Builder f
+replacePosition n !x = aux n
+  where
+    aux !_ !_ | False = undefined
+    aux _ Empty = mempty
+    aux 0 (Cons _ t) = builder x `mappend` builder t
+    aux n (Cons (Var x) t) = var x `mappend` aux (n-1) t
+    aux n (Cons t@(App f ts) u)
+      | n < len t =
+        app f (aux (n-1) ts) `mappend` builder u
+      | otherwise =
+        builder t `mappend` aux (n-len t) u
+
+-- | Replace the term at a given position in a term with a different term, while
+-- simultaneously applying a substitution. Useful for building critical pairs.
+{-# INLINE replacePositionSub #-}
+replacePositionSub :: (Substitution sub, SubstFun sub ~ f) => sub -> Int -> TermList f -> TermList f -> Builder f
+replacePositionSub sub n !x = aux n
+  where
+    aux !_ !_ | False = undefined
+    aux _ Empty = mempty
+    aux n (Cons t u)
+      | n < len t = inside n t `mappend` outside u
+      | otherwise = outside (singleton t) `mappend` aux (n-len t) u
+
+    inside 0 _ = outside x
+    inside n (App f ts) = app f (aux (n-1) ts)
+    inside _ _ = undefined -- implies n >= len t
+
+    outside t = substList sub t
+
+-- | Convert a position in a term, expressed as a single number, into a path.
+positionToPath :: Term f -> Int -> [Int]
+positionToPath t n = term t n
+  where
+    term _ 0 = []
+    term t n = list 0 (children t) (n-1)
+
+    list _ Empty _ = error "bad position"
+    list k (Cons t u) n
+      | n < len t = k:term t n
+      | otherwise = list (k+1) u (n-len t)
+
+-- | Convert a path in a term into a position.
+pathToPosition :: Term f -> [Int] -> Int
+pathToPosition t ns = term 0 t ns
+  where
+    term k _ [] = k
+    term k t (n:ns) = list (k+1) (children t) n ns
+
+    list _ Empty _ _ = error "bad path"
+    list k (Cons t _) 0 ns = term k t ns
+    list k (Cons t u) n ns =
+      list (k+len t) u (n-1) ns
+
+-- | A pattern which extracts the 'fun_value' from a 'Fun'.
+pattern F :: f -> Fun f
+pattern F x <- (fun_value -> x)
+{-# COMPLETE F #-}
+
+-- | Compare the 'fun_value's of two 'Fun's.
+(<<) :: Ord f => Fun f -> Fun f -> Bool
+f << g = fun_value f < fun_value g
diff --git a/Twee/Term/Core.hs b/Twee/Term/Core.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Term/Core.hs
@@ -0,0 +1,427 @@
+-- Terms and substitutions, implemented using flatterms.
+-- This module contains all the low-level icky bits
+-- and provides primitives for building higher-level stuff.
+{-# LANGUAGE CPP, PatternSynonyms, ViewPatterns,
+    MagicHash, UnboxedTuples, BangPatterns,
+    RankNTypes, RecordWildCards, GeneralizedNewtypeDeriving #-}
+module Twee.Term.Core where
+
+import Data.Primitive(sizeOf)
+#ifdef BOUNDS_CHECKS
+import Data.Primitive.ByteArray.Checked
+#else
+import Data.Primitive.ByteArray
+#endif
+import Control.Monad.ST.Strict
+import Data.Bits
+import Data.Int
+import GHC.Int(Int(..))
+import GHC.Prim
+import GHC.ST hiding (liftST)
+import Data.Ord
+import Twee.Label
+import Data.Typeable
+
+--------------------------------------------------------------------------------
+-- Symbols. A symbol is a single function or variable in a flatterm.
+--------------------------------------------------------------------------------
+
+data Symbol =
+  Symbol {
+    -- Is it a function?
+    isFun :: Bool,
+    -- What is its number?
+    index :: Int,
+    -- What is the size of the term rooted at this symbol?
+    size  :: Int }
+
+instance Show Symbol where
+  show Symbol{..}
+    | isFun = show (F index) ++ "=" ++ show size
+    | otherwise = show (V index)
+
+-- Convert symbols to/from Int64 for storage in flatterms.
+-- The encoding:
+--   * bits 0-30: size
+--   * bit  31: 0 (variable) or 1 (function)
+--   * bits 32-63: index
+{-# INLINE toSymbol #-}
+toSymbol :: Int64 -> Symbol
+toSymbol n =
+  Symbol (testBit n 31)
+    (fromIntegral (n `unsafeShiftR` 32))
+    (fromIntegral (n .&. 0x7fffffff))
+
+{-# INLINE fromSymbol #-}
+fromSymbol :: Symbol -> Int64
+fromSymbol Symbol{..} =
+  fromIntegral size +
+  fromIntegral index `unsafeShiftL` 32 +
+  fromIntegral (fromEnum isFun) `unsafeShiftL` 31
+
+--------------------------------------------------------------------------------
+-- Flatterms, or rather lists of terms.
+--------------------------------------------------------------------------------
+
+-- | @'TermList' f@ is a list of terms whose function symbols have type @f@.
+-- It is either a 'Cons' or an 'Empty'. You can turn it into a @['Term' f]@
+-- with 'Twee.Term.unpack'.
+
+-- A TermList is a slice of an unboxed array of symbols.
+data TermList f =
+  TermList {
+    low   :: {-# UNPACK #-} !Int,
+    high  :: {-# UNPACK #-} !Int,
+    array :: {-# UNPACK #-} !ByteArray }
+
+-- | Index into a termlist.
+at :: Int -> TermList f -> Term f
+at n (TermList lo hi arr)
+  | n < 0 || lo+n >= hi = error "term index out of bounds"
+  | otherwise =
+    case TermList (lo+n) hi arr of
+      UnsafeCons t _ -> t
+
+{-# INLINE lenList #-}
+-- | The length of (number of symbols in) a termlist.
+lenList :: TermList f -> Int
+lenList (TermList low high _) = high - low
+
+-- | @'Term' f@ is a term whose function symbols have type @f@.
+-- It is either a 'Var' or an 'App'.
+
+-- A term is a special case of a termlist.
+-- We store it as the termlist together with the root symbol.
+data Term f =
+  Term {
+    root     :: {-# UNPACK #-} !Int64,
+    termlist :: {-# UNPACK #-} !(TermList f) }
+
+instance Eq (Term f) where
+  x == y = termlist x == termlist y
+
+instance Ord (Term f) where
+  compare = comparing termlist
+
+-- Pattern synonyms for termlists:
+-- * Empty :: TermList f
+--   Empty is the empty termlist.
+-- * Cons t ts :: Term f -> TermList f -> TermList f
+--   Cons t ts is the termlist t:ts.
+-- * ConsSym t ts :: Term f -> TermList f -> TermList f
+--   ConsSym t ts is like Cons t ts but ts also includes t's children
+--   (operationally, ts seeks one term to the right in the termlist).
+-- * UnsafeCons/UnsafeConsSym: like Cons and ConsSym but don't check
+--   that the termlist is non-empty.
+
+-- | Matches the empty termlist.
+pattern Empty :: TermList f
+pattern Empty <- (patHead -> Nothing)
+
+-- | Matches a non-empty termlist, unpacking it into head and tail.
+pattern Cons :: Term f -> TermList f -> TermList f
+pattern Cons t ts <- (patHead -> Just (t, _, ts))
+
+{-# COMPLETE Empty, Cons #-}
+{-# COMPLETE Empty, ConsSym #-}
+
+-- | Like 'Cons', but does not check that the termlist is non-empty. Use only if
+-- you are sure the termlist is non-empty.
+pattern UnsafeCons :: Term f -> TermList f -> TermList f
+pattern UnsafeCons t ts <- (unsafePatHead -> Just (t, _, ts))
+
+-- | Matches a non-empty termlist, unpacking it into head and
+-- /everything except the root symbol of the head/.
+-- Useful for iterating through terms one symbol at a time.
+--
+-- For example, if @ts@ is the termlist @[f(x,y), g(z)]@,
+-- then @let ConsSym u us = ts@ results in the following bindings:
+--
+-- > u  = f(x,y)
+-- > us = [x, y, g(z)]
+pattern ConsSym :: Term f -> TermList f -> TermList f
+pattern ConsSym t ts <- (patHead -> Just (t, ts, _))
+
+-- | Like 'ConsSym', but does not check that the termlist is non-empty. Use only
+-- if you are sure the termlist is non-empty.
+pattern UnsafeConsSym :: Term f -> TermList f -> TermList f
+pattern UnsafeConsSym t ts <- (unsafePatHead -> Just (t, ts, _))
+
+-- A helper for UnsafeCons/UnsafeConsSym.
+{-# INLINE unsafePatHead #-}
+unsafePatHead :: TermList f -> Maybe (Term f, TermList f, TermList f)
+unsafePatHead TermList{..} =
+  Just (Term x (TermList low (low+size) array),
+        TermList (low+1) high array,
+        TermList (low+size) high array)
+  where
+    !x = indexByteArray array low
+    Symbol{..} = toSymbol x
+
+-- A helper for Cons/ConsSym.
+{-# INLINE patHead #-}
+patHead :: TermList f -> Maybe (Term f, TermList f, TermList f)
+patHead t@TermList{..}
+  | low == high = Nothing
+  | otherwise = unsafePatHead t
+
+-- Pattern synonyms for single terms.
+-- * Var :: Var -> Term f
+-- * App :: Fun f -> TermList f -> Term f
+
+-- | A function symbol. @f@ is the underlying type of function symbols defined
+-- by the user; @'Fun' f@ is an @f@ together with an automatically-generated unique number.
+newtype Fun f =
+  F {
+    -- | The unique number of a 'Fun'.
+    fun_id :: Int }
+instance Eq (Fun f) where
+  f == g = fun_id f == fun_id g
+instance Ord (Fun f) where
+  compare = comparing fun_id
+
+-- | Construct a 'Fun' from a function symbol.
+fun :: (Ord f, Typeable f) => f -> Fun f
+fun f = F (fromIntegral (labelNum (label f)))
+
+-- | The underlying function symbol of a 'Fun'.
+fun_value :: Fun f -> f
+fun_value f = find (unsafeMkLabel (fromIntegral (fun_id f)))
+
+-- | A variable.
+newtype Var =
+  V {
+    -- | The variable's number.
+    -- Don't use huge variable numbers:
+    -- they will be truncated to 32 bits when stored in a term.
+    var_id :: Int } deriving (Eq, Ord, Enum)
+instance Show (Fun f) where show f = "f" ++ show (fun_id f)
+instance Show Var     where show x = "x" ++ show (var_id x)
+
+-- | Matches a variable.
+pattern Var :: Var -> Term f
+pattern Var x <- (patTerm -> Left x)
+
+-- | Matches a function application.
+pattern App :: Fun f -> TermList f -> Term f
+pattern App f ts <- (patTerm -> Right (f, ts))
+
+{-# COMPLETE Var, App #-}
+
+-- A helper function for Var and App.
+{-# INLINE patTerm #-}
+patTerm :: Term f -> Either Var (Fun f, TermList f)
+patTerm t@Term{..}
+  | isFun     = Right (F index, ts)
+  | otherwise = Left (V index)
+  where
+    Symbol{..} = toSymbol root
+    !(UnsafeConsSym _ ts) = singleton t
+
+-- | Convert a term to a termlist.
+{-# INLINE singleton #-}
+singleton :: Term f -> TermList f
+singleton Term{..} = termlist
+
+-- We can implement equality almost without access to the
+-- internal representation of the termlists, but we cheat by
+-- comparing Int64s instead of Symbols.
+instance Eq (TermList f) where
+  -- Manual worker-wrapper to prevent too much from being inlined.
+  t == u = eqTermList t u
+
+{-# INLINE eqTermList #-}
+eqTermList :: TermList f -> TermList f -> Bool
+eqTermList
+  (TermList (I# low1) (I# high1) (ByteArray array1))
+  (TermList (I# low2) (I# high2) (ByteArray array2)) =
+    weqTermList low1 high1 array1 low2 high2 array2
+
+-- Manually worker-wrapper transform the thing, ugh...
+{-# NOINLINE weqTermList #-}
+weqTermList ::
+  Int# -> Int# -> ByteArray# ->
+  Int# -> Int# -> ByteArray# ->
+  Bool
+weqTermList low1 high1 array1 low2 high2 array2 =
+  lenList t == lenList u && eqSameLength t u
+  where
+    t = TermList (I# low1) (I# high1) (ByteArray array1)
+    u = TermList (I# low2) (I# high2) (ByteArray array2)
+    eqSameLength Empty !_ = True
+    eqSameLength (ConsSym s1 t) (UnsafeConsSym s2 u) =
+      root s1 == root s2 && eqSameLength t u
+
+instance Ord (TermList f) where
+  {-# INLINE compare #-}
+  compare t u =
+    case compare (lenList t) (lenList u) of
+      EQ -> compareContents t u
+      x  -> x
+
+compareContents :: TermList f -> TermList f -> Ordering
+compareContents Empty !_ = EQ
+compareContents (ConsSym s1 t) (UnsafeConsSym s2 u) =
+  case compare (root s1) (root s2) of
+    EQ -> compareContents t u
+    x  -> x
+
+--------------------------------------------------------------------------------
+-- Building terms.
+--------------------------------------------------------------------------------
+
+-- | A monoid for building terms.
+-- 'mempty' represents the empty termlist, while 'mappend' appends two termlists.
+newtype Builder f =
+  Builder {
+    unBuilder ::
+      -- Takes: the term array and size, and current position in the term.
+      -- Returns the final position, which may be out of bounds.
+      forall s. Builder1 s f }
+
+type Builder1 s f = State# s -> MutableByteArray# s -> Int# -> Int# -> (# State# s, Int# #)
+
+instance Monoid (Builder f) where
+  {-# INLINE mempty #-}
+  mempty = Builder built
+  {-# INLINE mappend #-}
+  Builder m1 `mappend` Builder m2 = Builder (m1 `then_` m2)
+
+-- Build a termlist from a Builder.
+-- Works by guessing an appropriate size, and retrying if that was too small.
+{-# INLINE buildTermList #-}
+buildTermList :: Builder f -> TermList f
+buildTermList builder = runST $ do
+  let
+    Builder m = builder
+    loop n@(I# n#) = do
+      MutableByteArray mbytearray# <-
+        newByteArray (n * sizeOf (fromSymbol undefined))
+      n' <-
+        ST $ \s ->
+          case m s mbytearray# n# 0# of
+            (# s, n# #) -> (# s, I# n# #)
+      if n' <= n then do
+        !bytearray <- unsafeFreezeByteArray (MutableByteArray mbytearray#)
+        return (TermList 0 n' bytearray)
+       else loop (n'*2)
+  loop 32
+
+-- Get at the term array.
+{-# INLINE getByteArray #-}
+getByteArray :: (MutableByteArray s -> Builder1 s f) -> Builder1 s f
+getByteArray k = \s bytearray n i -> k (MutableByteArray bytearray) s bytearray n i
+
+-- Get at the array size.
+{-# INLINE getSize #-}
+getSize :: (Int -> Builder1 s f) -> Builder1 s f
+getSize k = \s bytearray n i -> k (I# n) s bytearray n i
+
+-- Get at the current array index.
+{-# INLINE getIndex #-}
+getIndex :: (Int -> Builder1 s f) -> Builder1 s f
+getIndex k = \s bytearray n i -> k (I# i) s bytearray n i
+
+-- Change the current array index.
+{-# INLINE putIndex #-}
+putIndex :: Int -> Builder1 s f
+putIndex (I# i) = \s _ _ _ -> (# s, i #)
+
+-- Lift an ST computation into a builder.
+{-# INLINE liftST #-}
+liftST :: ST s () -> Builder1 s f
+liftST (ST m) =
+  \s _ _ i ->
+  case m s of
+    (# s, () #) -> (# s, i #)
+
+-- Finish building.
+{-# INLINE built #-}
+built :: Builder1 s f
+built = \s _ _ i -> (# s, i #)
+
+-- Sequence two builder operations.
+{-# INLINE then_ #-}
+then_ :: Builder1 s f -> Builder1 s f -> Builder1 s f
+then_ m1 m2 =
+  \s bytearray n i ->
+    case m1 s bytearray n i of
+      (# s, i #) -> m2 s bytearray n i
+
+-- checked j m executes m only if the array has room for j more symbols.
+{-# INLINE checked #-}
+checked :: Int -> Builder1 s f -> Builder1 s f
+checked j m =
+  getSize $ \n ->
+  getIndex $ \i ->
+  if i + j <= n then m else putIndex (i + j)
+
+-- Emit an arbitrary symbol, with given arguments.
+{-# INLINE emitSymbolBuilder #-}
+emitSymbolBuilder :: Symbol -> Builder f -> Builder f
+emitSymbolBuilder x inner =
+  Builder $ checked 1 $
+    getByteArray $ \bytearray ->
+    -- Skip the symbol itself, then fill it in at the end, when we know the size
+    -- of the symbol's arguments.
+    getIndex $ \n ->
+    putIndex (n+1) `then_`
+    unBuilder inner `then_`
+    -- Fill in the symbol.
+    getIndex (\m ->
+      liftST $ writeByteArray bytearray n (fromSymbol x { size = m - n }))
+
+-- Emit a function application.
+{-# INLINE emitApp #-}
+emitApp :: Fun f -> Builder f -> Builder f
+emitApp (F n) inner = emitSymbolBuilder (Symbol True n 0) inner
+
+-- Emit a variable.
+{-# INLINE emitVar #-}
+emitVar :: Var -> Builder f
+emitVar x = emitSymbolBuilder (Symbol False (var_id x) 1) mempty
+
+-- Emit a whole termlist.
+{-# INLINE emitTermList #-}
+emitTermList :: TermList f -> Builder f
+emitTermList (TermList lo hi array) =
+  Builder $ checked (hi-lo) $
+    getByteArray $ \mbytearray ->
+    getIndex $ \n ->
+    let k = sizeOf (fromSymbol undefined) in
+    liftST (copyByteArray mbytearray (n*k) array (lo*k) ((hi-lo)*k)) `then_`
+    putIndex (n + hi-lo)
+
+----------------------------------------------------------------------
+-- Efficient subterm testing.
+----------------------------------------------------------------------
+
+-- | Is a term contained as a subterm in a given termlist?
+{-# INLINE isSubtermOfList #-}
+isSubtermOfList :: Term f -> TermList f -> Bool
+isSubtermOfList t u =
+  isSubArrayOf (singleton t) u
+
+-- N.B. this one should not be exported from Twee.Term
+-- because subarray is not the same as subterm if t is not
+-- a singleton
+isSubArrayOf :: TermList f -> TermList f -> Bool
+isSubArrayOf t u =
+  lenList t <= lenList u && (here t u || next t u)
+  where
+    here Empty _ = True
+    here (ConsSym s1 t) (UnsafeConsSym s2 u) =
+      root s1 == root s2 && here t u
+
+    -- This is safe because lenList t <= lenList u
+    -- so if u = Empty, then t = Empty and here t u = True.
+    next t (UnsafeConsSym _ u) = isSubArrayOf t u
+
+-- | Check if a variable occurs in a termlist.
+{-# INLINE occursList #-}
+occursList :: Var -> TermList f -> Bool
+occursList (V x) t = symbolOccursList (fromSymbol (Symbol False x 1)) t
+
+symbolOccursList :: Int64 -> TermList f -> Bool
+symbolOccursList !_ Empty = False
+symbolOccursList n (ConsSym t ts) = root t == n || symbolOccursList n ts
diff --git a/Twee/Utils.hs b/Twee/Utils.hs
new file mode 100644
--- /dev/null
+++ b/Twee/Utils.hs
@@ -0,0 +1,145 @@
+-- | Miscellaneous utility functions.
+
+{-# LANGUAGE CPP, MagicHash #-}
+module Twee.Utils where
+
+import Control.Arrow((&&&))
+import Control.Exception
+import Data.List(groupBy, sortBy)
+import Data.Ord(comparing)
+import System.IO
+import GHC.Prim
+import GHC.Types
+import Data.Bits
+--import Test.QuickCheck hiding ((.&.))
+
+repeatM :: Monad m => m a -> m [a]
+repeatM = sequence . repeat
+
+partitionBy :: Ord b => (a -> b) -> [a] -> [[a]]
+partitionBy value =
+  map (map fst) .
+  groupBy (\x y -> snd x == snd y) .
+  sortBy (comparing snd) .
+  map (id &&& value)
+
+collate :: Ord a => ([b] -> c) -> [(a, b)] -> [(a, c)]
+collate f = map g . partitionBy fst
+  where
+    g xs = (fst (head xs), f (map snd xs))
+
+isSorted :: Ord a => [a] -> Bool
+isSorted xs = and (zipWith (<=) xs (tail xs))
+
+isSortedBy :: Ord b => (a -> b) -> [a] -> Bool
+isSortedBy f xs = isSorted (map f xs)
+
+usort :: Ord a => [a] -> [a]
+usort = usortBy compare
+
+usortBy :: (a -> a -> Ordering) -> [a] -> [a]
+usortBy f = map head . groupBy (\x y -> f x y == EQ) . sortBy f
+
+sortBy' :: Ord b => (a -> b) -> [a] -> [a]
+sortBy' f = map snd . sortBy (comparing fst) . map (\x -> (f x, x))
+
+usortBy' :: Ord b => (a -> b) -> [a] -> [a]
+usortBy' f = map snd . usortBy (comparing fst) . map (\x -> (f x, x))
+
+orElse :: Ordering -> Ordering -> Ordering
+EQ `orElse` x = x
+x  `orElse` _ = x
+
+unbuffered :: IO a -> IO a
+unbuffered x = do
+  buf <- hGetBuffering stdout
+  bracket_
+    (hSetBuffering stdout NoBuffering)
+    (hSetBuffering stdout buf)
+    x
+
+newtype Max a = Max { getMax :: Maybe a }
+
+getMaxWith :: Ord a => a -> Max a -> a
+getMaxWith x (Max (Just y)) = x `max` y
+getMaxWith x (Max Nothing)  = x
+
+instance Ord a => Monoid (Max a) where
+  mempty = Max Nothing
+  Max (Just x) `mappend` y = Max (Just (getMaxWith x y))
+  Max Nothing  `mappend` y = y
+
+newtype Min a = Min { getMin :: Maybe a }
+
+getMinWith :: Ord a => a -> Min a -> a
+getMinWith x (Min (Just y)) = x `min` y
+getMinWith x (Min Nothing)  = x
+
+instance Ord a => Monoid (Min a) where
+  mempty = Min Nothing
+  Min (Just x) `mappend` y = Min (Just (getMinWith x y))
+  Min Nothing  `mappend` y = y
+
+labelM :: Monad m => (a -> m b) -> [a] -> m [(a, b)]
+labelM f = mapM (\x -> do { y <- f x; return (x, y) })
+
+#if __GLASGOW_HASKELL__ < 710
+isSubsequenceOf :: Ord a => [a] -> [a] -> Bool
+[] `isSubsequenceOf` ys = True
+(x:xs) `isSubsequenceOf` [] = False
+(x:xs) `isSubsequenceOf` (y:ys)
+  | x == y = xs `isSubsequenceOf` ys
+  | otherwise = (x:xs) `isSubsequenceOf` ys
+#endif
+
+{-# INLINE fixpoint #-}
+fixpoint :: Eq a => (a -> a) -> a -> a
+fixpoint f x = fxp x
+  where
+    fxp x
+      | x == y = x
+      | otherwise = fxp y
+      where
+        y = f x
+
+-- From "Bit twiddling hacks": branchless min and max
+{-# INLINE intMin #-}
+intMin :: Int -> Int -> Int
+intMin x y =
+  y `xor` ((x `xor` y) .&. negate (x .<. y))
+  where
+    I# x .<. I# y = I# (x <# y)
+
+{-# INLINE intMax #-}
+intMax :: Int -> Int -> Int
+intMax x y =
+  x `xor` ((x `xor` y) .&. negate (x .<. y))
+  where
+    I# x .<. I# y = I# (x <# y)
+
+-- Split an interval (inclusive bounds) into a particular number of blocks
+splitInterval :: Integral a => a -> (a, a) -> [(a, a)]
+splitInterval k (lo, hi) =
+  [ (lo+i*blockSize, (lo+(i+1)*blockSize-1) `min` hi)
+  | i <- [0..k-1] ]
+  where
+    size = (hi-lo+1)
+    blockSize = (size + k - 1) `div` k -- division rounding up
+{-
+prop_split_1 (Positive k) (lo, hi) =
+  -- Check that all elements occur exactly once
+  concat [[x..y] | (x, y) <- splitInterval k (lo, hi)] === [lo..hi]
+
+-- Check that we have the correct number and distribution of blocks
+prop_split_2 (Positive k) (lo, hi) =
+  counterexample (show splits) $ conjoin
+    [counterexample "Reason: too many splits" $
+       length splits <= k,
+     counterexample "Reason: too few splits" $
+       length [lo..hi] >= k ==> length splits == k,
+     counterexample "Reason: uneven distribution" $
+      not (null splits) ==>
+       minimum (map length splits) + 1 >= maximum (map length splits)]
+  where
+    splits = splitInterval k (lo, hi)
+-}
diff --git a/misc/analyse_trace.pl b/misc/analyse_trace.pl
deleted file mode 100644
--- a/misc/analyse_trace.pl
+++ /dev/null
@@ -1,32 +0,0 @@
-:- use_module(boo067_good, []).
-:- use_module(boo067_bad, []).
-
-ground(Pred, X) :-
-	call(Pred, Y),
-	numbervars(Y, 1, _),
-	X=Y.
-
-default(Pred, X) :-
-    call(Pred, boo067_good, boo067_bad, X).
-
-missing(X) :- default(missing, X).
-missing(Good, Bad, X) :-
-	ground(Good:lemma, X),
-	\+ found(Bad, add(rule(_, X))).
-
-variant(rule(N, X=Y), rule(N, X=Y)).
-variant(rule(N, X=Y), rule(N, Y=X)).
-
-found(Mod, Rule) :-
-	variant(Rule, Rule1),
-	Mod:step(add(Rule1)).
-
-gone(Mod, rule(N, X)) :-
-	ground(Mod:lemma, X),
-	found(Mod, rule(N, X)),
-	Mod:step(delete(N)).
-
-reappeared(Mod, rule(N, X), M) :-
-	ground(found(Mod), rule(N, X)),
-	found(Mod, rule(M, X)),
-	M > N.
diff --git a/misc/bench.hs b/misc/bench.hs
deleted file mode 100644
--- a/misc/bench.hs
+++ /dev/null
@@ -1,74 +0,0 @@
-{-# LANGUAGE PatternGuards, FlexibleInstances #-}
-import Criterion.Main
-import Twee.Term hiding (isFun)
-import qualified Twee.Term
-import Test.QuickCheck
-import Data.Int
-import Data.Maybe
-import Twee.Term.Core hiding (subst)
-
-instance Num (Fun Int) where fromInteger n = F (fromInteger n) (fromInteger n)
-instance Num Var where fromInteger = V . fromInteger
-
-t0, t1, u0, u1, t2, t, u :: Term Int
-t0 = build $ fun 0 [var 0, fun 0 [var 0, fun 0 [fun 0 [var 0, var 1], var 2]]]
-u0 = build $ fun 0 [fun 0 [fun 2 [fun 2 [var 2, var 2], var 1], fun 0 [fun 2 [var 2, var 2], var 3]], fun 0 [fun 0 [fun 2 [fun 2 [var 2, var 2], var 1], fun 0 [fun 2 [var 2, var 2], var 3]], fun 0 [fun 0 [fun 0 [fun 2 [fun 2 [var 2, var 2], var 1], fun 0 [fun 2 [var 2, var 2], var 3]], fun 2 [fun 2 [var 2, var 2], var 1]], fun 2 [var 2, var 2]]]]
-
-t1 = build $ fun 0 [fun 1 [var 0], fun 1 [var 1]]
-u1 = build $ fun 0 [fun 1 [fun 0 [fun 2 emptyTermList, fun 3 emptyTermList]], fun 1 [fun 0 [fun 4 emptyTermList, fun 5 emptyTermList]]]
-
-t2 = build $ fun 0 [var 0, fun 1 [var 1, fun 1 [var 1, var 1]]]
-u2 = build $ fun 0 [fun 0 [var 2, var 2], var 2]
-
-t = t0
-u = u0
-
-Just sub = match t u
-
-mgu1 t u = let Just sub = unifyTri t u in build (subst sub t)
-mgu2 t u = let Just sub = unify t u in build (subst sub t)
-
-Just sub' = unifyTri t2 u2
-Just csub' = unify t2 u2
-
-main = do
-  print t
-  print u
-  print (match t u)
-  print (build (subst sub t))
-  print (unifyTri t2 u2)
-  print (close sub')
-  print (build (subst sub' t2))
-  print (build (subst sub' u2))
-  print (mgu1 t2 u2)
-  print (mgu2 t2 u2)
-  print (t == t)
-  print (build (subst sub t) == u)
-  print (build (subst sub' t2) == build (subst sub' u2))
-  print (build (subst csub' t1) == build (subst sub' t1))
-  print (mgu1 t2 u2 == mgu2 t2 u2)
-  print (build (subst csub' t2) == build (subst sub' t2))
-  defaultMain [
-    bench "eq-t" (whnf (uncurry (==)) (t, t)),
-    bench "eq-u" (whnf (uncurry (==)) (u, u)),
-    bench "match" (whnf (fromJust . uncurry match) (t, u)),
-    bench "subst" (whnf (build . uncurry subst) (sub, t)),
-    bench "unifyTri" (whnf (fromJust . uncurry unifyTri) (t2, u2)),
-    bench "unify-close" (whnf (uncurry unify) (t2, u2)),
-    bench "unify-subst-iter1" (whnf (build . uncurry subst) (sub', t2)),
-    bench "unify-subst-iter2" (whnf (build . uncurry subst) (sub', u2)),
-    bench "unify-subst-closed1" (whnf (build . uncurry subst) (csub', t2)),
-    bench "unify-subst-closed2" (whnf (build . uncurry subst) (csub', u2)),
-    bench "mgu-tri" (whnf (uncurry mgu1) (t2, u2)),
-    bench "mgu-close" (whnf (uncurry mgu2) (t2, u2)),
-    bench "make-constant" (whnf (build . uncurry fun) (F 0 0, emptyTermList)),
-    bench "baseline" (whnf (uncurry (+)) (0 :: Int, 0))]
-
-prop :: Bool -> NonNegative (Small Int) -> NonNegative (Small Int) -> Property
-prop fun_ (NonNegative (Small index_)) (NonNegative (Small size_)) =
-  (isFun x, index x, size x) === (fun_, index_, size_)
-  where
-    x = toSymbol (fromSymbol (Symbol fun_ index_ size_))
-
-prop2 :: Int64 -> Property
-prop2 x = fromSymbol (toSymbol x) === x
diff --git a/misc/ring_conn.pl b/misc/ring_conn.pl
deleted file mode 100644
--- a/misc/ring_conn.pl
+++ /dev/null
@@ -1,801 +0,0 @@
-:- module(ring_conn, [step/1, lemma/1]).
-:- discontiguous(step/1).
-:- discontiguous(lemma/1).
-:- style_check(-singleton).
-step(add(rule(1, (X1 + X2) = (X2 + X1)))).
-step(add(rule(2, ((X1 + X2) + X3) = (X1 + (X2 + X3))))).
-step(add(rule(3, (0 + X1) = X1))).
-step(add(rule(4, (X1 + -X1) = 0))).
-step(add(rule(5, ((X1 * X2) * X3) = (X1 * (X2 * X3))))).
-step(add(rule(6, ((X1 * X2) + (X1 * X3)) = (X1 * (X2 + X3))))).
-step(add(rule(7, ((X1 * X3) + (X2 * X3)) = ((X1 + X2) * X3)))).
-step(add(rule(8, (X1 * (X1 * X1)) = X1))).
-step(add(rule(9, -0 = 0))).
-step(add(rule(10, (X1 + 0) = X1))).
-step(add(rule(11, (X1 + (-X1 + X2)) = X2))).
-step(add(rule(12, -(-X1) = X1))).
-step(add(rule(13, (X1 + (X2 + X3)) = (X3 + (X1 + X2))))).
-step(add(rule(14, (X1 + (X2 + X3)) = (X2 + (X1 + X3))))).
-step(add(rule(15, ((X1 + X1) * X2) = (X1 * (X2 + X2))))).
-step(add(rule(16, (X2 + (X1 + -X2)) = X1))).
-step(add(rule(17, (0 * (X1 + X1)) = (0 * X1)))).
-step(add(rule(18, (X1 * (X1 * (X1 * X2))) = (X1 * X2)))).
-step(hard((X1 + (X2 + X3)) = (X3 + (X2 + X1)))).
-step(hard((X1 + (X2 + X3)) = (X1 + (X3 + X2)))).
-step(add(rule(19, (X1 + (X2 + -(X1 + X2))) = 0))).
-step(add(rule(20, (X1 + -(-X2 + X1)) = X2))).
-step(add(rule(21, (X1 * ((X1 * X1) + X2)) = (X1 + (X1 * X2))))).
-step(add(rule(22, (X1 + (X1 * 0)) = X1))).
-step(add(rule(23, (X1 * 0) = 0))).
-step(add(rule(24, (X1 * (X2 + (X1 * X1))) = (X1 + (X1 * X2))))).
-step(add(rule(25, (X2 + -(X1 + X2)) = -X1))).
-step(add(rule(26, ((X1 + X1) * (X1 * X1)) = (X1 + X1)))).
-step(hard(0 = (X1 + (X2 + -(X2 + X1))))).
-step(add(rule(27, (X2 + -(X2 + -X1)) = X1))).
-step(add(rule(28, -(-X1 + -X2) = (X2 + X1)))).
-step(add(rule(29, (X1 * (0 * X2)) = (0 * X2)))).
-step(add(rule(30, (X1 + (X2 * (X1 * X1))) = ((X1 + X2) * (X1 * X1))))).
-step(add(rule(31, (X2 + -(X2 + X1)) = -X1))).
-step(hard((-X1 + (X2 + (X3 + X1))) = (X3 + X2))).
-step(add(rule(32, (X3 + (X2 + (-X3 + X1))) = (X1 + X2)))).
-step(add(rule(33, (X3 + (X1 + (X2 + -X3))) = (X1 + X2)))).
-step(add(rule(34, -(X1 + -X2) = (X2 + -X1)))).
-step(add(rule(35, (-X1 + -X2) = -(X2 + X1)))).
-step(add(rule(36, (X1 + (X1 * -(X1 * X1))) = 0))).
-step(add(rule(37, (-X1 * -(-X1 * -X1)) = X1))).
-step(add(rule(38, (-X1 * (-X1 * X1)) = X1))).
-step(add(rule(39, (X1 * -(X1 * X1)) = -X1))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X2 + (X3 + (X4 + X1))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X2 + (X4 + X1))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X4 + (X1 + X2))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X4 + (X3 + (X1 + X2))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X1 + (X2 + X4))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X4 + (X2 + (X3 + X1))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X2 + (X4 + (X1 + X3))))).
-step(add(rule(40, ((X1 + X1) * (X2 * X3)) = (X1 * ((X2 + X2) * X3))))).
-step(add(rule(41, (X1 * (X1 * (X1 + X1))) = (X1 + X1)))).
-step(add(rule(42, (X1 * (X2 * (X3 + X3))) = (X1 * ((X2 + X2) * X3))))).
-step(add(rule(43, (X1 + (X1 * (X1 * X2))) = (X1 * (X1 * (X1 + X2)))))).
-step(add(rule(44, (X1 + (X1 * (X2 * X1))) = (X1 * ((X1 + X2) * X1))))).
-step(add(rule(45, (X1 + (0 * X1)) = X1))).
-step(add(rule(46, (0 * X1) = 0))).
-step(add(rule(47, (X2 + (X3 + (-(X2 + X3) + X1))) = X1))).
-step(hard((X1 + (X2 + (-(X2 + X1) + X3))) = X3)).
-step(add(rule(48, (X1 * (X1 * -X1)) = -X1))).
-step(add(rule(49, -(-X1 + X2) = (X1 + -X2)))).
-step(add(rule(50, ((X1 * X2) + ((X1 * X3) + X4)) = ((X1 * (X2 + X3)) + X4)))).
-step(add(rule(51, ((X1 * X2) + ((X3 * X2) + X4)) = (((X1 + X3) * X2) + X4)))).
-step(add(rule(52, ((X1 * (X2 * X4)) + (X3 * X4)) = (((X1 * X2) + X3) * X4)))).
-step(add(rule(53, (((X1 * X1) + X2) * X1) = (X1 + (X2 * X1))))).
-step(add(rule(54, (X1 + (-(X1 * X1) * X1)) = 0))).
-step(add(rule(55, (-(X1 * X1) * X1) = -X1))).
-step(add(rule(56, ((X1 + (X1 * X2)) * X3) = (X1 * (X3 + (X2 * X3)))))).
-step(add(rule(57, ((X2 + (X1 * X1)) * X1) = (X1 + (X2 * X1))))).
-step(add(rule(58, ((X1 * X4) + (X2 * (X3 * X4))) = ((X1 + (X2 * X3)) * X4)))).
-step(add(rule(59, (X1 * (X2 * (X1 * (X2 * (X1 * X2))))) = (X1 * X2)))).
-step(add(rule(60, (X1 * ((X1 * (X1 * X2)) + X3)) = (X1 * (X2 + X3))))).
-step(add(rule(61, (X1 * (X2 + (X1 * (X1 * X3)))) = (X1 * (X2 + X3))))).
-step(add(rule(62, (X1 * (X2 + X2)) = (X1 * (X1 * ((X1 + X1) * X2)))))).
-step(add(rule(63, (X1 * (X1 * (X1 + (X1 * X2)))) = (X1 + (X1 * X2))))).
-step(add(rule(64, (X1 + (X1 * (-(X1 * X1) + X2))) = (X1 * X2)))).
-step(add(rule(65, ((X1 + X1) * (X2 * X3)) = (X1 * (X2 * (X3 + X3)))))).
-step(add(rule(66, (-(X1 * X3) + (X1 * (X2 + X3))) = (X1 * X2)))).
-step(add(rule(67, -(X1 * -X2) = (X1 * X2)))).
-step(add(rule(68, -(X1 * X2) = (X1 * -X2)))).
-step(add(rule(69, (X1 * (X2 * (-X2 * -X2))) = (X1 * X2)))).
-step(add(rule(70, (-X1 * (X1 * -X1)) = X1))).
-step(add(rule(71, (X1 * (-X1 * -X1)) = X1))).
-step(add(rule(72, (-X1 * (X1 * X1)) = -X1))).
-step(add(rule(73, (X1 * (-X1 * X1)) = -X1))).
-step(add(rule(74, ((X2 * -X3) + ((X1 + X2) * X3)) = (X1 * X3)))).
-step(add(rule(75, (-X1 * -X2) = (X1 * X2)))).
-step(add(rule(76, (-X1 * X2) = (X1 * -X2)))).
-step(add(rule(77, (X2 + (X3 + (X1 + -(X2 + X3)))) = X1))).
-step(hard(X1 = (X2 + (X3 + (-(X3 + X2) + X1))))).
-step(add(rule(78, ((X1 + (X1 * X1)) * (X1 * X1)) = (X1 + (X1 * X1))))).
-step(add(rule(79, (X1 + (X1 * ((X1 + X1) * -X1))) = -X1))).
-step(add(rule(80, ((X1 * X2) + (X3 + (X1 * X4))) = (X3 + (X1 * (X4 + X2)))))).
-step(add(rule(81, ((X1 * X2) + (X3 + (X4 * X2))) = (X3 + ((X4 + X1) * X2))))).
-step(add(rule(82, ((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X1 + X2))) * X3)))).
-step(add(rule(83, ((X1 + (X1 + X2)) * X3) = ((X1 * (X3 + X3)) + (X2 * X3))))).
-step(add(rule(84, ((X1 + (X1 + X1)) * X2) = (X1 * (X2 + (X2 + X2)))))).
-step(add(rule(85, ((X1 + (X2 + X2)) * X3) = ((X1 * X3) + (X2 * (X3 + X3)))))).
-step(hard(((X1 + X2) * (X3 + X3)) = ((X2 + (X1 + (X2 + X1))) * X3))).
-step(add(rule(86, ((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X2 + X3))))))).
-step(add(rule(87, (X1 * (X2 + (X2 + X3))) = (((X1 + X1) * X2) + (X1 * X3))))).
-step(add(rule(88, (X1 * (X2 + (X3 + X3))) = ((X1 * X2) + ((X1 + X1) * X3))))).
-step(add(rule(89, (X1 + (X1 * (X2 + X2))) = (X1 + ((X1 + X1) * X2))))).
-step(add(rule(90, (X1 * -(X2 + X2)) = ((X1 + X1) * -X2)))).
-step(hard(((X1 + X1) * (X2 + X3)) = (X1 * (X3 + (X2 + (X3 + X2)))))).
-step(add(rule(91, (X1 * (((X1 * X1) + X2) * X3)) = ((X1 + (X1 * X2)) * X3)))).
-step(add(rule(92, (X1 * (X3 + ((X1 * X1) + X2))) = (X1 + (X1 * (X2 + X3)))))).
-step(add(rule(93, (X1 * (X2 + (X3 + (X1 * X1)))) = (X1 + (X1 * (X2 + X3)))))).
-step(add(rule(94, (X1 * ((X2 + (X1 * X1)) * X3)) = ((X1 + (X1 * X2)) * X3)))).
-step(add(rule(95, (X1 + (-(X2 + X1) + X3)) = (-X2 + X3)))).
-step(add(rule(96, (X3 + -(X1 + (X2 + X3))) = -(X1 + X2)))).
-step(add(rule(97, (X1 + (X2 + -(X3 + X1))) = (X2 + -X3)))).
-step(add(rule(98, (((X1 * X1) + X2) * (X1 * X3)) = ((X1 + (X2 * X1)) * X3)))).
-step(add(rule(99, ((X1 + (X2 * X3)) * (X3 * X3)) = (((X1 * X3) + X2) * X3)))).
-step(add(rule(100, ((X1 + (X2 * X2)) * (X2 * X3)) = ((X2 + (X1 * X2)) * X3)))).
-step(add(rule(101, (X1 * (X1 * -(X1 + X1))) = -(X1 + X1)))).
-step(add(rule(102, (X1 * (X1 * ((X1 + X1) * X2))) = ((X1 + X1) * X2)))).
-step(add(rule(103, ((X1 + (X1 + X1)) * (X1 * X1)) = (X1 + (X1 + X1))))).
-step(add(rule(104, ((X1 + (X1 * (X2 * X2))) * X2) = (X1 * (X2 + X2))))).
-step(add(rule(105, (((X1 * (X2 * X2)) + X3) * X2) = ((X1 + X3) * X2)))).
-step(add(rule(106, (X1 + (X1 * (X2 + (X1 * -X1)))) = (X1 * X2)))).
-step(add(rule(107, (X1 + (((X1 * -X1) + X2) * X1)) = (X2 * X1)))).
-step(add(rule(108, (X1 + ((X2 + (X1 * -X1)) * X1)) = (X2 * X1)))).
-step(add(rule(109, (X1 + ((-X2 + (X1 * X1)) * -X1)) = (X2 * X1)))).
-step(add(rule(110, ((X3 * -X2) + ((X3 + X1) * X2)) = (X1 * X2)))).
-step(hard(X1 = (X2 + (X3 + (X1 + -(X3 + X2)))))).
-step(add(rule(111, ((X3 * X2) + ((X1 + X3) * -X2)) = (X1 * -X2)))).
-step(add(rule(112, ((X1 + (X1 + (X1 + X1))) * X2) = (X1 * (X2 + (X2 + (X2 + X2))))))).
-step(add(rule(113, ((X1 * X3) + (X2 * (X1 * (X1 * X3)))) = ((X1 + X2) * (X1 * (X1 * X3)))))).
-step(add(rule(114, (X1 * ((X1 + X1) * ((X1 + X1) * (X2 + X2)))) = ((X1 + X1) * X2)))).
-step(add(rule(115, (((X1 + X2) * (X1 * X1)) + X3) = (X1 + ((X2 * (X1 * X1)) + X3))))).
-step(interreduce).
-step(delete(rule(11, (X1 + (-X1 + X2)) = X2))).
-step(delete(rule(13, (X1 + (X2 + X3)) = (X3 + (X1 + X2))))).
-step(delete(rule(17, (0 * (X1 + X1)) = (0 * X1)))).
-step(delete(rule(19, (X1 + (X2 + -(X1 + X2))) = 0))).
-step(delete(rule(20, (X1 + -(-X2 + X1)) = X2))).
-step(delete(rule(21, (X1 * ((X1 * X1) + X2)) = (X1 + (X1 * X2))))).
-step(delete(rule(22, (X1 + (X1 * 0)) = X1))).
-step(delete(rule(25, (X2 + -(X1 + X2)) = -X1))).
-step(delete(rule(26, ((X1 + X1) * (X1 * X1)) = (X1 + X1)))).
-step(delete(rule(27, (X2 + -(X2 + -X1)) = X1))).
-step(delete(rule(28, -(-X1 + -X2) = (X2 + X1)))).
-step(delete(rule(29, (X1 * (0 * X2)) = (0 * X2)))).
-step(delete(rule(36, (X1 + (X1 * -(X1 * X1))) = 0))).
-step(delete(rule(37, (-X1 * -(-X1 * -X1)) = X1))).
-step(delete(rule(38, (-X1 * (-X1 * X1)) = X1))).
-step(delete(rule(39, (X1 * -(X1 * X1)) = -X1))).
-step(delete(rule(45, (X1 + (0 * X1)) = X1))).
-step(delete(rule(47, (X2 + (X3 + (-(X2 + X3) + X1))) = X1))).
-step(delete(rule(50, ((X1 * X2) + ((X1 * X3) + X4)) = ((X1 * (X2 + X3)) + X4)))).
-step(delete(rule(51, ((X1 * X2) + ((X3 * X2) + X4)) = (((X1 + X3) * X2) + X4)))).
-step(delete(rule(53, (((X1 * X1) + X2) * X1) = (X1 + (X2 * X1))))).
-step(delete(rule(54, (X1 + (-(X1 * X1) * X1)) = 0))).
-step(delete(rule(55, (-(X1 * X1) * X1) = -X1))).
-step(delete(rule(60, (X1 * ((X1 * (X1 * X2)) + X3)) = (X1 * (X2 + X3))))).
-step(delete(rule(62, (X1 * (X2 + X2)) = (X1 * (X1 * ((X1 + X1) * X2)))))).
-step(delete(rule(64, (X1 + (X1 * (-(X1 * X1) + X2))) = (X1 * X2)))).
-step(delete(rule(66, (-(X1 * X3) + (X1 * (X2 + X3))) = (X1 * X2)))).
-step(delete(rule(67, -(X1 * -X2) = (X1 * X2)))).
-step(delete(rule(69, (X1 * (X2 * (-X2 * -X2))) = (X1 * X2)))).
-step(delete(rule(70, (-X1 * (X1 * -X1)) = X1))).
-step(delete(rule(71, (X1 * (-X1 * -X1)) = X1))).
-step(delete(rule(72, (-X1 * (X1 * X1)) = -X1))).
-step(delete(rule(73, (X1 * (-X1 * X1)) = -X1))).
-step(delete(rule(74, ((X2 * -X3) + ((X1 + X2) * X3)) = (X1 * X3)))).
-step(delete(rule(75, (-X1 * -X2) = (X1 * X2)))).
-step(delete(rule(77, (X2 + (X3 + (X1 + -(X2 + X3)))) = X1))).
-step(delete(rule(78, ((X1 + (X1 * X1)) * (X1 * X1)) = (X1 + (X1 * X1))))).
-step(delete(rule(79, (X1 + (X1 * ((X1 + X1) * -X1))) = -X1))).
-step(delete(rule(91, (X1 * (((X1 * X1) + X2) * X3)) = ((X1 + (X1 * X2)) * X3)))).
-step(delete(rule(95, (X1 + (-(X2 + X1) + X3)) = (-X2 + X3)))).
-step(delete(rule(98, (((X1 * X1) + X2) * (X1 * X3)) = ((X1 + (X2 * X1)) * X3)))).
-step(delete(rule(107, (X1 + (((X1 * -X1) + X2) * X1)) = (X2 * X1)))).
-step(add(rule(116, (X1 + (X2 * (X3 * (X1 * X1)))) = ((X1 + (X2 * X3)) * (X1 * X1))))).
-step(add(rule(117, (X1 + (X2 + (X3 * (X1 * X1)))) = (X2 + ((X1 + X3) * (X1 * X1)))))).
-step(add(rule(118, (X1 * (X1 * (X1 + (X1 + X1)))) = (X1 + (X1 + X1))))).
-step(add(rule(119, ((X1 + (X2 + X2)) * (X1 * X1)) = (X1 + (X2 * (X1 * (X1 + X1))))))).
-step(add(rule(120, (X1 + (X1 * (X2 * (X3 * X1)))) = (X1 * (((X2 * X3) + X1) * X1))))).
-step(add(rule(121, ((X2 * -X3) + (X1 * X3)) = ((X1 + -X2) * X3)))).
-step(add(rule(122, (X1 + (-(X1 + X2) + X3)) = (-X2 + X3)))).
-step(add(rule(123, (X1 + (X2 + -(X1 + X3))) = (X2 + -X3)))).
-step(add(rule(124, (X3 + -(X1 + (X3 + X2))) = -(X1 + X2)))).
-step(add(rule(125, (X1 * (X1 * (X2 + (X1 * X3)))) = (X1 * ((X1 * X2) + X3))))).
-step(add(rule(126, ((X3 + (X3 + (X2 + X2))) * X4) = ((X3 + X2) * (X4 + X4))))).
-step(hard(((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X2 + X1))) * X3))).
-step(hard(((X1 + X2) * (X3 + X3)) = ((X2 + (X1 + (X1 + X2))) * X3))).
-step(hard(((X1 * (X2 + X2)) + (X3 * X2)) = ((X1 + (X3 + X1)) * X2))).
-step(add(rule(127, ((X1 + (X1 + X2)) * X3) = ((X2 * X3) + (X1 * (X3 + X3)))))).
-step(hard(((X1 + (X1 + X2)) * X3) = ((X2 + (X1 + X1)) * X3))).
-step(hard(((X1 * X2) + (X3 * (X2 + X2))) = ((X3 + (X1 + X3)) * X2))).
-step(hard(((X1 + (X2 + X2)) * X3) = ((X2 * (X3 + X3)) + (X1 * X3)))).
-step(add(rule(128, (X1 * (X4 + (X4 + (X3 + X3)))) = ((X1 + X1) * (X4 + X3))))).
-step(hard(((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X3 + X2)))))).
-step(hard(((X1 + X1) * (X2 + X3)) = (X1 * (X3 + (X2 + (X2 + X3)))))).
-step(hard((((X1 + X1) * X2) + (X1 * X3)) = (X1 * (X2 + (X3 + X2))))).
-step(add(rule(129, (X1 * (X2 + (X2 + X3))) = ((X1 * X3) + ((X1 + X1) * X2))))).
-step(hard((X1 * (X2 + (X2 + X3))) = (X1 * (X3 + (X2 + X2))))).
-step(hard(((X1 * X2) + ((X1 + X1) * X3)) = (X1 * (X3 + (X2 + X3))))).
-step(hard((X1 * (X2 + (X3 + X3))) = (((X1 + X1) * X3) + (X1 * X2)))).
-step(add(rule(130, (X1 * ((X1 * (X1 * X2)) + (X3 + X3))) = ((X1 * X2) + ((X1 + X1) * X3))))).
-step(add(rule(131, (((X3 * X2) + X1) * (X2 * X2)) = (((X1 * X2) + X3) * X2)))).
-step(add(rule(132, (X2 + (-X2 + (X1 * -X2))) = (X1 * -X2)))).
-step(add(rule(133, (X1 * ((X1 + X1) * (X1 + (X1 + (X1 + X1))))) = (X1 + X1)))).
-step(add(rule(134, (X4 + (X2 + (X3 + (-X4 + X1)))) = (X1 + (X2 + X3))))).
-step(add(rule(135, -(X1 + (-X2 + X3)) = (X2 + -(X3 + X1))))).
-step(add(rule(136, (X4 + (X1 + (X2 + (X3 + -X4)))) = (X1 + (X2 + X3))))).
-step(add(rule(137, -(X1 + (X2 + -X3)) = (X3 + -(X1 + X2))))).
-step(add(rule(138, (-X1 + (-X2 + X3)) = (-(X2 + X1) + X3)))).
-step(add(rule(139, (-X1 + (X2 + -X3)) = (X2 + -(X3 + X1))))).
-step(add(rule(140, -(X3 + (X1 * -X2)) = ((X1 * X2) + -X3)))).
-step(add(rule(141, ((X2 * -X3) + -X1) = -(X1 + (X2 * X3))))).
-step(add(rule(142, (-X3 + (X1 * -X2)) = -((X1 * X2) + X3)))).
-step(add(rule(143, ((X1 + -X2) * -X3) = ((X2 + -X1) * X3)))).
-step(add(rule(144, ((X2 + (X1 * (X3 * X3))) * X3) = ((X1 + X2) * X3)))).
-step(add(rule(145, ((X1 + X1) * (X1 + (X1 + (X1 + X1)))) = (X1 * (X1 + X1))))).
-step(add(rule(146, (X1 + (X1 * ((X1 * -X1) + X2))) = (X1 * X2)))).
-step(add(rule(147, (X2 + (-X2 + (X1 * X2))) = (X1 * X2)))).
-step(add(rule(148, ((X1 * (X2 + X2)) + ((X1 + X1) * X3)) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(149, (((X1 + X1) * X2) + (X1 * (X3 + X3))) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(150, (X1 + (X1 + ((X1 + X1) * X2))) = ((X1 + X1) * (X2 + (X1 * X1)))))).
-step(add(rule(151, (((X1 + X1) * X3) + (X2 * (X3 + X3))) = ((X1 + X2) * (X3 + X3))))).
-step(add(rule(152, ((X1 * (X3 + X3)) + ((X2 + X2) * X3)) = ((X1 + X2) * (X3 + X3))))).
-step(add(rule(153, (((X1 + X1) * -X2) + X3) = ((X1 * -(X2 + X2)) + X3)))).
-step(add(rule(154, ((X1 * (X2 + X2)) + X3) = (((X1 + X1) * X2) + X3)))).
-step(add(rule(155, (X1 + ((X2 + X2) * X3)) = (X1 + (X2 * (X3 + X3)))))).
-step(add(rule(156, (X1 + ((X1 + (X1 * X1)) * -X1)) = (X1 * -X1)))).
-step(add(rule(157, (X2 + ((X1 + (X2 * X2)) * -X2)) = (X1 * -X2)))).
-step(add(rule(158, ((((X2 * -X2) + X1) * -X2) + X3) = (X2 + (X3 + (X1 * -X2)))))).
-step(add(rule(159, ((X3 * X2) + ((X3 + X1) * -X2)) = (X1 * -X2)))).
-step(add(rule(160, (((? * X2) + ((? * X2) + ((X3 + ?) * -(X2 + X2)))) * X4) = (X3 * (X2 * -(X4 + X4)))))).
-step(add(rule(161, (((X1 * X2) + ((X1 * X2) + ((X3 + X1) * -(X2 + X2)))) * X4) = (((? * X2) + ((? * X2) + ((X3 + ?) * -(X2 + X2)))) * X4)))).
-step(interreduce).
-step(delete(rule(63, (X1 * (X1 * (X1 + (X1 * X2)))) = (X1 + (X1 * X2))))).
-step(delete(rule(82, ((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X1 + X2))) * X3)))).
-step(delete(rule(85, ((X1 + (X2 + X2)) * X3) = ((X1 * X3) + (X2 * (X3 + X3)))))).
-step(delete(rule(86, ((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X2 + X3))))))).
-step(delete(rule(88, (X1 * (X2 + (X3 + X3))) = ((X1 * X2) + ((X1 + X1) * X3))))).
-step(delete(rule(89, (X1 + (X1 * (X2 + X2))) = (X1 + ((X1 + X1) * X2))))).
-step(delete(rule(96, (X3 + -(X1 + (X2 + X3))) = -(X1 + X2)))).
-step(delete(rule(104, ((X1 + (X1 * (X2 * X2))) * X2) = (X1 * (X2 + X2))))).
-step(delete(rule(105, (((X1 * (X2 * X2)) + X3) * X2) = ((X1 + X3) * X2)))).
-step(delete(rule(109, (X1 + ((-X2 + (X1 * X1)) * -X1)) = (X2 * X1)))).
-step(delete(rule(110, ((X3 * -X2) + ((X3 + X1) * X2)) = (X1 * X2)))).
-step(delete(rule(111, ((X3 * X2) + ((X1 + X3) * -X2)) = (X1 * -X2)))).
-step(delete(rule(122, (X1 + (-(X1 + X2) + X3)) = (-X2 + X3)))).
-step(delete(rule(132, (X2 + (-X2 + (X1 * -X2))) = (X1 * -X2)))).
-step(delete(rule(133, (X1 * ((X1 + X1) * (X1 + (X1 + (X1 + X1))))) = (X1 + X1)))).
-step(delete(rule(134, (X4 + (X2 + (X3 + (-X4 + X1)))) = (X1 + (X2 + X3))))).
-step(delete(rule(135, -(X1 + (-X2 + X3)) = (X2 + -(X3 + X1))))).
-step(delete(rule(138, (-X1 + (-X2 + X3)) = (-(X2 + X1) + X3)))).
-step(delete(rule(156, (X1 + ((X1 + (X1 * X1)) * -X1)) = (X1 * -X1)))).
-step(delete(rule(160, (((? * X2) + ((? * X2) + ((X3 + ?) * -(X2 + X2)))) * X4) = (X3 * (X2 * -(X4 + X4)))))).
-step(add(rule(162, (((? * X2) + ((? * X2) + ((? + X3) * -(X2 + X2)))) * X4) = (X3 * (X2 * -(X4 + X4)))))).
-step(delete(rule(161, (((X1 * X2) + ((X1 * X2) + ((X3 + X1) * -(X2 + X2)))) * X4) = (((? * X2) + ((? * X2) + ((X3 + ?) * -(X2 + X2)))) * X4)))).
-step(add(rule(163, (((X1 * X2) + ((X1 * X2) + ((X3 + X1) * -(X2 + X2)))) * X4) = (((? * X2) + ((? * X2) + ((? + X3) * -(X2 + X2)))) * X4)))).
-step(add(rule(164, (X1 * (X2 * ((X3 + X3) * X4))) = ((X1 + X1) * (X2 * (X3 * X4)))))).
-step(add(rule(165, (X1 * (X2 * ((X3 + X3) * X4))) = (X1 * ((X2 + X2) * (X3 * X4)))))).
-step(add(rule(166, ((X1 + X1) * (X2 * ((X2 + X2) * (X2 + X2)))) = (X1 * (X2 + X2))))).
-step(add(rule(167, (X1 * (X2 * (X3 * (X4 + X4)))) = (X1 * (X2 * ((X3 + X3) * X4)))))).
-step(add(rule(168, (X1 * (((X2 + X2) * X3) + X4)) = (X1 * ((X2 * (X3 + X3)) + X4))))).
-step(add(rule(169, (X1 * (X2 + ((X3 + X3) * X4))) = (X1 * (X2 + (X3 * (X4 + X4))))))).
-step(add(rule(170, ((X1 * (X1 * (X1 + X2))) + X3) = (X1 + ((X1 * (X1 * X2)) + X3))))).
-step(add(rule(171, (X1 + (X2 + (X1 * (X1 * X3)))) = (X2 + (X1 * (X1 * (X1 + X3))))))).
-step(add(rule(172, (X1 * (X1 * (X1 + (X2 + X2)))) = (X1 + (X1 * ((X1 + X1) * X2)))))).
-step(add(rule(173, ((X1 * ((X1 + X2) * X1)) + X3) = (X1 + ((X1 * (X2 * X1)) + X3))))).
-step(add(rule(174, (X1 + (X2 + (X1 * (X3 * X1)))) = (X2 + (X1 * ((X1 + X3) * X1)))))).
-step(add(rule(175, (X1 * ((X1 + (X2 + X2)) * X1)) = (X1 + (X1 * (X2 * (X1 + X1))))))).
-step(add(rule(176, ((((X1 + X1) * X2) + X3) * X4) = (((X1 * (X2 + X2)) + X3) * X4)))).
-step(add(rule(177, ((X1 + (X1 * X2)) * (X3 * X4)) = (X1 * ((X3 + (X2 * X3)) * X4))))).
-step(add(rule(178, (X1 * (X2 * (X3 + (X4 * X3)))) = (X1 * ((X2 + (X2 * X4)) * X3))))).
-step(add(rule(179, (X1 * (X2 + ((X3 + X3) * X2))) = ((X1 + ((X1 + X1) * X3)) * X2)))).
-step(add(rule(180, (X1 * (X2 + (X1 * (X3 * X2)))) = (X1 * (X1 * ((X1 + X3) * X2)))))).
-step(add(rule(181, (X1 * (X2 + (X3 * (X1 * X2)))) = (X1 * ((X1 + X3) * (X1 * X2)))))).
-step(add(rule(182, ((X1 + (X1 * (X2 * X3))) * X4) = (X1 * (X4 + (X2 * (X3 * X4))))))).
-step(add(rule(183, ((X1 + (X1 * (X2 + X2))) * X3) = (X1 * (X3 + (X2 * (X3 + X3))))))).
-step(add(rule(184, ((X1 + ((X2 + X2) * X3)) * X4) = ((X1 + (X2 * (X3 + X3))) * X4)))).
-step(add(rule(185, (X1 * -(X2 + (X1 * X1))) = -(X1 + (X1 * X2))))).
-step(add(rule(186, ((X1 + (X2 * X2)) * -X2) = -(X2 + (X1 * X2))))).
-step(add(rule(187, ((X2 * X1) + -(X1 + (X2 * X1))) = -X1))).
-step(add(rule(188, ((X1 * X3) + (X2 * -X3)) = ((X1 + -X2) * X3)))).
-step(add(rule(189, ((X1 * X2) + (X3 * (X2 + X4))) = ((X3 * X4) + ((X1 + X3) * X2))))).
-step(hard(((X1 + (X3 + X1)) * X2) = ((X3 + (X1 + X1)) * X2))).
-step(hard(((X1 + (X3 + X1)) * X2) = ((X1 + (X1 + X3)) * X2))).
-step(add(rule(190, (X1 + (X1 * ((X1 * X2) + X3))) = (X1 * (X3 + (X1 * (X1 + X2))))))).
-step(add(rule(191, (X1 + (X1 * ((X2 * X1) + X3))) = (X1 * (X3 + ((X1 + X2) * X1)))))).
-step(hard(((X1 + (X1 + X2)) * (X2 * X2)) = (X2 + (X1 * (X2 * (X2 + X2)))))).
-step(hard((X1 + (X1 * (X2 * (X1 + X1)))) = (X1 * ((X2 + (X2 + X1)) * X1)))).
-step(hard((X1 + (X1 * ((X1 + X1) * X2))) = (X1 * (X1 * (X2 + (X2 + X1)))))).
-step(add(rule(192, (X1 * ((X2 * X3) + ((X2 * X3) + X4))) = (X1 * (((X2 + X2) * X3) + X4))))).
-step(add(rule(193, (X1 * (X2 + (X2 + (X3 * X2)))) = ((X1 + (X1 + (X1 * X3))) * X2)))).
-step(add(rule(194, (X1 + (X1 + (X1 * (X2 + X2)))) = ((X1 + X1) * ((X1 * X1) + X2))))).
-step(add(rule(195, (X1 + (X1 * (X2 + (X1 * X3)))) = (X1 * (X2 + (X1 * (X3 + X1))))))).
-step(add(rule(196, (X1 + (X1 * (X2 + (X3 * X1)))) = (X1 * (X2 + ((X3 + X1) * X1)))))).
-step(add(rule(197, ((X1 + (X2 + (X1 * X1))) * (X1 * X1)) = (X1 + ((X1 + (X2 * X1)) * X1))))).
-step(add(rule(198, ((X1 + (X2 * -X2)) * -X2) = (X2 + (X1 * -X2))))).
-step(add(rule(199, (((X2 + X1) * (X1 * X1)) + X3) = (X1 + ((X2 * (X1 * X1)) + X3))))).
-step(add(rule(200, (X1 + (((X2 * X1) + X3) * X1)) = ((((X1 + X2) * X1) + X3) * X1)))).
-step(add(rule(201, (X1 + ((X2 + X3) * (X2 * X2))) = (X2 + ((X3 * (X2 * X2)) + X1))))).
-step(add(rule(202, ((X1 + ((X1 * X1) + X2)) * (X1 * X1)) = ((X1 + ((X2 + X1) * X1)) * X1)))).
-step(add(rule(203, (X1 + (X2 + (X3 * (X1 * X1)))) = (X2 + ((X3 + X1) * (X1 * X1)))))).
-step(add(rule(204, (X1 + ((X2 + (X3 * X1)) * X1)) = ((X2 + ((X1 + X3) * X1)) * X1)))).
-step(hard((X1 + (X2 * (X1 * (X1 + X1)))) = ((X2 + (X1 + X2)) * (X1 * X1)))).
-step(add(rule(205, ((X1 + X1) * (X2 + (X2 + X2))) = ((X1 + (X1 + X1)) * (X2 + X2))))).
-step(add(rule(206, (X1 * (X2 + (X2 + (X1 * (X1 + X1))))) = ((X1 + X1) * (X2 + (X1 * X1)))))).
-step(simplify_queue).
-step(interreduce).
-step(delete(rule(106, (X1 + (X1 * (X2 + (X1 * -X1)))) = (X1 * X2)))).
-step(delete(rule(117, (X1 + (X2 + (X3 * (X1 * X1)))) = (X2 + ((X1 + X3) * (X1 * X1)))))).
-step(delete(rule(121, ((X2 * -X3) + (X1 * X3)) = ((X1 + -X2) * X3)))).
-step(delete(rule(146, (X1 + (X1 * ((X1 * -X1) + X2))) = (X1 * X2)))).
-step(delete(rule(157, (X2 + ((X1 + (X2 * X2)) * -X2)) = (X1 * -X2)))).
-step(delete(rule(158, ((((X2 * -X2) + X1) * -X2) + X3) = (X2 + (X3 + (X1 * -X2)))))).
-step(delete(rule(159, ((X3 * X2) + ((X3 + X1) * -X2)) = (X1 * -X2)))).
-step(delete(rule(190, (X1 + (X1 * ((X1 * X2) + X3))) = (X1 * (X3 + (X1 * (X1 + X2))))))).
-step(delete(rule(191, (X1 + (X1 * ((X2 * X1) + X3))) = (X1 * (X3 + ((X1 + X2) * X1)))))).
-step(delete(rule(197, ((X1 + (X2 + (X1 * X1))) * (X1 * X1)) = (X1 + ((X1 + (X2 * X1)) * X1))))).
-step(add(rule(207, ((X1 + (X2 + (X1 * X1))) * (X1 * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(delete(rule(200, (X1 + (((X2 * X1) + X3) * X1)) = ((((X1 + X2) * X1) + X3) * X1)))).
-step(delete(rule(202, ((X1 + ((X1 * X1) + X2)) * (X1 * X1)) = ((X1 + ((X2 + X1) * X1)) * X1)))).
-step(hard(((X1 + X3) * (X2 + X2)) = ((X3 + X1) * (X2 + X2)))).
-step(hard(((X1 + X1) * (X2 + X3)) = ((X1 + X1) * (X3 + X2)))).
-step(add(rule(208, (X2 + (X2 + (X1 * (X2 * (X2 + X2))))) = ((X1 + X2) * (X2 * (X2 + X2)))))).
-step(add(rule(209, (X1 * ((X1 + X1) * (X1 + X1))) = (X1 + (X1 + (X1 + X1)))))).
-step(add(rule(210, (X3 + (X4 + (X2 + (-(X3 + X4) + X1)))) = (X1 + X2)))).
-step(add(rule(211, (X3 + (X4 + (X1 + (X2 + -(X3 + X4))))) = (X1 + X2)))).
-step(add(rule(212, (X1 * ((X1 * (X1 + X1)) + X2)) = (X1 + (X1 + (X1 * X2)))))).
-step(add(rule(213, (X1 * (X2 + (X1 * (X1 + X1)))) = (X1 + (X1 + (X1 * X2)))))).
-step(add(rule(214, ((X2 + (X3 + (X1 * X1))) * X1) = (X1 + ((X2 + X3) * X1))))).
-step(add(rule(215, (X2 + (X2 + (X1 * (X2 + X2)))) = ((X1 + (X2 * X2)) * (X2 + X2))))).
-step(add(rule(216, ((X1 + X1) * (X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(add(rule(217, ((X1 + X1) * ((X1 + X1) * (X2 + X2))) = (X1 * ((X1 + X1) * X2))))).
-step(add(rule(218, (X1 * (X1 * ((X1 * X3) + X2))) = (X1 * ((X1 * X2) + X3))))).
-step(hard(((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X1 + X2))) * X3))).
-step(hard(((X1 + (X1 + (X2 + X2))) * X3) = ((X2 + X1) * (X3 + X3)))).
-step(hard(((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X2 + X3)))))).
-step(hard((X1 * (X2 + (X2 + (X3 + X3)))) = ((X1 + X1) * (X3 + X2)))).
-step(add(rule(219, ((X1 + (X2 * (X2 + X2))) * X2) = (X2 + (X2 + (X1 * X2)))))).
-step(add(rule(220, -(X2 + (-X1 + X3)) = (X1 + -(X2 + X3))))).
-step(add(rule(221, -((X1 * -X2) + X3) = ((X1 * X2) + -X3)))).
-step(add(rule(222, ((? + (-? + X2)) * (X3 + X3)) = ((X2 + X2) * X3)))).
-step(add(rule(223, ((X1 + (-X1 + X2)) * (X3 + X3)) = ((? + (-? + X2)) * (X3 + X3))))).
-step(add(rule(224, ((X1 + X1) * (? + (-? + X3))) = (X1 * (X3 + X3))))).
-step(add(rule(225, ((X1 + X1) * (X2 + (-X2 + X3))) = ((X1 + X1) * (? + (-? + X3)))))).
-step(add(rule(226, ((-X1 + X2) * -X3) = ((X1 + -X2) * X3)))).
-step(add(rule(227, ((X1 * X2) + -(X3 + (X1 * X2))) = -X3))).
-step(add(rule(228, (((X1 + X1) * X2) + X3) = (X3 + (X1 * (X2 + X2)))))).
-step(add(rule(229, ((X1 * (X2 + X2)) + X3) = (X3 + ((X1 + X1) * X2))))).
-step(add(rule(230, (X1 * (X2 * (X1 * (X2 * (X1 * (X2 * X3)))))) = (X1 * (X2 * X3))))).
-step(add(rule(231, ((X1 * (X2 * X3)) + ((X4 * X3) + X5)) = ((((X1 * X2) + X4) * X3) + X5)))).
-step(add(rule(232, ((X1 * (X2 * (X3 * X5))) + (X4 * X5)) = (((X1 * (X2 * X3)) + X4) * X5)))).
-step(add(rule(233, ((X1 * (X2 * X5)) + (X3 * (X4 * X5))) = (((X1 * X2) + (X3 * X4)) * X5)))).
-step(add(rule(234, ((X1 * (X2 * X3)) + (X4 + (X5 * X3))) = (X4 + (((X1 * X2) + X5) * X3))))).
-step(add(rule(235, ((((X1 + X1) * X2) + X3) * X4) = ((X1 * (X2 * (X4 + X4))) + (X3 * X4))))).
-step(add(rule(236, ((X1 + ((X1 + X1) * X2)) * X3) = (X1 * (X3 + (X2 * (X3 + X3))))))).
-step(add(rule(237, (((X1 * (X2 + X2)) + X3) * X4) = ((X1 * (X2 * (X4 + X4))) + (X3 * X4))))).
-step(add(rule(238, (((X1 * X2) + (X3 + X3)) * X4) = ((X1 * (X2 * X4)) + (X3 * (X4 + X4)))))).
-step(add(rule(239, (((X1 * X1) + (X2 + X2)) * X1) = (X1 + (X2 * (X1 + X1)))))).
-step(add(rule(240, ((X1 + X1) * ((X2 + (X1 * X1)) * X3)) = ((X1 + X1) * (X3 + (X2 * X3)))))).
-step(add(rule(241, ((X2 + ((X1 * X1) + X2)) * X1) = (X1 + (X2 * (X1 + X1)))))).
-step(add(rule(242, (((X1 * X2) + X3) * (X2 * (X2 * X4))) = ((X1 + (X3 * X2)) * (X2 * X4))))).
-step(add(rule(243, ((X1 + (X1 * (X2 * -X2))) * (X2 * X3)) = 0))).
-step(add(rule(244, (X1 * (X2 + (X2 * (X1 * -X1)))) = 0))).
-step(add(rule(245, ((X1 + (X1 * (X2 * -X2))) * (X2 + X2)) = 0))).
-step(add(rule(246, (X1 * ((X2 + (X2 * (X1 * -X1))) * X3)) = 0))).
-step(add(rule(247, ((X1 + (X1 * (X2 * -X2))) * -X2) = 0))).
-step(add(rule(248, (X1 * -(X2 + (X2 * (X1 * -X1)))) = 0))).
-step(add(rule(249, (X1 * (-X2 + (X2 * (X1 * X1)))) = 0))).
-step(add(rule(250, ((X1 * X2) + ((X3 * (X4 * X2)) + X5)) = (((X1 + (X3 * X4)) * X2) + X5)))).
-step(add(rule(251, ((X1 * X5) + (X2 * (X3 * (X4 * X5)))) = ((X1 + (X2 * (X3 * X4))) * X5)))).
-step(add(rule(252, ((X1 * X2) + (X3 + (X4 * (X5 * X2)))) = (X3 + ((X1 + (X4 * X5)) * X2))))).
-step(add(rule(253, (X1 + ((X2 + (X1 * X3)) * X1)) = ((X2 + (X1 * (X1 + X3))) * X1)))).
-step(add(rule(254, ((X1 + (X1 + (X2 * X3))) * X4) = ((X1 * (X4 + X4)) + (X2 * (X3 * X4)))))).
-step(add(rule(255, ((X1 + ((X2 + X2) * X3)) * X4) = ((X1 * X4) + (X2 * (X3 * (X4 + X4))))))).
-step(add(rule(256, ((X1 + (X2 * (X3 + X3))) * X4) = ((X1 * X4) + (X2 * (X3 * (X4 + X4))))))).
-step(add(rule(257, ((X1 + (X2 * X3)) * (X3 * (X3 * X4))) = (((X1 * X3) + X2) * (X3 * X4))))).
-step(add(rule(258, (X1 * ((X2 * (X1 * (X2 * (X1 * X2)))) + X3)) = (X1 * (X2 + X3))))).
-step(add(rule(259, (X1 * (X2 + (X3 * (X1 * (X3 * (X1 * X3)))))) = (X1 * (X2 + X3))))).
-step(add(rule(260, (X2 + ((X1 + X2) * (X2 * -X2))) = (X1 * (X2 * -X2))))).
-step(add(rule(261, (X1 * (X2 * -(X3 + X3))) = (X1 * ((X2 + X2) * -X3))))).
-step(add(rule(262, (X1 * (X2 + (X3 * X2))) = (X1 * (X1 * ((X1 + (X1 * X3)) * X2)))))).
-step(add(rule(263, (X1 * (X2 + (X3 + (X1 * (X1 * X4))))) = (X1 * (X2 + (X3 + X4)))))).
-step(add(rule(264, (X1 * ((X2 + (X1 * (X1 * X3))) * X4)) = (X1 * ((X2 + X3) * X4))))).
-step(add(rule(265, (X1 * (X2 + (X3 + X3))) = (X1 * (X2 + (X1 * ((X1 + X1) * X3))))))).
-step(add(rule(266, (X1 * (X2 + (X1 * (X1 + (X1 * X3))))) = (X1 + (X1 * (X2 + X3)))))).
-step(add(rule(267, (X1 * (X2 * -(X3 + X3))) = ((X1 + X1) * (X2 * -X3))))).
-step(add(rule(268, ((X1 + (X1 * X2)) * -X3) = (X1 * -(X3 + (X2 * X3)))))).
-step(add(rule(269, (X1 + (X2 * (X1 * -X1))) = ((X1 + -X2) * (X1 * X1))))).
-step(add(rule(270, ((X1 + X1) * (X1 + X1)) = (X1 * -(X1 + X1))))).
-step(add(rule(271, ((X1 + X1) * -(X1 + X1)) = (X1 * (X1 + X1))))).
-step(add(rule(272, (X1 + (X1 + (X1 + X1))) = -(X1 + X1)))).
-step(add(rule(273, -(X1 + (X1 + X1)) = (X1 + (X1 + X1))))).
-step(add(rule(274, (-X1 + (X2 * (X1 * X1))) = ((-X1 + X2) * (X1 * X1))))).
-step(add(rule(275, -(X1 + (X1 * (X2 * -X1))) = (X1 * ((X2 + -X1) * X1))))).
-step(add(rule(276, (X1 + (X1 * (X2 * -X1))) = (X1 * ((X1 + -X2) * X1))))).
-step(add(rule(277, ((X1 + (X1 * -X2)) * X3) = (X1 * (X3 + (X2 * -X3)))))).
-step(add(rule(278, (X1 * (X2 + (X2 + X2))) = ((X1 + (X1 + X1)) * -X2)))).
-step(add(rule(279, ((X1 + X1) * (X2 + (X2 + X2))) = 0))).
-step(interreduce).
-step(delete(rule(108, (X1 + ((X2 + (X1 * -X1)) * X1)) = (X2 * X1)))).
-step(delete(rule(112, ((X1 + (X1 + (X1 + X1))) * X2) = (X1 * (X2 + (X2 + (X2 + X2))))))).
-step(delete(rule(113, ((X1 * X3) + (X2 * (X1 * (X1 * X3)))) = ((X1 + X2) * (X1 * (X1 * X3)))))).
-step(delete(rule(114, (X1 * ((X1 + X1) * ((X1 + X1) * (X2 + X2)))) = ((X1 + X1) * X2)))).
-step(delete(rule(145, ((X1 + X1) * (X1 + (X1 + (X1 + X1)))) = (X1 * (X1 + X1))))).
-step(delete(rule(154, ((X1 * (X2 + X2)) + X3) = (((X1 + X1) * X2) + X3)))).
-step(delete(rule(166, ((X1 + X1) * (X2 * ((X2 + X2) * (X2 + X2)))) = (X1 * (X2 + X2))))).
-step(add(rule(280, ((X1 + X1) * -(X2 + X2)) = (X1 * (X2 + X2))))).
-step(delete(rule(187, ((X2 * X1) + -(X1 + (X2 * X1))) = -X1))).
-step(delete(rule(205, ((X1 + X1) * (X2 + (X2 + X2))) = ((X1 + (X1 + X1)) * (X2 + X2))))).
-step(add(rule(281, ((X1 + (X1 + X1)) * (X2 + X2)) = 0))).
-step(delete(rule(209, (X1 * ((X1 + X1) * (X1 + X1))) = (X1 + (X1 + (X1 + X1)))))).
-step(delete(rule(210, (X3 + (X4 + (X2 + (-(X3 + X4) + X1)))) = (X1 + X2)))).
-step(delete(rule(212, (X1 * ((X1 * (X1 + X1)) + X2)) = (X1 + (X1 + (X1 * X2)))))).
-step(delete(rule(231, ((X1 * (X2 * X3)) + ((X4 * X3) + X5)) = ((((X1 * X2) + X4) * X3) + X5)))).
-step(delete(rule(242, (((X1 * X2) + X3) * (X2 * (X2 * X4))) = ((X1 + (X3 * X2)) * (X2 * X4))))).
-step(delete(rule(247, ((X1 + (X1 * (X2 * -X2))) * -X2) = 0))).
-step(delete(rule(250, ((X1 * X2) + ((X3 * (X4 * X2)) + X5)) = (((X1 + (X3 * X4)) * X2) + X5)))).
-step(delete(rule(258, (X1 * ((X2 * (X1 * (X2 * (X1 * X2)))) + X3)) = (X1 * (X2 + X3))))).
-step(delete(rule(260, (X2 + ((X1 + X2) * (X2 * -X2))) = (X1 * (X2 * -X2))))).
-step(delete(rule(271, ((X1 + X1) * -(X1 + X1)) = (X1 * (X1 + X1))))).
-step(delete(rule(275, -(X1 + (X1 * (X2 * -X1))) = (X1 * ((X2 + -X1) * X1))))).
-step(add(rule(282, ((X1 + (X1 + X1)) * -(X2 + X2)) = 0))).
-step(add(rule(283, ((X1 + X1) * (X2 + X2)) = (X1 * -(X2 + X2))))).
-step(add(rule(284, ((X1 + (X1 + X1)) * ((X2 + X2) * X3)) = 0))).
-step(add(rule(285, ((X1 + (X1 + X1)) * (X2 * (X3 + X3))) = 0))).
-step(add(rule(286, ((X1 + X1) * ((X2 + (X2 + X2)) * X3)) = 0))).
-step(add(rule(287, (((X1 + X1) * X2) + (((X1 + X1) * X2) + (((X1 + X1) * X2) + X3))) = X3))).
-step(add(rule(288, (((X1 + (X1 + X1)) * X2) + (((X1 + (X1 + X1)) * X2) + X3)) = X3))).
-step(add(rule(289, ((X1 + X1) * (X2 + (X2 * (X1 * (X1 + X1))))) = 0))).
-step(add(rule(290, ((X1 + (X1 * (X2 * (X2 + X2)))) * (X2 + X2)) = 0))).
-step(add(rule(291, ((X1 + X1) * (X2 * (X3 + (X3 + X3)))) = 0))).
-step(add(rule(292, (X1 * (X2 + (X2 + X3))) = (X1 * ((X1 * ((X1 + X1) * X2)) + X3))))).
-step(add(rule(293, ((X1 + (X1 + X1)) * (X1 * ((X1 + X1) * X2))) = 0))).
-step(add(rule(294, ((X1 + (X1 + X1)) * (X1 * -(X1 + X1))) = 0))).
-step(add(rule(295, (X1 * (-X2 + (-X2 + X3))) = (X1 * (-(X2 + X2) + X3))))).
-step(add(rule(296, (X1 * (X1 * ((X1 + (X1 * X2)) * X3))) = ((X1 + (X1 * X2)) * X3)))).
-step(add(rule(297, (((X1 * (X2 * (X3 * X3))) + X4) * X3) = (((X1 * X2) + X4) * X3)))).
-step(add(rule(298, (((X1 * (X2 * (X2 + X2))) + X3) * X2) = ((X1 + (X1 + X3)) * X2)))).
-step(add(rule(299, (X1 * (-(X2 + X2) + X3)) = (X1 * (X3 + -(X2 + X2)))))).
-step(add(rule(300, ((X1 * -(X2 + X2)) + X3) = (X3 + ((X1 + X1) * -X2))))).
-step(add(rule(301, (X1 + ((X2 + X2) * -X3)) = (X1 + (X2 * -(X3 + X3)))))).
-step(add(rule(302, -(X3 + ((X1 + X1) * X2)) = -(X3 + (X1 * (X2 + X2)))))).
-step(add(rule(303, (((X1 + X1) * -X2) + X3) = (X3 + (X1 * -(X2 + X2)))))).
-step(add(rule(304, (((X1 * (X2 * X2)) + X3) * (X2 * X4)) = ((X1 + X3) * (X2 * X4))))).
-step(add(rule(305, ((X1 + (X2 * (X3 * X3))) * (X3 * X4)) = ((X1 + X2) * (X3 * X4))))).
-step(add(rule(306, (X1 * ((X1 * -X1) + X2)) = (-X1 + (X1 * X2))))).
-step(add(rule(307, (X1 + (-X1 + (X1 * X2))) = (X1 * X2)))).
-step(add(rule(308, (X1 * (X2 + (X1 * -X1))) = (-X1 + (X1 * X2))))).
-step(add(rule(309, (X1 * (X2 * (X3 * (X4 + X4)))) = ((X1 + X1) * (X2 * (X3 * X4)))))).
-step(add(rule(310, (X1 * (X2 * (X3 * (X4 + X4)))) = (X1 * ((X2 + X2) * (X3 * X4)))))).
-step(add(rule(311, ((X1 + X1) * (X2 * (X3 + X3))) = (X1 * (X2 * -(X3 + X3)))))).
-step(add(rule(312, ((X1 + X1) * ((X2 + X2) * X3)) = (X1 * (X2 * -(X3 + X3)))))).
-step(add(rule(313, ((X1 * (X2 + X2)) + ((X3 + -X1) * X2)) = ((X1 + X3) * X2)))).
-step(add(rule(314, ((X1 * (X2 + X2)) + ((-X1 + X3) * X2)) = ((X3 + X1) * X2)))).
-step(add(rule(315, ((X1 + (X1 + X1)) * (X2 * X3)) = (X1 * ((X2 + (X2 + X2)) * X3))))).
-step(add(rule(316, (X1 * (X2 * (X3 + (X3 + X3)))) = (X1 * ((X2 + (X2 + X2)) * X3))))).
-step(add(rule(317, (((X1 + X1) * X2) + (X1 * (X3 + -X2))) = (X1 * (X2 + X3))))).
-step(add(rule(318, (((X1 + X1) * X2) + (X1 * (-X2 + X3))) = (X1 * (X3 + X2))))).
-step(add(rule(319, (X1 * (X2 + (X2 * (X1 * X1)))) = ((X1 + X1) * X2)))).
-step(add(rule(320, (X1 + (X1 * (X2 + ((X1 * -X1) + X3)))) = (X1 * (X3 + X2))))).
-step(hard(((X1 + (X1 + (X2 + X2))) * X3) = ((X1 + (X2 + (X1 + X2))) * X3))).
-step(hard((X1 * (X2 + (X2 + (X3 + X3)))) = (X1 * (X2 + (X3 + (X2 + X3)))))).
-step(add(rule(321, (X1 * (((X2 + X2) * X3) + X4)) = (X1 * (X4 + ((X2 + X2) * X3)))))).
-step(add(rule(322, (X1 * (((X2 + X2) * X3) + X4)) = (X1 * (X4 + (X2 * (X3 + X3))))))).
-step(add(rule(323, (X1 * ((X2 * (X3 + X3)) + X4)) = (X1 * (X4 + ((X2 + X2) * X3)))))).
-step(add(rule(324, (X1 + (X1 * (X2 * (X3 + X3)))) = (X1 + (X1 * ((X2 + X2) * X3)))))).
-step(hard((X1 * (X2 * (X3 + (X4 + X4)))) = (X1 * (X2 * (X4 + (X4 + X3)))))).
-step(hard((X1 * ((X2 + (X4 + X4)) * X3)) = (X1 * ((X4 + (X4 + X2)) * X3)))).
-step(add(rule(325, ((X1 * (X1 * (X2 + X1))) + X3) = (X1 + ((X1 * (X1 * X2)) + X3))))).
-step(add(rule(326, (X1 + (X2 * (X2 * (X2 + X3)))) = (X2 + ((X2 * (X2 * X3)) + X1))))).
-step(add(rule(327, (X1 + (X2 + (X1 * (X1 * X3)))) = (X2 + (X1 * (X1 * (X3 + X1))))))).
-step(hard((X1 + (X1 * ((X1 + X1) * X2))) = (X1 * (X1 * (X2 + (X1 + X2)))))).
-step(add(rule(328, ((X1 * ((X2 + X1) * X1)) + X3) = (X1 + ((X1 * (X2 * X1)) + X3))))).
-step(add(rule(329, (X1 + (X2 * ((X2 + X3) * X2))) = (X2 + ((X2 * (X3 * X2)) + X1))))).
-step(add(rule(330, (X1 + (X2 + (X1 * (X3 * X1)))) = (X2 + (X1 * ((X3 + X1) * X1)))))).
-step(hard((X1 + (X1 * (X2 * (X1 + X1)))) = (X1 * ((X2 + (X1 + X2)) * X1)))).
-step(add(rule(331, (((X1 * (X2 + X2)) + X3) * X4) = ((X3 + ((X1 + X1) * X2)) * X4)))).
-step(add(rule(332, ((((X1 + X1) * X2) + X3) * X4) = ((X3 + (X1 * (X2 + X2))) * X4)))).
-step(add(rule(333, ((X1 + (X1 * X2)) * (X2 * X3)) = (X1 * (X2 * (X3 + (X2 * X3))))))).
-step(add(rule(334, ((X1 + X1) * (X2 + (X1 * ((X1 + X1) * X2)))) = 0))).
-step(add(rule(335, (X1 * (X1 * ((X1 + X2) * X2))) = (X1 * ((X2 + X1) * (X2 * X2)))))).
-step(add(rule(336, (X1 * (X2 + (X1 * (X3 * X2)))) = (X1 * (X1 * ((X3 + X1) * X2)))))).
-step(add(rule(337, (X1 * ((X1 + X2) * (X1 * X2))) = (X1 * (X2 * ((X2 + X1) * X2)))))).
-step(add(rule(338, (X1 * (X2 + (X3 * (X1 * X2)))) = (X1 * ((X3 + X1) * (X1 * X2)))))).
-step(add(rule(339, (X1 * -(X2 + (X1 * (X1 * -X2)))) = 0))).
-step(add(rule(340, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * ((X3 + X2) * (X3 * X3)))))).
-step(add(rule(341, ((X1 + (X1 * (X2 * -X2))) * X2) = 0))).
-step(add(rule(342, ((X1 + (X1 * (X2 * -X2))) * -X2) = 0))).
-step(add(rule(343, ((X1 + (X1 * (X2 * X3))) * X2) = (X1 * (X2 * ((X2 + X3) * X2)))))).
-step(add(rule(344, (X1 * -((X1 * X1) + X2)) = -(X1 + (X1 * X2))))).
-step(add(rule(345, (((X1 * X1) + X2) * -X1) = -(X1 + (X2 * X1))))).
-step(add(rule(346, ((X1 * X2) + ((X3 + X1) * X4)) = ((X1 * (X4 + X2)) + (X3 * X4))))).
-step(hard((X1 * (X2 + (X3 + X2))) = (X1 * (X3 + (X2 + X2))))).
-step(hard((X1 * (X2 + (X3 + X2))) = (X1 * (X2 + (X2 + X3))))).
-step(add(rule(347, ((X1 * -X2) + ((X3 + X1) * X2)) = (X3 * X2)))).
-step(add(rule(348, ((X1 * X2) + ((X3 + X1) * X4)) = ((X3 * X4) + (X1 * (X2 + X4)))))).
-step(add(rule(349, (X1 * (X2 + -(X3 + X2))) = (X1 * -X3)))).
-step(add(rule(350, ((X1 + -(X3 + X1)) * X2) = (X3 * -X2)))).
-step(add(rule(351, (((X1 * X2) + (X4 + X1)) * X3) = ((X4 + (X1 + (X1 * X2))) * X3)))).
-step(interreduce).
-step(delete(rule(103, ((X1 + (X1 + X1)) * (X1 * X1)) = (X1 + (X1 + X1))))).
-step(delete(rule(153, (((X1 + X1) * -X2) + X3) = ((X1 * -(X2 + X2)) + X3)))).
-step(delete(rule(168, (X1 * (((X2 + X2) * X3) + X4)) = (X1 * ((X2 * (X3 + X3)) + X4))))).
-step(delete(rule(171, (X1 + (X2 + (X1 * (X1 * X3)))) = (X2 + (X1 * (X1 * (X1 + X3))))))).
-step(delete(rule(174, (X1 + (X2 + (X1 * (X3 * X1)))) = (X2 + (X1 * ((X1 + X3) * X1)))))).
-step(delete(rule(180, (X1 * (X2 + (X1 * (X3 * X2)))) = (X1 * (X1 * ((X1 + X3) * X2)))))).
-step(delete(rule(181, (X1 * (X2 + (X3 * (X1 * X2)))) = (X1 * ((X1 + X3) * (X1 * X2)))))).
-step(delete(rule(189, ((X1 * X2) + (X3 * (X2 + X4))) = ((X3 * X4) + ((X1 + X3) * X2))))).
-step(delete(rule(216, ((X1 + X1) * (X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(add(rule(352, (X1 * -(X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(delete(rule(217, ((X1 + X1) * ((X1 + X1) * (X2 + X2))) = (X1 * ((X1 + X1) * X2))))).
-step(delete(rule(262, (X1 * (X2 + (X3 * X2))) = (X1 * (X1 * ((X1 + (X1 * X3)) * X2)))))).
-step(delete(rule(270, ((X1 + X1) * (X1 + X1)) = (X1 * -(X1 + X1))))).
-step(delete(rule(288, (((X1 + (X1 + X1)) * X2) + (((X1 + (X1 + X1)) * X2) + X3)) = X3))).
-step(delete(rule(293, ((X1 + (X1 + X1)) * (X1 * ((X1 + X1) * X2))) = 0))).
-step(delete(rule(294, ((X1 + (X1 + X1)) * (X1 * -(X1 + X1))) = 0))).
-step(delete(rule(304, (((X1 * (X2 * X2)) + X3) * (X2 * X4)) = ((X1 + X3) * (X2 * X4))))).
-step(delete(rule(306, (X1 * ((X1 * -X1) + X2)) = (-X1 + (X1 * X2))))).
-step(delete(rule(341, ((X1 + (X1 * (X2 * -X2))) * X2) = 0))).
-step(add(rule(353, (X1 * ((X1 + X1) * ((X1 + X1) * X2))) = ((X1 + X1) * -X2)))).
-step(add(rule(354, (X1 + (X1 + (X1 * (X2 + X2)))) = ((X1 + X1) * (X2 + (X1 * X1)))))).
-step(add(rule(355, (X1 + (X1 * ((X1 * X3) + X2))) = (X1 * (X2 + (X1 * (X3 + X1))))))).
-step(add(rule(356, (X1 * (X2 + (X1 * (X2 + X1)))) = (X1 + ((X1 + (X1 * X1)) * X2))))).
-step(add(rule(357, (X1 + (X1 * ((X3 * X1) + X2))) = (X1 * (X2 + ((X3 + X1) * X1)))))).
-step(add(rule(358, (((X1 * -X1) + X2) * -X1) = (X1 + (X2 * -X1))))).
-step(add(rule(359, ((X1 + (X2 * -X2)) * X2) = (-X2 + (X1 * X2))))).
-step(hard((X1 + ((X2 * (X1 * X1)) + X3)) = (X3 + ((X2 + X1) * (X1 * X1))))).
-step(add(rule(360, (X1 + ((X2 + X3) * (X2 * X2))) = (X2 + (X1 + (X3 * (X2 * X2))))))).
-step(hard((X1 + ((X2 + X3) * (X3 * X3))) = (X1 + ((X3 + X2) * (X3 * X3))))).
-step(add(rule(361, (X2 + (((X3 * X2) + X1) * X2)) = ((X1 + ((X2 + X3) * X2)) * X2)))).
-step(add(rule(362, ((X1 + ((X2 + X1) * X2)) * X2) = (X2 + (X1 * (X2 + (X2 * X2))))))).
-step(add(rule(363, ((X1 + ((X1 * X1) + X2)) * (X1 * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(add(rule(364, ((X2 + (X1 + (X1 * X1))) * (X1 * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(add(rule(365, ((X1 + (X1 * X3)) * (X2 + X2)) = ((X1 + X1) * (X2 + (X3 * X2)))))).
-step(add(rule(366, (X2 + (((X2 * X3) + X1) * X2)) = ((X1 + (X2 * (X2 + X3))) * X2)))).
-step(add(rule(367, ((X1 + (X1 + (X1 + X2))) * (X3 + X3)) = (X2 * (X3 + X3))))).
-step(add(rule(368, ((X1 + ((X2 * X2) + X3)) * (X2 * X2)) = ((X2 + ((X1 + X3) * X2)) * X2)))).
-step(add(rule(369, (X1 * ((X2 * (X3 + X3)) + X4)) = (X1 * (((X2 + X2) * X3) + X4))))).
-step(hard((X1 + X2) = (X3 + (X4 + (X1 + (X2 + -(X4 + X3))))))).
-step(add(rule(370, ((X3 + ((X1 * X1) + X2)) * X1) = (X1 + ((X2 + X3) * X1))))).
-step(add(rule(371, (((X1 * (X1 + X1)) + X2) * X1) = (X1 + (X1 + (X2 * X1)))))).
-step(add(rule(372, (X1 * (X2 + (X3 + (X3 * (X1 * -X1))))) = (X1 * X2)))).
-step(add(rule(373, ((X1 + X1) * ((X2 + X2) * -X3)) = (X1 * ((X2 + X2) * X3))))).
-step(add(rule(374, ((X1 + X1) * (-(X2 + X2) + X3)) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(375, ((X1 + X1) * (X2 + -(X3 + X3))) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(376, ((X1 + X1) * (X2 * -(X3 + X3))) = (X1 * (X2 * (X3 + X3)))))).
-step(add(rule(377, ((X1 + (X1 * (X2 * (X2 + X2)))) * ((X2 + X2) * X3)) = 0))).
-step(add(rule(378, ((X1 + (X1 + X1)) * (X2 + (X3 + (X2 + X3)))) = 0))).
-step(add(rule(379, ((X1 + (X1 + X1)) * (X2 + (X2 + (X3 + X3)))) = 0))).
-step(add(rule(380, (X1 * (-X2 + (X1 * ((X1 + X1) * X2)))) = (X1 * X2)))).
-step(add(rule(381, ((X1 + (X1 + X1)) * -X2) = ((X1 + (X1 + X1)) * X2)))).
-step(add(rule(382, ((-X1 + (X1 * (X2 * (X2 + X2)))) * X2) = (X1 * X2)))).
-step(add(rule(383, (X1 * ((X1 * (X1 + X1)) + X2)) = (X1 + (X1 + (X1 * X2)))))).
-step(hard((((X1 * X1) + (X2 + X1)) * X1) = (X1 + ((X2 + X1) * X1)))).
-step(hard(((X1 + (-X1 + (X3 + X1))) * X2) = ((X3 + X1) * X2))).
-step(add(rule(384, (X2 + (-(X2 + (X3 * X2)) + ((X1 + (X3 + (X1 * X3))) * X2))) = (X1 * (X2 + (X3 * X2)))))).
-step(add(rule(385, (X2 + (X2 + (X2 + (X2 + (X2 + (X2 + ((X1 + (X1 + X1)) * X2))))))) = (X1 * (X2 + (X2 + X2)))))).
-step(add(rule(386, ((X2 * -X3) + ((X2 + (X2 + (X1 * X2))) * X3)) = ((X2 + (X1 * X2)) * X3)))).
-step(add(rule(387, (((X1 + X1) * X2) + X3) = (? + (? + (X3 + (-(? + ?) + (X1 * (X2 + X2))))))))).
-step(add(rule(388, (X4 + (X5 + (X3 + (-(X4 + X5) + (X1 * (X2 + X2)))))) = (? + (? + (X3 + (-(? + ?) + (X1 * (X2 + X2))))))))).
-step(add(rule(389, (((X1 + X1) * X2) + X3) = ((X1 * (X2 + X2)) + X3)))).
-step(add(rule(390, ((X1 * (X2 + X2)) + X3) = (? + (? + (X3 + (-(? + ?) + ((X1 + X1) * X2)))))))).
-step(add(rule(391, (X4 + (X5 + (X3 + (-(X4 + X5) + ((X1 + X1) * X2))))) = (? + (? + (X3 + (-(? + ?) + ((X1 + X1) * X2)))))))).
-step(add(rule(392, (X1 * (X2 * (X3 + (X3 * (X1 * -X1))))) = 0))).
-step(add(rule(393, (X1 * (X2 * (X3 + (X1 * (X1 * -X3))))) = 0))).
-step(add(rule(394, ((X1 + (X1 + X1)) * (X2 * -(X3 + X3))) = 0))).
-step(add(rule(395, ((X1 + (X1 * (X2 * (X2 + X2)))) * -(X2 + X2)) = 0))).
-step(add(rule(396, ((X3 * -X2) + ((X3 + X1) * X2)) = (X1 * X2)))).
-step(add(rule(397, ((X1 + X1) * ((X1 + X1) * X2)) = (X1 * ((X1 + X1) * -X2))))).
-step(add(rule(398, ((X1 + X1) * (X2 + (X2 * (X1 * -X1)))) = 0))).
-step(add(rule(399, (X1 * (X2 * (X3 + (X1 * (X2 * (X1 * X2)))))) = (X1 * (X2 + (X2 * X3)))))).
-step(add(rule(400, (X1 + (X2 + (X3 * ((X1 + X2) * (X1 + X2))))) = ((X1 + (X2 + X3)) * ((X1 + X2) * (X1 + X2)))))).
-step(add(rule(401, ((X1 + (X2 + (X1 + X2))) * ((X1 + X2) * (X1 + X2))) = (X1 + (X2 + (X1 + X2)))))).
-step(add(rule(402, ((X1 * X2) + (X3 * (X1 * (X2 * (X1 * X2))))) = (((X1 * X2) + X3) * (X1 * (X2 * (X1 * X2))))))).
-step(add(rule(403, (X1 * ((X1 + (X2 * X1)) * (X2 * (X1 * X2)))) = (X1 * (X2 + (X1 * (X2 * (X1 * X2)))))))).
-step(add(rule(404, ((X1 + (X1 * (X2 * -X2))) * (X3 * X2)) = 0))).
-step(add(rule(405, (X1 * (X3 * (X3 * (X2 * X3)))) = (X1 * (X2 * X3))))).
-step(add(rule(406, ((X3 * X4) + (X1 * (X2 * (X3 * (X3 * X4))))) = (((X1 * X2) + X3) * (X3 * (X3 * X4)))))).
-step(add(rule(407, ((X2 + -X1) * (X3 + X3)) = ((X2 + (-(X1 + X1) + X2)) * X3)))).
-step(add(rule(408, ((X1 + X1) * (X3 + -X2)) = (X1 * (X3 + (-(X2 + X2) + X3)))))).
-step(add(rule(409, ((X1 + (((X2 * -X2) + X1) * (X2 * -X2))) * (X2 * -X2)) = (X2 * -X2)))).
-step(add(rule(410, (X1 * (X2 + (X1 * ((X1 + X1) * -X2)))) = (X1 * -X2)))).
-step(add(rule(411, ((X1 + (X1 + X2)) * -(X3 + X3)) = ((X1 + -X2) * (X3 + X3))))).
-step(add(rule(412, ((X1 + X1) * ((X2 + X2) * X3)) = (X1 * ((X2 + X2) * -X3))))).
-step(add(rule(413, ((X1 + X1) * (X2 + (X2 + X3))) = ((X1 + X1) * (X3 + -X2))))).
-step(add(rule(414, ((X1 + X1) * (X2 + (X1 * ((X1 + X1) * X3)))) = ((X1 + X1) * (X2 + -X3))))).
-step(add(rule(415, ((X2 + (X2 * (X3 * -X3))) * X3) = 0))).
-step(add(rule(416, ((X2 + (X2 + X1)) * (X3 + X3)) = ((X1 + -X2) * (X3 + X3))))).
-step(add(rule(417, ((X1 + X1) * (X2 + (X3 + X3))) = ((X1 + X1) * (X2 + -X3))))).
-step(add(rule(418, ((X1 + (X2 * -(X2 + X2))) * X2) = ((X1 * X2) + -(X2 + X2))))).
-step(add(rule(419, ((X1 + (X2 * (X2 * -X1))) * -X2) = 0))).
-step(add(rule(420, (X1 * (X1 * (X2 * X1))) = (X2 * X1)))).
-step(add(rule(421, (X2 * (X2 * (X1 * -X2))) = (X1 * -X2)))).
-step(add(rule(422, ((X1 + (X2 * (X2 * -X1))) * X2) = 0))).
-step(add(rule(423, ((X1 + X2) * ((X3 + X3) * X4)) = ((X1 + (X2 + (X1 + X2))) * (X3 * X4))))).
-step(add(rule(424, ((X1 + (X2 + (X1 + X2))) * (X3 + (X3 + X3))) = 0))).
-step(add(rule(425, ((X1 + X2) * ((X3 + X3) * X4)) = ((X1 + (X1 + (X2 + X2))) * (X3 * X4))))).
-step(hard(((X1 + X2) * ((X3 + X3) * X4)) = ((X2 + X1) * (X3 * (X4 + X4))))).
-step(add(rule(426, ((X1 + -(X2 + X2)) * (X3 + X3)) = ((X1 + X2) * (X3 + X3))))).
-step(add(rule(427, (X1 * ((X2 + X2) * (X3 + (X2 * X2)))) = ((X1 + X1) * (X2 + (X2 * X3)))))).
-step(add(rule(428, ((X1 + X1) * ((X2 + X3) * X4)) = (X1 * ((X2 + (X3 + (X2 + X3))) * X4))))).
-step(add(rule(429, (X1 * (((X2 + X2) * X3) + X4)) = (((X1 + X1) * (X2 * X3)) + (X1 * X4))))).
-step(add(rule(430, (X1 + ((X1 + X1) * (X2 * X3))) = (X1 + (X1 * ((X2 + X2) * X3)))))).
-step(add(rule(431, (X1 * (X2 + ((X3 + X3) * X4))) = ((X1 * X2) + ((X1 + X1) * (X3 * X4)))))).
-step(add(rule(432, (X1 * (X2 + (X1 * -(X1 + X1)))) = ((X1 * X2) + -(X1 + X1))))).
-step(add(rule(433, ((X1 + X1) * ((X2 + X3) * X4)) = (X1 * ((X2 + (X2 + (X3 + X3))) * X4))))).
-step(hard(((X1 + X1) * ((X2 + X3) * X4)) = (X1 * ((X3 + X2) * (X4 + X4))))).
-step(add(rule(434, (X1 * ((X2 + X2) * (X3 + X4))) = (X1 * (X2 * (X3 + (X4 + (X3 + X4)))))))).
-step(add(rule(435, (X1 * ((X2 + X2) * (X3 + X4))) = (X1 * (X2 * (X3 + (X3 + (X4 + X4)))))))).
-step(hard((X1 * ((X2 + X2) * (X3 + X4))) = (X1 * ((X2 + X2) * (X4 + X3))))).
-step(add(rule(436, (X1 * ((X2 + X3) * (X4 + X4))) = (X1 * ((X2 + (X3 + (X2 + X3))) * X4))))).
-step(add(rule(437, (X1 * ((X2 + X3) * (X4 + X4))) = (X1 * ((X2 + (X2 + (X3 + X3))) * X4))))).
-step(interreduce).
-step(delete(rule(194, (X1 + (X1 + (X1 * (X2 + X2)))) = ((X1 + X1) * ((X1 * X1) + X2))))).
-step(delete(rule(207, ((X1 + (X2 + (X1 * X1))) * (X1 * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(delete(rule(240, ((X1 + X1) * ((X2 + (X1 * X1)) * X3)) = ((X1 + X1) * (X3 + (X2 * X3)))))).
-step(delete(rule(241, ((X2 + ((X1 * X1) + X2)) * X1) = (X1 + (X2 * (X1 + X1)))))).
-step(delete(rule(245, ((X1 + (X1 * (X2 * -X2))) * (X2 + X2)) = 0))).
-step(delete(rule(278, (X1 * (X2 + (X2 + X2))) = ((X1 + (X1 + X1)) * -X2)))).
-step(delete(rule(279, ((X1 + X1) * (X2 + (X2 + X2))) = 0))).
-step(delete(rule(281, ((X1 + (X1 + X1)) * (X2 + X2)) = 0))).
-step(delete(rule(282, ((X1 + (X1 + X1)) * -(X2 + X2)) = 0))).
-step(delete(rule(290, ((X1 + (X1 * (X2 * (X2 + X2)))) * (X2 + X2)) = 0))).
-step(delete(rule(312, ((X1 + X1) * ((X2 + X2) * X3)) = (X1 * (X2 * -(X3 + X3)))))).
-step(delete(rule(334, ((X1 + X1) * (X2 + (X1 * ((X1 + X1) * X2)))) = 0))).
-step(delete(rule(352, (X1 * -(X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(delete(rule(353, (X1 * ((X1 + X1) * ((X1 + X1) * X2))) = ((X1 + X1) * -X2)))).
-step(delete(rule(363, ((X1 + ((X1 * X1) + X2)) * (X1 * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(delete(rule(367, ((X1 + (X1 + (X1 + X2))) * (X3 + X3)) = (X2 * (X3 + X3))))).
-step(delete(rule(373, ((X1 + X1) * ((X2 + X2) * -X3)) = (X1 * ((X2 + X2) * X3))))).
-step(delete(rule(374, ((X1 + X1) * (-(X2 + X2) + X3)) = ((X1 + X1) * (X2 + X3))))).
-step(delete(rule(378, ((X1 + (X1 + X1)) * (X2 + (X3 + (X2 + X3)))) = 0))).
-step(delete(rule(386, ((X2 * -X3) + ((X2 + (X2 + (X1 * X2))) * X3)) = ((X2 + (X1 * X2)) * X3)))).
-step(delete(rule(397, ((X1 + X1) * ((X1 + X1) * X2)) = (X1 * ((X1 + X1) * -X2))))).
-step(delete(rule(401, ((X1 + (X2 + (X1 + X2))) * ((X1 + X2) * (X1 + X2))) = (X1 + (X2 + (X1 + X2)))))).
-step(add(rule(438, ((X1 + X2) * ((X1 + X2) * (X1 + (X1 + (X2 + X2))))) = (X1 + (X2 + (X1 + X2)))))).
-step(delete(rule(404, ((X1 + (X1 * (X2 * -X2))) * (X3 * X2)) = 0))).
-step(delete(rule(405, (X1 * (X3 * (X3 * (X2 * X3)))) = (X1 * (X2 * X3))))).
-step(delete(rule(413, ((X1 + X1) * (X2 + (X2 + X3))) = ((X1 + X1) * (X3 + -X2))))).
-step(delete(rule(423, ((X1 + X2) * ((X3 + X3) * X4)) = ((X1 + (X2 + (X1 + X2))) * (X3 * X4))))).
-step(delete(rule(428, ((X1 + X1) * ((X2 + X3) * X4)) = (X1 * ((X2 + (X3 + (X2 + X3))) * X4))))).
-step(delete(rule(434, (X1 * ((X2 + X2) * (X3 + X4))) = (X1 * (X2 * (X3 + (X4 + (X3 + X4)))))))).
-step(delete(rule(436, (X1 * ((X2 + X3) * (X4 + X4))) = (X1 * ((X2 + (X3 + (X2 + X3))) * X4))))).
-step(hard((X1 * ((X2 + X3) * (X4 + X4))) = (X1 * ((X3 + X2) * (X4 + X4))))).
-step(add(rule(439, ((X1 * X2) + (X3 * -(X2 + X2))) = ((X1 + -(X3 + X3)) * X2)))).
-step(add(rule(440, ((X1 + (X2 + X2)) * X3) = ((X1 * X3) + (X2 * (X3 + X3)))))).
-step(hard((((X1 + X1) * X2) + (X1 * -(X2 + X2))) = 0)).
-step(add(rule(441, ((X1 + X2) * (X3 + (X4 * X3))) = ((X1 + (X2 + ((X1 + X2) * X4))) * X3)))).
-step(add(rule(442, ((X1 + ((X1 * X2) + X3)) * X4) = ((X1 * (X4 + (X2 * X4))) + (X3 * X4))))).
-step(add(rule(443, ((X1 + (X2 + (X2 * X3))) * X4) = ((X1 * X4) + (X2 * (X4 + (X3 * X4))))))).
-step(add(rule(444, ((X1 + X1) * (X2 + (X3 * X2))) = ((X1 + (X1 + (X1 * (X3 + X3)))) * X2)))).
-step(add(rule(445, (X1 * (X2 + ((X3 + (X1 * X1)) * X2))) = ((X1 + (X1 + (X1 * X3))) * X2)))).
-step(add(rule(446, ((X1 + (X1 * X2)) * (X3 + X4)) = (X1 * (X3 + (X4 + (X2 * (X3 + X4)))))))).
-step(add(rule(447, (X1 * (X2 + ((X3 * X2) + X4))) = (((X1 + (X1 * X3)) * X2) + (X1 * X4))))).
-step(add(rule(448, (X1 * (-X2 + (X3 * X2))) = ((-X1 + (X1 * X3)) * X2)))).
-step(add(rule(449, (X1 + (X1 * (X2 + (X3 * X2)))) = (X1 + ((X1 + (X1 * X3)) * X2))))).
-step(add(rule(450, (X1 * ((X2 * X3) + ((X2 * -X3) + X4))) = (X1 * X4)))).
-step(add(rule(451, (X1 * (X2 * (X3 * (X1 * X1)))) = (X1 * (X2 * X3))))).
-step(add(rule(452, (X1 * (X2 * (X1 * X1))) = (X1 * X2)))).
-step(add(rule(453, (X1 * (X2 * (X1 * -X1))) = (X1 * -X2)))).
-step(add(rule(454, (X1 * (X2 + (X3 * (X1 * X1)))) = (X1 * (X2 + X3))))).
-step(add(rule(455, (X1 * (X2 * (X1 * (X1 + X1)))) = ((X1 + X1) * X2)))).
-step(add(rule(456, (X1 * ((X1 + (X2 * X1)) * X1)) = (X1 + (X1 * X2))))).
-step(add(rule(457, (X1 * (X2 * (X1 * X2))) = (X1 * (X2 * (X2 * X1)))))).
-step(add(rule(458, ((X1 + (X2 * X1)) * X1) = (X1 * (X1 + (X1 * X2)))))).
-step(add(rule(459, (X1 * (X2 + (X3 + (X4 * X3)))) = ((X1 * X2) + ((X1 + (X1 * X4)) * X3))))).
-step(add(rule(460, (X1 * (X2 + (X3 + X3))) = ((X1 * X2) + ((X1 + X1) * X3))))).
-step(add(rule(461, (X1 * ((X1 * X1) + (X2 + X2))) = (X1 + ((X1 + X1) * X2))))).
-step(add(rule(462, ((X1 + (X2 + X1)) * (X3 + X3)) = ((X2 + -X1) * (X3 + X3))))).
-step(add(rule(463, ((X2 + (X1 + X1)) * (X3 + X3)) = ((X2 + -X1) * (X3 + X3))))).
-step(add(rule(464, ((X1 + X1) * (X3 + (X2 * X3))) = (X1 * (X3 + (X3 + ((X2 + X2) * X3))))))).
-step(add(rule(465, (X1 * -(X2 + (X1 * (X1 * X3)))) = (X1 * -(X2 + X3))))).
-step(add(rule(466, ((X3 * X5) + (X1 + (X2 + (X3 * X4)))) = (X1 + (X2 + (X3 * (X4 + X5))))))).
-step(add(rule(467, (X1 + (X2 * (X3 + (X4 + X4)))) = (((X2 + X2) * X4) + (X1 + (X2 * X3)))))).
-step(add(rule(468, (X1 + (X2 * (X3 + (X3 + X3)))) = (X1 + ((X2 + (X2 + X2)) * X3))))).
-step(add(rule(469, (X1 + (X2 * (X3 + (X3 + X4)))) = ((X2 * X4) + (X1 + ((X2 + X2) * X3)))))).
-step(add(rule(470, (X1 + (X2 + (X2 * (X3 + X3)))) = (X2 + (X1 + ((X2 + X2) * X3)))))).
-step(hard((X1 * (X2 + (X3 + (X3 + X4)))) = (X1 * (X4 + (X3 + (X3 + X2)))))).
-step(add(rule(471, ((X2 * X3) + ((X1 + X2) * (X1 * X1))) = (X1 + (X2 * ((X1 * X1) + X3)))))).
-step(add(rule(472, ((X4 * X5) + (X1 + (X2 + (X3 * X5)))) = (X1 + (X2 + ((X3 + X4) * X5)))))).
-step(add(rule(473, (X1 + ((X2 + (X3 + X3)) * X4)) = ((X3 * (X4 + X4)) + (X1 + (X2 * X4)))))).
-step(add(rule(474, ((X1 + X1) * (X2 + (X3 + X2))) = ((X1 + X1) * (X3 + -X2))))).
-step(add(rule(475, (X1 + ((X2 + (X2 + X3)) * X4)) = ((X3 * X4) + (X1 + (X2 * (X4 + X4))))))).
-step(hard(((X1 + (X3 + (X3 + X4))) * X2) = ((X4 + (X3 + (X3 + X1))) * X2))).
-step(add(rule(476, ((X1 + (X1 + X2)) * (X3 * X4)) = (((X1 * (X3 + X3)) + (X2 * X3)) * X4)))).
-step(add(rule(477, (X1 * ((X2 * (X3 + X3)) + (X4 * X3))) = (X1 * ((X2 + (X2 + X4)) * X3))))).
-step(add(rule(478, ((X1 * (X2 + X2)) + ((X3 + X4) * X2)) = ((X1 + (X3 + (X1 + X4))) * X2)))).
-step(hard(((X1 + (X2 + (X1 + X3))) * X4) = ((X1 + (X1 + (X2 + X3))) * X4))).
-step(add(rule(479, ((X1 * (X2 + X2)) + ((X3 * X2) + X4)) = (((X1 + (X1 + X3)) * X2) + X4)))).
-step(hard(((X1 + (X1 + (X2 + X4))) * X3) = ((X2 + (X4 + (X1 + X1))) * X3))).
-step(add(rule(480, (((X1 * X2) + ((X1 * X2) + X3)) * X4) = ((X1 * (X2 * (X4 + X4))) + (X3 * X4))))).
-step(add(rule(481, (((X1 * X2) + ((X1 * X2) + X3)) * X4) = (((X1 * (X2 + X2)) + X3) * X4)))).
-step(add(rule(482, ((X1 * (X2 + X2)) + (X3 + (X4 * X2))) = (X3 + ((X1 + (X1 + X4)) * X2))))).
-step(hard((X1 + ((X2 + (X2 + X3)) * X4)) = (X1 + ((X3 + (X2 + X2)) * X4)))).
-step(hard(((X1 + (X3 + (X3 + X4))) * X2) = ((X3 + (X3 + (X1 + X4))) * X2))).
-step(add(rule(483, ((X1 * (X2 + X2)) + ((X3 + X4) * X2)) = ((X4 + (X1 + (X1 + X3))) * X2)))).
-step(add(rule(484, ((-? + (X2 + (X2 + ?))) * X3) = (X2 * (X3 + X3))))).
-step(add(rule(485, ((-X1 + (X2 + (X2 + X1))) * X3) = ((-? + (X2 + (X2 + ?))) * X3)))).
-step(hard(((X1 + (X2 + (X2 + X3))) * X4) = ((X2 + (X2 + (X3 + X1))) * X4))).
-step(hard(((X1 + (X2 + (X2 + X3))) * X4) = ((X2 + (X3 + (X2 + X1))) * X4))).
-step(add(rule(486, (X1 * (((X2 + X2) * X3) + (X2 * X4))) = (X1 * (X2 * (X3 + (X3 + X4))))))).
-step(add(rule(487, (X1 * ((X2 + (X2 + X3)) * X4)) = ((((X1 + X1) * X2) + (X1 * X3)) * X4)))).
-step(add(rule(488, ((X1 + (X1 + X1)) * (X2 * X3)) = (X1 * (X2 * (X3 + (X3 + X3))))))).
-step(add(rule(489, (((X1 + X1) * X2) + (X1 * (X3 + X4))) = (X1 * (X2 + (X3 + (X2 + X4))))))).
-step(hard((X1 * (X2 + (X3 + (X2 + X4)))) = (X1 * (X2 + (X2 + (X3 + X4)))))).
-step(add(rule(490, (((X1 + X1) * X2) + ((X1 * X3) + X4)) = ((X1 * (X2 + (X2 + X3))) + X4)))).
-step(hard((X1 * (X2 + (X2 + (X3 + X4)))) = (X1 * (X3 + (X4 + (X2 + X2)))))).
-step(add(rule(491, ((X1 * (X2 + X2)) + ((X1 * -(X2 + X2)) + X3)) = X3))).
-step(add(rule(492, (((X1 + X1) * X2) + (X3 + (X1 * X4))) = (X3 + (X1 * (X2 + (X2 + X4))))))).
-step(hard((X1 + (X2 * (X3 + (X3 + X4)))) = (X1 + (X2 * (X4 + (X3 + X3)))))).
-step(hard((X1 * (X2 + (X3 + (X3 + X4)))) = (X1 * (X3 + (X3 + (X2 + X4)))))).
-step(add(rule(493, (X1 * (X2 + (X2 + ((X3 + X3) * X4)))) = ((X1 + X1) * (X2 + (X3 * X4)))))).
-step(add(rule(494, (X1 * (X2 + X2)) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(add(rule(495, ((X1 + X1) * (X2 + (X3 + (X3 + X3)))) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(add(rule(496, ((X1 + X1) * (X2 + -X3)) = (X1 * (X2 + (X2 + -(X3 + X3))))))).
-step(add(rule(497, (X1 * (X1 * (X2 + (X2 + (X1 * X3))))) = (X1 * (((X1 + X1) * X2) + X3))))).
-step(add(rule(498, (((X1 + X1) * X2) + (X1 * (X3 + X4))) = (X1 * (X4 + (X2 + (X2 + X3))))))).
-step(add(rule(499, (X1 * (-? + (X3 + (X3 + ?)))) = ((X1 + X1) * X3)))).
-step(add(rule(500, (X1 * (-X2 + (X3 + (X3 + X2)))) = (X1 * (-? + (X3 + (X3 + ?))))))).
-step(hard((X1 * (X2 + (X3 + (X3 + X4)))) = (X1 * (X3 + (X3 + (X4 + X2)))))).
-step(hard((X1 * (X2 + (X3 + (X3 + X4)))) = (X1 * (X3 + (X4 + (X3 + X2)))))).
-step(add(rule(501, (X1 * (X3 + (X4 + ((X1 * X1) + X2)))) = (X1 + (X1 * (X2 + (X3 + X4))))))).
-step(add(rule(502, (X1 * ((X2 + ((X1 * X1) + X3)) * X4)) = ((X1 + (X1 * (X3 + X2))) * X4)))).
-step(add(rule(503, (X1 * (X2 + (X3 + (X4 + (X1 * X1))))) = (X1 + (X1 * (X2 + (X3 + X4))))))).
-step(add(rule(504, (X1 * ((X2 + (X3 + (X1 * X1))) * X4)) = ((X1 + (X1 * (X2 + X3))) * X4)))).
-step(hard(((X1 + (X1 * (X2 + X3))) * X4) = ((X1 + (X1 * (X3 + X2))) * X4))).
-step(add(rule(505, ((X1 + (X1 * X2)) * ((X2 + (X1 * X1)) * (X2 + (X1 * X1)))) = (X1 + (X1 * X2))))).
-step(interreduce).
-step(delete(rule(162, (((? * X2) + ((? * X2) + ((? + X3) * -(X2 + X2)))) * X4) = (X3 * (X2 * -(X4 + X4)))))).
-step(delete(rule(163, (((X1 * X2) + ((X1 * X2) + ((X3 + X1) * -(X2 + X2)))) * X4) = (((? * X2) + ((? * X2) + ((? + X3) * -(X2 + X2)))) * X4)))).
-step(delete(rule(249, (X1 * (-X2 + (X2 * (X1 * X1)))) = 0))).
-step(delete(rule(287, (((X1 + X1) * X2) + (((X1 + X1) * X2) + (((X1 + X1) * X2) + X3))) = X3))).
-step(delete(rule(319, (X1 * (X2 + (X2 * (X1 * X1)))) = ((X1 + X1) * X2)))).
-step(delete(rule(339, (X1 * -(X2 + (X1 * (X1 * -X2)))) = 0))).
-step(delete(rule(364, ((X2 + (X1 + (X1 * X1))) * (X1 * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(add(rule(506, ((X2 + (X1 + (X1 * X1))) * (X1 * X1)) = (X1 * (X1 + (X1 * (X1 + X2))))))).
-step(delete(rule(368, ((X1 + ((X2 * X2) + X3)) * (X2 * X2)) = ((X2 + ((X1 + X3) * X2)) * X2)))).
-step(add(rule(507, ((X1 + ((X2 * X2) + X3)) * (X2 * X2)) = (X2 * (X2 + (X2 * (X1 + X3))))))).
-step(delete(rule(408, ((X1 + X1) * (X3 + -X2)) = (X1 * (X3 + (-(X2 + X2) + X3)))))).
-step(delete(rule(416, ((X2 + (X2 + X1)) * (X3 + X3)) = ((X1 + -X2) * (X3 + X3))))).
-step(delete(rule(456, (X1 * ((X1 + (X2 * X1)) * X1)) = (X1 + (X1 * X2))))).
-step(delete(rule(464, ((X1 + X1) * (X3 + (X2 * X3))) = (X1 * (X3 + (X3 + ((X2 + X2) * X3))))))).
-step(delete(rule(467, (X1 + (X2 * (X3 + (X4 + X4)))) = (((X2 + X2) * X4) + (X1 + (X2 * X3)))))).
-step(delete(rule(473, (X1 + ((X2 + (X3 + X3)) * X4)) = ((X3 * (X4 + X4)) + (X1 + (X2 * X4)))))).
-step(delete(rule(480, (((X1 * X2) + ((X1 * X2) + X3)) * X4) = ((X1 * (X2 * (X4 + X4))) + (X3 * X4))))).
-step(delete(rule(484, ((-? + (X2 + (X2 + ?))) * X3) = (X2 * (X3 + X3))))).
-step(add(rule(508, ((? + (-? + (X2 + X2))) * X3) = (X2 * (X3 + X3))))).
-step(delete(rule(485, ((-X1 + (X2 + (X2 + X1))) * X3) = ((-? + (X2 + (X2 + ?))) * X3)))).
-step(add(rule(509, ((-X1 + (X2 + (X2 + X1))) * X3) = ((? + (-? + (X2 + X2))) * X3)))).
-step(delete(rule(494, (X1 * (X2 + X2)) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(add(rule(510, (X1 * (X2 + X2)) = ((X1 + X1) * (? + (? + (? + X2))))))).
-step(delete(rule(495, ((X1 + X1) * (X2 + (X3 + (X3 + X3)))) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(add(rule(511, ((X1 + X1) * (X2 + (X3 + (X3 + X3)))) = ((X1 + X1) * (? + (? + (? + X2))))))).
-step(delete(rule(499, (X1 * (-? + (X3 + (X3 + ?)))) = ((X1 + X1) * X3)))).
-step(add(rule(512, (X1 * (? + (-? + (X3 + X3)))) = ((X1 + X1) * X3)))).
-step(delete(rule(500, (X1 * (-X2 + (X3 + (X3 + X2)))) = (X1 * (-? + (X3 + (X3 + ?))))))).
-step(add(rule(513, (X1 * (-X2 + (X3 + (X3 + X2)))) = (X1 * (? + (-? + (X3 + X3))))))).
-step(delete(rule(501, (X1 * (X3 + (X4 + ((X1 * X1) + X2)))) = (X1 + (X1 * (X2 + (X3 + X4))))))).
-step(add(rule(514, ((X1 + (X3 * -X1)) * X1) = (X1 * (X1 + (X1 * -X3)))))).
-step(add(rule(515, (X1 * (X1 * -X2)) = (X2 * (X1 * -X1))))).
-step(add(rule(516, (X1 * (X2 * X2)) = (X2 * (X2 * X1))))).
-step(add(rule(517, (X1 * (X1 * X2)) = (X1 * (X2 * X1))))).
-step(add(rule(518, (X1 * X2) = (X2 * X1)))).
-
-lemma((X1 + 0) = X1).
-lemma((X1 + (-X1 + X2)) = X2).
-lemma(-(-X1) = X1).
-lemma((X1 + (X2 + X3)) = (X2 + (X1 + X3))).
-lemma((X2 + (X1 + -X2)) = X1).
-lemma((X1 * (X1 * (X1 * X2))) = (X1 * X2)).
-lemma((X1 + (X2 + -(X1 + X2))) = 0).
-lemma((X1 * ((X1 * X1) + X2)) = (X1 + (X1 * X2))).
-lemma((X1 * 0) = 0).
-lemma((X1 * (X2 + (X1 * X1))) = (X1 + (X1 * X2))).
-lemma((X2 + -(X1 + X2)) = -X1).
-lemma((X1 + (X2 * (X1 * X1))) = ((X1 + X2) * (X1 * X1))).
-lemma((X2 + -(X2 + X1)) = -X1).
-lemma(-(X1 + -X2) = (X2 + -X1)).
-lemma((X1 + (X1 * (X2 * X1))) = (X1 * ((X1 + X2) * X1))).
-lemma((0 * X1) = 0).
-lemma(((X1 * (X2 * X4)) + (X3 * X4)) = (((X1 * X2) + X3) * X4)).
-lemma((X1 * (X3 + (X2 * X3))) = ((X1 + (X1 * X2)) * X3)).
-lemma(((X1 + (X1 * X2)) * X3) = (X1 * (X3 + (X2 * X3)))).
-lemma(((X1 * X4) + (X2 * (X3 * X4))) = ((X1 + (X2 * X3)) * X4)).
-lemma(-(X1 * X2) = (X1 * -X2)).
-lemma((-X1 * X2) = (X1 * -X2)).
-lemma((X1 * ((X2 + (X1 * X1)) * X3)) = ((X1 + (X1 * X2)) * X3)).
-lemma(((X1 + (X2 * X3)) * (X3 * X3)) = (((X1 * X3) + X2) * X3)).
-lemma((((X3 * X2) + X1) * (X2 * X2)) = (((X1 * X2) + X3) * X2)).
-lemma(((X1 + -X2) * -X3) = ((X2 + -X1) * X3)).
-lemma(((X1 + (X1 * X2)) * (X3 * X4)) = (X1 * ((X3 + (X2 * X3)) * X4))).
-lemma(((X1 * X2) + (X3 * (X2 + X4))) = ((X3 * X4) + ((X1 + X3) * X2))).
-lemma(((X1 + (X1 * (X2 * -X2))) * (X2 * X3)) = 0).
-lemma((X1 * ((X2 + (X1 * (X1 * X3))) * X4)) = (X1 * ((X2 + X3) * X4))).
-lemma(((X1 + (X2 * (X3 * X3))) * (X3 * X4)) = ((X1 + X2) * (X3 * X4))).
-lemma((X1 * (X1 * (X2 * X1))) = (X2 * X1)).
-lemma((X1 * (X2 * (X3 * (X1 * X1)))) = (X1 * (X2 * X3))).
diff --git a/misc/ring_noconn.pl b/misc/ring_noconn.pl
deleted file mode 100644
--- a/misc/ring_noconn.pl
+++ /dev/null
@@ -1,977 +0,0 @@
-:- module(ring_noconn, [step/1, lemma/1]).
-:- discontiguous(step/1).
-:- discontiguous(lemma/1).
-:- style_check(-singleton).
-step(add(rule(1, (X1 + X2) = (X2 + X1)))).
-step(add(rule(2, ((X1 + X2) + X3) = (X1 + (X2 + X3))))).
-step(add(rule(3, (0 + X1) = X1))).
-step(add(rule(4, (X1 + -X1) = 0))).
-step(add(rule(5, ((X1 * X2) * X3) = (X1 * (X2 * X3))))).
-step(add(rule(6, ((X1 * X2) + (X1 * X3)) = (X1 * (X2 + X3))))).
-step(add(rule(7, ((X1 * X3) + (X2 * X3)) = ((X1 + X2) * X3)))).
-step(add(rule(8, (X1 * (X1 * X1)) = X1))).
-step(add(rule(9, -0 = 0))).
-step(add(rule(10, (X1 + 0) = X1))).
-step(add(rule(11, (X1 + (-X1 + X2)) = X2))).
-step(add(rule(12, -(-X1) = X1))).
-step(add(rule(13, (X1 + (X2 + X3)) = (X3 + (X1 + X2))))).
-step(add(rule(14, (X1 + (X2 + X3)) = (X2 + (X1 + X3))))).
-step(hard((X1 * (X2 + X3)) = (X1 * (X3 + X2)))).
-step(hard(((X1 + X2) * X3) = ((X2 + X1) * X3))).
-step(add(rule(15, ((X1 + X1) * X2) = (X1 * (X2 + X2))))).
-step(add(rule(16, (X2 + (X1 + -X2)) = X1))).
-step(add(rule(17, (0 * (X1 + X1)) = (0 * X1)))).
-step(add(rule(18, (X1 * (X1 * (X1 * X2))) = (X1 * X2)))).
-step(hard((X1 + (X2 + X3)) = (X3 + (X2 + X1)))).
-step(hard((X1 + (X2 + X3)) = (X1 + (X3 + X2)))).
-step(add(rule(19, (X1 + (X2 + -(X1 + X2))) = 0))).
-step(add(rule(20, (X1 + -(-X2 + X1)) = X2))).
-step(add(rule(21, (X1 * ((X1 * X1) + X2)) = (X1 + (X1 * X2))))).
-step(add(rule(22, (X1 + (X1 * 0)) = X1))).
-step(add(rule(23, (X1 * 0) = 0))).
-step(add(rule(24, (X1 * (X2 + (X1 * X1))) = (X1 + (X1 * X2))))).
-step(add(rule(25, (X2 + -(X1 + X2)) = -X1))).
-step(add(rule(26, ((X1 + X1) * (X1 * X1)) = (X1 + X1)))).
-step(hard(0 = (X1 + (X2 + -(X2 + X1))))).
-step(add(rule(27, (X2 + -(X2 + -X1)) = X1))).
-step(add(rule(28, -(-X1 + -X2) = (X2 + X1)))).
-step(add(rule(29, (X1 * (0 * X2)) = (0 * X2)))).
-step(add(rule(30, (X1 + (X2 * (X1 * X1))) = ((X1 + X2) * (X1 * X1))))).
-step(add(rule(31, (X2 + -(X2 + X1)) = -X1))).
-step(hard((-X1 + (X2 + (X3 + X1))) = (X3 + X2))).
-step(add(rule(32, (X3 + (X2 + (-X3 + X1))) = (X1 + X2)))).
-step(add(rule(33, (X3 + (X1 + (X2 + -X3))) = (X1 + X2)))).
-step(add(rule(34, -(X1 + -X2) = (X2 + -X1)))).
-step(add(rule(35, (-X1 + -X2) = -(X2 + X1)))).
-step(add(rule(36, -(-X2 + X1) = (-X1 + X2)))).
-step(hard(-(X1 + X2) = -(X2 + X1))).
-step(add(rule(37, (X1 + (X1 * -(X1 * X1))) = 0))).
-step(add(rule(38, (-X1 * -(-X1 * -X1)) = X1))).
-step(add(rule(39, (-X1 * (-X1 * X1)) = X1))).
-step(add(rule(40, (X1 * -(X1 * X1)) = -X1))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X2 + (X3 + (X4 + X1))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X1 + (X2 + X4))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X2 + (X4 + X1))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X4 + (X1 + X2))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X4 + (X3 + (X1 + X2))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X4 + (X2 + (X3 + X1))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X2 + (X4 + (X1 + X3))))).
-step(add(rule(41, ((X1 + X1) * (X2 * X3)) = (X1 * ((X2 + X2) * X3))))).
-step(add(rule(42, (X1 * (X1 * (X1 + X1))) = (X1 + X1)))).
-step(add(rule(43, (X1 * (X2 * (X3 + X3))) = (X1 * ((X2 + X2) * X3))))).
-step(add(rule(44, (X1 * (X2 * (X3 + X3))) = ((X1 + X1) * (X2 * X3))))).
-step(add(rule(45, (X1 + (X1 * (X1 * X2))) = (X1 * (X1 * (X1 + X2)))))).
-step(add(rule(46, (X1 + (X1 * (X2 * X1))) = (X1 * ((X1 + X2) * X1))))).
-step(add(rule(47, (X1 + (0 * X1)) = X1))).
-step(add(rule(48, (0 * X1) = 0))).
-step(hard((X1 * (X1 * (X1 + X2))) = (X1 * (X1 * (X2 + X1))))).
-step(hard((X1 * ((X1 + X2) * X1)) = (X1 * ((X2 + X1) * X1)))).
-step(add(rule(49, (X2 + (X3 + (-(X2 + X3) + X1))) = X1))).
-step(hard((X1 + (X2 + (-(X2 + X1) + X3))) = X3)).
-step(add(rule(50, (X1 * (X1 * -X1)) = -X1))).
-step(hard((X1 + X2) = (-X3 + (X2 + (X3 + X1))))).
-step(hard((X1 + X2) = (-X3 + (X1 + (X2 + X3))))).
-step(hard((X1 + X2) = (-X3 + (X2 + (X1 + X3))))).
-step(add(rule(51, ((X1 * X2) + ((X1 * X3) + X4)) = ((X1 * (X2 + X3)) + X4)))).
-step(hard(((X1 * (X2 + X3)) + X4) = ((X1 * (X3 + X2)) + X4))).
-step(add(rule(52, ((X1 * X2) + ((X3 * X2) + X4)) = (((X1 + X3) * X2) + X4)))).
-step(hard((((X1 + X2) * X3) + X4) = (((X2 + X1) * X3) + X4))).
-step(add(rule(53, (((X1 + X1) * X2) + X3) = ((X1 * (X2 + X2)) + X3)))).
-step(add(rule(54, ((X1 * (X2 * X4)) + (X3 * X4)) = (((X1 * X2) + X3) * X4)))).
-step(add(rule(55, (((X1 * X1) + X2) * X1) = (X1 + (X2 * X1))))).
-step(add(rule(56, (X1 + (-(X1 * X1) * X1)) = 0))).
-step(add(rule(57, (-(X1 * X1) * X1) = -X1))).
-step(add(rule(58, ((X1 + (X1 * X2)) * X3) = (X1 * (X3 + (X2 * X3)))))).
-step(add(rule(59, ((X2 + (X1 * X1)) * X1) = (X1 + (X2 * X1))))).
-step(add(rule(60, ((X1 * X4) + (X2 * (X3 * X4))) = ((X1 + (X2 * X3)) * X4)))).
-step(add(rule(61, (X1 * (X2 * (X1 * (X2 * (X1 * X2))))) = (X1 * X2)))).
-step(add(rule(62, (X3 + (X2 + -(X3 + X1))) = (-X1 + X2)))).
-step(add(rule(63, (X3 + (-(X3 + X2) + X1)) = (X1 + -X2)))).
-step(add(rule(64, (X1 * ((X1 * (X1 * X2)) + X3)) = (X1 * (X2 + X3))))).
-step(add(rule(65, (X1 * (X2 + (X1 * (X1 * X3)))) = (X1 * (X2 + X3))))).
-step(add(rule(66, (X1 * (X2 + X2)) = (X1 * (X1 * ((X1 + X1) * X2)))))).
-step(add(rule(67, (X1 * (X1 * (X1 + (X1 * X2)))) = (X1 + (X1 * X2))))).
-step(add(rule(68, -((X1 + X1) * X2) = -(X1 * (X2 + X2))))).
-step(hard((X1 + -(X3 + X2)) = (-(X2 + X3) + X1))).
-step(hard(-(X3 + (X1 + X2)) = -(X1 + (X3 + X2)))).
-step(add(rule(69, (X1 + (X1 * (-(X1 * X1) + X2))) = (X1 * X2)))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X2 + (X4 + (X3 + X1))))).
-step(add(rule(70, (-(X1 * X3) + (X1 * (X2 + X3))) = (X1 * X2)))).
-step(add(rule(71, -(X1 * -X2) = (X1 * X2)))).
-step(add(rule(72, -(X1 * X2) = (X1 * -X2)))).
-step(add(rule(73, (X1 * (X2 * (-X2 * -X2))) = (X1 * X2)))).
-step(add(rule(74, (-X1 * (X1 * -X1)) = X1))).
-step(add(rule(75, (X1 * (-X1 * -X1)) = X1))).
-step(add(rule(76, (-X1 * (X1 * X1)) = -X1))).
-step(add(rule(77, (X1 * (-X1 * X1)) = -X1))).
-step(add(rule(78, ((X2 * -X3) + ((X1 + X2) * X3)) = (X1 * X3)))).
-step(add(rule(79, (-X1 * -X2) = (X1 * X2)))).
-step(add(rule(80, (-X1 * X2) = (X1 * -X2)))).
-step(add(rule(81, ((X1 * (X2 + X2)) + X3) = (X3 + ((X1 + X1) * X2))))).
-step(add(rule(82, (X1 * -(X2 + X2)) = ((X1 + X1) * -X2)))).
-step(add(rule(83, (X1 + ((X2 + X2) * X3)) = (X1 + (X2 * (X3 + X3)))))).
-step(add(rule(84, (((X1 + X1) * X2) + X3) = (X3 + (X1 * (X2 + X2)))))).
-step(add(rule(85, (X2 + (X3 + (X1 + -(X2 + X3)))) = X1))).
-step(add(rule(86, ((X1 + (X1 * X1)) * (X1 * X1)) = (X1 + (X1 * X1))))).
-step(add(rule(87, ((X1 * X2) + (X3 + (X1 * X4))) = (X3 + (X1 * (X4 + X2)))))).
-step(hard((X1 + (X2 * (X3 + X4))) = ((X2 * (X4 + X3)) + X1))).
-step(hard((X1 * (X2 + (X3 + X4))) = (X1 * (X4 + (X2 + X3))))).
-step(hard((X1 + (X2 * (X3 + X4))) = (X1 + (X2 * (X4 + X3))))).
-step(add(rule(88, ((X1 * X2) + (X3 + (X4 * X2))) = (X3 + ((X4 + X1) * X2))))).
-step(hard((X1 + ((X2 + X3) * X4)) = (((X3 + X2) * X4) + X1))).
-step(hard(((X1 + (X3 + X4)) * X2) = ((X4 + (X1 + X3)) * X2))).
-step(hard((X1 + ((X2 + X3) * X4)) = (X1 + ((X3 + X2) * X4)))).
-step(add(rule(89, ((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X1 + X2))) * X3)))).
-step(add(rule(90, ((X1 + (X1 + X2)) * X3) = ((X1 * (X3 + X3)) + (X2 * X3))))).
-step(add(rule(91, ((X1 + (X1 + X1)) * X2) = (X1 * (X2 + (X2 + X2)))))).
-step(add(rule(92, ((X1 + (X2 + X2)) * X3) = ((X1 * X3) + (X2 * (X3 + X3)))))).
-step(add(rule(93, ((X1 + X2) * (X3 + X3)) = ((X2 + (X1 + (X2 + X1))) * X3)))).
-step(add(rule(94, ((X1 + X2) * (X3 + X3)) = ((X1 + (X1 + (X2 + X2))) * X3)))).
-step(add(rule(95, ((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X2 + X3))))))).
-step(add(rule(96, (X1 * (X2 + (X2 + X3))) = (((X1 + X1) * X2) + (X1 * X3))))).
-step(add(rule(97, (X1 * (X2 + (X3 + X3))) = ((X1 * X2) + ((X1 + X1) * X3))))).
-step(add(rule(98, ((X1 + X1) * (X2 + X3)) = (X1 * (X3 + (X2 + (X3 + X2))))))).
-step(add(rule(99, ((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X2 + (X3 + X3))))))).
-step(add(rule(100, (X1 * (((X1 * X1) + X2) * X3)) = ((X1 + (X1 * X2)) * X3)))).
-step(add(rule(101, (X1 * (X3 + ((X1 * X1) + X2))) = (X1 + (X1 * (X2 + X3)))))).
-step(add(rule(102, (X1 * (X2 + (X3 + (X1 * X1)))) = (X1 + (X1 * (X2 + X3)))))).
-step(add(rule(103, (X1 * ((X2 + (X1 * X1)) * X3)) = ((X1 + (X1 * X2)) * X3)))).
-step(add(rule(104, (X1 + (-(X2 + X1) + X3)) = (-X2 + X3)))).
-step(add(rule(105, (X3 + -(X1 + (X2 + X3))) = -(X1 + X2)))).
-step(add(rule(106, (X1 + (X2 + -(X3 + X1))) = (X2 + -X3)))).
-step(add(rule(107, (((X1 * X1) + X2) * (X1 * X3)) = ((X1 + (X2 * X1)) * X3)))).
-step(add(rule(108, ((X1 + (X2 * X3)) * (X3 * X3)) = (((X1 * X3) + X2) * X3)))).
-step(add(rule(109, ((X1 + (X2 * X2)) * (X2 * X3)) = ((X2 + (X1 * X2)) * X3)))).
-step(add(rule(110, (X1 * (X1 * -(X1 + X1))) = -(X1 + X1)))).
-step(add(rule(111, (X1 * (X1 * ((X1 + X1) * X2))) = ((X1 + X1) * X2)))).
-step(add(rule(112, ((X1 + (X1 + X1)) * (X1 * X1)) = (X1 + (X1 + X1))))).
-step(add(rule(113, (-X1 + (X2 + -X3)) = (X2 + -(X1 + X3))))).
-step(hard((X1 * -(X2 + X3)) = (X1 * -(X3 + X2)))).
-step(add(rule(114, ((X1 + (X1 * (X2 * X2))) * X2) = (X1 * (X2 + X2))))).
-step(hard(-(X2 + (X3 + X1)) = -(X1 + (X2 + X3)))).
-step(hard(-(X3 + (X1 + X2)) = -(X3 + (X2 + X1)))).
-step(add(rule(115, (((X1 * (X2 * X2)) + X3) * X2) = ((X1 + X3) * X2)))).
-step(hard((X1 + -(X2 + X3)) = (X1 + -(X3 + X2)))).
-step(add(rule(116, (X1 + (X1 * (X2 + (X1 * -X1)))) = (X1 * X2)))).
-step(add(rule(117, ((X1 * -X3) + ((X1 + X2) * X3)) = (X2 * X3)))).
-step(add(rule(118, (X1 + (((X1 * -X1) + X2) * X1)) = (X2 * X1)))).
-step(add(rule(119, (X1 + ((X2 + (X1 * -X1)) * X1)) = (X2 * X1)))).
-step(add(rule(120, (X1 + ((-X2 + (X1 * X1)) * -X1)) = (X2 * X1)))).
-step(interreduce).
-step(delete(rule(13, (X1 + (X2 + X3)) = (X3 + (X1 + X2))))).
-step(delete(rule(17, (0 * (X1 + X1)) = (0 * X1)))).
-step(delete(rule(19, (X1 + (X2 + -(X1 + X2))) = 0))).
-step(delete(rule(20, (X1 + -(-X2 + X1)) = X2))).
-step(delete(rule(22, (X1 + (X1 * 0)) = X1))).
-step(delete(rule(26, ((X1 + X1) * (X1 * X1)) = (X1 + X1)))).
-step(delete(rule(27, (X2 + -(X2 + -X1)) = X1))).
-step(delete(rule(28, -(-X1 + -X2) = (X2 + X1)))).
-step(delete(rule(29, (X1 * (0 * X2)) = (0 * X2)))).
-step(delete(rule(37, (X1 + (X1 * -(X1 * X1))) = 0))).
-step(delete(rule(38, (-X1 * -(-X1 * -X1)) = X1))).
-step(delete(rule(39, (-X1 * (-X1 * X1)) = X1))).
-step(delete(rule(40, (X1 * -(X1 * X1)) = -X1))).
-step(delete(rule(47, (X1 + (0 * X1)) = X1))).
-step(delete(rule(49, (X2 + (X3 + (-(X2 + X3) + X1))) = X1))).
-step(delete(rule(56, (X1 + (-(X1 * X1) * X1)) = 0))).
-step(delete(rule(57, (-(X1 * X1) * X1) = -X1))).
-step(delete(rule(66, (X1 * (X2 + X2)) = (X1 * (X1 * ((X1 + X1) * X2)))))).
-step(delete(rule(68, -((X1 + X1) * X2) = -(X1 * (X2 + X2))))).
-step(delete(rule(69, (X1 + (X1 * (-(X1 * X1) + X2))) = (X1 * X2)))).
-step(add(rule(121, (X1 + (X1 * ((X1 * -X1) + X2))) = (X1 * X2)))).
-step(delete(rule(70, (-(X1 * X3) + (X1 * (X2 + X3))) = (X1 * X2)))).
-step(delete(rule(71, -(X1 * -X2) = (X1 * X2)))).
-step(delete(rule(73, (X1 * (X2 * (-X2 * -X2))) = (X1 * X2)))).
-step(delete(rule(74, (-X1 * (X1 * -X1)) = X1))).
-step(delete(rule(75, (X1 * (-X1 * -X1)) = X1))).
-step(delete(rule(76, (-X1 * (X1 * X1)) = -X1))).
-step(delete(rule(77, (X1 * (-X1 * X1)) = -X1))).
-step(delete(rule(79, (-X1 * -X2) = (X1 * X2)))).
-step(delete(rule(85, (X2 + (X3 + (X1 + -(X2 + X3)))) = X1))).
-step(delete(rule(86, ((X1 + (X1 * X1)) * (X1 * X1)) = (X1 + (X1 * X1))))).
-step(delete(rule(89, ((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X1 + X2))) * X3)))).
-step(delete(rule(93, ((X1 + X2) * (X3 + X3)) = ((X2 + (X1 + (X2 + X1))) * X3)))).
-step(delete(rule(95, ((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X2 + X3))))))).
-step(delete(rule(98, ((X1 + X1) * (X2 + X3)) = (X1 * (X3 + (X2 + (X3 + X2))))))).
-step(add(rule(122, ((X1 + (X1 * (X2 * -X2))) * X2) = 0))).
-step(add(rule(123, ((X3 * X2) + ((X1 + X3) * -X2)) = (X1 * -X2)))).
-step(add(rule(124, ((X1 + (X1 + (X1 + X1))) * X2) = (X1 * (X2 + (X2 + (X2 + X2))))))).
-step(add(rule(125, ((X1 * X3) + (X2 * (X1 * (X1 * X3)))) = ((X1 + X2) * (X1 * (X1 * X3)))))).
-step(add(rule(126, (X1 * ((X1 + X1) * ((X1 + X1) * (X2 + X2)))) = ((X1 + X1) * X2)))).
-step(add(rule(127, (((X1 + X2) * (X1 * X1)) + X3) = (X1 + ((X2 * (X1 * X1)) + X3))))).
-step(add(rule(128, (X1 + (X2 * (X3 * (X1 * X1)))) = ((X1 + (X2 * X3)) * (X1 * X1))))).
-step(add(rule(129, (X1 + (X2 + (X3 * (X1 * X1)))) = (X2 + ((X1 + X3) * (X1 * X1)))))).
-step(add(rule(130, (X1 * (X1 * (X1 + (X1 + X1)))) = (X1 + (X1 + X1))))).
-step(add(rule(131, ((X1 + (X2 + X2)) * (X1 * X1)) = (X1 + (X2 * (X1 * (X1 + X1))))))).
-step(add(rule(132, ((X1 * (X2 + X3)) + (X4 * X3)) = ((X1 * X2) + ((X1 + X4) * X3))))).
-step(add(rule(133, ((X1 * X2) + ((X1 + X3) * -X2)) = (X3 * -X2)))).
-step(add(rule(134, (X1 + (X1 * ((X1 * X2) + X3))) = (X1 * ((X1 * (X1 + X2)) + X3))))).
-step(add(rule(135, (((X1 + X2) * X3) + (X2 * X4)) = ((X1 * X3) + (X2 * (X3 + X4)))))).
-step(add(rule(136, (((X1 + X2) * (X2 * X2)) + X3) = (X2 + ((X1 * (X2 * X2)) + X3))))).
-step(add(rule(137, (X1 + (X1 * ((X2 * X1) + X3))) = (X1 * (((X1 + X2) * X1) + X3))))).
-step(add(rule(138, (((X1 * X2) + X3) * (X2 * X2)) = ((X1 + (X3 * X2)) * X2)))).
-step(add(rule(139, ((X1 + (X2 * (X3 * X3))) * X3) = ((X1 + X2) * X3)))).
-step(add(rule(140, (X1 + (X1 * (X2 * (X3 * X1)))) = (X1 * (((X2 * X3) + X1) * X1))))).
-step(add(rule(141, ((X1 * -X3) + (X2 * X3)) = ((-X1 + X2) * X3)))).
-step(hard(((-X1 + (X2 + X1)) * X3) = (X2 * X3))).
-step(hard((X1 * X2) = ((-X3 + (X1 + X3)) * X2))).
-step(add(rule(142, (((X1 * -X1) + X2) * -X1) = (X1 + (X2 * -X1))))).
-step(add(rule(143, ((X1 + (X2 * -X2)) * -X2) = (X2 + (X1 * -X2))))).
-step(add(rule(144, (((X1 * -X1) + X2) * X1) = (-X1 + (X2 * X1))))).
-step(add(rule(145, (-X1 + (-X1 + X2)) = (-(X1 + X1) + X2)))).
-step(add(rule(146, (X1 * -((X1 * -X1) + X2)) = (X1 + (X1 * -X2))))).
-step(add(rule(147, (X1 * ((X1 * -X1) + X2)) = (-X1 + (X1 * X2))))).
-step(add(rule(148, (X3 + -(X1 + (X3 + X2))) = -(X1 + X2)))).
-step(hard(X1 = (-X3 + (X1 + X3)))).
-step(add(rule(149, ((X2 + ((X1 * X1) + X3)) * X1) = (X1 + ((X2 + X3) * X1))))).
-step(add(rule(150, (X1 * (X1 * ((X1 * X2) + X3))) = (X1 * (X2 + (X1 * X3)))))).
-step(add(rule(151, (X1 * (X1 * (X2 + (X1 * X3)))) = (X1 * ((X1 * X2) + X3))))).
-step(hard(((X1 * (X2 + X2)) + (X3 * X2)) = ((X1 + (X3 + X1)) * X2))).
-step(add(rule(152, ((X1 + (X1 + X2)) * X3) = ((X2 * X3) + (X1 * (X3 + X3)))))).
-step(hard(((X1 * X2) + (X3 * (X2 + X2))) = ((X3 + (X1 + X3)) * X2))).
-step(hard(((X1 + (X2 + X2)) * X3) = ((X2 * (X3 + X3)) + (X1 * X3)))).
-step(hard(((X1 + (X1 + (X2 + X2))) * X3) = ((X2 + X1) * (X3 + X3)))).
-step(hard(((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X2 + X1))) * X3))).
-step(hard(((X1 + X2) * (X3 + X3)) = ((X1 + (X2 + (X1 + X2))) * X3))).
-step(hard((((X1 + X1) * X2) + (X1 * X3)) = (X1 * (X2 + (X3 + X2))))).
-step(add(rule(153, (X1 * (X2 + (X2 + X3))) = ((X1 * X3) + ((X1 + X1) * X2))))).
-step(hard(((X1 * X2) + ((X1 + X1) * X3)) = (X1 * (X3 + (X2 + X3))))).
-step(hard((X1 * (X2 + (X3 + X3))) = (((X1 + X1) * X3) + (X1 * X2)))).
-step(hard((X1 * (X2 + (X2 + (X3 + X3)))) = ((X1 + X1) * (X3 + X2)))).
-step(hard(((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X3 + X2)))))).
-step(hard(((X1 + X1) * (X2 + X3)) = (X1 * (X2 + (X3 + (X2 + X3)))))).
-step(add(rule(154, (X1 * ((X1 + X1) * (X1 + (X1 + (X1 + X1))))) = (X1 + X1)))).
-step(add(rule(155, (X1 * ((X1 * (X1 + X1)) + X2)) = (X1 + (X1 + (X1 * X2)))))).
-step(add(rule(156, (X4 + (X2 + (X3 + (-X4 + X1)))) = (X1 + (X2 + X3))))).
-step(hard((X1 + (X2 + X3)) = (X2 + (X3 + X1)))).
-step(hard((X1 + (-X2 + (X3 + X2))) = (X1 + X3))).
-step(hard((-X1 + (X2 + (X1 + X3))) = (X2 + X3))).
-step(hard((-(X1 + X2) + (X3 + X1)) = (X3 + -X2))).
-step(hard((X4 + (X5 + (X2 + X3))) = (X4 + (X2 + (X3 + X5))))).
-step(hard((-(X1 + X2) + (X3 + X2)) = (-X1 + X3))).
-step(add(rule(157, -(X1 + (-X2 + X3)) = (X2 + -(X3 + X1))))).
-step(hard(-X1 = (-X2 + (-X1 + X2)))).
-step(add(rule(158, (X4 + (X1 + (X2 + (X3 + -X4)))) = (X1 + (X2 + X3))))).
-step(add(rule(159, -(X1 + (X2 + -X3)) = (X3 + -(X1 + X2))))).
-step(add(rule(160, (-X1 + (-X2 + X3)) = (-(X2 + X1) + X3)))).
-step(add(rule(161, -(X3 + (X1 * -X2)) = ((X1 * X2) + -X3)))).
-step(add(rule(162, ((X2 * -X3) + -X1) = -(X1 + (X2 * X3))))).
-step(add(rule(163, (-X3 + (X1 * -X2)) = -((X1 * X2) + X3)))).
-step(add(rule(164, -((X2 * -X3) + X1) = (-X1 + (X2 * X3))))).
-step(add(rule(165, ((X1 + -X2) * -X3) = ((X2 + -X1) * X3)))).
-step(add(rule(166, ((-X1 + X2) * -X3) = ((-X2 + X1) * X3)))).
-step(hard((X1 + (X1 * (-X2 + (X3 + X2)))) = (X1 + (X1 * X3)))).
-step(interreduce).
-step(delete(rule(67, (X1 * (X1 * (X1 + (X1 * X2)))) = (X1 + (X1 * X2))))).
-step(delete(rule(78, ((X2 * -X3) + ((X1 + X2) * X3)) = (X1 * X3)))).
-step(delete(rule(114, ((X1 + (X1 * (X2 * X2))) * X2) = (X1 * (X2 + X2))))).
-step(delete(rule(117, ((X1 * -X3) + ((X1 + X2) * X3)) = (X2 * X3)))).
-step(delete(rule(118, (X1 + (((X1 * -X1) + X2) * X1)) = (X2 * X1)))).
-step(delete(rule(120, (X1 + ((-X2 + (X1 * X1)) * -X1)) = (X2 * X1)))).
-step(delete(rule(121, (X1 + (X1 * ((X1 * -X1) + X2))) = (X1 * X2)))).
-step(delete(rule(145, (-X1 + (-X1 + X2)) = (-(X1 + X1) + X2)))).
-step(delete(rule(146, (X1 * -((X1 * -X1) + X2)) = (X1 + (X1 * -X2))))).
-step(add(rule(167, ((X1 + X1) * (X1 + (X1 + (X1 + X1)))) = (X1 * (X1 + X1))))).
-step(hard((-(X1 + X2) + X3) = (-(X2 + X1) + X3))).
-step(add(rule(168, ((X1 * (X2 + X2)) + ((X1 + X1) * -X2)) = 0))).
-step(add(rule(169, (((X1 + X1) * X2) + (X1 * -(X2 + X2))) = 0))).
-step(add(rule(170, ((X1 * X3) + (X2 * -X3)) = ((-X2 + X1) * X3)))).
-step(add(rule(171, ((X1 + (X1 * (X2 * -X2))) * -X2) = 0))).
-step(add(rule(172, ((X1 + (X1 * (X2 * -X2))) * (X2 * -X2)) = 0))).
-step(hard((X1 + (-X3 + (X2 + X3))) = (X2 + X1))).
-step(hard((X1 * X2) = ((-X4 + (X1 + X4)) * X2))).
-step(add(rule(173, ((X1 * (X2 + X2)) + ((X1 + X1) * X3)) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(174, (((X1 + X1) * X2) + (X1 * (X3 + X3))) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(175, (X1 + (X1 + ((X1 + X1) * X2))) = ((X1 + X1) * (X2 + (X1 * X1)))))).
-step(add(rule(176, (X1 * ((X1 + X1) * (X1 + X1))) = (X1 + (X1 + (X1 + X1)))))).
-step(add(rule(177, (((X1 + X1) * X3) + (X2 * (X3 + X3))) = ((X1 + X2) * (X3 + X3))))).
-step(add(rule(178, ((X1 * (X3 + X3)) + ((X2 + X2) * X3)) = ((X1 + X2) * (X3 + X3))))).
-step(add(rule(179, (X1 + (X1 * -(X2 + (X1 * X1)))) = (X1 * -X2)))).
-step(add(rule(180, (X1 + ((X2 + (X1 * X1)) * -X1)) = (X2 * -X1)))).
-step(add(rule(181, (X2 + (X3 + (-X1 + X4))) = (X2 + (X4 + (-X1 + X3)))))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X1 + (X4 + (X3 + X2))))).
-step(add(rule(182, (((X1 + X1) * -X2) + X3) = ((X1 * -(X2 + X2)) + X3)))).
-step(add(rule(183, (X1 + ((X2 + X2) * -X3)) = (X1 + (X2 * -(X3 + X3)))))).
-step(add(rule(184, (X1 + ((X2 + X2) * -X3)) = ((X2 * -(X3 + X3)) + X1)))).
-step(add(rule(185, (X3 * ((X2 + X2) * -X4)) = (X3 * (X2 * -(X4 + X4)))))).
-step(add(rule(186, (X1 * (X4 * -(X3 + X3))) = ((X1 + X1) * (X4 * -X3))))).
-step(add(rule(187, (X2 + (((X2 * X2) + X1) * -X2)) = (X1 * -X2)))).
-step(add(rule(188, ((X1 + (X1 + X1)) * -X2) = (X1 * -(X2 + (X2 + X2)))))).
-step(hard((-(X1 + X2) + (X3 + X4)) = (X3 + (X4 + -(X2 + X1))))).
-step(add(rule(189, (X1 + (X2 * -(X3 + X3))) = (((X2 + X2) * -X3) + X1)))).
-step(add(rule(190, (X1 * (X2 * ((X3 + X3) * X4))) = ((X1 + X1) * (X2 * (X3 * X4)))))).
-step(add(rule(191, (X1 * (X2 * ((X3 + X3) * X4))) = (X1 * ((X2 + X2) * (X3 * X4)))))).
-step(add(rule(192, ((X1 + X1) * (X2 + (X2 + (X2 + X2)))) = (X1 * (X2 + X2))))).
-step(add(rule(193, (X1 * (X2 * ((X3 + X3) * X4))) = (X1 * (X2 * (X3 * (X4 + X4))))))).
-step(add(rule(194, (X1 * ((X2 * (X3 + X3)) + X4)) = (X1 * (((X2 + X2) * X3) + X4))))).
-step(add(rule(195, (X1 * ((X2 + X2) * (X3 * X4))) = (X1 * (X2 * (X3 * (X4 + X4))))))).
-step(add(rule(196, (X1 * (X2 + (X3 * (X4 + X4)))) = (X1 * (X2 + ((X3 + X3) * X4)))))).
-step(add(rule(197, ((X1 + X1) * (X2 * (X3 * X4))) = (X1 * (X2 * (X3 * (X4 + X4))))))).
-step(interreduce).
-step(delete(rule(123, ((X3 * X2) + ((X1 + X3) * -X2)) = (X1 * -X2)))).
-step(delete(rule(133, ((X1 * X2) + ((X1 + X3) * -X2)) = (X3 * -X2)))).
-step(delete(rule(154, (X1 * ((X1 + X1) * (X1 + (X1 + (X1 + X1))))) = (X1 + X1)))).
-step(delete(rule(167, ((X1 + X1) * (X1 + (X1 + (X1 + X1)))) = (X1 * (X1 + X1))))).
-step(delete(rule(168, ((X1 * (X2 + X2)) + ((X1 + X1) * -X2)) = 0))).
-step(add(rule(198, ((X1 * (X1 * (X1 + X2))) + X3) = (X1 + ((X1 * (X1 * X2)) + X3))))).
-step(add(rule(199, (X1 + (X2 + (X1 * (X1 * X3)))) = (X2 + (X1 * (X1 * (X1 + X3))))))).
-step(add(rule(200, (X1 * (X1 * (X1 + (X2 + X2)))) = (X1 + (X1 * ((X1 + X1) * X2)))))).
-step(add(rule(201, ((X1 * ((X1 + X2) * X1)) + X3) = (X1 + ((X1 * (X2 * X1)) + X3))))).
-step(add(rule(202, (X1 + (X2 + (X1 * (X3 * X1)))) = (X2 + (X1 * ((X1 + X3) * X1)))))).
-step(add(rule(203, (X1 * ((X1 + (X2 + X2)) * X1)) = (X1 + (X1 * (X2 * (X1 + X1))))))).
-step(add(rule(204, ((X2 * -X3) + (((X1 + X2) * X3) + X4)) = ((X1 * X3) + X4)))).
-step(hard(((X1 + X3) * X2) = ((-X4 + (X1 + (X4 + X3))) * X2))).
-step(hard(((X1 * X2) + X3) = (((-X4 + (X1 + X4)) * X2) + X3))).
-step(hard(((-X3 + X1) * X2) = ((-(X3 + X4) + (X1 + X4)) * X2))).
-step(add(rule(205, ((X1 * (X2 * (X3 + X3))) + X4) = ((X1 * ((X2 + X2) * X3)) + X4)))).
-step(add(rule(206, ((X1 * (X2 * (X3 + X3))) + X4) = (((X1 + X1) * (X2 * X3)) + X4)))).
-step(add(rule(207, ((X1 * (X2 + X2)) + (X3 + X4)) = (X3 + (((X1 + X1) * X2) + X4))))).
-step(add(rule(208, (((X1 + X1) * X2) + (X3 + X4)) = (X3 + ((X1 * (X2 + X2)) + X4))))).
-step(add(rule(209, (X1 + (X1 * ((X2 + X2) * X3))) = (X1 + (X1 * (X2 * (X3 + X3))))))).
-step(add(rule(210, (((X1 + X1) * (X2 * X3)) + X4) = ((X1 * ((X2 + X2) * X3)) + X4)))).
-step(add(rule(211, ((((X1 + X1) * X2) + X3) * X4) = (((X1 * (X2 + X2)) + X3) * X4)))).
-step(add(rule(212, (X1 + ((X1 + (X2 * X1)) * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(simplify_queue).
-step(interreduce).
-step(hard((X1 + X2) = (X2 + (-X4 + (X1 + X4))))).
-step(hard((X1 + ((X1 * (X3 + X2)) + X4)) = (X1 + ((X1 * (X2 + X3)) + X4)))).
-step(hard((X4 + (X1 * (-X3 + (X2 + X3)))) = ((X1 * X2) + X4))).
-step(add(rule(213, (X1 + (X1 * (X1 + (X1 * X2)))) = (X1 * (X1 + (X1 * (X2 + X1))))))).
-step(hard((X1 * (X1 + (X1 * (X1 + X2)))) = (X1 * (X1 + (X1 * (X2 + X1)))))).
-step(add(rule(214, ((X1 + (X1 * X2)) * (X1 * X1)) = (X1 * ((X1 + (X2 * X1)) * X1))))).
-step(add(rule(215, (X1 + (X1 * (X1 + (X2 * X1)))) = (X1 * (X1 + ((X1 + X2) * X1)))))).
-step(add(rule(216, (X1 + (((X1 * X2) + X3) * X1)) = (((X1 * (X1 + X2)) + X3) * X1)))).
-step(add(rule(217, (X1 + (((X2 * X1) + X3) * X1)) = ((((X1 + X2) * X1) + X3) * X1)))).
-step(hard(((-X2 + (X1 + X2)) * (X1 * X1)) = X1)).
-step(add(rule(218, ((X1 + (X1 * X2)) * (X3 * X4)) = (X1 * ((X3 + (X2 * X3)) * X4))))).
-step(add(rule(219, (X1 * (X2 * (X3 + (X4 * X3)))) = (X1 * ((X2 + (X2 * X4)) * X3))))).
-step(add(rule(220, (X1 * (X2 * (X3 + (X2 * X3)))) = ((X1 + (X1 * X2)) * (X2 * X3))))).
-step(add(rule(221, (X1 * (X2 + ((X3 + X3) * X2))) = ((X1 + ((X1 + X1) * X3)) * X2)))).
-step(add(rule(222, (X1 * (X2 + (X1 * (X3 * X2)))) = (X1 * (X1 * ((X1 + X3) * X2)))))).
-step(add(rule(223, (X1 * (X2 + (X3 * (X1 * X2)))) = (X1 * ((X1 + X3) * (X1 * X2)))))).
-step(add(rule(224, ((X1 + (X1 * (X2 * X3))) * X4) = (X1 * (X4 + (X2 * (X3 * X4))))))).
-step(add(rule(225, ((X1 + (X1 * (X2 + X2))) * X3) = (X1 * (X3 + (X2 * (X3 + X3))))))).
-step(add(rule(226, ((X1 + ((X2 + X2) * X3)) * X4) = ((X1 + (X2 * (X3 + X3))) * X4)))).
-step(add(rule(227, (X1 * (X3 + (X2 * (X1 * X3)))) = (X1 * ((X2 + X1) * (X1 * X3)))))).
-step(add(rule(228, (X1 * -(X2 + (X1 * (X1 * -X2)))) = 0))).
-step(add(rule(229, (X1 * -((X1 * X1) + X2)) = -(X1 + (X1 * X2))))).
-step(add(rule(230, (X1 * -(X2 + (X1 * X1))) = -(X1 + (X1 * X2))))).
-step(add(rule(231, (((X1 * X1) + X2) * -X1) = -(X1 + (X2 * X1))))).
-step(interreduce).
-step(delete(rule(179, (X1 + (X1 * -(X2 + (X1 * X1)))) = (X1 * -X2)))).
-step(delete(rule(187, (X2 + (((X2 * X2) + X1) * -X2)) = (X1 * -X2)))).
-step(delete(rule(214, ((X1 + (X1 * X2)) * (X1 * X1)) = (X1 * ((X1 + (X2 * X1)) * X1))))).
-step(add(rule(232, ((X1 + (X2 * X2)) * -X2) = -(X2 + (X1 * X2))))).
-step(add(rule(233, ((X1 * X2) + (X3 * (X2 + X4))) = ((X3 * X4) + ((X1 + X3) * X2))))).
-step(hard((X1 + (X2 + (X2 * (X4 + X3)))) = (X2 + (X1 + (X2 * (X3 + X4)))))).
-step(add(rule(234, (X1 + (X1 * (X2 + (X1 * X3)))) = (X1 * (X2 + (X1 * (X3 + X1))))))).
-step(hard((X4 + (X1 * (X5 + (X2 + X3)))) = (X4 + (X1 * (X5 + (X3 + X2)))))).
-step(add(rule(235, ((X1 * X2) + (X3 * (X4 + X2))) = (((X3 + X1) * X2) + (X3 * X4))))).
-step(hard((X1 * (-X2 + (X3 + X2))) = (X1 * X3))).
-step(add(rule(236, (X1 + ((X2 + (X1 * X3)) * X1)) = ((X2 + (X1 * (X3 + X1))) * X1)))).
-step(add(rule(237, ((X1 * X2) + ((X1 + X3) * X4)) = ((X3 * X4) + (X1 * (X2 + X4)))))).
-step(add(rule(238, (X1 + ((X2 + X3) * (X3 * X3))) = (X3 + (X1 + (X2 * (X3 * X3))))))).
-step(add(rule(239, (X1 + ((X2 + X3) * (X2 * X2))) = ((X3 * (X2 * X2)) + (X1 + X2))))).
-step(add(rule(240, (X1 + (X1 * (X2 + (X3 * X1)))) = (X1 * (X2 + ((X3 + X1) * X1)))))).
-step(add(rule(241, ((X1 * X2) + ((X3 + X1) * X4)) = ((X1 * (X4 + X2)) + (X3 * X4))))).
-step(hard((X4 + ((X5 + (X1 + X2)) * X3)) = (X4 + ((X5 + (X2 + X1)) * X3)))).
-step(add(rule(242, (X1 + ((X2 + (X3 * X1)) * X1)) = ((X2 + ((X3 + X1) * X1)) * X1)))).
-step(hard(((X1 + (X1 + X2)) * (X2 * X2)) = (X2 + (X1 * (X2 * (X2 + X2)))))).
-step(hard((X1 + (X1 * (X2 * (X1 + X1)))) = (X1 * ((X2 + (X2 + X1)) * X1)))).
-step(hard(((X1 + (X1 + (X2 + X2))) * X3) = ((X1 + (X2 + (X1 + X2))) * X3))).
-step(add(rule(243, (((X1 * (X1 + X1)) + (X2 + X2)) * X1) = (((X1 * X1) + X2) * (X1 + X1))))).
-step(hard((X1 + (X1 * ((X1 + X1) * X2))) = (X1 * (X1 * (X2 + (X2 + X1)))))).
-step(add(rule(244, (X1 * (X3 + (X2 * (X3 + X3)))) = ((X1 + ((X1 + X1) * X2)) * X3)))).
-step(add(rule(245, (X1 * (X2 + (X2 + (X3 * X2)))) = ((X1 + (X1 + (X1 * X3))) * X2)))).
-step(hard((X1 * (X1 * (X2 + (X1 + X1)))) = (X1 * (X1 * (X1 + (X1 + X2)))))).
-step(hard((X1 * (X2 + (X2 + (X3 + X3)))) = (X1 * (X2 + (X3 + (X2 + X3)))))).
-step(add(rule(246, ((X1 + X1) * ((X1 * X1) + X2)) = (X1 + (X1 + (X1 * (X2 + X2))))))).
-step(hard((X1 * (X1 + ((X1 + X2) * X1))) = (X1 * (X1 + ((X2 + X1) * X1))))).
-step(add(rule(247, (X1 * (((X1 * (X1 + X1)) + X2) * X3)) = (X1 * (X3 + (X3 + (X2 * X3))))))).
-step(interreduce).
-step(delete(rule(116, (X1 + (X1 * (X2 + (X1 * -X1)))) = (X1 * X2)))).
-step(delete(rule(119, (X1 + ((X2 + (X1 * -X1)) * X1)) = (X2 * X1)))).
-step(delete(rule(180, (X1 + ((X2 + (X1 * X1)) * -X1)) = (X2 * -X1)))).
-step(delete(rule(212, (X1 + ((X1 + (X2 * X1)) * X1)) = ((X1 + ((X1 + X2) * X1)) * X1)))).
-step(delete(rule(213, (X1 + (X1 * (X1 + (X1 * X2)))) = (X1 * (X1 + (X1 * (X2 + X1))))))).
-step(delete(rule(215, (X1 + (X1 * (X1 + (X2 * X1)))) = (X1 * (X1 + ((X1 + X2) * X1)))))).
-step(add(rule(248, (X1 * (X2 + (X2 + (X1 * (X1 + X1))))) = ((X1 + X1) * ((X1 * X1) + X2))))).
-step(add(rule(249, (X1 + (X1 + (X1 * (X2 + X2)))) = ((X1 + X1) * (X2 + (X1 * X1)))))).
-step(hard((X1 * (X1 * ((X1 + X2) * X3))) = (X1 * (X1 * ((X2 + X1) * X3))))).
-step(hard((X1 * ((X1 + X2) * (X1 * X3))) = (X1 * ((X2 + X1) * (X1 * X3))))).
-step(add(rule(250, ((X1 + ((X1 * X1) + X2)) * (X1 * X1)) = ((X1 + ((X2 + X1) * X1)) * X1)))).
-step(add(rule(251, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * ((X2 + X3) * (X3 * X3)))))).
-step(hard((X1 * ((X2 + X1) * X2)) = (X1 * ((X1 + X2) * X2)))).
-step(add(rule(252, ((X1 + (X2 + (X1 * X1))) * (X1 * X1)) = ((X1 + ((X2 + X1) * X1)) * X1)))).
-step(add(rule(253, (X1 * ((X1 + (X2 * (X1 * X2))) * (X1 * X2))) = ((X1 + X1) * X2)))).
-step(hard((X1 + ((X2 * (X1 * X1)) + X3)) = (X3 + ((X1 + X2) * (X1 * X1))))).
-step(hard(((X1 + ((X2 + X1) * X1)) * X1) = ((X1 + ((X1 + X2) * X1)) * X1))).
-step(hard(((X1 + (X2 + X3)) * (X1 * X1)) = ((X2 + (X1 + X3)) * (X1 * X1)))).
-step(hard(((X2 + ((X3 + X1) * X1)) * X1) = ((X2 + ((X1 + X3) * X1)) * X1))).
-step(hard((X1 + (X2 * (X1 * (X1 + X1)))) = ((X2 + (X1 + X2)) * (X1 * X1)))).
-step(add(rule(254, ((X1 * X2) + ((X1 + X3) * X4)) = ((X1 * (X4 + X2)) + (X3 * X4))))).
-step(add(rule(255, ((X1 * (X2 + X3)) + (X4 * X3)) = (((X1 + X4) * X3) + (X1 * X2))))).
-step(add(rule(256, ((X1 * (X2 + X3)) + (X4 * X3)) = ((X1 * X2) + ((X4 + X1) * X3))))).
-step(hard((X1 * ((X1 * (X1 + X2)) + X3)) = (X1 * (X3 + (X1 * (X2 + X1)))))).
-step(add(rule(257, ((X1 * X2) + (X3 * (X2 + X4))) = (((X3 + X1) * X2) + (X3 * X4))))).
-step(hard((((X1 + X2) * X3) + (X2 * X4)) = ((X2 * (X3 + X4)) + (X1 * X3)))).
-step(add(rule(258, (((X1 + X2) * X3) + (X2 * X4)) = ((X1 * X3) + (X2 * (X4 + X3)))))).
-step(hard((X1 + ((X2 * (X1 * X1)) + X3)) = (X3 + ((X2 + X1) * (X1 * X1))))).
-step(hard(((((X1 + X2) * X1) + X3) * X1) = ((((X2 + X1) * X1) + X3) * X1))).
-step(hard((X1 * (((X1 + X2) * X1) + X3)) = (X1 * (X3 + ((X2 + X1) * X1))))).
-step(add(rule(259, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * ((X3 + X2) * (X3 * X3)))))).
-step(add(rule(260, ((X2 + (X1 * -X1)) * X1) = (-X1 + (X2 * X1))))).
-step(add(rule(261, (X1 * (X2 + (X1 * -X1))) = (-X1 + (X1 * X2))))).
-step(add(rule(262, ((X1 + X1) * (X2 + (X2 + X2))) = ((X1 + (X1 + X1)) * (X2 + X2))))).
-step(interreduce).
-step(delete(rule(241, ((X1 * X2) + ((X3 + X1) * X4)) = ((X1 * (X4 + X2)) + (X3 * X4))))).
-step(delete(rule(255, ((X1 * (X2 + X3)) + (X4 * X3)) = (((X1 + X4) * X3) + (X1 * X2))))).
-step(add(rule(263, (X1 + (X1 + (X2 * (X1 + X1)))) = (((X1 * X1) + X2) * (X1 + X1))))).
-step(add(rule(264, (X2 + (X2 + (X1 * (X2 * (X2 + X2))))) = ((X1 + X2) * (X2 * (X2 + X2)))))).
-step(add(rule(265, (X3 + (X4 + (X2 + (-(X3 + X4) + X1)))) = (X1 + X2)))).
-step(hard((X1 + -X2) = (-(X2 + X3) + (X1 + X3)))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X1 + (X2 + (X4 + X3))))).
-step(hard((X3 + (X4 + (X5 + X2))) = (X3 + (X5 + (X4 + X2))))).
-step(hard((X1 + X2) = (X2 + (-(X4 + X5) + (X1 + (X4 + X5)))))).
-step(add(rule(266, (X3 + (X4 + (X1 + (X2 + -(X3 + X4))))) = (X1 + X2)))).
-step(add(rule(267, (X1 * (X2 + (X1 * (X1 + X1)))) = (X1 + (X1 + (X1 * X2)))))).
-step(add(rule(268, ((X2 + (X3 + (X1 * X1))) * X1) = (X1 + ((X2 + X3) * X1))))).
-step(add(rule(269, (X1 * (X2 + ((X1 * (X1 * -X2)) + X3))) = (X1 * X3)))).
-step(hard((X1 * X2) = (X1 * (-X3 + (X2 + X3))))).
-step(add(rule(270, (X1 * (X2 + (X3 + (X1 * (X1 * -X2))))) = (X1 * X3)))).
-step(hard((X1 + -X2) = (-X3 + (X1 + (X3 + -X2))))).
-step(add(rule(271, ((X1 + X1) * (X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(add(rule(272, ((X1 + X1) * ((X1 + X1) * (X2 + X2))) = (X1 * ((X1 + X1) * X2))))).
-step(hard((-X2 + X1) = (-(X2 + X3) + (X1 + X3)))).
-step(add(rule(273, (((X1 * (X1 + X1)) + X2) * X1) = (X1 + (X1 + (X2 * X1)))))).
-step(hard(((X1 + (X3 + (X3 + X1))) * X2) = ((X3 + X1) * (X2 + X2)))).
-step(add(rule(274, ((X1 + (X2 * (X2 + X2))) * X2) = (X2 + (X2 + (X1 * X2)))))).
-step(hard((X1 * (X2 + (X3 + (X3 + X2)))) = ((X1 + X1) * (X3 + X2)))).
-step(hard((X1 + (X2 + X3)) = (-X4 + (X2 + (X3 + (X4 + X1)))))).
-step(hard((X1 + (-X2 + (X3 + (X4 + X2)))) = (X1 + (X4 + X3)))).
-step(hard((X1 + (X2 + X3)) = (-X4 + (X1 + (X2 + (X3 + X4)))))).
-step(hard((X1 + (X2 + (-X3 + (X4 + X3)))) = (X1 + (X2 + X4)))).
-step(hard(((X1 * X2) + X3) = (((-X5 + (X1 + X5)) * X2) + X3))).
-step(hard((X1 + ((-X2 + (X4 + X2)) * X3)) = (X1 + (X4 * X3)))).
-step(hard((X1 + (X3 + (-X2 + (X4 + X2)))) = (X3 + (X4 + X1)))).
-step(hard((X1 * (X2 * X3)) = (X1 * (X2 * (-X4 + (X3 + X4)))))).
-step(hard((X1 * (X2 * X3)) = (X1 * ((-X4 + (X2 + X4)) * X3)))).
-step(add(rule(275, (X1 * (X2 + (X1 * (X1 + (X1 * -X2))))) = X1))).
-step(hard(((X1 * -(X2 + X2)) + ((X1 + X1) * X2)) = 0)).
-step(add(rule(276, (X1 * (X2 * (X1 * (X2 * (X1 * (X2 * X3)))))) = (X1 * (X2 * X3))))).
-step(interreduce).
-step(delete(rule(126, (X1 * ((X1 + X1) * ((X1 + X1) * (X2 + X2)))) = ((X1 + X1) * X2)))).
-step(delete(rule(243, (((X1 * (X1 + X1)) + (X2 + X2)) * X1) = (((X1 * X1) + X2) * (X1 + X1))))).
-step(add(rule(277, (X1 + (X1 + ((X2 + X2) * X1))) = (((X1 * X1) + X2) * (X1 + X1))))).
-step(add(rule(278, ((X1 * X2) + (X3 + ((X1 * X4) + X5))) = (X3 + ((X1 * (X2 + X4)) + X5))))).
-step(hard((X1 + (X2 * (X3 + (X4 + X5)))) = (X1 + (X2 * (X4 + (X5 + X3)))))).
-step(hard(((X1 * (X2 + (X3 + X4))) + X5) = ((X1 * (X3 + (X2 + X4))) + X5))).
-step(add(rule(279, ((X1 * (X2 + (X2 + X3))) + X4) = (((X1 + X1) * X2) + ((X1 * X3) + X4))))).
-step(add(rule(280, (X4 + ((X1 * (X2 + X2)) + X5)) = (((X1 + X1) * X2) + (X5 + X4))))).
-step(add(rule(281, ((X1 * (X2 + (X2 + X2))) + X3) = (((X1 + (X1 + X1)) * X2) + X3)))).
-step(add(rule(282, (-? + ((X2 * (X3 + X3)) + ?)) = ((X2 + X2) * X3)))).
-step(add(rule(283, (-X1 + ((X2 * (X3 + X3)) + X1)) = (-? + ((X2 * (X3 + X3)) + ?))))).
-step(add(rule(284, ((X1 * (X2 + (X3 + X3))) + X4) = ((X1 * X2) + (((X1 + X1) * X3) + X4))))).
-step(add(rule(285, (X1 + (((X1 + X1) * X2) + X3)) = (X1 + ((X1 * (X2 + X2)) + X3))))).
-step(add(rule(286, ((X1 * (X2 * X4)) + ((X3 * X4) + X5)) = ((((X1 * X2) + X3) * X4) + X5)))).
-step(add(rule(287, ((-X3 + ((X1 * X2) + X3)) * X4) = (X1 * (X2 * X4))))).
-step(add(rule(288, ((X1 * X3) + ((X2 * (X1 * X3)) + X4)) = (((X1 + (X2 * X1)) * X3) + X4)))).
-step(add(rule(289, (((X1 + (X1 * X2)) * X3) + X4) = ((X1 * (X3 + (X2 * X3))) + X4)))).
-step(add(rule(290, ((X1 + (X1 * X2)) * -X3) = (X1 * -(X3 + (X2 * X3)))))).
-step(hard(((X1 + X1) * X2) = ((-X3 + (X1 + (X1 + X3))) * X2))).
-step(add(rule(291, ((X1 * X4) + ((X2 * (X3 * X4)) + X5)) = (((X1 + (X2 * X3)) * X4) + X5)))).
-step(add(rule(292, ((X1 * X2) + (X3 + ((X4 * X2) + X5))) = (X3 + (((X1 + X4) * X2) + X5))))).
-step(hard((X1 + ((-X3 + (X2 + X3)) * X4)) = ((X2 * X4) + X1))).
-step(hard((X1 + ((X2 + (X3 + X5)) * X4)) = (X1 + ((X3 + (X5 + X2)) * X4)))).
-step(hard((((X1 + (X3 + X4)) * X2) + X5) = (((X3 + (X1 + X4)) * X2) + X5))).
-step(add(rule(293, (X1 + (((X2 + X2) * X3) + X4)) = (X1 + ((X2 * (X3 + X3)) + X4))))).
-step(add(rule(294, (((X1 + (X1 + X2)) * X3) + X4) = ((X1 * (X3 + X3)) + ((X2 * X3) + X4))))).
-step(hard(((-X2 + (X1 + (X1 + X2))) * X3) = (X1 * (X3 + X3)))).
-step(add(rule(295, (((X1 + (X2 + X2)) * X3) + X4) = ((X1 * X3) + ((X2 * (X3 + X3)) + X4))))).
-step(add(rule(296, ((X1 * (X2 * (X3 * X5))) + (X4 * X5)) = (((X1 * (X2 * X3)) + X4) * X5)))).
-step(add(rule(297, ((X1 * (X2 * X5)) + (X3 * (X4 * X5))) = (((X1 * X2) + (X3 * X4)) * X5)))).
-step(add(rule(298, ((X1 * X4) + (X2 * (X3 * (X1 * X4)))) = ((X1 + (X2 * (X3 * X1))) * X4)))).
-step(add(rule(299, ((X1 * (X2 * X3)) + (X4 + (X5 * X3))) = (X4 + (((X1 * X2) + X5) * X3))))).
-step(add(rule(300, ((X1 + ((X3 * X4) + X5)) * X2) = (((X3 * X4) + (X1 + X5)) * X2)))).
-step(hard(((X1 + (X2 + X3)) * X4) = ((X2 + (X1 + X3)) * X4))).
-step(add(rule(301, ((X2 * X4) + (X1 + (X3 * (X2 * X4)))) = (X1 + ((X2 + (X3 * X2)) * X4))))).
-step(hard(((((X1 + X4) * X2) + X5) * X3) = ((((X4 + X1) * X2) + X5) * X3))).
-step(add(rule(302, (X1 + ((X2 + (X2 * X3)) * X4)) = (X1 + (X2 * (X4 + (X3 * X4))))))).
-step(add(rule(303, ((((X1 + X1) * X2) + X3) * X4) = ((X1 * (X2 * (X4 + X4))) + (X3 * X4))))).
-step(add(rule(304, ((X1 + X1) * ((X2 + (X1 * X1)) * X3)) = (X1 * (((X3 * X3) + X2) * (X3 + X3)))))).
-step(add(rule(305, ((X1 + X1) * (-X2 + (X1 * (X1 * X2)))) = 0))).
-step(add(rule(306, (((X1 * (X2 + X2)) + X3) * X4) = ((X1 * (X2 * (X4 + X4))) + (X3 * X4))))).
-step(add(rule(307, (((X1 * X2) + (X3 + X3)) * X4) = ((X1 * (X2 * X4)) + (X3 * (X4 + X4)))))).
-step(interreduce).
-step(delete(rule(125, ((X1 * X3) + (X2 * (X1 * (X1 * X3)))) = ((X1 + X2) * (X1 * (X1 * X3)))))).
-step(delete(rule(282, (-? + ((X2 * (X3 + X3)) + ?)) = ((X2 + X2) * X3)))).
-step(delete(rule(283, (-X1 + ((X2 * (X3 + X3)) + X1)) = (-? + ((X2 * (X3 + X3)) + ?))))).
-step(add(rule(308, (-X1 + ((X2 * (X3 + X3)) + X1)) = (X2 * (X3 + X3))))).
-step(delete(rule(285, (X1 + (((X1 + X1) * X2) + X3)) = (X1 + ((X1 * (X2 + X2)) + X3))))).
-step(delete(rule(288, ((X1 * X3) + ((X2 * (X1 * X3)) + X4)) = (((X1 + (X2 * X1)) * X3) + X4)))).
-step(hard((X1 + X1) = (-X2 + (X1 + (X1 + X2))))).
-step(hard((X1 + X2) = (-X3 + (X1 + (X3 + X2))))).
-step(add(rule(309, ((X1 + X1) * ((X2 + (X1 * X1)) * X3)) = ((X1 + X1) * (X3 + (X2 * X3)))))).
-step(add(rule(310, (((X1 * X2) + X3) * (X2 * (X2 * X4))) = ((X1 + (X3 * X2)) * (X2 * X4))))).
-step(add(rule(311, ((X1 + (X1 * (X2 * -X2))) * (X2 * X3)) = 0))).
-step(add(rule(312, (X1 * (X2 + (X2 * (X1 * -X1)))) = 0))).
-step(hard((X1 * ((-X2 + (X1 + X2)) * X1)) = X1)).
-step(add(rule(313, ((X1 + (X1 * (X2 * -X2))) * (X2 + X2)) = 0))).
-step(add(rule(314, (X1 * ((X2 + (X1 * (X1 * -X2))) * X3)) = 0))).
-step(add(rule(315, ((X1 + (X2 * (X2 * -X1))) * X2) = 0))).
-step(add(rule(316, (X1 * ((X2 + (X2 * (X1 * -X1))) * X3)) = 0))).
-step(add(rule(317, (X1 * -(X2 + (X2 * (X1 * -X1)))) = 0))).
-step(add(rule(318, ((-X1 + (X2 * (X2 * X1))) * X2) = 0))).
-step(add(rule(319, ((X1 * X5) + (X2 * (X3 * (X4 * X5)))) = ((X1 + (X2 * (X3 * X4))) * X5)))).
-step(add(rule(320, ((X1 * X2) + (X3 + (X4 * (X5 * X2)))) = (X3 + ((X1 + (X4 * X5)) * X2))))).
-step(hard(((X4 + ((X5 + X1) * X2)) * X3) = ((X4 + ((X1 + X5) * X2)) * X3))).
-step(hard(((X2 + (X1 * (X3 + X1))) * X1) = ((X2 + (X1 * (X1 + X3))) * X1))).
-step(add(rule(321, ((X1 + (X1 + (X2 * X3))) * X4) = ((X1 * (X4 + X4)) + (X2 * (X3 * X4)))))).
-step(add(rule(322, ((X1 + ((X2 + X2) * X3)) * X4) = ((X1 * X4) + (X2 * (X3 * (X4 + X4))))))).
-step(add(rule(323, (X1 + (X2 * (X3 * (X1 + X1)))) = (X1 + ((X2 + X2) * (X3 * X1)))))).
-step(add(rule(324, ((X1 + (X2 * (X3 + X3))) * X4) = ((X1 * X4) + (X2 * (X3 * (X4 + X4))))))).
-step(add(rule(325, (X1 + (X2 * (X3 * (X1 + X1)))) = (X1 + (X2 * ((X3 + X3) * X1)))))).
-step(add(rule(326, ((X1 + (X2 * X3)) * (X3 * (X3 * X4))) = (((X1 * X3) + X2) * (X3 * X4))))).
-step(add(rule(327, (X1 * ((X2 * (X1 * (X2 * (X1 * X2)))) + X3)) = (X1 * (X2 + X3))))).
-step(add(rule(328, (X1 * (X2 + (X3 * (X1 * (X3 * (X1 * X3)))))) = (X1 * (X2 + X3))))).
-step(add(rule(329, (X1 + (X2 + (-(X1 + X3) + X4))) = (-X3 + (X2 + X4))))).
-step(hard(-(X3 + (X2 + X1)) = -(X1 + (X2 + X3)))).
-step(add(rule(330, (X4 + (X2 + (X3 + -(X4 + X1)))) = (-X1 + (X2 + X3))))).
-step(hard((-(X1 + X2) + (X3 + X1)) = (-X2 + X3))).
-step(add(rule(331, (X4 + (X3 + -(X1 + (X4 + X2)))) = (-(X1 + X2) + X3)))).
-step(hard((X1 + X2) = (-X4 + (X1 + (X4 + X2))))).
-step(add(rule(332, (X4 + (-(X2 + (X4 + X3)) + X1)) = (X1 + -(X2 + X3))))).
-step(hard((X1 + X2) = (-X4 + (X2 + (X4 + X1))))).
-step(add(rule(333, (X2 + ((X2 + X1) * (X2 * -X2))) = (X1 * (X2 * -X2))))).
-step(add(rule(334, (X2 + (X1 * (X2 * -X2))) = ((-X1 + X2) * (X2 * X2))))).
-step(add(rule(335, (X1 + (X1 * (X2 * -X1))) = (X1 * ((-X2 + X1) * X1))))).
-step(add(rule(336, (((X1 * (X2 * (X1 * X2))) + X3) * (X1 * X2)) = ((X1 + (X3 * X1)) * X2)))).
-step(add(rule(337, (X1 * (X2 + (X3 * X2))) = (X1 * (X1 * ((X1 + (X1 * X3)) * X2)))))).
-step(add(rule(338, (X1 * (((X1 * (X1 * X2)) + X3) * X4)) = (X1 * ((X2 + X3) * X4))))).
-step(add(rule(339, (X1 * (X2 + ((X1 * (X1 * X3)) + X4))) = (X1 * (X2 + (X3 + X4)))))).
-step(hard((X1 * (X2 + (X3 + X4))) = (X1 * (X3 + (X2 + X4))))).
-step(add(rule(340, (X1 * (X2 + (X2 + X3))) = (X1 * ((X1 * ((X1 + X1) * X2)) + X3))))).
-step(interreduce).
-step(delete(rule(172, ((X1 + (X1 * (X2 * -X2))) * (X2 * -X2)) = 0))).
-step(delete(rule(269, (X1 * (X2 + ((X1 * (X1 * -X2)) + X3))) = (X1 * X3)))).
-step(delete(rule(298, ((X1 * X4) + (X2 * (X3 * (X1 * X4)))) = ((X1 + (X2 * (X3 * X1))) * X4)))).
-step(delete(rule(301, ((X2 * X4) + (X1 + (X3 * (X2 * X4)))) = (X1 + ((X2 + (X3 * X2)) * X4))))).
-step(delete(rule(333, (X2 + ((X2 + X1) * (X2 * -X2))) = (X1 * (X2 * -X2))))).
-step(add(rule(341, (X1 * ((X1 * (X1 + (X1 * X2))) + X3)) = (X1 + (X1 * (X2 + X3)))))).
-step(add(rule(342, (X1 * (X2 + (X3 + (X1 * (X1 * X4))))) = (X1 * (X2 + (X3 + X4)))))).
-step(hard((X1 * (X2 + (X3 + X4))) = (X1 * (X2 + (X4 + X3))))).
-step(add(rule(343, (X1 * ((X2 + (X1 * (X1 * X3))) * X4)) = (X1 * ((X2 + X3) * X4))))).
-step(hard((X1 * ((X2 + X3) * X4)) = (X1 * ((X3 + X2) * X4)))).
-step(add(rule(344, (X1 * (X2 + (X3 + X3))) = (X1 * (X2 + (X1 * ((X1 + X1) * X3))))))).
-step(add(rule(345, (X1 * (X2 + (X1 * (X1 + (X1 * X3))))) = (X1 + (X1 * (X3 + X2)))))).
-step(add(rule(346, (-X1 + (X2 * (X1 * X1))) = ((-X1 + X2) * (X1 * X1))))).
-step(add(rule(347, (X1 * (X1 * (-X1 + X2))) = (X1 * (X1 * (X2 + -X1)))))).
-step(hard(((X1 * -(X2 + X3)) + X4) = ((X1 * -(X3 + X2)) + X4))).
-step(add(rule(348, ((X1 + (X1 * -X2)) * X3) = (X1 * (X3 + (X2 * -X3)))))).
-step(hard((X1 + (X2 * -(X3 + X4))) = (X1 + (X2 * -(X4 + X3))))).
-step(add(rule(349, (X1 * (X1 * ((X1 + (X1 * X2)) * X3))) = ((X1 + (X1 * X2)) * X3)))).
-step(add(rule(350, -(((X1 + X1) * X2) + X3) = -((X1 * (X2 + X2)) + X3)))).
-step(add(rule(351, -(X1 + ((X2 + X2) * X3)) = -(X1 + (X2 * (X3 + X3)))))).
-step(add(rule(352, -((X1 * (X2 + X2)) + X3) = -(X3 + ((X1 + X1) * X2))))).
-step(add(rule(353, -(((X1 + X1) * X2) + X3) = -(X3 + (X1 * (X2 + X2)))))).
-step(add(rule(354, (((X1 * (X2 * (X3 * X3))) + X4) * X3) = (((X1 * X2) + X4) * X3)))).
-step(add(rule(355, (((X1 * (X2 * (X2 + X2))) + X3) * X2) = ((X1 + (X1 + X3)) * X2)))).
-step(hard((X1 + (X3 + -(X4 + X2))) = (X3 + (X1 + -(X2 + X4))))).
-step(add(rule(356, ((X1 + (X2 * (X3 * X1))) * (X1 * X1)) = (X1 + (X2 * (X3 * X1)))))).
-step(add(rule(357, ((X1 + (X2 * (X3 * (X4 * X4)))) * X4) = ((X1 + (X2 * X3)) * X4)))).
-step(add(rule(358, ((X1 + (X2 * (X3 * (X3 + X3)))) * X3) = ((X1 + (X2 + X2)) * X3)))).
-step(hard(((-(X1 + X2) + X4) * X3) = ((-(X2 + X1) + X4) * X3))).
-step(add(rule(359, ((-X1 + X2) * (X1 * -X1)) = ((-X2 + X1) * (X1 * X1))))).
-step(add(rule(360, (-X1 + (X1 * (X2 * X1))) = (X1 * ((-X1 + X2) * X1))))).
-step(add(rule(361, (-X1 + (X2 * (X1 * -X1))) = ((X1 + X2) * (X1 * -X1))))).
-step(add(rule(362, (-X1 + (X1 * (X1 * X2))) = (X1 * (X1 * (-X1 + X2)))))).
-step(add(rule(363, (-X1 + (X1 * (X2 * -X1))) = (X1 * ((X1 + X2) * -X1))))).
-step(add(rule(364, ((X1 + X1) * ((X1 * (X1 * X2)) + X3)) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(365, ((X1 + X1) * (X2 + (X1 * (X1 * -X2)))) = 0))).
-step(add(rule(366, ((X1 + X1) * (X2 + (X1 * (X1 * X3)))) = ((X1 + X1) * (X2 + X3))))).
-step(add(rule(367, (X1 * ((X1 + X1) * -(X1 + X1))) = -(X1 + (X1 + (X1 + X1)))))).
-step(add(rule(368, ((X1 + X1) * (X1 + (X1 + X1))) = 0))).
-step(interreduce).
-step(delete(rule(270, (X1 * (X2 + (X3 + (X1 * (X1 * -X2))))) = (X1 * X3)))).
-step(delete(rule(275, (X1 * (X2 + (X1 * (X1 + (X1 * -X2))))) = X1))).
-step(delete(rule(305, ((X1 + X1) * (-X2 + (X1 * (X1 * X2)))) = 0))).
-step(delete(rule(314, (X1 * ((X2 + (X1 * (X1 * -X2))) * X3)) = 0))).
-step(delete(rule(337, (X1 * (X2 + (X3 * X2))) = (X1 * (X1 * ((X1 + (X1 * X3)) * X2)))))).
-step(delete(rule(365, ((X1 + X1) * (X2 + (X1 * (X1 * -X2)))) = 0))).
-step(add(rule(369, ((X1 + (X1 + X1)) * -(X1 + X1)) = 0))).
-step(add(rule(370, ((X1 + X1) * -(X1 + (X1 + X1))) = 0))).
-step(add(rule(371, ((X1 + (X1 + X1)) * ((X1 + X1) * -X2)) = 0))).
-step(add(rule(372, ((X1 + (X1 + X1)) * -(X1 + (X1 + (X1 + X1)))) = 0))).
-step(add(rule(373, ((X1 + X1) * ((X1 + (X1 + X1)) * -X2)) = 0))).
-step(add(rule(374, ((X1 + X1) * ((X1 + (X1 + X1)) * X2)) = 0))).
-step(add(rule(375, ((X1 + (X1 + (X1 + X1))) * (X1 + (X1 + X1))) = 0))).
-step(add(rule(376, ((X1 + (X1 + X1)) * ((X1 + X1) * X2)) = 0))).
-step(add(rule(377, ((X1 + (X1 + X1)) * (X1 + (X1 + (X1 + X1)))) = 0))).
-step(add(rule(378, ((X1 + X1) * (X2 * (X1 + (X1 + X1)))) = 0))).
-step(add(rule(379, ((X1 + (X1 + X1)) * (X2 * (X1 + X1))) = 0))).
-step(hard((X1 + (X2 + (-X3 + X4))) = (X4 + (-X3 + (X2 + X1))))).
-step(add(rule(380, (X1 + (-X2 + (X3 + X4))) = (X3 + (-X2 + (X1 + X4)))))).
-step(hard((X2 + -X1) = (-(X1 + X4) + (X2 + X4)))).
-step(hard((X1 + (X2 + (X3 + X4))) = (X3 + (X2 + (X1 + X4))))).
-step(hard((X1 + (-(X3 + X2) + X4)) = (X1 + (X4 + -(X2 + X3))))).
-step(hard((X2 + (-(X3 + X1) + X4)) = (X2 + (-(X1 + X3) + X4)))).
-step(hard((X1 + (X3 + -(X4 + X2))) = (X1 + (X3 + -(X2 + X4))))).
-step(hard((X1 + -(X4 + (X3 + X2))) = (X1 + -(X4 + (X2 + X3))))).
-step(add(rule(381, (-X1 + (X2 + (X1 * -X1))) = (X2 + -(X1 + (X1 * X1)))))).
-step(hard(-(((X1 + X2) * X3) + X4) = -(X4 + ((X2 + X1) * X3)))).
-step(add(rule(382, ((X1 + (X1 * X2)) * (X2 * -X2)) = (X1 * -(X2 + (X2 * X2)))))).
-step(add(rule(383, (((X1 * (X2 * X2)) + X3) * (X2 * X4)) = ((X1 + X3) * (X2 * X4))))).
-step(add(rule(384, ((X1 + (X2 * (X3 * X3))) * (X3 * X4)) = ((X1 + X2) * (X3 * X4))))).
-step(hard((-(X2 + (X3 + X1)) + X4) = (X4 + -(X2 + (X1 + X3))))).
-step(hard((-(X2 + (X3 + X1)) + X4) = (-(X3 + (X1 + X2)) + X4))).
-step(hard((-(X2 + (X3 + X1)) + X4) = (X4 + -(X3 + (X2 + X1))))).
-step(hard((X2 + -(X1 + (X3 + X4))) = (X2 + -(X4 + (X3 + X1))))).
-step(hard((X1 + (X3 + -(X4 + X2))) = (X3 + (-(X2 + X4) + X1)))).
-step(hard(-(X3 + (X4 + (X1 + X2))) = -(X4 + (X1 + (X3 + X2))))).
-step(hard(-(X3 + (X4 + (X1 + X2))) = -(X1 + (X4 + (X2 + X3))))).
-step(hard((X2 + -(X1 + (X3 + X4))) = (X2 + -(X3 + (X1 + X4))))).
-step(hard(-(X3 + (X4 + (X1 + X2))) = -(X3 + (X1 + (X4 + X2))))).
-step(hard(-(X3 + (X4 + (X1 + X2))) = -(X1 + (X3 + (X2 + X4))))).
-step(hard((X2 + -(X1 + (X3 + X4))) = (-(X3 + (X4 + X1)) + X2))).
-step(hard((X1 + (-(X3 + X5) + X4)) = (X4 + (-(X5 + X3) + X1)))).
-step(add(rule(385, (X1 * (X1 * (-X1 + ((X1 + X1) * X2)))) = (-X1 + (X1 * (X2 + X2)))))).
-step(add(rule(386, (X1 + (X1 * (X2 + ((X1 * -X1) + X3)))) = (X1 * (X3 + X2))))).
-step(add(rule(387, -(X1 + (X1 + (X1 + X1))) = (X1 + X1)))).
-step(add(rule(388, -(X1 + (X1 + X1)) = (X1 + (X1 + X1))))).
-step(add(rule(389, (X1 + (X1 + (X1 + X1))) = -(X1 + X1)))).
-step(add(rule(390, ((X1 + X1) * -(X2 + X2)) = (X1 * (X2 + X2))))).
-step(add(rule(391, ((X1 + X1) * (X2 + (X2 + X2))) = 0))).
-step(add(rule(392, ((X1 + (X1 + X1)) * (X2 + X2)) = 0))).
-step(add(rule(393, ((X1 + X1) * (X2 + X2)) = (X1 * -(X2 + X2))))).
-step(hard((X1 * (X2 + (-X3 + (X4 + X3)))) = (X1 * (X2 + X4)))).
-step(add(rule(394, (X1 + (X1 * (X2 + (X3 + (X1 * -X1))))) = (X1 * (X2 + X3))))).
-step(add(rule(395, (((X1 + X2) * X3) + (X4 + (X2 * -X3))) = ((X1 * X3) + X4)))).
-step(hard(((X1 * X2) + X3) = (X3 + ((-X4 + (X1 + X4)) * X2)))).
-step(hard(-(X1 + ((X2 + X3) * X4)) = -(X1 + ((X3 + X2) * X4)))).
-step(add(rule(396, ((X1 * -X3) + (((X1 + X2) * X3) + X4)) = (X4 + (X2 * X3))))).
-step(add(rule(397, (((X1 + X2) * X3) + (X4 + (X1 * -X3))) = ((X2 * X3) + X4)))).
-step(hard(((-X1 + (X3 + (X4 + X1))) * X2) = ((X3 + X4) * X2))).
-step(hard(((X1 + X3) * X2) = ((-X4 + (X1 + (X3 + X4))) * X2))).
-step(add(rule(398, ((X1 * (X2 + X2)) + (((X1 + X1) * -X2) + X3)) = X3))).
-step(hard(X1 = (-(X2 + X2) + (X1 + (X2 + X2))))).
-step(hard(((X1 * X2) + X3) = ((X1 * (-X4 + (X2 + X4))) + X3))).
-step(add(rule(399, ((X1 * (X2 + X2)) + (X3 + ((X1 + X1) * -X2))) = X3))).
-step(add(rule(400, (((X1 + X1) * X2) + ((X1 * -(X2 + X2)) + X3)) = X3))).
-step(hard(X1 = (-X2 + (X1 + X2)))).
-step(add(rule(401, (((X1 + X1) * X2) + (X3 + (X1 * -(X2 + X2)))) = X3))).
-step(add(rule(402, (X1 + ((X2 + ((X1 * -X1) + X3)) * X1)) = ((X3 + X2) * X1)))).
-step(hard(((X1 + (-X2 + (X3 + X2))) * X4) = ((X1 + X3) * X4))).
-step(add(rule(403, (X1 + ((X2 + (X3 + (X1 * -X1))) * X1)) = ((X2 + X3) * X1)))).
-step(interreduce).
-step(delete(rule(124, ((X1 + (X1 + (X1 + X1))) * X2) = (X1 * (X2 + (X2 + (X2 + X2))))))).
-step(delete(rule(176, (X1 * ((X1 + X1) * (X1 + X1))) = (X1 + (X1 + (X1 + X1)))))).
-step(delete(rule(188, ((X1 + (X1 + X1)) * -X2) = (X1 * -(X2 + (X2 + X2)))))).
-step(add(rule(404, ((X1 + (X1 + X1)) * -X2) = (X1 * (X2 + (X2 + X2)))))).
-step(delete(rule(192, ((X1 + X1) * (X2 + (X2 + (X2 + X2)))) = (X1 * (X2 + X2))))).
-step(delete(rule(262, ((X1 + X1) * (X2 + (X2 + X2))) = ((X1 + (X1 + X1)) * (X2 + X2))))).
-step(delete(rule(271, ((X1 + X1) * (X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(add(rule(405, (X1 * -(X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(delete(rule(272, ((X1 + X1) * ((X1 + X1) * (X2 + X2))) = (X1 * ((X1 + X1) * X2))))).
-step(delete(rule(367, (X1 * ((X1 + X1) * -(X1 + X1))) = -(X1 + (X1 + (X1 + X1)))))).
-step(delete(rule(368, ((X1 + X1) * (X1 + (X1 + X1))) = 0))).
-step(delete(rule(370, ((X1 + X1) * -(X1 + (X1 + X1))) = 0))).
-step(delete(rule(371, ((X1 + (X1 + X1)) * ((X1 + X1) * -X2)) = 0))).
-step(delete(rule(372, ((X1 + (X1 + X1)) * -(X1 + (X1 + (X1 + X1)))) = 0))).
-step(delete(rule(373, ((X1 + X1) * ((X1 + (X1 + X1)) * -X2)) = 0))).
-step(delete(rule(375, ((X1 + (X1 + (X1 + X1))) * (X1 + (X1 + X1))) = 0))).
-step(delete(rule(377, ((X1 + (X1 + X1)) * (X1 + (X1 + (X1 + X1)))) = 0))).
-step(delete(rule(387, -(X1 + (X1 + (X1 + X1))) = (X1 + X1)))).
-step(add(rule(406, ((X1 + (X1 + X1)) * -(X2 + X2)) = 0))).
-step(add(rule(407, ((X1 + (X1 + X1)) * (X1 * -(X1 + X1))) = 0))).
-step(add(rule(408, ((X1 + X1) * (X2 * (X3 + (X3 + X3)))) = 0))).
-step(add(rule(409, ((X1 + X1) * ((X2 + (X2 + X2)) * X3)) = 0))).
-step(add(rule(410, ((X1 + (X1 + X1)) * -X2) = ((X1 + (X1 + X1)) * X2)))).
-step(add(rule(411, ((X1 + (X1 + X1)) * (X2 * -(X3 + X3))) = 0))).
-step(add(rule(412, ((X1 + X1) * ((X1 + X1) * X2)) = (X1 * (X1 * -(X2 + X2)))))).
-step(add(rule(413, (X1 * ((X1 + X1) * ((X1 + X1) * X2))) = ((X1 + X1) * -X2)))).
-step(add(rule(414, (X1 + ((X2 + X2) * (X3 * X1))) = (X1 + (X2 * ((X3 + X3) * X1)))))).
-step(add(rule(415, (X1 + (X2 * ((X3 + X3) * X4))) = ((X2 * (X3 * (X4 + X4))) + X1)))).
-step(add(rule(416, (X1 + ((X2 + X2) * (X3 * X4))) = ((X2 * (X3 * (X4 + X4))) + X1)))).
-step(add(rule(417, (X1 + ((X2 + X2) * (X3 * X4))) = ((X2 * ((X3 + X3) * X4)) + X1)))).
-step(add(rule(418, (X1 + (X2 + ((X3 + X3) * X4))) = (X1 + ((X3 * (X4 + X4)) + X2))))).
-step(add(rule(419, ((X1 + (X1 + X1)) * ((X2 + X2) * X3)) = 0))).
-step(add(rule(420, ((X1 + (X1 + X1)) * (X2 * (X3 + X3))) = 0))).
-step(add(rule(421, ((X1 * (X2 + X2)) + (X3 + X4)) = (X3 + (X4 + ((X1 + X1) * X2)))))).
-step(add(rule(422, (X1 * (X2 * -(X3 + X3))) = ((X1 + X1) * ((X2 + X2) * X3))))).
-step(add(rule(423, (X1 + (X1 + (X2 + (X1 + X1)))) = (X2 + -(X1 + X1))))).
-step(add(rule(424, ((X1 + X1) * (X2 * (X3 + X3))) = (X1 * (X2 * -(X3 + X3)))))).
-step(add(rule(425, (X1 + (X1 + (X1 + (X1 + X2)))) = (-(X1 + X1) + X2)))).
-step(add(rule(426, (X1 * ((X2 * (X3 + X3)) + X4)) = (X1 * (X4 + ((X2 + X2) * X3)))))).
-step(add(rule(427, (X1 + (X2 + ((X3 + X3) * X4))) = (X2 + ((X3 * (X4 + X4)) + X1))))).
-step(add(rule(428, (X1 + (X2 + (X3 * (X4 + X4)))) = (X1 + (X2 + ((X3 + X3) * X4)))))).
-step(add(rule(429, (X1 + (X2 * (X3 * (X4 + X4)))) = (X1 + (X2 * ((X3 + X3) * X4)))))).
-step(add(rule(430, (X1 + (X2 * (X3 * (X4 + X4)))) = (X1 + ((X2 + X2) * (X3 * X4)))))).
-step(hard((X1 + (X2 + ((X3 + X3) * X4))) = (X2 + (X1 + (X3 * (X4 + X4)))))).
-step(add(rule(431, (X1 + ((X2 + X2) * (X3 * X4))) = (X1 + (X2 * ((X3 + X3) * X4)))))).
-step(add(rule(432, (X1 + (X2 * (X3 * (X4 + X4)))) = ((X2 * ((X3 + X3) * X4)) + X1)))).
-step(add(rule(433, (X1 + (X2 * (X3 * (X4 + X4)))) = (((X2 + X2) * (X3 * X4)) + X1)))).
-step(add(rule(434, (X1 + (X2 + (X3 * (X4 + X4)))) = (X1 + (((X3 + X3) * X4) + X2))))).
-step(add(rule(435, (((X1 + X1) * X2) + (X3 + X4)) = (X3 + (X4 + (X1 * (X2 + X2))))))).
-step(add(rule(436, (X1 * (((X2 + X2) * X3) + X4)) = (X1 * (X4 + (X2 * (X3 + X3))))))).
-step(add(rule(437, (((X1 + X1) * (X2 * X3)) + X4) = (X4 + (X1 * ((X2 + X2) * X3)))))).
-step(add(rule(438, (X1 + (X2 + (X3 * (X4 + X4)))) = (X2 + (((X3 + X3) * X4) + X1))))).
-step(add(rule(439, ((X1 * (X2 + X2)) + ((-X1 + X3) * X2)) = ((X1 + X3) * X2)))).
-step(hard(((X1 + (X4 + X2)) * X3) = ((X1 + (X2 + X4)) * X3))).
-step(hard(((X1 + X2) * X3) = ((-X1 + (X2 + (X1 + X1))) * X3))).
-step(add(rule(440, ((X1 * (X2 + X2)) + ((X3 + -X1) * X2)) = ((X1 + X3) * X2)))).
-step(add(rule(441, ((X1 + (X1 + X1)) * (X2 * X3)) = (X1 * ((X2 + (X2 + X2)) * X3))))).
-step(add(rule(442, ((X1 + (X1 * (X2 * (X2 + X2)))) * (X2 + X2)) = 0))).
-step(add(rule(443, (X1 * (X2 * (X3 + (X3 + X3)))) = (X1 * ((X2 + (X2 + X2)) * X3))))).
-step(add(rule(444, (X1 * (X2 * (X3 + (X3 + X3)))) = ((X1 + (X1 + X1)) * (X2 * X3))))).
-step(add(rule(445, ((X1 + X1) * (X2 + (X1 * ((X1 + X1) * X2)))) = 0))).
-step(add(rule(446, (((X1 + X1) * X2) + (X1 * (-X2 + X3))) = (X1 * (X2 + X3))))).
-step(hard((X1 * (X2 * (X4 + X3))) = (X1 * (X2 * (X3 + X4))))).
-step(hard((X1 * (X2 + X3)) = (X1 * (-X2 + (X3 + (X2 + X2)))))).
-step(add(rule(447, (X1 * (X2 + (X2 * (X1 * X1)))) = ((X1 + X1) * X2)))).
-step(hard((X1 * (X1 * -(X1 + X2))) = (X1 * (X1 * -(X2 + X1))))).
-step(add(rule(448, (((X1 + X1) * X2) + (X1 * (X3 + -X2))) = (X1 * (X2 + X3))))).
-step(add(rule(449, ((X1 + ((X2 + X2) * X3)) * X4) = (((X2 * (X3 + X3)) + X1) * X4)))).
-step(add(rule(450, ((X1 + (X2 * (X3 + X3))) * X4) = ((((X2 + X2) * X3) + X1) * X4)))).
-step(hard((X1 * (X3 + (X1 * (-X2 + (X1 + X2))))) = (X1 + (X1 * X3)))).
-step(add(rule(451, ((X1 + (((X1 * -X2) + X3) * X2)) * X2) = (X3 * (X2 * X2))))).
-step(hard((X1 * X2) = ((-X2 + (X1 + X2)) * X2))).
-step(add(rule(452, (X1 * (X1 * (X2 * X1))) = (X2 * X1)))).
-step(add(rule(453, ((X1 + ((X2 + (X1 * -X3)) * X3)) * X3) = (X2 * (X3 * X3))))).
-step(add(rule(454, (X1 * (X1 * (X1 + (X2 * X1)))) = (X1 + (X2 * X1))))).
-step(hard((((X1 * (X1 + X2)) + X3) * X1) = (((X1 * (X2 + X1)) + X3) * X1))).
-step(hard((X1 + (X2 * ((X3 + X4) * X1))) = (X1 + (X2 * ((X4 + X3) * X1))))).
-step(add(rule(455, (X1 * (X3 + (X1 * (X2 * X3)))) = (X1 * (X1 * ((X2 + X1) * X3)))))).
-step(hard((X1 + (X2 + (X3 + (X4 + X5)))) = (X3 + (X5 + (X4 + (X1 + X2)))))).
-step(interreduce).
-step(delete(rule(112, ((X1 + (X1 + X1)) * (X1 * X1)) = (X1 + (X1 + X1))))).
-step(delete(rule(209, (X1 + (X1 * ((X2 + X2) * X3))) = (X1 + (X1 * (X2 * (X3 + X3))))))).
-step(delete(rule(323, (X1 + (X2 * (X3 * (X1 + X1)))) = (X1 + ((X2 + X2) * (X3 * X1)))))).
-step(delete(rule(325, (X1 + (X2 * (X3 * (X1 + X1)))) = (X1 + (X2 * ((X3 + X3) * X1)))))).
-step(delete(rule(369, ((X1 + (X1 + X1)) * -(X1 + X1)) = 0))).
-step(delete(rule(374, ((X1 + X1) * ((X1 + (X1 + X1)) * X2)) = 0))).
-step(delete(rule(376, ((X1 + (X1 + X1)) * ((X1 + X1) * X2)) = 0))).
-step(delete(rule(378, ((X1 + X1) * (X2 * (X1 + (X1 + X1)))) = 0))).
-step(delete(rule(379, ((X1 + (X1 + X1)) * (X2 * (X1 + X1))) = 0))).
-step(delete(rule(404, ((X1 + (X1 + X1)) * -X2) = (X1 * (X2 + (X2 + X2)))))).
-step(delete(rule(405, (X1 * -(X2 + X2)) = (X1 * ((X1 + X1) * ((X1 + X1) * X2)))))).
-step(delete(rule(406, ((X1 + (X1 + X1)) * -(X2 + X2)) = 0))).
-step(delete(rule(407, ((X1 + (X1 + X1)) * (X1 * -(X1 + X1))) = 0))).
-step(delete(rule(412, ((X1 + X1) * ((X1 + X1) * X2)) = (X1 * (X1 * -(X2 + X2)))))).
-step(delete(rule(414, (X1 + ((X2 + X2) * (X3 * X1))) = (X1 + (X2 * ((X3 + X3) * X1)))))).
-step(delete(rule(423, (X1 + (X1 + (X2 + (X1 + X1)))) = (X2 + -(X1 + X1))))).
-step(hard((X1 + (X2 + (X3 + (X4 + X5)))) = (X2 + (X4 + (X5 + (X3 + X1)))))).
-step(hard((X1 + (X2 + (X3 + (X4 + X5)))) = (X3 + (X4 + (X5 + (X1 + X2)))))).
-step(add(rule(456, ((X1 + X1) * ((X1 * (X1 + X1)) + X2)) = (-(X1 + X1) + ((X1 + X1) * X2))))).
-step(hard((X1 * ((X2 + (X4 + X4)) * X3)) = (X1 * ((X4 + (X4 + X2)) * X3)))).
-step(hard((X1 * (X2 * (X3 + (X4 + X4)))) = (X1 * (X2 * (X4 + (X4 + X3)))))).
-step(hard((X1 + ((X1 * (X1 * X2)) + X3)) = (X3 + (X1 * (X1 * (X1 + X2)))))).
-step(add(rule(457, ((X1 * (X1 * (X2 + X1))) + X3) = (X1 + ((X1 * (X1 * X2)) + X3))))).
-step(add(rule(458, (X1 + (X2 + (X1 * (X1 * X3)))) = (X2 + (X1 * (X1 * (X3 + X1))))))).
-step(hard((X1 + -(X2 + X2)) = (X2 + (X1 + (X2 + (X2 + X2)))))).
-step(hard((X1 + (X1 + (X2 + (X1 + X1)))) = (X2 + -(X1 + X1)))).
-step(hard((X1 * (X2 + (X1 * (X3 + X1)))) = (X1 * (X2 + (X1 * (X1 + X3)))))).
-step(hard((X1 + (X1 * ((X1 + X1) * X2))) = (X1 * (X1 * (X2 + (X1 + X2)))))).
-step(hard((X1 + ((X1 * (X2 * X1)) + X3)) = (X3 + (X1 * ((X1 + X2) * X1))))).
-step(add(rule(459, ((X1 * ((X2 + X1) * X1)) + X3) = (X1 + ((X1 * (X2 * X1)) + X3))))).
-step(add(rule(460, (X1 + (X2 + (X1 * (X3 * X1)))) = (X2 + (X1 * ((X3 + X1) * X1)))))).
-step(hard((X1 * (X2 + ((X3 + X1) * X1))) = (X1 * (X2 + ((X1 + X3) * X1))))).
-step(hard((X1 + (X1 * (X2 * (X1 + X1)))) = (X1 * ((X2 + (X1 + X2)) * X1)))).
-step(add(rule(461, ((X4 * -X2) + (X3 + ((X1 + X4) * X2))) = ((X1 * X2) + X3)))).
-step(hard(((X1 + X3) * X2) = ((-X4 + (X3 + (X1 + X4))) * X2))).
-step(hard(((X1 * X2) + X3) = (-X2 + (X3 + (X2 + (X1 * X2)))))).
-step(hard(((X1 * X2) + X3) = (X3 + ((-X5 + (X1 + X5)) * X2)))).
-step(hard(((-X3 + X1) * X2) = ((-(X4 + X3) + (X1 + X4)) * X2))).
-step(add(rule(462, (X1 + (((X2 + X2) * X3) + X4)) = ((X2 * (X3 + X3)) + (X4 + X1))))).
-step(hard(((X1 * (X2 + X2)) + (X3 + X4)) = (((X1 + X1) * X2) + (X4 + X3)))).
-step(hard((((X1 * (X1 + X2)) + X3) * X1) = ((X3 + (X1 * (X2 + X1))) * X1))).
-step(hard(((((X1 + X2) * X1) + X3) * X1) = ((X3 + ((X2 + X1) * X1)) * X1))).
-step(add(rule(463, (X1 * (X1 * ((X1 + X2) * X2))) = (X1 * ((X2 + X1) * (X2 * X2)))))).
-step(add(rule(464, (X1 * ((X1 + X2) * (X1 * X2))) = (X1 * (X2 * ((X2 + X1) * X2)))))).
-step(add(rule(465, ((X1 + (X1 * (X2 * X3))) * X2) = (X1 * (X2 * ((X2 + X3) * X2)))))).
-step(add(rule(466, (X1 * (X2 + (X3 * (X2 + X2)))) = (X1 * ((X3 + (X1 * (X1 + (X1 * X3)))) * X2))))).
-step(add(rule(467, (X1 * ((X2 + X1) * (X1 * X2))) = (X1 * (X2 * ((X2 + X1) * X2)))))).
-step(hard(((X1 * X2) + ((X3 + X1) * X4)) = ((X1 * (X4 + X2)) + (X3 * X4)))).
-step(add(rule(468, ((X1 * X2) + ((X3 + X1) * X4)) = ((X3 * X4) + (X1 * (X2 + X4)))))).
-step(interreduce).
-step(delete(rule(235, ((X1 * X2) + (X3 * (X4 + X2))) = (((X3 + X1) * X2) + (X3 * X4))))).
-step(delete(rule(256, ((X1 * (X2 + X3)) + (X4 * X3)) = ((X1 * X2) + ((X4 + X1) * X3))))).
-step(delete(rule(258, (((X1 + X2) * X3) + (X2 * X4)) = ((X1 * X3) + (X2 * (X4 + X3)))))).
-step(hard((X1 * ((X2 + (X4 + X4)) * X3)) = (X1 * ((X4 + (X2 + X4)) * X3)))).
-step(add(rule(469, ((X1 * X2) + (X3 * (X2 + X4))) = ((X3 * X4) + ((X3 + X1) * X2))))).
-step(hard((X1 * (X1 * (X2 + (X3 + X1)))) = (X1 * (X1 * (X1 + (X2 + X3)))))).
-step(add(rule(470, (X1 * (X2 + (X1 * (X2 + X1)))) = (X1 + ((X1 + (X1 * X1)) * X2))))).
-step(hard((X1 * ((X2 + (X3 + X1)) * X1)) = (X1 * ((X1 + (X2 + X3)) * X1)))).
-step(add(rule(471, ((X1 + (X2 + (X2 * X2))) * (X2 * X2)) = ((X2 + ((X1 + X2) * X2)) * X2)))).
-step(hard(((X1 * X2) + (X3 * (X4 + X2))) = (((X3 + X1) * X2) + (X3 * X4)))).
-step(hard((X1 * (X2 * (X3 + (X4 + X4)))) = (X1 * (X2 * (X4 + (X3 + X4)))))).
-step(hard(((X1 * (X2 * X2)) + (X3 + X2)) = (X3 + ((X1 + X2) * (X2 * X2))))).
-step(hard(((X1 * (X2 * X1)) + (X3 + X1)) = (X3 + (X1 * ((X1 + X2) * X1))))).
-step(add(rule(472, ((X1 + ((X1 + X2) * X2)) * X2) = (X2 + (X1 * (X2 + (X2 * X2))))))).
-step(add(rule(473, (X1 * (X1 * (X1 + (X1 + (X1 * (X2 + X2)))))) = ((X1 + X1) * (X2 + (X1 * X1)))))).
-step(add(rule(474, (X1 * ((X1 + X2) * (X2 * X2))) = (X1 * (X1 * ((X1 + X2) * X2)))))).
-step(hard(((X1 * (X2 + X3)) + (X4 * X2)) = (((X1 + X4) * X2) + (X1 * X3)))).
-step(add(rule(475, ((X1 + (X1 * X1)) * (X1 + X1)) = ((X1 + X1) * (X1 + (X1 * X1)))))).
-step(hard((X1 + (X2 + (X3 + (X4 + X5)))) = (X2 + (X3 + (X5 + (X4 + X1)))))).
-step(hard((X3 + (X4 + (X5 + (X6 + X2)))) = (X3 + (X5 + (X6 + (X4 + X2)))))).
-step(hard((X3 + (X4 + (X5 + (X6 + X2)))) = (X3 + (X4 + (X6 + (X5 + X2)))))).
-step(add(rule(476, (X1 + (X1 + ((X2 + X2) * X1))) = ((X2 + (X1 * X1)) * (X1 + X1))))).
-step(hard(((X1 * (X2 + (X2 + X3))) + X4) = ((X1 * (X3 + (X2 + X2))) + X4))).
-step(hard((((X1 + X1) * X2) + (X3 + X4)) = (X4 + (X3 + (X1 * (X2 + X2)))))).
-step(add(rule(477, (X1 + ((X2 * (X3 + X3)) + X4)) = (X4 + (((X2 + X2) * X3) + X1))))).
-step(add(rule(478, (((X1 + (X1 + X1)) * X2) + X3) = (X3 + (X1 * (X2 + (X2 + X2))))))).
-step(add(rule(479, (X1 + (X2 * (X3 + (X3 + X3)))) = (X1 + ((X2 + (X2 + X2)) * X3))))).
-step(add(rule(480, ((X1 * (X2 + (X2 + X2))) + X3) = (X3 + ((X1 + (X1 + X1)) * X2))))).
-step(hard(((X1 * (X2 + (X3 + X3))) + X4) = (X4 + (X1 * (X3 + (X3 + X2)))))).
-step(add(rule(481, ((X1 * (X2 + (X3 * X2))) + X4) = (X4 + ((X1 + (X1 * X3)) * X2))))).
-step(add(rule(482, (((X1 + (X1 * X2)) * X3) + X4) = (X4 + (X1 * (X3 + (X2 * X3))))))).
-step(hard((((X1 + (X1 + X2)) * X3) + X4) = (((X2 + (X1 + X1)) * X3) + X4))).
-step(hard((((X1 + (X2 + X2)) * X3) + X4) = (X4 + ((X2 + (X2 + X1)) * X3)))).
-step(hard(((X3 + (X1 * (X4 + X2))) * X5) = ((X3 + (X1 * (X2 + X4))) * X5))).
-step(hard(((X1 + (X1 + (X2 + X3))) * X4) = ((X2 + (X1 + (X1 + X3))) * X4))).
-step(hard((((X1 * (X2 + X3)) + X4) * X5) = (((X1 * (X3 + X2)) + X4) * X5))).
-step(add(rule(483, ((X1 + (X1 * X2)) * (X1 + X1)) = ((X1 + X1) * (X1 + (X2 * X1)))))).
-step(add(rule(484, ((X1 + (X1 * X3)) * (X2 + X2)) = ((X1 + X1) * (X2 + (X3 * X2)))))).
-step(add(rule(485, ((X1 + (X2 + (X3 * X3))) * (X3 * X3)) = ((X3 + ((X1 + X2) * X3)) * X3)))).
-step(hard((X1 + (X1 * (X3 + (X4 + X2)))) = (X1 + (X1 * (X4 + (X3 + X2)))))).
-step(hard((X1 * (X1 * (X3 + (X1 + X2)))) = (X1 * (X1 * (X1 + (X3 + X2)))))).
-step(hard((X1 * ((X3 + (X1 + X2)) * X1)) = (X1 * ((X1 + (X3 + X2)) * X1)))).
-step(hard((X1 * (((X2 + X1) * X1) + X3)) = (X1 * (((X1 + X2) * X1) + X3)))).
-step(hard((X1 + (X4 + ((X5 + X2) * X3))) = (X4 + (((X2 + X5) * X3) + X1)))).
-step(add(rule(486, ((X1 + X1) * (X2 + (X1 * (X1 + (X1 + X1))))) = ((X1 + X1) * X2)))).
-step(interreduce).
-step(delete(rule(252, ((X1 + (X2 + (X1 * X1))) * (X1 * X1)) = ((X1 + ((X2 + X1) * X1)) * X1)))).
-step(delete(rule(313, ((X1 + (X1 * (X2 * -X2))) * (X2 + X2)) = 0))).
-step(delete(rule(442, ((X1 + (X1 * (X2 * (X2 + X2)))) * (X2 + X2)) = 0))).
-step(delete(rule(471, ((X1 + (X2 + (X2 * X2))) * (X2 * X2)) = ((X2 + ((X1 + X2) * X2)) * X2)))).
-step(delete(rule(475, ((X1 + (X1 * X1)) * (X1 + X1)) = ((X1 + X1) * (X1 + (X1 * X1)))))).
-step(delete(rule(483, ((X1 + (X1 * X2)) * (X1 + X1)) = ((X1 + X1) * (X1 + (X2 * X1)))))).
-step(add(rule(487, ((X1 + X1) * (X2 * (X3 * (X1 + (X1 + X1))))) = 0))).
-step(add(rule(488, (((X1 * (X1 + (X1 + X1))) + X2) * (X1 + X1)) = (X2 * (X1 + X1))))).
-step(add(rule(489, ((X2 + (X1 * (X1 + (X1 + X1)))) * (X1 + X1)) = (X2 * (X1 + X1))))).
-step(add(rule(490, (X1 * (X2 + X2)) = ((X1 + X1) * (? + (? + (? + X2))))))).
-step(add(rule(491, ((X1 + X1) * (X3 + (X3 + (X3 + X2)))) = ((X1 + X1) * (? + (? + (? + X2))))))).
-step(add(rule(492, (X1 * (X2 + X2)) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(add(rule(493, ((X1 + X1) * (X2 + (X3 + (X3 + X3)))) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(hard((X2 + ((X1 + (X3 + X4)) * X2)) = (X2 + ((X4 + (X3 + X1)) * X2)))).
-step(hard(((X2 + X3) * ((X1 + X1) * X4)) = ((X3 + X2) * (X1 * (X4 + X4))))).
-step(hard((X1 * ((X3 + X4) * (X2 + X2))) = ((X1 + X1) * ((X4 + X3) * X2)))).
-step(hard((X3 + ((X4 + (X5 + X2)) * X3)) = (X3 + ((X5 + (X4 + X2)) * X3)))).
-step(add(rule(494, ((X1 + ((X1 * (X2 * -X2)) + X3)) * X2) = (X3 * X2)))).
-step(add(rule(495, ((X1 + (-X2 + X3)) * X4) = ((X3 + (-X2 + X1)) * X4)))).
-step(hard(((X1 + (X2 + X3)) * X4) = ((X3 + (X2 + X1)) * X4))).
-step(add(rule(496, ((X1 + (X2 + (X1 * (X3 * -X3)))) * X3) = (X2 * X3)))).
-step(hard((X1 + X2) = (-X4 + (X2 + (X1 + X4))))).
-step(hard((X1 + X2) = (-X5 + (X2 + (X1 + X5))))).
-step(add(rule(497, ((X1 + (X2 + (X2 * (X3 * -X3)))) * X3) = (X1 * X3)))).
-step(add(rule(498, (X1 * (X2 + (X2 * (X1 * (X2 * (X1 * -X2)))))) = 0))).
-step(add(rule(499, (X1 * (X2 + ((X2 * (X1 * -X1)) + X3))) = (X1 * X3)))).
-step(add(rule(500, (X1 * (X2 * (X1 * X1))) = (X1 * X2)))).
-step(add(rule(501, (X1 * (X2 * (X1 * -X1))) = (X1 * -X2)))).
-step(add(rule(502, (X1 * ((X2 * (X1 * X1)) + X3)) = (X1 * (X2 + X3))))).
-step(add(rule(503, (X1 * (X2 + (X3 * (X1 * X1)))) = (X1 * (X3 + X2))))).
-step(add(rule(504, (X1 * (X2 * (X1 * (X1 + X1)))) = (X1 * (X2 + X2))))).
-step(add(rule(505, (X1 * (X2 * (X1 * X2))) = (X1 * (X2 * (X2 * X1)))))).
-step(add(rule(506, (X1 * (X2 + (-X3 + X4))) = (X1 * (X4 + (-X3 + X2)))))).
-step(hard((X1 * (X2 + (X3 + X4))) = (X1 * (X4 + (X3 + X2))))).
-step(add(rule(507, (X1 * (X2 + (X3 + (X3 * (X1 * -X1))))) = (X1 * X2)))).
-step(add(rule(508, (X1 * (-X2 + (X1 * ((X1 + X1) * X2)))) = (X1 * X2)))).
-step(add(rule(509, ((-X1 + (X1 * (X2 * (X2 + X2)))) * X2) = (X1 * X2)))).
-step(add(rule(510, ((X5 * -X4) + (X2 + (X1 + (X5 * X4)))) = (X1 + X2)))).
-step(hard((X1 + X2) = (-(X3 + X3) + (X2 + (X1 + (X3 + X3)))))).
-step(add(rule(511, (X1 * (X1 * (X2 * -X1))) = (X2 * -X1)))).
-step(add(rule(512, (X3 * (X2 * X2)) = (X2 * (X2 * X3))))).
-step(hard(((X1 + X1) * (X2 + X1)) = (X1 * (X1 + (X2 + (X1 + X2)))))).
-step(hard((-X1 + (X2 + (X1 + (X3 + X4)))) = (X3 + (X4 + X2)))).
-step(hard((X1 + (X2 * X3)) = (X1 + ((-X4 + (X2 + X4)) * X3)))).
-step(hard((-X1 + (X2 + (X3 + (X1 + X4)))) = (X3 + (X2 + X4)))).
-step(hard((-X1 + (X2 + (X3 + (X4 + X1)))) = (X4 + (X2 + X3)))).
-step(hard((X1 + (X2 + X3)) = (-X4 + (X2 + (X1 + (X4 + X3)))))).
-step(hard((-X1 + (X4 + (X1 + (X3 + X5)))) = (X4 + (X5 + X3)))).
-step(hard((X1 + (X2 + X3)) = (-X4 + (X2 + (X3 + (X1 + X4)))))).
-step(hard((X1 + (X2 + (-X5 + (X4 + X5)))) = (X4 + (X1 + X2)))).
-step(hard(((X1 * X2) + X3) = (X3 + ((-X6 + (X1 + X6)) * X2)))).
-step(hard(((X1 + X3) * X2) = ((-X4 + (X3 + (X4 + X1))) * X2))).
-step(add(rule(513, (-? + (((X2 + X2) * X3) + ?)) = (X2 * (X3 + X3))))).
-step(add(rule(514, (-X1 + (((X2 + X2) * X3) + X1)) = (-? + (((X2 + X2) * X3) + ?))))).
-step(add(rule(515, ((X1 + (X2 * (X2 * X1))) * X2) = (X1 * (X2 + X2))))).
-step(hard((X1 * (X2 * X2)) = (X2 * (X2 * (-X2 + (X1 + X2)))))).
-step(add(rule(516, ((X2 + (X1 * (X1 + (X1 * -X2)))) * X1) = X1))).
-step(interreduce).
-step(delete(rule(30, (X1 + (X2 * (X1 * X1))) = ((X1 + X2) * (X1 * X1))))).
-step(add(rule(517, (X1 + (X2 * (X1 * X1))) = (X1 * (X1 * (X1 + X2)))))).
-step(delete(rule(108, ((X1 + (X2 * X3)) * (X3 * X3)) = (((X1 * X3) + X2) * X3)))).
-step(add(rule(518, (X3 * (X3 * (X1 + (X2 * X3)))) = (((X1 * X3) + X2) * X3)))).
-step(delete(rule(127, (((X1 + X2) * (X1 * X1)) + X3) = (X1 + ((X2 * (X1 * X1)) + X3))))).
-step(delete(rule(128, (X1 + (X2 * (X3 * (X1 * X1)))) = ((X1 + (X2 * X3)) * (X1 * X1))))).
-step(add(rule(519, (X1 + (X2 * (X3 * (X1 * X1)))) = (X1 * (X1 * (X1 + (X2 * X3))))))).
-step(delete(rule(129, (X1 + (X2 + (X3 * (X1 * X1)))) = (X2 + ((X1 + X3) * (X1 * X1)))))).
-step(delete(rule(131, ((X1 + (X2 + X2)) * (X1 * X1)) = (X1 + (X2 * (X1 * (X1 + X1))))))).
-step(add(rule(520, (X1 * (X1 * (X1 + (X2 + X2)))) = (X1 + (X2 * (X1 * (X1 + X1))))))).
-step(delete(rule(136, (((X1 + X2) * (X2 * X2)) + X3) = (X2 + ((X1 * (X2 * X2)) + X3))))).
-step(delete(rule(138, (((X1 * X2) + X3) * (X2 * X2)) = ((X1 + (X3 * X2)) * X2)))).
-step(add(rule(521, (X2 * (X2 * ((X1 * X2) + X3))) = ((X1 + (X3 * X2)) * X2)))).
-step(delete(rule(238, (X1 + ((X2 + X3) * (X3 * X3))) = (X3 + (X1 + (X2 * (X3 * X3))))))).
-step(add(rule(522, (X1 + (X3 * (X3 * (X2 + X3)))) = (X3 + (X1 + (X2 * (X3 * X3))))))).
-step(delete(rule(239, (X1 + ((X2 + X3) * (X2 * X2))) = ((X3 * (X2 * X2)) + (X1 + X2))))).
-step(add(rule(523, (X1 + (X2 * (X2 * (X2 + X3)))) = ((X3 * (X2 * X2)) + (X1 + X2))))).
-step(delete(rule(250, ((X1 + ((X1 * X1) + X2)) * (X1 * X1)) = ((X1 + ((X2 + X1) * X1)) * X1)))).
-step(add(rule(524, ((X1 + ((X2 + X1) * X1)) * X1) = (X1 * (X1 + (X1 * (X2 + X1))))))).
-step(delete(rule(251, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * ((X2 + X3) * (X3 * X3)))))).
-step(add(rule(525, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * (X3 * (X3 * (X2 + X3))))))).
-step(delete(rule(259, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * ((X3 + X2) * (X3 * X3)))))).
-step(add(rule(526, ((X1 + (X1 * (X2 * X3))) * X3) = (X1 * (X3 * (X3 * (X3 + X2))))))).
-step(delete(rule(334, (X2 + (X1 * (X2 * -X2))) = ((-X1 + X2) * (X2 * X2))))).
-step(add(rule(527, (X2 + (X1 * (X2 * -X2))) = (X2 * (X2 * (-X1 + X2)))))).
-step(delete(rule(346, (-X1 + (X2 * (X1 * X1))) = ((-X1 + X2) * (X1 * X1))))).
-step(add(rule(528, (-X1 + (X2 * (X1 * X1))) = (X1 * (X1 * (-X1 + X2)))))).
-step(delete(rule(356, ((X1 + (X2 * (X3 * X1))) * (X1 * X1)) = (X1 + (X2 * (X3 * X1)))))).
-step(add(rule(529, (X1 * (X1 * (X1 + (X2 * (X3 * X1))))) = (X1 + (X2 * (X3 * X1)))))).
-step(delete(rule(359, ((-X1 + X2) * (X1 * -X1)) = ((-X2 + X1) * (X1 * X1))))).
-step(add(rule(530, ((-X1 + X2) * (X1 * -X1)) = (X1 * (X1 * (-X2 + X1)))))).
-step(delete(rule(447, (X1 * (X2 + (X2 * (X1 * X1)))) = ((X1 + X1) * X2)))).
-step(delete(rule(454, (X1 * (X1 * (X1 + (X2 * X1)))) = (X1 + (X2 * X1))))).
-step(delete(rule(458, (X1 + (X2 + (X1 * (X1 * X3)))) = (X2 + (X1 * (X1 * (X3 + X1))))))).
-step(delete(rule(463, (X1 * (X1 * ((X1 + X2) * X2))) = (X1 * ((X2 + X1) * (X2 * X2)))))).
-step(add(rule(531, (X1 * (X1 * ((X1 + X2) * X2))) = (X1 * (X2 * (X2 * (X2 + X1))))))).
-step(delete(rule(474, (X1 * ((X1 + X2) * (X2 * X2))) = (X1 * (X1 * ((X1 + X2) * X2)))))).
-step(add(rule(532, (X1 * (X2 * (X2 * (X1 + X2)))) = (X1 * (X1 * ((X1 + X2) * X2)))))).
-step(delete(rule(485, ((X1 + (X2 + (X3 * X3))) * (X3 * X3)) = ((X3 + ((X1 + X2) * X3)) * X3)))).
-step(add(rule(533, ((X3 + ((X1 + X2) * X3)) * X3) = (X3 * (X3 + (X3 * (X1 + X2))))))).
-step(delete(rule(492, (X1 * (X2 + X2)) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(delete(rule(493, ((X1 + X1) * (X2 + (X3 + (X3 + X3)))) = ((X1 + X1) * (X2 + (? + (? + ?))))))).
-step(add(rule(534, ((X1 + X1) * (X2 + (X3 + (X3 + X3)))) = ((X1 + X1) * (? + (? + (? + X2))))))).
-step(delete(rule(513, (-? + (((X2 + X2) * X3) + ?)) = (X2 * (X3 + X3))))).
-step(delete(rule(514, (-X1 + (((X2 + X2) * X3) + X1)) = (-? + (((X2 + X2) * X3) + ?))))).
-step(add(rule(535, (-X1 + (((X2 + X2) * X3) + X1)) = ((X2 + X2) * X3)))).
-step(simplify_queue).
-step(add(rule(536, (X1 * ((X1 + (X2 * X1)) * X1)) = (X1 + (X1 * X2))))).
-step(add(rule(537, ((X2 + (X1 * X2)) * X2) = (X2 * (X2 + (X2 * X1)))))).
-step(add(rule(538, (X1 * (X2 * (X3 * X3))) = (X3 * (X3 * (X1 * X2)))))).
-step(add(rule(539, (X1 * (X2 * (X2 + X2))) = (X2 * (X2 * (X1 + X1)))))).
-step(hard((X1 * X2) = (X1 * (-X1 + (X2 + X1))))).
-step(add(rule(540, (X1 * (X2 * -X2)) = (X2 * (X2 * -X1))))).
-step(add(rule(541, ((X1 + X1) * X2) = (X2 * (X2 * (X1 * (X2 + X2))))))).
-step(add(rule(542, (X1 * (X1 * (X2 * X3))) = (X2 * (X1 * (X1 * X3)))))).
-step(add(rule(543, (X2 * X1) = (X1 * X2)))).
-
-lemma((X1 + 0) = X1).
-lemma((X1 + (-X1 + X2)) = X2).
-lemma(-(-X1) = X1).
-lemma((X2 + (X1 + -X2)) = X1).
-lemma((X1 * (X1 * (X1 * X2))) = (X1 * X2)).
-lemma((X1 + (X2 + -(X1 + X2))) = 0).
-lemma((X1 * ((X1 * X1) + X2)) = (X1 + (X1 * X2))).
-lemma((X1 * 0) = 0).
-lemma((-X1 * -(-X1 * -X1)) = X1).
-lemma((X1 + (X1 * (X1 * X2))) = (X1 * (X1 * (X1 + X2)))).
-lemma((0 * X1) = 0).
-lemma(((X1 * (X2 * X4)) + (X3 * X4)) = (((X1 * X2) + X3) * X4)).
-lemma(((X1 * X4) + (X2 * (X3 * X4))) = ((X1 + (X2 * X3)) * X4)).
-lemma((X1 * (X2 * (X1 * (X2 * (X1 * X2))))) = (X1 * X2)).
-lemma(-(X1 * X2) = (X1 * -X2)).
-lemma((-X1 * X2) = (X1 * -X2)).
-lemma(((X1 + (X1 * (X2 * -X2))) * (X2 * X3)) = 0).
-lemma(((X1 + (((X1 * -X2) + X3) * X2)) * X2) = (X3 * (X2 * X2))).
-lemma((X1 * (X1 * (X2 * X1))) = (X2 * X1)).
-lemma((X1 * (X2 * (X1 * X1))) = (X1 * X2)).
-lemma((X1 * (X2 * (X3 * X3))) = (X3 * (X3 * (X1 * X2)))).
diff --git a/misc/static-libstdc++ b/misc/static-libstdc++
deleted file mode 100644
--- a/misc/static-libstdc++
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/zsh
-typeset -a args
-
-process() {
-    for arg in $*; do
-        case $arg in
-            \"*\")
-                process $(echo $arg | cut -c2- | rev | cut -c2- | rev)
-                ;;
-            @*)
-                process $(cat $(echo $arg | cut -c2-))
-                ;;
-            -lstdc++ | -fuse-ld=gold)
-                ;;
-            *)
-                args+=$arg
-                ;;
-        esac
-    done
-}
-
-process $*
-
-exec g++ -static-libgcc -static-libstdc++ $args
diff --git a/misc/test.hs b/misc/test.hs
deleted file mode 100644
--- a/misc/test.hs
+++ /dev/null
@@ -1,161 +0,0 @@
-{-# LANGUAGE TemplateHaskell, FlexibleInstances, FlexibleContexts, UndecidableInstances, StandaloneDeriving, ScopedTypeVariables, TupleSections, DeriveGeneric #-}
-import Twee.Constraints
-import Twee.Term hiding (subst, canonicalise, F)
-import Twee.Term.Core hiding (F)
-import Test.QuickCheck hiding (Function, Fun)
-import Test.QuickCheck.All
-import Twee.Pretty
-import Twee.CP
-import Twee.Proof
-import qualified Twee.KBO as Ord
-import Text.PrettyPrint
-import Twee.Base hiding (F)
-import Twee.Rule
-import Twee.Equation
-import Control.Monad
-import qualified Data.Map as Map
-import Data.Maybe
-import Data.Ord
-import Data.List
-import Data.Typeable
-import qualified Twee.Index as Index
-import Data.Int
-import GHC.Generics
-
-newtype Func = F Int deriving (Eq, Ord, Show)
-
-instance Pretty Func where pPrint (F f) = text "f" <> int f
-instance PrettyTerm Func
-instance Arbitrary (Subst Func) where
-  arbitrary = fmap fromJust (fmap listToSubst (liftM2 zip (fmap nub arbitrary) (infiniteListOf arbitrary)))
-instance Arbitrary Func where
-  arbitrary = F <$> choose (1, 1)
-instance Minimal Func where
-  minimal = fun (F 0)
-instance Sized Func where size _ = 1
-instance Arity Func where
-  arity (F 0) = 0
-  arity (F 1) = 2
-instance Skolem Func
-instance EqualsBonus Func
-
-instance Arbitrary Var where arbitrary = fmap V (choose (0, 3))
-instance (Ord f, Typeable f, Arbitrary f) => Arbitrary (Fun f) where
-  arbitrary = fmap fun arbitrary
-
-instance (Ord f, Typeable f, Arbitrary f, Sized f, Arity f) => Arbitrary (Term f) where
-  arbitrary =
-    sized $ \n ->
-      oneof $
-        [ build <$> var <$> arbitrary ] ++
-        [ do { f <- arbitrary; build <$> app f <$> vectorOf (arity f) (resize ((n-1) `div` arity f) arbitrary :: Gen (Term f)) } | n > 0 ]
-  shrink (App f ts0) =
-    ts ++ (build <$> app f <$> shrinkOne ts)
-    where
-      ts = unpack ts0
-      shrinkOne [] = []
-      shrinkOne (x:xs) =
-        [ y:xs | y <- shrink x ] ++
-        [ x:ys | ys <- shrinkOne xs ]
-  shrink _ = []
-
-data Pair f = Pair (Term f) (Term f) deriving Show
-
-instance (Ord f, Typeable f, Arbitrary f, Arity f, Sized f) => Arbitrary (Pair f) where
-  arbitrary = liftM2 Pair arbitrary arbitrary
-  shrink (Pair x y) =
-    [ Pair x' y  | x' <- shrink x ] ++
-    [ Pair x y'  | y' <- shrink y ] ++
-    [ Pair x' y' | x' <- shrink x, y' <- shrink y ]
-
-instance Ordered Func where
-  lessIn = Ord.lessIn
-  lessEq = Ord.lessEq
-
-instance Function f => Arbitrary (Model f) where
-  arbitrary = fmap (modelFromOrder . map Variable . nub) arbitrary
-  shrink = weakenModel
-
-prop_1 :: Model Func -> Pair Func -> Subst Func -> Property
-prop_1 model (Pair t u) sub =
-  counterexample ("Model: " ++ prettyShow model) $
-  counterexample ("Subst: " ++ prettyShow sub) $
-  conjoin $ do
-    let cp = CriticalPair (t :=: u) 0 Nothing (axiom (Axiom 0 "dummy" (t :=: u)))
-    r@(Rule _ t' u') <- map orient (map cp_eqn (split cp))
-    return $
-      counterexample ("LHS:   " ++ prettyShow t') $
-      counterexample ("RHS:   " ++ prettyShow u') $
-      counterexample ("Rule:  " ++ prettyShow r) $
-      counterexample ("Inst:  " ++ prettyShow (Rule Oriented (subst sub t') (subst sub u'))) $
-      counterexample ("Res:   " ++ show (lessIn model (subst sub u') (subst sub t'))) $
-      not (reducesInModel model r sub) || isJust (lessIn model (subst sub u') (subst sub t'))
-
-prop_2 :: Model Func -> Pair Func -> Bool
-prop_2 model (Pair t u) =
-  not (lessIn model t u == Just Strict && isJust (lessIn model u t))
-
-prop_3 :: Pair Func -> Bool
-prop_3 (Pair t u) =
-  not (lessThan t u && lessEq u t)
-
-prop_4 :: Pair Func -> Property
-prop_4 (Pair t u) =
-  t /= u ==> 
-  not (lessEq t u && lessEq u t)
-
-prop_5 :: Term Func -> Property
-prop_5 t =
-  lessEq t t .&&. not (lessThan t t)
-
-prop_paths :: Term Func -> Property
-prop_paths t =
-  forAllShrink (choose (0, len t-1)) shrink $ \n ->
-    counterexample (show (positionToPath t n)) $
-    pathToPosition t (positionToPath t n) === n
-
-deriving instance Ord f => Ord (Subst f)
-
-prop_index :: [Term Func] -> Term Func -> Property
-prop_index ts u =
-  counterexample (show ts) $
-  counterexample (show idx) $
-  sort (catMaybes [fmap (,t) (match t u) | t <- ts]) ===
-  sort (Index.matches u idx)
-  where
-    idx = foldr (\t -> Index.insert t t) Index.empty ts
-
-deriving instance Eq Symbol
-deriving instance Generic Symbol
-
-instance Arbitrary Symbol where
-  arbitrary =
-    Symbol <$>
-      arbitrary <*>
-      fmap getLarge arbitrary <*>
-      (fmap (fromIntegral . getLarge) (arbitrary :: Gen (Large Int32)) `suchThat` (> 0) `suchThat` (< 2^31))
-  shrink s =
-    filter ok (genericShrink s)
-    where
-      ok s = Twee.Term.Core.size s > 0
-
-prop_symbol_1 :: Symbol -> Property
-prop_symbol_1 s =
-  withMaxSuccess 100000 $
-  counterexample ("fun/index/size = " ++ show (isFun s, index s, Twee.Term.Core.size s)) $
-  counterexample ("n = " ++ show (fromSymbol s)) $
-  toSymbol (fromSymbol s) === twiddle s
-  where
-    twiddle s =
-      s { index = fromIntegral (fromIntegral (index s) :: Int32) }
-
-prop_symbol_2 :: Int64 -> Property
-prop_symbol_2 n =
-  withMaxSuccess 100000 $
-  fromSymbol (toSymbol n) === n
-
-return []
-main = $forAllProperties (quickCheckWithResult stdArgs { maxSuccess = 1000000 })
-
-t :: Term Func
-t = build (app (fun (F 0)) [app (fun (F 1)) [var (V 0), var (V 1)], var (V 2)])
diff --git a/src/Data/ChurchList.hs b/src/Data/ChurchList.hs
deleted file mode 100644
--- a/src/Data/ChurchList.hs
+++ /dev/null
@@ -1,99 +0,0 @@
--- Church-encoded lists. Used in Twee.CP to make sure that fusion happens.
-{-# LANGUAGE Rank2Types, BangPatterns #-}
-module Data.ChurchList where
-
-import Prelude(Functor(..), Applicative(..), Monad(..), Bool(..), Maybe(..), (.), ($), id)
-import qualified Prelude
-import GHC.Magic(oneShot)
-import GHC.Exts(build)
-import Control.Monad(MonadPlus(..), liftM2)
-import Control.Applicative(Alternative(..))
-
-newtype ChurchList a =
-  ChurchList (forall b. (a -> b -> b) -> b -> b)
-
-{-# INLINE foldr #-}
-foldr :: (a -> b -> b) -> b -> ChurchList a -> b
-foldr op e (ChurchList f) = eta (f op (eta e))
-  -- Using eta here seems to help with eta-expanding foldl'
-
-{-# INLINE[0] eta #-}
-eta :: a -> a
-eta x = x
-{-# RULES "eta" forall f. eta f = \x -> f x #-}
-
-{-# INLINE nil #-}
-nil :: ChurchList a
-nil = ChurchList (\_ n -> n)
-
-{-# INLINE unit #-}
-unit :: a -> ChurchList a
-unit x = ChurchList (\c n -> c x n)
-
-{-# INLINE cons #-}
-cons :: a -> ChurchList a -> ChurchList a
-cons x xs = ChurchList (\c n -> c x (foldr c n xs))
-
-{-# INLINE append #-}
-append :: ChurchList a -> ChurchList a -> ChurchList a
-append xs ys = ChurchList (\c n -> foldr c (foldr c n ys) xs)
-
-{-# INLINE join #-}
-join :: ChurchList (ChurchList a) -> ChurchList a
-join xss = ChurchList (\c n -> foldr (\xs ys -> foldr c ys xs) n xss)
-
-instance Functor ChurchList where
-  {-# INLINE fmap #-}
-  fmap f xs = ChurchList (\c n -> foldr (c . f) n xs)
-
-instance Applicative ChurchList where
-  {-# INLINE pure #-}
-  pure = return
-  {-# INLINE (<*>) #-}
-  (<*>) = liftM2 ($)
-
-instance Monad ChurchList where
-  {-# INLINE return #-}
-  return = unit
-  {-# INLINE (>>=) #-}
-  xs >>= f = join (fmap f xs)
-
-instance Alternative ChurchList where
-  {-# INLINE empty #-}
-  empty = nil
-  {-# INLINE (<|>) #-}
-  (<|>) = append
-
-instance MonadPlus ChurchList where
-  {-# INLINE mzero #-}
-  mzero = empty
-  {-# INLINE mplus #-}
-  mplus = (<|>)
-
-{-# INLINE fromList #-}
-fromList :: [a] -> ChurchList a
-fromList xs = ChurchList (\c n -> Prelude.foldr c n xs)
-
-{-# INLINE toList #-}
-toList :: ChurchList a -> [a]
-toList (ChurchList f) = build f
-
-{-# INLINE foldl' #-}
-foldl' :: (b -> a -> b) -> b -> ChurchList a -> b
-foldl' op e xs =
-  foldr (\x f -> oneShot (\ (!acc) -> f (op acc x))) id xs e
-
-{-# INLINE filter #-}
-filter :: (a -> Bool) -> ChurchList a -> ChurchList a
-filter p xs =
-  ChurchList $ \c n ->
-    let            
-      {-# INLINE op #-}
-      op x xs = if p x then c x xs else xs
-    in
-      foldr op n xs
-
-{-# INLINE fromMaybe #-}
-fromMaybe :: Maybe a -> ChurchList a
-fromMaybe Nothing = nil
-fromMaybe (Just x) = unit x
diff --git a/src/Data/DynamicArray.hs b/src/Data/DynamicArray.hs
deleted file mode 100644
--- a/src/Data/DynamicArray.hs
+++ /dev/null
@@ -1,67 +0,0 @@
--- | Zero-indexed dynamic arrays, optimised for lookup.
--- Modification is slow. Uninitialised indices have a default value.
-{-# LANGUAGE CPP #-}
-module Data.DynamicArray where
-
-#ifdef BOUNDS_CHECKS
-import qualified Data.Primitive.SmallArray.Checked as P
-#else
-import qualified Data.Primitive.SmallArray as P
-#endif
-import Control.Monad.ST
-import Data.List
-
--- | A type which has a default value.
-class Default a where
-  -- | The default value.
-  def :: a
-
--- | An array.
-data Array a =
-  Array {
-    -- | The size of the array.
-    arraySize     :: {-# UNPACK #-} !Int,
-    -- | The contents of the array.
-    arrayContents :: {-# UNPACK #-} !(P.SmallArray a) }
-
--- | Convert an array to a list of (index, value) pairs.
-{-# INLINE toList #-}
-toList :: Array a -> [(Int, a)]
-toList arr =
-  [ (i, x)
-  | i <- [0..arraySize arr-1],
-    let x = P.indexSmallArray (arrayContents arr) i ]
-
-instance Show a => Show (Array a) where
-  show arr =
-    "{" ++
-    intercalate ", "
-      [ show i ++ "->" ++ show x
-      | (i, x) <- toList arr ] ++
-    "}"
-
--- | Create an empty array.
-newArray :: Default a => Array a
-newArray = runST $ do
-  marr <- P.newSmallArray 0 def
-  arr  <- P.unsafeFreezeSmallArray marr
-  return (Array 0 arr)
-
--- | Index into an array. O(1) time.
-{-# INLINE (!) #-}
-(!) :: Default a => Array a -> Int -> a
-arr ! n
-  | 0 <= n && n < arraySize arr =
-    P.indexSmallArray (arrayContents arr) n
-  | otherwise = def
-
--- | Update the array. O(n) time.
-{-# INLINEABLE update #-}
-update :: Default a => Int -> a -> Array a -> Array a
-update n x arr = runST $ do
-  let size = arraySize arr `max` (n+1)
-  marr <- P.newSmallArray size def
-  P.copySmallArray marr 0 (arrayContents arr) 0 (arraySize arr)
-  P.writeSmallArray marr n $! x
-  arr' <- P.unsafeFreezeSmallArray marr
-  return (Array size arr')
diff --git a/src/Data/Heap.hs b/src/Data/Heap.hs
deleted file mode 100644
--- a/src/Data/Heap.hs
+++ /dev/null
@@ -1,154 +0,0 @@
--- | Skew heaps.
-
-{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
-module Data.Heap(
-  Heap, empty, singleton, insert, removeMin, union, mapMaybe, size) where
-
--- | A heap.
-
--- Representation: the size of the heap, and the heap itself.
-data Heap a = Heap {-# UNPACK #-} !Int !(Heap1 a) deriving Show
--- N.B.: arguments are not strict so code has to take care
--- to force stuff appropriately.
-data Heap1 a = Nil | Node a (Heap1 a) (Heap1 a) deriving Show
-
--- | Take the union of two heaps.
-{-# INLINEABLE union #-}
-union :: Ord a => Heap a -> Heap a -> Heap a
-union (Heap n1 h1) (Heap n2 h2) = Heap (n1+n2) (union1 h1 h2)
-
-{-# INLINEABLE union1 #-}
-union1 :: forall a. Ord a => Heap1 a -> Heap1 a -> Heap1 a
-union1 = u1
-  where
-    -- The generated code is better when we do everything
-    -- through this u1 function instead of union1...
-    -- This is because u1 has no Ord constraint in its type.
-    u1 :: Heap1 a -> Heap1 a -> Heap1 a
-    u1 Nil h = h
-    u1 h Nil = h
-    u1 h1@(Node x1 l1 r1) h2@(Node x2 l2 r2)
-      | x1 <= x2 = (Node x1 $! u1 r1 h2) l1
-      | otherwise = (Node x2 $! u1 r2 h1) l2
-
--- | A singleton heap.
-{-# INLINE singleton #-}
-singleton :: a -> Heap a
-singleton !x = Heap 1 (Node x Nil Nil)
-
--- | The empty heap.
-{-# INLINE empty #-}
-empty :: Heap a
-empty = Heap 0 Nil
-
--- | Insert an element.
-{-# INLINEABLE insert #-}
-insert :: Ord a => a -> Heap a -> Heap a
-insert x h = union (singleton x) h
-
--- | Find and remove the minimum element.
-{-# INLINEABLE removeMin #-}
-removeMin :: Ord a => Heap a -> Maybe (a, Heap a)
-removeMin (Heap _ Nil) = Nothing
-removeMin (Heap n (Node x l r)) = Just (x, Heap (n-1) (union1 l r))
-
--- | Map a function over a heap, removing all values which
--- map to 'Nothing'. May be more efficient when the function
--- being mapped is mostly monotonic.
-{-# INLINEABLE mapMaybe #-}
-mapMaybe :: Ord b => (a -> Maybe b) -> Heap a -> Heap b
-mapMaybe f (Heap _ h) = Heap (sz 0 h') h'
-  where
-    -- Compute the size fairly efficiently.
-    sz !n Nil = n
-    sz !n (Node _ l r) = sz (sz (n+1) l) r
-
-    h' = mm h
-
-    mm Nil = Nil
-    mm (Node x l r) =
-      case f x of
-        -- If the value maps to Nothing, get rid of it.
-        Nothing -> union1 l' r'
-        -- Otherwise, check if the heap invariant still holds
-        -- and sift downwards to restore it.
-        Just !y -> down y l' r'
-      where
-        !l' = mm l
-        !r' = mm r
-
-    down x l@(Node y ll lr) r@(Node z rl rr)
-      -- Put the smallest of x, y and z at the root.
-      | y < x && y <= z =
-        (Node y $! down x ll lr) r
-      | z < x && z <= y =
-        Node z l $! down x rl rr
-    down x Nil (Node y l r)
-      -- Put the smallest of x and y at the root.
-      | y < x =
-        Node y Nil $! down x l r
-    down x (Node y l r) Nil
-      -- Put the smallest of x and y at the root.
-      | y < x =
-        (Node y $! down x l r) Nil
-    down x l r = Node x l r
-
--- | Return the number of elements in the heap.
-{-# INLINE size #-}
-size :: Heap a -> Int
-size (Heap n _) = n
-
--- Testing code:
--- import Test.QuickCheck
--- import qualified Data.List as List
--- import qualified Data.Maybe as Maybe
-
--- instance (Arbitrary a, Ord a) => Arbitrary (Heap a) where
---   arbitrary = sized arb
---     where
---       arb 0 = return empty
---       arb n =
---         frequency
---           [(1, singleton <$> arbitrary),
---            (n-1, union <$> arb' <*> arb')]
---         where
---           arb' = arb (n `div` 2)
-
--- toList :: Ord a => Heap a -> [a]
--- toList = List.unfoldr removeMin
-
--- invariant :: Ord a => Heap a -> Bool
--- invariant h@(Heap n h1) =
---   n == length (toList h) && ord h1
---   where
---     ord Nil = True
---     ord (Node x l r) = ord1 x l && ord1 x r
-
---     ord1 _ Nil = True
---     ord1 x h@(Node y _ _) = x <= y && ord h
-
--- prop_1 h = withMaxSuccess 10000 $ invariant h
--- prop_2 x h = withMaxSuccess 10000 $ invariant (insert x h)
--- prop_3 h =
---   withMaxSuccess 1000 $
---   case removeMin h of
---     Nothing -> discard
---     Just (_, h) -> invariant h
--- prop_4 h = withMaxSuccess 10000 $ List.sort (toList h) == toList h
--- prop_5 x h = withMaxSuccess 10000 $ toList (insert x h) == List.insert x (toList h)
--- prop_6 x h =
---   withMaxSuccess 1000 $
---   case removeMin h of
---     Nothing -> discard
---     Just (x, h') -> toList h == List.insert x (toList h')
--- prop_7 h1 h2 = withMaxSuccess 10000 $
---   invariant (union h1 h2)
--- prop_8 h1 h2 = withMaxSuccess 10000 $
---   toList (union h1 h2) == List.sort (toList h1 ++ toList h2)
--- prop_9 (Blind f) h = withMaxSuccess 10000 $
---   invariant (mapMaybe f h)
--- prop_10 (Blind f) h = withMaxSuccess 1000000 $
---   toList (mapMaybe f h) == List.sort (Maybe.mapMaybe f (toList h))
-
--- return []
--- main = $quickCheckAll
diff --git a/src/Data/Primitive/ByteArray/Checked.hs b/src/Data/Primitive/ByteArray/Checked.hs
deleted file mode 100644
--- a/src/Data/Primitive/ByteArray/Checked.hs
+++ /dev/null
@@ -1,74 +0,0 @@
--- | A bounds-checked version of 'Data.Primitive.ByteArray'.
--- See that module for documentation.
-
-{-# LANGUAGE ScopedTypeVariables #-}
-module Data.Primitive.ByteArray.Checked(
-  module Data.Primitive.ByteArray,
-  module Data.Primitive.ByteArray.Checked) where
-
-import Control.Monad.Primitive
-import qualified Data.Primitive.ByteArray as P
-import Data.Primitive(Prim)
-import Data.Primitive.ByteArray(
-  ByteArray(..), MutableByteArray(..),
-  newByteArray, newPinnedByteArray, newAlignedPinnedByteArray,
-  byteArrayContents, mutableByteArrayContents,
-  sameMutableByteArray,
-  unsafeFreezeByteArray, unsafeThawByteArray,
-  sizeofByteArray, sizeofMutableByteArray)
-import Data.Primitive.Checked
-import Data.Word
-
-instance Sized ByteArray where
-  size = sizeofByteArray
-instance Sized (MutableByteArray m) where
-  size = sizeofMutableByteArray
-
-{-# INLINE readByteArray #-}
-readByteArray :: forall m a. (PrimMonad m, Prim a) => MutableByteArray (PrimState m) -> Int -> m a
-readByteArray arr n =
-  checkPrim (undefined :: a) arr n $
-  P.readByteArray arr n
-
-{-# INLINE writeByteArray #-}
-writeByteArray :: (PrimMonad m, Prim a) => MutableByteArray (PrimState m) -> Int -> a -> m ()
-writeByteArray arr n x =
-  checkPrim x arr n $
-  P.writeByteArray arr n x
-
-{-# INLINE indexByteArray #-}
-indexByteArray :: forall a. Prim a => ByteArray -> Int -> a
-indexByteArray arr n =
-  checkPrim (undefined :: a) arr n $
-  P.indexByteArray arr n
-
-{-# INLINE copyByteArray #-}
-copyByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> ByteArray -> Int -> Int -> m ()
-copyByteArray arr1 n1 arr2 n2 len =
-  range arr1 n1 len $
-  range arr2 n2 len $
-  P.copyByteArray arr1 n1 arr2 n2 len
-
-{-# INLINE moveByteArray #-}
-moveByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> MutableByteArray (PrimState m) -> Int -> Int -> m ()
-moveByteArray arr1 n1 arr2 n2 len =
-  range arr1 n1 len $
-  range arr2 n2 len $
-  P.moveByteArray arr1 n1 arr2 n2 len
-
-{-# INLINE copyMutableByteArray #-}
-copyMutableByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> MutableByteArray (PrimState m) -> Int -> Int -> m ()
-copyMutableByteArray arr1 n1 arr2 n2 len =
-  range arr1 n1 len $
-  range arr2 n2 len $
-  P.copyMutableByteArray arr1 n1 arr2 n2 len
-
-{-# INLINE setByteArray #-}
-setByteArray :: (Prim a, PrimMonad m) => MutableByteArray (PrimState m) -> Int -> Int -> a -> m ()
-setByteArray arr n len x =
-  rangePrim x arr n len $
-  P.setByteArray arr n len x
-
-{-# INLINE fillByteArray #-}
-fillByteArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> Int -> Word8 -> m ()
-fillByteArray = setByteArray
diff --git a/src/Data/Primitive/Checked.hs b/src/Data/Primitive/Checked.hs
deleted file mode 100644
--- a/src/Data/Primitive/Checked.hs
+++ /dev/null
@@ -1,46 +0,0 @@
--- | A helper module for array bounds checking.
-
-module Data.Primitive.Checked where
-
-import Data.Primitive(Prim, sizeOf)
-
--- | A type class of things which have a size (e.g., arrays).
-class Sized a where
-  -- | Read the size of the thing.
-  size :: a -> Int
-
--- | Check that a single access is in bounds.
-{-# INLINE check #-}
-check :: Sized a => a -> Int -> b -> b
-check arr n x
-  | n >= 0 && n < size arr = x
-  | otherwise = error "out-of-bounds array access"
-
--- | Check that a range of accesses is in bounds.
--- The range is inclusive.
-{-# INLINE range #-}
-range :: Sized a => a -> Int -> Int -> b -> b
-range arr n len x
-  | len < 0 = error "array slice has negative length"
-  | len == 0 = x
-  | otherwise =
-    check arr n $
-    check arr (n+len-1) $ x
-
--- | Check that a single access is in bounds.
--- The index accessed is computed by multiplying by the size
--- of the first argument.
-{-# INLINE checkPrim #-}
-checkPrim :: (Sized a, Prim b) => b -> a -> Int -> c -> c
-checkPrim x arr n res =
-  range arr (n*sizeOf x) (sizeOf x) res
-  
--- | Check that a range of accesses is in bounds.
--- The range is inclusive.
--- The index accessed is computed by multiplying by the size
--- of the first argument.
-{-# INLINE rangePrim #-}
-rangePrim :: (Sized a, Prim b) => b -> a -> Int -> Int -> c -> c
-rangePrim x arr n len res =
-  range arr (n*sizeOf x) (len*sizeOf x) res
-  
diff --git a/src/Data/Primitive/SmallArray/Checked.hs b/src/Data/Primitive/SmallArray/Checked.hs
deleted file mode 100644
--- a/src/Data/Primitive/SmallArray/Checked.hs
+++ /dev/null
@@ -1,80 +0,0 @@
--- | A bounds-checked version of 'Data.Primitive.SmallArray'.
--- See that module for documentation.
-
-module Data.Primitive.SmallArray.Checked(
-  module Data.Primitive.SmallArray,
-  module Data.Primitive.SmallArray.Checked) where
-
-import Control.Monad.Primitive
-import qualified Data.Primitive.SmallArray as P
-import Data.Primitive.SmallArray(
-  SmallArray(..), SmallMutableArray(..), newSmallArray, unsafeFreezeSmallArray,
-  unsafeThawSmallArray, sizeofSmallArray, sizeofSmallMutableArray)
-import Data.Primitive.Checked
-
-instance Sized (SmallArray a) where
-  size = sizeofSmallArray
-instance Sized (SmallMutableArray m a) where
-  size = sizeofSmallMutableArray
-
-{-# INLINE readSmallArray #-}
-readSmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> m a
-readSmallArray arr n =
-  check arr n $
-  P.readSmallArray arr n
-
-{-# INLINE writeSmallArray #-}
-writeSmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> a -> m ()
-writeSmallArray arr n x =
-  check arr n $
-  P.writeSmallArray arr n x
-
-{-# INLINE indexSmallArrayM #-}
-indexSmallArrayM :: Monad m => SmallArray a -> Int -> m a
-indexSmallArrayM arr n =
-  check arr n $
-  P.indexSmallArrayM arr n
-
-{-# INLINE indexSmallArray #-}
-indexSmallArray :: SmallArray a -> Int -> a
-indexSmallArray arr n =
-  check arr n $
-  P.indexSmallArray arr n
-
-{-# INLINE cloneSmallArray #-}
-cloneSmallArray :: SmallArray a -> Int -> Int -> SmallArray a
-cloneSmallArray arr n len =
-  range arr n len $
-  P.cloneSmallArray arr n len
-
-{-# INLINE cloneSmallMutableArray #-}
-cloneSmallMutableArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> Int -> m (SmallMutableArray (PrimState m) a)
-cloneSmallMutableArray arr n len =
-  range arr n len $
-  P.cloneSmallMutableArray arr n len
-
-{-# INLINE freezeSmallArray #-}
-freezeSmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> Int -> m (SmallArray a)
-freezeSmallArray arr n len =
-  range arr n len $
-  P.freezeSmallArray arr n len
-
-{-# INLINE thawSmallArray #-}
-thawSmallArray :: PrimMonad m => SmallArray a -> Int -> Int -> m (SmallMutableArray (PrimState m) a)
-thawSmallArray arr n len =
-  range arr n len $
-  P.thawSmallArray arr n len
-
-{-# INLINE copySmallArray #-}
-copySmallArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> SmallArray a -> Int -> Int -> m ()
-copySmallArray arr1 n1 arr2 n2 len =
-  range arr1 n1 len $
-  range arr2 n2 len $
-  P.copySmallArray arr1 n1 arr2 n2 len
-
-{-# INLINE copySmallMutableArray #-}
-copySmallMutableArray :: PrimMonad m => SmallMutableArray (PrimState m) a -> Int -> SmallMutableArray (PrimState m) a -> Int -> Int -> m ()
-copySmallMutableArray arr1 n1 arr2 n2 len =
-  range arr1 n1 len $
-  range arr2 n2 len $
-  P.copySmallMutableArray arr1 n1 arr2 n2 len
diff --git a/src/Twee.hs b/src/Twee.hs
deleted file mode 100644
--- a/src/Twee.hs
+++ /dev/null
@@ -1,610 +0,0 @@
--- | The main prover loop.
-{-# LANGUAGE RecordWildCards, MultiParamTypeClasses, GADTs, BangPatterns, OverloadedStrings, ScopedTypeVariables, GeneralizedNewtypeDeriving, PatternGuards, TypeFamilies #-}
-module Twee where
-
-import Twee.Base
-import Twee.Rule
-import Twee.Equation
-import qualified Twee.Proof as Proof
-import Twee.Proof(Proof, Axiom(..), Lemma(..), ProvedGoal(..), provedGoal, certify, derivation, symm)
-import Twee.CP hiding (Config)
-import qualified Twee.CP as CP
-import Twee.Join hiding (Config, defaultConfig)
-import qualified Twee.Join as Join
-import qualified Twee.Rule.Index as RuleIndex
-import Twee.Rule.Index(RuleIndex(..))
-import qualified Twee.Index as Index
-import Twee.Index(Index)
-import Twee.Constraints
-import Twee.Utils
-import Twee.Task
-import qualified Twee.PassiveQueue as Queue
-import Twee.PassiveQueue(Queue, Passive(..))
-import qualified Data.IntMap.Strict as IntMap
-import Data.IntMap(IntMap)
-import Data.Maybe
-import Data.List
-import Data.Function
-import qualified Data.Set as Set
-import Data.Set(Set)
-import Data.Int
-import Data.Ord
-import Control.Monad
-import Control.Monad.IO.Class
-import Control.Monad.Trans.Class
-import qualified Control.Monad.Trans.State.Strict as StateM
-
-----------------------------------------------------------------------
--- * Configuration and prover state.
-----------------------------------------------------------------------
-
--- | The prover configuration.
-data Config =
-  Config {
-    cfg_max_term_size          :: Int,
-    cfg_max_critical_pairs     :: Int64,
-    cfg_max_cp_depth           :: Int,
-    cfg_simplify               :: Bool,
-    cfg_renormalise_percent    :: Int,
-    cfg_critical_pairs         :: CP.Config,
-    cfg_join                   :: Join.Config,
-    cfg_proof_presentation     :: Proof.Config }
-
--- | The prover state.
-data State f =
-  State {
-    st_rules          :: !(RuleIndex f (ActiveRule f)),
-    st_active_ids     :: !(IntMap (Active f)),
-    st_rule_ids       :: !(IntMap (ActiveRule f)),
-    st_joinable       :: !(Index f (Equation f)),
-    st_goals          :: ![Goal f],
-    st_queue          :: !(Queue Params),
-    st_next_active    :: {-# UNPACK #-} !Id,
-    st_next_rule      :: {-# UNPACK #-} !RuleId,
-    st_considered     :: {-# UNPACK #-} !Int64,
-    st_messages_rev   :: ![Message f] }
-
--- | The default prover configuration.
-defaultConfig :: Config
-defaultConfig =
-  Config {
-    cfg_max_term_size = maxBound,
-    cfg_max_critical_pairs = maxBound,
-    cfg_max_cp_depth = maxBound,
-    cfg_simplify = True,
-    cfg_renormalise_percent = 5,
-    cfg_critical_pairs = CP.defaultConfig,
-    cfg_join = Join.defaultConfig,
-    cfg_proof_presentation = Proof.defaultConfig }
-
--- | Does this configuration run the prover in a complete mode?
-configIsComplete :: Config -> Bool
-configIsComplete Config{..} =
-  cfg_max_term_size == maxBound &&
-  cfg_max_critical_pairs == maxBound &&
-  cfg_max_cp_depth == maxBound
-
--- | The initial state.
-initialState :: State f
-initialState =
-  State {
-    st_rules = RuleIndex.empty,
-    st_active_ids = IntMap.empty,
-    st_rule_ids = IntMap.empty,
-    st_joinable = Index.empty,
-    st_goals = [],
-    st_queue = Queue.empty,
-    st_next_active = 1,
-    st_next_rule = 0,
-    st_considered = 0,
-    st_messages_rev = [] }
-
-----------------------------------------------------------------------
--- * Messages.
-----------------------------------------------------------------------
-
--- | A message which is produced by the prover when something interesting happens.
-data Message f =
-    -- | A new rule.
-    NewActive !(Active f)
-    -- | A new joinable equation.
-  | NewEquation !(Equation f)
-    -- | A rule was deleted.
-  | DeleteActive !(Active f)
-    -- | The CP queue was simplified.
-  | SimplifyQueue
-    -- | The rules were reduced wrt each other.
-  | Interreduce
-
-instance Function f => Pretty (Message f) where
-  pPrint (NewActive rule) = pPrint rule
-  pPrint (NewEquation eqn) =
-    text "  (hard)" <+> pPrint eqn
-  pPrint (DeleteActive rule) =
-    text "  (delete rule " <> pPrint (active_id rule) <> text ")"
-  pPrint SimplifyQueue =
-    text "  (simplifying queued critical pairs...)"
-  pPrint Interreduce =
-    text "  (simplifying rules with respect to one another...)"
-
--- | Emit a message.
-message :: PrettyTerm f => Message f -> State f -> State f
-message !msg state@State{..} =
-  state { st_messages_rev = msg:st_messages_rev }
-
--- | Forget about all emitted messages.
-clearMessages :: State f -> State f
-clearMessages state@State{..} =
-  state { st_messages_rev = [] }
-
--- | Get all emitted messages.
-messages :: State f -> [Message f]
-messages state = reverse (st_messages_rev state)
-
-----------------------------------------------------------------------
--- * The CP queue.
-----------------------------------------------------------------------
-
-data Params
-instance Queue.Params Params where
-  type Score Params = Int
-  type Id Params = RuleId
-  type PackedId Params = Int32
-  type PackedScore Params = Int32
-  packScore _ = fromIntegral
-  unpackScore _ = fromIntegral
-  packId _ = fromIntegral
-  unpackId _ = fromIntegral
-
--- | Compute all critical pairs from a rule.
-{-# INLINEABLE makePassives #-}
-makePassives :: Function f => Config -> State f -> ActiveRule f -> [Passive Params]
-makePassives Config{..} State{..} rule =
-  {-# SCC makePassive #-}
-  [ Passive (fromIntegral (score cfg_critical_pairs o)) (rule_rid rule1) (rule_rid rule2) (fromIntegral (overlap_pos o))
-  | (rule1, rule2, o) <- overlaps (Depth cfg_max_cp_depth) (index_oriented st_rules) rules rule ]
-  where
-    rules = IntMap.elems st_rule_ids
-
--- | Turn a Passive back into an overlap.
--- Doesn't try to simplify it.
-{-# INLINEABLE findPassive #-}
-findPassive :: forall f. Function f => Config -> State f -> Passive Params -> Maybe (ActiveRule f, ActiveRule f, Overlap f)
-findPassive Config{..} State{..} Passive{..} = {-# SCC findPassive #-} do
-  rule1 <- IntMap.lookup (fromIntegral passive_rule1) st_rule_ids
-  rule2 <- IntMap.lookup (fromIntegral passive_rule2) st_rule_ids
-  let !depth = 1 + max (the rule1) (the rule2)
-  overlap <-
-    overlapAt (fromIntegral passive_pos) depth
-      (renameAvoiding (the rule2 :: Rule f) (the rule1)) (the rule2)
-  return (rule1, rule2, overlap)
-
--- | Renormalise a queued Passive.
-{-# INLINEABLE simplifyPassive #-}
-simplifyPassive :: Function f => Config -> State f -> Passive Params -> Maybe (Passive Params)
-simplifyPassive config@Config{..} state@State{..} passive = {-# SCC simplifyPassive #-} do
-  (_, _, overlap) <- findPassive config state passive
-  overlap <- simplifyOverlap (index_oriented st_rules) overlap
-  return passive {
-    passive_score = fromIntegral $
-      fromIntegral (passive_score passive) `intMin`
-      score cfg_critical_pairs overlap }
-
--- | Renormalise the entire queue.
-{-# INLINEABLE simplifyQueue #-}
-simplifyQueue :: Function f => Config -> State f -> State f
-simplifyQueue config state =
-  {-# SCC simplifyQueue #-}
-  state { st_queue = simp (st_queue state) }
-  where
-    simp =
-      Queue.mapMaybe (simplifyPassive config state)
-
--- | Enqueue a set of critical pairs.
-{-# INLINEABLE enqueue #-}
-enqueue :: Function f => State f -> RuleId -> [Passive Params] -> State f
-enqueue state rule passives =
-  {-# SCC enqueue #-}
-  state { st_queue = Queue.insert rule passives (st_queue state) }
-
--- | Dequeue a critical pair.
---
--- Also takes care of:
---
---   * removing any orphans from the head of the queue
---   * ignoring CPs that are too big
-{-# INLINEABLE dequeue #-}
-dequeue :: Function f => Config -> State f -> (Maybe (CriticalPair f, ActiveRule f, ActiveRule f), State f)
-dequeue config@Config{..} state@State{..} =
-  {-# SCC dequeue #-}
-  case deq 0 st_queue of
-    -- Explicitly make the queue empty, in case it e.g. contained a
-    -- lot of orphans
-    Nothing -> (Nothing, state { st_queue = Queue.empty })
-    Just (overlap, n, queue) ->
-      (Just overlap,
-       state { st_queue = queue, st_considered = st_considered + n })
-  where
-    deq !n queue = do
-      (passive, queue) <- Queue.removeMin queue
-      case findPassive config state passive of
-        Just (rule1, rule2, overlap)
-          | passive_score passive >= 0,
-            Just Overlap{overlap_eqn = t :=: u} <-
-              simplifyOverlap (index_oriented st_rules) overlap,
-            size t <= cfg_max_term_size,
-            size u <= cfg_max_term_size,
-            Just cp <- makeCriticalPair rule1 rule2 overlap ->
-              return ((cp, rule1, rule2), n+1, queue)
-        _ -> deq (n+1) queue
-
-----------------------------------------------------------------------
--- * Active rewrite rules.
-----------------------------------------------------------------------
-
-data Active f =
-  Active {
-    active_id    :: {-# UNPACK #-} !Id,
-    active_depth :: {-# UNPACK #-} !Depth,
-    active_rule  :: {-# UNPACK #-} !(Rule f),
-    active_top   :: !(Maybe (Term f)),
-    active_proof :: {-# UNPACK #-} !(Proof f),
-    -- A model in which the rule is false (used when reorienting)
-    active_model :: !(Model f),
-    active_rules :: ![ActiveRule f] }
-
-active_cp :: Active f -> CriticalPair f
-active_cp Active{..} =
-  CriticalPair {
-    cp_eqn = unorient active_rule,
-    cp_depth = active_depth,
-    cp_top = active_top,
-    cp_proof = derivation active_proof }
-
--- An active oriented in a particular direction.
-data ActiveRule f =
-  ActiveRule {
-    rule_active    :: {-# UNPACK #-} !Id,
-    rule_rid       :: {-# UNPACK #-} !RuleId,
-    rule_depth     :: {-# UNPACK #-} !Depth,
-    rule_rule      :: {-# UNPACK #-} !(Rule f),
-    rule_proof     :: {-# UNPACK #-} !(Proof f),
-    rule_positions :: !(Positions f) }
-
-instance PrettyTerm f => Symbolic (ActiveRule f) where
-  type ConstantOf (ActiveRule f) = f
-  termsDL ActiveRule{..} =
-    termsDL rule_rule `mplus`
-    termsDL (derivation rule_proof)
-  subst_ sub r@ActiveRule{..} =
-    r {
-      rule_rule = rule',
-      rule_proof = certify (subst_ sub (derivation rule_proof)),
-      rule_positions = positions (lhs rule') }
-    where
-      rule' = subst_ sub rule_rule
-
-instance Eq (Active f) where
-  (==) = (==) `on` active_id
-
-instance Eq (ActiveRule f) where
-  (==) = (==) `on` rule_rid
-
-instance Function f => Pretty (Active f) where
-  pPrint Active{..} =
-    pPrint active_id <> text "." <+> pPrint (canonicalise active_rule)
-
-instance Has (ActiveRule f) Id where the = rule_active
-instance Has (ActiveRule f) RuleId where the = rule_rid
-instance Has (ActiveRule f) Depth where the = rule_depth
-instance f ~ g => Has (ActiveRule f) (Rule g) where the = rule_rule
-instance f ~ g => Has (ActiveRule f) (Proof g) where the = rule_proof
-instance f ~ g => Has (ActiveRule f) (Lemma g) where the x = Lemma (the x) (the x)
-instance f ~ g => Has (ActiveRule f) (Positions g) where the = rule_positions
-
-newtype RuleId = RuleId Id deriving (Eq, Ord, Show, Num, Real, Integral, Enum)
-
--- Add a new active.
-{-# INLINEABLE addActive #-}
-addActive :: Function f => Config -> State f -> (Id -> RuleId -> RuleId -> Active f) -> State f
-addActive config state@State{..} active0 =
-  {-# SCC addActive #-}
-  let
-    active@Active{..} = active0 st_next_active st_next_rule (succ st_next_rule)
-    state' =
-      message (NewActive active) $
-      addActiveOnly state{st_next_active = st_next_active+1, st_next_rule = st_next_rule+2} active
-  in if subsumed st_joinable st_rules (unorient active_rule) then
-    state
-  else
-    normaliseGoals $
-    foldl' (uncurry . enqueue) state'
-      [ (the rule, makePassives config state' rule)
-      | rule <- active_rules ]
-
--- Add an active without generating critical pairs. Used in interreduction.
-{-# INLINEABLE addActiveOnly #-}
-addActiveOnly :: Function f => State f -> Active f -> State f
-addActiveOnly state@State{..} active@Active{..} =
-  state {
-    st_rules = foldl' insertRule st_rules active_rules,
-    st_active_ids = IntMap.insert (fromIntegral active_id) active st_active_ids,
-    st_rule_ids = foldl' insertRuleId st_rule_ids active_rules }
-  where
-    insertRule rules rule@ActiveRule{..} =
-      RuleIndex.insert (lhs rule_rule) rule rules
-    insertRuleId rules rule@ActiveRule{..} =
-      IntMap.insert (fromIntegral rule_rid) rule rules
-
--- Delete an active. Used in interreduction, not suitable for general use.
-{-# INLINE deleteActive #-}
-deleteActive :: Function f => State f -> Active f -> State f
-deleteActive state@State{..} Active{..} =
-  state {
-    st_rules = foldl' deleteRule st_rules active_rules,
-    st_active_ids = IntMap.delete (fromIntegral active_id) st_active_ids,
-    st_rule_ids = foldl' deleteRuleId st_rule_ids active_rules }
-  where
-    deleteRule rules rule =
-      RuleIndex.delete (lhs (rule_rule rule)) rule rules
-    deleteRuleId rules ActiveRule{..} =
-      IntMap.delete (fromIntegral rule_rid) rules
-
--- Try to join a critical pair.
-{-# INLINEABLE consider #-}
-consider :: Function f => Config -> State f -> CriticalPair f -> State f
-consider config state cp =
-  considerUsing (st_rules state) config state cp
-
--- Try to join a critical pair, but using a different set of critical
--- pairs for normalisation.
-{-# INLINEABLE considerUsing #-}
-considerUsing ::
-  Function f =>
-  RuleIndex f (ActiveRule f) -> Config -> State f -> CriticalPair f -> State f
-considerUsing rules config@Config{..} state@State{..} cp0 =
-  {-# SCC consider #-}
-  -- Important to canonicalise the rule so that we don't get
-  -- bigger and bigger variable indices over time
-  let cp = canonicalise cp0 in
-  case joinCriticalPair cfg_join st_joinable rules Nothing cp of
-    Right (mcp, cps) ->
-      let
-        state' = foldl' (considerUsing rules config) state cps
-      in case mcp of
-        Just cp -> addJoinable state' (cp_eqn cp)
-        Nothing -> state'
-
-    Left (cp, model) ->
-      foldl' (addCP config model) state (split cp)
-
-{-# INLINEABLE addCP #-}
-addCP :: Function f => Config -> Model f -> State f -> CriticalPair f -> State f
-addCP config model state@State{..} CriticalPair{..} =
-  addActive config state $ \n k1 k2 ->
-  let
-    pf = certify cp_proof
-    rule = orient cp_eqn
-
-    makeRule k r p =
-      ActiveRule {
-        rule_active = n,
-        rule_rid = k,
-        rule_depth = cp_depth,
-        rule_rule = r rule,
-        rule_proof = p pf,
-        rule_positions = positions (lhs (r rule)) }
-  in
-  Active {
-    active_id = n,
-    active_depth = cp_depth,
-    active_rule = rule,
-    active_model = model,
-    active_top = cp_top,
-    active_proof = pf,
-    active_rules =
-      usortBy (comparing (canonicalise . rule_rule)) $
-        makeRule k1 id id:
-        [ makeRule k2 backwards (certify . symm . derivation)
-        | not (oriented (orientation rule)) ] }
-
--- Add a new equation.
-{-# INLINEABLE addAxiom #-}
-addAxiom :: Function f => Config -> State f -> Axiom f -> State f
-addAxiom config state axiom =
-  consider config state $
-    CriticalPair {
-      cp_eqn = axiom_eqn axiom,
-      cp_depth = 0,
-      cp_top = Nothing,
-      cp_proof = Proof.axiom axiom }
-
--- Record an equation as being joinable.
-{-# INLINEABLE addJoinable #-}
-addJoinable :: Function f => State f -> Equation f -> State f
-addJoinable state eqn@(t :=: u) =
-  message (NewEquation eqn) $
-  state {
-    st_joinable =
-      Index.insert t (t :=: u) $
-      Index.insert u (u :=: t) (st_joinable state) }
-
--- For goal terms we store the set of all their normal forms.
--- Name and number are for information only.
-data Goal f =
-  Goal {
-    goal_name   :: String,
-    goal_number :: Int,
-    goal_eqn    :: Equation f,
-    goal_lhs    :: Set (Resulting f),
-    goal_rhs    :: Set (Resulting f) }
-
--- Add a new goal.
-{-# INLINEABLE addGoal #-}
-addGoal :: Function f => Config -> State f -> Goal f -> State f
-addGoal _config state@State{..} goal =
-  normaliseGoals state { st_goals = goal:st_goals }
-
--- Normalise all goals.
-{-# INLINEABLE normaliseGoals #-}
-normaliseGoals :: Function f => State f -> State f
-normaliseGoals state@State{..} =
-  {-# SCC normaliseGoals #-}
-  state {
-    st_goals =
-      map (goalMap (successors (rewrite reduces (index_all st_rules)) . Set.toList)) st_goals }
-  where
-    goalMap f goal@Goal{..} =
-      goal { goal_lhs = f goal_lhs, goal_rhs = f goal_rhs }
-
--- Create a goal.
-{-# INLINE goal #-}
-goal :: Int -> String -> Equation f -> Goal f
-goal n name (t :=: u) =
-  Goal {
-    goal_name = name,
-    goal_number = n,
-    goal_eqn = t :=: u,
-    goal_lhs = Set.singleton (reduce (Refl t)),
-    goal_rhs = Set.singleton (reduce (Refl u)) }
-
-----------------------------------------------------------------------
--- Interreduction.
-----------------------------------------------------------------------
-
--- Simplify all rules.
-{-# INLINEABLE interreduce #-}
-interreduce :: Function f => Config -> State f -> State f
-interreduce config@Config{..} state =
-  {-# SCC interreduce #-}
-  let
-    state' =
-      foldl' (interreduce1 config)
-        -- Clear out st_joinable, since we don't know which
-        -- equations have made use of each active.
-        state { st_joinable = Index.empty }
-        (IntMap.elems (st_active_ids state))
-    in state' { st_joinable = st_joinable state }
-
-{-# INLINEABLE interreduce1 #-}
-interreduce1 :: Function f => Config -> State f -> Active f -> State f
-interreduce1 config@Config{..} state active =
-  -- Exclude the active from the rewrite rules when testing
-  -- joinability, otherwise it will be trivially joinable.
-  case
-    joinCriticalPair cfg_join
-      (st_joinable state)
-      (st_rules (deleteActive state active))
-      (Just (active_model active)) (active_cp active)
-  of
-    Right (_, cps) ->
-      flip (foldl' (consider config)) cps $
-      message (DeleteActive active) $
-      deleteActive state active
-    Left (cp, model)
-      | not (cp_eqn cp `isInstanceOf` cp_eqn (active_cp active)) ->
-        flip (foldl' (addCP config model)) (split cp) $
-        message (DeleteActive active) $
-        deleteActive state active
-      | model /= active_model active ->
-        flip addActiveOnly active { active_model = model } $
-        deleteActive state active
-      | otherwise ->
-        state
-  where
-    (t :=: u) `isInstanceOf` (t' :=: u') = isJust $ do
-      sub <- match t' t
-      matchIn sub u' u
-
-
-----------------------------------------------------------------------
--- The main loop.
-----------------------------------------------------------------------
-
-data Output m f =
-  Output {
-    output_message :: Message f -> m () }
-
-{-# INLINE complete #-}
-complete :: (Function f, MonadIO m) => Output m f -> Config -> State f -> m (State f)
-complete Output{..} config@Config{..} state =
-  flip StateM.execStateT state $ do
-    tasks <- sequence
-      [newTask 1 (fromIntegral cfg_renormalise_percent / 100) $ do
-         lift $ output_message SimplifyQueue
-         state <- StateM.get
-         StateM.put $! simplifyQueue config state,
-       newTask 0.25 0.05 $ do
-         when cfg_simplify $ do
-           lift $ output_message Interreduce
-           state <- StateM.get
-           StateM.put $! interreduce config state]
-
-    let
-      loop = do
-        progress <- StateM.state (complete1 config)
-        state <- StateM.get
-        lift $ mapM_ output_message (messages state)
-        StateM.put (clearMessages state)
-        mapM_ checkTask tasks
-        when progress loop
-
-    loop
-
-{-# INLINEABLE complete1 #-}
-complete1 :: Function f => Config -> State f -> (Bool, State f)
-complete1 config@Config{..} state
-  | st_considered state >= cfg_max_critical_pairs =
-    (False, state)
-  | solved state = (False, state)
-  | otherwise =
-    case dequeue config state of
-      (Nothing, state) -> (False, state)
-      (Just (overlap, _, _), state) ->
-        (True, consider config state overlap)
-
-{-# INLINEABLE solved #-}
-solved :: Function f => State f -> Bool
-solved = not . null . solutions
-
--- Return whatever goals we have proved and their proofs.
-{-# INLINEABLE solutions #-}
-solutions :: Function f => State f -> [ProvedGoal f]
-solutions State{..} = {-# SCC solutions #-} do
-  Goal{goal_lhs = ts, goal_rhs = us, ..} <- st_goals
-  guard (not (null (Set.intersection ts us)))
-  let t:_ = filter (`Set.member` us) (Set.toList ts)
-      u:_ = filter (== t) (Set.toList us)
-      -- Strict so that we check the proof before returning a solution
-      !p =
-        Proof.certify $
-          reductionProof (reduction t) `Proof.trans`
-          Proof.symm (reductionProof (reduction u))
-  return (provedGoal goal_number goal_name p)
-
--- Return all current rewrite rules.
-{-# INLINEABLE rules #-}
-rules :: Function f => State f -> [Rule f]
-rules = map active_rule . IntMap.elems . st_active_ids
-
-----------------------------------------------------------------------
--- For code which uses twee as a library.
-----------------------------------------------------------------------
-
-{-# INLINEABLE completePure #-}
-completePure :: Function f => Config -> State f -> State f
-completePure cfg state
-  | progress = completePure cfg (clearMessages state')
-  | otherwise = state'
-  where
-    (progress, state') = complete1 cfg state
-
-{-# INLINEABLE normaliseTerm #-}
-normaliseTerm :: Function f => State f -> Term f -> Resulting f
-normaliseTerm State{..} t =
-  normaliseWith (const True) (rewrite reduces (index_all st_rules)) t
-
-{-# INLINEABLE simplifyTerm #-}
-simplifyTerm :: Function f => State f -> Term f -> Term f
-simplifyTerm State{..} t =
-  simplify (index_oriented st_rules) t
diff --git a/src/Twee/Base.hs b/src/Twee/Base.hs
deleted file mode 100644
--- a/src/Twee/Base.hs
+++ /dev/null
@@ -1,285 +0,0 @@
--- | Useful operations on terms and similar. Also re-exports some generally
--- useful modules such as 'Twee.Term' and 'Twee.Pretty'.
-
-{-# LANGUAGE TypeFamilies, FlexibleInstances, UndecidableInstances, DeriveFunctor, DefaultSignatures, FlexibleContexts, TypeOperators, MultiParamTypeClasses, GeneralizedNewtypeDeriving, ConstraintKinds, RecordWildCards #-}
-module Twee.Base(
-  -- * Re-exported functionality
-  module Twee.Term, module Twee.Pretty,
-  -- * The 'Symbolic' typeclass
-  Symbolic(..), subst, terms,
-  TermOf, TermListOf, SubstOf, TriangleSubstOf, BuilderOf, FunOf,
-  vars, isGround, funs, occ, occVar, canonicalise, renameAvoiding,
-  -- * General-purpose functionality
-  Id(..), Has(..),
-  -- * Typeclasses
-  Minimal(..), minimalTerm, isMinimal, erase,
-  Skolem(..), Arity(..), Sized(..), Ordered(..), lessThan, orientTerms, EqualsBonus(..), Strictness(..), Function, Extended(..)) where
-
-import Prelude hiding (lookup)
-import Control.Monad
-import qualified Data.DList as DList
-import Twee.Term hiding (subst, canonicalise)
-import qualified Twee.Term as Term
-import Twee.Pretty
-import Twee.Constraints hiding (funs)
-import Data.DList(DList)
-import Data.Typeable
-import Data.Int
-import Data.Maybe
-import qualified Data.IntMap.Strict as IntMap
-
--- | Represents a unique identifier (e.g., for a rule).
-newtype Id = Id { unId :: Int32 }
-  deriving (Eq, Ord, Show, Enum, Bounded, Num, Real, Integral)
-
-instance Pretty Id where
-  pPrint = text . show . unId
-
--- | Generalisation of term functionality to things that contain terms (e.g.,
--- rewrite rules and equations).
-class Symbolic a where
-  type ConstantOf a
-
-  -- | Compute a 'DList' of all terms which appear in the argument
-  -- (used for e.g. computing free variables).
-  -- See also 'terms'.
-  termsDL :: a -> DList (TermListOf a)
-
-  -- | Apply a substitution.
-  -- When using the 'Symbolic' type class, you can use 'subst' instead.
-  subst_ :: (Var -> BuilderOf a) -> a -> a
-
--- | Apply a substitution.
-subst :: (Symbolic a, Substitution s, SubstFun s ~ ConstantOf a) => s -> a -> a
-subst sub x = subst_ (evalSubst sub) x
-
--- | Find all terms occuring in the argument.
-terms :: Symbolic a => a -> [TermListOf a]
-terms = DList.toList . termsDL
-
--- | A term compatible with a given 'Symbolic'.
-type TermOf a = Term (ConstantOf a)
--- | A termlist compatible with a given 'Symbolic'.
-type TermListOf a = TermList (ConstantOf a)
--- | A substitution compatible with a given 'Symbolic'.
-type SubstOf a = Subst (ConstantOf a)
--- | A triangle substitution compatible with a given 'Symbolic'.
-type TriangleSubstOf a = TriangleSubst (ConstantOf a)
--- | A builder compatible with a given 'Symbolic'.
-type BuilderOf a = Builder (ConstantOf a)
--- | The underlying type of function symbols of a given 'Symbolic'.
-type FunOf a = Fun (ConstantOf a)
-
-instance Symbolic (Term f) where
-  type ConstantOf (Term f) = f
-  termsDL = return . singleton
-  subst_ sub = build . Term.subst sub
-
-instance Symbolic (TermList f) where
-  type ConstantOf (TermList f) = f
-  termsDL = return
-  subst_ sub = buildList . Term.substList sub
-
-instance Symbolic (Subst f) where
-  type ConstantOf (Subst f) = f
-  termsDL (Subst sub) = termsDL (IntMap.elems sub)
-  subst_ sub (Subst s) = Subst (fmap (subst_ sub) s)
-
-instance (ConstantOf a ~ ConstantOf b, Symbolic a, Symbolic b) => Symbolic (a, b) where
-  type ConstantOf (a, b) = ConstantOf a
-  termsDL (x, y) = termsDL x `mplus` termsDL y
-  subst_ sub (x, y) = (subst_ sub x, subst_ sub y)
-
-instance (ConstantOf a ~ ConstantOf b,
-          ConstantOf a ~ ConstantOf c,
-          Symbolic a, Symbolic b, Symbolic c) => Symbolic (a, b, c) where
-  type ConstantOf (a, b, c) = ConstantOf a
-  termsDL (x, y, z) = termsDL x `mplus` termsDL y `mplus` termsDL z
-  subst_ sub (x, y, z) = (subst_ sub x, subst_ sub y, subst_ sub z)
-
-instance Symbolic a => Symbolic [a] where
-  type ConstantOf [a] = ConstantOf a
-  termsDL xs = msum (map termsDL xs)
-  subst_ sub xs = map (subst_ sub) xs
-
-instance Symbolic a => Symbolic (Maybe a) where
-  type ConstantOf (Maybe a) = ConstantOf a
-  termsDL Nothing = mzero
-  termsDL (Just x) = termsDL x
-  subst_ sub x = fmap (subst_ sub) x
-
--- | An instance @'Has' a b@ indicates that a value of type @a@ contains a value
--- of type @b@ which is somehow part of the meaning of the @a@.
---
--- A number of functions use 'Has' constraints to work in a more general setting.
--- For example, the functions in 'Twee.CP' operate on rewrite rules, but actually
--- accept any @a@ satisfying @'Has' a ('Twee.Rule.Rule' f)@.
---
--- Use taste when definining 'Has' instances; don't do it willy-nilly.
-class Has a b where
-  -- | Get at the thing.
-  the :: a -> b
-
-instance Has a a where
-  the = id
-
--- | Find the variables occurring in the argument.
-{-# INLINE vars #-}
-vars :: Symbolic a => a -> [Var]
-vars x = [ v | t <- DList.toList (termsDL x), Var v <- subtermsList t ]
-
--- | Test if the argument is ground.
-{-# INLINE isGround #-}
-isGround :: Symbolic a => a -> Bool
-isGround = null . vars
-
--- | Find the function symbols occurring in the argument.
-{-# INLINE funs #-}
-funs :: Symbolic a => a -> [FunOf a]
-funs x = [ f | t <- DList.toList (termsDL x), App f _ <- subtermsList t ]
-
--- | Count how many times a function symbol occurs in the argument.
-{-# INLINE occ #-}
-occ :: Symbolic a => FunOf a -> a -> Int
-occ x t = length (filter (== x) (funs t))
-
--- | Count how many times a variable occurs in the argument.
-{-# INLINE occVar #-}
-occVar :: Symbolic a => Var -> a -> Int
-occVar x t = length (filter (== x) (vars t))
-
--- | Rename the argument so that variables are introduced in a canonical order
--- (starting with V0, then V1 and so on).
-{-# INLINEABLE canonicalise #-}
-canonicalise :: Symbolic a => a -> a
-canonicalise t = subst sub t
-  where
-    sub = Term.canonicalise (DList.toList (termsDL t))
-
--- | Rename the second argument so that it does not mention any variable which
--- occurs in the first.
-{-# INLINEABLE renameAvoiding #-}
-renameAvoiding :: (Symbolic a, Symbolic b) => a -> b -> b
-renameAvoiding x y
-  | x2 < y1 || y2 < x1 =
-    -- No overlap. Important in the case when x is ground,
-    -- in which case x2 == minBound and the calculation below doesn't work.
-    y
-  | otherwise =
-    -- Map y1 to x2+1
-    subst (\(V x) -> var (V (x-y1+x2+1))) y
-  where
-    (V x1, V x2) = boundLists (terms x)
-    (V y1, V y2) = boundLists (terms y)
-
--- | Check if a term is the minimal constant.
-isMinimal :: Minimal f => Term f -> Bool
-isMinimal (App f Empty) | f == minimal = True
-isMinimal _ = False
-
--- | Build the minimal constant as a term.
-minimalTerm :: Minimal f => Term f
-minimalTerm = build (con minimal)
-
--- | Erase a given set of variables from the argument, replacing them with the
--- minimal constant.
-erase :: (Symbolic a, ConstantOf a ~ f, Minimal f) => [Var] -> a -> a
-erase [] t = t
-erase xs t = subst sub t
-  where
-    sub = fromMaybe undefined $ listToSubst [(x, minimalTerm) | x <- xs]
-
--- | Construction of Skolem constants.
-class Skolem f where
-  -- | Turn a variable into a Skolem constant.
-  skolem  :: Var -> Fun f
-
--- | For types which have a notion of arity.
-class Arity f where
-  -- | Measure the arity.
-  arity :: f -> Int
-
-instance Arity f => Arity (Fun f) where
-  arity = arity . fun_value
-
--- | For types which have a notion of size.
-class Sized a where
-  -- | Compute the size.
-  size  :: a -> Int
-
-instance Sized f => Sized (Fun f) where
-  size = size . fun_value
-
-instance Sized f => Sized (TermList f) where
-  size = aux 0
-    where
-      aux n Empty = n
-      aux n (ConsSym (App f _) t) = aux (n+size f) t
-      aux n (Cons (Var _) t) = aux (n+1) t
-
-instance Sized f => Sized (Term f) where
-  size = size . singleton
-
--- | The collection of constraints which the type of function symbols must
--- satisfy in order to be used by twee.
-type Function f = (Ordered f, Arity f, Sized f, Minimal f, Skolem f, PrettyTerm f, EqualsBonus f)
-
--- | A hack for encoding Horn clauses. See 'Twee.CP.Score'.
--- The default implementation of 'hasEqualsBonus' should work OK.
-class EqualsBonus f where
-  hasEqualsBonus :: f -> Bool
-  hasEqualsBonus _ = False
-  isEquals, isTrue, isFalse :: f -> Bool
-  isEquals _ = False
-  isTrue _ = False
-  isFalse _ = False
-
-instance EqualsBonus f => EqualsBonus (Fun f) where
-  hasEqualsBonus = hasEqualsBonus . fun_value
-  isEquals = isEquals . fun_value
-  isTrue = isTrue . fun_value
-  isFalse = isFalse . fun_value
-
--- | A function symbol extended with a minimal constant and Skolem functions.
--- Comes equipped with 'Minimal' and 'Skolem' instances.
-data Extended f =
-    -- | The minimal constant.
-    Minimal
-    -- | A Skolem function.
-  | Skolem Var
-    -- | An ordinary function symbol.
-  | Function f
-  deriving (Eq, Ord, Show, Functor)
-
-instance Pretty f => Pretty (Extended f) where
-  pPrintPrec _ _ Minimal = text "?"
-  pPrintPrec _ _ (Skolem (V n)) = text "sk" <> pPrint n
-  pPrintPrec l p (Function f) = pPrintPrec l p f
-
-instance PrettyTerm f => PrettyTerm (Extended f) where
-  termStyle (Function f) = termStyle f
-  termStyle _ = uncurried
-
-instance Sized f => Sized (Extended f) where
-  size (Function f) = size f
-  size _ = 1
-
-instance Arity f => Arity (Extended f) where
-  arity (Function f) = arity f
-  arity _ = 0
-
-instance (Typeable f, Ord f) => Minimal (Extended f) where
-  minimal = fun Minimal
-
-instance (Typeable f, Ord f) => Skolem (Extended f) where
-  skolem x = fun (Skolem x)
-
-instance EqualsBonus f => EqualsBonus (Extended f) where
-  hasEqualsBonus (Function f) = hasEqualsBonus f
-  hasEqualsBonus _ = False
-  isEquals (Function f) = isEquals f
-  isEquals _ = False
-  isTrue (Function f) = isTrue f
-  isTrue _ = False
-  isFalse (Function f) = isFalse f
-  isFalse _ = False
diff --git a/src/Twee/CP.hs b/src/Twee/CP.hs
deleted file mode 100644
--- a/src/Twee/CP.hs
+++ /dev/null
@@ -1,328 +0,0 @@
--- | Critical pair generation.
-{-# LANGUAGE BangPatterns, FlexibleContexts, ScopedTypeVariables, MultiParamTypeClasses, RecordWildCards, OverloadedStrings, TypeFamilies, GeneralizedNewtypeDeriving #-}
-module Twee.CP where
-
-import qualified Twee.Term as Term
-import Twee.Base
-import Twee.Rule
-import Twee.Index(Index)
-import qualified Data.Set as Set
-import Control.Monad
-import Data.Maybe
-import Data.List
-import qualified Data.ChurchList as ChurchList
-import Data.ChurchList (ChurchList(..))
-import Twee.Utils
-import Twee.Equation
-import qualified Twee.Proof as Proof
-import Twee.Proof(Derivation, Lemma, congPath)
-
--- | The set of positions at which a term can have critical overlaps.
-data Positions f = NilP | ConsP {-# UNPACK #-} !Int !(Positions f)
-type PositionsOf a = Positions (ConstantOf a)
-
-instance Show (Positions f) where
-  show = show . ChurchList.toList . positionsChurch
-
--- | Calculate the set of positions for a term.
-positions :: Term f -> Positions f
-positions t = aux 0 Set.empty (singleton t)
-  where
-    -- Consider only general superpositions.
-    aux !_ !_ Empty = NilP
-    aux n m (Cons (Var _) t) = aux (n+1) m t
-    aux n m (ConsSym t@App{} u)
-      | t `Set.member` m = aux (n+1) m u
-      | otherwise = ConsP n (aux (n+1) (Set.insert t m) u)
-
-{-# INLINE positionsChurch #-}
-positionsChurch :: Positions f -> ChurchList Int
-positionsChurch posns =
-  ChurchList $ \c n ->
-    let
-      pos NilP = n
-      pos (ConsP x posns) = c x (pos posns)
-    in
-      pos posns
-
--- | A critical overlap of one rule with another.
-data Overlap f =
-  Overlap {
-    -- | The depth (1 for CPs of axioms, 2 for CPs whose rules have depth 1, etc.)
-    overlap_depth :: {-# UNPACK #-} !Depth,
-    -- | The critical term.
-    overlap_top   :: {-# UNPACK #-} !(Term f),
-    -- | The part of the critical term which the inner rule rewrites.
-    overlap_inner :: {-# UNPACK #-} !(Term f),
-    -- | The position in the critical term which is rewritten.
-    overlap_pos   :: {-# UNPACK #-} !Int,
-    -- | The critical pair itself.
-    overlap_eqn   :: {-# UNPACK #-} !(Equation f) }
-  deriving Show
-type OverlapOf a = Overlap (ConstantOf a)
-
--- | Represents the depth of a critical pair.
-newtype Depth = Depth Int deriving (Eq, Ord, Num, Real, Enum, Integral, Show)
-
--- | Compute all overlaps of a rule with a set of rules.
-{-# INLINEABLE overlaps #-}
-overlaps ::
-  (Function f, Has a (Rule f), Has a (Positions f), Has a Depth) =>
-  Depth -> Index f a -> [a] -> a -> [(a, a, Overlap f)]
-overlaps max_depth idx rules r =
-  ChurchList.toList (overlapsChurch max_depth idx rules r)
-
-{-# INLINE overlapsChurch #-}
-overlapsChurch :: forall f a.
-  (Function f, Has a (Rule f), Has a (Positions f), Has a Depth) =>
-  Depth -> Index f a -> [a] -> a -> ChurchList (a, a, Overlap f)
-overlapsChurch max_depth idx rules r1 = do
-  guard (the r1 < max_depth)
-  r2 <- ChurchList.fromList rules
-  guard (the r2 < max_depth)
-  let !depth = 1 + max (the r1) (the r2)
-  do { o <- asymmetricOverlaps idx depth (the r1) r1' (the r2); return (r1, r2, o) } `mplus`
-    do { o <- asymmetricOverlaps idx depth (the r2) (the r2) r1'; return (r2, r1, o) }
-  where
-    !r1' = renameAvoiding (map the rules :: [Rule f]) (the r1)
-
-{-# INLINE asymmetricOverlaps #-}
-asymmetricOverlaps ::
-  (Function f, Has a (Rule f), Has a Depth) =>
-  Index f a -> Depth -> Positions f -> Rule f -> Rule f -> ChurchList (Overlap f)
-asymmetricOverlaps idx depth posns r1 r2 = do
-  n <- positionsChurch posns
-  ChurchList.fromMaybe $
-    overlapAt n depth r1 r2 >>=
-    simplifyOverlap idx
-
--- | Create an overlap at a particular position in a term.
--- Doesn't simplify the overlap.
-{-# INLINE overlapAt #-}
-overlapAt :: Int -> Depth -> Rule f -> Rule f -> Maybe (Overlap f)
-overlapAt !n !depth (Rule _ !outer !outer') (Rule _ !inner !inner') = do
-  let t = at n (singleton outer)
-  sub <- unifyTri inner t
-  let
-    top = {-# SCC overlap_top #-} termSubst sub outer
-    innerTerm = {-# SCC overlap_inner #-} termSubst sub inner
-    -- Make sure to keep in sync with overlapProof
-    lhs = {-# SCC overlap_eqn_1 #-} termSubst sub outer'
-    rhs = {-# SCC overlap_eqn_2 #-}
-      buildReplacePositionSub sub n (singleton inner') (singleton outer)
-
-  guard (lhs /= rhs)
-  return Overlap {
-    overlap_depth = depth,
-    overlap_top = top,
-    overlap_inner = innerTerm,
-    overlap_pos = n,
-    overlap_eqn = lhs :=: rhs }
-
--- | Simplify an overlap and remove it if it's trivial.
-{-# INLINE simplifyOverlap #-}
-simplifyOverlap :: (Function f, Has a (Rule f)) => Index f a -> Overlap f -> Maybe (Overlap f)
-simplifyOverlap idx overlap@Overlap{overlap_eqn = lhs :=: rhs, ..}
-  | lhs == rhs'  = Nothing
-  | lhs' == rhs' = Nothing
-  | otherwise = Just overlap{overlap_eqn = lhs' :=: rhs'}
-  where
-    lhs' = simplify idx lhs
-    rhs' = simplify idx rhs
-
--- Put these in separate functions to avoid code blowup
-buildReplacePositionSub :: TriangleSubst f -> Int -> TermList f -> TermList f -> Term f
-buildReplacePositionSub !sub !n !inner' !outer =
-  build (replacePositionSub sub n inner' outer)
-
-termSubst :: TriangleSubst f -> Term f -> Term f
-termSubst sub t = build (Term.subst sub t)
-
--- | The configuration for the critical pair weighting heuristic.
-data Config =
-  Config {
-    cfg_lhsweight :: !Int,
-    cfg_rhsweight :: !Int,
-    cfg_funweight :: !Int,
-    cfg_varweight :: !Int,
-    cfg_depthweight :: !Int,
-    cfg_dupcost :: !Int,
-    cfg_dupfactor :: !Int }
-
--- | The default heuristic configuration.
-defaultConfig :: Config
-defaultConfig =
-  Config {
-    cfg_lhsweight = 3,
-    cfg_rhsweight = 1,
-    cfg_funweight = 7,
-    cfg_varweight = 6,
-    cfg_depthweight = 16,
-    cfg_dupcost = 7,
-    cfg_dupfactor = 0 }
-
--- | Compute a score for a critical pair.
-
--- We compute:
---   cfg_lhsweight * size l + cfg_rhsweight * size r
--- where l is the biggest term and r is the smallest,
--- and variables have weight 1 and functions have weight cfg_funweight.
-{-# INLINEABLE score #-}
-score :: Function f => Config -> Overlap f -> Int
-score Config{..} Overlap{..} =
-  fromIntegral overlap_depth * cfg_depthweight +
-  (m + n) * cfg_rhsweight +
-  intMax m n * (cfg_lhsweight - cfg_rhsweight)
-  where
-    l :=: r = overlap_eqn
-    m = size' 0 (singleton l)
-    n = size' 0 (singleton r)
-
-    size' !n Empty = n
-    size' n (Cons t ts)
-      | len t > 1, t `isSubtermOfList` ts =
-        size' (n+cfg_dupcost+cfg_dupfactor*size t) ts
-    size' n ts
-      | Cons (App f (Cons a (Cons b us))) vs <- ts,
-        hasEqualsBonus (fun_value f), isJust (unify a b) =
-        size' (size' (n+1) us) vs
-    size' n (Cons (Var _) ts) =
-      size' (n+cfg_varweight) ts
-    size' n (ConsSym (App f _) ts) =
-      size' (n+cfg_funweight*size f) ts
-
-----------------------------------------------------------------------
--- * Higher-level handling of critical pairs.
-----------------------------------------------------------------------
-
--- | A critical pair together with information about how it was derived
-data CriticalPair f =
-  CriticalPair {
-    -- | The critical pair itself.
-    cp_eqn   :: {-# UNPACK #-} !(Equation f),
-    -- | The depth of the critical pair.
-    cp_depth :: {-# UNPACK #-} !Depth,
-    -- | The critical term, if there is one.
-    -- (Axioms do not have a critical term.)
-    cp_top   :: !(Maybe (Term f)),
-    -- | A derivation of the critical pair from the axioms.
-    cp_proof :: !(Derivation f) }
-
-instance Symbolic (CriticalPair f) where
-  type ConstantOf (CriticalPair f) = f
-  termsDL CriticalPair{..} =
-    termsDL cp_eqn `mplus` termsDL cp_top `mplus` termsDL cp_proof
-  subst_ sub CriticalPair{..} =
-    CriticalPair {
-      cp_eqn = subst_ sub cp_eqn,
-      cp_depth = cp_depth,
-      cp_top = subst_ sub cp_top,
-      cp_proof = subst_ sub cp_proof }
-
-instance PrettyTerm f => Pretty (CriticalPair f) where
-  pPrint CriticalPair{..} =
-    vcat [
-      pPrint cp_eqn,
-      nest 2 (text "top:" <+> pPrint cp_top) ]
-
--- | Split a critical pair so that it can be turned into rules.
---
--- The resulting critical pairs have the property that no variable appears on
--- the right that is not on the left.
-
--- See the comment below.
-split :: Function f => CriticalPair f -> [CriticalPair f]
-split CriticalPair{cp_eqn = l :=: r, ..}
-  | l == r = []
-  | otherwise =
-    -- If we have something which is almost a rule, except that some
-    -- variables appear only on the right-hand side, e.g.:
-    --   f x y -> g x z
-    -- then we replace it with the following two rules:
-    --   f x y -> g x ?
-    --   g x z -> g x ?
-    -- where the second rule is weakly oriented and ? is the minimal
-    -- constant.
-    --
-    -- If we have an unoriented equation with a similar problem, e.g.:
-    --   f x y = g x z
-    -- then we replace it with potentially three rules:
-    --   f x ? = g x ?
-    --   f x y -> f x ?
-    --   g x z -> g x ?
-
-    -- The main rule l -> r' or r -> l' or l' = r'
-    [ CriticalPair {
-        cp_eqn   = l :=: r',
-        cp_depth = cp_depth,
-        cp_top   = eraseExcept (vars l) cp_top,
-        cp_proof = eraseExcept (vars l) cp_proof }
-    | ord == Just GT ] ++
-    [ CriticalPair {
-        cp_eqn   = r :=: l',
-        cp_depth = cp_depth,
-        cp_top   = eraseExcept (vars r) cp_top,
-        cp_proof = Proof.symm (eraseExcept (vars r) cp_proof) }
-    | ord == Just LT ] ++
-    [ CriticalPair {
-        cp_eqn   = l' :=: r',
-        cp_depth = cp_depth,
-        cp_top   = eraseExcept (vars l) $ eraseExcept (vars r) cp_top,
-        cp_proof = eraseExcept (vars l) $ eraseExcept (vars r) cp_proof }
-    | ord == Nothing ] ++
-
-    -- Weak rules l -> l' or r -> r'
-    [ CriticalPair {
-        cp_eqn   = l :=: l',
-        cp_depth = cp_depth + 1,
-        cp_top   = Nothing,
-        cp_proof = cp_proof `Proof.trans` Proof.symm (erase ls cp_proof) }
-    | not (null ls), ord /= Just GT ] ++
-    [ CriticalPair {
-        cp_eqn   = r :=: r',
-        cp_depth = cp_depth + 1,
-        cp_top   = Nothing,
-        cp_proof = Proof.symm cp_proof `Proof.trans` erase rs cp_proof }
-    | not (null rs), ord /= Just LT ]
-    where
-      ord = orientTerms l' r'
-      l' = erase ls l
-      r' = erase rs r
-      ls = usort (vars l) \\ usort (vars r)
-      rs = usort (vars r) \\ usort (vars l)
-
-      eraseExcept vs t =
-        erase (usort (vars t) \\ usort vs) t
-
--- | Make a critical pair from two rules and an overlap.
-{-# INLINEABLE makeCriticalPair #-}
-makeCriticalPair ::
-  (Has a (Rule f), Has a (Lemma f), Has a Id, Function f) =>
-  a -> a -> Overlap f -> Maybe (CriticalPair f)
-makeCriticalPair r1 r2 overlap@Overlap{..}
-  | lessEq overlap_top t = Nothing
-  | lessEq overlap_top u = Nothing
-  | otherwise =
-    Just $
-      CriticalPair overlap_eqn
-        overlap_depth
-        (Just overlap_top)
-        (overlapProof r1 r2 overlap)
-  where
-    t :=: u = overlap_eqn
-
--- | Return a proof for a critical pair.
-{-# INLINEABLE overlapProof #-}
-overlapProof ::
-  forall a f.
-  (Has a (Rule f), Has a (Lemma f), Has a Id) =>
-  a -> a -> Overlap f -> Derivation f
-overlapProof left right Overlap{..} =
-  Proof.symm (reductionProof (step left leftSub))
-  `Proof.trans`
-  congPath path overlap_top (reductionProof (step right rightSub))
-  where
-    Just leftSub = match (lhs (the left)) overlap_top
-    Just rightSub = match (lhs (the right)) overlap_inner
-
-    path = positionToPath (lhs (the left) :: Term f) overlap_pos
diff --git a/src/Twee/Constraints.hs b/src/Twee/Constraints.hs
deleted file mode 100644
--- a/src/Twee/Constraints.hs
+++ /dev/null
@@ -1,312 +0,0 @@
-{-# LANGUAGE FlexibleContexts, UndecidableInstances, RecordWildCards #-}
--- | Solving constraints on variable ordering.
-module Twee.Constraints where
-
---import Twee.Base hiding (equals, Term, pattern Fun, pattern Var, lookup, funs)
-import qualified Twee.Term as Flat
-import qualified Data.Map.Strict as Map
-import Twee.Pretty hiding (equals)
-import Twee.Utils
-import Data.Maybe
-import Data.List
-import Data.Function
-import Data.Graph
-import Data.Map.Strict(Map)
-import Data.Ord
-import Twee.Term hiding (lookup)
-
-data Atom f = Constant (Fun f) | Variable Var deriving (Show, Eq, Ord)
-
-{-# INLINE atoms #-}
-atoms :: Term f -> [Atom f]
-atoms t = aux (singleton t)
-  where
-    aux Empty = []
-    aux (Cons (App f Empty) t) = Constant f:aux t
-    aux (Cons (Var x) t) = Variable x:aux t
-    aux (ConsSym _ t) = aux t
-
-toTerm :: Atom f -> Term f
-toTerm (Constant f) = build (con f)
-toTerm (Variable x) = build (var x)
-
-fromTerm :: Flat.Term f -> Maybe (Atom f)
-fromTerm (App f Empty) = Just (Constant f)
-fromTerm (Var x) = Just (Variable x)
-fromTerm _ = Nothing
-
-instance PrettyTerm f => Pretty (Atom f) where
-  pPrint = pPrint . toTerm
-
-data Formula f =
-    Less   (Atom f) (Atom f)
-  | LessEq (Atom f) (Atom f)
-  | And [Formula f]
-  | Or  [Formula f]
-  deriving (Eq, Ord, Show)
-
-instance PrettyTerm f => Pretty (Formula f) where
-  pPrintPrec _ _ (Less t u) = hang (pPrint t <+> text "<") 2 (pPrint u)
-  pPrintPrec _ _ (LessEq t u) = hang (pPrint t <+> text "<=") 2 (pPrint u)
-  pPrintPrec _ _ (And []) = text "true"
-  pPrintPrec _ _ (Or []) = text "false"
-  pPrintPrec l p (And xs) =
-    maybeParens (p > 10)
-      (fsep (punctuate (text " &") (nest_ (map (pPrintPrec l 11) xs))))
-    where
-      nest_ (x:xs) = x:map (nest 2) xs
-      nest_ [] = undefined
-  pPrintPrec l p (Or xs) =
-    maybeParens (p > 10)
-      (fsep (punctuate (text " |") (nest_ (map (pPrintPrec l 11) xs))))
-    where
-      nest_ (x:xs) = x:map (nest 2) xs
-      nest_ [] = undefined
-
-negateFormula :: Formula f -> Formula f
-negateFormula (Less t u) = LessEq u t
-negateFormula (LessEq t u) = Less u t
-negateFormula (And ts) = Or (map negateFormula ts)
-negateFormula (Or ts) = And (map negateFormula ts)
-
-conj forms
-  | false `elem` forms' = false
-  | otherwise =
-    case forms' of
-      [x] -> x
-      xs  -> And xs
-  where
-    flatten (And xs) = xs
-    flatten x = [x]
-    forms' = filter (/= true) (usort (concatMap flatten forms))
-disj forms
-  | true `elem` forms' = true
-  | otherwise =
-    case forms' of
-      [x] -> x
-      xs  -> Or xs
-  where
-    flatten (Or xs) = xs
-    flatten x = [x]
-    forms' = filter (/= false) (usort (concatMap flatten forms))
-
-x &&& y = conj [x, y]
-x ||| y = disj [x, y]
-true  = And []
-false = Or []
-
-data Branch f =
-  -- Branches are kept normalised wrt equals
-  Branch {
-    funs        :: [Fun f],
-    less        :: [(Atom f, Atom f)],  -- sorted
-    equals      :: [(Atom f, Atom f)] } -- sorted, greatest atom first in each pair
-  deriving (Eq, Ord)
-
-instance PrettyTerm f => Pretty (Branch f) where
-  pPrint Branch{..} =
-    braces $ fsep $ punctuate (text ",") $
-      [pPrint x <+> text "<" <+> pPrint y | (x, y) <- less ] ++
-      [pPrint x <+> text "=" <+> pPrint y | (x, y) <- equals ]
-
-trueBranch :: Branch f
-trueBranch = Branch [] [] []
-
-norm :: Eq f => Branch f -> Atom f -> Atom f
-norm Branch{..} x = fromMaybe x (lookup x equals)
-
-contradictory :: (Minimal f, Ord f) => Branch f -> Bool
-contradictory Branch{..} =
-  or [f == minimal | (_, Constant f) <- less] ||
-  or [f /= g | (Constant f, Constant g) <- equals] ||
-  any cyclic (stronglyConnComp
-    [(x, x, [y | (x', y) <- less, x == x']) | x <- usort (map fst less)])
-  where
-    cyclic (AcyclicSCC _) = False
-    cyclic (CyclicSCC _) = True
-
-formAnd :: (Minimal f, Ordered f) => Formula f -> [Branch f] -> [Branch f]
-formAnd f bs = usort (bs >>= add f)
-  where
-    add (Less t u) b = addLess t u b
-    add (LessEq t u) b = addLess t u b ++ addEquals t u b
-    add (And []) b = [b]
-    add (And (f:fs)) b = add f b >>= add (And fs)
-    add (Or fs) b = usort (concat [ add f b | f <- fs ])
-
-branches :: (Minimal f, Ordered f) => Formula f -> [Branch f]
-branches x = aux [x]
-  where
-    aux [] = [Branch [] [] []]
-    aux (And xs:ys) = aux (xs ++ ys)
-    aux (Or xs:ys) = usort $ concat [aux (x:ys) | x <- xs]
-    aux (Less t u:xs) = usort $ concatMap (addLess t u) (aux xs)
-    aux (LessEq t u:xs) =
-      usort $
-      concatMap (addLess t u) (aux xs) ++
-      concatMap (addEquals u t) (aux xs)
-
-addLess :: (Minimal f, Ordered f) => Atom f -> Atom f -> Branch f -> [Branch f]
-addLess _ (Constant min) _ | min == minimal = []
-addLess (Constant min) _ b | min == minimal = [b]
-addLess t0 u0 b@Branch{..} =
-  filter (not . contradictory)
-    [addTerm t (addTerm u b{less = usort ((t, u):less)})]
-  where
-    t = norm b t0
-    u = norm b u0
-
-addEquals :: (Minimal f, Ordered f) => Atom f -> Atom f -> Branch f -> [Branch f]
-addEquals t0 u0 b@Branch{..}
-  | t == u || (t, u) `elem` equals = [b]
-  | otherwise =
-    filter (not . contradictory)
-      [addTerm t (addTerm u b {
-         equals      = usort $ (t, u):[(x', y') | (x, y) <- equals, let (y', x') = sort2 (sub x, sub y), x' /= y'],
-         less        = usort $ [(sub x, sub y) | (x, y) <- less] })]
-  where
-    sort2 (x, y) = (min x y, max x y)
-    (u, t) = sort2 (norm b t0, norm b u0)
-
-    sub x
-      | x == t = u
-      | otherwise = x
-
-addTerm :: (Minimal f, Ordered f) => Atom f -> Branch f -> Branch f
-addTerm (Constant f) b
-  | f `notElem` funs b =
-    b {
-      funs = f:funs b,
-      less =
-        usort $
-          [ (Constant f, Constant g) | g <- funs b, f << g ] ++
-          [ (Constant g, Constant f) | g <- funs b, g << f ] ++ less b }
-addTerm _ b = b
-
-newtype Model f = Model (Map (Atom f) (Int, Int))
-  deriving (Eq, Show)
--- Representation: map from atom to (major, minor)
--- x <  y if major x < major y
--- x <= y if major x = major y and minor x < minor y
-
-instance PrettyTerm f => Pretty (Model f) where
-  pPrint (Model m)
-    | Map.size m <= 1 = text "empty"
-    | otherwise = fsep (go (sortBy (comparing snd) (Map.toList m)))
-      where
-        go [(x, _)] = [pPrint x]
-        go ((x, (i, _)):xs@((_, (j, _)):_)) =
-          (pPrint x <+> text rel):go xs
-          where
-            rel = if i == j then "<=" else "<"
-
-modelToLiterals :: Model f -> [Formula f]
-modelToLiterals (Model m) = go (sortBy (comparing snd) (Map.toList m))
-  where
-    go []  = []
-    go [_] = []
-    go ((x, (i, _)):xs@((y, (j, _)):_)) =
-      rel x y:go xs
-      where
-        rel = if i == j then LessEq else Less
-
-modelFromOrder :: (Minimal f, Ord f) => [Atom f] -> Model f
-modelFromOrder xs =
-  Model (Map.fromList [(x, (i, i)) | (x, i) <- zip xs [0..]])
-
-weakenModel :: Model f -> [Model f]
-weakenModel (Model m) =
-  [ Model (Map.delete x m) | x <- Map.keys m ] ++
-  [ Model (Map.fromList xs)
-  | xs <- glue (sortBy (comparing snd) (Map.toList m)),
-    all ok (groupBy ((==) `on` (fst . snd)) xs) ]
-  where
-    glue [] = []
-    glue [_] = []
-    glue (a@(_x, (i1, j1)):b@(y, (i2, _)):xs) =
-      [ (a:(y, (i1, j1+1)):xs) | i1 < i2 ] ++
-      map (a:) (glue (b:xs))
-
-    -- We must never make two constants equal
-    ok xs = length [x | (Constant x, _) <- xs] <= 1
-
-varInModel :: (Minimal f, Ord f) => Model f -> Var -> Bool
-varInModel (Model m) x = Variable x `Map.member` m
-
-varGroups :: (Minimal f, Ord f) => Model f -> [(Fun f, [Var], Maybe (Fun f))]
-varGroups (Model m) = filter nonempty (go minimal (map fst (sortBy (comparing snd) (Map.toList m))))
-  where
-    go f xs =
-      case span isVariable xs of
-        (_, []) -> [(f, map unVariable xs, Nothing)]
-        (ys, Constant g:zs) ->
-          (f, map unVariable ys, Just g):go g zs
-    isVariable (Constant _) = False
-    isVariable (Variable _) = True
-    unVariable (Variable x) = x
-    nonempty (_, [], _) = False
-    nonempty _ = True
-
-class Minimal f where
-  minimal :: Fun f
-
-{-# INLINE lessEqInModel #-}
-lessEqInModel :: (Minimal f, Ordered f) => Model f -> Atom f -> Atom f -> Maybe Strictness
-lessEqInModel (Model m) x y
-  | Just (a, _) <- Map.lookup x m,
-    Just (b, _) <- Map.lookup y m,
-    a < b = Just Strict
-  | Just a <- Map.lookup x m,
-    Just b <- Map.lookup y m,
-    a < b = Just Nonstrict
-  | x == y = Just Nonstrict
-  | Constant a <- x, Constant b <- y, a << b = Just Strict
-  | Constant a <- x, a == minimal = Just Nonstrict
-  | otherwise = Nothing
-
-solve :: (Minimal f, Ordered f, PrettyTerm f) => [Atom f] -> Branch f -> Either (Model f) (Subst f)
-solve xs branch@Branch{..}
-  | null equals && not (all true less) =
-    error $ "Model " ++ prettyShow model ++ " is not a model of " ++ prettyShow branch ++ " (edges = " ++ prettyShow edges ++ ", vs = " ++ prettyShow vs ++ ")"
-  | null equals = Left model
-  | otherwise = Right sub
-    where
-      sub = fromMaybe undefined . listToSubst $
-        [(x, toTerm y) | (Variable x, y) <- equals] ++
-        [(y, toTerm x) | (x@Constant{}, Variable y) <- equals]
-      vs = Constant minimal:reverse (flattenSCCs (stronglyConnComp edges))
-      edges = [(x, x, [y | (x', y) <- less', x == x']) | x <- as, x /= Constant minimal]
-      less' = less ++ [(Constant x, Constant y) | Constant x <- as, Constant y <- as, x << y]
-      as = usort $ xs ++ map fst less ++ map snd less
-      model = modelFromOrder vs
-      true (t, u) = lessEqInModel model t u == Just Strict
-
-class Ord f => Ordered f where
-  -- | Return 'True' if the first term is less than or equal to the second,
-  -- in the term ordering.
-  lessEq :: Term f -> Term f -> Bool
-  -- | Check if the first term is less than or equal to the second in the given model,
-  -- and decide whether the inequality is strict or nonstrict.
-  lessIn :: Model f -> Term f -> Term f -> Maybe Strictness
-
--- | Describes whether an inequality is strict or nonstrict.
-data Strictness =
-    -- | The first term is strictly less than the second.
-    Strict
-    -- | The first term is less than or equal to the second.
-  | Nonstrict deriving (Eq, Show)
-
--- | Return 'True' if the first argument is strictly less than the second,
--- in the term ordering.
-lessThan :: Ordered f => Term f -> Term f -> Bool
-lessThan t u = lessEq t u && isNothing (unify t u)
-
--- | Return the direction in which the terms are oriented according to the term
--- ordering, or 'Nothing' if they cannot be oriented. A result of @'Just' 'LT'@
--- means that the first term is less than /or equal to/ the second.
-orientTerms :: Ordered f => Term f -> Term f -> Maybe Ordering
-orientTerms t u
-  | t == u = Just EQ
-  | lessEq t u = Just LT
-  | lessEq u t = Just GT
-  | otherwise = Nothing
diff --git a/src/Twee/Equation.hs b/src/Twee/Equation.hs
deleted file mode 100644
--- a/src/Twee/Equation.hs
+++ /dev/null
@@ -1,58 +0,0 @@
--- | Equations.
-{-# LANGUAGE TypeFamilies #-}
-module Twee.Equation where
-
-import Twee.Base
-import Data.Maybe
-import Control.Monad
-
---------------------------------------------------------------------------------
--- * Equations.
---------------------------------------------------------------------------------
-
-data Equation f =
-  (:=:) {
-    eqn_lhs :: {-# UNPACK #-} !(Term f),
-    eqn_rhs :: {-# UNPACK #-} !(Term f) }
-  deriving (Eq, Ord, Show)
-type EquationOf a = Equation (ConstantOf a)
-
-instance Symbolic (Equation f) where
-  type ConstantOf (Equation f) = f
-  termsDL (t :=: u) = termsDL t `mplus` termsDL u
-  subst_ sub (t :=: u) = subst_ sub t :=: subst_ sub u
-
-instance PrettyTerm f => Pretty (Equation f) where
-  pPrint (x :=: y) = pPrint x <+> text "=" <+> pPrint y
-
-instance Sized f => Sized (Equation f) where
-  size (x :=: y) = size x + size y
-
--- | Order an equation roughly left-to-right.
--- However, there is no guarantee that the result is oriented.
-order :: Function f => Equation f -> Equation f
-order (l :=: r)
-  | l == r = l :=: r
-  | otherwise =
-    case compare (size l) (size r) of
-      LT -> r :=: l
-      GT -> l :=: r
-      EQ -> if lessEq l r then r :=: l else l :=: r
-
--- | Apply a function to both sides of an equation.
-bothSides :: (Term f -> Term f') -> Equation f -> Equation f'
-bothSides f (t :=: u) = f t :=: f u
-
--- | Is an equation of the form t = t?
-trivial :: Eq f => Equation f -> Bool
-trivial (t :=: u) = t == u
-
-simplerThan :: Function f => Equation f -> Equation f -> Bool
-eq1 `simplerThan` eq2 =
-  t1 `lessEq` t2 &&
-  (isNothing (unify t1 t2) || (u1 `lessEq` u2))
-  where
-    t1 :=: u1 = skolemise eq1
-    t2 :=: u2 = skolemise eq2
-
-    skolemise = subst (con . skolem)
diff --git a/src/Twee/Index.hs b/src/Twee/Index.hs
deleted file mode 100644
--- a/src/Twee/Index.hs
+++ /dev/null
@@ -1,310 +0,0 @@
--- | A term index to accelerate matching.
--- An index is a multimap from terms to arbitrary values.
---
--- The type of query supported is: given a search term, find all keys such that
--- the search term is an instance of the key, and return the corresponding
--- values.
-
-{-# LANGUAGE BangPatterns, RecordWildCards, OverloadedStrings, FlexibleContexts #-}
--- We get some bogus warnings because of pattern synonyms.
-{-# OPTIONS_GHC -fno-warn-overlapping-patterns #-}
-module Twee.Index(
-  Index,
-  empty,
-  null,
-  singleton,
-  insert,
-  delete,
-  lookup,
-  matches,
-  approxMatches,
-  elems) where
-
-import qualified Prelude
-import Prelude hiding (null, lookup)
-import Data.Maybe
-import Twee.Base hiding (var, fun, empty, size, singleton, prefix, funs, lookupList, lookup)
-import qualified Twee.Term as Term
-import Twee.Term.Core(TermList(..))
-import Data.DynamicArray
-import qualified Data.List as List
-
--- The term index in this module is an _imperfect discrimination tree_.
--- This is a trie whose keys are terms, represented as flat lists of symbols,
--- but where all variables have been replaced by a single don't-care variable '_'.
--- That is, the edges of the trie can be either function symbols or '_'.
--- To insert a key-value pair into the discrimination tree, we first replace all
--- variables in the key with '_', and then do ordinary trie insertion.
---
--- Lookup maintains a term list, which is initially the search term.
--- It proceeds down the trie, consuming bits of the term list as it goes.
---
--- If the current trie node has an edge for a function symbol f, and the term at
--- the head of the term list is f(t1..tn), we can follow the f edge. We then
--- delete f from the term list, but keep t1..tn at the front of the term list.
--- (In other words we delete only the symbol f and not its arguments.)
---
--- If the current trie node has an edge for '_', we can always follow that edge.
--- We then remove the head term from the term list, as the '_' represents a
--- variable that should match that whole term.
---
--- If the term list ever becomes empty, we have a possible match. We then
--- do matching on the values stored at the current node to see if they are
--- genuine matches.
---
--- Often there are two edges we can follow (function symbol and '_'), and in
--- that case the algorithm uses backtracking.
-
--- | A term index: a multimap from @'Term' f@ to @a@.
-data Index f a =
-  -- A non-empty index.
-  Index {
-    -- Size of smallest term in index.
-    size   :: {-# UNPACK #-} !Int,
-    -- When all keys in the index start with the same sequence of symbols, we
-    -- compress them into this prefix; the "fun" and "var" fields below refer to
-    -- the first symbol _after_ the prefix, and the "here" field contains values
-    -- whose remaining key is exactly this prefix.
-    prefix :: {-# UNPACK #-} !(TermList f),
-    -- The values that are found at this node.
-    here   :: [a],
-    -- Function symbol edges.
-    -- The array is indexed by function number.
-    fun    :: {-# UNPACK #-} !(Array (Index f a)),
-    -- Variable edge.
-    var    :: !(Index f a) } |
-  -- An empty index.
-  Nil
-  deriving Show
-
-instance Default (Index f a) where def = Nil
-
--- To get predictable performance, the lookup function uses an explicit stack
--- instead of recursion to control backtracking.
-data Stack f a =
-  -- A normal stack frame: records the current index node and term.
-  Frame {
-    frame_term  :: {-# UNPACK #-} !(TermList f),
-    frame_index :: !(Index f a),
-    frame_rest  :: !(Stack f a) }
-  -- A stack frame which is used when we have found a match.
-  | Yield {
-    yield_found :: [a],
-    yield_rest  :: !(Stack f a) }
-  -- End of stack.
-  | Stop
-
--- Turn a stack into a list of results.
-run :: Stack f a -> [a]
-run Stop = []
-run Frame{..} = run ({-# SCC run_inner #-} step frame_term frame_index frame_rest)
-run Yield{..} = {-# SCC run_found #-} yield_found ++ run yield_rest
-
--- Execute a single stack frame.
-{-# INLINE step #-}
-step :: TermList f -> Index f a -> Stack f a -> Stack f a
-step !_ _ _ | False = undefined
-step t idx rest =
-  case idx of
-    Nil -> rest
-    Index{..}
-      | lenList t < size ->
-        rest -- the search term is smaller than any in this index
-      | otherwise ->
-        pref t prefix here fun var rest
-
--- The main work of 'step' goes on here.
--- It is carefully tweaked to generate nice code,
--- including using UnsafeCons and only casing on each
--- term list exactly once.
-pref :: TermList f -> TermList f -> [a] -> Array (Index f a) -> Index f a -> Stack f a -> Stack f a
-pref !_ !_ _ !_ !_ _ | False = undefined
-pref search prefix here fun var rest =
-  case search of
-    Empty ->
-      case prefix of
-        Empty ->
-          -- The search term matches this node.
-          case here of
-            [] -> rest
-            _ -> Yield here rest
-        _ ->
-          -- We've run out of search term - it doesn't match this node.
-          rest
-    UnsafeCons t ts ->
-      case prefix of
-        Cons u us ->
-          -- Check the search term against the prefix.
-          case (t, u) of
-            (_, Var _) ->
-              -- Prefix contains a variable - if there is a match, the
-              -- variable will be bound to t.
-              pref ts us here fun var rest
-            (App f _, App g _) | f == g ->
-              -- Term and prefix start with same symbol, chop them off.
-               let
-                 UnsafeConsSym _ ts' = search
-                 UnsafeConsSym _ us' = prefix
-               in pref ts' us' here fun var rest
-            _ ->
-              -- Term and prefix don't match.
-              rest
-        _ ->
-          -- We've exhausted the prefix, so let's descend into the tree.
-          -- Seems to work better to explore the function node first.
-          let
-            tryVar =
-              case var of
-                Nil -> rest
-                Index{} -> Frame ts var rest
-              where
-                UnsafeCons _ ts = search
-
-            tryFun =
-              case t of
-                App f _ ->
-                  case fun ! fun_id f of
-                    Nil -> tryVar
-                    idx -> Frame ts idx $! tryVar
-                _ ->
-                  tryVar
-              where
-                UnsafeConsSym t ts = search
-          in
-            tryFun
-
--- | An empty index.
-empty :: Index f a
-empty = Nil
-
--- | Is the index empty?
-null :: Index f a -> Bool
-null Nil = True
-null _ = False
-
--- | An index with one entry.
-singleton :: Term f -> a -> Index f a
-singleton !t x = singletonList (Term.singleton t) x
-
--- An index with one entry, giving a termlist instead of a term.
-{-# INLINE singletonList #-}
-singletonList :: TermList f -> a -> Index f a
-singletonList t x = Index 0 t [x] newArray Nil
-
--- | Insert an entry into the index.
-insert :: Term f -> a -> Index f a -> Index f a
-insert !t x !idx = {-# SCC insert #-} aux (Term.singleton t) idx
-  where
-    aux t Nil = singletonList t x
-    aux (Cons t ts) idx@Index{prefix = Cons u us} | t == u =
-      withPrefix (Term.singleton t) (aux ts idx{prefix = us})
-    aux t idx@Index{prefix = Cons{}} = aux t (expand idx)
-
-    aux Empty idx =
-      idx { size = 0, here = x:here idx }
-    aux t@(ConsSym (App f _) u) idx =
-      idx {
-        size = lenList t `min` size idx,
-        fun  = update (fun_id f) idx' (fun idx) }
-      where
-        idx' = aux u (fun idx ! fun_id f)
-    aux t@(ConsSym (Var _) u) idx =
-      idx {
-        size = lenList t `min` size idx,
-        var  = aux u (var idx) }
-
--- Add a prefix to an index.
--- Does not update the size field.
-{-# INLINE withPrefix #-}
-withPrefix :: TermList f -> Index f a -> Index f a
-withPrefix Empty idx = idx
-withPrefix _ Nil = Nil
-withPrefix t idx@Index{..} =
-  idx{prefix = buildList (builder t `mappend` builder prefix)}
-
--- Take an index with a prefix and pull out the first symbol of the prefix,
--- giving an index which doesn't start with a prefix.
-{-# INLINE expand #-}
-expand :: Index f a -> Index f a
-expand idx@Index{size = size, prefix = ConsSym t ts} =
-  case t of
-    Var _ ->
-      Index {
-        size = size,
-        prefix = Term.empty,
-        here = [],
-        fun = newArray,
-        var = idx { prefix = ts, size = size - 1 } }
-    App f _ ->
-      Index {
-        size = size,
-        prefix = Term.empty,
-        here = [],
-        fun = update (fun_id f) idx { prefix = ts, size = size - 1 } newArray,
-        var = Nil }
-
--- | Delete an entry from the index.
-{-# INLINEABLE delete #-}
-delete :: Eq a => Term f -> a -> Index f a -> Index f a
-delete !t x !idx = {-# SCC delete #-} aux (Term.singleton t) idx
-  where
-    aux _ Nil = Nil
-    aux (Cons t ts) idx@Index{prefix = Cons u us} | t == u =
-      withPrefix (Term.singleton t) (aux ts idx{prefix = us})
-    aux _ idx@Index{prefix = Cons{}} = idx
-
-    aux Empty idx
-      | x `List.elem` here idx =
-        idx { here = List.delete x (here idx) }
-      | otherwise =
-        error "deleted term not found in index"
-    aux (ConsSym (App f _) t) idx =
-      idx { fun = update (fun_id f) (aux t (fun idx ! fun_id f)) (fun idx) }
-    aux (ConsSym (Var _) t) idx =
-      idx { var = aux t (var idx) }
-
--- | Look up a term in the index. Finds all key-value such that the search term
--- is an instance of the key, and returns an instance of the the value which
--- makes the search term exactly equal to the key.
-{-# INLINE lookup #-}
-lookup :: (Has a b, Symbolic b, Has b (TermOf b)) => TermOf b -> Index (ConstantOf b) a -> [b]
-lookup t idx = lookupList (Term.singleton t) idx
-
-{-# INLINEABLE lookupList #-}
-lookupList :: (Has a b, Symbolic b, Has b (TermOf b)) => TermListOf b -> Index (ConstantOf b) a -> [b]
-lookupList t idx =
-  [ subst sub x
-  | x <- List.map the (approxMatchesList t idx),
-    sub <- maybeToList (matchList (Term.singleton (the x)) t)]
-
--- | Look up a term in the index. Like 'lookup', but returns the exact value
--- that was inserted into the index, not an instance. Also returns a substitution
--- which when applied to the value gives you the matching instance.
-{-# INLINE matches #-}
-matches :: Has a (Term f) => Term f -> Index f a -> [(Subst f, a)]
-matches t idx = matchesList (Term.singleton t) idx
-
-{-# INLINEABLE matchesList #-}
-matchesList :: Has a (Term f) => TermList f -> Index f a -> [(Subst f, a)]
-matchesList t idx =
-  [ (sub, x)
-  | x <- approxMatchesList t idx,
-    sub <- maybeToList (matchList (Term.singleton (the x)) t)]
-
--- | Look up a term in the index, possibly returning spurious extra results.
-{-# INLINE approxMatches #-}
-approxMatches :: Term f -> Index f a -> [a]
-approxMatches t idx = approxMatchesList (Term.singleton t) idx
-
-approxMatchesList :: TermList f -> Index f a -> [a]
-approxMatchesList t idx =
-  {-# SCC approxMatchesList #-}
-  run (Frame t idx Stop)
-
--- | Return all elements of the index.
-elems :: Index f a -> [a]
-elems Nil = []
-elems idx =
-  here idx ++
-  concatMap elems (Prelude.map snd (toList (fun idx))) ++
-  elems (var idx)
diff --git a/src/Twee/Join.hs b/src/Twee/Join.hs
deleted file mode 100644
--- a/src/Twee/Join.hs
+++ /dev/null
@@ -1,212 +0,0 @@
--- | Tactics for joining critical pairs.
-{-# LANGUAGE FlexibleContexts, BangPatterns, RecordWildCards, TypeFamilies #-}
-module Twee.Join where
-
-import Twee.Base
-import Twee.Rule
-import Twee.Equation
-import Twee.Proof(Lemma)
-import qualified Twee.Proof as Proof
-import Twee.CP hiding (Config)
-import Twee.Constraints
-import qualified Twee.Index as Index
-import Twee.Index(Index)
-import Twee.Rule.Index(RuleIndex(..))
-import Twee.Utils
-import Data.Maybe
-import Data.Either
-import Data.Ord
-import qualified Data.Set as Set
-
-data Config =
-  Config {
-    cfg_ground_join :: !Bool,
-    cfg_use_connectedness :: !Bool,
-    cfg_set_join :: !Bool }
-
-defaultConfig :: Config
-defaultConfig =
-  Config {
-    cfg_ground_join = True,
-    cfg_use_connectedness = True,
-    cfg_set_join = False }
-
-{-# INLINEABLE joinCriticalPair #-}
-joinCriticalPair ::
-  (Function f, Has a (Rule f), Has a (Lemma f)) =>
-  Config ->
-  Index f (Equation f) -> RuleIndex f a ->
-  Maybe (Model f) -> -- A model to try before checking ground joinability
-  CriticalPair f ->
-  Either
-    -- Failed to join critical pair.
-    -- Returns simplified critical pair and model in which it failed to hold.
-    (CriticalPair f, Model f)
-    -- Split critical pair into several instances.
-    -- Returns list of instances which must be joined,
-    -- and an optional equation which can be added to the joinable set
-    -- after successfully joining all instances.
-    (Maybe (CriticalPair f), [CriticalPair f])
-joinCriticalPair config eqns idx mmodel cp@CriticalPair{cp_eqn = t :=: u} =
-  {-# SCC joinCriticalPair #-}
-  case allSteps config eqns idx cp of
-    Nothing ->
-      Right (Nothing, [])
-    _ | cfg_set_join config &&
-        not (null $ Set.intersection
-          (normalForms (rewrite reduces (index_all idx)) [reduce (Refl t)])
-          (normalForms (rewrite reduces (index_all idx)) [reduce (Refl u)])) ->
-      Right (Just cp, [])
-    Just cp ->
-      case groundJoinFromMaybe config eqns idx mmodel (branches (And [])) cp of
-        Left model -> Left (cp, model)
-        Right cps -> Right (Just cp, cps)
-
-{-# INLINEABLE step1 #-}
-{-# INLINEABLE step2 #-}
-{-# INLINEABLE step3 #-}
-{-# INLINEABLE allSteps #-}
-step1, step2, step3, allSteps ::
-  (Function f, Has a (Rule f), Has a (Lemma f)) =>
-  Config -> Index f (Equation f) -> RuleIndex f a -> CriticalPair f -> Maybe (CriticalPair f)
-allSteps config eqns idx cp =
-  step1 config eqns idx cp >>=
-  step2 config eqns idx >>=
-  step3 config eqns idx
-step1 _ eqns idx = joinWith eqns idx (\t _ -> normaliseWith (const True) (rewrite reducesOriented (index_oriented idx)) t)
-step2 _ eqns idx = joinWith eqns idx (\t _ -> normaliseWith (const True) (rewrite reduces (index_all idx)) t)
-step3 Config{..} eqns idx cp
-  | not cfg_use_connectedness = Just cp
-  | otherwise =
-    case cp_top cp of
-      Just top ->
-        case (join (cp, top), join (flipCP (cp, top))) of
-          (Just _, Just _) -> Just cp
-          _ -> Nothing
-      _ -> Just cp
-  where
-    join (cp, top) =
-      joinWith eqns idx (\t u -> normaliseWith (`lessThan` top) (rewrite (ok t u) (index_all idx)) t) cp
-
-    ok t u rule sub =
-      unorient rule `simplerThan` (t :=: u) &&
-      reducesSkolem rule sub
-
-    flipCP :: Symbolic a => a -> a
-    flipCP t = subst sub t
-      where
-        n = maximum (0:map fromEnum (vars t))
-        sub (V x) = var (V (n - x))
-
-
-{-# INLINEABLE joinWith #-}
-joinWith ::
-  (Has a (Rule f), Has a (Lemma f)) =>
-  Index f (Equation f) -> RuleIndex f a -> (Term f -> Term f -> Resulting f) -> CriticalPair f -> Maybe (CriticalPair f)
-joinWith eqns idx reduce cp@CriticalPair{cp_eqn = lhs :=: rhs, ..}
-  | subsumed eqns idx eqn = Nothing
-  | otherwise =
-    Just cp {
-      cp_eqn = eqn,
-      cp_proof =
-        Proof.symm (reductionProof (reduction lred)) `Proof.trans`
-        cp_proof `Proof.trans`
-        reductionProof (reduction rred) }
-  where
-    lred = reduce lhs rhs
-    rred = reduce rhs lhs
-    eqn = result lred :=: result rred
-
-{-# INLINEABLE subsumed #-}
-subsumed ::
-  (Has a (Rule f), Has a (Lemma f)) =>
-  Index f (Equation f) -> RuleIndex f a -> Equation f -> Bool
-subsumed eqns idx (t :=: u)
-  | t == u = True
-  | or [ rhs rule == u | rule <- Index.lookup t (index_all idx) ] = True
-  | or [ rhs rule == t | rule <- Index.lookup u (index_all idx) ] = True
-    -- No need to do this symmetrically because addJoinable adds
-    -- both orientations of each equation
-  | or [ u == subst sub u'
-       | t' :=: u' <- Index.approxMatches t eqns,
-         sub <- maybeToList (match t' t) ] = True
-subsumed eqns idx (App f ts :=: App g us)
-  | f == g =
-    let
-      sub Empty Empty = True
-      sub (Cons t ts) (Cons u us) =
-        subsumed eqns idx (t :=: u) && sub ts us
-      sub _ _ = error "Function used with multiple arities"
-    in
-      sub ts us
-subsumed _ _ _ = False
-
-{-# INLINEABLE groundJoin #-}
-groundJoin ::
-  (Function f, Has a (Rule f), Has a (Lemma f)) =>
-  Config -> Index f (Equation f) -> RuleIndex f a -> [Branch f] -> CriticalPair f -> Either (Model f) [CriticalPair f]
-groundJoin config eqns idx ctx cp@CriticalPair{cp_eqn = t :=: u, ..} =
-  case partitionEithers (map (solve (usort (atoms t ++ atoms u))) ctx) of
-    ([], instances) ->
-      let cps = [ subst sub cp | sub <- instances ] in
-      Right (usortBy (comparing (canonicalise . order . cp_eqn)) cps)
-    (model:_, _) ->
-      groundJoinFrom config eqns idx model ctx cp
-
-{-# INLINEABLE groundJoinFrom #-}
-groundJoinFrom ::
-  (Function f, Has a (Rule f), Has a (Lemma f)) =>
-  Config -> Index f (Equation f) -> RuleIndex f a -> Model f -> [Branch f] -> CriticalPair f -> Either (Model f) [CriticalPair f]
-groundJoinFrom config@Config{..} eqns idx model ctx cp@CriticalPair{cp_eqn = t :=: u, ..}
-  | not cfg_ground_join ||
-    (modelOK model && isJust (allSteps config eqns idx cp { cp_eqn = t' :=: u' })) = Left model
-  | otherwise =
-      let model1 = optimise model weakenModel (\m -> not (modelOK m) || (valid m (reduction nt) && valid m (reduction nu)))
-          model2 = optimise model1 weakenModel (\m -> not (modelOK m) || isNothing (allSteps config eqns idx cp { cp_eqn = result (normaliseIn m t u) :=: result (normaliseIn m u t) }))
-
-          diag [] = Or []
-          diag (r:rs) = negateFormula r ||| (weaken r &&& diag rs)
-          weaken (LessEq t u) = Less t u
-          weaken x = x
-          ctx' = formAnd (diag (modelToLiterals model2)) ctx in
-
-      groundJoin config eqns idx ctx' cp
-  where
-    normaliseIn m t u = normaliseWith (const True) (rewrite (ok t u m) (index_all idx)) t
-    ok t u m rule sub =
-      reducesInModel m rule sub &&
-      unorient rule `simplerThan` (t :=: u)
-
-    nt = normaliseIn model t u
-    nu = normaliseIn model u t
-    t' = result nt
-    u' = result nu
-
-    -- XXX not safe to exploit the top term if we then add the equation to
-    -- the joinable set. (It might then be used to join a CP with an entirely
-    -- different top term.)
-    modelOK _ = True
-{-    modelOK m =
-      case cp_top of
-        Nothing -> True
-        Just top ->
-          isNothing (lessIn m top t) && isNothing (lessIn m top u)-}
-
-{-# INLINEABLE groundJoinFromMaybe #-}
-groundJoinFromMaybe ::
-  (Function f, Has a (Rule f), Has a (Lemma f)) =>
-  Config -> Index f (Equation f) -> RuleIndex f a -> Maybe (Model f) -> [Branch f] -> CriticalPair f -> Either (Model f) [CriticalPair f]
-groundJoinFromMaybe config eqns idx Nothing = groundJoin config eqns idx
-groundJoinFromMaybe config eqns idx (Just model) = groundJoinFrom config eqns idx model
-
-{-# INLINEABLE valid #-}
-valid :: Function f => Model f -> Reduction f -> Bool
-valid model red =
-  and [ reducesInModel model rule sub
-      | Step _ rule sub <- steps red ]
-
-optimise :: a -> (a -> [a]) -> (a -> Bool) -> a
-optimise x f p =
-  case filter p (f x) of
-    y:_ -> optimise y f p
-    _   -> x
diff --git a/src/Twee/KBO.hs b/src/Twee/KBO.hs
deleted file mode 100644
--- a/src/Twee/KBO.hs
+++ /dev/null
@@ -1,121 +0,0 @@
--- | An implementation of Knuth-Bendix ordering.
-
-{-# LANGUAGE PatternGuards #-}
-module Twee.KBO(lessEq, lessIn) where
-
-import Twee.Base hiding (lessEq, lessIn)
-import Data.List
-import Twee.Constraints hiding (lessEq, lessIn)
-import qualified Data.Map.Strict as Map
-import Data.Map.Strict(Map)
-import Data.Maybe
-import Control.Monad
-
--- | Check if one term is less than another in KBO.
-lessEq :: Function f => Term f -> Term f -> Bool
-lessEq (App f Empty) _ | f == minimal = True
-lessEq (Var x) (Var y) | x == y = True
-lessEq _ (Var _) = False
-lessEq (Var x) t = x `elem` vars t
-lessEq t@(App f ts) u@(App g us) =
-  (st < su ||
-   (st == su && f << g) ||
-   (st == su && f == g && lexLess ts us)) &&
-  xs `isSubsequenceOf` ys
-  where
-    lexLess Empty Empty = True
-    lexLess (Cons t ts) (Cons u us)
-      | t == u = lexLess ts us
-      | otherwise =
-        lessEq t u &&
-        case unify t u of
-          Nothing -> True
-          Just sub
-            | not (allSubst (\_ (Cons t Empty) -> isMinimal t) sub) -> error "weird term inequality"
-            | otherwise -> lexLess (subst sub ts) (subst sub us)
-    lexLess _ _ = error "incorrect function arity"
-    xs = sort (vars t)
-    ys = sort (vars u)
-    st = size t
-    su = size u
-
--- | Check if one term is less than another in a given model.
-
--- See "notes/kbo under assumptions" for how this works.
-
-lessIn :: Function f => Model f -> Term f -> Term f -> Maybe Strictness
-lessIn model t u =
-  case sizeLessIn model t u of
-    Nothing -> Nothing
-    Just Strict -> Just Strict
-    Just Nonstrict -> lexLessIn model t u
-
-sizeLessIn :: Function f => Model f -> Term f -> Term f -> Maybe Strictness
-sizeLessIn model t u =
-  case minimumIn model m of
-    Just l
-      | l >  -k -> Just Strict
-      | l == -k -> Just Nonstrict
-    _ -> Nothing
-  where
-    (k, m) =
-      foldr (addSize id)
-        (foldr (addSize negate) (0, Map.empty) (subterms t))
-        (subterms u)
-    addSize op (App f _) (k, m) = (k + op (size f), m)
-    addSize op (Var x) (k, m) = (k, Map.insertWith (+) x (op 1) m)
-
-minimumIn :: Function f => Model f -> Map Var Int -> Maybe Int
-minimumIn model t =
-  liftM2 (+)
-    (fmap sum (mapM minGroup (varGroups model)))
-    (fmap sum (mapM minOrphan (Map.toList t)))
-  where
-    minGroup (lo, xs, mhi)
-      | all (>= 0) sums = Just (sum coeffs * size lo)
-      | otherwise =
-        case mhi of
-          Nothing -> Nothing
-          Just hi ->
-            let coeff = negate (minimum coeffs) in
-            Just $
-              sum coeffs * size lo +
-              coeff * (size lo - size hi)
-      where
-        coeffs = map (\x -> Map.findWithDefault 0 x t) xs
-        sums = scanr1 (+) coeffs
-
-    minOrphan (x, k)
-      | varInModel model x = Just 0
-      | k < 0 = Nothing
-      | otherwise = Just k
-
-lexLessIn :: Function f => Model f -> Term f -> Term f -> Maybe Strictness
-lexLessIn _ t u | t == u = Just Nonstrict
-lexLessIn cond t u
-  | Just a <- fromTerm t,
-    Just b <- fromTerm u,
-    Just x <- lessEqInModel cond a b = Just x
-  | Just a <- fromTerm t,
-    any isJust
-      [ lessEqInModel cond a b
-      | v <- properSubterms u, Just b <- [fromTerm v]] =
-        Just Strict
-lexLessIn cond (App f ts) (App g us)
-  | f == g = loop ts us
-  | f << g = Just Strict
-  | otherwise = Nothing
-  where
-    loop Empty Empty = Just Nonstrict
-    loop (Cons t ts) (Cons u us)
-      | t == u = loop ts us
-      | otherwise =
-        case lessIn cond t u of
-          Nothing -> Nothing
-          Just Strict -> Just Strict
-          Just Nonstrict ->
-            let Just sub = unify t u in
-            loop (subst sub ts) (subst sub us)
-    loop _ _ = error "incorrect function arity"
-lexLessIn _ t _ | isMinimal t = Just Nonstrict
-lexLessIn _ _ _ = Nothing
diff --git a/src/Twee/Label.hs b/src/Twee/Label.hs
deleted file mode 100644
--- a/src/Twee/Label.hs
+++ /dev/null
@@ -1,125 +0,0 @@
--- | Assignment of unique IDs to values.
--- Inspired by the 'intern' package.
-
-{-# LANGUAGE RecordWildCards, ScopedTypeVariables, BangPatterns #-}
-module Twee.Label(Label, unsafeMkLabel, labelNum, label, find) where
-
-import Data.IORef
-import System.IO.Unsafe
-import qualified Data.Map.Strict as Map
-import Data.Map.Strict(Map)
-import qualified Data.IntMap.Strict as IntMap
-import Data.IntMap.Strict(IntMap)
-import Data.Typeable
-import GHC.Exts
-import Unsafe.Coerce
-import Data.Int
-
--- | A value of type @a@ which has been given a unique ID.
-newtype Label a =
-  Label {
-    -- | The unique ID of a label.
-    labelNum :: Int32 }
-  deriving (Eq, Ord, Show)
-
--- | Construct a @'Label' a@ from its unique ID, which must be the 'labelNum' of
--- an already existing 'Label'. Extremely unsafe!
-unsafeMkLabel :: Int32 -> Label a
-unsafeMkLabel = Label
-
--- The global cache of labels.
-{-# NOINLINE cachesRef #-}
-cachesRef :: IORef Caches
-cachesRef = unsafePerformIO (newIORef (Caches 0 Map.empty IntMap.empty))
-
-data Caches =
-  Caches {
-    -- The next id number to assign.
-    caches_nextId :: {-# UNPACK #-} !Int32,
-    -- A map from values to labels.
-    caches_from   :: !(Map TypeRep (Cache Any)),
-    -- The reverse map from labels to values.
-    caches_to     :: !(IntMap Any) }
-
-type Cache a = Map a Int32
-
-atomicModifyCaches :: (Caches -> (Caches, a)) -> IO a
-atomicModifyCaches f = do
-  -- N.B. atomicModifyIORef' ref f evaluates f ref *after* doing the
-  -- compare-and-swap. This causes bad things to happen when 'label'
-  -- is used reentrantly (i.e. the Ord instance itself calls label).
-  -- This function only lets the swap happen if caches_nextId didn't
-  -- change (i.e., no new values were inserted).
-  !caches <- readIORef cachesRef
-  -- First compute the update.
-  let !(!caches', !x) = f caches
-  -- Now see if anyone else updated the cache in between
-  -- (can happen if f called 'label', or in a concurrent setting).
-  ok <- atomicModifyIORef' cachesRef $ \cachesNow ->
-    if caches_nextId caches == caches_nextId cachesNow
-    then (caches', True)
-    else (cachesNow, False)
-  if ok then return x else atomicModifyCaches f
-
--- Versions of unsafeCoerce with slightly more type checking
-toAnyCache :: Cache a -> Cache Any
-toAnyCache = unsafeCoerce
-
-fromAnyCache :: Cache Any -> Cache a
-fromAnyCache = unsafeCoerce
-
-toAny :: a -> Any
-toAny = unsafeCoerce
-
-fromAny :: Any -> a
-fromAny = unsafeCoerce
-
--- | Assign a label to a value.
-{-# NOINLINE label #-}
-label :: forall a. (Typeable a, Ord a) => a -> Label a
-label x =
-  unsafeDupablePerformIO $ do
-    -- Common case: label is already there.
-    caches <- readIORef cachesRef
-    case tryFind caches of
-      Just l -> return l
-      Nothing -> do
-        -- Rare case: label was not there.
-        x <- atomicModifyCaches $ \caches ->
-          case tryFind caches of
-            Just l -> (caches, l)
-            Nothing ->
-              insert caches
-        return x
-
-  where
-    ty = typeOf x
-
-    tryFind :: Caches -> Maybe (Label a)
-    tryFind Caches{..} =
-      Label <$> (Map.lookup ty caches_from >>= Map.lookup x . fromAnyCache)
-
-    insert :: Caches -> (Caches, Label a)
-    insert caches@Caches{..} =
-      if n < 0 then error "label overflow" else
-      (caches {
-         caches_nextId = n+1,
-         caches_from = Map.insert ty (toAnyCache (Map.insert x n cache)) caches_from,
-         caches_to = IntMap.insert (fromIntegral n) (toAny x) caches_to },
-       Label n)
-      where
-        n = caches_nextId
-        cache =
-          fromAnyCache $
-          Map.findWithDefault Map.empty ty caches_from
-
--- | Recover the underlying value from a label.
-find :: Label a -> a
--- N.B. must force n before calling readIORef, otherwise a call of
--- the form
---   find (label x)
--- doesn't work.
-find (Label !n) = unsafeDupablePerformIO $ do
-  Caches{..} <- readIORef cachesRef
-  x <- return $! fromAny (IntMap.findWithDefault undefined (fromIntegral n) caches_to)
-  return x
diff --git a/src/Twee/PassiveQueue.hs b/src/Twee/PassiveQueue.hs
deleted file mode 100644
--- a/src/Twee/PassiveQueue.hs
+++ /dev/null
@@ -1,183 +0,0 @@
--- | A queue of passive critical pairs, using a memory-efficient representation.
-{-# LANGUAGE TypeFamilies, RecordWildCards, FlexibleContexts, ScopedTypeVariables, StandaloneDeriving #-}
-module Twee.PassiveQueue(
-  Params(..),
-  Queue,
-  Passive(..),
-  empty, insert, removeMin, mapMaybe) where
-
-import qualified Data.Heap as Heap
-import qualified Data.Vector.Unboxed as Vector
-import Data.Int
-import Data.List hiding (insert)
-import qualified Data.Maybe
-import Data.Ord
-import Data.Proxy
-import Twee.Utils
-
--- | A datatype representing all the type parameters of the queue.
-class (Eq (Id params), Integral (Id params), Ord (Score params), Vector.Unbox (PackedScore params), Vector.Unbox (PackedId params)) => Params params where
-  -- | The score assigned to critical pairs. Smaller scores are better.
-  type Score params
-  -- | The type of ID numbers used to name rules.
-  type Id params
-
-  -- | A 'Score' packed for storage into a 'Vector.Vector'. Must be an instance of 'Vector.Unbox'.
-  type PackedScore params
-  -- | An 'Id' packed for storage into a 'Vector.Vector'. Must be an instance of 'Vector.Unbox'.
-  type PackedId params
-
-  -- | Pack a 'Score'.
-  packScore :: proxy params -> Score params -> PackedScore params
-  -- | Unpack a 'PackedScore'.
-  unpackScore :: proxy params -> PackedScore params -> Score params
-  -- | Pack an 'Id'.
-  packId :: proxy params -> Id params -> PackedId params
-  -- | Unpack a 'PackedId'.
-  unpackId :: proxy params -> PackedId params -> Id params
-
--- | A critical pair queue.
-newtype Queue params =
-  Queue (Heap.Heap (PassiveSet params))
-
--- All passive CPs generated from one given rule.
-data PassiveSet params =
-  PassiveSet {
-    passiveset_best  :: {-# UNPACK #-} !(Passive params),
-    passiveset_rule  :: !(Id params),
-    -- CPs where the rule is the left-hand rule
-    passiveset_left  :: {-# UNPACK #-} !(Vector.Vector (PackedScore params, PackedId params, Int32)),
-    -- CPs where the rule is the right-hand rule
-    passiveset_right :: {-# UNPACK #-} !(Vector.Vector (PackedScore params, PackedId params, Int32)) }
-instance Params params => Eq (PassiveSet params) where
-  x == y = compare x y == EQ
-instance Params params => Ord (PassiveSet params) where
-  compare = comparing passiveset_best
-
--- A smart-ish constructor.
-{-# INLINEABLE mkPassiveSet #-}
-mkPassiveSet ::
-  Params params =>
-  Proxy params ->
-  Id params ->
-  Vector.Vector (PackedScore params, PackedId params, Int32) ->
-  Vector.Vector (PackedScore params, PackedId params, Int32) ->
-  Maybe (PassiveSet params)
-mkPassiveSet proxy rule left right
-  | Vector.null left && Vector.null right = Nothing
-  | not (Vector.null left) &&
-    (Vector.null right || l <= r) =
-    Just PassiveSet {
-      passiveset_best  = l,
-      passiveset_rule  = rule,
-      passiveset_left  = Vector.tail left,
-      passiveset_right = right }
-    -- In this case we must have not (Vector.null right).
-  | otherwise =
-    Just PassiveSet {
-      passiveset_best  = r,
-      passiveset_rule  = rule,
-      passiveset_left  = left,
-      passiveset_right = Vector.tail right }
-  where
-    l = unpack proxy rule True (Vector.head left)
-    r = unpack proxy rule False (Vector.head right)
-
--- Unpack a triple into a Passive.
-{-# INLINEABLE unpack #-}
-unpack :: Params params => Proxy params -> Id params -> Bool -> (PackedScore params, PackedId params, Int32) -> Passive params
-unpack proxy rule isLeft (score, id, pos) =
-  Passive {
-    passive_score = unpackScore proxy score,
-    passive_rule1 = if isLeft then rule else rule',
-    passive_rule2 = if isLeft then rule' else rule,
-    passive_pos = fromIntegral pos }
-  where
-    rule' = unpackId proxy id
-
--- Make a PassiveSet from a list of Passives.
-{-# INLINEABLE makePassiveSet #-}
-makePassiveSet :: forall params. Params params => Id params -> [Passive params] -> Maybe (PassiveSet params)
-makePassiveSet _ [] = Nothing
-makePassiveSet rule ps
-  | and [passive_rule2 p == rule | p <- right] =
-    mkPassiveSet proxy rule
-      (Vector.fromList (map (pack True) (sort left)))
-      (Vector.fromList (map (pack False) (sort right)))
-  | otherwise = error "rule id does not occur in passive"
-  where
-    proxy :: Proxy params
-    proxy = Proxy
-    
-    (left, right) = partition (\p -> passive_rule1 p == rule) ps
-    pack isLeft Passive{..} =
-      (packScore proxy passive_score,
-       packId proxy (if isLeft then passive_rule2 else passive_rule1),
-       fromIntegral passive_pos)
-
--- Find and remove the best element from a PassiveSet.
-{-# INLINEABLE unconsPassiveSet #-}
-unconsPassiveSet :: forall params. Params params => PassiveSet params -> (Passive params, Maybe (PassiveSet params))
-unconsPassiveSet PassiveSet{..} =
-  (passiveset_best, mkPassiveSet (Proxy :: Proxy params) passiveset_rule passiveset_left passiveset_right)
-
--- | A queued critical pair.
-data Passive params =
-  Passive {
-    -- | The score of this critical pair.
-    passive_score :: !(Score params),
-    -- | The rule which does the outermost rewrite in this critical pair.
-    passive_rule1 :: !(Id params),
-    -- | The rule which does the innermost rewrite in this critical pair.
-    passive_rule2 :: !(Id params),
-    -- | The position of the overlap. See 'Twee.CP.overlap_pos'.
-    passive_pos   :: {-# UNPACK #-} !Int }
-
-instance Params params => Eq (Passive params) where
-  x == y = compare x y == EQ
-
-instance Params params => Ord (Passive params) where
-  compare = comparing f
-    where
-      f Passive{..} =
-        (passive_score,
-         intMax (fromIntegral passive_rule1) (fromIntegral passive_rule2),
-         intMin (fromIntegral passive_rule1) (fromIntegral passive_rule2),
-         passive_pos)
-
--- | The empty queue.
-empty :: Queue params
-empty = Queue Heap.empty
-
--- | Add a set of 'Passive's to the queue.
-{-# INLINEABLE insert #-}
-insert :: Params params => Id params -> [Passive params] -> Queue params -> Queue params
-insert rule passives (Queue q) =
-  Queue $
-  case makePassiveSet rule passives of
-    Nothing -> q
-    Just p -> Heap.insert p q
-
--- | Remove the minimum 'Passive' from the queue.
-{-# INLINEABLE removeMin #-}
-removeMin :: Params params => Queue params -> Maybe (Passive params, Queue params)
-removeMin (Queue q) = do
-  (passiveset, q) <- Heap.removeMin q
-  case unconsPassiveSet passiveset of
-    (passive, Just passiveset') ->
-      Just (passive, Queue (Heap.insert passiveset' q))
-    (passive, Nothing) ->
-      Just (passive, Queue q)
-
--- | Map a function over all 'Passive's.
-{-# INLINEABLE mapMaybe #-}
-mapMaybe :: Params params => (Passive params -> Maybe (Passive params)) -> Queue params -> Queue params
-mapMaybe f (Queue q) = Queue (Heap.mapMaybe g q)
-  where
-    g PassiveSet{..} =
-      makePassiveSet passiveset_rule $ Data.Maybe.mapMaybe f $
-        passiveset_best:
-        map (unpack proxy passiveset_rule True) (Vector.toList passiveset_left) ++
-        map (unpack proxy passiveset_rule False) (Vector.toList passiveset_right)
-    proxy :: Proxy params
-    proxy = Proxy
diff --git a/src/Twee/Pretty.hs b/src/Twee/Pretty.hs
deleted file mode 100644
--- a/src/Twee/Pretty.hs
+++ /dev/null
@@ -1,182 +0,0 @@
--- | Pretty-printing of terms and assorted other values.
-
-{-# LANGUAGE Rank2Types #-}
-module Twee.Pretty(module Twee.Pretty, module Text.PrettyPrint.HughesPJClass, Pretty(..)) where
-
-import Text.PrettyPrint.HughesPJClass hiding (empty)
-import qualified Text.PrettyPrint.HughesPJClass as PP
-import qualified Data.Map as Map
-import Data.Map(Map)
-import qualified Data.Set as Set
-import Data.Set(Set)
-import Data.Ratio
-import Twee.Term
-
--- * Miscellaneous 'Pretty' instances and utilities.
-
--- | Print a value to the console.
-prettyPrint :: Pretty a => a -> IO ()
-prettyPrint x = putStrLn (prettyShow x)
-
--- | The empty document. Used to avoid name clashes with 'Twee.Term.empty'.
-pPrintEmpty :: Doc
-pPrintEmpty = PP.empty
-
-instance Pretty Doc where pPrint = id
-
--- | Print a tuple of values.
-pPrintTuple :: [Doc] -> Doc
-pPrintTuple = parens . fsep . punctuate comma
-
-instance Pretty a => Pretty (Set a) where
-  pPrint = pPrintSet . map pPrint . Set.toList
-
--- | Print a set of vlaues.
-pPrintSet :: [Doc] -> Doc
-pPrintSet = braces . fsep . punctuate comma
-
-instance Pretty Var where
-  pPrint (V n) =
-    text $
-      vars !! (n `mod` length vars):
-      case n `div` length vars of
-        0 -> ""
-        m -> show (m+1)
-    where
-      vars = "XYZWVUTS"
-
-instance (Pretty k, Pretty v) => Pretty (Map k v) where
-  pPrint = pPrintSet . map binding . Map.toList
-    where
-      binding (x, v) = hang (pPrint x <+> text "=>") 2 (pPrint v)
-
-instance (Eq a, Integral a, Pretty a) => Pretty (Ratio a) where
-  pPrint a
-    | denominator a == 1 = pPrint (numerator a)
-    | otherwise = text "(" <+> pPrint (numerator a) <> text "/" <> pPrint (denominator a) <+> text ")"
-
--- | Generate a list of candidate names for pretty-printing.
-supply :: [String] -> [String]
-supply names =
-  names ++
-  [ name ++ show i | i <- [2..], name <- names ]
-
--- * Pretty-printing of terms.
-
-instance Pretty f => Pretty (Fun f) where
-  pPrintPrec l p = pPrintPrec l p . fun_value
-
-instance PrettyTerm f => PrettyTerm (Fun f) where
-  termStyle f = termStyle (fun_value f)
-
-instance PrettyTerm f => Pretty (Term f) where
-  pPrintPrec l p (Var x) = pPrintPrec l p x
-  pPrintPrec l p (App f xs) =
-    pPrintTerm (termStyle f) l p (pPrint f) (unpack xs)
-
-instance PrettyTerm f => Pretty (TermList f) where
-  pPrintPrec _ _ = pPrint . unpack
-
-instance PrettyTerm f => Pretty (Subst f) where
-  pPrint sub = text "{" <> fsep (punctuate (text ",") docs) <> text "}"
-    where
-      docs =
-        [ hang (pPrint x <+> text "->") 2 (pPrint t)
-        | (x, t) <- substToList sub ]
-
--- | A class for customising the printing of function symbols.
-class Pretty f => PrettyTerm f where
-  -- | The style of the function symbol. Defaults to 'curried'.
-  termStyle :: f -> TermStyle
-  termStyle _ = curried
-
--- | Defines how to print out a function symbol.
-newtype TermStyle =
-  TermStyle {
-    -- | Renders a function application.
-    -- Takes the following arguments in this order:
-    -- Pretty-printing level, current precedence,
-    -- pretty-printed function symbol and list of arguments to the function.
-    pPrintTerm :: forall a. Pretty a => PrettyLevel -> Rational -> Doc -> [a] -> Doc }
-
-invisible, curried, uncurried, prefix, postfix :: TermStyle
-
--- | For operators like @$@ that should be printed as a blank space.
-invisible =
-  TermStyle $ \l p d ->
-    let
-      f [] = d
-      f [t] = pPrintPrec l p t
-      f (t:ts) =
-        maybeParens (p > 10) $
-          pPrint t <+>
-            (hsep (map (pPrintPrec l 11) ts))
-    in f
-
--- | For functions that should be printed curried.
-curried =
-  TermStyle $ \l p d ->
-    let
-      f [] = d
-      f xs =
-        maybeParens (p > 10) $
-          d <+>
-            (hsep (map (pPrintPrec l 11) xs))
-    in f
-
--- | For functions that should be printed uncurried.
-uncurried =
-  TermStyle $ \l _ d ->
-    let
-      f [] = d
-      f xs =
-        d <> parens (hsep (punctuate comma (map (pPrintPrec l 0) xs)))
-    in f
-
--- | A helper function that deals with under- and oversaturated applications.
-fixedArity :: Int -> TermStyle -> TermStyle
-fixedArity arity style =
-  TermStyle $ \l p d ->
-    let
-      f xs
-        | length xs < arity = pPrintTerm curried l p (parens d) xs
-        | length xs > arity =
-            maybeParens (p > 10) $
-              hsep (pPrintTerm style l 11 d ys:
-                    map (pPrintPrec l 11) zs)
-        | otherwise = pPrintTerm style l p d xs
-        where
-          (ys, zs) = splitAt arity xs
-    in f
-
--- | A helper function that drops a certain number of arguments.
-implicitArguments :: Int -> TermStyle -> TermStyle
-implicitArguments n (TermStyle pp) =
-  TermStyle $ \l p d xs -> pp l p d (drop n xs)
-
--- | For prefix operators.
-prefix =
-  fixedArity 1 $
-  TermStyle $ \l _ d [x] ->
-    d <> pPrintPrec l 11 x
-
--- | For postfix operators.
-postfix =
-  fixedArity 1 $
-  TermStyle $ \l _ d [x] ->
-    pPrintPrec l 11 x <> d
-
--- | For infix operators.
-infixStyle :: Int -> TermStyle
-infixStyle pOp =
-  fixedArity 2 $
-  TermStyle $ \l p d [x, y] ->
-    maybeParens (p > fromIntegral pOp) $
-      pPrintPrec l (fromIntegral pOp+1) x <+> d <+>
-      pPrintPrec l (fromIntegral pOp+1) y
-
--- | For tuples.
-tupleStyle :: TermStyle
-tupleStyle =
-  TermStyle $ \l _ _ xs ->
-    parens (hsep (punctuate comma (map (pPrintPrec l 0) xs)))
diff --git a/src/Twee/Proof.hs b/src/Twee/Proof.hs
deleted file mode 100644
--- a/src/Twee/Proof.hs
+++ /dev/null
@@ -1,723 +0,0 @@
--- | Equational proofs which are checked for correctedness.
-{-# LANGUAGE TypeFamilies, PatternGuards, RecordWildCards, ScopedTypeVariables #-}
-module Twee.Proof(
-  -- * Constructing proofs
-  Proof, Derivation(..), Lemma(..), Axiom(..),
-  certify, equation, derivation,
-  -- ** Smart constructors for derivations
-  lemma, axiom, symm, trans, cong, congPath,
-
-  -- * Analysing proofs
-  simplify, usedLemmas, usedAxioms, usedLemmasAndSubsts, usedAxiomsAndSubsts,
-
-  -- * Pretty-printing proofs
-  Config(..), defaultConfig, Presentation(..),
-  ProvedGoal(..), provedGoal, checkProvedGoal,
-  pPrintPresentation, present, describeEquation) where
-
-import Twee.Base hiding (invisible)
-import Twee.Equation
-import Twee.Utils
-import Control.Monad
-import Data.Maybe
-import Data.List
-import Data.Ord
-import qualified Data.Set as Set
-import qualified Data.Map.Strict as Map
-
-----------------------------------------------------------------------
--- Equational proofs. Only valid proofs can be constructed.
-----------------------------------------------------------------------
-
--- | A checked proof. If you have a value of type @Proof f@,
--- it should jolly well represent a valid proof!
---
--- The only way to construct a @Proof f@ is by using 'certify'.
-data Proof f =
-  Proof {
-    equation   :: !(Equation f),
-    derivation :: !(Derivation f) }
-  deriving (Eq, Show)
-
--- | A derivation is an unchecked proof. It might be wrong!
--- The way to check it is to call 'certify' to turn it into a 'Proof'.
-data Derivation f =
-    -- | Apply an existing rule (with proof!) to the root of a term
-    UseLemma {-# UNPACK #-} !(Lemma f) !(Subst f)
-    -- | Apply an axiom to the root of a term
-  | UseAxiom {-# UNPACK #-} !(Axiom f) !(Subst f)
-    -- | Reflexivity. @'Refl' t@ proves @t = t@.
-  | Refl !(Term f)
-    -- | Symmetry
-  | Symm !(Derivation f)
-    -- | Transivitity
-  | Trans !(Derivation f) !(Derivation f)
-    -- | Congruence.
-    -- Parallel, i.e., takes a function symbol and one derivation for each
-    -- argument of that function.
-  | Cong {-# UNPACK #-} !(Fun f) ![Derivation f]
-  deriving (Eq, Show)
-
--- | A lemma, which includes a proof.
-data Lemma f =
-  Lemma {
-    -- | The id number of the lemma.
-    -- Has no semantic meaning; for convenience only.
-    lemma_id :: {-# UNPACK #-} !Id,
-    -- | A proof of the lemma.
-    lemma_proof :: !(Proof f) }
-  deriving Show
-
---  | An axiom, which comes without proof.
-data Axiom f =
-  Axiom {
-    -- | The number of the axiom.
-    -- Has no semantic meaning; for convenience only.
-    axiom_number :: {-# UNPACK #-} !Int,
-    -- | A description of the axiom.
-    -- Has no semantic meaning; for convenience only.
-    axiom_name :: !String,
-    -- | The equation which the axiom asserts.
-    axiom_eqn :: !(Equation f) }
-  deriving (Eq, Ord, Show)
-
--- | Checks a 'Derivation' and, if it is correct, returns a
--- certified 'Proof'.
---
--- If the 'Derivation' is incorrect, throws an exception.
-
--- This is the trusted core of the module.
-{-# INLINEABLE certify #-}
-certify :: PrettyTerm f => Derivation f -> Proof f
-certify p =
-  {-# SCC certify #-}
-  case check p of
-    Nothing -> error ("Invalid proof created!\n" ++ prettyShow p)
-    Just eqn -> Proof eqn p
-  where
-    check (UseLemma Lemma{..} sub) =
-      return (subst sub (equation lemma_proof))
-    check (UseAxiom Axiom{..} sub) =
-      return (subst sub axiom_eqn)
-    check (Refl t) =
-      return (t :=: t)
-    check (Symm p) = do
-      t :=: u <- check p
-      return (u :=: t)
-    check (Trans p q) = do
-      t :=: u1 <- check p
-      u2 :=: v <- check q
-      guard (u1 == u2)
-      return (t :=: v)
-    check (Cong f ps) = do
-      eqns <- mapM check ps
-      return
-        (build (app f (map eqn_lhs eqns)) :=:
-         build (app f (map eqn_rhs eqns)))
-
-----------------------------------------------------------------------
--- Everything below this point need not be trusted, since all proof
--- construction goes through the "proof" function.
-----------------------------------------------------------------------
-
--- Typeclass instances.
-instance Eq (Lemma f) where
-  x == y = compare x y == EQ
-instance Ord (Lemma f) where
-  compare =
-    comparing (\x ->
-      -- Don't look into lemma proofs when comparing derivations,
-      -- to avoid exponential blowup
-      (lemma_id x, equation (lemma_proof x)))
-
-instance Symbolic (Derivation f) where
-  type ConstantOf (Derivation f) = f
-  termsDL (UseLemma _ sub) = termsDL sub
-  termsDL (UseAxiom _ sub) = termsDL sub
-  termsDL (Refl t) = termsDL t
-  termsDL (Symm p) = termsDL p
-  termsDL (Trans p q) = termsDL p `mplus` termsDL q
-  termsDL (Cong _ ps) = termsDL ps
-
-  subst_ sub (UseLemma lemma s) = UseLemma lemma (subst_ sub s)
-  subst_ sub (UseAxiom axiom s) = UseAxiom axiom (subst_ sub s)
-  subst_ sub (Refl t) = Refl (subst_ sub t)
-  subst_ sub (Symm p) = symm (subst_ sub p)
-  subst_ sub (Trans p q) = trans (subst_ sub p) (subst_ sub q)
-  subst_ sub (Cong f ps) = cong f (subst_ sub ps)
-
-instance Function f => Pretty (Proof f) where
-  pPrint = pPrintLemma defaultConfig prettyShow
-instance PrettyTerm f => Pretty (Derivation f) where
-  pPrint (UseLemma lemma sub) =
-    text "subst" <> pPrintTuple [pPrint lemma, pPrint sub]
-  pPrint (UseAxiom axiom sub) =
-    text "subst" <> pPrintTuple [pPrint axiom, pPrint sub]
-  pPrint (Refl t) =
-    text "refl" <> pPrintTuple [pPrint t]
-  pPrint (Symm p) =
-    text "symm" <> pPrintTuple [pPrint p]
-  pPrint (Trans p q) =
-    text "trans" <> pPrintTuple [pPrint p, pPrint q]
-  pPrint (Cong f ps) =
-    text "cong" <> pPrintTuple (pPrint f:map pPrint ps)
-
-instance PrettyTerm f => Pretty (Axiom f) where
-  pPrint Axiom{..} =
-    text "axiom" <>
-    pPrintTuple [pPrint axiom_number, text axiom_name, pPrint axiom_eqn]
-
-instance PrettyTerm f => Pretty (Lemma f) where
-  pPrint Lemma{..} =
-    text "lemma" <>
-    pPrintTuple [pPrint lemma_id, pPrint (equation lemma_proof)]
-
--- | Simplify a derivation.
---
--- After simplification, a derivation has the following properties:
---
---   * 'Symm' is pushed down next to 'Lemma' and 'Axiom'
---   * 'Refl' only occurs inside 'Cong' or at the top level
---   * 'Trans' is right-associated and is pushed inside 'Cong' if possible
-simplify :: Minimal f => (Lemma f -> Maybe (Derivation f)) -> Derivation f -> Derivation f
-simplify lem p = simp p
-  where
-    simp p@(UseLemma lemma sub) =
-      case lem lemma of
-        Nothing -> p
-        Just q ->
-          let
-            -- Get rid of any variables that are not bound by sub
-            -- (e.g., ones which only occur internally in q)
-            dead = usort (vars q) \\ substDomain sub
-          in simp (subst sub (erase dead q))
-    simp (Symm p) = symm (simp p)
-    simp (Trans p q) = trans (simp p) (simp q)
-    simp (Cong f ps) = cong f (map simp ps)
-    simp p = p
-
-lemma :: Lemma f -> Subst f -> Derivation f
-lemma lem@Lemma{..} sub = UseLemma lem sub
-
-axiom :: Axiom f -> Derivation f
-axiom ax@Axiom{..} =
-  UseAxiom ax $
-    fromJust $
-    listToSubst [(x, build (var x)) | x <- vars axiom_eqn]
-
-symm :: Derivation f -> Derivation f
-symm (Refl t) = Refl t
-symm (Symm p) = p
-symm (Trans p q) = trans (symm q) (symm p)
-symm (Cong f ps) = cong f (map symm ps)
-symm p = Symm p
-
-trans :: Derivation f -> Derivation f -> Derivation f
-trans Refl{} p = p
-trans p Refl{} = p
-trans (Trans p q) r =
-  -- Right-associate uses of transitivity.
-  -- p cannot be a Trans (if it was created with the smart
-  -- constructors) but q could be.
-  Trans p (trans q r)
--- Collect adjacent uses of congruence.
-trans (Cong f ps) (Cong g qs) | f == g =
-  transCong f ps qs
-trans (Cong f ps) (Trans (Cong g qs) r) | f == g =
-  trans (transCong f ps qs) r
-trans p q = Trans p q
-
-transCong :: Fun f -> [Derivation f] -> [Derivation f] -> Derivation f
-transCong f ps qs =
-  cong f (zipWith trans ps qs)
-
-cong :: Fun f -> [Derivation f] -> Derivation f
-cong f ps =
-  case sequence (map unRefl ps) of
-    Nothing -> Cong f ps
-    Just ts -> Refl (build (app f ts))
-  where
-    unRefl (Refl t) = Just t
-    unRefl _ = Nothing
-
--- | Find all lemmas which are used in a derivation.
-usedLemmas :: Derivation f -> [Lemma f]
-usedLemmas p = map fst (usedLemmasAndSubsts p)
-
--- | Find all lemmas which are used in a derivation,
--- together with the substitutions used.
-usedLemmasAndSubsts :: Derivation f -> [(Lemma f, Subst f)]
-usedLemmasAndSubsts p = lem p []
-  where
-    lem (UseLemma lemma sub) = ((lemma, sub):)
-    lem (Symm p) = lem p
-    lem (Trans p q) = lem p . lem q
-    lem (Cong _ ps) = foldr (.) id (map lem ps)
-    lem _ = id
-
--- | Find all axioms which are used in a derivation.
-usedAxioms :: Derivation f -> [Axiom f]
-usedAxioms p = map fst (usedAxiomsAndSubsts p)
-
--- | Find all axioms which are used in a derivation,
--- together with the substitutions used.
-usedAxiomsAndSubsts :: Derivation f -> [(Axiom f, Subst f)]
-usedAxiomsAndSubsts p = ax p []
-  where
-    ax (UseAxiom axiom sub) = ((axiom, sub):)
-    ax (Symm p) = ax p
-    ax (Trans p q) = ax p . ax q
-    ax (Cong _ ps) = foldr (.) id (map ax ps)
-    ax _ = id
-
--- | Applies a derivation at a particular path in a term.
-congPath :: [Int] -> Term f -> Derivation f -> Derivation f
-congPath [] _ p = p
-congPath (n:ns) (App f t) p | n <= length ts =
-  cong f $
-    map Refl (take n ts) ++
-    [congPath ns (ts !! n) p] ++
-    map Refl (drop (n+1) ts)
-  where
-    ts = unpack t
-congPath _ _ _ = error "bad path"
-
-----------------------------------------------------------------------
--- Pretty-printing of proofs.
-----------------------------------------------------------------------
-
--- | Options for proof presentation.
-data Config =
-  Config {
-    -- | Never inline lemmas.
-    cfg_all_lemmas :: !Bool,
-    -- | Inline all lemmas.
-    cfg_no_lemmas :: !Bool,
-    -- | Print out explicit substitutions.
-    cfg_show_instances :: !Bool }
-
--- | The default configuration.
-defaultConfig :: Config
-defaultConfig =
-  Config {
-    cfg_all_lemmas = False,
-    cfg_no_lemmas = False,
-    cfg_show_instances = False }
-
--- | A proof, with all axioms and lemmas explicitly listed.
-data Presentation f =
-  Presentation {
-    -- | The used axioms.
-    pres_axioms :: [Axiom f],
-    -- | The used lemmas.
-    pres_lemmas :: [Lemma f],
-    -- | The goals proved.
-    pres_goals  :: [ProvedGoal f] }
-  deriving Show
-
--- Note: only the pg_proof field should be trusted!
--- The remaining fields are for information only.
-data ProvedGoal f =
-  ProvedGoal {
-    pg_number  :: Int,
-    pg_name    :: String,
-    pg_proof   :: Proof f,
-
-    -- Extra fields for existentially-quantified goals, giving the original goal
-    -- and the existential witness. These fields are not verified. If you want
-    -- to check them, use checkProvedGoal.
-    --
-    -- In general, subst pg_witness_hint pg_goal_hint == equation pg_proof.
-    -- For non-existential goals, pg_goal_hint == equation pg_proof
-    -- and pg_witness_hint is the empty substitution.
-    pg_goal_hint    :: Equation f,
-    pg_witness_hint :: Subst f }
-  deriving Show
-
--- | Construct a @ProvedGoal@.
-provedGoal :: Int -> String -> Proof f -> ProvedGoal f
-provedGoal number name proof =
-  ProvedGoal {
-    pg_number = number,
-    pg_name = name,
-    pg_proof = proof,
-    pg_goal_hint = equation proof,
-    pg_witness_hint = emptySubst }
-
--- | Check that pg_goal/pg_witness match up with pg_proof.
-checkProvedGoal :: Function f => ProvedGoal f -> ProvedGoal f
-checkProvedGoal pg@ProvedGoal{..}
-  | subst pg_witness_hint pg_goal_hint == equation pg_proof =
-    pg
-  | otherwise =
-    error $ show $
-      text "Invalid ProvedGoal!" $$
-      text "Claims to prove" <+> pPrint pg_goal_hint $$
-      text "with witness" <+> pPrint pg_witness_hint <> text "," $$
-      text "but actually proves" <+> pPrint (equation pg_proof)
-
-instance Function f => Pretty (Presentation f) where
-  pPrint = pPrintPresentation defaultConfig
-
--- | Simplify and present a proof.
-present :: Function f => Config -> [ProvedGoal f] -> Presentation f
-present config goals =
-  -- First find all the used lemmas, then hand off to presentWithGoals
-  presentWithGoals config goals
-    (used Set.empty (concatMap (usedLemmas . derivation . pg_proof) goals))
-  where
-    used lems [] = Set.elems lems
-    used lems (x:xs)
-      | x `Set.member` lems = used lems xs
-      | otherwise =
-        used (Set.insert x lems)
-          (usedLemmas (derivation (lemma_proof x)) ++ xs)
-
-presentWithGoals ::
-  Function f =>
-  Config -> [ProvedGoal f] -> [Lemma f] -> Presentation f
-presentWithGoals config@Config{..} goals lemmas
-  -- We inline a lemma if one of the following holds:
-  --   * It only has one step
-  --   * It is subsumed by an earlier lemma
-  --   * It is only used once
-  --   * It has to do with $equals (for printing of the goal proof)
-  --   * The option cfg_no_lemmas is true
-  -- First we compute all inlinings, then apply simplify to remove them,
-  -- then repeat if any lemma was inlined
-  | Map.null inlinings =
-    let
-      axioms = usort $
-        concatMap (usedAxioms . derivation . pg_proof) goals ++
-        concatMap (usedAxioms . derivation . lemma_proof) lemmas
-    in
-      Presentation axioms
-        [ lemma { lemma_proof = flattenProof lemma_proof }
-        | lemma@Lemma{..} <- lemmas ]
-        [ decodeGoal (goal { pg_proof = flattenProof pg_proof })
-        | goal@ProvedGoal{..} <- goals ]
-
-  | otherwise =
-    let
-      inline lemma = Map.lookup lemma inlinings
-
-      goals' =
-        [ decodeGoal (goal { pg_proof = certify $ simplify inline (derivation pg_proof) })
-        | goal@ProvedGoal{..} <- goals ]
-      lemmas' =
-        [ Lemma n (certify $ simplify inline (derivation p))
-        | lemma@(Lemma n p) <- lemmas, not (lemma `Map.member` inlinings) ]
-    in
-      presentWithGoals config goals' lemmas'
-
-  where
-    inlinings =
-      Map.fromList
-        [ (lemma, p)
-        | lemma <- lemmas, Just p <- [tryInline lemma]]
-
-    tryInline (Lemma n p)
-      | shouldInline n p = Just (derivation p)
-    tryInline (Lemma n p)
-      -- Check for subsumption by an earlier lemma
-      | Just (Lemma m q) <- Map.lookup (canonicalise (t :=: u)) equations, m < n =
-        Just (subsume p (derivation q))
-      | Just (Lemma m q) <- Map.lookup (canonicalise (u :=: t)) equations, m < n =
-        Just (subsume p (Symm (derivation q)))
-      where
-        t :=: u = equation p
-    tryInline _ = Nothing
-
-    shouldInline n p =
-      cfg_no_lemmas ||
-      oneStep (derivation p) ||
-      (not cfg_all_lemmas &&
-       (isJust (decodeEquality (eqn_lhs (equation p))) ||
-        isJust (decodeEquality (eqn_rhs (equation p))) ||
-        Map.lookup n uses == Just 1))
-  
-    subsume p q =
-      -- Rename q so its variables match p's
-      subst sub q
-      where
-        t  :=: u  = equation p
-        t' :=: u' = equation (certify q)
-        Just sub  = matchList (buildList [t', u']) (buildList [t, u])
-
-    -- Record which lemma proves each equation
-    equations =
-      Map.fromList
-        [ (canonicalise (equation lemma_proof), lemma)
-        | lemma@Lemma{..} <- lemmas]
-
-    -- Count how many times each lemma is used
-    uses =
-      Map.fromListWith (+)
-        [ (lemma_id, 1)
-        | Lemma{..} <-
-            concatMap usedLemmas
-              (map (derivation . pg_proof) goals ++
-               map (derivation . lemma_proof) lemmas) ]
-
-    -- Check if a proof only has one step.
-    -- Trans only occurs at the top level by this point.
-    oneStep Trans{} = False
-    oneStep _ = True
-
-invisible :: Function f => Equation f -> Bool
-invisible (t :=: u) = show (pPrint t) == show (pPrint u)
-
--- Pretty-print the proof of a single lemma.
-pPrintLemma :: Function f => Config -> (Id -> String) -> Proof f -> Doc
-pPrintLemma Config{..} lemmaName p =
-  ppTerm (eqn_lhs (equation q)) $$ pp (derivation q)
-  where
-    q = flattenProof p
-
-    pp (Trans p q) = pp p $$ pp q
-    pp p | invisible (equation (certify p)) = pPrintEmpty
-    pp p =
-      (text "= { by" <+>
-       ppStep
-         (nub (map (show . ppLemma) (usedLemmasAndSubsts p)) ++
-          nub (map (show . ppAxiom) (usedAxiomsAndSubsts p))) <+>
-       text "}" $$
-       ppTerm (eqn_rhs (equation (certify p))))
-
-    ppTerm t = text "  " <> pPrint t
-
-    ppStep [] = text "reflexivity" -- ??
-    ppStep [x] = text x
-    ppStep xs =
-      hcat (punctuate (text ", ") (map text (init xs))) <+>
-      text "and" <+>
-      text (last xs)
-
-    ppLemma (Lemma{..}, sub) =
-      text "lemma" <+> text (lemmaName lemma_id) <> showSubst sub
-    ppAxiom (Axiom{..}, sub) =
-      text "axiom" <+> pPrint axiom_number <+> parens (text axiom_name) <> showSubst sub
-
-    showSubst sub
-      | cfg_show_instances && not (null (substToList sub)) =
-        text " with " <>
-        fsep (punctuate comma
-          [ pPrint x <+> text "->" <+> pPrint t
-          | (x, t) <- substToList sub ])
-      | otherwise = pPrintEmpty
-
--- Transform a proof so that each step uses exactly one axiom
--- or lemma. The proof will have the following form afterwards:
---   * Trans only occurs at the outermost level and is right-associated
---   * Each Cong has exactly one non-Refl argument (no parallel rewriting)
---   * Symm only occurs innermost, i.e., next to UseLemma or UseAxiom
---   * Refl only occurs as an argument to Cong, or outermost if the
---     whole proof is a single reflexivity step
-flattenProof :: Function f => Proof f -> Proof f
-flattenProof =
-  certify . flat . simplify (const Nothing) . derivation
-  where
-    flat (Trans p q) = trans (flat p) (flat q)
-    flat p@(Cong f ps) =
-      foldr trans (reflAfter p)
-        [ Cong f $
-            map reflAfter (take i ps) ++
-            [p] ++
-            map reflBefore (drop (i+1) ps)
-        | (i, q) <- zip [0..] qs,
-          p <- steps q ]
-      where
-        qs = map flat ps
-    flat p = p
-
-    reflBefore p = Refl (eqn_lhs (equation (certify p)))
-    reflAfter p  = Refl (eqn_rhs (equation (certify p)))
-
-    steps Refl{} = []
-    steps (Trans p q) = steps p ++ steps q
-    steps p = [p]
-
-    trans (Trans p q) r = trans p (trans q r)
-    trans Refl{} p = p
-    trans p Refl{} = p
-    trans p q =
-      case strip q of
-        Nothing -> Trans p q
-        Just q' -> trans p q'
-
-    strip p
-      | t == u = Just (Refl t)
-      | otherwise = strip' t p
-      where
-        t :=: u = equation (certify p)
-    strip' t (Trans _ q)
-      | eqn_lhs (equation (certify q)) == t = Just q
-      | otherwise = strip' t q
-    strip' _ _ = Nothing
-
--- Transform a derivation into a list of single steps.
--- Each step has the following form:
---   * Trans does not occur
---   * Symm only occurs innermost, i.e., next to UseLemma or UseAxiom
---   * Each Cong has exactly one non-Refl argument (no parallel rewriting)
---   * Refl only occurs as an argument to Cong
-derivSteps :: Function f => Derivation f -> [Derivation f]
-derivSteps = steps . derivation . flattenProof . certify
-  where
-    steps Refl{} = []
-    steps (Trans p q) = steps p ++ steps q
-    steps p = [p]
-
--- | Print a presented proof.
-pPrintPresentation :: forall f. Function f => Config -> Presentation f -> Doc
-pPrintPresentation config (Presentation axioms lemmas goals) =
-  vcat $ intersperse (text "") $
-    vcat [ describeEquation "Axiom" (show n) (Just name) eqn
-         | Axiom n name eqn <- axioms,
-           not (invisible eqn) ]:
-    [ pp "Lemma" (num n) Nothing (equation p) emptySubst p
-    | Lemma n p <- lemmas,
-      not (invisible (equation p)) ] ++
-    [ pp "Goal" (show num) (Just pg_name) pg_goal_hint pg_witness_hint pg_proof
-    | (num, ProvedGoal{..}) <- zip [1..] goals ]
-  where
-    pp kind n mname eqn witness p =
-      describeEquation kind n mname eqn $$
-      ppWitness witness $$
-      text "Proof:" $$
-      pPrintLemma config num p
-
-    num x = show (fromJust (Map.lookup x nums))
-    nums = Map.fromList (zip (map lemma_id lemmas) [n+1 ..])
-    n = maximum $ 0:map axiom_number axioms
-
-    ppWitness sub
-      | sub == emptySubst = pPrintEmpty
-      | otherwise =
-          vcat [
-            text "The goal is true when:",
-            nest 2 $ vcat
-              [ pPrint x <+> text "=" <+> pPrint t
-              | (x, t) <- substToList sub ],
-            if minimal `elem` funs sub then
-              text "where" <+> doubleQuotes (pPrint (minimal :: Fun f)) <+>
-              text "stands for an arbitrary term of your choice."
-            else pPrintEmpty,
-            text ""]
-
--- | Format an equation nicely.
---
--- Used both here and in the main file.
-describeEquation ::
-  PrettyTerm f =>
-  String -> String -> Maybe String -> Equation f -> Doc
-describeEquation kind num mname eqn =
-  text kind <+> text num <>
-  (case mname of
-     Nothing -> text ""
-     Just name -> text (" (" ++ name ++ ")")) <>
-  text ":" <+> pPrint eqn <> text "."
-
-----------------------------------------------------------------------
--- Making proofs of existential goals more readable.
-----------------------------------------------------------------------
-
--- The idea: the only axioms which mention $equals, $true and $false
--- are:
---   * $equals(x,x) = $true  (reflexivity)
---   * $equals(t,u) = $false (conjecture)
--- This implies that a proof $true = $false must have the following
--- structure, if we expand out all lemmas:
---   $true = $equals(s,s) = ... = $equals(t,u) = $false.
---
--- The substitution in the last step $equals(t,u) = $false is in fact the
--- witness to the existential.
---
--- Furthermore, we can make it so that the inner "..." doesn't use the $equals
--- axioms. If it does, one of the "..." steps results in either $true or $false,
--- and we can chop off everything before the $true or after the $false.
---
--- Once we have done that, every proof step in the "..." must be a congruence
--- step of the shape
---   $equals(t, u) = $equals(v, w).
--- This is because there are no other axioms which mention $equals. Hence we can
--- split the proof of $equals(s,s) = $equals(t,u) into separate proofs of s=t
--- and s=u.
---
--- What we have got out is:
---   * the witness to the existential
---   * a proof that both sides of the conjecture are equal
--- and we can present that to the user.
-
--- Decode $equals(t,u) into an equation t=u.
-decodeEquality :: Function f => Term f -> Maybe (Equation f)
-decodeEquality (App equals (Cons t (Cons u Empty)))
-  | isEquals equals = Just (t :=: u)
-decodeEquality _ = Nothing
-
--- Tries to transform a proof of $true = $false into a proof of
--- the original existentially-quantified formula.
-decodeGoal :: Function f => ProvedGoal f -> ProvedGoal f
-decodeGoal pg =
-  case maybeDecodeGoal pg of
-    Nothing -> pg
-    Just (name, witness, goal, deriv) ->
-      checkProvedGoal $
-      pg {
-        pg_name = name,
-        pg_proof = certify deriv,
-        pg_goal_hint = goal,
-        pg_witness_hint = witness }
-
-maybeDecodeGoal :: forall f. Function f =>
-  ProvedGoal f -> Maybe (String, Subst f, Equation f, Derivation f)
-maybeDecodeGoal ProvedGoal{..}
-  -- N.B. presentWithGoals takes care of expanding any lemma which mentions
-  -- $equals, and flattening the proof.
-  | isFalseTerm u = extract (derivSteps deriv)
-    -- Orient the equation so that $false is the RHS.
-  | isFalseTerm t = extract (derivSteps (symm deriv))
-  | otherwise = Nothing
-  where
-    isFalseTerm, isTrueTerm :: Term f -> Bool
-    isFalseTerm (App false _) = isFalse false
-    isFalseTerm _ = False
-    isTrueTerm (App true _) = isTrue true
-    isTrueTerm _ = False
-
-    t :=: u = equation pg_proof
-    deriv = derivation pg_proof
-
-    -- Detect $true = $equals(t, t).
-    decodeReflexivity :: Derivation f -> Maybe (Term f)
-    decodeReflexivity (Symm (UseAxiom Axiom{..} sub)) = do
-      guard (isTrueTerm (eqn_rhs axiom_eqn))
-      (t :=: u) <- decodeEquality (eqn_lhs axiom_eqn)
-      guard (t == u)
-      return (subst sub t)
-    decodeReflexivity _ = Nothing
-
-    -- Detect $equals(t, u) = $false.
-    decodeConjecture :: Derivation f -> Maybe (String, Equation f, Subst f)
-    decodeConjecture (UseAxiom Axiom{..} sub) = do
-      guard (isFalseTerm (eqn_rhs axiom_eqn))
-      eqn <- decodeEquality (eqn_lhs axiom_eqn)
-      return (axiom_name, eqn, sub)
-    decodeConjecture _ = Nothing
-
-    extract (p:ps) = do
-      -- Start by finding $true = $equals(t,u).
-      t <- decodeReflexivity p
-      cont (Refl t) (Refl t) ps
-    extract [] = Nothing
-
-    cont p1 p2 (p:ps)
-      | Just t <- decodeReflexivity p =
-        cont (Refl t) (Refl t) ps
-      | Just (name, eqn, sub) <- decodeConjecture p =
-        -- If p1: s=t and p2: s=u
-        -- then symm p1 `trans` p2: t=u.
-        return (name, sub, eqn, symm p1 `trans` p2)
-      | Cong eq [p1', p2'] <- p, isEquals eq =
-        cont (p1 `trans` p1') (p2 `trans` p2') ps
-    cont _ _ _ = Nothing
diff --git a/src/Twee/Rule.hs b/src/Twee/Rule.hs
deleted file mode 100644
--- a/src/Twee/Rule.hs
+++ /dev/null
@@ -1,488 +0,0 @@
--- | Term rewriting.
-{-# LANGUAGE TypeFamilies, FlexibleContexts, RecordWildCards, BangPatterns, OverloadedStrings, MultiParamTypeClasses, ScopedTypeVariables, GeneralizedNewtypeDeriving #-}
-module Twee.Rule where
-
-import Twee.Base
-import Twee.Constraints
-import qualified Twee.Index as Index
-import Twee.Index(Index)
-import Control.Monad
-import Control.Monad.Trans.Class
-import Control.Monad.Trans.State.Strict
-import Data.Maybe
-import Data.List
-import Twee.Utils
-import qualified Data.Set as Set
-import Data.Set(Set)
-import qualified Twee.Term as Term
-import Data.Ord
-import Twee.Equation
-import qualified Twee.Proof as Proof
-import Twee.Proof(Derivation, Lemma(..))
-import Data.Tuple
-
---------------------------------------------------------------------------------
--- * Rewrite rules.
---------------------------------------------------------------------------------
-
--- | A rewrite rule.
-data Rule f =
-  Rule {
-    -- | Information about whether and how the rule is oriented.
-    orientation :: !(Orientation f),
-    -- Invariant:
-    -- For oriented rules: vars rhs `isSubsetOf` vars lhs
-    -- For unoriented rules: vars lhs == vars rhs
-
-    -- | The left-hand side of the rule.
-    lhs :: {-# UNPACK #-} !(Term f),
-    -- | The right-hand side of the rule.
-    rhs :: {-# UNPACK #-} !(Term f) }
-  deriving (Eq, Ord, Show)
-type RuleOf a = Rule (ConstantOf a)
-
--- | A rule's orientation.
---
--- 'Oriented' and 'WeaklyOriented' rules are used only left-to-right.
--- 'Permutative' and 'Unoriented' rules are used bidirectionally.
-data Orientation f =
-    -- | An oriented rule.
-    Oriented
-    -- | A weakly oriented rule.
-    -- The first argument is the minimal constant, the second argument is a list
-    -- of terms which are weakly oriented in the rule.
-    -- 
-    -- A rule with orientation @'WeaklyOriented' k ts@ can be used unless
-    -- all terms in @ts@ are equal to @k@.
-  | WeaklyOriented {-# UNPACK #-} !(Fun f) [Term f]
-    -- | A permutative rule.
-    --
-    -- A rule with orientation @'Permutative' ts@ can be used if
-    -- @map fst ts@ is lexicographically greater than @map snd ts@.
-  | Permutative [(Term f, Term f)]
-    -- | An unoriented rule.
-  | Unoriented
-  deriving Show
-
-instance Eq (Orientation f) where _ == _ = True
-instance Ord (Orientation f) where compare _ _ = EQ
-
--- | Is a rule oriented or weakly oriented?
-oriented :: Orientation f -> Bool
-oriented Oriented{} = True
-oriented WeaklyOriented{} = True
-oriented _ = False
-
--- | Is a rule weakly oriented?
-weaklyOriented :: Orientation f -> Bool
-weaklyOriented WeaklyOriented{} = True
-weaklyOriented _ = False
-
-instance Symbolic (Rule f) where
-  type ConstantOf (Rule f) = f
-  termsDL (Rule or t u) = termsDL or `mplus` termsDL t `mplus` termsDL u
-  subst_ sub (Rule or t u) = Rule (subst_ sub or) (subst_ sub t) (subst_ sub u)
-
-instance f ~ g => Has (Rule f) (Term g) where
-  the = lhs
-
-instance Symbolic (Orientation f) where
-  type ConstantOf (Orientation f) = f
-
-  termsDL Oriented = mzero
-  termsDL (WeaklyOriented _ ts) = termsDL ts
-  termsDL (Permutative ts) = termsDL ts
-  termsDL Unoriented = mzero
-
-  subst_ _   Oriented = Oriented
-  subst_ sub (WeaklyOriented min ts) = WeaklyOriented min (subst_ sub ts)
-  subst_ sub (Permutative ts) = Permutative (subst_ sub ts)
-  subst_ _   Unoriented = Unoriented
-
-instance PrettyTerm f => Pretty (Rule f) where
-  pPrint (Rule or l r) =
-    pPrint l <+> text (showOrientation or) <+> pPrint r
-    where
-      showOrientation Oriented = "->"
-      showOrientation WeaklyOriented{} = "~>"
-      showOrientation Permutative{} = "<->"
-      showOrientation Unoriented = "="
-
--- | Turn a rule into an equation.
-unorient :: Rule f -> Equation f
-unorient (Rule _ l r) = l :=: r
-
--- | Turn an equation t :=: u into a rule t -> u by computing the
--- orientation info (e.g. oriented, permutative or unoriented).
---
--- Crashes if t -> u is not a valid rule, for example if there is
--- a variable in @u@ which is not in @t@. To prevent this happening,
--- combine with 'Twee.CP.split'.
-orient :: Function f => Equation f -> Rule f
-orient (t :=: u) = Rule o t u
-  where
-    o | lessEq u t =
-        case unify t u of
-          Nothing -> Oriented
-          Just sub
-            | allSubst (\_ (Cons t Empty) -> isMinimal t) sub ->
-              WeaklyOriented minimal (map (build . var . fst) (substToList sub))
-            | otherwise -> Unoriented
-      | lessEq t u = error "wrongly-oriented rule"
-      | not (null (usort (vars u) \\ usort (vars t))) =
-        error "unbound variables in rule"
-      | Just ts <- evalStateT (makePermutative t u) [],
-        permutativeOK t u ts =
-        Permutative ts
-      | otherwise = Unoriented
-
-    permutativeOK _ _ [] = True
-    permutativeOK t u ((Var x, Var y):xs) =
-      lessIn model u t == Just Strict &&
-      permutativeOK t' u' xs
-      where
-        model = modelFromOrder [Variable y, Variable x]
-        sub x' = if x == x' then var y else var x'
-        t' = subst sub t
-        u' = subst sub u
-
-    makePermutative t u = do
-      msub <- gets listToSubst
-      sub  <- lift msub
-      aux (subst sub t) (subst sub u)
-        where
-          aux (Var x) (Var y)
-            | x == y = return []
-            | otherwise = do
-              modify ((x, build $ var y):)
-              return [(build $ var x, build $ var y)]
-
-          aux (App f ts) (App g us)
-            | f == g =
-              fmap concat (zipWithM makePermutative (unpack ts) (unpack us))
-
-          aux _ _ = mzero
-
--- | Flip an unoriented rule so that it goes right-to-left.
-backwards :: Rule f -> Rule f
-backwards (Rule or t u) = Rule (back or) u t
-  where
-    back (Permutative xs) = Permutative (map swap xs)
-    back Unoriented = Unoriented
-    back _ = error "Can't turn oriented rule backwards"
-
---------------------------------------------------------------------------------
--- * Extra-fast rewriting, without proof output or unorientable rules.
---------------------------------------------------------------------------------
-
--- | Compute the normal form of a term wrt only oriented rules.
-{-# INLINEABLE simplify #-}
-simplify :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Term f
-simplify !idx !t = {-# SCC simplify #-} simplify1 idx t
-
-{-# INLINEABLE simplify1 #-}
-simplify1 :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Term f
-simplify1 idx t
-  | t == u = t
-  | otherwise = simplify idx u
-  where
-    u = build (simp (singleton t))
-
-    simp Empty = mempty
-    simp (Cons (Var x) t) = var x `mappend` simp t
-    simp (Cons t u)
-      | Just (rule, sub) <- simpleRewrite idx t =
-        Term.subst sub (rhs rule) `mappend` simp u
-    simp (Cons (App f ts) us) =
-      app f (simp ts) `mappend` simp us
-
--- | Check if a term can be simplified.
-{-# INLINEABLE canSimplify #-}
-canSimplify :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Bool
-canSimplify idx t = canSimplifyList idx (singleton t)
-
-{-# INLINEABLE canSimplifyList #-}
-canSimplifyList :: (Function f, Has a (Rule f)) => Index f a -> TermList f -> Bool
-canSimplifyList idx t =
-  {-# SCC canSimplifyList #-}
-  any (isJust . simpleRewrite idx) (filter isApp (subtermsList t))
-
--- | Find a simplification step that applies to a term.
-{-# INLINEABLE simpleRewrite #-}
-simpleRewrite :: (Function f, Has a (Rule f)) => Index f a -> Term f -> Maybe (Rule f, Subst f)
-simpleRewrite idx t =
-  -- Use instead of maybeToList to make fusion work
-  foldr (\x _ -> Just x) Nothing $ do
-    rule <- the <$> Index.approxMatches t idx
-    guard (oriented (orientation rule))
-    sub <- maybeToList (match (lhs rule) t)
-    guard (reducesOriented rule sub)
-    return (rule, sub)
-
---------------------------------------------------------------------------------
--- * Rewriting, with proof output.
---------------------------------------------------------------------------------
-
--- | A strategy gives a set of possible reductions for a term.
-type Strategy f = Term f -> [Reduction f]
-
--- | A multi-step rewrite proof @t ->* u@
-data Reduction f =
-    -- | Apply a single rewrite rule to the root of a term
-    Step {-# UNPACK #-} !(Lemma f) !(Rule f) !(Subst f)
-    -- | Reflexivity
-  | Refl {-# UNPACK #-} !(Term f)
-    -- | Transivitity
-  | Trans !(Reduction f) !(Reduction f)
-    -- | Congruence
-  | Cong {-# UNPACK #-} !(Fun f) ![Reduction f]
-  deriving Show
-
-instance Symbolic (Reduction f) where
-  type ConstantOf (Reduction f) = f
-  termsDL (Step _ _ sub) = termsDL sub
-  termsDL (Refl t) = termsDL t
-  termsDL (Trans p q) = termsDL p `mplus` termsDL q
-  termsDL (Cong _ ps) = termsDL ps
-
-  subst_ sub (Step lemma rule s) = Step lemma rule (subst_ sub s)
-  subst_ sub (Refl t) = Refl (subst_ sub t)
-  subst_ sub (Trans p q) = Trans (subst_ sub p) (subst_ sub q)
-  subst_ sub (Cong f ps) = Cong f (subst_ sub ps)
-
-instance Function f => Pretty (Reduction f) where
-  pPrint = pPrint . reductionProof
-
--- | A smart constructor for Trans which simplifies Refl.
-trans :: Reduction f -> Reduction f -> Reduction f
-trans Refl{} p = p
-trans p Refl{} = p
--- Make right-associative to improve performance of 'result'
-trans p (Trans q r) = Trans (Trans p q) r
-trans p q = Trans p q
-
--- | A smart constructor for Cong which simplifies Refl.
-cong :: Fun f -> [Reduction f] -> Reduction f
-cong f ps
-  | all isRefl ps = Refl (result (reduce (Cong f ps)))
-  | otherwise = Cong f ps
-  where
-    isRefl Refl{} = True
-    isRefl _ = False
-
--- | The list of all rewrite rules used in a rewrite proof.
-steps :: Reduction f -> [Reduction f]
-steps r = aux r []
-  where
-    aux step@Step{} = (step:)
-    aux (Refl _) = id
-    aux (Trans p q) = aux p . aux q
-    aux (Cong _ ps) = foldr (.) id (map aux ps)
-
--- | Turn a reduction into a proof.
-reductionProof :: Reduction f -> Derivation f
-reductionProof (Step lemma _ sub) =
-  Proof.lemma lemma sub
-reductionProof (Refl t) = Proof.Refl t
-reductionProof (Trans p q) =
-  Proof.trans (reductionProof p) (reductionProof q)
-reductionProof (Cong f ps) = Proof.cong f (map reductionProof ps)
-
--- | Construct a basic rewrite step.
-{-# INLINE step #-}
-step :: (Has a (Rule f), Has a (Lemma f)) => a -> Subst f -> Reduction f
-step x sub = Step (the x) (the x) sub
-
-----------------------------------------------------------------------
--- | A rewrite proof with the final term attached.
--- Has an @Ord@ instance which compares the final term.
-----------------------------------------------------------------------
-
-data Resulting f =
-  Resulting {
-    result :: {-# UNPACK #-} !(Term f),
-    reduction :: !(Reduction f) }
-  deriving Show
-
-instance Eq (Resulting f) where x == y = compare x y == EQ
-instance Ord (Resulting f) where compare = comparing result
-
-instance Symbolic (Resulting f) where
-  type ConstantOf (Resulting f) = f
-  termsDL (Resulting t red) =
-    termsDL t `mplus` termsDL red
-  subst_ sub (Resulting t red) =
-    Resulting (subst_ sub t) (subst_ sub red)
-
-instance Function f => Pretty (Resulting f) where
-  pPrint = pPrint . reduction
-
--- | Construct a 'Resulting' from a 'Reduction'.
-reduce :: Reduction f -> Resulting f
-reduce p =
-  Resulting (res p) p
-  where
-    res (Trans _ q) = res q
-    res (Refl t) = t
-    res p = {-# SCC res_emitRes #-} build (emitResult p)
-
-    emitResult (Step _ r sub) = Term.subst sub (rhs r)
-    emitResult (Refl t) = builder t
-    emitResult (Trans _ q) = emitResult q
-    emitResult (Cong f ps) = app f (map emitResult ps)
-
---------------------------------------------------------------------------------
--- * Strategy combinators.
---------------------------------------------------------------------------------
-
--- | Normalise a term wrt a particular strategy.
-{-# INLINE normaliseWith #-}
-normaliseWith :: Function f => (Term f -> Bool) -> Strategy f -> Term f -> Resulting f
-normaliseWith ok strat t = {-# SCC normaliseWith #-} res
-  where
-    res = aux 0 (Refl t) t
-    aux 1000 p _ =
-      error $
-        "Possibly nonterminating rewrite:\n" ++ prettyShow p
-    aux n p t =
-      case parallel strat t of
-        (q:_) | u <- result (reduce q), ok u ->
-          aux (n+1) (p `trans` q) u
-        _ -> Resulting t p
-
--- | Compute all normal forms of a set of terms wrt a particular strategy.
-normalForms :: Function f => Strategy f -> [Resulting f] -> Set (Resulting f)
-normalForms strat ps = snd (successorsAndNormalForms strat ps)
-
--- | Compute all successors of a set of terms (a successor of a term @t@
--- is a term @u@ such that @t ->* u@).
-successors :: Function f => Strategy f -> [Resulting f] -> Set (Resulting f)
-successors strat ps = Set.union qs rs
-  where
-    (qs, rs) = successorsAndNormalForms strat ps
-
-{-# INLINEABLE successorsAndNormalForms #-}
-successorsAndNormalForms :: Function f => Strategy f -> [Resulting f] ->
-  (Set (Resulting f), Set (Resulting f))
-successorsAndNormalForms strat ps =
-  {-# SCC successorsAndNormalForms #-} go Set.empty Set.empty ps
-  where
-    go dead norm [] = (dead, norm)
-    go dead norm (p:ps)
-      | p `Set.member` dead = go dead norm ps
-      | p `Set.member` norm = go dead norm ps
-      | null qs = go dead (Set.insert p norm) ps
-      | otherwise =
-        go (Set.insert p dead) norm (qs ++ ps)
-      where
-        qs =
-          [ reduce (reduction p `Trans` q)
-          | q <- anywhere strat (result p) ]
-
--- | Apply a strategy anywhere in a term.
-anywhere :: Strategy f -> Strategy f
-anywhere strat t = strat t ++ nested (anywhere strat) t
-
--- | Apply a strategy to some child of the root function.
-nested :: Strategy f -> Strategy f
-nested _ Var{} = []
-nested strat (App f ts) =
-  cong f <$> inner [] ts
-  where
-    inner _ Empty = []
-    inner before (Cons t u) =
-      [ reverse before ++ [p] ++ map Refl (unpack u)
-      | p <- strat t ] ++
-      inner (Refl t:before) u
-
--- | Apply a strategy in parallel in as many places as possible.
--- Takes only the first rewrite of each strategy.
-{-# INLINE parallel #-}
-parallel :: PrettyTerm f => Strategy f -> Strategy f
-parallel strat t =
-  case par t of
-    Refl{} -> []
-    p -> [p]
-  where
-    par t | p:_ <- strat t = p
-    par (App f ts) = cong f (inner [] ts)
-    par t = Refl t
-
-    inner before Empty = reverse before
-    inner before (Cons t u) = inner (par t:before) u
-
---------------------------------------------------------------------------------
--- * Basic strategies. These only apply at the root of the term.
---------------------------------------------------------------------------------
-
--- | A strategy which rewrites using an index.
-{-# INLINE rewrite #-}
-rewrite :: (Function f, Has a (Rule f), Has a (Lemma f)) => (Rule f -> Subst f -> Bool) -> Index f a -> Strategy f
-rewrite p rules t = do
-  rule <- Index.approxMatches t rules
-  tryRule p rule t
-
--- | A strategy which applies one rule only.
-{-# INLINEABLE tryRule #-}
-tryRule :: (Function f, Has a (Rule f), Has a (Lemma f)) => (Rule f -> Subst f -> Bool) -> a -> Strategy f
-tryRule p rule t = do
-  sub <- maybeToList (match (lhs (the rule)) t)
-  guard (p (the rule) sub)
-  return (step rule sub)
-
--- | Check if a rule can be applied, given an ordering <= on terms.
-{-# INLINEABLE reducesWith #-}
-reducesWith :: Function f => (Term f -> Term f -> Bool) -> Rule f -> Subst f -> Bool
-reducesWith _ (Rule Oriented _ _) _ = True
-reducesWith _ (Rule (WeaklyOriented min ts) _ _) sub =
-  -- Be a bit careful here not to build new terms
-  -- (reducesWith is used in simplify).
-  -- This is the same as:
-  --   any (not . isMinimal) (subst sub ts)
-  any (not . isMinimal . expand) ts
-  where
-    expand t@(Var x) = fromMaybe t (Term.lookup x sub)
-    expand t = t
-
-    isMinimal (App f Empty) = f == min
-    isMinimal _ = False
-reducesWith p (Rule (Permutative ts) _ _) sub =
-  aux ts
-  where
-    aux [] = False
-    aux ((t, u):ts)
-      | t' == u' = aux ts
-      | otherwise = p u' t'
-      where
-        t' = subst sub t
-        u' = subst sub u
-reducesWith p (Rule Unoriented t u) sub =
-  p u' t' && u' /= t'
-  where
-    t' = subst sub t
-    u' = subst sub u
-
--- | Check if a rule can be applied normally.
-{-# INLINEABLE reduces #-}
-reduces :: Function f => Rule f -> Subst f -> Bool
-reduces rule sub = reducesWith lessEq rule sub
-
--- | Check if a rule can be applied and is oriented.
-{-# INLINEABLE reducesOriented #-}
-reducesOriented :: Function f => Rule f -> Subst f -> Bool
-reducesOriented rule sub =
-  oriented (orientation rule) && reducesWith undefined rule sub
-
--- | Check if a rule can be applied in a particular model.
-{-# INLINEABLE reducesInModel #-}
-reducesInModel :: Function f => Model f -> Rule f -> Subst f -> Bool
-reducesInModel cond rule sub =
-  reducesWith (\t u -> isJust (lessIn cond t u)) rule sub
-
--- | Check if a rule can be applied to the Skolemised version of a term.
-{-# INLINEABLE reducesSkolem #-}
-reducesSkolem :: Function f => Rule f -> Subst f -> Bool
-reducesSkolem rule sub =
-  reducesWith (\t u -> lessEq (subst skolemise t) (subst skolemise u)) rule sub
-  where
-    skolemise = con . skolem
diff --git a/src/Twee/Rule/Index.hs b/src/Twee/Rule/Index.hs
deleted file mode 100644
--- a/src/Twee/Rule/Index.hs
+++ /dev/null
@@ -1,45 +0,0 @@
-{-# LANGUAGE RecordWildCards, ScopedTypeVariables, FlexibleContexts #-}
-module Twee.Rule.Index(
-  RuleIndex(..),
-  empty, insert, delete,
-  approxMatches, matches, lookup) where
-
-import Prelude hiding (lookup)
-import Twee.Base hiding (lookup, empty)
-import Twee.Rule
-import Twee.Index hiding (insert, delete, empty)
-import qualified Twee.Index as Index
-
-data RuleIndex f a =
-  RuleIndex {
-    index_oriented :: !(Index f a),
-    index_weak     :: !(Index f a),
-    index_all      :: !(Index f a) }
-  deriving Show
-
-empty :: RuleIndex f a
-empty = RuleIndex Index.empty Index.empty Index.empty
-
-insert :: forall f a. Has a (Rule f) => Term f -> a -> RuleIndex f a -> RuleIndex f a
-insert t x RuleIndex{..} =
-  RuleIndex {
-    index_oriented = insertWhen (oriented or) index_oriented,
-    index_weak = insertWhen (weaklyOriented or) index_weak,
-    index_all = insertWhen True index_all }
-  where
-    Rule or _ _ = the x :: Rule f
-
-    insertWhen False idx = idx
-    insertWhen True idx = Index.insert t x idx
-
-delete :: forall f a. (Eq a, Has a (Rule f)) => Term f -> a -> RuleIndex f a -> RuleIndex f a
-delete t x RuleIndex{..} =
-  RuleIndex {
-    index_oriented = deleteWhen (oriented or) index_oriented,
-    index_weak = deleteWhen (weaklyOriented or) index_weak,
-    index_all = deleteWhen True index_all }
-  where
-    Rule or _ _ = the x :: Rule f
-
-    deleteWhen False idx = idx
-    deleteWhen True idx = Index.delete t x idx
diff --git a/src/Twee/Task.hs b/src/Twee/Task.hs
deleted file mode 100644
--- a/src/Twee/Task.hs
+++ /dev/null
@@ -1,56 +0,0 @@
--- | A module which can run housekeeping tasks every so often.
-{-# LANGUAGE RecordWildCards #-}
-module Twee.Task(Task, newTask, checkTask) where
-
-import System.CPUTime
-import Data.IORef
-import Control.Monad.IO.Class
-
-data TaskData m a =
-  TaskData {
-    -- When was the task created?
-    task_start :: !Integer,
-    -- When was the task last run?
-    task_last :: !Integer,
-    -- How long have we spent on this task so far?
-    task_spent :: !Integer,
-    -- How often should we run this task at most, in seconds?
-    task_frequency :: !Double,
-    -- What proportion of our time should we spend on the task?
-    task_budget :: !Double,
-    -- The task itself
-    task_what :: m a }
-
--- | A task which runs in the monad @m@ and produces a value of type @a@.
-newtype Task m a = Task (IORef (TaskData m a))
-
--- | Create a new task that should be run a certain proportion
--- of the time. The first argument is how often in seconds the
--- task should run, at most. The second argument is the maximum
--- percentage of time that should be spent on the task.
-newTask :: MonadIO m => Double -> Double -> m a -> m (Task m a)
-newTask freq budget what = liftIO $ do
-  now <- getCPUTime
-  Task <$> newIORef (TaskData now now 0 freq budget what)
-
--- | Run a task if it's time to run it.
-checkTask :: MonadIO m => Task m a -> m (Maybe a)
-checkTask (Task ref) = do
-  task@TaskData{..} <- liftIO $ readIORef ref
-  now <- liftIO getCPUTime
-  if not (taskDue now task) then return Nothing else do
-    res <- task_what
-    after <- liftIO getCPUTime
-    liftIO $ writeIORef ref task {
-      task_last = after,
-      task_spent = task_spent + (after-now) }
-    return (Just res)
-
--- Check if a task should be run now.
-taskDue :: Integer -> TaskData m a -> Bool
-taskDue now TaskData{..} =
-  -- Don't run more than the frequency says.
-  fromInteger (now - task_last) >= task_frequency * 10^12 &&
-  -- Run if we spent less than task_budget proportion of the total time so far.
-  -- Use > rather than >= so that tasks with zero budget never get run.
-  fromInteger (now - task_start) * task_budget > fromInteger task_spent
diff --git a/src/Twee/Term.hs b/src/Twee/Term.hs
deleted file mode 100644
--- a/src/Twee/Term.hs
+++ /dev/null
@@ -1,646 +0,0 @@
--- | Terms and substitutions.
---
--- Terms in twee are represented as arrays rather than as an algebraic data
--- type. This module defines pattern synonyms ('App', 'Var', 'Cons', 'Empty')
--- which means that pattern matching on terms works just as normal.
--- The pattern synonyms can not be used to create new terms; for that you
--- have to use a builder interface ('Build').
---
--- This module also provides:
---
---   * pattern synonyms for iterating through a term one symbol at a time
---     ('ConsSym');
---   * substitutions ('Substitution', 'Subst', 'subst');
---   * unification ('unify') and matching ('match');
---   * miscellaneous useful functions on terms.
-{-# LANGUAGE BangPatterns, PatternSynonyms, ViewPatterns, TypeFamilies, OverloadedStrings, ScopedTypeVariables #-}
-module Twee.Term(
-  -- * Terms
-  Term, pattern Var, pattern App, isApp, isVar, singleton, len,
-  -- * Termlists
-  TermList, pattern Empty, pattern Cons, pattern ConsSym,
-  pattern UnsafeCons, pattern UnsafeConsSym,
-  empty, unpack, lenList,
-  -- * Function symbols and variables
-  Fun, fun, fun_id, fun_value, pattern F, Var(..), 
-  -- * Building terms
-  Build(..),
-  Builder,
-  build, buildList,
-  con, app, var,
-  -- * Access to subterms
-  children, properSubterms, subtermsList, subterms, occurs, isSubtermOf, isSubtermOfList, at,
-  -- * Substitutions
-  Substitution(..),
-  subst,
-  Subst(..),
-  -- ** Constructing and querying substitutions
-  emptySubst, listToSubst, substToList,
-  lookup, lookupList,
-  extend, extendList, unsafeExtendList,
-  retract,
-  -- ** Other operations on substitutions
-  foldSubst, allSubst, substDomain,
-  substSize,
-  substCompose, substCompatible, substUnion, idempotent, idempotentOn,
-  canonicalise,
-  -- * Matching
-  match, matchIn, matchList, matchListIn, isInstanceOf, isVariantOf,
-  -- * Unification
-  unify, unifyList,
-  unifyTri, unifyListTri,
-  TriangleSubst(..),
-  close,
-  -- * Positions in terms
-  positionToPath, pathToPosition,
-  replacePosition,
-  replacePositionSub,
-  -- * Miscellaneous functions
-  bound, boundList, boundLists, mapFun, mapFunList, (<<)) where
-
-import Prelude hiding (lookup)
-import Twee.Term.Core hiding (F)
-import Data.List hiding (lookup, find)
-import Data.Maybe
-import Data.Monoid
-import Data.IntMap.Strict(IntMap)
-import qualified Data.IntMap.Strict as IntMap
-
---------------------------------------------------------------------------------
--- * A type class for builders
---------------------------------------------------------------------------------
-
--- | Instances of 'Build' can be turned into terms using 'build' or 'buildList',
--- and turned into term builders using 'builder'. Has instances for terms,
--- termlists, builders, and Haskell lists.
-class Build a where
-  -- | The underlying type of function symbols.
-  type BuildFun a
-  -- | Convert a value into a 'Builder'.
-  builder :: a -> Builder (BuildFun a)
-
-instance Build (Builder f) where
-  type BuildFun (Builder f) = f
-  builder = id
-
-instance Build (Term f) where
-  type BuildFun (Term f) = f
-  builder = emitTermList . singleton
-
-instance Build (TermList f) where
-  type BuildFun (TermList f) = f
-  builder = emitTermList
-
-instance Build a => Build [a] where
-  type BuildFun [a] = BuildFun a
-  {-# INLINE builder #-}
-  builder = mconcat . map builder
-
--- | Build a term. The given builder must produce exactly one term.
-{-# INLINE build #-}
-build :: Build a => a -> Term (BuildFun a)
-build x =
-  case buildList x of
-    Cons t Empty -> t
-
--- | Build a termlist.
-{-# INLINE buildList #-}
-buildList :: Build a => a -> TermList (BuildFun a)
-buildList x = {-# SCC buildList #-} buildTermList (builder x)
-
--- | Build a constant (a function with no arguments).
-{-# INLINE con #-}
-con :: Fun f -> Builder f
-con x = emitApp x mempty
-
--- | Build a function application.
-{-# INLINE app #-}
-app :: Build a => Fun (BuildFun a) -> a -> Builder (BuildFun a)
-app f ts = emitApp f (builder ts)
-
--- | Build a variable.
-var :: Var -> Builder f
-var = emitVar
-
---------------------------------------------------------------------------------
--- Functions for substitutions.
---------------------------------------------------------------------------------
-
-{-# INLINE substToList' #-}
-substToList' :: Subst f -> [(Var, TermList f)]
-substToList' (Subst sub) = [(V x, t) | (x, t) <- IntMap.toList sub]
-
--- | Convert a substitution to a list of bindings.
-{-# INLINE substToList #-}
-substToList :: Subst f -> [(Var, Term f)]
-substToList sub =
-  [(x, t) | (x, Cons t Empty) <- substToList' sub]
-
--- | Fold a function over a substitution.
-{-# INLINE foldSubst #-}
-foldSubst :: (Var -> TermList f -> b -> b) -> b -> Subst f -> b
-foldSubst op e !sub = foldr (uncurry op) e (substToList' sub)
-
--- | Check if all bindings of a substitution satisfy a given property.
-{-# INLINE allSubst #-}
-allSubst :: (Var -> TermList f -> Bool) -> Subst f -> Bool
-allSubst p = foldSubst (\x t y -> p x t && y) True
-
--- | Compute the set of variables bound by a substitution.
-{-# INLINE substDomain #-}
-substDomain :: Subst f -> [Var]
-substDomain (Subst sub) = map V (IntMap.keys sub)
-
---------------------------------------------------------------------------------
--- Substitution.
---------------------------------------------------------------------------------
-
--- | A class for values which act as substitutions.
---
--- Instances include 'Subst' as well as functions from variables to terms.
-class Substitution s where
-  -- | The underlying type of function symbols.
-  type SubstFun s
-
-  -- | Apply the substitution to a variable.
-  evalSubst :: s -> Var -> Builder (SubstFun s)
-
-  -- | Apply the substitution to a termlist.
-  {-# INLINE substList #-}
-  substList :: s -> TermList (SubstFun s) -> Builder (SubstFun s)
-  substList sub ts = aux ts
-    where
-      aux Empty = mempty
-      aux (Cons (Var x) ts) = evalSubst sub x <> aux ts
-      aux (Cons (App f ts) us) = app f (aux ts) <> aux us
-
-instance (Build a, v ~ Var) => Substitution (v -> a) where
-  type SubstFun (v -> a) = BuildFun a
-
-  {-# INLINE evalSubst #-}
-  evalSubst sub x = builder (sub x)
-
-instance Substitution (Subst f) where
-  type SubstFun (Subst f) = f
-
-  {-# INLINE evalSubst #-}
-  evalSubst sub x =
-    case lookupList x sub of
-      Nothing -> var x
-      Just ts -> builder ts
-
--- | Apply a substitution to a term.
-{-# INLINE subst #-}
-subst :: Substitution s => s -> Term (SubstFun s) -> Builder (SubstFun s)
-subst sub t = substList sub (singleton t)
-
--- | A substitution which maps variables to terms of type @'Term' f@.
-newtype Subst f =
-  Subst {
-    unSubst :: IntMap (TermList f) }
-  deriving Eq
-
--- | Return the highest-number variable in a substitution plus 1.
-{-# INLINE substSize #-}
-substSize :: Subst f -> Int
-substSize (Subst sub)
-  | IntMap.null sub = 0
-  | otherwise = fst (IntMap.findMax sub) + 1
-
--- | Look up a variable in a substitution, returning a termlist.
-{-# INLINE lookupList #-}
-lookupList :: Var -> Subst f -> Maybe (TermList f)
-lookupList x (Subst sub) = IntMap.lookup (var_id x) sub
-
--- | Add a new binding to a substitution, giving a termlist.
-{-# INLINE extendList #-}
-extendList :: Var -> TermList f -> Subst f -> Maybe (Subst f)
-extendList x !t (Subst sub) =
-  case IntMap.lookup (var_id x) sub of
-    Nothing -> Just $! Subst (IntMap.insert (var_id x) t sub)
-    Just u
-      | t == u    -> Just (Subst sub)
-      | otherwise -> Nothing
-
--- | Remove a binding from a substitution.
-{-# INLINE retract #-}
-retract :: Var -> Subst f -> Subst f
-retract x (Subst sub) = Subst (IntMap.delete (var_id x) sub)
-
--- | Add a new binding to a substitution.
--- Overwrites any existing binding.
-{-# INLINE unsafeExtendList #-}
-unsafeExtendList :: Var -> TermList f -> Subst f -> Subst f
-unsafeExtendList x !t (Subst sub) = Subst (IntMap.insert (var_id x) t sub)
-
--- | Compose two substitutions.
-substCompose :: Substitution s => Subst (SubstFun s) -> s -> Subst (SubstFun s)
-substCompose (Subst !sub1) !sub2 =
-  Subst (IntMap.map (buildList . substList sub2) sub1)
-
--- | Check if two substitutions are compatible (they do not send the same
--- variable to different terms).
-substCompatible :: Subst f -> Subst f -> Bool
-substCompatible (Subst !sub1) (Subst !sub2) =
-  IntMap.null (IntMap.mergeWithKey f g h sub1 sub2)
-  where
-    f _ t u
-      | t == u = Nothing
-      | otherwise = Just t
-    g _ = IntMap.empty
-    h _ = IntMap.empty
-
--- | Take the union of two substitutions.
--- The substitutions must be compatible, which is not checked.
-substUnion :: Subst f -> Subst f -> Subst f
-substUnion (Subst !sub1) (Subst !sub2) =
-  Subst (IntMap.union sub1 sub2)
-
--- | Check if a substitution is idempotent (applying it twice has the same
--- effect as applying it once).
-{-# INLINE idempotent #-}
-idempotent :: Subst f -> Bool
-idempotent !sub = allSubst (\_ t -> sub `idempotentOn` t) sub
-
--- | Check if a substitution has no effect on a given term.
-{-# INLINE idempotentOn #-}
-idempotentOn :: Subst f -> TermList f -> Bool
-idempotentOn !sub = aux
-  where
-    aux Empty = True
-    aux (ConsSym App{} t) = aux t
-    aux (Cons (Var x) t) = isNothing (lookupList x sub) && aux t
-
--- | Iterate a triangle substitution to make it idempotent.
-close :: TriangleSubst f -> Subst f
-close (Triangle sub)
-  | idempotent sub = sub
-  | otherwise      = close (Triangle (substCompose sub sub))
-
--- | Return a substitution which renames the variables of a list of terms to put
--- them in a canonical order.
-canonicalise :: [TermList f] -> Subst f
-canonicalise [] = emptySubst
-canonicalise (t:ts) = loop emptySubst vars t ts
-  where
-    (V m, V n) = boundLists (t:ts)
-    vars =
-      buildTermList $
-        -- Produces two variables when the term is ground
-        -- (n = minBound, m = maxBound), which is OK.
-        mconcat [emitVar (V x) | x <- [0..n-m+1]]
-
-    loop !_ !_ !_ !_ | False = undefined
-    loop sub _ Empty [] = sub
-    loop sub Empty _ _ = sub
-    loop sub vs Empty (t:ts) = loop sub vs t ts
-    loop sub vs (ConsSym App{} t) ts = loop sub vs t ts
-    loop sub vs0@(Cons v vs) (Cons (Var x) t) ts =
-      case extend x v sub of
-        Just sub -> loop sub vs  t ts
-        Nothing  -> loop sub vs0 t ts
-
--- | The empty substitution.
-{-# NOINLINE emptySubst #-}
-emptySubst = Subst IntMap.empty
-
--- | Construct a substitution from a list.
--- Returns @Nothing@ if a variable is bound to several different terms.
-listToSubst :: [(Var, Term f)] -> Maybe (Subst f)
-listToSubst sub = matchList pat t
-  where
-    pat = buildList (map (var . fst) sub)
-    t   = buildList (map snd sub)
-
---------------------------------------------------------------------------------
--- Matching.
---------------------------------------------------------------------------------
-
--- | @'match' pat t@ matches the term @t@ against the pattern @pat@.
-{-# INLINE match #-}
-match :: Term f -> Term f -> Maybe (Subst f)
-match pat t = matchList (singleton pat) (singleton t)
-
--- | A variant of 'match' which extends an existing substitution.
-{-# INLINE matchIn #-}
-matchIn :: Subst f -> Term f -> Term f -> Maybe (Subst f)
-matchIn sub pat t = matchListIn sub (singleton pat) (singleton t)
-
--- | A variant of 'match' which works on termlists.
-{-# INLINE matchList #-}
-matchList :: TermList f -> TermList f -> Maybe (Subst f)
-matchList pat t = matchListIn emptySubst pat t
-
--- | A variant of 'match' which works on termlists
--- and extends an existing substitution.
-matchListIn :: Subst f -> TermList f -> TermList f -> Maybe (Subst f)
-matchListIn !sub !pat !t
-  | lenList t < lenList pat = Nothing
-  | otherwise =
-    let loop !_ !_ !_ | False = undefined
-        loop sub Empty Empty = Just sub
-        loop sub (ConsSym (App f _) pat) (ConsSym (App g _) t)
-          | f == g = loop sub pat t
-        loop sub (Cons (Var x) pat) (Cons t u) = do
-          sub <- extend x t sub
-          loop sub pat u
-        loop _ _ _ = Nothing
-    in {-# SCC match #-} loop sub pat t
-
---------------------------------------------------------------------------------
--- Unification.
---------------------------------------------------------------------------------
-
--- | A triangle substitution is one in which variables can be defined in terms
--- of each other, though not in a circular way.
---
--- The main use of triangle substitutions is in unification; 'unifyTri' returns
--- one. A triangle substitution can be converted to an ordinary substitution
--- with 'close', or used directly using its 'Substitution' instance.
-newtype TriangleSubst f = Triangle { unTriangle :: Subst f }
-  deriving Show
-
-instance Substitution (TriangleSubst f) where
-  type SubstFun (TriangleSubst f) = f
-
-  {-# INLINE evalSubst #-}
-  evalSubst (Triangle sub) x =
-    case lookupList x sub of
-      Nothing  -> var x
-      Just ts  -> substList (Triangle sub) ts
-
-  -- Redefine substList to get better inlining behaviour
-  {-# INLINE substList #-}
-  substList (Triangle sub) ts = aux ts
-    where
-      aux Empty = mempty
-      aux (Cons (Var x) ts) = auxVar x <> aux ts
-      aux (Cons (App f ts) us) = app f (aux ts) <> aux us
-
-      auxVar x =
-        case lookupList x sub of
-          Nothing -> var x
-          Just ts -> aux ts
-
--- | Unify two terms.
-unify :: Term f -> Term f -> Maybe (Subst f)
-unify t u = unifyList (singleton t) (singleton u)
-
--- | Unify two termlists.
-unifyList :: TermList f -> TermList f -> Maybe (Subst f)
-unifyList t u = do
-  sub <- unifyListTri t u
-  -- Not strict so that isJust (unify t u) doesn't force the substitution
-  return (close sub)
-
--- | Unify two terms, returning a triangle substitution.
--- This is slightly faster than 'unify'.
-unifyTri :: Term f -> Term f -> Maybe (TriangleSubst f)
-unifyTri t u = unifyListTri (singleton t) (singleton u)
-
--- | Unify two termlists, returning a triangle substitution.
--- This is slightly faster than 'unify'.
-unifyListTri :: TermList f -> TermList f -> Maybe (TriangleSubst f)
-unifyListTri !t !u = fmap Triangle ({-# SCC unify #-} loop emptySubst t u)
-  where
-    loop !_ !_ !_ | False = undefined
-    loop sub Empty Empty = Just sub
-    loop sub (ConsSym (App f _) t) (ConsSym (App g _) u)
-      | f == g = loop sub t u
-    loop sub (Cons (Var x) t) (Cons u v) = do
-      sub <- var sub x u
-      loop sub t v
-    loop sub (Cons t u) (Cons (Var x) v) = do
-      sub <- var sub x t
-      loop sub u v
-    loop _ _ _ = Nothing
-
-    var sub x t =
-      case lookupList x sub of
-        Just u -> loop sub u (singleton t)
-        Nothing -> var1 sub x t
-
-    var1 sub x t@(Var y)
-      | x == y = return sub
-      | otherwise =
-        case lookup y sub of
-          Just t  -> var1 sub x t
-          Nothing -> extend x t sub
-
-    var1 sub x t = do
-      occurs sub x (singleton t)
-      extend x t sub
-
-    occurs !_ !_ Empty = Just ()
-    occurs sub x (ConsSym App{} t) = occurs sub x t
-    occurs sub x (ConsSym (Var y) t)
-      | x == y = Nothing
-      | otherwise = do
-          occurs sub x t
-          case lookupList y sub of
-            Nothing -> Just ()
-            Just u  -> occurs sub x u
-
---------------------------------------------------------------------------------
--- Miscellaneous stuff.
---------------------------------------------------------------------------------
-
--- | The empty termlist.
-{-# NOINLINE empty #-}
-empty :: forall f. TermList f
-empty = buildList (mempty :: Builder f)
-
--- | Get the children (direct subterms) of a term.
-children :: Term f -> TermList f
-children t =
-  case singleton t of
-    UnsafeConsSym _ ts -> ts
-
--- | Convert a termlist into an ordinary list of terms.
-unpack :: TermList f -> [Term f]
-unpack t = unfoldr op t
-  where
-    op Empty = Nothing
-    op (Cons t ts) = Just (t, ts)
-
-instance Show (Term f) where
-  show (Var x) = show x
-  show (App f Empty) = show f
-  show (App f ts) = show f ++ "(" ++ intercalate "," (map show (unpack ts)) ++ ")"
-
-instance Show (TermList f) where
-  show = show . unpack
-
-instance Show (Subst f) where
-  show subst =
-    show
-      [ (i, t)
-      | i <- [0..substSize subst-1],
-        Just t <- [lookup (V i) subst] ]
-
--- | Look up a variable in a substitution.
-{-# INLINE lookup #-}
-lookup :: Var -> Subst f -> Maybe (Term f)
-lookup x s = do
-  Cons t Empty <- lookupList x s
-  return t
-
--- | Add a new binding to a substitution.
-{-# INLINE extend #-}
-extend :: Var -> Term f -> Subst f -> Maybe (Subst f)
-extend x t sub = extendList x (singleton t) sub
-
--- | Find the length of a term.
-{-# INLINE len #-}
-len :: Term f -> Int
-len = lenList . singleton
-
--- | Return the lowest- and highest-numbered variables in a term.
-{-# INLINE bound #-}
-bound :: Term f -> (Var, Var)
-bound t = boundList (singleton t)
-
--- | Return the lowest- and highest-numbered variables in a termlist.
-{-# INLINE boundList #-}
-boundList :: TermList f -> (Var, Var)
-boundList t = boundListFrom (V maxBound) (V minBound) t
-
-boundListFrom :: Var -> Var -> TermList f -> (Var, Var)
-boundListFrom !m !n Empty = (m, n)
-boundListFrom m n (ConsSym App{} t) = boundListFrom m n t
-boundListFrom m n (ConsSym (Var x) t) =
-  boundListFrom (m `min` x) (n `max` x) t
-
--- | Return the lowest- and highest-numbered variables in a list of termlists.
-boundLists :: [TermList f] -> (Var, Var)
-boundLists t = boundListsFrom (V maxBound) (V minBound) t
-
-boundListsFrom :: Var -> Var -> [TermList f] -> (Var, Var)
-boundListsFrom !m !n [] = (m, n)
-boundListsFrom m n (t:ts) =
-  let
-    (m', n') = boundListFrom m n t
-  in
-    boundListsFrom m' n' ts
-
--- | Check if a variable occurs in a term.
-{-# INLINE occurs #-}
-occurs :: Var -> Term f -> Bool
-occurs x t = occursList x (singleton t)
-
--- | Find all subterms of a termlist.
-{-# INLINE subtermsList #-}
-subtermsList :: TermList f -> [Term f]
-subtermsList t = unfoldr op t
-  where
-    op Empty = Nothing
-    op (ConsSym t u) = Just (t, u)
-
--- | Find all subterms of a term.
-{-# INLINE subterms #-}
-subterms :: Term f -> [Term f]
-subterms = subtermsList . singleton
-
--- | Find all proper subterms of a term.
-{-# INLINE properSubterms #-}
-properSubterms :: Term f -> [Term f]
-properSubterms = subtermsList . children
-
--- | Check if a term is a function application.
-isApp :: Term f -> Bool
-isApp App{} = True
-isApp _     = False
-
--- | Check if a term is a variable
-isVar :: Term f -> Bool
-isVar Var{} = True
-isVar _     = False
-
--- | @t \`'isInstanceOf'\` pat@ checks if @t@ is an instance of @pat@.
-isInstanceOf :: Term f -> Term f -> Bool
-t `isInstanceOf` pat = isJust (match pat t)
-
--- | Check if two terms are renamings of one another.
-isVariantOf :: Term f -> Term f -> Bool
-t `isVariantOf` u = t `isInstanceOf` u && u `isInstanceOf` t
-
--- | Is a term a subterm of another one?
-isSubtermOf :: Term f -> Term f -> Bool
-t `isSubtermOf` u = t `isSubtermOfList` singleton u
-
--- | Map a function over the function symbols in a term.
-mapFun :: (Fun f -> Fun g) -> Term f -> Builder g
-mapFun f = mapFunList f . singleton
-
--- | Map a function over the function symbols in a termlist.
-mapFunList :: (Fun f -> Fun g) -> TermList f -> Builder g
-mapFunList f ts = aux ts
-  where
-    aux Empty = mempty
-    aux (Cons (Var x) ts) = var x `mappend` aux ts
-    aux (Cons (App ff ts) us) = app (f ff) (aux ts) `mappend` aux us
-
--- | Replace the term at a given position in a term with a different term.
-{-# INLINE replacePosition #-}
-replacePosition :: (Build a, BuildFun a ~ f) => Int -> a -> TermList f -> Builder f
-replacePosition n !x = aux n
-  where
-    aux !_ !_ | False = undefined
-    aux _ Empty = mempty
-    aux 0 (Cons _ t) = builder x `mappend` builder t
-    aux n (Cons (Var x) t) = var x `mappend` aux (n-1) t
-    aux n (Cons t@(App f ts) u)
-      | n < len t =
-        app f (aux (n-1) ts) `mappend` builder u
-      | otherwise =
-        builder t `mappend` aux (n-len t) u
-
--- | Replace the term at a given position in a term with a different term, while
--- simultaneously applying a substitution. Useful for building critical pairs.
-{-# INLINE replacePositionSub #-}
-replacePositionSub :: (Substitution sub, SubstFun sub ~ f) => sub -> Int -> TermList f -> TermList f -> Builder f
-replacePositionSub sub n !x = aux n
-  where
-    aux !_ !_ | False = undefined
-    aux _ Empty = mempty
-    aux n (Cons t u)
-      | n < len t = inside n t `mappend` outside u
-      | otherwise = outside (singleton t) `mappend` aux (n-len t) u
-
-    inside 0 _ = outside x
-    inside n (App f ts) = app f (aux (n-1) ts)
-    inside _ _ = undefined -- implies n >= len t
-
-    outside t = substList sub t
-
--- | Convert a position in a term, expressed as a single number, into a path.
-positionToPath :: Term f -> Int -> [Int]
-positionToPath t n = term t n
-  where
-    term _ 0 = []
-    term t n = list 0 (children t) (n-1)
-
-    list _ Empty _ = error "bad position"
-    list k (Cons t u) n
-      | n < len t = k:term t n
-      | otherwise = list (k+1) u (n-len t)
-
--- | Convert a path in a term into a position.
-pathToPosition :: Term f -> [Int] -> Int
-pathToPosition t ns = term 0 t ns
-  where
-    term k _ [] = k
-    term k t (n:ns) = list (k+1) (children t) n ns
-
-    list _ Empty _ _ = error "bad path"
-    list k (Cons t _) 0 ns = term k t ns
-    list k (Cons t u) n ns =
-      list (k+len t) u (n-1) ns
-
--- | A pattern which extracts the 'fun_value' from a 'Fun'.
-pattern F :: f -> Fun f
-pattern F x <- (fun_value -> x)
-
--- | Compare the 'fun_value's of two 'Fun's.
-(<<) :: Ord f => Fun f -> Fun f -> Bool
-f << g = fun_value f < fun_value g
diff --git a/src/Twee/Term/Core.hs b/src/Twee/Term/Core.hs
deleted file mode 100644
--- a/src/Twee/Term/Core.hs
+++ /dev/null
@@ -1,422 +0,0 @@
--- Terms and substitutions, implemented using flatterms.
--- This module contains all the low-level icky bits
--- and provides primitives for building higher-level stuff.
-{-# LANGUAGE CPP, PatternSynonyms, ViewPatterns,
-    MagicHash, UnboxedTuples, BangPatterns,
-    RankNTypes, RecordWildCards, GeneralizedNewtypeDeriving #-}
-module Twee.Term.Core where
-
-import Data.Primitive(sizeOf)
-#ifdef BOUNDS_CHECKS
-import Data.Primitive.ByteArray.Checked
-#else
-import Data.Primitive.ByteArray
-#endif
-import Control.Monad.ST.Strict
-import Data.Bits
-import Data.Int
-import GHC.Int(Int(..))
-import GHC.Prim
-import GHC.ST hiding (liftST)
-import Data.Ord
-import Twee.Label
-import Data.Typeable
-
---------------------------------------------------------------------------------
--- Symbols. A symbol is a single function or variable in a flatterm.
---------------------------------------------------------------------------------
-
-data Symbol =
-  Symbol {
-    -- Is it a function?
-    isFun :: Bool,
-    -- What is its number?
-    index :: Int,
-    -- What is the size of the term rooted at this symbol?
-    size  :: Int }
-
-instance Show Symbol where
-  show Symbol{..}
-    | isFun = show (F index) ++ "=" ++ show size
-    | otherwise = show (V index)
-
--- Convert symbols to/from Int64 for storage in flatterms.
--- The encoding:
---   * bits 0-30: size
---   * bit  31: 0 (variable) or 1 (function)
---   * bits 32-63: index
-{-# INLINE toSymbol #-}
-toSymbol :: Int64 -> Symbol
-toSymbol n =
-  Symbol (testBit n 31)
-    (fromIntegral (n `unsafeShiftR` 32))
-    (fromIntegral (n .&. 0x7fffffff))
-
-{-# INLINE fromSymbol #-}
-fromSymbol :: Symbol -> Int64
-fromSymbol Symbol{..} =
-  fromIntegral size +
-  fromIntegral index `unsafeShiftL` 32 +
-  fromIntegral (fromEnum isFun) `unsafeShiftL` 31
-
---------------------------------------------------------------------------------
--- Flatterms, or rather lists of terms.
---------------------------------------------------------------------------------
-
--- | @'TermList' f@ is a list of terms whose function symbols have type @f@.
--- It is either a 'Cons' or an 'Empty'. You can turn it into a @['Term' f]@
--- with 'Twee.Term.unpack'.
-
--- A TermList is a slice of an unboxed array of symbols.
-data TermList f =
-  TermList {
-    low   :: {-# UNPACK #-} !Int,
-    high  :: {-# UNPACK #-} !Int,
-    array :: {-# UNPACK #-} !ByteArray }
-
--- | Index into a termlist.
-at :: Int -> TermList f -> Term f
-at n (TermList lo hi arr)
-  | n < 0 || lo+n >= hi = error "term index out of bounds"
-  | otherwise =
-    case TermList (lo+n) hi arr of
-      UnsafeCons t _ -> t
-
-{-# INLINE lenList #-}
--- | The length of (number of symbols in) a termlist.
-lenList :: TermList f -> Int
-lenList (TermList low high _) = high - low
-
--- | @'Term' f@ is a term whose function symbols have type @f@.
--- It is either a 'Var' or an 'App'.
-
--- A term is a special case of a termlist.
--- We store it as the termlist together with the root symbol.
-data Term f =
-  Term {
-    root     :: {-# UNPACK #-} !Int64,
-    termlist :: {-# UNPACK #-} !(TermList f) }
-
-instance Eq (Term f) where
-  x == y = termlist x == termlist y
-
-instance Ord (Term f) where
-  compare = comparing termlist
-
--- Pattern synonyms for termlists:
--- * Empty :: TermList f
---   Empty is the empty termlist.
--- * Cons t ts :: Term f -> TermList f -> TermList f
---   Cons t ts is the termlist t:ts.
--- * ConsSym t ts :: Term f -> TermList f -> TermList f
---   ConsSym t ts is like Cons t ts but ts also includes t's children
---   (operationally, ts seeks one term to the right in the termlist).
--- * UnsafeCons/UnsafeConsSym: like Cons and ConsSym but don't check
---   that the termlist is non-empty.
-
--- | Matches the empty termlist.
-pattern Empty :: TermList f
-pattern Empty <- (patHead -> Nothing)
-
--- | Matches a non-empty termlist, unpacking it into head and tail.
-pattern Cons :: Term f -> TermList f -> TermList f
-pattern Cons t ts <- (patHead -> Just (t, _, ts))
-
--- | Like 'Cons', but does not check that the termlist is non-empty. Use only if
--- you are sure the termlist is non-empty.
-pattern UnsafeCons :: Term f -> TermList f -> TermList f
-pattern UnsafeCons t ts <- (unsafePatHead -> Just (t, _, ts))
-
--- | Matches a non-empty termlist, unpacking it into head and
--- /everything except the root symbol of the head/.
--- Useful for iterating through terms one symbol at a time.
---
--- For example, if @ts@ is the termlist @[f(x,y), g(z)]@,
--- then @let ConsSym u us = ts@ results in the following bindings:
---
--- > u  = f(x,y)
--- > us = [x, y, g(z)]
-pattern ConsSym :: Term f -> TermList f -> TermList f
-pattern ConsSym t ts <- (patHead -> Just (t, ts, _))
-
--- | Like 'ConsSym', but does not check that the termlist is non-empty. Use only
--- if you are sure the termlist is non-empty.
-pattern UnsafeConsSym :: Term f -> TermList f -> TermList f
-pattern UnsafeConsSym t ts <- (unsafePatHead -> Just (t, ts, _))
-
--- A helper for UnsafeCons/UnsafeConsSym.
-{-# INLINE unsafePatHead #-}
-unsafePatHead :: TermList f -> Maybe (Term f, TermList f, TermList f)
-unsafePatHead TermList{..} =
-  Just (Term x (TermList low (low+size) array),
-        TermList (low+1) high array,
-        TermList (low+size) high array)
-  where
-    !x = indexByteArray array low
-    Symbol{..} = toSymbol x
-
--- A helper for Cons/ConsSym.
-{-# INLINE patHead #-}
-patHead :: TermList f -> Maybe (Term f, TermList f, TermList f)
-patHead t@TermList{..}
-  | low == high = Nothing
-  | otherwise = unsafePatHead t
-
--- Pattern synonyms for single terms.
--- * Var :: Var -> Term f
--- * App :: Fun f -> TermList f -> Term f
-
--- | A function symbol. @f@ is the underlying type of function symbols defined
--- by the user; @'Fun' f@ is an @f@ together with an automatically-generated unique number.
-newtype Fun f =
-  F {
-    -- | The unique number of a 'Fun'.
-    fun_id :: Int }
-instance Eq (Fun f) where
-  f == g = fun_id f == fun_id g
-instance Ord (Fun f) where
-  compare = comparing fun_id
-
--- | Construct a 'Fun' from a function symbol.
-fun :: (Ord f, Typeable f) => f -> Fun f
-fun f = F (fromIntegral (labelNum (label f)))
-
--- | The underlying function symbol of a 'Fun'.
-fun_value :: Fun f -> f
-fun_value f = find (unsafeMkLabel (fromIntegral (fun_id f)))
-
--- | A variable.
-newtype Var =
-  V {
-    -- | The variable's number.
-    -- Don't use huge variable numbers:
-    -- they will be truncated to 32 bits when stored in a term.
-    var_id :: Int } deriving (Eq, Ord, Enum)
-instance Show (Fun f) where show f = "f" ++ show (fun_id f)
-instance Show Var     where show x = "x" ++ show (var_id x)
-
--- | Matches a variable.
-pattern Var :: Var -> Term f
-pattern Var x <- (patTerm -> Left x)
-
--- | Matches a function application.
-pattern App :: Fun f -> TermList f -> Term f
-pattern App f ts <- (patTerm -> Right (f, ts))
-
--- A helper function for Var and App.
-{-# INLINE patTerm #-}
-patTerm :: Term f -> Either Var (Fun f, TermList f)
-patTerm t@Term{..}
-  | isFun     = Right (F index, ts)
-  | otherwise = Left (V index)
-  where
-    Symbol{..} = toSymbol root
-    !(UnsafeConsSym _ ts) = singleton t
-
--- | Convert a term to a termlist.
-{-# INLINE singleton #-}
-singleton :: Term f -> TermList f
-singleton Term{..} = termlist
-
--- We can implement equality almost without access to the
--- internal representation of the termlists, but we cheat by
--- comparing Int64s instead of Symbols.
-instance Eq (TermList f) where
-  -- Manual worker-wrapper to prevent too much from being inlined.
-  t == u = eqTermList t u
-
-{-# INLINE eqTermList #-}
-eqTermList :: TermList f -> TermList f -> Bool
-eqTermList
-  (TermList (I# low1) (I# high1) (ByteArray array1))
-  (TermList (I# low2) (I# high2) (ByteArray array2)) =
-    weqTermList low1 high1 array1 low2 high2 array2
-
--- Manually worker-wrapper transform the thing, ugh...
-{-# NOINLINE weqTermList #-}
-weqTermList ::
-  Int# -> Int# -> ByteArray# ->
-  Int# -> Int# -> ByteArray# ->
-  Bool
-weqTermList low1 high1 array1 low2 high2 array2 =
-  lenList t == lenList u && eqSameLength t u
-  where
-    t = TermList (I# low1) (I# high1) (ByteArray array1)
-    u = TermList (I# low2) (I# high2) (ByteArray array2)
-    eqSameLength Empty !_ = True
-    eqSameLength (ConsSym s1 t) (UnsafeConsSym s2 u) =
-      root s1 == root s2 && eqSameLength t u
-
-instance Ord (TermList f) where
-  {-# INLINE compare #-}
-  compare t u =
-    case compare (lenList t) (lenList u) of
-      EQ -> compareContents t u
-      x  -> x
-
-compareContents :: TermList f -> TermList f -> Ordering
-compareContents Empty !_ = EQ
-compareContents (ConsSym s1 t) (UnsafeConsSym s2 u) =
-  case compare (root s1) (root s2) of
-    EQ -> compareContents t u
-    x  -> x
-
---------------------------------------------------------------------------------
--- Building terms.
---------------------------------------------------------------------------------
-
--- | A monoid for building terms.
--- 'mempty' represents the empty termlist, while 'mappend' appends two termlists.
-newtype Builder f =
-  Builder {
-    unBuilder ::
-      -- Takes: the term array and size, and current position in the term.
-      -- Returns the final position, which may be out of bounds.
-      forall s. Builder1 s f }
-
-type Builder1 s f = State# s -> MutableByteArray# s -> Int# -> Int# -> (# State# s, Int# #)
-
-instance Monoid (Builder f) where
-  {-# INLINE mempty #-}
-  mempty = Builder built
-  {-# INLINE mappend #-}
-  Builder m1 `mappend` Builder m2 = Builder (m1 `then_` m2)
-
--- Build a termlist from a Builder.
--- Works by guessing an appropriate size, and retrying if that was too small.
-{-# INLINE buildTermList #-}
-buildTermList :: Builder f -> TermList f
-buildTermList builder = runST $ do
-  let
-    Builder m = builder
-    loop n@(I# n#) = do
-      MutableByteArray mbytearray# <-
-        newByteArray (n * sizeOf (fromSymbol undefined))
-      n' <-
-        ST $ \s ->
-          case m s mbytearray# n# 0# of
-            (# s, n# #) -> (# s, I# n# #)
-      if n' <= n then do
-        !bytearray <- unsafeFreezeByteArray (MutableByteArray mbytearray#)
-        return (TermList 0 n' bytearray)
-       else loop (n'*2)
-  loop 32
-
--- Get at the term array.
-{-# INLINE getByteArray #-}
-getByteArray :: (MutableByteArray s -> Builder1 s f) -> Builder1 s f
-getByteArray k = \s bytearray n i -> k (MutableByteArray bytearray) s bytearray n i
-
--- Get at the array size.
-{-# INLINE getSize #-}
-getSize :: (Int -> Builder1 s f) -> Builder1 s f
-getSize k = \s bytearray n i -> k (I# n) s bytearray n i
-
--- Get at the current array index.
-{-# INLINE getIndex #-}
-getIndex :: (Int -> Builder1 s f) -> Builder1 s f
-getIndex k = \s bytearray n i -> k (I# i) s bytearray n i
-
--- Change the current array index.
-{-# INLINE putIndex #-}
-putIndex :: Int -> Builder1 s f
-putIndex (I# i) = \s _ _ _ -> (# s, i #)
-
--- Lift an ST computation into a builder.
-{-# INLINE liftST #-}
-liftST :: ST s () -> Builder1 s f
-liftST (ST m) =
-  \s _ _ i ->
-  case m s of
-    (# s, () #) -> (# s, i #)
-
--- Finish building.
-{-# INLINE built #-}
-built :: Builder1 s f
-built = \s _ _ i -> (# s, i #)
-
--- Sequence two builder operations.
-{-# INLINE then_ #-}
-then_ :: Builder1 s f -> Builder1 s f -> Builder1 s f
-then_ m1 m2 =
-  \s bytearray n i ->
-    case m1 s bytearray n i of
-      (# s, i #) -> m2 s bytearray n i
-
--- checked j m executes m only if the array has room for j more symbols.
-{-# INLINE checked #-}
-checked :: Int -> Builder1 s f -> Builder1 s f
-checked j m =
-  getSize $ \n ->
-  getIndex $ \i ->
-  if i + j <= n then m else putIndex (i + j)
-
--- Emit an arbitrary symbol, with given arguments.
-{-# INLINE emitSymbolBuilder #-}
-emitSymbolBuilder :: Symbol -> Builder f -> Builder f
-emitSymbolBuilder x inner =
-  Builder $ checked 1 $
-    getByteArray $ \bytearray ->
-    -- Skip the symbol itself, then fill it in at the end, when we know the size
-    -- of the symbol's arguments.
-    getIndex $ \n ->
-    putIndex (n+1) `then_`
-    unBuilder inner `then_`
-    -- Fill in the symbol.
-    getIndex (\m ->
-      liftST $ writeByteArray bytearray n (fromSymbol x { size = m - n }))
-
--- Emit a function application.
-{-# INLINE emitApp #-}
-emitApp :: Fun f -> Builder f -> Builder f
-emitApp (F n) inner = emitSymbolBuilder (Symbol True n 0) inner
-
--- Emit a variable.
-{-# INLINE emitVar #-}
-emitVar :: Var -> Builder f
-emitVar x = emitSymbolBuilder (Symbol False (var_id x) 1) mempty
-
--- Emit a whole termlist.
-{-# INLINE emitTermList #-}
-emitTermList :: TermList f -> Builder f
-emitTermList (TermList lo hi array) =
-  Builder $ checked (hi-lo) $
-    getByteArray $ \mbytearray ->
-    getIndex $ \n ->
-    let k = sizeOf (fromSymbol undefined) in
-    liftST (copyByteArray mbytearray (n*k) array (lo*k) ((hi-lo)*k)) `then_`
-    putIndex (n + hi-lo)
-
-----------------------------------------------------------------------
--- Efficient subterm testing.
-----------------------------------------------------------------------
-
--- | Is a term contained as a subterm in a given termlist?
-{-# INLINE isSubtermOfList #-}
-isSubtermOfList :: Term f -> TermList f -> Bool
-isSubtermOfList t u =
-  isSubArrayOf (singleton t) u
-
--- N.B. this one should not be exported from Twee.Term
--- because subarray is not the same as subterm if t is not
--- a singleton
-isSubArrayOf :: TermList f -> TermList f -> Bool
-isSubArrayOf t u =
-  lenList t <= lenList u && (here t u || next t u)
-  where
-    here Empty _ = True
-    here (ConsSym s1 t) (UnsafeConsSym s2 u) =
-      root s1 == root s2 && here t u
-
-    -- This is safe because lenList t <= lenList u
-    -- so if u = Empty, then t = Empty and here t u = True.
-    next t (UnsafeConsSym _ u) = isSubArrayOf t u
-
--- | Check if a variable occurs in a termlist.
-{-# INLINE occursList #-}
-occursList :: Var -> TermList f -> Bool
-occursList (V x) t = symbolOccursList (fromSymbol (Symbol False x 1)) t
-
-symbolOccursList :: Int64 -> TermList f -> Bool
-symbolOccursList !_ Empty = False
-symbolOccursList n (ConsSym t ts) = root t == n || symbolOccursList n ts
diff --git a/src/Twee/Utils.hs b/src/Twee/Utils.hs
deleted file mode 100644
--- a/src/Twee/Utils.hs
+++ /dev/null
@@ -1,145 +0,0 @@
--- | Miscellaneous utility functions.
-
-{-# LANGUAGE CPP, MagicHash #-}
-module Twee.Utils where
-
-import Control.Arrow((&&&))
-import Control.Exception
-import Data.List(groupBy, sortBy)
-import Data.Ord(comparing)
-import System.IO
-import GHC.Prim
-import GHC.Types
-import Data.Bits
---import Test.QuickCheck hiding ((.&.))
-
-repeatM :: Monad m => m a -> m [a]
-repeatM = sequence . repeat
-
-partitionBy :: Ord b => (a -> b) -> [a] -> [[a]]
-partitionBy value =
-  map (map fst) .
-  groupBy (\x y -> snd x == snd y) .
-  sortBy (comparing snd) .
-  map (id &&& value)
-
-collate :: Ord a => ([b] -> c) -> [(a, b)] -> [(a, c)]
-collate f = map g . partitionBy fst
-  where
-    g xs = (fst (head xs), f (map snd xs))
-
-isSorted :: Ord a => [a] -> Bool
-isSorted xs = and (zipWith (<=) xs (tail xs))
-
-isSortedBy :: Ord b => (a -> b) -> [a] -> Bool
-isSortedBy f xs = isSorted (map f xs)
-
-usort :: Ord a => [a] -> [a]
-usort = usortBy compare
-
-usortBy :: (a -> a -> Ordering) -> [a] -> [a]
-usortBy f = map head . groupBy (\x y -> f x y == EQ) . sortBy f
-
-sortBy' :: Ord b => (a -> b) -> [a] -> [a]
-sortBy' f = map snd . sortBy (comparing fst) . map (\x -> (f x, x))
-
-usortBy' :: Ord b => (a -> b) -> [a] -> [a]
-usortBy' f = map snd . usortBy (comparing fst) . map (\x -> (f x, x))
-
-orElse :: Ordering -> Ordering -> Ordering
-EQ `orElse` x = x
-x  `orElse` _ = x
-
-unbuffered :: IO a -> IO a
-unbuffered x = do
-  buf <- hGetBuffering stdout
-  bracket_
-    (hSetBuffering stdout NoBuffering)
-    (hSetBuffering stdout buf)
-    x
-
-newtype Max a = Max { getMax :: Maybe a }
-
-getMaxWith :: Ord a => a -> Max a -> a
-getMaxWith x (Max (Just y)) = x `max` y
-getMaxWith x (Max Nothing)  = x
-
-instance Ord a => Monoid (Max a) where
-  mempty = Max Nothing
-  Max (Just x) `mappend` y = Max (Just (getMaxWith x y))
-  Max Nothing  `mappend` y = y
-
-newtype Min a = Min { getMin :: Maybe a }
-
-getMinWith :: Ord a => a -> Min a -> a
-getMinWith x (Min (Just y)) = x `min` y
-getMinWith x (Min Nothing)  = x
-
-instance Ord a => Monoid (Min a) where
-  mempty = Min Nothing
-  Min (Just x) `mappend` y = Min (Just (getMinWith x y))
-  Min Nothing  `mappend` y = y
-
-labelM :: Monad m => (a -> m b) -> [a] -> m [(a, b)]
-labelM f = mapM (\x -> do { y <- f x; return (x, y) })
-
-#if __GLASGOW_HASKELL__ < 710
-isSubsequenceOf :: Ord a => [a] -> [a] -> Bool
-[] `isSubsequenceOf` ys = True
-(x:xs) `isSubsequenceOf` [] = False
-(x:xs) `isSubsequenceOf` (y:ys)
-  | x == y = xs `isSubsequenceOf` ys
-  | otherwise = (x:xs) `isSubsequenceOf` ys
-#endif
-
-{-# INLINE fixpoint #-}
-fixpoint :: Eq a => (a -> a) -> a -> a
-fixpoint f x = fxp x
-  where
-    fxp x
-      | x == y = x
-      | otherwise = fxp y
-      where
-        y = f x
-
--- From "Bit twiddling hacks": branchless min and max
-{-# INLINE intMin #-}
-intMin :: Int -> Int -> Int
-intMin x y =
-  y `xor` ((x `xor` y) .&. negate (x .<. y))
-  where
-    I# x .<. I# y = I# (x <# y)
-
-{-# INLINE intMax #-}
-intMax :: Int -> Int -> Int
-intMax x y =
-  x `xor` ((x `xor` y) .&. negate (x .<. y))
-  where
-    I# x .<. I# y = I# (x <# y)
-
--- Split an interval (inclusive bounds) into a particular number of blocks
-splitInterval :: Integral a => a -> (a, a) -> [(a, a)]
-splitInterval k (lo, hi) =
-  [ (lo+i*blockSize, (lo+(i+1)*blockSize-1) `min` hi)
-  | i <- [0..k-1] ]
-  where
-    size = (hi-lo+1)
-    blockSize = (size + k - 1) `div` k -- division rounding up
-{-
-prop_split_1 (Positive k) (lo, hi) =
-  -- Check that all elements occur exactly once
-  concat [[x..y] | (x, y) <- splitInterval k (lo, hi)] === [lo..hi]
-
--- Check that we have the correct number and distribution of blocks
-prop_split_2 (Positive k) (lo, hi) =
-  counterexample (show splits) $ conjoin
-    [counterexample "Reason: too many splits" $
-       length splits <= k,
-     counterexample "Reason: too few splits" $
-       length [lo..hi] >= k ==> length splits == k,
-     counterexample "Reason: uneven distribution" $
-      not (null splits) ==>
-       minimum (map length splits) + 1 >= maximum (map length splits)]
-  where
-    splits = splitInterval k (lo, hi)
--}
diff --git a/tests/BOO067-1.p b/tests/BOO067-1.p
deleted file mode 100644
--- a/tests/BOO067-1.p
+++ /dev/null
@@ -1,32 +0,0 @@
-%--------------------------------------------------------------------------
-% File     : BOO067-1 : TPTP v6.3.0. Released v2.6.0.
-% Domain   : Boolean Algebra (Ternary)
-% Problem  : Ternary Boolean Algebra Single axiom is complete, part 1
-% Version  : [MP96] (equality) axioms.
-% English  :
-
-% Refs     : [McC98] McCune (1998), Email to G. Sutcliffe
-%          : [MP96]  McCune & Padmanabhan (1996), Automated Deduction in Eq
-% Source   : [TPTP]
-% Names    :
-
-% Status   : Unsatisfiable
-% Rating   : 0.42 v6.3.0, 0.35 v6.2.0, 0.29 v6.1.0, 0.31 v6.0.0, 0.48 v5.5.0, 0.47 v5.4.0, 0.33 v5.3.0, 0.25 v5.2.0, 0.29 v5.1.0, 0.33 v5.0.0, 0.29 v4.1.0, 0.18 v4.0.1, 0.36 v4.0.0, 0.38 v3.7.0, 0.11 v3.4.0, 0.12 v3.3.0, 0.21 v3.1.0, 0.33 v2.7.0, 0.27 v2.6.0
-% Syntax   : Number of clauses     :    2 (   0 non-Horn;   2 unit;   1 RR)
-%            Number of atoms       :    2 (   2 equality)
-%            Maximal clause size   :    1 (   1 average)
-%            Number of predicates  :    1 (   0 propositional; 2-2 arity)
-%            Number of functors    :    7 (   5 constant; 0-3 arity)
-%            Number of variables   :    7 (   0 singleton)
-%            Maximal term depth    :    5 (   3 average)
-% SPC      : CNF_UNS_RFO_PEQ_UEQ
-
-% Comments : A UEQ part of BOO035-1
-%--------------------------------------------------------------------------
-cnf(single_axiom,axiom,
-    ( multiply(multiply(A,inverse(A),B),inverse(multiply(multiply(C,D,E),F,multiply(C,D,G))),multiply(D,multiply(G,F,E),C)) = B )).
-
-cnf(prove_tba_axioms_1,negated_conjecture,
-    (  multiply(multiply(d,e,a),b,multiply(d,e,c)) != multiply(d,e,multiply(a,b,c)) )).
-
-%--------------------------------------------------------------------------
diff --git a/tests/LAT072-1.p b/tests/LAT072-1.p
deleted file mode 100644
--- a/tests/LAT072-1.p
+++ /dev/null
@@ -1,37 +0,0 @@
-%--------------------------------------------------------------------------
-% File     : LAT072-1 : TPTP v6.3.0. Released v2.6.0.
-% Domain   : Lattice Theory (Ortholattices)
-% Problem  : Given single axiom OML-23A, prove associativity
-% Version  : [MRV03] (equality) axioms.
-% English  : Given a single axiom candidate OML-23A for orthomodular lattices
-%            (OML) in terms of the Sheffer Stroke, prove a Sheffer stroke form
-%            of associativity.
-
-% Refs     : [MRV03] McCune et al. (2003), Sheffer Stroke Bases for Ortholatt
-% Source   : [MRV03]
-% Names    : OML-23A-associativity [MRV03]
-
-% Status   : Unsatisfiable
-% Rating   : 0.95 v6.3.0, 0.94 v6.2.0, 0.93 v6.1.0, 0.94 v6.0.0, 0.95 v5.4.0, 1.00 v2.6.0
-% Syntax   : Number of clauses     :    2 (   0 non-Horn;   2 unit;   1 RR)
-%            Number of atoms       :    2 (   2 equality)
-%            Maximal clause size   :    1 (   1 average)
-%            Number of predicates  :    1 (   0 propositional; 2-2 arity)
-%            Number of functors    :    4 (   3 constant; 0-2 arity)
-%            Number of variables   :    4 (   2 singleton)
-%            Maximal term depth    :    7 (   4 average)
-% SPC      : CNF_UNS_RFO_PEQ_UEQ
-
-% Comments :
-%--------------------------------------------------------------------------
-%----Single axiom OML-23A
-cnf(oml_23A,axiom,
-    ( f(f(f(f(B,A),f(A,C)),D),f(A,f(f(C,f(f(A,A),C)),C))) = A )).
-
-cnf(a, axiom, f(X,Y) = f(Y, X)).
-
-%----Denial of Sheffer stroke associativity
-cnf(associativity,negated_conjecture,
-    (  f(a,f(f(b,c),f(b,c))) != f(c,f(f(b,a),f(b,a))) )).
-
-%--------------------------------------------------------------------------
diff --git a/tests/ROB010-1.p b/tests/ROB010-1.p
deleted file mode 100644
--- a/tests/ROB010-1.p
+++ /dev/null
@@ -1,11 +0,0 @@
-cnf(condition,hypothesis,
-    ( negate(add(a,negate(b))) = c )).
-
-cnf(prove_result,negated_conjecture,
-    (  negate(add(c,negate(add(b,a)))) != a )).
-
-cnf(commutativity_of_add,axiom,
-    ( add(X,Y) = add(Y,X) )).
-
-cnf(robbins_axiom,axiom,
-    ( negate(add(negate(add(X,Y)),negate(add(X,negate(Y))))) = X )).
diff --git a/tests/append-rev.p b/tests/append-rev.p
deleted file mode 100644
--- a/tests/append-rev.p
+++ /dev/null
@@ -1,4 +0,0 @@
-cnf(rev_rev, axiom, rev(rev(X)) = X).
-cnf(app_assoc, axiom, '++'(X,'++'(Y,Z)) = '++'('++'(X,Y),Z)).
-cnf(rev_app, axiom, '++'(rev(X),rev(Y)) = rev('++'(Y,X))).
-cnf(conjecture, negated_conjecture, '++'(a,rev(b)) != rev('++'(b, rev(a)))).
diff --git a/tests/db.p b/tests/db.p
deleted file mode 100644
--- a/tests/db.p
+++ /dev/null
@@ -1,17 +0,0 @@
-% http://www.dcs.bbk.ac.uk/~szabolcs/rellat-jlamp-second-submission-2.pdf
-% appendix b. theorem 3.4, clause 8.
-cnf(a, axiom, '^'(X, Y) = '^'(Y, X)).
-cnf(a, axiom, '^'(X, '^'(Y, Z)) = '^'(Y, '^'(X, Z))).
-cnf(a, axiom, '^'('^'(X, Y), Z) = '^'(X, '^'(Y, Z))).
-cnf(a, axiom, v(X, Y) = v(Y, X)).
-cnf(a, axiom, v(X, v(Y, Z)) = v(Y, v(X, Z))).
-cnf(a, axiom, v(v(X, Y), Z) = v(X, v(Y, Z))).
-cnf(a, axiom, v(X, '^'(X, Y)) = X).
-cnf(a, axiom, '^'(X, v(X, Y)) = X).
-cnf(a, axiom, upme(X,Y,Z) = '^'(X, v(Y, Z))).
-cnf(a, axiom, lome(X,Y,Z) = v('^'(X, Y), '^'(X, Z))).
-cnf(a, axiom, upjo(X,Y,Z) = '^'(v(X, Y), v(X, Z))).
-cnf(a, axiom, lojo(X,Y,Z) = v(X, '^'(Y, Z))).
-cnf(a, axiom, v(upme('^'(a, X1),Y1,Z1), '^'(Y1, Z1)) = '^'(v('^'('^'(a, X1), Y1), Z1), v('^'('^'(a, X1), Z1), Y1))).
-cnf(a, axiom, upme(X,Y,Z) = v(upme(X,Y,'^'(a, Z)), upme(X,Z,'^'(a, Y)))).
-fof(a, conjecture, (upme(a,x2,y2) = upme(a,x2,z2) => upme(x2,y2,z2) = lome(x2,y2,z2))).
diff --git a/tests/deriv.p b/tests/deriv.p
deleted file mode 100644
--- a/tests/deriv.p
+++ /dev/null
@@ -1,39 +0,0 @@
-% Axioms about arithmetic.
-
-cnf('commutativity of +', axiom,
-	'+'(X, Y) = '+'(Y, X)).
-cnf('associativity of +', axiom,
-	'+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf('commutativity of *', axiom,
-	'*'(X, Y) = '*'(Y, X)).
-cnf('associativity of *', axiom,
-	'*'(X, '*'(Y, Z)) = '*'('*'(X, Y), Z)).
-cnf('plus 0', axiom,
-	'+'('0', X) = X).
-cnf('times 0', axiom,
-	'*'('0', X) = '0').
-cnf('times 1', axiom,
-	'*'('1', X) = X).
-cnf('distributivity', axiom,
-	'*'(X, '+'(Y, Z)) = '+'('*'(X, Y), '*'(X, Z))).
-cnf('minus', axiom,
-    '+'(X, '-'(X)) = '0').
-
-cnf('derivative of 0', axiom,
-	d('0') = '0').
-cnf('derivative of 1', axiom,
-	d('1') = '0').
-cnf('derivative of x', axiom,
-	d(x) = '1').
-cnf('derivative of +', axiom,
-	d('+'(T,U)) = '+'(d(T), d(U))).
-cnf('derivative of *', axiom,
-	d('*'(T, U)) = '+'('*'(T, d(U)), '*'(U, d(T)))).
-cnf('derivative of sin', axiom,
-    d(sin(T)) = '*'(cos(T), d(T))).
-cnf('derivative of cos', axiom,
-    d(cos(T)) = '-'('*'(sin(T), d(T)))).
-
-fof(goal, conjecture,
-	?[T]: d(T) = '*'(x, cos(x))).
-    
diff --git a/tests/diff.p b/tests/diff.p
deleted file mode 100644
--- a/tests/diff.p
+++ /dev/null
@@ -1,4 +0,0 @@
-cnf('x\\(y\\x)=x', axiom, '\\'(X, '\\'(Y, X)) = X).
-cnf('x\\(x\\y)=y\\(y\\x)', axiom, '\\'(X, '\\'(X, Y)) = '\\'(Y, '\\'(Y, X))).
-cnf('(x\\y)\\z=(x\\z)\\(y\\z)', axiom, '\\'('\\'(X, Y), Z) = '\\'('\\'(X, Z), '\\'(Y, Z))).
-cnf(conjecture, negated_conjecture, '\\'('\\'(a, c), b) != '\\'('\\'(a, b), c)).
diff --git a/tests/group.p b/tests/group.p
deleted file mode 100644
--- a/tests/group.p
+++ /dev/null
@@ -1,15 +0,0 @@
-fof(identity, axiom,
-    ![X]: f(X, e) = X).
-fof(right_inverse, axiom,
-    ![X]: f(X, i(X)) = e).
-fof(associativity, axiom,
-    ![X, Y, Z]: f(X, f(Y, Z)) = f(f(X, Y), Z)).
-%fof(left_inverse, conjecture,
-%    ![X]: f(i(X),X) = e).
-%fof(left_identity, conjecture,
-%    ![X]: f(e, X) = X).
-
-fof(inverse_distrib, axiom,
-    ![X,Y]: f(i(X),i(Y)) = i(f(X,Y))).
-fof(commutativity, conjecture,
-    ![X,Y]: f(X,Y) = f(Y,X)).
diff --git a/tests/lat.p b/tests/lat.p
deleted file mode 100644
--- a/tests/lat.p
+++ /dev/null
@@ -1,16 +0,0 @@
-cnf(idempotence_of_meet, axiom, meet(X, X)=X).
-cnf(idempotence_of_join, axiom, join(X, X)=X).
-cnf(absorption1, axiom, meet(X, join(X, Y))=X).
-cnf(absorption2, axiom, join(X, meet(X, Y))=X).
-cnf(commutativity_of_meet, axiom, meet(X, Y)=meet(Y, X)).
-cnf(commutativity_of_join, axiom, join(X, Y)=join(Y, X)).
-cnf(associativity_of_meet, axiom,
-    meet(meet(X, Y), Z)=meet(X, meet(Y, Z))).
-cnf(associativity_of_join, axiom,
-    join(join(X, Y), Z)=join(X, join(Y, Z))).
-cnf(equation_H34, axiom,
-    meet(X, join(Y, meet(Z, U)))=meet(X,
-                                      join(Y, meet(Z, join(Y, meet(U, join(Y, Z))))))).
-cnf(prove_H28, negated_conjecture,
-    meet(a, join(b, meet(a, meet(c, d))))!=meet(a,
-                                                join(b, meet(c, meet(d, join(a, meet(b, d))))))).
diff --git a/tests/lcl.p b/tests/lcl.p
deleted file mode 100644
--- a/tests/lcl.p
+++ /dev/null
@@ -1,7 +0,0 @@
-cnf(wajsberg_1, axiom, implies(truth, X)=X).
-cnf(wajsberg_3, axiom,
-    implies(implies(X, Y), Y)=implies(implies(Y, X), X)).
-cnf(wajsberg_4, axiom,
-    implies(implies(not(X), not(Y)), implies(Y, X))=truth).
-cnf(lemma_antecedent, axiom, implies(X, Y)=implies(Y, X)).
-cnf(prove_wajsberg_lemma, negated_conjecture, x!=y).
diff --git a/tests/loop.p b/tests/loop.p
deleted file mode 100644
--- a/tests/loop.p
+++ /dev/null
@@ -1,6 +0,0 @@
-cnf(mult_ld, axiom, '*'(X, '^'(X, Y)) = Y).
-cnf(ld_mult, axiom, '^'(X, '*'(X, Y)) = Y).
-cnf(mult_rd, axiom, '*'('/'(X, Y), Y) = X).
-cnf(rd_mult, axiom, '/'('*'(X, Y), Y) = X).
-cnf(moufang, axiom, '*'(X, '*'(Y, '*'(X, Z))) = '*'('*'('*'(X, Y), X), Z)).
-cnf(conjecture, negated_conjecture, '^'(a,a) != '/'(a,a)).
diff --git a/tests/loop2.p b/tests/loop2.p
deleted file mode 100644
--- a/tests/loop2.p
+++ /dev/null
@@ -1,6 +0,0 @@
-cnf('*-\\', axiom, '*'(X, '\\'(X, Y)) = Y).
-cnf('\\-*', axiom, '\\'(X, '*'(X, Y)) = Y).
-cnf('*-/', axiom, '*'('/'(X, Y), Y) = X).
-cnf('/-*', axiom, '/'('*'(X, Y), Y) = X).
-cnf(moufang, axiom, '*'(X, '*'(Y, '*'(X, Z))) = '*'('*'('*'(X, Y), X), Z)).
-cnf(conjecture, negated_conjecture, '*'(a,'/'(b,b)) != a).
diff --git a/tests/lukasiewicz.p b/tests/lukasiewicz.p
deleted file mode 100644
--- a/tests/lukasiewicz.p
+++ /dev/null
@@ -1,6 +0,0 @@
-cnf(imp_true, axiom, implies(true, X) = X).
-cnf(imp_compose, axiom, implies(implies(X, Y), implies(implies(Y, Z), implies(X, Z))) = true).
-cnf(imp_not, axiom, implies(implies(not(X), not(Y)), implies(Y, X)) = true).
-cnf(imp_switch, axiom, implies(implies(X, Y), Y) = implies(implies(Y, X), X)).
-cnf(or_def, axiom, or(X, Y) = implies(not(X), Y)).
-cnf(conjecture, negated_conjecture, or(a,or(b,c)) != or(or(a,b),c)).
diff --git a/tests/minus.p b/tests/minus.p
deleted file mode 100644
--- a/tests/minus.p
+++ /dev/null
@@ -1,12 +0,0 @@
-cnf(plus_zero, axiom,
-	'+'('0', X) = X).
-cnf(plus_zero, axiom,
-	'+'(X, '0') = X).
-cnf(minus_minus, axiom,
-	'-'('-'(X)) = X).
-cnf(minus_plus, axiom,
-	'-'('+'(X, Y)) = '+'('-'(X), '-'(Y))).
-
-cnf(goal, conjecture,
-    '-'('0') = '0').
-	%% ?[Y]: d(Y) = '+'(x, x)).
diff --git a/tests/nand.p b/tests/nand.p
deleted file mode 100644
--- a/tests/nand.p
+++ /dev/null
@@ -1,37 +0,0 @@
-%--------------------------------------------------------------------------
-% File     : LAT071-1 : TPTP v6.2.0. Released v2.6.0.
-% Domain   : Lattice Theory (Orthomodularlattices)
-% Problem  : Given single axiom OML-21C, prove associativity
-% Version  : [MRV03] (equality) axioms.
-% English  : Given a single axiom candidate OML-21C for orthomodular lattices
-%            (OML) in terms of the Sheffer Stroke, prove a Sheffer stroke form
-%            of associativity.
-
-% Refs     : [MRV03] McCune et al. (2003), Sheffer Stroke Bases for Ortholatt
-% Source   : [MRV03]
-% Names    : OML-21C-associativity [MRV03]
-
-% Status   : Open
-% Rating   : 1.00 v2.6.0
-% Syntax   : Number of clauses     :    2 (   0 non-Horn;   2 unit;   1 RR)
-%            Number of atoms       :    2 (   2 equality)
-%            Maximal clause size   :    1 (   1 average)
-%            Number of predicates  :    1 (   0 propositional; 2-2 arity)
-%            Number of functors    :    4 (   3 constant; 0-2 arity)
-%            Number of variables   :    4 (   2 singleton)
-%            Maximal term depth    :    6 (   4 average)
-% SPC      : CNF_UNK_UEQ
-
-% Comments :
-%--------------------------------------------------------------------------
-%----Single axiom OML-21C
-cnf(oml_21C,axiom,
-    ( f(f(B,A),f(f(f(f(B,A),A),f(C,A)),f(f(A,A),D))) = A )).
-
-cnf(a, axiom, f(z, f(z, z)) = k).
-
-%----Denial of Sheffer stroke associativity
-cnf(associativity,negated_conjecture,
-    (  f(a,f(f(b,c),f(b,c))) != f(c,f(f(b,a),f(b,a))) )).
-
-%--------------------------------------------------------------------------
diff --git a/tests/nicomachus.p b/tests/nicomachus.p
deleted file mode 100644
--- a/tests/nicomachus.p
+++ /dev/null
@@ -1,18 +0,0 @@
-cnf(plus_comm, axiom, plus(X, Y) = plus(Y, X)).
-cnf(plus_assoc, axiom, plus(X, plus(Y, Z)) = plus(plus(X, Y), Z)).
-cnf(times_comm, axiom, times(X, Y) = times(Y, X)).
-cnf(times_assoc, axiom, times(X, times(Y, Z)) = times(times(X, Y), Z)).
-cnf(plus_zero, axiom, plus(X, zero) = X).
-cnf(times_zero, axiom, times(X, zero) = zero).
-cnf(times_one, axiom, times(X, one) = X).
-cnf(distr, axiom, times(X, plus(Y, Z)) = plus(times(X, Y), times(X, Z))).
-cnf(distr, axiom, times(plus(X, Y), Z) = plus(times(X, Z), times(Y, Z))).
-cnf(plus_s, axiom, plus(s(X), Y) = s(plus(X, Y))).
-cnf(times_s, axiom, times(s(X), Y) = plus(Y, times(X, Y))).
-cnf(sum_zero, axiom, sum(zero) = zero).
-cnf(sum_s, axiom, sum(s(N)) = plus(s(N), sum(N))).
-cnf(cubes_zero, axiom, cubes(zero) = zero).
-cnf(cubes_s, axiom, cubes(s(N)) = plus(times(s(N), times(s(N), s(N))), cubes(N))).
-cnf(plus_sum, axiom, plus(sum(N), sum(N)) = times(N, s(N))).
-cnf(ih, axiom, times(sum(a), sum(a)) = cubes(a)).
-cnf(conjecture, negated_conjecture, times(sum(s(a)), sum(s(a))) != cubes(s(a))).
diff --git a/tests/ring.p b/tests/ring.p
deleted file mode 100644
--- a/tests/ring.p
+++ /dev/null
@@ -1,9 +0,0 @@
-cnf(plus_comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(plus_assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(plus_zero, axiom, '+'('0', X) = X).
-cnf(plus_inv, axiom, '+'(X, '-'(X)) = '0').
-cnf(times_assoc, axiom, '*'(X, '*'(Y, Z)) = '*'('*'(X, Y), Z)).
-cnf(distrib, axiom, '*'(X, '+'(Y, Z)) = '+'('*'(X, Y), '*'(X, Z))).
-cnf(distrib, axiom, '*'('+'(X, Y), Z) = '+'('*'(X, Z), '*'(Y, Z))).
-cnf(cube, axiom, X = '*'(X, '*'(X, X))).
-cnf(conjecture, negated_conjecture, '*'(a, b) != '*'(b, a)).
diff --git a/tests/ring2.p b/tests/ring2.p
deleted file mode 100644
--- a/tests/ring2.p
+++ /dev/null
@@ -1,9 +0,0 @@
-cnf(plus_comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(plus_assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(plus_zero, axiom, '+'('0', X) = X).
-cnf(plus_inv, axiom, '+'(X, '-'(X)) = '0').
-cnf(times_assoc, axiom, '*'(X, '*'(Y, Z)) = '*'('*'(X, Y), Z)).
-cnf(distrib, axiom, '*'(X, '+'(Y, Z)) = '+'('*'(X, Y), '*'(X, Z))).
-cnf(distrib, axiom, '*'('+'(X, Y), Z) = '+'('*'(X, Z), '*'(Y, Z))).
-cnf(power_six, axiom, X = '*'(X, '*'(X, '*'(X, '*'(X, '*'(X, X)))))).
-cnf(conjecture, negated_conjecture, '*'(a, b) != '*'(b, a)).
diff --git a/tests/ring3.p b/tests/ring3.p
deleted file mode 100644
--- a/tests/ring3.p
+++ /dev/null
@@ -1,9 +0,0 @@
-cnf(plus_comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(plus_assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(plus_zero, axiom, '+'('0', X) = X).
-cnf(plus_neg, axiom, '+'(X, '-'(X)) = '0').
-cnf(times_assoc, axiom, '*'(X, '*'(Y, Z)) = '*'('*'(X, Y), Z)).
-cnf(distrib, axiom, '*'(X, '+'(Y, Z)) = '+'('*'(X, Y), '*'(X, Z))).
-cnf(distrib, axiom, '*'('+'(X, Y), Z) = '+'('*'(X, Z), '*'(Y, Z))).
-cnf(power_four, axiom, X = '*'(X, '*'(X, '*'(X, X)))).
-cnf(conjecture, negated_conjecture, '*'(a, b) != '*'(b, a)).
diff --git a/tests/ring4.p b/tests/ring4.p
deleted file mode 100644
--- a/tests/ring4.p
+++ /dev/null
@@ -1,9 +0,0 @@
-cnf(plus_comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(plus_assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(plus_zero, axiom, '+'('0', X) = X).
-cnf(plus_inv, axiom, '+'(X, '-'(X)) = '0').
-cnf(times_ssoc, axiom, '*'(X, '*'(Y, Z)) = '*'('*'(X, Y), Z)).
-cnf(distrib, axiom, '*'(X, '+'(Y, Z)) = '+'('*'(X, Y), '*'(X, Z))).
-cnf(distrib, axiom, '*'('+'(X, Y), Z) = '+'('*'(X, Z), '*'(Y, Z))).
-cnf(power_five, axiom, X = '*'(X, '*'(X, '*'(X, '*'(X, X))))).
-cnf(conjecture, negated_conjecture, '*'(a, b) != '*'(b, a)).
diff --git a/tests/robbins-easy.p b/tests/robbins-easy.p
deleted file mode 100644
--- a/tests/robbins-easy.p
+++ /dev/null
@@ -1,4 +0,0 @@
-cnf(comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(funny, axiom, '+'('-'('+'('-'(X), Y)), '-'('+'('-'(X), '-'(Y)))) = X).
-cnf(conjecture, negated_conjecture, '-'('+'('-'('+'(a, b)), '-'('+'(a, '-'(b))))) != a).
diff --git a/tests/robbins.p b/tests/robbins.p
deleted file mode 100644
--- a/tests/robbins.p
+++ /dev/null
@@ -1,4 +0,0 @@
-cnf(comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(funny, axiom, '-'('+'('-'('+'(X, Y)), '-'('+'(X, '-'(Y))))) = X).
-cnf(conjecture, negated_conjecture, '-'('-'(a)) != a).
diff --git a/tests/sam.p b/tests/sam.p
deleted file mode 100644
--- a/tests/sam.p
+++ /dev/null
@@ -1,38 +0,0 @@
-cnf(f_assoc, axiom,
-    meet(X,meet(Y,Z)) = meet(meet(X,Y),Z)).
-cnf(f_comm, axiom,
-    meet(X,Y) = meet(Y,X)).
-cnf(f_idem, axiom,
-    meet(X,X) = X).
-cnf(g_assoc, axiom,
-    join(X,join(Y,Z)) = join(join(X,Y),Z)).
-cnf(g_comm, axiom,
-    join(X,Y) = join(Y,X)).
-cnf(g_idem, axiom,
-    join(X,X) = X).
-
-cnf(ax31, axiom,
-    meet(X, join(X,Y)) = X).
-cnf(ax32, axiom,
-    meet(zero, X) = zero).
-cnf(ax33, axiom,
-    join(zero, X) = X).
-cnf(ax34, axiom,
-    join(X, meet(X, Y)) = X).
-cnf(ax35, axiom,
-    meet(one, X) = X).
-cnf(ax36, axiom,
-    join(one, X) = one).
-cnf(ax37, axiom,
-    meet(X,Z) = X =>
-    meet(join(X,Y),Z) = join(X,meet(Y,Z))).
-
-cnf(comp, definition,
-    comp(X,Y) <=> (meet(X,Y) = zero & join(X,Y) = one)).
-
-cnf(premise1, assumption,
-    comp(a, join(c,d))).
-cnf(premise2, assumption,
-    comp(b, join(c,d))).
-cnf(goal, conjecture,
-    meet(join(a,meet(b,c)),join(a,meet(b,d)))=a).
diff --git a/tests/semigroup.p b/tests/semigroup.p
deleted file mode 100644
--- a/tests/semigroup.p
+++ /dev/null
@@ -1,4 +0,0 @@
-cnf(assoc, axiom, '*'(X, '*'(Y, Z)) = '*'('*'(X, Y), Z)).
-cnf(two_three, axiom, '*'(X, X) = '*'(X, '*'(X, X))).
-cnf(twiddle, axiom, '*'('*'(X, X), Y) = '*'(Y, '*'(X, X))).
-cnf(conjecture, negated_conjecture, '*'('*'(a, b), '*'(a, b)) != '*'('*'(a, a), '*'(b, b))).
diff --git a/tests/semigroup2.p b/tests/semigroup2.p
deleted file mode 100644
--- a/tests/semigroup2.p
+++ /dev/null
@@ -1,26 +0,0 @@
-% File     : GRP196-1 : TPTP v6.1.0. Released v2.2.0.
-% Domain   : Group Theory (Semigroups)
-% Problem  : In semigroups, xyyy=yyyx -> (uy)^9 = u^9v^9.
-% Version  : [MP96] (equality) axioms.
-% English  :
-% Refs     : [McC98] McCune (1998), Email to G. Sutcliffe
-%          : [MP96]  McCune & Padmanabhan (1996), Automated Deduction in Eq
-%          : [McC95] McCune (1995), Four Challenge Problems in Equational L
-% Source   : [McC98]
-% Names    : CS-3 [MP96]
-%          : Problem B [McC95]
-% Status   : Unsatisfiable
-% Rating   : 1.00 v4.0.1, 0.93 v4.0.0, 0.92 v3.7.0, 0.89 v3.4.0, 1.00 v3.3.0, 0.93 v3.1.0, 1.00 v2.2.1
-% Syntax   : Number of clauses     :    3 (   0 non-Horn;   3 unit;   1 RR)
-%            Number of atoms       :    3 (   3 equality)
-%            Maximal clause size   :    1 (   1 average)
-%            Number of predicates  :    1 (   0 propositional; 2-2 arity)
-%            Number of functors    :    3 (   2 constant; 0-2 arity)
-%            Number of variables   :    5 (   0 singleton)
-%            Maximal term depth    :   18 (   8 average)
-% SPC      : CNF_UNS_RFO_PEQ_UEQ
-% Comments : The problem was originally posed for cancellative semigroups,
-%            Otter does this with a nonstandard representation [MP96].
-cnf(assoc, axiom, '*'('*'(A,B),C)='*'(A,'*'(B,C))).
-cnf(twiddle, axiom, '*'(A,'*'(B,'*'(B,B)))='*'(B,'*'(B,'*'(B,A)))).
-cnf(conjecture, negated_conjecture, '*'(a,'*'(b,'*'(a,'*'(b,'*'(a,'*'(b,'*'(a,'*'(b,'*'(a,'*'(b,'*'(a,'*'(b,'*'(a,'*'(b,'*'(a,'*'(b,'*'(a,b))))))))))))))))) != '*'(a,'*'(a,'*'(a,'*'(a,'*'(a,'*'(a,'*'(a,'*'(a,'*'(a,'*'(b,'*'(b,'*'(b,'*'(b,'*'(b,'*'(b,'*'(b,'*'(b,b)))))))))))))))))).
diff --git a/tests/veroff.p b/tests/veroff.p
deleted file mode 100644
--- a/tests/veroff.p
+++ /dev/null
@@ -1,10 +0,0 @@
-cnf(majority, axiom,
-    f(X,X,Y) = X).
-cnf('2a', axiom,
-    f(X,Y,Z) = f(Z,X,Y)).
-cnf('2b', axiom,
-    f(X,Y,Z) = f(X,Z,Y)).
-cnf(associativity, axiom,
-    f(f(X,W,Y),W,Z) = f(X,W,f(Y,W,Z))).
-
-cnf(goal, axiom, f(f(a1,a2,a3),a4,a5) != f(f(a1,a4,a5),f(a2,a4,a5),f(a3,a4,a5))).
diff --git a/tests/winkler-easy.p b/tests/winkler-easy.p
deleted file mode 100644
--- a/tests/winkler-easy.p
+++ /dev/null
@@ -1,6 +0,0 @@
-% Needs case split on X < c.
-cnf(comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(idem, axiom, '+'(X, X) = X).
-cnf(funny, axiom, '-'('+'('-'('+'(X, Y)), '-'('+'(X, '-'(Y))))) = X).
-cnf(conjecture, negated_conjecture, '+'('-'('+'('-'(a), b)), '-'('+'('-'(a), '-'(b)))) != a).
diff --git a/tests/winkler.p b/tests/winkler.p
deleted file mode 100644
--- a/tests/winkler.p
+++ /dev/null
@@ -1,6 +0,0 @@
-% Needs case split on X < c.
-cnf(comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(idem_c, axiom, '+'(c, c) = c).
-cnf(funny, axiom, '-'('+'('-'('+'(X, Y)), '-'('+'(X, '-'(Y))))) = X).
-cnf(conjecture, negated_conjecture, '+'('-'('+'('-'(a), b)), '-'('+'('-'(a), '-'(b)))) != a).
diff --git a/tests/winkler2.p b/tests/winkler2.p
deleted file mode 100644
--- a/tests/winkler2.p
+++ /dev/null
@@ -1,6 +0,0 @@
-% Needs case split on X < c.
-cnf(comm, axiom, '+'(X, Y) = '+'(Y, X)).
-cnf(assoc, axiom, '+'(X, '+'(Y, Z)) = '+'('+'(X, Y), Z)).
-cnf(plus_c_d, axiom, '+'(c, d) = c).
-cnf(funny, axiom, '-'('+'('-'('+'(X, Y)), '-'('+'(X, '-'(Y))))) = X).
-cnf(conjecture, negated_conjecture, '+'('-'('+'('-'(a), b)), '-'('+'('-'(a), '-'(b)))) != a).
diff --git a/tests/y.p b/tests/y.p
deleted file mode 100644
--- a/tests/y.p
+++ /dev/null
@@ -1,3 +0,0 @@
-fof(k_def, axiom, ![X, Y]: '@'('@'(k, X), Y) = X).
-fof(s_def, axiom, ![X, Y, Z]: '@'('@'('@'(s, X), Y), Z) = '@'('@'(X, Z), '@'(Y, Z))).
-fof(conjecture, conjecture, ?[Y]: ![F]: '@'(Y, F) = '@'(F, '@'(Y, F))).
diff --git a/twee-lib.cabal b/twee-lib.cabal
--- a/twee-lib.cabal
+++ b/twee-lib.cabal
@@ -1,5 +1,5 @@
 name:                twee-lib
-version:             2.1
+version:             2.1.1
 synopsis:            An equational theorem prover
 homepage:            http://github.com/nick8325/twee
 license:             BSD3
@@ -9,7 +9,6 @@
 category:            Theorem Provers
 build-type:          Simple
 cabal-version:       >=1.10
-extra-source-files:  README.md tests/*.p misc/*.hs misc/*.pl misc/static-libstdc++
 description:
    Twee is an experimental equational theorem prover based on
    Knuth-Bendix completion.
@@ -80,7 +79,7 @@
     ghc-prim,
     primitive >= 0.6.2.0,
     vector
-  hs-source-dirs:      src
+  hs-source-dirs:      .
   ghc-options:         -W -fno-warn-incomplete-patterns -O2 -fmax-worker-args=100
   default-language:    Haskell2010
 
