From 0825447a4ec1e4e118050662b41d2ac0cda12a6b Mon Sep 17 00:00:00 2001 From: David Meichel Date: Wed, 20 May 2020 11:25:31 +0200 Subject: [PATCH 01/32] axis.jl: compute all possible decompositions The goal is to implement an automatic decomposition such that the user does not need to provide the decomposition axis. To find the best axis for the decomposition 1. find the index sets of the constraints (get_constraints_and_axes(...)) 2. compute each possible subset of this set of axes; each of these subsets corresponds to one possible decomposition 3. find out how the decomposition/block structure would look like for each of these subsets; for each subset do: get_block_structure(...) 3.1. built a graph where there is a node for every constraint in the model that is not constructed over a set that is contained in the considered subset 3.2. find the connected compontents in this graph 3.3. each connected component will correspond to one block in the decomposition 3.4. store the possible decomposition as an instance of Block_Structure To do: 4. implement methods to compare the possible decompositons and determine which block structure should be used for the decomposition --- src/BlockDecomposition.jl | 1 + src/axis.jl | 177 ++++++++++++++++++++++++++++++++++++++ src/tree.jl | 1 + 3 files changed, 179 insertions(+) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 7fb159e..dc14093 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -1,6 +1,7 @@ module BlockDecomposition using JuMP, MathOptInterface + import MathOptInterface import MathOptInterface.Utilities diff --git a/src/axis.jl b/src/axis.jl index 831a8b3..7417f4e 100644 --- a/src/axis.jl +++ b/src/axis.jl @@ -9,6 +9,9 @@ import Base.isless import Base.== import Base.vcat +using LightGraphs, MetaGraphs, TikzGraphs, Combinatorics, TikzPictures + + struct AxisId{Name, T} indice::T end @@ -54,6 +57,8 @@ function Axis(name::Symbol, container::A) where {T, A <: AbstractArray{T}} return Axis{name, T}(name, indices) end +Axis(container) = Axis(Symbol(), container) + name(axis::Axis) = axis.name iterate(axis::Axis) = iterate(axis.container) iterate(axis::Axis, state) = iterate(axis.container, state) @@ -61,6 +66,8 @@ length(axis::Axis) = length(axis.container) getindex(axis::Axis, elements) = getindex(axis.container, elements) lastindex(axis::Axis) = lastindex(axis.container) vcat(A::BlockDecomposition.Axis, B::AbstractArray) = vcat(A.container, B) +Base.isequal(i::Axis, j::Axis) = isequal(i.container, j.container) +Base.hash(i::Axis, h::UInt) = hash(i.container, h) function _generate_axis(name, container) sym_name = Meta.parse("Symbol(\"" * string(name) * "\")") @@ -80,3 +87,173 @@ macro axis(args...) return esc(exp) end + + + +function get_best_block_structure(model::JuMP.Model) + constraints_and_axes = get_constraints_and_axes(model) + decompositions = Array{Block_Structure,1}() + axesSets = collect(Combinatorics.powerset(collect(constraints_and_axes.axes))) + for axes in axesSets + decomposition = get_block_structure(axes, constraints_and_axes, model) + push!(decompositions, decomposition) + end + #to do: find and return the "best" decomposition + return decompositions[1] +end + +mutable struct Constraints_and_Axes #constains all the information we need to check different decompositons + constraints::Set{JuMP.ConstraintRef} + axes::Set{BlockDecomposition.Axis} + variables::Set{MOI.VariableIndex} # MOI. VariableIndex can be used for reverencing variables in a model + constraints_to_axes::Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}} + constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} +end + +function get_constraints_and_axes(model::JuMP.Model) #returns an instance of the struct Constraints_and_axes + constraints = Set{JuMP.ConstraintRef}() + axes = Set{BlockDecomposition.Axis}() + variables = Set{JuMP.MOI.VariableIndex}() + constraints_to_axes = Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}}() + constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() + constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) + + for k in keys(model.obj_dict) #check all names in the model + # store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) + reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) + if typeof(reference[1]) <: JuMP.ConstraintRef #constraint + for c in reference + _add_constraint!(constraints_and_axes, c, model, reference) + end + elseif typeof(reference[1]) <: JuMP.VariableRef + for v in reference + push!(variables, JuMP.index(v)) + end + end + end + constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) + add_anonymous_var_and_con!(constraints_and_axes, model) + return constraints_and_axes +end + +function add_anonymous_var_and_con!(car::Constraints_and_Axes, model::JuMP.Model) #adds anonymous constraints and axes from the model to constraints_and_axes (car) + types = JuMP.list_of_constraint_types(model) + for t in types + if t[1] != VariableRef + for c in JuMP.all_constraints(model, t[1], t[2]) + if !in(c, car.constraints) + push!(car.constraints, c) + car.constraints_to_axes[c] = Set{BlockDecomposition.Axis}() + car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) + end + end + end + end + for v in JuMP.all_variables(model) + push!(car.variables, JuMP.index(v)) + end +end + +function _add_constraint!(o::Constraints_and_Axes, c::JuMP.ConstraintRef, model::JuMP.Model, reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray + push!(o.constraints, c) + axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) + for r in axes_of_constraint + push!(o.axes, r) + end + o.constraints_to_axes[c] = axes_of_constraint + o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) +end + +function _get_axes_of_constraint(reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray + axes_of_constraint = Set{BlockDecomposition.Axis}() + for a in reference_constraints_name.axes + if a != 1 #axes of the form 1:1 do not matter (single constraints) + push!(axes_of_constraint, Axis(a)) + end + end + return axes_of_constraint +end + +function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.ConstraintRef) + f = MOI.get(model, MathOptInterface.ConstraintFunction(), constraint) + variables = Set{MOI.VariableIndex}() + for term in f.terms + push!(variables, term.variable_index) + end + return variables +end + +function get_block_structure(axes::Array{<:Axis,1}, constraints_and_axes::Constraints_and_Axes, model::JuMP.Model) + vertices = Set{JuMP.ConstraintRef}() + master_constraints = Set{JuMP.ConstraintRef}() + connected_components = Set{Set{JuMP.ConstraintRef}}() + + for c in keys(constraints_and_axes.constraints_to_axes) + if !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) #which constraints are build over at least one set that is contained in axes? + push!(master_constraints, c) + else + push!(vertices, c) + end + end + + graph = _create_graph(vertices, constraints_and_axes) + blocks = _get_connected_components!(graph) + block_structure = Block_Structure(master_constraints, axes, blocks, graph) + return block_structure +end + +struct Block_Structure + master_constraints::Set{JuMP.ConstraintRef} + master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints + blocks::Set{Set{JuMP.ConstraintRef}} + graph::MetaGraphs.MetaGraph +end + +function _get_connected_components!(graph::MetaGraphs.MetaGraph) + connected_components_int = connected_components(graph) + blocks = Set{Set{JuMP.ConstraintRef}}() + for component_int in connected_components_int + component_constraintref = Set{JuMP.ConstraintRef}() + for vertex_int in component_int + push!(component_constraintref, graph[vertex_int, :constraint_ref]) + end + push!(blocks, component_constraintref) + end + return blocks +end + +function _create_graph(vertices::Set{JuMP.ConstraintRef}, constraints_and_axes::Constraints_and_Axes) + graph = LightGraphs.SimpleGraph(0) + graph = MetaGraphs.MetaGraph(graph) + n_vertices = LightGraphs.add_vertices!(graph, length(vertices)) + MetaGraphs.set_indexing_prop!(graph, :constraint_ref) + i = 1 + #set values for indexing property :constraint_ref + for ver in vertices + MetaGraphs.set_prop!(graph, i, :constraint_ref, ver) + i = i+1 + end + #build edges + for v1 in vertices, v2 in vertices + if v1 != v2 + intersection = LightGraphs.intersect(constraints_and_axes.constraints_to_variables[v1], constraints_and_axes.constraints_to_variables[v2]) + isempty(intersection) ? true : LightGraphs.add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) + end + end + return graph +end + +function draw_graph(mgraph::MetaGraphs.MetaGraph) + sgraph = SimpleGraph(nv(mgraph)) + labels = Array{String}(undef, nv(mgraph)) + i = 1 + for v in collect(vertices(mgraph)) + labels[i] = convert(String, repr(mgraph[i, :constraint_ref])) + i = i + 1 + end + for e in edges(mgraph) + add_edge!(sgraph, e) + end + t = TikzGraphs.plot(sgraph, labels) + save(SVG("graph.svg"),t) +end \ No newline at end of file diff --git a/src/tree.jl b/src/tree.jl index 9b9edb0..2b8097f 100644 --- a/src/tree.jl +++ b/src/tree.jl @@ -152,6 +152,7 @@ function register_multi_index_subproblems!(n::AbstractNode, multi_index::Tuple, end end + macro dantzig_wolfe_decomposition(args...) if length(args) != 3 error("Three arguments expected: model, decomposition name, and axis") From 18724f11e55228469a429897164e45ba16bd0829 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Wed, 27 May 2020 15:13:00 +0200 Subject: [PATCH 02/32] Enable use of automatic decomposition for Dantzig Wolfe - BlockDecomposition.jl: allow user to construct a BlockModel where automatic decomposition is activated - decomposition.jl: modify the function register_decomposition such that an automatic decomposition can be registered - tree.jl: adapt macro @danzig_wolfe_decomposition for automatic decomposition --- src/BlockDecomposition.jl | 3 ++- src/axis.jl | 7 +++---- src/decomposition.jl | 35 +++++++++++++++++++++++++++++++---- src/tree.jl | 14 +++++++++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index dc14093..44429e7 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -22,9 +22,10 @@ include("decomposition.jl") include("objective.jl") include("callbacks.jl") -function BlockModel(args...; kw...) +function BlockModel(args...; automatic_decomposition = false, kw...) m = JuMP.Model(args...; kw...) JuMP.set_optimize_hook(m, optimize!) + m.ext[:automatic_decomposition] = automatic_decomposition return m end diff --git a/src/axis.jl b/src/axis.jl index 7417f4e..304fda7 100644 --- a/src/axis.jl +++ b/src/axis.jl @@ -98,8 +98,7 @@ function get_best_block_structure(model::JuMP.Model) decomposition = get_block_structure(axes, constraints_and_axes, model) push!(decompositions, decomposition) end - #to do: find and return the "best" decomposition - return decompositions[1] + return decompositions[2] #to do: find best decomposition end mutable struct Constraints_and_Axes #constains all the information we need to check different decompositons @@ -205,13 +204,13 @@ end struct Block_Structure master_constraints::Set{JuMP.ConstraintRef} master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints - blocks::Set{Set{JuMP.ConstraintRef}} + blocks::Array{Set{JuMP.ConstraintRef},1} graph::MetaGraphs.MetaGraph end function _get_connected_components!(graph::MetaGraphs.MetaGraph) connected_components_int = connected_components(graph) - blocks = Set{Set{JuMP.ConstraintRef}}() + blocks = Array{Set{JuMP.ConstraintRef},1}() for component_int in connected_components_int component_constraintref = Set{JuMP.ConstraintRef}() for vertex_int in component_int diff --git a/src/decomposition.jl b/src/decomposition.jl index 64e4b89..19a2c10 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -7,13 +7,40 @@ or the constraint is located. """ function register_decomposition(model::JuMP.Model) # Link to the tree - tree = gettree(model) - for (key, jump_obj) in model.obj_dict - _annotate_elements!(model, jump_obj, tree) - end + if model.ext[:automatic_decomposition] + register_automatic_decomposition(model) + else + tree = gettree(model) + for (key, jump_obj) in model.obj_dict + _annotate_elements!(model, jump_obj, tree) + end + end return end +function register_automatic_decomposition(model::JuMP.Model) + tree = gettree(model) + for variableref in JuMP.all_variables(model) + ann = _getannotation(tree, Vector{AxisId}()) + setannotation!(model, variableref, ann) + end + decomposition_structure = model.ext[:decomposition_structure] + for constraintref in decomposition_structure.master_constraints + ann = _getannotation(tree, Vector{AxisId}()) + setannotation!(model, constraintref, ann) + end + virtual_axis = BlockDecomposition.Axis(1:length(decomposition_structure.blocks)) + axisids = Vector{AxisId}() + for i in virtual_axis + empty!(axisids) + push!(axisids, i) + ann = _getannotation(tree, axisids) + for constraintref in decomposition_structure.blocks[i] #annotate constraints in one block with the same annotation + setannotation!(model, constraintref, ann) + end + end +end + _getrootmasterannotation(tree) = tree.root.master # Should return the annotation corresponding to the vector of AxisId. diff --git a/src/tree.jl b/src/tree.jl index 2b8097f..5e7ce9e 100644 --- a/src/tree.jl +++ b/src/tree.jl @@ -154,12 +154,16 @@ end macro dantzig_wolfe_decomposition(args...) - if length(args) != 3 - error("Three arguments expected: model, decomposition name, and axis") - end - node, name, axis = args + node, name, axis = args dw_exp = quote - $name = BlockDecomposition.decompose_leaf($node, BlockDecomposition.DantzigWolfe, $axis) # initialize a tree for the current root node + if length($args) != 3 + error("Three arguments expected: model, decomposition name, and axis") + end + if $node.ext[:automatic_decomposition] + $node.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure($node) + $axis = BlockDecomposition.Axis(1:length($node.ext[:decomposition_structure].blocks)) + end + $name = BlockDecomposition.decompose_leaf($node, BlockDecomposition.DantzigWolfe, $axis) # initialize a tree for the current root node BlockDecomposition.register_subproblems!($name, $axis, BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe) end return esc(dw_exp) From a1418b73b3ccc40c12036ce28dcb4051c89586ef Mon Sep 17 00:00:00 2001 From: David Meichel Date: Thu, 11 Jun 2020 20:59:49 +0200 Subject: [PATCH 03/32] axis.jl: plumple method to score decompositions Coeffecient matrix should be as white as possible; master constraints and blocks are black, everything else is white --- src/axis.jl | 48 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/axis.jl b/src/axis.jl index 304fda7..5dd1e8b 100644 --- a/src/axis.jl +++ b/src/axis.jl @@ -92,13 +92,20 @@ end function get_best_block_structure(model::JuMP.Model) constraints_and_axes = get_constraints_and_axes(model) - decompositions = Array{Block_Structure,1}() + block_structures = Array{Block_Structure,1}() axesSets = collect(Combinatorics.powerset(collect(constraints_and_axes.axes))) for axes in axesSets - decomposition = get_block_structure(axes, constraints_and_axes, model) - push!(decompositions, decomposition) + block_structure = get_block_structure(axes, constraints_and_axes, model) + push!(block_structures, block_structure) end - return decompositions[2] #to do: find best decomposition + return block_structures[3] #to do: find best decomposition +end + +struct Block_Structure + master_constraints::Set{JuMP.ConstraintRef} + master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints + blocks::Array{Set{JuMP.ConstraintRef},1} + graph::MetaGraphs.MetaGraph end mutable struct Constraints_and_Axes #constains all the information we need to check different decompositons @@ -109,6 +116,32 @@ mutable struct Constraints_and_Axes #constains all the information we need to c constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} end +function plumple(block_structures::Array{Block_Structure,1}, constraints_and_axes::Constraints_and_Axes) + result = nothing + best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) + for block_structure in block_structures + plumple_value = _get_plumple_value(block_structure, constraints_and_axes) + if plumple_value <= best + best = plumple_value + result = block_structure + end + end + return result +end + +function _get_plumple_value(block_structure::Block_Structure, constraints_and_axes::Constraints_and_Axes) + n_master = length(constraints_and_axes.variables) * length(block_structure.master_constraints) + n_blocks = 0 + for block in block_structure.blocks + for constraint in block + n_blocks = n_blocks + length(constraints_and_axes.constraints_to_variables[constraint]) + end + end + return n_master+n_blocks +end + + + function get_constraints_and_axes(model::JuMP.Model) #returns an instance of the struct Constraints_and_axes constraints = Set{JuMP.ConstraintRef}() axes = Set{BlockDecomposition.Axis}() @@ -201,13 +234,6 @@ function get_block_structure(axes::Array{<:Axis,1}, constraints_and_axes::Constr return block_structure end -struct Block_Structure - master_constraints::Set{JuMP.ConstraintRef} - master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints - blocks::Array{Set{JuMP.ConstraintRef},1} - graph::MetaGraphs.MetaGraph -end - function _get_connected_components!(graph::MetaGraphs.MetaGraph) connected_components_int = connected_components(graph) blocks = Array{Set{JuMP.ConstraintRef},1}() From 7df8bb753c8abe5dfbc4e4b820065b29a5a7730e Mon Sep 17 00:00:00 2001 From: David Meichel Date: Mon, 15 Jun 2020 11:14:22 +0200 Subject: [PATCH 04/32] Add test file test/automatic_decomposition.jl --- test/automatic_decomposition.jl | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/automatic_decomposition.jl diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl new file mode 100644 index 0000000..46bec8f --- /dev/null +++ b/test/automatic_decomposition.jl @@ -0,0 +1,35 @@ +nb_machines = 4 +nb_jobs = 30 +c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5 15.2 14.3 12.6 9.2 20.8 11.7 17.3 9.2 20.3 11.4 6.2 13.8 10.0 20.9 20.6; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2 19.7 19.5 7.2 6.4 23.2 8.1 13.6 24.6 15.6 22.3 8.8 19.1 18.4 22.9 8.0; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7 16.6 8.3 15.9 24.3 18.6 21.1 7.5 16.8 20.9 8.9 15.2 15.7 12.7 20.8 10.4; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2 12.9 11.3 7.5 6.5 11.3 7.8 13.8 20.7 16.8 23.6 19.1 16.8 19.3 12.5 11.0] +w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78 71 50 99 92 83 53 91 68 61 63 97 91 77 68 80; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91 75 66 100 69 60 87 98 78 62 90 89 67 87 65 100; 91 81 66 63 59 81 87 90 65 55 57 68 92 91 86 74 80 89 95 57 55 96 77 60 55 57 56 67 81 52; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54 66 50 56 70 56 72 62 85 70 100 57 96 69 65 50] +Q = [1020 1460 1530 1190] + +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +) + + +M = 1:nb_machines +J = 1:nb_jobs + +model = BlockModel(coluna, bridge_constraints = false; automatic_decomposition = true) +@variable(model, x[m in M, j in J], Bin) +@constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) +@constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) +@objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)) +@dantzig_wolfe_decomposition(model, decomposition, Axis) + + +master = getmaster(decomposition) +subproblems = getsubproblems(decomposition) + +specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) + +optimize!(model) + + +value.(x[1,:]) \ No newline at end of file From 1e1174bb028cc3a99dafb87d2969809341214633 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 19 Jun 2020 17:11:48 +0200 Subject: [PATCH 05/32] Move automatic decomposition to its own file It is now in automatic_decomposition.jl and was before in axis.jl. --- src/BlockDecomposition.jl | 3 +- src/automatic_decomposition.jl | 194 ++++++++++++++++++++++++++++++++ src/axis.jl | 199 --------------------------------- 3 files changed, 196 insertions(+), 200 deletions(-) create mode 100644 src/automatic_decomposition.jl diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 44429e7..a9ad69d 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -9,7 +9,7 @@ const MOI = MathOptInterface const MOIU = MOI.Utilities const JC = JuMP.Containers -export BlockModel, annotation, specify!, gettree, getmaster, getsubproblems, +export BlockModel, annotation, specify!, get_best_block_structure, gettree, getmaster, getsubproblems, indice, objectiveprimalbound!, objectivedualbound! export @axis, @dantzig_wolfe_decomposition, @benders_decomposition @@ -21,6 +21,7 @@ include("formulations.jl") include("decomposition.jl") include("objective.jl") include("callbacks.jl") +include("automatic_decomposition.jl") function BlockModel(args...; automatic_decomposition = false, kw...) m = JuMP.Model(args...; kw...) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl new file mode 100644 index 0000000..a8affec --- /dev/null +++ b/src/automatic_decomposition.jl @@ -0,0 +1,194 @@ +using LightGraphs, MetaGraphs, TikzGraphs, Combinatorics, TikzPictures + +function get_best_block_structure(model::JuMP.Model) + constraints_and_axes = get_constraints_and_axes(model) + block_structures = Array{Block_Structure,1}() + axesSets = collect(Combinatorics.powerset(collect(constraints_and_axes.axes))) + for axes in axesSets + block_structure = get_block_structure(axes, constraints_and_axes, model) + push!(block_structures, block_structure) + end + return block_structures[2] #to do: find best decomposition +end + +struct Block_Structure + master_constraints::Set{JuMP.ConstraintRef} + master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints + blocks::Array{Set{JuMP.ConstraintRef},1} + graph::MetaGraphs.MetaGraph +end + +mutable struct Constraints_and_Axes #constains all the information we need to check different decompositons + constraints::Set{JuMP.ConstraintRef} + axes::Set{BlockDecomposition.Axis} + variables::Set{MOI.VariableIndex} # MOI. VariableIndex can be used for reverencing variables in a model + constraints_to_axes::Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}} + constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} +end + +function plumple(block_structures::Array{Block_Structure,1}, constraints_and_axes::Constraints_and_Axes) + result = nothing + best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) + for block_structure in block_structures + plumple_value = _get_plumple_value(block_structure, constraints_and_axes) + if plumple_value <= best + best = plumple_value + result = block_structure + end + end + return result +end + +function _get_plumple_value(block_structure::Block_Structure, constraints_and_axes::Constraints_and_Axes) + n_master = length(constraints_and_axes.variables) * length(block_structure.master_constraints) + n_blocks = 0 + for block in block_structure.blocks + for constraint in block + n_blocks = n_blocks + length(constraints_and_axes.constraints_to_variables[constraint]) + end + end + return n_master+n_blocks +end + + + +function get_constraints_and_axes(model::JuMP.Model) #returns an instance of the struct Constraints_and_axes + constraints = Set{JuMP.ConstraintRef}() + axes = Set{BlockDecomposition.Axis}() + variables = Set{JuMP.MOI.VariableIndex}() + constraints_to_axes = Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}}() + constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() + constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) + + for k in keys(model.obj_dict) #check all names in the model + # store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) + reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) + if typeof(reference[1]) <: JuMP.ConstraintRef #constraint + for c in reference + _add_constraint!(constraints_and_axes, c, model, reference) + end + elseif typeof(reference[1]) <: JuMP.VariableRef + for v in reference + push!(variables, JuMP.index(v)) + end + end + end + constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) + add_anonymous_var_and_con!(constraints_and_axes, model) + return constraints_and_axes +end + +function add_anonymous_var_and_con!(car::Constraints_and_Axes, model::JuMP.Model) #adds anonymous constraints and axes from the model to constraints_and_axes (car) + types = JuMP.list_of_constraint_types(model) + for t in types + if t[1] != VariableRef + for c in JuMP.all_constraints(model, t[1], t[2]) + if !in(c, car.constraints) + push!(car.constraints, c) + car.constraints_to_axes[c] = Set{BlockDecomposition.Axis}() + car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) + end + end + end + end + for v in JuMP.all_variables(model) + push!(car.variables, JuMP.index(v)) + end +end + +function _add_constraint!(o::Constraints_and_Axes, c::JuMP.ConstraintRef, model::JuMP.Model, reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray + push!(o.constraints, c) + axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) + for r in axes_of_constraint + push!(o.axes, r) + end + o.constraints_to_axes[c] = axes_of_constraint + o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) +end + +function _get_axes_of_constraint(reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray + axes_of_constraint = Set{BlockDecomposition.Axis}() + for a in reference_constraints_name.axes + if a != 1 #axes of the form 1:1 do not matter (single constraints) + push!(axes_of_constraint, Axis(a)) + end + end + return axes_of_constraint +end + +function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.ConstraintRef) + f = MOI.get(model, MathOptInterface.ConstraintFunction(), constraint) + variables = Set{MOI.VariableIndex}() + for term in f.terms + push!(variables, term.variable_index) + end + return variables +end + +function get_block_structure(axes::Array{<:Axis,1}, constraints_and_axes::Constraints_and_Axes, model::JuMP.Model) + vertices = Set{JuMP.ConstraintRef}() + master_constraints = Set{JuMP.ConstraintRef}() + connected_components = Set{Set{JuMP.ConstraintRef}}() + + for c in keys(constraints_and_axes.constraints_to_axes) + if !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) #which constraints are build over at least one set that is contained in axes? + push!(master_constraints, c) + else + push!(vertices, c) + end + end + + graph = _create_graph(vertices, constraints_and_axes) + blocks = _get_connected_components!(graph) + block_structure = Block_Structure(master_constraints, axes, blocks, graph) + return block_structure +end + +function _get_connected_components!(graph::MetaGraphs.MetaGraph) + connected_components_int = connected_components(graph) + blocks = Array{Set{JuMP.ConstraintRef},1}() + for component_int in connected_components_int + component_constraintref = Set{JuMP.ConstraintRef}() + for vertex_int in component_int + push!(component_constraintref, graph[vertex_int, :constraint_ref]) + end + push!(blocks, component_constraintref) + end + return blocks +end + +function _create_graph(vertices::Set{JuMP.ConstraintRef}, constraints_and_axes::Constraints_and_Axes) + graph = LightGraphs.SimpleGraph(0) + graph = MetaGraphs.MetaGraph(graph) + n_vertices = LightGraphs.add_vertices!(graph, length(vertices)) + MetaGraphs.set_indexing_prop!(graph, :constraint_ref) + i = 1 + #set values for indexing property :constraint_ref + for ver in vertices + MetaGraphs.set_prop!(graph, i, :constraint_ref, ver) + i = i+1 + end + #build edges + for v1 in vertices, v2 in vertices + if v1 != v2 + intersection = LightGraphs.intersect(constraints_and_axes.constraints_to_variables[v1], constraints_and_axes.constraints_to_variables[v2]) + isempty(intersection) ? true : LightGraphs.add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) + end + end + return graph +end + +function draw_graph(mgraph::MetaGraphs.MetaGraph) + sgraph = SimpleGraph(nv(mgraph)) + labels = Array{String}(undef, nv(mgraph)) + i = 1 + for v in collect(vertices(mgraph)) + labels[i] = convert(String, repr(mgraph[i, :constraint_ref])) + i = i + 1 + end + for e in edges(mgraph) + add_edge!(sgraph, e) + end + t = TikzGraphs.plot(sgraph, labels) + save(SVG("graph.svg"),t) +end \ No newline at end of file diff --git a/src/axis.jl b/src/axis.jl index 5dd1e8b..de47638 100644 --- a/src/axis.jl +++ b/src/axis.jl @@ -9,9 +9,6 @@ import Base.isless import Base.== import Base.vcat -using LightGraphs, MetaGraphs, TikzGraphs, Combinatorics, TikzPictures - - struct AxisId{Name, T} indice::T end @@ -85,200 +82,4 @@ macro axis(args...) end exp = :($name = $(_generate_axis(name, container))) return esc(exp) -end - - - - -function get_best_block_structure(model::JuMP.Model) - constraints_and_axes = get_constraints_and_axes(model) - block_structures = Array{Block_Structure,1}() - axesSets = collect(Combinatorics.powerset(collect(constraints_and_axes.axes))) - for axes in axesSets - block_structure = get_block_structure(axes, constraints_and_axes, model) - push!(block_structures, block_structure) - end - return block_structures[3] #to do: find best decomposition -end - -struct Block_Structure - master_constraints::Set{JuMP.ConstraintRef} - master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints - blocks::Array{Set{JuMP.ConstraintRef},1} - graph::MetaGraphs.MetaGraph -end - -mutable struct Constraints_and_Axes #constains all the information we need to check different decompositons - constraints::Set{JuMP.ConstraintRef} - axes::Set{BlockDecomposition.Axis} - variables::Set{MOI.VariableIndex} # MOI. VariableIndex can be used for reverencing variables in a model - constraints_to_axes::Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}} - constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} -end - -function plumple(block_structures::Array{Block_Structure,1}, constraints_and_axes::Constraints_and_Axes) - result = nothing - best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) - for block_structure in block_structures - plumple_value = _get_plumple_value(block_structure, constraints_and_axes) - if plumple_value <= best - best = plumple_value - result = block_structure - end - end - return result -end - -function _get_plumple_value(block_structure::Block_Structure, constraints_and_axes::Constraints_and_Axes) - n_master = length(constraints_and_axes.variables) * length(block_structure.master_constraints) - n_blocks = 0 - for block in block_structure.blocks - for constraint in block - n_blocks = n_blocks + length(constraints_and_axes.constraints_to_variables[constraint]) - end - end - return n_master+n_blocks -end - - - -function get_constraints_and_axes(model::JuMP.Model) #returns an instance of the struct Constraints_and_axes - constraints = Set{JuMP.ConstraintRef}() - axes = Set{BlockDecomposition.Axis}() - variables = Set{JuMP.MOI.VariableIndex}() - constraints_to_axes = Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}}() - constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() - constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) - - for k in keys(model.obj_dict) #check all names in the model - # store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) - reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) - if typeof(reference[1]) <: JuMP.ConstraintRef #constraint - for c in reference - _add_constraint!(constraints_and_axes, c, model, reference) - end - elseif typeof(reference[1]) <: JuMP.VariableRef - for v in reference - push!(variables, JuMP.index(v)) - end - end - end - constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) - add_anonymous_var_and_con!(constraints_and_axes, model) - return constraints_and_axes -end - -function add_anonymous_var_and_con!(car::Constraints_and_Axes, model::JuMP.Model) #adds anonymous constraints and axes from the model to constraints_and_axes (car) - types = JuMP.list_of_constraint_types(model) - for t in types - if t[1] != VariableRef - for c in JuMP.all_constraints(model, t[1], t[2]) - if !in(c, car.constraints) - push!(car.constraints, c) - car.constraints_to_axes[c] = Set{BlockDecomposition.Axis}() - car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) - end - end - end - end - for v in JuMP.all_variables(model) - push!(car.variables, JuMP.index(v)) - end -end - -function _add_constraint!(o::Constraints_and_Axes, c::JuMP.ConstraintRef, model::JuMP.Model, reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray - push!(o.constraints, c) - axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) - for r in axes_of_constraint - push!(o.axes, r) - end - o.constraints_to_axes[c] = axes_of_constraint - o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) -end - -function _get_axes_of_constraint(reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray - axes_of_constraint = Set{BlockDecomposition.Axis}() - for a in reference_constraints_name.axes - if a != 1 #axes of the form 1:1 do not matter (single constraints) - push!(axes_of_constraint, Axis(a)) - end - end - return axes_of_constraint -end - -function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.ConstraintRef) - f = MOI.get(model, MathOptInterface.ConstraintFunction(), constraint) - variables = Set{MOI.VariableIndex}() - for term in f.terms - push!(variables, term.variable_index) - end - return variables -end - -function get_block_structure(axes::Array{<:Axis,1}, constraints_and_axes::Constraints_and_Axes, model::JuMP.Model) - vertices = Set{JuMP.ConstraintRef}() - master_constraints = Set{JuMP.ConstraintRef}() - connected_components = Set{Set{JuMP.ConstraintRef}}() - - for c in keys(constraints_and_axes.constraints_to_axes) - if !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) #which constraints are build over at least one set that is contained in axes? - push!(master_constraints, c) - else - push!(vertices, c) - end - end - - graph = _create_graph(vertices, constraints_and_axes) - blocks = _get_connected_components!(graph) - block_structure = Block_Structure(master_constraints, axes, blocks, graph) - return block_structure -end - -function _get_connected_components!(graph::MetaGraphs.MetaGraph) - connected_components_int = connected_components(graph) - blocks = Array{Set{JuMP.ConstraintRef},1}() - for component_int in connected_components_int - component_constraintref = Set{JuMP.ConstraintRef}() - for vertex_int in component_int - push!(component_constraintref, graph[vertex_int, :constraint_ref]) - end - push!(blocks, component_constraintref) - end - return blocks -end - -function _create_graph(vertices::Set{JuMP.ConstraintRef}, constraints_and_axes::Constraints_and_Axes) - graph = LightGraphs.SimpleGraph(0) - graph = MetaGraphs.MetaGraph(graph) - n_vertices = LightGraphs.add_vertices!(graph, length(vertices)) - MetaGraphs.set_indexing_prop!(graph, :constraint_ref) - i = 1 - #set values for indexing property :constraint_ref - for ver in vertices - MetaGraphs.set_prop!(graph, i, :constraint_ref, ver) - i = i+1 - end - #build edges - for v1 in vertices, v2 in vertices - if v1 != v2 - intersection = LightGraphs.intersect(constraints_and_axes.constraints_to_variables[v1], constraints_and_axes.constraints_to_variables[v2]) - isempty(intersection) ? true : LightGraphs.add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) - end - end - return graph -end - -function draw_graph(mgraph::MetaGraphs.MetaGraph) - sgraph = SimpleGraph(nv(mgraph)) - labels = Array{String}(undef, nv(mgraph)) - i = 1 - for v in collect(vertices(mgraph)) - labels[i] = convert(String, repr(mgraph[i, :constraint_ref])) - i = i + 1 - end - for e in edges(mgraph) - add_edge!(sgraph, e) - end - t = TikzGraphs.plot(sgraph, labels) - save(SVG("graph.svg"),t) end \ No newline at end of file From 124a7c67f32ad1ca1df4049d265323d237e7629f Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sat, 20 Jun 2020 11:24:47 +0200 Subject: [PATCH 06/32] Apply style guide blue style Source: https://github.com/invenia/BlueStyle --- src/BlockDecomposition.jl | 12 +- src/automatic_decomposition.jl | 360 ++++++++++++++++++--------------- src/decomposition.jl | 65 +++--- src/tree.jl | 24 +-- 4 files changed, 250 insertions(+), 211 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index a9ad69d..eea5fe2 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -1,6 +1,12 @@ module BlockDecomposition -using JuMP, MathOptInterface +using Combinatorics: powerset +using JuMP +using LightGraphs: SimpleGraph, add_vertices! +using MathOptInterface +using MetaGraphs: MetaGraph, add_edge!, connected_components, edges, intersect, set_indexing_prop!, set_prop!, vertices +using TikzGraphs: plot +using TikzPictures: SVG import MathOptInterface import MathOptInterface.Utilities @@ -23,10 +29,10 @@ include("objective.jl") include("callbacks.jl") include("automatic_decomposition.jl") -function BlockModel(args...; automatic_decomposition = false, kw...) +function BlockModel(args...; automatic_decomposition=false, kw...) m = JuMP.Model(args...; kw...) JuMP.set_optimize_hook(m, optimize!) - m.ext[:automatic_decomposition] = automatic_decomposition + m.ext[:automatic_decomposition] = automatic_decomposition return m end diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index a8affec..0ad22a3 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -1,194 +1,226 @@ -using LightGraphs, MetaGraphs, TikzGraphs, Combinatorics, TikzPictures - function get_best_block_structure(model::JuMP.Model) - constraints_and_axes = get_constraints_and_axes(model) - block_structures = Array{Block_Structure,1}() - axesSets = collect(Combinatorics.powerset(collect(constraints_and_axes.axes))) - for axes in axesSets - block_structure = get_block_structure(axes, constraints_and_axes, model) - push!(block_structures, block_structure) - end - return block_structures[2] #to do: find best decomposition + constraints_and_axes = get_constraints_and_axes(model) + block_structures = Array{BlockStructure,1}() + axesSets = collect(powerset(collect(constraints_and_axes.axes))) + for axes in axesSets + block_structure = get_block_structure(axes, constraints_and_axes, model) + push!(block_structures, block_structure) + end + return block_structures[2] # To do: find best decomposition end -struct Block_Structure +struct BlockStructure master_constraints::Set{JuMP.ConstraintRef} - master_sets::Array{BlockDecomposition.Axis,1} #index sets used to determine which constraints are master constraints - blocks::Array{Set{JuMP.ConstraintRef},1} - graph::MetaGraphs.MetaGraph + master_sets::Array{BlockDecomposition.Axis,1} + blocks::Array{Set{JuMP.ConstraintRef},1} + graph::MetaGraph end -mutable struct Constraints_and_Axes #constains all the information we need to check different decompositons - constraints::Set{JuMP.ConstraintRef} - axes::Set{BlockDecomposition.Axis} - variables::Set{MOI.VariableIndex} # MOI. VariableIndex can be used for reverencing variables in a model - constraints_to_axes::Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}} - constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} +# Contains all the information we need to check different decompositons +mutable struct Constraints_and_Axes + constraints::Set{JuMP.ConstraintRef} + axes::Set{BlockDecomposition.Axis} + variables::Set{MOI.VariableIndex} + constraints_to_axes::Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}} + constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} end -function plumple(block_structures::Array{Block_Structure,1}, constraints_and_axes::Constraints_and_Axes) - result = nothing - best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) - for block_structure in block_structures - plumple_value = _get_plumple_value(block_structure, constraints_and_axes) - if plumple_value <= best - best = plumple_value - result = block_structure - end - end - return result +function plumple( + block_structures::Array{BlockStructure,1}, + constraints_and_axes::Constraints_and_Axes, +) + result = nothing + best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) + for block_structure in block_structures + plumple_value = _get_plumple_value(block_structure, constraints_and_axes) + if plumple_value <= best + best = plumple_value + result = block_structure + end + end + return result end -function _get_plumple_value(block_structure::Block_Structure, constraints_and_axes::Constraints_and_Axes) - n_master = length(constraints_and_axes.variables) * length(block_structure.master_constraints) - n_blocks = 0 - for block in block_structure.blocks - for constraint in block - n_blocks = n_blocks + length(constraints_and_axes.constraints_to_variables[constraint]) - end - end - return n_master+n_blocks +function _get_plumple_value( + block_structure::BlockStructure, + constraints_and_axes::Constraints_and_Axes, +) + n_master = length(constraints_and_axes.variables) * length(block_structure.master_constraints) + n_blocks = 0 + for block in block_structure.blocks + for constraint in block + n_blocks = n_blocks + length(constraints_and_axes.constraints_to_variables[constraint]) + end + end + return n_master+n_blocks end - - -function get_constraints_and_axes(model::JuMP.Model) #returns an instance of the struct Constraints_and_axes - constraints = Set{JuMP.ConstraintRef}() - axes = Set{BlockDecomposition.Axis}() - variables = Set{JuMP.MOI.VariableIndex}() - constraints_to_axes = Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}}() - constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() - constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) - - for k in keys(model.obj_dict) #check all names in the model - # store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) - reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) - if typeof(reference[1]) <: JuMP.ConstraintRef #constraint - for c in reference - _add_constraint!(constraints_and_axes, c, model, reference) - end - elseif typeof(reference[1]) <: JuMP.VariableRef - for v in reference - push!(variables, JuMP.index(v)) - end - end - end - constraints_and_axes = Constraints_and_Axes(constraints, axes, variables, constraints_to_axes, constraints_to_variables) - add_anonymous_var_and_con!(constraints_and_axes, model) - return constraints_and_axes +# Returns an instance of the struct Constraints_and_axes +function get_constraints_and_axes(model::JuMP.Model) + constraints = Set{JuMP.ConstraintRef}() + axes = Set{BlockDecomposition.Axis}() + variables = Set{JuMP.MOI.VariableIndex}() + constraints_to_axes = Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}}() + constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() + constraints_and_axes = Constraints_and_Axes( + constraints, + axes, + variables, + constraints_to_axes, + constraints_to_variables + ) + for k in keys(model.obj_dict) # Check all names in the model + # Store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) + reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) + if typeof(reference[1]) <: JuMP.ConstraintRef + for c in reference + _add_constraint!(constraints_and_axes, c, model, reference) + end + elseif typeof(reference[1]) <: JuMP.VariableRef + for v in reference + push!(variables, JuMP.index(v)) + end + end + end + constraints_and_axes = Constraints_and_Axes( + constraints, + axes, + variables, + constraints_to_axes, + constraints_to_variables + ) + add_anonymous_var_con!(constraints_and_axes, model) + return constraints_and_axes end -function add_anonymous_var_and_con!(car::Constraints_and_Axes, model::JuMP.Model) #adds anonymous constraints and axes from the model to constraints_and_axes (car) - types = JuMP.list_of_constraint_types(model) - for t in types - if t[1] != VariableRef - for c in JuMP.all_constraints(model, t[1], t[2]) - if !in(c, car.constraints) - push!(car.constraints, c) - car.constraints_to_axes[c] = Set{BlockDecomposition.Axis}() - car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) - end - end - end - end - for v in JuMP.all_variables(model) - push!(car.variables, JuMP.index(v)) - end +# Add anonymous constraints and axes from the model to constraints_and_axes (car) +function add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) + types = JuMP.list_of_constraint_types(model) + for t in types + if t[1] != VariableRef + for c in JuMP.all_constraints(model, t[1], t[2]) + if !in(c, car.constraints) + push!(car.constraints, c) + car.constraints_to_axes[c] = Set{BlockDecomposition.Axis}() + car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) + end + end + end + end + for v in JuMP.all_variables(model) + push!(car.variables, JuMP.index(v)) + end end -function _add_constraint!(o::Constraints_and_Axes, c::JuMP.ConstraintRef, model::JuMP.Model, reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray - push!(o.constraints, c) - axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) - for r in axes_of_constraint - push!(o.axes, r) - end - o.constraints_to_axes[c] = axes_of_constraint - o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) -end +function _add_constraint!( + o::Constraints_and_Axes, + c::JuMP.ConstraintRef, + model::JuMP.Model, + reference_constraints_name::T, + ) where T <: JuMP.Containers.DenseAxisArray + push!(o.constraints, c) + axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) + for r in axes_of_constraint + push!(o.axes, r) + end + o.constraints_to_axes[c] = axes_of_constraint + o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) +end function _get_axes_of_constraint(reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray - axes_of_constraint = Set{BlockDecomposition.Axis}() - for a in reference_constraints_name.axes - if a != 1 #axes of the form 1:1 do not matter (single constraints) - push!(axes_of_constraint, Axis(a)) - end - end - return axes_of_constraint + axes_of_constraint = Set{BlockDecomposition.Axis}() + for a in reference_constraints_name.axes + if a != 1 # Axes of the form 1:1 do not matter (single constraints) + push!(axes_of_constraint, Axis(a)) + end + end + return axes_of_constraint end function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.ConstraintRef) f = MOI.get(model, MathOptInterface.ConstraintFunction(), constraint) - variables = Set{MOI.VariableIndex}() - for term in f.terms - push!(variables, term.variable_index) - end - return variables + variables = Set{MOI.VariableIndex}() + for term in f.terms + push!(variables, term.variable_index) + end + return variables end -function get_block_structure(axes::Array{<:Axis,1}, constraints_and_axes::Constraints_and_Axes, model::JuMP.Model) - vertices = Set{JuMP.ConstraintRef}() - master_constraints = Set{JuMP.ConstraintRef}() - connected_components = Set{Set{JuMP.ConstraintRef}}() - - for c in keys(constraints_and_axes.constraints_to_axes) - if !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) #which constraints are build over at least one set that is contained in axes? - push!(master_constraints, c) - else - push!(vertices, c) - end - end - - graph = _create_graph(vertices, constraints_and_axes) - blocks = _get_connected_components!(graph) - block_structure = Block_Structure(master_constraints, axes, blocks, graph) - return block_structure +function get_block_structure( + axes::Array{<:Axis,1}, + constraints_and_axes::Constraints_and_Axes, + model::JuMP.Model, +) + vertices = Set{JuMP.ConstraintRef}() + master_constraints = Set{JuMP.ConstraintRef}() + connected_components = Set{Set{JuMP.ConstraintRef}}() + + for c in keys(constraints_and_axes.constraints_to_axes) + # Check if constraints are constructed over at least one set which is contained in axes + if !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) + push!(master_constraints, c) + else + push!(vertices, c) + end + end + + graph = _create_graph(vertices, constraints_and_axes) + blocks = _get_connected_components!(graph) + block_structure = BlockStructure(master_constraints, axes, blocks, graph) + return block_structure end -function _get_connected_components!(graph::MetaGraphs.MetaGraph) - connected_components_int = connected_components(graph) - blocks = Array{Set{JuMP.ConstraintRef},1}() - for component_int in connected_components_int - component_constraintref = Set{JuMP.ConstraintRef}() - for vertex_int in component_int - push!(component_constraintref, graph[vertex_int, :constraint_ref]) - end - push!(blocks, component_constraintref) - end - return blocks +function _get_connected_components!(graph::MetaGraph) + connected_components_int = connected_components(graph) + blocks = Array{Set{JuMP.ConstraintRef},1}() + for component_int in connected_components_int + component_constraintref = Set{JuMP.ConstraintRef}() + for vertex_int in component_int + push!(component_constraintref, graph[vertex_int, :constraint_ref]) + end + push!(blocks, component_constraintref) + end + return blocks end -function _create_graph(vertices::Set{JuMP.ConstraintRef}, constraints_and_axes::Constraints_and_Axes) - graph = LightGraphs.SimpleGraph(0) - graph = MetaGraphs.MetaGraph(graph) - n_vertices = LightGraphs.add_vertices!(graph, length(vertices)) - MetaGraphs.set_indexing_prop!(graph, :constraint_ref) - i = 1 - #set values for indexing property :constraint_ref - for ver in vertices - MetaGraphs.set_prop!(graph, i, :constraint_ref, ver) - i = i+1 - end - #build edges - for v1 in vertices, v2 in vertices - if v1 != v2 - intersection = LightGraphs.intersect(constraints_and_axes.constraints_to_variables[v1], constraints_and_axes.constraints_to_variables[v2]) - isempty(intersection) ? true : LightGraphs.add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) - end - end - return graph +function _create_graph( + vertices::Set{JuMP.ConstraintRef}, + constraints_and_axes::Constraints_and_Axes, +) + graph = SimpleGraph(0) + graph = MetaGraph(graph) + n_vertices = add_vertices!(graph, length(vertices)) + set_indexing_prop!(graph, :constraint_ref) + i = 1 + # Set values for indexing property :constraint_ref + for ver in vertices + set_prop!(graph, i, :constraint_ref, ver) + i = i+1 + end + # Build edges + for v1 in vertices, v2 in vertices + if v1 != v2 + intersection = intersect( + constraints_and_axes.constraints_to_variables[v1], + constraints_and_axes.constraints_to_variables[v2], + ) + isempty(intersection) ? true : add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) + end + end + return graph end -function draw_graph(mgraph::MetaGraphs.MetaGraph) - sgraph = SimpleGraph(nv(mgraph)) - labels = Array{String}(undef, nv(mgraph)) - i = 1 - for v in collect(vertices(mgraph)) - labels[i] = convert(String, repr(mgraph[i, :constraint_ref])) - i = i + 1 - end - for e in edges(mgraph) - add_edge!(sgraph, e) - end - t = TikzGraphs.plot(sgraph, labels) - save(SVG("graph.svg"),t) -end \ No newline at end of file +function draw_graph(mgraph::MetaGraph) + sgraph = SimpleGraph(nv(mgraph)) + labels = Array{String}(undef, nv(mgraph)) + i = 1 + for v in collect(vertices(mgraph)) + labels[i] = convert(String, repr(mgraph[i, :constraint_ref])) + i = i + 1 + end + for e in edges(mgraph) + add_edge!(sgraph, e) + end + t = TikzGraphs.plot(sgraph, labels) + save(SVG("graph.svg"),t) +end diff --git a/src/decomposition.jl b/src/decomposition.jl index 19a2c10..268546c 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -1,44 +1,45 @@ """ register_decomposition(model) -Assign to each variable and constraint an annotation indicating in -which partition (master/subproblem) of the original formulation the variable +Assign to each variable and constraint an annotation indicating in +which partition (master/subproblem) of the original formulation the variable or the constraint is located. """ function register_decomposition(model::JuMP.Model) # Link to the tree - if model.ext[:automatic_decomposition] - register_automatic_decomposition(model) - else - tree = gettree(model) - for (key, jump_obj) in model.obj_dict - _annotate_elements!(model, jump_obj, tree) - end - end + if model.ext[:automatic_decomposition] + register_automatic_decomposition(model) + else + tree = gettree(model) + for (key, jump_obj) in model.obj_dict + _annotate_elements!(model, jump_obj, tree) + end + end return end function register_automatic_decomposition(model::JuMP.Model) tree = gettree(model) - for variableref in JuMP.all_variables(model) - ann = _getannotation(tree, Vector{AxisId}()) - setannotation!(model, variableref, ann) - end - decomposition_structure = model.ext[:decomposition_structure] - for constraintref in decomposition_structure.master_constraints - ann = _getannotation(tree, Vector{AxisId}()) - setannotation!(model, constraintref, ann) - end - virtual_axis = BlockDecomposition.Axis(1:length(decomposition_structure.blocks)) - axisids = Vector{AxisId}() - for i in virtual_axis - empty!(axisids) - push!(axisids, i) - ann = _getannotation(tree, axisids) - for constraintref in decomposition_structure.blocks[i] #annotate constraints in one block with the same annotation - setannotation!(model, constraintref, ann) - end - end + for variableref in JuMP.all_variables(model) + ann = _getannotation(tree, Vector{AxisId}()) + setannotation!(model, variableref, ann) + end + decomposition_structure = model.ext[:decomposition_structure] + for constraintref in decomposition_structure.master_constraints + ann = _getannotation(tree, Vector{AxisId}()) + setannotation!(model, constraintref, ann) + end + virtual_axis = BlockDecomposition.Axis(1:length(decomposition_structure.blocks)) + axisids = Vector{AxisId}() + for i in virtual_axis + empty!(axisids) + push!(axisids, i) + ann = _getannotation(tree, axisids) + # Annotate constraints in one block with the same annotation + for constraintref in decomposition_structure.blocks[i] + setannotation!(model, constraintref, ann) + end + end end _getrootmasterannotation(tree) = tree.root.master @@ -111,7 +112,7 @@ struct DecompositionTree <: MOI.AbstractModelAttribute end setannotation!(model, obj::JuMP.ConstraintRef, a) = MOI.set(model, ConstraintDecomposition(), obj, a) setannotation!(model, obj::JuMP.VariableRef, a) = MOI.set(model, VariableDecomposition(), obj, a) -function MOI.set(dest::MOIU.UniversalFallback, attribute::ConstraintDecomposition, +function MOI.set(dest::MOIU.UniversalFallback, attribute::ConstraintDecomposition, ci::MOI.ConstraintIndex, annotation::Annotation) if !haskey(dest.conattr, attribute) dest.conattr[attribute] = Dict{MOI.ConstraintIndex, Tuple}() @@ -121,7 +122,7 @@ function MOI.set(dest::MOIU.UniversalFallback, attribute::ConstraintDecompositio end function MOI.set( - dest::MOIU.UniversalFallback, attribute::VariableDecomposition, + dest::MOIU.UniversalFallback, attribute::VariableDecomposition, vi::MOI.VariableIndex, ann::Annotation ) if !haskey(dest.varattr, attribute) @@ -188,7 +189,7 @@ function setannotations!( end function setannotations!( - model::JuMP.Model, objref::AbstractArray, indices_set::Vector{Tuple}, + model::JuMP.Model, objref::AbstractArray, indices_set::Vector{Tuple}, ann::Annotation ) for indices in indices_set diff --git a/src/tree.jl b/src/tree.jl index 5e7ce9e..6c33025 100644 --- a/src/tree.jl +++ b/src/tree.jl @@ -32,7 +32,7 @@ struct Leaf{V} <: AbstractNode edge_id::V end -struct Node{N,V,T} <: AbstractNode +struct Node{N,V,T} <: AbstractNode tree::Tree parent::AbstractNode depth::Int @@ -99,7 +99,7 @@ function getnodes(tree::Tree) queue = Queue{AbstractNode}() enqueue!(queue, tree.root) while length(queue) > 0 - node = dequeue!(queue) + node = dequeue!(queue) for (key, child) in node.subproblems if typeof(child) <: Leaf # if child node is a leaf, append it to the nodes list push!(vec_nodes, child) @@ -134,7 +134,7 @@ function decompose_leaf(m::JuMP.Model, D::Type{<: Decomposition}, axis::Axis) end function decompose_leaf(n::AbstractNode, D::Type{<: Decomposition}, axis::Axis) - error("BlockDecomposition does not support nested decomposition yet.") + error("BlockDecomposition does not support nested decomposition yet.") return end @@ -154,15 +154,15 @@ end macro dantzig_wolfe_decomposition(args...) - node, name, axis = args - dw_exp = quote - if length($args) != 3 - error("Three arguments expected: model, decomposition name, and axis") - end - if $node.ext[:automatic_decomposition] - $node.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure($node) - $axis = BlockDecomposition.Axis(1:length($node.ext[:decomposition_structure].blocks)) - end + node, name, axis = args + dw_exp = quote + if length($args) != 3 + error("Three arguments expected: model, decomposition name, and axis") + end + if $node.ext[:automatic_decomposition] + $node.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure($node) + $axis = BlockDecomposition.Axis(1:length($node.ext[:decomposition_structure].blocks)) + end $name = BlockDecomposition.decompose_leaf($node, BlockDecomposition.DantzigWolfe, $axis) # initialize a tree for the current root node BlockDecomposition.register_subproblems!($name, $axis, BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe) end From aee20d7aca21560e7e448cc58cfa0403a291b0b8 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Mon, 22 Jun 2020 21:50:22 +0200 Subject: [PATCH 07/32] Add test method for automatic decomposition The test only consists of optimizing the example problem for Coluna using automatic decomposition. Note: An error occurs during the execution for an unknown reason. --- src/automatic_decomposition.jl | 2 +- test/automatic_decomposition.jl | 41 +++++---------------------------- test/formulations.jl | 31 +++++++++++++++++++++++++ test/runtests.jl | 2 ++ 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 0ad22a3..8374bbf 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -6,7 +6,7 @@ function get_best_block_structure(model::JuMP.Model) block_structure = get_block_structure(axes, constraints_and_axes, model) push!(block_structures, block_structure) end - return block_structures[2] # To do: find best decomposition + return plumple(block_structures, constraints_and_axes) end struct BlockStructure diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl index 46bec8f..9c64278 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_decomposition.jl @@ -1,35 +1,6 @@ -nb_machines = 4 -nb_jobs = 30 -c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5 15.2 14.3 12.6 9.2 20.8 11.7 17.3 9.2 20.3 11.4 6.2 13.8 10.0 20.9 20.6; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2 19.7 19.5 7.2 6.4 23.2 8.1 13.6 24.6 15.6 22.3 8.8 19.1 18.4 22.9 8.0; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7 16.6 8.3 15.9 24.3 18.6 21.1 7.5 16.8 20.9 8.9 15.2 15.7 12.7 20.8 10.4; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2 12.9 11.3 7.5 6.5 11.3 7.8 13.8 20.7 16.8 23.6 19.1 16.8 19.3 12.5 11.0] -w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78 71 50 99 92 83 53 91 68 61 63 97 91 77 68 80; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91 75 66 100 69 60 87 98 78 62 90 89 67 87 65 100; 91 81 66 63 59 81 87 90 65 55 57 68 92 91 86 74 80 89 95 57 55 96 77 60 55 57 56 67 81 52; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54 66 50 56 70 56 72 62 85 70 100 57 96 69 65 50] -Q = [1020 1460 1530 1190] - -coluna = optimizer_with_attributes( - Coluna.Optimizer, - "params" => Coluna.Params( - solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP - ), - "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems -) - - -M = 1:nb_machines -J = 1:nb_jobs - -model = BlockModel(coluna, bridge_constraints = false; automatic_decomposition = true) -@variable(model, x[m in M, j in J], Bin) -@constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) -@constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) -@objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)) -@dantzig_wolfe_decomposition(model, decomposition, Axis) - - -master = getmaster(decomposition) -subproblems = getsubproblems(decomposition) - -specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) - -optimize!(model) - - -value.(x[1,:]) \ No newline at end of file +function test_automatic_decomposition() + @testset "Example GAP automatic decomposition with Coluna" begin + model, x, cov, knp, dec, Axis = automatic_decomposition_coluna() + JuMP.optimize!(model) + end +end diff --git a/test/formulations.jl b/test/formulations.jl index f121fb0..cc56208 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -34,6 +34,37 @@ function generalized_assignement(d::GapData) return model, x, cov, knp, decomposition end +function automatic_decomposition_coluna() + nb_machines = 4 + nb_jobs = 30 + c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5 15.2 14.3 12.6 9.2 20.8 11.7 17.3 9.2 20.3 11.4 6.2 13.8 10.0 20.9 20.6; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2 19.7 19.5 7.2 6.4 23.2 8.1 13.6 24.6 15.6 22.3 8.8 19.1 18.4 22.9 8.0; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7 16.6 8.3 15.9 24.3 18.6 21.1 7.5 16.8 20.9 8.9 15.2 15.7 12.7 20.8 10.4; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2 12.9 11.3 7.5 6.5 11.3 7.8 13.8 20.7 16.8 23.6 19.1 16.8 19.3 12.5 11.0] + w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78 71 50 99 92 83 53 91 68 61 63 97 91 77 68 80; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91 75 66 100 69 60 87 98 78 62 90 89 67 87 65 100; 91 81 66 63 59 81 87 90 65 55 57 68 92 91 86 74 80 89 95 57 55 96 77 60 55 57 56 67 81 52; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54 66 50 56 70 56 72 62 85 70 100 57 96 69 65 50] + Q = [1020 1460 1530 1190] + M = 1:nb_machines + J = 1:nb_jobs + coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems + ) + model = BlockModel(coluna, automatic_decomposition = true) + @variable(model, x[m in M, j in J], Bin) + @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) + @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) + @objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)) + @dantzig_wolfe_decomposition(model, decomposition, Axis) + + + master = getmaster(decomposition) + subproblems = getsubproblems(decomposition) + + specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) + + return model, x, cov, knp, decomposition, Axis +end + # Test pure master variables, constraint without id & variables without id function generalized_assignement_penalties(d::GapData) BD.@axis(Machines, d.machines) diff --git a/test/runtests.jl b/test/runtests.jl index 505f0b3..3380731 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,9 +21,11 @@ include("formulations.jl") include("dantzigwolfe.jl") include("benders.jl") include("assignsolver.jl") +include("automatic_decomposition.jl") axis_declarations() test_dantzig_wolfe_different() +test_automatic_decomposition() test_dantzig_wolfe_identical() test_dummy_model_decompositions() test_benders() From 8ae4c6f7101b005e4e017c5c0a16da5440aed1f4 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Wed, 24 Jun 2020 10:11:08 +0200 Subject: [PATCH 08/32] Extend tests for automatic decomposition Includes check for all variables and constraints if they are annotated correctly. --- test/automatic_decomposition.jl | 60 +++++++++++++++++++++++++++++++-- test/formulations.jl | 31 ++++++++++++----- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl index 9c64278..7af7c3d 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_decomposition.jl @@ -1,6 +1,60 @@ function test_automatic_decomposition() - @testset "Example GAP automatic decomposition with Coluna" begin - model, x, cov, knp, dec, Axis = automatic_decomposition_coluna() - JuMP.optimize!(model) + @testset "Example AP automatic decomposition" begin + model, x, cov, knp, dec, Axis = example_assignment_automatic_decomposition() + try + JuMP.optimize!(model) + catch e + @test e isa NoOptimizer + end + for variableref in JuMP.all_variables(model) + x_ann = BD.annotation(model, variableref) + test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 1, 1) + end + for constraintref in model.ext[:decomposition_structure].master_constraints + x_ann = BD.annotation(model, xconstraintref) + test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 0, 1) + end + for block in model.ext[:decomposition_structure].blocks + for constraintref in block + x_ann = BD.annotation(model, constraintref) + test_annotation(x_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) + end + end + master = getmaster(dec) + @test repr(master) == "Master formulation.\n" + + subproblems = getsubproblems(dec) + @test repr(subproblems[1]) == "Subproblem formulation for = 1 contains :\t 0.0 <= multiplicity <= 1.0\n" + + tree = gettree(model) + @test gettree(model) == gettree(dec) + + @test repr(dec) == "Root - Annotation(BlockDecomposition.Master, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 2) with 1 subproblems :\n\t 1 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 0.0, um = 1.0, id = 3) \n" + end + + d = GapToyData(30,10) + @testset "GAP automatic decomposition" begin + model, x, cov, knp, dec, Axis = generalized_assignment_automatic_decomposition(d) + try + JuMP.optimize!(model) + catch e + @test e isa NoOptimizer + end + for variableref in JuMP.all_variables(model) + x_ann = BD.annotation(model, variableref) + test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 1, 1) + end + for constraintref in model.ext[:decomposition_structure].master_constraints + x_ann = BD.annotation(model, xconstraintref) + test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 0, 1) + end + for block in model.ext[:decomposition_structure].blocks + for constraintref in block + x_ann = BD.annotation(model, constraintref) + test_annotation(x_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) + end + end + master = getmaster(dec) + @test repr(master) == "Master formulation.\n" end end diff --git a/test/formulations.jl b/test/formulations.jl index cc56208..53b1095 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -34,7 +34,7 @@ function generalized_assignement(d::GapData) return model, x, cov, knp, decomposition end -function automatic_decomposition_coluna() +function example_assignment_automatic_decomposition() nb_machines = 4 nb_jobs = 30 c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5 15.2 14.3 12.6 9.2 20.8 11.7 17.3 9.2 20.3 11.4 6.2 13.8 10.0 20.9 20.6; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2 19.7 19.5 7.2 6.4 23.2 8.1 13.6 24.6 15.6 22.3 8.8 19.1 18.4 22.9 8.0; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7 16.6 8.3 15.9 24.3 18.6 21.1 7.5 16.8 20.9 8.9 15.2 15.7 12.7 20.8 10.4; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2 12.9 11.3 7.5 6.5 11.3 7.8 13.8 20.7 16.8 23.6 19.1 16.8 19.3 12.5 11.0] @@ -42,14 +42,7 @@ function automatic_decomposition_coluna() Q = [1020 1460 1530 1190] M = 1:nb_machines J = 1:nb_jobs - coluna = optimizer_with_attributes( - Coluna.Optimizer, - "params" => Coluna.Params( - solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP - ), - "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems - ) - model = BlockModel(coluna, automatic_decomposition = true) + model = BlockModel(automatic_decomposition = true) @variable(model, x[m in M, j in J], Bin) @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) @@ -65,6 +58,26 @@ function automatic_decomposition_coluna() return model, x, cov, knp, decomposition, Axis end +function generalized_assignment_automatic_decomposition(d::GapData) + model = BlockModel(automatic_decomposition = true) + + @variable(model, x[j in d.jobs, m in d.machines], Bin) + + @constraint(model, cov[j in d.jobs], sum(x[j, m] for m in d.machines) >= 1) + @constraint(model, knp[m in d.machines], + sum(d.weights[j, m] * x[j, m] for j in d.jobs) <= d.capacities[m]) + + @objective(model, Min, + sum(d.costs[j, m] * x[j, m] for j in d.jobs, m in d.machines)) + + @dantzig_wolfe_decomposition(model, decomposition, Axis) + master = getmaster(decomposition) + subproblems = getsubproblems(decomposition) + specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) + + return model, x, cov, knp, decomposition, Axis +end + # Test pure master variables, constraint without id & variables without id function generalized_assignement_penalties(d::GapData) BD.@axis(Machines, d.machines) From 8187bd54ebb1d0352445429f6f067c73c1f3c431 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Mon, 29 Jun 2020 21:34:26 +0200 Subject: [PATCH 09/32] Annotate variables that occur in a constraint in the same subproblem as the constraint --- src/decomposition.jl | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/decomposition.jl b/src/decomposition.jl index 268546c..57c624e 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -6,40 +6,48 @@ which partition (master/subproblem) of the original formulation the variable or the constraint is located. """ function register_decomposition(model::JuMP.Model) - # Link to the tree if model.ext[:automatic_decomposition] register_automatic_decomposition(model) else tree = gettree(model) - for (key, jump_obj) in model.obj_dict - _annotate_elements!(model, jump_obj, tree) - end + for (key, jump_obj) in model.obj_dict + _annotate_elements!(model, jump_obj, tree) + end end return end function register_automatic_decomposition(model::JuMP.Model) tree = gettree(model) - for variableref in JuMP.all_variables(model) - ann = _getannotation(tree, Vector{AxisId}()) - setannotation!(model, variableref, ann) - end + # Annotate master constraints decomposition_structure = model.ext[:decomposition_structure] - for constraintref in decomposition_structure.master_constraints - ann = _getannotation(tree, Vector{AxisId}()) - setannotation!(model, constraintref, ann) - end - virtual_axis = BlockDecomposition.Axis(1:length(decomposition_structure.blocks)) + _annotate_elements!(model, collect(decomposition_structure.master_constraints), tree) + # Annotate variables in blocks + variables_in_block = Set{MOI.VariableIndex}() + annotated_variables = Set{MOI.VariableIndex}() axisids = Vector{AxisId}() + virtual_axis = BlockDecomposition.Axis(1:length(decomposition_structure.blocks)) for i in virtual_axis + empty!(variables_in_block) empty!(axisids) push!(axisids, i) + # Annotate constraints in one block (and variables contained in these) with the same annotation ann = _getannotation(tree, axisids) - # Annotate constraints in one block with the same annotation for constraintref in decomposition_structure.blocks[i] setannotation!(model, constraintref, ann) + union!(variables_in_block, + model.ext[:decomposition_structure].constraints_and_axes.constraints_to_variables[constraintref] + ) + end + for v in variables_in_block + setannotation!(model, JuMP.VariableRef(model, v), ann) end + union!(annotated_variables, variables_in_block) end + + # Annotate all other variables + all_variables = MathOptInterface.get(model, MathOptInterface.ListOfVariableIndices()) + _annotate_elements!(model, [JuMP.VariableRef(model, v) for v in collect(setdiff(all_variables, annotated_variables))], tree) end _getrootmasterannotation(tree) = tree.root.master From b5d04c44dc468342ce07512f6d9c67485e63375a Mon Sep 17 00:00:00 2001 From: David Meichel Date: Tue, 30 Jun 2020 15:40:48 +0200 Subject: [PATCH 10/32] Improve the way automatic decomposition can be used The user does not have to use the macro @dantzig_wolfe_decompostion(...) anymore if automatic decomposition is used but decompose(model) can be called after the constructing all constraints and variables. Also fix a bug in the plumple method that scores different decompositions. The "black value" of a block is now computed as v*c where v is the number of different variables used in the block and c is the number of constraints in the block --- src/BlockDecomposition.jl | 11 ++-- src/automatic_decomposition.jl | 90 ++++++++++++++++++++------------- src/decomposition.jl | 6 +-- src/tree.jl | 13 ++--- test/automatic_decomposition.jl | 57 ++++++--------------- test/formulations.jl | 8 +-- 6 files changed, 87 insertions(+), 98 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index eea5fe2..5f145f4 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -2,11 +2,12 @@ module BlockDecomposition using Combinatorics: powerset using JuMP -using LightGraphs: SimpleGraph, add_vertices! +using LightGraphs: SimpleGraph, add_vertices!, nv using MathOptInterface -using MetaGraphs: MetaGraph, add_edge!, connected_components, edges, intersect, set_indexing_prop!, set_prop!, vertices +using MetaGraphs: MetaGraph, add_edge!, connected_components, edges, intersect, + set_indexing_prop!, set_prop!, vertices using TikzGraphs: plot -using TikzPictures: SVG +using TikzPictures: SVG, save import MathOptInterface import MathOptInterface.Utilities @@ -15,8 +16,8 @@ const MOI = MathOptInterface const MOIU = MOI.Utilities const JC = JuMP.Containers -export BlockModel, annotation, specify!, get_best_block_structure, gettree, getmaster, getsubproblems, - indice, objectiveprimalbound!, objectivedualbound! +export BlockModel, annotation, specify!, decompose, get_best_block_structure, gettree, + getmaster, getsubproblems, indice, objectiveprimalbound!, objectivedualbound! export @axis, @dantzig_wolfe_decomposition, @benders_decomposition diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 8374bbf..e75bbab 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -1,3 +1,4 @@ +# Finds the best decomposition structure of to be used by the solver function get_best_block_structure(model::JuMP.Model) constraints_and_axes = get_constraints_and_axes(model) block_structures = Array{BlockStructure,1}() @@ -6,33 +7,52 @@ function get_best_block_structure(model::JuMP.Model) block_structure = get_block_structure(axes, constraints_and_axes, model) push!(block_structures, block_structure) end - return plumple(block_structures, constraints_and_axes) + result = plumple(block_structures, constraints_and_axes) + return result end -struct BlockStructure - master_constraints::Set{JuMP.ConstraintRef} - master_sets::Array{BlockDecomposition.Axis,1} - blocks::Array{Set{JuMP.ConstraintRef},1} - graph::MetaGraph +# Decomposes the given JuMP Model automatically +function decompose(model::JuMP.Model) + model.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure(model) + decomposition_axis = BlockDecomposition.Axis(1:length(model.ext[:decomposition_structure].blocks)) + decomposition = BlockDecomposition.decompose_leaf( + model, + BlockDecomposition.DantzigWolfe, + decomposition_axis + ) + BlockDecomposition.register_subproblems!( + decomposition, + decomposition_axis, + BlockDecomposition.DwPricingSp, + BlockDecomposition.DantzigWolfe + ) + return decomposition, decomposition_axis end -# Contains all the information we need to check different decompositons +# Contains all the information about the constraints and variables in a model we might need +# to compute different block structures mutable struct Constraints_and_Axes constraints::Set{JuMP.ConstraintRef} axes::Set{BlockDecomposition.Axis} variables::Set{MOI.VariableIndex} - constraints_to_axes::Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}} + constraints_to_axes::Dict{JuMP.ConstraintRef, Array{BlockDecomposition.Axis}} constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} end -function plumple( - block_structures::Array{BlockStructure,1}, - constraints_and_axes::Constraints_and_Axes, -) +struct BlockStructure + # constraints_and_axes is the same for every possible BlockStructure of a model + constraints_and_axes::Constraints_and_Axes + master_constraints::Set{JuMP.ConstraintRef} + master_sets::Array{BlockDecomposition.Axis,1} + blocks::Array{Set{JuMP.ConstraintRef},1} + graph::MetaGraph +end + +function plumple(block_structures::Array{BlockStructure,1}, constraints_and_axes::Constraints_and_Axes) result = nothing best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) for block_structure in block_structures - plumple_value = _get_plumple_value(block_structure, constraints_and_axes) + plumple_value = _get_plumple_value(block_structure) if plumple_value <= best best = plumple_value result = block_structure @@ -41,26 +61,29 @@ function plumple( return result end -function _get_plumple_value( - block_structure::BlockStructure, - constraints_and_axes::Constraints_and_Axes, -) - n_master = length(constraints_and_axes.variables) * length(block_structure.master_constraints) +function _get_plumple_value(block_structure::BlockStructure) + n_master = length(block_structure.constraints_and_axes.variables) * length(block_structure.master_constraints) n_blocks = 0 + variables_in_block = Set{MOI.VariableIndex}() for block in block_structure.blocks + empty!(variables_in_block) for constraint in block - n_blocks = n_blocks + length(constraints_and_axes.constraints_to_variables[constraint]) + union!( + variables_in_block, + block_structure.constraints_and_axes.constraints_to_variables[constraint] + ) end + n_blocks = n_blocks + length(variables_in_block)*length(block) end return n_master+n_blocks end -# Returns an instance of the struct Constraints_and_axes +# Returns an instance of the struct Constraints_and_Axes function get_constraints_and_axes(model::JuMP.Model) constraints = Set{JuMP.ConstraintRef}() axes = Set{BlockDecomposition.Axis}() - variables = Set{JuMP.MOI.VariableIndex}() - constraints_to_axes = Dict{JuMP.ConstraintRef, Set{BlockDecomposition.Axis}}() + variables = Set{MOI.VariableIndex}() + constraints_to_axes = Dict{JuMP.ConstraintRef, Array{BlockDecomposition.Axis}}() constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() constraints_and_axes = Constraints_and_Axes( constraints, @@ -82,19 +105,13 @@ function get_constraints_and_axes(model::JuMP.Model) end end end - constraints_and_axes = Constraints_and_Axes( - constraints, - axes, - variables, - constraints_to_axes, - constraints_to_variables - ) - add_anonymous_var_con!(constraints_and_axes, model) + constraints_and_axes.variables = variables + _add_anonymous_var_con!(constraints_and_axes, model) return constraints_and_axes end # Add anonymous constraints and axes from the model to constraints_and_axes (car) -function add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) +function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) types = JuMP.list_of_constraint_types(model) for t in types if t[1] != VariableRef @@ -117,7 +134,7 @@ function _add_constraint!( c::JuMP.ConstraintRef, model::JuMP.Model, reference_constraints_name::T, - ) where T <: JuMP.Containers.DenseAxisArray +) where T <: JuMP.Containers.DenseAxisArray push!(o.constraints, c) axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) for r in axes_of_constraint @@ -128,7 +145,7 @@ function _add_constraint!( end function _get_axes_of_constraint(reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray - axes_of_constraint = Set{BlockDecomposition.Axis}() + axes_of_constraint = Array{BlockDecomposition.Axis,1}() for a in reference_constraints_name.axes if a != 1 # Axes of the form 1:1 do not matter (single constraints) push!(axes_of_constraint, Axis(a)) @@ -157,7 +174,8 @@ function get_block_structure( for c in keys(constraints_and_axes.constraints_to_axes) # Check if constraints are constructed over at least one set which is contained in axes - if !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) + # if axes is empty annotate everything as master + if isempty(axes) || !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) push!(master_constraints, c) else push!(vertices, c) @@ -166,7 +184,7 @@ function get_block_structure( graph = _create_graph(vertices, constraints_and_axes) blocks = _get_connected_components!(graph) - block_structure = BlockStructure(master_constraints, axes, blocks, graph) + block_structure = BlockStructure(constraints_and_axes, master_constraints, axes, blocks, graph) return block_structure end @@ -221,6 +239,6 @@ function draw_graph(mgraph::MetaGraph) for e in edges(mgraph) add_edge!(sgraph, e) end - t = TikzGraphs.plot(sgraph, labels) + t = plot(sgraph, labels) save(SVG("graph.svg"),t) end diff --git a/src/decomposition.jl b/src/decomposition.jl index 57c624e..7488ade 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -35,16 +35,16 @@ function register_automatic_decomposition(model::JuMP.Model) ann = _getannotation(tree, axisids) for constraintref in decomposition_structure.blocks[i] setannotation!(model, constraintref, ann) - union!(variables_in_block, + union!( + variables_in_block, model.ext[:decomposition_structure].constraints_and_axes.constraints_to_variables[constraintref] ) end - for v in variables_in_block + for v in variables_in_block setannotation!(model, JuMP.VariableRef(model, v), ann) end union!(annotated_variables, variables_in_block) end - # Annotate all other variables all_variables = MathOptInterface.get(model, MathOptInterface.ListOfVariableIndices()) _annotate_elements!(model, [JuMP.VariableRef(model, v) for v in collect(setdiff(all_variables, annotated_variables))], tree) diff --git a/src/tree.jl b/src/tree.jl index 6c33025..0acfd12 100644 --- a/src/tree.jl +++ b/src/tree.jl @@ -152,18 +152,13 @@ function register_multi_index_subproblems!(n::AbstractNode, multi_index::Tuple, end end - macro dantzig_wolfe_decomposition(args...) + if length(args) != 3 + error("Three arguments expected: model, decomposition name, and axis") + end node, name, axis = args dw_exp = quote - if length($args) != 3 - error("Three arguments expected: model, decomposition name, and axis") - end - if $node.ext[:automatic_decomposition] - $node.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure($node) - $axis = BlockDecomposition.Axis(1:length($node.ext[:decomposition_structure].blocks)) - end - $name = BlockDecomposition.decompose_leaf($node, BlockDecomposition.DantzigWolfe, $axis) # initialize a tree for the current root node + $name = BlockDecomposition.decompose_leaf($node, BlockDecomposition.DantzigWolfe, $axis) # initialize a tree for the current root node BlockDecomposition.register_subproblems!($name, $axis, BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe) end return esc(dw_exp) diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl index 7af7c3d..b12e9ea 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_decomposition.jl @@ -6,20 +6,24 @@ function test_automatic_decomposition() catch e @test e isa NoOptimizer end - for variableref in JuMP.all_variables(model) - x_ann = BD.annotation(model, variableref) - test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 1, 1) - end - for constraintref in model.ext[:decomposition_structure].master_constraints - x_ann = BD.annotation(model, xconstraintref) - test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 0, 1) - end - for block in model.ext[:decomposition_structure].blocks - for constraintref in block - x_ann = BD.annotation(model, constraintref) - test_annotation(x_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) + # all constraints build over the set machines are master constraints + # (due to the result of the plumple method that scores decompotions) + machines = 1:4 + jobs = 1:30 + + for j in jobs + cov_ann = BD.annotation(model, cov[j]) + test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) + for m in machines + x_ann = BD.annotation(model, x[m,j]) + test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) end end + for m in machines + knp_ann = BD.annotation(model, knp[m]) + test_annotation(knp_ann, BD.Master, BD.DantzigWolfe, 1, 1) + end + master = getmaster(dec) @test repr(master) == "Master formulation.\n" @@ -28,33 +32,4 @@ function test_automatic_decomposition() tree = gettree(model) @test gettree(model) == gettree(dec) - - @test repr(dec) == "Root - Annotation(BlockDecomposition.Master, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 2) with 1 subproblems :\n\t 1 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 0.0, um = 1.0, id = 3) \n" - end - - d = GapToyData(30,10) - @testset "GAP automatic decomposition" begin - model, x, cov, knp, dec, Axis = generalized_assignment_automatic_decomposition(d) - try - JuMP.optimize!(model) - catch e - @test e isa NoOptimizer - end - for variableref in JuMP.all_variables(model) - x_ann = BD.annotation(model, variableref) - test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 1, 1) - end - for constraintref in model.ext[:decomposition_structure].master_constraints - x_ann = BD.annotation(model, xconstraintref) - test_annotation(x_ann, BD.Master, BD.DantzigWolfe, 0, 1) - end - for block in model.ext[:decomposition_structure].blocks - for constraintref in block - x_ann = BD.annotation(model, constraintref) - test_annotation(x_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) - end - end - master = getmaster(dec) - @test repr(master) == "Master formulation.\n" - end end diff --git a/test/formulations.jl b/test/formulations.jl index 53b1095..832238b 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -47,7 +47,7 @@ function example_assignment_automatic_decomposition() @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) @objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)) - @dantzig_wolfe_decomposition(model, decomposition, Axis) + decomposition, decomposition_axis = decompose(model) master = getmaster(decomposition) @@ -55,7 +55,7 @@ function example_assignment_automatic_decomposition() specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) - return model, x, cov, knp, decomposition, Axis + return model, x, cov, knp, decomposition, decomposition_axis end function generalized_assignment_automatic_decomposition(d::GapData) @@ -70,12 +70,12 @@ function generalized_assignment_automatic_decomposition(d::GapData) @objective(model, Min, sum(d.costs[j, m] * x[j, m] for j in d.jobs, m in d.machines)) - @dantzig_wolfe_decomposition(model, decomposition, Axis) + decomposition, decomposition_axis = decompose(model) master = getmaster(decomposition) subproblems = getsubproblems(decomposition) specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) - return model, x, cov, knp, decomposition, Axis + return model, x, cov, knp, decomposition, decomposition_axis end # Test pure master variables, constraint without id & variables without id From 19eadee5c0d282838f8bf70cd9b04872ac17b853 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Tue, 30 Jun 2020 16:42:43 +0200 Subject: [PATCH 11/32] Fix typo in test for automatic decomposition --- test/automatic_decomposition.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl index b12e9ea..89e6131 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_decomposition.jl @@ -32,4 +32,5 @@ function test_automatic_decomposition() tree = gettree(model) @test gettree(model) == gettree(dec) + end end From 43c776714aebeb192d18cb0d67742a70aba892be Mon Sep 17 00:00:00 2001 From: David Meichel Date: Wed, 1 Jul 2020 10:17:24 +0200 Subject: [PATCH 12/32] Fix bug in src/automatic_decomposition.jl --- src/automatic_decomposition.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index e75bbab..4973091 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -95,11 +95,11 @@ function get_constraints_and_axes(model::JuMP.Model) for k in keys(model.obj_dict) # Check all names in the model # Store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) - if typeof(reference[1]) <: JuMP.ConstraintRef + if eltype(reference) <: JuMP.ConstraintRef for c in reference _add_constraint!(constraints_and_axes, c, model, reference) end - elseif typeof(reference[1]) <: JuMP.VariableRef + elseif eltype(reference) <: JuMP.VariableRef for v in reference push!(variables, JuMP.index(v)) end From c3923955706ec3a86f056a6b6c6645472f732cc5 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 3 Jul 2020 11:44:36 +0200 Subject: [PATCH 13/32] Remove return of decomposition and axis when decomposing automatically The standard value 1 for lower_multiplicity and upper_multiplicity is used for automatic decomposition. Therefore the user does not need to access the decomposition tree to set the multiplicities. --- src/automatic_decomposition.jl | 2 +- test/automatic_decomposition.jl | 15 +++------------ test/formulations.jl | 18 ++++-------------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 4973091..4fc6d15 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -26,7 +26,7 @@ function decompose(model::JuMP.Model) BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe ) - return decomposition, decomposition_axis + return end # Contains all the information about the constraints and variables in a model we might need diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl index 89e6131..7c14129 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_decomposition.jl @@ -1,6 +1,6 @@ function test_automatic_decomposition() @testset "Example AP automatic decomposition" begin - model, x, cov, knp, dec, Axis = example_assignment_automatic_decomposition() + model, x, cov, knp= example_assignment_automatic_decomposition() try JuMP.optimize!(model) catch e @@ -13,24 +13,15 @@ function test_automatic_decomposition() for j in jobs cov_ann = BD.annotation(model, cov[j]) - test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) + test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 1, 1) for m in machines x_ann = BD.annotation(model, x[m,j]) - test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 0, 1) + test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 1, 1) end end for m in machines knp_ann = BD.annotation(model, knp[m]) test_annotation(knp_ann, BD.Master, BD.DantzigWolfe, 1, 1) end - - master = getmaster(dec) - @test repr(master) == "Master formulation.\n" - - subproblems = getsubproblems(dec) - @test repr(subproblems[1]) == "Subproblem formulation for = 1 contains :\t 0.0 <= multiplicity <= 1.0\n" - - tree = gettree(model) - @test gettree(model) == gettree(dec) end end diff --git a/test/formulations.jl b/test/formulations.jl index 099e4fc..631a191 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -46,15 +46,9 @@ function example_assignment_automatic_decomposition() @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) @objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)) - decomposition, decomposition_axis = decompose(model) + decompose(model) - - master = getmaster(decomposition) - subproblems = getsubproblems(decomposition) - - specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) - - return model, x, cov, knp, decomposition, decomposition_axis + return model, x, cov, knp end function generalized_assignment_automatic_decomposition(d::GapData) @@ -68,13 +62,9 @@ function generalized_assignment_automatic_decomposition(d::GapData) @objective(model, Min, sum(d.costs[j, m] * x[j, m] for j in d.jobs, m in d.machines)) + decompose(model) - decomposition, decomposition_axis = decompose(model) - master = getmaster(decomposition) - subproblems = getsubproblems(decomposition) - specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) - - return model, x, cov, knp, decomposition, decomposition_axis + return model, x, cov, knp end # Test pure master variables, constraint without id & variables without id From d6fe943b8f87562f525e751404db33009f0f66d6 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sun, 5 Jul 2020 17:45:00 +0200 Subject: [PATCH 14/32] User does not have to call decompose(model) explicitly anymore Instead, decompose(model::JuMP.Model) is now automatically called in JuMP.optimize!(model::JuMP.Model). If the user now wants to use automatic decomposition, all it needs to do is to set automatic_decomposition to true when creating the block model. --- src/BlockDecomposition.jl | 5 ++++- test/automatic_decomposition.jl | 3 +-- test/formulations.jl | 2 -- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 002936c..b3ae6aa 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -16,7 +16,7 @@ const MOI = MathOptInterface const MOIU = MOI.Utilities const JC = JuMP.Containers -export BlockModel, annotation, specify!, decompose, get_best_block_structure, gettree, +export BlockModel, annotation, specify!, decompose,gettree, getmaster, getsubproblems, indice, objectiveprimalbound!, objectivedualbound! export @axis, @dantzig_wolfe_decomposition, @benders_decomposition @@ -39,6 +39,9 @@ function BlockModel(args...; automatic_decomposition=false, kw...) end function optimize!(m::JuMP.Model) + if m.ext[:automatic_decomposition] + decompose(m) + end register_decomposition(m) return JuMP.optimize!(m, ignore_optimize_hook = true) end diff --git a/test/automatic_decomposition.jl b/test/automatic_decomposition.jl index 7c14129..6ccd88c 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_decomposition.jl @@ -1,6 +1,6 @@ function test_automatic_decomposition() @testset "Example AP automatic decomposition" begin - model, x, cov, knp= example_assignment_automatic_decomposition() + model, x, cov, knp = example_assignment_automatic_decomposition() try JuMP.optimize!(model) catch e @@ -10,7 +10,6 @@ function test_automatic_decomposition() # (due to the result of the plumple method that scores decompotions) machines = 1:4 jobs = 1:30 - for j in jobs cov_ann = BD.annotation(model, cov[j]) test_annotation(cov_ann, BD.DwPricingSp, BD.DantzigWolfe, 1, 1) diff --git a/test/formulations.jl b/test/formulations.jl index 631a191..bce03a1 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -46,7 +46,6 @@ function example_assignment_automatic_decomposition() @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) @objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)) - decompose(model) return model, x, cov, knp end @@ -62,7 +61,6 @@ function generalized_assignment_automatic_decomposition(d::GapData) @objective(model, Min, sum(d.costs[j, m] * x[j, m] for j in d.jobs, m in d.machines)) - decompose(model) return model, x, cov, knp end From c59ca882303728cde3398ea76d00409d6a652e0d Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 10 Jul 2020 11:06:06 +0200 Subject: [PATCH 15/32] Rename "plumple" method to "white_score" --- src/automatic_decomposition.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 4fc6d15..130fcec 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -7,7 +7,7 @@ function get_best_block_structure(model::JuMP.Model) block_structure = get_block_structure(axes, constraints_and_axes, model) push!(block_structures, block_structure) end - result = plumple(block_structures, constraints_and_axes) + result = white_score(block_structures, constraints_and_axes) return result end @@ -48,20 +48,20 @@ struct BlockStructure graph::MetaGraph end -function plumple(block_structures::Array{BlockStructure,1}, constraints_and_axes::Constraints_and_Axes) +function white_score(block_structures::Array{BlockStructure,1}, constraints_and_axes::Constraints_and_Axes) result = nothing best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) for block_structure in block_structures - plumple_value = _get_plumple_value(block_structure) - if plumple_value <= best - best = plumple_value + white_score = _get_white_score(block_structure) + if white_score <= best + best = white_score result = block_structure end end return result end -function _get_plumple_value(block_structure::BlockStructure) +function _get_white_score(block_structure::BlockStructure) n_master = length(block_structure.constraints_and_axes.variables) * length(block_structure.master_constraints) n_blocks = 0 variables_in_block = Set{MOI.VariableIndex}() From 011a2e3d87c3174200fd0ccac05f1c536cc5324a Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 21 Aug 2020 16:11:27 +0200 Subject: [PATCH 16/32] Add the block border score for automatic decomposition The score is defined in Khaniyev, Taghi, Samir Elhedhli, and Fatih Safa Erenay. "Structure detection in mixed-integer programs." INFORMS Journal on Computing 30.3 (2018): 570-587. When calling BlockModel(..., automatic_decomposition=true, automatic_decomposition_score_type=x) the user can give the type of decomposition score to be used in the automatic decomposition. x = 0 -> white score x = 1 -> block border score from Khaniyev et al. The standard score is the white score. --- src/BlockDecomposition.jl | 3 +- src/automatic_decomposition.jl | 71 ++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index b3ae6aa..043080b 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -31,10 +31,11 @@ include("callbacks.jl") include("automatic_decomposition.jl") include("utils.jl") -function BlockModel(args...; automatic_decomposition=false, kw...) +function BlockModel(args...; automatic_decomposition = false, automatic_decomposition_score_type = 0, kw...) m = JuMP.Model(args...; kw...) JuMP.set_optimize_hook(m, optimize!) m.ext[:automatic_decomposition] = automatic_decomposition + m.ext[:automatic_decomposition_score_type] = automatic_decomposition_score_type return m end diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 130fcec..28c5a41 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -7,7 +7,15 @@ function get_best_block_structure(model::JuMP.Model) block_structure = get_block_structure(axes, constraints_and_axes, model) push!(block_structures, block_structure) end - result = white_score(block_structures, constraints_and_axes) + score_type = model.ext[:automatic_decomposition_score_type] + if score_type == 0 + result = white_score(block_structures, constraints_and_axes) + elseif score_type == 1 + result = block_border_score(block_structures, constraints_and_axes) + else + error("Score type ", score_type, " for automatic decomposition is not defined. the + available scores are: White Score: 0, Block Border Score: 1") + end return result end @@ -40,7 +48,7 @@ mutable struct Constraints_and_Axes end struct BlockStructure - # constraints_and_axes is the same for every possible BlockStructure of a model + # Constraints_and_axes is the same for every possible BlockStructure of a model constraints_and_axes::Constraints_and_Axes master_constraints::Set{JuMP.ConstraintRef} master_sets::Array{BlockDecomposition.Axis,1} @@ -48,7 +56,64 @@ struct BlockStructure graph::MetaGraph end -function white_score(block_structures::Array{BlockStructure,1}, constraints_and_axes::Constraints_and_Axes) +# This score is described in: Khaniyev, Taghi, Samir Elhedhli, and Fatih Safa Erenay. +# "Structure detection in mixed-integer programs." INFORMS Journal on Computing 30.3 (2018): 570-587. +function block_border_score( + block_structures::Array{BlockStructure,1}, + constraints_and_axes::Constraints_and_Axes +) + result = nothing + best = 0 + for block_structure in block_structures + block_border_score = _get_block_border_score(block_structure) + if block_border_score > best + best = block_border_score + result = block_structure + end + end + return result +end + +function _get_block_border_score(block_structure::BlockStructure) + # m describes the total number of nonzero entries in the blocks, + # e gives the numberof nonzero entries for each block + e = Array{Int64,1}() + m = 0 + lambda = 5 + for block in block_structure.blocks + n = _get_n_nonzero_entries(block, block_structure.constraints_and_axes) + push!(e, n) + m += n + end + q_a = 0 + for i in 1:length(block_structure.blocks) + q_a += (e[i] / m) * (1 - (e[i] / m)) + end + n_master_constraints = length(block_structure.master_constraints) + n_constraints = length(block_structure.constraints_and_axes.constraints) + p_a = MathConstants.e^(-1 * lambda * (n_master_constraints / n_constraints)) + println("Scores for decomposition with master sets ", block_structure.master_sets) + println("D = ", q_a, " P = ", p_a) + gamma = q_a*p_a + return gamma +end + +# Computes the number of nonzero entries in the given constraints +function _get_n_nonzero_entries( + constraints::Set{JuMP.ConstraintRef}, + constraints_and_axes::Constraints_and_Axes +) + result = 0 + for c in constraints + result = result + length(constraints_and_axes.constraints_to_variables[c]) + end + return result +end + +function white_score( + block_structures::Array{BlockStructure,1}, + constraints_and_axes::Constraints_and_Axes +) result = nothing best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) for block_structure in block_structures From 5bd026f01d59b44e44c3eb74a072c430e88badcd Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sat, 22 Aug 2020 15:32:55 +0200 Subject: [PATCH 17/32] Add the relative border area score for automatic decomposition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The score is defined in Bergner, Martin, et al. "Automatic Dantzig–Wolfe reformulation of mixed integer programs." Mathematical Programming 149.1-2 (2015): 391-424. The user can now choose between 3 different scores: BlockModel(..., automatic_decomposition=true, automatic_decomposition_score_type=x) for x = 0 -> white score x = 1 -> block border score x = 2 -> relative border area score --- src/automatic_decomposition.jl | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 28c5a41..a6f8ad3 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -12,9 +12,11 @@ function get_best_block_structure(model::JuMP.Model) result = white_score(block_structures, constraints_and_axes) elseif score_type == 1 result = block_border_score(block_structures, constraints_and_axes) + elseif score_type == 2 + result = relative_border_area_score(block_structures, constraints_and_axes) else - error("Score type ", score_type, " for automatic decomposition is not defined. the - available scores are: White Score: 0, Block Border Score: 1") + error("Score type ", score_type, " for automatic decomposition is not defined. + The available scores are: White Score: 0, Block Border Score: 1, Relative Border Area Score: 2") end return result end @@ -56,6 +58,32 @@ struct BlockStructure graph::MetaGraph end +# This score is described in: Bergner, Martin, et al. +# "Automatic Dantzig–Wolfe reformulation of mixed integer programs." +# Mathematical Programming 149.1-2 (2015): 391-424. +function relative_border_area_score( + block_structures::Array{BlockStructure,1}, + constraints_and_axes::Constraints_and_Axes + ) + result = nothing + best_score = 1 + for block_structure in block_structures + recent_score = _get_relative_border_area_score(block_structure) + if recent_score < best_score + best_score = recent_score + result = block_structure + end + end + return result +end + +function _get_relative_border_area_score(block_structure::BlockStructure) + n_linking_constraints = length(block_structure.master_constraints) + n_variables = length(block_structure.constraints_and_axes.variables) + n_constraints = length(block_structure.constraints_and_axes.constraints) + score = (n_linking_constraints*n_variables)/(n_variables*n_constraints) +end + # This score is described in: Khaniyev, Taghi, Samir Elhedhli, and Fatih Safa Erenay. # "Structure detection in mixed-integer programs." INFORMS Journal on Computing 30.3 (2018): 570-587. function block_border_score( From 595be5ff39c708518751e8a8db18797d15eae46e Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sun, 1 Nov 2020 21:30:15 +0100 Subject: [PATCH 18/32] Restructure the code in automatic_decomposition.jl --- src/BlockDecomposition.jl | 2 - src/automatic_decomposition.jl | 153 +++++++++++++++------------------ 2 files changed, 69 insertions(+), 86 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 043080b..4aa0f70 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -6,8 +6,6 @@ using LightGraphs: SimpleGraph, add_vertices!, nv using MathOptInterface using MetaGraphs: MetaGraph, add_edge!, connected_components, edges, intersect, set_indexing_prop!, set_prop!, vertices -using TikzGraphs: plot -using TikzPictures: SVG, save import MathOptInterface import MathOptInterface.Utilities diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index a6f8ad3..9e2b42f 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -1,26 +1,3 @@ -# Finds the best decomposition structure of to be used by the solver -function get_best_block_structure(model::JuMP.Model) - constraints_and_axes = get_constraints_and_axes(model) - block_structures = Array{BlockStructure,1}() - axesSets = collect(powerset(collect(constraints_and_axes.axes))) - for axes in axesSets - block_structure = get_block_structure(axes, constraints_and_axes, model) - push!(block_structures, block_structure) - end - score_type = model.ext[:automatic_decomposition_score_type] - if score_type == 0 - result = white_score(block_structures, constraints_and_axes) - elseif score_type == 1 - result = block_border_score(block_structures, constraints_and_axes) - elseif score_type == 2 - result = relative_border_area_score(block_structures, constraints_and_axes) - else - error("Score type ", score_type, " for automatic decomposition is not defined. - The available scores are: White Score: 0, Block Border Score: 1, Relative Border Area Score: 2") - end - return result -end - # Decomposes the given JuMP Model automatically function decompose(model::JuMP.Model) model.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure(model) @@ -39,6 +16,38 @@ function decompose(model::JuMP.Model) return end +# Finds the best decomposition structure to be used by the solver +function get_best_block_structure(model::JuMP.Model) + block_structures = get_all_block_structures(model) + score_type = model.ext[:automatic_decomposition_score_type] + if score_type == 0 # White Score + white_scores = BlockDecomposition.white_scores(block_structures) + result = block_structures[argmax(white_scores)] + elseif score_type == 1 # Block Border Score + block_border_scores = BlockDecomposition.block_border_scores(block_structures) + result = block_structures[argmax(block_border_scores)] + elseif score_type == 2 # Relative Border Area Score + relative_border_area_scores = BlockDecomposition.relative_border_area_scores(block_structures) + result = block_structures[argmin(relative_border_area_scores)] + else + error("Score type ", score_type, " for automatic decomposition is not defined. + The available scores are: White Score: 0, Block Border Score: 1, Relative Border Area Score: 2") + end + return result +end + +# Returns all possible block structures of the given model +function get_all_block_structures(model::JuMP.Model) + constraints_and_axes = get_constraints_and_axes(model) + block_structures = Array{BlockStructure,1}() + axesSets = collect(powerset(collect(constraints_and_axes.axes))) + for axes in axesSets + block_structure = get_block_structure(axes, constraints_and_axes, model) + push!(block_structures, block_structure) + end + return block_structures +end + # Contains all the information about the constraints and variables in a model we might need # to compute different block structures mutable struct Constraints_and_Axes @@ -58,48 +67,33 @@ struct BlockStructure graph::MetaGraph end + + # This score is described in: Bergner, Martin, et al. # "Automatic Dantzig–Wolfe reformulation of mixed integer programs." # Mathematical Programming 149.1-2 (2015): 391-424. -function relative_border_area_score( - block_structures::Array{BlockStructure,1}, - constraints_and_axes::Constraints_and_Axes - ) - result = nothing - best_score = 1 - for block_structure in block_structures - recent_score = _get_relative_border_area_score(block_structure) - if recent_score < best_score - best_score = recent_score - result = block_structure - end +function relative_border_area_scores(block_structures::Array{BlockStructure,1}) + scores = Array{Float64,1}(undef, length(block_structures)) + for i in eachindex(block_structures) + scores[i] = _get_relative_border_area_score(block_structures[i]) end - return result + return scores end function _get_relative_border_area_score(block_structure::BlockStructure) n_linking_constraints = length(block_structure.master_constraints) - n_variables = length(block_structure.constraints_and_axes.variables) n_constraints = length(block_structure.constraints_and_axes.constraints) - score = (n_linking_constraints*n_variables)/(n_variables*n_constraints) + score = n_linking_constraints/n_constraints end # This score is described in: Khaniyev, Taghi, Samir Elhedhli, and Fatih Safa Erenay. # "Structure detection in mixed-integer programs." INFORMS Journal on Computing 30.3 (2018): 570-587. -function block_border_score( - block_structures::Array{BlockStructure,1}, - constraints_and_axes::Constraints_and_Axes -) - result = nothing - best = 0 - for block_structure in block_structures - block_border_score = _get_block_border_score(block_structure) - if block_border_score > best - best = block_border_score - result = block_structure - end +function block_border_scores(block_structures::Array{BlockStructure,1}) + scores = Array{Float64,1}(undef, length(block_structures)) + for i in eachindex(block_structures) + scores[i] = _get_block_border_score(block_structures[i]) end - return result + return scores end function _get_block_border_score(block_structure::BlockStructure) @@ -120,8 +114,6 @@ function _get_block_border_score(block_structure::BlockStructure) n_master_constraints = length(block_structure.master_constraints) n_constraints = length(block_structure.constraints_and_axes.constraints) p_a = MathConstants.e^(-1 * lambda * (n_master_constraints / n_constraints)) - println("Scores for decomposition with master sets ", block_structure.master_sets) - println("D = ", q_a, " P = ", p_a) gamma = q_a*p_a return gamma end @@ -138,20 +130,13 @@ function _get_n_nonzero_entries( return result end -function white_score( - block_structures::Array{BlockStructure,1}, - constraints_and_axes::Constraints_and_Axes -) - result = nothing - best = length(constraints_and_axes.constraints) * length(constraints_and_axes.variables) - for block_structure in block_structures - white_score = _get_white_score(block_structure) - if white_score <= best - best = white_score - result = block_structure - end +# Returns the amount of "black" in the matrix +function white_scores(block_structures::Array{BlockStructure,1}) + scores = Array{Float64,1}(undef, length(block_structures)) + for i in eachindex(block_structures) + scores[i] = _get_white_score(block_structures[i]) end - return result + return scores end function _get_white_score(block_structure::BlockStructure) @@ -168,7 +153,10 @@ function _get_white_score(block_structure::BlockStructure) end n_blocks = n_blocks + length(variables_in_block)*length(block) end - return n_master+n_blocks + coefficient_matrix_size = length(block_structure.constraints_and_axes.constraints) * length(block_structure.constraints_and_axes.variables) + black_score = (n_master+n_blocks)/coefficient_matrix_size + white_score = 1-black_score + return white_score end # Returns an instance of the struct Constraints_and_Axes @@ -186,8 +174,7 @@ function get_constraints_and_axes(model::JuMP.Model) constraints_to_variables ) for k in keys(model.obj_dict) # Check all names in the model - # Store single references to constraints in a DenseAxisArray (already done if several constraints are referenced with the same name) - reference = typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray ? model.obj_dict[k] : JuMP.Containers.DenseAxisArray([model.obj_dict[k]],1) + reference = _get_constraint_reference(model, k) if eltype(reference) <: JuMP.ConstraintRef for c in reference _add_constraint!(constraints_and_axes, c, model, reference) @@ -203,6 +190,18 @@ function get_constraints_and_axes(model::JuMP.Model) return constraints_and_axes end +# Convert the constraint reference to a DenseAxisArrays +function _get_constraint_reference(model::JuMP.Model, k) + if typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray + return model.obj_dict[k] + elseif typeof(model.obj_dict[k]) <: Array + axs = axes(model.obj_dict[k]) + return JuMP.Containers.DenseAxisArray(model.obj_dict[k], axs...) + else + return error("Type of constraint reference can not be handled.") # Can this case happen? + end +end + # Add anonymous constraints and axes from the model to constraints_and_axes (car) function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) types = JuMP.list_of_constraint_types(model) @@ -211,7 +210,7 @@ function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) for c in JuMP.all_constraints(model, t[1], t[2]) if !in(c, car.constraints) push!(car.constraints, c) - car.constraints_to_axes[c] = Set{BlockDecomposition.Axis}() + car.constraints_to_axes[c] = Array{BlockDecomposition.Axis,1}() car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) end end @@ -321,17 +320,3 @@ function _create_graph( return graph end -function draw_graph(mgraph::MetaGraph) - sgraph = SimpleGraph(nv(mgraph)) - labels = Array{String}(undef, nv(mgraph)) - i = 1 - for v in collect(vertices(mgraph)) - labels[i] = convert(String, repr(mgraph[i, :constraint_ref])) - i = i + 1 - end - for e in edges(mgraph) - add_edge!(sgraph, e) - end - t = plot(sgraph, labels) - save(SVG("graph.svg"),t) -end From 38b302c292d32c83bfab726395f3466d0fab117a Mon Sep 17 00:00:00 2001 From: David Meichel Date: Tue, 17 Nov 2020 17:10:25 +0100 Subject: [PATCH 19/32] Update Project.toml Add Metagraphs, LightGraphs, Combinatorics as dependencies. --- Project.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Project.toml b/Project.toml index 0ad50b9..2353411 100644 --- a/Project.toml +++ b/Project.toml @@ -7,11 +7,17 @@ version = "1.2.3" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5" +LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d" +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" [compat] JuMP = "0.21" MathOptInterface = "0.9.10" julia = "1" +MetaGraphs = "0.6.5" +LightGraphs = "1.3.3" +Combinatorics = "1.0.2" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 83a1b118ae3f674fb7c44bc8886381d776a64f62 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Mon, 12 Apr 2021 14:10:37 +0200 Subject: [PATCH 20/32] Improve structure detection procedure Now for every subset S of the index sets, two structures are detected: 1. All constraints index by at least set from S are linking 2. All constraints not indexed by any set from S are linking Formatting of the code was improved, too. --- src/automatic_decomposition.jl | 90 ++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/src/automatic_decomposition.jl b/src/automatic_decomposition.jl index 9e2b42f..15fb6b4 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_decomposition.jl @@ -1,7 +1,9 @@ # Decomposes the given JuMP Model automatically function decompose(model::JuMP.Model) model.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure(model) - decomposition_axis = BlockDecomposition.Axis(1:length(model.ext[:decomposition_structure].blocks)) + decomposition_axis = BlockDecomposition.Axis( + 1:length(model.ext[:decomposition_structure].blocks) + ) decomposition = BlockDecomposition.decompose_leaf( model, BlockDecomposition.DantzigWolfe, @@ -30,8 +32,11 @@ function get_best_block_structure(model::JuMP.Model) relative_border_area_scores = BlockDecomposition.relative_border_area_scores(block_structures) result = block_structures[argmin(relative_border_area_scores)] else - error("Score type ", score_type, " for automatic decomposition is not defined. - The available scores are: White Score: 0, Block Border Score: 1, Relative Border Area Score: 2") + error( + "Score type ", score_type, " for automatic decomposition is not defined. + The available scores are: + White Score: 0, Block Border Score: 1, Relative Border Area Score: 2" + ) end return result end @@ -42,8 +47,14 @@ function get_all_block_structures(model::JuMP.Model) block_structures = Array{BlockStructure,1}() axesSets = collect(powerset(collect(constraints_and_axes.axes))) for axes in axesSets - block_structure = get_block_structure(axes, constraints_and_axes, model) - push!(block_structures, block_structure) + block_structure0, block_structure1 = get_block_structure(axes, constraints_and_axes, model) + # Only add block structures that where not found so far + if !structure_exists(block_structures, block_structure0) + push!(block_structures, block_structure0) + end + if !structure_exists(block_structures, block_structure1) + push!(block_structures, block_structure1) + end end return block_structures end @@ -63,11 +74,25 @@ struct BlockStructure constraints_and_axes::Constraints_and_Axes master_constraints::Set{JuMP.ConstraintRef} master_sets::Array{BlockDecomposition.Axis,1} + # Invert linked is true iff the linking constraints are the ones not indexed + # by any set from master_sets + invert_linking::Bool blocks::Array{Set{JuMP.ConstraintRef},1} graph::MetaGraph end - +# Returns true if and only if block_structure already exists in block_structures +function structure_exists( + block_structures::Array{BlockStructure,1}, + block_structure::BlockStructure +) + for bs in block_structures + if bs.master_constraints == block_structure.master_constraints + return true + end + end + return false +end # This score is described in: Bergner, Martin, et al. # "Automatic Dantzig–Wolfe reformulation of mixed integer programs." @@ -140,7 +165,8 @@ function white_scores(block_structures::Array{BlockStructure,1}) end function _get_white_score(block_structure::BlockStructure) - n_master = length(block_structure.constraints_and_axes.variables) * length(block_structure.master_constraints) + n_master = length(block_structure.constraints_and_axes.variables) * + length(block_structure.master_constraints) n_blocks = 0 variables_in_block = Set{MOI.VariableIndex}() for block in block_structure.blocks @@ -153,7 +179,8 @@ function _get_white_score(block_structure::BlockStructure) end n_blocks = n_blocks + length(variables_in_block)*length(block) end - coefficient_matrix_size = length(block_structure.constraints_and_axes.constraints) * length(block_structure.constraints_and_axes.variables) + coefficient_matrix_size = length(block_structure.constraints_and_axes.constraints) * + length(block_structure.constraints_and_axes.variables) black_score = (n_master+n_blocks)/coefficient_matrix_size white_score = 1-black_score return white_score @@ -198,7 +225,8 @@ function _get_constraint_reference(model::JuMP.Model, k) axs = axes(model.obj_dict[k]) return JuMP.Containers.DenseAxisArray(model.obj_dict[k], axs...) else - return error("Type of constraint reference can not be handled.") # Can this case happen? + # Can this case happen? + return error("Type of constraint reference can not be handled.") end end @@ -236,7 +264,9 @@ function _add_constraint!( o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) end -function _get_axes_of_constraint(reference_constraints_name::T) where T <: JuMP.Containers.DenseAxisArray +function _get_axes_of_constraint( + reference_constraints_name::T +) where T <: JuMP.Containers.DenseAxisArray axes_of_constraint = Array{BlockDecomposition.Axis,1}() for a in reference_constraints_name.axes if a != 1 # Axes of the form 1:1 do not matter (single constraints) @@ -255,29 +285,45 @@ function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.Constr return variables end +# Computes for the given axes two decomposition structures: +# In the first one (block_structure0) all constraints *indexed by at least one* element from +# axes are in the master, in the second one (block_structure1), all constraints *not indexed +# by any* element from axes are in the master function get_block_structure( axes::Array{<:Axis,1}, constraints_and_axes::Constraints_and_Axes, model::JuMP.Model, ) - vertices = Set{JuMP.ConstraintRef}() - master_constraints = Set{JuMP.ConstraintRef}() - connected_components = Set{Set{JuMP.ConstraintRef}}() - + vertices = [Set{JuMP.ConstraintRef}(), Set{JuMP.ConstraintRef}()] + master_constraints = [Set{JuMP.ConstraintRef}(),Set{JuMP.ConstraintRef}()] for c in keys(constraints_and_axes.constraints_to_axes) - # Check if constraints are constructed over at least one set which is contained in axes + # Master constraints are constructed over at least one set which is contained in axes # if axes is empty annotate everything as master if isempty(axes) || !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) - push!(master_constraints, c) + push!(master_constraints[1], c) + else + push!(vertices[1], c) + end + # Master constraints are constructed over no set which is contained in axes + # To do: ensure that no BBD is created where the master is empty + if size(axes)[1] == length(constraints_and_axes.axes) || isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) + push!(master_constraints[2], c) else - push!(vertices, c) + push!(vertices[2], c) end end - graph = _create_graph(vertices, constraints_and_axes) + graph = _create_graph(vertices[1], constraints_and_axes) blocks = _get_connected_components!(graph) - block_structure = BlockStructure(constraints_and_axes, master_constraints, axes, blocks, graph) - return block_structure + block_structure1 = BlockStructure( + constraints_and_axes, master_constraints[1], axes, false, blocks, graph + ) + graph = _create_graph(vertices[2], constraints_and_axes) + blocks = _get_connected_components!(graph) + block_structure2 = BlockStructure( + constraints_and_axes, master_constraints[2], axes, true, blocks, graph + ) + return block_structure1, block_structure2 end function _get_connected_components!(graph::MetaGraph) @@ -314,7 +360,9 @@ function _create_graph( constraints_and_axes.constraints_to_variables[v1], constraints_and_axes.constraints_to_variables[v2], ) - isempty(intersection) ? true : add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) + if !isempty(intersection) + add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) + end end end return graph From 3a85811cd9f42f274e67dc0c91446ccf43e736b7 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Tue, 13 Apr 2021 21:02:15 +0200 Subject: [PATCH 21/32] Rename functions and files to make clear that Dantzig-Wolfe is used in automatic decompositon --- src/BlockDecomposition.jl | 10 +++++----- ...tic_decomposition.jl => automatic_dantzig_wolfe.jl} | 8 ++++---- src/decomposition.jl | 6 +++--- ...tic_decomposition.jl => automatic_dantzig_wolfe.jl} | 6 +++--- test/formulations.jl | 8 ++++---- test/runtests.jl | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) rename src/{automatic_decomposition.jl => automatic_dantzig_wolfe.jl} (98%) rename test/{automatic_decomposition.jl => automatic_dantzig_wolfe.jl} (81%) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 4aa0f70..89ae275 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -26,19 +26,19 @@ include("formulations.jl") include("decomposition.jl") include("objective.jl") include("callbacks.jl") -include("automatic_decomposition.jl") +include("automatic_dantzig_wolfe.jl") include("utils.jl") -function BlockModel(args...; automatic_decomposition = false, automatic_decomposition_score_type = 0, kw...) +function BlockModel(args...; automatic_dantzig_wolfe = false, automatic_dantzig_wolfe_score = 0, kw...) m = JuMP.Model(args...; kw...) JuMP.set_optimize_hook(m, optimize!) - m.ext[:automatic_decomposition] = automatic_decomposition - m.ext[:automatic_decomposition_score_type] = automatic_decomposition_score_type + m.ext[:automatic_dantzig_wolfe] = automatic_dantzig_wolfe + m.ext[:automatic_dantzig_wolfe_score] = automatic_dantzig_wolfe_score return m end function optimize!(m::JuMP.Model) - if m.ext[:automatic_decomposition] + if m.ext[:automatic_dantzig_wolfe] decompose(m) end register_decomposition(m) diff --git a/src/automatic_decomposition.jl b/src/automatic_dantzig_wolfe.jl similarity index 98% rename from src/automatic_decomposition.jl rename to src/automatic_dantzig_wolfe.jl index 15fb6b4..9573de8 100644 --- a/src/automatic_decomposition.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -21,7 +21,7 @@ end # Finds the best decomposition structure to be used by the solver function get_best_block_structure(model::JuMP.Model) block_structures = get_all_block_structures(model) - score_type = model.ext[:automatic_decomposition_score_type] + score_type = model.ext[:automatic_dantzig_wolfe_score] if score_type == 0 # White Score white_scores = BlockDecomposition.white_scores(block_structures) result = block_structures[argmax(white_scores)] @@ -33,9 +33,9 @@ function get_best_block_structure(model::JuMP.Model) result = block_structures[argmin(relative_border_area_scores)] else error( - "Score type ", score_type, " for automatic decomposition is not defined. + "Score type ", score_type, " for automatic Dantzig-Wolfe decomposition is not defined. The available scores are: - White Score: 0, Block Border Score: 1, Relative Border Area Score: 2" + White Score: 0, Block-Border Score: 1, Relative Border Area Score: 2" ) end return result @@ -155,7 +155,7 @@ function _get_n_nonzero_entries( return result end -# Returns the amount of "black" in the matrix +# Returns the relative amount of "white" in the matrix function white_scores(block_structures::Array{BlockStructure,1}) scores = Array{Float64,1}(undef, length(block_structures)) for i in eachindex(block_structures) diff --git a/src/decomposition.jl b/src/decomposition.jl index 5c0e1e6..7adea60 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -6,8 +6,8 @@ which partition (master/subproblem) of the original formulation the variable or the constraint is located. """ function register_decomposition(model::JuMP.Model) - if model.ext[:automatic_decomposition] - register_automatic_decomposition(model) + if model.ext[:automatic_dantzig_wolfe] + register_automatic_dantzig_wolfe(model) else tree = gettree(model) tree === nothing && return @@ -18,7 +18,7 @@ function register_decomposition(model::JuMP.Model) return end -function register_automatic_decomposition(model::JuMP.Model) +function register_automatic_dantzig_wolfe(model::JuMP.Model) tree = gettree(model) # Annotate master constraints decomposition_structure = model.ext[:decomposition_structure] diff --git a/test/automatic_decomposition.jl b/test/automatic_dantzig_wolfe.jl similarity index 81% rename from test/automatic_decomposition.jl rename to test/automatic_dantzig_wolfe.jl index 6ccd88c..1a63979 100644 --- a/test/automatic_decomposition.jl +++ b/test/automatic_dantzig_wolfe.jl @@ -1,6 +1,6 @@ -function test_automatic_decomposition() - @testset "Example AP automatic decomposition" begin - model, x, cov, knp = example_assignment_automatic_decomposition() +function test_automatic_dantzig_wolfe() + @testset "Example AP automatic Dantzig-Wolfe decomposition" begin + model, x, cov, knp = example_assignment_automatic_dantzig_wolfe() try JuMP.optimize!(model) catch e diff --git a/test/formulations.jl b/test/formulations.jl index bce03a1..c3ddf0b 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -33,7 +33,7 @@ function generalized_assignement(d::GapData) return model, x, cov, knp, decomposition end -function example_assignment_automatic_decomposition() +function example_assignment_automatic_dantzig_wolfe() nb_machines = 4 nb_jobs = 30 c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5 15.2 14.3 12.6 9.2 20.8 11.7 17.3 9.2 20.3 11.4 6.2 13.8 10.0 20.9 20.6; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2 19.7 19.5 7.2 6.4 23.2 8.1 13.6 24.6 15.6 22.3 8.8 19.1 18.4 22.9 8.0; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7 16.6 8.3 15.9 24.3 18.6 21.1 7.5 16.8 20.9 8.9 15.2 15.7 12.7 20.8 10.4; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2 12.9 11.3 7.5 6.5 11.3 7.8 13.8 20.7 16.8 23.6 19.1 16.8 19.3 12.5 11.0] @@ -41,7 +41,7 @@ function example_assignment_automatic_decomposition() Q = [1020 1460 1530 1190] M = 1:nb_machines J = 1:nb_jobs - model = BlockModel(automatic_decomposition = true) + model = BlockModel(automatic_dantzig_wolfe = true) @variable(model, x[m in M, j in J], Bin) @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) @@ -50,8 +50,8 @@ function example_assignment_automatic_decomposition() return model, x, cov, knp end -function generalized_assignment_automatic_decomposition(d::GapData) - model = BlockModel(automatic_decomposition = true) +function generalized_assignment_automatic_dantzig_wolfe(d::GapData) + model = BlockModel(automatic_dantzig_wolfe = true) @variable(model, x[j in d.jobs, m in d.machines], Bin) diff --git a/test/runtests.jl b/test/runtests.jl index 3380731..195b123 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,11 +21,11 @@ include("formulations.jl") include("dantzigwolfe.jl") include("benders.jl") include("assignsolver.jl") -include("automatic_decomposition.jl") +include("automatic_dantzig_wolfe.jl") axis_declarations() test_dantzig_wolfe_different() -test_automatic_decomposition() +test_automatic_dantzig_wolfe() test_dantzig_wolfe_identical() test_dummy_model_decompositions() test_benders() From 86e45bf0164c0b5c460f52aba40cbe8cb0d951e0 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Wed, 28 Apr 2021 15:21:40 +0200 Subject: [PATCH 22/32] Use enum instead of int to choose automatic DW score To reduce the arguments that need to be given to BlockModel(...) from two to one (when using automatic DW), the enum type AutoDwStrategy is introduced. One can now use the automatic Dantzig-Wolfe decomposition by creating the model with BlockModel(...,automatic_dantzig_wolfe=x) where x = white_score | block_border_score | relativ_border_area_score. --- src/BlockDecomposition.jl | 9 ++++----- src/automatic_dantzig_wolfe.jl | 20 ++++++++++---------- src/decomposition.jl | 2 +- test/formulations.jl | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 89ae275..6df6784 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -14,10 +14,10 @@ const MOI = MathOptInterface const MOIU = MOI.Utilities const JC = JuMP.Containers -export BlockModel, annotation, specify!, decompose,gettree, +export BlockModel, annotation, specify!, decompose, gettree, getmaster, getsubproblems, indice, objectiveprimalbound!, objectivedualbound! - export @axis, @dantzig_wolfe_decomposition, @benders_decomposition +export AutoDwStrategy include("axis.jl") include("annotations.jl") @@ -29,16 +29,15 @@ include("callbacks.jl") include("automatic_dantzig_wolfe.jl") include("utils.jl") -function BlockModel(args...; automatic_dantzig_wolfe = false, automatic_dantzig_wolfe_score = 0, kw...) +function BlockModel(args...; automatic_dantzig_wolfe::AutoDwStrategy = inaktive, kw...) m = JuMP.Model(args...; kw...) JuMP.set_optimize_hook(m, optimize!) m.ext[:automatic_dantzig_wolfe] = automatic_dantzig_wolfe - m.ext[:automatic_dantzig_wolfe_score] = automatic_dantzig_wolfe_score return m end function optimize!(m::JuMP.Model) - if m.ext[:automatic_dantzig_wolfe] + if m.ext[:automatic_dantzig_wolfe] != inaktive decompose(m) end register_decomposition(m) diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index 9573de8..9f5a45c 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -1,3 +1,8 @@ + +# Available scores that can be used in an automatic Dantzig-Wolfe decomposition +# inaktive means that automatic Dantzig-Wolfe is not used +@enum(AutoDwStrategy, inaktive, white_score, block_border_score, relative_border_area_score) + # Decomposes the given JuMP Model automatically function decompose(model::JuMP.Model) model.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure(model) @@ -20,23 +25,18 @@ end # Finds the best decomposition structure to be used by the solver function get_best_block_structure(model::JuMP.Model) + @assert(model.ext[:automatic_dantzig_wolfe] != inaktive) block_structures = get_all_block_structures(model) - score_type = model.ext[:automatic_dantzig_wolfe_score] - if score_type == 0 # White Score + score_type = model.ext[:automatic_dantzig_wolfe] + if score_type == white_score white_scores = BlockDecomposition.white_scores(block_structures) result = block_structures[argmax(white_scores)] - elseif score_type == 1 # Block Border Score + elseif score_type == block_border_score block_border_scores = BlockDecomposition.block_border_scores(block_structures) result = block_structures[argmax(block_border_scores)] - elseif score_type == 2 # Relative Border Area Score + elseif score_type == relative_border_area_score relative_border_area_scores = BlockDecomposition.relative_border_area_scores(block_structures) result = block_structures[argmin(relative_border_area_scores)] - else - error( - "Score type ", score_type, " for automatic Dantzig-Wolfe decomposition is not defined. - The available scores are: - White Score: 0, Block-Border Score: 1, Relative Border Area Score: 2" - ) end return result end diff --git a/src/decomposition.jl b/src/decomposition.jl index 7adea60..5fcae09 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -6,7 +6,7 @@ which partition (master/subproblem) of the original formulation the variable or the constraint is located. """ function register_decomposition(model::JuMP.Model) - if model.ext[:automatic_dantzig_wolfe] + if model.ext[:automatic_dantzig_wolfe] != inaktive register_automatic_dantzig_wolfe(model) else tree = gettree(model) diff --git a/test/formulations.jl b/test/formulations.jl index c3ddf0b..7d82e18 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -41,7 +41,7 @@ function example_assignment_automatic_dantzig_wolfe() Q = [1020 1460 1530 1190] M = 1:nb_machines J = 1:nb_jobs - model = BlockModel(automatic_dantzig_wolfe = true) + model = BlockModel(automatic_dantzig_wolfe = white_score) @variable(model, x[m in M, j in J], Bin) @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) From c6d4a21509a885b0b268267b935da9896d6df403 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 30 Apr 2021 14:53:50 +0200 Subject: [PATCH 23/32] Rename functions to improve readability --- src/BlockDecomposition.jl | 2 +- src/automatic_dantzig_wolfe.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index 6df6784..ea0dce9 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -38,7 +38,7 @@ end function optimize!(m::JuMP.Model) if m.ext[:automatic_dantzig_wolfe] != inaktive - decompose(m) + automatic_dw_decomposition!(m) end register_decomposition(m) return JuMP.optimize!(m, ignore_optimize_hook = true) diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index 9f5a45c..70eab8e 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -4,7 +4,7 @@ @enum(AutoDwStrategy, inaktive, white_score, block_border_score, relative_border_area_score) # Decomposes the given JuMP Model automatically -function decompose(model::JuMP.Model) +function automatic_dw_decomposition!(model::JuMP.Model) model.ext[:decomposition_structure] = BlockDecomposition.get_best_block_structure(model) decomposition_axis = BlockDecomposition.Axis( 1:length(model.ext[:decomposition_structure].blocks) @@ -124,11 +124,11 @@ end function _get_block_border_score(block_structure::BlockStructure) # m describes the total number of nonzero entries in the blocks, # e gives the numberof nonzero entries for each block - e = Array{Int64,1}() + e = Int64[] m = 0 lambda = 5 for block in block_structure.blocks - n = _get_n_nonzero_entries(block, block_structure.constraints_and_axes) + n = _get_nb_nonzero_entries(block, block_structure.constraints_and_axes) push!(e, n) m += n end @@ -144,7 +144,7 @@ function _get_block_border_score(block_structure::BlockStructure) end # Computes the number of nonzero entries in the given constraints -function _get_n_nonzero_entries( +function _get_nb_nonzero_entries( constraints::Set{JuMP.ConstraintRef}, constraints_and_axes::Constraints_and_Axes ) From 856a6767d6a3facbc6d9658d1bf68ca6e710cd20 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sun, 16 May 2021 17:31:19 +0200 Subject: [PATCH 24/32] Improve readability of structure detection Improve readability of the function get_block_structure(...) which computes for a given set of index sets two candidate structures. --- src/automatic_dantzig_wolfe.jl | 52 ++++++++++++++++------------------ 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index 70eab8e..8b714f1 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -48,11 +48,14 @@ function get_all_block_structures(model::JuMP.Model) axesSets = collect(powerset(collect(constraints_and_axes.axes))) for axes in axesSets block_structure0, block_structure1 = get_block_structure(axes, constraints_and_axes, model) - # Only add block structures that where not found so far - if !structure_exists(block_structures, block_structure0) + # Only add structures that were not found already and were the set of master + # constraints is not empty + if !structure_exists(block_structures, block_structure0) && + !isempty(block_structure0.master_constraints) push!(block_structures, block_structure0) end - if !structure_exists(block_structures, block_structure1) + if !structure_exists(block_structures, block_structure1) && + !isempty(block_structure1.master_constraints) push!(block_structures, block_structure1) end end @@ -286,44 +289,37 @@ function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.Constr end # Computes for the given axes two decomposition structures: -# In the first one (block_structure0) all constraints *indexed by at least one* element from -# axes are in the master, in the second one (block_structure1), all constraints *not indexed -# by any* element from axes are in the master +# In the first one (bs0) all constraints *not indexed by any* axis from +# axes are in the master, in the second one (bs1), all constraints *indexed +# by at least one* axis from axes are in the master function get_block_structure( axes::Array{<:Axis,1}, constraints_and_axes::Constraints_and_Axes, model::JuMP.Model, ) - vertices = [Set{JuMP.ConstraintRef}(), Set{JuMP.ConstraintRef}()] - master_constraints = [Set{JuMP.ConstraintRef}(),Set{JuMP.ConstraintRef}()] + block_constraints = Set{JuMP.ConstraintRef}() + master_constraints = Set{JuMP.ConstraintRef}() for c in keys(constraints_and_axes.constraints_to_axes) - # Master constraints are constructed over at least one set which is contained in axes - # if axes is empty annotate everything as master - if isempty(axes) || !isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) - push!(master_constraints[1], c) + if isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) + push!(master_constraints, c) else - push!(vertices[1], c) - end - # Master constraints are constructed over no set which is contained in axes - # To do: ensure that no BBD is created where the master is empty - if size(axes)[1] == length(constraints_and_axes.axes) || isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) - push!(master_constraints[2], c) - else - push!(vertices[2], c) + push!(block_constraints, c) end end - - graph = _create_graph(vertices[1], constraints_and_axes) + # Create first block structure + graph = _create_graph(block_constraints, constraints_and_axes) blocks = _get_connected_components!(graph) - block_structure1 = BlockStructure( - constraints_and_axes, master_constraints[1], axes, false, blocks, graph + bs0 = BlockStructure( + constraints_and_axes, master_constraints, axes, false, blocks, graph ) - graph = _create_graph(vertices[2], constraints_and_axes) + # Create second block structure (the roles of master constraints and block + # constraints are switched) + graph = _create_graph(master_constraints, constraints_and_axes) blocks = _get_connected_components!(graph) - block_structure2 = BlockStructure( - constraints_and_axes, master_constraints[2], axes, true, blocks, graph + bs1 = BlockStructure( + constraints_and_axes, block_constraints, axes, true, blocks, graph ) - return block_structure1, block_structure2 + return bs0, bs1 end function _get_connected_components!(graph::MetaGraph) From ac8d93e3ef77f9db2ea46a91783cb636a3a83bb9 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 25 Jun 2021 08:48:03 +0200 Subject: [PATCH 25/32] Support the use of SparseAxisArrays The automatic decompositon can now deal with constraints stored in SparseAxisArrays. --- src/automatic_dantzig_wolfe.jl | 106 +++++++++++++++++++-------------- src/axis.jl | 3 +- 2 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index 8b714f1..b4cc6a5 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -48,7 +48,7 @@ function get_all_block_structures(model::JuMP.Model) axesSets = collect(powerset(collect(constraints_and_axes.axes))) for axes in axesSets block_structure0, block_structure1 = get_block_structure(axes, constraints_and_axes, model) - # Only add structures that were not found already and were the set of master + # Only add structures that were not found already and where the set of master # constraints is not empty if !structure_exists(block_structures, block_structure0) && !isempty(block_structure0.master_constraints) @@ -189,6 +189,25 @@ function _get_white_score(block_structure::BlockStructure) return white_score end +# Add anonymous constraints and axes from the model to constraints_and_axes (car) +function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) + types = JuMP.list_of_constraint_types(model) + for t in types + if t[1] != VariableRef + for c in JuMP.all_constraints(model, t[1], t[2]) + if !in(c, car.constraints) + push!(car.constraints, c) + car.constraints_to_axes[c] = Array{BlockDecomposition.Axis,1}() + car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) + end + end + end + end + for v in JuMP.all_variables(model) + push!(car.variables, JuMP.index(v)) + end +end + # Returns an instance of the struct Constraints_and_Axes function get_constraints_and_axes(model::JuMP.Model) constraints = Set{JuMP.ConstraintRef}() @@ -204,12 +223,13 @@ function get_constraints_and_axes(model::JuMP.Model) constraints_to_variables ) for k in keys(model.obj_dict) # Check all names in the model - reference = _get_constraint_reference(model, k) - if eltype(reference) <: JuMP.ConstraintRef + reference = model.obj_dict[k] + index_sets = _get_constraint_axes(reference) + if eltype(reference) <: JuMP.ConstraintRef # Add constraint for c in reference - _add_constraint!(constraints_and_axes, c, model, reference) + _add_constraint!(constraints_and_axes, c, model, index_sets) end - elseif eltype(reference) <: JuMP.VariableRef + elseif eltype(reference) <: JuMP.VariableRef # Add variable for v in reference push!(variables, JuMP.index(v)) end @@ -220,58 +240,31 @@ function get_constraints_and_axes(model::JuMP.Model) return constraints_and_axes end -# Convert the constraint reference to a DenseAxisArrays -function _get_constraint_reference(model::JuMP.Model, k) - if typeof(model.obj_dict[k]) <: JuMP.Containers.DenseAxisArray - return model.obj_dict[k] - elseif typeof(model.obj_dict[k]) <: Array - axs = axes(model.obj_dict[k]) - return JuMP.Containers.DenseAxisArray(model.obj_dict[k], axs...) - else - # Can this case happen? - return error("Type of constraint reference can not be handled.") - end -end - -# Add anonymous constraints and axes from the model to constraints_and_axes (car) -function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) - types = JuMP.list_of_constraint_types(model) - for t in types - if t[1] != VariableRef - for c in JuMP.all_constraints(model, t[1], t[2]) - if !in(c, car.constraints) - push!(car.constraints, c) - car.constraints_to_axes[c] = Array{BlockDecomposition.Axis,1}() - car.constraints_to_variables[c] = _get_variables_in_constraint(model, c) - end - end - end - end - for v in JuMP.all_variables(model) - push!(car.variables, JuMP.index(v)) - end -end - +# Adds a constraint to the Constraints_and_Axes object o function _add_constraint!( o::Constraints_and_Axes, c::JuMP.ConstraintRef, model::JuMP.Model, - reference_constraints_name::T, -) where T <: JuMP.Containers.DenseAxisArray + index_sets +) push!(o.constraints, c) - axes_of_constraint = _get_axes_of_constraint(reference_constraints_name) - for r in axes_of_constraint + for r in index_sets push!(o.axes, r) end - o.constraints_to_axes[c] = axes_of_constraint + o.constraints_to_axes[c] = index_sets o.constraints_to_variables[c] = _get_variables_in_constraint(model, c) end -function _get_axes_of_constraint( - reference_constraints_name::T -) where T <: JuMP.Containers.DenseAxisArray +# Computes the index sets of the constraint reference +function _get_constraint_axes(constraint_ref::AbstractArray) + axs = axes(constraint_ref) + ds = JuMP.Containers.DenseAxisArray(constraint_ref, axs...) + return _get_constraint_axes(ds) +end + +function _get_constraint_axes(constraint_ref::JuMP.Containers.DenseAxisArray) axes_of_constraint = Array{BlockDecomposition.Axis,1}() - for a in reference_constraints_name.axes + for a in constraint_ref.axes if a != 1 # Axes of the form 1:1 do not matter (single constraints) push!(axes_of_constraint, Axis(a)) end @@ -279,6 +272,27 @@ function _get_axes_of_constraint( return axes_of_constraint end +function _get_constraint_axes(constraint_ref::JuMP.Containers.SparseAxisArray) + indices = eachindex(constraint_ref) + axes = Array{Set{Any}}(undef, 1) + for index in indices + for j in 1:length(index) + if length(axes) < length(index) + resize!(axes, length(index)) + end + if !isassigned(axes, j) + axes[j] = Set() + end + push!(axes[j], index[j]) + end + end + result = Array{BlockDecomposition.Axis,1}() + for a in axes + push!(result, Axis(a)) + end + return result +end + function _get_variables_in_constraint(model::JuMP.Model, constraint::JuMP.ConstraintRef) f = MOI.get(model, MathOptInterface.ConstraintFunction(), constraint) variables = Set{MOI.VariableIndex}() diff --git a/src/axis.jl b/src/axis.jl index de47638..a79c7bf 100644 --- a/src/axis.jl +++ b/src/axis.jl @@ -30,6 +30,7 @@ Base.to_index(i::AxisId) = i.indice # Allow matching of AxisId key using the value of field indice (dict.jl:289) # and vice-versa +Base.isequal(i::AxisId{N,T}, j::AxisId{N,T}) where {N,T} = isequal(i.indice, j.indice) Base.isequal(i::T, j::AxisId{N,T}) where {N,T} = isequal(i, j.indice) Base.isequal(i::AxisId{N,T}, j::T) where {N,T} = isequal(i.indice, j) Base.isless(i::T, j::AxisId{N,T}) where {N,T} = isless(i, j.indice) @@ -46,7 +47,7 @@ struct Axis{Name, T} container::Vector{AxisId{Name, T}} end -function Axis(name::Symbol, container::A) where {T, A <: AbstractArray{T}} +function Axis(name::Symbol, container::A) where {T, A <: Union{AbstractArray{T}, Set{T}}} indices = AxisId{name, T}[] for val in container push!(indices, AxisId{name, T}(val)) From ceadb49292fa7305be482cf7902cb2939bddfa13 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Fri, 25 Jun 2021 22:02:40 +0200 Subject: [PATCH 26/32] Rename struct holding model information Rename from Constraints_and_axes to model_description --- src/automatic_dantzig_wolfe.jl | 74 +++++++++++++++++----------------- src/decomposition.jl | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index b4cc6a5..6423010 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -43,11 +43,11 @@ end # Returns all possible block structures of the given model function get_all_block_structures(model::JuMP.Model) - constraints_and_axes = get_constraints_and_axes(model) + model_description = get_model_description(model) block_structures = Array{BlockStructure,1}() - axesSets = collect(powerset(collect(constraints_and_axes.axes))) + axesSets = collect(powerset(collect(model_description.axes))) for axes in axesSets - block_structure0, block_structure1 = get_block_structure(axes, constraints_and_axes, model) + block_structure0, block_structure1 = get_block_structure(axes, model_description, model) # Only add structures that were not found already and where the set of master # constraints is not empty if !structure_exists(block_structures, block_structure0) && @@ -64,7 +64,7 @@ end # Contains all the information about the constraints and variables in a model we might need # to compute different block structures -mutable struct Constraints_and_Axes +mutable struct ModelDescription constraints::Set{JuMP.ConstraintRef} axes::Set{BlockDecomposition.Axis} variables::Set{MOI.VariableIndex} @@ -74,7 +74,7 @@ end struct BlockStructure # Constraints_and_axes is the same for every possible BlockStructure of a model - constraints_and_axes::Constraints_and_Axes + model_description::ModelDescription master_constraints::Set{JuMP.ConstraintRef} master_sets::Array{BlockDecomposition.Axis,1} # Invert linked is true iff the linking constraints are the ones not indexed @@ -110,7 +110,7 @@ end function _get_relative_border_area_score(block_structure::BlockStructure) n_linking_constraints = length(block_structure.master_constraints) - n_constraints = length(block_structure.constraints_and_axes.constraints) + n_constraints = length(block_structure.model_description.constraints) score = n_linking_constraints/n_constraints end @@ -131,7 +131,7 @@ function _get_block_border_score(block_structure::BlockStructure) m = 0 lambda = 5 for block in block_structure.blocks - n = _get_nb_nonzero_entries(block, block_structure.constraints_and_axes) + n = _get_nb_nonzero_entries(block, block_structure.model_description) push!(e, n) m += n end @@ -140,7 +140,7 @@ function _get_block_border_score(block_structure::BlockStructure) q_a += (e[i] / m) * (1 - (e[i] / m)) end n_master_constraints = length(block_structure.master_constraints) - n_constraints = length(block_structure.constraints_and_axes.constraints) + n_constraints = length(block_structure.model_description.constraints) p_a = MathConstants.e^(-1 * lambda * (n_master_constraints / n_constraints)) gamma = q_a*p_a return gamma @@ -149,11 +149,11 @@ end # Computes the number of nonzero entries in the given constraints function _get_nb_nonzero_entries( constraints::Set{JuMP.ConstraintRef}, - constraints_and_axes::Constraints_and_Axes + model_description::ModelDescription ) result = 0 for c in constraints - result = result + length(constraints_and_axes.constraints_to_variables[c]) + result = result + length(model_description.constraints_to_variables[c]) end return result end @@ -168,7 +168,7 @@ function white_scores(block_structures::Array{BlockStructure,1}) end function _get_white_score(block_structure::BlockStructure) - n_master = length(block_structure.constraints_and_axes.variables) * + n_master = length(block_structure.model_description.variables) * length(block_structure.master_constraints) n_blocks = 0 variables_in_block = Set{MOI.VariableIndex}() @@ -177,20 +177,20 @@ function _get_white_score(block_structure::BlockStructure) for constraint in block union!( variables_in_block, - block_structure.constraints_and_axes.constraints_to_variables[constraint] + block_structure.model_description.constraints_to_variables[constraint] ) end - n_blocks = n_blocks + length(variables_in_block)*length(block) + n_blocks = n_blocks + length(variables_in_block) * length(block) end - coefficient_matrix_size = length(block_structure.constraints_and_axes.constraints) * - length(block_structure.constraints_and_axes.variables) - black_score = (n_master+n_blocks)/coefficient_matrix_size + coefficient_matrix_size = length(block_structure.model_description.constraints) * + length(block_structure.model_description.variables) + black_score = (n_master + n_blocks) / coefficient_matrix_size white_score = 1-black_score return white_score end -# Add anonymous constraints and axes from the model to constraints_and_axes (car) -function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) +# Add anonymous constraints and axes from the model to model_description (car) +function _add_anonymous_var_con!(car::ModelDescription, model::JuMP.Model) types = JuMP.list_of_constraint_types(model) for t in types if t[1] != VariableRef @@ -208,14 +208,14 @@ function _add_anonymous_var_con!(car::Constraints_and_Axes, model::JuMP.Model) end end -# Returns an instance of the struct Constraints_and_Axes -function get_constraints_and_axes(model::JuMP.Model) +# Returns an instance of the struct ModelDescription +function get_model_description(model::JuMP.Model) constraints = Set{JuMP.ConstraintRef}() axes = Set{BlockDecomposition.Axis}() variables = Set{MOI.VariableIndex}() constraints_to_axes = Dict{JuMP.ConstraintRef, Array{BlockDecomposition.Axis}}() constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() - constraints_and_axes = Constraints_and_Axes( + model_description = ModelDescription( constraints, axes, variables, @@ -227,7 +227,7 @@ function get_constraints_and_axes(model::JuMP.Model) index_sets = _get_constraint_axes(reference) if eltype(reference) <: JuMP.ConstraintRef # Add constraint for c in reference - _add_constraint!(constraints_and_axes, c, model, index_sets) + _add_constraint!(model_description, c, model, index_sets) end elseif eltype(reference) <: JuMP.VariableRef # Add variable for v in reference @@ -235,14 +235,14 @@ function get_constraints_and_axes(model::JuMP.Model) end end end - constraints_and_axes.variables = variables - _add_anonymous_var_con!(constraints_and_axes, model) - return constraints_and_axes + model_description.variables = variables + _add_anonymous_var_con!(model_description, model) + return model_description end -# Adds a constraint to the Constraints_and_Axes object o +# Adds a constraint to the ModelDescription object o function _add_constraint!( - o::Constraints_and_Axes, + o::ModelDescription, c::JuMP.ConstraintRef, model::JuMP.Model, index_sets @@ -308,30 +308,30 @@ end # by at least one* axis from axes are in the master function get_block_structure( axes::Array{<:Axis,1}, - constraints_and_axes::Constraints_and_Axes, + model_description::ModelDescription, model::JuMP.Model, ) block_constraints = Set{JuMP.ConstraintRef}() master_constraints = Set{JuMP.ConstraintRef}() - for c in keys(constraints_and_axes.constraints_to_axes) - if isempty(intersect(axes, constraints_and_axes.constraints_to_axes[c])) + for c in keys(model_description.constraints_to_axes) + if isempty(intersect(axes, model_description.constraints_to_axes[c])) push!(master_constraints, c) else push!(block_constraints, c) end end # Create first block structure - graph = _create_graph(block_constraints, constraints_and_axes) + graph = _create_graph(block_constraints, model_description) blocks = _get_connected_components!(graph) bs0 = BlockStructure( - constraints_and_axes, master_constraints, axes, false, blocks, graph + model_description, master_constraints, axes, false, blocks, graph ) # Create second block structure (the roles of master constraints and block # constraints are switched) - graph = _create_graph(master_constraints, constraints_and_axes) + graph = _create_graph(master_constraints, model_description) blocks = _get_connected_components!(graph) bs1 = BlockStructure( - constraints_and_axes, block_constraints, axes, true, blocks, graph + model_description, block_constraints, axes, true, blocks, graph ) return bs0, bs1 end @@ -351,7 +351,7 @@ end function _create_graph( vertices::Set{JuMP.ConstraintRef}, - constraints_and_axes::Constraints_and_Axes, + model_description::ModelDescription, ) graph = SimpleGraph(0) graph = MetaGraph(graph) @@ -367,8 +367,8 @@ function _create_graph( for v1 in vertices, v2 in vertices if v1 != v2 intersection = intersect( - constraints_and_axes.constraints_to_variables[v1], - constraints_and_axes.constraints_to_variables[v2], + model_description.constraints_to_variables[v1], + model_description.constraints_to_variables[v2], ) if !isempty(intersection) add_edge!(graph, graph[v1, :constraint_ref], graph[v2, :constraint_ref]) diff --git a/src/decomposition.jl b/src/decomposition.jl index 5fcae09..5a847c4 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -38,7 +38,7 @@ function register_automatic_dantzig_wolfe(model::JuMP.Model) setannotation!(model, constraintref, ann) union!( variables_in_block, - model.ext[:decomposition_structure].constraints_and_axes.constraints_to_variables[constraintref] + model.ext[:decomposition_structure].model_description.constraints_to_variables[constraintref] ) end for v in variables_in_block From 071e2b7d1cc501e95822827e415c3386e4410fd6 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sat, 26 Jun 2021 19:30:54 +0200 Subject: [PATCH 27/32] Fix bug in automatic decomposition test Write BlockDecomposition.white_score instead of white_score --- test/formulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/formulations.jl b/test/formulations.jl index 7d82e18..6ca762d 100644 --- a/test/formulations.jl +++ b/test/formulations.jl @@ -41,7 +41,7 @@ function example_assignment_automatic_dantzig_wolfe() Q = [1020 1460 1530 1190] M = 1:nb_machines J = 1:nb_jobs - model = BlockModel(automatic_dantzig_wolfe = white_score) + model = BlockModel(automatic_dantzig_wolfe = BlockDecomposition.white_score) @variable(model, x[m in M, j in J], Bin) @constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1) @constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]) From c362b3448d0f673bdb2001207f7e25daf39e0767 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sat, 26 Jun 2021 19:56:25 +0200 Subject: [PATCH 28/32] Write JC instead of JuMP.Containers --- src/automatic_dantzig_wolfe.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index 6423010..c1956d8 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -258,11 +258,11 @@ end # Computes the index sets of the constraint reference function _get_constraint_axes(constraint_ref::AbstractArray) axs = axes(constraint_ref) - ds = JuMP.Containers.DenseAxisArray(constraint_ref, axs...) + ds = JC.DenseAxisArray(constraint_ref, axs...) return _get_constraint_axes(ds) end -function _get_constraint_axes(constraint_ref::JuMP.Containers.DenseAxisArray) +function _get_constraint_axes(constraint_ref::JC.DenseAxisArray) axes_of_constraint = Array{BlockDecomposition.Axis,1}() for a in constraint_ref.axes if a != 1 # Axes of the form 1:1 do not matter (single constraints) @@ -272,7 +272,7 @@ function _get_constraint_axes(constraint_ref::JuMP.Containers.DenseAxisArray) return axes_of_constraint end -function _get_constraint_axes(constraint_ref::JuMP.Containers.SparseAxisArray) +function _get_constraint_axes(constraint_ref::JC.SparseAxisArray) indices = eachindex(constraint_ref) axes = Array{Set{Any}}(undef, 1) for index in indices From 4830947f09229f7d90641466842ea67654e19ffd Mon Sep 17 00:00:00 2001 From: David Meichel Date: Mon, 28 Jun 2021 14:57:27 +0200 Subject: [PATCH 29/32] Read .mps file and construct decomposition using a .dec file --- src/BlockDecomposition.jl | 28 +++++++++-- src/automatic_dantzig_wolfe.jl | 16 ++++++- src/decomposition.jl | 4 +- src/read_decomposition.jl | 87 ++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/read_decomposition.jl diff --git a/src/BlockDecomposition.jl b/src/BlockDecomposition.jl index ea0dce9..bd608fe 100644 --- a/src/BlockDecomposition.jl +++ b/src/BlockDecomposition.jl @@ -15,7 +15,8 @@ const MOIU = MOI.Utilities const JC = JuMP.Containers export BlockModel, annotation, specify!, decompose, gettree, - getmaster, getsubproblems, indice, objectiveprimalbound!, objectivedualbound! + getmaster, getsubproblems, indice, objectiveprimalbound!, objectivedualbound!, + read_decomposition! export @axis, @dantzig_wolfe_decomposition, @benders_decomposition export AutoDwStrategy @@ -27,11 +28,29 @@ include("decomposition.jl") include("objective.jl") include("callbacks.jl") include("automatic_dantzig_wolfe.jl") +include("read_decomposition.jl") include("utils.jl") -function BlockModel(args...; automatic_dantzig_wolfe::AutoDwStrategy = inaktive, kw...) - m = JuMP.Model(args...; kw...) +function BlockModel( + args...; + automatic_dantzig_wolfe::AutoDwStrategy = inaktive, + read_decomposition::Bool = false, + model_filename::String = "", + decomp_filename::String = "", + kw... +) + if read_decomposition + src = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MPS, filename = model_filename) + MathOptInterface.read_from_file(src, model_filename) + m = JuMP.Model(args...; kw...) + MathOptInterface.copy_to(m, src) + m.ext[:model_filename] = model_filename + m.ext[:decomp_filename] = decomp_filename + else + m = JuMP.Model(args...; kw...) + end JuMP.set_optimize_hook(m, optimize!) + m.ext[:read_decomposition] = read_decomposition m.ext[:automatic_dantzig_wolfe] = automatic_dantzig_wolfe return m end @@ -40,6 +59,9 @@ function optimize!(m::JuMP.Model) if m.ext[:automatic_dantzig_wolfe] != inaktive automatic_dw_decomposition!(m) end + if m.ext[:read_decomposition] + read_decomposition!(m) + end register_decomposition(m) return JuMP.optimize!(m, ignore_optimize_hook = true) end diff --git a/src/automatic_dantzig_wolfe.jl b/src/automatic_dantzig_wolfe.jl index c1956d8..8f8044b 100644 --- a/src/automatic_dantzig_wolfe.jl +++ b/src/automatic_dantzig_wolfe.jl @@ -70,9 +70,15 @@ mutable struct ModelDescription variables::Set{MOI.VariableIndex} constraints_to_axes::Dict{JuMP.ConstraintRef, Array{BlockDecomposition.Axis}} constraints_to_variables::Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}} + ModelDescription(constraints_to_variables) = ( + md = new(); + md.constraints_to_variables = constraints_to_variables; + return md + ) + ModelDescription(c, a, v, cta, ctv) = new(c, a, v, cta, ctv) end -struct BlockStructure +mutable struct BlockStructure # Constraints_and_axes is the same for every possible BlockStructure of a model model_description::ModelDescription master_constraints::Set{JuMP.ConstraintRef} @@ -82,6 +88,14 @@ struct BlockStructure invert_linking::Bool blocks::Array{Set{JuMP.ConstraintRef},1} graph::MetaGraph + BlockStructure(master_constraints, blocks, constraints_to_variables) = ( + bs = new(); + bs.master_constraints = master_constraints; + bs.blocks = blocks; + bs.model_description = ModelDescription(constraints_to_variables); + return bs + ) + BlockStructure(md, mc, ms, il, b, g) = new(md, mc, ms, il, b, g) end # Returns true if and only if block_structure already exists in block_structures diff --git a/src/decomposition.jl b/src/decomposition.jl index 5a847c4..4196a00 100644 --- a/src/decomposition.jl +++ b/src/decomposition.jl @@ -6,7 +6,7 @@ which partition (master/subproblem) of the original formulation the variable or the constraint is located. """ function register_decomposition(model::JuMP.Model) - if model.ext[:automatic_dantzig_wolfe] != inaktive + if model.ext[:automatic_dantzig_wolfe] != inaktive || model.ext[:read_decomposition] register_automatic_dantzig_wolfe(model) else tree = gettree(model) @@ -32,7 +32,7 @@ function register_automatic_dantzig_wolfe(model::JuMP.Model) empty!(variables_in_block) empty!(axisids) push!(axisids, i) - # Annotate constraints in one block (and variables contained in these) with the same annotation + # Annotate constraints in one block (and variables contained in these) with the same annotation ann = _getannotation(tree, axisids) for constraintref in decomposition_structure.blocks[i] setannotation!(model, constraintref, ann) diff --git a/src/read_decomposition.jl b/src/read_decomposition.jl new file mode 100644 index 0000000..fc588bf --- /dev/null +++ b/src/read_decomposition.jl @@ -0,0 +1,87 @@ +# Reads the decomposition file, computes the decomposition of the model +# and stores it in model.ext[:decompositon_structure] +function read_decomposition!(model::JuMP.Model) + decomposition_filename = model.ext[:decomp_filename] + decomposition_structure = get_decomposition_structure(model, decomposition_filename) + model.ext[:decomposition_structure] = decomposition_structure + decomposition_axis = BlockDecomposition.Axis( + 1:length(model.ext[:decomposition_structure].blocks) + ) + decomposition = BlockDecomposition.decompose_leaf( + model, + BlockDecomposition.DantzigWolfe, + decomposition_axis + ) + BlockDecomposition.register_subproblems!( + decomposition, + decomposition_axis, + BlockDecomposition.DwPricingSp, + BlockDecomposition.DantzigWolfe + ) +end + +function get_decomposition_structure(model::JuMP.Model, decomposition_filename::String) + master_names, blocks_names = _read_constraint_names(decomposition_filename) + master_cons = Set{JuMP.ConstraintRef}() + blocks = Array{Set{JuMP.ConstraintRef},1}(undef, length(blocks_names)) + constraints_to_variables = Dict{JuMP.ConstraintRef, Set{MOI.VariableIndex}}() + for cons_name in master_names + cons = JuMP.constraint_by_name(model, cons_name) + push!(master_cons, cons) + constraints_to_variables[cons] = _get_variables_in_constraint(model, cons) + end + for block_nb in 1:length(blocks_names) + block = Set{JuMP.ConstraintRef}() + for cons_name in blocks_names[block_nb] + cons = JuMP.constraint_by_name(model, cons_name) + push!(block, cons) + constraints_to_variables[cons] = _get_variables_in_constraint(model, cons) + end + blocks[block_nb] = block + end + decomposition_structure = BlockStructure(master_cons, blocks, constraints_to_variables) + return decomposition_structure +end + +# Computes names of constraints in the master and blocks +function _read_constraint_names(decomp_filename::String) + master = Set{String}() # Names of master constraints + blocks = Array{Set{String},1}() # Names of constraints in blocks + lines = readlines(decomp_filename) + for index in eachindex(lines) + items = _line_to_items(lines[index]) + if items[1] == "NBLOCKS" # Initialize number of blocks + index = index + 1 + nb_blocks = parse(Int, lines[index]) + blocks = Array{Set{String},1}(undef, nb_blocks) + end + if items[1] == "BLOCK" # Add block + nb = parse(Int, items[2]) + constraint_names, new_index = _get_following_constraints(lines, index) + blocks[nb] = constraint_names + end + if items[1] == "MASTERCONSS" # Add master constraints + master, _ = _get_following_constraints(lines, index) + end + end + return master, blocks +end + +function _line_to_items(line) + items = split(line, " "; keepempty = false) + return String.(items) +end + +# Returns all the constraint names directly following the current position +function _get_following_constraints(lines::Vector{String}, index::Int) + constraint_names = Set{String}() + for new_index in index+1:length(lines) + items = _line_to_items(lines[new_index]) + head = items[1] + if head == "BLOCK" || head == "MASTERCONSS" + return constraint_names, new_index + end + push!(constraint_names, head) + end + return constraint_names, length(lines) +end \ No newline at end of file From 58f223bad7e941f6605c517d639943a2b6b45a99 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Thu, 8 Jul 2021 20:24:09 +0200 Subject: [PATCH 30/32] Add demos for read decomposition --- .../bin_packing/bpp_20_0.dec | 471 +++ .../bin_packing/bpp_20_0.mps | 3331 +++++++++++++++++ .../bin_packing/read_model.jl | 17 + read_decomposition_demos/gap/gap.dec | 41 + read_decomposition_demos/gap/gap.mps | 557 +++ read_decomposition_demos/gap/read_model.jl | 17 + 6 files changed, 4434 insertions(+) create mode 100644 read_decomposition_demos/bin_packing/bpp_20_0.dec create mode 100644 read_decomposition_demos/bin_packing/bpp_20_0.mps create mode 100644 read_decomposition_demos/bin_packing/read_model.jl create mode 100644 read_decomposition_demos/gap/gap.dec create mode 100644 read_decomposition_demos/gap/gap.mps create mode 100644 read_decomposition_demos/gap/read_model.jl diff --git a/read_decomposition_demos/bin_packing/bpp_20_0.dec b/read_decomposition_demos/bin_packing/bpp_20_0.dec new file mode 100644 index 0000000..e77fc37 --- /dev/null +++ b/read_decomposition_demos/bin_packing/bpp_20_0.dec @@ -0,0 +1,471 @@ +\\ ndetectors +\\ 2 +\\ name time nnewblocks %ofnewborderconss %ofnewblockconss %ofnewlinkingvars %ofnewblockvars +\\ consclass 0.000000 0 0.047619 0.000000 0.000000 0.000000 +\\ connectedbase 0.000000 20 0.000000 0.000000 0.000000 1.000000 +PRESOLVED +0 +NBLOCKS +20 +BLOCK 1 +R0021 +R0041 +R0061 +R0081 +R0101 +R0121 +R0141 +R0161 +R0181 +R0201 +R0221 +R0241 +R0261 +R0281 +R0301 +R0321 +R0341 +R0361 +R0381 +R0401 +R0421 +BLOCK 2 +R0022 +R0042 +R0062 +R0082 +R0102 +R0122 +R0142 +R0162 +R0182 +R0202 +R0222 +R0242 +R0262 +R0282 +R0302 +R0322 +R0342 +R0362 +R0382 +R0402 +R0422 +BLOCK 3 +R0023 +R0043 +R0063 +R0083 +R0103 +R0123 +R0143 +R0163 +R0183 +R0203 +R0223 +R0243 +R0263 +R0283 +R0303 +R0323 +R0343 +R0363 +R0383 +R0403 +R0423 +BLOCK 4 +R0024 +R0044 +R0064 +R0084 +R0104 +R0124 +R0144 +R0164 +R0184 +R0204 +R0224 +R0244 +R0264 +R0284 +R0304 +R0324 +R0344 +R0364 +R0384 +R0404 +R0424 +BLOCK 5 +R0025 +R0045 +R0065 +R0085 +R0105 +R0125 +R0145 +R0165 +R0185 +R0205 +R0225 +R0245 +R0265 +R0285 +R0305 +R0325 +R0345 +R0365 +R0385 +R0405 +R0425 +BLOCK 6 +R0026 +R0046 +R0066 +R0086 +R0106 +R0126 +R0146 +R0166 +R0186 +R0206 +R0226 +R0246 +R0266 +R0286 +R0306 +R0326 +R0346 +R0366 +R0386 +R0406 +R0426 +BLOCK 7 +R0027 +R0047 +R0067 +R0087 +R0107 +R0127 +R0147 +R0167 +R0187 +R0207 +R0227 +R0247 +R0267 +R0287 +R0307 +R0327 +R0347 +R0367 +R0387 +R0407 +R0427 +BLOCK 8 +R0028 +R0048 +R0068 +R0088 +R0108 +R0128 +R0148 +R0168 +R0188 +R0208 +R0228 +R0248 +R0268 +R0288 +R0308 +R0328 +R0348 +R0368 +R0388 +R0408 +R0428 +BLOCK 9 +R0029 +R0049 +R0069 +R0089 +R0109 +R0129 +R0149 +R0169 +R0189 +R0209 +R0229 +R0249 +R0269 +R0289 +R0309 +R0329 +R0349 +R0369 +R0389 +R0409 +R0429 +BLOCK 10 +R0030 +R0050 +R0070 +R0090 +R0110 +R0130 +R0150 +R0170 +R0190 +R0210 +R0230 +R0250 +R0270 +R0290 +R0310 +R0330 +R0350 +R0370 +R0390 +R0410 +R0430 +BLOCK 11 +R0031 +R0051 +R0071 +R0091 +R0111 +R0131 +R0151 +R0171 +R0191 +R0211 +R0231 +R0251 +R0271 +R0291 +R0311 +R0331 +R0351 +R0371 +R0391 +R0411 +R0431 +BLOCK 12 +R0032 +R0052 +R0072 +R0092 +R0112 +R0132 +R0152 +R0172 +R0192 +R0212 +R0232 +R0252 +R0272 +R0292 +R0312 +R0332 +R0352 +R0372 +R0392 +R0412 +R0432 +BLOCK 13 +R0033 +R0053 +R0073 +R0093 +R0113 +R0133 +R0153 +R0173 +R0193 +R0213 +R0233 +R0253 +R0273 +R0293 +R0313 +R0333 +R0353 +R0373 +R0393 +R0413 +R0433 +BLOCK 14 +R0034 +R0054 +R0074 +R0094 +R0114 +R0134 +R0154 +R0174 +R0194 +R0214 +R0234 +R0254 +R0274 +R0294 +R0314 +R0334 +R0354 +R0374 +R0394 +R0414 +R0434 +BLOCK 15 +R0035 +R0055 +R0075 +R0095 +R0115 +R0135 +R0155 +R0175 +R0195 +R0215 +R0235 +R0255 +R0275 +R0295 +R0315 +R0335 +R0355 +R0375 +R0395 +R0415 +R0435 +BLOCK 16 +R0036 +R0056 +R0076 +R0096 +R0116 +R0136 +R0156 +R0176 +R0196 +R0216 +R0236 +R0256 +R0276 +R0296 +R0316 +R0336 +R0356 +R0376 +R0396 +R0416 +R0436 +BLOCK 17 +R0037 +R0057 +R0077 +R0097 +R0117 +R0137 +R0157 +R0177 +R0197 +R0217 +R0237 +R0257 +R0277 +R0297 +R0317 +R0337 +R0357 +R0377 +R0397 +R0417 +R0437 +BLOCK 18 +R0038 +R0058 +R0078 +R0098 +R0118 +R0138 +R0158 +R0178 +R0198 +R0218 +R0238 +R0258 +R0278 +R0298 +R0318 +R0338 +R0358 +R0378 +R0398 +R0418 +R0438 +BLOCK 19 +R0039 +R0059 +R0079 +R0099 +R0119 +R0139 +R0159 +R0179 +R0199 +R0219 +R0239 +R0259 +R0279 +R0299 +R0319 +R0339 +R0359 +R0379 +R0399 +R0419 +R0439 +BLOCK 20 +R0040 +R0060 +R0080 +R0100 +R0120 +R0140 +R0160 +R0180 +R0200 +R0220 +R0240 +R0260 +R0280 +R0300 +R0320 +R0340 +R0360 +R0380 +R0400 +R0420 +R0440 +MASTERCONSS +R0001 +R0002 +R0003 +R0004 +R0005 +R0006 +R0007 +R0008 +R0009 +R0010 +R0011 +R0012 +R0013 +R0014 +R0015 +R0016 +R0017 +R0018 +R0019 +R0020 +R0441 diff --git a/read_decomposition_demos/bin_packing/bpp_20_0.mps b/read_decomposition_demos/bin_packing/bpp_20_0.mps new file mode 100644 index 0000000..6241bc6 --- /dev/null +++ b/read_decomposition_demos/bin_packing/bpp_20_0.mps @@ -0,0 +1,3331 @@ +NAME bpp_20_0 +ROWS + E R0001 + E R0002 + E R0003 + E R0004 + E R0005 + E R0006 + E R0007 + E R0008 + E R0009 + E R0010 + E R0011 + E R0012 + E R0013 + E R0014 + E R0015 + E R0016 + E R0017 + E R0018 + E R0019 + E R0020 + L R0021 + L R0022 + L R0023 + L R0024 + L R0025 + L R0026 + L R0027 + L R0028 + L R0029 + L R0030 + L R0031 + L R0032 + L R0033 + L R0034 + L R0035 + L R0036 + L R0037 + L R0038 + L R0039 + L R0040 + L R0041 + L R0042 + L R0043 + L R0044 + L R0045 + L R0046 + L R0047 + L R0048 + L R0049 + L R0050 + L R0051 + L R0052 + L R0053 + L R0054 + L R0055 + L R0056 + L R0057 + L R0058 + L R0059 + L R0060 + L R0061 + L R0062 + L R0063 + L R0064 + L R0065 + L R0066 + L R0067 + L R0068 + L R0069 + L R0070 + L R0071 + L R0072 + L R0073 + L R0074 + L R0075 + L R0076 + L R0077 + L R0078 + L R0079 + L R0080 + L R0081 + L R0082 + L R0083 + L R0084 + L R0085 + L R0086 + L R0087 + L R0088 + L R0089 + L R0090 + L R0091 + L R0092 + L R0093 + L R0094 + L R0095 + L R0096 + L R0097 + L R0098 + L R0099 + L R0100 + L R0101 + L R0102 + L R0103 + L R0104 + L R0105 + L R0106 + L R0107 + L R0108 + L R0109 + L R0110 + L R0111 + L R0112 + L R0113 + L R0114 + L R0115 + L R0116 + L R0117 + L R0118 + L R0119 + L R0120 + L R0121 + L R0122 + L R0123 + L R0124 + L R0125 + L R0126 + L R0127 + L R0128 + L R0129 + L R0130 + L R0131 + L R0132 + L R0133 + L R0134 + L R0135 + L R0136 + L R0137 + L R0138 + L R0139 + L R0140 + L R0141 + L R0142 + L R0143 + L R0144 + L R0145 + L R0146 + L R0147 + L R0148 + L R0149 + L R0150 + L R0151 + L R0152 + L R0153 + L R0154 + L R0155 + L R0156 + L R0157 + L R0158 + L R0159 + L R0160 + L R0161 + L R0162 + L R0163 + L R0164 + L R0165 + L R0166 + L R0167 + L R0168 + L R0169 + L R0170 + L R0171 + L R0172 + L R0173 + L R0174 + L R0175 + L R0176 + L R0177 + L R0178 + L R0179 + L R0180 + L R0181 + L R0182 + L R0183 + L R0184 + L R0185 + L R0186 + L R0187 + L R0188 + L R0189 + L R0190 + L R0191 + L R0192 + L R0193 + L R0194 + L R0195 + L R0196 + L R0197 + L R0198 + L R0199 + L R0200 + L R0201 + L R0202 + L R0203 + L R0204 + L R0205 + L R0206 + L R0207 + L R0208 + L R0209 + L R0210 + L R0211 + L R0212 + L R0213 + L R0214 + L R0215 + L R0216 + L R0217 + L R0218 + L R0219 + L R0220 + L R0221 + L R0222 + L R0223 + L R0224 + L R0225 + L R0226 + L R0227 + L R0228 + L R0229 + L R0230 + L R0231 + L R0232 + L R0233 + L R0234 + L R0235 + L R0236 + L R0237 + L R0238 + L R0239 + L R0240 + L R0241 + L R0242 + L R0243 + L R0244 + L R0245 + L R0246 + L R0247 + L R0248 + L R0249 + L R0250 + L R0251 + L R0252 + L R0253 + L R0254 + L R0255 + L R0256 + L R0257 + L R0258 + L R0259 + L R0260 + L R0261 + L R0262 + L R0263 + L R0264 + L R0265 + L R0266 + L R0267 + L R0268 + L R0269 + L R0270 + L R0271 + L R0272 + L R0273 + L R0274 + L R0275 + L R0276 + L R0277 + L R0278 + L R0279 + L R0280 + L R0281 + L R0282 + L R0283 + L R0284 + L R0285 + L R0286 + L R0287 + L R0288 + L R0289 + L R0290 + L R0291 + L R0292 + L R0293 + L R0294 + L R0295 + L R0296 + L R0297 + L R0298 + L R0299 + L R0300 + L R0301 + L R0302 + L R0303 + L R0304 + L R0305 + L R0306 + L R0307 + L R0308 + L R0309 + L R0310 + L R0311 + L R0312 + L R0313 + L R0314 + L R0315 + L R0316 + L R0317 + L R0318 + L R0319 + L R0320 + L R0321 + L R0322 + L R0323 + L R0324 + L R0325 + L R0326 + L R0327 + L R0328 + L R0329 + L R0330 + L R0331 + L R0332 + L R0333 + L R0334 + L R0335 + L R0336 + L R0337 + L R0338 + L R0339 + L R0340 + L R0341 + L R0342 + L R0343 + L R0344 + L R0345 + L R0346 + L R0347 + L R0348 + L R0349 + L R0350 + L R0351 + L R0352 + L R0353 + L R0354 + L R0355 + L R0356 + L R0357 + L R0358 + L R0359 + L R0360 + L R0361 + L R0362 + L R0363 + L R0364 + L R0365 + L R0366 + L R0367 + L R0368 + L R0369 + L R0370 + L R0371 + L R0372 + L R0373 + L R0374 + L R0375 + L R0376 + L R0377 + L R0378 + L R0379 + L R0380 + L R0381 + L R0382 + L R0383 + L R0384 + L R0385 + L R0386 + L R0387 + L R0388 + L R0389 + L R0390 + L R0391 + L R0392 + L R0393 + L R0394 + L R0395 + L R0396 + L R0397 + L R0398 + L R0399 + L R0400 + L R0401 + L R0402 + L R0403 + L R0404 + L R0405 + L R0406 + L R0407 + L R0408 + L R0409 + L R0410 + L R0411 + L R0412 + L R0413 + L R0414 + L R0415 + L R0416 + L R0417 + L R0418 + L R0419 + L R0420 + L R0421 + L R0422 + L R0423 + L R0424 + L R0425 + L R0426 + L R0427 + L R0428 + L R0429 + L R0430 + L R0431 + L R0432 + L R0433 + L R0434 + L R0435 + L R0436 + L R0437 + L R0438 + L R0439 + L R0440 + L R0441 + N R0442 +COLUMNS + C0001 R0001 1 + C0001 R0021 452 + C0001 R0041 1 + C0002 R0001 1 + C0002 R0022 452 + C0002 R0042 1 + C0003 R0001 1 + C0003 R0023 452 + C0003 R0043 1 + C0004 R0001 1 + C0004 R0024 452 + C0004 R0044 1 + C0005 R0001 1 + C0005 R0025 452 + C0005 R0045 1 + C0006 R0001 1 + C0006 R0026 452 + C0006 R0046 1 + C0007 R0001 1 + C0007 R0027 452 + C0007 R0047 1 + C0008 R0001 1 + C0008 R0028 452 + C0008 R0048 1 + C0009 R0001 1 + C0009 R0029 452 + C0009 R0049 1 + C0010 R0001 1 + C0010 R0030 452 + C0010 R0050 1 + C0011 R0001 1 + C0011 R0031 452 + C0011 R0051 1 + C0012 R0001 1 + C0012 R0032 452 + C0012 R0052 1 + C0013 R0001 1 + C0013 R0033 452 + C0013 R0053 1 + C0014 R0001 1 + C0014 R0034 452 + C0014 R0054 1 + C0015 R0001 1 + C0015 R0035 452 + C0015 R0055 1 + C0016 R0001 1 + C0016 R0036 452 + C0016 R0056 1 + C0017 R0001 1 + C0017 R0037 452 + C0017 R0057 1 + C0018 R0001 1 + C0018 R0038 452 + C0018 R0058 1 + C0019 R0001 1 + C0019 R0039 452 + C0019 R0059 1 + C0020 R0001 1 + C0020 R0040 452 + C0020 R0060 1 + C0021 R0002 1 + C0021 R0021 561 + C0021 R0061 1 + C0022 R0002 1 + C0022 R0022 561 + C0022 R0062 1 + C0023 R0002 1 + C0023 R0023 561 + C0023 R0063 1 + C0024 R0002 1 + C0024 R0024 561 + C0024 R0064 1 + C0025 R0002 1 + C0025 R0025 561 + C0025 R0065 1 + C0026 R0002 1 + C0026 R0026 561 + C0026 R0066 1 + C0027 R0002 1 + C0027 R0027 561 + C0027 R0067 1 + C0028 R0002 1 + C0028 R0028 561 + C0028 R0068 1 + C0029 R0002 1 + C0029 R0029 561 + C0029 R0069 1 + C0030 R0002 1 + C0030 R0030 561 + C0030 R0070 1 + C0031 R0002 1 + C0031 R0031 561 + C0031 R0071 1 + C0032 R0002 1 + C0032 R0032 561 + C0032 R0072 1 + C0033 R0002 1 + C0033 R0033 561 + C0033 R0073 1 + C0034 R0002 1 + C0034 R0034 561 + C0034 R0074 1 + C0035 R0002 1 + C0035 R0035 561 + C0035 R0075 1 + C0036 R0002 1 + C0036 R0036 561 + C0036 R0076 1 + C0037 R0002 1 + C0037 R0037 561 + C0037 R0077 1 + C0038 R0002 1 + C0038 R0038 561 + C0038 R0078 1 + C0039 R0002 1 + C0039 R0039 561 + C0039 R0079 1 + C0040 R0002 1 + C0040 R0040 561 + C0040 R0080 1 + C0041 R0003 1 + C0041 R0021 459 + C0041 R0081 1 + C0042 R0003 1 + C0042 R0022 459 + C0042 R0082 1 + C0043 R0003 1 + C0043 R0023 459 + C0043 R0083 1 + C0044 R0003 1 + C0044 R0024 459 + C0044 R0084 1 + C0045 R0003 1 + C0045 R0025 459 + C0045 R0085 1 + C0046 R0003 1 + C0046 R0026 459 + C0046 R0086 1 + C0047 R0003 1 + C0047 R0027 459 + C0047 R0087 1 + C0048 R0003 1 + C0048 R0028 459 + C0048 R0088 1 + C0049 R0003 1 + C0049 R0029 459 + C0049 R0089 1 + C0050 R0003 1 + C0050 R0030 459 + C0050 R0090 1 + C0051 R0003 1 + C0051 R0031 459 + C0051 R0091 1 + C0052 R0003 1 + C0052 R0032 459 + C0052 R0092 1 + C0053 R0003 1 + C0053 R0033 459 + C0053 R0093 1 + C0054 R0003 1 + C0054 R0034 459 + C0054 R0094 1 + C0055 R0003 1 + C0055 R0035 459 + C0055 R0095 1 + C0056 R0003 1 + C0056 R0036 459 + C0056 R0096 1 + C0057 R0003 1 + C0057 R0037 459 + C0057 R0097 1 + C0058 R0003 1 + C0058 R0038 459 + C0058 R0098 1 + C0059 R0003 1 + C0059 R0039 459 + C0059 R0099 1 + C0060 R0003 1 + C0060 R0040 459 + C0060 R0100 1 + C0061 R0004 1 + C0061 R0021 779 + C0061 R0101 1 + C0062 R0004 1 + C0062 R0022 779 + C0062 R0102 1 + C0063 R0004 1 + C0063 R0023 779 + C0063 R0103 1 + C0064 R0004 1 + C0064 R0024 779 + C0064 R0104 1 + C0065 R0004 1 + C0065 R0025 779 + C0065 R0105 1 + C0066 R0004 1 + C0066 R0026 779 + C0066 R0106 1 + C0067 R0004 1 + C0067 R0027 779 + C0067 R0107 1 + C0068 R0004 1 + C0068 R0028 779 + C0068 R0108 1 + C0069 R0004 1 + C0069 R0029 779 + C0069 R0109 1 + C0070 R0004 1 + C0070 R0030 779 + C0070 R0110 1 + C0071 R0004 1 + C0071 R0031 779 + C0071 R0111 1 + C0072 R0004 1 + C0072 R0032 779 + C0072 R0112 1 + C0073 R0004 1 + C0073 R0033 779 + C0073 R0113 1 + C0074 R0004 1 + C0074 R0034 779 + C0074 R0114 1 + C0075 R0004 1 + C0075 R0035 779 + C0075 R0115 1 + C0076 R0004 1 + C0076 R0036 779 + C0076 R0116 1 + C0077 R0004 1 + C0077 R0037 779 + C0077 R0117 1 + C0078 R0004 1 + C0078 R0038 779 + C0078 R0118 1 + C0079 R0004 1 + C0079 R0039 779 + C0079 R0119 1 + C0080 R0004 1 + C0080 R0040 779 + C0080 R0120 1 + C0081 R0005 1 + C0081 R0021 346 + C0081 R0121 1 + C0082 R0005 1 + C0082 R0022 346 + C0082 R0122 1 + C0083 R0005 1 + C0083 R0023 346 + C0083 R0123 1 + C0084 R0005 1 + C0084 R0024 346 + C0084 R0124 1 + C0085 R0005 1 + C0085 R0025 346 + C0085 R0125 1 + C0086 R0005 1 + C0086 R0026 346 + C0086 R0126 1 + C0087 R0005 1 + C0087 R0027 346 + C0087 R0127 1 + C0088 R0005 1 + C0088 R0028 346 + C0088 R0128 1 + C0089 R0005 1 + C0089 R0029 346 + C0089 R0129 1 + C0090 R0005 1 + C0090 R0030 346 + C0090 R0130 1 + C0091 R0005 1 + C0091 R0031 346 + C0091 R0131 1 + C0092 R0005 1 + C0092 R0032 346 + C0092 R0132 1 + C0093 R0005 1 + C0093 R0033 346 + C0093 R0133 1 + C0094 R0005 1 + C0094 R0034 346 + C0094 R0134 1 + C0095 R0005 1 + C0095 R0035 346 + C0095 R0135 1 + C0096 R0005 1 + C0096 R0036 346 + C0096 R0136 1 + C0097 R0005 1 + C0097 R0037 346 + C0097 R0137 1 + C0098 R0005 1 + C0098 R0038 346 + C0098 R0138 1 + C0099 R0005 1 + C0099 R0039 346 + C0099 R0139 1 + C0100 R0005 1 + C0100 R0040 346 + C0100 R0140 1 + C0101 R0006 1 + C0101 R0021 103 + C0101 R0141 1 + C0102 R0006 1 + C0102 R0022 103 + C0102 R0142 1 + C0103 R0006 1 + C0103 R0023 103 + C0103 R0143 1 + C0104 R0006 1 + C0104 R0024 103 + C0104 R0144 1 + C0105 R0006 1 + C0105 R0025 103 + C0105 R0145 1 + C0106 R0006 1 + C0106 R0026 103 + C0106 R0146 1 + C0107 R0006 1 + C0107 R0027 103 + C0107 R0147 1 + C0108 R0006 1 + C0108 R0028 103 + C0108 R0148 1 + C0109 R0006 1 + C0109 R0029 103 + C0109 R0149 1 + C0110 R0006 1 + C0110 R0030 103 + C0110 R0150 1 + C0111 R0006 1 + C0111 R0031 103 + C0111 R0151 1 + C0112 R0006 1 + C0112 R0032 103 + C0112 R0152 1 + C0113 R0006 1 + C0113 R0033 103 + C0113 R0153 1 + C0114 R0006 1 + C0114 R0034 103 + C0114 R0154 1 + C0115 R0006 1 + C0115 R0035 103 + C0115 R0155 1 + C0116 R0006 1 + C0116 R0036 103 + C0116 R0156 1 + C0117 R0006 1 + C0117 R0037 103 + C0117 R0157 1 + C0118 R0006 1 + C0118 R0038 103 + C0118 R0158 1 + C0119 R0006 1 + C0119 R0039 103 + C0119 R0159 1 + C0120 R0006 1 + C0120 R0040 103 + C0120 R0160 1 + C0121 R0007 1 + C0121 R0021 149 + C0121 R0161 1 + C0122 R0007 1 + C0122 R0022 149 + C0122 R0162 1 + C0123 R0007 1 + C0123 R0023 149 + C0123 R0163 1 + C0124 R0007 1 + C0124 R0024 149 + C0124 R0164 1 + C0125 R0007 1 + C0125 R0025 149 + C0125 R0165 1 + C0126 R0007 1 + C0126 R0026 149 + C0126 R0166 1 + C0127 R0007 1 + C0127 R0027 149 + C0127 R0167 1 + C0128 R0007 1 + C0128 R0028 149 + C0128 R0168 1 + C0129 R0007 1 + C0129 R0029 149 + C0129 R0169 1 + C0130 R0007 1 + C0130 R0030 149 + C0130 R0170 1 + C0131 R0007 1 + C0131 R0031 149 + C0131 R0171 1 + C0132 R0007 1 + C0132 R0032 149 + C0132 R0172 1 + C0133 R0007 1 + C0133 R0033 149 + C0133 R0173 1 + C0134 R0007 1 + C0134 R0034 149 + C0134 R0174 1 + C0135 R0007 1 + C0135 R0035 149 + C0135 R0175 1 + C0136 R0007 1 + C0136 R0036 149 + C0136 R0176 1 + C0137 R0007 1 + C0137 R0037 149 + C0137 R0177 1 + C0138 R0007 1 + C0138 R0038 149 + C0138 R0178 1 + C0139 R0007 1 + C0139 R0039 149 + C0139 R0179 1 + C0140 R0007 1 + C0140 R0040 149 + C0140 R0180 1 + C0141 R0008 1 + C0141 R0021 671 + C0141 R0181 1 + C0142 R0008 1 + C0142 R0022 671 + C0142 R0182 1 + C0143 R0008 1 + C0143 R0023 671 + C0143 R0183 1 + C0144 R0008 1 + C0144 R0024 671 + C0144 R0184 1 + C0145 R0008 1 + C0145 R0025 671 + C0145 R0185 1 + C0146 R0008 1 + C0146 R0026 671 + C0146 R0186 1 + C0147 R0008 1 + C0147 R0027 671 + C0147 R0187 1 + C0148 R0008 1 + C0148 R0028 671 + C0148 R0188 1 + C0149 R0008 1 + C0149 R0029 671 + C0149 R0189 1 + C0150 R0008 1 + C0150 R0030 671 + C0150 R0190 1 + C0151 R0008 1 + C0151 R0031 671 + C0151 R0191 1 + C0152 R0008 1 + C0152 R0032 671 + C0152 R0192 1 + C0153 R0008 1 + C0153 R0033 671 + C0153 R0193 1 + C0154 R0008 1 + C0154 R0034 671 + C0154 R0194 1 + C0155 R0008 1 + C0155 R0035 671 + C0155 R0195 1 + C0156 R0008 1 + C0156 R0036 671 + C0156 R0196 1 + C0157 R0008 1 + C0157 R0037 671 + C0157 R0197 1 + C0158 R0008 1 + C0158 R0038 671 + C0158 R0198 1 + C0159 R0008 1 + C0159 R0039 671 + C0159 R0199 1 + C0160 R0008 1 + C0160 R0040 671 + C0160 R0200 1 + C0161 R0009 1 + C0161 R0021 687 + C0161 R0201 1 + C0162 R0009 1 + C0162 R0022 687 + C0162 R0202 1 + C0163 R0009 1 + C0163 R0023 687 + C0163 R0203 1 + C0164 R0009 1 + C0164 R0024 687 + C0164 R0204 1 + C0165 R0009 1 + C0165 R0025 687 + C0165 R0205 1 + C0166 R0009 1 + C0166 R0026 687 + C0166 R0206 1 + C0167 R0009 1 + C0167 R0027 687 + C0167 R0207 1 + C0168 R0009 1 + C0168 R0028 687 + C0168 R0208 1 + C0169 R0009 1 + C0169 R0029 687 + C0169 R0209 1 + C0170 R0009 1 + C0170 R0030 687 + C0170 R0210 1 + C0171 R0009 1 + C0171 R0031 687 + C0171 R0211 1 + C0172 R0009 1 + C0172 R0032 687 + C0172 R0212 1 + C0173 R0009 1 + C0173 R0033 687 + C0173 R0213 1 + C0174 R0009 1 + C0174 R0034 687 + C0174 R0214 1 + C0175 R0009 1 + C0175 R0035 687 + C0175 R0215 1 + C0176 R0009 1 + C0176 R0036 687 + C0176 R0216 1 + C0177 R0009 1 + C0177 R0037 687 + C0177 R0217 1 + C0178 R0009 1 + C0178 R0038 687 + C0178 R0218 1 + C0179 R0009 1 + C0179 R0039 687 + C0179 R0219 1 + C0180 R0009 1 + C0180 R0040 687 + C0180 R0220 1 + C0181 R0010 1 + C0181 R0021 667 + C0181 R0221 1 + C0182 R0010 1 + C0182 R0022 667 + C0182 R0222 1 + C0183 R0010 1 + C0183 R0023 667 + C0183 R0223 1 + C0184 R0010 1 + C0184 R0024 667 + C0184 R0224 1 + C0185 R0010 1 + C0185 R0025 667 + C0185 R0225 1 + C0186 R0010 1 + C0186 R0026 667 + C0186 R0226 1 + C0187 R0010 1 + C0187 R0027 667 + C0187 R0227 1 + C0188 R0010 1 + C0188 R0028 667 + C0188 R0228 1 + C0189 R0010 1 + C0189 R0029 667 + C0189 R0229 1 + C0190 R0010 1 + C0190 R0030 667 + C0190 R0230 1 + C0191 R0010 1 + C0191 R0031 667 + C0191 R0231 1 + C0192 R0010 1 + C0192 R0032 667 + C0192 R0232 1 + C0193 R0010 1 + C0193 R0033 667 + C0193 R0233 1 + C0194 R0010 1 + C0194 R0034 667 + C0194 R0234 1 + C0195 R0010 1 + C0195 R0035 667 + C0195 R0235 1 + C0196 R0010 1 + C0196 R0036 667 + C0196 R0236 1 + C0197 R0010 1 + C0197 R0037 667 + C0197 R0237 1 + C0198 R0010 1 + C0198 R0038 667 + C0198 R0238 1 + C0199 R0010 1 + C0199 R0039 667 + C0199 R0239 1 + C0200 R0010 1 + C0200 R0040 667 + C0200 R0240 1 + C0201 R0011 1 + C0201 R0021 473 + C0201 R0241 1 + C0202 R0011 1 + C0202 R0022 473 + C0202 R0242 1 + C0203 R0011 1 + C0203 R0023 473 + C0203 R0243 1 + C0204 R0011 1 + C0204 R0024 473 + C0204 R0244 1 + C0205 R0011 1 + C0205 R0025 473 + C0205 R0245 1 + C0206 R0011 1 + C0206 R0026 473 + C0206 R0246 1 + C0207 R0011 1 + C0207 R0027 473 + C0207 R0247 1 + C0208 R0011 1 + C0208 R0028 473 + C0208 R0248 1 + C0209 R0011 1 + C0209 R0029 473 + C0209 R0249 1 + C0210 R0011 1 + C0210 R0030 473 + C0210 R0250 1 + C0211 R0011 1 + C0211 R0031 473 + C0211 R0251 1 + C0212 R0011 1 + C0212 R0032 473 + C0212 R0252 1 + C0213 R0011 1 + C0213 R0033 473 + C0213 R0253 1 + C0214 R0011 1 + C0214 R0034 473 + C0214 R0254 1 + C0215 R0011 1 + C0215 R0035 473 + C0215 R0255 1 + C0216 R0011 1 + C0216 R0036 473 + C0216 R0256 1 + C0217 R0011 1 + C0217 R0037 473 + C0217 R0257 1 + C0218 R0011 1 + C0218 R0038 473 + C0218 R0258 1 + C0219 R0011 1 + C0219 R0039 473 + C0219 R0259 1 + C0220 R0011 1 + C0220 R0040 473 + C0220 R0260 1 + C0221 R0012 1 + C0221 R0021 662 + C0221 R0261 1 + C0222 R0012 1 + C0222 R0022 662 + C0222 R0262 1 + C0223 R0012 1 + C0223 R0023 662 + C0223 R0263 1 + C0224 R0012 1 + C0224 R0024 662 + C0224 R0264 1 + C0225 R0012 1 + C0225 R0025 662 + C0225 R0265 1 + C0226 R0012 1 + C0226 R0026 662 + C0226 R0266 1 + C0227 R0012 1 + C0227 R0027 662 + C0227 R0267 1 + C0228 R0012 1 + C0228 R0028 662 + C0228 R0268 1 + C0229 R0012 1 + C0229 R0029 662 + C0229 R0269 1 + C0230 R0012 1 + C0230 R0030 662 + C0230 R0270 1 + C0231 R0012 1 + C0231 R0031 662 + C0231 R0271 1 + C0232 R0012 1 + C0232 R0032 662 + C0232 R0272 1 + C0233 R0012 1 + C0233 R0033 662 + C0233 R0273 1 + C0234 R0012 1 + C0234 R0034 662 + C0234 R0274 1 + C0235 R0012 1 + C0235 R0035 662 + C0235 R0275 1 + C0236 R0012 1 + C0236 R0036 662 + C0236 R0276 1 + C0237 R0012 1 + C0237 R0037 662 + C0237 R0277 1 + C0238 R0012 1 + C0238 R0038 662 + C0238 R0278 1 + C0239 R0012 1 + C0239 R0039 662 + C0239 R0279 1 + C0240 R0012 1 + C0240 R0040 662 + C0240 R0280 1 + C0241 R0013 1 + C0241 R0021 164 + C0241 R0281 1 + C0242 R0013 1 + C0242 R0022 164 + C0242 R0282 1 + C0243 R0013 1 + C0243 R0023 164 + C0243 R0283 1 + C0244 R0013 1 + C0244 R0024 164 + C0244 R0284 1 + C0245 R0013 1 + C0245 R0025 164 + C0245 R0285 1 + C0246 R0013 1 + C0246 R0026 164 + C0246 R0286 1 + C0247 R0013 1 + C0247 R0027 164 + C0247 R0287 1 + C0248 R0013 1 + C0248 R0028 164 + C0248 R0288 1 + C0249 R0013 1 + C0249 R0029 164 + C0249 R0289 1 + C0250 R0013 1 + C0250 R0030 164 + C0250 R0290 1 + C0251 R0013 1 + C0251 R0031 164 + C0251 R0291 1 + C0252 R0013 1 + C0252 R0032 164 + C0252 R0292 1 + C0253 R0013 1 + C0253 R0033 164 + C0253 R0293 1 + C0254 R0013 1 + C0254 R0034 164 + C0254 R0294 1 + C0255 R0013 1 + C0255 R0035 164 + C0255 R0295 1 + C0256 R0013 1 + C0256 R0036 164 + C0256 R0296 1 + C0257 R0013 1 + C0257 R0037 164 + C0257 R0297 1 + C0258 R0013 1 + C0258 R0038 164 + C0258 R0298 1 + C0259 R0013 1 + C0259 R0039 164 + C0259 R0299 1 + C0260 R0013 1 + C0260 R0040 164 + C0260 R0300 1 + C0261 R0014 1 + C0261 R0021 505 + C0261 R0301 1 + C0262 R0014 1 + C0262 R0022 505 + C0262 R0302 1 + C0263 R0014 1 + C0263 R0023 505 + C0263 R0303 1 + C0264 R0014 1 + C0264 R0024 505 + C0264 R0304 1 + C0265 R0014 1 + C0265 R0025 505 + C0265 R0305 1 + C0266 R0014 1 + C0266 R0026 505 + C0266 R0306 1 + C0267 R0014 1 + C0267 R0027 505 + C0267 R0307 1 + C0268 R0014 1 + C0268 R0028 505 + C0268 R0308 1 + C0269 R0014 1 + C0269 R0029 505 + C0269 R0309 1 + C0270 R0014 1 + C0270 R0030 505 + C0270 R0310 1 + C0271 R0014 1 + C0271 R0031 505 + C0271 R0311 1 + C0272 R0014 1 + C0272 R0032 505 + C0272 R0312 1 + C0273 R0014 1 + C0273 R0033 505 + C0273 R0313 1 + C0274 R0014 1 + C0274 R0034 505 + C0274 R0314 1 + C0275 R0014 1 + C0275 R0035 505 + C0275 R0315 1 + C0276 R0014 1 + C0276 R0036 505 + C0276 R0316 1 + C0277 R0014 1 + C0277 R0037 505 + C0277 R0317 1 + C0278 R0014 1 + C0278 R0038 505 + C0278 R0318 1 + C0279 R0014 1 + C0279 R0039 505 + C0279 R0319 1 + C0280 R0014 1 + C0280 R0040 505 + C0280 R0320 1 + C0281 R0015 1 + C0281 R0021 811 + C0281 R0321 1 + C0282 R0015 1 + C0282 R0022 811 + C0282 R0322 1 + C0283 R0015 1 + C0283 R0023 811 + C0283 R0323 1 + C0284 R0015 1 + C0284 R0024 811 + C0284 R0324 1 + C0285 R0015 1 + C0285 R0025 811 + C0285 R0325 1 + C0286 R0015 1 + C0286 R0026 811 + C0286 R0326 1 + C0287 R0015 1 + C0287 R0027 811 + C0287 R0327 1 + C0288 R0015 1 + C0288 R0028 811 + C0288 R0328 1 + C0289 R0015 1 + C0289 R0029 811 + C0289 R0329 1 + C0290 R0015 1 + C0290 R0030 811 + C0290 R0330 1 + C0291 R0015 1 + C0291 R0031 811 + C0291 R0331 1 + C0292 R0015 1 + C0292 R0032 811 + C0292 R0332 1 + C0293 R0015 1 + C0293 R0033 811 + C0293 R0333 1 + C0294 R0015 1 + C0294 R0034 811 + C0294 R0334 1 + C0295 R0015 1 + C0295 R0035 811 + C0295 R0335 1 + C0296 R0015 1 + C0296 R0036 811 + C0296 R0336 1 + C0297 R0015 1 + C0297 R0037 811 + C0297 R0337 1 + C0298 R0015 1 + C0298 R0038 811 + C0298 R0338 1 + C0299 R0015 1 + C0299 R0039 811 + C0299 R0339 1 + C0300 R0015 1 + C0300 R0040 811 + C0300 R0340 1 + C0301 R0016 1 + C0301 R0021 370 + C0301 R0341 1 + C0302 R0016 1 + C0302 R0022 370 + C0302 R0342 1 + C0303 R0016 1 + C0303 R0023 370 + C0303 R0343 1 + C0304 R0016 1 + C0304 R0024 370 + C0304 R0344 1 + C0305 R0016 1 + C0305 R0025 370 + C0305 R0345 1 + C0306 R0016 1 + C0306 R0026 370 + C0306 R0346 1 + C0307 R0016 1 + C0307 R0027 370 + C0307 R0347 1 + C0308 R0016 1 + C0308 R0028 370 + C0308 R0348 1 + C0309 R0016 1 + C0309 R0029 370 + C0309 R0349 1 + C0310 R0016 1 + C0310 R0030 370 + C0310 R0350 1 + C0311 R0016 1 + C0311 R0031 370 + C0311 R0351 1 + C0312 R0016 1 + C0312 R0032 370 + C0312 R0352 1 + C0313 R0016 1 + C0313 R0033 370 + C0313 R0353 1 + C0314 R0016 1 + C0314 R0034 370 + C0314 R0354 1 + C0315 R0016 1 + C0315 R0035 370 + C0315 R0355 1 + C0316 R0016 1 + C0316 R0036 370 + C0316 R0356 1 + C0317 R0016 1 + C0317 R0037 370 + C0317 R0357 1 + C0318 R0016 1 + C0318 R0038 370 + C0318 R0358 1 + C0319 R0016 1 + C0319 R0039 370 + C0319 R0359 1 + C0320 R0016 1 + C0320 R0040 370 + C0320 R0360 1 + C0321 R0017 1 + C0321 R0021 386 + C0321 R0361 1 + C0322 R0017 1 + C0322 R0022 386 + C0322 R0362 1 + C0323 R0017 1 + C0323 R0023 386 + C0323 R0363 1 + C0324 R0017 1 + C0324 R0024 386 + C0324 R0364 1 + C0325 R0017 1 + C0325 R0025 386 + C0325 R0365 1 + C0326 R0017 1 + C0326 R0026 386 + C0326 R0366 1 + C0327 R0017 1 + C0327 R0027 386 + C0327 R0367 1 + C0328 R0017 1 + C0328 R0028 386 + C0328 R0368 1 + C0329 R0017 1 + C0329 R0029 386 + C0329 R0369 1 + C0330 R0017 1 + C0330 R0030 386 + C0330 R0370 1 + C0331 R0017 1 + C0331 R0031 386 + C0331 R0371 1 + C0332 R0017 1 + C0332 R0032 386 + C0332 R0372 1 + C0333 R0017 1 + C0333 R0033 386 + C0333 R0373 1 + C0334 R0017 1 + C0334 R0034 386 + C0334 R0374 1 + C0335 R0017 1 + C0335 R0035 386 + C0335 R0375 1 + C0336 R0017 1 + C0336 R0036 386 + C0336 R0376 1 + C0337 R0017 1 + C0337 R0037 386 + C0337 R0377 1 + C0338 R0017 1 + C0338 R0038 386 + C0338 R0378 1 + C0339 R0017 1 + C0339 R0039 386 + C0339 R0379 1 + C0340 R0017 1 + C0340 R0040 386 + C0340 R0380 1 + C0341 R0018 1 + C0341 R0021 443 + C0341 R0381 1 + C0342 R0018 1 + C0342 R0022 443 + C0342 R0382 1 + C0343 R0018 1 + C0343 R0023 443 + C0343 R0383 1 + C0344 R0018 1 + C0344 R0024 443 + C0344 R0384 1 + C0345 R0018 1 + C0345 R0025 443 + C0345 R0385 1 + C0346 R0018 1 + C0346 R0026 443 + C0346 R0386 1 + C0347 R0018 1 + C0347 R0027 443 + C0347 R0387 1 + C0348 R0018 1 + C0348 R0028 443 + C0348 R0388 1 + C0349 R0018 1 + C0349 R0029 443 + C0349 R0389 1 + C0350 R0018 1 + C0350 R0030 443 + C0350 R0390 1 + C0351 R0018 1 + C0351 R0031 443 + C0351 R0391 1 + C0352 R0018 1 + C0352 R0032 443 + C0352 R0392 1 + C0353 R0018 1 + C0353 R0033 443 + C0353 R0393 1 + C0354 R0018 1 + C0354 R0034 443 + C0354 R0394 1 + C0355 R0018 1 + C0355 R0035 443 + C0355 R0395 1 + C0356 R0018 1 + C0356 R0036 443 + C0356 R0396 1 + C0357 R0018 1 + C0357 R0037 443 + C0357 R0397 1 + C0358 R0018 1 + C0358 R0038 443 + C0358 R0398 1 + C0359 R0018 1 + C0359 R0039 443 + C0359 R0399 1 + C0360 R0018 1 + C0360 R0040 443 + C0360 R0400 1 + C0361 R0019 1 + C0361 R0021 572 + C0361 R0401 1 + C0362 R0019 1 + C0362 R0022 572 + C0362 R0402 1 + C0363 R0019 1 + C0363 R0023 572 + C0363 R0403 1 + C0364 R0019 1 + C0364 R0024 572 + C0364 R0404 1 + C0365 R0019 1 + C0365 R0025 572 + C0365 R0405 1 + C0366 R0019 1 + C0366 R0026 572 + C0366 R0406 1 + C0367 R0019 1 + C0367 R0027 572 + C0367 R0407 1 + C0368 R0019 1 + C0368 R0028 572 + C0368 R0408 1 + C0369 R0019 1 + C0369 R0029 572 + C0369 R0409 1 + C0370 R0019 1 + C0370 R0030 572 + C0370 R0410 1 + C0371 R0019 1 + C0371 R0031 572 + C0371 R0411 1 + C0372 R0019 1 + C0372 R0032 572 + C0372 R0412 1 + C0373 R0019 1 + C0373 R0033 572 + C0373 R0413 1 + C0374 R0019 1 + C0374 R0034 572 + C0374 R0414 1 + C0375 R0019 1 + C0375 R0035 572 + C0375 R0415 1 + C0376 R0019 1 + C0376 R0036 572 + C0376 R0416 1 + C0377 R0019 1 + C0377 R0037 572 + C0377 R0417 1 + C0378 R0019 1 + C0378 R0038 572 + C0378 R0418 1 + C0379 R0019 1 + C0379 R0039 572 + C0379 R0419 1 + C0380 R0019 1 + C0380 R0040 572 + C0380 R0420 1 + C0381 R0020 1 + C0381 R0021 640 + C0381 R0421 1 + C0382 R0020 1 + C0382 R0022 640 + C0382 R0422 1 + C0383 R0020 1 + C0383 R0023 640 + C0383 R0423 1 + C0384 R0020 1 + C0384 R0024 640 + C0384 R0424 1 + C0385 R0020 1 + C0385 R0025 640 + C0385 R0425 1 + C0386 R0020 1 + C0386 R0026 640 + C0386 R0426 1 + C0387 R0020 1 + C0387 R0027 640 + C0387 R0427 1 + C0388 R0020 1 + C0388 R0028 640 + C0388 R0428 1 + C0389 R0020 1 + C0389 R0029 640 + C0389 R0429 1 + C0390 R0020 1 + C0390 R0030 640 + C0390 R0430 1 + C0391 R0020 1 + C0391 R0031 640 + C0391 R0431 1 + C0392 R0020 1 + C0392 R0032 640 + C0392 R0432 1 + C0393 R0020 1 + C0393 R0033 640 + C0393 R0433 1 + C0394 R0020 1 + C0394 R0034 640 + C0394 R0434 1 + C0395 R0020 1 + C0395 R0035 640 + C0395 R0435 1 + C0396 R0020 1 + C0396 R0036 640 + C0396 R0436 1 + C0397 R0020 1 + C0397 R0037 640 + C0397 R0437 1 + C0398 R0020 1 + C0398 R0038 640 + C0398 R0438 1 + C0399 R0020 1 + C0399 R0039 640 + C0399 R0439 1 + C0400 R0020 1 + C0400 R0040 640 + C0400 R0440 1 + INT1 'MARKER' 'INTORG' + C0401 R0041 -1 + C0401 R0441 1 + C0402 R0042 -1 + C0402 R0441 1 + C0403 R0043 -1 + C0403 R0441 1 + C0404 R0044 -1 + C0404 R0441 1 + C0405 R0045 -1 + C0405 R0441 1 + C0406 R0046 -1 + C0406 R0441 1 + C0407 R0047 -1 + C0407 R0441 1 + C0408 R0048 -1 + C0408 R0441 1 + C0409 R0049 -1 + C0409 R0441 1 + C0410 R0050 -1 + C0410 R0441 1 + C0411 R0051 -1 + C0411 R0441 1 + C0412 R0052 -1 + C0412 R0441 1 + C0413 R0053 -1 + C0413 R0441 1 + C0414 R0054 -1 + C0414 R0441 1 + C0415 R0055 -1 + C0415 R0441 1 + C0416 R0056 -1 + C0416 R0441 1 + C0417 R0057 -1 + C0417 R0441 1 + C0418 R0058 -1 + C0418 R0441 1 + C0419 R0059 -1 + C0419 R0441 1 + C0420 R0060 -1 + C0420 R0441 1 + C0421 R0061 -1 + C0421 R0441 1 + C0422 R0062 -1 + C0422 R0441 1 + C0423 R0063 -1 + C0423 R0441 1 + C0424 R0064 -1 + C0424 R0441 1 + C0425 R0065 -1 + C0425 R0441 1 + C0426 R0066 -1 + C0426 R0441 1 + C0427 R0067 -1 + C0427 R0441 1 + C0428 R0068 -1 + C0428 R0441 1 + C0429 R0069 -1 + C0429 R0441 1 + C0430 R0070 -1 + C0430 R0441 1 + C0431 R0071 -1 + C0431 R0441 1 + C0432 R0072 -1 + C0432 R0441 1 + C0433 R0073 -1 + C0433 R0441 1 + C0434 R0074 -1 + C0434 R0441 1 + C0435 R0075 -1 + C0435 R0441 1 + C0436 R0076 -1 + C0436 R0441 1 + C0437 R0077 -1 + C0437 R0441 1 + C0438 R0078 -1 + C0438 R0441 1 + C0439 R0079 -1 + C0439 R0441 1 + C0440 R0080 -1 + C0440 R0441 1 + C0441 R0081 -1 + C0441 R0441 1 + C0442 R0082 -1 + C0442 R0441 1 + C0443 R0083 -1 + C0443 R0441 1 + C0444 R0084 -1 + C0444 R0441 1 + C0445 R0085 -1 + C0445 R0441 1 + C0446 R0086 -1 + C0446 R0441 1 + C0447 R0087 -1 + C0447 R0441 1 + C0448 R0088 -1 + C0448 R0441 1 + C0449 R0089 -1 + C0449 R0441 1 + C0450 R0090 -1 + C0450 R0441 1 + C0451 R0091 -1 + C0451 R0441 1 + C0452 R0092 -1 + C0452 R0441 1 + C0453 R0093 -1 + C0453 R0441 1 + C0454 R0094 -1 + C0454 R0441 1 + C0455 R0095 -1 + C0455 R0441 1 + C0456 R0096 -1 + C0456 R0441 1 + C0457 R0097 -1 + C0457 R0441 1 + C0458 R0098 -1 + C0458 R0441 1 + C0459 R0099 -1 + C0459 R0441 1 + C0460 R0100 -1 + C0460 R0441 1 + C0461 R0101 -1 + C0461 R0441 1 + C0462 R0102 -1 + C0462 R0441 1 + C0463 R0103 -1 + C0463 R0441 1 + C0464 R0104 -1 + C0464 R0441 1 + C0465 R0105 -1 + C0465 R0441 1 + C0466 R0106 -1 + C0466 R0441 1 + C0467 R0107 -1 + C0467 R0441 1 + C0468 R0108 -1 + C0468 R0441 1 + C0469 R0109 -1 + C0469 R0441 1 + C0470 R0110 -1 + C0470 R0441 1 + C0471 R0111 -1 + C0471 R0441 1 + C0472 R0112 -1 + C0472 R0441 1 + C0473 R0113 -1 + C0473 R0441 1 + C0474 R0114 -1 + C0474 R0441 1 + C0475 R0115 -1 + C0475 R0441 1 + C0476 R0116 -1 + C0476 R0441 1 + C0477 R0117 -1 + C0477 R0441 1 + C0478 R0118 -1 + C0478 R0441 1 + C0479 R0119 -1 + C0479 R0441 1 + C0480 R0120 -1 + C0480 R0441 1 + C0481 R0121 -1 + C0481 R0441 1 + C0482 R0122 -1 + C0482 R0441 1 + C0483 R0123 -1 + C0483 R0441 1 + C0484 R0124 -1 + C0484 R0441 1 + C0485 R0125 -1 + C0485 R0441 1 + C0486 R0126 -1 + C0486 R0441 1 + C0487 R0127 -1 + C0487 R0441 1 + C0488 R0128 -1 + C0488 R0441 1 + C0489 R0129 -1 + C0489 R0441 1 + C0490 R0130 -1 + C0490 R0441 1 + C0491 R0131 -1 + C0491 R0441 1 + C0492 R0132 -1 + C0492 R0441 1 + C0493 R0133 -1 + C0493 R0441 1 + C0494 R0134 -1 + C0494 R0441 1 + C0495 R0135 -1 + C0495 R0441 1 + C0496 R0136 -1 + C0496 R0441 1 + C0497 R0137 -1 + C0497 R0441 1 + C0498 R0138 -1 + C0498 R0441 1 + C0499 R0139 -1 + C0499 R0441 1 + C0500 R0140 -1 + C0500 R0441 1 + C0501 R0141 -1 + C0501 R0441 1 + C0502 R0142 -1 + C0502 R0441 1 + C0503 R0143 -1 + C0503 R0441 1 + C0504 R0144 -1 + C0504 R0441 1 + C0505 R0145 -1 + C0505 R0441 1 + C0506 R0146 -1 + C0506 R0441 1 + C0507 R0147 -1 + C0507 R0441 1 + C0508 R0148 -1 + C0508 R0441 1 + C0509 R0149 -1 + C0509 R0441 1 + C0510 R0150 -1 + C0510 R0441 1 + C0511 R0151 -1 + C0511 R0441 1 + C0512 R0152 -1 + C0512 R0441 1 + C0513 R0153 -1 + C0513 R0441 1 + C0514 R0154 -1 + C0514 R0441 1 + C0515 R0155 -1 + C0515 R0441 1 + C0516 R0156 -1 + C0516 R0441 1 + C0517 R0157 -1 + C0517 R0441 1 + C0518 R0158 -1 + C0518 R0441 1 + C0519 R0159 -1 + C0519 R0441 1 + C0520 R0160 -1 + C0520 R0441 1 + C0521 R0161 -1 + C0521 R0441 1 + C0522 R0162 -1 + C0522 R0441 1 + C0523 R0163 -1 + C0523 R0441 1 + C0524 R0164 -1 + C0524 R0441 1 + C0525 R0165 -1 + C0525 R0441 1 + C0526 R0166 -1 + C0526 R0441 1 + C0527 R0167 -1 + C0527 R0441 1 + C0528 R0168 -1 + C0528 R0441 1 + C0529 R0169 -1 + C0529 R0441 1 + C0530 R0170 -1 + C0530 R0441 1 + C0531 R0171 -1 + C0531 R0441 1 + C0532 R0172 -1 + C0532 R0441 1 + C0533 R0173 -1 + C0533 R0441 1 + C0534 R0174 -1 + C0534 R0441 1 + C0535 R0175 -1 + C0535 R0441 1 + C0536 R0176 -1 + C0536 R0441 1 + C0537 R0177 -1 + C0537 R0441 1 + C0538 R0178 -1 + C0538 R0441 1 + C0539 R0179 -1 + C0539 R0441 1 + C0540 R0180 -1 + C0540 R0441 1 + C0541 R0181 -1 + C0541 R0441 1 + C0542 R0182 -1 + C0542 R0441 1 + C0543 R0183 -1 + C0543 R0441 1 + C0544 R0184 -1 + C0544 R0441 1 + C0545 R0185 -1 + C0545 R0441 1 + C0546 R0186 -1 + C0546 R0441 1 + C0547 R0187 -1 + C0547 R0441 1 + C0548 R0188 -1 + C0548 R0441 1 + C0549 R0189 -1 + C0549 R0441 1 + C0550 R0190 -1 + C0550 R0441 1 + C0551 R0191 -1 + C0551 R0441 1 + C0552 R0192 -1 + C0552 R0441 1 + C0553 R0193 -1 + C0553 R0441 1 + C0554 R0194 -1 + C0554 R0441 1 + C0555 R0195 -1 + C0555 R0441 1 + C0556 R0196 -1 + C0556 R0441 1 + C0557 R0197 -1 + C0557 R0441 1 + C0558 R0198 -1 + C0558 R0441 1 + C0559 R0199 -1 + C0559 R0441 1 + C0560 R0200 -1 + C0560 R0441 1 + C0561 R0201 -1 + C0561 R0441 1 + C0562 R0202 -1 + C0562 R0441 1 + C0563 R0203 -1 + C0563 R0441 1 + C0564 R0204 -1 + C0564 R0441 1 + C0565 R0205 -1 + C0565 R0441 1 + C0566 R0206 -1 + C0566 R0441 1 + C0567 R0207 -1 + C0567 R0441 1 + C0568 R0208 -1 + C0568 R0441 1 + C0569 R0209 -1 + C0569 R0441 1 + C0570 R0210 -1 + C0570 R0441 1 + C0571 R0211 -1 + C0571 R0441 1 + C0572 R0212 -1 + C0572 R0441 1 + C0573 R0213 -1 + C0573 R0441 1 + C0574 R0214 -1 + C0574 R0441 1 + C0575 R0215 -1 + C0575 R0441 1 + C0576 R0216 -1 + C0576 R0441 1 + C0577 R0217 -1 + C0577 R0441 1 + C0578 R0218 -1 + C0578 R0441 1 + C0579 R0219 -1 + C0579 R0441 1 + C0580 R0220 -1 + C0580 R0441 1 + C0581 R0221 -1 + C0581 R0441 1 + C0582 R0222 -1 + C0582 R0441 1 + C0583 R0223 -1 + C0583 R0441 1 + C0584 R0224 -1 + C0584 R0441 1 + C0585 R0225 -1 + C0585 R0441 1 + C0586 R0226 -1 + C0586 R0441 1 + C0587 R0227 -1 + C0587 R0441 1 + C0588 R0228 -1 + C0588 R0441 1 + C0589 R0229 -1 + C0589 R0441 1 + C0590 R0230 -1 + C0590 R0441 1 + C0591 R0231 -1 + C0591 R0441 1 + C0592 R0232 -1 + C0592 R0441 1 + C0593 R0233 -1 + C0593 R0441 1 + C0594 R0234 -1 + C0594 R0441 1 + C0595 R0235 -1 + C0595 R0441 1 + C0596 R0236 -1 + C0596 R0441 1 + C0597 R0237 -1 + C0597 R0441 1 + C0598 R0238 -1 + C0598 R0441 1 + C0599 R0239 -1 + C0599 R0441 1 + C0600 R0240 -1 + C0600 R0441 1 + C0601 R0241 -1 + C0601 R0441 1 + C0602 R0242 -1 + C0602 R0441 1 + C0603 R0243 -1 + C0603 R0441 1 + C0604 R0244 -1 + C0604 R0441 1 + C0605 R0245 -1 + C0605 R0441 1 + C0606 R0246 -1 + C0606 R0441 1 + C0607 R0247 -1 + C0607 R0441 1 + C0608 R0248 -1 + C0608 R0441 1 + C0609 R0249 -1 + C0609 R0441 1 + C0610 R0250 -1 + C0610 R0441 1 + C0611 R0251 -1 + C0611 R0441 1 + C0612 R0252 -1 + C0612 R0441 1 + C0613 R0253 -1 + C0613 R0441 1 + C0614 R0254 -1 + C0614 R0441 1 + C0615 R0255 -1 + C0615 R0441 1 + C0616 R0256 -1 + C0616 R0441 1 + C0617 R0257 -1 + C0617 R0441 1 + C0618 R0258 -1 + C0618 R0441 1 + C0619 R0259 -1 + C0619 R0441 1 + C0620 R0260 -1 + C0620 R0441 1 + C0621 R0261 -1 + C0621 R0441 1 + C0622 R0262 -1 + C0622 R0441 1 + C0623 R0263 -1 + C0623 R0441 1 + C0624 R0264 -1 + C0624 R0441 1 + C0625 R0265 -1 + C0625 R0441 1 + C0626 R0266 -1 + C0626 R0441 1 + C0627 R0267 -1 + C0627 R0441 1 + C0628 R0268 -1 + C0628 R0441 1 + C0629 R0269 -1 + C0629 R0441 1 + C0630 R0270 -1 + C0630 R0441 1 + C0631 R0271 -1 + C0631 R0441 1 + C0632 R0272 -1 + C0632 R0441 1 + C0633 R0273 -1 + C0633 R0441 1 + C0634 R0274 -1 + C0634 R0441 1 + C0635 R0275 -1 + C0635 R0441 1 + C0636 R0276 -1 + C0636 R0441 1 + C0637 R0277 -1 + C0637 R0441 1 + C0638 R0278 -1 + C0638 R0441 1 + C0639 R0279 -1 + C0639 R0441 1 + C0640 R0280 -1 + C0640 R0441 1 + C0641 R0281 -1 + C0641 R0441 1 + C0642 R0282 -1 + C0642 R0441 1 + C0643 R0283 -1 + C0643 R0441 1 + C0644 R0284 -1 + C0644 R0441 1 + C0645 R0285 -1 + C0645 R0441 1 + C0646 R0286 -1 + C0646 R0441 1 + C0647 R0287 -1 + C0647 R0441 1 + C0648 R0288 -1 + C0648 R0441 1 + C0649 R0289 -1 + C0649 R0441 1 + C0650 R0290 -1 + C0650 R0441 1 + C0651 R0291 -1 + C0651 R0441 1 + C0652 R0292 -1 + C0652 R0441 1 + C0653 R0293 -1 + C0653 R0441 1 + C0654 R0294 -1 + C0654 R0441 1 + C0655 R0295 -1 + C0655 R0441 1 + C0656 R0296 -1 + C0656 R0441 1 + C0657 R0297 -1 + C0657 R0441 1 + C0658 R0298 -1 + C0658 R0441 1 + C0659 R0299 -1 + C0659 R0441 1 + C0660 R0300 -1 + C0660 R0441 1 + C0661 R0301 -1 + C0661 R0441 1 + C0662 R0302 -1 + C0662 R0441 1 + C0663 R0303 -1 + C0663 R0441 1 + C0664 R0304 -1 + C0664 R0441 1 + C0665 R0305 -1 + C0665 R0441 1 + C0666 R0306 -1 + C0666 R0441 1 + C0667 R0307 -1 + C0667 R0441 1 + C0668 R0308 -1 + C0668 R0441 1 + C0669 R0309 -1 + C0669 R0441 1 + C0670 R0310 -1 + C0670 R0441 1 + C0671 R0311 -1 + C0671 R0441 1 + C0672 R0312 -1 + C0672 R0441 1 + C0673 R0313 -1 + C0673 R0441 1 + C0674 R0314 -1 + C0674 R0441 1 + C0675 R0315 -1 + C0675 R0441 1 + C0676 R0316 -1 + C0676 R0441 1 + C0677 R0317 -1 + C0677 R0441 1 + C0678 R0318 -1 + C0678 R0441 1 + C0679 R0319 -1 + C0679 R0441 1 + C0680 R0320 -1 + C0680 R0441 1 + C0681 R0321 -1 + C0681 R0441 1 + C0682 R0322 -1 + C0682 R0441 1 + C0683 R0323 -1 + C0683 R0441 1 + C0684 R0324 -1 + C0684 R0441 1 + C0685 R0325 -1 + C0685 R0441 1 + C0686 R0326 -1 + C0686 R0441 1 + C0687 R0327 -1 + C0687 R0441 1 + C0688 R0328 -1 + C0688 R0441 1 + C0689 R0329 -1 + C0689 R0441 1 + C0690 R0330 -1 + C0690 R0441 1 + C0691 R0331 -1 + C0691 R0441 1 + C0692 R0332 -1 + C0692 R0441 1 + C0693 R0333 -1 + C0693 R0441 1 + C0694 R0334 -1 + C0694 R0441 1 + C0695 R0335 -1 + C0695 R0441 1 + C0696 R0336 -1 + C0696 R0441 1 + C0697 R0337 -1 + C0697 R0441 1 + C0698 R0338 -1 + C0698 R0441 1 + C0699 R0339 -1 + C0699 R0441 1 + C0700 R0340 -1 + C0700 R0441 1 + C0701 R0341 -1 + C0701 R0441 1 + C0702 R0342 -1 + C0702 R0441 1 + C0703 R0343 -1 + C0703 R0441 1 + C0704 R0344 -1 + C0704 R0441 1 + C0705 R0345 -1 + C0705 R0441 1 + C0706 R0346 -1 + C0706 R0441 1 + C0707 R0347 -1 + C0707 R0441 1 + C0708 R0348 -1 + C0708 R0441 1 + C0709 R0349 -1 + C0709 R0441 1 + C0710 R0350 -1 + C0710 R0441 1 + C0711 R0351 -1 + C0711 R0441 1 + C0712 R0352 -1 + C0712 R0441 1 + C0713 R0353 -1 + C0713 R0441 1 + C0714 R0354 -1 + C0714 R0441 1 + C0715 R0355 -1 + C0715 R0441 1 + C0716 R0356 -1 + C0716 R0441 1 + C0717 R0357 -1 + C0717 R0441 1 + C0718 R0358 -1 + C0718 R0441 1 + C0719 R0359 -1 + C0719 R0441 1 + C0720 R0360 -1 + C0720 R0441 1 + C0721 R0361 -1 + C0721 R0441 1 + C0722 R0362 -1 + C0722 R0441 1 + C0723 R0363 -1 + C0723 R0441 1 + C0724 R0364 -1 + C0724 R0441 1 + C0725 R0365 -1 + C0725 R0441 1 + C0726 R0366 -1 + C0726 R0441 1 + C0727 R0367 -1 + C0727 R0441 1 + C0728 R0368 -1 + C0728 R0441 1 + C0729 R0369 -1 + C0729 R0441 1 + C0730 R0370 -1 + C0730 R0441 1 + C0731 R0371 -1 + C0731 R0441 1 + C0732 R0372 -1 + C0732 R0441 1 + C0733 R0373 -1 + C0733 R0441 1 + C0734 R0374 -1 + C0734 R0441 1 + C0735 R0375 -1 + C0735 R0441 1 + C0736 R0376 -1 + C0736 R0441 1 + C0737 R0377 -1 + C0737 R0441 1 + C0738 R0378 -1 + C0738 R0441 1 + C0739 R0379 -1 + C0739 R0441 1 + C0740 R0380 -1 + C0740 R0441 1 + C0741 R0381 -1 + C0741 R0441 1 + C0742 R0382 -1 + C0742 R0441 1 + C0743 R0383 -1 + C0743 R0441 1 + C0744 R0384 -1 + C0744 R0441 1 + C0745 R0385 -1 + C0745 R0441 1 + C0746 R0386 -1 + C0746 R0441 1 + C0747 R0387 -1 + C0747 R0441 1 + C0748 R0388 -1 + C0748 R0441 1 + C0749 R0389 -1 + C0749 R0441 1 + C0750 R0390 -1 + C0750 R0441 1 + C0751 R0391 -1 + C0751 R0441 1 + C0752 R0392 -1 + C0752 R0441 1 + C0753 R0393 -1 + C0753 R0441 1 + C0754 R0394 -1 + C0754 R0441 1 + C0755 R0395 -1 + C0755 R0441 1 + C0756 R0396 -1 + C0756 R0441 1 + C0757 R0397 -1 + C0757 R0441 1 + C0758 R0398 -1 + C0758 R0441 1 + C0759 R0399 -1 + C0759 R0441 1 + C0760 R0400 -1 + C0760 R0441 1 + C0761 R0401 -1 + C0761 R0441 1 + C0762 R0402 -1 + C0762 R0441 1 + C0763 R0403 -1 + C0763 R0441 1 + C0764 R0404 -1 + C0764 R0441 1 + C0765 R0405 -1 + C0765 R0441 1 + C0766 R0406 -1 + C0766 R0441 1 + C0767 R0407 -1 + C0767 R0441 1 + C0768 R0408 -1 + C0768 R0441 1 + C0769 R0409 -1 + C0769 R0441 1 + C0770 R0410 -1 + C0770 R0441 1 + C0771 R0411 -1 + C0771 R0441 1 + C0772 R0412 -1 + C0772 R0441 1 + C0773 R0413 -1 + C0773 R0441 1 + C0774 R0414 -1 + C0774 R0441 1 + C0775 R0415 -1 + C0775 R0441 1 + C0776 R0416 -1 + C0776 R0441 1 + C0777 R0417 -1 + C0777 R0441 1 + C0778 R0418 -1 + C0778 R0441 1 + C0779 R0419 -1 + C0779 R0441 1 + C0780 R0420 -1 + C0780 R0441 1 + C0781 R0421 -1 + C0781 R0441 1 + C0782 R0422 -1 + C0782 R0441 1 + C0783 R0423 -1 + C0783 R0441 1 + C0784 R0424 -1 + C0784 R0441 1 + C0785 R0425 -1 + C0785 R0441 1 + C0786 R0426 -1 + C0786 R0441 1 + C0787 R0427 -1 + C0787 R0441 1 + C0788 R0428 -1 + C0788 R0441 1 + C0789 R0429 -1 + C0789 R0441 1 + C0790 R0430 -1 + C0790 R0441 1 + C0791 R0431 -1 + C0791 R0441 1 + C0792 R0432 -1 + C0792 R0441 1 + C0793 R0433 -1 + C0793 R0441 1 + C0794 R0434 -1 + C0794 R0441 1 + C0795 R0435 -1 + C0795 R0441 1 + C0796 R0436 -1 + C0796 R0441 1 + C0797 R0437 -1 + C0797 R0441 1 + C0798 R0438 -1 + C0798 R0441 1 + C0799 R0439 -1 + C0799 R0441 1 + C0800 R0440 -1 + C0800 R0441 1 + C0801 R0021 -1000 + C0801 R0442 1 + C0802 R0022 -1000 + C0802 R0442 1 + C0803 R0023 -1000 + C0803 R0442 1 + C0804 R0024 -1000 + C0804 R0442 1 + C0805 R0025 -1000 + C0805 R0442 1 + C0806 R0026 -1000 + C0806 R0442 1 + C0807 R0027 -1000 + C0807 R0442 1 + C0808 R0028 -1000 + C0808 R0442 1 + C0809 R0029 -1000 + C0809 R0442 1 + C0810 R0030 -1000 + C0810 R0442 1 + C0811 R0031 -1000 + C0811 R0442 1 + C0812 R0032 -1000 + C0812 R0442 1 + C0813 R0033 -1000 + C0813 R0442 1 + C0814 R0034 -1000 + C0814 R0442 1 + C0815 R0035 -1000 + C0815 R0442 1 + C0816 R0036 -1000 + C0816 R0442 1 + C0817 R0037 -1000 + C0817 R0442 1 + C0818 R0038 -1000 + C0818 R0442 1 + C0819 R0039 -1000 + C0819 R0442 1 + C0820 R0040 -1000 + C0820 R0442 1 + INT1END 'MARKER' 'INTEND' +RHS + B R0001 1 + B R0002 1 + B R0003 1 + B R0004 1 + B R0005 1 + B R0006 1 + B R0007 1 + B R0008 1 + B R0009 1 + B R0010 1 + B R0011 1 + B R0012 1 + B R0013 1 + B R0014 1 + B R0015 1 + B R0016 1 + B R0017 1 + B R0018 1 + B R0019 1 + B R0020 1 + B R0441 20.5 +BOUNDS + UP BOUND C0001 1 + UP BOUND C0002 1 + UP BOUND C0003 1 + UP BOUND C0004 1 + UP BOUND C0005 1 + UP BOUND C0006 1 + UP BOUND C0007 1 + UP BOUND C0008 1 + UP BOUND C0009 1 + UP BOUND C0010 1 + UP BOUND C0011 1 + UP BOUND C0012 1 + UP BOUND C0013 1 + UP BOUND C0014 1 + UP BOUND C0015 1 + UP BOUND C0016 1 + UP BOUND C0017 1 + UP BOUND C0018 1 + UP BOUND C0019 1 + UP BOUND C0020 1 + UP BOUND C0021 1 + UP BOUND C0022 1 + UP BOUND C0023 1 + UP BOUND C0024 1 + UP BOUND C0025 1 + UP BOUND C0026 1 + UP BOUND C0027 1 + UP BOUND C0028 1 + UP BOUND C0029 1 + UP BOUND C0030 1 + UP BOUND C0031 1 + UP BOUND C0032 1 + UP BOUND C0033 1 + UP BOUND C0034 1 + UP BOUND C0035 1 + UP BOUND C0036 1 + UP BOUND C0037 1 + UP BOUND C0038 1 + UP BOUND C0039 1 + UP BOUND C0040 1 + UP BOUND C0041 1 + UP BOUND C0042 1 + UP BOUND C0043 1 + UP BOUND C0044 1 + UP BOUND C0045 1 + UP BOUND C0046 1 + UP BOUND C0047 1 + UP BOUND C0048 1 + UP BOUND C0049 1 + UP BOUND C0050 1 + UP BOUND C0051 1 + UP BOUND C0052 1 + UP BOUND C0053 1 + UP BOUND C0054 1 + UP BOUND C0055 1 + UP BOUND C0056 1 + UP BOUND C0057 1 + UP BOUND C0058 1 + UP BOUND C0059 1 + UP BOUND C0060 1 + UP BOUND C0061 1 + UP BOUND C0062 1 + UP BOUND C0063 1 + UP BOUND C0064 1 + UP BOUND C0065 1 + UP BOUND C0066 1 + UP BOUND C0067 1 + UP BOUND C0068 1 + UP BOUND C0069 1 + UP BOUND C0070 1 + UP BOUND C0071 1 + UP BOUND C0072 1 + UP BOUND C0073 1 + UP BOUND C0074 1 + UP BOUND C0075 1 + UP BOUND C0076 1 + UP BOUND C0077 1 + UP BOUND C0078 1 + UP BOUND C0079 1 + UP BOUND C0080 1 + UP BOUND C0081 1 + UP BOUND C0082 1 + UP BOUND C0083 1 + UP BOUND C0084 1 + UP BOUND C0085 1 + UP BOUND C0086 1 + UP BOUND C0087 1 + UP BOUND C0088 1 + UP BOUND C0089 1 + UP BOUND C0090 1 + UP BOUND C0091 1 + UP BOUND C0092 1 + UP BOUND C0093 1 + UP BOUND C0094 1 + UP BOUND C0095 1 + UP BOUND C0096 1 + UP BOUND C0097 1 + UP BOUND C0098 1 + UP BOUND C0099 1 + UP BOUND C0100 1 + UP BOUND C0101 1 + UP BOUND C0102 1 + UP BOUND C0103 1 + UP BOUND C0104 1 + UP BOUND C0105 1 + UP BOUND C0106 1 + UP BOUND C0107 1 + UP BOUND C0108 1 + UP BOUND C0109 1 + UP BOUND C0110 1 + UP BOUND C0111 1 + UP BOUND C0112 1 + UP BOUND C0113 1 + UP BOUND C0114 1 + UP BOUND C0115 1 + UP BOUND C0116 1 + UP BOUND C0117 1 + UP BOUND C0118 1 + UP BOUND C0119 1 + UP BOUND C0120 1 + UP BOUND C0121 1 + UP BOUND C0122 1 + UP BOUND C0123 1 + UP BOUND C0124 1 + UP BOUND C0125 1 + UP BOUND C0126 1 + UP BOUND C0127 1 + UP BOUND C0128 1 + UP BOUND C0129 1 + UP BOUND C0130 1 + UP BOUND C0131 1 + UP BOUND C0132 1 + UP BOUND C0133 1 + UP BOUND C0134 1 + UP BOUND C0135 1 + UP BOUND C0136 1 + UP BOUND C0137 1 + UP BOUND C0138 1 + UP BOUND C0139 1 + UP BOUND C0140 1 + UP BOUND C0141 1 + UP BOUND C0142 1 + UP BOUND C0143 1 + UP BOUND C0144 1 + UP BOUND C0145 1 + UP BOUND C0146 1 + UP BOUND C0147 1 + UP BOUND C0148 1 + UP BOUND C0149 1 + UP BOUND C0150 1 + UP BOUND C0151 1 + UP BOUND C0152 1 + UP BOUND C0153 1 + UP BOUND C0154 1 + UP BOUND C0155 1 + UP BOUND C0156 1 + UP BOUND C0157 1 + UP BOUND C0158 1 + UP BOUND C0159 1 + UP BOUND C0160 1 + UP BOUND C0161 1 + UP BOUND C0162 1 + UP BOUND C0163 1 + UP BOUND C0164 1 + UP BOUND C0165 1 + UP BOUND C0166 1 + UP BOUND C0167 1 + UP BOUND C0168 1 + UP BOUND C0169 1 + UP BOUND C0170 1 + UP BOUND C0171 1 + UP BOUND C0172 1 + UP BOUND C0173 1 + UP BOUND C0174 1 + UP BOUND C0175 1 + UP BOUND C0176 1 + UP BOUND C0177 1 + UP BOUND C0178 1 + UP BOUND C0179 1 + UP BOUND C0180 1 + UP BOUND C0181 1 + UP BOUND C0182 1 + UP BOUND C0183 1 + UP BOUND C0184 1 + UP BOUND C0185 1 + UP BOUND C0186 1 + UP BOUND C0187 1 + UP BOUND C0188 1 + UP BOUND C0189 1 + UP BOUND C0190 1 + UP BOUND C0191 1 + UP BOUND C0192 1 + UP BOUND C0193 1 + UP BOUND C0194 1 + UP BOUND C0195 1 + UP BOUND C0196 1 + UP BOUND C0197 1 + UP BOUND C0198 1 + UP BOUND C0199 1 + UP BOUND C0200 1 + UP BOUND C0201 1 + UP BOUND C0202 1 + UP BOUND C0203 1 + UP BOUND C0204 1 + UP BOUND C0205 1 + UP BOUND C0206 1 + UP BOUND C0207 1 + UP BOUND C0208 1 + UP BOUND C0209 1 + UP BOUND C0210 1 + UP BOUND C0211 1 + UP BOUND C0212 1 + UP BOUND C0213 1 + UP BOUND C0214 1 + UP BOUND C0215 1 + UP BOUND C0216 1 + UP BOUND C0217 1 + UP BOUND C0218 1 + UP BOUND C0219 1 + UP BOUND C0220 1 + UP BOUND C0221 1 + UP BOUND C0222 1 + UP BOUND C0223 1 + UP BOUND C0224 1 + UP BOUND C0225 1 + UP BOUND C0226 1 + UP BOUND C0227 1 + UP BOUND C0228 1 + UP BOUND C0229 1 + UP BOUND C0230 1 + UP BOUND C0231 1 + UP BOUND C0232 1 + UP BOUND C0233 1 + UP BOUND C0234 1 + UP BOUND C0235 1 + UP BOUND C0236 1 + UP BOUND C0237 1 + UP BOUND C0238 1 + UP BOUND C0239 1 + UP BOUND C0240 1 + UP BOUND C0241 1 + UP BOUND C0242 1 + UP BOUND C0243 1 + UP BOUND C0244 1 + UP BOUND C0245 1 + UP BOUND C0246 1 + UP BOUND C0247 1 + UP BOUND C0248 1 + UP BOUND C0249 1 + UP BOUND C0250 1 + UP BOUND C0251 1 + UP BOUND C0252 1 + UP BOUND C0253 1 + UP BOUND C0254 1 + UP BOUND C0255 1 + UP BOUND C0256 1 + UP BOUND C0257 1 + UP BOUND C0258 1 + UP BOUND C0259 1 + UP BOUND C0260 1 + UP BOUND C0261 1 + UP BOUND C0262 1 + UP BOUND C0263 1 + UP BOUND C0264 1 + UP BOUND C0265 1 + UP BOUND C0266 1 + UP BOUND C0267 1 + UP BOUND C0268 1 + UP BOUND C0269 1 + UP BOUND C0270 1 + UP BOUND C0271 1 + UP BOUND C0272 1 + UP BOUND C0273 1 + UP BOUND C0274 1 + UP BOUND C0275 1 + UP BOUND C0276 1 + UP BOUND C0277 1 + UP BOUND C0278 1 + UP BOUND C0279 1 + UP BOUND C0280 1 + UP BOUND C0281 1 + UP BOUND C0282 1 + UP BOUND C0283 1 + UP BOUND C0284 1 + UP BOUND C0285 1 + UP BOUND C0286 1 + UP BOUND C0287 1 + UP BOUND C0288 1 + UP BOUND C0289 1 + UP BOUND C0290 1 + UP BOUND C0291 1 + UP BOUND C0292 1 + UP BOUND C0293 1 + UP BOUND C0294 1 + UP BOUND C0295 1 + UP BOUND C0296 1 + UP BOUND C0297 1 + UP BOUND C0298 1 + UP BOUND C0299 1 + UP BOUND C0300 1 + UP BOUND C0301 1 + UP BOUND C0302 1 + UP BOUND C0303 1 + UP BOUND C0304 1 + UP BOUND C0305 1 + UP BOUND C0306 1 + UP BOUND C0307 1 + UP BOUND C0308 1 + UP BOUND C0309 1 + UP BOUND C0310 1 + UP BOUND C0311 1 + UP BOUND C0312 1 + UP BOUND C0313 1 + UP BOUND C0314 1 + UP BOUND C0315 1 + UP BOUND C0316 1 + UP BOUND C0317 1 + UP BOUND C0318 1 + UP BOUND C0319 1 + UP BOUND C0320 1 + UP BOUND C0321 1 + UP BOUND C0322 1 + UP BOUND C0323 1 + UP BOUND C0324 1 + UP BOUND C0325 1 + UP BOUND C0326 1 + UP BOUND C0327 1 + UP BOUND C0328 1 + UP BOUND C0329 1 + UP BOUND C0330 1 + UP BOUND C0331 1 + UP BOUND C0332 1 + UP BOUND C0333 1 + UP BOUND C0334 1 + UP BOUND C0335 1 + UP BOUND C0336 1 + UP BOUND C0337 1 + UP BOUND C0338 1 + UP BOUND C0339 1 + UP BOUND C0340 1 + UP BOUND C0341 1 + UP BOUND C0342 1 + UP BOUND C0343 1 + UP BOUND C0344 1 + UP BOUND C0345 1 + UP BOUND C0346 1 + UP BOUND C0347 1 + UP BOUND C0348 1 + UP BOUND C0349 1 + UP BOUND C0350 1 + UP BOUND C0351 1 + UP BOUND C0352 1 + UP BOUND C0353 1 + UP BOUND C0354 1 + UP BOUND C0355 1 + UP BOUND C0356 1 + UP BOUND C0357 1 + UP BOUND C0358 1 + UP BOUND C0359 1 + UP BOUND C0360 1 + UP BOUND C0361 1 + UP BOUND C0362 1 + UP BOUND C0363 1 + UP BOUND C0364 1 + UP BOUND C0365 1 + UP BOUND C0366 1 + UP BOUND C0367 1 + UP BOUND C0368 1 + UP BOUND C0369 1 + UP BOUND C0370 1 + UP BOUND C0371 1 + UP BOUND C0372 1 + UP BOUND C0373 1 + UP BOUND C0374 1 + UP BOUND C0375 1 + UP BOUND C0376 1 + UP BOUND C0377 1 + UP BOUND C0378 1 + UP BOUND C0379 1 + UP BOUND C0380 1 + UP BOUND C0381 1 + UP BOUND C0382 1 + UP BOUND C0383 1 + UP BOUND C0384 1 + UP BOUND C0385 1 + UP BOUND C0386 1 + UP BOUND C0387 1 + UP BOUND C0388 1 + UP BOUND C0389 1 + UP BOUND C0390 1 + UP BOUND C0391 1 + UP BOUND C0392 1 + UP BOUND C0393 1 + UP BOUND C0394 1 + UP BOUND C0395 1 + UP BOUND C0396 1 + UP BOUND C0397 1 + UP BOUND C0398 1 + UP BOUND C0399 1 + UP BOUND C0400 1 + UP BOUND C0401 1 + UP BOUND C0402 1 + UP BOUND C0403 1 + UP BOUND C0404 1 + UP BOUND C0405 1 + UP BOUND C0406 1 + UP BOUND C0407 1 + UP BOUND C0408 1 + UP BOUND C0409 1 + UP BOUND C0410 1 + UP BOUND C0411 1 + UP BOUND C0412 1 + UP BOUND C0413 1 + UP BOUND C0414 1 + UP BOUND C0415 1 + UP BOUND C0416 1 + UP BOUND C0417 1 + UP BOUND C0418 1 + UP BOUND C0419 1 + UP BOUND C0420 1 + UP BOUND C0421 1 + UP BOUND C0422 1 + UP BOUND C0423 1 + UP BOUND C0424 1 + UP BOUND C0425 1 + UP BOUND C0426 1 + UP BOUND C0427 1 + UP BOUND C0428 1 + UP BOUND C0429 1 + UP BOUND C0430 1 + UP BOUND C0431 1 + UP BOUND C0432 1 + UP BOUND C0433 1 + UP BOUND C0434 1 + UP BOUND C0435 1 + UP BOUND C0436 1 + UP BOUND C0437 1 + UP BOUND C0438 1 + UP BOUND C0439 1 + UP BOUND C0440 1 + UP BOUND C0441 1 + UP BOUND C0442 1 + UP BOUND C0443 1 + UP BOUND C0444 1 + UP BOUND C0445 1 + UP BOUND C0446 1 + UP BOUND C0447 1 + UP BOUND C0448 1 + UP BOUND C0449 1 + UP BOUND C0450 1 + UP BOUND C0451 1 + UP BOUND C0452 1 + UP BOUND C0453 1 + UP BOUND C0454 1 + UP BOUND C0455 1 + UP BOUND C0456 1 + UP BOUND C0457 1 + UP BOUND C0458 1 + UP BOUND C0459 1 + UP BOUND C0460 1 + UP BOUND C0461 1 + UP BOUND C0462 1 + UP BOUND C0463 1 + UP BOUND C0464 1 + UP BOUND C0465 1 + UP BOUND C0466 1 + UP BOUND C0467 1 + UP BOUND C0468 1 + UP BOUND C0469 1 + UP BOUND C0470 1 + UP BOUND C0471 1 + UP BOUND C0472 1 + UP BOUND C0473 1 + UP BOUND C0474 1 + UP BOUND C0475 1 + UP BOUND C0476 1 + UP BOUND C0477 1 + UP BOUND C0478 1 + UP BOUND C0479 1 + UP BOUND C0480 1 + UP BOUND C0481 1 + UP BOUND C0482 1 + UP BOUND C0483 1 + UP BOUND C0484 1 + UP BOUND C0485 1 + UP BOUND C0486 1 + UP BOUND C0487 1 + UP BOUND C0488 1 + UP BOUND C0489 1 + UP BOUND C0490 1 + UP BOUND C0491 1 + UP BOUND C0492 1 + UP BOUND C0493 1 + UP BOUND C0494 1 + UP BOUND C0495 1 + UP BOUND C0496 1 + UP BOUND C0497 1 + UP BOUND C0498 1 + UP BOUND C0499 1 + UP BOUND C0500 1 + UP BOUND C0501 1 + UP BOUND C0502 1 + UP BOUND C0503 1 + UP BOUND C0504 1 + UP BOUND C0505 1 + UP BOUND C0506 1 + UP BOUND C0507 1 + UP BOUND C0508 1 + UP BOUND C0509 1 + UP BOUND C0510 1 + UP BOUND C0511 1 + UP BOUND C0512 1 + UP BOUND C0513 1 + UP BOUND C0514 1 + UP BOUND C0515 1 + UP BOUND C0516 1 + UP BOUND C0517 1 + UP BOUND C0518 1 + UP BOUND C0519 1 + UP BOUND C0520 1 + UP BOUND C0521 1 + UP BOUND C0522 1 + UP BOUND C0523 1 + UP BOUND C0524 1 + UP BOUND C0525 1 + UP BOUND C0526 1 + UP BOUND C0527 1 + UP BOUND C0528 1 + UP BOUND C0529 1 + UP BOUND C0530 1 + UP BOUND C0531 1 + UP BOUND C0532 1 + UP BOUND C0533 1 + UP BOUND C0534 1 + UP BOUND C0535 1 + UP BOUND C0536 1 + UP BOUND C0537 1 + UP BOUND C0538 1 + UP BOUND C0539 1 + UP BOUND C0540 1 + UP BOUND C0541 1 + UP BOUND C0542 1 + UP BOUND C0543 1 + UP BOUND C0544 1 + UP BOUND C0545 1 + UP BOUND C0546 1 + UP BOUND C0547 1 + UP BOUND C0548 1 + UP BOUND C0549 1 + UP BOUND C0550 1 + UP BOUND C0551 1 + UP BOUND C0552 1 + UP BOUND C0553 1 + UP BOUND C0554 1 + UP BOUND C0555 1 + UP BOUND C0556 1 + UP BOUND C0557 1 + UP BOUND C0558 1 + UP BOUND C0559 1 + UP BOUND C0560 1 + UP BOUND C0561 1 + UP BOUND C0562 1 + UP BOUND C0563 1 + UP BOUND C0564 1 + UP BOUND C0565 1 + UP BOUND C0566 1 + UP BOUND C0567 1 + UP BOUND C0568 1 + UP BOUND C0569 1 + UP BOUND C0570 1 + UP BOUND C0571 1 + UP BOUND C0572 1 + UP BOUND C0573 1 + UP BOUND C0574 1 + UP BOUND C0575 1 + UP BOUND C0576 1 + UP BOUND C0577 1 + UP BOUND C0578 1 + UP BOUND C0579 1 + UP BOUND C0580 1 + UP BOUND C0581 1 + UP BOUND C0582 1 + UP BOUND C0583 1 + UP BOUND C0584 1 + UP BOUND C0585 1 + UP BOUND C0586 1 + UP BOUND C0587 1 + UP BOUND C0588 1 + UP BOUND C0589 1 + UP BOUND C0590 1 + UP BOUND C0591 1 + UP BOUND C0592 1 + UP BOUND C0593 1 + UP BOUND C0594 1 + UP BOUND C0595 1 + UP BOUND C0596 1 + UP BOUND C0597 1 + UP BOUND C0598 1 + UP BOUND C0599 1 + UP BOUND C0600 1 + UP BOUND C0601 1 + UP BOUND C0602 1 + UP BOUND C0603 1 + UP BOUND C0604 1 + UP BOUND C0605 1 + UP BOUND C0606 1 + UP BOUND C0607 1 + UP BOUND C0608 1 + UP BOUND C0609 1 + UP BOUND C0610 1 + UP BOUND C0611 1 + UP BOUND C0612 1 + UP BOUND C0613 1 + UP BOUND C0614 1 + UP BOUND C0615 1 + UP BOUND C0616 1 + UP BOUND C0617 1 + UP BOUND C0618 1 + UP BOUND C0619 1 + UP BOUND C0620 1 + UP BOUND C0621 1 + UP BOUND C0622 1 + UP BOUND C0623 1 + UP BOUND C0624 1 + UP BOUND C0625 1 + UP BOUND C0626 1 + UP BOUND C0627 1 + UP BOUND C0628 1 + UP BOUND C0629 1 + UP BOUND C0630 1 + UP BOUND C0631 1 + UP BOUND C0632 1 + UP BOUND C0633 1 + UP BOUND C0634 1 + UP BOUND C0635 1 + UP BOUND C0636 1 + UP BOUND C0637 1 + UP BOUND C0638 1 + UP BOUND C0639 1 + UP BOUND C0640 1 + UP BOUND C0641 1 + UP BOUND C0642 1 + UP BOUND C0643 1 + UP BOUND C0644 1 + UP BOUND C0645 1 + UP BOUND C0646 1 + UP BOUND C0647 1 + UP BOUND C0648 1 + UP BOUND C0649 1 + UP BOUND C0650 1 + UP BOUND C0651 1 + UP BOUND C0652 1 + UP BOUND C0653 1 + UP BOUND C0654 1 + UP BOUND C0655 1 + UP BOUND C0656 1 + UP BOUND C0657 1 + UP BOUND C0658 1 + UP BOUND C0659 1 + UP BOUND C0660 1 + UP BOUND C0661 1 + UP BOUND C0662 1 + UP BOUND C0663 1 + UP BOUND C0664 1 + UP BOUND C0665 1 + UP BOUND C0666 1 + UP BOUND C0667 1 + UP BOUND C0668 1 + UP BOUND C0669 1 + UP BOUND C0670 1 + UP BOUND C0671 1 + UP BOUND C0672 1 + UP BOUND C0673 1 + UP BOUND C0674 1 + UP BOUND C0675 1 + UP BOUND C0676 1 + UP BOUND C0677 1 + UP BOUND C0678 1 + UP BOUND C0679 1 + UP BOUND C0680 1 + UP BOUND C0681 1 + UP BOUND C0682 1 + UP BOUND C0683 1 + UP BOUND C0684 1 + UP BOUND C0685 1 + UP BOUND C0686 1 + UP BOUND C0687 1 + UP BOUND C0688 1 + UP BOUND C0689 1 + UP BOUND C0690 1 + UP BOUND C0691 1 + UP BOUND C0692 1 + UP BOUND C0693 1 + UP BOUND C0694 1 + UP BOUND C0695 1 + UP BOUND C0696 1 + UP BOUND C0697 1 + UP BOUND C0698 1 + UP BOUND C0699 1 + UP BOUND C0700 1 + UP BOUND C0701 1 + UP BOUND C0702 1 + UP BOUND C0703 1 + UP BOUND C0704 1 + UP BOUND C0705 1 + UP BOUND C0706 1 + UP BOUND C0707 1 + UP BOUND C0708 1 + UP BOUND C0709 1 + UP BOUND C0710 1 + UP BOUND C0711 1 + UP BOUND C0712 1 + UP BOUND C0713 1 + UP BOUND C0714 1 + UP BOUND C0715 1 + UP BOUND C0716 1 + UP BOUND C0717 1 + UP BOUND C0718 1 + UP BOUND C0719 1 + UP BOUND C0720 1 + UP BOUND C0721 1 + UP BOUND C0722 1 + UP BOUND C0723 1 + UP BOUND C0724 1 + UP BOUND C0725 1 + UP BOUND C0726 1 + UP BOUND C0727 1 + UP BOUND C0728 1 + UP BOUND C0729 1 + UP BOUND C0730 1 + UP BOUND C0731 1 + UP BOUND C0732 1 + UP BOUND C0733 1 + UP BOUND C0734 1 + UP BOUND C0735 1 + UP BOUND C0736 1 + UP BOUND C0737 1 + UP BOUND C0738 1 + UP BOUND C0739 1 + UP BOUND C0740 1 + UP BOUND C0741 1 + UP BOUND C0742 1 + UP BOUND C0743 1 + UP BOUND C0744 1 + UP BOUND C0745 1 + UP BOUND C0746 1 + UP BOUND C0747 1 + UP BOUND C0748 1 + UP BOUND C0749 1 + UP BOUND C0750 1 + UP BOUND C0751 1 + UP BOUND C0752 1 + UP BOUND C0753 1 + UP BOUND C0754 1 + UP BOUND C0755 1 + UP BOUND C0756 1 + UP BOUND C0757 1 + UP BOUND C0758 1 + UP BOUND C0759 1 + UP BOUND C0760 1 + UP BOUND C0761 1 + UP BOUND C0762 1 + UP BOUND C0763 1 + UP BOUND C0764 1 + UP BOUND C0765 1 + UP BOUND C0766 1 + UP BOUND C0767 1 + UP BOUND C0768 1 + UP BOUND C0769 1 + UP BOUND C0770 1 + UP BOUND C0771 1 + UP BOUND C0772 1 + UP BOUND C0773 1 + UP BOUND C0774 1 + UP BOUND C0775 1 + UP BOUND C0776 1 + UP BOUND C0777 1 + UP BOUND C0778 1 + UP BOUND C0779 1 + UP BOUND C0780 1 + UP BOUND C0781 1 + UP BOUND C0782 1 + UP BOUND C0783 1 + UP BOUND C0784 1 + UP BOUND C0785 1 + UP BOUND C0786 1 + UP BOUND C0787 1 + UP BOUND C0788 1 + UP BOUND C0789 1 + UP BOUND C0790 1 + UP BOUND C0791 1 + UP BOUND C0792 1 + UP BOUND C0793 1 + UP BOUND C0794 1 + UP BOUND C0795 1 + UP BOUND C0796 1 + UP BOUND C0797 1 + UP BOUND C0798 1 + UP BOUND C0799 1 + UP BOUND C0800 1 + UP BOUND C0801 1 + UP BOUND C0802 1 + UP BOUND C0803 1 + UP BOUND C0804 1 + UP BOUND C0805 1 + UP BOUND C0806 1 + UP BOUND C0807 1 + UP BOUND C0808 1 + UP BOUND C0809 1 + UP BOUND C0810 1 + UP BOUND C0811 1 + UP BOUND C0812 1 + UP BOUND C0813 1 + UP BOUND C0814 1 + UP BOUND C0815 1 + UP BOUND C0816 1 + UP BOUND C0817 1 + UP BOUND C0818 1 + UP BOUND C0819 1 + UP BOUND C0820 1 +ENDATA diff --git a/read_decomposition_demos/bin_packing/read_model.jl b/read_decomposition_demos/bin_packing/read_model.jl new file mode 100644 index 0000000..dbffc8b --- /dev/null +++ b/read_decomposition_demos/bin_packing/read_model.jl @@ -0,0 +1,17 @@ +using Coluna, BlockDecomposition, JuMP, GLPK +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +) + +model = BlockModel( + coluna, + read_decomposition = true, + model_filename = "./bpp_20_0.mps", # From: https://striplib.or.rwth-aachen.de/ + decomp_filename = "./bpp_20_0.dec" +) +optimize!(model) +return model diff --git a/read_decomposition_demos/gap/gap.dec b/read_decomposition_demos/gap/gap.dec new file mode 100644 index 0000000..e0df608 --- /dev/null +++ b/read_decomposition_demos/gap/gap.dec @@ -0,0 +1,41 @@ +NBLOCKS +4 +BLOCK 1 +knp[1] +BLOCK 2 +knp[2] +BLOCK 3 +knp[3] +BLOCK 4 +knp[4] +MASTERCONSS +cov[1] +cov[2] +cov[3] +cov[4] +cov[5] +cov[6] +cov[7] +cov[8] +cov[9] +cov[10] +cov[11] +cov[12] +cov[13] +cov[14] +cov[15] +cov[16] +cov[17] +cov[18] +cov[19] +cov[20] +cov[21] +cov[22] +cov[23] +cov[24] +cov[25] +cov[26] +cov[27] +cov[28] +cov[29] +cov[30] diff --git a/read_decomposition_demos/gap/gap.mps b/read_decomposition_demos/gap/gap.mps new file mode 100644 index 0000000..ecba5aa --- /dev/null +++ b/read_decomposition_demos/gap/gap.mps @@ -0,0 +1,557 @@ +NAME +ROWS + N OBJ + L knp[1] + L knp[2] + L knp[3] + L knp[4] + G cov[1] + G cov[2] + G cov[3] + G cov[4] + G cov[5] + G cov[6] + G cov[7] + G cov[8] + G cov[9] + G cov[10] + G cov[11] + G cov[12] + G cov[13] + G cov[14] + G cov[15] + G cov[16] + G cov[17] + G cov[18] + G cov[19] + G cov[20] + G cov[21] + G cov[22] + G cov[23] + G cov[24] + G cov[25] + G cov[26] + G cov[27] + G cov[28] + G cov[29] + G cov[30] +COLUMNS + MARKER 'MARKER' 'INTORG' + x[1,1] knp[1] 61 + x[1,1] cov[1] 1 + x[1,1] OBJ 12.7 + x[2,1] knp[2] 50 + x[2,1] cov[1] 1 + x[2,1] OBJ 19.1 + x[3,1] knp[3] 91 + x[3,1] cov[1] 1 + x[3,1] OBJ 18.6 + x[4,1] knp[4] 62 + x[4,1] cov[1] 1 + x[4,1] OBJ 13.1 + x[1,2] knp[1] 70 + x[1,2] cov[2] 1 + x[1,2] OBJ 22.5 + x[2,2] knp[2] 57 + x[2,2] cov[2] 1 + x[2,2] OBJ 24.8 + x[3,2] knp[3] 81 + x[3,2] cov[2] 1 + x[3,2] OBJ 14.1 + x[4,2] knp[4] 79 + x[4,2] cov[2] 1 + x[4,2] OBJ 16.2 + x[1,3] knp[1] 57 + x[1,3] cov[3] 1 + x[1,3] OBJ 8.9 + x[2,3] knp[2] 61 + x[2,3] cov[3] 1 + x[2,3] OBJ 24.4 + x[3,3] knp[3] 66 + x[3,3] cov[3] 1 + x[3,3] OBJ 22.7 + x[4,3] knp[4] 73 + x[4,3] cov[3] 1 + x[4,3] OBJ 16.8 + x[1,4] knp[1] 82 + x[1,4] cov[4] 1 + x[1,4] OBJ 20.8 + x[2,4] knp[2] 83 + x[2,4] cov[4] 1 + x[2,4] OBJ 23.6 + x[3,4] knp[3] 63 + x[3,4] cov[4] 1 + x[3,4] OBJ 9.9 + x[4,4] knp[4] 60 + x[4,4] cov[4] 1 + x[4,4] OBJ 16.7 + x[1,5] knp[1] 51 + x[1,5] cov[5] 1 + x[1,5] OBJ 13.6 + x[2,5] knp[2] 81 + x[2,5] cov[5] 1 + x[2,5] OBJ 16.1 + x[3,5] knp[3] 59 + x[3,5] cov[5] 1 + x[3,5] OBJ 24.2 + x[4,5] knp[4] 75 + x[4,5] cov[5] 1 + x[4,5] OBJ 9 + x[1,6] knp[1] 74 + x[1,6] cov[6] 1 + x[1,6] OBJ 12.4 + x[2,6] knp[2] 79 + x[2,6] cov[6] 1 + x[2,6] OBJ 20.6 + x[3,6] knp[3] 81 + x[3,6] cov[6] 1 + x[3,6] OBJ 24.5 + x[4,6] knp[4] 66 + x[4,6] cov[6] 1 + x[4,6] OBJ 16.9 + x[1,7] knp[1] 98 + x[1,7] cov[7] 1 + x[1,7] OBJ 24.8 + x[2,7] knp[2] 63 + x[2,7] cov[7] 1 + x[2,7] OBJ 15 + x[3,7] knp[3] 87 + x[3,7] cov[7] 1 + x[3,7] OBJ 20.8 + x[4,7] knp[4] 68 + x[4,7] cov[7] 1 + x[4,7] OBJ 17.9 + x[1,8] knp[1] 64 + x[1,8] cov[8] 1 + x[1,8] OBJ 19.1 + x[2,8] knp[2] 99 + x[2,8] cov[8] 1 + x[2,8] OBJ 9.5 + x[3,8] knp[3] 90 + x[3,8] cov[8] 1 + x[3,8] OBJ 12.9 + x[4,8] knp[4] 99 + x[4,8] cov[8] 1 + x[4,8] OBJ 12.1 + x[1,9] knp[1] 86 + x[1,9] cov[9] 1 + x[1,9] OBJ 11.5 + x[2,9] knp[2] 82 + x[2,9] cov[9] 1 + x[2,9] OBJ 7.9 + x[3,9] knp[3] 65 + x[3,9] cov[9] 1 + x[3,9] OBJ 17.7 + x[4,9] knp[4] 69 + x[4,9] cov[9] 1 + x[4,9] OBJ 17.5 + x[1,10] knp[1] 80 + x[1,10] cov[10] 1 + x[1,10] OBJ 17.4 + x[2,10] knp[2] 59 + x[2,10] cov[10] 1 + x[2,10] OBJ 11.3 + x[3,10] knp[3] 55 + x[3,10] cov[10] 1 + x[3,10] OBJ 11.9 + x[4,10] knp[4] 60 + x[4,10] cov[10] 1 + x[4,10] OBJ 22 + x[1,11] knp[1] 69 + x[1,11] cov[11] 1 + x[1,11] OBJ 24.7 + x[2,11] knp[2] 83 + x[2,11] cov[11] 1 + x[2,11] OBJ 22.6 + x[3,11] knp[3] 57 + x[3,11] cov[11] 1 + x[3,11] OBJ 18.7 + x[4,11] knp[4] 56 + x[4,11] cov[11] 1 + x[4,11] OBJ 19.9 + x[1,12] knp[1] 79 + x[1,12] cov[12] 1 + x[1,12] OBJ 6.8 + x[2,12] knp[2] 91 + x[2,12] cov[12] 1 + x[2,12] OBJ 8 + x[3,12] knp[3] 68 + x[3,12] cov[12] 1 + x[3,12] OBJ 10.1 + x[4,12] knp[4] 100 + x[4,12] cov[12] 1 + x[4,12] OBJ 14.6 + x[1,13] knp[1] 60 + x[1,13] cov[13] 1 + x[1,13] OBJ 21.7 + x[2,13] knp[2] 59 + x[2,13] cov[13] 1 + x[2,13] OBJ 21.5 + x[3,13] knp[3] 92 + x[3,13] cov[13] 1 + x[3,13] OBJ 9.1 + x[4,13] knp[4] 67 + x[4,13] cov[13] 1 + x[4,13] OBJ 18.2 + x[1,14] knp[1] 76 + x[1,14] cov[14] 1 + x[1,14] OBJ 14.3 + x[2,14] knp[2] 99 + x[2,14] cov[14] 1 + x[2,14] OBJ 14.7 + x[3,14] knp[3] 91 + x[3,14] cov[14] 1 + x[3,14] OBJ 8.9 + x[4,14] knp[4] 68 + x[4,14] cov[14] 1 + x[4,14] OBJ 19.6 + x[1,15] knp[1] 78 + x[1,15] cov[15] 1 + x[1,15] OBJ 10.5 + x[2,15] knp[2] 91 + x[2,15] cov[15] 1 + x[2,15] OBJ 23.2 + x[3,15] knp[3] 86 + x[3,15] cov[15] 1 + x[3,15] OBJ 7.7 + x[4,15] knp[4] 54 + x[4,15] cov[15] 1 + x[4,15] OBJ 24.2 + x[1,16] knp[1] 71 + x[1,16] cov[16] 1 + x[1,16] OBJ 15.2 + x[2,16] knp[2] 75 + x[2,16] cov[16] 1 + x[2,16] OBJ 19.7 + x[3,16] knp[3] 74 + x[3,16] cov[16] 1 + x[3,16] OBJ 16.6 + x[4,16] knp[4] 66 + x[4,16] cov[16] 1 + x[4,16] OBJ 12.9 + x[1,17] knp[1] 50 + x[1,17] cov[17] 1 + x[1,17] OBJ 14.3 + x[2,17] knp[2] 66 + x[2,17] cov[17] 1 + x[2,17] OBJ 19.5 + x[3,17] knp[3] 80 + x[3,17] cov[17] 1 + x[3,17] OBJ 8.3 + x[4,17] knp[4] 50 + x[4,17] cov[17] 1 + x[4,17] OBJ 11.3 + x[1,18] knp[1] 99 + x[1,18] cov[18] 1 + x[1,18] OBJ 12.6 + x[2,18] knp[2] 100 + x[2,18] cov[18] 1 + x[2,18] OBJ 7.2 + x[3,18] knp[3] 89 + x[3,18] cov[18] 1 + x[3,18] OBJ 15.9 + x[4,18] knp[4] 56 + x[4,18] cov[18] 1 + x[4,18] OBJ 7.5 + x[1,19] knp[1] 92 + x[1,19] cov[19] 1 + x[1,19] OBJ 9.2 + x[2,19] knp[2] 69 + x[2,19] cov[19] 1 + x[2,19] OBJ 6.4 + x[3,19] knp[3] 95 + x[3,19] cov[19] 1 + x[3,19] OBJ 24.3 + x[4,19] knp[4] 70 + x[4,19] cov[19] 1 + x[4,19] OBJ 6.5 + x[1,20] knp[1] 83 + x[1,20] cov[20] 1 + x[1,20] OBJ 20.8 + x[2,20] knp[2] 60 + x[2,20] cov[20] 1 + x[2,20] OBJ 23.2 + x[3,20] knp[3] 57 + x[3,20] cov[20] 1 + x[3,20] OBJ 18.6 + x[4,20] knp[4] 56 + x[4,20] cov[20] 1 + x[4,20] OBJ 11.3 + x[1,21] knp[1] 53 + x[1,21] cov[21] 1 + x[1,21] OBJ 11.7 + x[2,21] knp[2] 87 + x[2,21] cov[21] 1 + x[2,21] OBJ 8.1 + x[3,21] knp[3] 55 + x[3,21] cov[21] 1 + x[3,21] OBJ 21.1 + x[4,21] knp[4] 72 + x[4,21] cov[21] 1 + x[4,21] OBJ 7.8 + x[1,22] knp[1] 91 + x[1,22] cov[22] 1 + x[1,22] OBJ 17.3 + x[2,22] knp[2] 98 + x[2,22] cov[22] 1 + x[2,22] OBJ 13.6 + x[3,22] knp[3] 96 + x[3,22] cov[22] 1 + x[3,22] OBJ 7.5 + x[4,22] knp[4] 62 + x[4,22] cov[22] 1 + x[4,22] OBJ 13.8 + x[1,23] knp[1] 68 + x[1,23] cov[23] 1 + x[1,23] OBJ 9.2 + x[2,23] knp[2] 78 + x[2,23] cov[23] 1 + x[2,23] OBJ 24.6 + x[3,23] knp[3] 77 + x[3,23] cov[23] 1 + x[3,23] OBJ 16.8 + x[4,23] knp[4] 85 + x[4,23] cov[23] 1 + x[4,23] OBJ 20.7 + x[1,24] knp[1] 61 + x[1,24] cov[24] 1 + x[1,24] OBJ 20.3 + x[2,24] knp[2] 62 + x[2,24] cov[24] 1 + x[2,24] OBJ 15.6 + x[3,24] knp[3] 60 + x[3,24] cov[24] 1 + x[3,24] OBJ 20.9 + x[4,24] knp[4] 70 + x[4,24] cov[24] 1 + x[4,24] OBJ 16.8 + x[1,25] knp[1] 63 + x[1,25] cov[25] 1 + x[1,25] OBJ 11.4 + x[2,25] knp[2] 90 + x[2,25] cov[25] 1 + x[2,25] OBJ 22.3 + x[3,25] knp[3] 55 + x[3,25] cov[25] 1 + x[3,25] OBJ 8.9 + x[4,25] knp[4] 100 + x[4,25] cov[25] 1 + x[4,25] OBJ 23.6 + x[1,26] knp[1] 97 + x[1,26] cov[26] 1 + x[1,26] OBJ 6.2 + x[2,26] knp[2] 89 + x[2,26] cov[26] 1 + x[2,26] OBJ 8.8 + x[3,26] knp[3] 57 + x[3,26] cov[26] 1 + x[3,26] OBJ 15.2 + x[4,26] knp[4] 57 + x[4,26] cov[26] 1 + x[4,26] OBJ 19.1 + x[1,27] knp[1] 91 + x[1,27] cov[27] 1 + x[1,27] OBJ 13.8 + x[2,27] knp[2] 67 + x[2,27] cov[27] 1 + x[2,27] OBJ 19.1 + x[3,27] knp[3] 56 + x[3,27] cov[27] 1 + x[3,27] OBJ 15.7 + x[4,27] knp[4] 96 + x[4,27] cov[27] 1 + x[4,27] OBJ 16.8 + x[1,28] knp[1] 77 + x[1,28] cov[28] 1 + x[1,28] OBJ 10 + x[2,28] knp[2] 87 + x[2,28] cov[28] 1 + x[2,28] OBJ 18.4 + x[3,28] knp[3] 67 + x[3,28] cov[28] 1 + x[3,28] OBJ 12.7 + x[4,28] knp[4] 69 + x[4,28] cov[28] 1 + x[4,28] OBJ 19.3 + x[1,29] knp[1] 68 + x[1,29] cov[29] 1 + x[1,29] OBJ 20.9 + x[2,29] knp[2] 65 + x[2,29] cov[29] 1 + x[2,29] OBJ 22.9 + x[3,29] knp[3] 81 + x[3,29] cov[29] 1 + x[3,29] OBJ 20.8 + x[4,29] knp[4] 65 + x[4,29] cov[29] 1 + x[4,29] OBJ 12.5 + x[1,30] knp[1] 80 + x[1,30] cov[30] 1 + x[1,30] OBJ 20.6 + x[2,30] knp[2] 100 + x[2,30] cov[30] 1 + x[2,30] OBJ 8 + x[3,30] knp[3] 52 + x[3,30] cov[30] 1 + x[3,30] OBJ 10.4 + x[4,30] knp[4] 50 + x[4,30] cov[30] 1 + x[4,30] OBJ 11 +RHS + rhs knp[1] 1020 + rhs knp[2] 1460 + rhs knp[3] 1530 + rhs knp[4] 1190 + rhs cov[1] 1 + rhs cov[2] 1 + rhs cov[3] 1 + rhs cov[4] 1 + rhs cov[5] 1 + rhs cov[6] 1 + rhs cov[7] 1 + rhs cov[8] 1 + rhs cov[9] 1 + rhs cov[10] 1 + rhs cov[11] 1 + rhs cov[12] 1 + rhs cov[13] 1 + rhs cov[14] 1 + rhs cov[15] 1 + rhs cov[16] 1 + rhs cov[17] 1 + rhs cov[18] 1 + rhs cov[19] 1 + rhs cov[20] 1 + rhs cov[21] 1 + rhs cov[22] 1 + rhs cov[23] 1 + rhs cov[24] 1 + rhs cov[25] 1 + rhs cov[26] 1 + rhs cov[27] 1 + rhs cov[28] 1 + rhs cov[29] 1 + rhs cov[30] 1 +RANGES +BOUNDS + BV bounds x[1,1] + BV bounds x[2,1] + BV bounds x[3,1] + BV bounds x[4,1] + BV bounds x[1,2] + BV bounds x[2,2] + BV bounds x[3,2] + BV bounds x[4,2] + BV bounds x[1,3] + BV bounds x[2,3] + BV bounds x[3,3] + BV bounds x[4,3] + BV bounds x[1,4] + BV bounds x[2,4] + BV bounds x[3,4] + BV bounds x[4,4] + BV bounds x[1,5] + BV bounds x[2,5] + BV bounds x[3,5] + BV bounds x[4,5] + BV bounds x[1,6] + BV bounds x[2,6] + BV bounds x[3,6] + BV bounds x[4,6] + BV bounds x[1,7] + BV bounds x[2,7] + BV bounds x[3,7] + BV bounds x[4,7] + BV bounds x[1,8] + BV bounds x[2,8] + BV bounds x[3,8] + BV bounds x[4,8] + BV bounds x[1,9] + BV bounds x[2,9] + BV bounds x[3,9] + BV bounds x[4,9] + BV bounds x[1,10] + BV bounds x[2,10] + BV bounds x[3,10] + BV bounds x[4,10] + BV bounds x[1,11] + BV bounds x[2,11] + BV bounds x[3,11] + BV bounds x[4,11] + BV bounds x[1,12] + BV bounds x[2,12] + BV bounds x[3,12] + BV bounds x[4,12] + BV bounds x[1,13] + BV bounds x[2,13] + BV bounds x[3,13] + BV bounds x[4,13] + BV bounds x[1,14] + BV bounds x[2,14] + BV bounds x[3,14] + BV bounds x[4,14] + BV bounds x[1,15] + BV bounds x[2,15] + BV bounds x[3,15] + BV bounds x[4,15] + BV bounds x[1,16] + BV bounds x[2,16] + BV bounds x[3,16] + BV bounds x[4,16] + BV bounds x[1,17] + BV bounds x[2,17] + BV bounds x[3,17] + BV bounds x[4,17] + BV bounds x[1,18] + BV bounds x[2,18] + BV bounds x[3,18] + BV bounds x[4,18] + BV bounds x[1,19] + BV bounds x[2,19] + BV bounds x[3,19] + BV bounds x[4,19] + BV bounds x[1,20] + BV bounds x[2,20] + BV bounds x[3,20] + BV bounds x[4,20] + BV bounds x[1,21] + BV bounds x[2,21] + BV bounds x[3,21] + BV bounds x[4,21] + BV bounds x[1,22] + BV bounds x[2,22] + BV bounds x[3,22] + BV bounds x[4,22] + BV bounds x[1,23] + BV bounds x[2,23] + BV bounds x[3,23] + BV bounds x[4,23] + BV bounds x[1,24] + BV bounds x[2,24] + BV bounds x[3,24] + BV bounds x[4,24] + BV bounds x[1,25] + BV bounds x[2,25] + BV bounds x[3,25] + BV bounds x[4,25] + BV bounds x[1,26] + BV bounds x[2,26] + BV bounds x[3,26] + BV bounds x[4,26] + BV bounds x[1,27] + BV bounds x[2,27] + BV bounds x[3,27] + BV bounds x[4,27] + BV bounds x[1,28] + BV bounds x[2,28] + BV bounds x[3,28] + BV bounds x[4,28] + BV bounds x[1,29] + BV bounds x[2,29] + BV bounds x[3,29] + BV bounds x[4,29] + BV bounds x[1,30] + BV bounds x[2,30] + BV bounds x[3,30] + BV bounds x[4,30] +ENDATA diff --git a/read_decomposition_demos/gap/read_model.jl b/read_decomposition_demos/gap/read_model.jl new file mode 100644 index 0000000..a3fefd2 --- /dev/null +++ b/read_decomposition_demos/gap/read_model.jl @@ -0,0 +1,17 @@ +using Coluna, BlockDecomposition, JuMP, GLPK +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +) + +model = BlockModel( + coluna, + read_decomposition = true, + model_filename = "./gap.mps", + decomp_filename = "./gap.dec" +) +optimize!(model) +return model From 2568039234d5d4db4798d9d7091a5b440258f618 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Sat, 24 Jul 2021 15:00:22 +0200 Subject: [PATCH 31/32] Improve reading of .dec files --- src/read_decomposition.jl | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/read_decomposition.jl b/src/read_decomposition.jl index fc588bf..7a5a97f 100644 --- a/src/read_decomposition.jl +++ b/src/read_decomposition.jl @@ -48,20 +48,26 @@ function _read_constraint_names(decomp_filename::String) master = Set{String}() # Names of master constraints blocks = Array{Set{String},1}() # Names of constraints in blocks lines = readlines(decomp_filename) - for index in eachindex(lines) + index = 1 + while index <= size(lines,1) items = _line_to_items(lines[index]) - if items[1] == "NBLOCKS" # Initialize number of blocks - index = index + 1 - nb_blocks = parse(Int, lines[index]) + if items[1] == "\\\\" + index += 1 + elseif items[1] == "PRESOLVED" + index += 2 + elseif items[1] == "NBLOCKS" # Initialize number of blocks + nb_blocks = parse(Int, lines[index + 1]) blocks = Array{Set{String},1}(undef, nb_blocks) - end - if items[1] == "BLOCK" # Add block + index += 2 + elseif items[1] == "BLOCK" # Add block nb = parse(Int, items[2]) - constraint_names, new_index = _get_following_constraints(lines, index) + constraint_names, index = _get_following_constraints(lines, index) blocks[nb] = constraint_names - end - if items[1] == "MASTERCONSS" # Add master constraints + elseif items[1] == "MASTERCONSS" # Add master constraints master, _ = _get_following_constraints(lines, index) + break + else + error("Error parsing decomposition file because line starting with ", items[1], " not allowed.") end end return master, blocks From d422c83f67408310feb5bcd34b320328b76c7b44 Mon Sep 17 00:00:00 2001 From: David Meichel Date: Thu, 4 Nov 2021 09:28:40 +0100 Subject: [PATCH 32/32] Fix reading decomp files without master constraints --- src/read_decomposition.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/read_decomposition.jl b/src/read_decomposition.jl index 7a5a97f..006cdfc 100644 --- a/src/read_decomposition.jl +++ b/src/read_decomposition.jl @@ -89,5 +89,5 @@ function _get_following_constraints(lines::Vector{String}, index::Int) end push!(constraint_names, head) end - return constraint_names, length(lines) + return constraint_names, length(lines)+1 end \ No newline at end of file