11"""
2- BirkhoffLMO
2+ BirkhoffLMO(dim, int_vars; append_by_column=true, atol=1e-6, rtol=1e-3)
33
4- A bounded LMO for the Birkhoff polytope. This oracle computes an extreme point subject to
5- node-specific bounds on the integer variables.
4+ A bounded Linear Minimization Oracle (LMO) for the Birkhoff polytope. The oracle
5+ computes extreme points (permutation matrices) possibly under node-specific bound
6+ constraints on a subset of integer variables. It also supports mixed-integer
7+ variants, partial fixings (reduced maps), and in-face oracles used by DICG-like methods.
68"""
79mutable struct BirkhoffLMO <: FrankWolfe.LinearMinimizationOracle
810 append_by_column:: Bool
@@ -17,10 +19,14 @@ mutable struct BirkhoffLMO <: FrankWolfe.LinearMinimizationOracle
1719 updated_lmo:: Bool
1820 atol:: Float64
1921 rtol:: Float64
20- boscia_use:: Bool
2122end
2223
23- # return an integer-type BirkhoffLMO
24+ """
25+ BirkhoffLMO(dim, int_vars; append_by_column=true, atol=1e-6, rtol=1e-3)
26+
27+ Constructor for a mixed-integer Birkhoff LMO. All variables listed in
28+ `int_vars` are treated as integer with default bounds `[0,1]`.
29+ """
2430BirkhoffLMO (dim, int_vars; append_by_column= true , atol= 1e-6 , rtol= 1e-3 ) = BirkhoffLMO (
2531 append_by_column,
2632 dim,
@@ -34,10 +40,8 @@ BirkhoffLMO(dim, int_vars; append_by_column=true, atol=1e-6, rtol=1e-3) = Birkho
3440 true ,
3541 atol,
3642 rtol,
37- true ,
3843)
3944
40- # return a continuous BirkhoffLMO
4145BirkhoffLMO (dim; append_by_column= true , atol= 1e-6 , rtol= 1e-3 ) = BirkhoffLMO (
4246 append_by_column,
4347 dim,
@@ -51,15 +55,15 @@ BirkhoffLMO(dim; append_by_column=true, atol=1e-6, rtol=1e-3) = BirkhoffLMO(
5155 true ,
5256 atol,
5357 rtol,
54- false ,
5558)
5659
5760# # Necessary
5861
59- """
60- Computes the extreme point given an direction d, the current lower and upper bounds on the integer variables, and the set of integer variables.
61- """
62- function Boscia. compute_extreme_point (lmo:: BirkhoffLMO , d:: AbstractMatrix{T} ; kwargs... ) where {T}
62+ function FrankWolfe. compute_extreme_point (
63+ lmo:: BirkhoffLMO ,
64+ d:: AbstractMatrix{T} ;
65+ kwargs... ,
66+ ) where {T}
6367 n = lmo. dim
6468
6569 fixed_to_one_rows = lmo. fixed_to_one_rows
@@ -133,7 +137,11 @@ function Boscia.compute_extreme_point(lmo::BirkhoffLMO, d::AbstractMatrix{T}; kw
133137 return m
134138end
135139
136- function Boscia. compute_extreme_point (lmo:: BirkhoffLMO , d:: AbstractVector{T} ; kwargs... ) where {T}
140+ function FrankWolfe. compute_extreme_point (
141+ lmo:: BirkhoffLMO ,
142+ d:: AbstractVector{T} ;
143+ kwargs... ,
144+ ) where {T}
137145 n = lmo. dim
138146 d = lmo. append_by_column ? reshape (d, (n, n)) : transpose (reshape (d, (n, n)))
139147 m = Boscia. compute_extreme_point (lmo, d; kwargs... )
@@ -152,9 +160,6 @@ function Boscia.compute_extreme_point(lmo::BirkhoffLMO, d::AbstractVector{T}; kw
152160 return m
153161end
154162
155- """
156- Computes the extreme point given an direction d, the current lower and upper bounds on the integer variables, and the set of integer variables.
157- """
158163function FrankWolfe. compute_inface_extreme_point (
159164 lmo:: BirkhoffLMO ,
160165 direction:: AbstractMatrix{T} ,
@@ -186,7 +191,7 @@ function FrankWolfe.compute_inface_extreme_point(
186191 for i in 1 : nreduced
187192 row_orig = index_map_rows[i]
188193 col_orig = index_map_cols[j]
189- if x[row_orig, col_orig] >= 1 - eps ()
194+ if x[row_orig, col_orig] >= 1 - eps ()
190195 push! (fixed_to_one_rows, row_orig)
191196 push! (fixed_to_one_cols, col_orig)
192197
@@ -210,9 +215,9 @@ function FrankWolfe.compute_inface_extreme_point(
210215 for i in 1 : nreduced
211216 row_orig = index_map_rows[i]
212217 if lmo. append_by_column
213- orig_linear_idx = (col_orig- 1 ) * n + row_orig
218+ orig_linear_idx = (col_orig - 1 ) * n + row_orig
214219 else
215- orig_linear_idx = (row_orig- 1 ) * n + col_orig
220+ orig_linear_idx = (row_orig - 1 ) * n + col_orig
216221 end
217222 if x[row_orig, col_orig] <= eps ()
218223 if lmo. append_by_column
@@ -289,10 +294,6 @@ function FrankWolfe.compute_inface_extreme_point(
289294 return m
290295end
291296
292- """
293- LMO-like operation which computes a vertex minimizing in `direction` on the face defined by the current fixings.
294- Fixings are maintained by the oracle (or deduced from `x` itself).
295- """
296297function FrankWolfe. dicg_maximum_step (lmo:: BirkhoffLMO , direction, x; kwargs... )
297298 n = lmo. dim
298299 T = promote_type (eltype (x), eltype (direction))
@@ -321,9 +322,6 @@ function FrankWolfe.is_decomposition_invariant_oracle(lmo::BirkhoffLMO)
321322 return true
322323end
323324
324- """
325- The sum of each row and column has to be equal to 1.
326- """
327325function Boscia. is_linear_feasible (blmo:: BirkhoffLMO , v:: AbstractVector )
328326 for (i, int_var) in enumerate (blmo. int_vars)
329327 if ! (
@@ -351,7 +349,6 @@ function Boscia.is_linear_feasible(blmo::BirkhoffLMO, v::AbstractVector)
351349 return true
352350end
353351
354- # Read global bounds from the problem.
355352function Boscia. build_global_bounds (blmo:: BirkhoffLMO , integer_variables)
356353 global_bounds = Boscia. IntegerBounds ()
357354 for (idx, int_var) in enumerate (blmo. int_vars)
@@ -361,34 +358,27 @@ function Boscia.build_global_bounds(blmo::BirkhoffLMO, integer_variables)
361358 return global_bounds
362359end
363360
364- # Get list of variables indices.
365- # If the problem has n variables, they are expected to contiguous and ordered from 1 to n.
366361function Boscia. get_list_of_variables (blmo:: BirkhoffLMO )
367362 n = blmo. dim^ 2
368363 return n, collect (1 : n)
369364end
370365
371- # Get list of integer variables
372366function Boscia. get_integer_variables (blmo:: BirkhoffLMO )
373367 return blmo. int_vars
374368end
375369
376- # Get the index of the integer variable the bound is working on.
377370function Boscia. get_int_var (blmo:: BirkhoffLMO , cidx)
378371 return blmo. int_vars[cidx]
379372end
380373
381- # Get the list of lower bounds.
382374function Boscia. get_lower_bound_list (blmo:: BirkhoffLMO )
383375 return collect (1 : length (blmo. lower_bounds))
384376end
385377
386- # Get the list of upper bounds.
387378function Boscia. get_upper_bound_list (blmo:: BirkhoffLMO )
388379 return collect (1 : length (blmo. upper_bounds))
389380end
390381
391- # Read bound value for c_idx.
392382function Boscia. get_bound (blmo:: BirkhoffLMO , c_idx, sense:: Symbol )
393383 if sense == :lessthan
394384 return blmo. upper_bounds[c_idx]
401391
402392# # Changing the bounds constraints.
403393
404- # Change the value of the bound c_idx.
405394function Boscia. set_bound! (blmo:: BirkhoffLMO , c_idx, value, sense:: Symbol )
406395 # Reset the lmo if necessary
407396 if blmo. updated_lmo
@@ -432,7 +421,12 @@ function Boscia.set_bound!(blmo::BirkhoffLMO, c_idx, value, sense::Symbol)
432421 end
433422end
434423
435- # Delete bounds.
424+ """
425+ Boscia.delete_bounds!(blmo::BirkhoffLMO, cons_delete)
426+
427+ Delete a collection of bounds given as pairs `(idx, sense)` and rebuild the reduced index maps `index_map_rows`
428+ and `index_map_cols` based on entries fixed to one.
429+ """
436430function Boscia. delete_bounds! (blmo:: BirkhoffLMO , cons_delete)
437431 for (d_idx, sense) in cons_delete
438432 if sense == :greaterthan
@@ -469,7 +463,12 @@ function Boscia.delete_bounds!(blmo::BirkhoffLMO, cons_delete)
469463 return true
470464end
471465
472- # Add bound constraint.
466+ """
467+ Boscia.add_bound_constraint!(blmo::BirkhoffLMO, key, value, sense::Symbol)
468+
469+ Add or overwrite a single bound for the integer variable. If a lower bound is set to `1.0`,
470+ update `fixed_to_one_rows` and `fixed_to_one_cols`.
471+ """
473472function Boscia. add_bound_constraint! (blmo:: BirkhoffLMO , key, value, sense:: Symbol )
474473 idx = findfirst (x -> x == key, blmo. int_vars)
475474 if sense == :greaterthan
@@ -497,25 +496,20 @@ end
497496
498497# # Checks
499498
500- # Check if the subject of the bound c_idx is an integer variable (recorded in int_vars).
501499function Boscia. is_constraint_on_int_var (blmo:: BirkhoffLMO , c_idx, int_vars)
502500 return blmo. int_vars[c_idx] in int_vars
503501end
504502
505- # To check if there is bound for the variable in the global or node bounds.
506503function Boscia. is_bound_in (blmo:: BirkhoffLMO , c_idx, bounds)
507504 return haskey (bounds, blmo. int_vars[c_idx])
508505end
509506
510- # Has variable an integer constraint?
511507function Boscia. has_integer_constraint (blmo:: BirkhoffLMO , idx)
512508 return idx in blmo. int_vars
513509end
514510
515511# # Safety Functions
516512
517- # Check if the bounds were set correctly in build_LMO.
518- # Safety check only.
519513function Boscia. build_LMO_correct (blmo:: BirkhoffLMO , node_bounds)
520514 for key in keys (node_bounds. lower_bounds)
521515 idx = findfirst (x -> x == key, blmo. int_vars)
534528
535529# # Optional
536530
531+ """
532+ Boscia.check_feasibility(blmo::BirkhoffLMO)
533+
534+ Quick feasibility test for the bounds alone (without a specific `x`). It validates
535+ that `ub ≥ lb` componentwise and that row/column sums can still achieve `1` given
536+ the current lower/upper bounds.
537+ """
537538function Boscia. check_feasibility (blmo:: BirkhoffLMO )
538539 for (lb, ub) in zip (blmo. lower_bounds, blmo. upper_bounds)
539540 if ub < lb
0 commit comments