From 670bce845bf06cc9bf06c127d3bbab8b4216013f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20Besan=C3=A7on?= Date: Thu, 19 Oct 2023 15:14:26 +0200 Subject: [PATCH] cleanup --- src/Boscia.jl | 2 +- src/cube_blmo.jl | 160 ++++++++++++--------------------- src/heuristics.jl | 1 - src/integer_bounds.jl | 18 +--- src/lmo_wrapper.jl | 202 ------------------------------------------ src/node.jl | 14 --- 6 files changed, 57 insertions(+), 340 deletions(-) delete mode 100644 src/lmo_wrapper.jl diff --git a/src/Boscia.jl b/src/Boscia.jl index aac7ba34b..f247b77de 100644 --- a/src/Boscia.jl +++ b/src/Boscia.jl @@ -15,7 +15,7 @@ const MOIU = MOI.Utilities import MathOptSetDistances as MOD include("integer_bounds.jl") -include("lmo_wrapper.jl") +include("blmo_interface.jl") include("time_tracking_lmo.jl") include("build_lmo.jl") include("frank_wolfe_variants.jl") diff --git a/src/cube_blmo.jl b/src/cube_blmo.jl index 8e8961340..47a26372f 100644 --- a/src/cube_blmo.jl +++ b/src/cube_blmo.jl @@ -1,42 +1,36 @@ + """ CubeBLMO - -A Bounded Linear Minimization Oracle over a cube. + +A Bounded Linear Minimization Oracle over a cube. """ -mutable struct CubeBLMO <: Boscia.BoundedLinearMinimizationOracle +mutable struct CubeBLMO <: BoundedLinearMinimizationOracle n::Int int_vars::Vector{Int} bin_vars::Vector{Int} - bounds::Boscia.IntegerBounds - solving_time + bounds::IntegerBounds + solving_time::Float64 end CubeBLMO(n, int_vars, bin_vars, bounds) = CubeBLMO(n, int_vars, bin_vars, bounds, 0.0) ## Necessary -""" -Implement `FrankWolfe.compute_extreme_point` -Given a direction d solves the problem - min_x d^T x -where x has to be an integer feasible point -""" -function Boscia.compute_extreme_point(blmo::CubeBLMO, d; kwargs...) +# computing an extreme point for the cube amounts to checking the sign of the gradient +function compute_extreme_point(blmo::CubeBLMO, d; kwargs...) time_ref = Dates.now() v = zeros(length(d)) for i in eachindex(d) v[i] = d[i] > 0 ? blmo.bounds[i, :greaterthan].lower : blmo.bounds[i, :lessthan].upper - end + end blmo.solving_time = float(Dates.value(Dates.now() - time_ref)) return v end -## -""" -Read global bounds from the problem. -""" -function Boscia.build_global_bounds(blmo::CubeBLMO, integer_variables) - global_bounds = Boscia.IntegerBounds() +## + +function build_global_bounds(blmo::CubeBLMO, integer_variables) + global_bounds = IntegerBounds() for i in 1:blmo.n if i in integer_variables push!(global_bounds, (i, blmo.bounds[i, :lessthan])) @@ -45,64 +39,47 @@ function Boscia.build_global_bounds(blmo::CubeBLMO, integer_variables) end return global_bounds end -""" -Add explicit bounds for binary variables, if not already done from the get-go. -""" -function Boscia.explicit_bounds_binary_var(blmo::CubeBLMO, gb::Boscia.IntegerBounds, binary_vars) + +function explicit_bounds_binary_var(blmo::CubeBLMO, gb::IntegerBounds, binary_vars) nothing end ## Read information from problem -""" -Get list of variables indices. -If the problem has n variables, they are expected to contiguous and ordered from 1 to n. -""" -function Boscia.get_list_of_variables(blmo::CubeBLMO) +function get_list_of_variables(blmo::CubeBLMO) return blmo.n, collect(1:blmo.n) end -""" -Get list of binary and integer variables, respectively. -""" -function Boscia.get_binary_variables(blmo::CubeBLMO) +# Get list of binary and integer variables, respectively. +function get_binary_variables(blmo::CubeBLMO) return blmo.bin_vars end -function Boscia.get_integer_variables(blmo::CubeBLMO) + +function get_integer_variables(blmo::CubeBLMO) return blmo.int_vars -end -""" -Get the index of the integer variable the bound is working on. -""" -function Boscia.get_int_var(blmo::CubeBLMO, cidx) +end + +function get_int_var(blmo::CubeBLMO, cidx) return cidx end -""" -Get the list of lower bounds. -""" -function Boscia.get_lower_bound_list(blmo::CubeBLMO) + +function get_lower_bound_list(blmo::CubeBLMO) return keys(blmo.bounds.lower_bounds) end -""" -Get the list of upper bounds. -""" -function Boscia.get_upper_bound_list(blmo::CubeBLMO) + +function get_upper_bound_list(blmo::CubeBLMO) return keys(blmo.bounds.upper_bounds) -end -""" -Read bound value for c_idx. -""" -function Boscia.get_lower_bound(blmo::CubeBLMO, c_idx) +end + +function get_lower_bound(blmo::CubeBLMO, c_idx) return blmo.bounds[c_idx, :greaterthan] end -function Boscia.get_upper_bound(blmo::CubeBLMO, c_idx) + +function get_upper_bound(blmo::CubeBLMO, c_idx) return blmo.bounds[c_idx, :lessthan] end ## Changing the bounds constraints. -""" -Change the value of the bound c_idx. -""" -function Boscia.set_bound!(blmo::CubeBLMO, c_idx, value) +function set_bound!(blmo::CubeBLMO, c_idx, value) if value isa MOI.GreaterThan{Float64} blmo.bounds.lower_bounds[c_idx] = value elseif value isa MOI.LessThan{Float64} @@ -111,41 +88,30 @@ function Boscia.set_bound!(blmo::CubeBLMO, c_idx, value) error("We expect the value to be of type MOI.GreaterThan or Moi.LessThan!") end end -""" -Delete bounds. -""" -function Boscia.delete_bounds!(blmo::CubeBLMO, cons_delete) + +function delete_bounds!(::CubeBLMO, cons_delete) # For the cube this shouldn't happen! Otherwise we get unbounded! if !isempty(cons_delete) error("Trying to delete bounds of the cube!") end end -""" -Add bound constraint. -""" -function Boscia.add_bound_constraint!(blmo::CubeBLMO, key, value) + +function add_bound_constraint!(::CubeBLMO, key, value) # Should not be necessary error("Trying to add bound constraints of the cube!") end -## Checks -""" -Check if the subject of the bound c_idx is an integer variable (recorded in int_vars). -""" -function Boscia.is_constraint_on_int_var(blmo::CubeBLMO, c_idx, int_vars) +## Checks + +function is_constraint_on_int_var(blmo::CubeBLMO, c_idx, int_vars) return c_idx in int_vars end -""" -To check if there is bound for the variable in the global or node bounds. -""" -function Boscia.is_bound_in(blmo::CubeBLMO, c_idx, bounds) + +function is_bound_in(blmo::CubeBLMO, c_idx, bounds) return haskey(bounds, c_idx) end -""" -Is a given point v linear feasible for the model? -That means does v satisfy all bounds and other linear constraints? -""" -function Boscia.is_linear_feasible(blmo::CubeBLMO, v::AbstractVector) + +function is_linear_feasible(blmo::CubeBLMO, v::AbstractVector) for i in eachindex(v) if !(blmo.bounds[i, :greaterthan].lower ≤ v[i] + 1e-6 || !(v[i] - 1e-6 ≤ blmo.bounds[i, :lessthan].upper)) @debug("Vertex entry: $(v[i]) Lower bound: $(blmo.bounds[i, :greaterthan].lower) Upper bound: $(blmo.bounds[i, :lessthan].upper))") @@ -154,16 +120,12 @@ function Boscia.is_linear_feasible(blmo::CubeBLMO, v::AbstractVector) end return true end -""" -Does the variable have a binary constraint? -""" -function Boscia.has_binary_constraint(blmo::CubeBLMO, idx) + +function has_binary_constraint(blmo::CubeBLMO, idx) return idx in blmo.int_vars end -""" -Has variable an integer constraint? -""" -function Boscia.has_integer_constraint(blmo::CubeBLMO, idx) + +function has_integer_constraint(blmo::CubeBLMO, idx) return idx in blmo.bin_vars end @@ -171,11 +133,8 @@ end ###################### Optional ## Safety Functions -""" -Check if the bounds were set correctly in build_LMO. -Safety check only. -""" -function Boscia.build_LMO_correct(blmo::CubeBLMO, node_bounds) + +function build_LMO_correct(blmo::CubeBLMO, node_bounds) for key in keys(node_bounds.lower_bounds) if !haskey(blmo.bounds, (key, :greaterthan)) || blmo.bounds[key, :greaterthan] != node_bounds[key, :greaterthan] return false @@ -185,15 +144,12 @@ function Boscia.build_LMO_correct(blmo::CubeBLMO, node_bounds) if !haskey(blmo.bounds, (key, :lessthan)) || blmo.bounds[key, :lessthan] != node_bounds[key, :lessthan] return false end - end + end return true end -""" -Check if problem is bounded and feasible, i.e. no contradicting constraints. -""" -function Boscia.check_feasibility(blmo::CubeBLMO) - for i in 1:blmo.n +function check_feasibility(blmo::CubeBLMO) + for i in 1:blmo.n if !haskey(blmo.bounds, (i, :greaterthan)) || !haskey(blmo.bounds, (i, :lessthan)) return MOI.DUAL_INFEASIBLE end @@ -201,17 +157,11 @@ function Boscia.check_feasibility(blmo::CubeBLMO) return MOI.OPTIMAL end -""" -Check whether a split is valid, i.e. the upper and lower on variable vidx are not the same. -""" -function Boscia.is_valid_split(tree::Bonobo.BnBTree, blmo::CubeBLMO, vidx::Int) +function is_valid_split(tree::Bonobo.BnBTree, blmo::CubeBLMO, vidx::Int) return blmo.bounds[vidx, :lessthan] != blmo.bounds[vidx, :greaterthan] end ## Logs -""" -Get solve time, number of nodes and number of iterations, if applicable. -""" -function Boscia.get_BLMO_solve_data(blmo::CubeBLMO) +function get_BLMO_solve_data(blmo::CubeBLMO) return blmo.solving_time, 0.0, 0.0 end diff --git a/src/heuristics.jl b/src/heuristics.jl index 816c3d9cf..bc6719bd6 100644 --- a/src/heuristics.jl +++ b/src/heuristics.jl @@ -1,4 +1,3 @@ -#### TO DO: Implement Feasibility Pump? """ Finds the best solution in the SCIP solution storage, based on the objective function `f`. diff --git a/src/integer_bounds.jl b/src/integer_bounds.jl index 931836106..23f61d792 100644 --- a/src/integer_bounds.jl +++ b/src/integer_bounds.jl @@ -1,7 +1,3 @@ -""" -Constant to handle half open interval bounds on variables -""" -const inf_bound = 10.0^6 """ IntegerBounds @@ -11,7 +7,7 @@ Keeps track of the bounds of the integer (binary) variables. `lower_bounds` dictionary of the MOI.GreaterThan, index is the key. `upper_bounds` dictionary of the MOI.LessThan, index is the key. """ -mutable struct IntegerBounds #<: AbstractVector{Tuple{Int,MOI.LessThan{Float64}, MOI.GreaterThan{Float64}}} +mutable struct IntegerBounds lower_bounds::Dict{Int,MOI.GreaterThan{Float64}} upper_bounds::Dict{Int,MOI.LessThan{Float64}} end @@ -28,9 +24,6 @@ function Base.push!(ib::IntegerBounds, (idx, bound)) return ib end -#Base.get(ib::GlobalIntegerBounds, i) = (ib.indices[i], ib.lessthan[i], ib.greaterthan[i]) -#Base.size(ib::GlobalIntegerBounds) = size(ib.indices) - function Base.isempty(ib::IntegerBounds) return isempty(ib.lower_bounds) && isempty(ib.upper_bounds) end @@ -70,12 +63,3 @@ function Base.haskey(ib::IntegerBounds, (idx, sense)) return haskey(ib.lower_bounds, idx) end end - -#=function find_bound(ib::GlobalIntegerBounds, vidx) - @inbounds for idx in eachindex(ib) - if ib.indices[idx] == vidx - return idx - end - end - return -1 -end =# diff --git a/src/lmo_wrapper.jl b/src/lmo_wrapper.jl deleted file mode 100644 index f33910ac1..000000000 --- a/src/lmo_wrapper.jl +++ /dev/null @@ -1,202 +0,0 @@ -""" - BLMO - -Supertype for the Bounded Linear Minimization Oracles -""" -abstract type BoundedLinearMinimizationOracle <: FrankWolfe.LinearMinimizationOracle end - -###################################### Necessary to implement #################################### - -""" -Implement `FrankWolfe.compute_extreme_point` - -Given a direction d solves the problem - min_x d^T x -where x has to be an integer feasible point -""" -function compute_extreme_point end - -""" -Read global bounds from the problem. -""" -function build_global_bounds end - -""" -Add explicit bounds for binary variables, if not already done from the get-go. -""" -function explicit_bounds_binary_var end - - -## Read information from problem -""" -Get list of variables indices. -If the problem has n variables, they are expected to contiguous and ordered from 1 to n. -""" -function get_list_of_variables end - -""" -Get list of binary variables. -""" -function get_binary_variables end - -""" -Get list of integer variables. -""" -function get_integer_variables end - -""" -Get the index of the integer variable the bound is working on. -""" -function get_int_var end - -""" -Get the list of lower bounds. -""" -function get_lower_bound_list end - -""" -Get the list of upper bounds. -""" -function get_upper_bound_list end - -""" -Read bound value for c_idx. -""" -function get_bound end - - -## Changing the bounds constraints. -""" -Change the value of the bound c_idx. -""" -function set_bound! end - -""" -Delete bounds. -""" -function delete_bounds! end - -""" -Add bound constraint. -""" -function add_bound_constraint! end - - -## Checks -""" -Check if the subject of the bound c_idx is an integer variable (recorded in int_vars). -""" -function is_constraint_on_int_var end - -""" -To check if there is bound for the variable in the global or node bounds. -""" -function is_bound_in end - -""" -Is a given point v linear feasible for the model? -That means does v satisfy all bounds and other linear constraints? -""" -function is_linear_feasible end - -""" -Has variable a binary constraint? -""" -function has_binary_constraint end - -""" -Has variable an integer constraint? -""" -function has_integer_constraint end - - - -#################### Optional to implement #################### - -# These are safety check, utilities and log functions. -# They are not strictly necessary for Boscia to run but would be beneficial to add, especially in the case of the safety functions. - -## Safety Functions -""" -Check if the bounds were set correctly in build_LMO. -Safety check only. -""" -function build_LMO_correct(blmo::BoundedLinearMinimizationOracle, node_bounds) - return true -end - -""" -Check if problem is bounded and feasible, i.e. no contradicting constraints. -""" -function check_feasibility(blmo::BoundedLinearMinimizationOracle) - return MOI.OPTIMAL -end - -""" -Check whether a split is valid, i.e. the upper and lower on variable vidx are not the same. -""" -function is_valid_split(tree::Bonobo.BnBTree, blmo::BoundedLinearMinimizationOracle, vidx::Int) - return true -end - -""" -Is a given point v indicator feasible, i.e. meets the indicator constraints? If applicable. -""" -function is_indicator_feasible(blmo::BoundedLinearMinimizationOracle, v; atol= 1e-6, rtol=1e-6) - return true -end - -""" -Are indicator constraints present? -""" -function indicator_present(blmo::BoundedLinearMinimizationOracle) - return false -end - -""" -Deal with infeasible vertex if necessary, e.g. check what caused it etc. -""" -function check_infeasible_vertex(blmo::BoundedLinearMinimizationOracle, tree) -end - - -## Utility -""" -Free model data from previous solve (if necessary). -""" -function free_model(blmo::BoundedLinearMinimizationOracle) - return true -end - -""" -Get solving tolerance for the BLMO. -""" -function get_tol(blmo::BoundedLinearMinimizationOracle) - return 1e-6 -end - -""" -Find best solution from the solving process. -""" -function find_best_solution(f::Function, blmo::BoundedLinearMinimizationOracle, vars, domain_oracle) - return (nothing, Inf) -end - -""" -List of all variable pointers. Depends on how you save your variables internally. In the easy case, this is simply `collect(1:N)`. - -Is used in `find_best_solution`. -""" -function get_variables_pointers(blmo::BoundedLinearMinimizationOracle, tree) - N = tree.root.problem.nvars - return collect(1:N) -end - - -## Logs -""" -Get solve time, number of nodes and number of iterations, if applicable. -""" -function get_BLMO_solve_data(blmo::BoundedLinearMinimizationOracle) - return 0.0, 0.0, 0.0 -end diff --git a/src/node.jl b/src/node.jl index c90705f27..a9ed2fc12 100644 --- a/src/node.jl +++ b/src/node.jl @@ -240,20 +240,6 @@ function Bonobo.evaluate_node!(tree::Bonobo.BnBTree, node::FrankWolfeNode) return NaN, NaN end - #= if isempty(node.active_set) - consI_list = MOI.get( - tree.root.problem.tlmo.blmo.o, - MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Integer}(), - ) + MOI.get( - tree.root.problem.tlmo.blmo.o, - MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.ZeroOne}(), - ) - if !isempty(consI_list) - @error "Unreachable node! Active set is empty!" - end - restart_active_set(node, tree.root.problem.tlmo.blmo, tree.root.problem.nvars) - end =# - # Check feasibility of the iterate active_set = node.active_set x = FrankWolfe.compute_active_set_iterate!(node.active_set)