packages feed

sop-satisfier-0.3.4.5: src/SoPSat/Internal/Unify.hs

{-# LANGUAGE RecordWildCards #-}

module SoPSat.Internal.Unify
where

import Data.Function (on)
import Data.List (find, intersect, nub, partition, (\\))

import SoPSat.Internal.SoP (
  Atom (..),
  Product (..),
  SoP (..),
  Symbol (..),
 )
import SoPSat.SoP (
  toSoP,
  (|*|),
  (|+|),
  (|-|),
  (|^|),
 )
import qualified SoPSat.SoP as SoP

data Unifier f c
  = Subst
  { sConst :: Atom f c
  , sSoP :: SoP f c
  }
  deriving (Eq, Show)

substsSoP :: (Ord f, Ord c) => [Unifier f c] -> SoP f c -> SoP f c
substsSoP [] u = u
substsSoP ((Subst{..}) : s) u = substsSoP s (substSoP sConst sSoP u)

substSoP :: (Ord f, Ord c) => Atom f c -> SoP f c -> SoP f c -> SoP f c
substSoP cons subs = foldr1 (|+|) . map (substProduct cons subs) . unS

substProduct :: (Ord f, Ord c) => Atom f c -> SoP f c -> Product f c -> SoP f c
substProduct cons subs = foldr1 (|*|) . map (substSymbol cons subs) . unP

substSymbol :: (Ord f, Ord c) => Atom f c -> SoP f c -> Symbol f c -> SoP f c
substSymbol _ _ s@(I _) = toSoP s
substSymbol cons subst s@(A a)
  | cons == a = subst
  | otherwise = S [P [s]]
substSymbol cons subst (E b p) = substSoP cons subst b |^| substProduct cons subst p

substsSubst :: (Ord f, Ord c) => [Unifier f c] -> [Unifier f c] -> [Unifier f c]
substsSubst s = map subst
 where
  subst sub@(Subst{..}) = sub{sSoP = substsSoP s sSoP}

unifiers :: (Ord f, Ord c) => SoP f c -> SoP f c -> [Unifier f c]
unifiers (S [P [A a]]) (S []) = [Subst a (S [P [I 0]])]
unifiers (S []) (S [P [A a]]) = [Subst a (S [P [I 0]])]
unifiers (S [P [I _]]) (S [P [I _]]) = []
-- (z ^ a) ~ (z ^ b) ==> [a := b]
unifiers (S [P [E s1 p1]]) (S [P [E s2 p2]])
  | s1 == s2 = unifiers (toSoP p1) (toSoP p2)
-- (2*e ^ d) ~ (2*e*a*c) ==> [a*c := 2*e ^ (d-1)]
unifiers (S [P [E (S [P s1]) p1]]) (S [P p2])
  | all (`elem` p2) s1 =
      let base = s1 `intersect` p2
          diff = p2 \\ s1
       in unifiers (S [P diff]) (S [P [E (S [P base]) (P [I (-1)]), E (S [P base]) p1]])
unifiers (S [P p2]) (S [P [E (S [P s1]) p1]])
  | all (`elem` p2) s1 =
      let base = s1 `intersect` p2
          diff = p2 \\ s1
       in unifiers (S [P diff]) (S [P [E (S [P base]) (P [I (-1)]), E (S [P base]) p1]])
-- (i ^ a) ~ j ==> [a := round (logBase i j)], when `i` and `j` are integers,
unifiers (S [P [E (S [P [I i]]) p]]) (S [P [I j]]) =
  case integerLogBase i j of
    Just k -> unifiers (S [p]) (S [P [I k]])
    Nothing -> []
unifiers (S [P [I j]]) (S [P [E (S [P [I i]]) p]]) =
  case integerLogBase i j of
    Just k -> unifiers (S [p]) (S [P [I k]])
    Nothing -> []
-- x ^ i = j => [x := root i j]
unifiers (S [P [E s (P [I p])]]) (S [P [I j]]) =
  case integerRt p j of
    Just k -> unifiers s (S [P [I k]])
    Nothing -> []
unifiers (S [P [I j]]) (S [P [E s (P [I p])]]) =
  case integerRt p j of
    Just k -> unifiers s (S [P [I k]])
    Nothing -> []
-- a^d * a^e ~ a^c ==> [c := d + e]
unifiers (S [P [E s1 p1]]) (S [p2]) = case collectBases p2 of
  Just (b : bs, ps)
    | all (== s1) (b : bs) ->
        unifiers (S [p1]) (S ps)
  _ -> []
unifiers (S [p2]) (S [P [E s1 p1]]) = case collectBases p2 of
  Just (b : bs, ps)
    | all (== s1) (b : bs) ->
        unifiers (S ps) (S [p1])
  _ -> []
-- (i * a) ~ (j * b) ==> [a := (div j i) * b]
-- Where 'a' and 'b' are variables, 'i' and 'j' are integer literals, and j `mod` i == 0
unifiers (S [P ((I i) : ps)]) (S [P ((I j) : ps1)])
  | (Just k) <- safeDiv j i = unifiers (S [P ps]) (SoP.int k |*| S [P ps1])
  | (Just k) <- safeDiv i j = unifiers (SoP.int k |*| S [P ps]) (S [P ps1])
  | otherwise = []
-- (2*a) ~ (2*b) ==> [a := b]
-- unifiers (S [P (p:ps1)]) (S [P (p':ps2)])
--     | p == p'   = unifiers' ct (S [P ps1]) (S [P ps2])
--     | otherwise = []
unifiers (S [P ps1@(_ : _ : _)]) (S [P ps2])
  | null psx = []
  | otherwise = unifiers (S [P ps1'']) (S [P ps2''])
 where
  ps1' = ps1 \\ psx
  ps2' = ps2 \\ psx
  ps1''
    | null ps1' = [I 1]
    | otherwise = ps1'
  ps2''
    | null ps2' = [I 1]
    | otherwise = ps2'
  psx = ps1 `intersect` ps2
unifiers (S [P ps1]) (S [P ps2@(_ : _ : _)])
  | null psx = []
  | otherwise = unifiers (S [P ps1'']) (S [P ps2''])
 where
  ps1' = ps1 \\ psx
  ps2' = ps2 \\ psx
  ps1''
    | null ps1' = [I 1]
    | otherwise = ps1'
  ps2''
    | null ps2' = [I 1]
    | otherwise = ps2'
  psx = ps1 `intersect` ps2
unifiers (S [P [A a]]) s = [Subst a s]
unifiers s (S [P [A a]]) = [Subst a s]
-- (2 + a) ~ 5 ==> [a := 3]
unifiers (S ((P [I i]) : ps1)) (S ((P [I j]) : ps2))
  | i < j = unifiers (S ps1) (S (P [I (j - i)] : ps2))
  | i > j = unifiers (S (P [I (i - j)] : ps1)) (S ps2)
-- (a + c) ~ (b + c) ==> [a := b]
unifiers s1@(S ps1) s2@(S ps2) = case splitSoP s1 s2 of
  (s1', s2')
    | s1' /= s1 || s2' /= s2 ->
        unifiers s1' s2'
  _
    | null psx
    , length ps1 == length ps2 ->
        case nub (concat (zipWith (\x y -> unifiers (S [x]) (S [y])) ps1 ps2)) of
          [] -> unifiers' s1 s2
          [k] -> [k]
          _ -> []
    | null psx ->
        unifiers' s1 s2
  _ -> unifiers' (S ps1'') (S ps2'')
 where
  ps1' = ps1 \\ psx
  ps2' = ps2 \\ psx
  ps1''
    | null ps1' = [P [I 0]]
    | otherwise = ps1'
  ps2''
    | null ps2' = [P [I 0]]
    | otherwise = ps2'
  psx = ps1 `intersect` ps2

unifiers' :: (Ord f, Ord c) => SoP f c -> SoP f c -> [Unifier f c]
unifiers' (S [P [I i], P [A a]]) s2 =
  [Subst a (s2 |+| S [P [I (negate i)]])]
unifiers' s1 (S [P [I i], P [A a]]) =
  [Subst a (s1 |+| S [P [I (negate i)]])]
unifiers' _ _ = []

splitSoP :: (Ord f, Ord c) => SoP f c -> SoP f c -> (SoP f c, SoP f c)
splitSoP u v = (lhs, rhs)
 where
  reduced = v |-| u
  (lhs', rhs') = partition neg (unS reduced)
  lhs
    | null lhs' = SoP.int 0
    | otherwise = ((|*|) `on` S) lhs' [P [I (-1)]]
  rhs
    | null rhs' = SoP.int 0
    | otherwise = S rhs'

  neg (P ((I i) : _)) = i < 0
  neg _ = False

collectBases :: Product f c -> Maybe ([SoP f c], [Product f c])
collectBases = fmap unzip . traverse go . unP
 where
  go (E s1 p1) = Just (s1, p1)
  go _ = Nothing

safeDiv :: Integer -> Integer -> Maybe Integer
safeDiv i j
  | j == 0 = Just 0
  | otherwise = case divMod i j of
      (k, 0) -> Just k
      _ -> Nothing

integerLogBase :: Integer -> Integer -> Maybe Integer
integerLogBase x y
  | x > 1 && y > 0 =
      let z1 = integerLogBase' x y
          z2 = integerLogBase' x (y - 1)
       in if z1 == z2
            then Nothing
            else Just z1
integerLogBase _ _ = Nothing

integerLogBase' :: Integer -> Integer -> Integer
integerLogBase' b m = snd (go b)
 where
  go :: Integer -> (Integer, Integer)
  go pw | m < pw = (m, 0)
  go pw = case go (pw ^ (2 :: Int)) of
    (q, e) | q < pw -> (q, 2 * e)
    (q, e) -> (q `quot` pw, 2 * e + 1)

-- Naive implementation of exact integer root
integerRt :: Integer -> Integer -> Maybe Integer
integerRt 1 y = Just y
integerRt x y = find ((== y) . (^ x)) [1 .. y]