packages feed

math-programming-tests 0.4.0 → 0.5.0

raw patch · 9 files changed

+396/−260 lines, 9 filesdep +QuickCheckdep +containersdep +hspecdep −tastydep −tasty-hunitdep −tasty-quickcheckdep ~basedep ~math-programmingdep ~textPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, containers, hspec, microlens, microlens-mtl, microlens-th, mtl, random

Dependencies removed: tasty, tasty-hunit, tasty-quickcheck

Dependency ranges changed: base, math-programming, text

API changes (from Hackage documentation)

- Math.Programming.Tests.Api: AddConstraint :: Action
- Math.Programming.Tests.Api: AddThenDeleteConstraint :: Action
- Math.Programming.Tests.Api: AddThenDeleteVariable :: Action
- Math.Programming.Tests.Api: AddVariable :: Action
- Math.Programming.Tests.Api: LPActions :: m () -> LPActions m
- Math.Programming.Tests.Api: arbitraryLPActionsProp :: LPActions m -> m ()
- Math.Programming.Tests.Api: bindOver :: Monad m => m a -> (a -> m b) -> m () -> m b
- Math.Programming.Tests.Api: data Action
- Math.Programming.Tests.Api: instance GHC.Enum.Enum Math.Programming.Tests.Api.Action
- Math.Programming.Tests.Api: instance GHC.Show.Show Math.Programming.Tests.Api.Action
- Math.Programming.Tests.Api: instance Math.Programming.Types.LPMonad m => Test.QuickCheck.Arbitrary.Arbitrary (Math.Programming.Tests.Api.LPActions m)
- Math.Programming.Tests.Api: instance Test.QuickCheck.Arbitrary.Arbitrary Math.Programming.Tests.Api.Action
- Math.Programming.Tests.Api: lpActions :: LPMonad m => Int -> Gen (m ())
- Math.Programming.Tests.Api: newtype LPActions m
+ Math.Programming.Tests.Fuzz: AddConstraint :: Constraint -> LPAction
+ Math.Programming.Tests.Fuzz: AddObjective :: Objective -> LPAction
+ Math.Programming.Tests.Fuzz: AddThenRemoveConstraint :: Constraint -> LPAction
+ Math.Programming.Tests.Fuzz: AddThenRemoveObjective :: Objective -> LPAction
+ Math.Programming.Tests.Fuzz: AddThenRemoveVariable :: Variable -> LPAction
+ Math.Programming.Tests.Fuzz: AddVariable :: Variable -> LPAction
+ Math.Programming.Tests.Fuzz: Constraint :: Int -> Constraint
+ Math.Programming.Tests.Fuzz: LPActions :: [LPAction] -> LPActions
+ Math.Programming.Tests.Fuzz: LPState :: Map Variable v -> Map Variable Text -> Map Constraint c -> Map Constraint Text -> Map Objective o -> Map Objective Text -> [LPAction] -> IOGenM StdGen -> LPState v c o
+ Math.Programming.Tests.Fuzz: Objective :: Int -> Objective
+ Math.Programming.Tests.Fuzz: Optimize :: LPAction
+ Math.Programming.Tests.Fuzz: Variable :: Int -> Variable
+ Math.Programming.Tests.Fuzz: [_constraintNames] :: LPState v c o -> Map Constraint Text
+ Math.Programming.Tests.Fuzz: [_constraints] :: LPState v c o -> Map Constraint c
+ Math.Programming.Tests.Fuzz: [_objectiveNames] :: LPState v c o -> Map Objective Text
+ Math.Programming.Tests.Fuzz: [_objectives] :: LPState v c o -> Map Objective o
+ Math.Programming.Tests.Fuzz: [_pending] :: LPState v c o -> [LPAction]
+ Math.Programming.Tests.Fuzz: [_randomGen] :: LPState v c o -> IOGenM StdGen
+ Math.Programming.Tests.Fuzz: [_variableNames] :: LPState v c o -> Map Variable Text
+ Math.Programming.Tests.Fuzz: [_variables] :: LPState v c o -> Map Variable v
+ Math.Programming.Tests.Fuzz: add :: (LPFuzz v c o m, Ord k) => k -> m a -> ASetter' (LPState v c o) (Map k a) -> m ()
+ Math.Programming.Tests.Fuzz: addThenRemove :: (LPFuzz v c o m, Ord k) => k -> m a -> (a -> m ()) -> Lens' (LPState v c o) (Map k a) -> m ()
+ Math.Programming.Tests.Fuzz: chooseExpr :: LPFuzz v c o m => m (Expr v)
+ Math.Programming.Tests.Fuzz: chooseInequality :: LPFuzz v c o m => m (Expr v -> Expr v -> m c)
+ Math.Programming.Tests.Fuzz: constraintNames :: forall v_aa1t c_aa1u o_aa1v. Lens' (LPState v_aa1t c_aa1u o_aa1v) (Map Constraint Text)
+ Math.Programming.Tests.Fuzz: constraints :: forall v_aa1t c_aa1u o_aa1v c_aaQd. Lens (LPState v_aa1t c_aa1u o_aa1v) (LPState v_aa1t c_aaQd o_aa1v) (Map Constraint c_aa1u) (Map Constraint c_aaQd)
+ Math.Programming.Tests.Fuzz: data LPAction
+ Math.Programming.Tests.Fuzz: data LPState v c o
+ Math.Programming.Tests.Fuzz: evalAction :: LPFuzz v c o m => LPAction -> m ()
+ Math.Programming.Tests.Fuzz: evalAction' :: LPFuzz v c o m => LPAction -> m ()
+ Math.Programming.Tests.Fuzz: evalPending :: LPFuzz v c o m => m ()
+ Math.Programming.Tests.Fuzz: initLPState :: Int -> [LPAction] -> IO (LPState v c o)
+ Math.Programming.Tests.Fuzz: instance GHC.Classes.Eq Math.Programming.Tests.Fuzz.Constraint
+ Math.Programming.Tests.Fuzz: instance GHC.Classes.Eq Math.Programming.Tests.Fuzz.Objective
+ Math.Programming.Tests.Fuzz: instance GHC.Classes.Eq Math.Programming.Tests.Fuzz.Variable
+ Math.Programming.Tests.Fuzz: instance GHC.Classes.Ord Math.Programming.Tests.Fuzz.Constraint
+ Math.Programming.Tests.Fuzz: instance GHC.Classes.Ord Math.Programming.Tests.Fuzz.Objective
+ Math.Programming.Tests.Fuzz: instance GHC.Classes.Ord Math.Programming.Tests.Fuzz.Variable
+ Math.Programming.Tests.Fuzz: instance GHC.Show.Show Math.Programming.Tests.Fuzz.Constraint
+ Math.Programming.Tests.Fuzz: instance GHC.Show.Show Math.Programming.Tests.Fuzz.LPAction
+ Math.Programming.Tests.Fuzz: instance GHC.Show.Show Math.Programming.Tests.Fuzz.LPActions
+ Math.Programming.Tests.Fuzz: instance GHC.Show.Show Math.Programming.Tests.Fuzz.Objective
+ Math.Programming.Tests.Fuzz: instance GHC.Show.Show Math.Programming.Tests.Fuzz.Variable
+ Math.Programming.Tests.Fuzz: instance Test.QuickCheck.Arbitrary.Arbitrary Math.Programming.Tests.Fuzz.LPActions
+ Math.Programming.Tests.Fuzz: makeConstraint :: LPFuzz v c o m => m c
+ Math.Programming.Tests.Fuzz: makeFuzzTests :: (MonadIO m, MonadLP v c o m) => (m (Seq String) -> IO ()) -> Spec
+ Math.Programming.Tests.Fuzz: makeObjective :: LPFuzz v c o m => m o
+ Math.Programming.Tests.Fuzz: newtype Constraint
+ Math.Programming.Tests.Fuzz: newtype LPActions
+ Math.Programming.Tests.Fuzz: newtype Objective
+ Math.Programming.Tests.Fuzz: newtype Variable
+ Math.Programming.Tests.Fuzz: objectiveNames :: forall v_aa1t c_aa1u o_aa1v. Lens' (LPState v_aa1t c_aa1u o_aa1v) (Map Objective Text)
+ Math.Programming.Tests.Fuzz: objectives :: forall v_aa1t c_aa1u o_aa1v o_aaQe. Lens (LPState v_aa1t c_aa1u o_aa1v) (LPState v_aa1t c_aa1u o_aaQe) (Map Objective o_aa1v) (Map Objective o_aaQe)
+ Math.Programming.Tests.Fuzz: pending :: forall v_aa1t c_aa1u o_aa1v. Lens' (LPState v_aa1t c_aa1u o_aa1v) [LPAction]
+ Math.Programming.Tests.Fuzz: randomGen :: forall v_aa1t c_aa1u o_aa1v. Lens' (LPState v_aa1t c_aa1u o_aa1v) (IOGenM StdGen)
+ Math.Programming.Tests.Fuzz: type LPFuzz v c o m = (MonadState (LPState v c o) m, MonadLP v c o m, MonadWriter (Seq String) m, MonadIO m)
+ Math.Programming.Tests.Fuzz: variableNames :: forall v_aa1t c_aa1u o_aa1v. Lens' (LPState v_aa1t c_aa1u o_aa1v) (Map Variable Text)
+ Math.Programming.Tests.Fuzz: variables :: forall v_aa1t c_aa1u o_aa1v v_aaQf. Lens (LPState v_aa1t c_aa1u o_aa1v) (LPState v_aaQf c_aa1u o_aa1v) (Map Variable v_aa1t) (Map Variable v_aaQf)
- Math.Programming.Tests: makeAllTests :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, IPMonad m) => String -> (m () -> IO ()) -> TestTree
+ Math.Programming.Tests: makeAllTests :: (MonadIO m, MonadIP v c o m) => String -> (m () -> IO ()) -> Spec
- Math.Programming.Tests.Api: makeApiTests :: (Num (Numeric m), MonadIO m, LPMonad m) => (m () -> IO ()) -> TestTree
+ Math.Programming.Tests.Api: makeApiTests :: (MonadIO m, MonadLP v c o m) => (m () -> IO ()) -> Spec
- Math.Programming.Tests.Api: setGetConstraintName :: (Num (Numeric m), MonadIO m, LPMonad m) => m ()
+ Math.Programming.Tests.Api: setGetConstraintName :: (MonadIO m, MonadLP v c o m) => m ()
- Math.Programming.Tests.Api: setGetVariableName :: (MonadIO m, LPMonad m) => m ()
+ Math.Programming.Tests.Api: setGetVariableName :: (MonadIO m, MonadLP v c o m) => m ()
- Math.Programming.Tests.IP: makeIPTests :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, IPMonad m) => (m () -> IO ()) -> TestTree
+ Math.Programming.Tests.IP: makeIPTests :: (MonadIO m, MonadIP v c o m) => (m () -> IO ()) -> Spec
- Math.Programming.Tests.IP: simpleMIPTest :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, IPMonad m) => m ()
+ Math.Programming.Tests.IP: simpleMIPTest :: (MonadIO m, MonadIP v c o m) => m ()
- Math.Programming.Tests.LP: dietProblemTest :: forall m. (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, LPMonad m) => m ()
+ Math.Programming.Tests.LP: dietProblemTest :: (MonadIO m, MonadLP v c o m) => m ()
- Math.Programming.Tests.LP: makeLPTests :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, LPMonad m) => (m () -> IO ()) -> TestTree
+ Math.Programming.Tests.LP: makeLPTests :: (MonadIO m, MonadLP v c o m) => (m () -> IO ()) -> Spec

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Changelog for math-programming-api-tests +## [0.5.0] -- 18 January 2023+### Added++- Fuzz testing of APIs+ ## [0.4.0] -- 5 July 2020  Update version bounds.
LICENSE view
@@ -1,4 +1,4 @@-Copyright Patrick Steele (c) 2018+Copyright (c) 2018-2023, Patrick Steele  All rights reserved. 
− README.md
@@ -1,4 +0,0 @@-# math-programming-tests--This library provides utility functions for testing backends to the-`math-programming` library.
math-programming-tests.cabal view
@@ -1,44 +1,40 @@-cabal-version:      1.12-name:               math-programming-tests-version:            0.4.0-license:            BSD3-license-file:       LICENSE-copyright:          2018 Patrick Steele-maintainer:         steele.pat@gmail.com-author:             Patrick Steele-homepage:           https://github.com/prsteele/math-programming-tests#readme-bug-reports:        https://github.com/prsteele/math-programming-tests/issues-synopsis:-    Utility functions for testing implementations of the math-programming library.--description:-    Please see the README on GitHub at <https://github.com/prsteele/math-programming-api-tests#readme>--category:           Math-build-type:         Simple-extra-source-files:-    README.md-    ChangeLog.md+cabal-version:       2.4+name:                math-programming-tests+version:             0.5.0+synopsis:            Utility functions for testing implementations of 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-tests+  type:     git+  location: https://github.com/prsteele/math-programming  library-    exposed-modules:-        Math.Programming.Tests-        Math.Programming.Tests.Api-        Math.Programming.Tests.IP-        Math.Programming.Tests.LP--    hs-source-dirs:   src-    other-modules:    Paths_math_programming_tests-    default-language: Haskell2010-    ghc-options:      -Wall-    build-depends:-        base >=4.12.0.0 && <4.13,-        math-programming >=0.4.0 && <0.5,-        tasty >=1.2.3 && <1.3,-        tasty-hunit >=0.10.0.2 && <0.11,-        tasty-quickcheck >=0.10.1.1 && <0.11,-        text >=1.2.3.1 && <1.3+  default-language:  Haskell2010+  hs-source-dirs:    src+  exposed-modules:   Math.Programming.Tests+                   , Math.Programming.Tests.Api+                   , Math.Programming.Tests.LP+                   , Math.Programming.Tests.IP+                   , Math.Programming.Tests.Fuzz+  other-modules:+  ghc-options:       -Wall+  build-depends:     base             <5+                   , containers       ^>=0.6+                   , math-programming ^>=0.5+                   , microlens        ^>=0.4+                   , microlens-th     ^>=0.4+                   , microlens-mtl    ^>=0.2+                   , hspec            ^>=2.8+                   , QuickCheck       ^>=2.14+                   , random           ^>=1.2+                   , mtl              ^>=2.2+                   , text             ^>=1.2
src/Math/Programming/Tests.hs view
@@ -1,24 +1,29 @@ {-# LANGUAGE FlexibleContexts #-}-module Math.Programming.Tests where -import           Control.Monad.IO.Class-import           Test.Tasty-import           Text.Printf+module Math.Programming.Tests where -import           Math.Programming-import           Math.Programming.Tests.Api-import           Math.Programming.Tests.IP-import           Math.Programming.Tests.LP+import Control.Monad+import Control.Monad.IO.Class+import Math.Programming+import Math.Programming.Tests.Api+import Math.Programming.Tests.Fuzz+import Math.Programming.Tests.IP+import Math.Programming.Tests.LP+import Test.Hspec+import Text.Printf -makeAllTests-  :: (PrintfArg (Numeric m), RealFrac  (Numeric m), MonadIO m, IPMonad m)-  => String           -- ^ The name of the API being tested. This will-                      -- be used to generate test group names.-  -> (m () -> IO ())  -- ^ The runner for the API being tested.-  -> TestTree         -- ^ The resulting test suite.-makeAllTests apiName runner-  = testGroup (printf "Math.Programming tests (%s)" apiName)-    [ makeApiTests runner-    , makeLPTests runner-    , makeIPTests runner-    ]+makeAllTests ::+  (MonadIO m, MonadIP v c o m) =>+  -- | The name of the API being tested. This will+  -- be used to generate test group names.+  String ->+  -- | The runner for the API being tested.+  (m () -> IO ()) ->+  -- | The resulting test suite.+  Spec+makeAllTests apiName runner =+  describe (printf "Math.Programming API tests (%s backend)" apiName) $ do+    makeApiTests runner+    makeLPTests runner+    makeIPTests runner+    makeFuzzTests (runner . void)
src/Math/Programming/Tests/Api.hs view
@@ -1,84 +1,37 @@-{-# LANGUAGE FlexibleContexts           #-}-{-# LANGUAGE FlexibleInstances          #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Math.Programming.Tests.Api where+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-} -import           Control.Monad.IO.Class-import           Test.Tasty-import           Test.Tasty.HUnit-import           Test.Tasty.QuickCheck+module Math.Programming.Tests.Api where -import           Math.Programming+import Control.Monad.IO.Class+import Math.Programming+import Test.Hspec -makeApiTests-  :: (Num (Numeric m), MonadIO m, LPMonad m)-  => (m () -> IO ())  -- ^ The runner for the API being tested.-  -> TestTree         -- ^ The resulting test suite.-makeApiTests runner = testGroup "API tests"-  [ testCase "Set/get variable names" (runner setGetVariableName)-  , testCase "Set/get constraint names" (runner setGetConstraintName)-  ]+makeApiTests ::+  (MonadIO m, MonadLP v c o m) =>+  -- | The runner for the API being tested.+  (m () -> IO ()) ->+  -- | The resulting test suite.+  Spec+makeApiTests runner =+  describe "API tests" $ do+    it "sets and gets variable names" (runner setGetVariableName)+    it "sets and gets constraint names" (runner setGetConstraintName) --- | We should be able to set and retrieve variable names-setGetVariableName :: (MonadIO m, LPMonad m) => m ()+setGetVariableName :: (MonadIO m, MonadLP v c o m) => m () setGetVariableName = do   let name = "foo"   x <- free   setVariableName x name-   vName <- getVariableName x-  liftIO $ vName @?= name+  liftIO $ vName `shouldBe` name --- | We should be able to set and retrieve constraint names-setGetConstraintName :: (Num (Numeric m), MonadIO m, LPMonad m) => m ()+setGetConstraintName :: (MonadIO m, MonadLP v c o m) => m () setGetConstraintName = do   let name = "foo"   x <- free-  c <- (x @>=# 0) `named` name+  c <- var x .>= 0+  setConstraintName c name   cName <- getConstraintName c-  liftIO $ cName @?= name--data Action-  = AddVariable-  | AddConstraint-  | AddThenDeleteVariable-  | AddThenDeleteConstraint-  deriving-    ( Enum-    , Show-    )--instance Arbitrary Action where-  arbitrary = elements actions-    where-      actions = [AddVariable .. AddThenDeleteConstraint]--newtype LPActions m = LPActions (m ())--instance LPMonad m => Arbitrary (LPActions m) where-  arbitrary = LPActions <$> sized lpActions--lpActions :: LPMonad m => Int -> Gen (m ())-lpActions remaining-  | remaining <= 0 = return (return ())-  | otherwise      = do-      action <- arbitrary-      case action of-        AddVariable-          -> return (addVariable >> return ())-        AddThenDeleteVariable-          -> bindOver addVariable removeVariable <$> lpActions (remaining - 1)-        _ -> return (return ())---- | Execute the monadic bind (>>=), with some other actions taken in--- between.-bindOver-  :: (Monad m)-  => m a         -- ^ The action providing the passed value-  -> (a -> m b)  -- ^ The function to bind to-  -> m ()        -- ^ The intermediate actions-  -> m b         -- ^ The resulting value-bindOver action fn intermediate = action >>= (\x -> intermediate >> fn x)--arbitraryLPActionsProp :: LPActions m -> m ()-arbitraryLPActionsProp (LPActions actions) = actions+  liftIO $ cName `shouldBe` name
+ src/Math/Programming/Tests/Fuzz.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}++-- | Fuzz testing for math programming backends.+module Math.Programming.Tests.Fuzz where++import Control.Monad.IO.Class+import Control.Monad.State+import Control.Monad.Writer+import qualified Data.Map as M+import qualified Data.Sequence as S+import qualified Data.Text as T+import Lens.Micro+import Lens.Micro.Mtl+import Lens.Micro.TH+import Math.Programming+import System.Random+import System.Random.Stateful+import Test.Hspec hiding (focus, pending)+import Test.Hspec.QuickCheck+import Test.QuickCheck++newtype Variable = Variable Int+  deriving+    ( Show,+      Eq,+      Ord+    )++newtype Constraint = Constraint Int+  deriving+    ( Show,+      Eq,+      Ord+    )++newtype Objective = Objective Int+  deriving+    ( Show,+      Eq,+      Ord+    )++-- | The types of actions we can perform on a linear program+data LPAction+  = AddVariable Variable+  | AddThenRemoveVariable Variable+  | AddConstraint Constraint+  | AddThenRemoveConstraint Constraint+  | AddObjective Objective+  | AddThenRemoveObjective Objective+  | Optimize+  deriving (Show)++newtype LPActions = LPActions [LPAction]+  deriving (Show)++instance Arbitrary LPActions where+  arbitrary = do+    NonNegative actionCount <- arbitrary+    actions <- forM [1 .. actionCount] $ \i -> do+      d7 <- fmap (`mod` (7 :: Int)) arbitrary+      pure $ case d7 of+        0 -> AddVariable (Variable i)+        1 -> AddThenRemoveVariable (Variable i)+        2 -> AddConstraint (Constraint i)+        3 -> AddThenRemoveConstraint (Constraint i)+        4 -> AddObjective (Objective i)+        5 -> AddThenRemoveObjective (Objective i)+        _ -> Optimize+    pure (LPActions actions)++data LPState v c o = LPState+  { _variables :: M.Map Variable v,+    _variableNames :: M.Map Variable T.Text,+    _constraints :: M.Map Constraint c,+    _constraintNames :: M.Map Constraint T.Text,+    _objectives :: M.Map Objective o,+    _objectiveNames :: M.Map Objective T.Text,+    _pending :: [LPAction],+    _randomGen :: IOGenM StdGen+  }++makeLenses ''LPState++initLPState :: Int -> [LPAction] -> IO (LPState v c o)+initLPState seed todo = do+  g <- newIOGenM (mkStdGen seed)+  pure+    LPState+      { _variables = M.empty,+        _variableNames = M.empty,+        _constraints = M.empty,+        _constraintNames = M.empty,+        _objectives = M.empty,+        _objectiveNames = M.empty,+        _pending = todo,+        _randomGen = g+      }++type LPFuzz v c o m =+  ( MonadState (LPState v c o) m,+    MonadLP v c o m,+    MonadWriter (S.Seq String) m,+    MonadIO m+  )++evalPending :: LPFuzz v c o m => m ()+evalPending = do+  todo <- use pending+  case todo of+    [] -> pure ()+    (x : xs) -> do+      assign pending xs+      evalAction x+      evalPending++evalAction :: LPFuzz v c o m => LPAction -> m ()+evalAction action = tell (S.singleton (show action)) >> evalAction' action++evalAction' :: LPFuzz v c o m => LPAction -> m ()+evalAction' (AddVariable k) = add k addVariable variables+evalAction' (AddThenRemoveVariable k) = addThenRemove k addVariable deleteVariable variables+evalAction' (AddConstraint k) = add k makeConstraint constraints+evalAction' (AddThenRemoveConstraint k) = addThenRemove k makeConstraint deleteConstraint constraints+evalAction' (AddObjective k) = add k makeObjective objectives+evalAction' (AddThenRemoveObjective k) = addThenRemove k makeObjective deleteObjective objectives+evalAction' Optimize = void optimizeLP++add :: (LPFuzz v c o m, Ord k) => k -> m a -> ASetter' (LPState v c o) (M.Map k a) -> m ()+add k create focus =+  create >>= modifying focus . M.insert k++addThenRemove :: (LPFuzz v c o m, Ord k) => k -> (m a) -> (a -> m ()) -> Lens' (LPState v c o) (M.Map k a) -> m ()+addThenRemove k create destroy focus = do+  collection <- use focus+  case M.lookup k collection of+    Just v -> destroy v >> modifying focus (M.delete k)+    Nothing -> add k create focus++makeConstraint :: LPFuzz v c o m => m c+makeConstraint = do+  lhs <- chooseExpr+  rhs <- chooseExpr+  op <- chooseInequality+  lhs `op` rhs++chooseExpr :: LPFuzz v c o m => m (Expr v)+chooseExpr = do+  g <- use randomGen+  vs <- use variables+  terms <- forM (M.elems vs) $ \v -> do+    c <- liftIO (uniformRM (-1e10, 1e10) g)+    pure (c *. v)++  pure (esum terms)++chooseInequality :: LPFuzz v c o m => m (Expr v -> Expr v -> m c)+chooseInequality = do+  g <- use randomGen+  d3 <- liftIO (uniformRM (0 :: Int, 2) g)+  case d3 of+    0 -> pure (.<=.)+    1 -> pure (.>=.)+    _ -> pure (.==.)++makeObjective :: LPFuzz v c o m => m o+makeObjective = do+  g <- use randomGen+  minimizing <- liftIO (uniformM g)++  if minimizing+    then chooseExpr >>= minimize+    else chooseExpr >>= maximize++makeFuzzTests ::+  (MonadIO m, MonadLP v c o m) =>+  -- | The runner for the API being tested.+  (m (S.Seq String) -> IO ()) ->+  -- | The resulting test suite.+  Spec+makeFuzzTests runner =+  describe "Fuzz testing" $ do+    prop "finds no failures" $ \seed (LPActions todo) -> do+      initState <- liftIO (initLPState seed todo)+      runner . execWriterT+        . flip evalStateT initState+        $ evalPending
src/Math/Programming/Tests/IP.hs view
@@ -1,20 +1,20 @@ {-# LANGUAGE FlexibleContexts #-}-module Math.Programming.Tests.IP where -import           Control.Monad.IO.Class-import           Test.Tasty-import           Test.Tasty.HUnit-import           Text.Printf+module Math.Programming.Tests.IP where -import           Math.Programming+import Control.Monad.IO.Class+import Math.Programming+import Test.Hspec -makeIPTests-  :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, IPMonad m)-  => (m () -> IO ())  -- ^ The runner for the API being tested.-  -> TestTree         -- ^ The resulting test suite.-makeIPTests runner = testGroup "IP problems"-  [ testCase "Simple MIP" (runner simpleMIPTest)-  ]+makeIPTests ::+  (MonadIO m, MonadIP v c o m) =>+  -- | The runner for the API being tested.+  (m () -> IO ()) ->+  -- | The resulting test suite.+  Spec+makeIPTests runner =+  describe "IP problems" $ do+    it "solves a simple MIP" (runner simpleMIPTest)  -- | We solve a simple MIP of the form --@@ -28,29 +28,27 @@ -- @ -- -- The optimal solution to this MIP is x = 2, y = 1.1.-simpleMIPTest :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, IPMonad m) => m ()+simpleMIPTest :: (MonadIO m, MonadIP v c o m) => m () simpleMIPTest = do   x <- bounded 0 5 `asKind` Integer   y <- bounded 0 5 `asKind` Continuous-  _ <- x @>=# 1.1-  _ <- y @>=# 1.1-  objective <- minimize $ x @+@ y+  _ <- var x .>= 1.1+  _ <- var y .>= 1.1+  objective <- minimize $ var x .+. var y   status <- optimizeIP    -- Check that we reached optimality-  liftIO $ status @?= Optimal+  liftIO $ status `shouldBe` Optimal -  vx <- getVariableValue x   let expectedX = 2-      xmsg = printf "Expected x to be 2, but is %.3f" vx-  liftIO $ assertBool xmsg (abs (vx - expectedX) <= 1e-3)+      expectedY = 1.1+      expectedObj = expectedX + expectedY +  vx <- getVariableValue x+  liftIO $ abs (vx - expectedX) `shouldSatisfy` (<= 1e-3)+   vy <- getVariableValue y-  let expectedY = 1.1-      ymsg = printf "Expected y to be 1.1, but is %.3f" vy-  liftIO $ assertBool ymsg (abs (vy - expectedY) <= 1e-3)+  liftIO $ abs (vy - expectedY) `shouldSatisfy` (<= 1e-3)    vobj <- getObjectiveValue objective-  let expectedObj = expectedX + expectedY-      objMsg = printf "Expected optimal solution to be %f, but is %f" expectedObj vobj-  liftIO $ assertBool objMsg (abs (vobj - expectedObj) <= 1e-3)+  liftIO $ abs (vobj - expectedObj) `shouldSatisfy` (<= 1e-3)
src/Math/Programming/Tests/LP.hs view
@@ -1,129 +1,122 @@-{-# LANGUAGE FlexibleContexts    #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-module Math.Programming.Tests.LP where -import           Control.Monad-import           Control.Monad.IO.Class-import           Test.Tasty-import           Test.Tasty.HUnit-import           Text.Printf+module Math.Programming.Tests.LP where -import           Math.Programming+import Control.Monad+import Control.Monad.IO.Class+import qualified Data.Text as T+import Math.Programming+import Test.Hspec+import Text.Printf -makeLPTests-  :: (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, LPMonad m)-  => (m () -> IO ())  -- ^ The runner for the API being tested.-  -> TestTree         -- ^ The resulting test suite.-makeLPTests runner = testGroup "LP problems"-  [ testCase "Diet problem" (runner dietProblemTest)-  ]+makeLPTests ::+  (MonadIO m, MonadLP v c o m) =>+  -- | The runner for the API being tested.+  (m () -> IO ()) ->+  -- | The resulting test suite.+  Spec+makeLPTests runner =+  describe "LP problems" $ do+    it "solves the diet problem" (runner dietProblemTest)  data Food = Corn | Milk | Bread   deriving-    ( Eq-    , Ord-    , Read-    , Show+    ( Eq,+      Ord,+      Read,+      Show     )  data Nutrient = Calories | VitaminA   deriving-    ( Eq-    , Ord-    , Read-    , Show+    ( Eq,+      Ord,+      Read,+      Show     ) -dietProblemTest :: forall m. (PrintfArg (Numeric m), RealFrac (Numeric m), MonadIO m, LPMonad m) => m ()+dietProblemTest :: (MonadIO m, MonadLP v c o m) => m () dietProblemTest =-  let-    cost :: Food -> Numeric m-    cost Corn  = 0.18-    cost Milk  = 0.23-    cost Bread = 0.05--    nutrition :: Nutrient -> Food -> Numeric m-    nutrition Calories Corn  = 72-    nutrition VitaminA Corn  = 107-    nutrition Calories Milk  = 121-    nutrition VitaminA Milk  = 500-    nutrition Calories Bread = 65-    nutrition VitaminA Bread = 0--    foods :: [Food]-    foods = [Corn, Milk, Bread]+  let cost :: Food -> Double+      cost Corn = 0.18+      cost Milk = 0.23+      cost Bread = 0.05 -    nutrients :: [Nutrient]-    nutrients = [Calories, VitaminA]+      nutrition :: Nutrient -> Food -> Double+      nutrition Calories Corn = 72+      nutrition VitaminA Corn = 107+      nutrition Calories Milk = 121+      nutrition VitaminA Milk = 500+      nutrition Calories Bread = 65+      nutrition VitaminA Bread = 0 -    maxServings :: Numeric m-    maxServings = 10+      foods :: [Food]+      foods = [Corn, Milk, Bread] -    nutrientBounds :: Nutrient -> (Numeric m, Numeric m)-    nutrientBounds Calories = (2000, 2250)-    nutrientBounds VitaminA = (5000, 50000)+      nutrients :: [Nutrient]+      nutrients = [Calories, VitaminA] -    expected :: Food -> Numeric m-    expected Corn  = 1.94-    expected Milk  = 10-    expected Bread = 10+      maxServings :: Double+      maxServings = 10 -    expectedCost :: Numeric m-    expectedCost = 3.15+      nutrientBounds :: Nutrient -> (Double, Double)+      nutrientBounds Calories = (2000, 2250)+      nutrientBounds VitaminA = (5000, 50000) -    amountInterval :: Bounds (Numeric m)-    amountInterval = Interval 0 maxServings+      expected :: Food -> Double+      expected Corn = 1.94+      expected Milk = 10+      expected Bread = 10 -    amountName :: Food -> String-    amountName food = printf "amount[%s]" (show food)+      expectedCost :: Double+      expectedCost = 3.15 -    nutrientMaxName :: Nutrient -> String-    nutrientMaxName nutrient = printf "%s_max" (show nutrient)+      amountInterval :: Bounds+      amountInterval = Interval 0 maxServings -    nutrientMinName :: Nutrient -> String-    nutrientMinName nutrient = printf "%s_min" (show nutrient)+      amountName :: Food -> T.Text+      amountName food = T.pack $ printf "amount[%s]" (show food) -  in do-    -- Create the decision variables-    amounts <- forM foods $ \food -> do-      v <- addVariable `within` amountInterval `named` (amountName food)-      return (food, v)+      nutrientMaxName :: Nutrient -> T.Text+      nutrientMaxName nutrient = T.pack $ printf "%s_max" (show nutrient) -    -- Create the nutrient constraints-    forM_ nutrients $ \nutrient -> do-      let lhs = exprSum [nutrition nutrient food #*@ v | (food, v) <- amounts]-          (lower, upper) = nutrientBounds nutrient-      _ <- (lhs .<=# upper) `named` (nutrientMaxName nutrient)-      _ <- (lhs .>=# lower) `named` (nutrientMinName nutrient)-      pure ()+      nutrientMinName :: Nutrient -> T.Text+      nutrientMinName nutrient = T.pack $ printf "%s_min" (show nutrient)+   in do+        -- Create the decision variables+        amounts <- forM foods $ \food -> do+          v <- free `within` amountInterval+          setVariableName v (amountName food)+          return (food, v) -    -- Set the objective-    let objectiveExpr = exprSum [cost food #*@ v | (food, v) <- amounts]-    objective <- addObjective objectiveExpr-    setObjectiveSense objective Minimization+        -- Create the nutrient constraints+        forM_ nutrients $ \nutrient -> do+          let lhs = esum [nutrition nutrient food *. v | (food, v) <- amounts]+              (lower, upper) = nutrientBounds nutrient+          u <- lhs .<= upper+          setConstraintName u (nutrientMaxName nutrient)+          l <- lhs .>= lower+          setConstraintName l (nutrientMinName nutrient)+          pure () -    -- Solve the problem-    status <- optimizeLP+        -- Set the objective+        let objectiveExpr = esum [cost food *. v | (food, v) <- amounts]+        objective <- addObjective objectiveExpr+        setObjectiveSense objective Minimization -    -- Check that we reached optimality-    liftIO $ status @?= Optimal+        -- Solve the problem+        status <- optimizeLP -    -- Check the variable values-    forM_ amounts $ \(food, v) -> do-      x <- getVariableValue v+        -- Check that we reached optimality+        liftIO $ status `shouldBe` Optimal -      let correct = expected food-          msg = printf-                "Amount of %s should be about %.2f, but is %.3f"-                (show food)-                correct-                x-      liftIO $ assertBool msg (abs (x - correct) <= 1e-1)+        -- Check the variable values+        forM_ amounts $ \(food, v) -> do+          x <- getVariableValue v+          liftIO $ abs (x - expected food) `shouldSatisfy` (<= 1e-1) -    -- Check the objective value-    objectiveValue <- evalExpr objectiveExpr-    let msg = printf-              "Objective should be about %.2f, but is %.3f"-              expectedCost-              objectiveValue-    liftIO $ assertBool msg (abs (objectiveValue - expectedCost) < 1e-1)+        -- Check the objective value+        objectiveValue <- evalExpr objectiveExpr+        liftIO $ abs (objectiveValue - expectedCost) `shouldSatisfy` (<= 1e-1)