Skip to content

Commit 632ca6c

Browse files
authored
Branching with non-trivial domain oracle (#207)
User has to provide a domain feasible point respecting the new bound constraints. We solve the projection problem.
1 parent 297ccfb commit 632ca6c

12 files changed

+240
-50
lines changed

examples/oed_utils.jl

+68-9
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ m - number of experiments.
66
fusion - boolean deiciding whether we build the fusion or standard problem.
77
corr - boolean deciding whether we build the independent or correlated data.
88
"""
9-
function build_data(m)
9+
function build_data(m, n)
1010
# set up
11-
n = Int(floor(m/10))
12-
1311
B = rand(m,n)
1412
B = B'*B
1513
@assert isposdef(B)
@@ -22,7 +20,7 @@ function build_data(m)
2220
u = floor(N/3)
2321
ub = rand(1.0:u, m)
2422

25-
return A, n, N, ub
23+
return A, N, ub
2624
end
2725

2826
"""
@@ -155,10 +153,13 @@ end
155153
"""
156154
Find n linearly independent rows of A to build the starting point.
157155
"""
158-
function linearly_independent_rows(A)
156+
function linearly_independent_rows(A; u=fill(1, size(A, 1)))
159157
S = []
160158
m, n = size(A)
161159
for i in 1:m
160+
if iszero(u[i])
161+
continue
162+
end
162163
S_i= vcat(S, i)
163164
if rank(A[S_i,:])==length(S_i)
164165
S=S_i
@@ -178,8 +179,22 @@ function add_to_min(x, u)
178179
if x[perm[i]] < u[perm[i]]
179180
x[perm[i]] += 1
180181
break
181-
else
182-
continue
182+
end
183+
end
184+
return x
185+
end
186+
187+
"""
188+
We want to add to the smallest value of x while respecting the upper bounds u.
189+
In constrast to the add_to_min function, we do not require x to have coordinates at zero.
190+
"""
191+
function add_to_min2(x,u)
192+
perm = sortperm(x)
193+
194+
for i in perm
195+
if x[i] < u[i]
196+
x[i] += 1
197+
break
183198
end
184199
end
185200
return x
@@ -193,8 +208,6 @@ function remove_from_max(x)
193208
if x[perm[i]] > 1
194209
x[perm[i]] -= 1
195210
break
196-
else
197-
continue
198211
end
199212
end
200213
return x
@@ -237,6 +250,52 @@ function build_start_point(A, N, ub)
237250
return x, active_set, S
238251
end
239252

253+
"""
254+
Build domain feasible point for any node.
255+
The point has to be domain feasible as well as respect the node bounds.
256+
If no such point can be constructed, return nothing.
257+
"""
258+
function build_domain_point_function(domain_oracle, A, N, int_vars, initial_lb, initial_ub)
259+
return function domain_point(local_bounds)
260+
lb = initial_lb
261+
ub = initial_ub
262+
for idx in int_vars
263+
if haskey(local_bounds.lower_bounds, idx)
264+
lb[idx] = max(initial_lb[idx], local_bounds.lower_bounds[idx])
265+
end
266+
if haskey(local_bounds.upper_bounds, idx)
267+
ub[idx] = min(initial_ub[idx], local_bounds.upper_bounds[idx])
268+
end
269+
end
270+
# Node itself infeasible
271+
if sum(lb) > N
272+
return nothing
273+
end
274+
# No intersection between node and domain
275+
if !domain_oracle(ub)
276+
return nothing
277+
end
278+
x = lb
279+
280+
S = linearly_independent_rows(A, u=.!(iszero.(ub)))
281+
while sum(x) <= N
282+
if sum(x) == N
283+
if domain_oracle(x)
284+
return x
285+
else
286+
return nothing
287+
end
288+
end
289+
if !iszero(x[S]-ub[S])
290+
y = add_to_min2(x[S], ub[S])
291+
x[S] = y
292+
else
293+
x = add_to_min2(x, ub)
294+
end
295+
end
296+
end
297+
end
298+
240299
"""
241300
Create first incumbent for Boscia and custom BB in a greedy fashion.
242301
"""

examples/optimal_experiment_design.jl

+13-6
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,25 @@ include("oed_utils.jl")
4242

4343

4444
m = 50
45+
n = Int(floor(m / 10))
4546
verbose = true
4647

4748
## A-Optimal Design Problem
4849
@testset "A-Optimal Design" begin
4950

50-
Ex_mat, n, N, ub = build_data(m)
51+
Ex_mat, N, ub = build_data(m, n)
5152

5253
g, grad! = build_a_criterion(Ex_mat, build_safe=false)
5354
blmo = build_blmo(m, N, ub)
5455
heu = Boscia.Heuristic(Boscia.rounding_hyperplane_heuristic, 0.7, :hyperplane_aware_rounding)
5556
domain_oracle = build_domain_oracle(Ex_mat, n)
57+
domain_point = build_domain_point_function(domain_oracle, Ex_mat, N, collect(1:m), fill(0.0, m), ub)
5658

5759
# precompile
5860
line_search = FrankWolfe.MonotonicGenericStepsize(FrankWolfe.Adaptive(), domain_oracle)
5961
x0, active_set = build_start_point(Ex_mat, N, ub)
6062
z = greedy_incumbent(Ex_mat, N, ub)
61-
x, _, _ = Boscia.solve(g, grad!, blmo, active_set=active_set, start_solution=z, time_limit=10, verbose=false, domain_oracle=domain_oracle, custom_heuristics=[heu], line_search=line_search)
63+
x, _, _ = Boscia.solve(g, grad!, blmo, active_set=active_set, start_solution=z, time_limit=10, verbose=false, domain_oracle=domain_oracle, find_domain_point=domain_point, custom_heuristics=[heu], line_search=line_search)
6264

6365
# proper run with MGLS and Adaptive
6466
line_search = FrankWolfe.MonotonicGenericStepsize(FrankWolfe.Adaptive(), domain_oracle)
@@ -72,6 +74,7 @@ verbose = true
7274
start_solution=z,
7375
verbose=verbose,
7476
domain_oracle=domain_oracle,
77+
find_domain_point=domain_point,
7578
custom_heuristics=[heu],
7679
line_search=line_search,
7780
)
@@ -89,29 +92,31 @@ verbose = true
8992
start_solution=z,
9093
verbose=verbose,
9194
domain_oracle=domain_oracle,
95+
find_domain_point=domain_point,
9296
custom_heuristics=[heu],
9397
line_search=line_search,
9498
)
9599

96-
@test result_s[:dual_bound] <= g(x) + 1e-4
97-
@test result[:dual_bound] <= g(x_s) + 1e-4
100+
@test result_s[:dual_bound] <= g(x) + 1e-3
101+
@test result[:dual_bound] <= g(x_s) + 1e-3
98102
@test isapprox(g(x), g(x_s), atol=1e-3)
99103
end
100104

101105
## D-Optimal Design Problem
102106
@testset "D-optimal Design" begin
103-
Ex_mat, n, N, ub = build_data(m)
107+
Ex_mat, N, ub = build_data(m, n)
104108

105109
g, grad! = build_d_criterion(Ex_mat, build_safe=false)
106110
blmo = build_blmo(m, N, ub)
107111
heu = Boscia.Heuristic(Boscia.rounding_hyperplane_heuristic, 0.7, :hyperplane_aware_rounding)
108112
domain_oracle = build_domain_oracle(Ex_mat, n)
113+
domain_point = build_domain_point_function(domain_oracle, Ex_mat, N, collect(1:m), fill(0.0, m), ub)
109114

110115
# precompile
111116
line_search = FrankWolfe.MonotonicGenericStepsize(FrankWolfe.Adaptive(), domain_oracle)
112117
x0, active_set = build_start_point(Ex_mat, N, ub)
113118
z = greedy_incumbent(Ex_mat, N, ub)
114-
x, _, _ = Boscia.solve(g, grad!, blmo, active_set=active_set, start_solution=z, time_limit=10, verbose=false, domain_oracle=domain_oracle, custom_heuristics=[heu], line_search=line_search)
119+
x, _, _ = Boscia.solve(g, grad!, blmo, active_set=active_set, start_solution=z, time_limit=10, verbose=false, domain_oracle=domain_oracle, find_domain_point=domain_point, custom_heuristics=[heu], line_search=line_search)
115120

116121
# proper run with MGLS and Adaptive
117122
line_search = FrankWolfe.MonotonicGenericStepsize(FrankWolfe.Adaptive(), domain_oracle)
@@ -125,6 +130,7 @@ end
125130
start_solution=z,
126131
verbose=verbose,
127132
domain_oracle=domain_oracle,
133+
find_domain_point=domain_point,
128134
custom_heuristics=[heu],
129135
line_search=line_search,
130136
)
@@ -142,6 +148,7 @@ end
142148
start_solution=z,
143149
verbose=verbose,
144150
domain_oracle=domain_oracle,
151+
find_domain_point=domain_point,
145152
custom_heuristics=[heu],
146153
line_search=line_search,
147154
)

examples/strong_branching_portfolio.jl

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ using Boscia
22
using FrankWolfe
33
using Test
44
using Random
5-
using SCIP
65
using LinearAlgebra
76
using Distributions
87
import MathOptInterface
@@ -28,7 +27,7 @@ const Mi = (Ai + Ai') / 2
2827
@assert isposdef(Mi)
2928

3029
function prepare_portfolio_lmo()
31-
o = SCIP.Optimizer()
30+
o = HiGHS.Optimizer()
3231
MOI.set(o, MOI.Silent(), true)
3332
MOI.empty!(o)
3433
x = MOI.add_variables(o, n)

src/Boscia.jl

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using FrankWolfe
44
import FrankWolfe: compute_extreme_point
55
export compute_extreme_point
66
using Random
7+
using LinearAlgebra
78
import Bonobo
89
using Printf
910
using Dates

src/MOI_bounded_oracle.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,8 @@ function solve(
631631
strong_convexity=0.0,
632632
sharpness_constant = 0.0,
633633
sharpness_exponent = Inf,
634-
domain_oracle=x -> true,
634+
domain_oracle=_trivial_domain,
635+
find_domain_point= _trivial_domain_point,
635636
start_solution=nothing,
636637
fw_verbose=false,
637638
use_shadow_set=true,
@@ -671,6 +672,7 @@ function solve(
671672
sharpness_constant=sharpness_constant,
672673
sharpness_exponent=sharpness_exponent,
673674
domain_oracle=domain_oracle,
675+
find_domain_point=find_domain_point,
674676
start_solution=start_solution,
675677
fw_verbose=fw_verbose,
676678
use_shadow_set=use_shadow_set,

src/heuristics.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function run_heuristics(tree, x, heuristic_list; rng=Random.GLOBAL_RNG)
5353
min_val = Inf
5454
min_idx = -1
5555
for (i, x_heu) in enumerate(list_x_heu)
56-
feasible = check_feasibility ? is_linear_feasible(tree.root.problem.tlmo, x_heu) && is_integer_feasible(tree, x_heu) : false
56+
feasible = check_feasibility ? is_linear_feasible(tree.root.problem.tlmo, x_heu) && is_integer_feasible(tree, x_heu) && tree.root.options[:domain_oracle](x_heu) : false
5757
if feasible
5858
val = tree.root.problem.f(x_heu)
5959
if val < min_val

src/interface.jl

+11-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ sharpness_constant - the constant M > 0 for (θ, M)-sharpness.
4949
where X* is the set minimizer of f.
5050
sharpness_exponent - the exponent θ ∈ [0, 1/2] for (θ, M)-sharpness.
5151
domain_oracle - For a point x: returns true if x is in the domain of f, else false. Per default is true.
52-
In case of the non trivial domain oracle, the starting point has to be feasible for f. Also, depending
52+
In case of the non trivial domain oracle, the starting point has to be feasible for f. Additionally,
53+
the user has to provide a function `domain_point`, see below. Also, depending
5354
on the Line Search method, you might have to provide the domain oracle to it, too.
55+
find_domain_point - Given the current node bounds return a domain feasible point respecting the bounds.
56+
If no such point can be found, return nothing.
5457
start_solution - initial solution to start with an incumbent
5558
fw_verbose - if true, FrankWolfe logs are printed
5659
use_shadow_set - The shadow set is the set of discarded vertices which is inherited by the children nodes.
@@ -98,7 +101,8 @@ function solve(
98101
strong_convexity=0.0,
99102
sharpness_constant = 0.0,
100103
sharpness_exponent = Inf,
101-
domain_oracle=x -> true,
104+
domain_oracle=_trivial_domain,
105+
find_domain_point= _trivial_domain_point,
102106
start_solution=nothing,
103107
fw_verbose=false,
104108
use_shadow_set=true,
@@ -146,6 +150,10 @@ function solve(
146150

147151
global_bounds = build_global_bounds(blmo, integer_variables)
148152

153+
if typeof(domain_oracle) != typeof(_trivial_domain) && typeof(find_domain_point) == typeof(_trivial_domain_point)
154+
@warn "For a non trivial domain oracle, please provide the DOMAIN POINT function. Otherwise, Boscia might not converge."
155+
end
156+
149157
v = []
150158
if active_set === nothing
151159
direction = collect(1.0:n)
@@ -201,6 +209,7 @@ function solve(
201209
global_tightenings=IntegerBounds(),
202210
options=Dict{Symbol,Any}(
203211
:domain_oracle => domain_oracle,
212+
:find_domain_point => find_domain_point,
204213
:dual_gap => dual_gap,
205214
:dual_gap_decay_factor => dual_gap_decay_factor,
206215
:dual_tightening => dual_tightening,

src/managed_blmo.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ function solve(
271271
strong_convexity=0.0,
272272
sharpness_constant = 0.0,
273273
sharpness_exponent = Inf,
274-
domain_oracle=x -> true,
274+
domain_oracle=_trivial_domain,
275+
find_domain_point= _trivial_domain_point,
275276
start_solution=nothing,
276277
fw_verbose=false,
277278
use_shadow_set=true,
@@ -313,6 +314,7 @@ function solve(
313314
sharpness_constant=sharpness_constant,
314315
sharpness_exponent=sharpness_exponent,
315316
domain_oracle=domain_oracle,
317+
find_domain_point=find_domain_point,
316318
start_solution=start_solution,
317319
fw_verbose=fw_verbose,
318320
use_shadow_set=use_shadow_set,

0 commit comments

Comments
 (0)