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

Vector of optimizers in Formulation #534

Merged
merged 13 commits into from
May 26, 2021
6 changes: 2 additions & 4 deletions src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@ include("formstorages.jl")
include("interface.jl")

# Basic algorithms
include("basic/solveipform.jl")
include("basic/solvelpform.jl")
include("basic/solveipform.jl")
include("basic/cutcallback.jl")
include("basic/pricingcallback.jl")

# Child algorithms used by conquer algorithms
include("pricing.jl")
include("colgenstabilization.jl")
include("colgen.jl")
include("benders.jl")
Expand Down Expand Up @@ -72,7 +70,7 @@ export getterminationstatus, setterminationstatus!,

# Algorithm's types
export AbstractOptimizationAlgorithm, TreeSearchAlgorithm, ColCutGenConquer, ColumnGeneration,
DefaultPricing, PricingCallback, BendersConquer, BendersCutGeneration, SolveIpForm,
BendersConquer, BendersCutGeneration, SolveIpForm, MoiOptimize, UserOptimizer,
SolveLpForm, ExactBranchingPhase, OnlyRestrictedMasterBranchingPhase, PreprocessAlgorithm,
RestrictedMasterIPHeuristic, OptimizationInput, OptimizationOutput, OptimizationState,
EmptyInput
Expand Down
50 changes: 0 additions & 50 deletions src/Algorithm/basic/pricingcallback.jl

This file was deleted.

224 changes: 163 additions & 61 deletions src/Algorithm/basic/solveipform.jl
Original file line number Diff line number Diff line change
@@ -1,29 +1,114 @@
################################################################################
# Parameters for each type of optimizer
################################################################################
"""
MoiOptimize(
time_limit = 600
deactivate_artificial_vars = false
enforce_integrality = false
get_dual_bound = true
)

User parameters for an optimizer that calls a subsolver through MathOptInterface.
"""
@with_kw struct MoiOptimize
time_limit::Int = 600
deactivate_artificial_vars::Bool = true
enforce_integrality::Bool = true
get_dual_bound::Bool = true
max_nb_ip_primal_sols::Int = 50
log_level::Int = 2
silent::Bool = true
end

"""
UserOptimize(
stage = 1
max_nb_ip_primal_sols = 50
)

User parameters for an optimizer that calls a callback to solve the problem.
"""
@with_kw struct UserOptimize
stage::Int = 1
max_nb_ip_primal_sols::Int = 50
end

"""
CustomOptimize()

User parameters for an optimizer that calls a custom solver to solve a custom model.
"""
struct CustomOptimize end

################################################################################
# Algorithm
################################################################################
"""
Coluna.Algorithm.SolveIpForm(
time_limit::Int = 600,
deactivate_artificial_vars = true,
enforce_integrality = true,
silent = true,
max_nb_ip_primal_sols = 50,
log_level = 0
optimizer_id = 1
moi_params = MoiOptimize()
user_params = UserOptimize()
custom_params = CustomOptimize()
)

Solve a mixed integer linear program.
Solve an optimization problem. It can call a :
- subsolver through MathOptInterface to optimize a mixed integer program
- pricing callback defined by the user
- custom optimizer to solve a custom model

The algorithms calls optimizer with id `optimizer_id`.
The user can specify different optimizers using the method `BlockDecomposition.specify!`.
In that case `optimizer_id` is the position of the optimizer in the array of optimizers
passed to `specify!`.
By default, the algorihm uses the first optimizer or the default optimizer if no
optimizer has been specified.

Depending on the type of the optimizer chosen, the algorithm will use one the
three configurations : `moi_params`, `user_params`, or `custom_params`.
"""
@with_kw struct SolveIpForm <: AbstractOptimizationAlgorithm
time_limit::Int = 600
deactivate_artificial_vars = true
enforce_integrality = true
get_dual_bound = true
silent = true
max_nb_ip_primal_sols = 50
log_level = 0
optimizer_id::Int = 1
moi_params::MoiOptimize = MoiOptimize()
user_params::UserOptimize = UserOptimize()
custom_params::CustomOptimize = CustomOptimize()
end

# SolveIpForm does not have child algorithms, therefore get_child_algorithms() is not defined

# Dispatch on the type of the optimizer to return the parameters
_optimizer_params(algo::SolveIpForm, ::MoiOptimizer) = algo.moi_params
_optimizer_params(algo::SolveIpForm, ::UserOptimizer) = algo.user_params
# TODO : custom optimizer
_optimizer_params(::SolveIpForm, ::NoOptimizer) = nothing

function run!(algo::SolveIpForm, env::Env, form::Formulation, input::OptimizationInput)::OptimizationOutput
opt = getoptimizer(form, algo.optimizer_id)
params = _optimizer_params(algo, opt)
if params !== nothing
return run!(params, env, form, input; optimizer_id = algo.optimizer_id)
end
Comment on lines +86 to +90
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that there is some dispatch happening in runtime on lines 86, 87 and 89.
I think we should open an issue to investigate the impact later

return error("Cannot optimize formulation with optimizer of type $(typeof(opt)).")
end

run!(algo::SolveIpForm, env::Env, reform::Reformulation, input::OptimizationInput) =
run!(algo, env, getmaster(reform), input)

################################################################################
# Get units usage (depends on the type of the optimizer)
################################################################################
function get_units_usage(algo::SolveIpForm, form::Formulation)
opt = getoptimizer(form, algo.optimizer_id)
params = _optimizer_params(algo, opt)
if params !== nothing
return get_units_usage(params, form)
end
return error("Cannot get units usage of optimizer of type $(typeof(opt)).")
end

# get_units_usage of MoiOptimize
function get_units_usage(
algo::SolveIpForm, form::Formulation{Duty}
::MoiOptimize, form::Formulation{Duty}
) where {Duty<:MathProg.AbstractFormDuty}
# we use storage units in the read only mode, as all modifications
# (deactivating artificial vars and enforcing integrality)
Expand All @@ -40,31 +125,48 @@ function get_units_usage(
return units_usage
end

get_units_usage(algo::SolveIpForm, reform::Reformulation) =
get_units_usage(algo::SolveIpForm, reform::Reformulation) =
get_units_usage(algo, getmaster(reform))

# get_units_usage of UserOptimize
function get_units_usage(::UserOptimize, spform::Formulation{DwSp})
units_usage = Tuple{AbstractModel, UnitType, UnitAccessMode}[]
push!(units_usage, (spform, StaticVarConstrUnit, READ_ONLY))
return units_usage
end

# TODO : get_units_usage of CustomOptimize

################################################################################
# run! methods (depends on the type of the optimizer)
################################################################################

function check_if_optimizer_supports_ip(optimizer::MoiOptimizer)
return MOI.supports_constraint(optimizer.inner, MOI.SingleVariable, MOI.Integer)
end
check_if_optimizer_supports_ip(optimizer::UserOptimizer) = false
check_if_optimizer_supports_ip(optimizer::NoOptimizer) = false

function run!(algo::SolveIpForm, env::Env, form::Formulation, input::OptimizationInput)::OptimizationOutput

# run! of MoiOptimize
function run!(
algo::MoiOptimize, ::Env, form::Formulation, input::OptimizationInput;
optimizer_id::Int = 1
)::OptimizationOutput
result = OptimizationState(
form,
ip_primal_bound = get_ip_primal_bound(getoptstate(input)),
max_length_ip_primal_sols = algo.max_nb_ip_primal_sols
)

ip_supported = check_if_optimizer_supports_ip(getmoioptimizer(form))
ip_supported = check_if_optimizer_supports_ip(getoptimizer(form, optimizer_id))
if !ip_supported
@warn "Optimizer of formulation with id =", getuid(form),
" does not support integer variables. Skip SolveIpForm algorithm."
setterminationstatus!(result, UNKNOWN_TERMINATION_STATUS)
return OptimizationOutput(result)
end

primal_sols = optimize_ip_form!(algo, getmoioptimizer(form), form, result)
primal_sols = optimize_ip_form!(algo, getoptimizer(form, optimizer_id), form, result)

partial_sol = nothing
partial_sol_value = 0.0
Expand All @@ -73,7 +175,7 @@ function run!(algo::SolveIpForm, env::Env, form::Formulation, input::Optimizatio
partial_sol = get_primal_solution(partsolunit, form)
partial_sol_value = getvalue(partial_sol)
end

if length(primal_sols) > 0
if partial_sol !== nothing
for primal_sol in primal_sols
Expand Down Expand Up @@ -104,47 +206,8 @@ function run!(algo::SolveIpForm, env::Env, form::Formulation, input::Optimizatio
return OptimizationOutput(result)
end

run!(algo::SolveIpForm, env::Env, reform::Reformulation, input::OptimizationInput) =
run!(algo, env, getmaster(reform), input)

function termination_status!(result::OptimizationState, optimizer::MoiOptimizer)
terminationstatus = MOI.get(getinner(optimizer), MOI.TerminationStatus())
if terminationstatus != MOI.INFEASIBLE &&
terminationstatus != MOI.DUAL_INFEASIBLE &&
terminationstatus != MOI.INFEASIBLE_OR_UNBOUNDED &&
terminationstatus != MOI.OPTIMIZE_NOT_CALLED &&
terminationstatus != MOI.INVALID_MODEL &&
terminationstatus != MOI.TIME_LIMIT

setterminationstatus!(result, convert_status(terminationstatus))

if MOI.get(getinner(optimizer), MOI.ResultCount()) <= 0
msg = """
Termination status = $(terminationstatus) but no results.
Please, open an issue at https://github.com/atoptima/Coluna.jl/issues
"""
error(msg)
end
else
@warn "Solver has no result to show."
setterminationstatus!(result, INFEASIBLE)
end
return
end

function optimize_with_moi!(optimizer::MoiOptimizer, form::Formulation, result::OptimizationState)
sync_solver!(optimizer, form)
nbvars = MOI.get(form.moioptimizer.inner, MOI.NumberOfVariables())
if nbvars <= 0
@warn "No variable in the formulation."
end
MOI.optimize!(getinner(optimizer))
termination_status!(result, optimizer)
return
end

function optimize_ip_form!(
algo::SolveIpForm, optimizer::MoiOptimizer, form::Formulation, result::OptimizationState
algo::MoiOptimize, optimizer::MoiOptimizer, form::Formulation, result::OptimizationState
)
MOI.set(optimizer.inner, MOI.TimeLimitSec(), algo.time_limit)
MOI.set(optimizer.inner, MOI.Silent(), algo.silent)
Expand All @@ -169,3 +232,42 @@ function optimize_ip_form!(
end
return primal_sols
end

# run! of UserOptimize
function run!(
algo::UserOptimize, ::Env, spform::Formulation{DwSp}, input::OptimizationInput;
optimizer_id::Int = 1
)::OptimizationOutput
result = OptimizationState(
spform,
ip_primal_bound = get_ip_primal_bound(getoptstate(input)),
max_length_ip_primal_sols = algo.max_nb_ip_primal_sols
)

optimizer = getoptimizer(spform, optimizer_id)
cbdata = MathProg.PricingCallbackData(spform, algo.stage)
optimizer.user_oracle(cbdata)

if length(cbdata.primal_solutions) > 0
for primal_sol in cbdata.primal_solutions
add_ip_primal_sol!(result, primal_sol)
end

if algo.stage == 1 # stage 1 is exact by convention
dual_bound = getvalue(get_ip_primal_bound(result))
set_ip_dual_bound!(result, DualBound(spform, dual_bound))
setterminationstatus!(result, OPTIMAL)
else
setterminationstatus!(result, OTHER_LIMIT)
end
else
if algo.stage == 1
setterminationstatus!(result, INFEASIBLE)
else
setterminationstatus!(result, OTHER_LIMIT)
end
end
return OptimizationOutput(result)
end

# TODO : run! of CustomOptimize
Loading