Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pseudo-branching #190

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a76e6ee
Pseudo-branching
LeChStan Jun 5, 2024
17ad8fe
Added utility function to print the values of the parameters for Pseu…
LeChStan Jun 5, 2024
f70429c
Created some examples where I can test Pseudocost branching based on …
LeChStan Jun 5, 2024
45e9d34
included the file for pseudo branching
LeChStan Jun 5, 2024
5aaed15
Made changes in order for PSEUDO_COST branching to run without failin…
LeChStan Jun 5, 2024
38e9ed9
What I have written down so far. Structure of Master Thesis not set y…
LeChStan Jun 9, 2024
1efdf5b
version_16.06
LeChStan Jun 16, 2024
424c3d0
version_16.06 with questions in yellow
LeChStan Jun 16, 2024
efd0ed3
modified example to use the diffw = Random.rand(Bool, n) * 0.6 .+ 0.3…
LeChStan Jul 3, 2024
ff5afa7
This version should perform updates to pseudocosts without extra effo…
LeChStan Jul 3, 2024
0c53bfc
changed FrankWolfeNode to include additional information and added co…
LeChStan Jul 3, 2024
400063c
added SparseArrays
LeChStan Jul 3, 2024
069cd5d
changed Bonbo.set_root! to also have the default information used by …
LeChStan Jul 3, 2024
b88555b
Modified example only to the extend needed to use pseudocost strategy
LeChStan Jul 3, 2024
2fabd32
Created a function pseudo_branch! to replace branch! in the case of p…
LeChStan Jul 10, 2024
7ebecb0
moved pseudos and branch_tracker sparse arrays to be created before c…
LeChStan Jul 10, 2024
e6768d8
passed pseudos and branch_tracker as function arguments to get_branch…
LeChStan Jul 10, 2024
0580269
Example where pseudocost decisions are made occasionaly (depends on t…
LeChStan Jul 10, 2024
0895df7
fixed some bugs and the pseudocost updates are now scaled.
LeChStan Jul 10, 2024
0d2cce3
Merge pull request #1 from LeChStan/modify_tree
LeChStan Jul 10, 2024
5c5cfbd
moved pseudo cost updates to also be made when pseudocost decisions a…
LeChStan Jul 11, 2024
f448a3d
made changes such that other branching_strategies continue to work.
LeChStan Jul 11, 2024
33590e6
changes to interface in order to match change to nodes
LeChStan Jul 13, 2024
c9f9c21
added distance to integer feasibility of parent node in order to allo…
LeChStan Jul 13, 2024
7ebb308
added another decision function (product) and made changes to how upd…
LeChStan Jul 13, 2024
79d0332
added a function to write out the content of results into 3 CSV files
LeChStan Aug 2, 2024
f422ecc
modified the result saving function to be less error prone
LeChStan Aug 4, 2024
4351da9
modified the decision functions for branching variable selection
LeChStan Aug 4, 2024
42f2417
Changes to perform updates for nodes to be discarded due to lb >= inc…
LeChStan Aug 28, 2024
d0d9040
poisson_reg run multiple examples with multiple strategies
LeChStan Aug 28, 2024
790f80e
node changed to see if a node gets pruned by strong convexity before …
LeChStan Aug 28, 2024
91752b2
hierarchical pseudobranching strategy
LeChStan Aug 28, 2024
b61d3dc
Merge pull request #2 from LeChStan/hierachy_branching
LeChStan Aug 28, 2024
5cd06cd
some fixes to make code run
LeChStan Aug 28, 2024
338546e
gradient based branching
LeChStan Aug 31, 2024
d1c52ba
alter
LeChStan Sep 17, 2024
389ae3f
alternative branching strategies
LeChStan Sep 17, 2024
9864b2a
examples modified to run multiple strategies
LeChStan Sep 17, 2024
77625bd
Deleted anything non src related to enable future merge of new branch…
LeChStan Sep 18, 2024
28f2b78
removed function which is used to save experiments from non experimen…
LeChStan Sep 18, 2024
6342af4
deleted mps-example copy for pseudocost experiments of non experiment…
LeChStan Sep 18, 2024
6274937
removed a pdf from branch
LeChStan Sep 18, 2024
fa3f29b
bug fixes for gradient (+) most infeasible strategy
LeChStan Sep 18, 2024
f153257
added all Boscia branching strategies with the exception of strong br…
LeChStan Sep 18, 2024
a7dc57f
removed CSV, DataFrames from non experiment branch
LeChStan Sep 18, 2024
be486bc
removed unwanted whitespace
LeChStan Sep 18, 2024
b519432
removing debug prints etc.
LeChStan Sep 25, 2024
eef7f20
Readability change
LeChStan Sep 26, 2024
cc4a8a4
changes made for code readability and simplicity.
LeChStan Sep 26, 2024
242794c
moving pseudocost storage and branch_tracker for pseudocost strategy …
LeChStan Oct 2, 2024
0a6690a
Modified pseudocost branching to allow multiple alternative strats
LeChStan Oct 2, 2024
1cc243c
bug fix in pseudo branching mutable struct creation
LeChStan Oct 2, 2024
753be8d
hierarchy branching change
LeChStan Oct 23, 2024
85f22eb
added best minimum decision function for pseudocost strategies
LeChStan Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions examples/HiGHS_example_PSEUDO_COST.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Boscia
using FrankWolfe
using Random
using HiGHS
using LinearAlgebra
import MathOptInterface

const MOI = MathOptInterface

n = 6

const diffw = 0.5 * ones(n)
LeChStan marked this conversation as resolved.
Show resolved Hide resolved
o = HiGHS.Optimizer()

MOI.set(o, MOI.Silent(), true)

x = MOI.add_variables(o, n)

for xi in x
MOI.add_constraint(o, xi, MOI.GreaterThan(0.0))
MOI.add_constraint(o, xi, MOI.LessThan(1.0))
MOI.add_constraint(o, xi, MOI.ZeroOne())
end
lmo = Boscia.MathOptBLMO(o)

function f(x)
return 0.5 * sum((x .- diffw) .^ 2)
end

function grad!(storage, x)
@. storage = x - diffw
end
#pseudos = Dict{Int,Array{Float64}}(idx=>zeros(2) for idx in Boscia.get_integer_variables(lmo))
#branch_tracker = Dict{Int, Float64}(idx=> 0 for idx in Boscia.get_integer_variables(lmo))
iterations_stable = 1::Int
x, _, result = Boscia.solve(f, grad!, lmo, branching_strategy=Boscia.PSEUDO_COST(iterations_stable,false, lmo), verbose=true)
66 changes: 66 additions & 0 deletions examples/portfolio_pseudocost.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Boscia
using FrankWolfe
using Test
using Random
using SCIP
using LinearAlgebra
import MathOptInterface
const MOI = MathOptInterface

# seed = 0x946d4b7835e92ffa takes 90 minutes to solve! -> not anymore
seed = 0x946d4b7835e92ffa
Random.seed!(seed)

n = 30
const ri = rand(n)
const ai = rand(n)
const Ωi = rand(Float64)
const bi = sum(ai)
Ai = randn(n, n)
Ai = Ai' * Ai
const Mi = (Ai + Ai') / 2
@assert isposdef(Mi)


@testset "Buchheim et. al. example" begin
o = SCIP.Optimizer()
MOI.set(o, MOI.Silent(), true)
MOI.empty!(o)
x = MOI.add_variables(o, n)
I = collect(1:n) #rand(1:n0, Int64(floor(n0/2)))
for i in 1:n
MOI.add_constraint(o, x[i], MOI.GreaterThan(0.0))
if i in I
MOI.add_constraint(o, x[i], MOI.Integer())
end
end
MOI.add_constraint(
o,
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ai, x), 0.0),
MOI.LessThan(bi),
)
MOI.add_constraint(
o,
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(n), x), 0.0),
MOI.GreaterThan(1.0),
)
lmo = Boscia.MathOptBLMO(o)

function f(x)
return 1 / 2 * Ωi * dot(x, Mi, x) - dot(ri, x)
end
function grad!(storage, x)
mul!(storage, Mi, x, Ωi, 0)
storage .-= ri
return storage
end

depth = 5
heu = Boscia.Heuristic((tree, blmo, x) -> Boscia.follow_gradient_heuristic(tree,blmo,x, depth), 0.2, :follow_gradient)
heuristics = [heu]
# heuristics = []
iterations_stable = 3::Int# how many times until we consider a pseudocost as stable
LeChStan marked this conversation as resolved.
Show resolved Hide resolved
x, _, result = Boscia.solve(f, grad!, lmo, branching_strategy=Boscia.PSEUDO_COST(iterations_stable,false, lmo), verbose=true, time_limit=600, custom_heuristics=heuristics)
@test dot(ai, x) <= bi + 1e-2
@test f(x) <= f(result[:raw_solution]) + 1e-6
end
Binary file added in_work_master_thesis_branching_Leon_Stanzel.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions src/Boscia.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ include("callbacks.jl")
include("problem.jl")
include("heuristics.jl")
include("strong_branching.jl")
include("pseudo_branching.jl")
include("utilities.jl")
include("interface.jl")
include("managed_blmo.jl")
Expand Down
207 changes: 207 additions & 0 deletions src/pseudo_branching.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
struct PSEUDO_COST{BLMO<:BoundedLinearMinimizationOracle} <: Bonobo.AbstractBranchStrategy
iterations_until_stable::Int
stable::Bool
bounded_lmo::BLMO
end

# """ Function that keeps track of which branching candidates are stable """
# function is_stable(idx::Int, branching::PSEUDO_COST{BLMO})
# local branch_tracker = Dict{Int, Float64}(idx=> 0 for idx in Boscia.get_integer_variables(branching.bounded_lmo))
# if branch_tracker[idx] >= branching.iterations_until_stable
# return true
# else
# return false
# end
# end


function pseudo_weight_update!(
tree::Bonobo.BnBTree,
node::Bonobo.AbstractNode,
idx::Int,
values,
pseudos::Dict{Int,Array{Float64}},
branch_tracker::Dict{Int, Int},
branching::PSEUDO_COST{BLMO}
) where BLMO <: BoundedLinearMinimizationOracle
@assert !isempty(node.active_set)
active_set = copy(node.active_set)
LeChStan marked this conversation as resolved.
Show resolved Hide resolved
empty!(active_set)
current_dual_gap = node.dual_gap# if this is a fw_node this should be the fw_dual_gap

fx = floor(values[idx])
# create LMO
boundsLeft = copy(node.local_bounds)
if haskey(boundsLeft.upper_bounds, idx)
delete!(boundsLeft.upper_bounds, idx)
end
push!(boundsLeft.upper_bounds, (idx => fx))
build_LMO(
branching.bounded_lmo,
tree.root.problem.integer_variable_bounds,
boundsLeft,
Bonobo.get_branching_indices(tree.root),
)
status = check_feasibility(branching.bounded_lmo)
if status == OPTIMAL
empty!(active_set)
for (λ, v) in node.active_set
if v[idx] <= values[idx]
push!(active_set, ((λ, v)))
end
end
@assert !isempty(active_set)
FrankWolfe.active_set_renormalize!(active_set)
x, primal, dual_gap, active_set
_, _, primal_relaxed, dual_gap_relaxed, _ =
FrankWolfe.blended_pairwise_conditional_gradient(
tree.root.problem.f,
tree.root.problem.g,
branching.bounded_lmo,
active_set,
verbose=false,
epsilon=branching.solving_epsilon,
max_iteration=5,
)
left_update = current_dual_gap - dual_gap_relaxed
else
@debug "Left non-optimal status $(status)"
left_update = Inf
end

#right node: x_i >= floor(̂x_i)
cx = ceil(values[idx])
boundsRight = copy(node.local_bounds)
if haskey(boundsRight.lower_bounds, idx)
delete!(boundsRight.lower_bounds, idx)
end
push!(boundsRight.lower_bounds, (idx => cx))
build_LMO(
branching.bounded_lmo,
tree.root.problem.integer_variable_bounds,
boundsRight,
Bonobo.get_branching_indices(tree.root),
)
status = check_feasibility(branching.bounded_lmo)
if status == OPTIMAL
empty!(active_set)
for (λ, v) in node.active_set
if v[idx] >= values[idx]
push!(active_set, (λ, v))
end
end
if isempty(active_set)
@show values[idx]
@show length(active_set)
@info [active_set.atoms[idx] for idx in eachindex(active_set)]
error("Empty active set, unreachable")
end
FrankWolfe.active_set_renormalize!(active_set)
_, _, primal_relaxed, dual_gap_relaxed, _ =
FrankWolfe.blended_pairwise_conditional_gradient(
tree.root.problem.f,
tree.root.problem.g,
branching.bounded_lmo,
active_set,
verbose=false,
epsilon=branching.solving_epsilon,
max_iteration=5,
)
right_update = current_dual_gap - dual_gap_relaxed
else
@debug "Right non-optimal status $(status)"
right_update = Inf
end
# reset LMO
build_LMO(
branching.bounded_lmo,
tree.root.problem.integer_variable_bounds,
node.local_bounds,
Bonobo.get_branching_indices(tree.root),
)
pseudos[idx][1] = update_avg(left_update, pseudos[idx][1], branch_tracker[idx])
pseudos[idx][2] = update_avg(right_update, pseudos[idx][2], branch_tracker[idx])
branch_tracker[idx] += 1
end

function best_pseudo_choice(# currently not in use. To be implemented later in order to improve readability
tree::Bonobo.BnBTree,
)
branching_candidates = Bonobo.get_branching_indices(tree.root)
return argmax(map(idx-> maximum(pseudos[idx]), branching_candidates))
end


"""
get_branching_variable(
tree::Bonobo.BnBTree,
branching::PSEUDO_COST,
node::Bonobo.AbstractNode
)

Get branching variable using Pseudocost branching after costs have stabilized.
Prior to stabilization an adaptation of the Bonobo MOST_INFEASIBLE is used.

"""
function Bonobo.get_branching_variable(
tree::Bonobo.BnBTree,
branching::PSEUDO_COST{BLMO},
node::Bonobo.AbstractNode,
) where BLMO <: BoundedLinearMinimizationOracle
#the following should create the dictionaries only on first function call and then use existing ones
local pseudos = Dict{Int,Array{Float64}}(idx=>zeros(2) for idx in Boscia.get_integer_variables(branching.bounded_lmo))
local branch_tracker = Dict{Int, Int}(idx=> 0 for idx in Boscia.get_integer_variables(branching.bounded_lmo))
local call_tracker = 0

best_idx = -1
all_stable = true
for idx in Bonobo.get_branching_indices(tree.root)
if branch_tracker[idx] >= branching.iterations_until_stable
all_stable = true
else
all_stable = false
break
end
end
if !all_stable# THEN Use Most Infeasible
values = Bonobo.get_relaxed_values(tree, node)
max_distance_to_feasible = 0.0
for i in Bonobo.get_branching_indices(tree.root)
value = values[i]
if !Bonobo.is_approx_feasible(tree, value)
distance_to_feasible = Bonobo.get_distance_to_feasible(tree, value)
if distance_to_feasible > max_distance_to_feasible
best_idx = i
max_distance_to_feasible = distance_to_feasible
end
end
end
if best_idx != -1
pseudo_weight_update!(tree, node, best_idx, values, pseudos, branch_tracker, branching)
end
return best_idx
else
println("Pseudos are stable")
# Pseudocosts have stabilized
call_tracker +=1
println("pseudocosts have stabilized ", call_tracker)

branching_candidates = Bonobo.get_branching_indices(tree.root)
best_idx = argmax(map(idx-> maximum(pseudos[idx]), branching_candidates))# argmax randomly chosen and to be replaced later
return best_idx
end
end

function update_avg(new_val::Float64, avg::Float64, N::Int)
# N is the number of values used to compute the current avg
# avg is the current average
# new_val is the value that the current average has to be updated with
if N > 1
return 1/(N+1) * (N * avg + new_val)
else
return new_val
end
end
#pseudos = Dict{Int,Array{Float64}}(i=>zeros(2) for idx in get_integer_variables(blmo))

#branch_tracker = Dict{Int, Int}(idx-> 0 for idx in get_integer_variables(blmo))
1 change: 1 addition & 0 deletions src/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,4 @@ _value_to_print(::Bonobo.BestFirstSearch) = "Move best bound"
_value_to_print(::PartialStrongBranching) = "Partial strong branching"
_value_to_print(::HybridStrongBranching) = "Hybrid strong branching"
_value_to_print(::Bonobo.MOST_INFEASIBLE) = "Most infeasible"
_value_to_print(::PSEUDO_COST) = "Pseudo Cost"