Skip to content

Commit bccb127

Browse files
authored
Branching priority (#103)
* - Introduced AbstractCustomVarData and AbstractCustomConstrData - Branching priority for DW subproblems and for AbstractCustomVarData * Forgot to push one change * More tests
1 parent 2491880 commit bccb127

12 files changed

+134
-44
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode/settings.json
2+
Manifest.toml

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "BlockDecomposition"
22
uuid = "6cde8614-403a-11e9-12f1-c10d0f0caca0"
33
authors = ["Guillaume Marques", "Vitor Nesello", "François Vanderbeck"]
4-
version = "1.14.1"
4+
version = "1.15.0"
55

66
[deps]
77
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"

src/BlockDecomposition.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ include("subproblem_representative.jl")
2929
include("checker.jl")
3030
include("decomposition.jl")
3131
include("objective.jl")
32+
include("customdata.jl")
3233
include("callbacks.jl")
3334
include("automatic_dantzig_wolfe.jl")
3435
include("utils.jl")
3536
include("branchingpriority.jl")
36-
include("customdata.jl")
3737
include("soldisaggregation.jl")
3838

3939
function model_factory(::Val{true}, optimizer; kw...)::JuMP.Model

src/annotations.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mutable struct Annotation{T, F<:Formulation, D<:Decomposition}
2121
axis_index_value::T
2222
lower_multiplicity::Float64
2323
upper_multiplicity::Float64
24+
branching_priority::Float64
2425
optimizer_builders::Vector
2526
end
2627

@@ -31,24 +32,26 @@ getdecomposition(a::Annotation) = a.decomposition
3132
getlowermultiplicity(a::Annotation) = a.lower_multiplicity
3233
getuppermultiplicity(a::Annotation) = a.upper_multiplicity
3334
getoptimizerbuilders(a::Annotation) = a.optimizer_builders
35+
getbranchingpriority(a::Annotation) = a.branching_priority
3436

3537
setlowermultiplicity!(a::Annotation, lm::Real) = a.lower_multiplicity = lm
3638
setuppermultiplicity!(a::Annotation, um::Real) = a.upper_multiplicity = um
39+
setbranchingpriority!(a::Annotation, bp::Real) = a.branching_priority = bp
3740
emptyoptimizerbuilders!(a::Annotation) = empty!(a.optimizer_builders)
3841
pushoptimizerbuilder!(a::Annotation, f::MOI.AbstractOptimizer) = push!(a.optimizer_builders, f)
3942
pushoptimizerbuilder!(a::Annotation, f::Function) = push!(a.optimizer_builders, f)
4043
pushoptimizerbuilder!(a::Annotation, f::AbstractCustomOptimizer) = push!(a.optimizer_builders, f)
4144

42-
OriginalAnnotation() = Annotation(0, 0, Original, NoDecomposition, 0, 1.0, 1.0, [])
45+
OriginalAnnotation() = Annotation(0, 0, Original, NoDecomposition, 0, 1.0, 1.0, 1.0, [])
4346

4447
function MasterAnnotation(tree, D::Type{<:Decomposition})
4548
uid = generateannotationid(tree)
46-
return Annotation(uid, 0, Master, D, 0, 1.0, 1.0, [])
49+
return Annotation(uid, 0, Master, D, 0, 1.0, 1.0, 1.0, [])
4750
end
4851

4952
function Annotation(tree, F::Type{<:Formulation}, D::Type{<:Decomposition}, v)
5053
uid = generateannotationid(tree)
51-
return Annotation(uid, 0, F, D, v, 1.0, 1.0, [])
54+
return Annotation(uid, 0, F, D, v, 1.0, 1.0, 1.0, [])
5255
end
5356

5457
function Base.show(io::IO, a::Annotation)
@@ -60,6 +63,8 @@ function Base.show(io::IO, a::Annotation)
6063
print(io, getlowermultiplicity(a))
6164
print(io, ", um = ")
6265
print(io, getuppermultiplicity(a))
66+
print(io, ", bp = ")
67+
print(io, getbranchingpriority(a))
6368
print(io, ", id = ")
6469
print(io, getid(a))
6570
print(io, ")")

src/branchingpriority.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The idea is to have both "soft" and "hard" branching priorities. For instance :
1010
- 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
1111
- 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)
1212
- 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
13+
14+
Branching priority is also used in rounding and diving heuristics to determine which variables should be fixed first.
1315
"""
1416
branchingpriority!(x::JuMP.VariableRef, priority) = MOI.set(
1517
x.model, VarBranchingPriority(), x, priority

src/callbacks.jl

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,6 @@ function callback_ub(cbdata, x::JuMP.VariableRef)
108108
)
109109
end
110110

111-
"""
112-
Every custom data assigned to a pricing variable or a user cut should inherit from it.
113-
"""
114-
abstract type AbstractCustomData end
115-
116-
function MOI.submit(
117-
model, cb, con, custom_data
118-
)
119-
return MOI.submit(JuMP.backend(model), cb, JuMP.moi_function(con.func), con.set, custom_data)
120-
end
121-
122111
"""
123112
A callback to provide initial columns to the optimizer before starting the optimization.
124113
"""

src/customdata.jl

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,78 @@
1+
"""
2+
AbstractCustomData
3+
4+
Left for compatibility with BlockDecomposition versions 1.14.1 and below.
5+
One should use [AbstractCustomVarData](@ref) or [AbstractCustomConstrData](@ref) instead.
6+
"""
7+
abstract type AbstractCustomData end
8+
9+
"""
10+
AbstractCustomVarData
11+
12+
Every custom data associated to a solution passed in [PricingSolution](@ref)
13+
should inherit from AbstractCustomVarData.
14+
15+
This data is used to
16+
- Determine the coefficient of the corresponding column in non-robust constraints
17+
- Store the information about pricing solution not expressed with subproblem variables
18+
(and thus not used in the master formulation); this information can then be retrieved
19+
using [customdata(info)]@ref by the user.
20+
- determine the branching priority of the corresponding column
21+
(if [branchingpriority(::AbstractCustomVarData)] is defined for the concrete type).
22+
"""
23+
abstract type AbstractCustomVarData <: AbstractCustomData end
24+
25+
branchingpriority(::Nothing) = nothing
26+
27+
"""
28+
branchingpriority(<:AbstractCustomVarData)
29+
30+
This function should be redefined for a concrete type which inherits from AbstractCustomVarData
31+
if a custom branching priority is defined for columns associated with this data type.
32+
If this function is not redefined, the branching priority of each column equals to the
33+
branching priority of the pricing problem which generated it.
34+
"""
35+
branchingpriority(::AbstractCustomVarData) = nothing
36+
37+
38+
"""
39+
AbstractCustomConstrData
40+
41+
Every custom data associated to a non-robust constraint should inherit from AbstractCustomConstrData.
42+
43+
This data is used to determine the coefficient of the columns in non-robust constraints.
44+
"""
45+
abstract type AbstractCustomConstrData <: AbstractCustomData end
46+
47+
function MOI.submit(
48+
model, cb, con::JuMP.AbstractConstraint, custom_data::AbstractCustomConstrData
49+
)
50+
return MOI.submit(JuMP.backend(model), cb, JuMP.moi_function(con.func), con.set, custom_data)
51+
end
52+
153
struct CustomVars <: MOI.AbstractModelAttribute end
254
struct CustomConstrs <: MOI.AbstractModelAttribute end
355

456
"""
5-
customvars!(model, customvar::Type{AbstractCustomData})
6-
customvars!(model, customvars::Vector{Type{<:AbstractCustomData}})
57+
customvars!(model, customvar::Type{<:AbstractCustomVarData})
58+
customvars!(model, customvars::Vector{Type{<:AbstractCustomVarData}})
759
860
Set the possible custom data types of variables in a model.
961
"""
10-
customvars!(model, customvar::DataType) = MOI.set(
62+
customvars!(model, customvar::Type{<:AbstractCustomData}) = MOI.set(
1163
model, CustomVars(), [customvar]
1264
)
1365
customvars!(model, customvars::Vector{DataType}) = MOI.set(
1466
model, CustomVars(), customvars
1567
)
1668

1769
"""
18-
customconstrs!(model, customconstr::DataType)
19-
customconstrs!(model, customconstrs::Vector{DataType})
70+
customconstrs!(model, customconstr::Type{AbstractCustomConstrData})
71+
customconstrs!(model, customconstrs::Vector{Type{AbstractCustomConstrData}})
2072
2173
Set the possible custom data types of constraints in a model.
2274
"""
23-
customconstrs!(model, customconstr::DataType) = MOI.set(
75+
customconstrs!(model, customconstr::Type{<:AbstractCustomData}) = MOI.set(
2476
model, CustomConstrs(), [customconstr]
2577
)
2678
customconstrs!(model, customconstrs::Vector{DataType}) = MOI.set(
@@ -146,12 +198,12 @@ function MOI.get(dest::MOIU.UniversalFallback, attribute::CustomConstrValue, ci:
146198
end
147199

148200
MathOptInterface.Utilities.map_indices(
149-
variable_map::MathOptInterface.Utilities.IndexMap, x::AbstractCustomData
201+
::MathOptInterface.Utilities.IndexMap, x::AbstractCustomData
150202
) = x
151203
MathOptInterface.Utilities.map_indices(
152-
variable_map::MathOptInterface.Utilities.IndexMap, x::Vector{AbstractCustomData}
204+
::MathOptInterface.Utilities.IndexMap, x::Vector{AbstractCustomData}
153205
) = x
154206

155207
# added for compatibility with MathOptInterface v1.23
156-
MathOptInterface.Utilities.map_indices(f::Function, x::AbstractCustomData) = x
157-
MathOptInterface.Utilities.map_indices(f::Function, x::Vector{AbstractCustomData}) = x
208+
MathOptInterface.Utilities.map_indices(::Function, x::AbstractCustomData) = x
209+
MathOptInterface.Utilities.map_indices(::Function, x::Vector{AbstractCustomData}) = x

src/formulations.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,22 @@ end
7272
subproblem,
7373
lower_multiplicity = 1,
7474
upper_multiplicity = 1,
75-
solver = nothing
75+
solver = nothing,
76+
branching_priority = 1
7677
)
7778
7879
Method that allows the user to specify additional property of the subproblems.
7980
80-
The multiplicity of `subproblem` is the number of times that the same independant
81+
The multiplicity of `subproblem` is the number of times that the same independent
8182
block shaped by the subproblem in the coefficient matrix appears in the model.
8283
It is equivalent to the number of solutions to the subproblem that can appear in the solution
8384
of the original problem.
8485
86+
Branching priority of a subproblem is equal to the branching priority of the associated integer variable
87+
(the number of columns from this subproblem in the global solution). It also determines
88+
the default branching priority of columns generated by this subproblem. Branching priority is also used
89+
in rounding and diving heuristics to prioritize which variables and columns to fix the first.
90+
8591
The solver of the subproblem is the way the subproblem will be optimized. It can
8692
be either a function (pricing callback), an optimizer of MathOptInterface
8793
(e.g. `Gurobi.Optimizer`, `CPLEX.Optimizer`, `Glpk.Optimizer`... with attributes),
@@ -98,10 +104,12 @@ buffered to all solvers. So you may degrade performances if you use a lot of sol
98104
"""
99105
function specify!(
100106
sp::SubproblemForm; lower_multiplicity::Real = 1,
101-
upper_multiplicity::Real = 1, solver = nothing
107+
upper_multiplicity::Real = 1, solver = nothing,
108+
branching_priority::Real = 1
102109
)
103110
setlowermultiplicity!(sp.annotation, lower_multiplicity)
104111
setuppermultiplicity!(sp.annotation, upper_multiplicity)
112+
setbranchingpriority!(sp.annotation, branching_priority)
105113
emptyoptimizerbuilders!(sp.annotation)
106114
_specify!(sp, solver)
107115
return

test/customdata.jl

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
1-
struct MyCustomVarData1 <: BlockDecomposition.AbstractCustomData
1+
struct MyCustomVarData1 <: BlockDecomposition.AbstractCustomVarData
22
nb_items::Int
3+
branching_priority::Float64
34
end
45

5-
struct MyCustomVarData2 <: BlockDecomposition.AbstractCustomData
6+
BD.branchingpriority(data::MyCustomVarData1) = data.branching_priority
7+
8+
struct MyCustomVarData2 <: BlockDecomposition.AbstractCustomVarData
69
nb_items::Float64
710
end
811

9-
struct MyCustomCutData1 <: BlockDecomposition.AbstractCustomData
12+
struct MyCustomCutData1 <: BlockDecomposition.AbstractCustomConstrData
1013
min_items::Int
1114
end
1215

13-
struct MyCustomCutData2 <: BlockDecomposition.AbstractCustomData
16+
struct MyCustomCutData2 <: BlockDecomposition.AbstractCustomConstrData
1417
min_items::Float64
1518
end
1619

20+
struct CutCallbackContext end
21+
22+
function MOI.submit(
23+
::MockOptimizer,
24+
::MOI.UserCut{CutCallbackContext},
25+
::MOI.ScalarAffineFunction{Float64},
26+
::Union{MOI.LessThan{Float64}, MOI.GreaterThan{Float64}, MOI.EqualTo{Float64}},
27+
::BD.AbstractCustomConstrData
28+
)
29+
return nothing
30+
end
31+
1732
function test_custom_data()
1833
model = Model()
1934
customvars!(model, [MyCustomVarData1, MyCustomVarData2])
@@ -53,24 +68,40 @@ function test_custom_data()
5368
return
5469
end
5570

71+
72+
5673
function test_attach_custom_data()
57-
model = Model()
74+
model = Model(MockOptimizer)
5875
@variable(model, x[1:2])
5976
@constraint(model, con, x[1] + x[2] <= 1)
6077

6178
@testset "attach custom data to variable from unregistered custom data family" begin
62-
@test_throws UnregisteredCustomDataFamily customdata!(x[1], MyCustomVarData1(1))
79+
@test_throws UnregisteredCustomDataFamily customdata!(x[1], MyCustomVarData1(1, 2.0))
80+
@test_throws UnregisteredCustomDataFamily customdata!(con, MyCustomCutData1(1))
6381
end
6482

6583
@testset "attach custom data to a variable" begin
6684
customvars!(model, MyCustomVarData1)
67-
customdata!(x[1], MyCustomVarData1(1))
68-
@test customdata(x[1]) == MyCustomVarData1(1)
85+
customvars!(model, MyCustomVarData2)
86+
customdata!(x[1], MyCustomVarData1(1, 2.0))
87+
@test customdata(x[1]) == MyCustomVarData1(1, 2.0)
88+
@test branchingpriority(customdata(x[1])) == 2.0
89+
customdata!(x[2], MyCustomVarData2(2))
90+
@test customdata(x[2]) == MyCustomVarData2(2)
91+
@test branchingpriority(customdata(x[2])) === nothing
6992
end
7093

7194
@testset "attach custom data to a constraint" begin
7295
customconstrs!(model, MyCustomCutData1)
7396
customdata!(con, MyCustomCutData1(1))
7497
@test customdata(con) == MyCustomCutData1(1)
7598
end
99+
100+
@testset "submit a cut with attached custom data " begin
101+
MOI.submit(
102+
model, MOI.UserCut(CutCallbackContext()),
103+
JuMP.ScalarConstraint(JuMP.AffExpr(0.0), MOI.LessThan(1.0)), MyCustomCutData1(2)
104+
)
105+
@test_throws UnregisteredCustomDataFamily JuMP.optimize!(model)
106+
end
76107
end

test/dantzigwolfe.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function test_dantzig_wolfe_different()
2020
subproblems = getsubproblems(dec)
2121
@test repr(subproblems[1]) == "Subproblem formulation for Machines = 1 contains :\t 0.0 <= multiplicity <= 1.0\n"
2222
@test gettree(model) == gettree(dec)
23-
@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"
23+
@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"
2424
end
2525

2626
d = GapToyData(30, 10)

0 commit comments

Comments
 (0)