packages feed

limp 0.3.0.0 → 0.3.1.0

raw patch · 18 files changed

+509/−20 lines, 18 filesdep +QuickCheckdep +limpdep +tasty

Dependencies added: QuickCheck, limp, tasty, tasty-quickcheck, tasty-th

Files

limp.cabal view
@@ -1,5 +1,5 @@ name:                limp-version:             0.3.0.0+version:             0.3.1.0 synopsis:            representation of Integer Linear Programs description:         so far, this package just provides two representations for linear programs: "Numeric.Limp.Program", which is what I expect end-users to use, and                      "Numeric.Limp.Canon", which is simpler, but would be less nice for writing linear programs.@@ -9,7 +9,7 @@ license-file:        LICENSE author:              Amos Robinson maintainer:          amos.robinson@gmail.com-category:            numeric+category:            Numeric build-type:          Simple cabal-version:       >=1.10 homepage:            https://github.com/amosr/limp@@ -36,7 +36,13 @@         Numeric.Limp.Canon.Constraint         Numeric.Limp.Canon.Convert         Numeric.Limp.Canon.Program+        Numeric.Limp.Canon.Pretty         Numeric.Limp.Canon+        Numeric.Limp.Canon.Analyse.Constants+        Numeric.Limp.Canon.Simplify.Bounder+        Numeric.Limp.Canon.Simplify.Crunch+        Numeric.Limp.Canon.Simplify.Subst+        Numeric.Limp.Canon.Simplify    -- other-modules:          build-depends:@@ -44,6 +50,23 @@         containers  == 0.5.*    ghc-options: -Wall -fno-warn-orphans+  default-language: Haskell2010+  default-extensions:       TemplateHaskell TypeFamilies FlexibleContexts GeneralizedNewtypeDeriving DataKinds GADTs RankNTypes StandaloneDeriving FlexibleInstances+++test-suite test+  type: exitcode-stdio-1.0+  main-is: Main.hs+  hs-source-dirs: tests+  build-depends:+        base        < 5,+        containers  == 0.5.*,+        tasty       == 0.10.*,+        tasty-th    == 0.1.*,+        tasty-quickcheck == 0.8.*,+        QuickCheck       == 2.7.*,+        limp+   default-language: Haskell2010   default-extensions:       TemplateHaskell TypeFamilies FlexibleContexts GeneralizedNewtypeDeriving DataKinds GADTs RankNTypes StandaloneDeriving FlexibleInstances 
+ src/Numeric/Limp/Canon/Analyse/Constants.hs view
@@ -0,0 +1,41 @@+-- | Analyse a program to find all constants+module Numeric.Limp.Canon.Analyse.Constants where+import Numeric.Limp.Canon.Program+import Numeric.Limp.Rep++import qualified Data.Map as M+++-- | Find the constants in a program, only by looking at the bounds with lo==up.+-- (See "Numeric.Limp.Canon.Simplify.Stride" to convert constraints to bounds)+constantsProgram :: (Ord z, Ord r, Rep c) => Program z r c -> Assignment z r c+constantsProgram p+ = mkAss $ concatMap eq $ M.toList $ _bounds p+ where++  eq (var, (Just lo, Just up))+   | lo == up+   = [(var, lo)]++  eq _+   = []++  mkAss ms+   = Assignment+      (M.fromList $ concatMap tkLeft ms)+      (M.fromList $ concatMap tkRight ms)++  tkLeft (Left z, v)+   -- Wow! What if the bounds aren't integral?+   -- Well, I guess the ILP solver will eventually figure out it's infeasible.+   -- Maybe it would be nice to trigger that error here.+   | v == (fromZ $ truncate v)+   = [(z, truncate v)]+  tkLeft _+   = []++  tkRight (Right r, v)+   = [(r, v)]+  tkRight _+   = []+
src/Numeric/Limp/Canon/Constraint.hs view
@@ -16,6 +16,7 @@  -- In order to be meaningful, at least one of lower or upper bound should be @Just@.  = C1 (Maybe (R c)) (Linear z r c) (Maybe (R c)) + -- | Check whether an assignment satisfies the constraint check :: (Rep c, Ord z, Ord r) => Assignment z r c -> Constraint z r c -> Bool check a (Constraint cs) = all go cs
src/Numeric/Limp/Canon/Convert.hs view
@@ -110,28 +110,14 @@    = constraint $ P._constraints p    bnds-   = M.fromListWith merge+   = M.fromListWith mergeBounds    $ map extract    $ P._bounds p -  merge (l1,u1) (l2,u2)-   = ( mmaybe max l1 l2-     , mmaybe min u1 u2 )--  mmaybe f a b-   = case (a,b) of-     (Nothing, Nothing)-      -> Nothing-     (Nothing, Just b')-      -> Just $ b'-     (Just a', Nothing)-      -> Just $ a'-     (Just a', Just b')-      -> Just $ f a' b'-   extract :: Rep c => P.Bounds z r c -> (Either z r, (Maybe (R c), Maybe (R c)))   extract (P.BoundZ (l,k,u))    = (Left k, (fromZ <$> l, fromZ <$> u))   extract (P.BoundR (l,k,u))    = (Right k, (l,u))+ 
src/Numeric/Limp/Canon/Linear.hs view
@@ -10,12 +10,15 @@ data Linear z r c  = Linear (M.Map (Either z r) (R c)) +deriving instance (Ord z, Ord r, Rep c) => Eq (Linear z r c)+deriving instance (Ord z, Ord r, Rep c) => Ord (Linear z r c)+ -- | Create linear function from list of variables and coefficients-mkLinear :: (Ord z, Ord r)+mkLinear :: (Ord z, Ord r, Rep c)          => [(Either z r, R c)]          -> Linear z r c mkLinear zrs- = Linear (M.fromList zrs)+ = Linear (M.fromListWith (+) zrs)   -- | Evaluate linear function with given assignment
+ src/Numeric/Limp/Canon/Pretty.hs view
@@ -0,0 +1,84 @@+module Numeric.Limp.Canon.Pretty where+import Numeric.Limp.Canon.Constraint+import Numeric.Limp.Canon.Linear+import Numeric.Limp.Canon.Program+import Numeric.Limp.Rep++import qualified Data.Map as M+import qualified Data.Set as S+import Data.Either++instance (Show (Z c), Show (R c), Rep c, Show z, Show r, Ord z, Ord r) => Show (Program z r c) where+ show = ppr show show++ppr :: (Show (Z c), Show (R c), Rep c, Show z, Show r, Ord z, Ord r) => (z -> String) -> (r -> String) -> Program z r c -> String+ppr pZ pR p+ = unlines+ [ "Minimize"+ , indent $ pprL $ _objective p+ , "Subject to"+ , pprCs $ _constraints p+ , "Bounds"+ , pprBs $ _bounds p+ , "Generals"+ , pprGs $ varsOfProgram p ]++ where+  indent = ("\t"++)++  pprV v+   = filter (/=' ') $ either pZ pR v++  pprL (Linear m)+   = pprLf+   $ M.toList m++  pprLf xs@((_,c): _)+   | c < 0+   = "-" ++ pprLfs xs+  pprLf xs+   = pprLfs xs++  pprLfs []+   = ""+  pprLfs [x]+   = pprL1 x+  pprLfs (x : rs@((_,c):_) )+   =  pprL1 x+   ++ (if c >= 0 then " + " else " - ")+   ++ pprLfs rs++  pprL1 (v,c) = show (abs c) ++ " " ++ pprV v++  pprCs (Constraint cs)+   = unlines $ map indent $ concatMap pprC cs++  pprC (C1 lo f up)+   =  case lo of+       Nothing  -> []+       Just lo' -> [pprL f ++ " >= " ++ show lo']+   ++ case up of+       Nothing  -> []+       Just up' -> [pprL f ++ " <= " ++ show up']++  pprLo (Just l)+   = show l ++ " <= "+  pprLo Nothing+   = ""++  pprUp (Just l)+   = " <= " ++ show l+  pprUp Nothing+   = ""++  pprBs m+   = unlines $ map (indent.pprB) $ M.toList m++  pprB (v, (lo,up))+   = pprLo lo ++ pprV v ++ pprUp up++  pprGs fvs+   = unlines $ map pprV+   $ filter isLeft+   $ S.toList fvs+
src/Numeric/Limp/Canon/Program.hs view
@@ -28,3 +28,38 @@  , varsOfConstraint $ _constraints p  , M.keysSet        $ _bounds      p ] ++-- | Merge some lower and upper bounds+mergeBounds :: Rep c => (Maybe (R c), Maybe (R c)) -> (Maybe (R c), Maybe (R c)) -> (Maybe (R c), Maybe (R c))+mergeBounds (l1,u1) (l2,u2)+ = ( mmaybe max l1 l2+   , mmaybe min u1 u2 )+ where+  mmaybe f a b+   = case (a,b) of+    (Nothing, Nothing)+     -> Nothing+    (Nothing, Just b')+     -> Just $ b'+    (Just a', Nothing)+     -> Just $ a'+    (Just a', Just b')+     -> Just $ f a' b'+++-- | Check whether an assignment satisfies the program's constraints and bounds+checkProgram :: (Rep c, Ord z, Ord r) => Assignment z r c -> Program z r c -> Bool+checkProgram a p+ =  check a (_constraints p)+ && checkBounds a (_bounds p)++checkBounds :: (Rep c, Ord z, Ord r) => Assignment z r c -> Map (Either z r) (Maybe (R c), Maybe (R c)) -> Bool+checkBounds ass bs+ =  M.fold (&&) True (M.mapWithKey checkB bs)+ where+  checkB k (lo,up)+   = let v = zrOf ass k+     in maybe True (<=v) lo+     && maybe True (v<=) up+     +
+ src/Numeric/Limp/Canon/Simplify.hs view
@@ -0,0 +1,27 @@+-- | Perform some simple optimisations on program+module Numeric.Limp.Canon.Simplify where+import Numeric.Limp.Canon.Program+import Numeric.Limp.Rep++import Numeric.Limp.Canon.Analyse.Constants++import Numeric.Limp.Canon.Simplify.Bounder+import Numeric.Limp.Canon.Simplify.Crunch+import Numeric.Limp.Canon.Simplify.Subst++import Data.Monoid++simplify :: (Ord z, Ord r, Rep c) => Program z r c -> (Assignment z r c, Program z r c)+simplify p+ = simplify' mempty p++simplify' :: (Ord z, Ord r, Rep c) => Assignment z r c -> Program z r c -> (Assignment z r c, Program z r c)+simplify' sub1 p+ = let p'   = crunchProgram    p+       p''  = bounderProgram   p'+       sub2 = constantsProgram p''+   in  if   assSize sub2 == 0+       then (sub1, p'')+       else simplify' (sub1 <> sub2) (substProgram sub2 p'')++
+ src/Numeric/Limp/Canon/Simplify/Bounder.hs view
@@ -0,0 +1,67 @@+-- | Convert linear constraints that only mention one variable to bounds+module Numeric.Limp.Canon.Simplify.Bounder where+import Numeric.Limp.Canon.Constraint+import Numeric.Limp.Canon.Linear+import Numeric.Limp.Canon.Program+import Numeric.Limp.Rep++import Data.Either+import qualified Data.Map as M++type Bound z r c = (Either z r, (Maybe (R c), Maybe (R c)))+++-- | Convert a single constraint into a bound, if possible.+--+-- > bounder $ Constraint (5 <= y <= 10)+-- > == Bound (Just 5) y (Just 10)+--+-- > bounder $ Constraint (5 <= 2y <= 10)+-- > == Bound (Just 2.5) y (Just 5)+--+bounderConstraint1 :: (Ord z, Ord r, Rep c) => Constraint1 z r c -> Maybe (Bound z r c)+bounderConstraint1 (C1 low (Linear mf) upp)+ | M.size mf == 1+ , [(k,c)]   <- M.toList mf+ , c /= 0+ = let fixup = (/ c)+       low'  = fmap fixup low+       upp'  = fmap fixup upp+       bounds+        | c >= 0+        = (low',upp')+        | otherwise+        = (upp',low')+   in  Just (k, bounds)++ | otherwise+ = Nothing+   ++bounderConstraint :: (Ord z, Ord r, Rep c) => Constraint z r c -> (Constraint z r c, [Bound z r c])+bounderConstraint (Constraint cs)+ = let (cs', bs) = partitionEithers $ map bounderC cs+   in  (Constraint cs', bs)+ where+  bounderC c+   = case bounderConstraint1 c of+     Nothing -> Left c+     Just b  -> Right b+   ++-- +bounderProgram :: (Ord z, Ord r, Rep c) => Program z r c -> Program z r c+bounderProgram p+ = let (c',bs) = bounderConstraint $ _constraints p+   in p+    { _constraints = c'+    , _bounds      = foldl merge (_bounds p) bs }++ where+  merge m (k,v)+   = case M.lookup k m of+     Just v'+      -> M.insert k (mergeBounds v' v) m+     Nothing+      -> M.insert k                 v  m+
+ src/Numeric/Limp/Canon/Simplify/Crunch.hs view
@@ -0,0 +1,54 @@+-- | Crunch together all constraints with same linear function+module Numeric.Limp.Canon.Simplify.Crunch where+import Numeric.Limp.Canon.Constraint+import Numeric.Limp.Canon.Program+import Numeric.Limp.Rep++import Data.List+import Data.Function+import Data.Maybe++-- | Crunch the constraints in some program+crunchProgram :: (Ord z, Ord r, Rep c) => Program z r c -> Program z r c+crunchProgram p+ = p { _constraints = crunchConstraint $ _constraints p }++-- | Crunch some constraints.+-- Constraints with the same function, for example+--+-- >              2x + y    < 5+-- > &&   0 <     2x + y+-- > &&           2x + y    < 10+--+-- becomes+--+-- >      0 <     2x + y    < 5+--+-- This should satisfy:+--+-- > forall a c. check a c == check a (crunchConstraint c)+-- > forall a.   length (checkConstraint c) <= length c+--+crunchConstraint :: (Ord z, Ord r, Rep c) => Constraint z r c -> Constraint z r c+crunchConstraint (Constraint cs)+ = Constraint+ $ map crunchC+ $ groupBy ((==) `on` getLin) cs+ where+  getLin (C1 _   lin _  ) = lin+  getLow (C1 low _   _  ) = low+  getUpp (C1 _   _   upp) = upp++  crunchC grp@(c:_)+   = let low = compareMaybes maximum $ map getLow grp+         upp = compareMaybes minimum $ map getUpp grp+     in  C1 low (getLin c) upp++  crunchC []+   = error "Impossible - groupBy should not produce empty lists"++  compareMaybes f ms+   = case catMaybes ms of+      ms'@(_:_) -> Just $ f ms'+      []        -> Nothing+
+ src/Numeric/Limp/Canon/Simplify/Subst.hs view
@@ -0,0 +1,110 @@+-- | Substitute an assignment into functions, constraints and programs+module Numeric.Limp.Canon.Simplify.Subst where+import Numeric.Limp.Canon.Constraint+import Numeric.Limp.Canon.Linear+import Numeric.Limp.Canon.Program+import Numeric.Limp.Rep++import qualified Data.Map as M+++-- | Substitute assignment into linear function.+-- However, 'Linear' isn't quite a linear function! That is, it doesn't have a constant summand.+-- So we must return the constant summand we lose.+--+-- Satisfies:+--+-- > forall a b f.+-- > let (f', c') = substLinear a f+-- > in  eval (a <> b) f == eval b f' + c'+--+-- > subst (x := 5) in 2x + y+-- > (y, 10)+--+substLinear :: (Ord z, Ord r, Rep c) => Assignment z r c -> Linear z r c -> (Linear z r c, R c)+substLinear (Assignment mz mr) (Linear mf)+ = ( Linear $ M.fromList $ concatMap update mf'+   ,          sum        $ map       getC   mf' )+ where+  mf' = M.toList mf++  get (v,co)+   | Left  z <- v+   , Just zv <- M.lookup z mz+   = Just $ fromZ zv * co+   | Right r <- v+   , Just rv <- M.lookup r mr+   = Just $       rv * co++   | otherwise+   = Nothing++  update vc+   | Just _ <- get vc+   = []+   | otherwise+   = [vc]++  getC vc+   | Just n <- get vc+   = n+   | otherwise+   = 0+++-- | Substitute assignment into a single linear constraint.+-- See 'substConstraint'.+--+-- > 5 <= 2x + y <= 10+-- > subst (y := 3)+-- > 2 <= 2x     <= 7+--+substConstraint1 :: (Ord z, Ord r, Rep c) => Assignment z r c -> Constraint1 z r c -> Constraint1 z r c+substConstraint1 ass (C1 low lin upp)+ = let (lin', const') = substLinear ass lin+       fixup bound    = bound - const'+   in C1 (fmap fixup low) lin' (fmap fixup upp)+++-- | Substitute assignment into a set of linear constraints.+-- Satisfies:+--+-- > forall a b f.+-- > let c' = substConstraint a c+-- > in  check (a <> b) c == check b c'+--+-- > subst (x := 5) in 15 <= 2x + y <= 20+-- > 5 <= y <= 10+--+substConstraint :: (Ord z, Ord r, Rep c) => Assignment z r c -> Constraint z r c -> Constraint z r c+substConstraint ass (Constraint cs)+ = Constraint+ $ map (substConstraint1 ass) cs+++-- | Substitute assignment into a program.+-- What does this satisfy? Hm.+substProgram :: (Ord z, Ord r, Rep c) => Assignment z r c -> Program z r c -> Program z r c+substProgram ass@(Assignment mz mr) p+ = p+ { _objective   = fst $ substLinear     ass $ _objective   p+ , _constraints =       substConstraint ass $ _constraints p+ , _bounds      =       cullBounds          $ _bounds      p+ }+ where+  cullBounds+   = M.mapMaybeWithKey cullB++  cullB k v++   | Left  z <- k+   , Just  _ <- M.lookup z mz+   = Nothing+   | Right r <- k+   , Just  _ <- M.lookup r mr+   = Nothing++   | otherwise+   = Just v++
src/Numeric/Limp/Program/Bounds.hs view
@@ -8,6 +8,8 @@  = BoundZ (B (Z c) z)  | BoundR (B (R c) r) +deriving instance (Show z, Show r, Show (Z c), Show (R c)) => (Show (Bounds z r c))+ -- | Maybe a lower bound, the variable's name, and maybe an upper bound. type B rep v  = (Maybe rep, v, Maybe rep)
src/Numeric/Limp/Program/Constraint.hs view
@@ -1,6 +1,7 @@ module Numeric.Limp.Program.Constraint where import Numeric.Limp.Program.Linear import Numeric.Limp.Program.ResultKind+import Numeric.Limp.Rep  import Data.Monoid @@ -39,6 +40,8 @@  (:&&)   :: Constraint z r c -> Constraint z r c -> Constraint z r c  (:!)    :: String           -> Constraint z r c -> Constraint z r c  CTrue   ::                                         Constraint z r c++deriving instance (Show z, Show r, Show (Z c), Show (R c)) => (Show (Constraint z r c))  infix  5 :== infix  5 :<=
src/Numeric/Limp/Program/Eval.hs view
@@ -1,8 +1,10 @@ -- | Functions for evaluating linear functions and checking constraints. module Numeric.Limp.Program.Eval where import Numeric.Limp.Rep+import Numeric.Limp.Program.Bounds import Numeric.Limp.Program.Constraint import Numeric.Limp.Program.Linear+import Numeric.Limp.Program.Program import Numeric.Limp.Program.ResultKind  -- | Evaluate a linear function with given assignment.@@ -51,3 +53,24 @@    go CTrue    = True++-- | Check whether an assignment satisfies the program's constraints and bounds+checkProgram :: (Rep c, Ord z, Ord r) => Assignment z r c -> Program z r c -> Bool+checkProgram a p+ =  check a (_constraints p)+ && checkBounds a (_bounds p)++checkBounds :: (Rep c, Ord z, Ord r) => Assignment z r c -> [Bounds z r c] -> Bool+checkBounds ass bs+ = all checkB bs+ where+  checkB (BoundZ (lo,z',up))+   = checkBo (zOf ass z') lo up+  checkB (BoundR (lo,r',up))+   = checkBo (rOf ass r') lo up++  checkBo v lo up+   =  maybe True (<=v) lo+   && maybe True (v<=) up+     +
src/Numeric/Limp/Program/Program.hs view
@@ -11,6 +11,7 @@ data Direction  = Minimise  | Maximise+   deriving Show  -- | Whole program, parameterised by: --@@ -30,6 +31,8 @@    -- Not all variables need to be mentioned, and if variables are mentioned multiple times, the intersection is used.    , _bounds        :: [Bounds z r c]    }++deriving instance (Show z, Show r, Show (Z c), Show (R c)) => (Show (Program z r c))  program :: Rep c => Direction -> Linear z r c k -> Constraint z r c -> [Bounds z r c] -> Program z r c program dir obj constr bounds
src/Numeric/Limp/Program/ResultKind.hs view
@@ -21,6 +21,8 @@  LZ :: [(z, Z c)]          -> (Z c) -> Linear z r c KZ  LR :: [(Either z r, R c)] -> (R c) -> Linear z r c KR +deriving instance (Show z, Show r, Show (Z c), Show (R c)) => (Show (Linear z r c k))+  -- | Find the result type of merging, or adding, two linear functions: -- adding two integers produces an integer, while adding a real on either side produces a real.
src/Numeric/Limp/Rep.hs view
@@ -10,6 +10,8 @@ import Data.Map (Map) import qualified Data.Map as M +import Data.Monoid+ -- | The Representation class. Requires its members @Z c@ and @R c@ to be @Num@, @Ord@ and @Eq@. -- -- For some reason, for type inference to work, the members must be @data@ instead of @type@.@@ -36,7 +38,12 @@  deriving instance (Show (Z c), Show (R c), Show z, Show r) => Show (Assignment z r c) +instance (Ord z, Ord r) => Monoid (Assignment z r c) where+ mempty = Assignment M.empty M.empty+ mappend (Assignment z1 r1) (Assignment z2 r2)+  = Assignment (M.union z1 z2) (M.union r1 r2) + -- | Retrieve value of integer variable - or 0, if there is no value. zOf :: (Rep c, Ord z) => Assignment z r c -> z -> Z c zOf (Assignment zs _) z@@ -51,6 +58,9 @@ zrOf :: (Rep c, Ord z, Ord r) => Assignment z r c -> Either z r -> R c zrOf a = either (fromZ . zOf a) (rOf a) +assSize :: Assignment z r c -> Int+assSize (Assignment mz mr)+ = M.size mz + M.size mr   -- | A representation that uses native 64-bit ints and 64-bit doubles.
+ tests/Main.hs view
@@ -0,0 +1,15 @@++import Test.Tasty++import qualified Convert+import qualified Simplify++main = defaultMain properties++properties :: TestTree+properties+ = testGroup "Properties"+ [ Convert.tests+ , Simplify.tests+ ]+