packages feed

math-programming-glpk 0.4.1 → 0.5.0

raw patch · 11 files changed

+614/−541 lines, 11 filesdep +hspecdep +hspec-coredep +transformersdep −containersdep −tastydep −tasty-discoverdep ~basedep ~glpk-headersdep ~math-programmingPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, hspec-core, transformers, unliftio

Dependencies removed: containers, tasty, tasty-discover, tasty-hunit

Dependency ranges changed: base, glpk-headers, math-programming, math-programming-tests, mtl, text

API changes (from Hackage documentation)

- Math.Programming.Glpk: data Glpk a
- Math.Programming.Glpk: data GlpkError
- Math.Programming.Glpk.Internal: Glpk :: ExceptT GlpkError (ReaderT GlpkEnv IO) a -> Glpk a
- Math.Programming.Glpk.Internal: GlpkEnv :: Ptr Problem -> IORef [GlpkVariable] -> IORef [GlpkConstraint] -> IORef SimplexMethodControlParameters -> IORef (MIPControlParameters Void) -> IORef (Maybe SolveType) -> GlpkEnv
- Math.Programming.Glpk.Internal: InteriorPoint :: SolveType
- Math.Programming.Glpk.Internal: LP :: SolveType
- Math.Programming.Glpk.Internal: MIP :: SolveType
- Math.Programming.Glpk.Internal: NamedRef :: Int -> IORef a -> NamedRef a
- Math.Programming.Glpk.Internal: UnknownCode :: String -> CInt -> GlpkError
- Math.Programming.Glpk.Internal: UnknownVariable :: GlpkVariable -> GlpkError
- Math.Programming.Glpk.Internal: [_glpkConstraints] :: GlpkEnv -> IORef [GlpkConstraint]
- Math.Programming.Glpk.Internal: [_glpkEnvProblem] :: GlpkEnv -> Ptr Problem
- Math.Programming.Glpk.Internal: [_glpkLastSolveType] :: GlpkEnv -> IORef (Maybe SolveType)
- Math.Programming.Glpk.Internal: [_glpkMIPControl] :: GlpkEnv -> IORef (MIPControlParameters Void)
- Math.Programming.Glpk.Internal: [_glpkSimplexControl] :: GlpkEnv -> IORef SimplexMethodControlParameters
- Math.Programming.Glpk.Internal: [_glpkVariables] :: GlpkEnv -> IORef [GlpkVariable]
- Math.Programming.Glpk.Internal: [_runGlpk] :: Glpk a -> ExceptT GlpkError (ReaderT GlpkEnv IO) a
- Math.Programming.Glpk.Internal: [namedRefId] :: NamedRef a -> Int
- Math.Programming.Glpk.Internal: [namedRefRef] :: NamedRef a -> IORef a
- Math.Programming.Glpk.Internal: addConstraint' :: Inequality (LinearExpression Double (Variable Glpk)) -> Glpk (Constraint Glpk)
- Math.Programming.Glpk.Internal: addObjective' :: LinearExpression Double (Variable Glpk) -> Glpk (Objective Glpk)
- Math.Programming.Glpk.Internal: addVariable' :: Glpk (Variable Glpk)
- Math.Programming.Glpk.Internal: askConstraintsRef :: Glpk (IORef [GlpkConstraint])
- Math.Programming.Glpk.Internal: askProblem :: Glpk (Ptr Problem)
- Math.Programming.Glpk.Internal: askVariablesRef :: Glpk (IORef [GlpkVariable])
- Math.Programming.Glpk.Internal: data GlpkEnv
- Math.Programming.Glpk.Internal: data GlpkError
- Math.Programming.Glpk.Internal: data NamedRef a
- Math.Programming.Glpk.Internal: data SolveType
- Math.Programming.Glpk.Internal: getConstraintName' :: Constraint Glpk -> Glpk String
- Math.Programming.Glpk.Internal: getDualValue' :: Constraint Glpk -> Glpk Double
- Math.Programming.Glpk.Internal: getObjectiveName' :: Objective Glpk -> Glpk String
- Math.Programming.Glpk.Internal: getObjectiveSense' :: Objective Glpk -> Glpk Sense
- Math.Programming.Glpk.Internal: getObjectiveValue' :: Objective Glpk -> Glpk Double
- Math.Programming.Glpk.Internal: getRelativeMIPGap' :: RealFrac a => Glpk a
- Math.Programming.Glpk.Internal: getTimeout' :: RealFrac a => Glpk a
- Math.Programming.Glpk.Internal: getVariableBounds' :: Variable Glpk -> Glpk (Bounds Double)
- Math.Programming.Glpk.Internal: getVariableDomain' :: Variable Glpk -> Glpk Domain
- Math.Programming.Glpk.Internal: getVariableName' :: Variable Glpk -> Glpk String
- Math.Programming.Glpk.Internal: getVariableValue' :: Variable Glpk -> Glpk Double
- Math.Programming.Glpk.Internal: instance Control.Monad.Error.Class.MonadError Math.Programming.Glpk.Internal.GlpkError Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance Control.Monad.IO.Class.MonadIO Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance Control.Monad.Reader.Class.MonadReader Math.Programming.Glpk.Internal.GlpkEnv Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance GHC.Base.Applicative Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance GHC.Base.Functor Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance GHC.Base.Monad Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance GHC.Classes.Eq (Math.Programming.Glpk.Internal.NamedRef a)
- Math.Programming.Glpk.Internal: instance GHC.Classes.Eq (Math.Programming.Types.Constraint Math.Programming.Glpk.Internal.Glpk)
- Math.Programming.Glpk.Internal: instance GHC.Classes.Eq (Math.Programming.Types.Variable Math.Programming.Glpk.Internal.Glpk)
- Math.Programming.Glpk.Internal: instance GHC.Classes.Ord (Math.Programming.Glpk.Internal.NamedRef a)
- Math.Programming.Glpk.Internal: instance GHC.Classes.Ord (Math.Programming.Types.Constraint Math.Programming.Glpk.Internal.Glpk)
- Math.Programming.Glpk.Internal: instance GHC.Classes.Ord (Math.Programming.Types.Variable Math.Programming.Glpk.Internal.Glpk)
- Math.Programming.Glpk.Internal: instance GHC.Show.Show (Math.Programming.Glpk.Internal.NamedRef a)
- Math.Programming.Glpk.Internal: instance GHC.Show.Show (Math.Programming.Types.Constraint Math.Programming.Glpk.Internal.Glpk)
- Math.Programming.Glpk.Internal: instance GHC.Show.Show (Math.Programming.Types.Variable Math.Programming.Glpk.Internal.Glpk)
- Math.Programming.Glpk.Internal: instance GHC.Show.Show Math.Programming.Glpk.Internal.GlpkError
- Math.Programming.Glpk.Internal: instance Math.Programming.Types.IPMonad Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: instance Math.Programming.Types.LPMonad Math.Programming.Glpk.Internal.Glpk
- Math.Programming.Glpk.Internal: maxCDouble :: CDouble
- Math.Programming.Glpk.Internal: newtype Glpk a
- Math.Programming.Glpk.Internal: optimizeIP' :: Glpk SolutionStatus
- Math.Programming.Glpk.Internal: optimizeLP' :: Glpk SolutionStatus
- Math.Programming.Glpk.Internal: readColumn :: Variable Glpk -> Glpk Column
- Math.Programming.Glpk.Internal: readRow :: Constraint Glpk -> Glpk Row
- Math.Programming.Glpk.Internal: register :: Glpk (IORef [NamedRef a]) -> NamedRef a -> Glpk ()
- Math.Programming.Glpk.Internal: removeConstraint' :: Constraint Glpk -> Glpk ()
- Math.Programming.Glpk.Internal: removeVariable' :: Variable Glpk -> Glpk ()
- Math.Programming.Glpk.Internal: runGlpk :: Glpk a -> IO (Either GlpkError a)
- Math.Programming.Glpk.Internal: setConstraintName' :: Constraint Glpk -> String -> Glpk ()
- Math.Programming.Glpk.Internal: setObjectiveName' :: Objective Glpk -> String -> Glpk ()
- Math.Programming.Glpk.Internal: setObjectiveSense' :: Objective Glpk -> Sense -> Glpk ()
- Math.Programming.Glpk.Internal: setRelativeMIPGap' :: RealFrac a => a -> Glpk ()
- Math.Programming.Glpk.Internal: setTimeout' :: RealFrac a => a -> Glpk ()
- Math.Programming.Glpk.Internal: setVariableBounds' :: Variable Glpk -> Bounds Double -> Glpk ()
- Math.Programming.Glpk.Internal: setVariableDomain' :: Variable Glpk -> Domain -> Glpk ()
- Math.Programming.Glpk.Internal: setVariableName' :: Variable Glpk -> String -> Glpk ()
- Math.Programming.Glpk.Internal: solutionStatus :: GlpkSolutionStatus -> SolutionStatus
- Math.Programming.Glpk.Internal: type GlpkConstraint = NamedRef Row
- Math.Programming.Glpk.Internal: type GlpkVariable = NamedRef Column
- Math.Programming.Glpk.Internal: unregister :: Enum a => Glpk (IORef [NamedRef a]) -> NamedRef a -> Glpk ()
- Math.Programming.Glpk.Internal: writeFormulation :: FilePath -> Glpk ()
+ Math.Programming.Glpk: class (MonadLP GlpkVariable GlpkConstraint GlpkObjective m, MonadIP GlpkVariable GlpkConstraint GlpkObjective m) => MonadGlpk m
+ Math.Programming.Glpk: data GlpkException
+ Math.Programming.Glpk: data GlpkObjective
+ Math.Programming.Glpk: data GlpkT m a
+ Math.Programming.Glpk: type Glpk = GlpkT IO
+ Math.Programming.Glpk: type GlpkConstraint = GlpkPtr Row
+ Math.Programming.Glpk: type GlpkVariable = GlpkPtr Column
- Math.Programming.Glpk: runGlpk :: Glpk a -> IO (Either GlpkError a)
+ Math.Programming.Glpk: runGlpk :: Glpk a -> IO a
- Math.Programming.Glpk: writeFormulation :: FilePath -> Glpk ()
+ Math.Programming.Glpk: writeFormulation :: MonadGlpk m => FilePath -> m ()

Files

ChangeLog.md view
@@ -1,4 +1,12 @@-# Changelog for math-programming-glpk+# Changelog for `math-programming-glpk`++## [0.5.0] -- 18 January 2023++### Changed++- Make the `Glpk` monad work with threaded runtimes. The underlying+  library requires that GLPK environments be accessed from a single+  thread, so we run the monad in bound threads.  ## [0.4.1] -- 5 July 2020 
LICENSE view
@@ -1,4 +1,4 @@-Copyright Patrick Steele (c) 2018+Copyright (c) 2018-2023, Patrick Steele  All rights reserved. 
− README.md
@@ -1,5 +0,0 @@-# math-programming-glpk: A GLPK backend to the math-programming library--This package provides an interface to the-[GLPK](https://www.gnu.org/software/glpk/) LP and MIP solver for the-[`math-programming`](https://github.com/prsteele/math-programming) package.
math-programming-glpk.cabal view
@@ -1,65 +1,47 @@-cabal-version:      1.12-name:               math-programming-glpk-version:            0.4.1-license:            BSD3-license-file:       LICENSE-copyright:          2018 Patrick Steele-maintainer:         steele.pat@gmail.com-author:             Patrick Steele-homepage:           https://github.com/prsteele/math-programming-glpk#readme-bug-reports:        https://github.com/prsteele/math-programming-glpk/issues-synopsis:           A GLPK backend to the math-programming library.-description:-    Please see the README on GitHub at <https://github.com/prsteele/math-programming-glpk#readme>--category:           Math-build-type:         Simple-extra-source-files:-    README.md-    ChangeLog.md+cabal-version:       2.4+name:                math-programming-glpk+version:             0.5.0+synopsis:            A GLPK backend to the math-programming library.+description:         Please see the <https://github.com/prsteele/math-programming/blob/main/README.md README on GitHub>.+bug-reports:         https://github.com/prsteele/math-programming/issues+license:             BSD-3-Clause+license-file:        LICENSE+author:              Patrick Steele+maintainer:          steele.pat@gmail.com+copyright:           2018-2023, Patrick Steele+category:            Math+build-type:          Simple+extra-source-files:  ChangeLog.md  source-repository head-    type:     git-    location: https://github.com/prsteele/math-programming-glpk+  type:     git+  location: https://github.com/prsteele/math-programming  library-    exposed-modules:-        Math.Programming.Glpk-        Math.Programming.Glpk.Internal--    hs-source-dirs:   src-    other-modules:    Paths_math_programming_glpk-    default-language: Haskell2010-    ghc-options:      -Wall-    build-depends:-        base >=4.7 && <5,-        containers >=0.6.0.1 && <0.7,-        glpk-headers >=0.4.0 && <0.5,-        math-programming >=0.4.0 && <0.5,-        mtl >=2.2.2 && <2.3,-        text >=1.2.3.1 && <1.3--test-suite math-programming-glpk-test-    type:             exitcode-stdio-1.0-    main-is:          Driver.hs-    hs-source-dirs:   test-    other-modules:-        ApiTests-        RegressionTests-        Paths_math_programming_glpk+  default-language:  Haskell2010+  hs-source-dirs:    src+  exposed-modules:   Math.Programming.Glpk+  other-modules:     Math.Programming.Glpk.Internal+  ghc-options:       -Wall+  build-depends:     base             <5+                   , glpk-headers     ^>=0.5+                   , math-programming ^>=0.5+                   , mtl              ^>=2.2+                   , text             ^>=1.2+                   , transformers     ^>=0.5+                   , unliftio         ^>=0.2 -    default-language: Haskell2010-    extra-libraries:  glpk-    ghc-options:      -Wall-    build-depends:-        base >=4.7 && <5,-        containers >=0.6.0.1 && <0.7,-        glpk-headers >=0.4.0 && <0.5,-        math-programming >=0.4.0 && <0.5,-        math-programming-glpk -any,-        math-programming-tests >=0.4.0 && <0.5,-        mtl >=2.2.2 && <2.3,-        tasty >=1.2.3 && <1.3,-        tasty-discover >=4.2.1 && <4.3,-        tasty-hunit >=0.10.0.2 && <0.11,-        text >=1.2.3.1 && <1.3+test-suite math-programming-glpk-test-suite+  default-language:  Haskell2010+  type:              exitcode-stdio-1.0+  main-is:           Spec.hs+  other-modules:     Math.Programming.GlpkSpec+  hs-source-dirs:    test+  ghc-options:       -threaded -with-rtsopts=-N -Wall+  build-depends:     math-programming-glpk+                   , base                   <5+                   , math-programming       ^>=0.5+                   , math-programming-tests ^>=0.5+                   , unliftio               ^>=0.2+                   , hspec                  ^>=2.8+                   , hspec-core             ^>=2.8
src/Math/Programming/Glpk.hs view
@@ -2,17 +2,23 @@ -- -- This package allows both linear and mixed-integer programs to be solved. module Math.Programming.Glpk-  ( Glpk-  , runGlpk-  , writeFormulation-  -- ** Controlling GLPK behavior-  -- $settings-  , GlpkEnv-  , GlpkError+  ( Glpk,+    GlpkVariable,+    GlpkConstraint,+    GlpkObjective,+    runGlpk,+    writeFormulation,+    GlpkT,+    MonadGlpk,++    -- ** Controlling GLPK behavior+    -- $settings+    GlpkEnv,+    GlpkException,   )-  where+where -import           Math.Programming.Glpk.Internal+import Math.Programming.Glpk.Internal  -- $settings --
src/Math/Programming/Glpk/Internal.hs view
@@ -1,466 +1,543 @@-{-| This module contains the full definitions backing the simplified API-exposed in 'Math.Programming.Glpk'.--}-{-# LANGUAGE DeriveFunctor              #-}-{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE TypeFamilies               #-}-{-# LANGUAGE TypeSynonymInstances       #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}++-- | This module contains the full definitions backing the simplified API+-- exposed in 'Math.Programming.Glpk'. module Math.Programming.Glpk.Internal where -import           Control.Exception-import           Control.Monad-import           Control.Monad.Except-import           Control.Monad.Reader-import           Data.IORef-import           Data.List-import           Data.Typeable-import           Data.Void-import           Foreign.C.String-import           Foreign.C.Types-import           Foreign.Marshal.Alloc-import           Foreign.Ptr-import           Foreign.Storable+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Trans.Reader+import Control.Monad.Trans.State+import Data.Functor+import qualified Data.Text as T+import Data.Typeable+import Data.Void+import Foreign.C.String+import Foreign.C.Types+import Foreign.Marshal.Alloc+import Foreign.Ptr+import Foreign.Storable+import Math.Programming+import Math.Programming.Glpk.Header+import UnliftIO+import UnliftIO.Concurrent -import           Math.Programming-import           Math.Programming.Glpk.Header+-- | A reference to a GLPK variable.+type GlpkVariable = GlpkPtr Column --- | An environment to solve math programs using GLPK.-newtype Glpk a = Glpk { _runGlpk :: ExceptT GlpkError (ReaderT GlpkEnv IO) a }+-- | A reference to a GLPK constraint.+type GlpkConstraint = GlpkPtr Row++-- | A placeholder for an objective.+--+-- GLPK supports only single-objective problems, and so no indices+-- need to be stored.+newtype GlpkObjective = GlpkObjective ()++class (MonadLP GlpkVariable GlpkConstraint GlpkObjective m, MonadIP GlpkVariable GlpkConstraint GlpkObjective m) => MonadGlpk m where+  writeFormulation :: FilePath -> m ()++-- | An interface to the low-level GLPK API.+--+-- High-level solver settings can be modified by altering the+-- 'SimplexMethodControlParameters' and 'MIPControlParameters' values+-- for LP and IP solves, respectively.+data GlpkEnv = GlpkEnv+  { -- | A pointer to the Problem object. Most GLPK routines take this+    -- as the first argument.+    _glpkEnvProblem :: Ptr Problem,+    -- | The variables in the model+    _glpkVariables :: IORef [GlpkVariable],+    -- | The next unique ID to assign to a variable.+    _glpkNextVariableId :: IORef Integer,+    -- | The constraints in the model+    _glpkConstraints :: IORef [GlpkConstraint],+    -- | The next unique ID to assign to a variable.+    _glpkNextConstraintId :: IORef Integer,+    -- | The control parameters for the simplex method+    _glpkSimplexControl :: IORef SimplexMethodControlParameters,+    -- | The control parameters for the MIP solver+    _glpkMIPControl :: IORef (MIPControlParameters Void),+    -- | The type of the last solve. This is needed to know whether to+    -- retrieve simplex, interior point, or MIP solutions.+    _glpkLastSolveType :: IORef (Maybe SolveType)+  }++-- | A pointer to a GLPK row or column.+--+-- We assign an immutable unique value to each 'GlpkPtr' we create.+--+-- Internally, GLPK refers to variables and constraints by their+-- column and row indices, respectively. These indices can change when+-- rows and columns are deleted, so we update this value as necessary.+data GlpkPtr a = GlpkPtr+  { -- | An immutable, unique value associated with this pointer.+    _glpkPtrId :: Integer,+    -- | The referenced object.+    _glpkPtrRef :: IORef a,+    -- | Whether this reference has been deleted from the problem.+    _glpkPtrDeleted :: IORef Bool+  }++instance Eq (GlpkPtr a) where+  (GlpkPtr x _ _) == (GlpkPtr y _ _) = x == y++instance Ord (GlpkPtr a) where+  compare (GlpkPtr x _ _) (GlpkPtr y _ _) = compare x y++-- | An error that GLPK can encounter.+data GlpkException+  = UnknownVariable+  | UnknownCode T.Text CInt+  | GlpkFailure T.Text   deriving-    ( Functor-    , Applicative-    , Monad-    , MonadIO-    , MonadReader GlpkEnv-    , MonadError GlpkError+    ( Show,+      Typeable     ) -instance LPMonad Glpk where-  type Numeric Glpk = Double--  data Variable Glpk-    = Variable { fromVariable :: GlpkVariable }-    deriving-      ( Eq-      , Ord-      , Show-      )+instance Exception GlpkException -  data Constraint Glpk-    = Constraint { fromConstraint :: GlpkConstraint }-    deriving-      ( Eq-      , Ord-      , Show-      )+-- | An environment to solve math programs using GLPK.+newtype GlpkT m a = GlpkT {_runGlpk :: ReaderT GlpkEnv m a}+  deriving+    ( Functor,+      Applicative,+      Monad,+      MonadIO,+      MonadUnliftIO,+      MonadTrans+    ) -  data Objective Glpk = Objective+type Glpk = GlpkT IO +instance MonadLP GlpkVariable GlpkConstraint GlpkObjective Glpk where   addVariable = addVariable'-  removeVariable = removeVariable'+  deleteVariable = deleteVariable'   getVariableName = getVariableName'   setVariableName = setVariableName'+  getVariableValue = getVariableValue'   getVariableBounds = getVariableBounds'   setVariableBounds = setVariableBounds'-  getVariableValue = getVariableValue'+   addConstraint = addConstraint'-  removeConstraint = removeConstraint'+  deleteConstraint = deleteConstraint'   getConstraintName = getConstraintName'   setConstraintName = setConstraintName'-  getDualValue = getDualValue'+  getConstraintValue = getDualValue+   addObjective = addObjective'+  deleteObjective = deleteObjective'   getObjectiveName = getObjectiveName'   setObjectiveName = setObjectiveName'-  getObjectiveSense = getObjectiveSense'-  setObjectiveSense = setObjectiveSense'   getObjectiveValue = getObjectiveValue'+  getObjectiveSense = getSense'+  setObjectiveSense = setSense'+   getTimeout = getTimeout'   setTimeout = setTimeout'   optimizeLP = optimizeLP' -instance IPMonad Glpk where-  optimizeIP = optimizeIP'+instance MonadIP GlpkVariable GlpkConstraint GlpkObjective Glpk where   getVariableDomain = getVariableDomain'   setVariableDomain = setVariableDomain'   getRelativeMIPGap = getRelativeMIPGap'   setRelativeMIPGap = setRelativeMIPGap'+  optimizeIP = optimizeIP' -runGlpk :: Glpk a -> IO (Either GlpkError a)-runGlpk glpk = do-  -- Turn off terminal output. If we don't, users won't be able to-  -- inhibit terminal output generated from our setup.-  _ <- glp_term_out glpkOff+instance MonadGlpk Glpk where+  writeFormulation = writeFormulation' -  bracket glp_create_prob glp_delete_prob $ \problem -> do-    -- Load the default simplex control parameters-    defaultSimplexControl <- alloca $ \simplexControlPtr -> do-      glp_init_smcp simplexControlPtr-      peek simplexControlPtr+instance MonadGlpk m => MonadGlpk (ReaderT r m) where+  writeFormulation = lift . writeFormulation -    -- Load the default MIP control parameters-    defaultMipControl <- alloca $ \mipControlPtr -> do-      glp_init_iocp mipControlPtr-      peek mipControlPtr+instance MonadGlpk m => MonadGlpk (StateT s m) where+  writeFormulation = lift . writeFormulation -    -- Turn on presolve, because it seems insane not to.-    ---    -- In particular, this ensures that a naked call to optimizeIP-    -- doesn't fail because of the lack of a basis. Sophisticated users-    -- can control this parameter as they see fit befor the first-    -- optimization call.-    let simplexControl = defaultSimplexControl { smcpPresolve = glpkPresolveOn }-        mipControl = defaultMipControl { iocpPresolve = glpkPresolveOn }+withGlpkErrorHook :: (Ptr a -> IO CInt) -> Ptr a -> IO b -> IO b+withGlpkErrorHook hook ptr actions =+  bracket (mkHaskellErrorHook hook) freeHaskellFunPtr $ \hookPtr -> do+    glp_error_hook hookPtr ptr+    actions -    env <- GlpkEnv problem-           <$> newIORef []-           <*> newIORef []-           <*> newIORef simplexControl-           <*> newIORef mipControl-           <*> newIORef Nothing+removeGlpkErrorHook :: IO ()+removeGlpkErrorHook = glp_error_hook nullFunPtr nullPtr -    runReaderT (runExceptT (_runGlpk glpk)) env+runGlpk :: Glpk a -> IO a+runGlpk program =+  let withGlpkEnv actions =+        bracket glp_init_env (const glp_free_env) $ \case+          0 -> actions+          1 -> throwIO (GlpkFailure "GLPK already initialized")+          2 -> throwIO (GlpkFailure "GLPK failed to initialize; not enough memory")+          3 -> throwIO (GlpkFailure "GLPK failed to initialize; unsupported programming model")+          r -> throwIO (GlpkFailure ("GLPK failed to initialize; unknown status code " <> T.pack (show r)))+   in runInBoundThread $+        withGlpkEnv $+          flip finally removeGlpkErrorHook $+            withGlpkErrorHook (const glp_free_env) nullPtr $+              runGlpk' program +getDefaultSimplexControlParameters :: IO SimplexMethodControlParameters+getDefaultSimplexControlParameters = do+  params <- alloca $ \simplexControlPtr -> do+    glp_init_smcp simplexControlPtr+    peek simplexControlPtr -data SolveType = LP | MIP | InteriorPoint+  -- Turn on presolve. Users can simply turn this off before the first+  -- optimization call if they desire.+  pure (params {smcpPresolve = glpkPresolveOn}) --- | An interface to the low-level GLPK API.------ High-level solver settings can be modified by altering the--- 'SimplexMethodControlParameters' and 'MIPControlParameters' values--- for LP and IP solves, respectively.-data GlpkEnv-  = GlpkEnv-  { _glpkEnvProblem     :: Ptr Problem-  -- ^ A pointer to the Problem object. Most GLPK routines take this-  -- as the first argument.-  , _glpkVariables      :: IORef [GlpkVariable]-  -- ^ The variables in the model-  , _glpkConstraints    :: IORef [GlpkConstraint]-  -- ^ The constraints in the model-  , _glpkSimplexControl :: IORef SimplexMethodControlParameters-  -- ^ The control parameters for the simplex method-  , _glpkMIPControl     :: IORef (MIPControlParameters Void)-  -- ^ The control parameters for the MIP solver-  , _glpkLastSolveType  :: IORef (Maybe SolveType)-  -- ^ The type of the last solve. This is needed to know whether to-  -- retrieve simplex, interior point, or MIP solutions.-  }+getDefaultMIPControlParameters :: IO (MIPControlParameters Void)+getDefaultMIPControlParameters = do+  params <- alloca $ \mipControlPtr -> do+    glp_init_iocp mipControlPtr+    peek mipControlPtr -data NamedRef a-  = NamedRef-    { namedRefId  :: Int-    , namedRefRef :: IORef a-    }+  -- Turn on presolve. Users can simply turn this off before the first+  -- optimization call if they desire.+  pure (params {iocpPresolve = glpkPresolveOn}) -instance Eq (NamedRef a) where-  x == y = namedRefId x == namedRefId y+runGlpk' :: Glpk a -> IO a+runGlpk' glpk = do+  -- Turn off terminal output. If we don't, users won't be able to+  -- inhibit terminal output generated from our setup.+  _ <- glp_term_out glpkOff -instance Ord (NamedRef a) where-  x <= y = namedRefId x <= namedRefId y+  bracket glp_create_prob glp_delete_prob $ \problem -> do+    env <-+      GlpkEnv problem+        <$> newIORef []+        <*> newIORef 0+        <*> newIORef []+        <*> newIORef 0+        <*> (getDefaultSimplexControlParameters >>= newIORef)+        <*> (getDefaultMIPControlParameters >>= newIORef)+        <*> newIORef Nothing -instance Show (NamedRef a) where-  show = show . namedRefId+    runReaderT (_runGlpk glpk) env -type GlpkConstraint = NamedRef Row+data SolveType = LP | MIP | InteriorPoint -type GlpkVariable = NamedRef Column+-- | Retrieve a component of the Glpk context+askGlpk :: Monad m => (GlpkEnv -> a) -> GlpkT m a+askGlpk = flip fmap (GlpkT ask) -askProblem :: Glpk (Ptr Problem)-askProblem = asks _glpkEnvProblem+-- | The underlying Glpk problem pointer+askProblem :: Monad m => GlpkT m (Ptr Problem)+askProblem = askGlpk _glpkEnvProblem +-- | All registered variables askVariablesRef :: Glpk (IORef [GlpkVariable])-askVariablesRef = asks _glpkVariables+askVariablesRef = askGlpk _glpkVariables +-- | All registered constraints askConstraintsRef :: Glpk (IORef [GlpkConstraint])-askConstraintsRef = asks _glpkConstraints--register :: Glpk (IORef [NamedRef a]) -> NamedRef a -> Glpk ()-register askRef x = do-  ref <- askRef-  liftIO $ modifyIORef' ref (x :)+askConstraintsRef = askGlpk _glpkConstraints -unregister :: (Enum a) => Glpk (IORef [NamedRef a]) -> NamedRef a -> Glpk ()-unregister askRef x =-  let-    decrement (NamedRef _ ref) = modifyIORef' ref pred+-- | Note that a new row or column has been added to the to the problem.+register :: GlpkPtr a -> IORef [GlpkPtr a] -> Glpk ()+register newPtr ptrRefs = do+  liftIO $ modifyIORef' ptrRefs (newPtr :) -    mogrify []                  = return ()-    mogrify (z: zs) | z <= x    = return ()-                    | otherwise = decrement z >> mogrify zs-  in do-    ref <- askRef-    liftIO $ do-      -- Remove the element to be unregistered-      modifyIORef' ref (delete x)+-- | Note that a row or column has been deleted from the problem, and+-- update row or column indices accordingly.+unregister :: Integral a => GlpkPtr a -> IORef [GlpkPtr a] -> Glpk ()+unregister deletedPtr ptrsRef =+  let update removed (GlpkPtr _ ptr _) = do+        z <- readIORef ptr+        when (z > removed) $+          modifyIORef' ptr pred+   in liftIO $ do+        -- If the reference was already deleted, do nothing+        deleted <- readIORef (_glpkPtrDeleted deletedPtr)+        unless deleted $ do+          -- Mark deletion+          writeIORef (_glpkPtrDeleted deletedPtr) True -      -- Modify the referenced values that were greater than the-      -- referenced element-      readIORef ref >>= mogrify+          -- Remove the element to be unregistered+          modifyIORef' ptrsRef (filter ((/= _glpkPtrId deletedPtr) . _glpkPtrId)) -data GlpkError-  = UnknownVariable GlpkVariable-  | UnknownCode String CInt-  deriving-    ( Show-    )+          -- Modify the referenced values that were greater than the+          -- referenced element.+          deletedId <- readIORef (_glpkPtrRef deletedPtr)+          ptrs <- readIORef ptrsRef+          mapM_ (update deletedId) ptrs -readColumn :: Variable Glpk -> Glpk Column-readColumn = liftIO . readIORef . namedRefRef . fromVariable+readColumn :: GlpkVariable -> Glpk Column+readColumn = liftIO . readIORef . _glpkPtrRef -readRow :: Constraint Glpk -> Glpk Row-readRow = liftIO . readIORef . namedRefRef . fromConstraint+readRow :: GlpkConstraint -> Glpk Row+readRow = liftIO . readIORef . _glpkPtrRef -addVariable' :: Glpk (Variable Glpk)+addVariable' :: Glpk GlpkVariable addVariable' = do   problem <- askProblem   variable <- liftIO $ do     column <- glp_add_cols problem 1-     glp_set_col_bnds problem column glpkFree 0 0+    GlpkPtr (fromIntegral column)+      <$> newIORef column+      <*> newIORef False -    columnRef <- newIORef column-    return $ NamedRef (fromIntegral column) columnRef-  register askVariablesRef variable-  return (Variable variable)+  askVariablesRef >>= register variable+  setVariableName' variable (defaultVariableName variable)+  pure variable -setVariableName' :: Variable Glpk -> String -> Glpk ()+defaultVariableName :: GlpkVariable -> T.Text+defaultVariableName (GlpkPtr x _ _) = "x" <> T.pack (show x)++defaultConstraintName :: GlpkConstraint -> T.Text+defaultConstraintName (GlpkPtr x _ _) = "c" <> T.pack (show x)++setVariableName' :: GlpkVariable -> T.Text -> Glpk () setVariableName' variable name = do-    problem <- askProblem-    column <- readColumn variable-    liftIO $ withCString name (glp_set_col_name problem column)+  problem <- askProblem+  column <- readColumn variable+  liftIO $ withCText name (glp_set_col_name problem column) -getVariableName' :: Variable Glpk -> Glpk String+getVariableName' :: GlpkVariable -> Glpk T.Text getVariableName' variable = do   problem <- askProblem   column <- readColumn variable-  liftIO $ glp_get_col_name problem column >>= peekCString+  name <- liftIO $ glp_get_col_name problem column >>= peekCString+  pure (T.pack name) -removeVariable' :: Variable Glpk -> Glpk ()-removeVariable' variable = do+deleteVariable' :: GlpkVariable -> Glpk ()+deleteVariable' variable = do   problem <- askProblem   column <- readColumn variable   liftIO $ allocaGlpkArray [column] (glp_del_cols problem 1)-  unregister askVariablesRef (fromVariable variable)+  askVariablesRef >>= unregister variable -addConstraint' :: Inequality (LinearExpression Double (Variable Glpk)) -> Glpk (Constraint Glpk)+addConstraint' :: Inequality (Expr GlpkVariable) -> Glpk GlpkConstraint addConstraint' (Inequality ordering lhs rhs) =-  let-    LinearExpression terms constant = (simplify (lhs .-. rhs)) :: LinearExpression Double (Variable Glpk)+  let LinExpr terms constant = simplify (lhs .-. rhs) :: Expr GlpkVariable -    constraintType :: GlpkConstraintType-    constraintType = case ordering of-      LT -> glpkLT-      GT -> glpkGT-      EQ -> glpkFixed+      constraintType :: GlpkConstraintType+      constraintType = case ordering of+        LT -> glpkLT+        GT -> glpkGT+        EQ -> glpkFixed -    constraintRhs :: CDouble-    constraintRhs = realToFrac (negate constant)+      constraintRhs :: CDouble+      constraintRhs = realToFrac (negate constant) -    numVars :: CInt-    numVars = fromIntegral (length terms)+      numVars :: CInt+      numVars = fromIntegral (length terms) -    variables :: [Variable Glpk]-    variables = map snd terms+      variables :: [GlpkVariable]+      variables = map snd terms -    coefficients :: [CDouble]-    coefficients = map (realToFrac . fst) terms-  in do-    problem <- askProblem-    columns <- mapM readColumn variables-    constraintId <- liftIO $ do-      row <- glp_add_rows problem 1-      rowRef <- newIORef row-      allocaGlpkArray columns $ \columnArr ->-        allocaGlpkArray coefficients $ \coefficientArr -> do-          glp_set_row_bnds problem row constraintType constraintRhs constraintRhs-          glp_set_mat_row problem row numVars columnArr coefficientArr-      return $ NamedRef (fromIntegral row) rowRef+      coefficients :: [CDouble]+      coefficients = map (realToFrac . fst) terms+   in do+        problem <- askProblem+        columns <- mapM readColumn variables+        constraintPtr <- liftIO $ do+          row <- glp_add_rows problem 1+          allocaGlpkArray columns $ \columnArr ->+            allocaGlpkArray coefficients $ \coefficientArr -> do+              glp_set_row_bnds problem row constraintType constraintRhs constraintRhs+              glp_set_mat_row problem row numVars columnArr coefficientArr -    register askConstraintsRef constraintId-    return (Constraint constraintId)+          GlpkPtr (fromIntegral row)+            <$> newIORef row+            <*> newIORef False -setConstraintName' :: Constraint Glpk -> String -> Glpk ()+        askConstraintsRef >>= register constraintPtr+        setConstraintName' constraintPtr (defaultConstraintName constraintPtr)+        pure constraintPtr++setConstraintName' :: GlpkConstraint -> T.Text -> Glpk () setConstraintName' constraintId name = do   problem <- askProblem   row <- readRow constraintId-  liftIO $ withCString name (glp_set_row_name problem row)+  liftIO $ withCText name (glp_set_row_name problem row) -getConstraintName' :: Constraint Glpk -> Glpk String+getConstraintName' :: GlpkConstraint -> Glpk T.Text getConstraintName' constraint = do   problem <- askProblem   row <- readRow constraint-  liftIO $ glp_get_row_name problem row >>= peekCString+  name <- liftIO $ glp_get_row_name problem row >>= peekCString+  pure (T.pack name) -getDualValue' :: Constraint Glpk -> Glpk Double-getDualValue' constraint = do+getDualValue :: GlpkConstraint -> Glpk Double+getDualValue constraint = do   problem <- askProblem   row <- readRow constraint   fmap realToFrac . liftIO $ glp_get_row_dual problem row -removeConstraint' :: Constraint Glpk -> Glpk ()-removeConstraint' constraintId = do+deleteConstraint' :: GlpkConstraint -> Glpk ()+deleteConstraint' constraint = do   problem <- askProblem-  row <- readRow constraintId+  row <- readRow constraint   liftIO $ allocaGlpkArray [row] (glp_del_rows problem 1)-  unregister askConstraintsRef (fromConstraint constraintId)+  askConstraintsRef >>= unregister constraint -addObjective' :: LinearExpression Double (Variable Glpk) -> Glpk (Objective Glpk)+addObjective' :: Expr GlpkVariable -> Glpk GlpkObjective addObjective' expr =-  let-    LinearExpression terms constant = simplify expr-  in do-    problem <- askProblem+  let LinExpr terms constant = simplify expr+   in do+        problem <- askProblem -    -- Set the constant term-    liftIO $ glp_set_obj_coef problem (GlpkInt 0) (realToFrac constant)+        -- Set the constant term+        liftIO $ glp_set_obj_coef problem (GlpkInt 0) (realToFrac constant) -    -- Set the variable terms-    forM_ terms $ \(coef, variable) -> do-      column <- readColumn variable-      liftIO $ glp_set_obj_coef problem column (realToFrac coef)+        -- Set the variable terms+        forM_ terms $ \(coef, variable) -> do+          column <- readColumn variable+          liftIO $ glp_set_obj_coef problem column (realToFrac coef) -    pure Objective+        pure (GlpkObjective ()) -getObjectiveName' :: Objective Glpk -> Glpk String+-- | Delete an objective+--+-- There is nothing to actually delete, so we just set a zero objective+deleteObjective' :: GlpkObjective -> Glpk ()+deleteObjective' _ = void (addObjective' mempty)++getObjectiveName' :: GlpkObjective -> Glpk T.Text getObjectiveName' _ = do   problem <- askProblem-  liftIO $ glp_get_obj_name problem >>= peekCString+  name <- liftIO $ glp_get_obj_name problem >>= peekCString+  pure (T.pack name) -setObjectiveName' :: Objective Glpk -> String -> Glpk ()+setObjectiveName' :: GlpkObjective -> T.Text -> Glpk () setObjectiveName' _ name = do   problem <- askProblem-  liftIO $ withCString name (glp_set_obj_name problem)+  liftIO $ withCText name (glp_set_obj_name problem) -getObjectiveSense' :: Objective Glpk -> Glpk Sense-getObjectiveSense' _ = do+getSense' :: GlpkObjective -> Glpk Sense+getSense' _ = do   problem <- askProblem   direction <- liftIO $ glp_get_obj_dir problem   if direction == glpkMin     then pure Minimization     else pure Maximization -setObjectiveSense' :: Objective Glpk -> Sense -> Glpk ()-setObjectiveSense' _ sense =-  let-    direction = case sense of-      Minimization -> glpkMin-      Maximization -> glpkMax-  in do-    problem <- askProblem-    liftIO $ glp_set_obj_dir problem direction+setSense' :: GlpkObjective -> Sense -> Glpk ()+setSense' _ sense =+  let direction = case sense of+        Minimization -> glpkMin+        Maximization -> glpkMax+   in do+        problem <- askProblem+        liftIO $ glp_set_obj_dir problem direction -getObjectiveValue' :: Objective Glpk -> Glpk Double+getObjectiveValue' :: GlpkObjective -> Glpk Double getObjectiveValue' _ = do   problem <- askProblem-  lastSolveRef <- asks _glpkLastSolveType+  lastSolveRef <- askGlpk _glpkLastSolveType   lastSolve <- (liftIO . readIORef) lastSolveRef   fmap realToFrac . liftIO $ case lastSolve of-    Just MIP           -> glp_mip_obj_val problem-    Just LP            -> glp_get_obj_val problem+    Just MIP -> glp_mip_obj_val problem+    Just LP -> glp_get_obj_val problem     Just InteriorPoint -> glp_ipt_obj_val problem-    Nothing            -> glp_get_obj_val problem -- There's been no solve, so who cares+    Nothing -> glp_get_obj_val problem -- There's been no solve, so who cares  optimizeLP' :: Glpk SolutionStatus optimizeLP' = do-    -- Note that we've run an LP solve-    solveTypeRef <- asks _glpkLastSolveType-    liftIO $ writeIORef solveTypeRef (Just LP)+  -- Note that we've run an LP solve+  solveTypeRef <- askGlpk _glpkLastSolveType+  liftIO $ writeIORef solveTypeRef (Just LP) -    -- Run Simplex-    problem <- askProblem-    controlRef <- asks _glpkSimplexControl-    liftIO $ do-      control <- readIORef controlRef-      alloca $ \controlPtr -> do-        poke controlPtr control-        _<- glp_simplex problem controlPtr-        glp_get_status problem >>= pure . solutionStatus+  -- Run Simplex+  problem <- askProblem+  controlRef <- askGlpk _glpkSimplexControl+  liftIO $ do+    control <- readIORef controlRef+    alloca $ \controlPtr -> do+      poke controlPtr control+      _ <- glp_simplex problem controlPtr+      glp_get_status problem Data.Functor.<&> solutionStatus  optimizeIP' :: Glpk SolutionStatus optimizeIP' = do-    -- Note that we've run a MIP solve-    solveTypeRef <- asks _glpkLastSolveType-    liftIO $ writeIORef solveTypeRef (Just MIP)+  -- Note that we've run a MIP solve+  solveTypeRef <- askGlpk _glpkLastSolveType+  liftIO $ writeIORef solveTypeRef (Just MIP) -    problem <- askProblem-    controlRef <- asks _glpkMIPControl-    liftIO $ do-      control <- readIORef controlRef-      alloca $ \controlPtr -> do-        poke controlPtr control-        _ <- glp_intopt problem controlPtr-        glp_mip_status problem >>= pure . solutionStatus+  problem <- askProblem+  controlRef <- askGlpk _glpkMIPControl+  liftIO $ do+    control <- readIORef controlRef+    alloca $ \controlPtr -> do+      poke controlPtr control+      _ <- glp_intopt problem controlPtr+      glp_mip_status problem Data.Functor.<&> solutionStatus -setVariableBounds' :: Variable Glpk -> Bounds Double -> Glpk ()+setVariableBounds' :: GlpkVariable -> Bounds -> Glpk () setVariableBounds' variable bounds =-  let-    (boundType, cLow, cHigh) = case bounds of-      Free              -> (glpkFree, 0, 0)-      NonNegativeReals  -> (glpkGT, 0, 0)-      NonPositiveReals  -> (glpkLT, 0, 0)-      Interval low high -> (glpkBounded, realToFrac low, realToFrac high)-  in do-    problem <- askProblem-    column <- readColumn variable-    liftIO $ glp_set_col_bnds problem column boundType cLow cHigh+  let (boundType, cLow, cHigh) = case bounds of+        Free -> (glpkFree, 0, 0)+        NonNegativeReals -> (glpkGT, 0, 0)+        NonPositiveReals -> (glpkLT, 0, 0)+        Interval low high -> (glpkBounded, realToFrac low, realToFrac high)+   in do+        problem <- askProblem+        column <- readColumn variable+        liftIO $ glp_set_col_bnds problem column boundType cLow cHigh -getVariableBounds' :: Variable Glpk -> Glpk (Bounds Double)+getVariableBounds' :: GlpkVariable -> Glpk Bounds getVariableBounds' variable =-  let-    boundsFor lb ub | lb == -maxCDouble && ub == maxCDouble = Free-                    | lb == -maxCDouble && ub == 0.0        = NonPositiveReals-                    | lb == 0.0         && ub == maxCDouble = NonNegativeReals-                    | otherwise                             = Interval lb' ub'-      where-        lb' = realToFrac lb-        ub' = realToFrac ub-  in do-    problem <- askProblem-    column <- readColumn variable-    lb <- liftIO (glp_get_col_lb problem column)-    ub <- liftIO (glp_get_col_ub problem column)-    return (boundsFor lb ub)+  let boundsFor lb ub+        | lb == - maxCDouble && ub == maxCDouble = Free+        | lb == - maxCDouble && ub == 0.0 = NonPositiveReals+        | lb == 0.0 && ub == maxCDouble = NonNegativeReals+        | otherwise = Interval lb' ub'+        where+          lb' = realToFrac lb+          ub' = realToFrac ub+   in do+        problem <- askProblem+        column <- readColumn variable+        lb <- liftIO (glp_get_col_lb problem column)+        ub <- liftIO (glp_get_col_ub problem column)+        return (boundsFor lb ub) -setVariableDomain' :: Variable Glpk -> Domain -> Glpk ()+setVariableDomain' :: GlpkVariable -> Domain -> Glpk () setVariableDomain' variable domain =-  let-    vType = case domain of-      Continuous -> glpkContinuous-      Integer    -> glpkInteger-      Binary     -> glpkBinary-  in do-    problem <- askProblem-    column <- readColumn variable-    liftIO $ glp_set_col_kind problem column vType+  let vType = case domain of+        Continuous -> glpkContinuous+        Integer -> glpkInteger+        Binary -> glpkBinary+   in do+        problem <- askProblem+        column <- readColumn variable+        liftIO $ glp_set_col_kind problem column vType -getVariableDomain' :: Variable Glpk -> Glpk Domain+getVariableDomain' :: GlpkVariable -> Glpk Domain getVariableDomain' variable =-  let-    getDomain :: GlpkVariableType -> Glpk Domain-    getDomain vType | vType == glpkContinuous = return Continuous-    getDomain vType | vType == glpkInteger    = return Integer-    getDomain vType | vType == glpkBinary     = return Binary-                    | otherwise               = throwError unknownCode-      where-        typeName = show (typeOf vType)-        GlpkVariableType code = vType-        unknownCode = UnknownCode typeName code-  in do-    problem <- askProblem-    column <- readColumn variable-    getDomain =<< liftIO (glp_get_col_kind problem column)+  let getDomain' :: GlpkVariableType -> Glpk Domain+      getDomain' vType | vType == glpkContinuous = return Continuous+      getDomain' vType | vType == glpkInteger = return Integer+      getDomain' vType+        | vType == glpkBinary = return Binary+        | otherwise = throwIO unknownCode+        where+          typeName = T.pack . show . typeOf $ vType+          GlpkVariableType code = vType+          unknownCode = UnknownCode typeName code+   in do+        problem <- askProblem+        column <- readColumn variable+        getDomain' =<< liftIO (glp_get_col_kind problem column) -getVariableValue' :: Variable Glpk -> Glpk Double+getVariableValue' :: GlpkVariable -> Glpk Double getVariableValue' variable = do-  lastSolveRef <- asks _glpkLastSolveType+  lastSolveRef <- askGlpk _glpkLastSolveType   lastSolve <- (liftIO . readIORef) lastSolveRef    let method = case lastSolve of-        Nothing            -> glp_get_col_prim-        Just LP            -> glp_get_col_prim-        Just MIP           -> glp_mip_col_val+        Nothing -> glp_get_col_prim+        Just LP -> glp_get_col_prim+        Just MIP -> glp_mip_col_val         Just InteriorPoint -> glp_ipt_col_prim    problem <- askProblem@@ -469,51 +546,49 @@  getTimeout' :: RealFrac a => Glpk a getTimeout' =-  let-    fromMillis :: RealFrac a => CInt -> a-    fromMillis millis = realToFrac millis / 1000-  in do-    controlRef <- asks _glpkSimplexControl-    control <- liftIO (readIORef controlRef)-    return $ fromMillis (smcpTimeLimitMillis control)+  let fromMillis :: RealFrac a => CInt -> a+      fromMillis millis = realToFrac millis / 1000+   in do+        controlRef <- askGlpk _glpkSimplexControl+        control <- liftIO (readIORef controlRef)+        return $ fromMillis (smcpTimeLimitMillis control)  setTimeout' :: RealFrac a => a -> Glpk () setTimeout' seconds =-  let-    millis :: Integer-    millis = round (seconds * 1000)-  in do-    controlRef <- asks _glpkSimplexControl-    control <- liftIO (readIORef controlRef)-    let control' = control { smcpTimeLimitMillis = fromIntegral millis }-    liftIO (writeIORef controlRef control')+  let millis :: Integer+      millis = round (seconds * 1000)+   in do+        controlRef <- askGlpk _glpkSimplexControl+        control <- liftIO (readIORef controlRef)+        let control' = control {smcpTimeLimitMillis = fromIntegral millis}+        liftIO (writeIORef controlRef control')  setRelativeMIPGap' :: RealFrac a => a -> Glpk () setRelativeMIPGap' gap = do-  controlRef <- asks _glpkMIPControl+  controlRef <- askGlpk _glpkMIPControl   control <- liftIO (readIORef controlRef)-  let control' = control { iocpRelativeMIPGap = realToFrac gap }+  let control' = control {iocpRelativeMIPGap = realToFrac gap}   liftIO (writeIORef controlRef control')  getRelativeMIPGap' :: RealFrac a => Glpk a getRelativeMIPGap' = do-  controlRef <- asks _glpkMIPControl+  controlRef <- askGlpk _glpkMIPControl   control <- liftIO (readIORef controlRef)   return $ realToFrac (iocpRelativeMIPGap control)  solutionStatus :: GlpkSolutionStatus -> SolutionStatus solutionStatus status-  | status == glpkOptimal    = Optimal-  | status == glpkFeasible   = Feasible+  | status == glpkOptimal = Optimal+  | status == glpkFeasible = Feasible   | status == glpkInfeasible = Infeasible   | status == glpkNoFeasible = Infeasible-  | status == glpkUnbounded  = Unbounded-  | status == glpkUndefined  = Infeasible-  | otherwise                = Error+  | status == glpkUnbounded = Unbounded+  | status == glpkUndefined = Infeasible+  | otherwise = Error  -- | Write out the current formulation to a file.-writeFormulation :: FilePath -> Glpk ()-writeFormulation fileName = do+writeFormulation' :: FilePath -> Glpk ()+writeFormulation' fileName = do   problem <- askProblem   _ <- liftIO $ withCString fileName (glp_write_lp problem nullPtr)   return ()@@ -526,3 +601,6 @@     (_, maxExponent) = floatRange (undefined :: CDouble)     significand' = base ^ precision - 1     exponent' = maxExponent - precision++withCText :: T.Text -> (CString -> IO a) -> IO a+withCText = withCString . T.unpack
− test/ApiTests.hs
@@ -1,18 +0,0 @@-module ApiTests where--import Test.Tasty-import Test.Tasty.HUnit--import Math.Programming.Glpk-import Math.Programming.Tests--test_tree :: TestTree-test_tree = makeAllTests "GLPK" glpkRunner--glpkRunner :: Glpk () -> IO ()-glpkRunner program = do-  result <- runGlpk program-  case result of-    Left errorMsg -> assertFailure (show errorMsg)-    Right () -> return ()-  return ()
− test/Driver.hs
@@ -1,10 +0,0 @@-{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --ignores="*~" #-}-{-|-Module : Main--The entry point for tests. We use tasty-discover to automatically-discover test functions in this directory, so simply preface tasty-tests with test_ for tasty TestTrees, or prop_ for QuickCheck-properties.--}-module Main where
+ test/Math/Programming/GlpkSpec.hs view
@@ -0,0 +1,120 @@+module Math.Programming.GlpkSpec where++import Control.Monad+import Control.Monad.IO.Class+import Data.IORef+import Math.Programming+import Math.Programming.Glpk+import Math.Programming.Tests+import Math.Programming.Tests.Fuzz+import Math.Programming.Tests.IP (simpleMIPTest)+import Math.Programming.Tests.LP (dietProblemTest)+import Test.Hspec+import UnliftIO.Async++logFormulation :: IORef Int -> Glpk ()+logFormulation iRef = do+  i <- liftIO $ readIORef iRef+  writeFormulation ("instance" <> show i <> ".mip")+  liftIO $ modifyIORef' iRef succ++spec :: Spec+spec = do+  makeAllTests "GLPK" runGlpk++  -- Toggle this to log formulations generated during fuzzing+  let writeFuzzedFormulations = False+  when writeFuzzedFormulations $ do+    iRef <- runIO $ newIORef 0+    makeFuzzTests (\action -> runGlpk (action >> logFormulation iRef))++  describe "Regression tests" $ do+    it "solves an LP with free variables" testFreeVariablesLP+    it "solves an IP with free variables" testFreeVariablesIP+    it "finds an infeasible LP to be infeasible" testInfeasibleLP+    it "finds an infeasible IP to be infeasible" testInfeasibleIP++  describe "Threaded runtime tests" $ do+    it "can solve problems in parallel" testParallelSolves++assertFeasible :: SolutionStatus -> Glpk ()+assertFeasible result =+  liftIO $ case result of+    Error -> expectationFailure "Failed to solve program"+    Unbounded -> expectationFailure "Unbounded program"+    Infeasible -> expectationFailure "Infeasible program"+    _ -> pure ()++testFreeVariablesLP :: IO ()+testFreeVariablesLP = runGlpk $ do+  x <- free+  y <- free+  z <- free++  _ <- var x .== 0+  _ <- var y .== 3.1+  _ <- var z .== -3.1++  optimizeLP >>= assertFeasible++  vx <- getVariableValue x+  vy <- getVariableValue y+  vz <- getVariableValue z++  liftIO $ 0 `shouldBe` vx+  liftIO $ 3.1 `shouldBe` vy+  liftIO $ -3.1 `shouldBe` vz++testFreeVariablesIP :: IO ()+testFreeVariablesIP = runGlpk $ do+  x <- integer+  y <- integer+  z <- integer++  _ <- var x .== 0+  _ <- var y .== 3+  _ <- var z .== -3++  optimizeIP >>= assertFeasible++  vx <- getVariableValue x+  vy <- getVariableValue y+  vz <- getVariableValue z++  liftIO $ 0 `shouldBe` vx+  liftIO $ 3 `shouldBe` vy+  liftIO $ -3 `shouldBe` vz++testInfeasibleLP :: IO ()+testInfeasibleLP = runGlpk $ do+  x <- free+  _ <- var x .>= 2+  _ <- var x .<= 1++  status <- optimizeLP++  liftIO $ Infeasible `shouldBe` status++testInfeasibleIP :: IO ()+testInfeasibleIP = runGlpk $ do+  x <- integer+  _ <- var x .>= 2+  _ <- var x .<= 1++  status <- optimizeIP++  liftIO $ Infeasible `shouldBe` status++testParallelSolves :: IO ()+testParallelSolves =+  let problems =+        [ dietProblemTest,+          simpleMIPTest,+          dietProblemTest,+          simpleMIPTest,+          dietProblemTest,+          simpleMIPTest,+          dietProblemTest,+          simpleMIPTest+        ]+   in pooledMapConcurrently_ runGlpk problems
− test/RegressionTests.hs
@@ -1,91 +0,0 @@-module RegressionTests where--import           Control.Monad.IO.Class-import           Test.Tasty-import           Test.Tasty.HUnit--import           Math.Programming-import           Math.Programming.Glpk--test_tree :: TestTree-test_tree = testGroup "Regression tests"-            [ testCase "Free variables (LP)" testFreeVariablesLP-            , testCase "Free variables (IP)" testFreeVariablesIP-            , testCase "Infeasible (LP)" testInfeasibleLP-            , testCase "Infeasible (IP)" testInfeasibleIP-            ]--assertFeasible :: SolutionStatus -> Glpk ()-assertFeasible result-  = liftIO $ case result of-      Error      -> assertFailure "Failed to solve program"-      Unbounded  -> assertFailure "Unbounded program"-      Infeasible -> assertFailure "Infeasible program"-      _          -> pure ()--assertRunGlpk :: Glpk a -> Assertion-assertRunGlpk program = do-  eResult <- runGlpk program-  case eResult of-    Left err -> assertFailure ("Error solving program: " <> show err)-    Right _  -> pure ()--testFreeVariablesLP :: Assertion-testFreeVariablesLP = assertRunGlpk $ do-  x <- free-  y <- free-  z <- free--  _ <- x @==# 0-  _ <- y @==# 3.1-  _ <- z @==# -3.1--  optimizeLP >>= assertFeasible--  vx <- getVariableValue x-  vy <- getVariableValue y-  vz <- getVariableValue z--  liftIO $ 0 @=? vx-  liftIO $ 3.1 @=? vy-  liftIO $ -3.1 @=? vz--testFreeVariablesIP :: Assertion-testFreeVariablesIP = assertRunGlpk $ do-  x <- integer-  y <- integer-  z <- integer--  _ <- x @==# 0-  _ <- y @==# 3-  _ <- z @==# -3--  optimizeIP >>= assertFeasible--  vx <- getVariableValue x-  vy <- getVariableValue y-  vz <- getVariableValue z--  liftIO $ 0 @=? vx-  liftIO $ 3 @=? vy-  liftIO $ -3 @=? vz--testInfeasibleLP :: Assertion-testInfeasibleLP = assertRunGlpk $ do-  x <- free-  _ <- x @>=# 2-  _ <- x @<=# 1--  status <- optimizeLP--  liftIO $ Infeasible @=? status--testInfeasibleIP :: Assertion-testInfeasibleIP = assertRunGlpk $ do-  x <- integer-  _ <- x @>=# 2-  _ <- x @<=# 1--  status <- optimizeIP--  liftIO $ Infeasible @=? status
+ test/Spec.hs view
@@ -0,0 +1,3 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}++module Spec where