99
1010`QuadtoSOCBridge` converts quadratic inequalities
1111```math
12- \\ frac{1}{2}x^T Q x + a^T x + b \\ le 0
12+ \\ frac{1}{2}x^T Q x + a^T x \\ le ub
1313```
1414into [`MOI.RotatedSecondOrderCone`](@ref) constraints, but it only applies when
1515``Q`` is positive definite.
1616
1717This is because, if `Q` is positive definite, there exists `U` such that
1818``Q = U^T U``, and so the inequality can then be rewritten as;
1919```math
20- \\ |U x\\ |_2^2 \\ le 2 (-a^T x - b )
20+ \\ |U x\\ |_2^2 \\ le 2 (-a^T x + ub )
2121```
2222
23- Therefore, `QuadtoSOCBridge` implements the following reformulation :
23+ Therefore, `QuadtoSOCBridge` implements the following reformulations :
2424
25- * ``\\ frac{1}{2}x^T Q x + a^T x + b \\ le 0`` into
26- ``(1, -a^T x - b, Ux) \\ in RotatedSecondOrderCone``
25+ * ``\\ frac{1}{2}x^T Q x + a^T x \\ le ub`` into
26+ ``(1, -a^T x + ub, Ux) \\ in RotatedSecondOrderCone``
27+ where ``Q = U^T U``
28+ * ``\\ frac{1}{2}x^T Q x + a^T x \\ ge lb`` into
29+ ``(1, a^T x - lb, Ux) \\ in RotatedSecondOrderCone``
30+ where ``-Q = U^T U``
2731
2832## Source node
2933
@@ -50,6 +54,7 @@ struct QuadtoSOCBridge{T} <: AbstractBridge
5054 dimension:: Int # dimension of the SOC constraint
5155 less_than:: Bool # whether the constraint was ≤ or ≥
5256 set_constant:: T # the constant that was on the set
57+ index_to_variable_map:: Vector{MOI.VariableIndex}
5358end
5459
5560const QuadtoSOC{T,OT<: MOI.ModelLike } =
@@ -84,17 +89,17 @@ function bridge_constraint(
8489 )
8590 end
8691 # Construct the VectorAffineFunction. We're aiming for:
87- # | 1 |
88- # | -a^T x - b | ∈ RotatedSecondOrderCone()
89- # | Lx + 0 |
92+ # | 1 |
93+ # | -a^T x + ub | ∈ RotatedSecondOrderCone()
94+ # | Ux + 0 |
9095 # Start with the -a^T x terms...
9196 vector_terms = MOI. VectorAffineTerm{T}[
9297 MOI. VectorAffineTerm (
9398 2 ,
9499 MOI. ScalarAffineTerm (scale * term. coefficient, term. variable),
95100 ) for term in func. affine_terms
96101 ]
97- # Add the Lx terms...
102+ # Note that `L = U'` so we add the terms of L'x
98103 L, p = SparseArrays. sparse (F. L), F. p
99104 I, J, V = SparseArrays. findnz (L)
100105 for i in 1 : length (V)
@@ -108,31 +113,32 @@ function bridge_constraint(
108113 MOI. VectorAffineTerm (J[i] + 2 , MOI. ScalarAffineTerm (V[i], xi)),
109114 )
110115 end
111- # This is the [1, b , 0] vector...
116+ # This is the [1, ub , 0] vector...
112117 set_constant = MOI. constant (set)
113- vector_constant = vcat (
114- one (T),
115- scale * (func. constant - set_constant),
116- zeros (T, size (L, 1 )),
117- )
118+ MOI. throw_if_scalar_and_constant_not_zero (func, typeof (set))
119+ vector_constant = vcat (one (T), - scale * set_constant, zeros (T, size (L, 1 )))
118120 f = MOI. VectorAffineFunction (vector_terms, vector_constant)
119121 dimension = MOI. output_dimension (f)
120122 soc = MOI. add_constraint (model, f, MOI. RotatedSecondOrderCone (dimension))
121- return QuadtoSOCBridge (soc, dimension, less_than, set_constant)
123+ return QuadtoSOCBridge (
124+ soc,
125+ dimension,
126+ less_than,
127+ set_constant,
128+ index_to_variable_map,
129+ )
122130end
123131
124132function _matrix_from_quadratic_terms (
125133 terms:: Vector{MOI.ScalarQuadraticTerm{T}} ,
126134) where {T}
127135 variable_to_index_map = Dict {MOI.VariableIndex,Int} ()
128- index_to_variable_map = Dict {Int,MOI.VariableIndex} ()
129- n = 0
136+ index_to_variable_map = MOI. VariableIndex[]
130137 for term in terms
131138 for variable in (term. variable_1, term. variable_2)
132139 if ! (variable in keys (variable_to_index_map))
133- n += 1
134- variable_to_index_map[variable] = n
135- index_to_variable_map[n] = variable
140+ push! (index_to_variable_map, variable)
141+ variable_to_index_map[variable] = length (index_to_variable_map)
136142 end
137143 end
138144 end
@@ -150,6 +156,7 @@ function _matrix_from_quadratic_terms(
150156 end
151157 end
152158 # Duplicate terms are summed together in `sparse`
159+ n = length (index_to_variable_map)
153160 return SparseArrays. sparse (I, J, V, n, n), index_to_variable_map
154161end
155162
@@ -209,13 +216,32 @@ function MOI.delete(model::MOI.ModelLike, bridge::QuadtoSOCBridge)
209216 return
210217end
211218
212- # Attributes, Bridge acting as a constraint
219+ function MOI. supports (
220+ model:: MOI.ModelLike ,
221+ attr:: Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart} ,
222+ :: Type{QuadtoSOCBridge{T}} ,
223+ ) where {T}
224+ F, S = MOI. VectorAffineFunction{T}, MOI. RotatedSecondOrderCone
225+ return MOI. supports (model, MOI. VariablePrimalStart (), MOI. VariableIndex) &&
226+ MOI. supports (model, attr, MOI. ConstraintIndex{F,S})
227+ end
228+
213229function MOI. get (
214230 model:: MOI.ModelLike ,
215- attr:: MOI.ConstraintPrimal ,
231+ attr:: Union{ MOI.ConstraintPrimal,MOI.ConstraintPrimalStart} ,
216232 bridge:: QuadtoSOCBridge ,
217233)
234+ # The constraint primal is x'Qx/2 + a'x
235+ # If `less_than` then `Q = U'U` and we have the value of
236+ # `Ux` and `-a'x + ub`, so we get it with
237+ # `(Ux)'Ux / 2 - (-a'x + ub) + ub`
238+ # Otherwise, `Q = -U'U` and we have the value of
239+ # `Ux` and `a'x - ub`, so we get it with
240+ # `-(Ux)'Ux / 2 + (a'x - lb) + ub`
218241 soc = MOI. get (model, attr, bridge. soc)
242+ if soc === nothing
243+ return nothing
244+ end
219245 output = sum (soc[i]^ 2 for i in 3 : bridge. dimension)
220246 output /= 2
221247 output -= soc[1 ] * soc[2 ]
@@ -226,6 +252,68 @@ function MOI.get(
226252 return output
227253end
228254
255+ function _primal_start_or_error (model, attr, v)
256+ var_attr = MOI. VariablePrimalStart ()
257+ value = MOI. get (model, MOI. VariablePrimalStart (), v)
258+ if isnothing (value)
259+ error (
260+ " In order to set the `$attr `, the" ,
261+ " `MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the " ,
262+ " `$var_attr ` but it is not set. Set the `$var_attr ` first before " ,
263+ " setting the `$attr ` in order to fix this." ,
264+ )
265+ end
266+ return value
267+ end
268+
269+ function MOI. set (
270+ model:: MOI.ModelLike ,
271+ attr:: MOI.ConstraintPrimalStart ,
272+ bridge:: QuadtoSOCBridge{T} ,
273+ value,
274+ ) where {T}
275+ # `value` represent `x'Qx/2 + a'x + ε` where `ε` is
276+ # the difference between the value of the slack variable and the value of the function.
277+ # That is, if `less_than`, we set
278+ # | 1 |
279+ # | -a'x - ε + ub |
280+ # | U * x |
281+ # which is obtained as
282+ # | 1 |
283+ # | x'Qx/2 - value + ub |
284+ # | U * x |
285+ # Otherwise, we set
286+ # | 1 |
287+ # | a'x + ε - lb |
288+ # | U * x |
289+ # which is obtained as
290+ # | 1 |
291+ # | value - x'Qx/2 - lb |
292+ # | U * x |
293+ # where we compute `x'Qx/2` and `U * x` using the starting values of the variable.
294+ soc = MOI. get (model, MOI. ConstraintFunction (), bridge. soc)
295+ Ux = MOI. Utilities. eval_variables (MOI. Utilities. eachscalar (soc)[3 : end ]) do v
296+ return _primal_start_or_error (model, attr, v)
297+ end
298+ if bridge. less_than
299+ s2 = Ux' Ux / 2 - value + bridge. set_constant
300+ else
301+ s2 = Ux' Ux / 2 + value - bridge. set_constant
302+ end
303+ MOI. set (model, attr, bridge. soc, [1 ; s2; Ux])
304+ return
305+ end
306+
307+ function MOI. set (
308+ model:: MOI.ModelLike ,
309+ attr:: MOI.ConstraintPrimalStart ,
310+ bridge:: QuadtoSOCBridge ,
311+ :: Nothing ,
312+ )
313+ MOI. set (model, attr, bridge. soc, nothing )
314+ return
315+ end
316+
229317# Lemma: If (1, s, x), (v, u, y) in RotatedSecondOrderCone and
230318# (1, s, x) ⋅ (v, u, y) = 0, then we have
231319# y = -u*x, v = u*||x||_2^2/2.
@@ -261,13 +349,48 @@ end
261349# is exactly the same. Q.E.D.
262350function MOI. get (
263351 model:: MOI.ModelLike ,
264- attr:: MOI.ConstraintDual ,
352+ attr:: Union{ MOI.ConstraintDual,MOI.ConstraintDualStart} ,
265353 bridge:: QuadtoSOCBridge ,
266354)
267- λ = MOI. get (model, attr, bridge. soc)[2 ]
355+ dual = MOI. get (model, attr, bridge. soc)
356+ if dual === nothing
357+ return nothing
358+ end
359+ λ = dual[2 ]
268360 return bridge. less_than ? - λ : λ
269361end
270362
363+ # Let `(v, u, y)` be the dual of the RSOC and `λ` be the dual of the quadratic.
364+ # From same reasoning as above, we know that `u` is `-λ`.
365+ # From the Lemma above, we have ``
366+ # `y = -u * x`, `v = u*||x||_2^2/2`
367+ function MOI. set (
368+ model:: MOI.ModelLike ,
369+ attr:: MOI.ConstraintDualStart ,
370+ bridge:: QuadtoSOCBridge{T} ,
371+ λ,
372+ ) where {T}
373+ u = bridge. less_than ? - λ : λ
374+ x = T[
375+ _primal_start_or_error (model, attr, xi) for
376+ xi in bridge. index_to_variable_map
377+ ]
378+ v = u * sum (x .^ 2 ) / 2
379+ y = - u * x
380+ MOI. set (model, attr, bridge. soc, [v; u; y])
381+ return
382+ end
383+
384+ function MOI. set (
385+ model:: MOI.ModelLike ,
386+ attr:: MOI.ConstraintDualStart ,
387+ bridge:: QuadtoSOCBridge ,
388+ :: Nothing ,
389+ )
390+ MOI. set (model, attr, bridge. soc, nothing )
391+ return
392+ end
393+
271394function MOI. get (
272395 :: MOI.ModelLike ,
273396 :: MOI.ConstraintSet ,
0 commit comments