mirror of
https://github.com/ANL-CEEESA/MIPLearn.jl.git
synced 2025-12-06 08:28:52 -06:00
Implement lazy callbacks
This commit is contained in:
@@ -9,9 +9,13 @@ mutable struct FileInstance <: Instance
|
|||||||
loaded::Union{Nothing,JuMPInstance}
|
loaded::Union{Nothing,JuMPInstance}
|
||||||
filename::AbstractString
|
filename::AbstractString
|
||||||
h5::PyCall.PyObject
|
h5::PyCall.PyObject
|
||||||
|
lazycb::Union{Nothing,Tuple{Function,Function}}
|
||||||
|
|
||||||
function FileInstance(filename::AbstractString)::FileInstance
|
function FileInstance(
|
||||||
instance = new(nothing, nothing, filename)
|
filename::AbstractString;
|
||||||
|
lazycb::Union{Nothing,Tuple{Function,Function}} = nothing,
|
||||||
|
)::FileInstance
|
||||||
|
instance = new(nothing, nothing, filename, nothing, lazycb)
|
||||||
instance.py = PyFileInstance(instance)
|
instance.py = PyFileInstance(instance)
|
||||||
instance.h5 = Hdf5Sample(filename)
|
instance.h5 = Hdf5Sample(filename)
|
||||||
instance.filename = filename
|
instance.filename = filename
|
||||||
@@ -20,16 +24,27 @@ mutable struct FileInstance <: Instance
|
|||||||
end
|
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, names) =
|
get_variable_features(instance::FileInstance, names) =
|
||||||
get_variable_features(instance.loaded, names)
|
get_variable_features(instance.loaded, names)
|
||||||
|
|
||||||
get_variable_categories(instance::FileInstance, names) =
|
get_variable_categories(instance::FileInstance, names) =
|
||||||
get_variable_categories(instance.loaded, names)
|
get_variable_categories(instance.loaded, names)
|
||||||
|
|
||||||
get_constraint_features(instance::FileInstance, names) =
|
get_constraint_features(instance::FileInstance, names) =
|
||||||
get_constraint_features(instance.loaded, names)
|
get_constraint_features(instance.loaded, names)
|
||||||
|
|
||||||
get_constraint_categories(instance::FileInstance, names) =
|
get_constraint_categories(instance::FileInstance, names) =
|
||||||
get_constraint_categories(instance.loaded, names)
|
get_constraint_categories(instance.loaded, names)
|
||||||
|
|
||||||
|
find_violated_lazy_constraints(instance::FileInstance, solver) =
|
||||||
|
find_violated_lazy_constraints(instance.loaded, solver)
|
||||||
|
|
||||||
|
enforce_lazy_constraint(instance::FileInstance, solver, violation) =
|
||||||
|
enforce_lazy_constraint(instance.loaded, solver, violation)
|
||||||
|
|
||||||
function get_samples(instance::FileInstance)
|
function get_samples(instance::FileInstance)
|
||||||
return [instance.h5]
|
return [instance.h5]
|
||||||
end
|
end
|
||||||
@@ -40,7 +55,7 @@ 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, lazycb = instance.lazycb)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -72,6 +87,10 @@ function __init_PyFileInstance__()
|
|||||||
load(self) = load(self.jl)
|
load(self) = load(self.jl)
|
||||||
free(self) = free(self.jl)
|
free(self) = free(self.jl)
|
||||||
flush(self) = flush(self.jl)
|
flush(self) = flush(self.jl)
|
||||||
|
find_violated_lazy_constraints(self, solver, _) =
|
||||||
|
find_violated_lazy_constraints(self.jl, solver)
|
||||||
|
enforce_lazy_constraint(self, solver, _, violation) =
|
||||||
|
enforce_lazy_constraint(self.jl, solver, violation)
|
||||||
end
|
end
|
||||||
copy!(PyFileInstance, Class)
|
copy!(PyFileInstance, Class)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ mutable struct JuMPInstance <: Instance
|
|||||||
ext::AbstractDict
|
ext::AbstractDict
|
||||||
samples::Vector{PyCall.PyObject}
|
samples::Vector{PyCall.PyObject}
|
||||||
|
|
||||||
function JuMPInstance(model::JuMP.Model)
|
function JuMPInstance(model::JuMP.Model)::JuMPInstance
|
||||||
init_miplearn_ext(model)
|
init_miplearn_ext(model)
|
||||||
instance = new(nothing, model, nothing, model.ext[:miplearn], [])
|
instance = new(nothing, model, nothing, model.ext[:miplearn], [])
|
||||||
py = PyJuMPInstance(instance)
|
py = PyJuMPInstance(instance)
|
||||||
@@ -84,6 +84,18 @@ function create_sample!(instance::JuMPInstance)
|
|||||||
return sample
|
return sample
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function find_violated_lazy_constraints(instance::JuMPInstance, solver)::Vector{String}
|
||||||
|
if "lazy_find_cb" ∈ keys(instance.model.ext[:miplearn])
|
||||||
|
return instance.model.ext[:miplearn]["lazy_find_cb"](instance.model, solver.data)
|
||||||
|
else
|
||||||
|
return []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function enforce_lazy_constraint(instance::JuMPInstance, solver, violation::String)::Nothing
|
||||||
|
instance.model.ext[:miplearn]["lazy_enforce_cb"](instance.model, solver.data, violation)
|
||||||
|
end
|
||||||
|
|
||||||
function __init_PyJuMPInstance__()
|
function __init_PyJuMPInstance__()
|
||||||
@pydef mutable struct Class <: miplearn.Instance
|
@pydef mutable struct Class <: miplearn.Instance
|
||||||
function __init__(self, jl)
|
function __init__(self, jl)
|
||||||
@@ -101,6 +113,10 @@ function __init_PyJuMPInstance__()
|
|||||||
to_str_array(get_constraint_categories(self.jl, from_str_array(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)
|
||||||
|
find_violated_lazy_constraints(self, solver, _) =
|
||||||
|
find_violated_lazy_constraints(self.jl, solver)
|
||||||
|
enforce_lazy_constraint(self, solver, _, violation) =
|
||||||
|
enforce_lazy_constraint(self.jl, solver, violation)
|
||||||
end
|
end
|
||||||
copy!(PyJuMPInstance, Class)
|
copy!(PyJuMPInstance, Class)
|
||||||
end
|
end
|
||||||
@@ -116,7 +132,11 @@ function save(filename::AbstractString, instance::JuMPInstance)::Nothing
|
|||||||
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]))
|
|
||||||
|
ext = copy(model.ext[:miplearn])
|
||||||
|
delete!(ext, "lazy_find_cb")
|
||||||
|
delete!(ext, "lazy_enforce_cb")
|
||||||
|
h5.put_scalar("jump_ext", JSON.json(ext))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -130,12 +150,19 @@ function _check_miplearn_version(h5)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function load_instance(filename::AbstractString)::JuMPInstance
|
function load_instance(
|
||||||
|
filename::AbstractString;
|
||||||
|
lazycb::Union{Nothing,Tuple{Function,Function}} = nothing,
|
||||||
|
)::JuMPInstance
|
||||||
h5 = Hdf5Sample(filename)
|
h5 = Hdf5Sample(filename)
|
||||||
_check_miplearn_version(h5)
|
_check_miplearn_version(h5)
|
||||||
mps = h5.get_bytes("mps")
|
mps = h5.get_bytes("mps")
|
||||||
ext = h5.get_scalar("jump_ext")
|
ext = JSON.parse(h5.get_scalar("jump_ext"))
|
||||||
instance = JuMPInstance(Vector{UInt8}(mps), JSON.parse(ext))
|
if lazycb !== nothing
|
||||||
|
ext["lazy_find_cb"] = lazycb[1]
|
||||||
|
ext["lazy_enforce_cb"] = lazycb[2]
|
||||||
|
end
|
||||||
|
instance = JuMPInstance(Vector{UInt8}(mps), ext)
|
||||||
return instance
|
return instance
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ using MathOptInterface
|
|||||||
using TimerOutputs
|
using TimerOutputs
|
||||||
const MOI = MathOptInterface
|
const MOI = MathOptInterface
|
||||||
|
|
||||||
|
import JuMP: value
|
||||||
|
|
||||||
mutable struct JuMPSolverData
|
mutable struct JuMPSolverData
|
||||||
optimizer_factory::Any
|
optimizer_factory::Any
|
||||||
varname_to_var::Dict{String,VariableRef}
|
varname_to_var::Dict{String,VariableRef}
|
||||||
@@ -19,6 +21,7 @@ mutable struct JuMPSolverData
|
|||||||
solution::Dict{JuMP.VariableRef,Float64}
|
solution::Dict{JuMP.VariableRef,Float64}
|
||||||
reduced_costs::Vector{Float64}
|
reduced_costs::Vector{Float64}
|
||||||
dual_values::Dict{JuMP.ConstraintRef,Float64}
|
dual_values::Dict{JuMP.ConstraintRef,Float64}
|
||||||
|
cb_data::Any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -175,10 +178,25 @@ function remove_constraints(data::JuMPSolverData, names::Vector{String})::Nothin
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function solve(data::JuMPSolverData; tee::Bool = false, iteration_cb = nothing)
|
function solve(
|
||||||
|
data::JuMPSolverData;
|
||||||
|
tee::Bool = false,
|
||||||
|
iteration_cb = nothing,
|
||||||
|
lazy_cb = nothing,
|
||||||
|
)
|
||||||
model = data.model
|
model = data.model
|
||||||
wallclock_time = 0
|
wallclock_time = 0
|
||||||
log = ""
|
log = ""
|
||||||
|
|
||||||
|
if lazy_cb !== nothing
|
||||||
|
function lazy_cb_wrapper(cb_data)
|
||||||
|
data.cb_data = cb_data
|
||||||
|
lazy_cb(nothing, nothing)
|
||||||
|
data.cb_data = nothing
|
||||||
|
end
|
||||||
|
MOI.set(model, MOI.LazyConstraintCallback(), lazy_cb_wrapper)
|
||||||
|
end
|
||||||
|
|
||||||
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)
|
||||||
@@ -189,6 +207,7 @@ function solve(data::JuMPSolverData; tee::Bool = false, iteration_cb = nothing)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if is_infeasible(data)
|
if is_infeasible(data)
|
||||||
data.solution = Dict()
|
data.solution = Dict()
|
||||||
primal_bound = nothing
|
primal_bound = nothing
|
||||||
@@ -452,6 +471,7 @@ function __init_JuMPSolver__()
|
|||||||
Dict(), # solution
|
Dict(), # solution
|
||||||
[], # reduced_costs
|
[], # reduced_costs
|
||||||
Dict(), # dual_values
|
Dict(), # dual_values
|
||||||
|
nothing, # cb_data
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -555,12 +575,27 @@ function __init_JuMPSolver__()
|
|||||||
iteration_cb = nothing,
|
iteration_cb = nothing,
|
||||||
lazy_cb = nothing,
|
lazy_cb = nothing,
|
||||||
user_cut_cb = nothing,
|
user_cut_cb = nothing,
|
||||||
) = solve(self.data, tee = tee, iteration_cb = iteration_cb)
|
) = solve(self.data, tee = tee, iteration_cb = iteration_cb, lazy_cb = lazy_cb)
|
||||||
|
|
||||||
solve_lp(self; tee = false) = solve_lp(self.data, tee = tee)
|
solve_lp(self; tee = false) = solve_lp(self.data, tee = tee)
|
||||||
end
|
end
|
||||||
copy!(JuMPSolver, Class)
|
copy!(JuMPSolver, Class)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function value(solver::JuMPSolverData, var::VariableRef)
|
||||||
|
if solver.cb_data !== nothing
|
||||||
|
return JuMP.callback_value(solver.cb_data, var)
|
||||||
|
else
|
||||||
|
return JuMP.value(var)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
export JuMPSolver
|
function submit(solver::JuMPSolverData, con::AbstractConstraint, name::String = "")
|
||||||
|
if solver.cb_data !== nothing
|
||||||
|
MOI.submit(solver.model, MOI.LazyConstraint(solver.cb_data), con)
|
||||||
|
else
|
||||||
|
JuMP.add_constraint(solver.model, con, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
export JuMPSolver, submit
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ function set_category!(c::ConstraintRef, category::String)::Nothing
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function set_lazy_callback!(model::Model, find_cb::Function, enforce_cb::Function)::Nothing
|
||||||
|
ext = init_miplearn_ext(model)
|
||||||
|
ext["lazy_find_cb"] = find_cb
|
||||||
|
ext["lazy_enforce_cb"] = enforce_cb
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
macro feature(obj, features)
|
macro feature(obj, features)
|
||||||
quote
|
quote
|
||||||
@@ -67,6 +74,12 @@ macro category(obj, category)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
macro lazycb(obj, find_cb, enforce_cb)
|
||||||
|
quote
|
||||||
|
set_lazy_callback!($(esc(obj)), $(esc(find_cb)), $(esc(enforce_cb)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function _get_and_check_name(obj)
|
function _get_and_check_name(obj)
|
||||||
n = name(obj)
|
n = name(obj)
|
||||||
length(n) > 0 || error(
|
length(n) > 0 || error(
|
||||||
@@ -77,4 +90,4 @@ function _get_and_check_name(obj)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
export @feature, @category
|
export @feature, @category, @lazycb
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
using PackageCompiler
|
using PackageCompiler
|
||||||
|
|
||||||
using CSV
|
|
||||||
using Cbc
|
using Cbc
|
||||||
using Clp
|
using Clp
|
||||||
using Conda
|
using Conda
|
||||||
|
using CSV
|
||||||
using DataFrames
|
using DataFrames
|
||||||
using Distributed
|
using Distributed
|
||||||
using JLD2
|
using JLD2
|
||||||
@@ -20,10 +20,10 @@ using PyCall
|
|||||||
using TimerOutputs
|
using TimerOutputs
|
||||||
|
|
||||||
pkg = [
|
pkg = [
|
||||||
:CSV
|
|
||||||
:Cbc
|
:Cbc
|
||||||
:Clp
|
:Clp
|
||||||
:Conda
|
:Conda
|
||||||
|
:CSV
|
||||||
:DataFrames
|
:DataFrames
|
||||||
:Distributed
|
:Distributed
|
||||||
:JLD2
|
:JLD2
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
[deps]
|
[deps]
|
||||||
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
|
||||||
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
|
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
|
||||||
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
|
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
|
||||||
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
|
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
|
||||||
|
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
|
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
|
||||||
JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
|
JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
|
||||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
MIPLearn = "2b1277c3-b477-4c49-a15e-7ba350325c68"
|
|
||||||
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
||||||
|
MIPLearn = "2b1277c3-b477-4c49-a15e-7ba350325c68"
|
||||||
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
|
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"
|
||||||
|
|||||||
@@ -2,29 +2,62 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
using Cbc
|
||||||
|
using JuMP
|
||||||
|
using MathOptInterface
|
||||||
using MIPLearn
|
using MIPLearn
|
||||||
|
const MOI = MathOptInterface
|
||||||
|
|
||||||
|
function find_lazy(model::Model, cb_data)::Vector{String}
|
||||||
|
x = variable_by_name(model, "x")
|
||||||
|
y = variable_by_name(model, "y")
|
||||||
|
x_val = value(cb_data, x)
|
||||||
|
y_val = value(cb_data, y)
|
||||||
|
if x_val + y_val > 1 + 1e-6
|
||||||
|
return ["con"]
|
||||||
|
end
|
||||||
|
return []
|
||||||
|
end
|
||||||
|
|
||||||
|
function enforce_lazy(model::Model, cb_data, violation::String)::Nothing
|
||||||
|
if violation == "con"
|
||||||
|
x = variable_by_name(model, "x")
|
||||||
|
y = variable_by_name(model, "y")
|
||||||
|
con = @build_constraint(x + y <= 1)
|
||||||
|
submit(cb_data, con)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
function build_model()
|
||||||
|
model = Model()
|
||||||
|
@variable(model, x, Bin)
|
||||||
|
@variable(model, y, Bin)
|
||||||
|
@objective(model, Max, 2 * x + y)
|
||||||
|
@constraint(model, c1, x + y <= 2)
|
||||||
|
@lazycb(model, find_lazy, enforce_lazy)
|
||||||
|
return model
|
||||||
|
end
|
||||||
|
|
||||||
|
@testset "Lazy callback" begin
|
||||||
@testset "JuMPInstance" begin
|
@testset "JuMPInstance" begin
|
||||||
@testset "Save and load" begin
|
model = build_model()
|
||||||
# Build instance and solve
|
|
||||||
model = model = build_knapsack_model()
|
|
||||||
instance = JuMPInstance(model)
|
instance = JuMPInstance(model)
|
||||||
solver = LearningSolver(Gurobi.Optimizer)
|
solver = LearningSolver(Cbc.Optimizer)
|
||||||
stats = solve!(solver, instance)
|
solve!(solver, instance)
|
||||||
@test length(instance.py.samples) == 1
|
@test value(model[:x]) == 1.0
|
||||||
|
@test value(model[:y]) == 0.0
|
||||||
|
end
|
||||||
|
|
||||||
# Save model to file
|
@testset "FileInstance" begin
|
||||||
|
model = build_model()
|
||||||
|
instance = JuMPInstance(model)
|
||||||
filename = tempname()
|
filename = tempname()
|
||||||
save(filename, instance)
|
save(filename, instance)
|
||||||
@test isfile(filename)
|
file_instance = FileInstance(filename, lazycb = (find_lazy, enforce_lazy))
|
||||||
|
solver = LearningSolver(Cbc.Optimizer)
|
||||||
# Read model from file
|
solve!(solver, file_instance)
|
||||||
loaded = load_instance(filename)
|
h5 = MIPLearn.Hdf5Sample(filename)
|
||||||
x = variable_by_name(loaded.model, "x")
|
@test h5.get_array("mip_var_values") == [1.0, 0.0]
|
||||||
@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
|
end
|
||||||
@@ -10,6 +10,7 @@ MIPLearn.setup_logger()
|
|||||||
@testset "MIPLearn" begin
|
@testset "MIPLearn" begin
|
||||||
include("fixtures/knapsack.jl")
|
include("fixtures/knapsack.jl")
|
||||||
include("instance/file_test.jl")
|
include("instance/file_test.jl")
|
||||||
|
include("instance/jump_test.jl")
|
||||||
include("solvers/jump_test.jl")
|
include("solvers/jump_test.jl")
|
||||||
include("solvers/learning_test.jl")
|
include("solvers/learning_test.jl")
|
||||||
include("utils/benchmark_test.jl")
|
include("utils/benchmark_test.jl")
|
||||||
|
|||||||
Reference in New Issue
Block a user