comfort-glpk 0.0.0.1 → 0.0.1
raw patch · 8 files changed
+822/−146 lines, 8 filesdep +randomdep +transformersdep ~non-emptydep ~utility-htPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: random, transformers
Dependency ranges changed: non-empty, utility-ht
API changes (from Hackage documentation)
- Numeric.GLPK: instance Control.DeepSeq.NFData Numeric.GLPK.NoSolutionType
- Numeric.GLPK: instance Control.DeepSeq.NFData Numeric.GLPK.SolutionType
- Numeric.GLPK: instance GHC.Base.Functor Numeric.GLPK.Inequality
- Numeric.GLPK: instance GHC.Classes.Eq Numeric.GLPK.NoSolutionType
- Numeric.GLPK: instance GHC.Classes.Eq Numeric.GLPK.SolutionType
- Numeric.GLPK: instance GHC.Show.Show Numeric.GLPK.Bound
- Numeric.GLPK: instance GHC.Show.Show Numeric.GLPK.NoSolutionType
- Numeric.GLPK: instance GHC.Show.Show Numeric.GLPK.SolutionType
- Numeric.GLPK: instance GHC.Show.Show ix => GHC.Show.Show (Numeric.GLPK.Term ix)
- Numeric.GLPK: instance GHC.Show.Show x => GHC.Show.Show (Numeric.GLPK.Inequality x)
+ Numeric.GLPK: class FormatIdentifier ix
+ Numeric.GLPK: exactMulti :: (Indexed sh, Index sh ~ ix) => Bounds ix -> Constraints ix -> sh -> T [] (Direction, [Term ix]) -> ([Double], Solution sh)
+ Numeric.GLPK: exactSuccessive :: (Traversable f, Eq sh, Indexed sh, Index sh ~ ix) => Bounds ix -> Constraints ix -> (Direction, Objective sh) -> f ((SolutionType, (Double, Array sh Double)) -> Constraints ix, (Direction, Objective sh)) -> Either NoSolutionType (T f (SolutionType, (Double, Array sh Double)))
+ Numeric.GLPK: formatMathProg :: (Indexed sh, Index sh ~ ix, FormatIdentifier ix) => Bounds ix -> Constraints ix -> (Direction, Objective sh) -> [String]
+ Numeric.GLPK: instance Numeric.GLPK.FormatIdentifier GHC.Integer.Type.Integer
+ Numeric.GLPK: instance Numeric.GLPK.FormatIdentifier GHC.Types.Char
+ Numeric.GLPK: instance Numeric.GLPK.FormatIdentifier GHC.Types.Int
+ Numeric.GLPK: instance Numeric.GLPK.FormatIdentifier c => Numeric.GLPK.FormatIdentifier [c]
+ Numeric.GLPK: interiorSuccessive :: (Traversable f, Eq sh, Indexed sh, Index sh ~ ix) => Bounds ix -> Constraints ix -> (Direction, Objective sh) -> f ((SolutionType, (Double, Array sh Double)) -> Constraints ix, (Direction, Objective sh)) -> Either NoSolutionType (T f (SolutionType, (Double, Array sh Double)))
+ Numeric.GLPK: simplexSuccessive :: (Traversable f, Eq sh, Indexed sh, Index sh ~ ix) => Bounds ix -> Constraints ix -> (Direction, Objective sh) -> f ((SolutionType, (Double, Array sh Double)) -> Constraints ix, (Direction, Objective sh)) -> Either NoSolutionType (T f (SolutionType, (Double, Array sh Double)))
+ Numeric.GLPK: solveSuccessive :: (Traversable f, Eq sh, Indexed sh, Index sh ~ ix) => (Constraints ix -> (Direction, Objective sh) -> Solution sh) -> Constraints ix -> (Direction, Objective sh) -> f ((SolutionType, (Double, Array sh Double)) -> Constraints ix, (Direction, Objective sh)) -> Either NoSolutionType (T f (SolutionType, (Double, Array sh Double)))
+ Numeric.GLPK.Monad: Maximize :: Direction
+ Numeric.GLPK.Monad: Minimize :: Direction
+ Numeric.GLPK.Monad: data Direction
+ Numeric.GLPK.Monad: data T sh a
+ Numeric.GLPK.Monad: exact :: (Eq sh, Indexed sh, Index sh ~ ix) => Constraints ix -> (Direction, Objective sh) -> T sh (Solution sh)
+ Numeric.GLPK.Monad: instance GHC.Base.Applicative (Numeric.GLPK.Monad.T sh)
+ Numeric.GLPK.Monad: instance GHC.Base.Functor (Numeric.GLPK.Monad.T sh)
+ Numeric.GLPK.Monad: instance GHC.Base.Monad (Numeric.GLPK.Monad.T sh)
+ Numeric.GLPK.Monad: run :: (Indexed sh, Index sh ~ ix) => sh -> Bounds ix -> T sh a -> a
+ Numeric.GLPK.Monad: simplex :: (Eq sh, Indexed sh, Index sh ~ ix) => Constraints ix -> (Direction, Objective sh) -> T sh (Solution sh)
Files
- comfort-glpk.cabal +12/−4
- src/Numeric/GLPK.hs +227/−128
- src/Numeric/GLPK/Monad.hs +110/−0
- src/Numeric/GLPK/Private.hs +177/−0
- test/Main.hs +2/−0
- test/Test/Numeric/GLPK.hs +60/−14
- test/Test/Numeric/GLPK/Generator.hs +208/−0
- test/Test/Numeric/GLPK/Monad.hs +26/−0
comfort-glpk.cabal view
@@ -1,6 +1,6 @@ Cabal-Version: 2.2 Name: comfort-glpk-Version: 0.0.0.1+Version: 0.0.1 License: BSD-3-Clause License-File: LICENSE Author: Henning Thielemann <haskell@henning-thielemann.de>@@ -30,10 +30,10 @@ > printf "absol %f, pos %f, neg %f\n" absol pos neg > _ -> fail "GLPK solver failed" .- Alternatives: @hmatrix-glpk@, @glpk-hs@+ Alternatives: @coinor-clp@, @hmatrix-glpk@, @glpk-hs@ Source-Repository this- Tag: 0.0.0.1+ Tag: 0.0.1 Type: darcs Location: https://hub.darcs.net/thielema/comfort-glpk/ @@ -46,6 +46,7 @@ glpk-headers >=0.4.1 && <0.6, comfort-array >=0.4 && <0.6, deepseq >=1.3 && <1.5,+ transformers >=0.3 && <0.7, non-empty >=0.3.2 && <0.4, utility-ht >=0.0.16 && <0.1, base >=4.5 && <5@@ -56,16 +57,21 @@ Extra-Libraries: glpk Exposed-modules: Numeric.GLPK+ Numeric.GLPK.Monad+ Other-Modules:+ Numeric.GLPK.Private Test-Suite comfort-glpk-test Type: exitcode-stdio-1.0 Build-Depends: comfort-glpk, comfort-array >=0.5.2,- utility-ht,+ non-empty,+ utility-ht >=0.0.17, doctest-exitcode-stdio >=0.0 && <0.1, doctest-lib >=0.1 && <0.2, QuickCheck >=2.1 && <3,+ random >=1.0 && <1.3, base >=4.5 && <5 GHC-Options: -Wall@@ -73,4 +79,6 @@ Default-Language: Haskell98 Main-Is: Main.hs Other-Modules:+ Test.Numeric.GLPK.Generator+ Test.Numeric.GLPK.Monad Test.Numeric.GLPK
src/Numeric/GLPK.hs view
@@ -38,34 +38,49 @@ objectiveFromTerms, simplex, simplexMulti,+ simplexSuccessive, exact,+ exactMulti,+ exactSuccessive, interior, interiorMulti,+ interiorSuccessive,++ solveSuccessive,++ FormatIdentifier,+ formatMathProg, ) where import qualified Math.Programming.Glpk.Header as FFI+import Numeric.GLPK.Private import qualified Data.Array.Comfort.Storable.Mutable as Mutable import qualified Data.Array.Comfort.Storable as Array import qualified Data.Array.Comfort.Shape as Shape import qualified Data.NonEmpty as NonEmpty+import qualified Data.List as List import Data.Array.Comfort.Storable (Array) import Data.Tuple.HT (mapFst, mapSnd)-import Data.Maybe (fromMaybe)-import Data.Foldable (traverse_, for_)+import Data.Traversable (for)+import Data.Foldable (for_) -import Control.Monad (void)+import Text.Printf (printf)++import qualified Control.Monad.Trans.Except as ME+import qualified Control.Monad.Trans.State as MS+import Control.Monad (void, when) import Control.Applicative (liftA2) import Control.Exception (bracket)-import Control.DeepSeq (NFData, rnf) import System.IO.Unsafe (unsafePerformIO) import qualified Foreign import Foreign.Ptr (nullPtr)-import Foreign.C.Types (CDouble) + {- $setup+>>> import qualified Test.Numeric.GLPK.Generator as TestLP >>> import qualified Numeric.GLPK as LP >>> import Numeric.GLPK ((.*), (<=.), (==.)) >>>@@ -97,10 +112,7 @@ (.*) :: Double -> ix -> Term ix (.*) = Term -data Term ix = Term Double ix- deriving (Show) - infix 4 <=., >=., >=<., ==. (<=.), (>=.), (==.) :: x -> Double -> Inequality x@@ -114,88 +126,21 @@ free :: x -> Inequality x free x = Inequality x Free -data Inequality x = Inequality x Bound- deriving Show -data Bound =- LessEqual Double- | GreaterEqual Double- | Between Double Double- | Equal Double- | Free- deriving Show -instance Functor Inequality where- fmap f (Inequality x bnd) = Inequality (f x) bnd--data NoSolutionType =- Undefined- | NoFeasible- | Unbounded- deriving (Eq, Show)--data SolutionType =- Feasible- | Infeasible- | Optimal- deriving (Eq, Show)--instance NFData NoSolutionType where- rnf NoFeasible = ()- rnf _ = ()--instance NFData SolutionType where- rnf Optimal = ()- rnf _ = ()--type Solution sh =- Either NoSolutionType (SolutionType, (Double, Array sh Double))--type Constraints ix = [Inequality [Term ix]]--data Direction = Minimize | Maximize--type Objective sh = Array sh Double--type Bounds ix = [Inequality ix]--- objectiveFromTerms :: (Shape.Indexed sh, Shape.Index sh ~ ix) => sh -> [Term ix] -> Objective sh objectiveFromTerms sh = Array.fromAssociations 0 sh . map (\(Term x ix) -> (ix,x)) -prepareBounds :: Inequality a -> (a, (FFI.GlpkConstraintType, CDouble, CDouble))-prepareBounds (Inequality x bnd) =- (,) x $- (\(bndType,lo,up) -> (bndType, realToFrac lo, realToFrac up)) $- case bnd of- LessEqual up -> (FFI.glpkLT, 0, up)- GreaterEqual lo -> (FFI.glpkGT, lo, 0)- Between lo up -> (FFI.glpkBounded, lo, up)- Equal y -> (FFI.glpkFixed, y, y)- Free -> (FFI.glpkFree, 0, 0)--columnIndex :: (Shape.Indexed sh, Shape.Index sh ~ ix) => sh -> ix -> FFI.Column-columnIndex shape var = 1 + fromIntegral (Shape.offset shape var)--deferredColumnIndex :: Shape.DeferredIndex ix -> FFI.Column-deferredColumnIndex (Shape.DeferredIndex var) = 1 + fromIntegral var--allocaArray :: (Foreign.Storable a) => Int -> (FFI.GlpkArray a -> IO b) -> IO b-allocaArray n f = Foreign.allocaArray (n+1) $ f . FFI.GlpkArray--pokeElem :: (Foreign.Storable a) => FFI.GlpkArray a -> Int -> a -> IO ()-pokeElem (FFI.GlpkArray ptr) k a = Foreign.pokeElemOff ptr k a-- {- | >>> case Shape.indexTupleFromShape tripletShape of (x1,x2,x3) -> mapSnd (mapSnd Array.toTuple) <$> LP.simplex [] [[2.*x1, 1.*x2] <=. 10, [1.*x2, 5.*x3] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double) Right (Optimal,(28.0,(5.0,0.0,4.0))) +prop> \target -> case Shape.indexTupleFromShape pairShape of (pos,neg) -> case mapSnd (mapSnd Array.toTuple) <$> LP.simplex [] [[1.*pos, (-1).*neg] ==. target] (LP.Minimize, Array.fromTuple (1,1) :: Array.Array PairShape Double) of (Right (LP.Optimal,(absol,(posResult,negResult)))) -> QC.property (TestLP.approxReal 0.001 absol (abs target)) .&&. (posResult === 0 .||. negResult === 0); _ -> QC.property False prop> \(QC.Positive posWeight) (QC.Positive negWeight) target -> case Shape.indexTupleFromShape pairShape of (pos,neg) -> case mapSnd (mapSnd Array.toTuple) <$> LP.simplex [] [[1.*pos, (-1).*neg] ==. target] (LP.Minimize, Array.fromTuple (posWeight,negWeight) :: Array.Array PairShape Double) of (Right (LP.Optimal,(absol,(posResult,negResult)))) -> QC.property (absol>=0) .&&. (posResult === 0 .||. negResult === 0); _ -> QC.property False+prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjective origin) $ \(dir,obj) -> case LP.simplex bounds constrs (dir,obj) of Right (LP.Optimal, _) -> True; _ -> False -} simplex :: (Shape.Indexed sh, Shape.Index sh ~ ix) =>@@ -203,6 +148,9 @@ (Direction, Objective sh) -> Solution sh simplex = solve (flip FFI.glp_simplex nullPtr) +{-# DEPRECATED simplexMulti "use GLPK.Monad instead" #-}+{-# DEPRECATED exactMulti "use GLPK.Monad instead" #-}+{-# DEPRECATED interiorMulti "run 'interior' in Either monad instead" #-} {- | Optimize for one objective after another. That is, if the first optimization succeeds@@ -227,12 +175,19 @@ because an added objective can make the system infeasible due to rounding errors. E.g. a non-negative objective can become very small but negative.+++prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAllShrink (TestLP.genProblem origin) TestLP.shrinkProblem $ \(bounds, constrs) -> QC.forAllShrink (TestLP.genObjectives origin) TestLP.shrinkObjectives $ \objs -> case LP.simplexMulti bounds constrs (Array.shape origin) objs of (_, Right (LP.Optimal, _)) -> QC.property True; result -> QC.counterexample (show result) False++The same property fails for 'exactMulti' and 'interiorMulti'.+I guess, due to rounding errors. -}-simplexMulti, interiorMulti ::+simplexMulti, exactMulti, interiorMulti :: (Shape.Indexed sh, Shape.Index sh ~ ix) => Bounds ix -> Constraints ix -> sh -> NonEmpty.T [] (Direction, [Term ix]) -> ([Double], Solution sh) simplexMulti = solveMulti . simplex+exactMulti = solveMulti . exact interiorMulti = solveMulti . interior solveMulti ::@@ -245,16 +200,78 @@ mapFst (opt:) $ let extConstrs = (curObj==.opt) : constrs in go extConstrs obj objs $- solver extConstrs- (dir, objectiveFromTerms sh obj)+ solver extConstrs (dir, objectiveFromTerms sh obj) go _ _ _ sol = ([], sol) in go constrs0 (snd obj0) objs0 $ solver constrs0 $ mapSnd (objectiveFromTerms sh) obj0 +{-# DEPRECATED simplexSuccessive "use GLPK.Monad instead" #-}+{-# DEPRECATED exactSuccessive "use GLPK.Monad instead" #-}+{-# DEPRECATED interiorSuccessive "run 'interior' in Either monad instead" #-} {- |+Like the @Multi@ functions,+but allows not only to fix the previously+found optimal solution as constraint,+but allows constraints with a tolerance.+This is necessary in the presence of rounding errors.++prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAllShrink (TestLP.genProblem origin) TestLP.shrinkProblem $ \(bounds, constrs) -> QC.forAllShrink (TestLP.genObjectives origin) TestLP.shrinkObjectives $ \objs -> case uncurry (LP.simplexSuccessive bounds constrs) $ TestLP.successiveObjectives origin 0.01 objs of result -> QC.counterexample (show result) $ case result of Right results -> all (\r -> case r of (LP.Optimal, _) -> True; _ -> False) results; _ -> False+prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAllShrink (TestLP.genProblem origin) TestLP.shrinkProblem $ \(bounds, constrs) -> QC.forAllShrink (TestLP.genObjectives origin) TestLP.shrinkObjectives $ \objs -> case uncurry (LP.exactSuccessive bounds constrs) $ TestLP.successiveObjectives origin 0.01 objs of result -> QC.counterexample (show result) $ case result of Right results -> all (\r -> case r of (LP.Optimal, _) -> True; _ -> False) results; _ -> False+-}+simplexSuccessive, exactSuccessive, interiorSuccessive ::+ (Traversable f, Eq sh, Shape.Indexed sh, Shape.Index sh ~ ix) =>+ Bounds ix -> Constraints ix ->+ (Direction, Objective sh) ->+ f ((SolutionType, (Double, Array sh Double)) -> Constraints ix,+ (Direction, Objective sh)) ->+ Either NoSolutionType+ (NonEmpty.T f (SolutionType, (Double, Array sh Double)))+simplexSuccessive = solveSuccessiveInPlace (flip FFI.glp_simplex nullPtr)+exactSuccessive = solveSuccessiveInPlace (flip FFI.glp_exact nullPtr)+interiorSuccessive = solveSuccessive . interior++{-# DEPRECATED solveSuccessive+ "run simple solvers in GLPK.Monad or Either monad instead" #-}+{- |+Allows for generic implementation of 'simplexSuccessive' et.al.+without reuse of interim results.++prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjectives origin) $ (. TestLP.successiveObjectives origin 0.01) $ \(obj,objs) -> case (LP.simplexSuccessive bounds constrs obj objs, LP.solveSuccessive (LP.simplex bounds) constrs obj objs) of (resultA,resultB) -> TestLP.approxSuccession 0.01 resultA resultB+prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjectives origin) $ (. TestLP.successiveObjectives origin 0.01) $ \(obj,objs) -> case (LP.exactSuccessive bounds constrs obj objs, LP.solveSuccessive (LP.exact bounds) constrs obj objs) of (resultA,resultB) -> TestLP.approxSuccession 0.01 resultA resultB+-}+solveSuccessive ::+ (Traversable f, Eq sh, Shape.Indexed sh, Shape.Index sh ~ ix) =>+ (Constraints ix -> (Direction, Objective sh) -> Solution sh) ->+ Constraints ix ->+ (Direction, Objective sh) ->+ f ((SolutionType, (Double, Array sh Double)) -> Constraints ix,+ (Direction, Objective sh)) ->+ Either NoSolutionType+ (NonEmpty.T f (SolutionType, (Double, Array sh Double)))+solveSuccessive solver constrs0 obj0 objs = do+ let checkShape obj =+ if Array.shape (snd obj0) == Array.shape obj+ then obj+ else error "GLPK.solveSuccessive: objective shapes mismatch"+ let solveWithConstraints constrs problem =+ (\sol -> (sol, (constrs,sol))) <$> solver constrs problem+ (sol0,state0) <- solveWithConstraints constrs0 obj0+ NonEmpty.cons sol0 <$>+ MS.evalStateT+ (for objs $+ \(newConstrs,(dir,obj)) -> MS.StateT $ \(constrs,sol) ->+ solveWithConstraints+ (newConstrs sol ++ constrs)+ (dir, checkShape obj))+ state0+++{- | >>> case Shape.indexTupleFromShape tripletShape of (x1,x2,x3) -> mapSnd (mapSnd Array.toTuple) <$> LP.exact [] [[2.*x1, 1.*x2] <=. 10, [1.*x2, 5.*x3] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double) Right (Optimal,(28.0,(5.0,0.0,4.0)))++prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjective origin) $ \(dir,obj) -> case (LP.simplex bounds constrs (dir,obj), LP.exact bounds constrs (dir,obj)) of (Right (LP.Optimal, (optSimplex,_)), Right (LP.Optimal, (optExact,_))) -> TestLP.approx "optimum" 0.001 optSimplex optExact; _ -> QC.property False -} exact :: (Shape.Indexed sh, Shape.Index sh ~ ix) =>@@ -273,20 +290,60 @@ bracket FFI.glp_create_prob FFI.glp_delete_prob $ \lp -> do storeProblem bounds constrs (dir,obj) lp void $ solver lp- let examine =- liftA2 (,)- (realToFrac <$> FFI.glp_get_obj_val lp)- (readGLPArray (Array.shape obj) $ \arr ix ->- Mutable.write arr ix . realToFrac- =<< FFI.glp_get_col_prim lp (deferredColumnIndex ix))- status <- FFI.glp_get_status lp- either (return . Left) (\typ -> Right . (,) typ <$> examine) $- analyzeStatus status+ peekSimplexSolution (Array.shape obj) lp +{-# INLINE solveSuccessiveInPlace #-}+solveSuccessiveInPlace ::+ (Traversable f, Eq sh, Shape.Indexed sh, Shape.Index sh ~ ix) =>+ (Foreign.Ptr FFI.Problem -> IO FFI.GlpkSimplexStatus) ->+ Bounds ix -> Constraints ix ->+ (Direction, Objective sh) ->+ f ((SolutionType, (Double, Array sh Double)) -> Constraints ix,+ (Direction, Objective sh)) ->+ Either NoSolutionType+ (NonEmpty.T f (SolutionType, (Double, Array sh Double)))+solveSuccessiveInPlace solver bounds constrs0 (dir0,obj0) objs =+ unsafePerformIO $+ bracket FFI.glp_create_prob FFI.glp_delete_prob $ \lp -> ME.runExceptT $ do+ let shape = Array.shape obj0+ sol0 <- ME.ExceptT $ do+ storeProblem bounds constrs0 (dir0,obj0) lp+ void $ solver lp+ peekSimplexSolution shape lp+ NonEmpty.cons sol0 <$>+ MS.evalStateT+ (for objs $+ \(makeNewConstrs,(dir,obj)) -> MS.StateT $ \sol ->+ fmap (\sol1 -> (sol1, sol1)) $ ME.ExceptT $ do+ setDirection lp dir+ when (shape /= Array.shape obj) $+ error "GLPK.solveSuccessiveInplace: objective shapes mismatch"+ setObjective lp obj+ let newConstrs = makeNewConstrs sol+ newRow <- FFI.glp_add_rows lp $ fromIntegral $ length newConstrs+ for_ (zip [newRow..] (map prepareBounds newConstrs)) $+ \(row, (terms,(bnd,lo,up))) -> do+ FFI.glp_set_row_bnds lp row bnd lo up+ let numTerms = length terms+ allocaArray numTerms $ \indicesPtr ->+ allocaArray numTerms $ \coeffsPtr -> do+ for_ (zip [1..] terms) $+ \(k, Term c x) -> do+ pokeElem indicesPtr k (columnIndex shape x)+ pokeElem coeffsPtr k (realToFrac c)+ FFI.glp_set_mat_row lp row+ (fromIntegral numTerms) indicesPtr coeffsPtr+ void $ solver lp+ peekSimplexSolution shape lp)+ sol0 ++ {- | >>> case Shape.indexTupleFromShape tripletShape of (x1,x2,x3) -> mapSnd (mapPair (round3, Array.toTuple . Array.map round3)) <$> LP.interior [] [[2.*x1, 1.*x2] <=. 10, [1.*x2, 5.*x3] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double) Right (Optimal,(28.0,(5.0,0.0,4.0)))++prop> QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjective origin) $ \(dir,obj) -> case (LP.simplex bounds constrs (dir,obj), LP.interior bounds constrs (dir,obj)) of (Right (LP.Optimal, (optSimplex,_)), Right (LP.Optimal, (optExact,_))) -> TestLP.approx "optimum" 0.001 optSimplex optExact; _ -> QC.property False -} interior :: (Shape.Indexed sh, Shape.Index sh ~ ix) =>@@ -312,27 +369,19 @@ Bounds ix -> Constraints ix -> (Direction, Objective sh) -> Foreign.Ptr FFI.Problem -> IO () storeProblem bounds constrs (dir,obj) lp = do- let shape = Array.shape obj void $ FFI.glp_term_out FFI.glpkOff- FFI.glp_set_obj_dir lp $- case dir of- Minimize -> FFI.glpkMin- Maximize -> FFI.glpkMax+ let shape = Array.shape obj+ setDirection lp dir firstRow <- FFI.glp_add_rows lp $ fromIntegral $ length constrs for_ (zip [firstRow..] $ map prepareBounds constrs) $ \(row,(_x,(bnd,lo,up))) -> FFI.glp_set_row_bnds lp row bnd lo up- _firstCol <- FFI.glp_add_cols lp $ fromIntegral $ Shape.size shape- for_ (Shape.indices $ Shape.Deferred shape) $ \x ->- FFI.glp_set_col_bnds lp (deferredColumnIndex x) FFI.glpkGT 0 0- for_ (map prepareBounds bounds) $ \(x,(bnd,lo,up)) ->- FFI.glp_set_col_bnds lp (columnIndex shape x) bnd lo up- for_ (Array.toAssociations obj) $ \(x,c) ->- FFI.glp_set_obj_coef lp (columnIndex shape x) (realToFrac c)- let numRows = length $ concatMap (fst . prepareBounds) constrs- allocaArray numRows $ \ia ->- allocaArray numRows $ \ja ->- allocaArray numRows $ \ar -> do+ storeBounds lp shape bounds+ setObjective lp obj+ let numTerms = length $ concatMap (fst . prepareBounds) constrs+ allocaArray numTerms $ \ia ->+ allocaArray numTerms $ \ja ->+ allocaArray numTerms $ \ar -> do for_ (zip [1..] $ concat $ zipWith (map . (,)) [firstRow..] $ map (fst . prepareBounds) constrs) $@@ -340,28 +389,78 @@ pokeElem ia k row pokeElem ja k (columnIndex shape x) pokeElem ar k (realToFrac c)- FFI.glp_load_matrix lp (fromIntegral numRows) ia ja ar+ FFI.glp_load_matrix lp (fromIntegral numTerms) ia ja ar -{-# INLINE readGLPArray #-}-readGLPArray ::- (Shape.C sh, Foreign.Storable a, Num a) =>- sh ->- (Mutable.Array IO (Shape.Deferred sh) a ->- Shape.DeferredIndex sh -> IO ()) ->- IO (Array sh a)-readGLPArray shape act = do- let defShape = Shape.Deferred shape- arr <- Mutable.new defShape 0- traverse_ (act arr) (Shape.indices defShape)- Array.reshape shape <$> Mutable.freeze arr -analyzeStatus :: FFI.GlpkSolutionStatus -> Either NoSolutionType SolutionType-analyzeStatus status =- fromMaybe (error "glpk-solver: solution type unknown") $ lookup status $- (FFI.glpkUndefined, Left Undefined) :- (FFI.glpkFeasible, Right Feasible) :- (FFI.glpkInfeasible, Right Infeasible) :- (FFI.glpkNoFeasible, Left NoFeasible) :- (FFI.glpkOptimal, Right Optimal) :- (FFI.glpkUnbounded, Left Unbounded) :- []+++class FormatIdentifier ix where+ formatIdentifier :: ix -> String++instance FormatIdentifier Char where+ formatIdentifier x = [x]++instance FormatIdentifier c => FormatIdentifier [c] where+ formatIdentifier = concatMap formatIdentifier++instance FormatIdentifier Int where+ formatIdentifier = printf "x%d"++instance FormatIdentifier Integer where+ formatIdentifier = printf "x%d"+++formatBound :: (FormatIdentifier ix) => Inequality ix -> String+formatBound (Inequality ix bnd) =+ printf "var %s%s;" (formatIdentifier ix) $+ case bnd of+ LessEqual up -> printf ", <=%f" up+ GreaterEqual lo -> printf ", >=%f" lo+ Between lo up -> printf ", >=%f, <=%f" lo up+ Equal x -> printf ", =%f" x+ Free -> ""+++formatSum :: (FormatIdentifier ix) => [Term ix] -> String+formatSum [] = "0"+formatSum xs =+ let formatTerm (Term c ix) = printf "%f*%s" c (formatIdentifier ix) in+ List.intercalate "+" $ map formatTerm xs++formatConstraint :: (FormatIdentifier ix) => Inequality [Term ix] -> String+formatConstraint (Inequality terms bnd) =+ let sumStr = formatSum terms in+ case bnd of+ LessEqual up -> printf "%s <= %f" sumStr up+ GreaterEqual lo -> printf "%f <= %s" lo sumStr+ Between lo up -> printf "%f <= %s <= %f" lo sumStr up+ Equal x -> printf "%s = %f" sumStr x+ Free -> sumStr++formatDirection :: Direction -> String+formatDirection Minimize = "minimize"+formatDirection Maximize = "maximize"++formatObjective ::+ (Shape.Indexed sh, Shape.Index sh ~ ix, FormatIdentifier ix) =>+ Objective sh -> String+formatObjective =+ formatSum . map (\(ix,c) -> Term c ix) . Array.toAssociations++formatMathProg ::+ (Shape.Indexed sh, Shape.Index sh ~ ix, FormatIdentifier ix) =>+ Bounds ix -> Constraints ix ->+ (Direction, Objective sh) -> [String]+formatMathProg bounds constrs (dir,obj) =+ map formatBound bounds +++ "" :+ formatDirection dir :+ printf "value: %s;" (formatObjective obj) :+ "" :+ "subject to" :+ zipWith+ (\k constr -> printf "constr%d: %s;" k $ formatConstraint constr)+ [(0::Int)..] constrs +++ "" :+ "end;" :+ []
+ src/Numeric/GLPK/Monad.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{- |+The monadic interface to GLPK allows to optimize+with respect to multiple objectives, successively.+-}+module Numeric.GLPK.Monad (+ T,+ run,+ simplex,+ exact,+ Direction(..),+ ) where++import qualified Math.Programming.Glpk.Header as FFI+import Numeric.GLPK.Private+ (Term(Term), Constraints, Solution,+ allocaArray, pokeElem, columnIndex, prepareBounds, storeBounds,+ setDirection, setObjective, peekSimplexSolution)+import Numeric.GLPK (Bounds, Direction(..), Objective)++import qualified Data.Array.Comfort.Storable as Array+import qualified Data.Array.Comfort.Shape as Shape+import Data.Foldable (for_)++import qualified Control.Monad.Trans.Reader as MR+import Control.Monad.IO.Class (liftIO)+import Control.Monad (void, when)+import Control.Exception (bracket)++import System.IO.Unsafe (unsafePerformIO)++import Foreign.Ptr (Ptr, nullPtr)+++{- $setup+>>> import qualified Numeric.GLPK.Monad as LP+>>> import Test.Numeric.GLPK (TripletShape, tripletShape)+>>> import Numeric.GLPK ((.*), (<=.))+>>>+>>> import qualified Data.Array.Comfort.Storable as Array+>>> import qualified Data.Array.Comfort.Shape as Shape+>>>+>>> import Data.Tuple.HT (mapSnd)+-}+++newtype T sh a = Cons (MR.ReaderT (sh, Ptr FFI.Problem) IO a)+ deriving (Functor, Applicative, Monad)++run ::+ (Shape.Indexed sh, Shape.Index sh ~ ix) =>+ sh -> Bounds ix -> T sh a -> a+run shape bounds (Cons act) =+ unsafePerformIO $+ bracket FFI.glp_create_prob FFI.glp_delete_prob $ \lp -> do+ void $ FFI.glp_term_out FFI.glpkOff+ storeBounds lp shape bounds+ liftIO $ MR.runReaderT act (shape, lp)+++{- |+Add new constraints to an existing problem+and solve with a new direction and objective.++>>> case Shape.indexTupleFromShape tripletShape of (x,y,z) -> mapSnd (mapSnd Array.toTuple) <$> LP.run tripletShape [] (LP.simplex [[2.*x, 1.*y] <=. 10, [1.*y, (5::Double).*z] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double))+Right (Optimal,(28.0,(5.0,0.0,4.0)))+-}+simplex ::+ (Eq sh, Shape.Indexed sh, Shape.Index sh ~ ix) =>+ Constraints ix ->+ (Direction, Objective sh) -> T sh (Solution sh)+simplex = solve (flip FFI.glp_simplex nullPtr)++exact ::+ (Eq sh, Shape.Indexed sh, Shape.Index sh ~ ix) =>+ Constraints ix ->+ (Direction, Objective sh) -> T sh (Solution sh)+exact = solve (flip FFI.glp_exact nullPtr)++solve ::+ (Eq sh, Shape.Indexed sh, Shape.Index sh ~ ix) =>+ (Ptr FFI.Problem -> IO FFI.GlpkSimplexStatus) ->+ Constraints ix ->+ (Direction, Objective sh) ->+ T sh (Solution sh)+solve method constrs (dir,obj) = Cons $ do+ (shape, lp) <- MR.ask+ when (shape /= Array.shape obj) $+ error "GLPK.Monad.solve: objective shape mismatch"++ liftIO $ do+ setDirection lp dir+ setObjective lp obj+ newRow <- FFI.glp_add_rows lp $ fromIntegral $ length constrs+ for_ (zip [newRow..] (map prepareBounds constrs)) $+ \(row, (terms,(bnd,lo,up))) -> do+ FFI.glp_set_row_bnds lp row bnd lo up+ let numTerms = length terms+ allocaArray numTerms $ \indicesPtr ->+ allocaArray numTerms $ \coeffsPtr -> do+ for_ (zip [1..] terms) $+ \(k, Term c x) -> do+ pokeElem indicesPtr k (columnIndex shape x)+ pokeElem coeffsPtr k (realToFrac c)+ FFI.glp_set_mat_row lp row+ (fromIntegral numTerms) indicesPtr coeffsPtr+ void $ method lp+ peekSimplexSolution shape lp
+ src/Numeric/GLPK/Private.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+module Numeric.GLPK.Private where++import qualified Math.Programming.Glpk.Header as FFI++import qualified Data.Array.Comfort.Storable.Mutable as Mutable+import qualified Data.Array.Comfort.Storable as Array+import qualified Data.Array.Comfort.Shape as Shape+import Data.Array.Comfort.Storable (Array)+import Data.Maybe (fromMaybe)+import Data.Foldable (for_)++import Control.Applicative (liftA2)+import Control.DeepSeq (NFData, rnf)++import qualified Foreign+import Foreign.C.Types (CDouble)++++data Term ix = Term Double ix+ deriving (Show)+++data Inequality x = Inequality x Bound+ deriving Show++data Bound =+ LessEqual Double+ | GreaterEqual Double+ | Between Double Double+ | Equal Double+ | Free+ deriving Show++instance Functor Inequality where+ fmap f (Inequality x bnd) = Inequality (f x) bnd++type Bounds ix = [Inequality ix]++type Constraints ix = [Inequality [Term ix]]++data Direction = Minimize | Maximize+ deriving (Eq, Enum, Bounded, Show)++type Objective sh = Array sh Double+++data NoSolutionType =+ Undefined+ | NoFeasible+ | Unbounded+ deriving (Eq, Show)++data SolutionType =+ Feasible+ | Infeasible+ | Optimal+ deriving (Eq, Show)++instance NFData NoSolutionType where+ rnf NoFeasible = ()+ rnf _ = ()++instance NFData SolutionType where+ rnf Optimal = ()+ rnf _ = ()++type Solution sh =+ Either NoSolutionType (SolutionType, (Double, Array sh Double))+++{- |+@libglpk@ considers (Between x x) an error. @glpsol@ does not.+In handwritten problems, (Between x x) might indicate a mistake.+In automatically generated problems it will certainly not.+-}+canonicalizeBounds :: Inequality a -> Inequality a+canonicalizeBounds (Inequality x bnd) =+ Inequality x $+ case bnd of+ Between lo up -> if lo == up then Equal lo else bnd+ _ -> bnd++prepareBoundsFFI ::+ Inequality a -> (a, (FFI.GlpkConstraintType, CDouble, CDouble))+prepareBoundsFFI (Inequality x bnd) =+ (,) x $+ (\(bndType,lo,up) -> (bndType, realToFrac lo, realToFrac up)) $+ case bnd of+ LessEqual up -> (FFI.glpkLT, 0, up)+ GreaterEqual lo -> (FFI.glpkGT, lo, 0)+ Between lo up -> (FFI.glpkBounded, lo, up)+ Equal y -> (FFI.glpkFixed, y, y)+ Free -> (FFI.glpkFree, 0, 0)++prepareBounds ::+ Inequality a -> (a, (FFI.GlpkConstraintType, CDouble, CDouble))+prepareBounds = prepareBoundsFFI . canonicalizeBounds++storeBounds ::+ (Shape.Indexed sh, Shape.Index sh ~ ix) =>+ Foreign.Ptr FFI.Problem -> sh -> Bounds ix -> IO ()+storeBounds lp shape bounds = do+ _firstCol <- FFI.glp_add_cols lp $ fromIntegral $ Shape.size shape+ for_ (Shape.indices $ Shape.Deferred shape) $ \x ->+ FFI.glp_set_col_bnds lp (deferredColumnIndex x) FFI.glpkGT 0 0+ for_ (map prepareBounds bounds) $ \(x,(bnd,lo,up)) ->+ FFI.glp_set_col_bnds lp (columnIndex shape x) bnd lo up++++columnIndex :: (Shape.Indexed sh, Shape.Index sh ~ ix) => sh -> ix -> FFI.Column+columnIndex shape var = 1 + fromIntegral (Shape.offset shape var)++deferredColumnIndex :: Shape.DeferredIndex ix -> FFI.Column+deferredColumnIndex (Shape.DeferredIndex var) = 1 + fromIntegral var++allocaArray :: (Foreign.Storable a) => Int -> (FFI.GlpkArray a -> IO b) -> IO b+allocaArray n f = Foreign.allocaArray (n+1) $ f . FFI.GlpkArray++pokeElem :: (Foreign.Storable a) => FFI.GlpkArray a -> Int -> a -> IO ()+pokeElem (FFI.GlpkArray ptr) k a = Foreign.pokeElemOff ptr k a++++setDirection :: Foreign.Ptr FFI.Problem -> Direction -> IO ()+setDirection lp dir =+ FFI.glp_set_obj_dir lp $+ case dir of+ Minimize -> FFI.glpkMin+ Maximize -> FFI.glpkMax++setObjective ::+ (Shape.Indexed sh) => Foreign.Ptr FFI.Problem -> Objective sh -> IO ()+setObjective lp obj =+ for_ (Array.toAssociations obj) $ \(x,c) ->+ FFI.glp_set_obj_coef lp (columnIndex (Array.shape obj) x) (realToFrac c)++{-# INLINE readGLPArray #-}+readGLPArray ::+ (Shape.C sh, Foreign.Storable a, Num a) =>+ sh ->+ (Mutable.Array IO (Shape.Deferred sh) a ->+ Shape.DeferredIndex sh -> IO ()) ->+ IO (Array sh a)+readGLPArray shape act = do+ let defShape = Shape.Deferred shape+ arr <- Mutable.new defShape 0+ for_ (Shape.indices defShape) (act arr)+ Array.reshape shape <$> Mutable.freeze arr++analyzeStatus :: FFI.GlpkSolutionStatus -> Either NoSolutionType SolutionType+analyzeStatus status =+ fromMaybe (error "glpk-solver: solution type unknown") $ lookup status $+ (FFI.glpkUndefined, Left Undefined) :+ (FFI.glpkFeasible, Right Feasible) :+ (FFI.glpkInfeasible, Right Infeasible) :+ (FFI.glpkNoFeasible, Left NoFeasible) :+ (FFI.glpkOptimal, Right Optimal) :+ (FFI.glpkUnbounded, Left Unbounded) :+ []+++peekSimplexSolution ::+ (Shape.C sh) => sh -> Foreign.Ptr FFI.Problem -> IO (Solution sh)+peekSimplexSolution shape lp = do+ let examine =+ liftA2 (,)+ (realToFrac <$> FFI.glp_get_obj_val lp)+ (readGLPArray shape $ \arr ix ->+ Mutable.write arr ix . realToFrac+ =<< FFI.glp_get_col_prim lp (deferredColumnIndex ix))+ status <- FFI.glp_get_status lp+ either (return . Left) (\typ -> Right . (,) typ <$> examine) $+ analyzeStatus status
test/Main.hs view
@@ -2,9 +2,11 @@ module Main where import qualified Test.Numeric.GLPK+import qualified Test.Numeric.GLPK.Monad import qualified Test.DocTest.Driver as DocTest main :: IO () main = DocTest.run $ do Test.Numeric.GLPK.test+ Test.Numeric.GLPK.Monad.test
test/Test/Numeric/GLPK.hs view
@@ -1,12 +1,13 @@ -- Do not edit! Automatically created with doctest-extract from src/Numeric/GLPK.hs-{-# LINE 68 "src/Numeric/GLPK.hs" #-}+{-# LINE 82 "src/Numeric/GLPK.hs" #-} module Test.Numeric.GLPK where import Test.DocTest.Base import qualified Test.DocTest.Driver as DocTest -{-# LINE 69 "src/Numeric/GLPK.hs" #-}+{-# LINE 83 "src/Numeric/GLPK.hs" #-}+import qualified Test.Numeric.GLPK.Generator as TestLP import qualified Numeric.GLPK as LP import Numeric.GLPK ((.*), (<=.), (==.)) @@ -33,26 +34,71 @@ test :: DocTest.T () test = do- DocTest.printPrefix "Numeric.GLPK:198: "-{-# LINE 198 "src/Numeric/GLPK.hs" #-}+ DocTest.printPrefix "Numeric.GLPK:141: "+{-# LINE 141 "src/Numeric/GLPK.hs" #-} DocTest.property-{-# LINE 198 "src/Numeric/GLPK.hs" #-}+{-# LINE 141 "src/Numeric/GLPK.hs" #-}+ (\target -> case Shape.indexTupleFromShape pairShape of (pos,neg) -> case mapSnd (mapSnd Array.toTuple) <$> LP.simplex [] [[1.*pos, (-1).*neg] ==. target] (LP.Minimize, Array.fromTuple (1,1) :: Array.Array PairShape Double) of (Right (LP.Optimal,(absol,(posResult,negResult)))) -> QC.property (TestLP.approxReal 0.001 absol (abs target)) .&&. (posResult === 0 .||. negResult === 0); _ -> QC.property False)+ DocTest.printPrefix "Numeric.GLPK:142: "+{-# LINE 142 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 142 "src/Numeric/GLPK.hs" #-} (\(QC.Positive posWeight) (QC.Positive negWeight) target -> case Shape.indexTupleFromShape pairShape of (pos,neg) -> case mapSnd (mapSnd Array.toTuple) <$> LP.simplex [] [[1.*pos, (-1).*neg] ==. target] (LP.Minimize, Array.fromTuple (posWeight,negWeight) :: Array.Array PairShape Double) of (Right (LP.Optimal,(absol,(posResult,negResult)))) -> QC.property (absol>=0) .&&. (posResult === 0 .||. negResult === 0); _ -> QC.property False)- DocTest.printPrefix "Numeric.GLPK:195: "-{-# LINE 195 "src/Numeric/GLPK.hs" #-}+ DocTest.printPrefix "Numeric.GLPK:143: "+{-# LINE 143 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 143 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjective origin) $ \(dir,obj) -> case LP.simplex bounds constrs (dir,obj) of Right (LP.Optimal, _) -> True; _ -> False)+ DocTest.printPrefix "Numeric.GLPK:138: "+{-# LINE 138 "src/Numeric/GLPK.hs" #-} DocTest.example-{-# LINE 195 "src/Numeric/GLPK.hs" #-}+{-# LINE 138 "src/Numeric/GLPK.hs" #-} (case Shape.indexTupleFromShape tripletShape of (x1,x2,x3) -> mapSnd (mapSnd Array.toTuple) <$> LP.simplex [] [[2.*x1, 1.*x2] <=. 10, [1.*x2, 5.*x3] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double)) [ExpectedLine [LineChunk "Right (Optimal,(28.0,(5.0,0.0,4.0)))"]]- DocTest.printPrefix "Numeric.GLPK:256: "-{-# LINE 256 "src/Numeric/GLPK.hs" #-}+ DocTest.printPrefix "Numeric.GLPK:180: "+{-# LINE 180 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 180 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAllShrink (TestLP.genProblem origin) TestLP.shrinkProblem $ \(bounds, constrs) -> QC.forAllShrink (TestLP.genObjectives origin) TestLP.shrinkObjectives $ \objs -> case LP.simplexMulti bounds constrs (Array.shape origin) objs of (_, Right (LP.Optimal, _)) -> QC.property True; result -> QC.counterexample (show result) False)+ DocTest.printPrefix "Numeric.GLPK:219: "+{-# LINE 219 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 219 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAllShrink (TestLP.genProblem origin) TestLP.shrinkProblem $ \(bounds, constrs) -> QC.forAllShrink (TestLP.genObjectives origin) TestLP.shrinkObjectives $ \objs -> case uncurry (LP.simplexSuccessive bounds constrs) $ TestLP.successiveObjectives origin 0.01 objs of result -> QC.counterexample (show result) $ case result of Right results -> all (\r -> case r of (LP.Optimal, _) -> True; _ -> False) results; _ -> False)+ DocTest.printPrefix "Numeric.GLPK:220: "+{-# LINE 220 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 220 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAllShrink (TestLP.genProblem origin) TestLP.shrinkProblem $ \(bounds, constrs) -> QC.forAllShrink (TestLP.genObjectives origin) TestLP.shrinkObjectives $ \objs -> case uncurry (LP.exactSuccessive bounds constrs) $ TestLP.successiveObjectives origin 0.01 objs of result -> QC.counterexample (show result) $ case result of Right results -> all (\r -> case r of (LP.Optimal, _) -> True; _ -> False) results; _ -> False)+ DocTest.printPrefix "Numeric.GLPK:240: "+{-# LINE 240 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 240 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjectives origin) $ (. TestLP.successiveObjectives origin 0.01) $ \(obj,objs) -> case (LP.simplexSuccessive bounds constrs obj objs, LP.solveSuccessive (LP.simplex bounds) constrs obj objs) of (resultA,resultB) -> TestLP.approxSuccession 0.01 resultA resultB)+ DocTest.printPrefix "Numeric.GLPK:241: "+{-# LINE 241 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 241 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjectives origin) $ (. TestLP.successiveObjectives origin 0.01) $ \(obj,objs) -> case (LP.exactSuccessive bounds constrs obj objs, LP.solveSuccessive (LP.exact bounds) constrs obj objs) of (resultA,resultB) -> TestLP.approxSuccession 0.01 resultA resultB)+ DocTest.printPrefix "Numeric.GLPK:274: "+{-# LINE 274 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 274 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjective origin) $ \(dir,obj) -> case (LP.simplex bounds constrs (dir,obj), LP.exact bounds constrs (dir,obj)) of (Right (LP.Optimal, (optSimplex,_)), Right (LP.Optimal, (optExact,_))) -> TestLP.approx "optimum" 0.001 optSimplex optExact; _ -> QC.property False)+ DocTest.printPrefix "Numeric.GLPK:271: "+{-# LINE 271 "src/Numeric/GLPK.hs" #-} DocTest.example-{-# LINE 256 "src/Numeric/GLPK.hs" #-}+{-# LINE 271 "src/Numeric/GLPK.hs" #-} (case Shape.indexTupleFromShape tripletShape of (x1,x2,x3) -> mapSnd (mapSnd Array.toTuple) <$> LP.exact [] [[2.*x1, 1.*x2] <=. 10, [1.*x2, 5.*x3] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double)) [ExpectedLine [LineChunk "Right (Optimal,(28.0,(5.0,0.0,4.0)))"]]- DocTest.printPrefix "Numeric.GLPK:288: "-{-# LINE 288 "src/Numeric/GLPK.hs" #-}+ DocTest.printPrefix "Numeric.GLPK:346: "+{-# LINE 346 "src/Numeric/GLPK.hs" #-}+ DocTest.property+{-# LINE 346 "src/Numeric/GLPK.hs" #-}+ (QC.forAllShrink TestLP.genOrigin TestLP.shrinkOrigin $ \origin -> QC.forAll (TestLP.genProblem origin) $ \(bounds, constrs) -> QC.forAll (TestLP.genObjective origin) $ \(dir,obj) -> case (LP.simplex bounds constrs (dir,obj), LP.interior bounds constrs (dir,obj)) of (Right (LP.Optimal, (optSimplex,_)), Right (LP.Optimal, (optExact,_))) -> TestLP.approx "optimum" 0.001 optSimplex optExact; _ -> QC.property False)+ DocTest.printPrefix "Numeric.GLPK:343: "+{-# LINE 343 "src/Numeric/GLPK.hs" #-} DocTest.example-{-# LINE 288 "src/Numeric/GLPK.hs" #-}+{-# LINE 343 "src/Numeric/GLPK.hs" #-} (case Shape.indexTupleFromShape tripletShape of (x1,x2,x3) -> mapSnd (mapPair (round3, Array.toTuple . Array.map round3)) <$> LP.interior [] [[2.*x1, 1.*x2] <=. 10, [1.*x2, 5.*x3] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double)) [ExpectedLine [LineChunk "Right (Optimal,(28.0,(5.0,0.0,4.0)))"]]
+ test/Test/Numeric/GLPK/Generator.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE TypeFamilies #-}+module Test.Numeric.GLPK.Generator where++import qualified Numeric.GLPK as LP+import Numeric.GLPK ((<=.), (>=.))++import qualified Test.QuickCheck as QC+import System.Random (Random)++import qualified Data.Array.Comfort.Boxed as BoxedArray+import qualified Data.Array.Comfort.Storable as Array+import qualified Data.Array.Comfort.Shape as Shape+import qualified Data.NonEmpty as NonEmpty+import qualified Data.List.HT as ListHT+import qualified Data.Ix as Ix+import Data.Array.Comfort.Storable (Array, (!))+import Data.Traversable (sequenceA, for)+import Data.Tuple.HT (mapSnd)+import Data.Maybe (fromMaybe)+import Data.Int (Int64)++import Control.Applicative (liftA2)++import Text.Printf (PrintfArg, printf)++import Foreign.Storable (Storable)+++{- |+Generate constraints in the form of a polyhedron+which contains warrantedly the zero vector.+That is, there is an admissible solution.+In order to assert that the polyhedron is closed,+we bound all variables by a hypercube.+-}+genProblem ::+ (Shape.Indexed sh, Shape.Index sh ~ ix, Element a) =>+ Array sh a -> QC.Gen (LP.Bounds ix, LP.Constraints ix)+genProblem origin =+ liftA2 (,)+ (for (Array.toAssociations origin) $ \(ix,x) ->+ LP.Inequality ix <$>+ liftA2 LP.Between+ (doubleFromElement . (x+) <$> QC.choose (-100,-50))+ (doubleFromElement . (x+) <$> QC.choose (50,100)))+ (do+ numConstraints <- QC.choose (1,20)+ QC.vectorOf numConstraints $ do+ ixs <- QC.sublistOf $ Shape.indices $ Array.shape origin+ terms <- for ixs $ \ix -> do+ coeff <- QC.choose (-10,10)+ return (coeff, ix)+ let offset = scalarProduct terms origin+ let deviation = 25+ LP.Inequality+ (map (uncurry (LP.Term . doubleFromElement)) terms)+ <$>+ QC.oneof (+ (do bound <- QC.choose (offset-deviation, offset+deviation)+ return $+ if bound > offset+ then LP.LessEqual $ doubleFromElement bound+ else LP.GreaterEqual $ doubleFromElement bound) :+ (liftA2 LP.Between+ (doubleFromElement <$>+ QC.choose (offset-deviation, offset))+ (doubleFromElement <$>+ QC.choose (offset, offset+deviation))) :+ []))++scalarProduct ::+ (Shape.Indexed sh, Shape.Index sh ~ ix, Storable a, Num a) =>+ [(a,ix)] -> Array sh a -> a+scalarProduct terms origin =+ sum $ map (\(coeff, ix) -> coeff * origin!ix) terms++genVarShape :: QC.Gen (Shape.Range Char)+genVarShape = Shape.Range 'a' <$> QC.choose ('a','j')++genOrigin :: QC.Gen (Array (Shape.Range Char) Int64)+genOrigin = genVector =<< genVarShape++_genOrigin :: QC.Gen (Array (Shape.Range Char) Double)+_genOrigin = genVector =<< genVarShape+++shrinkVarShape :: Shape.Range Char -> [Shape.Range Char]+shrinkVarShape (Shape.Range from to) =+ if from<to then [Shape.Range from (pred to)] else []++shrinkOrigin ::+ (Storable a) => Array (Shape.Range Char) a -> [Array (Shape.Range Char) a]+shrinkOrigin vec =+ case Array.shape vec of+ Shape.Range from to ->+ if from<to+ then [Array.sample (Shape.Range from (pred to)) (vec!)]+ else []+++class (Storable a, Random a, Num a, Ord a) => Element a where+ doubleFromElement :: a -> Double++instance Element Double where+ doubleFromElement = id++instance Element Int64 where+ doubleFromElement = fromIntegral++genObjective ::+ (Shape.Indexed sh, Shape.Index sh ~ ix, Element a) =>+ Array sh a -> QC.Gen (LP.Direction, LP.Objective sh)+genObjective origin =+ liftA2 (,) QC.arbitraryBoundedEnum+ (fmap (Array.map doubleFromElement . flip asTypeOf origin) $+ genVector $ Array.shape origin)++genVector :: (Shape.Indexed sh, Element a) => sh -> QC.Gen (Array sh a)+genVector shape =+ fmap Array.fromBoxed $ sequenceA $+ BoxedArray.fromAssociations (QC.choose (-10,10)) shape []++shrinkProblem ::+ (LP.Bounds ix, LP.Constraints ix) ->+ [(LP.Bounds ix, LP.Constraints ix)]+shrinkProblem (bounds, constraints) =+ map (\shrinked -> (bounds, shrinked)) $+ filter (not . null) $ QC.shrinkList (const []) constraints++genObjectives ::+ (Shape.Indexed sh, Shape.Index sh ~ ix, Element a) =>+ Array sh a -> QC.Gen (NonEmpty.T [] (LP.Direction, [LP.Term ix]))+genObjectives origin = do+ let shape = Array.shape origin+ let stageRange :: (Int,Int)+ stageRange = (0,3)+ stages <- for (Shape.indices shape) $ \ix -> (,) ix <$> QC.choose stageRange+ let varSets =+ fromMaybe (error "there should be at least one stage") $+ NonEmpty.fetch $+ filter (not . null) $+ map (\k -> map fst $ filter ((k==) . snd) stages) $+ Ix.range stageRange+ let asTypeOfElement :: a -> f a -> a+ asTypeOfElement = const+ for varSets $ \varSet ->+ liftA2 (,)+ QC.arbitraryBoundedEnum+ (for varSet $ \ix ->+ flip LP.Term ix . doubleFromElement+ <$> QC.choose (-10, 10 `asTypeOfElement` origin))++shrinkObjectives ::+ NonEmpty.T [] (LP.Direction, [LP.Term ix]) ->+ [NonEmpty.T [] (LP.Direction, [LP.Term ix])]+shrinkObjectives (NonEmpty.Cons obj objs) =+ map (NonEmpty.Cons obj) $+ QC.shrinkList+ (\(dir,terms) ->+ map ((,) dir) $ filter (not . null) $+ QC.shrinkList (const []) terms)+ objs++successiveObjectives ::+ (Shape.Indexed sh, Shape.Index sh ~ ix) =>+ Array sh a -> Double ->+ NonEmpty.T [] (LP.Direction, [LP.Term ix]) ->+ ((LP.Direction, LP.Objective sh),+ [((LP.SolutionType, (Double, Array sh Double)) -> LP.Constraints ix,+ (LP.Direction, LP.Objective sh))])+successiveObjectives origin tol xs =+ let shape = Array.shape origin in+ (mapSnd (LP.objectiveFromTerms shape) $ NonEmpty.head xs,+ NonEmpty.mapAdjacent+ (\(dir0,obj0) y1 ->+ (\(_sol,(opt,_vec)) ->+ case dir0 of+ LP.Minimize -> [obj0 <=. opt + tol]+ LP.Maximize -> [obj0 >=. opt - tol],+ mapSnd (LP.objectiveFromTerms shape) y1))+ xs)+++approxReal :: (Ord a, Num a) => a -> a -> a -> Bool+approxReal tol x y = abs (x-y) <= tol++approx :: (PrintfArg a, Ord a, Num a) => String -> a -> a -> a -> QC.Property+approx name tol x y =+ QC.counterexample (printf "%s: %f - %f" name x y) (approxReal tol x y)++approxSuccession ::+ (Shape.C sh, Show sh, Show a, Ord a, Num a, Storable a) =>+ a ->+ Either LP.NoSolutionType+ (NonEmpty.T [] (LP.SolutionType, (a, Array sh a))) ->+ Either LP.NoSolutionType+ (NonEmpty.T [] (LP.SolutionType, (a, Array sh a))) ->+ QC.Property+approxSuccession tol x y =+ QC.counterexample (show x) $+ QC.counterexample (show y) $+ case (x,y) of+ (Left sx, Left sy) -> sx==sy+ (Right (NonEmpty.Cons xh xs), Right (NonEmpty.Cons yh ys)) ->+ let equalSol (solX, (optX, _)) (solY, (optY, _)) =+ solX == solY && approxReal tol optX optY+ in equalSol xh yh && ListHT.equalWith equalSol xs ys+ _ -> False
+ test/Test/Numeric/GLPK/Monad.hs view
@@ -0,0 +1,26 @@+-- Do not edit! Automatically created with doctest-extract from src/Numeric/GLPK/Monad.hs+{-# LINE 37 "src/Numeric/GLPK/Monad.hs" #-}++module Test.Numeric.GLPK.Monad where++import Test.DocTest.Base+import qualified Test.DocTest.Driver as DocTest++{-# LINE 38 "src/Numeric/GLPK/Monad.hs" #-}+import qualified Numeric.GLPK.Monad as LP+import Test.Numeric.GLPK (TripletShape, tripletShape)+import Numeric.GLPK ((.*), (<=.))++import qualified Data.Array.Comfort.Storable as Array+import qualified Data.Array.Comfort.Shape as Shape++import Data.Tuple.HT (mapSnd)++test :: DocTest.T ()+test = do+ DocTest.printPrefix "Numeric.GLPK.Monad:67: "+{-# LINE 67 "src/Numeric/GLPK/Monad.hs" #-}+ DocTest.example+{-# LINE 67 "src/Numeric/GLPK/Monad.hs" #-}+ (case Shape.indexTupleFromShape tripletShape of (x,y,z) -> mapSnd (mapSnd Array.toTuple) <$> LP.run tripletShape [] (LP.simplex [[2.*x, 1.*y] <=. 10, [1.*y, (5::Double).*z] <=. 20] (LP.Maximize, Array.fromTuple (4,-3,2) :: Array.Array TripletShape Double)))+ [ExpectedLine [LineChunk "Right (Optimal,(28.0,(5.0,0.0,4.0)))"]]