509 lines
27 KiB
Haskell
509 lines
27 KiB
Haskell
{-
|
|
Tock: a compiler for parallel languages
|
|
Copyright (C) 2007 University of Kent
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation, either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
-}
|
|
|
|
module Omega where
|
|
|
|
import Control.Monad.State
|
|
import Data.Array.IArray
|
|
import Data.List
|
|
import qualified Data.Map as Map
|
|
import Data.Maybe
|
|
|
|
import Utils
|
|
|
|
|
|
type CoeffIndex = Int
|
|
type EqualityConstraintEquation = Array CoeffIndex Integer
|
|
type EqualityProblem = [EqualityConstraintEquation]
|
|
|
|
-- Assumed to be >= 0
|
|
type InequalityConstraintEquation = Array CoeffIndex Integer
|
|
type InequalityProblem = [InequalityConstraintEquation]
|
|
|
|
-- | As we proceed with eliminating variables from equations (with the possible
|
|
-- addition of one new variable), we perform substitutions like:
|
|
-- x_k = a_k'.x_k' + sum (i = 0 .. n without k) of a_i . x_i
|
|
-- where a_k' can be zero (no new variable is introduced).
|
|
--
|
|
-- We want to keep a record of these substitutions because then
|
|
-- if we end up with no remaining inequalities, we know the exact results
|
|
-- assigned to each of our variables
|
|
--
|
|
-- We need to know the substitution for x_k; that is,
|
|
-- we can map from x_k to the RHS of its substitution (including the resolved value for x_k').
|
|
-- We keep a map from the original variables into the current variables.
|
|
-- This does not require fractional coefficients.
|
|
type VariableMapping = Map.Map CoeffIndex EqualityConstraintEquation
|
|
|
|
-- | Given a maximum variable, produces a default mapping
|
|
defaultMapping :: Int -> VariableMapping
|
|
defaultMapping n = Map.fromList $ [ (i,array (0,n) [(j,if i == j then 1 else 0) | j <- [0 .. n]]) | i <- [0 .. n]]
|
|
|
|
-- | Adds a new variable to a map. The first parameter is (k,value of old x_k)
|
|
addToMapping :: (CoeffIndex,EqualityConstraintEquation) -> VariableMapping -> VariableMapping
|
|
addToMapping (k, subst) = addOldToNew
|
|
where
|
|
-- We want to update all the existing entries to be scaled according to the new substitution.
|
|
-- Additionally, iff there was no previous entry for k, we should add the new substitution.
|
|
--
|
|
-- In terms of maths, we want to replace cur_a_k . x_k with a value in terms of x_k':
|
|
-- cur_a_k . x_k = cur_a_k . (a_k'.x_k' + sum (i = 0 .. n without k) of a_i . x_i)
|
|
--
|
|
-- So we just add the substitution for x_k, scaled by cur_a_k.
|
|
--
|
|
-- As a more readable example, you currently have:
|
|
--
|
|
-- y = sigma + 3tau
|
|
--
|
|
-- You have a new subsitution:
|
|
--
|
|
-- tau = -2sigma - 1
|
|
--
|
|
-- Therefore you must update your reference for y by adding 3*tau:
|
|
--
|
|
-- y = sigma + (-6sigma - 3) = -5sigma - 3
|
|
addOldToNew :: Map.Map CoeffIndex EqualityConstraintEquation -> Map.Map CoeffIndex EqualityConstraintEquation
|
|
addOldToNew = (Map.insertWith ignoreNewVal k subst) . (Map.map updateSub)
|
|
where
|
|
ignoreNewVal = flip const
|
|
|
|
updateSub eq = arrayZipWith (+) (eq // [(k,0)]) $ scaleEq eq_k subst
|
|
where
|
|
eq_k = eq ! k
|
|
|
|
-- | Returns a mapping from i to constant values of x_i for the solutions of the equations.
|
|
-- This function should only be called if the VariableMapping comes from a problem that
|
|
-- definitely has constant solutions after all equalities have been eliminated.
|
|
-- If variables remain in the inequalities, you will get invalid\/odd answers from this function.
|
|
getCounterEqs :: VariableMapping -> Map.Map CoeffIndex Integer
|
|
getCounterEqs origToLast = Map.delete 0 $ Map.map expressAsConst origToLast
|
|
where
|
|
expressAsConst rhs = rhs ! 0
|
|
|
|
scaleEq :: (IArray a e, Ix i, Num e) => e -> a i e -> a i e
|
|
scaleEq n = amap (* n)
|
|
|
|
-- | Solves all the constraints in the Equality Problem (taking them to be == 0),
|
|
-- and transforms the InequalityProblems appropriately. It also records
|
|
-- a variable mapping so that we can feed back the final answer to the user
|
|
solveConstraints :: VariableMapping -> EqualityProblem -> InequalityProblem -> Maybe (VariableMapping, InequalityProblem)
|
|
solveConstraints vm p ineq
|
|
= normaliseEq p >>= (\p' -> execStateT (solve p') (vm,ineq))
|
|
where
|
|
-- | Normalises an equation by dividing all coefficients by their greatest common divisor.
|
|
-- If the unit coefficient (a_0) doesn't divide by this GCD, Nothing will be returned
|
|
-- (the constraints do not have an integer solution)
|
|
normaliseEq :: EqualityProblem -> Maybe EqualityProblem
|
|
normaliseEq = mapM normaliseEq' --Note the mapM; if any calls to normalise' fail, so will normalise
|
|
where
|
|
normaliseEq' :: EqualityConstraintEquation -> Maybe EqualityConstraintEquation
|
|
normaliseEq' e | g == 0 = Just e
|
|
| ((e ! 0) `mod` g) /= 0 = Nothing
|
|
| otherwise = Just $ amap (\x -> x `div` g) e
|
|
where g = mygcdList (tail $ elems e) -- g is the GCD of a_1 .. a_n (not a_0)
|
|
|
|
-- | Solves all equality problems in the given list.
|
|
-- Will either succeed (Just () in the Error\/Maybe monad) or fail (Nothing)
|
|
solve :: EqualityProblem -> StateT (VariableMapping, InequalityProblem) Maybe ()
|
|
solve [] = return ()
|
|
solve p = (solveUnits p >>* removeRedundant) >>= liftF checkFalsifiable >>= solveNext >>= solve
|
|
|
|
-- | Checks if any of the coefficients in the equation have an absolute value of 1.
|
|
-- Returns either Just <the first such coefficient> or Nothing (there are no such coefficients in the equation).
|
|
-- This function only looks at a_1 .. a_n. That is, a_0 is ignored.
|
|
checkForUnit :: EqualityConstraintEquation -> Maybe CoeffIndex
|
|
checkForUnit = listToMaybe . map fst . filter coeffAbsVal1 . tail . assocs
|
|
where
|
|
coeffAbsVal1 :: (a, Integer) -> Bool
|
|
coeffAbsVal1 (_,x) = (abs x) == 1
|
|
|
|
-- | Finds the first unit coefficient (|a_k| == 1) in a set of equality constraints.
|
|
-- Returns Nothing if there are no unit coefficients. Otherwise it returns
|
|
-- (Just (equation, indexOfUnitCoeff), otherEquations); that is, the specified equation is not
|
|
-- present in the list of equations.
|
|
findFirstUnit :: EqualityProblem -> (Maybe (EqualityConstraintEquation,CoeffIndex),EqualityProblem)
|
|
findFirstUnit [] = (Nothing,[])
|
|
findFirstUnit (e:es) = case checkForUnit e of
|
|
Just ci -> (Just (e,ci),es)
|
|
Nothing -> let (me,es') = findFirstUnit es in (me,e:es')
|
|
|
|
|
|
-- | Substitutes a value for x_k into an equation. Given k, the value for x_k in terms
|
|
-- of coefficients of other variables (let's call it x_k_val), it subsitutes this into
|
|
-- all the equations in the list by adding x_k_val (scaled by a_k) to each equation and
|
|
-- then zeroing out the a_k value. Note that the (x_k_val ! k) value will be ignored;
|
|
-- it should be zero, in any case (otherwise x_k would be defined in terms of itself!).
|
|
substIn :: CoeffIndex -> Array CoeffIndex Integer -> (VariableMapping, EqualityProblem) -> (VariableMapping, EqualityProblem)
|
|
substIn k x_k_val = transformPair (addToMapping (k,x_k_val)) (map substIn')
|
|
where
|
|
substIn' eq = (arrayZipWith (+) eq scaled_x_k_val) // [(k,0)]
|
|
where
|
|
scaled_x_k_val = amap (* (eq ! k)) x_k_val
|
|
|
|
-- | Solves (i.e. removes by substitution) all unit coefficients in the given list of equations.
|
|
solveUnits :: EqualityProblem -> StateT (VariableMapping, InequalityProblem) Maybe EqualityProblem
|
|
solveUnits p
|
|
= case findFirstUnit p of
|
|
(Nothing,p') -> return p' -- p' should equal p anyway
|
|
(Just (eq,ind),p') -> modify change >> change' p' >>= liftF normaliseEq >>= solveUnits
|
|
where
|
|
change = substIn ind (arrayMapWithIndex (modifyOthersZeroSpecific ind) eq)
|
|
change' p = do (mp,ineq) <- get
|
|
let (_,p') = change (undefined,p)
|
|
put (mp,ineq)
|
|
return p'
|
|
origVal = eq ! ind
|
|
|
|
-- Zeroes a specific coefficient, modifies the others as follows:
|
|
-- If the coefficient of x_k is 1, we need to negate the other coefficients
|
|
-- to get its definition. However, if the coefficient is -1, we don't need to
|
|
-- do this. For example, consider 2 + 3x_1 + x_2 - 4x_3 = 0. In this case
|
|
-- x_2 = -2 - 3x_1 + 4x_3; the negation of the original equation (ignoring x_2).
|
|
-- If however, it was 2 + 3x_1 - x_2 - 4x_3 = 0 then x_2 = 2 + 3x_1 - 4x_3;
|
|
-- that is, identical to the original equation if we ignore x_2.
|
|
modifyOthersZeroSpecific :: CoeffIndex -> (CoeffIndex -> Integer -> Integer)
|
|
modifyOthersZeroSpecific match ind
|
|
| match == ind = const 0 -- The specific value to zero out
|
|
| origVal == 1 = negate -- Original coeff was 1; negate
|
|
| otherwise = id -- Original coeff was -1; don't do anything
|
|
|
|
-- | Finds the coefficient with the smallest absolute value of a_1 .. a_n (i.e. not a_0)
|
|
-- that is non-zero (i.e. zero coefficients are ignored).
|
|
findSmallestAbsCoeff :: EqualityConstraintEquation -> CoeffIndex
|
|
findSmallestAbsCoeff = fst . minimumBy cmpAbsSnd . filter ((/= 0) . snd) . tail . assocs
|
|
where
|
|
cmpAbsSnd :: (a,Integer) -> (a,Integer) -> Ordering
|
|
cmpAbsSnd (_,x) (_,y) = compare (abs x) (abs y)
|
|
|
|
-- | Solves the next equality and returns the new set of equalities.
|
|
solveNext :: EqualityProblem -> StateT (VariableMapping, InequalityProblem) Maybe EqualityProblem
|
|
solveNext [] = return []
|
|
solveNext (e:es) = -- We transform the kth variable into sigma, effectively
|
|
-- So once we have x_k = ... (in terms of sigma) we add a_k * RHS
|
|
-- to all other equations, AFTER zeroing the a_k coefficient (so
|
|
-- that the multiple of sigma is added on properly)
|
|
modify change >> change' (e:es) >>= liftF normaliseEq
|
|
where
|
|
change' p = do (mp,ineq) <- get
|
|
let (_,p') = change (undefined,p)
|
|
put (mp,ineq)
|
|
return p'
|
|
change = transformPair (addToMapping (k,x_k_eq)) (map alterEquation)
|
|
|
|
-- | Adds a scaled version of x_k_eq onto the current equation, after zeroing out
|
|
-- the a_k coefficient in the current equation.
|
|
alterEquation :: EqualityConstraintEquation -> EqualityConstraintEquation
|
|
alterEquation eq = arrayZipWith (+) (eq // [(k,0)]) (amap (\x -> x * (eq ! k)) x_k_eq)
|
|
|
|
k = findSmallestAbsCoeff e
|
|
a_k = e ! k
|
|
m = (abs a_k) + 1
|
|
sign_a_k = signum a_k
|
|
x_k_eq = amap (\a_i -> sign_a_k * (a_i `mymod` m)) e // [(k,(- sign_a_k) * m)]
|
|
|
|
-- I think this is probably equivalent to mod, but let's follow the maths:
|
|
mymod :: Integer -> Integer -> Integer
|
|
mymod x y = x - (y * (floordivplushalf x y))
|
|
|
|
-- This is floor (x/y + 1/2). Probably a way to do it without reverting to float arithmetic:
|
|
floordivplushalf :: Integer -> Integer -> Integer
|
|
floordivplushalf x y = floor ((fromInteger x / fromInteger y) + (0.5 :: Double))
|
|
|
|
-- Removes all equations where the coefficients are all zero
|
|
removeRedundant :: EqualityProblem -> EqualityProblem
|
|
removeRedundant = mapMaybe (boolToMaybe (not . isRedundant))
|
|
where
|
|
isRedundant :: EqualityConstraintEquation -> Bool
|
|
isRedundant = all (== 0) . elems
|
|
|
|
|
|
-- Searches for all equations where only the a_0 coefficient is non-zero; this means the equation cannot be satisfied
|
|
checkFalsifiable :: EqualityProblem -> Maybe EqualityProblem
|
|
checkFalsifiable = boolToMaybe (not . any checkFalsifiable')
|
|
where
|
|
-- | Returns True if the equation is definitely unsatisfiable
|
|
checkFalsifiable' :: EqualityConstraintEquation -> Bool
|
|
checkFalsifiable' e = (e ! 0 /= 0) && (all (== 0) . tail . elems) e
|
|
|
|
|
|
mygcd :: Integer -> Integer -> Integer
|
|
mygcd 0 0 = 0
|
|
mygcd x y = gcd x y
|
|
|
|
mygcdList :: [Integer] -> Integer
|
|
mygcdList [] = 0
|
|
mygcdList [x] = abs x
|
|
mygcdList (x:xs) = foldl mygcd x xs
|
|
|
|
-- | Prunes the inequalities. It does what is described in section 2.3 of Pugh's ACM paper;
|
|
-- it removes redundant inequalities, fails (evaluates to Nothing) if it finds a contradiction
|
|
-- and turns any 2x + y <= 4, 2x + y >= 4 pairs into equalities. The list of such equalities
|
|
-- (which may well be an empty list) and the remaining inequalities is returned.
|
|
-- As an additional step not specified in the paper, equations with no variables in them are checked
|
|
-- for consistency. That is, all equations c >= 0 (where c is constant) are checked to
|
|
-- ensure c is indeed >= 0, and those equations are removed. Also, all equations are normalised
|
|
-- according to the procedure outlined in the slides.
|
|
pruneIneq :: InequalityProblem -> Maybe (EqualityProblem, InequalityProblem)
|
|
pruneIneq ineq = do let (opps,others) = splitEither $ groupOpposites $ map pruneGroup groupedIneq
|
|
(opps', eq) <- mapM checkOpposite opps >>* splitEither
|
|
checked <- mapM checkConstantEq (concat opps' ++ others) >>* catMaybes
|
|
return (eq, checked)
|
|
where
|
|
groupedIneq = groupBy (\x y -> EQ == coeffSort x y) $ sortBy coeffSort $ map normaliseIneq ineq
|
|
|
|
normaliseIneq :: InequalityConstraintEquation -> InequalityConstraintEquation
|
|
normaliseIneq ineq | g > 1 = arrayMapWithIndex norm ineq
|
|
| otherwise = ineq
|
|
where
|
|
norm ind val | ind == 0 = normaliseUnits val
|
|
| otherwise = val `div` g
|
|
|
|
g = mygcdList $ tail $ elems ineq
|
|
-- I think div would do here, because g will always be positive, but
|
|
-- I feel safer using the mathematical description:
|
|
normaliseUnits a_0 = floor $ (fromInteger a_0 :: Double) / (fromInteger g)
|
|
|
|
coeffSort :: InequalityConstraintEquation -> InequalityConstraintEquation -> Ordering
|
|
coeffSort x y = compare (tail $ elems x) (tail $ elems y)
|
|
|
|
-- | Takes in a group of inequalities with identical a_1 .. a_n coefficients
|
|
-- and returns the equation with the smallest unit coefficient. Consider the standard equation:
|
|
-- a_1.x_1 + a_2.x_2 .. a_n.x_n >= -a_0. We want one equation with the maximum value of -a_0
|
|
-- (this will be the strongest equation), which is therefore the minimum value of a_0.
|
|
-- This therefore automatically removes duplicate and redundant equations.
|
|
pruneGroup :: [InequalityConstraintEquation] -> InequalityConstraintEquation
|
|
pruneGroup = minimumBy (\x y -> compare (x ! 0) (y ! 0))
|
|
|
|
-- | Groups all equations with their opposites, if found. Returns either a pair
|
|
-- or a singleton. O(N^2), but there shouldn't be that many inequalities to process (<= 10, I expect).
|
|
-- Assumes equations have already been pruned, and that therefore for every unique a_1 .. a_n
|
|
-- set, there is only one equation.
|
|
groupOpposites :: InequalityProblem -> [Either (InequalityConstraintEquation,InequalityConstraintEquation) InequalityConstraintEquation]
|
|
groupOpposites [] = []
|
|
groupOpposites (e:es) = case findOpposite e es of
|
|
Just (opp,rest) -> (Left (e,opp)) : (groupOpposites rest)
|
|
Nothing -> (Right e) : (groupOpposites es)
|
|
|
|
findOpposite :: InequalityConstraintEquation -> [InequalityConstraintEquation] -> Maybe (InequalityConstraintEquation,[InequalityConstraintEquation])
|
|
findOpposite _ [] = Nothing
|
|
findOpposite target (e:es) | negTarget == (tail $ elems e) = Just (e,es)
|
|
| otherwise = case findOpposite target es of
|
|
Just (opp,rest) -> Just (opp,e:rest)
|
|
Nothing -> Nothing
|
|
where
|
|
negTarget = map negate $ tail $ elems target
|
|
|
|
-- Checks if two "opposite" constraints are inconsistent. If they are inconsistent, Nothing is returned.
|
|
-- If they could be consistent, either the resulting equality or the inequalities are returned
|
|
--
|
|
-- If the equations are opposite, then setting z = sum (1 .. n) of a_n . x_n, the two equations must be:
|
|
-- b + z >= 0
|
|
-- c - z >= 0
|
|
-- The choice of which equation is which is arbitrary.
|
|
--
|
|
-- It is easily seen that adding the two equations gives:
|
|
--
|
|
-- (b + c) >= 0
|
|
--
|
|
-- Therefore if (b + c) < 0, the equations are inconsistent.
|
|
-- If (b + c) = 0, we can substitute into the original equations b = -c:
|
|
-- -c + z >= 0
|
|
-- c - z >= 0
|
|
-- Rearranging both gives:
|
|
-- z >= c
|
|
-- z <= c
|
|
-- This implies c = z. Therefore we can take either of the original inequalities
|
|
-- and treat them directly as equality (c - z = 0, and b + z = 0)
|
|
-- If (b + c) > 0 then the equations are consistent but we cannot do anything new with them
|
|
checkOpposite :: (InequalityConstraintEquation,InequalityConstraintEquation) ->
|
|
Maybe (Either [InequalityConstraintEquation] EqualityConstraintEquation)
|
|
checkOpposite (x,y) | (x ! 0) + (y ! 0) < 0 = Nothing
|
|
| (x ! 0) + (y ! 0) == 0 = Just $ Right x
|
|
| otherwise = Just $ Left [x,y]
|
|
|
|
-- The type of this function is quite confusing. We want to use in the Maybe monad, so
|
|
-- the outer type indicates error; Nothing is an error. Just x indicates non-failure,
|
|
-- but x may either be Just y (keep the equation) or Nothing (remove it). So the three
|
|
-- possible returns are:
|
|
-- * Nothing: Equation inconsistent
|
|
-- * Just Nothing: Equation redundant
|
|
-- * Just (Just e) : Keep equation.
|
|
checkConstantEq :: InequalityConstraintEquation -> Maybe (Maybe InequalityConstraintEquation)
|
|
checkConstantEq eq | all (== 0) (tail $ elems eq) = if (eq ! 0) >= 0 then Just Nothing else Nothing
|
|
| otherwise = Just $ Just eq
|
|
|
|
-- | Returns the number of variables (of x_1 .. x_n; x_0 is not counted)
|
|
-- that have non-zero coefficients in the given inequality problems.
|
|
numVariables :: InequalityProblem -> Int
|
|
numVariables ineq = length (nub $ concatMap findVars ineq)
|
|
where
|
|
findVars :: InequalityConstraintEquation -> [CoeffIndex]
|
|
findVars = map fst . filter ((/= 0) . snd) . tail . assocs
|
|
|
|
-- | Adds a constant value to an equation:
|
|
addConstant :: Integer -> Array Int Integer -> Array Int Integer
|
|
addConstant x e = e // [(0,(e ! 0) + x)]
|
|
|
|
-- | Eliminating the inequalities works as follows:
|
|
--
|
|
-- Rearrange (and normalise) equations for a particular variable x_k to eliminate so that
|
|
-- a_k is always positive and you have:
|
|
-- A: a_Ak . x_k <= sum (i is 0 to n, without k) a_Ai . x_i
|
|
-- B: a_Bk . x_k >= sum (i is 0 to n, without k) a_Bi . x_i
|
|
-- C: equations where a_k is zero.
|
|
--
|
|
-- Determine if there is an integer solution for x_k:
|
|
--
|
|
-- If it is an inexact projection, the function recurses into both the real and dark shadow.
|
|
-- If necessary, it does brute-forcing.
|
|
--
|
|
--
|
|
-- Real shadow:
|
|
--
|
|
-- Form lots of new equations:
|
|
-- Given a_Ak . x_k <= RHS(A)
|
|
-- a_Bk . x_k >= RHS(B)
|
|
-- We can get (since a_Ak and a_bk are positive):
|
|
-- a_Ak . A_Bk . x_k <= A_Bk . RHS(A)
|
|
-- a_Ak . A_Bk . x_k >= A_Ak . RHS(B)
|
|
-- For every combination of the RHS(A) and RHS(B) generate an inequality: a_Bk . RHS(A) - a_Ak . RHS(B) >=0
|
|
-- Add these new equations to the set C, and iterate
|
|
--
|
|
-- Dark shadow:
|
|
--
|
|
-- Form lots of new equations:
|
|
-- Given a_Ak . x_k <= RHS(A)
|
|
-- a_Bk . x_k >= RHS(B)
|
|
-- We need to form the equations:
|
|
-- a_Bk . RHS(A) - a_Ak . RHS(B) - (a_Ak - 1)(a_Bk - 1) >= 0
|
|
--
|
|
-- That is, the dark shadow is the same as the real shadow but with the constant subtracted
|
|
|
|
fmElimination :: VariableMapping -> InequalityProblem -> Maybe VariableMapping
|
|
fmElimination vm ineq = fmElimination' vm (presentItems ineq) ineq
|
|
where
|
|
-- Finds all variables that have at least one non-zero coefficient in the equation set.
|
|
-- a_0 is ignored; 0 will never be in the returned list
|
|
presentItems :: InequalityProblem -> [Int]
|
|
presentItems = nub . map fst . filter ((/= 0) . snd) . concatMap (tail . assocs)
|
|
|
|
-- The real body of the function:
|
|
fmElimination' :: VariableMapping -> [Int] -> InequalityProblem -> Maybe VariableMapping
|
|
fmElimination' vm [] ineqs = pruneAndHandleIneq (vm,ineqs) >>* fst
|
|
-- We have to prune the ineqs when they have no variables to
|
|
-- ensure none are inconsistent
|
|
fmElimination' vm indexes@(ix:ixs) ineqs
|
|
= do (vm',ineqsPruned) <- pruneAndHandleIneq (vm,ineqs)
|
|
case listToMaybe $ filter (flip isExactProjection ineqsPruned) indexes of
|
|
-- If there is an exact projection (real shadow = dark shadow), eliminate that
|
|
-- variable, and therefore just recurse to process this shadow:
|
|
Just ex -> fmElimination' vm' (indexes \\ [ex]) (getRealShadow ex ineqsPruned)
|
|
Nothing ->
|
|
-- Otherwise, check the real shadow first:
|
|
case fmElimination' vm' ixs (getRealShadow ix ineqsPruned) of
|
|
-- No solution to the real shadow means no solution to the problem:
|
|
Nothing -> Nothing
|
|
-- Check the dark shadow:
|
|
Just vm'' -> case fmElimination' vm'' ixs (getDarkShadow ix ineqsPruned) of
|
|
-- Solution to the dark shadow means there is a solution to the problem:
|
|
Just vm''' -> return vm'''
|
|
-- Solution to real but not to dark; we must brute force the problem.
|
|
-- If we find any solutions during the brute-forcing, we have our solution.
|
|
-- Otherwise there is no solution
|
|
Nothing -> listToMaybe $ mapMaybe (uncurry $ solveProblem' vm'') $ getBruteForceProblems ix ineqsPruned
|
|
|
|
-- Prunes the inequalities. If any equalities arise, those are processed, so
|
|
-- that the return is only inequalities
|
|
pruneAndHandleIneq :: (VariableMapping, InequalityProblem) -> Maybe (VariableMapping, InequalityProblem)
|
|
pruneAndHandleIneq (vm,ineq)
|
|
= do (eq,ineq') <- pruneIneq ineq
|
|
if null eq then return (vm,ineq') else solveConstraints vm eq ineq'
|
|
|
|
|
|
-- We need to partition the related equations into sets A,B and C.
|
|
-- C is straightforward (a_k is zero).
|
|
-- In set B, a_k > 0, and "RHS(B)" (as described above) is the negation of the other
|
|
-- coefficients. Therefore "-RHS(B)" is the other coefficients as-is.
|
|
-- In set A, a_k < 0, and "RHS(A)" (as described above) is the other coefficients, untouched
|
|
-- So we simply zero out a_k, and return the rest, associated with the absolute value of a_k.
|
|
splitBounds :: Int -> InequalityProblem -> ([(Integer, InequalityConstraintEquation)], [(Integer,InequalityConstraintEquation)], [InequalityConstraintEquation])
|
|
splitBounds k = (\(x,y,z) -> (concat x, concat y, concat z)) . unzip3 . map partition'
|
|
where
|
|
partition' e | a_k == 0 = ([],[],[e])
|
|
| a_k < 0 = ([(abs a_k, e // [(k,0)])],[],[])
|
|
| a_k > 0 = ([],[(abs a_k, e // [(k,0)])],[])
|
|
where
|
|
a_k = e ! k
|
|
|
|
-- Gets the real shadow of a given variable. The real shadow, for all possible
|
|
-- upper bounds (ax <= alpha) and lower bounds (beta <= bx) is the inequality
|
|
-- (a beta <= b alpha), or (a beta - b alpha >= 0).
|
|
getRealShadow :: Int -> InequalityProblem -> InequalityProblem
|
|
getRealShadow k ineqs = eqC ++ map (uncurry pairIneqs) (product2 (eqA,eqB))
|
|
where
|
|
(eqA,eqB,eqC) = splitBounds k ineqs
|
|
|
|
pairIneqs :: (Integer, InequalityConstraintEquation) -> (Integer, InequalityConstraintEquation) -> InequalityConstraintEquation
|
|
pairIneqs (x,ex) (y,ey) = arrayZipWith (+) (amap (* y) ex) (amap (* x) ey)
|
|
|
|
-- Gets the dark shadow of a given variable. The dark shadow, for possible
|
|
-- upper bounds (ax <= alpha) and lower bounds (beta <= bx) is the inequality
|
|
-- (a beta - b alpha - (a - 1)(b - 1) )
|
|
getDarkShadow :: Int -> InequalityProblem -> InequalityProblem
|
|
getDarkShadow k ineqs = eqC ++ map (uncurry pairIneqsDark) (product2 (eqA,eqB))
|
|
where
|
|
(eqA,eqB,eqC) = splitBounds k ineqs
|
|
|
|
pairIneqsDark :: (Integer, InequalityConstraintEquation) -> (Integer, InequalityConstraintEquation) -> InequalityConstraintEquation
|
|
pairIneqsDark (x,ex) (y,ey) = addConstant (-1*(y-1)*(x-1)) (arrayZipWith (+) (amap (* y) ex) (amap (* x) ey))
|
|
|
|
-- Checks if eliminating the specified variable would yield an exact projection (real shadow = dark shadow):
|
|
-- This will be the case if the coefficient on all lower bounds or on all upper bounds is 1. We check
|
|
-- this by making sure either all the positive coefficients (lower bounds) are 1 or all the negative
|
|
-- coefficients (upper bounds) are -1.
|
|
isExactProjection :: Int -> InequalityProblem -> Bool
|
|
isExactProjection n ineqs = (all (== 1) $ pos n ineqs) || (all (== (-1)) $ neg n ineqs)
|
|
where
|
|
pos :: Int -> InequalityProblem -> [Integer]
|
|
pos n ineqs = filter (> 0) $ map (! n) ineqs
|
|
neg :: Int -> InequalityProblem -> [Integer]
|
|
neg n ineqs = filter (< 0) $ map (! n) ineqs
|
|
|
|
-- Gets the brute force equality/inequality sets as described in the paper and the slides.
|
|
getBruteForceProblems :: Int -> InequalityProblem -> [(EqualityProblem,InequalityProblem)]
|
|
getBruteForceProblems k ineqs = concatMap setLowerBound eqB
|
|
where
|
|
(eqA,eqB,_) = splitBounds k ineqs
|
|
|
|
largestUpperA = maximum $ map fst eqA
|
|
|
|
setLowerBound (b,beta) = map (\i -> ([addConstant (-i) (beta // [(k,b)])],ineqs)) [0 .. max]
|
|
where
|
|
max = ((largestUpperA * b) - largestUpperA - b) `div` largestUpperA
|
|
|
|
-- | Like solveProblem but allows a custom variable mapping to be used.
|
|
solveProblem' :: VariableMapping -> EqualityProblem -> InequalityProblem -> Maybe VariableMapping
|
|
solveProblem' vm eq ineq = solveConstraints vm eq ineq >>= uncurry fmElimination
|
|
|
|
-- | Solves a problem using the full Omega Test, and a default variable mapping
|
|
solveProblem :: EqualityProblem -> InequalityProblem -> Maybe VariableMapping
|
|
solveProblem eq ineq = solveProblem' (defaultMapping maxVar) eq ineq
|
|
where
|
|
maxVar = if null eq && null ineq then 0 else
|
|
if null eq then snd $ bounds $ head ineq else snd $ bounds $ head eq
|