packages feed

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 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