├── README.md ├── test ├── runtests.jl ├── mathprog.jl ├── params.jl └── setbounds.jl ├── .travis.yml ├── Project.toml ├── src ├── GLPKMathProgInterface.jl ├── GLPKInterfaceLP.jl ├── GLPKInterfaceBase.jl └── GLPKInterfaceMIP.jl ├── .gitignore └── LICENSE.md /README.md: -------------------------------------------------------------------------------- 1 | GLPKMathProgInterface.jl 2 | ======================== 3 | 4 | **This package has been deprecated. Use https://github.com/JuliaOpt/GLPK.jl instead.** 5 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using GLPKMathProgInterface, GLPK 2 | using MathProgBase 3 | using Test 4 | 5 | include("params.jl") 6 | include("setbounds.jl") 7 | include("mathprog.jl") 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | os: 3 | - linux 4 | - osx 5 | julia: 6 | - 1.0 7 | - 1 8 | notifications: 9 | email: false 10 | sudo: false # use a docker worker 11 | addons: 12 | apt_packages: 13 | - libgmp-dev 14 | # uncomment the following lines to override the default test script 15 | #script: 16 | # - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi 17 | # - julia --check-bounds=yes -e 'Pkg.clone(pwd()); Pkg.build("GLPK"); Pkg.test("GLPK")' 18 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "GLPKMathProgInterface" 2 | uuid = "3c7084bd-78ad-589a-b5bb-dbd673274bea" 3 | version = "0.5.0" 4 | 5 | [deps] 6 | GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" 7 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 8 | MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73" 9 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 10 | 11 | [compat] 12 | GLPK = "0.6,0.7,0.8,0.9,0.10,0.11,0.12,0.13" 13 | MathProgBase = "0.5,0.7,0.8" 14 | julia = "1" 15 | 16 | [extras] 17 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 18 | 19 | [targets] 20 | test = ["Test"] 21 | -------------------------------------------------------------------------------- /src/GLPKMathProgInterface.jl: -------------------------------------------------------------------------------- 1 | __precompile__() 2 | module GLPKMathProgInterface 3 | 4 | export 5 | GLPKInterfaceLP, 6 | GLPKInterfaceMIP, 7 | GLPKSolverLP, 8 | GLPKSolverMIP 9 | 10 | include("GLPKInterfaceBase.jl") 11 | include("GLPKInterfaceLP.jl") 12 | include("GLPKInterfaceMIP.jl") 13 | 14 | const GLPKSolverLP = GLPKInterfaceLP.GLPKSolverLP 15 | const GLPKSolverMIP = GLPKInterfaceMIP.GLPKSolverMIP 16 | 17 | # Enables GLPK to act as a conic solver 18 | import MathProgBase 19 | MathProgBase.ConicModel(s::Union{GLPKSolverLP,GLPKSolverMIP}) = MathProgBase.LPQPtoConicBridge(MathProgBase.LinearQuadraticModel(s)) 20 | MathProgBase.supportedcones(::Union{GLPKSolverLP,GLPKSolverMIP}) = [:Free,:Zero,:NonNeg,:NonPos] 21 | 22 | end 23 | -------------------------------------------------------------------------------- /test/mathprog.jl: -------------------------------------------------------------------------------- 1 | const mathprogbase_test = joinpath(dirname(pathof(MathProgBase)), "..", "test") 2 | 3 | @testset "MathProgBase tests" begin 4 | 5 | include(joinpath(mathprogbase_test, "linprog.jl")) 6 | linprogtest(GLPKSolverLP()) 7 | 8 | include(joinpath(mathprogbase_test, "linproginterface.jl")) 9 | linprogsolvertest(GLPKSolverLP()) 10 | 11 | include(joinpath(mathprogbase_test, "mixintprog.jl")) 12 | mixintprogtest(GLPKSolverMIP()) 13 | 14 | include(joinpath(mathprogbase_test,"conicinterface.jl")) 15 | coniclineartest(GLPKSolverLP()) 16 | 17 | solver = GLPKSolverMIP(msg_lev=GLPK.MSG_ALL) 18 | 19 | # Silent should override GLPK.MSG_ALL 20 | MathProgBase.setparameters!(solver, Silent=true, TimeLimit=100.0) 21 | coniclineartest(solver) 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/params.jl: -------------------------------------------------------------------------------- 1 | @testset "Set parameters" begin 2 | lpm = MathProgBase.LinearQuadraticModel(GLPKSolverLP(it_lim=9513, tol_bnd=4.149)) 3 | @test lpm.param.it_lim == 9513 4 | @test lpm.param.tol_bnd == 4.149 5 | mipm = MathProgBase.LinearQuadraticModel(GLPKSolverMIP(it_lim=5910, tol_obj=1.52e-3)) 6 | @test mipm.smplxparam.it_lim == 5910 7 | @test mipm.param.tol_obj == 1.52e-3 8 | end 9 | 10 | @testset "Set MPB parameters" begin 11 | # setparameters! on model 12 | lpm = MathProgBase.LinearQuadraticModel(GLPKSolverLP()) 13 | MathProgBase.setparameters!(lpm, TimeLimit=23.0) 14 | @test lpm.param.tm_lim == 23000.0 15 | 16 | # setparameters! on solver 17 | lps = GLPKSolverLP() 18 | MathProgBase.setparameters!(lps, TimeLimit=23.0) 19 | lpm2 = MathProgBase.LinearQuadraticModel(lps) 20 | @test lpm2.param.tm_lim == 23000.0 21 | end 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/julia 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=julia 4 | 5 | ### Julia ### 6 | # Files generated by invoking Julia with --code-coverage 7 | *.jl.cov 8 | *.jl.*.cov 9 | 10 | # Files generated by invoking Julia with --track-allocation 11 | *.jl.mem 12 | 13 | # System-specific files and directories generated by the BinaryProvider and BinDeps packages 14 | # They contain absolute paths specific to the host computer, and so should not be committed 15 | deps/deps.jl 16 | deps/build.log 17 | deps/downloads/ 18 | deps/usr/ 19 | deps/src/ 20 | 21 | # Build artifacts for creating documentation generated by the Documenter package 22 | docs/build/ 23 | docs/site/ 24 | 25 | # File generated by Pkg, the package manager, based on a corresponding Project.toml 26 | # It records a fixed state of all packages used by the project. As such, it should not be 27 | # committed for packages, but should be committed for applications that require a static 28 | # environment. 29 | Manifest.toml 30 | 31 | # End of https://www.toptal.com/developers/gitignore/api/julia 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The GLPKMathProgInterface.jl Julia module is licensed under the MIT License: 2 | 3 | > Copyright (c) 2013: Carlo Baldassi 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/setbounds.jl: -------------------------------------------------------------------------------- 1 | using GLPKMathProgInterface, MathProgBase 2 | using Test 3 | 4 | @testset "Invalid bounds" begin 5 | solver = GLPKSolverLP() 6 | m = MathProgBase.LinearQuadraticModel(solver) 7 | function testmodel(sense) 8 | MathProgBase.optimize!(m) 9 | @test MathProgBase.status(m) == :Infeasible 10 | @test MathProgBase.getobjval(m) == (sense == :Min ? Inf : -Inf) 11 | @test MathProgBase.getinfeasibilityray(m) == zeros(1) 12 | @test_throws ErrorException MathProgBase.getsolution(m) 13 | @test_throws ErrorException MathProgBase.getconstrsolution(m) 14 | @test_throws ErrorException MathProgBase.getreducedcosts(m) 15 | @test_throws ErrorException MathProgBase.getconstrduals(m) 16 | @test_throws ErrorException MathProgBase.getunboundedray(m) 17 | end 18 | # invalid variable bounds 19 | MathProgBase.loadproblem!(m, [1 0], [0, 0], [1, -1], [0, 0], [0], [1], :Min) 20 | testmodel(:Min) 21 | # invalid constraint bounds 22 | MathProgBase.loadproblem!(m, [1 0], [0, 0], [1, 1], [0, 0], [0], [-1], :Max) 23 | testmodel(:Max) 24 | MathProgBase.loadproblem!(m, [1 0], [0, 0], [1, 1], [0, 0], [0], [1], :Min) 25 | # Those do not throws errors since the invalid bounds may be temporary 26 | MathProgBase.setvarLB!(m, [2, 0]) 27 | MathProgBase.setvarUB!(m, [1, -1]) 28 | MathProgBase.setconstrLB!(m, [2]) 29 | MathProgBase.setconstrUB!(m, [-1]) 30 | testmodel(:Min) 31 | end 32 | -------------------------------------------------------------------------------- /src/GLPKInterfaceLP.jl: -------------------------------------------------------------------------------- 1 | module GLPKInterfaceLP 2 | 3 | using SparseArrays 4 | using LinearAlgebra 5 | 6 | import GLPK 7 | import MathProgBase 8 | const MPB = MathProgBase 9 | using ..GLPKInterfaceBase 10 | 11 | export GLPKSolverLP 12 | 13 | mutable struct GLPKMathProgModelLP <: GLPKMathProgModel 14 | inner::GLPK.Prob 15 | method::Symbol 16 | param::Union{GLPK.SimplexParam, GLPK.InteriorParam} 17 | infeasible_bounds::Bool 18 | end 19 | 20 | mutable struct GLPKSolverLP <: MPB.AbstractMathProgSolver 21 | presolve::Bool 22 | method::Symbol 23 | opts 24 | function GLPKSolverLP(;presolve::Bool=false, method::Symbol=:Simplex, opts...) 25 | method in [:Simplex, :Exact, :InteriorPoint] || 26 | error(""" 27 | Unknown method for GLPK LP solver: $method 28 | Allowed methods: 29 | :Simplex 30 | :Exact 31 | :InteriorPoint""") 32 | new(presolve, method, opts) 33 | end 34 | end 35 | 36 | function Base.copy(m::GLPKMathProgModelLP) 37 | 38 | m2inner = GLPK.Prob() 39 | 40 | GLPK.copy_prob(m2inner, m.inner, GLPK.ON) 41 | 42 | return GLPKMathProgModelLP(m2inner, m.method, deepcopy(m.param), m.infeasible_bounds) 43 | end 44 | 45 | function MPB.LinearQuadraticModel(s::GLPKSolverLP) 46 | if s.method == :Simplex || s.method == :Exact 47 | param = GLPK.SimplexParam() 48 | if s.presolve 49 | param.presolve = GLPK.ON 50 | end 51 | elseif s.method == :InteriorPoint 52 | param = GLPK.InteriorParam() 53 | if s.presolve 54 | @warn "Ignored option: presolve" 55 | end 56 | else 57 | error("This is a bug") 58 | end 59 | param.msg_lev = GLPK.MSG_ERR 60 | for (k,v) in s.opts 61 | i = findfirst(x->x==k, fieldnames(typeof(param))) 62 | if (VERSION < v"0.7-" && i > 0) || (VERSION >= v"0.7-" && i !== nothing) 63 | t = typeof(param).types[i] 64 | setfield!(param, i, convert(t, v)) 65 | else 66 | @warn "Ignored option: $(string(k))" 67 | end 68 | end 69 | lpm = GLPKMathProgModelLP(GLPK.Prob(), s.method, param, false) 70 | return lpm 71 | end 72 | 73 | function MPB.setparameters!(s::GLPKSolverLP; mpboptions...) 74 | opts = collect(Any, s.opts) 75 | for (optname, optval) in mpboptions 76 | if optname == :TimeLimit 77 | push!(opts, (:tm_lim,round(Int,1000*optval))) # milliseconds 78 | elseif optname == :Silent 79 | if optval == true 80 | push!(opts, (:msg_lev,GLPK.MSG_OFF)) 81 | end 82 | else 83 | error("Unrecognized parameter $optname") 84 | end 85 | end 86 | s.opts = opts 87 | nothing 88 | end 89 | 90 | function MPB.setparameters!(m::GLPKMathProgModelLP; mpboptions...) 91 | for (optname, optval) in mpboptions 92 | if optname == :TimeLimit 93 | m.param.tm_lim = round(Int,1000*optval) 94 | elseif optname == :Silent 95 | if optval == true 96 | m.param.msg_lev = GLPK.MSG_OFF 97 | m.smplxparam.msg_lev = GLPK.MSG_OFF 98 | end 99 | else 100 | error("Unrecognized parameter $optname") 101 | end 102 | end 103 | end 104 | 105 | function MPB.optimize!(lpm::GLPKMathProgModelLP) 106 | lpm.infeasible_bounds = false 107 | lp = lpm.inner 108 | for c in 1:MPB.numvar(lpm) 109 | if GLPK.get_col_lb(lp, c) > GLPK.get_col_ub(lp, c) 110 | lpm.infeasible_bounds = true 111 | break 112 | end 113 | end 114 | if !lpm.infeasible_bounds 115 | for r in 1:MPB.numconstr(lpm) 116 | if GLPK.get_row_lb(lp, r) > GLPK.get_row_ub(lp, r) 117 | lpm.infeasible_bounds = true 118 | break 119 | end 120 | end 121 | end 122 | if !lpm.infeasible_bounds 123 | if lpm.method == :Simplex 124 | solve = GLPK.simplex 125 | elseif lpm.method == :Exact 126 | solve = GLPK.exact 127 | elseif lpm.method == :InteriorPoint 128 | solve = GLPK.interior 129 | else 130 | error("bug") 131 | end 132 | return solve(lpm.inner, lpm.param) 133 | end 134 | end 135 | 136 | function MPB.status(lpm::GLPKMathProgModelLP) 137 | if lpm.infeasible_bounds 138 | return :Infeasible 139 | end 140 | if lpm.method == :Simplex || lpm.method == :Exact 141 | get_status = GLPK.get_status 142 | elseif lpm.method == :InteriorPoint 143 | get_status = GLPK.ipt_status 144 | else 145 | error("bug") 146 | end 147 | s = get_status(lpm.inner) 148 | if s == GLPK.OPT 149 | return :Optimal 150 | elseif s == GLPK.INFEAS 151 | return :Infeasible 152 | elseif s == GLPK.UNBND 153 | return :Unbounded 154 | elseif s == GLPK.FEAS 155 | return :Feasible 156 | elseif s == GLPK.NOFEAS 157 | return :Infeasible 158 | elseif s == GLPK.UNDEF 159 | return :Undefined 160 | else 161 | error("Internal library error") 162 | end 163 | end 164 | 165 | function MPB.getobjval(lpm::GLPKMathProgModelLP) 166 | if lpm.infeasible_bounds 167 | if GLPK.get_obj_dir(lpm.inner) == GLPK.MAX 168 | return -Inf 169 | else 170 | return Inf 171 | end 172 | end 173 | if lpm.method == :Simplex || lpm.method == :Exact 174 | get_obj_val = GLPK.get_obj_val 175 | elseif lpm.method == :InteriorPoint 176 | get_obj_val = GLPK.ipt_obj_val 177 | else 178 | error("bug") 179 | end 180 | return get_obj_val(lpm.inner) 181 | end 182 | 183 | function check_feasible_bounds(lpm::GLPKMathProgModelLP, name::String) 184 | if lpm.infeasible_bounds 185 | error("$name is not available when some constraint bounds are infeasible (lower bound > upper bound)") 186 | end 187 | end 188 | 189 | 190 | function MPB.getsolution(lpm::GLPKMathProgModelLP) 191 | check_feasible_bounds(lpm, "getsolution") 192 | lp = lpm.inner 193 | n = GLPK.get_num_cols(lp) 194 | 195 | if lpm.method == :Simplex || lpm.method == :Exact 196 | get_col_prim = GLPK.get_col_prim 197 | elseif lpm.method == :InteriorPoint 198 | get_col_prim = GLPK.ipt_col_prim 199 | else 200 | error("bug") 201 | end 202 | 203 | return [get_col_prim(lp, i) for i in 1:n] 204 | end 205 | 206 | function MPB.getconstrsolution(lpm::GLPKMathProgModelLP) 207 | check_feasible_bounds(lpm, "getconstrsolution") 208 | lp = lpm.inner 209 | m = GLPK.get_num_rows(lp) 210 | 211 | if lpm.method == :Simplex || lpm.method == :Exact 212 | get_row_prim = GLPK.get_row_prim 213 | elseif lpm.method == :InteriorPoint 214 | get_row_prim = GLPK.ipt_row_prim 215 | else 216 | error("bug") 217 | end 218 | 219 | return [get_row_prim(lp, i) for i in 1:m] 220 | end 221 | 222 | function MPB.getreducedcosts(lpm::GLPKMathProgModelLP) 223 | check_feasible_bounds(lpm, "getreducedcosts") 224 | lp = lpm.inner 225 | n = GLPK.get_num_cols(lp) 226 | 227 | if lpm.method == :Simplex || lpm.method == :Exact 228 | get_col_dual = GLPK.get_col_dual 229 | elseif lpm.method == :InteriorPoint 230 | get_col_dual = GLPK.ipt_col_dual 231 | else 232 | error("bug") 233 | end 234 | 235 | return [get_col_dual(lp, i) for i in 1:n] 236 | end 237 | 238 | function MPB.getconstrduals(lpm::GLPKMathProgModelLP) 239 | check_feasible_bounds(lpm, "getconstrduals") 240 | 241 | lp = lpm.inner 242 | m = GLPK.get_num_rows(lp) 243 | 244 | if lpm.method == :Simplex || lpm.method == :Exact 245 | get_row_dual = GLPK.get_row_dual 246 | elseif lpm.method == :InteriorPoint 247 | get_row_dual = GLPK.ipt_row_dual 248 | else 249 | error("bug") 250 | end 251 | 252 | return [get_row_dual(lp, i) for i in 1:m] 253 | end 254 | 255 | # The functions getinfeasibilityray and getunboundedray are adapted from code 256 | # taken from the LEMON C++ optimization library. This is the copyright notice: 257 | # 258 | ### Copyright (C) 2003-2010 259 | ### Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport 260 | ### (Egervary Research Group on Combinatorial Optimization, EGRES). 261 | ### 262 | ### Permission to use, modify and distribute this software is granted 263 | ### provided that this copyright notice appears in all copies. For 264 | ### precise terms see the accompanying LICENSE file. 265 | ### 266 | ### This software is provided "AS IS" with no warranty of any kind, 267 | ### express or implied, and with no claim as to its suitability for any 268 | ### purpose. 269 | 270 | function MPB.getinfeasibilityray(lpm::GLPKMathProgModelLP) 271 | if lpm.infeasible_bounds 272 | # See https://github.com/JuliaOpt/GLPKMathProgInterface.jl/pull/34 273 | return zeros(MPB.numconstr(lpm)) 274 | end 275 | 276 | lp = lpm.inner 277 | 278 | if lpm.method == :Simplex || lpm.method == :Exact 279 | elseif lpm.method == :InteriorPoint 280 | error("getinfeasibilityray is not available when using the InteriorPoint method") 281 | else 282 | error("bug") 283 | end 284 | 285 | m = GLPK.get_num_rows(lp) 286 | 287 | ray = zeros(m) 288 | 289 | ur = GLPK.get_unbnd_ray(lp) 290 | if ur != 0 291 | if ur <= m 292 | k = ur 293 | get_stat = GLPK.get_row_stat 294 | get_bind = GLPK.get_row_bind 295 | get_prim = GLPK.get_row_prim 296 | get_ub = GLPK.get_row_ub 297 | else 298 | k = ur - m 299 | get_stat = GLPK.get_col_stat 300 | get_bind = GLPK.get_col_bind 301 | get_prim = GLPK.get_col_prim 302 | get_ub = GLPK.get_col_ub 303 | end 304 | 305 | get_stat(lp, k) == GLPK.BS || error("unbounded ray is primal (use getunboundedray)") 306 | 307 | ray[get_bind(lp, k)] = (get_prim(lp, k) > get_ub(lp, k)) ? -1 : 1 308 | 309 | GLPK.btran(lp, ray) 310 | else 311 | eps = 1e-7 312 | for i = 1:m 313 | idx = GLPK.get_bhead(lp, i) 314 | if idx <= m 315 | k = idx 316 | get_prim = GLPK.get_row_prim 317 | get_ub = GLPK.get_row_ub 318 | get_lb = GLPK.get_row_lb 319 | else 320 | k = idx - m 321 | get_prim = GLPK.get_col_prim 322 | get_ub = GLPK.get_col_ub 323 | get_lb = GLPK.get_col_lb 324 | end 325 | 326 | res = get_prim(lp, k) 327 | if res > get_ub(lp, k) + eps 328 | ray[i] = -1 329 | elseif res < get_lb(lp, k) - eps 330 | ray[i] = 1 331 | else 332 | continue # ray[i] == 0 333 | end 334 | 335 | if idx <= m 336 | ray[i] *= GLPK.get_rii(lp, k) 337 | else 338 | ray[i] /= GLPK.get_sjj(lp, k) 339 | end 340 | end 341 | 342 | GLPK.btran(lp, ray) 343 | 344 | for i = 1:m 345 | ray[i] /= GLPK.get_rii(lp, i) 346 | end 347 | end 348 | 349 | return ray 350 | end 351 | 352 | function MPB.getunboundedray(lpm::GLPKMathProgModelLP) 353 | check_feasible_bounds(lpm, "getreducedcosts") 354 | 355 | lp = lpm.inner 356 | 357 | if lpm.method == :Simplex || lpm.method == :Exact 358 | elseif lpm.method == :InteriorPoint 359 | error("getunboundedray is not available when using the InteriorPoint method") 360 | else 361 | error("bug") 362 | end 363 | 364 | m = GLPK.get_num_rows(lp) 365 | n = GLPK.get_num_cols(lp) 366 | 367 | ray = zeros(n) 368 | 369 | ur = GLPK.get_unbnd_ray(lp) 370 | if ur != 0 371 | if ur <= m 372 | k = ur 373 | get_stat = GLPK.get_row_stat 374 | get_dual = GLPK.get_row_dual 375 | else 376 | k = ur - m 377 | get_stat = GLPK.get_col_stat 378 | get_dual = GLPK.get_col_dual 379 | ray[k] = 1 380 | end 381 | 382 | get_stat(lp, k) != GLPK.BS || error("unbounded ray is dual (use getinfeasibilityray)") 383 | 384 | for (ri, rv) in zip(GLPK.eval_tab_col(lp, ur)...) 385 | ri > m && (ray[ri - m] = rv) 386 | end 387 | 388 | if (GLPK.get_obj_dir(lp) == GLPK.MAX) ⊻ (get_dual(lp, k) > 0) 389 | ray .*= -1.0 390 | end 391 | else 392 | for i = 1:n 393 | ray[i] = GLPK.get_col_prim(lp, i) 394 | end 395 | end 396 | 397 | return ray 398 | end 399 | 400 | end 401 | -------------------------------------------------------------------------------- /src/GLPKInterfaceBase.jl: -------------------------------------------------------------------------------- 1 | module GLPKInterfaceBase 2 | 3 | using SparseArrays 4 | using LinearAlgebra 5 | import GLPK 6 | 7 | import MathProgBase 8 | const MPB = MathProgBase 9 | 10 | export GLPKMathProgModel 11 | 12 | 13 | abstract type GLPKMathProgModel <: MPB.AbstractLinearQuadraticModel end 14 | 15 | function MPB.loadproblem!(lpm::GLPKMathProgModel, filename::AbstractString) 16 | if endswith(filename, ".mps") || endswith(filename, ".mps.gz") 17 | GLPK.read_mps(lpm.inner, GLPK.MPS_FILE, filename) 18 | elseif endswith(filename, ".lp") || endswith(filename, ".lp.gz") 19 | GLPK.read_lp(lpm.inner, filename) 20 | elseif endswith(filename, ".prob") || endswith(filename, ".prob.gz") 21 | GLPK.read_prob(lpm.inner, filename) 22 | else 23 | error("unrecognized input format extension in $filename") 24 | end 25 | end 26 | 27 | nonnull(x) = (x != nothing && !isempty(x)) 28 | 29 | function MPB.loadproblem!(lpm::GLPKMathProgModel, A::AbstractMatrix, collb, colub, obj, rowlb, rowub, sense) 30 | lp = lpm.inner 31 | 32 | m, n = size(A) 33 | 34 | function checksize(x, l, str) 35 | if nonnull(x) && length(x) != l 36 | error("size of $str is incompatible with size of A") 37 | end 38 | end 39 | 40 | checksize(collb, n, "collb") 41 | checksize(colub, n, "colub") 42 | checksize(rowlb, m, "rowlb") 43 | checksize(rowub, m, "rowub") 44 | checksize(obj, n, "obj") 45 | 46 | (ia, ja, ar) = findnz(sparse(A)) 47 | 48 | GLPK.erase_prob(lp) 49 | 50 | function getbounds(lb, ub, i) 51 | if nonnull(lb) && nonnull(ub) && lb[i] != -Inf && ub[i] != Inf 52 | if lb[i] != ub[i] 53 | return lb[i], ub[i], GLPK.DB 54 | else 55 | return lb[i], ub[i], GLPK.FX 56 | end 57 | elseif nonnull(lb) && lb[i] != -Inf 58 | return lb[i], Inf, GLPK.LO 59 | elseif nonnull(ub) && ub[i] != Inf 60 | return -Inf, ub[i], GLPK.UP 61 | else 62 | return -Inf, Inf, GLPK.FR 63 | end 64 | end 65 | 66 | m > 0 && GLPK.add_rows(lp, m) 67 | prev_preemptive_check = GLPK.jl_get_preemptive_check() 68 | GLPK.jl_set_preemptive_check(false) 69 | for r = 1:m 70 | #println(" r=$r b=$(b[r])") 71 | l, u, t = getbounds(rowlb, rowub, r) 72 | GLPK.set_row_bnds(lp, r, t, l, u) 73 | end 74 | GLPK.jl_set_preemptive_check(prev_preemptive_check) 75 | 76 | n > 0 && GLPK.add_cols(lp, n) 77 | prev_preemptive_check = GLPK.jl_get_preemptive_check() 78 | GLPK.jl_set_preemptive_check(false) 79 | for c = 1:n 80 | #println(" r=$r b=$(b[r])") 81 | l, u, t = getbounds(collb, colub, c) 82 | GLPK.set_col_bnds(lp, c, t, l, u) 83 | end 84 | GLPK.jl_set_preemptive_check(prev_preemptive_check) 85 | 86 | if nonnull(obj) 87 | for c = 1:n 88 | GLPK.set_obj_coef(lp, c, obj[c]) 89 | end 90 | else 91 | for c = 1:n 92 | GLPK.set_obj_coef(lp, c, 0) 93 | end 94 | end 95 | 96 | m > 0 && n > 0 && GLPK.load_matrix(lp, ia, ja, ar) 97 | MPB.setsense!(lpm, sense) 98 | 99 | return lpm 100 | end 101 | 102 | #writeproblem(m, filename::AbstractString) 103 | 104 | function MPB.getvarLB(lpm::GLPKMathProgModel) 105 | lp = lpm.inner 106 | n = GLPK.get_num_cols(lp) 107 | lb = Array{Float64}(undef, n) 108 | for c = 1:n 109 | l = GLPK.get_col_lb(lp, c) 110 | if l <= -floatmax(Float64) 111 | l = -Inf 112 | end 113 | lb[c] = l 114 | end 115 | return lb 116 | end 117 | 118 | function MPB.setvarLB!(lpm::GLPKMathProgModel, collb) 119 | lp = lpm.inner 120 | n = GLPK.get_num_cols(lp) 121 | if nonnull(collb) && length(collb) != n 122 | error("invalid size of collb") 123 | end 124 | prev_preemptive_check = GLPK.jl_get_preemptive_check() 125 | GLPK.jl_set_preemptive_check(false) 126 | for c = 1:n 127 | u = GLPK.get_col_ub(lp, c) 128 | if u >= floatmax(Float64) 129 | u = Inf 130 | end 131 | if nonnull(collb) && collb[c] != -Inf 132 | l = collb[c] 133 | if u < Inf 134 | if l != u 135 | GLPK.set_col_bnds(lp, c, GLPK.DB, l, u) 136 | else 137 | GLPK.set_col_bnds(lp, c, GLPK.FX, l, u) 138 | end 139 | else 140 | GLPK.set_col_bnds(lp, c, GLPK.LO, l, 0.0) 141 | end 142 | else 143 | if u < Inf 144 | GLPK.set_col_bnds(lp, c, GLPK.UP, 0.0, u) 145 | else 146 | GLPK.set_col_bnds(lp, c, GLPK.FR, 0.0, 0.0) 147 | end 148 | end 149 | end 150 | GLPK.jl_set_preemptive_check(prev_preemptive_check) 151 | end 152 | 153 | function MPB.getvarUB(lpm::GLPKMathProgModel) 154 | lp = lpm.inner 155 | n = GLPK.get_num_cols(lp) 156 | ub = Array{Float64}(undef, n) 157 | for c = 1:n 158 | u = GLPK.get_col_ub(lp, c) 159 | if u >= floatmax(Float64) 160 | u = Inf 161 | end 162 | ub[c] = u 163 | end 164 | return ub 165 | end 166 | 167 | function MPB.setvarUB!(lpm::GLPKMathProgModel, colub) 168 | lp = lpm.inner 169 | n = GLPK.get_num_cols(lp) 170 | if nonnull(colub) && length(colub) != n 171 | error("invalid size of colub") 172 | end 173 | prev_preemptive_check = GLPK.jl_get_preemptive_check() 174 | GLPK.jl_set_preemptive_check(false) 175 | for c = 1:n 176 | l = GLPK.get_col_lb(lp, c) 177 | if l <= -floatmax(Float64) 178 | l = -Inf 179 | end 180 | if nonnull(colub) && colub[c] != Inf 181 | u = colub[c] 182 | if l > -Inf 183 | if l != u 184 | GLPK.set_col_bnds(lp, c, GLPK.DB, l, u) 185 | else 186 | GLPK.set_col_bnds(lp, c, GLPK.FX, l, u) 187 | end 188 | else 189 | GLPK.set_col_bnds(lp, c, GLPK.UP, 0.0, u) 190 | end 191 | else 192 | if l > -Inf 193 | GLPK.set_col_bnds(lp, c, GLPK.LO, l, 0.0) 194 | else 195 | GLPK.set_col_bnds(lp, c, GLPK.FR, 0.0, 0.0) 196 | end 197 | end 198 | end 199 | GLPK.jl_set_preemptive_check(prev_preemptive_check) 200 | end 201 | 202 | function MPB.getconstrLB(lpm::GLPKMathProgModel) 203 | lp = lpm.inner 204 | m = GLPK.get_num_rows(lp) 205 | lb = Array{Float64}(undef, m) 206 | for r = 1:m 207 | l = GLPK.get_row_lb(lp, r) 208 | if l <= -floatmax(Float64) 209 | l = -Inf 210 | end 211 | lb[r] = l 212 | end 213 | return lb 214 | end 215 | 216 | function MPB.setconstrLB!(lpm::GLPKMathProgModel, rowlb) 217 | lp = lpm.inner 218 | m = GLPK.get_num_rows(lp) 219 | if nonnull(rowlb) && length(rowlb) != m 220 | error("invalid size of rowlb") 221 | end 222 | prev_preemptive_check = GLPK.jl_get_preemptive_check() 223 | GLPK.jl_set_preemptive_check(false) 224 | for r = 1:m 225 | u = GLPK.get_row_ub(lp, r) 226 | if u >= floatmax(Float64) 227 | u = Inf 228 | end 229 | if nonnull(rowlb) && rowlb[r] != -Inf 230 | l = rowlb[r] 231 | if u < Inf 232 | if l != u 233 | GLPK.set_row_bnds(lp, r, GLPK.DB, l, u) 234 | else 235 | GLPK.set_row_bnds(lp, r, GLPK.FX, l, u) 236 | end 237 | else 238 | GLPK.set_row_bnds(lp, r, GLPK.LO, l, 0.0) 239 | end 240 | else 241 | if u < Inf 242 | GLPK.set_row_bnds(lp, r, GLPK.UP, 0.0, u) 243 | else 244 | GLPK.set_row_bnds(lp, r, GLPK.FR, 0.0, 0.0) 245 | end 246 | end 247 | end 248 | GLPK.jl_set_preemptive_check(prev_preemptive_check) 249 | end 250 | 251 | function MPB.getconstrUB(lpm::GLPKMathProgModel) 252 | lp = lpm.inner 253 | m = GLPK.get_num_rows(lp) 254 | ub = zeros(m) 255 | for r = 1:m 256 | u = GLPK.get_row_ub(lp, r) 257 | if u >= floatmax(Float64) 258 | u = Inf 259 | end 260 | ub[r] = u 261 | end 262 | return ub 263 | end 264 | 265 | function MPB.setconstrUB!(lpm::GLPKMathProgModel, rowub) 266 | lp = lpm.inner 267 | m = GLPK.get_num_rows(lp) 268 | if nonnull(rowub) && length(rowub) != m 269 | error("invalid size of rowub") 270 | end 271 | prev_preemptive_check = GLPK.jl_get_preemptive_check() 272 | GLPK.jl_set_preemptive_check(false) 273 | for r = 1:m 274 | l = GLPK.get_row_lb(lp, r) 275 | if l <= -floatmax(Float64) 276 | l = -Inf 277 | end 278 | if nonnull(rowub) && rowub[r] != Inf 279 | u = rowub[r] 280 | if l > -Inf 281 | if l != u 282 | GLPK.set_row_bnds(lp, r, GLPK.DB, l, u) 283 | else 284 | GLPK.set_row_bnds(lp, r, GLPK.FX, l, u) 285 | end 286 | else 287 | GLPK.set_row_bnds(lp, r, GLPK.UP, 0.0, u) 288 | end 289 | else 290 | if l > -Inf 291 | GLPK.set_row_bnds(lp, r, GLPK.LO, l, 0.0) 292 | else 293 | GLPK.set_row_bnds(lp, r, GLPK.FR, 0.0, 0.0) 294 | end 295 | end 296 | end 297 | GLPK.jl_set_preemptive_check(prev_preemptive_check) 298 | end 299 | 300 | function MPB.getconstrmatrix(lpm::GLPKMathProgModel) 301 | lp = lpm.inner 302 | m = GLPK.get_num_rows(lp) 303 | n = GLPK.get_num_cols(lp) 304 | colwise = [GLPK.get_mat_col(lp,i) for i in 1:n] 305 | nnz = 0 306 | for i in 1:n 307 | nnz += length(colwise[i][1]) 308 | end 309 | colptr = Array{Int}(undef, n+1) 310 | rowval = Array{Int}(undef, nnz) 311 | nzval = Array{Float64}(undef, nnz) 312 | cur_nnz = 1 313 | for i in 1:n 314 | colptr[i] = cur_nnz 315 | ind,vals = colwise[i] 316 | p = sortperm(ind) # indices must be sorted 317 | rowval[cur_nnz:(cur_nnz+length(ind)-1)] = ind[p] 318 | nzval[cur_nnz:(cur_nnz+length(ind)-1)] = vals[p] 319 | cur_nnz += length(ind) 320 | end 321 | colptr[n+1] = cur_nnz 322 | return SparseMatrixCSC(m,n,colptr,rowval,nzval) 323 | end 324 | 325 | 326 | function MPB.getobj(lpm::GLPKMathProgModel) 327 | lp = lpm.inner 328 | n = GLPK.get_num_cols(lp) 329 | return [GLPK.get_obj_coef(lp, i) for i in 1:n] 330 | end 331 | 332 | function MPB.setobj!(lpm::GLPKMathProgModel, obj) 333 | lp = lpm.inner 334 | n = GLPK.get_num_cols(lp) 335 | if nonnull(obj) && length(obj) != n 336 | error("invalid size of obj") 337 | end 338 | for c = 1:n 339 | if nonnull(obj) 340 | GLPK.set_obj_coef(lp, c, obj[c]) 341 | else 342 | GLPK.set_obj_coef(lp, c, 0.0) 343 | end 344 | end 345 | end 346 | 347 | function MPB.addvar!(lpm::GLPKMathProgModel, rowidx::Vector, rowcoef::Vector, collb::Real, colub::Real, objcoef::Real) 348 | if length(rowidx) != length(rowcoef) 349 | error("rowidx and rowcoef have different legths") 350 | end 351 | lp = lpm.inner 352 | GLPK.add_cols(lp, 1) 353 | n = GLPK.get_num_cols(lp) 354 | GLPK.set_mat_col(lp, n, rowidx, rowcoef) 355 | if collb > -Inf && colub < Inf 356 | if collb != colub 357 | bt = GLPK.DB 358 | else 359 | bt = GLPK.FX 360 | end 361 | elseif collb > -Inf 362 | bt = GLPK.LO 363 | elseif colub < Inf 364 | bt = GLPK.UP 365 | else 366 | bt = GLPK.FR 367 | end 368 | GLPK.set_col_bnds(lp, n, bt, collb, colub) 369 | GLPK.set_obj_coef(lp, n, objcoef) 370 | return 371 | end 372 | 373 | function MPB.delvars!(lpm::GLPKMathProgModel, idx::Vector) 374 | GLPK.std_basis(lpm.inner) 375 | GLPK.del_cols(lpm.inner, length(idx), idx) 376 | end 377 | 378 | function MPB.addconstr!(lpm::GLPKMathProgModel, colidx::Vector, colcoef::Vector, rowlb::Real, rowub::Real) 379 | if length(colidx) != length(colcoef) 380 | error("colidx and colcoef have different legths") 381 | end 382 | lp = lpm.inner 383 | GLPK.add_rows(lp, 1) 384 | m = GLPK.get_num_rows(lp) 385 | GLPK.set_mat_row(lp, m, colidx, colcoef) 386 | if rowlb > -Inf && rowub < Inf 387 | if rowlb != rowub 388 | bt = GLPK.DB 389 | else 390 | bt = GLPK.FX 391 | end 392 | elseif rowlb > -Inf 393 | bt = GLPK.LO 394 | elseif rowub < Inf 395 | bt = GLPK.UP 396 | else 397 | bt = GLPK.FR 398 | end 399 | GLPK.set_row_bnds(lp, m, bt, rowlb, rowub) 400 | return 401 | end 402 | 403 | function MPB.delconstrs!(lpm::GLPKMathProgModel, idx::Vector) 404 | GLPK.std_basis(lpm.inner) 405 | GLPK.del_rows(lpm.inner, length(idx), idx) 406 | end 407 | 408 | 409 | function MPB.setsense!(lpm::GLPKMathProgModel, sense) 410 | lp = lpm.inner 411 | if sense == :Min 412 | GLPK.set_obj_dir(lp, GLPK.MIN) 413 | elseif sense == :Max 414 | GLPK.set_obj_dir(lp, GLPK.MAX) 415 | else 416 | error("Unrecognized objective sense $sense") 417 | end 418 | end 419 | 420 | function MPB.getsense(lpm::GLPKMathProgModel) 421 | lp = lpm.inner 422 | s = GLPK.get_obj_dir(lp) 423 | if s == GLPK.MIN 424 | return :Min 425 | elseif s == GLPK.MAX 426 | return :Max 427 | else 428 | error("Internal library error") 429 | end 430 | end 431 | 432 | MPB.numvar(lpm::GLPKMathProgModel) = GLPK.get_num_cols(lpm.inner) 433 | MPB.numconstr(lpm::GLPKMathProgModel) = GLPK.get_num_rows(lpm.inner) 434 | 435 | MPB.getrawsolver(lpm::GLPKMathProgModel) = lpm.inner 436 | 437 | end 438 | -------------------------------------------------------------------------------- /src/GLPKInterfaceMIP.jl: -------------------------------------------------------------------------------- 1 | module GLPKInterfaceMIP 2 | 3 | import GLPK 4 | import MathProgBase 5 | const MPB = MathProgBase 6 | using ..GLPKInterfaceBase 7 | using SparseArrays 8 | using LinearAlgebra 9 | 10 | export GLPKSolverMIP, GLPKCallbackData 11 | 12 | mutable struct GLPKMathProgModelMIP <: GLPKMathProgModel 13 | inner::GLPK.Prob 14 | param::GLPK.IntoptParam 15 | smplxparam::GLPK.SimplexParam 16 | lazycb::Union{Function,Nothing} 17 | cutcb::Union{Function,Nothing} 18 | heuristiccb::Union{Function,Nothing} 19 | infocb::Union{Function,Nothing} 20 | objbound::Float64 21 | cbdata::MPB.MathProgCallbackData 22 | binaries::Vector{Int} 23 | userlimit::Bool 24 | function GLPKMathProgModelMIP() 25 | lpm = new(GLPK.Prob(), GLPK.IntoptParam(), GLPK.SimplexParam(), 26 | nothing, nothing, nothing, nothing, -Inf) 27 | lpm.cbdata = GLPKCallbackData(lpm) 28 | lpm.binaries = Int[] 29 | lpm.userlimit = false 30 | return lpm 31 | end 32 | end 33 | 34 | function Base.copy(m::GLPKMathProgModelMIP) 35 | 36 | m2 = GLPKMathProgModelMIP() 37 | 38 | GLPK.copy_prob(m2.inner, m.inner, GLPK.ON) 39 | 40 | m2.param = deepcopy(m.param) 41 | m2.smplxparam = deepcopy(m.smplxparam) 42 | 43 | m.lazycb == nothing || @warn "Callbacks can't be copied, lazy callback ignored" 44 | m.cutcb == nothing || @warn "Callbacks can't be copied, cut callback ignored" 45 | m.heuristiccb == nothing || @warn "Callbacks can't be copied, heuristic callback ignored" 46 | m.infocb == nothing || @warn "Callbacks can't be copied, info callback ignored" 47 | 48 | m2.objbound = m.objbound 49 | 50 | m.cbdata == nothing || @warn "Callbacks can't be copied, callbackdata ignored" 51 | 52 | m2.binaries = deepcopy(m.binaries) 53 | m2.userlimit = m.userlimit 54 | 55 | return m2 56 | end 57 | 58 | mutable struct GLPKCallbackData <: MPB.MathProgCallbackData 59 | model::GLPKMathProgModelMIP 60 | tree::Ptr{Cvoid} 61 | state::Symbol 62 | reason::Cint 63 | sol::Vector{Float64} 64 | vartype::Vector{Symbol} 65 | GLPKCallbackData(model::GLPKMathProgModelMIP) = new(model, C_NULL, :Other, -1, Float64[], Char[]) 66 | end 67 | 68 | mutable struct GLPKSolverMIP <: MPB.AbstractMathProgSolver 69 | presolve::Bool 70 | opts 71 | GLPKSolverMIP(;presolve::Bool=false, opts...) = new(presolve, opts) 72 | end 73 | 74 | callback_abort(stat, tree) = (stat == :Exit && GLPK.ios_terminate(tree)) 75 | 76 | function _internal_callback(tree::Ptr{Cvoid}, info::Ptr{Cvoid}) 77 | cb_data = unsafe_pointer_to_objref(info)::GLPKCallbackData 78 | lpm = cb_data.model 79 | cb_data.tree = tree 80 | 81 | reason = GLPK.ios_reason(tree) 82 | cb_data.reason = reason 83 | 84 | if reason == GLPK.ISELECT 85 | #println("reason=SELECT") 86 | cb_data.state = :Intermediate 87 | elseif reason == GLPK.IPREPRO 88 | #println("reason=PREPRO") 89 | cb_data.state = :MIPNode 90 | elseif reason == GLPK.IROWGEN 91 | #println("reason=ROWGEN") 92 | cb_data.state = :MIPNode 93 | # if the current solution is actually integer feasible, then 94 | # return MIPSol status. 95 | _initsolution!(cb_data) 96 | MPB.cbgetlpsolution(cb_data, cb_data.sol) 97 | # tol_int = 1e-5 by default 98 | # TODO: query from GLPK 99 | all_integer = true 100 | for i in 1:length(cb_data.sol) 101 | v::Symbol = cb_data.vartype[i] 102 | (v == :Int || v == :Bin) || continue 103 | if abs(cb_data.sol[i]-round(cb_data.sol[i])) > 1e-5 104 | all_integer = false 105 | break 106 | end 107 | end 108 | if all_integer 109 | cb_data.state = :MIPSol 110 | end 111 | fill!(cb_data.sol, NaN) 112 | 113 | if lpm.lazycb != nothing 114 | stat = lpm.lazycb(cb_data) 115 | callback_abort(stat,tree) 116 | end 117 | elseif reason == GLPK.IHEUR 118 | #println("reason=HEUR") 119 | cb_data.state = :MIPNode 120 | if lpm.heuristiccb != nothing 121 | stat = lpm.heuristiccb(cb_data) 122 | callback_abort(stat,tree) 123 | end 124 | elseif reason == GLPK.ICUTGEN 125 | #println("reason=CUTGEN") 126 | cb_data.state = :MIPNode 127 | if lpm.cutcb != nothing 128 | stat = lpm.cutcb(cb_data) 129 | callback_abort(stat,tree) 130 | end 131 | elseif reason == GLPK.IBRANCH 132 | #println("reason=BRANCH") 133 | cb_data.state = :MIPNode 134 | elseif reason == GLPK.IBINGO 135 | #println("reason=BINGO") 136 | cb_data.state = :MIPSol 137 | else 138 | error("internal library error") 139 | end 140 | 141 | # Doesn't seem like there's a natural "reason" to put this with, 142 | # so let's just call it everywhere for now 143 | if lpm.infocb != nothing 144 | stat = lpm.infocb(cb_data) 145 | callback_abort(stat,tree) 146 | end 147 | 148 | bn = GLPK.ios_best_node(tree) 149 | bn != 0 && (lpm.objbound = GLPK.ios_node_bound(tree, bn)) 150 | 151 | return 152 | end 153 | 154 | function MPB.LinearQuadraticModel(s::GLPKSolverMIP) 155 | lpm = GLPKMathProgModelMIP() 156 | lpm.param.msg_lev = GLPK.MSG_ERR 157 | lpm.smplxparam.msg_lev = GLPK.MSG_ERR 158 | if s.presolve 159 | lpm.param.presolve = GLPK.ON 160 | end 161 | 162 | lpm.param.cb_func = @cfunction(_internal_callback, Cvoid, (Ptr{Cvoid}, Ptr{Cvoid})) 163 | lpm.param.cb_info = pointer_from_objref(lpm.cbdata) 164 | 165 | for (k,v) in s.opts 166 | if k in [:cb_func, :cb_info] 167 | @warn "ignored option: $(string(k)); use the MathProgBase callback interface instead" 168 | continue 169 | end 170 | i = findfirst(x->x==k, fieldnames(typeof(lpm.param))) 171 | s = findfirst(x->x==k, fieldnames(typeof(lpm.smplxparam))) 172 | if (VERSION < v"0.7-" && i > 0) || (VERSION >= v"0.7-" && i !== nothing) 173 | t = typeof(lpm.param).types[i] 174 | setfield!(lpm.param, i, convert(t, v)) 175 | elseif (VERSION < v"0.7-" && s > 0) || (VERSION >= v"0.7-" && s !== nothing) 176 | t = typeof(lpm.smplxparam).types[s] 177 | setfield!(lpm.smplxparam, s, convert(t, v)) 178 | else 179 | @warn "Ignored option: $(string(k))" 180 | continue 181 | end 182 | end 183 | 184 | return lpm 185 | end 186 | 187 | function MPB.setparameters!(s::GLPKSolverMIP; mpboptions...) 188 | opts = collect(Any, s.opts) 189 | for (optname, optval) in mpboptions 190 | if optname == :TimeLimit 191 | push!(opts, (:tm_lim,round(Int,1000*optval))) # milliseconds 192 | elseif optname == :Silent 193 | if optval == true 194 | push!(opts, (:msg_lev,GLPK.MSG_OFF)) 195 | end 196 | else 197 | error("Unrecognized parameter $optname") 198 | end 199 | end 200 | s.opts = opts 201 | nothing 202 | end 203 | 204 | function MPB.setparameters!(m::GLPKMathProgModelMIP; mpboptions...) 205 | for (optname, optval) in mpboptions 206 | if optname == :TimeLimit 207 | m.param.tm_lim = round(Int,1000*optval) 208 | elseif optname == :Silent 209 | if optval == true 210 | m.param.msg_lev = GLPK.MSG_OFF 211 | m.smplxparam.msg_lev = GLPK.MSG_OFF 212 | end 213 | else 214 | error("Unrecognized parameter $optname") 215 | end 216 | end 217 | end 218 | 219 | MPB.setlazycallback!(m::GLPKMathProgModel, f::Union{Function,Nothing}) = (m.lazycb = f) 220 | MPB.setcutcallback!(m::GLPKMathProgModel, f::Union{Function,Nothing}) = (m.cutcb = f) 221 | MPB.setheuristiccallback!(m::GLPKMathProgModel, f::Union{Function,Nothing}) = (m.heuristiccb = f) 222 | MPB.setinfocallback!(m::GLPKMathProgModel, f::Union{Function,Nothing}) = (m.infocb = f) 223 | 224 | _check_tree(d::GLPKCallbackData, funcname::AbstractString) = 225 | (d.tree != C_NULL && d.reason != -1) || error("$funcname can only be called from within a callback") 226 | 227 | MPB.cbgetstate(d::GLPKCallbackData) = d.state 228 | 229 | function MPB.cbgetlpsolution(d::GLPKCallbackData, output::Vector) 230 | _check_tree(d, "cbgetlpsolution") 231 | lp = GLPK.ios_get_prob(d.tree) 232 | n = GLPK.get_num_cols(lp) 233 | length(output) >= n || error("output vector is too short") 234 | 235 | for c = 1:n 236 | output[c] = GLPK.get_col_prim(lp, c) 237 | end 238 | return output 239 | end 240 | 241 | function MPB.cbgetlpsolution(d::GLPKCallbackData) 242 | _check_tree(d, "cbgetlpsolution") 243 | lp = GLPK.ios_get_prob(d.tree) 244 | n = GLPK.get_num_cols(lp) 245 | output = Vector{Float64}(undef, n) 246 | 247 | for c = 1:n 248 | output[c] = GLPK.get_col_prim(lp, c) 249 | end 250 | return output 251 | end 252 | 253 | 254 | function MPB.cbgetmipsolution(d::GLPKCallbackData, output::Vector) 255 | # assuming we're in the lazy callback where 256 | # the LP solution is actually integral. 257 | # If we add an informational callback for GLPK.IBINGO, 258 | # then this will need to be modified. 259 | return MPB.cbgetlpsolution(d, output) 260 | end 261 | MPB.cbgetmipsolution(d::GLPKCallbackData) = MPB.cbgetlpsolution(d) 262 | 263 | function MPB.cbgetbestbound(d::GLPKCallbackData) 264 | _check_tree(d, "cbbestbound") 265 | lpm = d.model 266 | return lpm.objbound 267 | end 268 | 269 | function MPB.cbgetobj(d::GLPKCallbackData) 270 | _check_tree(d, "cbgetobj") 271 | lp = GLPK.ios_get_prob(d.tree) 272 | return GLPK.mip_obj_val(lp) 273 | end 274 | 275 | function MPB.cbgetexplorednodes(d::GLPKCallbackData) 276 | _check_tree(d, "cbgetexplorednodes") 277 | a, _, t = GLPK.ios_tree_size(d.tree) 278 | return t - a 279 | end 280 | 281 | function MPB.cbaddlazy!(d::GLPKCallbackData, colidx::Vector, colcoef::Vector, sense::Char, rhs::Real) 282 | #println("Adding lazy") 283 | (d.tree != C_NULL && d.reason == GLPK.IROWGEN) || 284 | error("cbaddlazy! can only be called from within a lazycallback") 285 | length(colidx) == length(colcoef) || error("colidx and colcoef have different legths") 286 | if sense == '=' 287 | bt = GLPK.FX 288 | rowlb = rhs 289 | rowub = rhs 290 | elseif sense == '<' 291 | bt = GLPK.UP 292 | rowlb = -Inf 293 | rowub = rhs 294 | elseif sense == '>' 295 | bt = GLPK.LO 296 | rowlb = rhs 297 | rowub = Inf 298 | else 299 | error("sense must be '=', '<' or '>'") 300 | end 301 | # allocating a new vector is not efficient 302 | solution = MPB.cbgetmipsolution(d) 303 | # if the cut does not exclude the current solution, ignore it 304 | val = dot(colcoef,solution[colidx]) 305 | if (rowlb - 1e-8 <= val <= rowub + 1e-8) 306 | # would be better to use GLPK's internal tolerances 307 | #Base.warn_once("Ignoring lazy constraint which is already satisfied") 308 | return 309 | end 310 | 311 | lp = GLPK.ios_get_prob(d.tree) 312 | GLPK.add_rows(lp, 1) 313 | m = GLPK.get_num_rows(lp) 314 | GLPK.set_mat_row(lp, m, colidx, colcoef) 315 | GLPK.set_row_bnds(lp, m, bt, rowlb, rowub) 316 | return 317 | end 318 | 319 | function MPB.cbaddcut!(d::GLPKCallbackData, colidx::Vector, colcoef::Vector, sense::Char, rhs::Real) 320 | #println("Adding cut") 321 | (d.tree != C_NULL && d.reason == GLPK.ICUTGEN) || 322 | error("cbaddcut! can only be called from within a cutcallback") 323 | if sense == '<' 324 | bt = GLPK.UP 325 | elseif sense == '>' 326 | bt = GLPK.LO 327 | elseif sense == '=' 328 | error("unsupported sense in cut plane '='") 329 | else 330 | error("sense must be '<' or '>'") 331 | end 332 | GLPK.ios_add_row(d.tree, "", 101, colidx, colcoef, bt, rhs) 333 | return 334 | end 335 | 336 | function _initsolution!(d::GLPKCallbackData) 337 | lp = GLPK.ios_get_prob(d.tree) 338 | n = GLPK.get_num_cols(lp) 339 | length(d.sol) == n && return 340 | resize!(d.sol, n) 341 | fill!(d.sol, NaN) 342 | return 343 | end 344 | 345 | function _fillsolution!(d::GLPKCallbackData) 346 | lp = GLPK.ios_get_prob(d.tree) 347 | n = GLPK.get_num_cols(lp) 348 | sol = d.sol 349 | for c = 1:n 350 | isnan(sol[c]) || continue 351 | sol[c] = GLPK.mip_col_val(lp, c) 352 | end 353 | end 354 | 355 | function MPB.cbaddsolution!(d::GLPKCallbackData) 356 | #println("Adding sol") 357 | (d.tree != C_NULL && d.reason == GLPK.IHEUR) || 358 | error("cbaddsolution! can only be called from within a heuristiccallback") 359 | _initsolution!(d) 360 | _fillsolution!(d) 361 | # test feasibility of solution, would be better if GLPK supported this 362 | l = MPB.getvarLB(d.model) 363 | u = MPB.getvarUB(d.model) 364 | for i in 1:length(l) 365 | if d.sol[i] < l[i] - 1e-6 || d.sol[i] > u[i] + 1e-6 366 | @warn "Ignoring infeasible solution from heuristic callback" 367 | return 368 | end 369 | end 370 | A = MPB.getconstrmatrix(d.model) 371 | lb = MPB.getconstrLB(d.model) 372 | ub = MPB.getconstrUB(d.model) 373 | y = A*d.sol 374 | for i in 1:length(lb) 375 | if y[i] < lb[i] - 1e-6 || y[i] > ub[i] + 1e-6 376 | @warn "Ignoring infeasible solution from heuristic callback" 377 | return 378 | end 379 | end 380 | GLPK.ios_heur_sol(d.tree, d.sol) 381 | fill!(d.sol, NaN) 382 | end 383 | 384 | function MPB.cbsetsolutionvalue!(d::GLPKCallbackData, idx::Integer, val::Real) 385 | _check_tree(d, "cbsetsolutionvalue!") 386 | _initsolution!(d) 387 | d.sol[idx] = val 388 | end 389 | 390 | function MPB.setsense!(lpm::GLPKMathProgModelMIP, sense) 391 | lp = lpm.inner 392 | if sense == :Min 393 | GLPK.set_obj_dir(lp, GLPK.MIN) 394 | lpm.objbound = -Inf 395 | elseif sense == :Max 396 | GLPK.set_obj_dir(lp, GLPK.MAX) 397 | lpm.objbound = Inf 398 | else 399 | error("unrecognized objective sense: $sense") 400 | end 401 | end 402 | 403 | function MPB.setvartype!(lpm::GLPKMathProgModelMIP, vartype::Vector{Symbol}) 404 | lp = lpm.inner 405 | lpm.binaries = Int[] 406 | ncol = MPB.numvar(lpm) 407 | @assert length(vartype) == ncol 408 | for i in 1:ncol 409 | if vartype[i] == :Int 410 | coltype = GLPK.IV 411 | elseif vartype[i] == :Cont 412 | coltype = GLPK.CV 413 | elseif vartype[i] == :Bin 414 | push!(lpm.binaries, i) 415 | coltype = GLPK.IV 416 | else 417 | error("invalid variable type: $(vartype[i])") 418 | end 419 | GLPK.set_col_kind(lp, i, coltype) 420 | end 421 | end 422 | 423 | const vartype_map = Dict( 424 | GLPK.CV => :Cont, 425 | GLPK.IV => :Int, 426 | GLPK.BV => :Bin 427 | ) 428 | 429 | function MPB.getvartype(lpm::GLPKMathProgModelMIP) 430 | lp = lpm.inner 431 | ncol = MPB.numvar(lpm) 432 | coltype = Array{Symbol}(undef, ncol) 433 | for i in 1:ncol 434 | ct = GLPK.get_col_kind(lp, i) 435 | coltype[i] = vartype_map[ct] 436 | if i in lpm.binaries 437 | coltype[i] = :Bin 438 | elseif coltype[i] == :Bin # GLPK said it was binary, but we didn't tell it 439 | coltype[i] = :Int 440 | end 441 | end 442 | return coltype 443 | end 444 | 445 | function MPB.optimize!(lpm::GLPKMathProgModelMIP) 446 | vartype = MPB.getvartype(lpm) 447 | lb = MPB.getvarLB(lpm) 448 | ub = MPB.getvarUB(lpm) 449 | old_lb = copy(lb) 450 | old_ub = copy(ub) 451 | for c in 1:length(vartype) 452 | vartype[c] in [:Int,:Bin] && (lb[c] = ceil(lb[c]); ub[c] = floor(ub[c])) 453 | vartype[c] == :Bin && (lb[c] = max(lb[c],0.0); ub[c] = min(ub[c],1.0)) 454 | end 455 | lpm.cbdata.vartype = vartype 456 | try 457 | MPB.setvarLB!(lpm, lb) 458 | MPB.setvarUB!(lpm, ub) 459 | if lpm.param.presolve == GLPK.OFF 460 | ret_ps = GLPK.simplex(lpm.inner, lpm.smplxparam) 461 | ret_ps != 0 && return ret_ps 462 | end 463 | ret = GLPK.intopt(lpm.inner, lpm.param) 464 | if ret == GLPK.EMIPGAP || ret == GLPK.ETMLIM || ret == GLPK.ESTOP 465 | lpm.userlimit = true 466 | end 467 | finally 468 | MPB.setvarLB!(lpm, old_lb) 469 | MPB.setvarUB!(lpm, old_ub) 470 | end 471 | end 472 | 473 | function MPB.status(lpm::GLPKMathProgModelMIP) 474 | if lpm.userlimit 475 | return :UserLimit 476 | end 477 | s = GLPK.mip_status(lpm.inner) 478 | if s == GLPK.UNDEF 479 | if lpm.param.presolve == GLPK.OFF && GLPK.get_status(lpm.inner) == GLPK.NOFEAS 480 | return :Infeasible 481 | else 482 | return :Error 483 | end 484 | end 485 | if s == GLPK.OPT 486 | return :Optimal 487 | elseif s == GLPK.INFEAS 488 | return :Infeasible 489 | elseif s == GLPK.UNBND 490 | return :Unbounded 491 | elseif s == GLPK.FEAS 492 | return :Feasible 493 | elseif s == GLPK.NOFEAS 494 | return :Infeasible 495 | elseif s == GLPK.UNDEF 496 | return :Undefined 497 | else 498 | error("internal library error") 499 | end 500 | end 501 | 502 | function MPB.getobjval(lpm::GLPKMathProgModelMIP) 503 | status = GLPK.mip_status(lpm.inner) 504 | if status == GLPK.UNDEF || status == GLPK.NOFEAS 505 | # no feasible solution so objective is NaN 506 | return NaN 507 | end 508 | 509 | return GLPK.mip_obj_val(lpm.inner) 510 | end 511 | 512 | function MPB.getobjbound(lpm::GLPKMathProgModelMIP) 513 | # This is a hack. We observed some cases where mip_status == OPT 514 | # and objval and objbound didn't match. 515 | # We can fix this case, but objbound may still be incorrect in 516 | # cases where the solver terminates early. 517 | if GLPK.mip_status(lpm.inner) == GLPK.OPT 518 | return GLPK.mip_obj_val(lpm.inner) 519 | else 520 | return lpm.objbound 521 | end 522 | end 523 | 524 | function MPB.getsolution(lpm::GLPKMathProgModelMIP) 525 | lp = lpm.inner 526 | n = GLPK.get_num_cols(lp) 527 | status = GLPK.mip_status(lpm.inner) 528 | if status == GLPK.UNDEF || status == GLPK.NOFEAS 529 | # no feasible solution to return 530 | return fill(NaN, n) 531 | end 532 | 533 | return [GLPK.mip_col_val(lp, i) for i in 1:n] 534 | end 535 | 536 | function MPB.getconstrsolution(lpm::GLPKMathProgModelMIP) 537 | lp = lpm.inner 538 | m = GLPK.get_num_rows(lp) 539 | 540 | return [GLPK.mip_row_val(lp, i) for i in 1:m] 541 | end 542 | 543 | end 544 | --------------------------------------------------------------------------------