Skip to content

Commit 2fde3bb

Browse files
authored
Polynomial to QCQP meta-solver (#95)
* Polynomial to QCQP meta-solver * Fix format * Add MOI wrapper * Fix format * Fixes * Fixes * Add test * Add bounds * Add suppor to constraints * Fix format
1 parent 55e55f9 commit 2fde3bb

File tree

7 files changed

+652
-16
lines changed

7 files changed

+652
-16
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ repo = "https://github.com/jump-dev/PolyJuMP.jl.git"
44
version = "0.7.0"
55

66
[deps]
7+
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
78
DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07"
89
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
910
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -15,6 +16,7 @@ MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
1516
SemialgebraicSets = "8e049039-38e8-557d-ae3a-bc521ccf6204"
1617

1718
[compat]
19+
DataStructures = "0.18"
1820
DynamicPolynomials = "0.5"
1921
JuMP = "1"
2022
MathOptInterface = "1"

src/PolyJuMP.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ include("default.jl")
3333

3434
include("model.jl")
3535
include("KKT/KKT.jl")
36+
include("QCQP/QCQP.jl")
3637

3738
end # module

src/QCQP/MOI_wrapper.jl

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
import MathOptInterface as MOI
2+
3+
mutable struct Optimizer{T,O<:MOI.ModelLike} <: MOI.AbstractOptimizer
4+
model::O
5+
objective::Union{Nothing,PolyJuMP.ScalarPolynomialFunction{T}}
6+
constraints::DataStructures.OrderedDict{
7+
Type,
8+
Tuple{Type,MOI.Utilities.VectorOfConstraints},
9+
}
10+
end
11+
12+
function Optimizer{T}(model::MOI.ModelLike) where {T}
13+
return Optimizer{T,typeof(model)}(
14+
model,
15+
nothing,
16+
DataStructures.OrderedDict{Type,MOI.Utilities.VectorOfConstraints}(),
17+
)
18+
end
19+
20+
Optimizer(model::MOI.ModelLike) = Optimizer{Float64}(model)
21+
22+
function MOI.get(
23+
model::Optimizer{T},
24+
attr::MOI.Bridges.ListOfNonstandardBridges,
25+
) where {T}
26+
list = copy(MOI.get(model.model, attr))
27+
push!(list, PolyJuMP.Bridges.Constraint.ToPolynomialBridge{T})
28+
push!(list, PolyJuMP.Bridges.Objective.ToPolynomialBridge{T})
29+
return list
30+
end
31+
32+
MOI.is_empty(model::Optimizer) = MOI.is_empty(model.model)
33+
function MOI.empty!(model::Optimizer)
34+
MOI.empty!(model.model)
35+
model.objective = nothing
36+
empty!(model.constraints)
37+
return
38+
end
39+
40+
MOI.is_valid(model::Optimizer, i::MOI.Index) = MOI.is_valid(model.model, i)
41+
function MOI.is_valid(
42+
model::Optimizer{T},
43+
::MOI.ConstraintIndex{PolyJuMP.ScalarPolynomialFunction{T},S},
44+
) where {T,S<:MOI.AbstractScalarSet}
45+
return haskey(model.constraints, S) &&
46+
MOI.is_valid(model.constraints[S][2], ci)
47+
end
48+
49+
function MOI.get(
50+
model::Optimizer,
51+
attr::MOI.AbstractConstraintAttribute,
52+
ci::MOI.ConstraintIndex,
53+
)
54+
return MOI.get(model.model, attr, ci)
55+
end
56+
57+
MOI.add_variable(model::Optimizer) = MOI.add_variable(model.model)
58+
59+
function MOI.supports_add_constrained_variable(
60+
model::Optimizer,
61+
::Type{S},
62+
) where {S<:MOI.AbstractScalarSet}
63+
return MOI.supports_add_constrained_variable(model.model, S)
64+
end
65+
66+
function MOI.supports_add_constrained_variables(
67+
model::Optimizer,
68+
::Type{MOI.Reals},
69+
)
70+
return MOI.supports_add_constrained_variables(model.model, MOI.Reals)
71+
end
72+
73+
function MOI.supports_add_constrained_variables(
74+
model::Optimizer,
75+
::Type{S},
76+
) where {S<:MOI.AbstractVectorSet}
77+
return MOI.supports_add_constrained_variables(model.model, S)
78+
end
79+
80+
function MOI.supports(model::Optimizer, attr::MOI.AbstractModelAttribute)
81+
return MOI.supports(model.model, attr)
82+
end
83+
84+
function MOI.supports(
85+
::Optimizer,
86+
::MOI.ObjectiveFunction{<:PolyJuMP.ScalarPolynomialFunction},
87+
)
88+
return true
89+
end
90+
91+
function MOI.set(
92+
model::Optimizer{T},
93+
::MOI.ObjectiveFunction{F},
94+
f::F,
95+
) where {T,F<:PolyJuMP.ScalarPolynomialFunction{T}}
96+
model.objective = f
97+
return
98+
end
99+
100+
function MOI.set(model::Optimizer, attr::MOI.AbstractModelAttribute, value)
101+
return MOI.set(model.model, attr, value)
102+
end
103+
104+
function MOI.get(model::Optimizer, attr::MOI.AbstractModelAttribute)
105+
return MOI.get(model.model, attr)
106+
end
107+
108+
function MOI.supports_constraint(
109+
model::Optimizer,
110+
::Type{F},
111+
::Type{S},
112+
) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
113+
return MOI.supports_constraint(model.model, F, S)
114+
end
115+
function MOI.supports_constraint(
116+
model::Optimizer{T},
117+
::Type{<:PolyJuMP.ScalarPolynomialFunction{T}},
118+
::Type{S},
119+
) where {T,S<:MOI.AbstractScalarSet}
120+
return MOI.supports_constraint(
121+
model.model,
122+
MOI.ScalarQuadraticFunction{T},
123+
S,
124+
)
125+
end
126+
127+
function MOI.add_constraint(
128+
model::Optimizer,
129+
func::MOI.AbstractFunction,
130+
set::MOI.AbstractSet,
131+
)
132+
return MOI.add_constraint(model.model, func, set)
133+
end
134+
function MOI.add_constraint(
135+
model::Optimizer{T},
136+
func::PolyJuMP.ScalarPolynomialFunction{T,P},
137+
set::MOI.AbstractScalarSet,
138+
) where {T,P}
139+
F = typeof(func)
140+
S = typeof(set)
141+
if !haskey(model.constraints, S)
142+
con = MOI.Utilities.VectorOfConstraints{F,S}()
143+
model.constraints[S] = (P, con)
144+
end
145+
return MOI.add_constraint(model.constraints[S][2], func, set)
146+
end
147+
148+
function MOI.get(
149+
model::Optimizer{T},
150+
attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet},
151+
ci::MOI.ConstraintIndex{<:PolyJuMP.ScalarPolynomialFunction{T},S},
152+
) where {T,S}
153+
return MOI.get(model.constraints[S][2], attr, ci)
154+
end
155+
156+
function MOI.get(
157+
model::Optimizer{T},
158+
attr::MOI.ListOfConstraintIndices{<:PolyJuMP.ScalarPolynomialFunction{T},S},
159+
) where {T,S<:MOI.AbstractScalarSet}
160+
return MOI.get(model.constraints[S][2], attr)
161+
end
162+
163+
function MOI.supports_incremental_interface(model::Optimizer)
164+
return MOI.supports_incremental_interface(model.model)
165+
end
166+
167+
function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
168+
return MOI.Utilities.default_copy_to(dest, src)
169+
end
170+
171+
MOI.optimize!(model::Optimizer) = MOI.optimize!(model.model)
172+
173+
function _quad_convert(p::MP.AbstractPolynomialLike{T}, index, div) where {T}
174+
q = zero(MOI.ScalarQuadraticFunction{T})
175+
for t in MP.terms(p)
176+
α = MP.coefficient(t)
177+
mono = MP.monomial(t)
178+
if MP.degree(mono) == 0
179+
MA.operate!(+, q, α)
180+
else
181+
if haskey(index, mono)
182+
MA.operate!(MA.add_mul, q, α, index[mono])
183+
else
184+
x = div[mono]
185+
y = MP.div_multiple(mono, x)
186+
MA.operate!(MA.add_mul, q, α, index[x], index[y])
187+
end
188+
end
189+
end
190+
return q
191+
end
192+
193+
function _add_monomials!(p::PolyJuMP.ScalarPolynomialFunction, monos1)
194+
monos2 = MP.monomials(p.polynomial)
195+
if isnothing(monos1)
196+
return monos2
197+
else
198+
return MP.merge_monomial_vectors([monos1, monos2])
199+
end
200+
end
201+
202+
function _subs!(
203+
p::PolyJuMP.ScalarPolynomialFunction{T,P},
204+
::Nothing,
205+
) where {T,P}
206+
return p,
207+
Dict{MOI.VariableIndex,MP.variable_union_type(P)}(
208+
vi => var for (vi, var) in zip(p.variables, MP.variables(p.polynomial))
209+
)
210+
end
211+
212+
function _subs!(
213+
p::PolyJuMP.ScalarPolynomialFunction,
214+
index_to_var::Dict{K,V},
215+
) where {K,V}
216+
old_var = V[]
217+
new_var = V[]
218+
for (vi, var) in zip(p.variables, MP.variables(p.polynomial))
219+
if haskey(index_to_var, vi)
220+
if var != index_to_var[vi]
221+
push!(old_var, var)
222+
push!(new_var, index_to_var[vi])
223+
end
224+
else
225+
index_to_var[vi] = var
226+
end
227+
end
228+
if !isempty(old_var)
229+
poly = MP.subs(p.polynomial, old_var => new_var)
230+
p = PolyJuMP.ScalarPolynomialFunction(poly, p.variables)
231+
end
232+
return p, index_to_var
233+
end
234+
235+
function _add_variables!(
236+
p::PolyJuMP.ScalarPolynomialFunction{T,P},
237+
d,
238+
) where {T,P}
239+
if isnothing(d)
240+
d = Dict{MP.monomial_type(P),MOI.VariableIndex}()
241+
else
242+
M = promote_type(keytype(d), MP.monomial_type(P))
243+
if keytype(d) !== M
244+
d = convert(Dict{M,MOI.VariableIndex}, d)
245+
end
246+
end
247+
for (v, vi) in zip(MP.variables(p.polynomial), p.variables)
248+
d[v] = vi
249+
end
250+
return d
251+
end
252+
253+
function monomial_variable_index(
254+
model::Optimizer{T},
255+
d::Dict,
256+
div,
257+
mono::MP.AbstractMonomialLike,
258+
) where {T}
259+
if !haskey(d, mono)
260+
x = div[mono]
261+
vx = monomial_variable_index(model, d, div, x)
262+
y = MP.div_multiple(mono, x)
263+
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))
270+
end
271+
u = max(bounds...)
272+
d[mono], _ =
273+
MOI.add_constrained_variable(model.model, MOI.Interval(l, u))
274+
MOI.add_constraint(
275+
model,
276+
MA.@rewrite(one(T) * d[mono] - one(T) * vx * vy),
277+
MOI.EqualTo(zero(T)),
278+
)
279+
end
280+
return d[mono]
281+
end
282+
283+
function _add_constraints(model::Optimizer, cis, index_to_var, d, div)
284+
for ci in cis
285+
func = MOI.get(model, MOI.ConstraintFunction(), ci)
286+
set = MOI.get(model, MOI.ConstraintSet(), ci)
287+
func, index_to_var = _subs!(func, index_to_var)
288+
quad = _quad_convert(func.polynomial, d, div)
289+
MOI.add_constraint(model, quad, set)
290+
end
291+
end
292+
293+
function MOI.Utilities.final_touch(model::Optimizer{T}, _) where {T}
294+
index_to_var = nothing
295+
vars = nothing
296+
monos = nothing
297+
if !isnothing(model.objective)
298+
func, index_to_var = _subs!(model.objective, index_to_var)
299+
vars = _add_variables!(func, vars)
300+
monos = _add_monomials!(func, monos)
301+
end
302+
if !isempty(model.constraints)
303+
for S in keys(model.constraints)
304+
for ci in MOI.get(
305+
model,
306+
MOI.ListOfConstraintIndices{
307+
PolyJuMP.ScalarPolynomialFunction{
308+
T,
309+
model.constraints[S][1],
310+
},
311+
S,
312+
}(),
313+
)
314+
func = MOI.get(model, MOI.ConstraintFunction(), ci)
315+
func, index_to_var = _subs!(func, index_to_var)
316+
vars = _add_variables!(func, vars)
317+
monos = _add_monomials!(func, monos)
318+
end
319+
end
320+
end
321+
div = decompose(monos)
322+
for mono in sort(collect(keys(div)))
323+
if haskey(vars, mono)
324+
continue
325+
end
326+
a = div[mono]
327+
monomial_variable_index(model, vars, div, a)
328+
b = MP.div_multiple(mono, a)
329+
monomial_variable_index(model, vars, div, b)
330+
end
331+
if !isnothing(model.objective)
332+
func, index_to_var = _subs!(model.objective, index_to_var)
333+
obj = _quad_convert(func.polynomial, vars, div)
334+
MOI.set(model.model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
335+
end
336+
for S in keys(model.constraints)
337+
F = PolyJuMP.ScalarPolynomialFunction{T,model.constraints[S][1]}
338+
cis = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
339+
_add_constraints(model, cis, index_to_var, vars, div)
340+
end
341+
return
342+
end
343+
344+
function MOI.get(model::Optimizer, attr::MOI.SolverName)
345+
name = MOI.get(model.model, attr)
346+
return "PolyJuMP.QCQP with $name"
347+
end

0 commit comments

Comments
 (0)