1- const GeometricConicForm{T} = MOI. Utilities. GenericModel{
1+ module ConicProgram
2+
3+ using LinearAlgebra, SparseArrays
4+
5+ using MathOptInterface
6+ const MOI = MathOptInterface
7+
8+ import BlockDiagonals
9+ import IterativeSolvers
10+
11+ import DiffOpt
12+
13+ Base. @kwdef struct Cache
14+ M:: SparseMatrixCSC{Float64, Int}
15+ vp:: Vector{Float64}
16+ Dπv:: BlockDiagonals.BlockDiagonal{Float64, Matrix{Float64}}
17+ A:: SparseMatrixCSC{Float64, Int}
18+ b:: Vector{Float64}
19+ c:: Vector{Float64}
20+ end
21+
22+ Base. @kwdef struct ForwCache
23+ du:: Vector{Float64}
24+ dv:: Vector{Float64}
25+ dw:: Vector{Float64}
26+ end
27+ Base. @kwdef struct ReverseCache
28+ g:: Vector{Float64}
29+ πz:: Vector{Float64}
30+ end
31+
32+ # Geometric conic standard form
33+ const Form{T} = MOI. Utilities. GenericModel{
234 T,
335 MOI. Utilities. ObjectiveContainer{T},
436 MOI. Utilities. FreeVariables,
@@ -13,37 +45,54 @@ const GeometricConicForm{T} = MOI.Utilities.GenericModel{
1345 MOI. Utilities. OneBasedIndexing,
1446 },
1547 Vector{T},
16- ProductOfSets{T},
48+ DiffOpt . ProductOfSets{T},
1749 },
1850}
1951
20- mutable struct ConicDiffProblem <: DiffModel
52+ """
53+ Diffopt.ConicProgram.Model <: DiffOpt.AbstractModel
54+
55+ Model to differentiate conic programs.
56+
57+ The forward differentiation computes the product of the derivative (Jacobian) at the
58+ conic program parameters `A`, `b`, `c` to the perturbations `dA`, `db`, `dc`.
59+
60+ The reverse differentiation computes the product of the transpose of the derivative (Jacobian) at the
61+ conic program parameters `A`, `b`, `c` to the perturbations `dx`, `dy`, `ds`.
62+
63+ For theoretical background, refer Section 3 of Differentiating Through a Cone Program, https://arxiv.org/abs/1904.09043
64+ """
65+ mutable struct Model <: DiffOpt.AbstractModel
2166 # storage for problem data in matrix form
22- model:: GeometricConicForm {Float64}
67+ model:: Form {Float64}
2368 # includes maps from matrix indices to problem data held in `optimizer`
2469 # also includes KKT matrices
2570 # also includes the solution
26- gradient_cache:: Union{Nothing,ConicCache }
71+ gradient_cache:: Union{Nothing,Cache }
2772
2873 # caches for sensitivity output
2974 # result from solving KKT/residualmap linear systems
3075 # this allows keeping the same `gradient_cache`
3176 # if only sensitivy input changes
32- forw_grad_cache:: Union{Nothing,ConicForwCache }
33- back_grad_cache:: Union{Nothing,ConicReverseCache }
77+ forw_grad_cache:: Union{Nothing,ForwCache }
78+ back_grad_cache:: Union{Nothing,ReverseCache }
3479
3580 # sensitivity input cache using MOI like sparse format
36- input_cache:: DiffInputCache
81+ input_cache:: DiffOpt.InputCache
3782
3883 x:: Vector{Float64} # Primal
3984 s:: Vector{Float64} # Slack
4085 y:: Vector{Float64} # Dual
4186end
42- function ConicDiffProblem ()
43- return ConicDiffProblem (GeometricConicForm {Float64} (), nothing , nothing , nothing , DiffInputCache (), Float64[], Float64[], Float64[])
87+ function Model ()
88+ return Model (Form {Float64} (), nothing , nothing , nothing , DiffOpt. InputCache (), Float64[], Float64[], Float64[])
89+ end
90+
91+ function MOI. is_empty (model:: Model )
92+ return MOI. is_empty (model. model)
4493end
4594
46- function MOI. empty! (model:: ConicDiffProblem )
95+ function MOI. empty! (model:: Model )
4796 MOI. empty! (model. model)
4897 model. gradient_cache = nothing
4998 model. forw_grad_cache = nothing
@@ -55,25 +104,25 @@ function MOI.empty!(model::ConicDiffProblem)
55104 return
56105end
57106
58- function MOI. supports_constraint (model:: ConicDiffProblem , F:: Type{MOI.VectorAffineFunction{Float64}} , :: Type{S} ) where {S<: MOI.AbstractVectorSet }
59- if add_set_types (model. model. constraints. sets, S)
107+ function MOI. supports_constraint (model:: Model , F:: Type{MOI.VectorAffineFunction{Float64}} , :: Type{S} ) where {S<: MOI.AbstractVectorSet }
108+ if DiffOpt . add_set_types (model. model. constraints. sets, S)
60109 push! (model. model. constraints. caches, Tuple{F,S}[])
61110 push! (model. model. constraints. are_indices_mapped, BitSet ())
62111 end
63112 return MOI. supports_constraint (model. model, F, S)
64113end
65114
66- function MOI. set (model:: ConicDiffProblem , :: MOI.ConstraintPrimalStart , ci:: MOI.ConstraintIndex , value)
115+ function MOI. set (model:: Model , :: MOI.ConstraintPrimalStart , ci:: MOI.ConstraintIndex , value)
67116 MOI. throw_if_not_valid (model, ci)
68- _enlarge_set (model. s, MOI. Utilities. rows (model. model. constraints, ci), value)
117+ DiffOpt . _enlarge_set (model. s, MOI. Utilities. rows (model. model. constraints, ci), value)
69118end
70119
71- function MOI. set (model:: ConicDiffProblem , :: MOI.ConstraintDualStart , ci:: MOI.ConstraintIndex , value)
120+ function MOI. set (model:: Model , :: MOI.ConstraintDualStart , ci:: MOI.ConstraintIndex , value)
72121 MOI. throw_if_not_valid (model, ci)
73- _enlarge_set (model. y, MOI. Utilities. rows (model. model. constraints, ci), value)
122+ DiffOpt . _enlarge_set (model. y, MOI. Utilities. rows (model. model. constraints, ci), value)
74123end
75124
76- function _gradient_cache (model:: ConicDiffProblem )
125+ function _gradient_cache (model:: Model )
77126 if model. gradient_cache != = nothing
78127 return model. gradient_cache
79128 end
@@ -87,7 +136,7 @@ function _gradient_cache(model::ConicDiffProblem)
87136 c = spzeros (size (A, 2 ))
88137 else
89138 obj = MOI. get (model, MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} ())
90- c = sparse_array_representation (obj, size (A, 2 )). terms
139+ c = DiffOpt . sparse_array_representation (obj, size (A, 2 )). terms
91140 if MOI. get (model, MOI. ObjectiveSense ()) == MOI. MAX_SENSE
92141 c = - c
93142 end
@@ -106,7 +155,7 @@ function _gradient_cache(model::ConicDiffProblem)
106155
107156
108157 # find gradient of projections on dual of the cones
109- Dπv = Dπ (v, model. model, model. model. constraints. sets)
158+ Dπv = DiffOpt . Dπ (v, model. model, model. model. constraints. sets)
110159
111160 # Q = [
112161 # 0 A' c;
@@ -130,9 +179,9 @@ function _gradient_cache(model::ConicDiffProblem)
130179 - c' - b' * Dπv 0.0
131180 ]
132181 # find projections on dual of the cones
133- vp = π (v, model. model, model. model. constraints. sets)
182+ vp = DiffOpt . π (v, model. model, model. model. constraints. sets)
134183
135- model. gradient_cache = ConicCache (
184+ model. gradient_cache = Cache (
136185 M = M,
137186 vp = vp,
138187 Dπv = Dπv,
@@ -144,15 +193,7 @@ function _gradient_cache(model::ConicDiffProblem)
144193 return model. gradient_cache
145194end
146195
147- """
148- forward_differentiate!(model::ConicDiffProblem)
149-
150- Method to compute the product of the derivative (Jacobian) at the
151- conic program parameters `A`, `b`, `c` to the perturbations `dA`, `db`, `dc`.
152-
153- For theoretical background, refer Section 3 of Differentiating Through a Cone Program, https://arxiv.org/abs/1904.09043
154- """
155- function forward_differentiate! (model:: ConicDiffProblem )
196+ function DiffOpt. forward_differentiate! (model:: Model )
156197 gradient_cache = _gradient_cache (model)
157198 M = gradient_cache. M
158199 vp = gradient_cache. vp
@@ -164,12 +205,12 @@ function forward_differentiate!(model::ConicDiffProblem)
164205 b = gradient_cache. b
165206 c = gradient_cache. c
166207
167- objective_function = _convert (MOI. ScalarAffineFunction{Float64}, model. input_cache. objective)
168- sparse_array_obj = sparse_array_representation (objective_function, length (c))
208+ objective_function = DiffOpt . _convert (MOI. ScalarAffineFunction{Float64}, model. input_cache. objective)
209+ sparse_array_obj = DiffOpt . sparse_array_representation (objective_function, length (c))
169210 dc = sparse_array_obj. terms
170211
171212 db = zeros (length (b))
172- _fill (S -> false , gradient_cache, model. input_cache, model. model. constraints. sets, db)
213+ DiffOpt . _fill (S -> false , gradient_cache, model. input_cache, model. model. constraints. sets, db)
173214 (lines, cols) = size (A)
174215 nz = nnz (A)
175216 dAi = zeros (Int, 0 )
@@ -178,7 +219,7 @@ function forward_differentiate!(model::ConicDiffProblem)
178219 sizehint! (dAi, nz)
179220 sizehint! (dAj, nz)
180221 sizehint! (dAv, nz)
181- _fill (S -> false , gradient_cache, model. input_cache, model. model. constraints. sets, dAi, dAj, dAv)
222+ DiffOpt . _fill (S -> false , gradient_cache, model. input_cache, model. model. constraints. sets, dAi, dAj, dAv)
182223 dA = sparse (dAi, dAj, dAv, lines, cols)
183224
184225 m = size (A, 1 )
@@ -193,27 +234,19 @@ function forward_differentiate!(model::ConicDiffProblem)
193234 dz = if norm (RHS) <= 1e-400 # TODO : parametrize or remove
194235 RHS .= 0 # because M is square
195236 else
196- lsqr (M, RHS)
237+ IterativeSolvers . lsqr (M, RHS)
197238 end
198239
199240 du, dv, dw = dz[1 : n], dz[n+ 1 : n+ m], dz[n+ m+ 1 ]
200- model. forw_grad_cache = ConicForwCache (du, dv, [dw])
241+ model. forw_grad_cache = ForwCache (du, dv, [dw])
201242 return nothing
202243 # dx = du - x * dw
203244 # dy = Dπv * dv - y * dw
204245 # ds = Dπv * dv - dv - s * dw
205246 # return -dx, -dy, -ds
206247end
207248
208- """
209- reverse_differentiate!(model::ConicDiffProblem)
210-
211- Method to compute the product of the transpose of the derivative (Jacobian) at the
212- conic program parameters `A`, `b`, `c` to the perturbations `dx`, `dy`, `ds`.
213-
214- For theoretical background, refer Section 3 of Differentiating Through a Cone Program, https://arxiv.org/abs/1904.09043
215- """
216- function reverse_differentiate! (model:: ConicDiffProblem )
249+ function DiffOpt. reverse_differentiate! (model:: Model )
217250 gradient_cache = _gradient_cache (model)
218251 M = gradient_cache. M
219252 vp = gradient_cache. vp
@@ -248,7 +281,7 @@ function reverse_differentiate!(model::ConicDiffProblem)
248281 g = if norm (dz) <= 1e-4 # TODO : parametrize or remove
249282 dz .= 0 # because M is square
250283 else
251- lsqr (M, dz)
284+ IterativeSolvers . lsqr (M, dz)
252285 end
253286
254287 πz = [
@@ -262,7 +295,7 @@ function reverse_differentiate!(model::ConicDiffProblem)
262295 # http://reports-archive.adm.cs.cmu.edu/anon/2019/CMU-CS-19-109.pdf
263296 # pg 97, cap 7.4.2
264297
265- model. back_grad_cache = ConicReverseCache (g, πz)
298+ model. back_grad_cache = ReverseCache (g, πz)
266299 return nothing
267300 # dQ = - g * πz'
268301 # dA = - dQ[1:n, n+1:n+m]' + dQ[n+1:n+m, 1:n]
@@ -271,36 +304,38 @@ function reverse_differentiate!(model::ConicDiffProblem)
271304 # return dA, db, dc
272305end
273306
274- function MOI. get (model:: ConicDiffProblem , :: ReverseObjectiveFunction )
307+ function MOI. get (model:: Model , :: DiffOpt. ReverseObjectiveFunction )
275308 g = model. back_grad_cache. g
276309 πz = model. back_grad_cache. πz
277- dc = lazy_combination (- , πz, g, length (g))
278- return VectorScalarAffineFunction (dc, 0.0 )
310+ dc = DiffOpt . lazy_combination (- , πz, g, length (g))
311+ return DiffOpt . VectorScalarAffineFunction (dc, 0.0 )
279312end
280313
281- function MOI. get (model:: ConicDiffProblem , :: ForwardVariablePrimal , vi:: MOI.VariableIndex )
314+ function MOI. get (model:: Model , :: DiffOpt. ForwardVariablePrimal , vi:: MOI.VariableIndex )
282315 i = vi. value
283316 du = model. forw_grad_cache. du
284317 dw = model. forw_grad_cache. dw
285- return - (du[i] - model. x[i] * dw[])
318+ return - (du[i] - model. x[i] * dw[])
286319end
287- function _get_db (model:: ConicDiffProblem , ci:: CI {F,S}
320+ function DiffOpt . _get_db (model:: Model , ci:: MOI.ConstraintIndex {F,S}
288321) where {F<: MOI.AbstractVectorFunction ,S}
289322 i = MOI. Utilities. rows (model. model. constraints, ci) # vector
290323 # i = ci.value
291324 n = length (model. x) # columns in A
292325 # db = - dQ[n+1:n+m, end] + dQ[end, n+1:n+m]'
293326 g = model. back_grad_cache. g
294327 πz = model. back_grad_cache. πz
295- return lazy_combination (- , πz, g, length (g), n .+ i)
328+ return DiffOpt . lazy_combination (- , πz, g, length (g), n .+ i)
296329end
297- function _get_dA (model:: ConicDiffProblem , ci:: CI {<:MOI.AbstractVectorFunction} )
330+ function DiffOpt . _get_dA (model:: Model , ci:: MOI.ConstraintIndex {<:MOI.AbstractVectorFunction} )
298331 i = MOI. Utilities. rows (model. model. constraints, ci) # vector
299332 # i = ci.value
300333 n = length (model. x) # columns in A
301334 m = length (model. y) # lines in A
302335 # dA = - dQ[1:n, n+1:n+m]' + dQ[n+1:n+m, 1:n]
303336 g = model. back_grad_cache. g
304337 πz = model. back_grad_cache. πz
305- return lazy_combination (- , g, πz, i, n .+ (1 : n))
338+ return DiffOpt. lazy_combination (- , g, πz, i, n .+ (1 : n))
339+ end
340+
306341end
0 commit comments