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{
2
34
T,
3
35
MOI. Utilities. ObjectiveContainer{T},
4
36
MOI. Utilities. FreeVariables,
@@ -13,37 +45,54 @@ const GeometricConicForm{T} = MOI.Utilities.GenericModel{
13
45
MOI. Utilities. OneBasedIndexing,
14
46
},
15
47
Vector{T},
16
- ProductOfSets{T},
48
+ DiffOpt . ProductOfSets{T},
17
49
},
18
50
}
19
51
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
21
66
# storage for problem data in matrix form
22
- model:: GeometricConicForm {Float64}
67
+ model:: Form {Float64}
23
68
# includes maps from matrix indices to problem data held in `optimizer`
24
69
# also includes KKT matrices
25
70
# also includes the solution
26
- gradient_cache:: Union{Nothing,ConicCache }
71
+ gradient_cache:: Union{Nothing,Cache }
27
72
28
73
# caches for sensitivity output
29
74
# result from solving KKT/residualmap linear systems
30
75
# this allows keeping the same `gradient_cache`
31
76
# 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 }
34
79
35
80
# sensitivity input cache using MOI like sparse format
36
- input_cache:: DiffInputCache
81
+ input_cache:: DiffOpt.InputCache
37
82
38
83
x:: Vector{Float64} # Primal
39
84
s:: Vector{Float64} # Slack
40
85
y:: Vector{Float64} # Dual
41
86
end
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)
44
93
end
45
94
46
- function MOI. empty! (model:: ConicDiffProblem )
95
+ function MOI. empty! (model:: Model )
47
96
MOI. empty! (model. model)
48
97
model. gradient_cache = nothing
49
98
model. forw_grad_cache = nothing
@@ -55,25 +104,25 @@ function MOI.empty!(model::ConicDiffProblem)
55
104
return
56
105
end
57
106
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)
60
109
push! (model. model. constraints. caches, Tuple{F,S}[])
61
110
push! (model. model. constraints. are_indices_mapped, BitSet ())
62
111
end
63
112
return MOI. supports_constraint (model. model, F, S)
64
113
end
65
114
66
- function MOI. set (model:: ConicDiffProblem , :: MOI.ConstraintPrimalStart , ci:: MOI.ConstraintIndex , value)
115
+ function MOI. set (model:: Model , :: MOI.ConstraintPrimalStart , ci:: MOI.ConstraintIndex , value)
67
116
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)
69
118
end
70
119
71
- function MOI. set (model:: ConicDiffProblem , :: MOI.ConstraintDualStart , ci:: MOI.ConstraintIndex , value)
120
+ function MOI. set (model:: Model , :: MOI.ConstraintDualStart , ci:: MOI.ConstraintIndex , value)
72
121
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)
74
123
end
75
124
76
- function _gradient_cache (model:: ConicDiffProblem )
125
+ function _gradient_cache (model:: Model )
77
126
if model. gradient_cache != = nothing
78
127
return model. gradient_cache
79
128
end
@@ -87,7 +136,7 @@ function _gradient_cache(model::ConicDiffProblem)
87
136
c = spzeros (size (A, 2 ))
88
137
else
89
138
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
91
140
if MOI. get (model, MOI. ObjectiveSense ()) == MOI. MAX_SENSE
92
141
c = - c
93
142
end
@@ -106,7 +155,7 @@ function _gradient_cache(model::ConicDiffProblem)
106
155
107
156
108
157
# 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)
110
159
111
160
# Q = [
112
161
# 0 A' c;
@@ -130,9 +179,9 @@ function _gradient_cache(model::ConicDiffProblem)
130
179
- c' - b' * Dπv 0.0
131
180
]
132
181
# 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)
134
183
135
- model. gradient_cache = ConicCache (
184
+ model. gradient_cache = Cache (
136
185
M = M,
137
186
vp = vp,
138
187
Dπv = Dπv,
@@ -144,15 +193,7 @@ function _gradient_cache(model::ConicDiffProblem)
144
193
return model. gradient_cache
145
194
end
146
195
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 )
156
197
gradient_cache = _gradient_cache (model)
157
198
M = gradient_cache. M
158
199
vp = gradient_cache. vp
@@ -164,12 +205,12 @@ function forward_differentiate!(model::ConicDiffProblem)
164
205
b = gradient_cache. b
165
206
c = gradient_cache. c
166
207
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))
169
210
dc = sparse_array_obj. terms
170
211
171
212
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)
173
214
(lines, cols) = size (A)
174
215
nz = nnz (A)
175
216
dAi = zeros (Int, 0 )
@@ -178,7 +219,7 @@ function forward_differentiate!(model::ConicDiffProblem)
178
219
sizehint! (dAi, nz)
179
220
sizehint! (dAj, nz)
180
221
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)
182
223
dA = sparse (dAi, dAj, dAv, lines, cols)
183
224
184
225
m = size (A, 1 )
@@ -193,27 +234,19 @@ function forward_differentiate!(model::ConicDiffProblem)
193
234
dz = if norm (RHS) <= 1e-400 # TODO : parametrize or remove
194
235
RHS .= 0 # because M is square
195
236
else
196
- lsqr (M, RHS)
237
+ IterativeSolvers . lsqr (M, RHS)
197
238
end
198
239
199
240
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])
201
242
return nothing
202
243
# dx = du - x * dw
203
244
# dy = Dπv * dv - y * dw
204
245
# ds = Dπv * dv - dv - s * dw
205
246
# return -dx, -dy, -ds
206
247
end
207
248
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 )
217
250
gradient_cache = _gradient_cache (model)
218
251
M = gradient_cache. M
219
252
vp = gradient_cache. vp
@@ -248,7 +281,7 @@ function reverse_differentiate!(model::ConicDiffProblem)
248
281
g = if norm (dz) <= 1e-4 # TODO : parametrize or remove
249
282
dz .= 0 # because M is square
250
283
else
251
- lsqr (M, dz)
284
+ IterativeSolvers . lsqr (M, dz)
252
285
end
253
286
254
287
πz = [
@@ -262,7 +295,7 @@ function reverse_differentiate!(model::ConicDiffProblem)
262
295
# http://reports-archive.adm.cs.cmu.edu/anon/2019/CMU-CS-19-109.pdf
263
296
# pg 97, cap 7.4.2
264
297
265
- model. back_grad_cache = ConicReverseCache (g, πz)
298
+ model. back_grad_cache = ReverseCache (g, πz)
266
299
return nothing
267
300
# dQ = - g * πz'
268
301
# dA = - dQ[1:n, n+1:n+m]' + dQ[n+1:n+m, 1:n]
@@ -271,36 +304,38 @@ function reverse_differentiate!(model::ConicDiffProblem)
271
304
# return dA, db, dc
272
305
end
273
306
274
- function MOI. get (model:: ConicDiffProblem , :: ReverseObjectiveFunction )
307
+ function MOI. get (model:: Model , :: DiffOpt. ReverseObjectiveFunction )
275
308
g = model. back_grad_cache. g
276
309
π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 )
279
312
end
280
313
281
- function MOI. get (model:: ConicDiffProblem , :: ForwardVariablePrimal , vi:: MOI.VariableIndex )
314
+ function MOI. get (model:: Model , :: DiffOpt. ForwardVariablePrimal , vi:: MOI.VariableIndex )
282
315
i = vi. value
283
316
du = model. forw_grad_cache. du
284
317
dw = model. forw_grad_cache. dw
285
- return - (du[i] - model. x[i] * dw[])
318
+ return - (du[i] - model. x[i] * dw[])
286
319
end
287
- function _get_db (model:: ConicDiffProblem , ci:: CI {F,S}
320
+ function DiffOpt . _get_db (model:: Model , ci:: MOI.ConstraintIndex {F,S}
288
321
) where {F<: MOI.AbstractVectorFunction ,S}
289
322
i = MOI. Utilities. rows (model. model. constraints, ci) # vector
290
323
# i = ci.value
291
324
n = length (model. x) # columns in A
292
325
# db = - dQ[n+1:n+m, end] + dQ[end, n+1:n+m]'
293
326
g = model. back_grad_cache. g
294
327
π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)
296
329
end
297
- function _get_dA (model:: ConicDiffProblem , ci:: CI {<:MOI.AbstractVectorFunction} )
330
+ function DiffOpt . _get_dA (model:: Model , ci:: MOI.ConstraintIndex {<:MOI.AbstractVectorFunction} )
298
331
i = MOI. Utilities. rows (model. model. constraints, ci) # vector
299
332
# i = ci.value
300
333
n = length (model. x) # columns in A
301
334
m = length (model. y) # lines in A
302
335
# dA = - dQ[1:n, n+1:n+m]' + dQ[n+1:n+m, 1:n]
303
336
g = model. back_grad_cache. g
304
337
π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
+
306
341
end
0 commit comments