From e9fbf4a3c77b88139452fc30f63a2496b43a8dbd Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Fri, 14 May 2021 11:03:09 -0500 Subject: [PATCH] Use compact variable and constraint features --- Manifest.toml | 65 +++--- Project.toml | 2 - deps/build.jl | 2 +- src/modeling/jump_instance.jl | 74 +++---- src/modeling/jump_solver.jl | 321 ++++++++++++++++++++++++++---- src/utils/pycall.jl | 24 --- test/Project.toml | 1 + test/modeling/jump_solver_test.jl | 24 ++- test/runtests.jl | 6 +- 9 files changed, 370 insertions(+), 149 deletions(-) delete mode 100644 src/utils/pycall.jl diff --git a/Manifest.toml b/Manifest.toml index 15f7a1c..c1f3ca8 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -11,9 +11,9 @@ uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[BenchmarkTools]] deps = ["JSON", "Logging", "Printf", "Statistics", "UUIDs"] -git-tree-sha1 = "9e62e66db34540a0c919d72172cc2f642ac71260" +git-tree-sha1 = "068fda9b756e41e6c75da7b771e6f89fa8a43d15" uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -version = "0.5.0" +version = "0.7.0" [[Bzip2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -29,9 +29,9 @@ version = "0.5.1" [[ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "44e9f638aa9ed1ad58885defc568c133010140aa" +git-tree-sha1 = "e6b23566e025d3b0d9ccc397f5c7a134af552e27" uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "0.9.37" +version = "0.9.42" [[CodecBzip2]] deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"] @@ -53,9 +53,9 @@ version = "0.3.0" [[Compat]] deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "ac4132ad78082518ec2037ae5770b6e796f7f956" +git-tree-sha1 = "0a817fbe51c976de090aa8c997b7b719b786118d" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.27.0" +version = "3.28.0" [[CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] @@ -63,9 +63,9 @@ uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" [[Conda]] deps = ["JSON", "VersionParsing"] -git-tree-sha1 = "6231e40619c15148bcb80aa19d731e629877d762" +git-tree-sha1 = "299304989a5e6473d985212c28928899c74e9421" uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d" -version = "1.5.1" +version = "1.5.2" [[DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] @@ -109,9 +109,9 @@ version = "0.10.18" [[HTTP]] deps = ["Base64", "Dates", "IniFile", "MbedTLS", "NetworkOptions", "Sockets", "URIs"] -git-tree-sha1 = "c9f380c76d8aaa1fa7ea9cf97bddbc0d5b15adc2" +git-tree-sha1 = "b855bf8247d6e946c75bb30f593bfe7fe591058d" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "0.9.5" +version = "0.9.8" [[IniFile]] deps = ["Test"] @@ -124,9 +124,10 @@ deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[JLLWrappers]] -git-tree-sha1 = "a431f5f2ca3f4feef3bd7a5e94b8b8d4f2f647a0" +deps = ["Preferences"] +git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.2.0" +version = "1.3.0" [[JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] @@ -141,10 +142,10 @@ uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" version = "0.3.3" [[JuMP]] -deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Random", "SparseArrays", "SpecialFunctions", "Statistics"] -git-tree-sha1 = "e952f49e2242fa21edcf27bbd6c67041685bee5d" +deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Printf", "Random", "SparseArrays", "SpecialFunctions", "Statistics"] +git-tree-sha1 = "8dfc5df8aad9f2cfebc8371b69700efd02060827" uuid = "4076af6c-e467-56ae-b986-b466b2749572" -version = "0.21.6" +version = "0.21.8" [[LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] @@ -184,9 +185,9 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[MathOptInterface]] deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "JSONSchema", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "SparseArrays", "Test", "Unicode"] -git-tree-sha1 = "606efe4246da5407d7505265a1ead72467528996" +git-tree-sha1 = "cd3057ca89a9ab83ce37ec42324523b8db0c60dc" uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "0.9.20" +version = "0.9.21" [[MbedTLS]] deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] @@ -206,9 +207,9 @@ uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[MutableArithmetics]] deps = ["LinearAlgebra", "SparseArrays", "Test"] -git-tree-sha1 = "ff3aa3e4dbc837f80c2031de2f90125c8b3793f3" +git-tree-sha1 = "ad9b2bce6021631e0e20706d361972343a03e642" uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" -version = "0.2.15" +version = "0.2.19" [[NaNMath]] git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb" @@ -220,14 +221,14 @@ uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" [[OpenSpecFun_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "9db77584158d0ab52307f8c04f8e7c08ca76b5b3" +git-tree-sha1 = "b9b8b8ed236998f91143938a760c2112dceeb2b4" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.3+4" +version = "0.5.4+0" [[OrderedCollections]] -git-tree-sha1 = "4fa2ba51070ec13fcc7517db714445b4ab986bdf" +git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.4.0" +version = "1.4.1" [[Parsers]] deps = ["Dates"] @@ -239,15 +240,21 @@ version = "1.1.0" deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +[[Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.2.2" + [[Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" [[PyCall]] deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"] -git-tree-sha1 = "dd1a970b543bd02efce2984582e996af28cab27f" +git-tree-sha1 = "169bb8ea6b1b143c5cf57df6d34d022a7b60c6db" uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" -version = "1.92.2" +version = "1.92.3" [[REPL]] deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] @@ -282,9 +289,9 @@ version = "1.3.0" [[StaticArrays]] deps = ["LinearAlgebra", "Random", "Statistics"] -git-tree-sha1 = "2f01a51c23eed210ff4a1be102c4cc8236b66e5b" +git-tree-sha1 = "fb46e45ef2cade8be20bb445b3ffeca3c6d6f7d3" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.1.0" +version = "1.1.3" [[Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -315,9 +322,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" version = "0.9.5" [[URIs]] -git-tree-sha1 = "7855809b88d7b16e9b029afd17880930626f54a2" +git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.2.0" +version = "1.3.0" [[UUIDs]] deps = ["Random", "SHA"] diff --git a/Project.toml b/Project.toml index 7f2d60c..904dd0b 100644 --- a/Project.toml +++ b/Project.toml @@ -13,9 +13,7 @@ PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -Conda = "1.4" JuMP = "0.21" MathOptInterface = "0.9" -PyCall = "1" TimerOutputs = "0.5" julia = "1" diff --git a/deps/build.jl b/deps/build.jl index d016e80..91e14dd 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -5,7 +5,7 @@ function install_miplearn() Conda.update() pip = joinpath(dirname(pyimport("sys").executable), "pip") isfile(pip) || error("$pip: invalid path") - run(`$pip install miplearn==0.2.0.dev3`) + run(`$pip install miplearn==0.2.0.dev5`) end install_miplearn() diff --git a/src/modeling/jump_instance.jl b/src/modeling/jump_instance.jl index 7cbd2c8..0afb0c7 100644 --- a/src/modeling/jump_instance.jl +++ b/src/modeling/jump_instance.jl @@ -4,46 +4,46 @@ @pydef mutable struct JuMPInstance <: miplearn.Instance function __init__(self, model) - init_miplearn_ext(model) - features = model.ext[:miplearn][:features] self.model = model - # Copy training data - training_data = [] - for sample in self.model.ext[:miplearn][:training_samples] - pysample = miplearn.TrainingSample() - pysample.__dict__ = sample - push!(training_data, pysample) - end - self.training_data = training_data + # init_miplearn_ext(model) + # features = model.ext[:miplearn][:features] + # # Copy training data + # training_data = [] + # for sample in self.model.ext[:miplearn][:training_samples] + # pysample = miplearn.TrainingSample() + # pysample.__dict__ = sample + # push!(training_data, pysample) + # end + # self.training_data = training_data - # Copy features to data classes - self.features = miplearn.Features( - instance=miplearn.InstanceFeatures( - user_features=PyCall.array2py( - features[:instance][:user_features], - ), - lazy_constraint_count=0, - ), - variables=Dict( - varname => miplearn.VariableFeatures( - category=vfeatures[:category], - user_features=PyCall.array2py( - vfeatures[:user_features], - ), - ) - for (varname, vfeatures) in features[:variables] - ), - constraints=Dict( - cname => miplearn.ConstraintFeatures( - category=cfeat[:category], - user_features=PyCall.array2py( - cfeat[:user_features], - ), - ) - for (cname, cfeat) in features[:constraints] - ), - ) + # # Copy features to data classes + # self.features = miplearn.Features( + # instance=miplearn.InstanceFeatures( + # user_features=PyCall.array2py( + # features[:instance][:user_features], + # ), + # lazy_constraint_count=0, + # ), + # variables=Dict( + # varname => miplearn.VariableFeatures( + # category=vfeatures[:category], + # user_features=PyCall.array2py( + # vfeatures[:user_features], + # ), + # ) + # for (varname, vfeatures) in features[:variables] + # ), + # constraints=Dict( + # cname => miplearn.ConstraintFeatures( + # category=cfeat[:category], + # user_features=PyCall.array2py( + # cfeat[:user_features], + # ), + # ) + # for (cname, cfeat) in features[:constraints] + # ), + # ) end function to_model(self) diff --git a/src/modeling/jump_solver.jl b/src/modeling/jump_solver.jl index f80981b..abc8ad0 100644 --- a/src/modeling/jump_solver.jl +++ b/src/modeling/jump_solver.jl @@ -14,8 +14,10 @@ mutable struct JuMPSolverData instance model bin_vars - solution::Union{Nothing, Dict{String,Float64}} + solution cname_to_constr + reduced_costs + dual_values end @@ -26,34 +28,27 @@ Optimizes a given JuMP model while capturing the solver log, then returns that l If tee=true, prints the solver log to the standard output as the optimization takes place. """ function optimize_and_capture_output!(model; tee::Bool=false) - original_stdout = stdout - rd, wr = redirect_stdout() - task = @async begin - log = "" - while true - line = String(readavailable(rd)) - isopen(rd) || break - log *= String(line) - if tee - print(original_stdout, line) - flush(original_stdout) - end - end - return log + logname = tempname() + logfile = open(logname, "w") + redirect_stdout(logfile) do + JuMP.optimize!(model) + Base.Libc.flush_cstdio() end - JuMP.optimize!(model) - sleep(1) - redirect_stdout(original_stdout) - close(rd) - return fetch(task) + close(logfile) + log = String(read(logname)) + rm(logname) + if tee + println(log) + end + return log end function solve( data::JuMPSolverData; tee::Bool=false, - iteration_cb, -)::Dict + iteration_cb=nothing, +) instance, model = data.instance, data.model wallclock_time = 0 log = "" @@ -78,14 +73,14 @@ function solve( lower_bound = primal_bound upper_bound = dual_bound end - return Dict( - "Lower bound" => lower_bound, - "Upper bound" => upper_bound, - "Sense" => sense, - "Wallclock time" => wallclock_time, - "Nodes" => 1, - "MIP log" => log, - "Warm start value" => nothing, + return miplearn.solvers.internal.MIPSolveStats( + mip_lower_bound=lower_bound, + mip_upper_bound=upper_bound, + mip_sense=sense, + mip_wallclock_time=wallclock_time, + mip_nodes=1, + mip_log=log, + mip_warm_start_value=nothing, ) end @@ -97,24 +92,55 @@ function solve_lp(data::JuMPSolverData; tee::Bool=false) JuMP.set_upper_bound(var, 1.0) JuMP.set_lower_bound(var, 0.0) end - log = optimize_and_capture_output!(model, tee=tee) + wallclock_time = @elapsed begin + log = optimize_and_capture_output!(model, tee=tee) + end update_solution!(data) obj_value = JuMP.objective_value(model) for var in bin_vars JuMP.set_binary(var) end - return Dict( - "LP value" => obj_value, - "LP log" => log, + return miplearn.solvers.internal.LPSolveStats( + lp_value=obj_value, + lp_log=log, + lp_wallclock_time=wallclock_time, ) end function update_solution!(data::JuMPSolverData) - data.solution = Dict( - JuMP.name(var) => JuMP.value(var) - for var in JuMP.all_variables(data.model) - ) + vars = JuMP.all_variables(data.model) + data.solution = [JuMP.value(var) for var in vars] + + # Reduced costs + if has_duals(data.model) + data.reduced_costs = [] + for var in vars + rc = 0.0 + if has_upper_bound(var) + rc += shadow_price(UpperBoundRef(var)) + end + if has_lower_bound(var) + # FIXME: Remove negative sign + rc -= shadow_price(LowerBoundRef(var)) + end + if is_fixed(var) + rc += shadow_price(FixRef(var)) + end + push!(data.reduced_costs, rc) + end + + data.dual_values = Dict() + for (ftype, stype) in JuMP.list_of_constraint_types(data.model) + for constr in JuMP.all_constraints(data.model, ftype, stype) + # FIXME: Remove negative sign + data.dual_values[constr] = -JuMP.dual(constr) + end + end + else + data.reduced_costs = nothing + data.dual_values = nothing + end end @@ -195,6 +221,151 @@ function get_constraint_sense(data::JuMPSolverData, cname) end +function get_variables( + data::JuMPSolverData; + with_static::Bool, +) + vars = JuMP.all_variables(data.model) + lb, ub, types, obj_coeffs = nothing, nothing, nothing, nothing + rc = nothing + + # Variable names + names = Tuple(JuMP.name.(vars)) + + if with_static + # Lower bounds + lb = Tuple( + JuMP.is_binary(v) ? 0.0 : + JuMP.has_lower_bound(v) ? JuMP.lower_bound(v) : + -Inf + for v in vars + ) + + # Upper bounds + ub = Tuple( + JuMP.is_binary(v) ? 1.0 : + JuMP.has_upper_bound(v) ? JuMP.upper_bound(v) : + Inf + for v in vars + ) + + # Variable types + types = Tuple( + JuMP.is_binary(v) ? "B" : + JuMP.is_integer(v) ? "I" : + "C" + for v in vars + ) + + # Objective function coefficients + obj = objective_function(data.model) + obj_coeffs = Tuple( + v ∈ keys(obj.terms) ? obj.terms[v] : 0.0 + for v in vars + ) + end + + rc = data.reduced_costs === nothing ? nothing : Tuple(data.reduced_costs) + values = data.solution === nothing ? nothing : Tuple(data.solution) + + return miplearn.features.VariableFeatures( + names=names, + lower_bounds=lb, + upper_bounds=ub, + types=types, + obj_coeffs=obj_coeffs, + reduced_costs=rc, + values=values, + ) +end + + +function get_constraints( + data::JuMPSolverData; + with_static::Bool, +) + names = [] + senses, lhs, rhs = nothing, nothing, nothing + dual_values = nothing + + if data.dual_values !== nothing + dual_values = [] + end + + if with_static + senses, lhs, rhs = [], [], [] + end + + for (ftype, stype) in JuMP.list_of_constraint_types(data.model) + ftype in [JuMP.AffExpr, JuMP.VariableRef] || error("Unsupported constraint type: ($ftype, $stype)") + for constr in JuMP.all_constraints(data.model, ftype, stype) + cset = MOI.get( + constr.model.moi_backend, + MOI.ConstraintSet(), + constr.index, + ) + name = JuMP.name(constr) + length(name) > 0 || continue + push!(names, name) + + if data.dual_values !== nothing + push!(dual_values, data.dual_values[constr]) + end + + if with_static + if ftype == JuMP.AffExpr + push!( + lhs, + Tuple( + ( + MOI.get( + constr.model.moi_backend, + MOI.VariableName(), + term.variable_index + ), + term.coefficient, + ) + for term in MOI.get( + constr.model.moi_backend, + MOI.ConstraintFunction(), + constr.index, + ).terms + ) + ) + if stype == MOI.EqualTo{Float64} + push!(senses, "=") + push!(rhs, cset.value) + elseif stype == MOI.LessThan{Float64} + push!(senses, "<") + push!(rhs, cset.upper) + elseif stype == MOI.GreaterThan{Float64} + push!(senses, ">") + push!(rhs, cset.lower) + else + error("Unsupported set: $stype") + end + else + error("Unsupported ftype: $ftype") + end + end + end + end + + function to_tuple(x) + x !== nothing || return nothing + return Tuple(x) + end + + return miplearn.features.ConstraintFeatures( + names=to_tuple(names), + senses=to_tuple(senses), + lhs=to_tuple(lhs), + rhs=to_tuple(rhs), + dual_values=to_tuple(dual_values), + ) +end + + # Constraints: ScalarAffineFunction, LessThan # ------------------------------------------------------------------------- function get_constraint_rhs( @@ -355,6 +526,23 @@ function get_constraint_sense( return "=" end +# Test instances +# --------------------------------------------- +function build_test_instance_knapsack() + weights = [23.0, 26.0, 20.0, 18.0] + prices = [505.0, 352.0, 458.0, 220.0] + capacity = 67.0 + + model = Model() + n = length(weights) + @variable(model, x[0:n-1], Bin) + @variable(model, z, lower_bound=0.0, upper_bound=capacity) + @objective(model, Max, sum(x[i-1] * prices[i] for i in 1:n)) + @constraint(model, eq_capacity, sum(x[i-1] * weights[i] for i in 1:n) - z == 0) + + return JuMPInstance(model) +end + @pydef mutable struct JuMPSolver <: miplearn.solvers.internal.InternalSolver function __init__(self; optimizer) @@ -366,6 +554,8 @@ end nothing, # bin_vars nothing, # solution nothing, # cname_to_constr + nothing, # reduced_costs + nothing, # dual_values ) end @@ -381,9 +571,9 @@ end solve( self; tee=false, - iteration_cb, - lazy_cb, - user_cut_cb, + iteration_cb=nothing, + lazy_cb=nothing, + user_cut_cb=nothing, ) = solve( self.data, tee=tee, @@ -396,8 +586,8 @@ end get_solution(self) = self.data.solution - get_variables(self) = - get_variables(self.data) + get_variables(self; with_static=true) = + get_variables(self.data; with_static=with_static) set_branching_priorities(self, priorities) = @warn "JuMPSolver: set_branching_priorities not implemented" @@ -411,6 +601,9 @@ end is_infeasible(self) = is_infeasible(self.data) + get_constraints(self; with_static=true) = + get_constraints(self.data; with_static=with_static) + get_constraint_ids(self) = get_constraint_ids(self.data) @@ -423,8 +616,45 @@ end get_constraint_sense(self, cname) = get_constraint_sense(self.data, cname) + build_test_instance_knapsack(self) = + build_test_instance_knapsack() + clone(self) = self + get_variable_attrs(self) = [ + "names", + # "basis_status", + "categories", + "lower_bounds", + "obj_coeffs", + "reduced_costs", + # "sa_lb_down", + # "sa_lb_up", + # "sa_obj_down", + # "sa_obj_up", + # "sa_ub_down", + # "sa_ub_up", + "types", + "upper_bounds", + "user_features", + "values", + ] + + get_constraint_attrs(self) = [ + # "basis_status", + "categories", + "dual_values", + "lazy", + "lhs", + "names", + "rhs", + # "sa_rhs_down", + # "sa_rhs_up", + "senses", + # "slacks", + "user_features", + ] + add_cut(self) = error("not implemented") extract_constraint(self) = error("not implemented") is_constraint_satisfied(self) = error("not implemented") @@ -433,6 +663,11 @@ end get_inequality_slacks(self) = error("not implemented") get_dual(self) = error("not implemented") get_sense(self) = error("not implemented") + build_test_instance_infeasible(self) = error("not implemented") + build_test_instance_redundancy(self) = error("not implemented") + get_constraints_old(self) = error("not implemented") + is_constraint_satisfied_old(self) = error("not implemented") + remove_constraint(self) = error("not implemented") end export JuMPSolver, solve!, fit!, add! diff --git a/src/utils/pycall.jl b/src/utils/pycall.jl deleted file mode 100644 index 473f468..0000000 --- a/src/utils/pycall.jl +++ /dev/null @@ -1,24 +0,0 @@ -# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization -# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved. -# Released under the modified BSD license. See COPYING.md for more details. - -macro pycall(expr) - quote - err_msg = nothing - result = nothing - try - result = $(esc(expr)) - catch err - args = err.val.args[1] - if (err isa PyCall.PyError) && (args isa String) && startswith(args, "Julia") - err_msg = replace(args, r"Stacktrace.*" => "") - else - rethrow(err) - end - end - if err_msg != nothing - error(err_msg) - end - result - end -end \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index 463b9a1..5cf0f13 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -4,6 +4,7 @@ [deps] Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" +Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b" Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/test/modeling/jump_solver_test.jl b/test/modeling/jump_solver_test.jl index c6c3fac..96a8ddc 100644 --- a/test/modeling/jump_solver_test.jl +++ b/test/modeling/jump_solver_test.jl @@ -4,18 +4,22 @@ using Test using MIPLearn -using Cbc +using Gurobi using PyCall +using JuMP miplearn_tests = pyimport("miplearn.solvers.tests") +traceback = pyimport("traceback") @testset "JuMPSolver" begin - model = MIPLearn.knapsack_model( - [23., 26., 20., 18.], - [505., 352., 458., 220.], - 67.0, - ) - instance = JuMPInstance(model) - solver = JuMPSolver(optimizer=Cbc.Optimizer) - miplearn_tests.test_internal_solver(solver, instance, model) -end \ No newline at end of file + solver = JuMPSolver(optimizer=Gurobi.Optimizer) + try + miplearn_tests.run_internal_solver_tests(solver) + catch e + if isa(e, PyCall.PyError) + printstyled("Uncaught Python exception:\n", bold=true, color=:red) + traceback.print_exception(e.T, e.val, e.traceback) + end + rethrow() + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 3a564cd..1c1d2e3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,6 +8,6 @@ using MIPLearn MIPLearn.setup_logger() @testset "MIPLearn" begin - #include("modeling/jump_solver_test.jl") - include("modeling/learning_solver_test.jl") -end \ No newline at end of file + include("modeling/jump_solver_test.jl") + #include("modeling/learning_solver_test.jl") +end