Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 31 additions & 8 deletions src/MathOptLazy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -421,13 +421,17 @@ function MOI.optimize!(model::Optimizer)
while needs_solve
needs_solve = false
MOI.optimize!(model.inner)
if MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
if MOI.get(model, MOI.TerminationStatus()) == MOI.DUAL_INFEASIBLE
# The problem is unbounded, but it might not be if we add more
# constraints.
for v in values(model.lazy)
needs_solve |= _add_if_unbounded(model, v)
end
elseif MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
X = Dict(xi => MOI.get(model, MOI.VariablePrimal(), xi) for xi in x)
constraints_added = 0
for v in values(model.lazy)
constraints_added += _add_if_necessary(model, v, X)
needs_solve |= _add_if_feasible(model, v, X)
end
needs_solve = constraints_added > 0
if start && needs_solve
for (xi, v) in X
MOI.set(model, MOI.VariablePrimalStart(), xi, v)
Expand All @@ -438,12 +442,31 @@ function MOI.optimize!(model::Optimizer)
return
end

function _add_if_necessary(
function _add_if_unbounded(model::Optimizer, data::_LazyData)
# Strategy: add 1/3 of the total constraints. This is arbitrary. If a model
# is unbounded with lazy constraints, it's not a great model, and it has a
# high likelihood that it requires _all_ the constraints to be added. I'm
# imagining something like 0 <= x <= Lazy(1) in a knapsack problem.
n = div(length(data.data), 3, RoundUp)
constraints_added = 0
for (i, (f, s)) in enumerate(data.data)
if constraints_added >= n
break
elseif !data.active[i]
data.index[i] = MOI.add_constraint(model.inner, f, s)
data.active[i] = true
constraints_added += 1
end
end
return constraints_added > 0
end

function _add_if_feasible(
model::Optimizer,
data::_LazyData,
x::Dict{MOI.VariableIndex},
)
constraints_added = 0
needs_solve = false
for (i, (f, s)) in enumerate(data.data)
if data.active[i]
continue
Expand All @@ -452,10 +475,10 @@ function _add_if_necessary(
if MOI.Utilities.distance_to_set(y, s) > 0
data.index[i] = MOI.add_constraint(model.inner, f, s)
data.active[i] = true
constraints_added += 1
needs_solve = true
end
end
return constraints_added
return needs_solve
end

end # module MathOptLazy
29 changes: 29 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,35 @@ function test_writing_mof_file()
return
end

function test_lazy_bounds()
model = MathOptLazy.Optimizer(HiGHS.Optimizer)
x = MOI.add_variable(model)
set = MathOptLazy.LazyScalarSet(MOI.GreaterThan(0.0))
MOI.add_constraint(model, x, set)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
f = 1.0 * x
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
MOI.optimize!(model)
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(model, MOI.VariablePrimal(), x) == 0.0
return
end

function test_lazy_bounds_knapsack()
model = MathOptLazy.Optimizer(HiGHS.Optimizer)
x = MOI.add_variables(model, 22)
set = MathOptLazy.LazyScalarSet(MOI.GreaterThan(0.0))
MOI.add_constraint.(model, x, set)
set = MathOptLazy.LazyScalarSet(MOI.LessThan(1.0))
MOI.add_constraint.(model, x, set)
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
f = rand(22)' * x
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
MOI.optimize!(model)
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
return
end

end # TestMathOptLazy

TestMathOptLazy.runtests()
Loading