Skip to content

Commit ccebc98

Browse files
authored
Add support for deleting constraints (#17)
1 parent 68145fb commit ccebc98

3 files changed

Lines changed: 109 additions & 13 deletions

File tree

‎ext/MathOptLazyGurobiExt.jl‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ function _add_constraints!(
1414
data::MathOptLazy._LazyData,
1515
)
1616
for (i, (f, s)) in enumerate(data.data)
17-
if !data.active[i]
17+
if data.status[i] == MathOptLazy._kLAZY_CONSTRAINT_INACTIVE
1818
c = data.index[i] = MOI.add_constraint(model.inner, f, s)
1919
MOI.set(model.inner, Gurobi.ConstraintAttribute("Lazy"), c, 1)
20-
data.active[i] = true
20+
data.status[i] = MathOptLazy._kLAZY_CONSTRAINT_ACTIVE
2121
end
2222
end
2323
return

‎src/MathOptLazy.jl‎

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,27 @@ Base.broadcastable(::Lazy) = Ref(Lazy())
6969

7070
### _LazyData
7171

72+
@enum(
73+
_LAZY_CONSTRAINT_STATUS,
74+
_kLAZY_CONSTRAINT_ACTIVE,
75+
_kLAZY_CONSTRAINT_INACTIVE,
76+
_kLAZY_CONSTRAINT_DELETED,
77+
)
78+
7279
struct _LazyData{F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
7380
data::Vector{Tuple{F,S}}
74-
active::Vector{Bool}
81+
status::Vector{_LAZY_CONSTRAINT_STATUS}
7582
index::Vector{MOI.ConstraintIndex{F,S}}
7683

7784
function _LazyData{
7885
F,
7986
S,
8087
}() where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
81-
return new{F,S}(Tuple{F,S}[], Bool[], MOI.ConstraintIndex{F,S}[])
88+
return new{F,S}(
89+
Tuple{F,S}[],
90+
_LAZY_CONSTRAINT_STATUS[],
91+
MOI.ConstraintIndex{F,S}[],
92+
)
8293
end
8394
end
8495

@@ -330,6 +341,27 @@ function MOI.add_constraint(
330341
return MOI.add_constraint(model.inner, f, s)
331342
end
332343

344+
### MOI.delete!
345+
346+
function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex{F,S}) where {F,S}
347+
MOI.delete(model.inner, ci)
348+
return
349+
end
350+
351+
function MOI.delete(
352+
model::Optimizer,
353+
ci::MOI.ConstraintIndex{F,LazyScalarSet{S}},
354+
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
355+
data = _data(model, F, S)
356+
if data.status[ci.value] == _kLAZY_CONSTRAINT_ACTIVE
357+
MOI.delete(model.inner, data.index[ci.value])
358+
data.status[ci.value] = _kLAZY_CONSTRAINT_DELETED
359+
elseif data.status[ci.value] == _kLAZY_CONSTRAINT_INACTIVE
360+
data.status[ci.value] = _kLAZY_CONSTRAINT_DELETED
361+
end
362+
return
363+
end
364+
333365
### MOI.compute_conflict!
334366

335367
MOI.compute_conflict!(model::Optimizer) = MOI.compute_conflict!(model.inner)
@@ -361,7 +393,11 @@ function MOI.is_valid(
361393
ci::MOI.ConstraintIndex{F,LazyScalarSet{S}},
362394
) where {F,S}
363395
ret = _maybe_data(model, F, S)
364-
return ret !== nothing && 1 <= ci.value <= length(ret.data)
396+
if ret == nothing
397+
return false
398+
end
399+
status = get(ret.status, ci.value, _kLAZY_CONSTRAINT_DELETED)
400+
return status != _kLAZY_CONSTRAINT_DELETED
365401
end
366402

367403
function MOI.get(
@@ -387,7 +423,7 @@ function MOI.add_constraint(
387423
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
388424
data = _data(model, F, S)
389425
push!(data.data, (f, s.set))
390-
push!(data.active, false)
426+
push!(data.status, _kLAZY_CONSTRAINT_INACTIVE)
391427
push!(data.index, MOI.ConstraintIndex{F,S}(0))
392428
return MOI.ConstraintIndex{F,LazyScalarSet{S}}(length(data.data))
393429
end
@@ -430,7 +466,9 @@ function MOI.get(
430466
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
431467
n = MOI.get(model.inner, attr)
432468
if (data = _maybe_data(model, F, S)) !== nothing
433-
n -= sum((data).active)
469+
for status in data.status
470+
n -= status == _kLAZY_CONSTRAINT_ACTIVE
471+
end
434472
end
435473
return n
436474
end
@@ -441,7 +479,10 @@ function MOI.get(
441479
) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet}
442480
ret = MOI.get(model.inner, attr)
443481
if (data = _maybe_data(model, F, S)) !== nothing
444-
in_model = Set(ci for (ci, z) in zip(data.index, data.active) if z)
482+
in_model = Set{MOI.ConstraintIndex{F,S}}(
483+
ci for (ci, status) in zip(data.index, data.status) if
484+
status == _kLAZY_CONSTRAINT_ACTIVE
485+
)
445486
ret = filter!(ci -> !(ci in in_model), ret)
446487
end
447488
return ret
@@ -523,9 +564,9 @@ function _add_if_unbounded(model::Optimizer, data::_LazyData)
523564
for (i, (f, s)) in enumerate(data.data)
524565
if constraints_added >= n
525566
break
526-
elseif !data.active[i]
567+
elseif data.status[i] == _kLAZY_CONSTRAINT_INACTIVE
527568
data.index[i] = MOI.add_constraint(model.inner, f, s)
528-
data.active[i] = true
569+
data.status[i] = _kLAZY_CONSTRAINT_ACTIVE
529570
constraints_added += 1
530571
end
531572
end
@@ -539,13 +580,13 @@ function _add_if_feasible(
539580
)
540581
needs_solve = false
541582
for (i, (f, s)) in enumerate(data.data)
542-
if data.active[i]
583+
if data.status[i] != _kLAZY_CONSTRAINT_INACTIVE
543584
continue
544585
end
545586
y = MOI.Utilities.eval_variables(Base.Fix1(getindex, x), model, f)
546587
if MOI.Utilities.distance_to_set(y, s) > 0
547588
data.index[i] = MOI.add_constraint(model.inner, f, s)
548-
data.active[i] = true
589+
data.status[i] = _kLAZY_CONSTRAINT_ACTIVE
549590
needs_solve = true
550591
end
551592
end

‎test/runtests.jl‎

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function _basic_constraint_test_helper(
109109
data = MathOptLazy._data(model, F, IS)
110110
for (i, (f, s)) in enumerate(data.data)
111111
data.index[i] = MOI.add_constraint(model.inner, f, s)
112-
data.active[i] = true
112+
data.status[i] = MathOptLazy._kLAZY_CONSTRAINT_ACTIVE
113113
end
114114
end
115115
c_inner = MOI.add_constraint(model, constraint_function, inner_set)
@@ -313,6 +313,61 @@ function test_glpk_solver_specific()
313313
return
314314
end
315315

316+
function test_delete_bound()
317+
model = MathOptLazy.Optimizer(HiGHS.Optimizer)
318+
x = MOI.add_variable(model)
319+
c1 = MOI.add_constraint(model, x, MOI.ZeroOne())
320+
@test MOI.is_valid(model, c1)
321+
MOI.delete(model, c1)
322+
@test !MOI.is_valid(model, c1)
323+
return
324+
end
325+
326+
function test_delete_lazy_inactive()
327+
model = MathOptLazy.Optimizer(HiGHS.Optimizer)
328+
MOI.set(model, MOI.Silent(), true)
329+
x = MOI.add_variable(model)
330+
MOI.add_constraint(model, x, MOI.EqualTo(0.0))
331+
set = MathOptLazy.LazyScalarSet(MOI.EqualTo(1.0))
332+
c = MOI.add_constraint(model, 1.0 * x, set)
333+
@test MOI.is_valid(model, c)
334+
MOI.delete(model, c)
335+
@test !MOI.is_valid(model, c)
336+
MOI.optimize!(model)
337+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
338+
return
339+
end
340+
341+
function test_delete_lazy_active()
342+
model = MathOptLazy.Optimizer(HiGHS.Optimizer)
343+
MOI.set(model, MOI.Silent(), true)
344+
x = MOI.add_variable(model)
345+
MOI.add_constraint(model, x, MOI.EqualTo(0.0))
346+
set = MathOptLazy.LazyScalarSet(MOI.EqualTo(1.0))
347+
c = MOI.add_constraint(model, 1.0 * x, set)
348+
MOI.optimize!(model)
349+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE
350+
MOI.delete(model, c)
351+
MOI.optimize!(model)
352+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
353+
return
354+
end
355+
356+
function test_is_valid()
357+
model = MathOptLazy.Optimizer(HiGHS.Optimizer)
358+
x = MOI.add_variable(model)
359+
set = MathOptLazy.LazyScalarSet(MOI.EqualTo(1.0))
360+
c = MOI.add_constraint(model, 1.0 * x, set)
361+
@test !MOI.is_valid(model, typeof(c)(c.value - 1))
362+
@test MOI.is_valid(model, c)
363+
@test !MOI.is_valid(model, typeof(c)(c.value + 1))
364+
F, S = MOI.VariableIndex, MathOptLazy.LazyScalarSet{MOI.ZeroOne}
365+
@test !MOI.is_valid(model, MOI.ConstraintIndex{F,S}(-1))
366+
@test !MOI.is_valid(model, MOI.ConstraintIndex{F,S}(0))
367+
@test !MOI.is_valid(model, MOI.ConstraintIndex{F,S}(1))
368+
return
369+
end
370+
316371
end # TestMathOptLazy
317372

318373
TestMathOptLazy.runtests()

0 commit comments

Comments
 (0)