Skip to content

Commit 1038e00

Browse files
odowblegat
andauthored
Fix monomial_variable_index in QCQP optimizer (#108)
* Fix monomial_variable_index in QCQP optimizer * Add test * Use IntervalArithmetics --------- Co-authored-by: Benoît Legat <[email protected]>
1 parent 5ffe3c9 commit 1038e00

File tree

3 files changed

+93
-12
lines changed

3 files changed

+93
-12
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "0.7.1"
66
[deps]
77
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
88
DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07"
9+
IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
910
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
1011
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
@@ -18,6 +19,7 @@ SemialgebraicSets = "8e049039-38e8-557d-ae3a-bc521ccf6204"
1819
[compat]
1920
DataStructures = "0.18"
2021
DynamicPolynomials = "0.5"
22+
IntervalArithmetic = "0.22"
2123
JuMP = "1"
2224
MathOptInterface = "1"
2325
MultivariateBases = "0.2"

src/QCQP/MOI_wrapper.jl

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ function MOI.supports_constraint(
112112
) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
113113
return MOI.supports_constraint(model.model, F, S)
114114
end
115+
115116
function MOI.supports_constraint(
116117
model::Optimizer{T},
117118
::Type{<:PolyJuMP.ScalarPolynomialFunction{T}},
@@ -131,6 +132,7 @@ function MOI.add_constraint(
131132
)
132133
return MOI.add_constraint(model.model, func, set)
133134
end
135+
134136
function MOI.add_constraint(
135137
model::Optimizer{T},
136138
func::PolyJuMP.ScalarPolynomialFunction{T,P},
@@ -250,31 +252,65 @@ function _add_variables!(
250252
return d
251253
end
252254

255+
import IntervalArithmetic
256+
253257
function monomial_variable_index(
254258
model::Optimizer{T},
255259
d::Dict,
256260
div,
257261
mono::MP.AbstractMonomialLike,
258262
) where {T}
259263
if !haskey(d, mono)
264+
# If we don't have a variable for `mono` yet,
265+
# we create one now by equal to `x * y`.
266+
mono_bounds = IntervalArithmetic.interval(one(T))
267+
for var in MP.variables(mono)
268+
deg = MP.degree(mono, var)
269+
if deg == 0
270+
continue
271+
end
272+
vi = monomial_variable_index(model, d, div, var)
273+
lb_var, ub_var = MOI.Utilities.get_bounds(model, T, vi)
274+
F = float(T)
275+
var_bounds = IntervalArithmetic.interval(
276+
lb_var == typemin(lb_var) ? typemin(F) : float(lb_var),
277+
ub_var == typemax(ub_var) ? typemax(F) : float(ub_var),
278+
)
279+
mono_bounds *= var_bounds^deg
280+
end
260281
x = div[mono]
261282
vx = monomial_variable_index(model, d, div, x)
262283
y = MP.div_multiple(mono, x)
263284
vy = monomial_variable_index(model, d, div, y)
264-
lx, ux = MOI.Utilities.get_bounds(model, T, vx)
265-
ly, uy = MOI.Utilities.get_bounds(model, T, vy)
266-
bounds = (lx * ly, lx * uy, ux * ly, ux * uy)
267-
l = min(bounds...)
268-
if vx == vy
269-
l = max(l, zero(T))
285+
lb = IntervalArithmetic.inf(mono_bounds)
286+
ub = IntervalArithmetic.sup(mono_bounds)
287+
if isfinite(lb)
288+
if isfinite(ub)
289+
if lb == ub
290+
set = MOI.EqualTo(T(lb))
291+
else
292+
set = MOI.Interval(T(lb), T(ub))
293+
end
294+
else
295+
set = MOI.GreaterThan(T(lb))
296+
end
297+
else
298+
if isfinite(ub)
299+
set = MOI.LessThan(T(ub))
300+
else
301+
set = nothing
302+
end
270303
end
271-
u = max(bounds...)
272-
d[mono], _ =
273-
MOI.add_constrained_variable(model.model, MOI.Interval(l, u))
274-
MOI.add_constraint(
304+
if isnothing(set)
305+
d[mono] = MOI.add_variable(model.model)
306+
else
307+
d[mono], _ = MOI.add_constrained_variable(model.model, set)
308+
end
309+
MOI.Utilities.normalize_and_add_constraint(
275310
model,
276311
MA.@rewrite(one(T) * d[mono] - one(T) * vx * vy),
277-
MOI.EqualTo(zero(T)),
312+
MOI.EqualTo(zero(T));
313+
allow_modify_function = true,
278314
)
279315
end
280316
return d[mono]
@@ -286,8 +322,9 @@ function _add_constraints(model::Optimizer, cis, index_to_var, d, div)
286322
set = MOI.get(model, MOI.ConstraintSet(), ci)
287323
func, index_to_var = _subs!(func, index_to_var)
288324
quad = _quad_convert(func.polynomial, d, div)
289-
MOI.add_constraint(model, quad, set)
325+
MOI.Utilities.normalize_and_add_constraint(model, quad, set)
290326
end
327+
return
291328
end
292329

293330
function MOI.Utilities.final_touch(model::Optimizer{T}, _) where {T}

test/qcqp.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,48 @@ function test_no_monomials(x, y, T)
157157
return
158158
end
159159

160+
function test_scalar_constant_not_zero(x, y, T)
161+
inner = Model{T}()
162+
model = PolyJuMP.JuMP.GenericModel{T}() do
163+
return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner))
164+
end
165+
PolyJuMP.@variable(model, -1 <= x[1:3] <= 2)
166+
PolyJuMP.@constraint(model, 4 * x[1] * x[2] * x[3] == 1)
167+
PolyJuMP.@objective(model, Min, sum(x))
168+
PolyJuMP.optimize!(model)
169+
for (F, S) in MOI.get(inner, MOI.ListOfConstraintTypesPresent())
170+
for ci in MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())
171+
if F isa MOI.ScalarQuadraticFunction
172+
f = MOI.get(inner, MOI.ConstraintFunction(), ci)
173+
@test iszero(f.constant)
174+
end
175+
end
176+
end
177+
return
178+
end
179+
180+
function test_unbound_polynomial(x, y, T)
181+
inner = Model{T}()
182+
model = PolyJuMP.JuMP.GenericModel{T}() do
183+
return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner))
184+
end
185+
PolyJuMP.@variable(model, x >= 0)
186+
PolyJuMP.@objective(model, Min, x^3)
187+
PolyJuMP.optimize!(model)
188+
F = MOI.VariableIndex
189+
S = MOI.Interval{T}
190+
@test MOI.get(inner, MOI.NumberOfConstraints{F,S}()) == 0
191+
S = MOI.LessThan{T}
192+
@test MOI.get(inner, MOI.NumberOfConstraints{F,S}()) == 0
193+
S = MOI.GreaterThan{T}
194+
@test MOI.get(inner, MOI.NumberOfConstraints{F,S}()) == 2
195+
for ci in MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())
196+
set = MOI.get(inner, MOI.ConstraintSet(), ci)
197+
@test set.lower == zero(T)
198+
end
199+
return
200+
end
201+
160202
function runtests(x, y)
161203
for name in names(@__MODULE__; all = true)
162204
if startswith("$name", "test_")

0 commit comments

Comments
 (0)