mirror of
https://github.com/ANL-CEEESA/MIPLearn.jl.git
synced 2025-12-06 08:28:52 -06:00
Make compatible with MIPLearn 5b3a56f0; reformat source code
This commit is contained in:
25
Makefile
Normal file
25
Makefile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||||
|
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
|
JULIA := julia --color=yes --project=@.
|
||||||
|
VERSION := 0.2
|
||||||
|
|
||||||
|
build/sysimage.so: src/utils/sysimage.jl Project.toml Manifest.toml
|
||||||
|
mkdir -p build
|
||||||
|
$(JULIA) --trace-compile=build/precompile.jl test/runtests.jl
|
||||||
|
$(JULIA) src/utils/sysimage.jl
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build/*
|
||||||
|
|
||||||
|
test: build/sysimage.so
|
||||||
|
$(JULIA) --sysimage build/sysimage.so test/runtests.jl
|
||||||
|
|
||||||
|
format:
|
||||||
|
julia -e 'using JuliaFormatter; format(["src", "test", "benchmark"], verbose=true);'
|
||||||
|
|
||||||
|
install-deps:
|
||||||
|
julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.14.4"))'
|
||||||
|
|
||||||
|
.PHONY: docs test format install-deps
|
||||||
@@ -361,6 +361,12 @@ git-tree-sha1 = "6a9967c4394858f38b7fc49787b983ba3847e73d"
|
|||||||
uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd"
|
uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd"
|
||||||
version = "0.108.6+2"
|
version = "0.108.6+2"
|
||||||
|
|
||||||
|
[[PackageCompiler]]
|
||||||
|
deps = ["Libdl", "Pkg", "UUIDs"]
|
||||||
|
git-tree-sha1 = "bb40ed7cb3aac2b4cdf42f898c26a58ab797ac62"
|
||||||
|
uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
|
||||||
|
version = "1.3.0"
|
||||||
|
|
||||||
[[Parsers]]
|
[[Parsers]]
|
||||||
deps = ["Dates"]
|
deps = ["Dates"]
|
||||||
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
|
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
|||||||
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"
|
||||||
|
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
|
||||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
|
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
|
||||||
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
|
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
|
||||||
|
|||||||
@@ -44,13 +44,28 @@ function __init__()
|
|||||||
__init_PyFileInstance__()
|
__init_PyFileInstance__()
|
||||||
__init_PyJuMPInstance__()
|
__init_PyJuMPInstance__()
|
||||||
__init_JuMPSolver__()
|
__init_JuMPSolver__()
|
||||||
|
|
||||||
|
py"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def to_str_array(values):
|
||||||
|
if values is None:
|
||||||
|
return None
|
||||||
|
return np.array(values, dtype="S")
|
||||||
|
|
||||||
|
def from_str_array(values):
|
||||||
|
return [v.decode() for v in values]
|
||||||
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
to_str_array(values) = py"to_str_array"(values)
|
||||||
|
from_str_array(values) = py"from_str_array"(values)
|
||||||
|
|
||||||
export DynamicLazyConstraintsComponent,
|
export DynamicLazyConstraintsComponent,
|
||||||
UserCutsComponent,
|
UserCutsComponent,
|
||||||
ObjectiveValueComponent,
|
ObjectiveValueComponent,
|
||||||
PrimalSolutionComponent,
|
PrimalSolutionComponent,
|
||||||
StaticLazyConstraintsComponent,
|
StaticLazyConstraintsComponent,
|
||||||
MinPrecisionThreshold
|
MinPrecisionThreshold
|
||||||
|
|
||||||
end # module
|
end # module
|
||||||
|
|||||||
@@ -2,7 +2,4 @@
|
|||||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
# Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
|
||||||
abstract type Instance
|
abstract type Instance end
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Base: flush
|
|||||||
|
|
||||||
mutable struct FileInstance <: Instance
|
mutable struct FileInstance <: Instance
|
||||||
py::Union{Nothing,PyCall.PyObject}
|
py::Union{Nothing,PyCall.PyObject}
|
||||||
loaded::Union{Nothing, JuMPInstance}
|
loaded::Union{Nothing,JuMPInstance}
|
||||||
filename::AbstractString
|
filename::AbstractString
|
||||||
h5::PyCall.PyObject
|
h5::PyCall.PyObject
|
||||||
|
|
||||||
@@ -21,9 +21,14 @@ end
|
|||||||
|
|
||||||
to_model(instance::FileInstance) = to_model(instance.loaded)
|
to_model(instance::FileInstance) = to_model(instance.loaded)
|
||||||
get_instance_features(instance::FileInstance) = get_instance_features(instance.loaded)
|
get_instance_features(instance::FileInstance) = get_instance_features(instance.loaded)
|
||||||
get_variable_features(instance::FileInstance) = get_variable_features(instance.loaded)
|
get_variable_features(instance::FileInstance, names) =
|
||||||
get_variable_categories(instance::FileInstance) = get_variable_categories(instance.loaded)
|
get_variable_features(instance.loaded, names)
|
||||||
get_constraint_features(instance::FileInstance) = get_constraint_features(instance.loaded)
|
get_variable_categories(instance::FileInstance, names) =
|
||||||
|
get_variable_categories(instance.loaded, names)
|
||||||
|
get_constraint_features(instance::FileInstance, names) =
|
||||||
|
get_constraint_features(instance.loaded, names)
|
||||||
|
get_constraint_categories(instance::FileInstance, names) =
|
||||||
|
get_constraint_categories(instance.loaded, names)
|
||||||
|
|
||||||
function get_samples(instance::FileInstance)
|
function get_samples(instance::FileInstance)
|
||||||
return [instance.h5]
|
return [instance.h5]
|
||||||
@@ -33,10 +38,6 @@ function create_sample!(instance::FileInstance)
|
|||||||
return instance.h5
|
return instance.h5
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_constraint_categories(instance::FileInstance)
|
|
||||||
return get_constraint_categories(instance.loaded)
|
|
||||||
end
|
|
||||||
|
|
||||||
function load(instance::FileInstance)
|
function load(instance::FileInstance)
|
||||||
if instance.loaded === nothing
|
if instance.loaded === nothing
|
||||||
instance.loaded = load_instance(instance.filename)
|
instance.loaded = load_instance(instance.filename)
|
||||||
@@ -49,8 +50,7 @@ function free(instance::FileInstance)
|
|||||||
GC.gc()
|
GC.gc()
|
||||||
end
|
end
|
||||||
|
|
||||||
function flush(instance::FileInstance)
|
function flush(instance::FileInstance) end
|
||||||
end
|
|
||||||
|
|
||||||
function __init_PyFileInstance__()
|
function __init_PyFileInstance__()
|
||||||
@pydef mutable struct Class <: miplearn.Instance
|
@pydef mutable struct Class <: miplearn.Instance
|
||||||
@@ -59,10 +59,14 @@ function __init_PyFileInstance__()
|
|||||||
end
|
end
|
||||||
to_model(self) = to_model(self.jl)
|
to_model(self) = to_model(self.jl)
|
||||||
get_instance_features(self) = get_instance_features(self.jl)
|
get_instance_features(self) = get_instance_features(self.jl)
|
||||||
get_variable_features(self) = get_variable_features(self.jl)
|
get_variable_features(self, names) =
|
||||||
get_variable_categories(self) = get_variable_categories(self.jl)
|
get_variable_features(self.jl, from_str_array(names))
|
||||||
get_constraint_features(self) = get_constraint_features(self.jl)
|
get_variable_categories(self, names) =
|
||||||
get_constraint_categories(self) = get_constraint_categories(self.jl)
|
to_str_array(get_variable_categories(self.jl, from_str_array(names)))
|
||||||
|
get_constraint_features(self, names) =
|
||||||
|
get_constraint_features(self.jl, from_str_array(names))
|
||||||
|
get_constraint_categories(self, names) =
|
||||||
|
to_str_array(get_constraint_categories(self.jl, from_str_array(names)))
|
||||||
get_samples(self) = get_samples(self.jl)
|
get_samples(self) = get_samples(self.jl)
|
||||||
create_sample(self) = create_sample!(self.jl)
|
create_sample(self) = create_sample!(self.jl)
|
||||||
load(self) = load(self.jl)
|
load(self) = load(self.jl)
|
||||||
|
|||||||
@@ -38,11 +38,44 @@ function to_model(instance::JuMPInstance)::JuMP.Model
|
|||||||
return instance.model
|
return instance.model
|
||||||
end
|
end
|
||||||
|
|
||||||
get_instance_features(instance::JuMPInstance) = instance.ext["instance_features"]
|
function get_instance_features(instance::JuMPInstance)::Union{Vector{Float64},Nothing}
|
||||||
get_variable_features(instance::JuMPInstance) = instance.ext["variable_features"]
|
return instance.ext["instance_features"]
|
||||||
get_variable_categories(instance::JuMPInstance) = instance.ext["variable_categories"]
|
end
|
||||||
get_constraint_features(instance::JuMPInstance) = instance.ext["constraint_features"]
|
|
||||||
get_constraint_categories(instance::JuMPInstance) = instance.ext["constraint_categories"]
|
function _concat_features(dict, names)::Matrix{Float64}
|
||||||
|
if isempty(dict)
|
||||||
|
return zeros(length(names), 1)
|
||||||
|
end
|
||||||
|
ncols = length(first(dict).second)
|
||||||
|
return vcat([n in keys(dict) ? dict[n]' : zeros(ncols) for n in names]...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _concat_categories(dict, names)::Vector{String}
|
||||||
|
return String[n in keys(dict) ? dict[n] : n for n in names]
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_variable_features(
|
||||||
|
instance::JuMPInstance,
|
||||||
|
names::Vector{String},
|
||||||
|
)::Matrix{Float64}
|
||||||
|
return _concat_features(instance.ext["variable_features"], names)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_variable_categories(instance::JuMPInstance, names::Vector{String})
|
||||||
|
return _concat_categories(instance.ext["variable_categories"], names)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_constraint_features(
|
||||||
|
instance::JuMPInstance,
|
||||||
|
names::Vector{String},
|
||||||
|
)::Matrix{Float64}
|
||||||
|
return _concat_features(instance.ext["constraint_features"], names)
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_constraint_categories(instance::JuMPInstance, names::Vector{String})
|
||||||
|
return _concat_categories(instance.ext["constraint_categories"], names)
|
||||||
|
end
|
||||||
|
|
||||||
get_samples(instance::JuMPInstance) = instance.samples
|
get_samples(instance::JuMPInstance) = instance.samples
|
||||||
|
|
||||||
function create_sample!(instance::JuMPInstance)
|
function create_sample!(instance::JuMPInstance)
|
||||||
@@ -58,10 +91,14 @@ function __init_PyJuMPInstance__()
|
|||||||
end
|
end
|
||||||
to_model(self) = to_model(self.jl)
|
to_model(self) = to_model(self.jl)
|
||||||
get_instance_features(self) = get_instance_features(self.jl)
|
get_instance_features(self) = get_instance_features(self.jl)
|
||||||
get_variable_features(self) = get_variable_features(self.jl)
|
get_variable_features(self, names) =
|
||||||
get_variable_categories(self) = get_variable_categories(self.jl)
|
get_variable_features(self.jl, from_str_array(names))
|
||||||
get_constraint_features(self,) = get_constraint_features(self.jl)
|
get_variable_categories(self, names) =
|
||||||
get_constraint_categories(self) = get_constraint_categories(self.jl)
|
to_str_array(get_variable_categories(self.jl, from_str_array(names)))
|
||||||
|
get_constraint_features(self, names) =
|
||||||
|
get_constraint_features(self.jl, from_str_array(names))
|
||||||
|
get_constraint_categories(self, names) =
|
||||||
|
to_str_array(get_constraint_categories(self.jl, from_str_array(names)))
|
||||||
get_samples(self) = get_samples(self.jl)
|
get_samples(self) = get_samples(self.jl)
|
||||||
create_sample(self) = create_sample!(self.jl)
|
create_sample(self) = create_sample!(self.jl)
|
||||||
end
|
end
|
||||||
@@ -76,7 +113,7 @@ function save(filename::AbstractString, instance::JuMPInstance)::Nothing
|
|||||||
mps = read(mps_filename)
|
mps = read(mps_filename)
|
||||||
|
|
||||||
# Generate HDF5
|
# Generate HDF5
|
||||||
h5 = Hdf5Sample(filename, mode="w")
|
h5 = Hdf5Sample(filename, mode = "w")
|
||||||
h5.put_scalar("miplearn_version", "0002")
|
h5.put_scalar("miplearn_version", "0002")
|
||||||
h5.put_bytes("mps", mps)
|
h5.put_bytes("mps", mps)
|
||||||
h5.put_scalar("jump_ext", JSON.json(model.ext[:miplearn]))
|
h5.put_scalar("jump_ext", JSON.json(model.ext[:miplearn]))
|
||||||
@@ -89,7 +126,7 @@ function _check_miplearn_version(h5)
|
|||||||
"The file you are trying to load has been generated by " *
|
"The file you are trying to load has been generated by " *
|
||||||
"MIPLearn $(v) and you are currently running MIPLearn 0002 " *
|
"MIPLearn $(v) and you are currently running MIPLearn 0002 " *
|
||||||
"Reading files generated by different versions of MIPLearn is " *
|
"Reading files generated by different versions of MIPLearn is " *
|
||||||
"not currently supported."
|
"not currently supported.",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ using MathOptInterface
|
|||||||
using TimerOutputs
|
using TimerOutputs
|
||||||
const MOI = MathOptInterface
|
const MOI = MathOptInterface
|
||||||
|
|
||||||
|
|
||||||
mutable struct JuMPSolverData
|
mutable struct JuMPSolverData
|
||||||
optimizer_factory
|
optimizer_factory::Any
|
||||||
varname_to_var::Dict{String,VariableRef}
|
varname_to_var::Dict{String,VariableRef}
|
||||||
cname_to_constr::Dict{String,JuMP.ConstraintRef}
|
cname_to_constr::Dict{String,JuMP.ConstraintRef}
|
||||||
instance::Union{Nothing,PyObject}
|
instance::Union{Nothing,PyObject}
|
||||||
@@ -29,10 +28,10 @@ end
|
|||||||
Optimizes a given JuMP model while capturing the solver log, then returns that log.
|
Optimizes a given JuMP model while capturing the solver log, then returns that log.
|
||||||
If tee=true, prints the solver log to the standard output as the optimization takes place.
|
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)
|
function _optimize_and_capture_output!(model; tee::Bool = false)
|
||||||
logname = tempname()
|
logname = tempname()
|
||||||
logfile = open(logname, "w")
|
logfile = open(logname, "w")
|
||||||
redirect_stdout(logfile) do
|
redirect_stdout(logfile) do
|
||||||
JuMP.optimize!(model)
|
JuMP.optimize!(model)
|
||||||
Base.Libc.flush_cstdio()
|
Base.Libc.flush_cstdio()
|
||||||
end
|
end
|
||||||
@@ -50,10 +49,7 @@ end
|
|||||||
|
|
||||||
function _update_solution!(data::JuMPSolverData)
|
function _update_solution!(data::JuMPSolverData)
|
||||||
vars = JuMP.all_variables(data.model)
|
vars = JuMP.all_variables(data.model)
|
||||||
data.solution = Dict(
|
data.solution = Dict(var => JuMP.value(var) for var in vars)
|
||||||
var => JuMP.value(var)
|
|
||||||
for var in vars
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reduced costs
|
# Reduced costs
|
||||||
if has_duals(data.model)
|
if has_duals(data.model)
|
||||||
@@ -89,7 +85,7 @@ end
|
|||||||
|
|
||||||
function add_constraints(
|
function add_constraints(
|
||||||
data::JuMPSolverData;
|
data::JuMPSolverData;
|
||||||
lhs::Vector{Vector{Tuple{String, Float64}}},
|
lhs::Vector{Vector{Tuple{String,Float64}}},
|
||||||
rhs::Vector{Float64},
|
rhs::Vector{Float64},
|
||||||
senses::Vector{String},
|
senses::Vector{String},
|
||||||
names::Vector{String},
|
names::Vector{String},
|
||||||
@@ -104,8 +100,10 @@ function add_constraints(
|
|||||||
constr = @constraint(data.model, lhs_expr <= rhs[i])
|
constr = @constraint(data.model, lhs_expr <= rhs[i])
|
||||||
elseif sense == ">"
|
elseif sense == ">"
|
||||||
constr = @constraint(data.model, lhs_expr >= rhs[i])
|
constr = @constraint(data.model, lhs_expr >= rhs[i])
|
||||||
else
|
elseif sense == "="
|
||||||
constr = @constraint(data.model, lhs_expr == rhs[i])
|
constr = @constraint(data.model, lhs_expr == rhs[i])
|
||||||
|
else
|
||||||
|
error("unknown sense: $(sense)")
|
||||||
end
|
end
|
||||||
set_name(constr, names[i])
|
set_name(constr, names[i])
|
||||||
data.cname_to_constr[names[i]] = constr
|
data.cname_to_constr[names[i]] = constr
|
||||||
@@ -116,10 +114,10 @@ end
|
|||||||
|
|
||||||
function are_constraints_satisfied(
|
function are_constraints_satisfied(
|
||||||
data::JuMPSolverData;
|
data::JuMPSolverData;
|
||||||
lhs::Vector{Vector{Tuple{String, Float64}}},
|
lhs::Vector{Vector{Tuple{String,Float64}}},
|
||||||
rhs::Vector{Float64},
|
rhs::Vector{Float64},
|
||||||
senses::Vector{String},
|
senses::Vector{String},
|
||||||
tol::Float64=1e-5,
|
tol::Float64 = 1e-5,
|
||||||
)::Vector{Bool}
|
)::Vector{Bool}
|
||||||
result = []
|
result = []
|
||||||
for (i, sense) in enumerate(senses)
|
for (i, sense) in enumerate(senses)
|
||||||
@@ -132,8 +130,10 @@ function are_constraints_satisfied(
|
|||||||
push!(result, lhs_value <= rhs[i] + tol)
|
push!(result, lhs_value <= rhs[i] + tol)
|
||||||
elseif sense == ">"
|
elseif sense == ">"
|
||||||
push!(result, lhs_value >= rhs[i] - tol)
|
push!(result, lhs_value >= rhs[i] - tol)
|
||||||
else
|
elseif sense == "="
|
||||||
push!(result, abs(lhs_value - rhs[i]) <= tol)
|
push!(result, abs(lhs_value - rhs[i]) <= tol)
|
||||||
|
else
|
||||||
|
error("unknown sense: $(sense)")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
@@ -148,9 +148,9 @@ function build_test_instance_knapsack()
|
|||||||
model = Model()
|
model = Model()
|
||||||
n = length(weights)
|
n = length(weights)
|
||||||
@variable(model, x[0:n-1], Bin)
|
@variable(model, x[0:n-1], Bin)
|
||||||
@variable(model, z, lower_bound=0.0, upper_bound=capacity)
|
@variable(model, z, lower_bound = 0.0, upper_bound = capacity)
|
||||||
@objective(model, Max, sum(x[i-1] * prices[i] for i in 1:n))
|
@objective(model, Max, sum(x[i-1] * prices[i] for i = 1:n))
|
||||||
@constraint(model, eq_capacity, sum(x[i-1] * weights[i] for i in 1:n) - z == 0)
|
@constraint(model, eq_capacity, sum(x[i-1] * weights[i] for i = 1:n) - z == 0)
|
||||||
|
|
||||||
return JuMPInstance(model).py
|
return JuMPInstance(model).py
|
||||||
end
|
end
|
||||||
@@ -165,10 +165,7 @@ function build_test_instance_infeasible()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function remove_constraints(
|
function remove_constraints(data::JuMPSolverData, names::Vector{String})::Nothing
|
||||||
data::JuMPSolverData,
|
|
||||||
names::Vector{String},
|
|
||||||
)::Nothing
|
|
||||||
for name in names
|
for name in names
|
||||||
constr = data.cname_to_constr[name]
|
constr = data.cname_to_constr[name]
|
||||||
delete(data.model, constr)
|
delete(data.model, constr)
|
||||||
@@ -178,17 +175,13 @@ function remove_constraints(
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function solve(
|
function solve(data::JuMPSolverData; tee::Bool = false, iteration_cb = nothing)
|
||||||
data::JuMPSolverData;
|
|
||||||
tee::Bool=false,
|
|
||||||
iteration_cb=nothing,
|
|
||||||
)
|
|
||||||
model = data.model
|
model = data.model
|
||||||
wallclock_time = 0
|
wallclock_time = 0
|
||||||
log = ""
|
log = ""
|
||||||
while true
|
while true
|
||||||
wallclock_time += @elapsed begin
|
wallclock_time += @elapsed begin
|
||||||
log *= _optimize_and_capture_output!(model, tee=tee)
|
log *= _optimize_and_capture_output!(model, tee = tee)
|
||||||
end
|
end
|
||||||
if iteration_cb !== nothing
|
if iteration_cb !== nothing
|
||||||
iteration_cb() || break
|
iteration_cb() || break
|
||||||
@@ -215,18 +208,18 @@ function solve(
|
|||||||
upper_bound = dual_bound
|
upper_bound = dual_bound
|
||||||
end
|
end
|
||||||
return miplearn.solvers.internal.MIPSolveStats(
|
return miplearn.solvers.internal.MIPSolveStats(
|
||||||
mip_lower_bound=lower_bound,
|
mip_lower_bound = lower_bound,
|
||||||
mip_upper_bound=upper_bound,
|
mip_upper_bound = upper_bound,
|
||||||
mip_sense=sense,
|
mip_sense = sense,
|
||||||
mip_wallclock_time=wallclock_time,
|
mip_wallclock_time = wallclock_time,
|
||||||
mip_nodes=1,
|
mip_nodes = 1,
|
||||||
mip_log=log,
|
mip_log = log,
|
||||||
mip_warm_start_value=nothing,
|
mip_warm_start_value = nothing,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function solve_lp(data::JuMPSolverData; tee::Bool=false)
|
function solve_lp(data::JuMPSolverData; tee::Bool = false)
|
||||||
model, bin_vars = data.model, data.bin_vars
|
model, bin_vars = data.model, data.bin_vars
|
||||||
for var in bin_vars
|
for var in bin_vars
|
||||||
~is_fixed(var) || continue
|
~is_fixed(var) || continue
|
||||||
@@ -242,7 +235,7 @@ function solve_lp(data::JuMPSolverData; tee::Bool=false)
|
|||||||
set_optimizer(model, Clp.Optimizer)
|
set_optimizer(model, Clp.Optimizer)
|
||||||
end
|
end
|
||||||
wallclock_time = @elapsed begin
|
wallclock_time = @elapsed begin
|
||||||
log = _optimize_and_capture_output!(model, tee=tee)
|
log = _optimize_and_capture_output!(model, tee = tee)
|
||||||
end
|
end
|
||||||
if is_infeasible(data)
|
if is_infeasible(data)
|
||||||
data.solution = Dict()
|
data.solution = Dict()
|
||||||
@@ -259,9 +252,9 @@ function solve_lp(data::JuMPSolverData; tee::Bool=false)
|
|||||||
set_binary(var)
|
set_binary(var)
|
||||||
end
|
end
|
||||||
return miplearn.solvers.internal.LPSolveStats(
|
return miplearn.solvers.internal.LPSolveStats(
|
||||||
lp_value=obj_value,
|
lp_value = obj_value,
|
||||||
lp_log=log,
|
lp_log = log,
|
||||||
lp_wallclock_time=wallclock_time,
|
lp_wallclock_time = wallclock_time,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -276,15 +269,8 @@ function set_instance!(
|
|||||||
model = instance.to_model()
|
model = instance.to_model()
|
||||||
end
|
end
|
||||||
data.model = model
|
data.model = model
|
||||||
data.bin_vars = [
|
data.bin_vars = [var for var in JuMP.all_variables(model) if JuMP.is_binary(var)]
|
||||||
var
|
data.varname_to_var = Dict(JuMP.name(var) => var for var in JuMP.all_variables(model))
|
||||||
for var in JuMP.all_variables(model)
|
|
||||||
if JuMP.is_binary(var)
|
|
||||||
]
|
|
||||||
data.varname_to_var = Dict(
|
|
||||||
JuMP.name(var) => var
|
|
||||||
for var in JuMP.all_variables(model)
|
|
||||||
)
|
|
||||||
JuMP.set_optimizer(model, data.optimizer_factory)
|
JuMP.set_optimizer(model, data.optimizer_factory)
|
||||||
data.cname_to_constr = Dict()
|
data.cname_to_constr = Dict()
|
||||||
for (ftype, stype) in JuMP.list_of_constraint_types(model)
|
for (ftype, stype) in JuMP.list_of_constraint_types(model)
|
||||||
@@ -302,7 +288,7 @@ function fix!(data::JuMPSolverData, solution)
|
|||||||
for (varname, value) in solution
|
for (varname, value) in solution
|
||||||
value !== nothing || continue
|
value !== nothing || continue
|
||||||
var = data.varname_to_var[varname]
|
var = data.varname_to_var[varname]
|
||||||
JuMP.fix(var, value, force=true)
|
JuMP.fix(var, value, force = true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -317,17 +303,12 @@ end
|
|||||||
|
|
||||||
|
|
||||||
function is_infeasible(data::JuMPSolverData)
|
function is_infeasible(data::JuMPSolverData)
|
||||||
return JuMP.termination_status(data.model) in [
|
return JuMP.termination_status(data.model) in
|
||||||
MOI.INFEASIBLE,
|
[MOI.INFEASIBLE, MOI.INFEASIBLE_OR_UNBOUNDED]
|
||||||
MOI.INFEASIBLE_OR_UNBOUNDED,
|
|
||||||
]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function get_variables(
|
function get_variables(data::JuMPSolverData; with_static::Bool)
|
||||||
data::JuMPSolverData;
|
|
||||||
with_static::Bool,
|
|
||||||
)
|
|
||||||
vars = JuMP.all_variables(data.model)
|
vars = JuMP.all_variables(data.model)
|
||||||
lb, ub, types, obj_coeffs = nothing, nothing, nothing, nothing
|
lb, ub, types, obj_coeffs = nothing, nothing, nothing, nothing
|
||||||
values, rc = nothing, nothing
|
values, rc = nothing, nothing
|
||||||
@@ -343,46 +324,32 @@ function get_variables(
|
|||||||
if with_static
|
if with_static
|
||||||
# Lower bounds
|
# Lower bounds
|
||||||
lb = [
|
lb = [
|
||||||
JuMP.is_binary(v) ? 0.0 :
|
JuMP.is_binary(v) ? 0.0 : JuMP.has_lower_bound(v) ? JuMP.lower_bound(v) : -Inf for v in vars
|
||||||
JuMP.has_lower_bound(v) ? JuMP.lower_bound(v) :
|
|
||||||
-Inf
|
|
||||||
for v in vars
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Upper bounds
|
# Upper bounds
|
||||||
ub = [
|
ub = [
|
||||||
JuMP.is_binary(v) ? 1.0 :
|
JuMP.is_binary(v) ? 1.0 : JuMP.has_upper_bound(v) ? JuMP.upper_bound(v) : Inf for v in vars
|
||||||
JuMP.has_upper_bound(v) ? JuMP.upper_bound(v) :
|
|
||||||
Inf
|
|
||||||
for v in vars
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Variable types
|
# Variable types
|
||||||
types = [
|
types = [JuMP.is_binary(v) ? "B" : JuMP.is_integer(v) ? "I" : "C" for v in vars]
|
||||||
JuMP.is_binary(v) ? "B" :
|
|
||||||
JuMP.is_integer(v) ? "I" :
|
|
||||||
"C"
|
|
||||||
for v in vars
|
|
||||||
]
|
|
||||||
|
|
||||||
# Objective function coefficients
|
# Objective function coefficients
|
||||||
obj = objective_function(data.model)
|
obj = objective_function(data.model)
|
||||||
obj_coeffs = [
|
obj_coeffs = [v ∈ keys(obj.terms) ? obj.terms[v] : 0.0 for v in vars]
|
||||||
v ∈ keys(obj.terms) ? obj.terms[v] : 0.0
|
|
||||||
for v in vars
|
|
||||||
]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
rc = isempty(data.reduced_costs) ? nothing : data.reduced_costs
|
rc = isempty(data.reduced_costs) ? nothing : data.reduced_costs
|
||||||
|
|
||||||
vf = miplearn.solvers.internal.Variables(
|
vf = miplearn.solvers.internal.Variables(
|
||||||
names=names,
|
names = to_str_array(names),
|
||||||
lower_bounds=lb,
|
lower_bounds = lb,
|
||||||
upper_bounds=ub,
|
upper_bounds = ub,
|
||||||
types=types,
|
types = to_str_array(types),
|
||||||
obj_coeffs=obj_coeffs,
|
obj_coeffs = obj_coeffs,
|
||||||
reduced_costs=rc,
|
reduced_costs = rc,
|
||||||
values=values,
|
values = values,
|
||||||
)
|
)
|
||||||
return vf
|
return vf
|
||||||
end
|
end
|
||||||
@@ -394,26 +361,23 @@ function get_constraints(
|
|||||||
with_sa::Bool,
|
with_sa::Bool,
|
||||||
with_lhs::Bool,
|
with_lhs::Bool,
|
||||||
)
|
)
|
||||||
names = []
|
names = String[]
|
||||||
senses, lhs, rhs = nothing, nothing, nothing
|
senses, lhs, rhs = nothing, nothing, nothing
|
||||||
dual_values = nothing
|
dual_values = nothing
|
||||||
|
|
||||||
if !isempty(data.dual_values)
|
if !isempty(data.dual_values)
|
||||||
dual_values = []
|
dual_values = Float64[]
|
||||||
end
|
end
|
||||||
|
|
||||||
if with_static
|
if with_static
|
||||||
senses, lhs, rhs = [], [], []
|
senses, lhs, rhs = String[], [], Float64[]
|
||||||
end
|
end
|
||||||
|
|
||||||
for (ftype, stype) in JuMP.list_of_constraint_types(data.model)
|
for (ftype, stype) in JuMP.list_of_constraint_types(data.model)
|
||||||
ftype in [JuMP.AffExpr, JuMP.VariableRef] || error("Unsupported constraint type: ($ftype, $stype)")
|
ftype in [JuMP.AffExpr, JuMP.VariableRef] ||
|
||||||
|
error("Unsupported constraint type: ($ftype, $stype)")
|
||||||
for constr in JuMP.all_constraints(data.model, ftype, stype)
|
for constr in JuMP.all_constraints(data.model, ftype, stype)
|
||||||
cset = MOI.get(
|
cset = MOI.get(constr.model.moi_backend, MOI.ConstraintSet(), constr.index)
|
||||||
constr.model.moi_backend,
|
|
||||||
MOI.ConstraintSet(),
|
|
||||||
constr.index,
|
|
||||||
)
|
|
||||||
name = JuMP.name(constr)
|
name = JuMP.name(constr)
|
||||||
length(name) > 0 || continue
|
length(name) > 0 || continue
|
||||||
push!(names, name)
|
push!(names, name)
|
||||||
@@ -429,19 +393,21 @@ function get_constraints(
|
|||||||
lhs,
|
lhs,
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
MOI.get(
|
pybytes(
|
||||||
constr.model.moi_backend,
|
MOI.get(
|
||||||
MOI.VariableName(),
|
constr.model.moi_backend,
|
||||||
term.variable_index
|
MOI.VariableName(),
|
||||||
|
term.variable_index,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
term.coefficient,
|
term.coefficient,
|
||||||
)
|
) for term in
|
||||||
for term in MOI.get(
|
MOI.get(
|
||||||
constr.model.moi_backend,
|
constr.model.moi_backend,
|
||||||
MOI.ConstraintFunction(),
|
MOI.ConstraintFunction(),
|
||||||
constr.index,
|
constr.index,
|
||||||
).terms
|
).terms
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
if stype == MOI.EqualTo{Float64}
|
if stype == MOI.EqualTo{Float64}
|
||||||
@@ -464,11 +430,11 @@ function get_constraints(
|
|||||||
end
|
end
|
||||||
|
|
||||||
return miplearn.solvers.internal.Constraints(
|
return miplearn.solvers.internal.Constraints(
|
||||||
names=names,
|
names = to_str_array(names),
|
||||||
senses=senses,
|
senses = to_str_array(senses),
|
||||||
lhs=lhs,
|
lhs = lhs,
|
||||||
rhs=rhs,
|
rhs = rhs,
|
||||||
dual_values=dual_values,
|
dual_values = dual_values,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -497,14 +463,14 @@ function __init_JuMPSolver__()
|
|||||||
end
|
end
|
||||||
add_constraints(
|
add_constraints(
|
||||||
self.data,
|
self.data,
|
||||||
lhs=lhs,
|
lhs = lhs,
|
||||||
rhs=cf.rhs,
|
rhs = cf.rhs,
|
||||||
senses=cf.senses,
|
senses = from_str_array(cf.senses),
|
||||||
names=cf.names,
|
names = from_str_array(cf.names),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function are_constraints_satisfied(self, cf; tol=1e-5)
|
function are_constraints_satisfied(self, cf; tol = 1e-5)
|
||||||
lhs = cf.lhs
|
lhs = cf.lhs
|
||||||
if lhs isa Matrix
|
if lhs isa Matrix
|
||||||
# Undo incorrect automatic conversion performed by PyCall
|
# Undo incorrect automatic conversion performed by PyCall
|
||||||
@@ -512,38 +478,30 @@ function __init_JuMPSolver__()
|
|||||||
end
|
end
|
||||||
return are_constraints_satisfied(
|
return are_constraints_satisfied(
|
||||||
self.data,
|
self.data,
|
||||||
lhs=lhs,
|
lhs = lhs,
|
||||||
rhs=cf.rhs,
|
rhs = cf.rhs,
|
||||||
senses=cf.senses,
|
senses = from_str_array(cf.senses),
|
||||||
tol=tol,
|
tol = tol,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
build_test_instance_infeasible(self) =
|
build_test_instance_infeasible(self) = build_test_instance_infeasible()
|
||||||
build_test_instance_infeasible()
|
|
||||||
|
build_test_instance_knapsack(self) = build_test_instance_knapsack()
|
||||||
|
|
||||||
build_test_instance_knapsack(self) =
|
|
||||||
build_test_instance_knapsack()
|
|
||||||
|
|
||||||
clone(self) = JuMPSolver(self.data.optimizer_factory)
|
clone(self) = JuMPSolver(self.data.optimizer_factory)
|
||||||
|
|
||||||
fix(self, solution) =
|
fix(self, solution) = fix!(self.data, solution)
|
||||||
fix!(self.data, solution)
|
|
||||||
|
|
||||||
get_solution(self) =
|
|
||||||
isempty(self.data.solution) ? nothing : self.data.solution
|
|
||||||
|
|
||||||
get_constraints(
|
get_solution(self) = isempty(self.data.solution) ? nothing : self.data.solution
|
||||||
self;
|
|
||||||
with_static=true,
|
get_constraints(self; with_static = true, with_sa = true, with_lhs = true) =
|
||||||
with_sa=true,
|
get_constraints(
|
||||||
with_lhs=true,
|
self.data,
|
||||||
) = get_constraints(
|
with_static = with_static,
|
||||||
self.data,
|
with_sa = with_sa,
|
||||||
with_static=with_static,
|
with_lhs = with_lhs,
|
||||||
with_sa=with_sa,
|
)
|
||||||
with_lhs=with_lhs,
|
|
||||||
)
|
|
||||||
|
|
||||||
get_constraint_attrs(self) = [
|
get_constraint_attrs(self) = [
|
||||||
# "basis_status",
|
# "basis_status",
|
||||||
@@ -559,14 +517,11 @@ function __init_JuMPSolver__()
|
|||||||
# "slacks",
|
# "slacks",
|
||||||
"user_features",
|
"user_features",
|
||||||
]
|
]
|
||||||
|
|
||||||
get_variables(
|
get_variables(self; with_static = true, with_sa = true) =
|
||||||
self;
|
get_variables(self.data; with_static = with_static)
|
||||||
with_static=true,
|
|
||||||
with_sa=true,
|
get_variable_attrs(self) = [
|
||||||
) = get_variables(self.data; with_static=with_static)
|
|
||||||
|
|
||||||
get_variable_attrs(self) = [
|
|
||||||
"names",
|
"names",
|
||||||
# "basis_status",
|
# "basis_status",
|
||||||
"categories",
|
"categories",
|
||||||
@@ -585,35 +540,24 @@ function __init_JuMPSolver__()
|
|||||||
"values",
|
"values",
|
||||||
]
|
]
|
||||||
|
|
||||||
is_infeasible(self) =
|
is_infeasible(self) = is_infeasible(self.data)
|
||||||
is_infeasible(self.data)
|
|
||||||
|
|
||||||
remove_constraints(self, names) =
|
remove_constraints(self, names) = remove_constraints(self.data, [n for n in names])
|
||||||
remove_constraints(
|
|
||||||
self.data,
|
|
||||||
[n for n in names],
|
|
||||||
)
|
|
||||||
|
|
||||||
set_instance(self, instance, model=nothing) =
|
set_instance(self, instance, model = nothing) =
|
||||||
set_instance!(self.data, instance, model=model)
|
set_instance!(self.data, instance, model = model)
|
||||||
|
|
||||||
set_warm_start(self, solution) =
|
set_warm_start(self, solution) = set_warm_start!(self.data, solution)
|
||||||
set_warm_start!(self.data, solution)
|
|
||||||
|
|
||||||
solve(
|
solve(
|
||||||
self;
|
self;
|
||||||
tee=false,
|
tee = false,
|
||||||
iteration_cb=nothing,
|
iteration_cb = nothing,
|
||||||
lazy_cb=nothing,
|
lazy_cb = nothing,
|
||||||
user_cut_cb=nothing,
|
user_cut_cb = nothing,
|
||||||
) = solve(
|
) = solve(self.data, tee = tee, iteration_cb = iteration_cb)
|
||||||
self.data,
|
|
||||||
tee=tee,
|
solve_lp(self; tee = false) = solve_lp(self.data, tee = tee)
|
||||||
iteration_cb=iteration_cb,
|
|
||||||
)
|
|
||||||
|
|
||||||
solve_lp(self; tee=false) =
|
|
||||||
solve_lp(self.data, tee=tee)
|
|
||||||
end
|
end
|
||||||
copy!(JuMPSolver, Class)
|
copy!(JuMPSolver, Class)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using JLD2
|
|||||||
|
|
||||||
struct LearningSolver
|
struct LearningSolver
|
||||||
py::PyCall.PyObject
|
py::PyCall.PyObject
|
||||||
optimizer_factory
|
optimizer_factory::Any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -23,13 +23,13 @@ function LearningSolver(
|
|||||||
)::LearningSolver
|
)::LearningSolver
|
||||||
return LearningSolver(
|
return LearningSolver(
|
||||||
miplearn.LearningSolver(
|
miplearn.LearningSolver(
|
||||||
solver=JuMPSolver(optimizer_factory),
|
solver = JuMPSolver(optimizer_factory),
|
||||||
mode=mode,
|
mode = mode,
|
||||||
solve_lp=solve_lp,
|
solve_lp = solve_lp,
|
||||||
simulate_perfect=simulate_perfect,
|
simulate_perfect = simulate_perfect,
|
||||||
components=components,
|
components = components,
|
||||||
extract_lhs=extract_lhs,
|
extract_lhs = extract_lhs,
|
||||||
extract_sa=extract_sa,
|
extract_sa = extract_sa,
|
||||||
),
|
),
|
||||||
optimizer_factory,
|
optimizer_factory,
|
||||||
)
|
)
|
||||||
@@ -44,8 +44,8 @@ function solve!(
|
|||||||
)
|
)
|
||||||
return @python_call solver.py.solve(
|
return @python_call solver.py.solve(
|
||||||
instance.py,
|
instance.py,
|
||||||
tee=tee,
|
tee = tee,
|
||||||
discard_output=discard_output,
|
discard_output = discard_output,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,19 +56,11 @@ function fit!(solver::LearningSolver, instances::Vector{<:Instance})
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function _solve(
|
function _solve(solver_filename, instance_filename; discard_output::Bool)
|
||||||
solver_filename,
|
|
||||||
instance_filename;
|
|
||||||
discard_output::Bool,
|
|
||||||
)
|
|
||||||
@info "solve $instance_filename"
|
@info "solve $instance_filename"
|
||||||
solver = load_solver(solver_filename)
|
solver = load_solver(solver_filename)
|
||||||
solver.py._silence_miplearn_logger()
|
solver.py._silence_miplearn_logger()
|
||||||
stats = solve!(
|
stats = solve!(solver, FileInstance(instance_filename), discard_output = discard_output)
|
||||||
solver,
|
|
||||||
FileInstance(instance_filename),
|
|
||||||
discard_output = discard_output,
|
|
||||||
)
|
|
||||||
solver.py._restore_miplearn_logger()
|
solver.py._restore_miplearn_logger()
|
||||||
GC.gc()
|
GC.gc()
|
||||||
@info "solve $instance_filename [done]"
|
@info "solve $instance_filename [done]"
|
||||||
@@ -85,13 +77,10 @@ function parallel_solve!(
|
|||||||
solver_filename = tempname()
|
solver_filename = tempname()
|
||||||
save(solver_filename, solver)
|
save(solver_filename, solver)
|
||||||
return pmap(
|
return pmap(
|
||||||
instance_filename -> _solve(
|
instance_filename ->
|
||||||
solver_filename,
|
_solve(solver_filename, instance_filename, discard_output = discard_output),
|
||||||
instance_filename,
|
|
||||||
discard_output = discard_output,
|
|
||||||
),
|
|
||||||
instance_filenames,
|
instance_filenames,
|
||||||
on_error=identity,
|
on_error = identity,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -108,9 +97,9 @@ function save(filename::AbstractString, solver::LearningSolver)
|
|||||||
solver.py.internal_solver_prototype = internal_solver_prototype
|
solver.py.internal_solver_prototype = internal_solver_prototype
|
||||||
jldsave(
|
jldsave(
|
||||||
filename;
|
filename;
|
||||||
miplearn_version="0.2",
|
miplearn_version = "0.2",
|
||||||
solver_py=solver_py,
|
solver_py = solver_py,
|
||||||
optimizer_factory=solver.optimizer_factory,
|
optimizer_factory = solver.optimizer_factory,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -123,18 +112,9 @@ function load_solver(filename::AbstractString)::LearningSolver
|
|||||||
solver_py = miplearn.read_pickle_gz(solve_py_filename)
|
solver_py = miplearn.read_pickle_gz(solve_py_filename)
|
||||||
internal_solver = JuMPSolver(file["optimizer_factory"])
|
internal_solver = JuMPSolver(file["optimizer_factory"])
|
||||||
solver_py.internal_solver_prototype = internal_solver
|
solver_py.internal_solver_prototype = internal_solver
|
||||||
return LearningSolver(
|
return LearningSolver(solver_py, file["optimizer_factory"])
|
||||||
solver_py,
|
|
||||||
file["optimizer_factory"],
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
export Instance,
|
export Instance, LearningSolver, solve!, fit!, parallel_solve!, save, load_solver
|
||||||
LearningSolver,
|
|
||||||
solve!,
|
|
||||||
fit!,
|
|
||||||
parallel_solve!,
|
|
||||||
save,
|
|
||||||
load_solver
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ function init_miplearn_ext(model)::Dict
|
|||||||
if :miplearn ∉ keys(model.ext)
|
if :miplearn ∉ keys(model.ext)
|
||||||
model.ext[:miplearn] = Dict()
|
model.ext[:miplearn] = Dict()
|
||||||
model.ext[:miplearn]["instance_features"] = [0.0]
|
model.ext[:miplearn]["instance_features"] = [0.0]
|
||||||
model.ext[:miplearn]["variable_features"] = Dict{AbstractString, Vector{Float64}}()
|
model.ext[:miplearn]["variable_features"] = Dict{AbstractString,Vector{Float64}}()
|
||||||
model.ext[:miplearn]["variable_categories"] = Dict{AbstractString, String}()
|
model.ext[:miplearn]["variable_categories"] = Dict{AbstractString,String}()
|
||||||
model.ext[:miplearn]["constraint_features"] = Dict{AbstractString, Vector{Float64}}()
|
model.ext[:miplearn]["constraint_features"] = Dict{AbstractString,Vector{Float64}}()
|
||||||
model.ext[:miplearn]["constraint_categories"] = Dict{AbstractString, String}()
|
model.ext[:miplearn]["constraint_categories"] = Dict{AbstractString,String}()
|
||||||
end
|
end
|
||||||
return model.ext[:miplearn]
|
return model.ext[:miplearn]
|
||||||
end
|
end
|
||||||
@@ -71,11 +71,10 @@ function _get_and_check_name(obj)
|
|||||||
n = name(obj)
|
n = name(obj)
|
||||||
length(n) > 0 || error(
|
length(n) > 0 || error(
|
||||||
"Features and categories can only be assigned to variables and " *
|
"Features and categories can only be assigned to variables and " *
|
||||||
"constraints that have names. Unnamed model element detected."
|
"constraints that have names. Unnamed model element detected.",
|
||||||
)
|
)
|
||||||
return n
|
return n
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
export @feature,
|
export @feature, @category
|
||||||
@category
|
|
||||||
|
|||||||
@@ -16,11 +16,8 @@ mutable struct BenchmarkRunner
|
|||||||
solvers,
|
solvers,
|
||||||
nothing, # results
|
nothing, # results
|
||||||
miplearn.BenchmarkRunner(
|
miplearn.BenchmarkRunner(
|
||||||
Dict(
|
Dict(sname => solver.py for (sname, solver) in solvers),
|
||||||
sname => solver.py
|
),
|
||||||
for (sname, solver) in solvers
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -33,7 +30,7 @@ function parallel_solve!(
|
|||||||
instances = repeat(instances, n_trials)
|
instances = repeat(instances, n_trials)
|
||||||
for (solver_name, solver) in runner.solvers
|
for (solver_name, solver) in runner.solvers
|
||||||
@info "benchmark $solver_name"
|
@info "benchmark $solver_name"
|
||||||
stats = parallel_solve!(solver, instances, discard_output=true)
|
stats = parallel_solve!(solver, instances, discard_output = true)
|
||||||
for (i, s) in enumerate(stats)
|
for (i, s) in enumerate(stats)
|
||||||
s["Solver"] = solver_name
|
s["Solver"] = solver_name
|
||||||
s["Instance"] = instances[i].filename
|
s["Instance"] = instances[i].filename
|
||||||
@@ -41,29 +38,20 @@ function parallel_solve!(
|
|||||||
if runner.results === nothing
|
if runner.results === nothing
|
||||||
runner.results = DataFrame(s)
|
runner.results = DataFrame(s)
|
||||||
else
|
else
|
||||||
push!(runner.results, s, cols=:union)
|
push!(runner.results, s, cols = :union)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@info "benchmark $solver_name [done]"
|
@info "benchmark $solver_name [done]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function fit!(
|
function fit!(runner::BenchmarkRunner, instances::Vector{FileInstance})::Nothing
|
||||||
runner::BenchmarkRunner,
|
|
||||||
instances::Vector{FileInstance}
|
|
||||||
)::Nothing
|
|
||||||
@python_call runner.py.fit([instance.py for instance in instances])
|
@python_call runner.py.fit([instance.py for instance in instances])
|
||||||
end
|
end
|
||||||
|
|
||||||
function write_csv!(
|
function write_csv!(runner::BenchmarkRunner, filename::AbstractString)::Nothing
|
||||||
runner::BenchmarkRunner,
|
|
||||||
filename::AbstractString,
|
|
||||||
)::Nothing
|
|
||||||
CSV.write(filename, runner.results)
|
CSV.write(filename, runner.results)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
export BenchmarkRunner,
|
export BenchmarkRunner, parallel_solve!, fit!, write_csv!
|
||||||
parallel_solve!,
|
|
||||||
fit!,
|
|
||||||
write_csv!
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ macro python_call(expr)
|
|||||||
return $(esc(expr))
|
return $(esc(expr))
|
||||||
catch e
|
catch e
|
||||||
if isa(e, PyCall.PyError)
|
if isa(e, PyCall.PyError)
|
||||||
printstyled("Uncaught Python exception:\n", bold=true, color=:red)
|
printstyled("Uncaught Python exception:\n", bold = true, color = :red)
|
||||||
traceback.print_exception(e.T, e.val, e.traceback)
|
traceback.print_exception(e.T, e.val, e.traceback)
|
||||||
end
|
end
|
||||||
rethrow()
|
rethrow()
|
||||||
|
|||||||
@@ -7,35 +7,37 @@ using Base.CoreLogging, Logging, Printf
|
|||||||
|
|
||||||
struct TimeLogger <: AbstractLogger
|
struct TimeLogger <: AbstractLogger
|
||||||
initial_time::Float64
|
initial_time::Float64
|
||||||
file::Union{Nothing, IOStream}
|
file::Union{Nothing,IOStream}
|
||||||
screen_log_level
|
screen_log_level::Any
|
||||||
io_log_level
|
io_log_level::Any
|
||||||
end
|
end
|
||||||
|
|
||||||
function TimeLogger(;
|
function TimeLogger(;
|
||||||
initial_time::Float64,
|
initial_time::Float64,
|
||||||
file::Union{Nothing, IOStream} = nothing,
|
file::Union{Nothing,IOStream} = nothing,
|
||||||
screen_log_level = CoreLogging.Info,
|
screen_log_level = CoreLogging.Info,
|
||||||
io_log_level = CoreLogging.Info,
|
io_log_level = CoreLogging.Info,
|
||||||
) :: TimeLogger
|
)::TimeLogger
|
||||||
return TimeLogger(initial_time, file, screen_log_level, io_log_level)
|
return TimeLogger(initial_time, file, screen_log_level, io_log_level)
|
||||||
end
|
end
|
||||||
|
|
||||||
min_enabled_level(logger::TimeLogger) = logger.io_log_level
|
min_enabled_level(logger::TimeLogger) = logger.io_log_level
|
||||||
shouldlog(logger::TimeLogger, level, _module, group, id) = true
|
shouldlog(logger::TimeLogger, level, _module, group, id) = true
|
||||||
|
|
||||||
function handle_message(logger::TimeLogger,
|
function handle_message(
|
||||||
level,
|
logger::TimeLogger,
|
||||||
message,
|
level,
|
||||||
_module,
|
message,
|
||||||
group,
|
_module,
|
||||||
id,
|
group,
|
||||||
filepath,
|
id,
|
||||||
line;
|
filepath,
|
||||||
kwargs...)
|
line;
|
||||||
|
kwargs...,
|
||||||
|
)
|
||||||
elapsed_time = time() - logger.initial_time
|
elapsed_time = time() - logger.initial_time
|
||||||
time_string = @sprintf("[%12.3f] ", elapsed_time)
|
time_string = @sprintf("[%12.3f] ", elapsed_time)
|
||||||
|
|
||||||
if level >= Logging.Error
|
if level >= Logging.Error
|
||||||
color = :light_red
|
color = :light_red
|
||||||
elseif level >= Logging.Warn
|
elseif level >= Logging.Warn
|
||||||
@@ -43,12 +45,12 @@ function handle_message(logger::TimeLogger,
|
|||||||
else
|
else
|
||||||
color = :light_green
|
color = :light_green
|
||||||
end
|
end
|
||||||
|
|
||||||
flush(stdout)
|
flush(stdout)
|
||||||
flush(stderr)
|
flush(stderr)
|
||||||
Base.Libc.flush_cstdio()
|
Base.Libc.flush_cstdio()
|
||||||
if level >= logger.screen_log_level
|
if level >= logger.screen_log_level
|
||||||
printstyled(time_string, color=color)
|
printstyled(time_string, color = color)
|
||||||
println(message)
|
println(message)
|
||||||
end
|
end
|
||||||
if logger.file !== nothing && level >= logger.io_log_level
|
if logger.file !== nothing && level >= logger.io_log_level
|
||||||
@@ -64,7 +66,7 @@ end
|
|||||||
|
|
||||||
function setup_logger()
|
function setup_logger()
|
||||||
initial_time = time()
|
initial_time = time()
|
||||||
global_logger(TimeLogger(initial_time=initial_time))
|
global_logger(TimeLogger(initial_time = initial_time))
|
||||||
miplearn = pyimport("miplearn")
|
miplearn = pyimport("miplearn")
|
||||||
miplearn.setup_logger(initial_time)
|
miplearn.setup_logger(initial_time)
|
||||||
end
|
end
|
||||||
|
|||||||
44
src/utils/sysimage.jl
Normal file
44
src/utils/sysimage.jl
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
using PackageCompiler
|
||||||
|
|
||||||
|
using CSV
|
||||||
|
using Cbc
|
||||||
|
using Clp
|
||||||
|
using Conda
|
||||||
|
using DataFrames
|
||||||
|
using Distributed
|
||||||
|
using JLD2
|
||||||
|
using JSON
|
||||||
|
using JuMP
|
||||||
|
using Logging
|
||||||
|
using MathOptInterface
|
||||||
|
using Printf
|
||||||
|
using PyCall
|
||||||
|
using TimerOutputs
|
||||||
|
|
||||||
|
pkg = [
|
||||||
|
:CSV
|
||||||
|
:Cbc
|
||||||
|
:Clp
|
||||||
|
:Conda
|
||||||
|
:DataFrames
|
||||||
|
:Distributed
|
||||||
|
:JLD2
|
||||||
|
:JSON
|
||||||
|
:JuMP
|
||||||
|
:Logging
|
||||||
|
:MathOptInterface
|
||||||
|
:Printf
|
||||||
|
:PyCall
|
||||||
|
:TimerOutputs
|
||||||
|
]
|
||||||
|
|
||||||
|
@info "Building system image..."
|
||||||
|
create_sysimage(
|
||||||
|
pkg,
|
||||||
|
precompile_statements_file = "build/precompile.jl",
|
||||||
|
sysimage_path = "build/sysimage.so",
|
||||||
|
)
|
||||||
8
test/fixtures/knapsack.jl
vendored
8
test/fixtures/knapsack.jl
vendored
@@ -15,14 +15,14 @@ function build_knapsack_model()
|
|||||||
|
|
||||||
n = length(weights)
|
n = length(weights)
|
||||||
@variable(model, x[1:n], Bin)
|
@variable(model, x[1:n], Bin)
|
||||||
@objective(model, Max, sum(x[i] * prices[i] for i in 1:n))
|
@objective(model, Max, sum(x[i] * prices[i] for i = 1:n))
|
||||||
@constraint(model, c1, sum(x[i] * weights[i] for i in 1:n) <= capacity)
|
@constraint(model, c1, sum(x[i] * weights[i] for i = 1:n) <= capacity)
|
||||||
|
|
||||||
# Add ML information to the model
|
# Add ML information to the model
|
||||||
@feature(model, [5.0])
|
@feature(model, [5.0])
|
||||||
@feature(c1, [1.0, 2.0, 3.0])
|
@feature(c1, [1.0, 2.0, 3.0])
|
||||||
@category(c1, "c1")
|
@category(c1, "c1")
|
||||||
for i in 1:n
|
for i = 1:n
|
||||||
@feature(x[i], [weights[i]; prices[i]])
|
@feature(x[i], [weights[i]; prices[i]])
|
||||||
@category(x[i], "type-$i")
|
@category(x[i], "type-$i")
|
||||||
end
|
end
|
||||||
@@ -36,7 +36,7 @@ function build_knapsack_model()
|
|||||||
@test model.ext[:miplearn]["variable_categories"]["x[3]"] == "type-3"
|
@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_features"]["c1"] == [1.0, 2.0, 3.0]
|
||||||
@test model.ext[:miplearn]["constraint_categories"]["c1"] == "c1"
|
@test model.ext[:miplearn]["constraint_categories"]["c1"] == "c1"
|
||||||
@test model.ext[:miplearn]["instance_features"] == [5.0]
|
@test model.ext[:miplearn]["instance_features"] == [5.0]
|
||||||
|
|
||||||
return model
|
return model
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ using Cbc
|
|||||||
solver = LearningSolver(Cbc.Optimizer)
|
solver = LearningSolver(Cbc.Optimizer)
|
||||||
solve!(solver, file_instance)
|
solve!(solver, file_instance)
|
||||||
|
|
||||||
@test length(h5.get_vector("mip_var_values")) == 3
|
@test length(h5.get_array("mip_var_values")) == 3
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ using MIPLearn
|
|||||||
@testset "Discard output" begin
|
@testset "Discard output" begin
|
||||||
instance = build_knapsack_file_instance()
|
instance = build_knapsack_file_instance()
|
||||||
solver = LearningSolver(Cbc.Optimizer)
|
solver = LearningSolver(Cbc.Optimizer)
|
||||||
solve!(solver, instance, discard_output=true)
|
solve!(solver, instance, discard_output = true)
|
||||||
loaded = load_instance(instance.filename)
|
loaded = load_instance(instance.filename)
|
||||||
@test length(loaded.samples) == 0
|
@test length(loaded.samples) == 0
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,38 +9,24 @@ using DataFrames
|
|||||||
|
|
||||||
@testset "BenchmarkRunner" begin
|
@testset "BenchmarkRunner" begin
|
||||||
@info "Building training data..."
|
@info "Building training data..."
|
||||||
instances = [
|
instances = [build_knapsack_file_instance(), build_knapsack_file_instance()]
|
||||||
build_knapsack_file_instance(),
|
stats = parallel_solve!(LearningSolver(Cbc.Optimizer), instances)
|
||||||
build_knapsack_file_instance(),
|
|
||||||
]
|
|
||||||
stats = parallel_solve!(
|
|
||||||
LearningSolver(Cbc.Optimizer),
|
|
||||||
instances,
|
|
||||||
)
|
|
||||||
@test length(stats) == 2
|
@test length(stats) == 2
|
||||||
@test stats[1] !== nothing
|
@test stats[1] !== nothing
|
||||||
@test stats[2] !== nothing
|
@test stats[2] !== nothing
|
||||||
|
|
||||||
benchmark = BenchmarkRunner(
|
benchmark = BenchmarkRunner(
|
||||||
solvers=Dict(
|
solvers = Dict(
|
||||||
"baseline" => LearningSolver(
|
"baseline" => LearningSolver(Cbc.Optimizer, components = []),
|
||||||
Cbc.Optimizer,
|
"ml-exact" => LearningSolver(Cbc.Optimizer),
|
||||||
components=[],
|
"ml-heur" => LearningSolver(Cbc.Optimizer, mode = "heuristic"),
|
||||||
),
|
|
||||||
"ml-exact" => LearningSolver(
|
|
||||||
Cbc.Optimizer,
|
|
||||||
),
|
|
||||||
"ml-heur" => LearningSolver(
|
|
||||||
Cbc.Optimizer,
|
|
||||||
mode="heuristic",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@info "Fitting..."
|
@info "Fitting..."
|
||||||
fit!(benchmark, instances)
|
fit!(benchmark, instances)
|
||||||
|
|
||||||
@info "Benchmarking..."
|
@info "Benchmarking..."
|
||||||
parallel_solve!(benchmark, instances, n_trials=2)
|
parallel_solve!(benchmark, instances, n_trials = 2)
|
||||||
|
|
||||||
csv_filename = tempname()
|
csv_filename = tempname()
|
||||||
write_csv!(benchmark, csv_filename)
|
write_csv!(benchmark, csv_filename)
|
||||||
|
|||||||
Reference in New Issue
Block a user