Make compatible with MIPLearn 0.2.0

master
Alinson S. Xavier 5 years ago
parent 4af6c78026
commit 96f7243d4c

@ -1,5 +1,11 @@
# This file is machine-generated - editing it directly is not advised
[[Artifacts]]
deps = ["Pkg"]
git-tree-sha1 = "c30985d8821e0cd73870b17b0ed0ce6dc44cb744"
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
version = "1.3.0"
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
@ -10,10 +16,10 @@ uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "0.5.0"
[[Bzip2_jll]]
deps = ["Libdl", "Pkg"]
git-tree-sha1 = "03a44490020826950c68005cafb336e5ba08b7e8"
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.6+4"
version = "1.0.6+5"
[[Calculus]]
deps = ["LinearAlgebra"]
@ -21,6 +27,12 @@ git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
version = "0.5.1"
[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "44e9f638aa9ed1ad58885defc568c133010140aa"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "0.9.37"
[[CodecBzip2]]
deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"]
git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7"
@ -41,27 +53,27 @@ 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 = "7c7f4cda0d58ec999189d70f5ee500348c4b4df1"
git-tree-sha1 = "4fecfd5485d3c5de4003e19f00c6898cccd40667"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.16.0"
version = "3.26.0"
[[CompilerSupportLibraries_jll]]
deps = ["Libdl", "Pkg"]
git-tree-sha1 = "7c4f882c41faa72118841185afc58a2eb00ef612"
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "8e695f735fca77e9708e795eda62afdb869cbb70"
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "0.3.3+0"
version = "0.3.4+0"
[[Conda]]
deps = ["JSON", "VersionParsing"]
git-tree-sha1 = "7a58bb32ce5d85f8bf7559aa7c2842f9aecf52fc"
git-tree-sha1 = "6231e40619c15148bcb80aa19d731e629877d762"
uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d"
version = "1.4.1"
version = "1.5.1"
[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "0347f23484a96d56e7096eb1f55c6975be34b11a"
git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.6"
version = "0.18.9"
[[Dates]]
deps = ["Printf"]
@ -73,31 +85,31 @@ uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
[[DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "da24935df8e0c6cf28de340b958f6aac88eaa0cc"
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.2"
version = "1.0.3"
[[DiffRules]]
deps = ["NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "eb0c34204c8410888844ada5359ac8b96292cfd1"
git-tree-sha1 = "214c3fcac57755cfda163d91c58893a8723f93e9"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.0.1"
version = "1.0.2"
[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
[[ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "NaNMath", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "1d090099fb82223abc48f7ce176d3f7696ede36d"
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "e2af66012e08966366a43251e1fd421522908be6"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.12"
version = "0.10.18"
[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "MbedTLS", "Sockets"]
git-tree-sha1 = "c7ec02c4c6a039a98a15f955462cd7aea5df4508"
deps = ["Base64", "Dates", "IniFile", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "c9f380c76d8aaa1fa7ea9cf97bddbc0d5b15adc2"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.8.19"
version = "0.9.5"
[[IniFile]]
deps = ["Test"]
@ -109,6 +121,11 @@ version = "0.5.0"
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[JLLWrappers]]
git-tree-sha1 = "a431f5f2ca3f4feef3bd7a5e94b8b8d4f2f647a0"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.2.0"
[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
@ -123,15 +140,15 @@ version = "0.3.2"
[[JSONSchema]]
deps = ["HTTP", "JSON", "ZipFile"]
git-tree-sha1 = "a9ecdbc90be216912a2e3e8a8e38dc4c93f0d065"
git-tree-sha1 = "b84ab8139afde82c7c65ba2b792fe12e01dd7307"
uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
version = "0.3.2"
version = "0.3.3"
[[JuMP]]
deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "766014f271bd33b7f9d9bdc4847e214ee20ae84d"
deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Random", "SparseArrays", "SpecialFunctions", "Statistics"]
git-tree-sha1 = "e952f49e2242fa21edcf27bbd6c67041685bee5d"
uuid = "4076af6c-e467-56ae-b986-b466b2749572"
version = "0.21.4"
version = "0.21.6"
[[LibGit2]]
deps = ["Printf"]
@ -149,9 +166,9 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "f7d2e3f654af75f01ec49be82c231c382214223a"
git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.5"
version = "0.5.6"
[[Markdown]]
deps = ["Base64"]
@ -159,58 +176,63 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[MathOptInterface]]
deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "JSONSchema", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "SparseArrays", "Test", "Unicode"]
git-tree-sha1 = "cee244578983f9c9eb09278ef54981209b09d9cb"
git-tree-sha1 = "606efe4246da5407d7505265a1ead72467528996"
uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
version = "0.9.16"
version = "0.9.20"
[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "426a6978b03a97ceb7ead77775a1da066343ec6e"
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.2"
version = "1.0.3"
[[MbedTLS_jll]]
deps = ["Libdl", "Pkg"]
git-tree-sha1 = "c0b1286883cac4e2b617539de41111e0776d02e8"
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "0eef589dd1c26a3ac9d753fe1a8bcad63f956fa6"
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.16.8+0"
version = "2.16.8+1"
[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[MutableArithmetics]]
deps = ["LinearAlgebra", "SparseArrays", "Test"]
git-tree-sha1 = "6cf09794783b9de2e662c4e8b60d743021e338d0"
git-tree-sha1 = "6b6bb8f550dc38310afd4a0af0786dc3222459e2"
uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
version = "0.2.10"
version = "0.2.14"
[[NaNMath]]
git-tree-sha1 = "c84c576296d0e2fbb3fc134d3e09086b3ea617cd"
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.4"
version = "0.3.5"
[[NetworkOptions]]
git-tree-sha1 = "ed3157f48a05543cce9b241e1f2815f7e843d96e"
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[OpenSpecFun_jll]]
deps = ["CompilerSupportLibraries_jll", "Libdl", "Pkg"]
git-tree-sha1 = "d51c416559217d974a1113522d5919235ae67a87"
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9db77584158d0ab52307f8c04f8e7c08ca76b5b3"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.3+3"
version = "0.5.3+4"
[[OrderedCollections]]
git-tree-sha1 = "16c08bf5dba06609fe45e30860092d6fa41fde7b"
git-tree-sha1 = "4fa2ba51070ec13fcc7517db714445b4ab986bdf"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.3.1"
version = "1.4.0"
[[PackageCompiler]]
deps = ["Libdl", "Pkg", "UUIDs"]
git-tree-sha1 = "98aa9c653e1dc3473bb5050caf8501293db9eee1"
git-tree-sha1 = "d448727c4b86be81b225b738c88d30334fda6779"
uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
version = "1.2.1"
version = "1.2.5"
[[Parsers]]
deps = ["Dates", "Test"]
git-tree-sha1 = "8077624b3c450b15c087944363606a6ba12f925e"
deps = ["Dates"]
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.0.10"
version = "1.1.0"
[[Pkg]]
deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
@ -222,9 +244,9 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[PyCall]]
deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"]
git-tree-sha1 = "3a3fdb9000d35958c9ba2323ca7c4958901f115d"
git-tree-sha1 = "dd1a970b543bd02efce2984582e996af28cab27f"
uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
version = "1.91.4"
version = "1.92.2"
[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets"]
@ -252,16 +274,16 @@ deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
[[SpecialFunctions]]
deps = ["OpenSpecFun_jll"]
git-tree-sha1 = "d8d8b8a9f4119829410ecd706da4cc8594a1e020"
deps = ["ChainRulesCore", "OpenSpecFun_jll"]
git-tree-sha1 = "5919936c0e92cff40e57d0ddf0ceb667d42e5902"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "0.10.3"
version = "1.3.0"
[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "016d1e1a00fabc556473b07161da3d39726ded35"
git-tree-sha1 = "2f01a51c23eed210ff4a1be102c4cc8236b66e5b"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "0.12.4"
version = "1.1.0"
[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
@ -273,9 +295,9 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[TimerOutputs]]
deps = ["Printf"]
git-tree-sha1 = "f458ca23ff80e46a630922c555d838303e4b9603"
git-tree-sha1 = "32cdbe6cd2d214c25a0b88f985c9e0092877c236"
uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
version = "0.5.6"
version = "0.5.8"
[[TranscodingStreams]]
deps = ["Random", "Test"]
@ -283,6 +305,11 @@ git-tree-sha1 = "7c53c35547de1c5b9d46a4797cf6d8253807108c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.5"
[[URIs]]
git-tree-sha1 = "7855809b88d7b16e9b029afd17880930626f54a2"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.2.0"
[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
@ -297,12 +324,12 @@ version = "1.2.0"
[[ZipFile]]
deps = ["Libdl", "Printf", "Zlib_jll"]
git-tree-sha1 = "254975fef2fc526583bb9b7c9420fe66ffe09f2f"
git-tree-sha1 = "c3a5637e27e914a7a445b8d0ad063d701931e9f7"
uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
version = "0.9.2"
version = "0.9.3"
[[Zlib_jll]]
deps = ["Libdl", "Pkg"]
git-tree-sha1 = "fdd89e5ab270ea0f2a0174bd9093e557d06d4bfa"
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "320228915c8debb12cb434c59057290f0834dbf6"
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.11+16"
version = "1.2.11+18"

@ -1,7 +1,7 @@
name = "MIPLearn"
uuid = "2b1277c3-b477-4c49-a15e-7ba350325c68"
authors = ["Alinson S Xavier <git@axavier.org>"]
version = "0.1.0"
version = "0.2.0"
[deps]
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"

2
deps/build.jl vendored

@ -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.1.0`)
run(`$pip install miplearn==0.2.0`)
end
install_miplearn()

@ -8,67 +8,56 @@ import Base: dump
to_model(instance) =
error("not implemented: to_model")
get_instance_features(instance) =
error("not implemented: get_instance_features")
get_instance_features(instance) = [0.0]
get_variable_features(instance, var, index) =
error("not implemented: get_variable_features")
get_variable_features(instance, varname) = [0.0]
get_variable_category(instance, var, index) = "default"
get_variable_category(instance, varname) = "default"
find_violated_lazy_constraints(instance, model) = []
build_lazy_constraint(instance, model, v) = nothing
dump(instance::PyCall.PyObject, filename) = @pycall instance.dump(filename)
load!(instance::PyCall.PyObject, filename) = @pycall instance.load(filename)
macro Instance(klass)
quote
@pydef mutable struct Wrapper <: Instance
function __init__(self, args...; kwargs...)
self.data = $(esc(klass))(args...; kwargs...)
end
function dump(self, filename)
prev_data = self.data
self.data = JSON2.write(prev_data)
Instance.dump(self, filename)
self.data = prev_data
end
function load(self, filename)
Instance.load(self, filename)
self.data = JSON2.read(self.data, $(esc(klass)))
self.training_data = []
self.features = miplearn.Features()
end
to_model(self) =
$(esc(:to_model))(self.data)
get_instance_features(self) =
get_instance_features(self.data)
$(esc(:get_instance_features))(self.data)
get_variable_features(self, var, index) =
get_variable_features(self.data, var, index)
get_variable_features(self, varname) =
$(esc(:get_variable_features))(self.data, varname)
get_variable_category(self, var, index) =
get_variable_category(self.data, var, index)
get_variable_category(self, varname) =
$(esc(:get_variable_category))(self.data, varname)
function find_violated_lazy_constraints(self, model)
find_violated_lazy_constraints(self, model) =
find_violated_lazy_constraints(self.data, model)
end
function build_lazy_constraint(self, model, v)
build_lazy_constraint(self, model, v) =
build_lazy_constraint(self.data, model, v)
end
load(self) = nothing
flush(self) = nothing
end
end
end
export get_instance_features,
get_variable_features,
get_variable_category,
find_violated_lazy_constraints,
build_lazy_constraint,
to_model,
dump,
load!,
@Instance

@ -9,23 +9,13 @@ using TimerOutputs
mutable struct JuMPSolverData
basename_idx_to_var
var_to_basename_idx
varname_to_var
optimizer
instance
model
bin_vars
solution::Union{Nothing,Dict{String,Dict{String,Float64}}}
time_limit::Union{Nothing, Float64}
end
function varname_split(varname::String)
m = match(r"([^[]*)\[(.*)\]", varname)
if m == nothing
return varname, ""
end
return m.captures[1], m.captures[2]
solution::Union{Nothing, Dict{String,Float64}}
cname_to_constr
end
@ -59,29 +49,24 @@ function optimize_and_capture_output!(model; tee::Bool=false)
end
function solve(data::JuMPSolverData; tee::Bool=false)
function solve(
data::JuMPSolverData;
tee::Bool=false,
iteration_cb,
)::Dict
instance, model = data.instance, data.model
if data.time_limit != nothing
JuMP.set_time_limit_sec(model, data.time_limit)
end
wallclock_time = 0
found_lazy = []
log = ""
while true
log *= optimize_and_capture_output!(model, tee=tee)
wallclock_time += JuMP.solve_time(model)
violations = instance.find_violated_lazy_constraints(model)
if length(violations) == 0
if iteration_cb !== nothing
iteration_cb() || break
else
break
end
append!(found_lazy, violations)
for v in violations
instance.build_lazy_constraint(data.model, v)
end
end
update_solution!(data)
instance.found_violated_lazy_constraints = found_lazy
instance.found_violated_user_cuts = []
primal_bound = JuMP.objective_value(model)
dual_bound = JuMP.objective_bound(model)
if JuMP.objective_sense(model) == MOI.MIN_SENSE
@ -93,13 +78,15 @@ function solve(data::JuMPSolverData; tee::Bool=false)
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,
"Log" => log,
"Warm start value" => nothing)
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,
)
end
@ -116,95 +103,270 @@ function solve_lp(data::JuMPSolverData; tee::Bool=false)
for var in bin_vars
JuMP.set_binary(var)
end
return Dict("Optimal value" => obj_value,
"Log" => log)
return Dict(
"LP value" => obj_value,
"LP log" => log,
)
end
function update_solution!(data::JuMPSolverData)
var_to_basename_idx, model = data.var_to_basename_idx, data.model
solution = Dict{String,Dict{String,Float64}}()
for var in JuMP.all_variables(model)
var in keys(var_to_basename_idx) || continue
basename, idx = var_to_basename_idx[var]
if !haskey(solution, basename)
solution[basename] = Dict{String,Float64}()
end
solution[basename][idx] = JuMP.value(var)
end
data.solution = solution
end
function get_variables(data::JuMPSolverData)
var_to_basename_idx, model = data.var_to_basename_idx, data.model
variables = Dict()
for var in JuMP.all_variables(model)
var in keys(var_to_basename_idx) || continue
basename, idx = var_to_basename_idx[var]
if !haskey(variables, basename)
variables[basename] = []
end
push!(variables[basename], idx)
end
return variables
data.solution = Dict(
JuMP.name(var) => JuMP.value(var)
for var in JuMP.all_variables(data.model)
)
end
function set_instance!(data::JuMPSolverData, instance, model)
data.instance = instance
data.model = model
data.var_to_basename_idx = Dict(var => varname_split(JuMP.name(var))
for var in JuMP.all_variables(model))
data.basename_idx_to_var = Dict(varname_split(JuMP.name(var)) => var
for var in JuMP.all_variables(model))
data.bin_vars = [var
for var in JuMP.all_variables(model)
if JuMP.is_binary(var)]
if data.optimizer != nothing
data.bin_vars = [
var
for var in JuMP.all_variables(data.model)
if JuMP.is_binary(var)
]
data.varname_to_var = Dict(
JuMP.name(var) => var
for var in JuMP.all_variables(data.model)
)
if data.optimizer !== nothing
JuMP.set_optimizer(model, data.optimizer)
end
end
data.cname_to_constr = Dict()
for (ftype, stype) in JuMP.list_of_constraint_types(model)
for constr in JuMP.all_constraints(model, ftype, stype)
name = JuMP.name(constr)
length(name) > 0 || continue
data.cname_to_constr[name] = constr
end
end
end
function fix!(data::JuMPSolverData, solution)
count = 0
for (basename, subsolution) in solution
for (idx, value) in subsolution
value != nothing || continue
var = data.basename_idx_to_var[basename, idx]
JuMP.fix(var, value, force=true)
count += 1
end
for (varname, value) in solution
value !== nothing || continue
var = data.varname_to_var[varname]
JuMP.fix(var, value, force=true)
end
@info "Fixing $count variables"
end
function set_warm_start!(data::JuMPSolverData, solution)
count = 0
for (basename, subsolution) in solution
for (idx, value) in subsolution
value != nothing || continue
var = data.basename_idx_to_var[basename, idx]
JuMP.set_start_value(var, value)
count += 1
end
for (varname, value) in solution
value !== nothing || continue
var = data.varname_to_var[varname]
JuMP.set_start_value(var, value)
end
@info "Setting warm start values for $count variables"
end
end
function get_variable_names(data::JuMPSolverData)
return [JuMP.name(var) for var in JuMP.all_variables(data.model)]
end
function is_infeasible(data::JuMPSolverData)
return JuMP.termination_status(data.model) == MOI.INFEASIBLE
end
function get_constraint_ids(data::JuMPSolverData)
return [cname for cname in keys(data.cname_to_constr)]
end
function get_constraint_rhs(data::JuMPSolverData, cname)
constr = data.cname_to_constr[cname]
return get_constraint_rhs(constr)
end
function get_constraint_lhs(data::JuMPSolverData, cname)
constr = data.cname_to_constr[cname]
return get_constraint_lhs(constr)
end
function get_constraint_sense(data::JuMPSolverData, cname)
constr = data.cname_to_constr[cname]
return get_constraint_sense(constr)
end
# Constraints: ScalarAffineFunction, LessThan
# -------------------------------------------------------------------------
function get_constraint_rhs(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.LessThan{T},
},
ScalarShape,
},
)::T where T
return MOI.get(
constr.model.moi_backend,
MOI.ConstraintSet(),
constr.index,
).upper
end
function _terms_dict(constr)
terms = MOI.get(
constr.model.moi_backend,
MOI.ConstraintFunction(),
constr.index,
).terms
return Dict(
MOI.get(
constr.model.moi_backend,
MOI.VariableName(),
term.variable_index
) => term.coefficient
for term in terms
)
end
function get_constraint_lhs(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.LessThan{T},
},
ScalarShape,
},
)::Dict{String, T} where T
return _terms_dict(constr)
end
function get_constraint_sense(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.LessThan{T},
},
ScalarShape,
},
)::String where T
return "<"
end
# Constraints: ScalarAffineFunction, GreaterThan
# -------------------------------------------------------------------------
function get_constraint_rhs(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.GreaterThan{T},
},
ScalarShape,
},
)::T where T
return MOI.get(
constr.model.moi_backend,
MOI.ConstraintSet(),
constr.index,
).lower
end
function get_constraint_lhs(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.GreaterThan{T},
},
ScalarShape,
},
)::Dict{String, T} where T
return _terms_dict(constr)
end
function get_constraint_sense(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.GreaterThan{T},
},
ScalarShape,
},
)::String where T
return ">"
end
# Constraints: ScalarAffineFunction, EqualTo
# -------------------------------------------------------------------------
function get_constraint_rhs(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.EqualTo{T},
},
ScalarShape,
},
)::T where T
return MOI.get(
constr.model.moi_backend,
MOI.ConstraintSet(),
constr.index,
).value
end
function get_constraint_lhs(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.EqualTo{T},
},
ScalarShape,
},
)::Dict{String, T} where T
return _terms_dict(constr)
end
function get_constraint_sense(
constr::ConstraintRef{
Model,
MathOptInterface.ConstraintIndex{
MathOptInterface.ScalarAffineFunction{T},
MathOptInterface.EqualTo{T},
},
ScalarShape,
},
)::String where T
return "="
end
@pydef mutable struct JuMPSolver <: miplearn.solvers.internal.InternalSolver
function __init__(self; optimizer)
self.data = JuMPSolverData(nothing, # basename_idx_to_var
nothing, # var_to_basename_idx
optimizer,
nothing, # instance
nothing, # model
nothing, # bin_vars
nothing, # solution
nothing, # time limit
)
self.data = JuMPSolverData(
nothing, # varname_to_var
optimizer,
nothing, # instance
nothing, # model
nothing, # bin_vars
nothing, # solution
nothing, # cname_to_constr
)
end
set_warm_start(self, solution) =
@ -216,8 +378,17 @@ end
set_instance(self, instance, model) =
set_instance!(self.data, instance, model)
solve(self; tee=false) =
solve(self.data, tee=tee)
solve(
self;
tee=false,
iteration_cb,
lazy_cb,
user_cut_cb,
) = solve(
self.data,
tee=tee,
iteration_cb=iteration_cb,
)
solve_lp(self; tee=false) =
solve_lp(self.data, tee=tee)
@ -228,26 +399,40 @@ end
get_variables(self) =
get_variables(self.data)
set_time_limit(self, time_limit) =
self.data.time_limit = time_limit
set_gap_tolerance(self, gap_tolerance) =
@warn "JuMPSolver: set_gap_tolerance not implemented"
set_node_limit(self) =
@warn "JuMPSolver: set_node_limit not implemented"
set_threads(self, threads) =
@warn "JuMPSolver: set_threads not implemented"
set_branching_priorities(self, priorities) =
@warn "JuMPSolver: set_branching_priorities not implemented"
add_constraint(self, constraint) = nothing
add_constraint(self, constraint) =
nothing
get_variable_names(self) =
get_variable_names(self.data)
is_infeasible(self) =
is_infeasible(self.data)
get_constraint_ids(self) =
get_constraint_ids(self.data)
get_constraint_rhs(self, cname) =
get_constraint_rhs(self.data, cname)
get_constraint_lhs(self, cname) =
get_constraint_lhs(self.data, cname)
get_constraint_sense(self, cname) =
get_constraint_sense(self.data, cname)
clear_warm_start(self) =
error("JuMPSolver.clear_warm_start should never be called")
clone(self) = self
add_cut(self) = error("not implemented")
extract_constraint(self) = error("not implemented")
is_constraint_satisfied(self) = error("not implemented")
set_constraint_sense(self) = error("not implemented")
relax(self) = error("not implemented")
get_inequality_slacks(self) = error("not implemented")
get_dual(self) = error("not implemented")
get_sense(self) = error("not implemented")
end
export JuMPSolver, solve!, fit!, add!
export JuMPSolver, solve!, fit!, add!

@ -10,19 +10,18 @@ function LearningSolver(;
optimizer,
kwargs...,
)::LearningSolver
py = @pycall miplearn.LearningSolver(;
kwargs...,
solver=JuMPSolver(optimizer=optimizer))
py = miplearn.LearningSolver(
;
kwargs...,
solver=JuMPSolver(optimizer=optimizer),
)
return LearningSolver(py)
end
solve!(solver::LearningSolver, instance; kwargs...) =
@pycall solver.py.solve(instance; kwargs...)
solver.py.solve(instance; kwargs...)
fit!(solver::LearningSolver, instances; kwargs...) =
@pycall solver.py.fit(instances; kwargs...)
add!(solver::LearningSolver, component; kwargs...) =
@pycall solver.py.add(component; kwargs...)
solver.py.fit(instances; kwargs...)
export LearningSolver

@ -1,5 +1,4 @@
[deps]
CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0"
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"

@ -4,62 +4,20 @@
using Test
using MIPLearn
using CPLEX
using Gurobi
using PyCall
@testset "varname_split" begin
@test MIPLearn.varname_split("x[1]") == ("x", "1")
end
miplearn_tests = pyimport("miplearn.solvers.tests")
@testset "JuMPSolver" begin
for optimizer in [CPLEX.Optimizer, Gurobi.Optimizer]
instance = KnapsackInstance([23., 26., 20., 18.],
[505., 352., 458., 220.],
67.0)
for optimizer in [Gurobi.Optimizer]
instance = KnapsackInstance(
[23., 26., 20., 18.],
[505., 352., 458., 220.],
67.0,
)
model = instance.to_model()
solver = JuMPSolver(optimizer=optimizer)
solver.set_instance(instance, model)
solver.set_time_limit(30)
solver.set_warm_start(Dict("x" => Dict(
"1" => 1.0,
"2" => 0.0,
"3" => 0.0,
"4" => 1.0,
)))
stats = solver.solve()
@test stats["Lower bound"] == 1183.0
@test stats["Upper bound"] == 1183.0
@test stats["Sense"] == "max"
@test stats["Wallclock time"] > 0
@test length(stats["Log"]) > 100
solution = solver.get_solution()
@test solution["x"]["1"] == 1.0
@test solution["x"]["2"] == 0.0
@test solution["x"]["3"] == 1.0
@test solution["x"]["4"] == 1.0
stats = solver.solve_lp()
@test round(stats["Optimal value"], digits=3) == 1287.923
@test length(stats["Log"]) > 100
solution = solver.get_solution()
@test round(solution["x"]["1"], digits=3) == 1.000
@test round(solution["x"]["2"], digits=3) == 0.923
@test round(solution["x"]["3"], digits=3) == 1.000
@test round(solution["x"]["4"], digits=3) == 0.000
solver.fix(Dict("x" => Dict(
"1" => 1.0,
"2" => 0.0,
"3" => 0.0,
"4" => 1.0,
)))
stats = solver.solve()
@test stats["Lower bound"] == 725.0
@test stats["Upper bound"] == 725.0
miplearn_tests.test_internal_solver(solver, instance, model)
end
end

@ -17,9 +17,16 @@ end
function to_model(data::KnapsackData)
model = Model()
n = length(data.weights)
@variable(model, x[1:n], Bin)
@objective(model, Max, sum(x[i] * data.prices[i] for i in 1:n))
@constraint(model, sum(x[i] * data.weights[i] for i in 1:n) <= data.capacity)
@variable(model, x[0:(n-1)], Bin)
@objective(model, Max, sum(x[i] * data.prices[i+1] for i in 0:(n-1)))
@constraint(
model,
eq_capacity,
sum(
x[i] * data.weights[i+1]
for i in 0:(n-1)
) <= data.capacity,
)
return model
end

@ -4,48 +4,22 @@
using Test
using MIPLearn
using CPLEX
using Gurobi
@testset "Instance" begin
weights = [23., 26., 20., 18.]
prices = [505., 352., 458., 220.]
capacity = 67.0
instance = KnapsackInstance(weights, prices, capacity)
filename = tempname()
dump(instance, filename)
instance = KnapsackInstance([0.0], [0.0], 0.0)
load!(instance, filename)
@test instance.data.weights == weights
@test instance.data.prices == prices
@test instance.data.capacity == capacity
end
@testset "LearningSolver" begin
for optimizer in [CPLEX.Optimizer, Gurobi.Optimizer]
instance = KnapsackInstance([23., 26., 20., 18.],
[505., 352., 458., 220.],
67.0)
solver = LearningSolver(optimizer=optimizer,
mode="heuristic",
time_limit=90)
for optimizer in [Gurobi.Optimizer]
instance = KnapsackInstance(
[23., 26., 20., 18.],
[505., 352., 458., 220.],
67.0,
)
solver = LearningSolver(
optimizer=optimizer,
mode="heuristic",
)
stats = solve!(solver, instance)
@test instance.solution["x"]["1"] == 1.0
@test instance.solution["x"]["2"] == 0.0
@test instance.solution["x"]["3"] == 1.0
@test instance.solution["x"]["4"] == 1.0
@test instance.lower_bound == 1183.0
@test instance.upper_bound == 1183.0
@test round(instance.lp_solution["x"]["1"], digits=3) == 1.000
@test round(instance.lp_solution["x"]["2"], digits=3) == 0.923
@test round(instance.lp_solution["x"]["3"], digits=3) == 1.000
@test round(instance.lp_solution["x"]["4"], digits=3) == 0.000
@test round(instance.lp_value, digits=3) == 1287.923
fit!(solver, [instance])
solve!(solver, instance)
end
end
end

Loading…
Cancel
Save