limp 0.1.0.0 → 0.3.0.0
raw patch · 14 files changed
+341/−125 lines, 14 filesdep −lensdep −template-haskelldep −vectorPVP ok
version bump matches the API change (PVP)
Dependencies removed: lens, template-haskell, vector, void
API changes (from Hackage documentation)
- Numeric.Limp.Canon.Program: bounds :: Lens' (Program z_adRW r_adRX c_adRY) (Map (Either z_adRW r_adRX) (Maybe (R c_adRY), Maybe (R c_adRY)))
- Numeric.Limp.Canon.Program: constraints :: Lens' (Program z_adRW r_adRX c_adRY) (Constraint z_adRW r_adRX c_adRY)
- Numeric.Limp.Canon.Program: objective :: Lens' (Program z_adRW r_adRX c_adRY) (Linear z_adRW r_adRX c_adRY)
- Numeric.Limp.Program.Constraint: check :: (Rep c, Ord z, Ord r) => Assignment z r c -> Constraint z r c -> Bool
- Numeric.Limp.Program.Linear: KR :: K
- Numeric.Limp.Program.Linear: KZ :: K
- Numeric.Limp.Program.Linear: data K
- Numeric.Limp.Program.Linear: eval :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> KRep k c
- Numeric.Limp.Program.Linear: evalR :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> R c
- Numeric.Limp.Program.Linear: on2 :: (b -> c) -> (a, b) -> (a, c)
- Numeric.Limp.Program.Program: bounds :: Lens' (Program z_abru r_abrv c_abrw) [Bounds z_abru r_abrv c_abrw]
- Numeric.Limp.Program.Program: constraints :: Lens' (Program z_abru r_abrv c_abrw) (Constraint z_abru r_abrv c_abrw)
- Numeric.Limp.Program.Program: direction :: Lens' (Program z_abru r_abrv c_abrw) Direction
- Numeric.Limp.Program.Program: objective :: Lens' (Program z_abru r_abrv c_abrw) (Linear z_abru r_abrv c_abrw KR)
- Numeric.Limp.Rep: instance Read (R IntDouble)
- Numeric.Limp.Rep: instance Read (Z IntDouble)
+ Numeric.Limp.Program.Eval: check :: (Rep c, Ord z, Ord r) => Assignment z r c -> Constraint z r c -> Bool
+ Numeric.Limp.Program.Eval: eval :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> KRep k c
+ Numeric.Limp.Program.Eval: evalR :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> R c
+ Numeric.Limp.Program.Linear: conR :: Rep c => R c -> Linear z r c KR
+ Numeric.Limp.Program.Linear: conZ :: Rep c => Z c -> Linear z r c KZ
+ Numeric.Limp.Program.Program: maximise :: Rep c => Linear z r c k -> Constraint z r c -> [Bounds z r c] -> Program z r c
+ Numeric.Limp.Program.Program: minimise :: Rep c => Linear z r c k -> Constraint z r c -> [Bounds z r c] -> Program z r c
+ Numeric.Limp.Program.Program: program :: Rep c => Direction -> Linear z r c k -> Constraint z r c -> [Bounds z r c] -> Program z r c
+ Numeric.Limp.Program.ResultKind: KR :: K
+ Numeric.Limp.Program.ResultKind: KZ :: K
+ Numeric.Limp.Program.ResultKind: LR :: [(Either z r, R c)] -> (R c) -> Linear z r c KR
+ Numeric.Limp.Program.ResultKind: LZ :: [(z, Z c)] -> (Z c) -> Linear z r c KZ
+ Numeric.Limp.Program.ResultKind: data K
+ Numeric.Limp.Program.ResultKind: data Linear z r c k
+ Numeric.Limp.Rep: instance (Show (Z c), Show (R c), Show z, Show r) => Show (Assignment z r c)
+ Numeric.Limp.Rep: instance Fractional (R IntDouble)
+ Numeric.Limp.Rep: instance Real (R IntDouble)
+ Numeric.Limp.Rep: instance RealFrac (R IntDouble)
- Numeric.Limp.Program.Program: Program :: Linear z r c KR -> Direction -> Constraint z r c -> [Bounds z r c] -> Program z r c
+ Numeric.Limp.Program.Program: Program :: Direction -> Linear z r c KR -> Constraint z r c -> [Bounds z r c] -> Program z r c
- Numeric.Limp.Rep: class (Num (Z c), Ord (Z c), Eq (Z c), Integral (Z c), Num (R c), Ord (R c), Eq (R c)) => Rep c where data family Z c data family R c fromZ = fromIntegral
+ Numeric.Limp.Rep: class (Num (Z c), Ord (Z c), Eq (Z c), Integral (Z c), Num (R c), Ord (R c), Eq (R c), RealFrac (R c)) => Rep c where data family Z c data family R c fromZ = fromIntegral
Files
- limp.cabal +8/−9
- src/Numeric/Limp/Canon.hs +18/−5
- src/Numeric/Limp/Canon/Constraint.hs +8/−0
- src/Numeric/Limp/Canon/Convert.hs +28/−6
- src/Numeric/Limp/Canon/Linear.hs +5/−0
- src/Numeric/Limp/Canon/Program.hs +7/−6
- src/Numeric/Limp/Program.hs +24/−6
- src/Numeric/Limp/Program/Bounds.hs +11/−0
- src/Numeric/Limp/Program/Constraint.hs +26/−36
- src/Numeric/Limp/Program/Eval.hs +53/−0
- src/Numeric/Limp/Program/Linear.hs +39/−41
- src/Numeric/Limp/Program/Program.hs +30/−11
- src/Numeric/Limp/Program/ResultKind.hs +38/−0
- src/Numeric/Limp/Rep.hs +46/−5
limp.cabal view
@@ -1,8 +1,8 @@ name: limp-version: 0.1.0.0+version: 0.3.0.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.+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. see the limp-cbc package for a simple solver. license: MIT@@ -26,8 +26,10 @@ Numeric.Limp.Program.Bounds Numeric.Limp.Program.Constraint+ Numeric.Limp.Program.Eval Numeric.Limp.Program.Linear Numeric.Limp.Program.Program+ Numeric.Limp.Program.ResultKind Numeric.Limp.Program Numeric.Limp.Canon.Linear@@ -39,12 +41,9 @@ -- other-modules: build-depends: base < 5,- containers == 0.5.*,- lens == 4.2.*,- vector == 0.10.*,- void,- template-haskell+ containers == 0.5.*+ ghc-options: -Wall -fno-warn-orphans default-language: Haskell2010- default-extensions: TemplateHaskell TypeFamilies FlexibleContexts GeneralizedNewtypeDeriving DataKinds GADTs RankNTypes+ default-extensions: TemplateHaskell TypeFamilies FlexibleContexts GeneralizedNewtypeDeriving DataKinds GADTs RankNTypes StandaloneDeriving FlexibleInstances
src/Numeric/Limp/Canon.hs view
@@ -1,8 +1,21 @@+-- | A simpler representation of programs.+-- The frontend representation ("Numeric.Limp.Program") has many different kinds of constraints+-- (@<=@, @<@, @==@, @between@), as well as constant additions on each linear function+-- (eg. @x + 2y + 5@).+-- The so-called canonical representation removes the constant addition from each linear constraint,+-- and converts each constraint (@Lin Op Lin@) to (@Num <= Lin <= Num@).+--+-- The most interesting function here is 'Numeric.Limp.Canon.Convert.program' for converting+-- from Program representation to Canon. module Numeric.Limp.Canon- (module X) where+ ( module Numeric.Limp.Canon.Linear+ , module Numeric.Limp.Canon.Constraint+ , module Numeric.Limp.Canon.Program+ , module Numeric.Limp.Canon.Convert+ ) where -import Numeric.Limp.Canon.Linear as X-import Numeric.Limp.Canon.Constraint as X-import Numeric.Limp.Canon.Program as X-import Numeric.Limp.Canon.Convert as X+import Numeric.Limp.Canon.Linear+import Numeric.Limp.Canon.Constraint+import Numeric.Limp.Canon.Program+import Numeric.Limp.Canon.Convert
src/Numeric/Limp/Canon/Constraint.hs view
@@ -1,15 +1,22 @@+-- | Representation of linear constraints module Numeric.Limp.Canon.Constraint where import Numeric.Limp.Rep import Numeric.Limp.Canon.Linear import qualified Data.Set as S +-- | Conjunction of simple constraints data Constraint z r c = Constraint [Constraint1 z r c] +-- | A simple constraint data Constraint1 z r c+ -- | Maybe a lower bound, a linear function, and maybe an upper bound.+ --+ -- 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 where@@ -21,6 +28,7 @@ && maybe True (lin' <=) upper +-- | Get set of variables in constraint varsOfConstraint :: (Ord z, Ord r) => Constraint z r c -> S.Set (Either z r) varsOfConstraint (Constraint cs) = S.unions
src/Numeric/Limp/Canon/Convert.hs view
@@ -1,3 +1,4 @@+-- | Convert from "Numeric.Limp.Program" representation to simpler, so-called canonical representation. module Numeric.Limp.Canon.Convert where import Numeric.Limp.Rep@@ -12,9 +13,14 @@ import qualified Numeric.Limp.Program.Program as P import Control.Applicative-import Control.Lens import qualified Data.Map as M ++-- | Convert a Frontend 'P.Linear' into a Canon 'Linear'.+-- Returns the constant summand as well, as Canon Linear do not have these.+--+-- Should satisfy that+-- @forall a l. P.evalR a l == evalR a (fst $ linear l) + (snd $ linear l)@ linear :: (Rep c, Ord z, Ord r) => P.Linear z r c k -> (Linear z r c, R c) linear (P.LZ ls co) = (mkLinear $ map conv ls, fromZ co)@@ -23,6 +29,10 @@ linear (P.LR ls co) = (mkLinear ls, co) +-- | Convert a Frontend 'P.Constraint' into a Canon 'Constraint'.+--+-- Should satisfy that+-- @forall a c. P.check a c == check a (constraint c)@ constraint :: (Rep c, Ord z, Ord r) => P.Constraint z r c -> Constraint z r c constraint z = Constraint $ go z@@ -69,28 +79,40 @@ go P.CTrue = [] --- lemma: check a (constraint c) == P.check a c +-- | Convert a Frontend 'P.Program' into a Canon 'Program'.+--+-- If we had a solve function that worked on either, it would ideally satisfy+-- @forall p. P.solve p == solve (program p)@+--+-- However, due to potential non-determinism in solving functions, it could be possible to get a different, but still optimal, solution:+--+-- > forall p. let aP = P.solve p+-- > p' = program p+-- > a = solve p'+-- > in P.eval aP (P._objective p) == eval a (_objective p')+-- > && check a (P._constraints p) && check ...+-- program :: (Rep c, Ord z, Ord r) => P.Program z r c -> Program z r c program p = Program obj constr bnds where obj- = case p ^. P.direction of+ = case P._direction p of P.Minimise -> fst $ linear $ obj_orig P.Maximise -> fst $ linear $ P.neg obj_orig obj_orig- = p ^. P.objective+ = P._objective p constr- = constraint $ p ^. P.constraints+ = constraint $ P._constraints p bnds = M.fromListWith merge $ map extract- $ p ^. P.bounds+ $ P._bounds p merge (l1,u1) (l2,u2) = ( mmaybe max l1 l2
src/Numeric/Limp/Canon/Linear.hs view
@@ -1,3 +1,4 @@+-- | Representation of subset of linear functions: only variables and coefficients, no constant summand module Numeric.Limp.Canon.Linear where import Numeric.Limp.Rep @@ -5,9 +6,11 @@ import qualified Data.Set as S +-- | Linear function is represented as a map from either a integral variable or an real variable, to a real coefficient. data Linear z r c = Linear (M.Map (Either z r) (R c)) +-- | Create linear function from list of variables and coefficients mkLinear :: (Ord z, Ord r) => [(Either z r, R c)] -> Linear z r c@@ -15,6 +18,7 @@ = Linear (M.fromList zrs) +-- | Evaluate linear function with given assignment evalR :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c -> R c evalR a (Linear ls) = sum (map get $ M.toList ls)@@ -22,6 +26,7 @@ get (l, co) = zrOf a l * co +-- | Find set of all variables mentioned in function varsOfLinear :: (Ord z, Ord r) => Linear z r c -> S.Set (Either z r) varsOfLinear (Linear m) = M.keysSet m
src/Numeric/Limp/Canon/Program.hs view
@@ -1,16 +1,17 @@-{-# LANGUAGE TemplateHaskell #-}+-- | Canon representation of linear program module Numeric.Limp.Canon.Program where import Numeric.Limp.Canon.Linear import Numeric.Limp.Canon.Constraint import Numeric.Limp.Rep -import Control.Lens import Data.Map (Map) import qualified Data.Map as M import Data.Set (Set) import qualified Data.Set as S +-- | A program represented by objective, constraints and bounds.+-- There is no need for an optimisation direction; the objective is just negated. data Program z r c = Program { _objective :: Linear z r c@@ -18,12 +19,12 @@ , _bounds :: Map (Either z r) (Maybe (R c), Maybe (R c)) } -makeLenses ''Program +-- | Find set of all variables mentioned in program varsOfProgram :: (Ord z, Ord r) => Program z r c -> Set (Either z r) varsOfProgram p = S.unions- [ varsOfLinear $ p ^. objective- , varsOfConstraint $ p ^. constraints- , M.keysSet $ p ^. bounds ]+ [ varsOfLinear $ _objective p+ , varsOfConstraint $ _constraints p+ , M.keysSet $ _bounds p ]
src/Numeric/Limp/Program.hs view
@@ -1,9 +1,27 @@-{-# LANGUAGE TemplateHaskell #-}+-- | Front-end representation of programs.+-- See 'Numeric.Limp.Program.Program.Program' for the entire program;+-- 'Numeric.Limp.Program.Constraint.Constraint' for constraints such as less than or equal, greater than, etc;+-- and 'Numeric.Limp.Program.Linear.Linear' for linear functions. module Numeric.Limp.Program- (module X) where+ ( -- | Each variable can have a lower or upper bound.+ module Numeric.Limp.Program.Bounds+ -- | Constraints such as less than or equal, greater than or equal, between,...+ , module Numeric.Limp.Program.Constraint+ -- | Functions for evaluating linear functions constraints for a given assignment of variables.+ , module Numeric.Limp.Program.Eval+ -- | Linear functions with constant coefficients on variables, and a constant addition.+ , module Numeric.Limp.Program.Linear+ -- | An entire program.+ , module Numeric.Limp.Program.Program+ -- | Linear functions are classified as either int-valued or real-valued,+ -- so we define @KZ@ and @KR@ as data kinds to denote this in the type.+ , module Numeric.Limp.Program.ResultKind+ ) where -import Numeric.Limp.Program.Bounds as X-import Numeric.Limp.Program.Linear as X-import Numeric.Limp.Program.Constraint as X-import Numeric.Limp.Program.Program as X+import Numeric.Limp.Program.Bounds+import Numeric.Limp.Program.Constraint+import Numeric.Limp.Program.Eval+import Numeric.Limp.Program.Linear+import Numeric.Limp.Program.Program+import Numeric.Limp.Program.ResultKind
src/Numeric/Limp/Program/Bounds.hs view
@@ -1,37 +1,48 @@+-- | Define upper and lower bounds of program variables. module Numeric.Limp.Program.Bounds where import Numeric.Limp.Rep +-- | Define upper and lower bounds of program variables.+-- Bounds may be specified multiple times: the intersection of all bounds is used. data Bounds z r c = BoundZ (B (Z c) z) | BoundR (B (R c) r) +-- | Maybe a lower bound, the variable's name, and maybe an upper bound. type B rep v = (Maybe rep, v, Maybe rep) +-- | Create a lower and upper bound for an integer variable. lowerUpperZ :: Rep c => Z c -> z -> Z c -> Bounds z r c lowerUpperZ l v u = BoundZ (Just l, v, Just u) +-- | Create only a lower bound for an integer variable. lowerZ :: Rep c => Z c -> z -> Bounds z r c lowerZ l v = BoundZ (Just l, v, Nothing) +-- | Create only an upper bound for an integer variable. upperZ :: Rep c => z -> Z c -> Bounds z r c upperZ v u = BoundZ (Nothing, v, Just u) +-- | A binary integer variable: can only be @0@ or @1@. binary :: Rep c => z -> Bounds z r c binary v = BoundZ (Just 0, v, Just 1) +-- | Create a lower and upper bound for a real variable. lowerUpperR :: Rep c => R c -> r -> R c -> Bounds z r c lowerUpperR l v u = BoundR (Just l, v, Just u) +-- | Create only a lower bound for a real variable. lowerR :: Rep c => R c -> r -> Bounds z r c lowerR l v = BoundR (Just l, v, Nothing) +-- | Create only an upper bound for a real variable. upperR :: Rep c => r -> R c -> Bounds z r c upperR v u = BoundR (Nothing, v, Just u)
src/Numeric/Limp/Program/Constraint.hs view
@@ -1,9 +1,34 @@ module Numeric.Limp.Program.Constraint where-import Numeric.Limp.Rep import Numeric.Limp.Program.Linear+import Numeric.Limp.Program.ResultKind import Data.Monoid +-- | Different kind of constraints.+--+-- These are not all necessary, but I have a hunch that keeping some structure may be helpful in the future.+--+-- Constructors:+--+-- [@:==@] Equality constraint+--+-- [@:<=@] Less than or equal+--+-- [@:<@] Strictly less than: this is only allowed for purely integer functions+--+-- [@:>=@] Greater than or equal+--+-- [@:>@] Strictly greater than: this is only allowed for purely integer functions+--+-- [@Between@] @Between a b c@ is equivalent to @a :<= b :&& b :<= c@+-- +-- [@:&&@] Conjunction of two constraints+--+-- [@:!@] @"name" :! constr@ Annotate a constraint with a name, or other useless information+--+-- [@CTrue@] Trivially true constraint+--+ data Constraint z r c where (:==) :: Linear z r c k1 -> Linear z r c k2 -> Constraint z r c (:<=) :: Linear z r c k1 -> Linear z r c k2 -> Constraint z r c@@ -14,10 +39,6 @@ (:&&) :: 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--- These are not all necessary, but I have a hunch that keeping some structure may be helpful in the future.--- Also for pretty printing.------ Less than is interesting: we can only construct a < b if both are integral. infix 5 :== infix 5 :<=@@ -26,37 +47,6 @@ infix 5 :> infix 4 :! infixr 3 :&&--check :: (Rep c, Ord z, Ord r) => Assignment z r c -> Constraint z r c -> Bool-check ass = go- where- -- ev :: Linear z r c k -> R c- -- ev l = evalR ass l-- -- TODO should there be tolerance here?- -- that's probably something that should go in Rep class- go (a :== b)- = evalR ass a == evalR ass b- go (a :<= b)- = evalR ass a <= evalR ass b- go (a :>= b)- = evalR ass a >= evalR ass b-- -- They are both ints, so no conversion to R is necessary- go (a :< b)- = eval ass a < eval ass b- go (a :> b)- = eval ass a > eval ass b-- go (Between a b c)- = evalR ass a <= evalR ass b && evalR ass b <= evalR ass c- go (a :&& b)- = go a && go b- go (_ :! a)- = go a-- go CTrue- = True instance Monoid (Constraint z r c) where mempty = CTrue
+ src/Numeric/Limp/Program/Eval.hs view
@@ -0,0 +1,53 @@+-- | Functions for evaluating linear functions and checking constraints.+module Numeric.Limp.Program.Eval where+import Numeric.Limp.Rep+import Numeric.Limp.Program.Constraint+import Numeric.Limp.Program.Linear+import Numeric.Limp.Program.ResultKind++-- | Evaluate a linear function with given assignment.+-- If the linear function is purely integral, a @Z@ will be returned; otherwise, @R@.+eval :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> KRep k c+eval a (LZ ls c)+ = sum (map get ls) + c+ where+ get (l, co) = zOf a l * co++eval a (LR ls c)+ = sum (map get ls) + c+ where+ get (l, co) = zrOf a l * co+++-- | Evaluate a linear function with given assignment, returning real value.+evalR :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> R c+evalR a l@(LZ{}) = fromZ (eval a l)+evalR a l@(LR{}) = eval a l+++-- | Check whether assignment satisfies constraint.+check :: (Rep c, Ord z, Ord r) => Assignment z r c -> Constraint z r c -> Bool+check ass = go+ where+ go (a :== b)+ = evalR ass a == evalR ass b+ go (a :<= b)+ = evalR ass a <= evalR ass b+ go (a :>= b)+ = evalR ass a >= evalR ass b++ -- They are both ints, so no conversion to R is necessary+ go (a :< b)+ = eval ass a < eval ass b+ go (a :> b)+ = eval ass a > eval ass b++ go (Between a b c)+ = evalR ass a <= evalR ass b && evalR ass b <= evalR ass c+ go (a :&& b)+ = go a && go b+ go (_ :! a)+ = go a++ go CTrue+ = True
src/Numeric/Limp/Program/Linear.hs view
@@ -1,29 +1,27 @@-module Numeric.Limp.Program.Linear where-import Numeric.Limp.Rep---- import Control.Lens---- | The kind of a linear function:--- it can be integral (Z) or real (R).-data K = KZ | KR--data Linear z r c k where- LZ :: [(z, Z c)] -> (Z c) -> Linear z r c KZ- LR :: [(Either z r, R c)] -> (R c) -> Linear z r c KR+-- | Representation, constructors and limited arithmetic on linear functions.+--+-- The linear function is indexed by its result type: either purely integer (@KZ@) or mixed/real (@KR@).+-- This index is used to allow strictly-less-than constraints only on integer functions,+-- and to allow retrieving integer values from purely integer functions.+--+module Numeric.Limp.Program.Linear+ ( Linear(..)+ , toR --- | The upper bound of two kinds is real, unless both are integral-type family KMerge (a :: K) (b :: K) :: K where- KMerge KZ KZ = KZ- KMerge KR b = KR- KMerge a KR = KR+ , z, z1+ , r, r1 --- | The upper bound of two kinds is real, unless both are integral-type family KRep (a :: K) :: * -> * where- KRep KZ = Z- KRep KR = R+ , con, conZ, conR+ , c0, c1 + , neg+ , (.*), (*.)+ , (.+.), (.-.) )+ where+import Numeric.Limp.Rep+import Numeric.Limp.Program.ResultKind --- | Any linear function can be made into a real, as it is the upper bound / top+-- | Any linear function can be converted into a real linear function. toR :: Rep c => Linear z r c k -> Linear z r c KR toR (LZ ls co) = LR (map go ls) (fromZ co) where@@ -31,9 +29,6 @@ toR l@(LR{}) = l ---------------------------- Creation functions- -- | Integral variable z :: Rep c => z -> Z c -> Linear z r c KZ z z' c@@ -55,16 +50,28 @@ = r r' 1 --- | An integral constant+-- | An integral constant summand con :: Rep c => Z c -> Linear z r c KZ con c' = LZ [] c' +-- | An integral constant summand+conZ :: Rep c => Z c -> Linear z r c KZ+conZ = con++-- | Constant @0@ c0 :: Rep c => Linear z r c KZ c0 = con 0+-- | Constant @1@ c1 :: Rep c => Linear z r c KZ c1 = con 1 +-- | A real constant+conR :: Rep c => R c -> Linear z r c KR+conR c'+ = LR [] c'++-- | Helper for applying function to second element of tuple on2 :: (b -> c) -> (a, b) -> (a, c) on2 f (a,b) = (a, f b) @@ -77,16 +84,21 @@ = LR (map (on2 negate) ls) (negate c) +-- | Multiply a linear function by some constant.+--+-- Note that you cannot multiply a linear function by another linear function, as the result would likely be non-linear! (.*) :: Rep c => Linear z r c k -> KRep k c -> Linear z r c k (.*) (LZ ls c) z' = LZ (map (on2 (*z')) ls) (c * z') (.*) (LR ls c) r' = LR (map (on2 (*r')) ls) (c * r') +-- | Multiply a linear function by some constant. (*.) :: Rep c => KRep k c -> Linear z r c k -> Linear z r c k (*.) = flip (.*) +-- | Add two linear functions together. They can have different result types. (.+.) :: Rep c => Linear z r c k1 -> Linear z r c k2 -> Linear z r c (KMerge k1 k2) (.+.) a b = case (a,b) of@@ -103,6 +115,7 @@ +-- | Subtract one linear function from another. They can have different result types. (.-.) :: Rep c => Linear z r c k1 -> Linear z r c k2 -> Linear z r c (KMerge k1 k2) (.-.) a b = a .+. neg b@@ -112,19 +125,4 @@ infix 7 .* infixl 6 .+. infixl 6 .-.--eval :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> KRep k c-eval a (LZ ls c)- = sum (map get ls) + c- where- get (l, co) = zOf a l * co--eval a (LR ls c)- = sum (map get ls) + c- where- get (l, co) = zrOf a l * co--evalR :: (Rep c, Ord z, Ord r) => Assignment z r c -> Linear z r c k -> R c-evalR a l@(LZ{}) = fromZ (eval a l)-evalR a l@(LR{}) = eval a l
src/Numeric/Limp/Program/Program.hs view
@@ -1,27 +1,46 @@-{-# LANGUAGE TemplateHaskell #-}+-- | Definition of a whole program module Numeric.Limp.Program.Program where -import Numeric.Limp.Program.Linear-import Numeric.Limp.Program.Constraint import Numeric.Limp.Program.Bounds--import Control.Lens+import Numeric.Limp.Program.Constraint+import Numeric.Limp.Program.Linear+import Numeric.Limp.Program.ResultKind+import Numeric.Limp.Rep +-- | Direction to optimise program in: minimise or maximise. data Direction = Minimise | Maximise +-- | Whole program, parameterised by:+--+-- [@z@] type of integer variables+-- [@r@] type of real variables+-- [@c@] representation of integers and reals (see 'Numeric.Limp.Rep.Rep')+-- data Program z r c- = Program- { _objective :: Linear z r c KR- , _direction :: Direction+ = Program {+ -- | Optimisation direction+ _direction :: Direction+ -- | The objective function+ , _objective :: Linear z r c KR+ -- | All constraints bundled up with @:&&@. , _constraints :: Constraint z r c+ -- | Upper and lower bounds of variables.+ -- Not all variables need to be mentioned, and if variables are mentioned multiple times, the intersection is used. , _bounds :: [Bounds z r c] } -makeLenses ''Program+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+ = Program dir (toR obj) constr bounds +minimise :: Rep c => Linear z r c k -> Constraint z r c -> [Bounds z r c] -> Program z r c+minimise+ = program Minimise+ --- relax :: Program z r -> Program Void (Either z r)--- relax = undefined+maximise :: Rep c => Linear z r c k -> Constraint z r c -> [Bounds z r c] -> Program z r c+maximise+ = program Maximise
+ src/Numeric/Limp/Program/ResultKind.hs view
@@ -0,0 +1,38 @@+-- | Type-level functions on result types.+--+-- Linear functions are classified as either int-valued or real-valued,+-- so we define @KZ@ and @KR@ as data kinds to denote this in the type.+--+module Numeric.Limp.Program.ResultKind where+import Numeric.Limp.Rep+++-- | Classify the result type of a linear function to either integral or real:+data K+ -- | Integral @Z@+ = KZ+ -- | Real or mixed @R@+ | KR+++-- | Representation of either integral of real linear functions:+-- a list of variables with coefficients, plus a constant summand.+data Linear z r c k where+ LZ :: [(z, Z c)] -> (Z c) -> Linear z r c KZ+ LR :: [(Either z r, R c)] -> (R c) -> Linear z r c KR+++-- | 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.+type family KMerge (a :: K) (b :: K) :: K where+ KMerge KZ KZ = KZ+ KMerge KR b = KR+ KMerge a KR = KR++-- | Convert a @K@ to its actual representation (@Z@ or @R@).+type family KRep (a :: K) :: * -> * where+ KRep KZ = Z+ KRep KR = R+++
src/Numeric/Limp/Rep.hs view
@@ -1,38 +1,79 @@+-- | Representation of integers (Z) and reals (R) of similar precision.+-- Programs are abstracted over this, so that ideally in the future we could have a+-- solver that produces Integers and Rationals, instead of just Ints and Doubles.+--+-- We bundle Z and R up into a single representation instead of abstracting over both,+-- because we must be able to convert from Z to R without loss.+-- module Numeric.Limp.Rep where import Data.Map (Map) import qualified Data.Map as M +-- | 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@.+-- This gives some minor annoyances when unpacking them. See 'unwrapR' below.+-- class ( Num (Z c), Ord (Z c), Eq (Z c), Integral (Z c)- , Num (R c), Ord (R c), Eq (R c)) => Rep c where+ , Num (R c), Ord (R c), Eq (R c), RealFrac (R c)) => Rep c where++ -- | Integers data Z c+ -- | Real numbers data R c + -- | Convert an integer to a real. This should not lose any precision.+ -- (whereas @fromIntegral 1000 :: Word8@ would lose precision) fromZ :: Z c -> R c fromZ = fromIntegral ++-- | An assignment from variables to values.+-- Maps integer variables to integers, and real variables to reals. data Assignment z r c = Assignment (Map z (Z c)) (Map r (R c)) +deriving instance (Show (Z c), Show (R c), Show z, Show r) => Show (Assignment z r c)+++-- | 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- = zs M.! z+ = maybe 0 id $ M.lookup z zs +-- | Retrieve value of real variable - or 0, if there is no value. rOf :: (Rep c, Ord r) => Assignment z r c -> r -> R c rOf (Assignment _ rs) r- = rs M.! r+ = maybe 0 id $ M.lookup r rs +-- | Retrieve value of an integer or real variable, with result cast to a real regardless. zrOf :: (Rep c, Ord z, Ord r) => Assignment z r c -> Either z r -> R c zrOf a = either (fromZ . zOf a) (rOf a) +++-- | A representation that uses native 64-bit ints and 64-bit doubles.+-- Really, this should be 32-bit ints. data IntDouble instance Rep IntDouble where+ -- | Automatically defer numeric operations to the native int. newtype Z IntDouble = Z Int- deriving (Ord,Eq,Show,Read,Integral,Real,Num,Enum)+ deriving (Ord,Eq,Integral,Real,Num,Enum) newtype R IntDouble = R Double- deriving (Ord,Eq,Show,Read,Num,Enum)+ deriving (Ord,Eq,Num,Enum,Fractional,Real,RealFrac) +-- | Define show manually, so we can strip out the "Z" and "R" prefixes.+instance Show (Z IntDouble) where+ show (Z i) = show i++instance Show (R IntDouble) where+ show (R i) = show i++++-- | Convert a wrapped (R IntDouble) to an actual Double. unwrapR :: R IntDouble -> Double unwrapR (R d) = d