module ToySolver.Arith.OmegaTest.Base
( Model
, solve
, solveQFLIRAConj
, Options (..)
, defaultOptions
, checkRealNoCheck
, checkRealByFM
, zmod
) where
import Control.Exception (assert)
import Control.Monad
import Data.Default.Class
import Data.List
import Data.Maybe
import Data.Ord
import Data.Ratio
import qualified Data.IntMap as IM
import qualified Data.IntSet as IS
import Data.VectorSpace
import ToySolver.Data.ArithRel
import ToySolver.Data.Boolean
import ToySolver.Data.DNF
import qualified ToySolver.Data.LA as LA
import ToySolver.Data.Var
import ToySolver.Internal.Util (combineMaybe)
import qualified ToySolver.Arith.FourierMotzkin as FM
data Options
= Options
{ optCheckReal :: VarSet -> [LA.Atom Rational] -> Bool
}
instance Default Options where
def = defaultOptions
defaultOptions :: Options
defaultOptions =
Options
{ optCheckReal =
checkRealByFM
}
checkRealNoCheck :: VarSet -> [LA.Atom Rational] -> Bool
checkRealNoCheck _ _ = True
checkRealByFM :: VarSet -> [LA.Atom Rational] -> Bool
checkRealByFM vs as = isJust $ FM.solve vs as
type ExprZ = LA.Expr Integer
data Constr
= IsNonneg ExprZ
| IsZero ExprZ
deriving (Show, Eq, Ord)
instance Variables Constr where
vars (IsNonneg t) = vars t
vars (IsZero t) = vars t
simplify :: [Constr] -> Maybe [Constr]
simplify = fmap concat . mapM f
where
f :: Constr -> Maybe [Constr]
f constr@(IsNonneg e) =
case LA.asConst e of
Just x -> guard (x >= 0) >> return []
Nothing -> return [constr]
f constr@(IsZero e) =
case LA.asConst e of
Just x -> guard (x == 0) >> return []
Nothing -> return [constr]
leZ, ltZ, geZ, gtZ, eqZ :: ExprZ -> ExprZ -> Constr
leZ e1 e2 = IsNonneg (LA.mapCoeff (`div` d) e)
where
e = e2 ^-^ e1
d = abs $ gcd' [c | (c,v) <- LA.terms e, v /= LA.unitVar]
ltZ e1 e2 = (e1 ^+^ LA.constant 1) `leZ` e2
geZ = flip leZ
gtZ = flip ltZ
eqZ e1 e2 =
case isZero (e1 ^-^ e2) of
Just c -> c
Nothing -> IsZero (LA.constant 1)
isZero :: ExprZ -> Maybe Constr
isZero e
= if LA.coeff LA.unitVar e `mod` d == 0
then Just $ IsZero $ LA.mapCoeff (`div` d) e
else Nothing
where
d = abs $ gcd' [c | (c,v) <- LA.terms e, v /= LA.unitVar]
applySubst1Constr :: Var -> ExprZ -> Constr -> Constr
applySubst1Constr v e (IsNonneg e2) = LA.applySubst1 v e e2 `geZ` LA.constant 0
applySubst1Constr v e (IsZero e2) = LA.applySubst1 v e e2 `eqZ` LA.constant 0
evalConstr :: Model Integer -> Constr -> Bool
evalConstr m (IsNonneg t) = LA.evalExpr m t >= 0
evalConstr m (IsZero t) = LA.evalExpr m t == 0
type Rat = (ExprZ, Integer)
type BoundsZ = ([Rat],[Rat])
evalBoundsZ :: Model Integer -> BoundsZ -> IntervalZ
evalBoundsZ model (ls,us) =
foldl' intersectZ univZ $
[ (Just (ceiling (LA.evalExpr model x % c)), Nothing) | (x,c) <- ls ] ++
[ (Nothing, Just (floor (LA.evalExpr model x % c))) | (x,c) <- us ]
collectBoundsZ :: Var -> [Constr] -> (BoundsZ, [Constr])
collectBoundsZ v = foldr phi (([],[]),[])
where
phi :: Constr -> (BoundsZ,[Constr]) -> (BoundsZ,[Constr])
phi constr@(IsNonneg t) ((ls,us),xs) =
case LA.extract v t of
(c,t') ->
case c `compare` 0 of
EQ -> ((ls, us), constr : xs)
GT -> (((negateV t', c) : ls, us), xs)
LT -> ((ls, (t', negate c) : us), xs)
phi constr@(IsZero _) (bnd,xs) = (bnd, constr : xs)
solve' :: Options -> VarSet -> [Constr] -> Maybe (Model Integer)
solve' opt vs2 xs = simplify xs >>= go vs2
where
go :: VarSet -> [Constr] -> Maybe (Model Integer)
go vs cs | IS.null vs = do
let m = IM.empty
guard $ all (evalConstr m) cs
return m
go vs cs | Just (e,cs2) <- extractEq cs = do
(vs',cs3, mt) <- eliminateEq e vs cs2
m <- go vs' =<< simplify cs3
return (mt m)
go vs cs = do
guard $ optCheckReal opt vs (map toLAAtom cs)
if isExact bnd then
case1
else
case1 `mplus` case2
where
(v,vs',bnd@(ls,us),rest) = chooseVariable vs cs
case1 = do
let zs = [ LA.constant ((a1)*(b1)) `leZ` (a *^ d ^-^ b *^ c)
| (c,a)<-ls , (d,b)<-us ]
model <- go vs' =<< simplify (zs ++ rest)
case pickupZ (evalBoundsZ model bnd) of
Nothing -> error "ToySolver.OmegaTest.solve': should not happen"
Just val -> return $ IM.insert v val model
case2 = msum
[ do c <- isZero $ a' *^ LA.var v ^-^ (c' ^+^ LA.constant k)
model <- go vs (c : cs)
return model
| let m = maximum [b | (_,b)<-us]
, (c',a') <- ls
, k <- [0 .. (m*a'a'm) `div` m]
]
isExact :: BoundsZ -> Bool
isExact (ls,us) = and [a==1 || b==1 | (_,a)<-ls , (_,b)<-us]
toLAAtom :: Constr -> LA.Atom Rational
toLAAtom (IsNonneg e) = LA.mapCoeff fromInteger e .>=. LA.constant 0
toLAAtom (IsZero e) = LA.mapCoeff fromInteger e .==. LA.constant 0
chooseVariable :: VarSet -> [Constr] -> (Var, VarSet, BoundsZ, [Constr])
chooseVariable vs xs = head $ [e | e@(_,_,bnd,_) <- table, isExact bnd] ++ table
where
table = [ (v, IS.fromAscList vs', bnd, rest)
| (v,vs') <- pickup (IS.toAscList vs), let (bnd, rest) = collectBoundsZ v xs
]
extractEq :: [Constr] -> Maybe (ExprZ, [Constr])
extractEq = msum . map f . pickup
where
f :: (Constr, [Constr]) -> Maybe (ExprZ, [Constr])
f (IsZero e, ls) = Just (e, ls)
f _ = Nothing
eliminateEq :: ExprZ -> VarSet -> [Constr] -> Maybe (VarSet, [Constr], Model Integer -> Model Integer)
eliminateEq e vs cs | Just c <- LA.asConst e = guard (c==0) >> return (vs, cs, id)
eliminateEq e vs cs = do
let (ak,xk) = minimumBy (comparing (abs . fst)) [(a,x) | (a,x) <- LA.terms e, x /= LA.unitVar]
if abs ak == 1 then
case LA.extract xk e of
(_, e') -> do
let xk_def = signum ak *^ negateV e'
return $
( IS.delete xk vs
, [applySubst1Constr xk xk_def c | c <- cs]
, \model -> IM.insert xk (LA.evalExpr model xk_def) model
)
else do
let m = abs ak + 1
assert (ak `zmod` m == signum ak) $ return ()
let
sigma = if IS.null vs then 0 else IS.findMax vs + 1
xk_def = ( signum ak * m) *^ LA.var sigma ^+^
LA.fromTerms [(signum ak * (a `zmod` m), x) | (a,x) <- LA.terms e, x /= xk]
e2 = ( abs ak) *^ LA.var sigma ^+^
LA.fromTerms [((floor (a%m + 1/2) + (a `zmod` m)), x) | (a,x) <- LA.terms e, x /= xk]
assert (m *^ e2 == LA.applySubst1 xk xk_def e) $ return ()
let mt model = IM.delete sigma $ IM.insert xk (LA.evalExpr model xk_def) model
c <- isZero e2
return (IS.insert sigma (IS.delete xk vs), c : [applySubst1Constr xk xk_def c | c <- cs], mt)
type IntervalZ = (Maybe Integer, Maybe Integer)
univZ :: IntervalZ
univZ = (Nothing, Nothing)
intersectZ :: IntervalZ -> IntervalZ -> IntervalZ
intersectZ (l1,u1) (l2,u2) = (combineMaybe max l1 l2, combineMaybe min u1 u2)
pickupZ :: IntervalZ -> Maybe Integer
pickupZ (Nothing,Nothing) = return 0
pickupZ (Just x, Nothing) = return x
pickupZ (Nothing, Just x) = return x
pickupZ (Just x, Just y) = if x <= y then return x else mzero
solve :: Options -> VarSet -> [LA.Atom Rational] -> Maybe (Model Integer)
solve opt vs cs = msum [solve' opt vs cs | cs <- unDNF dnf]
where
dnf = andB (map f cs)
f (ArithRel lhs op rhs) =
case op of
Lt -> DNF [[a `ltZ` b]]
Le -> DNF [[a `leZ` b]]
Gt -> DNF [[a `gtZ` b]]
Ge -> DNF [[a `geZ` b]]
Eql -> DNF [[a `eqZ` b]]
NEq -> DNF [[a `ltZ` b], [a `gtZ` b]]
where
(e1,c1) = g lhs
(e2,c2) = g rhs
a = c2 *^ e1
b = c1 *^ e2
g :: LA.Expr Rational -> (ExprZ, Integer)
g a = (LA.mapCoeff (\c -> floor (c * fromInteger d)) a, d)
where
d = foldl' lcm 1 [denominator c | (c,_) <- LA.terms a]
solveQFLIRAConj :: Options -> VarSet -> [LA.Atom Rational] -> VarSet -> Maybe (Model Rational)
solveQFLIRAConj opt vs cs ivs = listToMaybe $ do
(cs2, mt) <- FM.projectN rvs cs
m <- maybeToList $ solve opt ivs cs2
return $ mt $ IM.map fromInteger m
where
rvs = vs `IS.difference` ivs
zmod :: Integer -> Integer -> Integer
a `zmod` b = a b * floor (a % b + 1 / 2)
gcd' :: [Integer] -> Integer
gcd' [] = 1
gcd' xs = foldl1' gcd xs
pickup :: [a] -> [(a,[a])]
pickup [] = []
pickup (x:xs) = (x,xs) : [(y,x:ys) | (y,ys) <- pickup xs]