mirror of
https://github.com/ANL-CEEESA/MIPLearn.jl.git
synced 2025-12-07 00:48:50 -06:00
Reorganize package; add macros
This commit is contained in:
@@ -1,41 +1,25 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, 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.
|
||||
|
||||
__precompile__(false)
|
||||
module MIPLearn
|
||||
|
||||
using PyCall
|
||||
|
||||
export JuMPInstance
|
||||
export LearningSolver
|
||||
export @feature
|
||||
export @category
|
||||
|
||||
miplearn = pyimport("miplearn")
|
||||
Instance = miplearn.Instance
|
||||
BenchmarkRunner = miplearn.BenchmarkRunner
|
||||
|
||||
macro pycall(expr)
|
||||
quote
|
||||
err_msg = nothing
|
||||
result = nothing
|
||||
try
|
||||
result = $(esc(expr))
|
||||
catch err
|
||||
args = err.val.args[1]
|
||||
if (err isa PyCall.PyError) && (args isa String) && startswith(args, "Julia")
|
||||
err_msg = replace(args, r"Stacktrace.*" => "")
|
||||
else
|
||||
rethrow(err)
|
||||
end
|
||||
end
|
||||
if err_msg != nothing
|
||||
error(err_msg)
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
include("log.jl")
|
||||
include("jump_solver.jl")
|
||||
include("learning_solver.jl")
|
||||
include("instance.jl")
|
||||
|
||||
export Instance, BenchmarkRunner
|
||||
include("utils/log.jl")
|
||||
include("utils/pycall.jl")
|
||||
include("modeling/jump_instance.jl")
|
||||
include("modeling/jump_solver.jl")
|
||||
include("modeling/learning_solver.jl")
|
||||
include("modeling/macros.jl")
|
||||
include("problems/knapsack.jl")
|
||||
|
||||
end # module
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
using JSON2
|
||||
import Base: dump
|
||||
|
||||
to_model(instance) =
|
||||
error("not implemented: to_model")
|
||||
|
||||
get_instance_features(instance) = [0.0]
|
||||
|
||||
get_variable_features(instance, varname) = [0.0]
|
||||
|
||||
get_variable_category(instance, varname) = "default"
|
||||
|
||||
find_violated_lazy_constraints(instance, model) = []
|
||||
|
||||
build_lazy_constraint(instance, model, v) = nothing
|
||||
|
||||
macro Instance(klass)
|
||||
quote
|
||||
@pydef mutable struct Wrapper <: Instance
|
||||
function __init__(self, args...; kwargs...)
|
||||
self.data = $(esc(klass))(args...; kwargs...)
|
||||
self.training_data = []
|
||||
self.features = miplearn.Features()
|
||||
end
|
||||
|
||||
to_model(self) =
|
||||
$(esc(:to_model))(self.data)
|
||||
|
||||
get_instance_features(self) =
|
||||
$(esc(:get_instance_features))(self.data)
|
||||
|
||||
get_variable_features(self, varname) =
|
||||
$(esc(:get_variable_features))(self.data, varname)
|
||||
|
||||
get_variable_category(self, varname) =
|
||||
$(esc(:get_variable_category))(self.data, varname)
|
||||
|
||||
find_violated_lazy_constraints(self, model) =
|
||||
find_violated_lazy_constraints(self.data, model)
|
||||
|
||||
build_lazy_constraint(self, model, v) =
|
||||
build_lazy_constraint(self.data, model, v)
|
||||
|
||||
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
|
||||
@@ -1,27 +0,0 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
struct LearningSolver
|
||||
py::PyCall.PyObject
|
||||
end
|
||||
|
||||
function LearningSolver(;
|
||||
optimizer,
|
||||
kwargs...,
|
||||
)::LearningSolver
|
||||
py = miplearn.LearningSolver(
|
||||
;
|
||||
kwargs...,
|
||||
solver=JuMPSolver(optimizer=optimizer),
|
||||
)
|
||||
return LearningSolver(py)
|
||||
end
|
||||
|
||||
solve!(solver::LearningSolver, instance; kwargs...) =
|
||||
solver.py.solve(instance; kwargs...)
|
||||
|
||||
fit!(solver::LearningSolver, instances; kwargs...) =
|
||||
solver.py.fit(instances; kwargs...)
|
||||
|
||||
export LearningSolver
|
||||
52
src/modeling/jump_instance.jl
Normal file
52
src/modeling/jump_instance.jl
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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.
|
||||
|
||||
@pydef mutable struct JuMPInstance <: miplearn.Instance
|
||||
function __init__(self, model)
|
||||
init_miplearn_ext(model)
|
||||
features = model.ext[:miplearn][:features]
|
||||
self.model = model
|
||||
|
||||
# Copy training data
|
||||
training_data = []
|
||||
for sample in self.model.ext[:miplearn][:training_samples]
|
||||
pysample = miplearn.TrainingSample()
|
||||
pysample.__dict__ = sample
|
||||
push!(training_data, pysample)
|
||||
end
|
||||
self.training_data = training_data
|
||||
|
||||
# Copy features to data classes
|
||||
self.features = miplearn.Features(
|
||||
instance=miplearn.InstanceFeatures(
|
||||
user_features=PyCall.array2py(
|
||||
features[:instance][:user_features],
|
||||
),
|
||||
lazy_constraint_count=0,
|
||||
),
|
||||
variables=Dict(
|
||||
varname => miplearn.VariableFeatures(
|
||||
category=vfeatures[:category],
|
||||
user_features=PyCall.array2py(
|
||||
vfeatures[:user_features],
|
||||
),
|
||||
)
|
||||
for (varname, vfeatures) in features[:variables]
|
||||
),
|
||||
constraints=Dict(
|
||||
cname => miplearn.ConstraintFeatures(
|
||||
category=cfeat[:category],
|
||||
user_features=PyCall.array2py(
|
||||
cfeat[:user_features],
|
||||
),
|
||||
)
|
||||
for (cname, cfeat) in features[:constraints]
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
function to_model(self)
|
||||
return self.model
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, 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.
|
||||
|
||||
using JuMP
|
||||
32
src/modeling/learning_solver.jl
Normal file
32
src/modeling/learning_solver.jl
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
|
||||
struct LearningSolver
|
||||
py::PyCall.PyObject
|
||||
end
|
||||
|
||||
function LearningSolver(
|
||||
;
|
||||
optimizer,
|
||||
)::LearningSolver
|
||||
py = miplearn.LearningSolver(solver=JuMPSolver(optimizer=optimizer))
|
||||
return LearningSolver(py)
|
||||
end
|
||||
|
||||
function solve!(solver::LearningSolver, model::Model)
|
||||
instance = JuMPInstance(model)
|
||||
mip_stats = solver.py.solve(instance)
|
||||
push!(
|
||||
model.ext[:miplearn][:training_samples],
|
||||
instance.training_data[1].__dict__,
|
||||
)
|
||||
return mip_stats
|
||||
end
|
||||
|
||||
function fit!(solver::LearningSolver, models::Array{Model})
|
||||
instances = [JuMPInstance(m) for m in models]
|
||||
solver.py.fit(instances)
|
||||
end
|
||||
|
||||
export LearningSolver
|
||||
84
src/modeling/macros.jl
Normal file
84
src/modeling/macros.jl
Normal file
@@ -0,0 +1,84 @@
|
||||
# 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.
|
||||
|
||||
function init_miplearn_ext(model)::Dict
|
||||
if :miplearn ∉ keys(model.ext)
|
||||
model.ext[:miplearn] = Dict{Symbol,Any}(
|
||||
:features => Dict(
|
||||
:variables => Dict{String,Dict}(),
|
||||
:constraints => Dict{String,Dict}(),
|
||||
:instance => Dict{Symbol,Any}(),
|
||||
),
|
||||
:training_samples => [],
|
||||
)
|
||||
end
|
||||
return model.ext[:miplearn]
|
||||
end
|
||||
|
||||
|
||||
function init_miplearn_ext(v::VariableRef)::Dict
|
||||
ext = init_miplearn_ext(v.model)
|
||||
if name(v) ∉ keys(ext[:features][:variables])
|
||||
ext[:features][:variables][name(v)] = Dict{Symbol,Any}()
|
||||
end
|
||||
return ext
|
||||
end
|
||||
|
||||
|
||||
function init_miplearn_ext(c::ConstraintRef)::Dict
|
||||
ext = init_miplearn_ext(c.model)
|
||||
if name(c) ∉ keys(ext[:features][:constraints])
|
||||
ext[:features][:constraints][name(c)] = Dict{Symbol,Any}()
|
||||
end
|
||||
return ext
|
||||
end
|
||||
|
||||
|
||||
function set_features!(m::Model, f::Array{Float64})::Nothing
|
||||
ext = init_miplearn_ext(m)
|
||||
ext[:features][:instance][:user_features] = f
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function set_features!(v::VariableRef, f::Array{Float64})::Nothing
|
||||
ext = init_miplearn_ext(v)
|
||||
ext[:features][:variables][name(v)][:user_features] = f
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function set_category!(v::VariableRef, category::String)::Nothing
|
||||
ext = init_miplearn_ext(v)
|
||||
ext[:features][:variables][name(v)][:category] = category
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function set_features!(c::ConstraintRef, f::Array{Float64})::Nothing
|
||||
ext = init_miplearn_ext(c)
|
||||
ext[:features][:constraints][name(c)][:user_features] = f
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
function set_category!(c::ConstraintRef, category::String)::Nothing
|
||||
ext = init_miplearn_ext(c)
|
||||
ext[:features][:constraints][name(c)][:category] = category
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
macro feature(obj, features)
|
||||
quote
|
||||
set_features!($(esc(obj)), $(esc(features)))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
macro category(obj, category)
|
||||
quote
|
||||
set_category!($(esc(obj)), $(esc(category)))
|
||||
end
|
||||
end
|
||||
25
src/problems/knapsack.jl
Normal file
25
src/problems/knapsack.jl
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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 JuMP
|
||||
|
||||
function knapsack_model(
|
||||
weights::Array{Float64, 1},
|
||||
prices::Array{Float64, 1},
|
||||
capacity::Float64,
|
||||
)
|
||||
model = Model()
|
||||
n = length(weights)
|
||||
@variable(model, x[0:(n-1)], Bin)
|
||||
@objective(model, Max, sum(x[i] * prices[i+1] for i in 0:(n-1)))
|
||||
@constraint(
|
||||
model,
|
||||
eq_capacity,
|
||||
sum(
|
||||
x[i] * weights[i+1]
|
||||
for i in 0:(n-1)
|
||||
) <= capacity,
|
||||
)
|
||||
return model
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, 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.
|
||||
|
||||
import Logging: min_enabled_level, shouldlog, handle_message
|
||||
24
src/utils/pycall.jl
Normal file
24
src/utils/pycall.jl
Normal file
@@ -0,0 +1,24 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
macro pycall(expr)
|
||||
quote
|
||||
err_msg = nothing
|
||||
result = nothing
|
||||
try
|
||||
result = $(esc(expr))
|
||||
catch err
|
||||
args = err.val.args[1]
|
||||
if (err isa PyCall.PyError) && (args isa String) && startswith(args, "Julia")
|
||||
err_msg = replace(args, r"Stacktrace.*" => "")
|
||||
else
|
||||
rethrow(err)
|
||||
end
|
||||
end
|
||||
if err_msg != nothing
|
||||
error(err_msg)
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user