packages feed

sop-satisfier-0.3.4.5: src/SoPSat/Satisfier.hs

{-# LANGUAGE RecordWildCards #-}

module SoPSat.Satisfier (
  -- * State
  SolverState,

  -- * State manipulation
  declare,
  assert,
  unify,

  -- * State information
  range,
  ranges,

  -- * State execution
  withState,
  runStatements,
  evalStatements,

  -- * Expressions
  evalSoP,
)
where

import Control.Applicative ((<|>))
import Control.Arrow (second)
import Control.Monad (unless, when, (>=>))

import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (isNothing)

import SoPSat.Internal.NewtonsMethod
import SoPSat.Internal.Range
import SoPSat.Internal.SoP (
  Atom (..),
  Product (..),
  SoP (..),
  Symbol (..),
 )
import SoPSat.Internal.SolverMonad
import SoPSat.Internal.Unify
import SoPSat.SoP

parts :: [a] -> [[a]]
parts [] = []
parts (x : xs) = xs : map (x :) (parts xs)

{- | Declares atom in the state
ignores constants and only declare function arguments
-}
declareAtom :: (Ord f, Ord c) => Atom f c -> SolverState f c Bool
declareAtom (C _) = return True
declareAtom (F _ args) = and <$> mapM declareSoP args

{- | Declares symbol in the state with the default interval
If symbol exists preserves the old interval
-}
declareSymbol :: (Ord f, Ord c) => Symbol f c -> SolverState f c Bool
declareSymbol (I _) = return True
declareSymbol (A a) = do
  existing <- getRanges
  when (isNothing (M.lookup a existing)) (putRange a rangeNatural)
  declareAtom a
 where
  rangeNatural = Range (Bound (int 0)) Inf
declareSymbol (E b p) = (&&) <$> declareSoP b <*> declareProduct p

-- | Similar to @declareSoP@ but for @Product@
declareProduct :: (Ord f, Ord c) => Product f c -> SolverState f c Bool
declareProduct = fmap and . mapM declareSymbol . unP

{- | Declare SoP in the state with default values
Creates range for free-variables
-}
declareSoP :: (Ord f, Ord c) => SoP f c -> SolverState f c Bool
declareSoP s@(S ps)
  | left /= int 0 =
      (&&) <$> (and <$> mapM declareProduct ps) <*> assert (SoPE left right LeR)
  | otherwise = and <$> mapM declareProduct ps
 where
  (left, right) = splitSoP (int 0) s

{- | Declare expression to state, returns normalised expression

Common for @declare@, @assert@, and @unify@
-}
declareToState :: (Ord f, Ord c) => SoPE f c -> SolverState f c (SoPE f c)
declareToState SoPE{..} = do
  r1 <- declareSoP lhs
  r2 <- declareSoP rhs
  us <- getUnifiers
  let
    lhs' = substsSoP us lhs
    rhs' = substsSoP us rhs
  unless (r1 && r2) (fail "")
  return (SoPE lhs' rhs' op)

{- | Declare equality of two expressions
Adds new unifiers to the state
-}
declareEq ::
  (Ord f, Ord c) =>
  -- | First expression
  SoP f c ->
  -- | Second expression
  SoP f c ->
  -- | Similar to @declare@ but handles only equalities
  SolverState f c Bool
declareEq u v =
  do
    (Range low1 up1) <- getRangeSoP u
    (Range low2 up2) <- getRangeSoP v
    lowRes <- boundComp low1 low2
    upRes <- boundComp up1 up2

    -- Declaration and assertions of expression is done on the whole domain
    -- if two expressions are equal, their domains will intersect
    --
    -- g(x) in [1,5] and forall x  g(x) = f(x) then f(x) in [1,5]
    lowerUpdate <-
      case (lowRes, low1, low2) of
        (True, _, Bound lowB2) -> propagateInEqSoP u GeR lowB2
        (False, Bound lowB1, _) -> propagateInEqSoP v GeR lowB1
        (_, _, _) -> return True

    upperUpdate <-
      case (upRes, up1, up2) of
        (True, _, Bound upB2) -> propagateInEqSoP u LeR upB2
        (False, Bound upB1, _) -> propagateInEqSoP v LeR upB1
        (_, _, _) -> return True

    declareEq' u v
    return (lowerUpdate && upperUpdate)
 where
  boundComp Inf _ = return False
  boundComp _ Inf = return True
  boundComp (Bound a) (Bound b) = assert (SoPE a b LeR)

declareEq' :: (Ord f, Ord c) => SoP f c -> SoP f c -> SolverState f c ()
declareEq' (S [P [A a]]) v = putUnifiers [Subst a v]
declareEq' u (S [P [A a]]) = putUnifiers [Subst a u]
declareEq' u v = putUnifiers $ unifiers u v

-- | Updates interval information for a symbol
propagateInEqSymbol ::
  (Ord f, Ord c) =>
  -- | Updated symbol
  Symbol f c ->
  -- | Relationship between the symbol and target
  OrdRel ->
  -- | Target Boundary
  SoP f c ->
  -- | Similat to @declareInEq@
  SolverState f c Bool
propagateInEqSymbol (I _) _ _ =
  return True -- No need to update numbers
propagateInEqSymbol (A a) rel bound = do
  (Range low up) <- getRange a
  -- New bound is less/greater than the old one
  -- The check is done before propagation
  -- This assumption is potentially wrong
  case rel of
    LeR ->
      putRange a (Range low rangeBound)
    GeR ->
      putRange a (Range rangeBound up)
    EqR -> error "propagateInEqSymbol:EqR: unreachable"
  return True
 where
  rangeBound = Bound bound
propagateInEqSymbol (E b (P [I i])) rel (S [P [I j]])
  | (Just p) <- integerRt i j =
      propagateInEqSoP b rel (int p)
propagateInEqSymbol (E (S [P [I i]]) p) rel (S [P [I j]])
  | (Just e) <- integerLogBase i j =
      propagateInEqProduct p rel (int e)
propagateInEqSymbol _ _ _ = fail ""

-- | Propagates interval information down the Product
propagateInEqProduct ::
  (Ord f, Ord c) =>
  -- | Updates expression
  Product f c ->
  -- | Relationship between the expression and target
  OrdRel ->
  -- | Target boundary
  SoP f c ->
  -- | Similar to @declareInEq@
  SolverState f c Bool
propagateInEqProduct (P [symb]) rel target_bound = propagateInEqSymbol symb rel target_bound
propagateInEqProduct (P ss) rel target_bound =
  and <$> mapM (uncurry propagate) (zipWith (curry (second P)) ss (parts ss))
 where
  -- a <= x * y => a/y <= x and a/x <= y
  -- Currently simply propagating the bound further
  -- a <= x * y => a <= x and a <= y
  propagate symb _prod =
    propagateInEqSymbol
      symb
      rel
      target_bound

-- (target_bound |/| prod)

-- | Propagates interval information down the SoP
propagateInEqSoP ::
  (Ord f, Ord c) =>
  -- | Updated expression
  SoP f c ->
  -- | Relationship between the expression and target
  OrdRel ->
  -- | Target boundary
  SoP f c ->
  -- | Similar to @declareInEq@
  SolverState f c Bool
propagateInEqSoP (S [P [symb]]) rel target_bound = propagateInEqSymbol symb rel target_bound
propagateInEqSoP (S ps) rel target_bound =
  and <$> mapM (uncurry propagate) (zipWith (curry (second S)) ps (parts ps))
 where
  -- a <= x + y => a - y <= x and a - x <= y
  propagate prod sm =
    propagateInEqProduct
      prod
      rel
      (target_bound |-| sm)

{- | Declare inequality of two expressions
Updates interval information in the state
-}
declareInEq ::
  (Ord f, Ord c) =>
  -- | Relationship between expressions
  OrdRel ->
  -- | Left-hand side expression
  SoP f c ->
  -- | Right-hand side expression
  SoP f c ->
  -- | Similar to @declare@ but handles only inequalities
  SolverState f c Bool
declareInEq EqR u v = declareEq u v >> return True
declareInEq op u v =
  let
    (u', v') = splitSoP u v
   in
    -- If inequality holds with current interval information
    -- then no need to update it
    do
      res <- assert (SoPE u' v' op)
      if res
        then return True
        else case op of
          LeR -> do
            a1 <- propagateInEqSoP u' LeR v'
            a2 <- propagateInEqSoP v' GeR u'
            return (a1 && a2)
          GeR -> do
            a1 <- propagateInEqSoP u' GeR v'
            a2 <- propagateInEqSoP v' LeR u'
            return (a1 && a2)

-- | Declare expression to the state
declare ::
  (Ord f, Ord c) =>
  -- | Expression to declare
  SoPE f c ->
  -- | - True - if expression was declared
  --   - False - if expression contradicts current state
  --
  -- State will become @Nothing@ if it cannot reason about these kind of expressions
  SolverState f c Bool
declare =
  declareToState >=> \SoPE{..} ->
    case op of
      EqR -> declareEq lhs rhs
      _ -> declareInEq op lhs rhs

-- | Assert that two expressions are equal using unifiers from the state
assertEq ::
  (Ord f, Ord c) =>
  -- | Left-hand side expression
  SoP f c ->
  -- | Right-hand size expression
  SoP f c ->
  -- | Similar to assert but only checks for equality @lhs = rhs@
  SolverState f c Bool
assertEq lhs rhs = return (lhs == rhs)

-- | Assert using only ranges stores in the state
assertRange ::
  (Ord f, Ord c) =>
  -- | Left-hand side expression
  SoP f c ->
  -- | Right-hand size expression
  SoP f c ->
  -- | Similar to @assert@ but uses only intervals from the state to check @lhs <= rhs@
  SolverState f c Bool
assertRange lhs rhs = uncurry assertRange' $ splitSoP lhs rhs

assertRange' :: (Ord f, Ord c) => SoP f c -> SoP f c -> SolverState f c Bool
assertRange' (S [P [I i]]) (S [P [I j]]) = return (i <= j)
assertRange' lhs rhs = do
  (Range _ up1) <- getRangeSoP lhs
  (Range low2 up2) <- getRangeSoP rhs
  -- If both sides increase infinitely, fail to use Newton's method
  -- Information about rate of growth is required
  -- to check inequality on the whole domain
  if up1 == up2 && up2 == Inf
    then fail ""
    else case (up1, low2) of
      (Inf, _) -> return False
      (_, Inf) -> return False
      (Bound ub1, Bound lb2) ->
        -- Orders of recursive checks matters
        -- @runLemma2@ in the tests loops indefinitely
        -- possibly other test cases too
        do
          r1 <-
            if ub1 /= lhs
              then assert (SoPE ub1 rhs LeR)
              else return False
          r2 <-
            if lb2 /= rhs
              then assert (SoPE lhs lb2 LeR)
              else return False
          return (r1 || r2)

-- | Assert using only Newton's method
assertNewton ::
  (Ord f, Ord c) =>
  -- | Left-hand side expression
  SoP f c ->
  -- | Right-hand side expression
  SoP f c ->
  -- | Similar to @assert@ but uses only Newton's method to check @lhs <= rhs@
  SolverState f c Bool
assertNewton lhs rhs =
  let
    expr = rhs |-| lhs |+| int 1
   in
    checkExpr expr
 where
  -- hasFunction :: (Ord f, Ord c) => SoP f c -> Bool
  -- hasFunction = any isFunction . atoms

  checkExpr :: (Ord f, Ord c) => SoP f c -> SolverState f c Bool
  checkExpr expr
    | (Right binds) <- newtonMethod expr =
        not <$> checkBinds binds
    | otherwise =
        return True

  checkBinds :: (Ord f, Ord c) => Map (Atom f c) Double -> SolverState f c Bool
  checkBinds binds = and <$> mapM (uncurry (checkBind binds)) (M.toList binds)

  checkBind ::
    (Ord f, Ord c, Ord n, Floating n) =>
    Map (Atom f c) n -> Atom f c -> n -> SolverState f c Bool
  checkBind binds c v = do
    (Range left right) <- getRange c
    return (checkLeft binds v left && checkRight binds v right)

  checkLeft ::
    (Ord f, Ord c, Ord n, Floating n) => Map (Atom f c) n -> n -> Bound f c -> Bool
  checkLeft _ _ Inf = True
  checkLeft binds v (Bound sop) = evalSoP sop binds <= v

  checkRight ::
    (Ord f, Ord c, Ord n, Floating n) => Map (Atom f c) n -> n -> Bound f c -> Bool
  checkRight _ _ Inf = True
  checkRight binds v (Bound sop) = v <= evalSoP sop binds

-- | Assert if given expression holds in the current environment
assert ::
  (Ord f, Ord c) =>
  -- | Asserted expression
  SoPE f c ->
  -- | - True - if expressions holds
  --   - False - otherwise
  --
  -- State will become @Nothing@ if it cannot reason about these kind of expressions
  SolverState f c Bool
assert =
  declareToState >=> \SoPE{..} ->
    case op of
      EqR -> assertEq lhs rhs
      LeR -> do
        r1 <- assertEq lhs rhs
        if r1
          then return True
          else do
            assertRange lhs rhs <|> assertNewton lhs rhs
      GeR -> do
        r1 <- assertEq lhs rhs
        if r1
          then return True
          else do
            assertRange rhs lhs <|> assertNewton rhs lhs

{- | Get unifiers for an expression
minimal set of expressions that should hold for the expression to hold
-}
unify ::
  (Ord f, Ord c) =>
  -- | Unified expression
  SoPE f c ->
  -- | List of unifiers - Minimal list of unifiers for the expression to hold.
  -- The list is empty, if it never holds
  --
  -- State will always be valid after a call
  SolverState f c [SoPE f c]
unify =
  declareToState >=> \expr@SoPE{..} ->
    case op of
      EqR -> return (filter (/= expr) $ map unifier2SoPE (unifiers lhs rhs))
      _ -> return []
 where
  unifier2SoPE Subst{..} = SoPE (symbol sConst) sSoP EqR

-- | Get range of possible values for an expression
range ::
  (Ord f, Ord c) =>
  -- | Expression
  SoP f c ->
  -- | (lower bound, upper bound) - Range for an expression
  --
  -- @Nothing@ means that the expression is unbounded
  -- from that side
  SolverState f c (Maybe (SoP f c), Maybe (SoP f c))
range sop = do
  _ <- declareSoP sop
  (Range low up) <- getRangeSoP sop
  return (boundSoP low, boundSoP up)

-- | Get list of all ranges stored in a state
ranges ::
  (Ord f, Ord c) =>
  -- | (lower bound, symbol, upper bound) - Similar to @range@
  --     but also provides expression
  SolverState f c [(Maybe (SoP f c), SoP f c, Maybe (SoP f c))]
ranges =
  map (\(a, Range low up) -> (boundSoP low, symbol a, boundSoP up)) . M.toList <$> getRanges