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

Branching priority #103

Merged
merged 3 commits into from
Dec 6, 2023
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode/settings.json
Manifest.toml
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "BlockDecomposition"
uuid = "6cde8614-403a-11e9-12f1-c10d0f0caca0"
authors = ["Guillaume Marques", "Vitor Nesello", "François Vanderbeck"]
version = "1.14.1"
version = "1.15.0"

[deps]
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
Expand Down
2 changes: 1 addition & 1 deletion src/BlockDecomposition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ include("subproblem_representative.jl")
include("checker.jl")
include("decomposition.jl")
include("objective.jl")
include("customdata.jl")
include("callbacks.jl")
include("automatic_dantzig_wolfe.jl")
include("utils.jl")
include("branchingpriority.jl")
include("customdata.jl")
include("soldisaggregation.jl")

function model_factory(::Val{true}, optimizer; kw...)::JuMP.Model
Expand Down
11 changes: 8 additions & 3 deletions src/annotations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mutable struct Annotation{T, F<:Formulation, D<:Decomposition}
axis_index_value::T
lower_multiplicity::Float64
upper_multiplicity::Float64
branching_priority::Float64
optimizer_builders::Vector
end

Expand All @@ -31,24 +32,26 @@ getdecomposition(a::Annotation) = a.decomposition
getlowermultiplicity(a::Annotation) = a.lower_multiplicity
getuppermultiplicity(a::Annotation) = a.upper_multiplicity
getoptimizerbuilders(a::Annotation) = a.optimizer_builders
getbranchingpriority(a::Annotation) = a.branching_priority

setlowermultiplicity!(a::Annotation, lm::Real) = a.lower_multiplicity = lm
setuppermultiplicity!(a::Annotation, um::Real) = a.upper_multiplicity = um
setbranchingpriority!(a::Annotation, bp::Real) = a.branching_priority = bp
emptyoptimizerbuilders!(a::Annotation) = empty!(a.optimizer_builders)
pushoptimizerbuilder!(a::Annotation, f::MOI.AbstractOptimizer) = push!(a.optimizer_builders, f)
pushoptimizerbuilder!(a::Annotation, f::Function) = push!(a.optimizer_builders, f)
pushoptimizerbuilder!(a::Annotation, f::AbstractCustomOptimizer) = push!(a.optimizer_builders, f)

OriginalAnnotation() = Annotation(0, 0, Original, NoDecomposition, 0, 1.0, 1.0, [])
OriginalAnnotation() = Annotation(0, 0, Original, NoDecomposition, 0, 1.0, 1.0, 1.0, [])

function MasterAnnotation(tree, D::Type{<:Decomposition})
uid = generateannotationid(tree)
return Annotation(uid, 0, Master, D, 0, 1.0, 1.0, [])
return Annotation(uid, 0, Master, D, 0, 1.0, 1.0, 1.0, [])
end

function Annotation(tree, F::Type{<:Formulation}, D::Type{<:Decomposition}, v)
uid = generateannotationid(tree)
return Annotation(uid, 0, F, D, v, 1.0, 1.0, [])
return Annotation(uid, 0, F, D, v, 1.0, 1.0, 1.0, [])
end

function Base.show(io::IO, a::Annotation)
Expand All @@ -60,6 +63,8 @@ function Base.show(io::IO, a::Annotation)
print(io, getlowermultiplicity(a))
print(io, ", um = ")
print(io, getuppermultiplicity(a))
print(io, ", bp = ")
print(io, getbranchingpriority(a))
print(io, ", id = ")
print(io, getid(a))
print(io, ")")
Expand Down
2 changes: 2 additions & 0 deletions src/branchingpriority.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The idea is to have both "soft" and "hard" branching priorities. For instance :
- if the number of branching candidates with priority 4.0 is less than the maximum number of candidates considered, no branching candidates with priority 3.0 will be considered
- if the number of branching candidates with priority 3.5 is less than the maximum number of candidates considered, then some branching candidates with priority 3.0 will be considered (to bring the total number to the maximum)
- if the number of branching candidates with priority 3.5 is not less than the maximum number, then no branching candidates with priority 3.0 will be considered

Branching priority is also used in rounding and diving heuristics to determine which variables should be fixed first.
"""
branchingpriority!(x::JuMP.VariableRef, priority) = MOI.set(
x.model, VarBranchingPriority(), x, priority
Expand Down
11 changes: 0 additions & 11 deletions src/callbacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,6 @@ function callback_ub(cbdata, x::JuMP.VariableRef)
)
end

"""
Every custom data assigned to a pricing variable or a user cut should inherit from it.
"""
abstract type AbstractCustomData end

function MOI.submit(
model, cb, con, custom_data
)
return MOI.submit(JuMP.backend(model), cb, JuMP.moi_function(con.func), con.set, custom_data)
end

"""
A callback to provide initial columns to the optimizer before starting the optimization.
"""
Expand Down
72 changes: 62 additions & 10 deletions src/customdata.jl
Original file line number Diff line number Diff line change
@@ -1,26 +1,78 @@
"""
AbstractCustomData

Left for compatibility with BlockDecomposition versions 1.14.1 and below.
One should use [AbstractCustomVarData](@ref) or [AbstractCustomConstrData](@ref) instead.
"""
abstract type AbstractCustomData end

"""
AbstractCustomVarData

Every custom data associated to a solution passed in [PricingSolution](@ref)
should inherit from AbstractCustomVarData.

This data is used to
- Determine the coefficient of the corresponding column in non-robust constraints
- Store the information about pricing solution not expressed with subproblem variables
(and thus not used in the master formulation); this information can then be retrieved
using [customdata(info)]@ref by the user.
- determine the branching priority of the corresponding column
(if [branchingpriority(::AbstractCustomVarData)] is defined for the concrete type).
"""
abstract type AbstractCustomVarData <: AbstractCustomData end

branchingpriority(::Nothing) = nothing

Check warning on line 25 in src/customdata.jl

View check run for this annotation

Codecov / codecov/patch

src/customdata.jl#L25

Added line #L25 was not covered by tests

"""
branchingpriority(<:AbstractCustomVarData)

This function should be redefined for a concrete type which inherits from AbstractCustomVarData
if a custom branching priority is defined for columns associated with this data type.
If this function is not redefined, the branching priority of each column equals to the
branching priority of the pricing problem which generated it.
"""
branchingpriority(::AbstractCustomVarData) = nothing


"""
AbstractCustomConstrData

Every custom data associated to a non-robust constraint should inherit from AbstractCustomConstrData.

This data is used to determine the coefficient of the columns in non-robust constraints.
"""
abstract type AbstractCustomConstrData <: AbstractCustomData end

function MOI.submit(
model, cb, con::JuMP.AbstractConstraint, custom_data::AbstractCustomConstrData
)
return MOI.submit(JuMP.backend(model), cb, JuMP.moi_function(con.func), con.set, custom_data)
end

struct CustomVars <: MOI.AbstractModelAttribute end
struct CustomConstrs <: MOI.AbstractModelAttribute end

"""
customvars!(model, customvar::Type{AbstractCustomData})
customvars!(model, customvars::Vector{Type{<:AbstractCustomData}})
customvars!(model, customvar::Type{<:AbstractCustomVarData})
customvars!(model, customvars::Vector{Type{<:AbstractCustomVarData}})

Set the possible custom data types of variables in a model.
"""
customvars!(model, customvar::DataType) = MOI.set(
customvars!(model, customvar::Type{<:AbstractCustomData}) = MOI.set(
model, CustomVars(), [customvar]
)
customvars!(model, customvars::Vector{DataType}) = MOI.set(
model, CustomVars(), customvars
)

"""
customconstrs!(model, customconstr::DataType)
customconstrs!(model, customconstrs::Vector{DataType})
customconstrs!(model, customconstr::Type{AbstractCustomConstrData})
customconstrs!(model, customconstrs::Vector{Type{AbstractCustomConstrData}})

Set the possible custom data types of constraints in a model.
"""
customconstrs!(model, customconstr::DataType) = MOI.set(
customconstrs!(model, customconstr::Type{<:AbstractCustomData}) = MOI.set(
model, CustomConstrs(), [customconstr]
)
customconstrs!(model, customconstrs::Vector{DataType}) = MOI.set(
Expand Down Expand Up @@ -146,12 +198,12 @@
end

MathOptInterface.Utilities.map_indices(
variable_map::MathOptInterface.Utilities.IndexMap, x::AbstractCustomData
::MathOptInterface.Utilities.IndexMap, x::AbstractCustomData
) = x
MathOptInterface.Utilities.map_indices(
variable_map::MathOptInterface.Utilities.IndexMap, x::Vector{AbstractCustomData}
::MathOptInterface.Utilities.IndexMap, x::Vector{AbstractCustomData}
) = x

# added for compatibility with MathOptInterface v1.23
MathOptInterface.Utilities.map_indices(f::Function, x::AbstractCustomData) = x
MathOptInterface.Utilities.map_indices(f::Function, x::Vector{AbstractCustomData}) = x
MathOptInterface.Utilities.map_indices(::Function, x::AbstractCustomData) = x
MathOptInterface.Utilities.map_indices(::Function, x::Vector{AbstractCustomData}) = x

Check warning on line 209 in src/customdata.jl

View check run for this annotation

Codecov / codecov/patch

src/customdata.jl#L209

Added line #L209 was not covered by tests
14 changes: 11 additions & 3 deletions src/formulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,22 @@ end
subproblem,
lower_multiplicity = 1,
upper_multiplicity = 1,
solver = nothing
solver = nothing,
branching_priority = 1
)

Method that allows the user to specify additional property of the subproblems.

The multiplicity of `subproblem` is the number of times that the same independant
The multiplicity of `subproblem` is the number of times that the same independent
block shaped by the subproblem in the coefficient matrix appears in the model.
It is equivalent to the number of solutions to the subproblem that can appear in the solution
of the original problem.

Branching priority of a subproblem is equal to the branching priority of the associated integer variable
(the number of columns from this subproblem in the global solution). It also determines
the default branching priority of columns generated by this subproblem. Branching priority is also used
in rounding and diving heuristics to prioritize which variables and columns to fix the first.

The solver of the subproblem is the way the subproblem will be optimized. It can
be either a function (pricing callback), an optimizer of MathOptInterface
(e.g. `Gurobi.Optimizer`, `CPLEX.Optimizer`, `Glpk.Optimizer`... with attributes),
Expand All @@ -98,10 +104,12 @@ buffered to all solvers. So you may degrade performances if you use a lot of sol
"""
function specify!(
sp::SubproblemForm; lower_multiplicity::Real = 1,
upper_multiplicity::Real = 1, solver = nothing
upper_multiplicity::Real = 1, solver = nothing,
branching_priority::Real = 1
)
setlowermultiplicity!(sp.annotation, lower_multiplicity)
setuppermultiplicity!(sp.annotation, upper_multiplicity)
setbranchingpriority!(sp.annotation, branching_priority)
emptyoptimizerbuilders!(sp.annotation)
_specify!(sp, solver)
return
Expand Down
47 changes: 39 additions & 8 deletions test/customdata.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
struct MyCustomVarData1 <: BlockDecomposition.AbstractCustomData
struct MyCustomVarData1 <: BlockDecomposition.AbstractCustomVarData
nb_items::Int
branching_priority::Float64
end

struct MyCustomVarData2 <: BlockDecomposition.AbstractCustomData
BD.branchingpriority(data::MyCustomVarData1) = data.branching_priority

struct MyCustomVarData2 <: BlockDecomposition.AbstractCustomVarData
nb_items::Float64
end

struct MyCustomCutData1 <: BlockDecomposition.AbstractCustomData
struct MyCustomCutData1 <: BlockDecomposition.AbstractCustomConstrData
min_items::Int
end

struct MyCustomCutData2 <: BlockDecomposition.AbstractCustomData
struct MyCustomCutData2 <: BlockDecomposition.AbstractCustomConstrData
min_items::Float64
end

struct CutCallbackContext end

function MOI.submit(
::MockOptimizer,
::MOI.UserCut{CutCallbackContext},
::MOI.ScalarAffineFunction{Float64},
::Union{MOI.LessThan{Float64}, MOI.GreaterThan{Float64}, MOI.EqualTo{Float64}},
::BD.AbstractCustomConstrData
)
return nothing
end

function test_custom_data()
model = Model()
customvars!(model, [MyCustomVarData1, MyCustomVarData2])
Expand Down Expand Up @@ -53,24 +68,40 @@ function test_custom_data()
return
end



function test_attach_custom_data()
model = Model()
model = Model(MockOptimizer)
@variable(model, x[1:2])
@constraint(model, con, x[1] + x[2] <= 1)

@testset "attach custom data to variable from unregistered custom data family" begin
@test_throws UnregisteredCustomDataFamily customdata!(x[1], MyCustomVarData1(1))
@test_throws UnregisteredCustomDataFamily customdata!(x[1], MyCustomVarData1(1, 2.0))
@test_throws UnregisteredCustomDataFamily customdata!(con, MyCustomCutData1(1))
end

@testset "attach custom data to a variable" begin
customvars!(model, MyCustomVarData1)
customdata!(x[1], MyCustomVarData1(1))
@test customdata(x[1]) == MyCustomVarData1(1)
customvars!(model, MyCustomVarData2)
customdata!(x[1], MyCustomVarData1(1, 2.0))
@test customdata(x[1]) == MyCustomVarData1(1, 2.0)
@test branchingpriority(customdata(x[1])) == 2.0
customdata!(x[2], MyCustomVarData2(2))
@test customdata(x[2]) == MyCustomVarData2(2)
@test branchingpriority(customdata(x[2])) === nothing
end

@testset "attach custom data to a constraint" begin
customconstrs!(model, MyCustomCutData1)
customdata!(con, MyCustomCutData1(1))
@test customdata(con) == MyCustomCutData1(1)
end

@testset "submit a cut with attached custom data " begin
MOI.submit(
model, MOI.UserCut(CutCallbackContext()),
JuMP.ScalarConstraint(JuMP.AffExpr(0.0), MOI.LessThan(1.0)), MyCustomCutData1(2)
)
@test_throws UnregisteredCustomDataFamily JuMP.optimize!(model)
end
end
2 changes: 1 addition & 1 deletion test/dantzigwolfe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function test_dantzig_wolfe_different()
subproblems = getsubproblems(dec)
@test repr(subproblems[1]) == "Subproblem formulation for Machines = 1 contains :\t 0.0 <= multiplicity <= 1.0\n"
@test gettree(model) == gettree(dec)
@test repr(dec) == "Root - Annotation(BlockDecomposition.Master, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 2) with 2 subproblems :\n\t 2 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 0.0, um = 1.0, id = 4) \n\t 1 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 0.0, um = 1.0, id = 3) \n"
@test repr(dec) == "Root - Annotation(BlockDecomposition.Master, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, bp = 1.0, id = 2) with 2 subproblems :\n\t 2 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 0.0, um = 1.0, bp = 2.0, id = 4) \n\t 1 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 0.0, um = 1.0, bp = 1.0, id = 3) \n"
end

d = GapToyData(30, 10)
Expand Down
8 changes: 4 additions & 4 deletions test/formulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ function generalized_assignement(d::GapData)
sum(d.costs[j, m] * x[j, m] for j in d.jobs, m in Machines))

@dantzig_wolfe_decomposition(model, decomposition, Machines)
master = getmaster(decomposition)
subproblems = getsubproblems(decomposition)
specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1)
for (i,_) in enumerate(Machines)
specify!(subproblems[i], lower_multiplicity = 0, upper_multiplicity = 1, branching_priority = i)
end
return model, x, cov, knp, decomposition
end

Expand Down Expand Up @@ -67,9 +68,8 @@ function generalized_assignement_penalties(d::GapData)

@dantzig_wolfe_decomposition(model, decomposition, Machines)

master = getmaster(decomposition)
subproblems = getsubproblems(decomposition)
for (i,m) in enumerate(Machines)
for (i,_) in enumerate(Machines)
specify!(subproblems[i], lower_multiplicity = 0, upper_multiplicity = 1)
end
return model, x, y, z, cov, knp, lim, decomposition
Expand Down
5 changes: 3 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ MOI.optimize!(::MockOptimizer) = nothing

# Helper to tests annotations
function test_annotation(ann::BD.Annotation, F::Type{<:BD.Formulation},
D::Type{<:BD.Decomposition}, minmult, maxmult)
D::Type{<:BD.Decomposition}, minmult, maxmult, branchpr = 1.0)
@test BD.getformulation(ann) == F
@test BD.getdecomposition(ann) == D
@test BD.getlowermultiplicity(ann) == minmult
@test BD.getuppermultiplicity(ann) == maxmult
@test BD.getbranchingpriority(ann) == branchpr
id = BD.getid(ann)
@test repr(ann) == "Annotation($(F), $(D), lm = $(float(minmult)), um = $(float(maxmult)), id = $(id))"
@test repr(ann) == "Annotation($(F), $(D), lm = $(float(minmult)), um = $(float(maxmult)), bp = $(float(branchpr)), id = $(id))"
return
end

Expand Down
Loading