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.
MIPLearn.jl/src/BB/optimize.jl

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