Skip to content

Commit 74de329

Browse files
committed
update DiffOpt integration and tests
1 parent 6640e38 commit 74de329

File tree

11 files changed

+548
-434
lines changed

11 files changed

+548
-434
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ rev.grad_p # gradient of the loss with respect to the parameters
2929

3030
## JuMP interface
3131

32-
MadDiff aims to be a drop-in replacement for [DiffOpt](https://github.com/jump-dev/DiffOpt.jl) with MadNLP. Simply switch `DiffOpt.diff_optimizer(MadNLP.Optimizer)` for `MadDiff.diff_optimizer(MadNLP.Optimizer)` and enjoy the speedup!
32+
MadDiff aims to be a drop-in replacement for [DiffOpt](https://github.com/jump-dev/DiffOpt.jl) with MadNLP. Simply switch `DiffOpt.diff_model(MadNLP.Optimizer)` for `MadDiff.diff_model(MadNLP.Optimizer)` and enjoy the speedup!
3333

3434
```julia
3535
using JuMP, DiffOpt
3636
using MadDiff, MadNLP
3737

38-
model = Model(MadDiff.diff_optimizer(MadNLP.Optimizer)) # use MadDiff.diff_optimizer
38+
model = MadDiff.diff_model(MadNLP.Optimizer)
3939
@variable(model, x)
4040
@variable(model, p in MOI.Parameter(1.0))
4141
@constraint(model, x >= 2p)

docs/src/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ rev.grad_p # gradient of the loss with respect to the parameters
2727

2828
## JuMP interface
2929

30-
MadDiff aims to be a drop-in replacement for [DiffOpt](https://github.com/jump-dev/DiffOpt.jl) with MadNLP. Simply switch `DiffOpt.diff_optimizer(MadNLP.Optimizer)` for `MadDiff.diff_optimizer(MadNLP.Optimizer)` and enjoy the speedup!
30+
MadDiff aims to be a drop-in replacement for [DiffOpt](https://github.com/jump-dev/DiffOpt.jl) with MadNLP. Simply switch `DiffOpt.diff_model(MadNLP.Optimizer)` for `MadDiff.diff_model(MadNLP.Optimizer)` and enjoy the speedup!
3131

3232
```julia
3333
using JuMP, DiffOpt
3434
using MadDiff, MadNLP
3535

36-
model = Model(MadDiff.diff_optimizer(MadNLP.Optimizer)) # use MadDiff.diff_optimizer
36+
model = MadDiff.diff_model(MadNLP.Optimizer)
3737
@variable(model, x)
3838
@variable(model, p in MOI.Parameter(1.0))
3939
@constraint(model, x >= 2p)

ext/DiffOptExt/DiffOptExt.jl

Lines changed: 261 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,270 @@ module DiffOptExt
22

33
import MadDiff
44
import DiffOpt
5+
import MadNLP
56
const MOI = DiffOpt.MOI
7+
const POI = DiffOpt.POI
68

79
MOIExt = Base.get_extension(MadDiff, :MathOptInterfaceExt)
8-
Optimizer = MOIExt.Optimizer
9-
10-
DiffOpt.forward_differentiate!(model::Optimizer) = MadDiff.forward_differentiate!(model)
11-
DiffOpt.reverse_differentiate!(model::Optimizer) = MadDiff.reverse_differentiate!(model)
12-
DiffOpt.empty_input_sensitivities!(model::Optimizer) = MadDiff.empty_input_sensitivities!(model)
13-
MadDiff.nonlinear_diff_model(optimizer_constructor; kwargs...) = DiffOpt.JuMP.Model(MadDiff.diff_optimizer(optimizer_constructor; kwargs...))
14-
15-
MOI.set(m::Optimizer, ::DiffOpt.ForwardConstraintSet, ci::MOI.ConstraintIndex, set) =
16-
MOI.set(m, MadDiff.ForwardConstraintSet(), ci, set)
17-
MOI.get(m::Optimizer, ::DiffOpt.ForwardVariablePrimal, vi::MOI.VariableIndex) =
18-
MOI.get(m, MadDiff.ForwardVariablePrimal(), vi)
19-
MOI.get(m::Optimizer, ::DiffOpt.ForwardConstraintDual, ci::MOI.ConstraintIndex) =
20-
MOI.get(m, MadDiff.ForwardConstraintDual(), ci)
21-
MOI.get(m::Optimizer, ::DiffOpt.ForwardObjectiveSensitivity) =
22-
MOI.get(m, MadDiff.ForwardObjectiveSensitivity())
23-
24-
MOI.set(m::Optimizer, ::DiffOpt.ReverseVariablePrimal, vi::MOI.VariableIndex, value) =
25-
MOI.set(m, MadDiff.ReverseVariablePrimal(), vi, value)
26-
MOI.set(m::Optimizer, ::DiffOpt.ReverseConstraintDual, ci::MOI.ConstraintIndex, value) =
27-
MOI.set(m, MadDiff.ReverseConstraintDual(), ci, value)
28-
MOI.set(m::Optimizer, ::DiffOpt.ReverseObjectiveSensitivity, value) =
29-
MOI.set(m, MadDiff.ReverseObjectiveSensitivity(), value)
30-
MOI.get(m::Optimizer, ::DiffOpt.ReverseConstraintSet, ci::MOI.ConstraintIndex) =
31-
MOI.get(m, MadDiff.ReverseConstraintSet(), ci)
32-
33-
MOI.get(m::Optimizer, ::DiffOpt.DifferentiateTimeSec) =
34-
MOI.get(m, MadDiff.DifferentiateTimeSec())
10+
11+
mutable struct DiffOptModel <: MOI.AbstractOptimizer
12+
wrapper::Union{Nothing,MOIExt.DiffOptWrapper}
13+
source_to_inner::MOI.Utilities.IndexMap
14+
sensitivity_config::MadDiff.MadDiffConfig
15+
end
16+
17+
function DiffOptModel(; sensitivity_config::MadDiff.MadDiffConfig = MadDiff.MadDiffConfig())
18+
return DiffOptModel(nothing, MOI.Utilities.IndexMap(), sensitivity_config)
19+
end
20+
21+
_backend(model::DiffOptModel) = model.wrapper::MOIExt.DiffOptWrapper
22+
23+
MOI.supports_incremental_interface(::DiffOptModel) = true
24+
MOI.supports_add_constrained_variable(::DiffOptModel, ::Type{<:MOI.AbstractScalarSet}) = true
25+
MOI.supports_add_constrained_variables(::DiffOptModel, ::Type{<:MOI.AbstractVectorSet}) = true
26+
MOI.supports_add_constrained_variables(::DiffOptModel, ::Type{MOI.Reals}) = true
27+
MOI.supports_constraint(::DiffOptModel, ::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) = true
28+
MOI.is_empty(model::DiffOptModel) = isnothing(model.wrapper)
29+
30+
function MOI.empty!(model::DiffOptModel)
31+
model.wrapper = nothing
32+
model.source_to_inner = MOI.Utilities.IndexMap()
33+
return
34+
end
35+
36+
function _madnlp_optimizer_type()
37+
if isdefined(MadNLP, :Optimizer)
38+
return getproperty(MadNLP, :Optimizer)
39+
end
40+
ext = Base.get_extension(MadNLP, :MathOptInterfaceExt)
41+
if isnothing(ext)
42+
return nothing
43+
end
44+
return getproperty(ext, :Optimizer)
45+
end
46+
47+
function _is_madnlp_optimizer(optimizer)
48+
optimizer_type = _madnlp_optimizer_type()
49+
return !isnothing(optimizer_type) && optimizer isa optimizer_type
50+
end
51+
52+
function _compose_index_maps(
53+
source_to_mid::MOI.Utilities.IndexMap,
54+
mid_to_dest::MOI.Utilities.IndexMap,
55+
)
56+
output = MOI.Utilities.IndexMap()
57+
for (source, mid) in source_to_mid
58+
output[source] = mid_to_dest[mid]
59+
end
60+
return output
61+
end
62+
63+
function _has_active_bridges(model::MOI.Bridges.LazyBridgeOptimizer)
64+
return !isempty(MOI.Bridges.Variable.bridges(model)) ||
65+
!isempty(MOI.Bridges.Constraint.bridges(model)) ||
66+
!isempty(MOI.Bridges.Objective.bridges(model))
67+
end
68+
69+
function _unwrap_to_madnlp(
70+
root_optimizer,
71+
optimizer,
72+
source_to_optimizer::Union{Nothing,MOI.Utilities.IndexMap},
73+
)
74+
_is_madnlp_optimizer(optimizer) &&
75+
return optimizer, something(source_to_optimizer, MOI.Utilities.identity_index_map(root_optimizer))
76+
error("MadDiff requires a wrapper chain ending in MadNLP. Got $(typeof(optimizer)).")
77+
end
78+
79+
function _unwrap_to_madnlp(
80+
root_optimizer,
81+
optimizer::MOI.Utilities.CachingOptimizer,
82+
source_to_optimizer::Union{Nothing,MOI.Utilities.IndexMap},
83+
)
84+
map_step = deepcopy(optimizer.model_to_optimizer_map)
85+
source_to_inner = isnothing(source_to_optimizer) ?
86+
map_step : _compose_index_maps(source_to_optimizer, map_step)
87+
return _unwrap_to_madnlp(root_optimizer, optimizer.optimizer, source_to_inner)
88+
end
89+
90+
function _unwrap_to_madnlp(
91+
root_optimizer,
92+
optimizer::MOI.Bridges.LazyBridgeOptimizer,
93+
source_to_optimizer::Union{Nothing,MOI.Utilities.IndexMap},
94+
)
95+
_has_active_bridges(optimizer) &&
96+
error("MadDiff does not support active MOI bridges in the DiffOpt chain.")
97+
return _unwrap_to_madnlp(root_optimizer, optimizer.model, source_to_optimizer)
98+
end
99+
100+
function _unwrap_to_madnlp(
101+
root_optimizer,
102+
optimizer::POI.Optimizer,
103+
source_to_optimizer::Union{Nothing,MOI.Utilities.IndexMap},
104+
)
105+
return _unwrap_to_madnlp(root_optimizer, optimizer.optimizer, source_to_optimizer)
106+
end
107+
108+
_unwrap_to_madnlp(root_optimizer) = _unwrap_to_madnlp(root_optimizer, root_optimizer, nothing)
109+
110+
function _refresh_parameter_map!(
111+
optimizer::MOIExt.DiffOptWrapper,
112+
source_optimizer,
113+
source_to_madnlp::MOI.Utilities.IndexMap,
114+
)
115+
empty!(optimizer.param_ci_to_vi)
116+
for source_ci in MOI.get(
117+
source_optimizer,
118+
MOI.ListOfConstraintIndices{
119+
MOI.VariableIndex,
120+
MOI.Parameter{Float64},
121+
}(),
122+
)
123+
source_vi = MOI.get(source_optimizer, MOI.ConstraintFunction(), source_ci)
124+
optimizer.param_ci_to_vi[source_to_madnlp[source_ci]] =
125+
source_to_madnlp[source_vi]
126+
end
127+
return optimizer
128+
end
129+
130+
function MOI.copy_to(model::DiffOptModel, src::MOI.ModelLike)
131+
madnlp_optimizer, source_to_madnlp = _unwrap_to_madnlp(src)
132+
backend = MOIExt.DiffOptWrapper(madnlp_optimizer)
133+
backend.sensitivity_config = deepcopy(model.sensitivity_config)
134+
_refresh_parameter_map!(backend, src, source_to_madnlp)
135+
model.wrapper = backend
136+
model.source_to_inner = source_to_madnlp
137+
return MOI.Utilities.identity_index_map(src)
138+
end
139+
140+
function MOI.copy_to(
141+
model::MOI.Bridges.LazyBridgeOptimizer{<:DiffOptModel},
142+
src::MOI.ModelLike,
143+
)
144+
return MOI.copy_to(model.model, src)
145+
end
146+
147+
_map_source_to_inner(model::DiffOptModel, idx) = model.source_to_inner[idx]
148+
149+
DiffOpt.forward_differentiate!(model::DiffOptModel) =
150+
MadDiff.forward_differentiate!(_backend(model))
151+
DiffOpt.reverse_differentiate!(model::DiffOptModel) =
152+
MadDiff.reverse_differentiate!(_backend(model))
153+
DiffOpt.empty_input_sensitivities!(model::DiffOptModel) =
154+
MadDiff.empty_input_sensitivities!(_backend(model))
155+
156+
MOI.supports(::DiffOptModel, ::DiffOpt.NonLinearKKTJacobianFactorization) = true
157+
MOI.supports(::DiffOptModel, ::DiffOpt.AllowObjectiveAndSolutionInput) = true
158+
MOI.set(::DiffOptModel, ::DiffOpt.NonLinearKKTJacobianFactorization, _) = nothing
159+
MOI.set(::DiffOptModel, ::DiffOpt.AllowObjectiveAndSolutionInput, _) = nothing
160+
161+
function MOI.set(
162+
model::DiffOptModel,
163+
::DiffOpt.ForwardConstraintSet,
164+
ci::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}},
165+
set::MOI.Parameter{T},
166+
) where {T}
167+
inner_ci = _map_source_to_inner(model, ci)
168+
return MOI.set(_backend(model), MadDiff.ForwardConstraintSet(), inner_ci, set)
169+
end
170+
171+
function MOI.get(
172+
model::DiffOptModel,
173+
::DiffOpt.ForwardVariablePrimal,
174+
vi::MOI.VariableIndex,
175+
)
176+
inner_vi = _map_source_to_inner(model, vi)
177+
return MOI.get(_backend(model), MadDiff.ForwardVariablePrimal(), inner_vi)
178+
end
179+
180+
function MOI.get(
181+
model::DiffOptModel,
182+
::DiffOpt.ForwardConstraintDual,
183+
ci::MOI.ConstraintIndex,
184+
)
185+
inner_ci = _map_source_to_inner(model, ci)
186+
return MOI.get(_backend(model), MadDiff.ForwardConstraintDual(), inner_ci)
187+
end
188+
189+
MOI.get(model::DiffOptModel, ::DiffOpt.ForwardObjectiveSensitivity) =
190+
MOI.get(_backend(model), MadDiff.ForwardObjectiveSensitivity())
191+
192+
function MOI.set(
193+
model::DiffOptModel,
194+
::DiffOpt.ReverseVariablePrimal,
195+
vi::MOI.VariableIndex,
196+
value,
197+
)
198+
inner_vi = _map_source_to_inner(model, vi)
199+
return MOI.set(_backend(model), MadDiff.ReverseVariablePrimal(), inner_vi, value)
200+
end
201+
202+
function MOI.set(
203+
model::DiffOptModel,
204+
::DiffOpt.ReverseConstraintDual,
205+
ci::MOI.ConstraintIndex,
206+
value,
207+
)
208+
inner_ci = _map_source_to_inner(model, ci)
209+
return MOI.set(_backend(model), MadDiff.ReverseConstraintDual(), inner_ci, value)
210+
end
211+
212+
MOI.set(model::DiffOptModel, ::DiffOpt.ReverseObjectiveSensitivity, value) =
213+
MOI.set(_backend(model), MadDiff.ReverseObjectiveSensitivity(), value)
214+
215+
function MOI.get(
216+
model::DiffOptModel,
217+
::DiffOpt.ReverseConstraintSet,
218+
ci::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}},
219+
) where {T}
220+
inner_ci = _map_source_to_inner(model, ci)
221+
return MOI.get(_backend(model), MadDiff.ReverseConstraintSet(), inner_ci)
222+
end
223+
224+
MOI.get(model::DiffOptModel, ::DiffOpt.DifferentiateTimeSec) =
225+
MOI.get(_backend(model), MadDiff.DifferentiateTimeSec())
35226

36227
DiffOpt.get_reverse_parameter(
37-
model::Optimizer,
38-
ci::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Parameter{T}},
39-
) where {T} = MadDiff.get_reverse_parameter(model, ci)
228+
model::DiffOptModel,
229+
ci::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}},
230+
) where {T} = MadDiff.get_reverse_parameter(
231+
_backend(model),
232+
_map_source_to_inner(model, ci),
233+
)
234+
235+
MOI.get(model::DiffOptModel, ::MOI.SolverName) = "MadDiff[MadNLP]"
236+
237+
DiffOpt._copy_dual(::DiffOptModel, ::MOI.ModelLike, _) = nothing
238+
DiffOpt._copy_dual(
239+
::MOI.Bridges.LazyBridgeOptimizer{<:DiffOptModel},
240+
::MOI.ModelLike,
241+
_,
242+
) = nothing
243+
244+
"""
245+
MadDiff.diffopt_model_constructor(; config = MadDiff.MadDiffConfig())
246+
247+
Return a DiffOpt `ModelConstructor` callable that reuses a solved MadNLP
248+
optimizer for differentiation.
249+
"""
250+
function MadDiff.diffopt_model_constructor(;
251+
config::MadDiff.MadDiffConfig = MadDiff.MadDiffConfig(),
252+
)
253+
return () -> DiffOptModel(; sensitivity_config = config)
254+
end
255+
256+
function MadDiff.diff_model(
257+
optimizer_constructor;
258+
config::MadDiff.MadDiffConfig = MadDiff.MadDiffConfig(),
259+
kwargs...,
260+
)
261+
model = DiffOpt.diff_model(optimizer_constructor; kwargs...)
262+
MOI.set(model, DiffOpt.AllowObjectiveAndSolutionInput(), true)
263+
MOI.set(
264+
model,
265+
DiffOpt.ModelConstructor(),
266+
MadDiff.diffopt_model_constructor(config = config),
267+
)
268+
return model
269+
end
40270

41-
end # module
271+
end # module DiffOptExt

ext/MathOptInterfaceExt/MathOptInterfaceExt.jl

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ WorkBuffers{T}() where {T} = WorkBuffers{T}(
4646
Vector{T}(undef, 0),
4747
)
4848

49-
mutable struct Optimizer{OT <: MOI.AbstractOptimizer, T} <: MOI.AbstractOptimizer
49+
mutable struct DiffOptWrapper{OT <: MOI.AbstractOptimizer, T}
5050
inner::OT
5151
param_ci_to_vi::Dict{MOI.ConstraintIndex, MOI.VariableIndex}
5252
forward::ForwardModeData{T}
@@ -57,8 +57,8 @@ mutable struct Optimizer{OT <: MOI.AbstractOptimizer, T} <: MOI.AbstractOptimize
5757
diff_time::T
5858
end
5959

60-
function Optimizer(inner::OT; T::Type = Float64) where {OT <: MOI.AbstractOptimizer}
61-
return Optimizer{OT, T}(
60+
function DiffOptWrapper(inner::OT; T::Type = Float64) where {OT <: MOI.AbstractOptimizer}
61+
return DiffOptWrapper{OT, T}(
6262
inner,
6363
Dict{MOI.ConstraintIndex, MOI.VariableIndex}(),
6464
ForwardModeData{T}(),
@@ -70,10 +70,6 @@ function Optimizer(inner::OT; T::Type = Float64) where {OT <: MOI.AbstractOptimi
7070
)
7171
end
7272

73-
function MadDiff.diff_optimizer(optimizer_constructor; kwargs...)
74-
return () -> Optimizer(optimizer_constructor(; kwargs...))
75-
end
76-
7773
include("moi_wrapper.jl")
7874
include("forward_mode.jl")
7975
include("reverse_mode.jl")

0 commit comments

Comments
 (0)