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

New structures for Solutions & Results #286

Merged
merged 12 commits into from
Mar 13, 2020
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
## Documentation: http://docs.travis-ci.com/user/languages/julia/

language: julia

os:
- linux
# - osx

julia:
- 1.1
- 1.2
- 1.3

notifications:
email: false
Expand All @@ -32,12 +32,12 @@ script: # the default script is equivalent to the following
- julia --project=test/ -e 'using Pkg; Pkg.clone("https://github.com/atoptima/ColunaDemos.jl.git"); Pkg.clone(pwd()); Pkg.build("Coluna"); Pkg.test("Coluna"; coverage=true)';

after_success:
- julia -e 'using Pkg; Pkg.add("Coverage"); cd(Pkg.dir("Coluna")); using Coverage; Codecov.submit(Codecov.process_folder())'
- julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())'

jobs:
include:
- stage: "Documentation"
julia: 1.2
julia: 1.3
os: linux
script:
- julia --project=docs/ -e 'using Pkg; Pkg.clone("https://github.com/atoptima/BlockDecomposition.jl.git"); Pkg.clone("https://github.com/atoptima/ColunaDemos.jl.git"); Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
Expand Down
43 changes: 30 additions & 13 deletions src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ import DataStructures
import MathOptInterface
import TimerOutputs

import ..Coluna
using ..Coluna
using ..Containers
using ..MathProg

# To be deleted :
import .MathProg: getsense, optimize! # because of branch

# TO be deleted ???
import .MathProg: FeasibilityStatus, TerminationStatus, AbstractStorage, EmptyStorage, getstorage, setprimalbound!, setdualbound!
import .MathProg: OPTIMAL, TIME_LIMIT, NODE_LIMIT, OTHER_LIMIT, EMPTY_RESULT, NOT_YET_DETERMINED
import ..Coluna: AbstractSense

import .MathProg: get_lp_primal_sol, get_lp_dual_bound, setfeasibilitystatus!, setterminationstatus!, getvalue
import .MathProg: getvalue

using Logging
using Printf
Expand All @@ -27,17 +24,25 @@ const MOI = MathOptInterface

import Base: push!

# Abstract algorithm
include("interface.jl")
# Import to extend methods to OptimizationState
import .MathProg: getfeasibilitystatus, getterminationstatus, setfeasibilitystatus!,
setterminationstatus!, isfeasible, get_ip_primal_bound, get_ip_dual_bound,
get_lp_primal_bound, get_lp_dual_bound, update_ip_primal_bound!, update_ip_dual_bound!,
update_lp_primal_bound!, update_lp_dual_bound!, set_ip_primal_bound!,
set_ip_dual_bound!, set_lp_primal_bound!, set_lp_dual_bound!, ip_gap

# Utilities to build algorithms
include("utilities/optimizationstate.jl")
include("utilities/record.jl")

# Abstract record
include("record.jl")
# Abstract algorithm (interface should be moved in Containers)
include("interface.jl")

# Here include slave algorithms used by conquer algorithms
include("solveipform.jl")
include("solvelpform.jl")
include("colgen.jl")
include("benders.jl")
include("masteripheur.jl")
include("masterlp.jl")
include("preprocessing.jl")

# Here include conquer algorithms
Expand All @@ -55,9 +60,21 @@ include("branching/branchingalgo.jl")

include("treesearch.jl")

# Types
# Algorithm should export only methods usefull to define & parametrize algorithms, and
# data structures from utilities.
# Other Coluna's submodules should be independent to Algorithm

# Utilities
export OptimizationState, getterminationstatus, getfeasibilitystatus, setterminationstatus!,
setfeasibilitystatus!, isfeasible, nb_ip_primal_sols, nb_lp_primal_sols, nb_lp_dual_sols,
get_ip_primal_sols, get_lp_primal_sols, get_lp_dual_sols, get_best_ip_primal_sol,
get_best_lp_primal_sol, get_best_lp_dual_sol, update_ip_primal_sol!,
update_lp_primal_sol!, update_lp_dual_sol!, add_ip_primal_sol!, add_lp_primal_sol!,
add_lp_dual_sol!, set_ip_primal_sol!, set_lp_primal_sol!, set_lp_dual_sol!

# Algorithm's types
export AbstractOptimizationAlgorithm, TreeSearchAlgorithm, ColGenConquer, ColumnGeneration,
BendersConquer, BendersCutGeneration, MasterIpHeuristic, ExactBranchingPhase,
BendersConquer, BendersCutGeneration, SolveIpForm, SolveLpForm, ExactBranchingPhase,
OnlyRestrictedMasterBranchingPhase

end
98 changes: 50 additions & 48 deletions src/Algorithm/benders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ Base.@kwdef struct BendersCutGeneration <: AbstractOptimizationAlgorithm
end

mutable struct BendersCutGenRuntimeData
incumbents::Incumbents
incumbents::OptimizationState
has_converged::Bool
is_feasible::Bool
spform_phase::Dict{Int, FormulationPhase}
spform_phase_applied::Dict{Int, Bool}
spform_phase::Dict{FormId, FormulationPhase}
spform_phase_applied::Dict{FormId, Bool}
#slack_cost_increase::Float64
#slack_cost_increase_applied::Bool
end
Expand All @@ -23,37 +23,38 @@ function all_sp_in_phase2(algdata::BendersCutGenRuntimeData)
return true
end

function BendersCutGenRuntimeData(S::Type{<:Coluna.AbstractSense}, node_inc::Incumbents)
i = Incumbents(S)
update_ip_primal_sol!(i, get_ip_primal_sol(node_inc))

function BendersCutGenRuntimeData(form::Reformulation, node_inc::OptimizationState)
i = OptimizationState(getmaster(form))
if nb_ip_primal_sols(node_inc) > 0
add_ip_primal_sol!(i, get_best_ip_primal_sol(node_inc))
end
return BendersCutGenRuntimeData(i, false, true, Dict{FormId, FormulationPhase}(), Dict{FormId, Bool}())#0.0, true)
end

function run!(algo::BendersCutGeneration, reform::Reformulation, input::OptimizationInput)::OptimizationOutput
function run!(algo::BendersCutGeneration, reform::Reformulation, input::NewOptimizationInput)::OptimizationOutput

initincumb = getincumbents(input)
data = BendersCutGenRuntimeData(reform.master.obj_sense, initincumb)
init_res = getinputresult(input)
data = BendersCutGenRuntimeData(reform, init_res)
@logmsg LogLevel(-1) "Run BendersCutGeneration."
Base.@time bend_rec = bend_cutting_plane_main_loop!(algo, data, reform)

Sense = getsense(initincumb)
ip_primal_sols = Vector{PrimalSolution{Sense}}()
if length(get_ip_primal_sol(data.incumbents)) > 0
push!(ip_primal_sols, get_ip_primal_sol(data.incumbents))
end

return OptimizationOutput(
OptimizationResult{Sense}(
data.has_converged ? OPTIMAL : OTHER_LIMIT,
data.is_feasible ? FEASIBLE : INFEASIBLE,
get_ip_primal_bound(data.incumbents), get_ip_dual_bound(data.incumbents),
ip_primal_sols, Vector{DualSolution{Sense}}()
),
get_lp_primal_sol(data.incumbents),
get_lp_dual_bound(data.incumbents)
runtime_res = data.incumbents
result = OptimizationState(
getmaster(reform),
feasibility_status = data.is_feasible ? FEASIBLE : INFEASIBLE,
termination_status = data.has_converged ? OPTIMAL : OTHER_LIMIT,
ip_dual_bound = get_ip_dual_bound(runtime_res),
lp_dual_bound = get_lp_dual_bound(runtime_res)
)

if nb_lp_primal_sols(runtime_res) > 0
add_lp_primal_sol!(result, get_best_lp_primal_sol(runtime_res))
end
if nb_ip_primal_sols(runtime_res) > 0
for ip_primal_sol in get_ip_primal_sols(runtime_res)
add_ip_primal_sol!(result, ip_primal_sol)
end
end
return OptimizationOutput(result)
end

function update_benders_sp_slackvar_cost_for_ph1!(spform::Formulation)
Expand Down Expand Up @@ -93,8 +94,8 @@ end

function update_benders_sp_problem!(
algo::BendersCutGeneration, algdata::BendersCutGenRuntimeData, spform::Formulation,
master_primal_sol::PrimalSolution{S}, master_dual_sol::DualSolution{S}
) where {S}
master_primal_sol::PrimalSolution, master_dual_sol::DualSolution
)
masterform = spform.parent_formulation

# Update rhs of technological constraints
Expand Down Expand Up @@ -162,8 +163,8 @@ end

function record_solutions!(
algo::BendersCutGeneration, algdata::BendersCutGenRuntimeData, spform::Formulation,
spresult::OptimizationResult{S}
)::Vector{ConstrId} where {S}
spresult::MoiResult
)::Vector{ConstrId}

recorded_dual_solution_ids = Vector{ConstrId}()

Expand Down Expand Up @@ -213,8 +214,8 @@ function insert_cuts_in_master!(
end

function compute_benders_sp_lagrangian_bound_contrib(
algdata::BendersCutGenRuntimeData, spform::Formulation, spsol::OptimizationResult{S}
) where {S}
algdata::BendersCutGenRuntimeData, spform::Formulation, spsol::MoiResult
)
dualsol = getbestdualsol(spsol)
contrib = getvalue(dualsol)
return contrib
Expand All @@ -223,10 +224,10 @@ end
function solve_sp_to_gencut!(
algo::BendersCutGeneration, algdata::BendersCutGenRuntimeData,
masterform::Formulation, spform::Formulation,
master_primal_sol::PrimalSolution{S}, master_dual_sol::DualSolution{S},
master_primal_sol::PrimalSolution, master_dual_sol::DualSolution,
up_to_phase::FormulationPhase
)::Tuple{Bool, Bool, Vector{ConstrId}, Float64, Float64} where {S}

)::Tuple{Bool, Bool, Vector{ConstrId}, Float64, Float64}
S = getobjsense(masterform)
recorded_dual_solution_ids = Vector{ConstrId}()
sp_is_feasible = true

Expand Down Expand Up @@ -278,7 +279,7 @@ function solve_sp_to_gencut!(
benders_sp_lagrangian_bound_contrib = compute_benders_sp_lagrangian_bound_contrib(algdata, spform, optresult)

primalsol = getbestprimalsol(optresult)
spsol_relaxed = contains(spform, primalsol, BendSpSlackFirstStageVar)
spsol_relaxed = contains(primalsol, varid -> getduty(varid) == BendSpSlackFirstStageVar)

benders_sp_primal_bound_contrib = 0.0
# compute benders_sp_primal_bound_contrib which stands for the sum of nu var,
Expand Down Expand Up @@ -347,9 +348,9 @@ end

function solve_sps_to_gencuts!(
algo::BendersCutGeneration, algdata::BendersCutGenRuntimeData,
reform::Reformulation, master_primalsol::PrimalSolution{S},
master_dualsol::DualSolution{S}, up_to_phase::FormulationPhase
) where {S}
reform::Reformulation, master_primalsol::PrimalSolution,
master_dualsol::DualSolution, up_to_phase::FormulationPhase
)

nb_new_cuts = 0
spsols_relaxed = false
Expand Down Expand Up @@ -399,16 +400,16 @@ end


function compute_master_pb_contrib(algdata::BendersCutGenRuntimeData, master::Formulation,
restricted_master_sol_value::DualBound{S}) where {S}
restricted_master_sol_value)
# TODO: will change with stabilization
return PrimalBound(master, getvalue(restricted_master_sol_value))
return PrimalBound(master, restricted_master_sol_value)
end

function update_lagrangian_pb!(algdata::BendersCutGenRuntimeData, reform::Reformulation,
restricted_master_sol_dual_sol::DualSolution{S},
benders_sp_sp_primal_bound_contrib) where {S}
restricted_master_sol_dual_sol::DualSolution,
benders_sp_sp_primal_bound_contrib)
master = getmaster(reform)
restricted_master_sol_value = getbound(restricted_master_sol_dual_sol)
restricted_master_sol_value = getvalue(restricted_master_sol_dual_sol)
lagran_bnd = PrimalBound(master, 0.0)
lagran_bnd += compute_master_pb_contrib(algdata, master, restricted_master_sol_value)
lagran_bnd += benders_sp_sp_primal_bound_contrib
Expand All @@ -425,9 +426,10 @@ end

function generatecuts!(
algo::BendersCutGeneration, algdata::BendersCutGenRuntimeData, reform::Reformulation,
master_primal_sol::PrimalSolution{S}, master_dual_sol::DualSolution{S}, phase::FormulationPhase
)::Tuple{Int, Bool, PrimalBound{S}} where {S}
master_primal_sol::PrimalSolution, master_dual_sol::DualSolution, phase::FormulationPhase
)::Tuple{Int, Bool, PrimalBound}
masterform = getmaster(reform)
S = getobjsense(masterform)
filtered_dual_sol = filter(elem -> getduty(elem[1]) == MasterPureConstr, master_dual_sol)

## TODO stabilization : move the following code inside a loop
Expand All @@ -442,7 +444,8 @@ function generatecuts!(
end
# end TODO
#primal_bound = PrimalBound(masterform, getvalue(master_primal_sol) + getvalue(pb_correction))
primal_bound = getbound(master_primal_sol) + pb_correction
val = getvalue(master_primal_sol) + pb_correction
primal_bound = PrimalBound(masterform, float(val))
#setvalue!(master_primal_sol, getvalue(master_primal_sol) + pb_correction)
return nb_new_cuts, spsols_relaxed, primal_bound
end
Expand Down Expand Up @@ -573,7 +576,6 @@ function bend_cutting_plane_main_loop!(
end
if sol_integer
update_ip_primal_sol!(algdata.incumbents, master_primal_sol)
update_ip_primal_bound!(algdata.incumbents, primal_bound)
end
end
return
Expand Down
39 changes: 21 additions & 18 deletions src/Algorithm/branching/branchingalgo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ end
function run!(algo::NoBranching, reform::Reformulation, input::DivideInput)::DivideOutput
parent = getparent(input)
parent_incumb = getincumbents(parent)
Sense = getsense(parent_incumb)
result = OptimizationResult{Sense}()
result = OptimizationState(getmaster(reform))
return DivideOutput([], result)
end

Expand Down Expand Up @@ -91,7 +90,7 @@ end

function perform_strong_branching_with_phases!(
algo::StrongBranching, reform::Reformulation, parent::Node,
groups::Vector{BranchingGroup}, result::OptimizationResult
groups::Vector{BranchingGroup}, result::OptimizationState
)
for (phase_index, current_phase) in enumerate(algo.phases)
nb_candidates_for_next_phase::Int64 = 1
Expand Down Expand Up @@ -186,13 +185,12 @@ end

function run!(algo::StrongBranching, reform::Reformulation, input::DivideInput)::DivideOutput
parent = getparent(input)
parent_incumb = getincumbents(parent)
sense = getsense(parent_incumb)
result = OptimizationResult{sense}()
setprimalbound!(result, input.ip_primal_bound)
setdualbound!(result, get_ip_dual_bound(parent_incumb))
parent_incumb_res = getincumbentresult(parent)
result = OptimizationState(getmaster(reform))
set_ip_primal_bound!(result, input.ip_primal_bound)
set_ip_dual_bound!(result, get_ip_dual_bound(parent_incumb_res))
if isempty(algo.rules)
@logmsg LogLevel(0) "No branching rule is defined. No children will be generated."
@logmsg LogLevel(1) "No branching rule is defined. No children will be generated."
return DivideOutput([], result)
end

Expand All @@ -204,13 +202,18 @@ function run!(algo::StrongBranching, reform::Reformulation, input::DivideInput):

# we obtain the original and extended solutions
master = getmaster(reform)
original_solution = PrimalSolution{sense}()
extended_solution = PrimalSolution{sense}()
if projection_is_possible(master)
extended_solution = get_lp_primal_sol(parent.incumbents)
original_solution = proj_cols_on_rep(extended_solution, master)
original_solution = PrimalSolution(getmaster(reform))
extended_solution = PrimalSolution(getmaster(reform))
if nb_lp_primal_sols(parent_incumb_res) > 0
if projection_is_possible(master)
extended_solution = get_best_lp_primal_sol(parent_incumb_res)
original_solution = proj_cols_on_rep(extended_solution, master)
else
original_solution = get_best_lp_primal_sol(parent_incumb_res)
end
else
original_solution = get_lp_primal_sol(parent.incumbents)
@logmsg LogLevel(1) "No branching candidates found. No children will be generated."
return DivideOutput(Vector{Node}(), result)
end

# phase 0 of branching : we ask branching rules to generate branching candidates
Expand Down Expand Up @@ -244,7 +247,7 @@ function run!(algo::StrongBranching, reform::Reformulation, input::DivideInput):
nb_candidates_found += length(output.groups)
append!(kept_branch_groups, output.groups)
local_id = output.local_id

if projection_is_possible(master)
output = run!(rule, reform, BranchingRuleInput(
extended_solution, false, nb_candidates_needed, algo.selection_criterion, local_id
Expand All @@ -264,9 +267,9 @@ function run!(algo::StrongBranching, reform::Reformulation, input::DivideInput):
resize!(kept_branch_groups, nb_candidates_needed)
end
end

if isempty(kept_branch_groups)
@logmsg LogLevel(0) "No branching candidates found. No children will be generated."
@logmsg LogLevel(1) "No branching candidates found. No children will be generated."
return DivideOutput(Vector{Node}(), result)
end

Expand Down
Loading