mirror of
https://github.com/ANL-CEEESA/MIPLearn.jl.git
synced 2025-12-06 08:28:52 -06:00
Rewrite FileInstance
This commit is contained in:
@@ -2,72 +2,66 @@
|
||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
import Base: flush
|
||||
|
||||
mutable struct FileInstance <: Instance
|
||||
py::Union{Nothing,PyCall.PyObject}
|
||||
loaded::Union{Nothing, JuMPInstance}
|
||||
filename::AbstractString
|
||||
|
||||
function FileInstance(filename::String)::FileInstance
|
||||
instance = new(nothing, nothing, filename)
|
||||
instance.py = PyFileInstance(instance)
|
||||
return instance
|
||||
end
|
||||
end
|
||||
|
||||
to_model(instance::FileInstance) = to_model(instance.loaded)
|
||||
get_instance_features(instance::FileInstance) = get_instance_features(instance.loaded)
|
||||
get_variable_features(instance::FileInstance) = get_variable_features(instance.loaded)
|
||||
get_variable_categories(instance::FileInstance) = get_variable_categories(instance.loaded)
|
||||
get_constraint_features(instance::FileInstance) = get_constraint_features(instance.loaded)
|
||||
get_samples(instance::FileInstance) = get_samples(instance.loaded)
|
||||
push_sample!(instance::FileInstance, sample::PyCall.PyObject) = push_sample!(instance.loaded, sample)
|
||||
|
||||
function get_constraint_categories(instance::FileInstance)
|
||||
return get_constraint_categories(instance.loaded)
|
||||
end
|
||||
|
||||
function load(instance::FileInstance)
|
||||
if instance.loaded === nothing
|
||||
instance.loaded = load_instance(instance.filename)
|
||||
end
|
||||
end
|
||||
|
||||
function free(instance::FileInstance)
|
||||
instance.loaded.samples = []
|
||||
instance.loaded = nothing
|
||||
GC.gc()
|
||||
end
|
||||
|
||||
function flush(instance::FileInstance)
|
||||
save(instance.filename, instance.loaded)
|
||||
end
|
||||
|
||||
function __init_PyFileInstance__()
|
||||
@pydef mutable struct Class <: miplearn.Instance
|
||||
function __init__(self, filename)
|
||||
self.filename = filename
|
||||
self.loaded = nothing
|
||||
self.samples = nothing
|
||||
end
|
||||
|
||||
function to_model(self)
|
||||
return self.loaded.py.to_model()
|
||||
end
|
||||
|
||||
function get_instance_features(self)
|
||||
return self.loaded.py.get_instance_features()
|
||||
end
|
||||
|
||||
function get_variable_features(self, var_name)
|
||||
return self.loaded.py.get_variable_features(var_name)
|
||||
end
|
||||
|
||||
function get_variable_category(self, var_name)
|
||||
return self.loaded.py.get_variable_category(var_name)
|
||||
end
|
||||
|
||||
function get_constraint_features(self, cname)
|
||||
return self.loaded.py.get_constraint_features(cname)
|
||||
end
|
||||
|
||||
function get_constraint_category(self, cname)
|
||||
return self.loaded.py.get_constraint_category(cname)
|
||||
end
|
||||
|
||||
function load(self)
|
||||
if self.loaded === nothing
|
||||
self.loaded = load_instance(self.filename)
|
||||
self.samples = self.loaded.py.samples
|
||||
end
|
||||
end
|
||||
|
||||
function free(self)
|
||||
self.loaded = nothing
|
||||
self.samples = nothing
|
||||
end
|
||||
|
||||
function flush(self)
|
||||
self.loaded.py.samples = self.samples
|
||||
save(self.filename, self.loaded)
|
||||
function __init__(self, jl)
|
||||
self.jl = jl
|
||||
end
|
||||
to_model(self) = to_model(self.jl)
|
||||
get_instance_features(self) = get_instance_features(self.jl)
|
||||
get_variable_features(self) = get_variable_features(self.jl)
|
||||
get_variable_categories(self) = get_variable_categories(self.jl)
|
||||
get_constraint_features(self) = get_constraint_features(self.jl)
|
||||
get_constraint_categories(self) = get_constraint_categories(self.jl)
|
||||
get_samples(self) = get_samples(self.jl)
|
||||
push_sample(self, sample) = push_sample!(self.jl, sample)
|
||||
load(self) = load(self.jl)
|
||||
free(self) = free(self.jl)
|
||||
flush(self) = flush(self.jl)
|
||||
end
|
||||
copy!(PyFileInstance, Class)
|
||||
end
|
||||
|
||||
struct FileInstance <: Instance
|
||||
py::PyCall.PyObject
|
||||
filename::AbstractString
|
||||
end
|
||||
|
||||
|
||||
function FileInstance(filename)::FileInstance
|
||||
filename isa AbstractString || error("filename should be a string. Found $(typeof(filename)) instead.")
|
||||
return FileInstance(
|
||||
PyFileInstance(filename),
|
||||
filename,
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
export FileInstance
|
||||
|
||||
@@ -5,85 +5,88 @@
|
||||
using JuMP
|
||||
using JLD2
|
||||
|
||||
mutable struct JuMPInstance <: Instance
|
||||
py::Union{Nothing,PyCall.PyObject}
|
||||
model::Union{Nothing,JuMP.Model}
|
||||
mps::Union{Nothing,Vector{UInt8}}
|
||||
ext::AbstractDict
|
||||
samples::Vector{PyCall.PyObject}
|
||||
|
||||
function JuMPInstance(model::JuMP.Model)
|
||||
init_miplearn_ext(model)
|
||||
instance = new(nothing, model, nothing, model.ext[:miplearn], [])
|
||||
py = PyJuMPInstance(instance)
|
||||
instance.py = py
|
||||
return instance
|
||||
end
|
||||
|
||||
function JuMPInstance(mps::Vector{UInt8}, ext::AbstractDict)
|
||||
"instance_features" in keys(ext) || error("provided ext is not initialized")
|
||||
instance = new(nothing, nothing, mps, ext, [])
|
||||
instance.py = PyJuMPInstance(instance)
|
||||
return instance
|
||||
end
|
||||
end
|
||||
|
||||
function to_model(instance::JuMPInstance)::JuMP.Model
|
||||
if instance.model === nothing
|
||||
mps_filename = "$(tempname()).mps.gz"
|
||||
write(mps_filename, instance.mps)
|
||||
instance.model = read_from_file(mps_filename)
|
||||
instance.model.ext[:miplearn] = instance.ext
|
||||
end
|
||||
return instance.model
|
||||
end
|
||||
|
||||
get_instance_features(instance::JuMPInstance) = instance.ext["instance_features"]
|
||||
get_variable_features(instance::JuMPInstance) = instance.ext["variable_features"]
|
||||
get_variable_categories(instance::JuMPInstance) = instance.ext["variable_categories"]
|
||||
get_constraint_features(instance::JuMPInstance) = instance.ext["constraint_features"]
|
||||
get_constraint_categories(instance::JuMPInstance) = instance.ext["constraint_categories"]
|
||||
get_samples(instance::JuMPInstance) = instance.samples
|
||||
|
||||
function push_sample!(instance::JuMPInstance, sample::PyCall.PyObject)
|
||||
push!(instance.samples, sample)
|
||||
end
|
||||
|
||||
function __init_PyJuMPInstance__()
|
||||
@pydef mutable struct Class <: miplearn.Instance
|
||||
function __init__(self, model)
|
||||
init_miplearn_ext(model)
|
||||
self.model = model
|
||||
self.samples = []
|
||||
end
|
||||
|
||||
function to_model(self)
|
||||
return self.model
|
||||
end
|
||||
|
||||
function get_instance_features(self)
|
||||
return self.model.ext[:miplearn][:instance_features]
|
||||
end
|
||||
|
||||
function get_variable_features(self, var_name)
|
||||
model = self.model
|
||||
return get(model.ext[:miplearn][:variable_features], var_name, nothing)
|
||||
end
|
||||
|
||||
function get_variable_category(self, var_name)
|
||||
model = self.model
|
||||
return get(model.ext[:miplearn][:variable_categories], var_name, nothing)
|
||||
end
|
||||
|
||||
function get_constraint_features(self, cname)
|
||||
model = self.model
|
||||
return get(model.ext[:miplearn][:constraint_features], cname, nothing)
|
||||
end
|
||||
|
||||
function get_constraint_category(self, cname)
|
||||
model = self.model
|
||||
return get(model.ext[:miplearn][:constraint_categories], cname, nothing)
|
||||
function __init__(self, jl)
|
||||
self.jl = jl
|
||||
end
|
||||
to_model(self) = to_model(self.jl)
|
||||
get_instance_features(self) = get_instance_features(self.jl)
|
||||
get_variable_features(self) = get_variable_features(self.jl)
|
||||
get_variable_categories(self) = get_variable_categories(self.jl)
|
||||
get_constraint_features(self,) = get_constraint_features(self.jl)
|
||||
get_constraint_categories(self) = get_constraint_categories(self.jl)
|
||||
get_samples(self) = get_samples(self.jl)
|
||||
push_sample(self, sample) = push_sample!(self.jl, sample)
|
||||
end
|
||||
copy!(PyJuMPInstance, Class)
|
||||
end
|
||||
|
||||
|
||||
struct JuMPInstance <: Instance
|
||||
py::PyCall.PyObject
|
||||
model::Model
|
||||
end
|
||||
|
||||
|
||||
function JuMPInstance(model)
|
||||
model isa Model || error("model should be a JuMP.Model. Found $(typeof(model)) instead.")
|
||||
return JuMPInstance(
|
||||
PyJuMPInstance(model),
|
||||
model,
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
function save(filename::AbstractString, instance::JuMPInstance)::Nothing
|
||||
@info "Writing: $filename"
|
||||
time = @elapsed begin
|
||||
# Convert JuMP model to MPS
|
||||
mps_filename = "$(tempname()).mps.gz"
|
||||
write_to_file(instance.model, mps_filename)
|
||||
model = instance.py.to_model()
|
||||
write_to_file(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, quiet=true)
|
||||
py_samples = read(py_samples_filename)
|
||||
samples_filename = tempname()
|
||||
miplearn.write_pickle_gz(instance.samples, samples_filename)
|
||||
samples = read(samples_filename)
|
||||
|
||||
# Generate JLD2 file
|
||||
jldsave(
|
||||
filename;
|
||||
miplearn_version="0.2",
|
||||
mps=mps,
|
||||
ext=instance.model.ext[:miplearn],
|
||||
py_samples=py_samples,
|
||||
ext=model.ext[:miplearn],
|
||||
samples=samples,
|
||||
)
|
||||
end
|
||||
@info @sprintf("File written in %.2f seconds", time)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -97,32 +100,15 @@ function _check_miplearn_version(file)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
function load_instance(filename::AbstractString)::JuMPInstance
|
||||
@info "Reading: $filename"
|
||||
instance = nothing
|
||||
time = @elapsed begin
|
||||
jldopen(filename, "r") do file
|
||||
_check_miplearn_version(file)
|
||||
|
||||
# Convert MPS to JuMP
|
||||
mps_filename = "$(tempname()).mps.gz"
|
||||
write(mps_filename, file["mps"])
|
||||
model = read_from_file(mps_filename)
|
||||
model.ext[:miplearn] = file["ext"]
|
||||
|
||||
# Unpickle instance.py.samples
|
||||
py_samples_filename = tempname()
|
||||
write(py_samples_filename, file["py_samples"])
|
||||
py_samples = miplearn.read_pickle_gz(py_samples_filename, quiet=true)
|
||||
|
||||
instance = JuMPInstance(model)
|
||||
instance.py.samples = py_samples
|
||||
end
|
||||
end
|
||||
@info @sprintf("File read in %.2f seconds", time)
|
||||
instance = JuMPInstance(file["mps"], file["ext"])
|
||||
samples_filename = tempname()
|
||||
write(samples_filename, file["samples"])
|
||||
@time instance.samples = miplearn.read_pickle_gz(samples_filename)
|
||||
return instance
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
export JuMPInstance, save, load_instance
|
||||
|
||||
@@ -41,6 +41,8 @@ function _optimize_and_capture_output!(model; tee::Bool=false)
|
||||
rm(logname)
|
||||
if tee
|
||||
println(log)
|
||||
flush(stdout)
|
||||
Base.Libc.flush_cstdio()
|
||||
end
|
||||
return log
|
||||
end
|
||||
@@ -150,7 +152,7 @@ function build_test_instance_knapsack()
|
||||
@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 PyJuMPInstance(model)
|
||||
return JuMPInstance(model).py
|
||||
end
|
||||
|
||||
|
||||
@@ -159,7 +161,7 @@ function build_test_instance_infeasible()
|
||||
@variable(model, x, Bin)
|
||||
@objective(model, Max, x)
|
||||
@constraint(model, x >= 2)
|
||||
return PyJuMPInstance(model)
|
||||
return JuMPInstance(model).py
|
||||
end
|
||||
|
||||
|
||||
@@ -389,6 +391,8 @@ end
|
||||
function get_constraints(
|
||||
data::JuMPSolverData;
|
||||
with_static::Bool,
|
||||
with_sa::Bool,
|
||||
with_lhs::Bool,
|
||||
)
|
||||
names = []
|
||||
senses, lhs, rhs = nothing, nothing, nothing
|
||||
@@ -420,6 +424,7 @@ function get_constraints(
|
||||
|
||||
if with_static
|
||||
if ftype == JuMP.AffExpr
|
||||
if with_lhs
|
||||
push!(
|
||||
lhs,
|
||||
[
|
||||
@@ -438,6 +443,7 @@ function get_constraints(
|
||||
).terms
|
||||
]
|
||||
)
|
||||
end
|
||||
if stype == MOI.EqualTo{Float64}
|
||||
push!(senses, "=")
|
||||
push!(rhs, cset.value)
|
||||
@@ -535,6 +541,8 @@ function __init_JuMPSolver__()
|
||||
) = get_constraints(
|
||||
self.data,
|
||||
with_static=with_static,
|
||||
with_sa=with_sa,
|
||||
with_lhs=with_lhs,
|
||||
)
|
||||
|
||||
get_constraint_attrs(self) = [
|
||||
|
||||
@@ -17,6 +17,8 @@ function LearningSolver(
|
||||
mode::AbstractString = "exact",
|
||||
simulate_perfect::Bool = false,
|
||||
solve_lp::Bool = true,
|
||||
extract_sa::Bool = true,
|
||||
extract_lhs::Bool = true,
|
||||
)::LearningSolver
|
||||
return LearningSolver(
|
||||
miplearn.LearningSolver(
|
||||
@@ -25,6 +27,8 @@ function LearningSolver(
|
||||
solve_lp=solve_lp,
|
||||
simulate_perfect=simulate_perfect,
|
||||
components=components,
|
||||
extract_lhs=extract_lhs,
|
||||
extract_sa=extract_sa,
|
||||
),
|
||||
optimizer_factory,
|
||||
)
|
||||
@@ -47,67 +51,84 @@ end
|
||||
|
||||
function fit!(solver::LearningSolver, instances::Vector{<:Instance})
|
||||
@python_call solver.py.fit([instance.py for instance in instances])
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function parallel_solve!(solver::LearningSolver, instances::Vector{FileInstance})
|
||||
filenames = [instance.filename for instance in instances]
|
||||
function _solve(
|
||||
solver_filename,
|
||||
instance_filename;
|
||||
discard_output::Bool,
|
||||
)
|
||||
@info "solve $instance_filename"
|
||||
solver = load_solver(solver_filename)
|
||||
solver.py._silence_miplearn_logger()
|
||||
stats = solve!(
|
||||
solver,
|
||||
FileInstance(instance_filename),
|
||||
discard_output = discard_output,
|
||||
)
|
||||
solver.py._restore_miplearn_logger()
|
||||
GC.gc()
|
||||
@info "solve $instance_filename [done]"
|
||||
return stats
|
||||
end
|
||||
|
||||
|
||||
function parallel_solve!(
|
||||
solver::LearningSolver,
|
||||
instances::Vector{FileInstance};
|
||||
discard_output::Bool = false,
|
||||
)
|
||||
instance_filenames = [instance.filename for instance in instances]
|
||||
solver_filename = tempname()
|
||||
save(solver_filename, solver)
|
||||
@sync @distributed for filename in filenames
|
||||
s = load_solver(solver_filename)
|
||||
solve!(s, FileInstance(filename))
|
||||
nothing
|
||||
end
|
||||
return pmap(
|
||||
instance_filename -> _solve(
|
||||
solver_filename,
|
||||
instance_filename,
|
||||
discard_output = discard_output,
|
||||
),
|
||||
instance_filenames,
|
||||
on_error=identity,
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
function save(filename::AbstractString, solver::LearningSolver)
|
||||
@info "Writing: $filename"
|
||||
time = @elapsed begin
|
||||
# Pickle solver.py
|
||||
internal_solver = solver.py.internal_solver
|
||||
internal_solver_prototype = solver.py.internal_solver_prototype
|
||||
solver.py.internal_solver = nothing
|
||||
solver.py.internal_solver_prototype = nothing
|
||||
solver_py_filename = tempname()
|
||||
miplearn.write_pickle_gz(solver.py, solver_py_filename, quiet=true)
|
||||
miplearn.write_pickle_gz(solver.py, solver_py_filename)
|
||||
solver_py = read(solver_py_filename)
|
||||
solver.py.internal_solver = internal_solver
|
||||
solver.py.internal_solver_prototype = internal_solver_prototype
|
||||
|
||||
jldsave(
|
||||
filename;
|
||||
miplearn_version="0.2",
|
||||
solver_py=solver_py,
|
||||
optimizer_factory=solver.optimizer_factory,
|
||||
)
|
||||
end
|
||||
@info @sprintf("File written in %.2f seconds", time)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function load_solver(filename::AbstractString)::LearningSolver
|
||||
@info "Reading: $filename"
|
||||
solver = nothing
|
||||
time = @elapsed begin
|
||||
jldopen(filename, "r") do file
|
||||
_check_miplearn_version(file)
|
||||
solve_py_filename = tempname()
|
||||
write(solve_py_filename, file["solver_py"])
|
||||
solver_py = miplearn.read_pickle_gz(solve_py_filename, quiet=true)
|
||||
solver_py = miplearn.read_pickle_gz(solve_py_filename)
|
||||
internal_solver = JuMPSolver(file["optimizer_factory"])
|
||||
solver_py.internal_solver_prototype = internal_solver
|
||||
solver = LearningSolver(
|
||||
return LearningSolver(
|
||||
solver_py,
|
||||
file["optimizer_factory"],
|
||||
)
|
||||
end
|
||||
end
|
||||
@info @sprintf("File read in %.2f seconds", time)
|
||||
return solver
|
||||
end
|
||||
|
||||
|
||||
export Instance,
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
function init_miplearn_ext(model)::Dict
|
||||
if :miplearn ∉ keys(model.ext)
|
||||
model.ext[:miplearn] = Dict{Symbol, Any}()
|
||||
model.ext[:miplearn][:instance_features] = [0.0]
|
||||
model.ext[:miplearn][:variable_features] = Dict{AbstractString, Vector{Float64}}()
|
||||
model.ext[:miplearn][:variable_categories] = Dict{AbstractString, String}()
|
||||
model.ext[:miplearn][:constraint_features] = Dict{AbstractString, Vector{Float64}}()
|
||||
model.ext[:miplearn][:constraint_categories] = Dict{AbstractString, String}()
|
||||
model.ext[:miplearn] = Dict()
|
||||
model.ext[:miplearn]["instance_features"] = [0.0]
|
||||
model.ext[:miplearn]["variable_features"] = Dict{AbstractString, Vector{Float64}}()
|
||||
model.ext[:miplearn]["variable_categories"] = Dict{AbstractString, String}()
|
||||
model.ext[:miplearn]["constraint_features"] = Dict{AbstractString, Vector{Float64}}()
|
||||
model.ext[:miplearn]["constraint_categories"] = Dict{AbstractString, String}()
|
||||
end
|
||||
return model.ext[:miplearn]
|
||||
end
|
||||
@@ -17,7 +17,7 @@ end
|
||||
|
||||
function set_features!(m::Model, f::Array{Float64})::Nothing
|
||||
ext = init_miplearn_ext(m)
|
||||
ext[:instance_features] = f
|
||||
ext["instance_features"] = f
|
||||
return
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ end
|
||||
function set_features!(v::VariableRef, f::Array{Float64})::Nothing
|
||||
ext = init_miplearn_ext(v.model)
|
||||
n = _get_and_check_name(v)
|
||||
ext[:variable_features][n] = f
|
||||
ext["variable_features"][n] = f
|
||||
return
|
||||
end
|
||||
|
||||
@@ -33,7 +33,7 @@ end
|
||||
function set_category!(v::VariableRef, category::String)::Nothing
|
||||
ext = init_miplearn_ext(v.model)
|
||||
n = _get_and_check_name(v)
|
||||
ext[:variable_categories][n] = category
|
||||
ext["variable_categories"][n] = category
|
||||
return
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ end
|
||||
function set_features!(c::ConstraintRef, f::Array{Float64})::Nothing
|
||||
ext = init_miplearn_ext(c.model)
|
||||
n = _get_and_check_name(c)
|
||||
ext[:constraint_features][n] = f
|
||||
ext["constraint_features"][n] = f
|
||||
return
|
||||
end
|
||||
|
||||
@@ -49,7 +49,7 @@ end
|
||||
function set_category!(c::ConstraintRef, category::String)::Nothing
|
||||
ext = init_miplearn_ext(c.model)
|
||||
n = _get_and_check_name(c)
|
||||
ext[:constraint_categories][n] = category
|
||||
ext["constraint_categories"][n] = category
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -9,61 +9,60 @@ using DataFrames
|
||||
mutable struct BenchmarkRunner
|
||||
solvers::Dict
|
||||
results::Union{Nothing,DataFrame}
|
||||
end
|
||||
|
||||
py::PyCall.PyObject
|
||||
|
||||
function BenchmarkRunner(; solvers::Dict)
|
||||
return BenchmarkRunner(
|
||||
return new(
|
||||
solvers,
|
||||
nothing, # results
|
||||
miplearn.BenchmarkRunner(
|
||||
Dict(
|
||||
sname => solver.py
|
||||
for (sname, solver) in solvers
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function parallel_solve!(
|
||||
runner::BenchmarkRunner,
|
||||
instances::Vector{FileInstance};
|
||||
n_trials::Int = 3,
|
||||
)::Nothing
|
||||
instances = repeat(instances, n_trials)
|
||||
for (solver_name, solver) in runner.solvers
|
||||
@info "Benchmarking: $solver_name"
|
||||
for i in 1:n_trials
|
||||
for instance in instances
|
||||
stats = solve!(solver, instance, discard_output=true)
|
||||
instance.py.free()
|
||||
stats["Solver"] = solver_name
|
||||
stats = Dict(k => isnothing(v) ? missing : v for (k, v) in stats)
|
||||
@info "benchmark $solver_name"
|
||||
stats = parallel_solve!(solver, instances, discard_output=true)
|
||||
for (i, s) in enumerate(stats)
|
||||
s["Solver"] = solver_name
|
||||
s["Instance"] = instances[i].filename
|
||||
s = Dict(k => isnothing(v) ? missing : v for (k, v) in s)
|
||||
if runner.results === nothing
|
||||
runner.results = DataFrame(stats)
|
||||
runner.results = DataFrame(s)
|
||||
else
|
||||
push!(runner.results, stats, cols=:union)
|
||||
push!(runner.results, s, cols=:union)
|
||||
end
|
||||
end
|
||||
@info "benchmark $solver_name [done]"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function fit!(
|
||||
runner::BenchmarkRunner,
|
||||
instances::Vector{FileInstance}
|
||||
)::Nothing
|
||||
for (solver_name, solver) in runner.solvers
|
||||
fit!(solver, instances)
|
||||
@python_call runner.py.fit([instance.py for instance in instances])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function write_csv!(
|
||||
runner::BenchmarkRunner,
|
||||
filename::AbstractString,
|
||||
)::Nothing
|
||||
@info "Writing: $filename"
|
||||
CSV.write(filename, runner.results)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
export BenchmarkRunner,
|
||||
parallel_solve!,
|
||||
fit!,
|
||||
|
||||
@@ -44,16 +44,22 @@ function handle_message(logger::TimeLogger,
|
||||
color = :light_green
|
||||
end
|
||||
|
||||
flush(stdout)
|
||||
flush(stderr)
|
||||
Base.Libc.flush_cstdio()
|
||||
if level >= logger.screen_log_level
|
||||
printstyled(time_string, color=color)
|
||||
println(message)
|
||||
end
|
||||
if logger.file != nothing && level >= logger.io_log_level
|
||||
if logger.file !== nothing && level >= logger.io_log_level
|
||||
write(logger.file, time_string)
|
||||
write(logger.file, message)
|
||||
write(logger.file, "\n")
|
||||
flush(logger.file)
|
||||
end
|
||||
flush(stdout)
|
||||
flush(stderr)
|
||||
Base.Libc.flush_cstdio()
|
||||
end
|
||||
|
||||
function setup_logger()
|
||||
|
||||
18
test/fixtures/knapsack.jl
vendored
18
test/fixtures/knapsack.jl
vendored
@@ -28,15 +28,15 @@ function build_knapsack_model()
|
||||
end
|
||||
|
||||
# Should store ML information
|
||||
@test model.ext[:miplearn][:variable_features]["x[1]"] == [1.0, 5.0]
|
||||
@test model.ext[:miplearn][:variable_features]["x[2]"] == [2.0, 6.0]
|
||||
@test model.ext[:miplearn][:variable_features]["x[3]"] == [3.0, 7.0]
|
||||
@test model.ext[:miplearn][:variable_categories]["x[1]"] == "type-1"
|
||||
@test model.ext[:miplearn][:variable_categories]["x[2]"] == "type-2"
|
||||
@test model.ext[:miplearn][:variable_categories]["x[3]"] == "type-3"
|
||||
@test model.ext[:miplearn][:constraint_features]["c1"] == [1.0, 2.0, 3.0]
|
||||
@test model.ext[:miplearn][:constraint_categories]["c1"] == "c1"
|
||||
@test model.ext[:miplearn][:instance_features] == [5.0]
|
||||
@test model.ext[:miplearn]["variable_features"]["x[1]"] == [1.0, 5.0]
|
||||
@test model.ext[:miplearn]["variable_features"]["x[2]"] == [2.0, 6.0]
|
||||
@test model.ext[:miplearn]["variable_features"]["x[3]"] == [3.0, 7.0]
|
||||
@test model.ext[:miplearn]["variable_categories"]["x[1]"] == "type-1"
|
||||
@test model.ext[:miplearn]["variable_categories"]["x[2]"] == "type-2"
|
||||
@test model.ext[:miplearn]["variable_categories"]["x[3]"] == "type-3"
|
||||
@test model.ext[:miplearn]["constraint_features"]["c1"] == [1.0, 2.0, 3.0]
|
||||
@test model.ext[:miplearn]["constraint_categories"]["c1"] == "c1"
|
||||
@test model.ext[:miplearn]["instance_features"] == [5.0]
|
||||
|
||||
return model
|
||||
end
|
||||
|
||||
@@ -19,6 +19,6 @@ using Cbc
|
||||
solve!(solver, file_instance)
|
||||
|
||||
loaded = load_instance(filename)
|
||||
@test length(loaded.py.samples) == 1
|
||||
@test length(loaded.samples) == 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ using MIPLearn
|
||||
instance = JuMPInstance(model)
|
||||
stats = solve!(solver, instance)
|
||||
@test stats["mip_lower_bound"] == 11.0
|
||||
@test length(instance.py.samples) == 1
|
||||
@test length(instance.samples) == 1
|
||||
fit!(solver, [instance])
|
||||
solve!(solver, instance)
|
||||
end
|
||||
@@ -41,6 +41,6 @@ using MIPLearn
|
||||
solver = LearningSolver(Cbc.Optimizer)
|
||||
solve!(solver, instance, discard_output=true)
|
||||
loaded = load_instance(instance.filename)
|
||||
@test length(loaded.py.samples) == 0
|
||||
@test length(loaded.samples) == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,17 +8,19 @@ using DataFrames
|
||||
|
||||
|
||||
@testset "BenchmarkRunner" begin
|
||||
# Initialize instances and generate training data
|
||||
@info "Building training data..."
|
||||
instances = [
|
||||
build_knapsack_file_instance(),
|
||||
build_knapsack_file_instance(),
|
||||
]
|
||||
parallel_solve!(
|
||||
stats = parallel_solve!(
|
||||
LearningSolver(Cbc.Optimizer),
|
||||
instances,
|
||||
)
|
||||
@test length(stats) == 2
|
||||
@test stats[1] !== nothing
|
||||
@test stats[2] !== nothing
|
||||
|
||||
# Fit and benchmark
|
||||
benchmark = BenchmarkRunner(
|
||||
solvers=Dict(
|
||||
"baseline" => LearningSolver(
|
||||
@@ -34,13 +36,15 @@ using DataFrames
|
||||
),
|
||||
),
|
||||
)
|
||||
@info "Fitting..."
|
||||
fit!(benchmark, instances)
|
||||
parallel_solve!(benchmark, instances, n_trials=1)
|
||||
|
||||
# Write CSV
|
||||
@info "Benchmarking..."
|
||||
parallel_solve!(benchmark, instances, n_trials=2)
|
||||
|
||||
csv_filename = tempname()
|
||||
write_csv!(benchmark, csv_filename)
|
||||
@test isfile(csv_filename)
|
||||
csv = DataFrame(CSV.File(csv_filename))
|
||||
@test size(csv)[1] == 6
|
||||
@test size(csv)[1] == 12
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user