33# Use of this source code is governed by an MIT-style license that can be found
44# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
55
6+ function MOI. get (
7+ model:: MOI.ModelLike ,
8+ :: ObjectiveFunctionAttribute{ReverseObjectiveFunction} ,
9+ bridge:: MOI.Bridges.Objective.SlackBridge ,
10+ )
11+ return MOI. get (model, ReverseConstraintFunction (), bridge. constraint)
12+ end
13+
14+ function MOI. set (
15+ model:: MOI.ModelLike ,
16+ :: ObjectiveFunctionAttribute{ForwardObjectiveFunction} ,
17+ bridge:: MOI.Bridges.Objective.SlackBridge ,
18+ value,
19+ )
20+ return MOI. set (model, ForwardConstraintFunction (), bridge. constraint, value)
21+ end
22+
623function MOI. set (
724 model:: MOI.ModelLike ,
825 attr:: ForwardConstraintFunction ,
@@ -44,6 +61,23 @@ function MOI.set(
4461 return MOI. set (model, attr, bridge. constraint, mapped_func)
4562end
4663
64+ function _variable_to_index_map (bridge)
65+ return Dict {MOI.VariableIndex,MOI.VariableIndex} (
66+ v => MOI. VariableIndex (i) for
67+ (i, v) in enumerate (bridge. index_to_variable_map)
68+ )
69+ end
70+
71+ function _U (func:: MOI.VectorAffineFunction , n, variable_to_index_map)
72+ # x'Qx/2 + a'x + β is bridged into
73+ # (1, -a'x - β, U*x) in RSOC
74+ # where Q = U' * U
75+ # The linear part of `func` is `[0; -a'; U]`
76+ Ux = MOI. Utilities. eachscalar (func)[3 : end ]
77+ func_array = sparse_array_representation (Ux, n, variable_to_index_map)
78+ return func_array. terms
79+ end
80+
4781function MOI. get (
4882 model:: MOI.ModelLike ,
4983 attr:: DiffOpt.ReverseConstraintFunction ,
@@ -53,55 +87,167 @@ function MOI.get(
5387 return MOI. Bridges. adjoint_map_function (typeof (bridge), func)
5488end
5589
56- function MOI. set (
90+ function MOI. get (
5791 model:: MOI.ModelLike ,
58- attr:: DiffOpt.ForwardConstraintFunction ,
92+ attr:: DiffOpt.ReverseConstraintFunction ,
5993 bridge:: MOI.Bridges.Constraint.QuadtoSOCBridge{T} ,
60- diff:: MOI.ScalarQuadraticFunction ,
6194) where {T}
62- func = MOI. get (model, MOI. ConstraintFunction (), bridge. soc)
63- index_map = index_map_to_oneto (func)
64- index_map_to_oneto! (index_map, diff)
65- n = length (index_map. var_map)
66- func_array = sparse_array_representation (func, n, index_map)
67- # x'Qx/2 + a'x + β is bridged into
68- # (1, -a'x - β, U*x) in RSOC
69- # where Q = U' * U
70- # `func_array.terms` is `[0; -a'; U]`
71- U = func_array. terms[3 : end , :]
72- diff_array = sparse_array_representation (diff, n, index_map)
73- dU = Matrix (diff_array. quadratic_terms)
74- dU = dU_from_dQ! (Matrix (diff_array. quadratic_terms), U)
75- x = Vector {MOI.VariableIndex} (undef, n)
76- for v in keys (index_map. var_map)
77- x[index_map[v]. value] = v
95+ variable_to_index_map = _variable_to_index_map (bridge)
96+ n = length (bridge. index_to_variable_map)
97+ U = _U (
98+ MOI. get (model, MOI. ConstraintFunction (), bridge. soc),
99+ n,
100+ variable_to_index_map,
101+ )
102+ Δfunc =
103+ convert (MOI. VectorAffineFunction{T}, MOI. get (model, attr, bridge. soc))
104+ filter! (Δfunc. terms) do t
105+ return haskey (variable_to_index_map, t. scalar_term. variable)
78106 end
79- diff_aff = MOI. VectorAffineFunction {T} (
80- MOI. VectorAffineTerm{T}[],
81- zeros (T, size (dU, 1 ) + 2 ),
107+ aff = sparse_array_representation (
108+ - MOI. Utilities. eachscalar (Δfunc)[2 ],
109+ n,
110+ variable_to_index_map,
111+ )
112+ ΔU = _U (Δfunc, n, variable_to_index_map)
113+ ΔQ = ΔQ_from_ΔU! (ΔU, U)
114+ func = MatrixScalarQuadraticFunction (
115+ convert (VectorScalarAffineFunction{T,Vector{T}}, aff),
116+ ΔQ,
82117 )
83- for t in diff. affine_terms
118+ index_map = MOI. Utilities. IndexMap ()
119+ for (i, vi) in enumerate (bridge. index_to_variable_map)
120+ index_map[MOI. VariableIndex (i)] = vi
121+ end
122+ Δ = standard_form (IndexMappedFunction (func, index_map))
123+ if ! bridge. less_than
124+ Δ = - Δ
125+ end
126+ return Δ
127+ end
128+
129+ function _quad_to_soc_diff (
130+ :: MOI.ModelLike ,
131+ bridge:: MOI.Bridges.Constraint.QuadtoSOCBridge{T} ,
132+ diff:: MOI.ScalarAffineFunction{T} ,
133+ ) where {T}
134+ n = length (bridge. index_to_variable_map)
135+ diff_soc =
136+ MOI. VectorAffineFunction {T} (MOI. VectorAffineTerm{T}[], zeros (T, n + 2 ))
137+ for t in diff. terms
84138 push! (
85- diff_aff . terms,
139+ diff_soc . terms,
86140 MOI. VectorAffineTerm (
87141 2 ,
88142 MOI. ScalarAffineTerm (- t. coefficient, t. variable),
89143 ),
90144 )
91145 end
92- diff_aff. constants[2 ] = - diff. constant
146+ diff_soc. constants[2 ] = - diff. constant
147+ return diff_soc
148+ end
149+
150+ function _quad_to_soc_diff (
151+ model:: MOI.ModelLike ,
152+ bridge:: MOI.Bridges.Constraint.QuadtoSOCBridge{T} ,
153+ diff:: MOI.ScalarQuadraticFunction{T} ,
154+ ) where {T}
155+ variable_to_index_map = _variable_to_index_map (bridge)
156+ n = length (bridge. index_to_variable_map)
157+ U = _U (
158+ MOI. get (model, MOI. ConstraintFunction (), bridge. soc),
159+ n,
160+ variable_to_index_map,
161+ )
162+ # We assume `diff.quadratic_terms` only contains quadratic terms with
163+ # variables already in a quadratic term of the initial constraint
164+ # Otherwise, the `U` matrix will not be square so it's a TODO
165+ # We remove the affine terms here as they might have other variables not
166+ # in `variable_to_index_map`
167+ diff_quad = MOI. ScalarQuadraticFunction (
168+ diff. quadratic_terms,
169+ MOI. ScalarAffineTerm{T}[],
170+ zero (T),
171+ )
172+ diff_aff = MOI. ScalarAffineFunction (diff. affine_terms, diff. constant)
173+ diff_array =
174+ sparse_array_representation (diff_quad, n, variable_to_index_map)
175+ dU = dU_from_dQ! (Matrix (diff_array. quadratic_terms), U)
176+ diff_soc = _quad_to_soc_diff (model, bridge, diff_aff)
93177 for i in axes (dU, 1 )
94178 for j in axes (dU, 2 )
95179 if ! iszero (dU[i, j])
96- scalar = MOI. ScalarAffineTerm (dU[i, j], x[j])
97- push! (diff_aff. terms, MOI. VectorAffineTerm (2 + i, scalar))
180+ scalar = MOI. ScalarAffineTerm (
181+ dU[i, j],
182+ bridge. index_to_variable_map[j],
183+ )
184+ push! (diff_soc. terms, MOI. VectorAffineTerm (2 + i, scalar))
98185 end
99186 end
100187 end
101- MOI. set (model, attr, bridge. soc, diff_aff)
188+ return diff_soc
189+ end
190+
191+ function MOI. set (
192+ model:: MOI.ModelLike ,
193+ attr:: DiffOpt.ForwardConstraintFunction ,
194+ bridge:: MOI.Bridges.Constraint.QuadtoSOCBridge{T} ,
195+ diff:: MOI.AbstractScalarFunction ,
196+ ) where {T}
197+ if ! bridge. less_than
198+ diff = - diff
199+ end
200+ diff_soc = _quad_to_soc_diff (model, bridge, diff)
201+ MOI. set (model, attr, bridge. soc, diff_soc)
102202 return
103203end
104204
205+ """
206+ ΔQ_from_ΔU!(ΔU, U)
207+
208+ Return the symmetric solution `ΔQ` of the matrix equation
209+ `triu(ΔU) = 2triu(U * ΔQ)`
210+ where `ΔU` and `U` are the two argument of the function.
211+
212+ This function overwrites the first argument `ΔU` to store the solution.
213+ The matrix `U` is not however modified.
214+
215+ The matrix `U` is assumed to be upper triangular.
216+
217+ We can exploit the structure of `U` here:
218+
219+ * If the factorization was obtained from SVD, `U` would be orthogonal
220+ * If the factorization was obtained from Cholesky, `U` would be upper triangular.
221+
222+ The MOI bridge uses Cholesky in order to exploit sparsity so we are in the
223+ second case.
224+
225+ We can find each column of `ΔQ` by solving a triangular linear system.
226+ """
227+ function ΔQ_from_ΔU! (ΔU, U)
228+ n = LinearAlgebra. checksquare (ΔU)
229+ LinearAlgebra. rdiv! (ΔU, 2 )
230+ for j in n: - 1 : 1
231+ # FIXME MA does not support mutating subarray
232+ # MA.operate!(MA.sub_mul, view(ΔU, 1:j, j), view(U, 1:j, (j + 1):n), view(ΔU, j, (j+1):n))
233+ for row in 1 : j
234+ for col in (j+ 1 ): n
235+ ΔU[row, j] -= U[row, col] * ΔU[j, col]
236+ end
237+ end
238+ _U = LinearAlgebra. UpperTriangular (view (U, 1 : j, 1 : j))
239+ LinearAlgebra. ldiv! (_U, view (ΔU, 1 : j, j))
240+ end
241+ for j in 1 : n
242+ for i in 1 : (j- 1 )
243+ if i != j
244+ ΔU[j, i] = ΔU[i, j]
245+ end
246+ end
247+ end
248+ return ΔU
249+ end
250+
105251"""
106252 dU_from_dQ!(dQ, U)
107253
0 commit comments