You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.4 KiB
170 lines
5.4 KiB
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
|
# Copyright (C) 2020-2023, UChicago Argonne, LLC. All rights reserved.
|
|
# Released under the modified BSD license. See COPYING.md for more details.
|
|
|
|
using Printf
|
|
using Base.Threads
|
|
import Base.Threads: @threads, nthreads, threadid
|
|
|
|
import ..H5File
|
|
|
|
function solve!(
|
|
mip::MIP;
|
|
time_limit::Float64 = Inf,
|
|
node_limit::Int = typemax(Int),
|
|
gap_limit::Float64 = 1e-4,
|
|
print_interval::Int = 5,
|
|
initial_primal_bound::Float64 = Inf,
|
|
branch_rule::VariableBranchingRule = ReliabilityBranching(),
|
|
enable_plunging = true,
|
|
)::NodePool
|
|
time_initial = time()
|
|
pool = NodePool(mip = mip)
|
|
pool.primal_bound = initial_primal_bound
|
|
|
|
root_node = _create_node(mip)
|
|
if isempty(root_node.fractional_variables)
|
|
println("root relaxation is integer feasible")
|
|
pool.dual_bound = root_node.obj
|
|
pool.primal_bound = root_node.obj
|
|
return pool
|
|
else
|
|
print_progress_header()
|
|
end
|
|
|
|
offer(
|
|
pool,
|
|
parent_node = nothing,
|
|
child_nodes = [root_node],
|
|
print_interval = print_interval,
|
|
)
|
|
@threads for t = 1:nthreads()
|
|
child_one, child_zero, suggestions = nothing, nothing, Node[]
|
|
while true
|
|
time_elapsed = time() - time_initial
|
|
if enable_plunging && (child_one !== nothing)
|
|
suggestions = Node[child_one, child_zero]
|
|
end
|
|
node = take(
|
|
pool,
|
|
suggestions = suggestions,
|
|
time_remaining = time_limit - time_elapsed,
|
|
node_limit = node_limit,
|
|
gap_limit = gap_limit,
|
|
)
|
|
if node == :END
|
|
break
|
|
elseif node == :WAIT
|
|
sleep(0.1)
|
|
continue
|
|
else
|
|
# Assert node is feasible
|
|
_set_node_bounds(node)
|
|
status, _ = solve_relaxation!(mip)
|
|
@assert status == :Optimal
|
|
_unset_node_bounds(node)
|
|
|
|
# Find branching variable
|
|
ids = generate_indices(pool, 2)
|
|
branch_var = find_branching_var(branch_rule, node, pool)
|
|
|
|
# Find current variable lower and upper bounds
|
|
offset = findfirst(isequal(branch_var), mip.int_vars)
|
|
var_lb = mip.int_vars_lb[offset]
|
|
var_ub = mip.int_vars_ub[offset]
|
|
for (offset, v) in enumerate(node.branch_vars)
|
|
if v == branch_var
|
|
var_lb = max(var_lb, node.branch_lb[offset])
|
|
var_ub = min(var_ub, node.branch_ub[offset])
|
|
end
|
|
end
|
|
|
|
# Query current fractional value
|
|
offset = findfirst(isequal(branch_var), node.fractional_variables)
|
|
var_value = node.fractional_values[offset]
|
|
|
|
child_zero = _create_node(
|
|
mip,
|
|
index = ids[2],
|
|
parent = node,
|
|
branch_var = branch_var,
|
|
branch_var_lb = var_lb,
|
|
branch_var_ub = floor(var_value),
|
|
)
|
|
child_one = _create_node(
|
|
mip,
|
|
index = ids[1],
|
|
parent = node,
|
|
branch_var = branch_var,
|
|
branch_var_lb = ceil(var_value),
|
|
branch_var_ub = var_ub,
|
|
)
|
|
offer(
|
|
pool,
|
|
parent_node = node,
|
|
child_nodes = [child_one, child_zero],
|
|
time_elapsed = time_elapsed,
|
|
print_interval = print_interval,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
return pool
|
|
end
|
|
|
|
function _create_node(
|
|
mip;
|
|
index::Int = 0,
|
|
parent::Union{Nothing,Node} = nothing,
|
|
branch_var::Union{Nothing,Variable} = nothing,
|
|
branch_var_lb::Union{Nothing,Float64} = nothing,
|
|
branch_var_ub::Union{Nothing,Float64} = nothing,
|
|
)::Node
|
|
if parent === nothing
|
|
branch_vars = Variable[]
|
|
branch_lb = Float64[]
|
|
branch_ub = Float64[]
|
|
depth = 1
|
|
else
|
|
branch_vars = [parent.branch_vars; branch_var]
|
|
branch_lb = [parent.branch_lb; branch_var_lb]
|
|
branch_ub = [parent.branch_ub; branch_var_ub]
|
|
depth = parent.depth + 1
|
|
end
|
|
set_bounds!(mip, branch_vars, branch_lb, branch_ub)
|
|
status, obj = solve_relaxation!(mip)
|
|
if status == :Optimal
|
|
vals = values(mip, mip.int_vars)
|
|
fractional_indices = [
|
|
j for j in 1:length(mip.int_vars) if 1e-6 < vals[j] - floor(vals[j]) < 1 - 1e-6
|
|
]
|
|
fractional_values = vals[fractional_indices]
|
|
fractional_variables = mip.int_vars[fractional_indices]
|
|
else
|
|
fractional_variables = Variable[]
|
|
fractional_values = Float64[]
|
|
end
|
|
set_bounds!(mip, mip.int_vars, mip.int_vars_lb, mip.int_vars_ub)
|
|
return Node(
|
|
mip,
|
|
index,
|
|
depth,
|
|
obj,
|
|
status,
|
|
branch_vars,
|
|
branch_lb,
|
|
branch_ub,
|
|
fractional_variables,
|
|
fractional_values,
|
|
parent,
|
|
)
|
|
end
|
|
|
|
function _set_node_bounds(node::Node)
|
|
set_bounds!(node.mip, node.branch_vars, node.branch_lb, node.branch_ub)
|
|
end
|
|
|
|
function _unset_node_bounds(node::Node)
|
|
set_bounds!(node.mip, node.mip.int_vars, node.mip.int_vars_lb, node.mip.int_vars_ub)
|
|
end
|