Skip to content

Commit cdd09a1

Browse files
Merge branch 'master' into ar/VectorOfVariables
2 parents 1365431 + 0c84ac7 commit cdd09a1

14 files changed

Lines changed: 661 additions & 129 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
fail-fast: false
1818
matrix:
1919
include:
20-
- version: '1'
20+
- version: 'lts'
2121
os: ubuntu-latest
2222
arch: x64
2323
- version: '1'

src/ConicProgram/ConicProgram.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,15 +461,23 @@ function MOI.get(
461461
return MOI.get(model.model, attr, ci)
462462
end
463463

464+
"""
465+
Method not supported for `DiffOpt.ConicProgram.Model` directly.
466+
However, a fallback is provided in `DiffOpt`.
467+
"""
464468
function MOI.get(::Model, ::DiffOpt.ForwardObjectiveSensitivity)
465-
return error(
466-
"ForwardObjectiveSensitivity is not implemented for the Conic Optimization backend",
469+
return throw(
470+
MOI.UnsupportedAttribute(DiffOpt.ForwardObjectiveSensitivity()),
467471
)
468472
end
469473

474+
"""
475+
Method not supported for `DiffOpt.ConicProgram.Model` directly.
476+
However, a fallback is provided in `DiffOpt`.
477+
"""
470478
function MOI.set(::Model, ::DiffOpt.ReverseObjectiveSensitivity, val)
471-
return error(
472-
"ReverseObjectiveSensitivity is not implemented for the Conic Optimization backend",
479+
return throw(
480+
MOI.UnsupportedAttribute(DiffOpt.ReverseObjectiveSensitivity()),
473481
)
474482
end
475483

src/NonLinearProgram/NonLinearProgram.jl

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -553,45 +553,46 @@ function DiffOpt.reverse_differentiate!(model::Model; tol = 1e-6)
553553
# Compute Jacobian
554554
Δs, df_dp = _compute_sensitivity(model; tol = tol)
555555
Δp = if !iszero(model.input_cache.dobj)
556-
model.input_cache.dobj * df_dp
556+
df_dp'model.input_cache.dobj
557557
else
558-
num_primal = length(cache.primal_vars)
559-
# Fetch primal sensitivities
560-
Δx = zeros(num_primal)
561-
for (i, var_idx) in enumerate(cache.primal_vars)
562-
if haskey(model.input_cache.dx, var_idx)
563-
Δx[i] = model.input_cache.dx[var_idx]
564-
end
558+
zeros(length(cache.params))
559+
end
560+
num_primal = length(cache.primal_vars)
561+
# Fetch primal sensitivities
562+
Δx = zeros(num_primal)
563+
for (i, var_idx) in enumerate(cache.primal_vars)
564+
if haskey(model.input_cache.dx, var_idx)
565+
Δx[i] = model.input_cache.dx[var_idx]
565566
end
566-
# Fetch dual sensitivities
567-
num_constraints = length(cache.cons)
568-
num_up = length(cache.has_up)
569-
num_low = length(cache.has_low)
570-
Δdual = zeros(num_constraints + num_up + num_low)
571-
for (i, ci) in enumerate(cache.cons)
572-
idx = form.nlp_index_2_constraint[ci]
573-
if haskey(model.input_cache.dy, idx)
574-
Δdual[i] = model.input_cache.dy[idx]
575-
end
567+
end
568+
# Fetch dual sensitivities
569+
num_constraints = length(cache.cons)
570+
num_up = length(cache.has_up)
571+
num_low = length(cache.has_low)
572+
Δdual = zeros(num_constraints + num_up + num_low)
573+
for (i, ci) in enumerate(cache.cons)
574+
idx = form.nlp_index_2_constraint[ci]
575+
if haskey(model.input_cache.dy, idx)
576+
Δdual[i] = model.input_cache.dy[idx]
576577
end
577-
for (i, var_idx) in enumerate(cache.primal_vars[cache.has_low])
578-
idx = form.constraint_lower_bounds[var_idx.value]
579-
if haskey(model.input_cache.dy, idx)
580-
Δdual[num_constraints+i] = model.input_cache.dy[idx]
581-
end
578+
end
579+
for (i, var_idx) in enumerate(cache.primal_vars[cache.has_low])
580+
idx = form.constraint_lower_bounds[var_idx.value]
581+
if haskey(model.input_cache.dy, idx)
582+
Δdual[num_constraints+i] = model.input_cache.dy[idx]
582583
end
583-
for (i, var_idx) in enumerate(cache.primal_vars[cache.has_up])
584-
idx = form.constraint_upper_bounds[var_idx.value]
585-
if haskey(model.input_cache.dy, idx)
586-
Δdual[num_constraints+num_low+i] = model.input_cache.dy[idx]
587-
end
584+
end
585+
for (i, var_idx) in enumerate(cache.primal_vars[cache.has_up])
586+
idx = form.constraint_upper_bounds[var_idx.value]
587+
if haskey(model.input_cache.dy, idx)
588+
Δdual[num_constraints+num_low+i] = model.input_cache.dy[idx]
588589
end
589-
# Extract Parameter sensitivities
590-
Δw = zeros(size(Δs, 1))
591-
Δw[1:num_primal] = Δx
592-
Δw[cache.index_duals] = Δdual
593-
Δp = Δs' * Δw
594590
end
591+
# Extract Parameter sensitivities
592+
Δw = zeros(size(Δs, 1))
593+
Δw[1:num_primal] = Δx
594+
Δw[cache.index_duals] = Δdual
595+
Δp += Δs' * Δw
595596

596597
Δp_dict = Dict{MOI.ConstraintIndex,Float64}(
597598
form.var2ci[var_idx] => Δp[form.var2param[var_idx].value]

src/NonLinearProgram/nlp_utilities.jl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -489,9 +489,16 @@ function _compute_sensitivity(model::Model; tol = 1e-6)
489489
# Dual bounds upper
490490
∂s[((num_w+num_cons+num_lower+1):end), :] *= -_sense_multiplier
491491

492-
# dual wrt parameter
492+
grad = _compute_gradient(model)
493+
# `grad` = [∇ₓf(x,p); ∇ₚf(x,p)] where `x` is the primal vars, `p` is the params,
494+
# and `f(x,p)` is the objective function. we extract the components
495+
# so we can form `∇ₚfᵒ(x,p) = ∇ₓf(x,p) * ∇ₚxᵒ(p) + ∇ₚf(x,p) * ∇ₚpᵒ(p)`
496+
# where `ᵒ` denotes "optimal". note that parameters are fixed, so
497+
# pᵒ(p) = p and ∇ₚpᵒ(p) = 𝐈ₚ.
493498
primal_idx = [i.value for i in model.cache.primal_vars]
494-
df_dx = _compute_gradient(model)[primal_idx]
495-
df_dp = df_dx'∂s[1:num_vars, :]
499+
params_idx = [i.value for i in model.cache.params]
500+
df_dx = grad[primal_idx] # ∇ₓf(x,p)
501+
df_dp_direct = grad[params_idx] # ∇ₚf(x,p)
502+
df_dp = df_dx'∂s[1:num_vars, :] + df_dp_direct' # ∇ₚfᵒ(x,p) = ∇ₓf(x,p) * ∇ₚxᵒ(p) + ∇ₚf(x,p) * 𝐈ₚ
496503
return ∂s, df_dp
497504
end

src/QuadraticProgram/QuadraticProgram.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -501,15 +501,23 @@ function MOI.set(model::Model, ::LinearAlgebraSolver, linear_solver)
501501
return model.linear_solver = linear_solver
502502
end
503503

504+
"""
505+
Method not supported for `DiffOpt.QuadraticProgram.Model` directly.
506+
However, a fallback is provided in `DiffOpt`.
507+
"""
504508
function MOI.get(::Model, ::DiffOpt.ForwardObjectiveSensitivity)
505-
return error(
506-
"ForwardObjectiveSensitivity is not implemented for the Quadratic Optimization backend",
509+
return throw(
510+
MOI.UnsupportedAttribute(DiffOpt.ForwardObjectiveSensitivity()),
507511
)
508512
end
509513

514+
"""
515+
Method not supported for `DiffOpt.QuadraticProgram.Model` directly.
516+
However, a fallback is provided in `DiffOpt`.
517+
"""
510518
function MOI.set(::Model, ::DiffOpt.ReverseObjectiveSensitivity, val)
511-
return error(
512-
"ReverseObjectiveSensitivity is not implemented for the Quadratic Optimization backend",
519+
return throw(
520+
MOI.UnsupportedAttribute(DiffOpt.ReverseObjectiveSensitivity()),
513521
)
514522
end
515523

src/diff_opt.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Base.@kwdef mutable struct InputCache
3131
MOIDD.DoubleDict{MOI.VectorAffineFunction{Float64}}() # also includes G for QPs
3232
objective::Union{Nothing,MOI.AbstractScalarFunction} = nothing
3333
factorization::Union{Nothing,Function} = nothing
34+
allow_objective_and_solution_input::Bool = false
3435
end
3536

3637
function Base.empty!(cache::InputCache)
@@ -122,6 +123,8 @@ MOI.set(model, DiffOpt.NonLinearKKTJacobianFactorization(), factorization)
122123
"""
123124
struct NonLinearKKTJacobianFactorization <: MOI.AbstractModelAttribute end
124125

126+
struct AllowObjectiveAndSolutionInput <: MOI.AbstractModelAttribute end
127+
125128
"""
126129
ForwardConstraintFunction <: MOI.AbstractConstraintAttribute
127130
@@ -440,6 +443,15 @@ function MOI.set(
440443
return
441444
end
442445

446+
function MOI.set(
447+
model::AbstractModel,
448+
::AllowObjectiveAndSolutionInput,
449+
allow::Bool,
450+
)
451+
model.input_cache.allow_objective_and_solution_input = allow
452+
return
453+
end
454+
443455
function MOI.set(
444456
model::AbstractModel,
445457
::ReverseVariablePrimal,

src/jump_moi_overloads.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ function MOI.set(
2929
return MOI.set(JuMP.backend(model), attr, factorization)
3030
end
3131

32+
function MOI.set(
33+
model::JuMP.Model,
34+
attr::AllowObjectiveAndSolutionInput,
35+
allow::Bool,
36+
)
37+
return MOI.set(JuMP.backend(model), attr, allow)
38+
end
39+
3240
function MOI.set(
3341
model::JuMP.Model,
3442
attr::ForwardObjectiveFunction,

src/jump_wrapper.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,21 @@ Get the value of a variable output sensitivity for forward mode.
143143
function get_forward_variable(model::JuMP.Model, variable::JuMP.VariableRef)
144144
return MOI.get(model, ForwardVariablePrimal(), variable)
145145
end
146+
147+
"""
148+
set_reverse_objective(model::JuMP.Model, value::Number)
149+
150+
Set the value of the objective input sensitivity for reverse mode.
151+
"""
152+
function set_reverse_objective(model::JuMP.Model, value::Number)
153+
return MOI.set(model, ReverseObjectiveSensitivity(), value)
154+
end
155+
156+
"""
157+
get_forward_objective(model::JuMP.Model)
158+
159+
Get the value of the objective output sensitivity for forward mode.
160+
"""
161+
function get_forward_objective(model::JuMP.Model)
162+
return MOI.get(model, ForwardObjectiveSensitivity())
163+
end

0 commit comments

Comments
 (0)