Skip to content

Commit

Permalink
Heuristic interface + extract restricted master implem from conquer (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
guimarqu authored Apr 26, 2023
1 parent b29f794 commit a1bcbf5
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 37 deletions.
6 changes: 5 additions & 1 deletion src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using DataStructures
import MathOptInterface
import TimerOutputs

using ..Coluna, ..ColunaBase, ..MathProg, ..MustImplement, ..ColGen, ..Benders, ..AlgoAPI, ..TreeSearch, ..Branching
using ..Coluna, ..ColunaBase, ..MathProg, ..MustImplement, ..ColGen, ..Benders, ..AlgoAPI, ..TreeSearch, ..Branching, ..Heuristic

using Crayons, DynamicSparseArrays, Logging, Parameters, Printf, Random, Statistics, SparseArrays, LinearAlgebra

Expand Down Expand Up @@ -64,6 +64,10 @@ include("branching/single_var_branching.jl")
include("branching/printer.jl")
include("branching/branchingalgo.jl")

# Heuristics
include("heuristic/restricted_master.jl")

# Tree search
include("treesearch.jl")
include("treesearch/printer.jl")
include("treesearch/branch_and_bound.jl")
Expand Down
5 changes: 1 addition & 4 deletions src/Algorithm/branchcutprice.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,7 @@ function BranchCutAndPriceAlgorithm(;
heuristics = ParameterizedHeuristic[]
if restmastipheur_timelimit > 0
heuristic = ParameterizedHeuristic(
SolveIpForm(moi_params = MoiOptimize(
get_dual_bound = false,
time_limit = restmastipheur_timelimit
)),
RestrictedMasterHeuristic(),
1.0, 1.0, restmastipheur_frequency,
restmastipheur_maxdepth, "Restricted Master IP"
)
Expand Down
41 changes: 9 additions & 32 deletions src/Algorithm/conquer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@
# ParameterizedHeuristic
####################################################################

"""
Coluna.Algorithm.RestrictedMasterHeuristic()
This algorithm enforces integrality of column variables in the master formulation and then
solves the master formulation with its optimizer.
"""
RestrictedMasterIPHeuristic() = SolveIpForm(moi_params = MoiOptimize(get_dual_bound = false))

struct ParameterizedHeuristic{OptimAlgorithm<:AbstractOptimizationAlgorithm}
struct ParameterizedHeuristic{OptimAlgorithm}
algorithm::OptimAlgorithm
root_priority::Float64
nonroot_priority::Float64
Expand All @@ -21,7 +13,7 @@ end

ParamRestrictedMasterHeuristic() =
ParameterizedHeuristic(
RestrictedMasterIPHeuristic(),
RestrictedMasterHeuristic(),
1.0, 1.0, 1, 1000, "Restricted Master IP"
)

Expand Down Expand Up @@ -153,9 +145,9 @@ function get_child_algorithms(algo::ColCutGenConquer, reform::Reformulation)
if !isnothing(algo.preprocess)
push!(child_algos, (algo.preprocess, reform))
end
for heuristic in algo.primal_heuristics
push!(child_algos, (heuristic.algorithm, reform))
end
# for heuristic in algo.primal_heuristics
# push!(child_algos, (heuristic.algorithm, reform))
# end
if !isnothing(algo.before_cutgen_user_algorithm)
push!(child_algos, (algo.before_cutgen_user_algorithm, reform))
end
Expand Down Expand Up @@ -272,7 +264,7 @@ function get_heuristics_to_run(ctx::ColCutGenContext, node)
end

# run_heuristics!
function run_heuristics!(ctx::ColCutGenContext, heuristics, env, reform, node_state,)
function run_heuristics!(ctx::ColCutGenContext, heuristics, env, reform, node_state)
for heuristic in heuristics
# TODO: check time limit of Coluna

Expand All @@ -284,24 +276,9 @@ function run_heuristics!(ctx::ColCutGenContext, heuristics, env, reform, node_st
records = create_records(reform)
end

heur_output = run!(heuristic.algorithm, env, reform, node_state)
if getterminationstatus(heur_output) == TIME_LIMIT
setterminationstatus!(node_state, TIME_LIMIT)
end

ip_primal_sols = get_ip_primal_sols(heur_output)
if !isnothing(ip_primal_sols) && length(ip_primal_sols) > 0
# we start with worst solution to add all improving solutions
for sol in sort(ip_primal_sols)
cutgen = CutCallbacks(call_robust_facultative = false)
# TODO (Ruslan): Heuristics should ensure themselves that the returned solution is feasible (Ruslan)
# NOTE (Guillaume): I don't know how we can do that because the heuristic should not have
# access to the cut callback algorithm.
cutcb_output = run!(cutgen, env, getmaster(reform), CutCallbacksInput(sol))
if cutcb_output.nb_cuts_added == 0
update_ip_primal_sol!(node_state, sol)
end
end
output = AlgoAPI.run!(heuristic.algorithm, env, getmaster(reform), get_best_ip_primal_sol(node_state))
for sol in Heuristic.get_primal_sols(output)
update_ip_primal_sol!(node_state, sol)
end

if ismanager(heuristic.algorithm)
Expand Down
46 changes: 46 additions & 0 deletions src/Algorithm/heuristic/restricted_master.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
The restricted master heuristic enforces integrality of the master column variables and
optimizes the master problem restricted to active master column variables using a MIP solver.
If the heuristic finds a solution, it checks that this solution does not violate any essential
cut.
"""
struct RestrictedMasterHeuristic <: Heuristic.AbstractHeuristic end

ismanager(::RestrictedMasterHeuristic) = false

struct RestrictedMasterHeuristicOutput <: Heuristic.AbstractHeuristicOutput
ip_primal_sols::Vector{PrimalSolution}
end

Heuristic.get_primal_sols(o::RestrictedMasterHeuristicOutput) = o.ip_primal_sols

function Heuristic.run(::RestrictedMasterHeuristic, env, master, cur_inc_primal_sol)
solve_ip_form = SolveIpForm(moi_params = MoiOptimize(get_dual_bound = false))

input = OptimizationState(master)
if !isnothing(cur_inc_primal_sol)
update_ip_primal_sol!(input, cur_inc_primal_sol)
end

ip_form_output = run!(solve_ip_form, env, master, input)
return RestrictedMasterHeuristicOutput(get_ip_primal_sols(ip_form_output))
end

function AlgoAPI.run!(::RestrictedMasterHeuristic, env, master, cur_inc_primal_sol)
output = Heuristic.run(RestrictedMasterHeuristic(), env, master, cur_inc_primal_sol)
ip_primal_sols = Heuristic.get_primal_sols(output)

# We need to make sure that the solution is feasible by separating essential cuts and then
# project the solution on master.
feasible_ip_primal_sols = PrimalSolution[]
if length(ip_primal_sols) > 0
for sol in sort(ip_primal_sols) # we start with worst solution to add all improving solutions
cutgen = CutCallbacks(call_robust_facultative = false)
cutcb_output = run!(cutgen, env, master, CutCallbacksInput(sol))
if cutcb_output.nb_cuts_added == 0
push!(feasible_ip_primal_sols, sol)
end
end
end
return RestrictedMasterHeuristicOutput(feasible_ip_primal_sols)
end
3 changes: 3 additions & 0 deletions src/Coluna.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ using .ColGen
include("Benders/Benders.jl")
using .Benders

include("Heuristic/Heuristic.jl")
using .Heuristic

include("Algorithm/Algorithm.jl")
using .Algorithm

Expand Down
29 changes: 29 additions & 0 deletions src/Heuristic/Heuristic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Heuristic

!true && include("../MustImplement/MustImplement.jl") # linter
using ..MustImplement

!true && include("../interface.jl") # linter
using ..AlgoAPI

"Supertype for heuristic."
abstract type AbstractHeuristic end

"""
Output of a heuristic algorithm.
"""
abstract type AbstractHeuristicOutput end

"Returns a collection of primal solutions found by the heuristic."
@mustimplement "HeuristicOutput" get_primal_sols(::AbstractHeuristicOutput) = nothing

"""
run the heuristic using following arguments:
- `form`: a formulation (or any representation) of the problem
- `cur_inc_primal_sol`: current incumbent primal solution
and returns an `AbstractHeuristicOutput` object.
"""
@mustimplement "Heuristic" run(::AbstractHeuristic, env, form, cur_inc_primal_sol) = nothing

end

0 comments on commit a1bcbf5

Please sign in to comment.