1111 objective_sink::Symbol = :none,
1212 ) where {T,M}
1313
14- A model layer that stores affine and quadratic objectives and constraints in
15- a [`QPBlockData`](@ref) and forwards everything else to the `inner` model,
16- typically a [`Model`](@ref).
14+ A model layer that owns the variables of the model, stores affine and
15+ quadratic objectives and constraints in a [`QPBlockData`](@ref), and forwards
16+ everything else to the `inner` model, typically a [`Model`](@ref).
1717
1818`ModelWithQuad(inner)` and `ModelWithQuad{T}(inner)` create an empty
1919[`QPBlockData`](@ref), with `T` defaulting to `Float64`.
2020
21+ Add variables with `MOI.add_variable`: the layer guarantees that the variable
22+ indices are `1:n`, like `MOI.Utilities.MatrixOfConstraints`. Add parameters
23+ with `MOI.add_constrained_variable(model, ::MOI.Parameter)`: parameters get
24+ indices offset by [`_PARAMETER_OFFSET`](@ref), their values are stored in the
25+ inner model through [`add_parameter`](@ref), and `qp.parameters` aliases that
26+ storage, so a parameter update is visible to both blocks.
27+
2128Add constraints with [`add_constraint`](@ref) or `MOI.add_constraint`, and
2229set the objective with [`set_objective`](@ref): affine and quadratic
2330functions are routed to the QP block, everything else to the inner model.
2431`objective_sink` records where the objective currently lives (`:none`,
2532`:quad` or `:inner`).
2633
2734Create the corresponding evaluator, [`EvaluatorWithQuad`](@ref), with
28- `Evaluator(model, backend, ordered_variables)`, or construct it directly from
29- an inner `MOI.AbstractNLPEvaluator`. The rows of the QP block come first,
30- followed by the rows of the inner evaluator.
31-
32- Functions added to the QP block may contain parameters, following the
33- convention documented in [`QPBlockData`](@ref): a variable is a parameter if
34- and only if its index is a key of `qp.parameters`.
35+ `Evaluator(model, backend)`, or construct it directly from an inner
36+ `MOI.AbstractNLPEvaluator`. The rows of the QP block come first, followed by
37+ the rows of the inner evaluator.
3538"""
3639mutable struct ModelWithQuad{T,M}
40+ variables:: MOI.Utilities.VariablesContainer{T}
3741 qp:: QPBlockData{T}
3842 inner:: M
3943 objective_sink:: Symbol # :none, :quad or :inner
@@ -43,7 +47,14 @@ mutable struct ModelWithQuad{T,M}
4347 inner:: M ;
4448 objective_sink:: Symbol = :none ,
4549 ) where {T,M}
46- return new {T,M} (qp, inner, objective_sink)
50+ model = new {T,M} (
51+ MOI. Utilities. VariablesContainer {T} (),
52+ qp,
53+ inner,
54+ objective_sink,
55+ )
56+ _share_parameters (model. qp, model. inner)
57+ return model
4758 end
4859end
4960
5364
5465ModelWithQuad (inner) = ModelWithQuad {Float64} (inner)
5566
67+ # The QP block reads the parameter values from the inner model's storage.
68+ _share_parameters (:: QPBlockData , :: Any ) = nothing
69+
70+ function _share_parameters (qp:: QPBlockData{Float64} , inner:: Model )
71+ qp. parameters = inner. parameters
72+ return
73+ end
74+
75+ # The variables and the parameters.
76+
77+ MOI. add_variable (model:: ModelWithQuad ) = MOI. add_variable (model. variables)
78+
79+ function MOI. add_constrained_variable (
80+ model:: ModelWithQuad{T} ,
81+ set:: MOI.Parameter{T} ,
82+ ) where {T}
83+ p = add_parameter (model. inner, set. value)
84+ x = MOI. VariableIndex (_PARAMETER_OFFSET + p. value)
85+ ci = MOI. ConstraintIndex {MOI.VariableIndex,MOI.Parameter{T}} (x. value)
86+ return x, ci
87+ end
88+
89+ function MOI. is_valid (model:: ModelWithQuad , x:: MOI.VariableIndex )
90+ if _is_parameter (x)
91+ return 1 <= x. value - _PARAMETER_OFFSET <= length (model. qp. parameters)
92+ end
93+ return MOI. is_valid (model. variables, x)
94+ end
95+
96+ function MOI. is_valid (
97+ model:: ModelWithQuad{T} ,
98+ ci:: MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}} ,
99+ ) where {T}
100+ return MOI. is_valid (model, MOI. VariableIndex (ci. value))
101+ end
102+
103+ function MOI. get (
104+ model:: ModelWithQuad{T} ,
105+ :: MOI.NumberOfConstraints{MOI.VariableIndex,MOI.Parameter{T}} ,
106+ ) where {T}
107+ return length (model. qp. parameters)
108+ end
109+
110+ function MOI. get (
111+ model:: ModelWithQuad{T} ,
112+ :: MOI.ListOfConstraintIndices{F,S} ,
113+ ) where {T,F<: MOI.VariableIndex ,S<: MOI.Parameter{T} }
114+ n = length (model. qp. parameters)
115+ return MOI. ConstraintIndex {F,S} .(_PARAMETER_OFFSET .+ (1 : n))
116+ end
117+
118+ function MOI. get (
119+ model:: ModelWithQuad{T} ,
120+ :: MOI.ConstraintFunction ,
121+ ci:: MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}} ,
122+ ) where {T}
123+ return MOI. VariableIndex (ci. value)
124+ end
125+
126+ function MOI. get (
127+ model:: ModelWithQuad{T} ,
128+ :: MOI.ConstraintSet ,
129+ ci:: MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}} ,
130+ ) where {T}
131+ return MOI. Parameter (model. qp. parameters[ci. value- _PARAMETER_OFFSET])
132+ end
133+
134+ function MOI. set (
135+ model:: ModelWithQuad{T} ,
136+ :: MOI.ConstraintSet ,
137+ ci:: MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}} ,
138+ set:: MOI.Parameter{T} ,
139+ ) where {T}
140+ model. qp. parameters[ci. value- _PARAMETER_OFFSET] = set. value
141+ return
142+ end
143+
56144"""
57145 Base.length(model::ModelWithQuad)
58146
@@ -204,101 +292,63 @@ end
204292 EvaluatorWithQuad(
205293 model::ModelWithQuad,
206294 inner::MOI.AbstractNLPEvaluator,
207- ordered_variables::Vector{MOI.VariableIndex},
208295 ) <: MOI.AbstractNLPEvaluator
209296
210297The evaluator of a [`ModelWithQuad`](@ref) layer. It implements the
211298[`MOI.AbstractNLPEvaluator`](@ref) interface: the rows of the QP block come
212299first, followed by the rows of `inner`, and the Jacobian and Hessian product
213300callbacks compose the contributions of the two blocks.
214301
215- Create it with `Evaluator(model::ModelWithQuad, backend, ordered_variables )`,
216- which recursively creates the evaluator of the inner model, or construct it
217- directly from an existing inner evaluator.
302+ Create it with `Evaluator(model::ModelWithQuad, backend)`, which recursively
303+ creates the evaluator of the inner model, or construct it directly from an
304+ existing inner evaluator.
218305
219- During [`MOI.initialize `](@ref), the variables of the QP block are mapped to
220- their consecutive 1-based index in `ordered_variables`; variables absent from
221- `ordered_variables` are parameters and keep their index .
306+ The QP block is evaluated as stored: [`ModelWithQuad `](@ref) owns the
307+ variables of the model, so their indices are the columns `1:n` and no
308+ remapping is needed .
222309"""
223310mutable struct EvaluatorWithQuad{T,M,E<: MOI.AbstractNLPEvaluator } < :
224311 MOI. AbstractNLPEvaluator
225312 model:: ModelWithQuad{T,M}
226313 inner:: E
227- ordered_variables:: Vector{MOI.VariableIndex}
228- # A copy of `model.qp` with the variables mapped to their consecutive
229- # 1-based index in `ordered_variables`, and the number of entries of its
230- # Jacobian and of its Hessian of the Lagrangian. Rebuilt during
231- # `MOI.initialize`.
232- qp:: QPBlockData{T}
314+ # The number of entries of the Jacobian and of the Hessian of the
315+ # Lagrangian of the QP block, computed during `MOI.initialize`.
233316 qp_nnzj:: Int
234317 qp_nnzh:: Int
235318
236319 function EvaluatorWithQuad (
237320 model:: ModelWithQuad{T,M} ,
238321 inner:: E ,
239- ordered_variables:: Vector{MOI.VariableIndex} ,
240322 ) where {T,M,E<: MOI.AbstractNLPEvaluator }
241- return new {T,M,E} (
242- model,
243- inner,
244- ordered_variables,
245- QPBlockData {T} (),
246- 0 ,
247- 0 ,
248- )
323+ return new {T,M,E} (model, inner, 0 , 0 )
249324 end
250325end
251326
252327function Evaluator (
253328 model:: ModelWithQuad ,
254329 backend:: AbstractAutomaticDifferentiation ,
255- ordered_variables:: Vector{MOI.VariableIndex} ,
256330)
257- inner = Evaluator (model. inner, backend, ordered_variables)
258- return EvaluatorWithQuad (model, inner, ordered_variables)
331+ vars = MOI. get (model. variables, MOI. ListOfVariableIndices ())
332+ inner = Evaluator (model. inner, backend, vars)
333+ return EvaluatorWithQuad (model, inner)
259334end
260335
261336function MOI. features_available (d:: EvaluatorWithQuad )
262337 features = MOI. features_available (d. inner)
263338 return filter (f -> f in (:Grad , :Jac , :JacVec , :Hess , :HessVec ), features)
264339end
265340
266- function MOI. initialize (
267- d:: EvaluatorWithQuad{T} ,
268- features:: Vector{Symbol} ,
269- ) where {T}
270- index_map = Dict {MOI.VariableIndex,MOI.VariableIndex} (
271- x => MOI. VariableIndex (i) for (i, x) in enumerate (d. ordered_variables)
272- )
273- # Variables absent from `ordered_variables` are parameters: they keep
274- # their index, which the `parameters` dictionary uses as key.
275- fmap = v:: MOI.VariableIndex -> get (index_map, v, v)
276- src = d. model. qp
277- qp = QPBlockData {T} ()
278- qp. objective = MOI. Utilities. map_indices (fmap, src. objective)
279- qp. objective_function_type = src. objective_function_type
280- for f in src. constraints
281- push! (qp. constraints, MOI. Utilities. map_indices (fmap, f))
282- end
283- append! (qp. g_L, src. g_L)
284- append! (qp. g_U, src. g_U)
285- append! (qp. mult_g, src. mult_g)
286- append! (qp. function_type, src. function_type)
287- append! (qp. bound_type, src. bound_type)
288- # Alias, do not copy: parameter values updated in the model between
289- # solves must be visible to the evaluator without re-initializing.
290- qp. parameters = src. parameters
291- d. qp = qp
292- d. qp_nnzj = length (MOI. jacobian_structure (qp))
293- d. qp_nnzh = length (MOI. hessian_lagrangian_structure (qp))
341+ function MOI. initialize (d:: EvaluatorWithQuad , features:: Vector{Symbol} )
342+ d. qp_nnzj = length (MOI. jacobian_structure (d. model. qp))
343+ d. qp_nnzh = length (MOI. hessian_lagrangian_structure (d. model. qp))
294344 MOI. initialize (d. inner, features)
295345 return
296346end
297347
298348function MOI. eval_objective (d:: EvaluatorWithQuad{T} , x) where {T}
299349 sink = d. model. objective_sink
300350 if sink == :quad
301- return MOI. eval_objective (d. qp, x)
351+ return MOI. eval_objective (d. model . qp, x)
302352 elseif sink == :inner
303353 return MOI. eval_objective (d. inner, x)
304354 else
309359function MOI. eval_objective_gradient (d:: EvaluatorWithQuad{T} , grad, x) where {T}
310360 sink = d. model. objective_sink
311361 if sink == :quad
312- MOI. eval_objective_gradient (d. qp, grad, x)
362+ MOI. eval_objective_gradient (d. model . qp, grad, x)
313363 elseif sink == :inner
314364 MOI. eval_objective_gradient (d. inner, grad, x)
315365 else
@@ -319,15 +369,15 @@ function MOI.eval_objective_gradient(d::EvaluatorWithQuad{T}, grad, x) where {T}
319369end
320370
321371function MOI. eval_constraint (d:: EvaluatorWithQuad , g, x)
322- m = length (d. qp)
323- MOI. eval_constraint (d. qp, view (g, 1 : m), x)
372+ m = length (d. model . qp)
373+ MOI. eval_constraint (d. model . qp, view (g, 1 : m), x)
324374 MOI. eval_constraint (d. inner, view (g, (m+ 1 ): length (g)), x)
325375 return
326376end
327377
328378function MOI. jacobian_structure (d:: EvaluatorWithQuad )
329- J = MOI. jacobian_structure (d. qp)
330- offset = length (d. qp)
379+ J = MOI. jacobian_structure (d. model . qp)
380+ offset = length (d. model . qp)
331381 # An evaluator is only required to implement `jacobian_structure` if it
332382 # supports `:Jac`. If the inner evaluator does not (it then must not have
333383 # any rows for the stack to be usable), append nothing.
@@ -340,24 +390,24 @@ function MOI.jacobian_structure(d::EvaluatorWithQuad)
340390end
341391
342392function MOI. eval_constraint_jacobian (d:: EvaluatorWithQuad , J, x)
343- MOI. eval_constraint_jacobian (d. qp, J, x)
393+ MOI. eval_constraint_jacobian (d. model . qp, J, x)
344394 MOI. eval_constraint_jacobian (d. inner, view (J, (d. qp_nnzj+ 1 ): length (J)), x)
345395 return
346396end
347397
348398function MOI. hessian_lagrangian_structure (d:: EvaluatorWithQuad )
349- H = MOI. hessian_lagrangian_structure (d. qp)
399+ H = MOI. hessian_lagrangian_structure (d. model . qp)
350400 if :Hess in MOI. features_available (d. inner)
351401 append! (H, MOI. hessian_lagrangian_structure (d. inner))
352402 end
353403 return H
354404end
355405
356406function MOI. eval_hessian_lagrangian (d:: EvaluatorWithQuad , H, x, σ, μ)
357- m = length (d. qp)
358- # If the objective is not in the QP block, `d.qp.objective` is zero, so
407+ m = length (d. model . qp)
408+ # If the objective is not in the QP block, `d.model. qp.objective` is zero, so
359409 # passing `σ` is harmless; and vice versa for the inner evaluator.
360- MOI. eval_hessian_lagrangian (d. qp, H, x, σ, view (μ, 1 : m))
410+ MOI. eval_hessian_lagrangian (d. model . qp, H, x, σ, view (μ, 1 : m))
361411 MOI. eval_hessian_lagrangian (
362412 d. inner,
363413 view (H, (d. qp_nnzh+ 1 ): length (H)),
@@ -372,14 +422,14 @@ end
372422# block write its own rows.
373423function MOI. eval_constraint_jacobian_product (d:: EvaluatorWithQuad , y, x, w)
374424 fill! (y, zero (eltype (y)))
375- m = length (d. qp)
425+ m = length (d. model . qp)
376426 MOI. eval_constraint_jacobian_product (
377427 d. inner,
378428 view (y, (m+ 1 ): length (y)),
379429 x,
380430 w,
381431 )
382- _add_constraint_jacobian_product (d. qp, y, x, w)
432+ _add_constraint_jacobian_product (d. model . qp, y, x, w)
383433 return
384434end
385435
@@ -393,14 +443,14 @@ function MOI.eval_constraint_jacobian_transpose_product(
393443 w,
394444)
395445 fill! (y, zero (eltype (y)))
396- m = length (d. qp)
446+ m = length (d. model . qp)
397447 MOI. eval_constraint_jacobian_transpose_product (
398448 d. inner,
399449 y,
400450 x,
401451 view (w, (m+ 1 ): length (w)),
402452 )
403- _add_constraint_jacobian_transpose_product (d. qp, y, x, view (w, 1 : m))
453+ _add_constraint_jacobian_transpose_product (d. model . qp, y, x, view (w, 1 : m))
404454 return
405455end
406456
@@ -413,7 +463,7 @@ function MOI.eval_hessian_lagrangian_product(
413463 μ,
414464)
415465 fill! (H, zero (eltype (H)))
416- m = length (d. qp)
466+ m = length (d. model . qp)
417467 MOI. eval_hessian_lagrangian_product (
418468 d. inner,
419469 H,
@@ -422,7 +472,7 @@ function MOI.eval_hessian_lagrangian_product(
422472 σ,
423473 view (μ, (m+ 1 ): length (μ)),
424474 )
425- _add_hessian_lagrangian_product (d. qp, H, x, v, σ, view (μ, 1 : m))
475+ _add_hessian_lagrangian_product (d. model . qp, H, x, v, σ, view (μ, 1 : m))
426476 return
427477end
428478
0 commit comments