Skip to content

Commit e287ec2

Browse files
blegatmatbesancon
andauthored
Refactoring into submodules (#219)
* Create QuadraticProgram module * Rework QuadraticProgram module * Create ConicProgram module * Fixes * Refactor optimizer * Fixes * Fix doc build * Fix docs * error on naming * rem _view * Address comments * Fix Co-authored-by: Mathieu Besançon <[email protected]>
1 parent 0e9d35a commit e287ec2

File tree

10 files changed

+477
-438
lines changed

10 files changed

+477
-438
lines changed

docs/src/reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
```
55

66
```@autodocs
7-
Modules = [DiffOpt]
7+
Modules = [DiffOpt, DiffOpt.QuadraticProgram, DiffOpt.ConicProgram]
88
```

src/conic_diff.jl renamed to src/ConicProgram/ConicProgram.jl

Lines changed: 92 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,36 @@
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
4186
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)
4493
end
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
56105
end
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)
64113
end
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)
69118
end
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)
74123
end
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 = (v, model.model, model.model.constraints.sets)
158+
Dπv = DiffOpt.(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
145194
end
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
206247
end
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
272305
end
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)
279312
end
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[])
286319
end
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)
296329
end
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+
306341
end

src/DiffOpt.jl

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module DiffOpt
33
using LinearAlgebra
44
import LinearAlgebra: , dot, Diagonal
55
using SparseArrays
6-
using IterativeSolvers: lsqr
76
using JuMP
87

98
import LazyArrays
@@ -19,47 +18,29 @@ const MOSD = MathOptSetDistances
1918
const VI = MOI.VariableIndex
2019
const CI = MOI.ConstraintIndex
2120

22-
const SUPPORTED_OBJECTIVES = Union{
23-
MOI.VariableIndex,
24-
MOI.ScalarAffineFunction{Float64},
25-
MOI.ScalarQuadraticFunction{Float64}
26-
}
27-
28-
const SUPPORTED_SCALAR_SETS = Union{
29-
MOI.GreaterThan{Float64},
30-
MOI.LessThan{Float64},
31-
MOI.EqualTo{Float64},
32-
MOI.Interval{Float64}
33-
}
34-
35-
const SUPPORTED_SCALAR_FUNCTIONS = Union{
36-
MOI.VariableIndex,
37-
MOI.ScalarAffineFunction{Float64}
38-
}
39-
40-
const SUPPORTED_VECTOR_FUNCTIONS = Union{
41-
MOI.VectorOfVariables,
42-
MOI.VectorAffineFunction{Float64},
43-
}
44-
45-
const SUPPORTED_VECTOR_SETS = Union{
46-
MOI.Zeros,
47-
MOI.Nonpositives,
48-
MOI.Nonnegatives,
49-
MOI.SecondOrderCone,
50-
MOI.PositiveSemidefiniteConeTriangle,
51-
}
52-
5321
include("utils.jl")
5422
include("product_of_sets.jl")
5523
include("diff_opt.jl")
56-
include("conic_diff.jl")
57-
include("quadratic_diff.jl")
5824
include("moi_wrapper.jl")
5925
include("jump_moi_overloads.jl")
6026

6127
include("bridges.jl")
6228

29+
include("QuadraticProgram/QuadraticProgram.jl")
30+
include("ConicProgram/ConicProgram.jl")
31+
32+
"""
33+
add_all_model_constructors(model)
34+
35+
Add all constructors of [`AbstractModel`](@ref) defined in this package to
36+
`model` with [`add_model_constructor`](@ref).
37+
"""
38+
function add_all_model_constructors(model)
39+
add_model_constructor(model, QuadraticProgram.Model)
40+
add_model_constructor(model, ConicProgram.Model)
41+
return
42+
end
43+
6344
export diff_optimizer
6445

6546
end # module

0 commit comments

Comments
 (0)