Skip to content

Commit fa830c8

Browse files
authored
Simplex Blmo (#155)
1 parent 276db53 commit fa830c8

7 files changed

+223
-79
lines changed

examples/approx_planted_point.jl

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ using Distributions
99
import MathOptInterface
1010
const MOI = MathOptInterface
1111

12+
include("cube_blmo.jl")
13+
1214
n = 20
1315
diffi = Random.rand(Bool, n) * 0.6 .+ 0.3
1416

@@ -47,7 +49,7 @@ diffi = Random.rand(Bool, n) * 0.6 .+ 0.3
4749
push!(bounds, (i, 0.0), :greaterthan)
4850
push!(bounds, (i, 1.0), :lessthan)
4951
end
50-
blmo = Boscia.CubeBLMO(n, int_vars, bounds)
52+
blmo = CubeBLMO(n, int_vars, bounds)
5153

5254
x, _, result = Boscia.solve(f, grad!, blmo, verbose=true)
5355

@@ -110,7 +112,7 @@ end
110112
push!(bounds, (i, 0.0), :greaterthan)
111113
push!(bounds, (i, 1.0), :lessthan)
112114
end
113-
blmo = Boscia.CubeBLMO(n, int_vars, bounds)
115+
blmo = CubeBLMO(n, int_vars, bounds)
114116

115117
x, _, result = Boscia.solve(f, grad!, blmo, verbose=true)
116118

+30-72
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
## How to implement the BLMO Interface using the cube as an example
2+
using Boscia
3+
using Bonobo
4+
using Dates
15

26
"""
37
CubeBLMO
48
59
A Bounded Linear Minimization Oracle over a cube.
610
"""
7-
mutable struct CubeBLMO <: BoundedLinearMinimizationOracle
11+
mutable struct CubeBLMO <: Boscia.BoundedLinearMinimizationOracle
812
n::Int
913
int_vars::Vector{Int}
10-
bounds::IntegerBounds
14+
bounds::Boscia.IntegerBounds
1115
solving_time::Float64
1216
end
1317

@@ -16,7 +20,7 @@ CubeBLMO(n, int_vars, bounds) = CubeBLMO(n, int_vars, bounds, 0.0)
1620
## Necessary
1721

1822
# computing an extreme point for the cube amounts to checking the sign of the gradient
19-
function compute_extreme_point(blmo::CubeBLMO, d; kwargs...)
23+
function Boscia.compute_extreme_point(blmo::CubeBLMO, d; kwargs...)
2024
time_ref = Dates.now()
2125
v = zeros(length(d))
2226
for i in eachindex(d)
@@ -28,8 +32,8 @@ end
2832

2933
##
3034

31-
function build_global_bounds(blmo::CubeBLMO, integer_variables)
32-
global_bounds = IntegerBounds()
35+
function Boscia.build_global_bounds(blmo::CubeBLMO, integer_variables)
36+
global_bounds = Boscia.IntegerBounds()
3337
for i in 1:blmo.n
3438
if i in integer_variables
3539
push!(global_bounds, (i, blmo.bounds[i, :lessthan]), :lessthan)
@@ -41,42 +45,34 @@ end
4145

4246

4347
## Read information from problem
44-
function get_list_of_variables(blmo::CubeBLMO)
48+
function Boscia.get_list_of_variables(blmo::CubeBLMO)
4549
return blmo.n, collect(1:blmo.n)
4650
end
4751

4852
# Get list of integer variables, respectively.
49-
function get_integer_variables(blmo::CubeBLMO)
53+
function Boscia.get_integer_variables(blmo::CubeBLMO)
5054
return blmo.int_vars
5155
end
5256

53-
function get_int_var(blmo::CubeBLMO, cidx)
57+
function Boscia.get_int_var(blmo::CubeBLMO, cidx)
5458
return cidx
5559
end
5660

57-
function get_lower_bound_list(blmo::CubeBLMO)
61+
function Boscia.get_lower_bound_list(blmo::CubeBLMO)
5862
return keys(blmo.bounds.lower_bounds)
5963
end
6064

61-
function get_upper_bound_list(blmo::CubeBLMO)
65+
function Boscia.get_upper_bound_list(blmo::CubeBLMO)
6266
return keys(blmo.bounds.upper_bounds)
6367
end
6468

65-
function get_bound(blmo::CubeBLMO, c_idx, sense::Symbol)
69+
function Boscia.get_bound(blmo::CubeBLMO, c_idx, sense::Symbol)
6670
@assert sense == :lessthan || sense == :greaterthan
6771
return blmo[c_idx, sense]
6872
end
6973

70-
#function get_lower_bound(blmo::CubeBLMO, c_idx)
71-
# return blmo.bounds[c_idx, :greaterthan]
72-
#end
73-
74-
#function get_upper_bound(blmo::CubeBLMO, c_idx)
75-
# return blmo.bounds[c_idx, :lessthan]
76-
#end
77-
7874
## Changing the bounds constraints.
79-
function set_bound!(blmo::CubeBLMO, c_idx, value, sense::Symbol)
75+
function Boscia.set_bound!(blmo::CubeBLMO, c_idx, value, sense::Symbol)
8076
if sense == :greaterthan
8177
blmo.bounds.lower_bounds[c_idx] = value
8278
elseif sense == :lessthan
@@ -86,29 +82,29 @@ function set_bound!(blmo::CubeBLMO, c_idx, value, sense::Symbol)
8682
end
8783
end
8884

89-
function delete_bounds!(blmo::CubeBLMO, cons_delete)
85+
function Boscia.delete_bounds!(blmo::CubeBLMO, cons_delete)
9086
# For the cube this shouldn't happen! Otherwise we get unbounded!
9187
if !isempty(cons_delete)
9288
error("Trying to delete bounds of the cube!")
9389
end
9490
end
9591

96-
function add_bound_constraint!(blmo::CubeBLMO, key, value, sense::Symbol)
92+
function Boscia.add_bound_constraint!(blmo::CubeBLMO, key, value, sense::Symbol)
9793
# Should not be necessary
9894
return error("Trying to add bound constraints of the cube!")
9995
end
10096

10197
## Checks
10298

103-
function is_constraint_on_int_var(blmo::CubeBLMO, c_idx, int_vars)
99+
function Boscia.is_constraint_on_int_var(blmo::CubeBLMO, c_idx, int_vars)
104100
return c_idx in int_vars
105101
end
106102

107-
function is_bound_in(blmo::CubeBLMO, c_idx, bounds)
103+
function Boscia.is_bound_in(blmo::CubeBLMO, c_idx, bounds)
108104
return haskey(bounds, c_idx)
109105
end
110106

111-
function is_linear_feasible(blmo::CubeBLMO, v::AbstractVector)
107+
function Boscia.is_linear_feasible(blmo::CubeBLMO, v::AbstractVector)
112108
for i in eachindex(v)
113109
if !(
114110
blmo.bounds[i, :greaterthan] v[i] + 1e-6 ||
@@ -123,16 +119,15 @@ function is_linear_feasible(blmo::CubeBLMO, v::AbstractVector)
123119
return true
124120
end
125121

126-
function has_integer_constraint(blmo::CubeBLMO, idx)
122+
function Boscia.has_integer_constraint(blmo::CubeBLMO, idx)
127123
return idx in blmo.int_vars
128124
end
129125

130126

131-
132127
###################### Optional
133128
## Safety Functions
134129

135-
function build_LMO_correct(blmo::CubeBLMO, node_bounds)
130+
function Boscia.build_LMO_correct(blmo::CubeBLMO, node_bounds)
136131
for key in keys(node_bounds.lower_bounds)
137132
if !haskey(blmo.bounds, (key, :greaterthan)) ||
138133
blmo.bounds[key, :greaterthan] != node_bounds[key, :greaterthan]
@@ -148,60 +143,23 @@ function build_LMO_correct(blmo::CubeBLMO, node_bounds)
148143
return true
149144
end
150145

151-
function check_feasibility(blmo::CubeBLMO)
146+
function Boscia.check_feasibility(blmo::CubeBLMO)
152147
for i in 1:blmo.n
153148
if !haskey(blmo.bounds, (i, :greaterthan)) || !haskey(blmo.bounds, (i, :lessthan))
154-
return UNBOUNDED
149+
return Boscia.UNBOUNDED
155150
end
156151
if blmo.bounds[i, :greaterthan] > blmo.bounds[i, :lessthan]
157-
return INFEASIBLE
152+
return Boscia.INFEASIBLE
158153
end
159154
end
160-
return OPTIMAL
155+
return Boscia.OPTIMAL
161156
end
162157

163-
function is_valid_split(tree::Bonobo.BnBTree, blmo::CubeBLMO, vidx::Int)
158+
function Boscia.is_valid_split(tree::Bonobo.BnBTree, blmo::CubeBLMO, vidx::Int)
164159
return blmo.bounds[vidx, :lessthan] != blmo.bounds[vidx, :greaterthan]
165160
end
166161

167162
## Logs
168-
function get_BLMO_solve_data(blmo::CubeBLMO)
163+
function Boscia.get_BLMO_solve_data(blmo::CubeBLMO)
169164
return blmo.solving_time, 0.0, 0.0
170-
end
171-
172-
########################################################################
173-
"""
174-
CubeSimpleBLMO{T}(lower_bounds, upper_bounds)
175-
176-
Hypercube with lower and upper bounds implementing the `SimpleBoundableLMO` interface.
177-
"""
178-
struct CubeSimpleBLMO <: SimpleBoundableLMO
179-
lower_bounds::Vector{Float64}
180-
upper_bounds::Vector{Float64}
181-
int_vars::Vector{Int}
182-
end
183-
184-
function bounded_compute_extreme_point(sblmo::CubeSimpleBLMO, d, lb, ub, int_vars; kwargs...)
185-
v = zeros(length(d))
186-
for i in eachindex(d)
187-
if i in int_vars
188-
idx = findfirst(x -> x == i, int_vars)
189-
v[i] = d[i] > 0 ? lb[idx] : ub[idx]
190-
else
191-
v[i] = d[i] > 0 ? sblmo.lower_bounds[i] : sblmo.upper_bounds[i]
192-
end
193-
end
194-
return v
195-
end
196-
197-
function is_linear_feasible(sblmo::CubeSimpleBLMO, v)
198-
for i in setdiff(eachindex(v), sblmo.int_vars)
199-
if !(sblmo.lower_bounds[i] v[i] + 1e-6 || !(v[i] - 1e-6 blmo.upper_bounds[i]))
200-
@debug(
201-
"Vertex entry: $(v[i]) Lower bound: $(blmo.bounds[i, :greaterthan]) Upper bound: $(blmo.bounds[i, :lessthan]))"
202-
)
203-
return false
204-
end
205-
end
206-
return true
207-
end
165+
end

src/Boscia.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ include("utilities.jl")
2727
include("interface.jl")
2828
include("managed_blmo.jl")
2929
include("MOI_bounded_oracle.jl")
30-
include("cube_blmo.jl")
30+
include("polytope_blmos.jl")
3131

3232
# For extensions
3333
if !isdefined(Base, :get_extension)

src/managed_blmo.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ function is_linear_feasible(blmo::ManagedBoundedLMO, v::AbstractVector)
174174
blmo.lower_bounds[i] v[int_var] + 1e-6 || !(v[int_var] - 1e-6 blmo.upper_bounds[i])
175175
)
176176
@debug(
177-
"Vertex entry: $(v[int_var]) Lower bound: $(blmo.lower_bounds[i]) Upper bound: $(blmo.upper_bounds[i]))"
177+
"Variable: $(int_var) Vertex entry: $(v[int_var]) Lower bound: $(blmo.lower_bounds[i]) Upper bound: $(blmo.upper_bounds[i]))"
178178
)
179179
return false
180180
end

src/polytope_blmos.jl

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
CubeSimpleBLMO{T}(lower_bounds, upper_bounds)
3+
4+
Hypercube with lower and upper bounds implementing the `SimpleBoundableLMO` interface.
5+
"""
6+
struct CubeSimpleBLMO <: SimpleBoundableLMO
7+
lower_bounds::Vector{Float64}
8+
upper_bounds::Vector{Float64}
9+
int_vars::Vector{Int}
10+
end
11+
12+
"""
13+
If the entry is positve, choose the lower bound. Else, choose the upper bound.
14+
"""
15+
function bounded_compute_extreme_point(sblmo::CubeSimpleBLMO, d, lb, ub, int_vars; kwargs...)
16+
v = zeros(length(d))
17+
for i in eachindex(d)
18+
if i in int_vars
19+
idx = findfirst(x -> x == i, int_vars)
20+
v[i] = d[i] > 0 ? lb[idx] : ub[idx]
21+
else
22+
v[i] = d[i] > 0 ? sblmo.lower_bounds[i] : sblmo.upper_bounds[i]
23+
end
24+
end
25+
return v
26+
end
27+
28+
function is_linear_feasible(sblmo::CubeSimpleBLMO, v)
29+
for i in setdiff(eachindex(v), sblmo.int_vars)
30+
if !(sblmo.lower_bounds[i] v[i] + 1e-6 || !(v[i] - 1e-6 blmo.upper_bounds[i]))
31+
@debug(
32+
"Vertex entry: $(v[i]) Lower bound: $(blmo.bounds[i, :greaterthan]) Upper bound: $(blmo.bounds[i, :lessthan]))"
33+
)
34+
return false
35+
end
36+
end
37+
return true
38+
end
39+
40+
"""
41+
ProbablitySimplexSimpleBLMO(N)
42+
43+
Scaled Probability Simplex: ∑ x = N.
44+
"""
45+
struct ProbabilitySimplexSimpleBLMO <: SimpleBoundableLMO
46+
N::Float64
47+
end
48+
49+
"""
50+
Assign the largest possible values to the entries corresponding to the smallest entries of d.
51+
"""
52+
function bounded_compute_extreme_point(sblmo::ProbabilitySimplexSimpleBLMO, d, lb, ub, int_vars; kwargs...)
53+
v = zeros(length(d))
54+
indices = collect(1:length(d))
55+
perm = sortperm(d)
56+
57+
# The lower bounds always have to be met.
58+
v[int_vars] = lb
59+
60+
for i in indices[perm]
61+
if i in int_vars
62+
idx = findfirst(x -> x == i, int_vars)
63+
v[i] += min(ub[idx]-lb[idx], sblmo.N - sum(v))
64+
else
65+
v[i] += N - sum(v)
66+
end
67+
end
68+
return v
69+
end
70+
71+
function is_linear_feasible(sblmo::ProbabilitySimplexSimpleBLMO, v)
72+
if sum(v .≥ 0) < length(v)
73+
@debug "v has negative entries: $(v)"
74+
return false
75+
end
76+
return isapprox(sum(v), sblmo.N, atol=1e-4, rtol=1e-2)
77+
end
78+
79+
"""
80+
UnitSimplexSimpleBLMO(N)
81+
82+
Scaled Unit Simplex: ∑ x ≤ N.
83+
"""
84+
struct UnitSimplexSimpleBLMO <: SimpleBoundableLMO
85+
N::Float64
86+
end
87+
88+
"""
89+
For all positive entries of d, assign the corresponding lower bound.
90+
For non-positive entries, assign largest possible value in increasing order.
91+
"""
92+
function bounded_compute_extreme_point(sblmo::UnitSimplexSimpleBLMO, d, lb, ub, int_vars; kwargs...)
93+
v = zeros(length(d))
94+
# The wloer bounds always have to be met.
95+
v[int_vars] = lb
96+
cont_vars = setdiff(collect(1:length(d)), int_vars)
97+
if !isempty(cont_vars)
98+
v[cont_vars] .= 0.0
99+
end
100+
101+
idx_neg = findall(x-> x <= 0, d)
102+
perm = sortperm(d[idx_neg])
103+
for i in idx_neg[perm]
104+
if i in int_vars
105+
idx = findfirst(x -> x == i, int_vars)
106+
v[i] += min(ub[idx]-lb[idx], sblmo.N - sum(v))
107+
else
108+
v[i] += N - sum(v)
109+
end
110+
end
111+
return v
112+
end
113+
114+
function is_linear_feasible(sblmo::UnitSimplexSimpleBLMO, v)
115+
if sum(v .≥ 0) < length(v)
116+
@debug "v has negative entries: $(v)"
117+
return false
118+
end
119+
return sum(v) sblmo.N + 1e-3
120+
end

0 commit comments

Comments
 (0)