Omega-0.1.1: Data/Presburger/Omega/LowLevel.hsc
-- | This module provides a low-level interface for creating,
-- manipulating, and querying Presburger arithmetic formulae.
-- The real work is done by the C++ Omega library
-- (<http://github.com/davewathaverford/the-omega-project>).
--
-- The main data types are 'OmegaSet' and 'OmegaRel', which use a formula
-- to define a set or relation, respectively, on integer-valued points in
-- Cartesian space.
-- A typical use involves creating a Presburger arithmetic 'Formula', using
-- it to create a set or relation, and then querying the set or relation.
{-# OPTIONS_GHC -XForeignFunctionInterface -fwarn-incomplete-patterns
-XEmptyDataDecls -XRankNTypes -XMultiParamTypeClasses
-XFunctionalDependencies -XTypeSynonymInstances
-XFlexibleInstances -XFlexibleContexts #-}
module Data.Presburger.Omega.LowLevel
(-- * Sets and relations
Presburger,
OmegaSet, newOmegaSet,
OmegaRel, newOmegaRel,
-- * Inspecting sets and relations directly
queryDNFSet, queryDNFRelation,
-- * Queries on sets and relations
lowerBoundSatisfiable, upperBoundSatisfiable,
obviousTautology, definiteTautology,
exact, inexact, unknown,
-- * Creating new sets and relations from old ones
-- ** Bounds
upperBound, lowerBound,
-- ** Binary operations
equal, union, intersection, composition,
restrictDomain, restrictRange,
difference, crossProduct,
Effort(..),
gist,
-- ** Unary operations
transitiveClosure,
domain, range, inverse, complement,
deltas, approximate,
-- * Constructing formulas
Formula,
true, false,
conjunction, disjunction, negation,
VarHandle,
qForall, qExists,
Coefficient(..),
inequality, equality
)
where
#include "C_omega.h"
#let alignof x = "%d", __alignof__(x)
import Control.Monad
import Data.Int
import Data.List(findIndex)
import Data.Word
import Foreign.C
import Foreign.ForeignPtr
import qualified Foreign.Marshal.Alloc as ForeignAlloc
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import System.IO.Unsafe(unsafePerformIO)
-------------------------------------------------------------------------------
-- Data types, classes, and functions imported from C++
-- External data types, these have the same name as in C.
data Relation -- A set or relation
data Form -- A logic formula (Formula)
data F_And -- A conjunction
data F_Declaration -- A forall or exists formula
data Var_Decl -- A handle to a variable
data DNF_Iterator -- Iterator over a DNF clause
data Conjunct -- One conjunct within a DNF clause
data Tuple_Iterator a -- Iterator over a Tuple (in Omega library)
data EQ_Iterator -- Iterator over a set of EQ constraints
data EQ_Handle -- Handle to an EQ constraint
data GEQ_Iterator -- Iterator over a set of GEQ constraints
data GEQ_Handle -- Handle to a GEQ constraint
data Constr_Vars_Iter -- Iterate over coefficients in a constraint
class Constraint a -- The 'Constraint' base class
instance Constraint EQ_Handle
instance Constraint GEQ_Handle
-- Pointers to external data types
type C_Relation = Ptr Relation
type C_Form = Ptr Form
type C_And = Ptr F_And
type C_Quantifier = Ptr F_Declaration
type C_Var = Ptr Var_Decl
type C_DNF_Iterator = Ptr DNF_Iterator
type C_Conjunct = Ptr Conjunct
type C_Tuple_Iterator a = Ptr (Tuple_Iterator a)
type C_EQ_Iterator = Ptr EQ_Iterator
type C_EQ_Handle = Ptr EQ_Handle
type C_GEQ_Iterator = Ptr GEQ_Iterator
type C_GEQ_Handle = Ptr GEQ_Handle
type C_Constr_Vars_Iter = Ptr Constr_Vars_Iter
-- | The 'gist' routine takes a parameter specifying how much effort to
-- put into generating a good result. Higher effort takes more time.
-- It's unspecified what a given effort level does.
data Effort = Light | Moderate | Strenuous
deriving (Eq, Show, Enum)
-- Everything containing a formula is an instance of class Logical
class Logical f where
add_and :: f -> IO C_Form
add_or :: f -> IO C_Form
add_not :: f -> IO C_Form
add_forall :: f -> IO C_Quantifier
add_exists :: f -> IO C_Quantifier
convert_to_and :: f -> IO C_And
finalize :: f -> IO ()
instance Logical C_Relation where
add_and = hsw_relation_add_and
add_or = hsw_relation_add_or
add_not = hsw_relation_add_not
add_forall = hsw_relation_add_forall
add_exists = hsw_relation_add_exists
-- We take advantage of the fact that C_And is a subclass of C_Form
-- here, and simply cast the pointer.
convert_to_and r = liftM castPtr $ hsw_relation_add_and r
finalize = hsw_relation_finalize
instance Logical C_Form where
add_and = hsw_formula_add_and
add_or = hsw_formula_add_or
add_not = hsw_formula_add_not
add_forall = hsw_formula_add_forall
add_exists = hsw_formula_add_exists
convert_to_and = hsw_formula_to_and
finalize = hsw_formula_finalize
-- C_And is a subclass of C_Form and implements all its methods.
-- Consequently, we simply cast to C_Form
instance Logical C_And where
add_and = hsw_formula_add_and . castPtr
add_or = hsw_formula_add_or . castPtr
add_not = hsw_formula_add_not . castPtr
add_forall = hsw_formula_add_forall . castPtr
add_exists = hsw_formula_add_exists . castPtr
convert_to_and = return
finalize = hsw_formula_finalize . castPtr
-- C_Quantifier is a subclass of C_Form and implements all its methods.
-- Consequently, we simply cast to C_Form
instance Logical C_Quantifier where
add_and = hsw_formula_add_and . castPtr
add_or = hsw_formula_add_or . castPtr
add_not = hsw_formula_add_not . castPtr
add_forall = hsw_formula_add_forall . castPtr
add_exists = hsw_formula_add_exists . castPtr
convert_to_and = hsw_formula_to_and . castPtr
finalize = hsw_formula_finalize . castPtr
-- Used for freeing data that was allocated in C
foreign import ccall safe free :: Ptr a -> IO ()
foreign import ccall safe hsw_new_relation
:: CInt -> CInt -> IO C_Relation
foreign import ccall safe hsw_new_set
:: CInt -> IO C_Relation
foreign import ccall safe hsw_free_relation
:: C_Relation -> IO ()
foreign import ccall "&hsw_free_relation" ptr_to_free_relation
:: FunPtr (C_Relation -> IO ())
foreign import ccall safe hsw_relation_show
:: C_Relation -> IO CString
foreign import ccall safe hsw_num_input_vars
:: C_Relation -> IO CInt
foreign import ccall safe hsw_num_output_vars
:: C_Relation -> IO CInt
foreign import ccall safe hsw_num_set_vars
:: C_Relation -> IO CInt
foreign import ccall safe hsw_input_var
:: C_Relation -> CInt -> IO C_Var
foreign import ccall safe hsw_output_var
:: C_Relation -> CInt -> IO C_Var
foreign import ccall safe hsw_set_var
:: C_Relation -> CInt -> IO C_Var
foreign import ccall safe hsw_is_lower_bound_satisfiable
:: C_Relation -> IO Bool
foreign import ccall safe hsw_is_upper_bound_satisfiable
:: C_Relation -> IO Bool
foreign import ccall safe hsw_is_obvious_tautology
:: C_Relation -> IO Bool
foreign import ccall safe hsw_is_definite_tautology
:: C_Relation -> IO Bool
foreign import ccall safe hsw_is_exact
:: C_Relation -> IO Bool
foreign import ccall safe hsw_is_inexact
:: C_Relation -> IO Bool
foreign import ccall safe hsw_is_unknown
:: C_Relation -> IO Bool
foreign import ccall safe hsw_upper_bound
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_lower_bound
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_equal
:: C_Relation -> C_Relation -> IO CInt
foreign import ccall safe hsw_union
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_intersection
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_composition
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_restrict_domain
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_restrict_range
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_difference
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_cross_product
:: C_Relation -> C_Relation -> IO C_Relation
foreign import ccall safe hsw_gist
:: C_Relation -> C_Relation -> CInt -> IO C_Relation
foreign import ccall safe hsw_transitive_closure
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_domain
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_range
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_inverse
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_complement
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_deltas
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_approximate
:: C_Relation -> IO C_Relation
foreign import ccall safe hsw_relation_add_and
:: C_Relation -> IO C_Form
foreign import ccall safe hsw_relation_add_or
:: C_Relation -> IO C_Form
foreign import ccall safe hsw_relation_add_not
:: C_Relation -> IO C_Form
foreign import ccall safe hsw_relation_add_forall
:: C_Relation -> IO C_Quantifier
foreign import ccall safe hsw_relation_add_exists
:: C_Relation -> IO C_Quantifier
foreign import ccall safe hsw_relation_finalize
:: C_Relation -> IO ()
-- These functions take formula pointer arguments
foreign import ccall safe hsw_formula_add_and
:: C_Form -> IO C_Form
foreign import ccall safe hsw_formula_add_or
:: C_Form -> IO C_Form
foreign import ccall safe hsw_formula_add_not
:: C_Form -> IO C_Form
foreign import ccall safe hsw_formula_add_forall
:: C_Form -> IO C_Quantifier
foreign import ccall safe hsw_formula_add_exists
:: C_Form -> IO C_Quantifier
foreign import ccall safe hsw_formula_finalize
:: C_Form -> IO ()
foreign import ccall safe hsw_declaration_declare
:: C_Quantifier -> IO C_Var
-- If the argument is a C_And, the argument is returned;
-- otherwise, add_and is called
foreign import ccall safe hsw_formula_to_and
:: C_Form -> IO C_And
foreign import ccall safe hsw_add_constraint
:: C_And -> Bool -> CInt -> Ptr CInt -> Ptr C_Var -> CInt -> IO ()
foreign import ccall safe separate_relation_dimensions
:: Ptr C_Relation -> C_Relation -> IO ()
-- Look at the internal representation of a set
foreign import ccall safe hsw_query_dnf
:: C_Relation -> IO C_DNF_Iterator
foreign import ccall safe hsw_dnf_iterator_next
:: C_DNF_Iterator -> IO C_Conjunct
foreign import ccall safe hsw_dnf_iterator_free
:: C_DNF_Iterator -> IO ()
-- For inspecting Omega data structures
foreign import ccall safe hsw_get_conjunct_variables
:: C_Conjunct -> IO (C_Tuple_Iterator C_Var)
foreign import ccall safe hsw_tuple_iterator_next
:: (C_Tuple_Iterator (Ptr a)) -> IO (Ptr a)
foreign import ccall safe hsw_tuple_iterator_free
:: (C_Tuple_Iterator a) -> IO ()
foreign import ccall safe hsw_get_eqs
:: C_Conjunct -> IO C_EQ_Iterator
foreign import ccall safe hsw_eqs_next
:: C_EQ_Iterator -> IO C_EQ_Handle
foreign import ccall safe hsw_eqs_free
:: C_EQ_Iterator -> IO ()
foreign import ccall safe hsw_eq_handle_free
:: C_EQ_Handle -> IO ()
foreign import ccall safe hsw_get_geqs
:: C_Conjunct -> IO C_GEQ_Iterator
foreign import ccall safe hsw_geqs_next
:: C_GEQ_Iterator -> IO C_GEQ_Handle
foreign import ccall safe hsw_geqs_free
:: C_GEQ_Iterator -> IO ()
foreign import ccall safe hsw_geq_handle_free
:: C_GEQ_Handle -> IO ()
foreign import ccall safe hsw_constraint_get_const
:: Ptr a -> IO #{type coefficient_t}
foreign import ccall safe hsw_constraint_get_coefficients
:: Ptr a -> IO C_Constr_Vars_Iter
foreign import ccall safe hsw_constr_vars_next
:: Ptr Coefficient -> C_Constr_Vars_Iter -> IO Bool
foreign import ccall safe hsw_constr_vars_free
:: C_Constr_Vars_Iter -> IO ()
-- For debugging
foreign import ccall safe hsw_debug_print_eq
:: C_EQ_Handle -> IO ()
foreign import ccall safe hsw_debug_print_geq
:: C_GEQ_Handle -> IO ()
-------------------------------------------------------------------------------
-- Marshalling from Omega Library to Haskell
-- A C++ iterator
class Iterator i a | i -> a where next :: i -> IO a
instance Iterator C_DNF_Iterator C_Conjunct where
next = hsw_dnf_iterator_next
instance Iterator (C_Tuple_Iterator (Ptr a)) (Ptr a) where
next = hsw_tuple_iterator_next
instance Iterator C_EQ_Iterator C_EQ_Handle where
next = hsw_eqs_next
instance Iterator C_GEQ_Iterator C_GEQ_Handle where
next = hsw_geqs_next
-- Imperatively accumulate over the contents of the iterator
foreach :: Iterator i (Ptr b) => (a -> Ptr b -> IO a) -> a -> i -> IO a
foreach f x iter = visit x
where
visit x = do y <- next iter
if y == nullPtr
then return x
else visit =<< f x y
-- Iterate through each conjunct in a DNF clause
iterateDNF :: (a -> C_Conjunct -> IO a) -> a -> C_Relation -> IO a
iterateDNF f x rel = do
iter <- hsw_query_dnf rel
x' <- foreach f x iter
hsw_dnf_iterator_free iter
return x'
-- Iterate through the variables in a conjunct
iterateConjVars :: (a -> C_Var -> IO a) -> a -> C_Conjunct -> IO a
iterateConjVars f x conj = do
iter <- hsw_get_conjunct_variables conj
x' <- foreach f x iter
hsw_tuple_iterator_free iter
return x'
-- Iterate through the equality constraints in a conjunct
iterateEqs :: (a -> C_EQ_Handle -> IO a) -> a -> C_Conjunct -> IO a
iterateEqs f x conj = do
iter <- hsw_get_eqs conj
x' <- foreach wrapped_f x iter
hsw_eqs_free iter
return x'
where
-- This wrapper just makes sure the handle is freed after use
wrapped_f x eqHdl = do
x' <- f x eqHdl
hsw_eq_handle_free eqHdl
return x'
-- Iterate through the inequality constraints in a conjunct
iterateGeqs :: (a -> C_GEQ_Handle -> IO a) -> a -> C_Conjunct -> IO a
iterateGeqs f x conj = do
iter <- hsw_get_geqs conj
x' <- foreach wrapped_f x iter
hsw_geqs_free iter
return x'
where
-- This wrapper just makes sure the handle is freed after use
wrapped_f x geqHdl = do
x' <- f x geqHdl
hsw_geq_handle_free geqHdl
return x'
-- Read the coefficients from a Constraint
peekConstraintVars :: Constraint a => Ptr a -> IO [Coefficient]
peekConstraintVars cst = do
iter <- hsw_constraint_get_coefficients cst
-- Allocate temporary storage on the C side for some data
c_var_info <- ForeignAlloc.malloc
-- Traverse and pull out values
coeffs <- getCoefficients iter c_var_info []
-- Free the temporary storage and the iterator
ForeignAlloc.free c_var_info
hsw_constr_vars_free iter
return coeffs
where
getCoefficients iter c_var_info coeffs = do
-- Read one coefficient
ok <- hsw_constr_vars_next c_var_info iter
-- If it returned false, we're done
if not ok then return coeffs else do
coeff <- peek c_var_info
getCoefficients iter c_var_info (coeff:coeffs)
-- Read the list of input variables [N, N-1 ... 1].
-- This will probably crash if the number of variables is not specified
-- correctly.
peekInputVars, peekOutputVars, peekSetVars
:: CInt -> C_Relation -> IO [VarHandle]
peekInputVars n rel =
mapM (liftM VarHandle . hsw_input_var rel) [n, n - 1 .. 1]
peekOutputVars n rel =
mapM (liftM VarHandle . hsw_output_var rel) [n, n - 1 .. 1]
peekSetVars n rel =
mapM (liftM VarHandle . hsw_set_var rel) [n, n - 1 .. 1]
-- Helper function to read a constraint.
queryConstraint :: Constraint c =>
([Coefficient] -> Int -> a -> a) -- Accumulating function
-> a -- Initial value
-> Ptr c -- Constraint to query
-> IO a
queryConstraint f acc eq = do
coefficients <- peekConstraintVars eq
constant <- hsw_constraint_get_const eq
return $ f coefficients (fromIntegral constant) acc
-------------------------------------------------------------------------------
-- Exported interface
-- | Data types containing Presburger formulae.
class Presburger a where
-- | Extract the pointer from a formula
pPtr :: a -> ForeignPtr Relation
-- | Test whether two sets or relations have the same arity
sameArity :: a -> a -> Bool
-- | Convert a raw pointer to an OmegaSet or OmegaRel
fromPtr :: Ptr Relation -> IO a
-- Use a wrapped relation or set
withPresburger :: Presburger a => a -> (C_Relation -> IO b) -> IO b
withPresburger p = withForeignPtr (pPtr p)
-- Use two wrapped relations or sets
withPresburger2 :: (Presburger a, Presburger b) =>
a -> b -> (C_Relation -> C_Relation -> IO c) -> IO c
withPresburger2 p q f = withForeignPtr (pPtr p) $ \ptr ->
withForeignPtr (pPtr q) $ \ptr2 ->
f ptr ptr2
-- | A set of points in Z^n.
-- This is a wrapper around the Omega library's Relation type.
data OmegaSet = OmegaSet { sPtr :: {-# UNPACK #-} !(ForeignPtr Relation)
, sDom :: [VarHandle]
}
instance Show OmegaSet where
show rel = unsafePerformIO $ withPresburger rel $ \ptr -> do
-- Call hsw_relation_show to get a C string, then convert to String
cStr <- hsw_relation_show ptr
str <- peekCString cStr
free cStr
return str
instance Presburger OmegaSet where
pPtr = sPtr
sameArity s1 s2 =
length (sDom s1) == length (sDom s2)
fromPtr ptr = do
numVars <- hsw_num_set_vars ptr
varIDs <- peekSetVars numVars ptr
wrapOmegaSet ptr varIDs
-- Convert a raw set pointer to an OmegaSet
wrapOmegaSet :: C_Relation -> [VarHandle] -> IO OmegaSet
wrapOmegaSet ptr dom = do
foreignptr <- newForeignPtr ptr_to_free_relation ptr
return $! OmegaSet { sPtr = foreignptr
, sDom = dom
}
-- | Create an Omega set. The first parameter is the number of dimensions
-- the set inhabits. The second parameter builds a formula
-- defining the set's members. The set's members are those points
-- for which the formula evaluates to True.
newOmegaSet :: Int -- ^ Dimensionality of the space that the set
-- inhabits
-> ([VarHandle] -> Formula) -- ^ Set members
-> IO OmegaSet
newOmegaSet numVars init = do
rel <- hsw_new_set (fromIntegral numVars)
-- Look up the ID for each variable in the tuple. Variables are ordered
-- from last to first because the last variable is "innermost," has
-- de Bruijn index 1, and belongs at position 1 in the list.
freeVarIDs <- peekSetVars (fromIntegral numVars) rel
runFD (init freeVarIDs) rel
wrapOmegaSet rel freeVarIDs
-- | Inspect a set's low-level representation directly. This function
-- takes care of data structure traversal and relies on other routines to
-- interpret the data.
--
-- All three accumulating functions take the set variables as their
-- first parameter, and any existentially quantified variables as
-- their second parameter. The set variables are returned along with
-- a result value.
queryDNFSet :: ([VarHandle] -> [VarHandle] -> [Coefficient] -> Int -> a -> a)
-- ^ Accumulating function for equality constraints
-> a -- ^ Initial value for equality constraints
-> ([VarHandle] -> [VarHandle] -> [Coefficient] -> Int -> b -> b)
-- ^ Accumulating function for inequality constraints
-> b -- ^ Initial value for inequality constraints
-> ([VarHandle] -> [VarHandle] -> a -> b -> c -> c)
-- ^ Accumulating function for conjuncts
-> c -- ^ Initial value for conjuncts
-> OmegaSet -- ^ Set to query
-> IO ([VarHandle], c)
queryDNFSet readEq unitEq readGeq unitGeq readConj unitConj s = do
conjuncts <- withPresburger s $ iterateDNF doConjunct unitConj
return (sDom s, conjuncts)
where
doConjunct acc conjunct = do
-- Find existentially bound variables in this conjunct, which
-- Omega calls "wildcard variables"
wildcardVars <- iterateConjVars findWildcards [] conjunct
let wc = map VarHandle wildcardVars
-- For each EQ relation, get the relation
eqs <- iterateEqs (queryConstraint $ readEq (sDom s) wc)
unitEq conjunct
-- For each GE relation, get the relation
geqs <- iterateGeqs (queryConstraint $ readGeq (sDom s) wc)
unitGeq conjunct
return $ readConj (sDom s) wc eqs geqs acc
findWildcards acc var =
-- Is this an input variable?
case findIndex (var ==) (map unVarHandle $ sDom s)
of Just n -> return acc
Nothing -> -- Otherwise, assume it's a wildcard
-- FIXME: call into C to check the variable's kind
return $ var : acc
-- | A relation from points in a /domain/ Z^m
-- to points in a /range/ Z^n.
-- This is a wrapper around the Omega library's Relation type.
--
-- A relation can be considered as just a set of points in Z^(m+n).
-- However, many routines treat the domain and range differently.
data OmegaRel = OmegaRel { rPtr :: {-# UNPACK #-} !(ForeignPtr Relation)
, rDom :: [VarHandle]
, rRng :: [VarHandle]
}
instance Show OmegaRel where
show rel = unsafePerformIO $ withPresburger rel $ \ptr -> do
-- Call hsw_relation_show to get a C string, then convert to String
cStr <- hsw_relation_show ptr
str <- peekCString cStr
free cStr
return str
instance Presburger OmegaRel where
pPtr = rPtr
sameArity r1 r2 =
length (rDom r1) == length (rDom r2) &&
length (rRng r1) == length (rRng r2)
fromPtr ptr = do
numOutputs <- hsw_num_output_vars ptr
outputVarIDs <- peekOutputVars numOutputs ptr
numInputs <- hsw_num_input_vars ptr
inputVarIDs <- peekInputVars numInputs ptr
wrapOmegaRel ptr inputVarIDs outputVarIDs
-- Convert a raw relation pointer to an OmegaSet
wrapOmegaRel :: C_Relation -> [VarHandle] -> [VarHandle] -> IO OmegaRel
wrapOmegaRel ptr dom rng = do
foreignptr <- newForeignPtr ptr_to_free_relation ptr
return $! OmegaRel { rPtr = foreignptr
, rDom = dom
, rRng = rng }
-- | Create an Omega relation. The first two parameters are the number
-- of dimensions of the domain and range, respectively. The third parameter
-- builds a formula defining the relation. Two points are related iff the
-- formula evaluates to True on those points.
newOmegaRel :: Int -- ^ Dimensionality of the domain
-> Int -- ^ Dimensionality of the range
-> ([VarHandle] -> [VarHandle] -> Formula)
-- ^ The relation
-> IO OmegaRel
newOmegaRel numInputs numOutputs init = do
rel <- hsw_new_relation (fromIntegral numInputs) (fromIntegral numOutputs)
-- Look up the IDs for the input and output variables.
outputVarIds <- peekOutputVars (fromIntegral numOutputs) rel
inputVarIds <- peekInputVars (fromIntegral numInputs) rel
runFD (init inputVarIds outputVarIds) rel
wrapOmegaRel rel inputVarIds outputVarIds
-- | Inspect a relation's low-level representation directly. This function
-- takes care of data structure traversal and relies on other routines to
-- interpret the data.
--
-- All three accumulating functions take the relation's input and
-- output variables as their first and second parameters, respectively,
-- and any existentially quantified variables as
-- their second parameter. The relation's input and output variables are
-- returned along with a result value.
queryDNFRelation :: ([VarHandle] -> [VarHandle] -> [VarHandle] -> [Coefficient] -> Int -> a -> a)
-- ^ Accumulating function for equality constraints
-> a -- ^ Initial value for equality constraints
-> ([VarHandle] -> [VarHandle] -> [VarHandle] -> [Coefficient] -> Int -> b -> b)
-- ^ Accumulating function for inequality constraints
-> b -- ^ Initial value for inequality constraints
-> ([VarHandle] -> [VarHandle] -> [VarHandle] -> a -> b -> c -> c)
-- ^ Accumulating function for conjuncts
-> c -- ^ Initial value for conjuncts
-> OmegaRel -- ^ Relation to query
-> IO ([VarHandle], [VarHandle], c) -- ^ Input variables, output variables, and result
queryDNFRelation readEq unitEq readGeq unitGeq readConj unitConj r = do
conjuncts <- withPresburger r $ iterateDNF doConjunct unitConj
return (rDom r, rRng r, conjuncts)
where
doConjunct acc conjunct = do
-- Find existentially bound variables in this conjunct, which
-- Omega calls "wildcard variables"
wildcardVars <- iterateConjVars findWildcards [] conjunct
let wc = map VarHandle wildcardVars
-- For each EQ relation, get the relation
eqs <- iterateEqs (queryConstraint $ readEq (rDom r) (rRng r) wc)
unitEq conjunct
-- For each GE relation, get the relation
geqs <- iterateGeqs (queryConstraint $ readGeq (rDom r) (rRng r) wc)
unitGeq conjunct
return $ readConj (rDom r) (rRng r) wc eqs geqs acc
findWildcards acc var =
-- Is this an input variable?
case findIndex (var ==) (map unVarHandle $ rDom r ++ rRng r)
of Just n -> return acc
Nothing -> -- Otherwise, assume it's a wildcard
-- FIXME: call into C to check the variable's kind
return $ var : acc
-- | Get a list of relations, one per output variable, with the same
-- input and output dimensions as the original, but whose constraints
-- mention only one output variable and no existential constraints.
--
-- This function is needed to create a high-level Rel from a low-level
-- OmegaRel.
separateRelationDimensions :: OmegaRel -> IO [OmegaRel]
separateRelationDimensions r = do
-- Allocate an array to store outputs
allocaArray numOutputs $ \outputArray -> do
-- Call into C
withPresburger r $ \ptr -> separate_relation_dimensions outputArray ptr
-- Wrap each output as a relation
mapM readRelation =<< peekArray numOutputs outputArray
where
numInputs = length $ rDom r
numOutputs = length $ rRng r
readRelation rel = do
-- Look up the IDs for the input and output variables.
outputVarIds <- peekOutputVars (fromIntegral numOutputs) rel
inputVarIds <- peekInputVars (fromIntegral numInputs) rel
wrapOmegaRel rel inputVarIds outputVarIds
-------------------------------------------------------------------------------
-- Queries
-- | Determine a lower bound on whether the formula is satisfiable.
-- The lower bound is based on treating all UNKNOWN constraints as false.
lowerBoundSatisfiable :: Presburger a => a -> IO Bool
-- | Determine an upper bound on whether the formula is satisfiable.
-- The lower bound is based on treating all UNKNOWN constraints as false.
upperBoundSatisfiable :: Presburger a => a -> IO Bool
-- | Use simple, fast tests to decide whether the formula is a tautology.
obviousTautology :: Presburger a => a -> IO Bool
-- | True if the formula is a tautology.
definiteTautology :: Presburger a => a -> IO Bool
-- | True if the formula has no UNKNOWN constraints.
exact :: Presburger a => a -> IO Bool
-- | True if the formula has UNKNOWN constraints.
inexact :: Presburger a => a -> IO Bool
-- | True if the formula is UNKNOWN.
unknown :: Presburger a => a -> IO Bool
lowerBoundSatisfiable rel = withPresburger rel hsw_is_lower_bound_satisfiable
upperBoundSatisfiable rel = withPresburger rel hsw_is_upper_bound_satisfiable
obviousTautology rel = withPresburger rel hsw_is_obvious_tautology
definiteTautology rel = withPresburger rel hsw_is_definite_tautology
exact rel = withPresburger rel hsw_is_exact
inexact rel = withPresburger rel hsw_is_inexact
unknown rel = withPresburger rel hsw_is_unknown
-------------------------------------------------------------------------------
-- Creating new sets and relations from old ones
-- | Compute the upper bound of a set or relation by setting all UNKNOWN
-- constraints to true.
upperBound :: Presburger a => a -> IO a
upperBound rel = fromPtr =<< withPresburger rel hsw_upper_bound
-- | Compute the lower bound of a set or relation by setting all UNKNOWN
-- constraints to false.
lowerBound :: Presburger a => a -> IO a
lowerBound rel = fromPtr =<< withPresburger rel hsw_lower_bound
-- | Test whether two sets or relations are equal.
-- The sets or relations must have the same arity.
--
-- The answer is precise if both arguments are 'exact'.
-- If either argument is inexact, this function returns @False@.
equal :: Presburger a => a -> a -> IO Bool
equal rel1 rel2
| sameArity rel1 rel2 = do
eq <- withPresburger2 rel1 rel2 hsw_equal
return $! eq /= 0
| otherwise = error "equal: arguments have different arities"
-- | Compute the union of two sets or relations. The sets or relations
-- must have the same arity.
union :: Presburger a => a -> a -> IO a
union rel1 rel2
| sameArity rel1 rel2 =
fromPtr =<< withPresburger2 rel1 rel2 hsw_union
| otherwise = error "union: arguments have different arities"
-- | Compute the intersection of two sets or relations. The sets or relations
-- must have the same arity.
intersection :: Presburger a => a -> a -> IO a
intersection rel1 rel2
| sameArity rel1 rel2 =
fromPtr =<< withPresburger2 rel1 rel2 hsw_intersection
| otherwise = error "intersection: arguments have different arities"
-- | Compute the composition of two sets or relations. The
-- first relation's domain must be the same dimension as the second's range.
composition :: OmegaRel -> OmegaRel -> IO OmegaRel
composition rel1 rel2
| length (rDom rel1) == length (rRng rel2) =
fromPtr =<< withPresburger2 rel1 rel2 hsw_composition
| otherwise = error "composition: argument arities do not agree"
restrictDomain :: OmegaRel -> OmegaSet -> IO OmegaRel
restrictDomain rel1 set
| length (rDom rel1) == length (sDom set) =
fromPtr =<< withPresburger2 rel1 set hsw_restrict_domain
| otherwise = error "restrictDomain: argument arities do not agree"
restrictRange :: OmegaRel -> OmegaSet -> IO OmegaRel
restrictRange rel1 set
| length (rDom rel1) == length (sDom set) =
fromPtr =<< withPresburger2 rel1 set hsw_restrict_range
| otherwise = error "restrictRange: argument arities do not agree"
difference :: Presburger a => a -> a -> IO a
difference rel1 rel2
| sameArity rel1 rel2 =
fromPtr =<< withPresburger2 rel1 rel2 hsw_difference
| otherwise = error "difference: arguments have different arities"
crossProduct :: OmegaSet -> OmegaSet -> IO OmegaRel
crossProduct set1 set2 =
fromPtr =<< withPresburger2 set1 set2 hsw_cross_product
-- | Get the gist of a set or relation, given some background truth. The
-- gist operator uses heuristics to make a set or relation simpler, while
-- still retaining sufficient information to regenerate the original by
-- re-introducing the background truth. The sets or relations
-- must have the same arity.
--
-- Given @x@ computed by
--
-- > x <- intersection given =<< gist effort r given
--
-- we have @x == r@.
gist :: Presburger a => Effort -> a -> a -> IO a
gist effort rel given
| sameArity rel given =
withPresburger2 rel given $ \ptr ptrGiven ->
fromPtr =<< hsw_gist ptr ptrGiven (fromIntegral $ fromEnum effort)
| otherwise = error "gist: arguments have different arities"
-- | Get the transitive closure of a relation. In some cases, the transitive
-- closure cannot be computed exactly, in which case a lower bound is
-- returned.
transitiveClosure :: OmegaRel -> IO OmegaRel
transitiveClosure rel = fromPtr =<< withPresburger rel hsw_transitive_closure
-- | Get the domain of a relation.
domain :: OmegaRel -> IO OmegaSet
domain rel = fromPtr =<< withPresburger rel hsw_domain
-- | Get the range of a relation.
range :: OmegaRel -> IO OmegaSet
range rel = fromPtr =<< withPresburger rel hsw_range
-- | Get the inverse of a relation.
inverse :: OmegaRel -> IO OmegaRel
inverse rel = fromPtr =<< withPresburger rel hsw_inverse
-- | Get the complement of a set or relation.
complement :: Presburger a => a -> IO a
complement rel = fromPtr =<< withPresburger rel hsw_complement
-- | Get the deltas of a relation.
-- The relation's input dimensionality must be the same as its output
-- dimensionality.
deltas :: OmegaRel -> IO OmegaSet
deltas rel
| length (rDom rel) == length (rRng rel) =
fromPtr =<< withPresburger rel hsw_deltas
| otherwise =
error "deltas: relation has different input and output dimensionality"
-- | Approximate a set or relation by allowing all existentially quantified
-- variables to take on rational values. This allows these variables to be
-- eliminated from the formula.
approximate :: Presburger a => a -> IO a
approximate rel = fromPtr =<< withPresburger rel hsw_approximate
-------------------------------------------------------------------------------
-- Formulae
-- | A boolean-valued Presburger formula.
-- This is actually a function that builds a Presburger formula.
newtype Formula = FD {runFD :: forall a. Logical a => a -> IO ()}
-- | Logical conjunction (and).
conjunction :: [Formula] -> Formula
conjunction formulaDefs = FD $ \f -> do
newF <- add_and f
mapM_ (\func -> runFD func newF) formulaDefs
finalize newF
-- | Logical disjunction (or).
disjunction :: [Formula] -> Formula
disjunction formulaDefs = FD $ \f -> do
newF <- add_or f
mapM_ (\func -> runFD func newF) formulaDefs
finalize newF
-- | Logical negation.
negation :: Formula -> Formula
negation formulaDef = FD $ \f -> do
newF <- add_not f
runFD formulaDef newF
finalize newF
-- | Universal quantification. The 'VarHandle' parameter is the variable
-- bound by the quantifier.
qForall :: (VarHandle -> Formula) -> Formula
qForall makeBody = FD $ \f -> do
newFormula <- add_forall f
localVar <- hsw_declaration_declare newFormula
runFD (makeBody (VarHandle localVar)) newFormula
finalize newFormula
-- | Existential quantification. The 'VarHandle' parameter is the variable
-- bound by the quantifier.
qExists :: (VarHandle -> Formula) -> Formula
qExists makeBody = FD $ \f -> do
newFormula <- add_exists f
localVar <- hsw_declaration_declare newFormula
runFD (makeBody (VarHandle localVar)) newFormula
finalize newFormula
-- Add an equality or inequality constraint to a conjunction.
addConstraint :: Bool -> [Coefficient] -> Int -> C_And -> IO ()
addConstraint kind terms constant formula = do
let numTerms = length terms
numTermsCInt = fromIntegral numTerms
constantCInt = fromIntegral constant
coefficients = map (fromIntegral . coeffValue) terms
variables = map ((\(VarHandle h) -> h) . coeffVar) terms
-- Marshal the coefficients and variables to C as arrays
withArray coefficients $ \coeffPtr ->
withArray variables $ \varPtr ->
-- then, call code to set the constraint
hsw_add_constraint formula kind numTermsCInt coeffPtr varPtr constantCInt
-- | Construct an inequation of the form @a*x + b*y + ... + d >= 0@.
inequality :: [Coefficient] -> Int -> Formula
inequality terms constant = FD $ \formula ->
addConstraint False terms constant =<< convert_to_and formula
-- | Construct an equation of the form @a*x + b*y + ... + d = 0@.
equality :: [Coefficient] -> Int -> Formula
equality terms constant = FD $ \formula ->
addConstraint True terms constant =<< convert_to_and formula
-- | Truth.
true :: Formula
true = equality [] 0
-- | Falsity.
false :: Formula
false = equality [] 1
-- | A variable in a formula.
-- These data structures are owned by OmegaSet or OmegaRel instances,
-- which take care of allocation and deallocation.
newtype VarHandle = VarHandle { unVarHandle :: C_Var } deriving(Eq)
-- | An integer-valued term @n*v@ in a formula.
data Coefficient =
Coefficient { coeffVar :: {-# UNPACK #-} !VarHandle
, coeffValue :: {-# UNPACK #-} !Int
}
instance Show Coefficient where
show (Coefficient v n) =
"(" ++ show n ++ " * " ++ show (unVarHandle v) ++ ")"
instance Storable Coefficient where
sizeOf _ = #{size Variable_Info_struct}
alignment _ = #{alignof Variable_Info_struct}
peek p = do
var <- #{peek Variable_Info_struct, var} p :: IO C_Var
coef <- #{peek Variable_Info_struct, coef} p :: IO #{type coefficient_t}
return $ Coefficient { coeffVar = VarHandle var
, coeffValue = fromIntegral coef
}