Implement save, load_jump_instance

master
Alinson S. Xavier 4 years ago
parent f94eb598c9
commit 62974e2438

@ -101,6 +101,12 @@ uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
deps = ["ArgTools", "LibCURL", "NetworkOptions"] deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "cfb694feaddf4f0381ef3cc9d4c0d8fc6b7e2ea7"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.9.0"
[[ForwardDiff]] [[ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "e2af66012e08966366a43251e1fd421522908be6" git-tree-sha1 = "e2af66012e08966366a43251e1fd421522908be6"
@ -123,6 +129,12 @@ version = "0.5.0"
deps = ["Markdown"] deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[JLD2]]
deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"]
git-tree-sha1 = "236b8ca4b8f01ebc6f2fceedf344a077f0e69e79"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
version = "0.4.7"
[[JLLWrappers]] [[JLLWrappers]]
deps = ["Preferences"] deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e" git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
@ -264,6 +276,17 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
deps = ["Serialization"] deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[Reexport]]
git-tree-sha1 = "57d8440b0c7d98fc4f889e478e80f268d534c9d5"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.0.0"
[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"
[[SHA]] [[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

@ -5,6 +5,7 @@ version = "0.2.0"
[deps] [deps]
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"

@ -108,13 +108,12 @@ end
fit!(solver, training_instances) fit!(solver, training_instances)
# Save trained solver to disk # Save trained solver to disk
save!(solver, "solver.bin") save("solver.mls", solver)
# Application restarts... # Application restarts...
# Load trained solver from disk # Load trained solver from disk
solver = LearningSolver(Cbc.Optimizer) solver = load("solver.mls")
load!(solver, "solver.bin")
# Solve additional instances # Solve additional instances
test_instances = [...] test_instances = [...]
@ -140,6 +139,43 @@ test_instances = [...]
parallel_solve!(solver, test_instances) parallel_solve!(solver, test_instances)
``` ```
### 1.6 Solving instances from disk
```julia
using MIPLearn
using JuMP
using Cbc
# Create 600 problem instances and save them to files
for i in 1:600
m = Model()
@variable(m, x, Bin)
@objective(m, Min, x)
@feature(x, [1.0])
instance = JuMPInstance(m)
save("instance-$i.bin", instance)
end
# Initialize instances and solver
training_instances = [FileInstance("instance-$i.bin") for i in 1:500]
test_instances = [FileInstance("instance-$i.bin") for i in 501:600]
solver = LearningSolver(Cbc.Optimizer)
# Solve training instances
for instance in training_instances
solve!(solver, instance)
end
# Train ML models
fit!(solver, training_instances)
# Solve test instances
for instance in test_instances
solve!(solver, instance)
end
```
## 2. Customization ## 2. Customization
### 2.1 Selecting solver components ### 2.1 Selecting solver components

@ -3,6 +3,7 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
using JuMP using JuMP
using JLD2
@pydef mutable struct PyJuMPInstance <: miplearn.Instance @pydef mutable struct PyJuMPInstance <: miplearn.Instance
@ -23,38 +24,149 @@ using JuMP
function get_variable_features(self, var_name) function get_variable_features(self, var_name)
model = self.model model = self.model
v = variable_by_name(model, var_name) v = variable_by_name(model, var_name)
return get(model.ext[:miplearn][:variable_features], v, [0.0]) return get(model.ext[:miplearn][:variable_features], v, nothing)
end end
function get_variable_category(self, var_name) function get_variable_category(self, var_name)
model = self.model model = self.model
v = variable_by_name(model, var_name) v = variable_by_name(model, var_name)
return get(model.ext[:miplearn][:variable_categories], v, "default") return get(model.ext[:miplearn][:variable_categories], v, nothing)
end end
function get_constraint_features(self, cname) function get_constraint_features(self, cname)
model = self.model model = self.model
c = constraint_by_name(model, cname) c = constraint_by_name(model, cname)
return get(model.ext[:miplearn][:constraint_features], c, [0.0]) return get(model.ext[:miplearn][:constraint_features], c, nothing)
end end
function get_constraint_category(self, cname) function get_constraint_category(self, cname)
model = self.model model = self.model
c = constraint_by_name(model, cname) c = constraint_by_name(model, cname)
return get(model.ext[:miplearn][:constraint_categories], c, "default") return get(model.ext[:miplearn][:constraint_categories], c, nothing)
end end
end end
struct JuMPInstance struct JuMPInstance
py::PyCall.PyObject py::PyCall.PyObject
model::Model
end end
function JuMPInstance(model) function JuMPInstance(model)
model isa Model || error("model should be a JuMP.Model. Found $(typeof(model)) instead.") model isa Model || error("model should be a JuMP.Model. Found $(typeof(model)) instead.")
return JuMPInstance(PyJuMPInstance(model)) return JuMPInstance(
PyJuMPInstance(model),
model,
)
end end
export JuMPInstance function save(filename::AbstractString, instance::JuMPInstance)::Nothing
# Convert JuMP model to MPS
mps_filename = "$(tempname()).mps.gz"
write_to_file(instance.model, mps_filename)
mps = read(mps_filename)
# Pickle instance.py.samples. Ideally, we would use dumps and loads, but this
# causes some issues with PyCall, probably due to automatic type conversions.
py_samples_filename = tempname()
miplearn.write_pickle_gz(instance.py.samples, py_samples_filename)
py_samples = read(py_samples_filename)
# Replace variable/constraint refs by names
_to_names(d) = Dict(name(var) => value for (var, value) in d)
ext_original = instance.model.ext[:miplearn]
ext_names = Dict(
:variable_features => _to_names(ext_original[:variable_features]),
:variable_categories => _to_names(ext_original[:variable_categories]),
:constraint_features => _to_names(ext_original[:constraint_features]),
:constraint_categories => _to_names(ext_original[:constraint_categories]),
:instance_features => ext_original[:instance_features],
)
# Generate JLD2 file
jldsave(
filename;
miplearn_version=0.2,
mps=mps,
ext=ext_names,
py_samples=py_samples,
)
return
end
function load_jump_instance(filename::AbstractString)::JuMPInstance
jldopen(filename, "r") do file
file["miplearn_version"] == 0.2 || error(
"MIPLearn version 0.2 cannot read instance files generated by " *
"version $(file["miplearn_version"])."
)
# Convert MPS to JuMP
mps_filename = "$(tempname()).mps.gz"
write(mps_filename, file["mps"])
model = read_from_file(mps_filename)
# Unpickle instance.py.samples
py_samples_filename = tempname()
write(py_samples_filename, file["py_samples"])
py_samples = miplearn.read_pickle_gz(py_samples_filename)
# Replace variable/constraint names by refs
_to_var(model, d) = Dict(
variable_by_name(model, varname) => value
for (varname, value) in d
)
_to_constr(model, d) = Dict(
constraint_by_name(model, cname) => value
for (cname, value) in d
)
ext = file["ext"]
model.ext[:miplearn] = Dict(
:variable_features => _to_var(model, ext[:variable_features]),
:variable_categories => _to_var(model, ext[:variable_categories]),
:constraint_features => _to_constr(model, ext[:constraint_features]),
:constraint_categories => _to_constr(model, ext[:constraint_categories]),
:instance_features => ext[:instance_features],
)
instance = JuMPInstance(model)
instance.py.samples = py_samples
return instance
end
end
struct FileInstance
filename::AbstractString
loaded::Union{Nothing,JuMPInstance}
end
function FileInstance(filename::AbstractString)::FileInstance
return FileInstance(
filename,
nothing,
)
end
function load!(instance::FileInstance)
instance.loaded = load_jump_instance(instance.filename)
end
function free!(instance::FileInstance)
instance.loaded = nothing
end
function flush!(instance::FileInstance)
save(instance.filename, instance.loaded)
end
export JuMPInstance, save, load_jump_instance

@ -48,7 +48,7 @@ using Gurobi
solve!(solver, instance) solve!(solver, instance)
end end
@testset "plain model" begin @testset "model without annotations" begin
model = Model() model = Model()
@variable(model, x, Bin) @variable(model, x, Bin)
@variable(model, y, Bin) @variable(model, y, Bin)
@ -57,4 +57,34 @@ using Gurobi
instance = JuMPInstance(model) instance = JuMPInstance(model)
stats = solve!(solver, instance) stats = solve!(solver, instance)
end end
@testset "file model" begin
# Create basic model
model = Model()
@variable(model, x, Bin)
@variable(model, y, Bin)
@objective(model, Max, x + y)
@feature(x, [1.0])
@category(x, "cat1")
@feature(model, [5.0])
# Solve
instance = JuMPInstance(model)
solver = LearningSolver(Gurobi.Optimizer)
stats = solve!(solver, instance)
@test length(instance.py.samples) == 1
# Save model to file
filename = tempname()
save(filename, instance)
@test isfile(filename)
# Read model from file
loaded = load_jump_instance(filename)
x = variable_by_name(loaded.model, "x")
@test loaded.model.ext[:miplearn][:variable_features][x] == [1.0]
@test loaded.model.ext[:miplearn][:variable_categories][x] == "cat1"
@test loaded.model.ext[:miplearn][:instance_features] == [5.0]
@test length(loaded.py.samples) == 1
end
end end

Loading…
Cancel
Save