Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better implementation of the time limit #744

Merged
merged 2 commits into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Algorithm/colgen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,9 @@ function cg_main_loop!(
@warn "Maximum number of column generation iteration is reached."
return true, false
end
if time_limit_reached!(cg_optstate, env)
return true, false
end
essential_cuts_separated = false
end
return false, false
Expand Down
30 changes: 18 additions & 12 deletions src/Algorithm/conquer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function run!(algo::BendersConquer, env::Env, reform::Reformulation, input::Abst
node_state = get_opt_state(node)
output = run!(algo.benders, env, reform, node_state)
update!(node_state, output)
return
return
end

####################################################################
Expand All @@ -81,7 +81,7 @@ problem decomposed using Dantzig-Wolfe paradigm.
This algorithm applies a set of column generation algorithms whose definitions are
stored in `stages`. These algorithms are called in the reverse order of vector `stages`.
So usually, the first stage is the one with exact pricing, and other stages use heuristic pricing (the higher is the position of the stage,
the faster is the heuristic).
the faster is the heuristic).

This algorithm also applies `cutgen` for the cut generation phase.
It can apply several primal heuristics stored in `primal_heuristics` to more efficiently find feasible solutions.
Expand Down Expand Up @@ -111,8 +111,7 @@ function isverbose(algo::ColCutGenConquer)
end

# ColCutGenConquer does not use any storage unit for the moment, therefore
# get_units_usage() is not defined for it

# get_units_usage() is not defined for i
function get_child_algorithms(algo::ColCutGenConquer, reform::Reformulation)
child_algos = Tuple{AbstractAlgorithm, AbstractModel}[]
for colgen in algo.stages
Expand All @@ -132,15 +131,14 @@ struct ColCutGenContext
params::ColCutGenConquer
end

function type_of_context(algo::ColCutGenConquer)
function type_of_context(::ColCutGenConquer)
return ColCutGenContext
end

function new_context(::Type{ColCutGenContext}, algo::ColCutGenConquer, reform, input)
return ColCutGenContext(algo)
end

# run_cutgen!
"""
Runs a round of cut generation.
Returns `true` if at least one cut is separated; `false` otherwise.
Expand Down Expand Up @@ -170,7 +168,9 @@ function run_colgen!(ctx::ColCutGenContext, colgen, env, reform, node_state)
end

"""

Runs several rounds of column and cut generation.
Returns `false` if the column generation returns `false` or time limit is reached.
Returns `true` if the conquer algorithm continues.
"""
function run_colcutgen!(ctx::ColCutGenContext, env, reform, node_state)
nb_cut_rounds = 0
Expand All @@ -195,6 +195,8 @@ function run_colcutgen!(ctx::ColCutGenContext, env, reform, node_state)
else
@warn "Column generation did not produce an LP primal solution. Skip cut generation."
end

time_limit_reached!(node_state, env) && return false
end
return true
end
Expand Down Expand Up @@ -314,30 +316,34 @@ function run_colcutgen_conquer!(ctx::ColCutGenContext, env, reform, input)
restore_from_records!(get_units_to_restore(input), get_records(node))
node_state = get_opt_state(node)

# TODO: check time limit of Coluna
time_limit_reached!(node_state, env) && return

if !isnothing(ctx.params.preprocess)
run_conquer = run_preprocessing!(ctx, ctx.params.preprocess, env, reform, node_state)
!run_conquer && return
end

# TODO: check time limit of Coluna
time_limit_reached!(node_state, env) && return

run_conquer = run_colcutgen!(ctx, env, reform, node_state)
!run_conquer && return

# TODO: check time limit of Coluna
time_limit_reached!(node_state, env) && return

heuristics_to_run = get_heuristics_to_run(ctx, node)
run_conquer = run_heuristics!(ctx, heuristics_to_run, env, reform, node_state)
!run_conquer && return

# TODO: check time limit of Coluna
time_limit_reached!(node_state, env) && return

# if the gap is still unclosed, try to run the node finalizer
node_finalizer = ctx.params.node_finalizer
if !ip_gap_closed(node_state, atol = ctx.params.opt_atol, rtol = ctx.params.opt_rtol) && !isnothing(node_finalizer)
run_node_finalizer!(ctx, node_finalizer, env, reform, node, node_state)
end

# TODO: check time limit of Coluna
time_limit_reached!(node_state, env) && return

if ip_gap_closed(node_state, atol = ctx.params.opt_atol, rtol = ctx.params.opt_rtol)
setterminationstatus!(node_state, OPTIMAL)
elseif getterminationstatus(node_state) != TIME_LIMIT && getterminationstatus(node_state) != INFEASIBLE
Expand Down
14 changes: 12 additions & 2 deletions src/Algorithm/treesearch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
conqueralg::AbstractConquerAlgorithm = ColCutGenConquer(),
dividealg::AbstractDivideAlgorithm = Branching(),
explorestrategy::AbstractExploreStrategy = DepthFirstStrategy(),
maxnumnodes::Int = 100000,
opennodeslimit::Int = 100,
maxnumnodes = 100000,
opennodeslimit = 100,
timelimit = -1, # -1 means no time limit
opt_atol::Float64 = DEF_OPTIMALITY_ATOL,
opt_rtol::Float64 = DEF_OPTIMALITY_RTOL,
branchingtreefile = ""
Expand All @@ -18,6 +19,7 @@ to select the next node to treat.
Parameters :
- `maxnumnodes` : maximum number of nodes explored by the algorithm
- `opennodeslimit` : maximum number of nodes waiting to be explored
- `timelimit` : time limit in seconds of the algorithm
- `opt_atol` : optimality absolute tolerance (alpha)
- `opt_rtol` : optimality relative tolerance (alpha)

Expand All @@ -30,6 +32,7 @@ Options :
explorestrategy::AbstractExploreStrategy = DepthFirstStrategy()
maxnumnodes::Int64 = 100000
opennodeslimit::Int64 = 100
timelimit::Int64 = -1 # means no time limit
opt_atol::Float64 = Coluna.DEF_OPTIMALITY_ATOL
opt_rtol::Float64 = Coluna.DEF_OPTIMALITY_RTOL
branchingtreefile::String = ""
Expand All @@ -46,6 +49,13 @@ function get_child_algorithms(algo::TreeSearchAlgorithm, reform::Reformulation)
end

function run!(algo::TreeSearchAlgorithm, env::Env, reform::Reformulation, input::OptimizationState)
# TreeSearchAlgorithm is the only algorithm that changes the global time limit in the
# environment. However, time limit set from JuMP/MOI has priority.
if env.global_time_limit == -1
env.global_time_limit = algo.timelimit
else
@warn "Global time limit has been set through JuMP/MOI. Ignoring the time limit of TreeSearchAlgorithm."
end
search_space = new_space(search_space_type(algo), algo, reform, input)
return tree_search(algo.explorestrategy, search_space, env, input)
end
2 changes: 2 additions & 0 deletions src/Algorithm/treesearch/branch_and_bound.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ mutable struct BaBSearchSpace <: AbstractColunaSearchSpace
divide::AbstractDivideAlgorithm
max_num_nodes::Int64
open_nodes_limit::Int64
time_limit::Int64
opt_atol::Float64
opt_rtol::Float64
previous::Union{Nothing,Node}
Expand Down Expand Up @@ -116,6 +117,7 @@ function new_space(
algo.dividealg,
algo.maxnumnodes,
algo.opennodeslimit,
algo.timelimit,
algo.opt_atol,
algo.opt_rtol,
nothing,
Expand Down
13 changes: 12 additions & 1 deletion src/Algorithm/utilities/helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,15 @@ active_and_explicit((form, (id, _))) = iscuractive(form, id) && isexplicit(form,

duty((_, (id, _))) = getduty(id)

combine(op, args, functions...) = Iterators.mapreduce(f -> f(args...), op, functions)
combine(op, args, functions...) = Iterators.mapreduce(f -> f(args...), op, functions)

############################################################################################
# Time limit
############################################################################################
function time_limit_reached!(optim_state, env)
if Coluna.time_limit_reached(env)
setterminationstatus!(optim_state, TIME_LIMIT)
return true
end
return false
end
6 changes: 4 additions & 2 deletions src/env.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mutable struct Env{Id}
env_starting_time::DateTime
optim_starting_time::Union{Nothing, DateTime}
global_time_limit::Int64 # -1 means no time limit
params::Params
kpis::Kpis
form_counter::Int # 0 is for original form
Expand All @@ -11,9 +12,10 @@ mutable struct Env{Id}
end

Env{Id}(params::Params) where {Id} = Env{Id}(
now(), nothing, params, Kpis(nothing, nothing), 0, 0, 0,
now(), nothing, -1, params, Kpis(nothing, nothing), 0, 0, 0,
MOI.Utilities.CleverDicts.CleverDict{MOI.VariableIndex, Id}(),
Dict{DataType, Int}()
)
set_optim_start_time!(env::Env) = env.optim_starting_time = now()
elapsed_optim_time(env::Env) = Dates.toms(now() - env.optim_starting_time) / Dates.toms(Second(1))
elapsed_optim_time(env::Env) = Dates.toms(now() - env.optim_starting_time) / Dates.toms(Second(1))
time_limit_reached(env::Env) = env.global_time_limit > 0 && elapsed_optim_time(env) > env.global_time_limit