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