Skip to content

Commit 5098d31

Browse files
authored
[QCQP] Implement variable and constraint attributes (#119)
* [QCQP] Implement variable and constraint bridges * Fix format * Add tests * Add tests
1 parent 9895fa0 commit 5098d31

File tree

3 files changed

+199
-36
lines changed

3 files changed

+199
-36
lines changed

src/QCQP/MOI_wrapper.jl

Lines changed: 118 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import MathOptInterface as MOI
22

3+
const _Model{F,S} =
4+
MOI.Utilities.UniversalFallback{MOI.Utilities.VectorOfConstraints{F,S}}
5+
36
mutable struct Optimizer{T,O<:MOI.ModelLike} <: MOI.AbstractOptimizer
47
model::O
58
objective::Union{Nothing,PolyJuMP.ScalarPolynomialFunction{T}}
6-
constraints::DataStructures.OrderedDict{
7-
Type,
8-
Tuple{Type,MOI.Utilities.VectorOfConstraints},
9-
}
9+
constraints::DataStructures.OrderedDict{Type,Tuple{Type,_Model}}
10+
index_map::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}
1011
end
1112

1213
function Optimizer{T}(model::MOI.ModelLike) where {T}
1314
return Optimizer{T,typeof(model)}(
1415
model,
1516
nothing,
1617
DataStructures.OrderedDict{Type,MOI.Utilities.VectorOfConstraints}(),
18+
Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}(),
1719
)
1820
end
1921

@@ -34,18 +36,45 @@ function MOI.empty!(model::Optimizer)
3436
MOI.empty!(model.model)
3537
model.objective = nothing
3638
empty!(model.constraints)
39+
empty!(model.index_map)
3740
return
3841
end
3942

4043
MOI.is_valid(model::Optimizer, i::MOI.Index) = MOI.is_valid(model.model, i)
4144
function MOI.is_valid(
4245
model::Optimizer{T},
43-
::MOI.ConstraintIndex{PolyJuMP.ScalarPolynomialFunction{T},S},
46+
ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S},
4447
) where {T,S<:MOI.AbstractScalarSet}
4548
return haskey(model.constraints, S) &&
4649
MOI.is_valid(model.constraints[S][2], ci)
4750
end
4851

52+
function MOI.supports(
53+
model::Optimizer,
54+
attr::MOI.AbstractConstraintAttribute,
55+
C::Type{<:MOI.ConstraintIndex},
56+
)
57+
return MOI.supports(model.model, attr, C)
58+
end
59+
60+
function MOI.set(
61+
model::Optimizer,
62+
attr::MOI.AbstractConstraintAttribute,
63+
ci::MOI.ConstraintIndex,
64+
value,
65+
)
66+
return MOI.set(model.model, attr, ci, value)
67+
end
68+
69+
function MOI.set(
70+
model::Optimizer{T},
71+
attr::MOI.AbstractConstraintAttribute,
72+
ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S},
73+
value,
74+
) where {T,S<:MOI.AbstractScalarSet}
75+
return MOI.get(model.constraints[S][2], attr, model.index_map[ci], value)
76+
end
77+
4978
function MOI.get(
5079
model::Optimizer,
5180
attr::MOI.AbstractConstraintAttribute,
@@ -54,8 +83,28 @@ function MOI.get(
5483
return MOI.get(model.model, attr, ci)
5584
end
5685

86+
function MOI.get(
87+
model::Optimizer{T},
88+
attr::MOI.AbstractConstraintAttribute,
89+
ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S},
90+
) where {T,S<:MOI.AbstractScalarSet}
91+
if MOI.is_set_by_optimize(attr)
92+
return MOI.get(model.model, attr, model.index_map[ci])
93+
else
94+
return MOI.get(model.constraints[S][2], attr, ci)
95+
end
96+
end
97+
5798
MOI.add_variable(model::Optimizer) = MOI.add_variable(model.model)
5899

100+
function MOI.supports(
101+
model::Optimizer,
102+
attr::MOI.AbstractVariableAttribute,
103+
::Type{MOI.VariableIndex},
104+
)
105+
return MOI.supports(model.model, attr, MOI.VariableIndex)
106+
end
107+
59108
function MOI.set(
60109
model::Optimizer,
61110
attr::MOI.AbstractVariableAttribute,
@@ -158,7 +207,9 @@ function MOI.add_constraint(
158207
F = typeof(func)
159208
S = typeof(set)
160209
if !haskey(model.constraints, S)
161-
con = MOI.Utilities.VectorOfConstraints{F,S}()
210+
con = MOI.Utilities.UniversalFallback(
211+
MOI.Utilities.VectorOfConstraints{F,S}(),
212+
)
162213
model.constraints[S] = (P, con)
163214
end
164215
return MOI.add_constraint(model.constraints[S][2], func, set)
@@ -168,7 +219,7 @@ function MOI.get(
168219
model::Optimizer{T},
169220
attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet},
170221
ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S},
171-
) where {T,S}
222+
) where {T,S<:MOI.AbstractScalarSet}
172223
return MOI.get(model.constraints[S][2], attr, ci)
173224
end
174225

@@ -296,6 +347,7 @@ function monomial_variable_index(
296347
# If we don't have a variable for `mono` yet,
297348
# we create one now by equal to `x * y`.
298349
mono_bounds = IntervalArithmetic.interval(one(T))
350+
mono_start = one(T)
299351
for var in MP.variables(mono)
300352
deg = MP.degree(mono, var)
301353
if deg == 0
@@ -309,6 +361,16 @@ function monomial_variable_index(
309361
ub_var == typemax(ub_var) ? typemax(F) : float(ub_var),
310362
)
311363
mono_bounds *= var_bounds^deg
364+
attr = MOI.VariablePrimalStart()
365+
if !isnothing(mono_start) &&
366+
MOI.supports(model, attr, MOI.VariableIndex)
367+
start = MOI.get(model, attr, vi)
368+
if isnothing(start)
369+
mono_start = nothing
370+
else
371+
mono_start *= start^deg
372+
end
373+
end
312374
end
313375
x = div[mono]
314376
vx = monomial_variable_index(model, d, div, x)
@@ -338,6 +400,10 @@ function monomial_variable_index(
338400
else
339401
d[mono], _ = MOI.add_constrained_variable(model.model, set)
340402
end
403+
if !isnothing(mono_start) &&
404+
MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex)
405+
MOI.set(model.model, MOI.VariablePrimalStart(), d[mono], mono_start)
406+
end
341407
MOI.Utilities.normalize_and_add_constraint(
342408
model,
343409
MA.@rewrite(one(T) * d[mono] - one(T) * vx * vy),
@@ -348,14 +414,44 @@ function monomial_variable_index(
348414
return d[mono]
349415
end
350416

351-
function _add_constraints(model::Optimizer, cis, index_to_var, d, div)
352-
for ci in cis
353-
func = MOI.get(model, MOI.ConstraintFunction(), ci)
354-
set = MOI.get(model, MOI.ConstraintSet(), ci)
417+
function _add_constraints(
418+
dest,
419+
src,
420+
index_map,
421+
cis_src::Vector{MOI.ConstraintIndex{F,S}},
422+
index_to_var,
423+
d,
424+
div,
425+
) where {F,S}
426+
for ci in cis_src
427+
func = MOI.get(src, MOI.ConstraintFunction(), ci)
428+
set = MOI.get(src, MOI.ConstraintSet(), ci)
355429
func, index_to_var = _subs!(func, index_to_var)
356430
quad = _quad_convert(func.polynomial, d, div)
357-
MOI.Utilities.normalize_and_add_constraint(model, quad, set)
431+
dest_ci = MOI.Utilities.normalize_and_add_constraint(dest, quad, set)
432+
index_map[ci] = dest_ci
358433
end
434+
# `Utilities.pass_attributes` needs `index_map` to be an `IndexMap` :(
435+
#MOI.Utilities.pass_attributes(dest, src, index_map, cis_src)
436+
# `ListOfConstraintAttributesSet` not defined for `VectorOfConstraints`
437+
# for attr in MOI.get(src, MOI.ListOfConstraintAttributesSet{F,S}())
438+
# if !MOI.supports(dest, attr)
439+
# if attr == MOI.Name()
440+
# continue # Skipping names is okay.
441+
# end
442+
# end
443+
# for ci in cis_src
444+
# value = MOI.get(src, attr, ci)
445+
# if value !== nothing
446+
# MOI.set(
447+
# dest,
448+
# attr,
449+
# index_map[ci],
450+
# MOI.Utilities.map_indices(index_map, attr, value),
451+
# )
452+
# end
453+
# end
454+
# end
359455
return
360456
end
361457

@@ -396,7 +492,16 @@ function MOI.Utilities.final_touch(model::Optimizer{T}, _) where {T}
396492
for S in keys(model.constraints)
397493
F = PolyJuMP.ScalarPolynomialFunction{T,model.constraints[S][1]}
398494
cis = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
399-
_add_constraints(model, cis, index_to_var, vars, div)
495+
src = model.constraints[S][2]
496+
_add_constraints(
497+
model.model,
498+
src,
499+
model.index_map,
500+
cis,
501+
index_to_var,
502+
vars,
503+
div,
504+
)
400505
end
401506
end
402507
return

0 commit comments

Comments
 (0)