MIP 0.2.0.0 → 0.2.0.1
raw patch · 20 files changed
+382/−32 lines, 20 filesdep +directoryPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: directory
API changes (from Hackage documentation)
+ Numeric.Optimization.MIP: ($dmdef) :: (Default a, Generic a, GDefault (Rep a)) => a
+ Numeric.Optimization.MIP.Base: ($dmdef) :: (Default a, Generic a, GDefault (Rep a)) => a
+ Numeric.Optimization.MIP.Solver: ($dmdef) :: (Default a, Generic a, GDefault (Rep a)) => a
+ Numeric.Optimization.MIP.Solver.Base: ($dmdef) :: (Default a, Generic a, GDefault (Rep a)) => a
- Numeric.Optimization.MIP: class () => Default a
+ Numeric.Optimization.MIP: class Default a
- Numeric.Optimization.MIP: data () => Extended r
+ Numeric.Optimization.MIP: data Extended r
- Numeric.Optimization.MIP: data () => OptDir
+ Numeric.Optimization.MIP: data OptDir
- Numeric.Optimization.MIP.Base: class () => Default a
+ Numeric.Optimization.MIP.Base: class Default a
- Numeric.Optimization.MIP.Base: data () => Extended r
+ Numeric.Optimization.MIP.Base: data Extended r
- Numeric.Optimization.MIP.Base: data () => OptDir
+ Numeric.Optimization.MIP.Base: data OptDir
- Numeric.Optimization.MIP.Solver: class () => Default a
+ Numeric.Optimization.MIP.Solver: class Default a
- Numeric.Optimization.MIP.Solver: class Monad m => IsSolver s m | s -> m
+ Numeric.Optimization.MIP.Solver: class Monad m => IsSolver s (m :: Type -> Type) | s -> m
- Numeric.Optimization.MIP.Solver.Base: class () => Default a
+ Numeric.Optimization.MIP.Solver.Base: class Default a
- Numeric.Optimization.MIP.Solver.Base: class Monad m => IsSolver s m | s -> m
+ Numeric.Optimization.MIP.Solver.Base: class Monad m => IsSolver s (m :: Type -> Type) | s -> m
Files
- ChangeLog.md +4/−1
- MIP.cabal +18/−1
- samples/lp/test-solution-cbc-infeasible.txt +4/−0
- samples/lp/test-solution-cbc-unbounded.txt +3/−0
- samples/lp/test-solution-cbc.txt +5/−0
- samples/lp/test-solution-cplex-unbounded.sol +32/−0
- samples/lp/test-solution-cplex.sol +36/−0
- samples/lp/test-solution-glpk-infeasible.sol +30/−0
- samples/lp/test-solution-glpk-long.sol +32/−0
- samples/lp/test-solution-glpk-unbounded.sol +27/−0
- samples/lp/test-solution-glpk.sol +31/−0
- samples/lp/test-solution-gurobi.sol +6/−0
- samples/lp/test-solution-highs-infeasible.sol +12/−0
- samples/lp/test-solution-highs-unbounded.sol +12/−0
- samples/lp/test-solution-highs.sol +22/−0
- samples/lp/test-solution-printemps-infeasible.json +22/−0
- samples/lp/test-solution-printemps.json +30/−0
- samples/lp/test-solution-scip.sol +6/−0
- src/Numeric/Optimization/MIP/Solver/CBC.hs +33/−30
- test/Test/MIPSolver.hs +17/−0
ChangeLog.md view
@@ -1,6 +1,9 @@ # Changelog for MIP -## Unreleased changes+## 0.2.0.1 (2026-01-05)++* Add solution files needed for testing as `extra-source-files`+* Produce more user-friendly error messages when the CBC solver exited with 0 but the output file was not generated (thanks to @dpvanbalen) ## 0.2.0.0 (2025-02-03)
MIP.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: MIP-version: 0.2.0.0+version: 0.2.0.1 synopsis: Library for using Mixed Integer Programming (MIP) description: Please see the README on GitHub at <https://github.com/msakai/haskell-MIP/tree/master/MIP#readme> category: Math, Algorithms, Optimisation, Optimization@@ -63,6 +63,22 @@ samples/lp/test-user-cuts.lp samples/lp/test.lp samples/lp/unbounded-ip.lp+ samples/lp/test-solution-cbc-infeasible.txt+ samples/lp/test-solution-cbc-unbounded.txt+ samples/lp/test-solution-cbc.txt+ samples/lp/test-solution-cplex-unbounded.sol+ samples/lp/test-solution-cplex.sol+ samples/lp/test-solution-glpk-infeasible.sol+ samples/lp/test-solution-glpk-long.sol+ samples/lp/test-solution-glpk-unbounded.sol+ samples/lp/test-solution-glpk.sol+ samples/lp/test-solution-gurobi.sol+ samples/lp/test-solution-highs-infeasible.sol+ samples/lp/test-solution-highs-unbounded.sol+ samples/lp/test-solution-highs.sol+ samples/lp/test-solution-scip.sol+ samples/lp/test-solution-printemps-infeasible.json+ samples/lp/test-solution-printemps.json extra-doc-files: doc-images/MIP-Status-diagram.png @@ -153,6 +169,7 @@ , base >=4.12 && <5 , containers >=0.5.0 , data-default-class+ , directory >=1.3.3.0 && <1.4.0.0 , extended-reals >=0.1 && <1.0 , filepath , hashable >=1.2.7.0 && <1.6.0.0
+ samples/lp/test-solution-cbc-infeasible.txt view
@@ -0,0 +1,4 @@+Integer infeasible - objective value 0.00000000+ 0 x 0.11111111 0+ 1 y 0 0+ 2 z 0.33333333 0
+ samples/lp/test-solution-cbc-unbounded.txt view
@@ -0,0 +1,3 @@+Unbounded - objective value 0.00000000+** 0 x 0 0+ 1 y 0 -1.5
+ samples/lp/test-solution-cbc.txt view
@@ -0,0 +1,5 @@+Optimal - objective value -122.50000000+ 0 x1 40 -4+ 1 x2 10.5 0+ 2 x3 19.5 0+ 3 x4 3 32.5
+ samples/lp/test-solution-cplex-unbounded.sol view
@@ -0,0 +1,32 @@+<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>+<CPLEXSolution version="1.2">+ <header+ problemName="samples/lp/test-solution-glpk-unbounded.lp"+ solutionName="incumbent"+ solutionIndex="-1"+ objectiveValue="3"+ solutionTypeValue="3"+ solutionTypeString="primal"+ solutionStatusValue="118"+ solutionStatusString="integer unbounded"+ solutionMethodString="mip"+ primalFeasible="1"+ dualFeasible="0"+ MIPNodes="0"+ MIPIterations="0"+ writeLevel="1"/>+ <quality+ epInt="1.0000000000000001e-05"+ epRHS="9.9999999999999995e-07"+ maxIntInfeas="0"+ maxPrimalInfeas="0"+ maxX="2"+ maxSlack="0"/>+ <linearConstraints>+ <constraint name="c1" index="0" slack="0"/>+ </linearConstraints>+ <variables>+ <variable name="x" index="0" value="1"/>+ <variable name="y" index="1" value="2"/>+ </variables>+</CPLEXSolution>
+ samples/lp/test-solution-cplex.sol view
@@ -0,0 +1,36 @@+<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>+<CPLEXSolution version="1.2">+ <header+ problemName="samples/lp/test.lp"+ solutionName="incumbent"+ solutionIndex="-1"+ objectiveValue="122.5"+ solutionTypeValue="3"+ solutionTypeString="primal"+ solutionStatusValue="101"+ solutionStatusString="integer optimal solution"+ solutionMethodString="mip"+ primalFeasible="1"+ dualFeasible="1"+ MIPNodes="0"+ MIPIterations="3"+ writeLevel="1"/>+ <quality+ epInt="1.0000000000000001e-05"+ epRHS="9.9999999999999995e-07"+ maxIntInfeas="0"+ maxPrimalInfeas="0"+ maxX="40"+ maxSlack="2"/>+ <linearConstraints>+ <constraint name="c1" index="0" slack="0"/>+ <constraint name="c2" index="1" slack="2"/>+ <constraint name="c3" index="2" slack="0"/>+ </linearConstraints>+ <variables>+ <variable name="x1" index="0" value="40"/>+ <variable name="x2" index="1" value="10.5"/>+ <variable name="x3" index="2" value="19.5"/>+ <variable name="x4" index="3" value="3"/>+ </variables>+</CPLEXSolution>
+ samples/lp/test-solution-glpk-infeasible.sol view
@@ -0,0 +1,30 @@+Problem: +Rows: 3+Columns: 3 (3 integer, 0 binary)+Non-zeros: 5+Status: INTEGER EMPTY+Objective: obj = 0 (MINimum)++ No. Row name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 r.4 0 0 = + 2 r.5 0 1 + 3 r.6 0 2 ++ No. Column name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 x * 0 + 2 y * 0 + 3 z * 0 0 ++Integer feasibility conditions:++KKT.PE: max.abs.err = 0.00e+00 on row 0+ max.rel.err = 0.00e+00 on row 0+ High quality++KKT.PB: max.abs.err = 1.00e+00 on row 2+ max.rel.err = 5.00e-01 on row 2+ SOLUTION IS INFEASIBLE++End of output
+ samples/lp/test-solution-glpk-long.sol view
@@ -0,0 +1,32 @@+Problem: +Rows: 3+Columns: 4 (1 integer, 0 binary)+Non-zeros: 9+Status: INTEGER OPTIMAL+Objective: obj = 122.5 (MAXimum)++ No. Row name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 c1 20 20 + 2 c2 28 30 + 3 c3 0 0 = ++ No. Column name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 x1AAAAAAAAAAAAAAAAAAAAAAAAAAAA+ 40 0 40 + 2 x2 10.5 0 + 3 x3 19.5 0 + 4 x4 * 3 2 3 ++Integer feasibility conditions:++KKT.PE: max.abs.err = 0.00e+00 on row 0+ max.rel.err = 0.00e+00 on row 0+ High quality++KKT.PB: max.abs.err = 0.00e+00 on row 0+ max.rel.err = 0.00e+00 on row 0+ High quality++End of output
+ samples/lp/test-solution-glpk-unbounded.sol view
@@ -0,0 +1,27 @@+Problem: +Rows: 1+Columns: 2 (2 integer, 0 binary)+Non-zeros: 2+Status: INTEGER UNDEFINED+Objective: obj = 0 (MAXimum)++ No. Row name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 c1 0 0 ++ No. Column name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 x * 0 1 + 2 y * 0 0 ++Integer feasibility conditions:++KKT.PE: max.abs.err = 0.00e+00 on row 0+ max.rel.err = 0.00e+00 on row 0+ High quality++KKT.PB: max.abs.err = 1.00e+00 on column 1+ max.rel.err = 5.00e-01 on column 1+ SOLUTION IS INFEASIBLE++End of output
+ samples/lp/test-solution-glpk.sol view
@@ -0,0 +1,31 @@+Problem: +Rows: 3+Columns: 4 (1 integer, 0 binary)+Non-zeros: 9+Status: INTEGER OPTIMAL+Objective: obj = 122.5 (MAXimum)++ No. Row name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 c1 20 20 + 2 c2 28 30 + 3 c3 0 0 = ++ No. Column name Activity Lower bound Upper bound+------ ------------ ------------- ------------- -------------+ 1 x1 40 0 40 + 2 x2 10.5 0 + 3 x3 19.5 0 + 4 x4 * 3 2 3 ++Integer feasibility conditions:++KKT.PE: max.abs.err = 0.00e+00 on row 0+ max.rel.err = 0.00e+00 on row 0+ High quality++KKT.PB: max.abs.err = 0.00e+00 on row 0+ max.rel.err = 0.00e+00 on row 0+ High quality++End of output
+ samples/lp/test-solution-gurobi.sol view
@@ -0,0 +1,6 @@+# Solution for model obj+# Objective value = 1.2250000000000006e+02+x1 40+x2 10.5+x3 1.9500000000000018e+01+x4 3
+ samples/lp/test-solution-highs-infeasible.sol view
@@ -0,0 +1,12 @@+Model status+Infeasible++# Primal solution values+None++# Dual solution values+None++# Basis+HiGHS v1+None
+ samples/lp/test-solution-highs-unbounded.sol view
@@ -0,0 +1,12 @@+Model status+Primal infeasible or unbounded++# Primal solution values+None++# Dual solution values+None++# Basis+HiGHS v1+None
+ samples/lp/test-solution-highs.sol view
@@ -0,0 +1,22 @@+Model status+Optimal++# Primal solution values+Feasible+Objective 122.5+# Columns 4+x1 40+x2 10.5+x3 19.5+x4 3+# Rows 3+c1 20+c2 28+c3 0++# Dual solution values+None++# Basis+HiGHS v1+None
+ samples/lp/test-solution-printemps-infeasible.json view
@@ -0,0 +1,22 @@+{ + "version": "v2.5.0", + "name": "infeasible2", + "number_of_variables": 2, + "number_of_constraints": 2, + "is_found_feasible_solution": false, + "objective": 0.000000e+00, + "total_violation": 2.000000e+00, + "variables": { + "x": 0, + "y": 0+ }, + "expressions": {}, + "constraints": { + "row1": 1.000000e+00, + "row2": 1.000000e+00+ }, + "violations": { + "row1": 1.000000e+00, + "row2": 1.000000e+00+ }+}
+ samples/lp/test-solution-printemps.json view
@@ -0,0 +1,30 @@+{ + "version": "v2.5.0", + "name": "test", + "number_of_variables": 4, + "number_of_constraints": 4, + "is_found_feasible_solution": true, + "objective": -1.070000e+02, + "total_violation": 0.000000e+00, + "variables": { + "x3": 22, + "x2": 7, + "x1": 29, + "x4": 2+ }, + "expressions": { + "x2_dependent": 7.000000e+00+ }, + "constraints": { + "c3_less": -3.000000e+00, + "c1": 0.000000e+00, + "c2": 0.000000e+00, + "c3": 0.000000e+00+ }, + "violations": { + "c3_less": 0.000000e+00, + "c1": 0.000000e+00, + "c2": 0.000000e+00, + "c3": 0.000000e+00+ }+}
+ samples/lp/test-solution-scip.sol view
@@ -0,0 +1,6 @@+solution status: optimal solution found+objective value: 122.5+x4 3 (obj:1)+x2 10.5 (obj:2)+x3 19.5 (obj:3)+x1 40 (obj:1)
src/Numeric/Optimization/MIP/Solver/CBC.hs view
@@ -17,10 +17,12 @@ , cbc ) where +import Control.Monad import Data.Default.Class import qualified Data.Text.Lazy.IO as TLIO+import System.Directory import System.Exit-import System.IO+import System.FilePath import System.IO.Temp import qualified Numeric.Optimization.MIP.Base as MIP import qualified Numeric.Optimization.MIP.LPFile as LPFile@@ -49,35 +51,36 @@ case LPFile.render def prob{ MIP.objectiveFunction = obj' } of Left err -> ioError $ userError err Right lp -> do- withSystemTempFile "cbc.lp" $ \fname1 h1 -> do- TLIO.hPutStr h1 lp- hClose h1- withSystemTempFile "cbc.sol" $ \fname2 h2 -> do- hClose h2- let args = cbcArgs solver- ++ [fname1]- ++ (case solveTimeLimit opt of- Nothing -> []- Just sec -> ["sec", show sec])- ++ (case solveTol opt of- Nothing -> []- Just tol ->- [ "integerTolerance", show (MIP.integralityTol tol)- , "primalTolerance", show (MIP.feasibilityTol tol)- , "dualTolerance", show (MIP.optimalityTol tol)- ])- ++ ["solve", "solu", fname2]- onGetLine = solveLogger opt- onGetErrorLine = solveErrorLogger opt- exitcode <- runProcessWithOutputCallback (cbcPath solver) args Nothing "" onGetLine onGetErrorLine- case exitcode of- ExitFailure n -> ioError $ userError $ "exit with " ++ show n- ExitSuccess -> do- sol <- CBCSol.readFile fname2- if isMax then- return $ sol{ MIP.solObjectiveValue = fmap negate (MIP.solObjectiveValue sol) }- else- return sol+ withSystemTempDirectory "haskell-mip-cbc" $ \dir -> do+ let fname1 = dir </> "cbc.lp"+ fname2 = dir </> "cbc.sol"+ TLIO.writeFile fname1 lp+ let args = cbcArgs solver+ ++ [fname1]+ ++ (case solveTimeLimit opt of+ Nothing -> []+ Just sec -> ["sec", show sec])+ ++ (case solveTol opt of+ Nothing -> []+ Just tol ->+ [ "integerTolerance", show (MIP.integralityTol tol)+ , "primalTolerance", show (MIP.feasibilityTol tol)+ , "dualTolerance", show (MIP.optimalityTol tol)+ ])+ ++ ["solve", "solu", fname2]+ onGetLine = solveLogger opt+ onGetErrorLine = solveErrorLogger opt+ exitcode <- runProcessWithOutputCallback (cbcPath solver) args Nothing "" onGetLine onGetErrorLine+ case exitcode of+ ExitFailure n -> ioError $ userError $ "exit with " ++ show n+ ExitSuccess -> do+ m <- doesFileExist fname2+ unless m $ ioError $ userError "CBC returned exit code 0, but wrote no solution file. You may want to use the solveLogger or solveErrorLogger for more information"+ sol <- CBCSol.readFile fname2+ if isMax then+ return $ sol{ MIP.solObjectiveValue = fmap negate (MIP.solObjectiveValue sol) }+ else+ return sol where obj = MIP.objectiveFunction prob isMax = MIP.objDir obj == MIP.OptMax
test/Test/MIPSolver.hs view
@@ -1,10 +1,12 @@ {-# OPTIONS_GHC -Wall -Wno-unused-top-binds #-} {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} module Test.MIPSolver (mipSolverTestGroup) where import Control.Arrow ((***))+import Control.Exception import Control.Monad import Data.Default.Class import qualified Data.Map as Map@@ -56,6 +58,20 @@ , " but got: " ++ show status ] +case_cbc_no_output_file :: Assertion+case_cbc_no_output_file = do+ let prob = def+ { MIP.constraints =+ [ MIP.varExpr "x" - MIP.varExpr "x" MIP..<=. 0+ ]+ , MIP.varDomains = Map.fromList [("x", (MIP.ContinuousVariable, (0, 1)))]+ }+ ret <- try $ solve cbc def prob+ let expected = userError "CBC returned exit code 0, but wrote no solution file. You may want to use the solveLogger or solveErrorLogger for more information"+ case ret of+ Left (e :: IOException) -> e @?= expected+ Right _ -> assertFailure "An exception should have been raised"+ -- ------------------------------------------------------------------------ case_cplex :: Assertion@@ -318,6 +334,7 @@ , testCase "cbc unbounded" case_cbc_unbounded , testCase "cbc infeasible" case_cbc_infeasible , testCase "cbc infeasible2" case_cbc_infeasible2+ , testCase "cbc no output file" case_cbc_no_output_file ] #endif #ifdef TEST_CPLEX