Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/src/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ MOI.set(model, DiffOpt.ForwardConstraintSet(), ParameterRef(p), Parameter(3.0))
MOI.set(model, DiffOpt.ForwardConstraintSet(), ParameterRef(p_c), Parameter(3.0))
DiffOpt.forward_differentiate!(model)

MOI.get(model, DiffOpt.ForwardObjectiveSensitivity())
MOI.get(model, DiffOpt.ForwardObjectiveValue())
```

In reverse mode, we can calculate the parameter perturbation with respect to the objective perturbation:
Expand All @@ -174,7 +174,7 @@ DiffOpt.empty_input_sensitivities!(model)

MOI.set(
model,
DiffOpt.ReverseObjectiveSensitivity(),
DiffOpt.ReverseObjectiveValue(),
0.1,
)

Expand All @@ -183,4 +183,4 @@ DiffOpt.reverse_differentiate!(model)
MOI.get(model, DiffOpt.ReverseConstraintSet(), ParameterRef(p))
```

It is important to note that the (reverse) parameter perturbation given an objective perturbation is somewhat equivalent to the perturbation with respect to solution (since one can be calculated from the other). Therefore, one cannot set both the objective sensitivity (`DiffOpt.ReverseObjectiveSensitivity`) and the solution sensitivity (e.g. `DiffOpt.ReverseVariablePrimal`) at the same time - the code will throw an error if you try to do so.
It is important to note that the (reverse) parameter perturbation given an objective perturbation is somewhat equivalent to the perturbation with respect to solution (since one can be calculated from the other). Therefore, one cannot set both the objective sensitivity (`DiffOpt.ReverseObjectiveValue`) and the solution sensitivity (e.g. `DiffOpt.ReverseVariablePrimal`) at the same time - the code will throw an error if you try to do so.
6 changes: 2 additions & 4 deletions src/ConicProgram/ConicProgram.jl
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,8 @@ end
Method not supported for `DiffOpt.ConicProgram.Model` directly.
However, a fallback is provided in `DiffOpt`.
"""
function MOI.get(::Model, ::DiffOpt.ForwardObjectiveSensitivity)
return throw(
MOI.UnsupportedAttribute(DiffOpt.ForwardObjectiveSensitivity()),
)
function MOI.get(::Model, ::DiffOpt.ForwardObjectiveValue)
return throw(MOI.UnsupportedAttribute(DiffOpt.ForwardObjectiveValue()))
end

"""
Expand Down
2 changes: 1 addition & 1 deletion src/NonLinearProgram/NonLinearProgram.jl
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ function MOI.get(
return MOI.Parameter{T}(model.back_grad_cache.Δp[ci])
end

function MOI.get(model::Model, ::DiffOpt.ForwardObjectiveSensitivity)
function MOI.get(model::Model, ::DiffOpt.ForwardObjectiveValue)
return model.forw_grad_cache.objective_sensitivity_p
end

Expand Down
6 changes: 2 additions & 4 deletions src/QuadraticProgram/QuadraticProgram.jl
Original file line number Diff line number Diff line change
Expand Up @@ -509,10 +509,8 @@ end
Method not supported for `DiffOpt.QuadraticProgram.Model` directly.
However, a fallback is provided in `DiffOpt`.
"""
function MOI.get(::Model, ::DiffOpt.ForwardObjectiveSensitivity)
return throw(
MOI.UnsupportedAttribute(DiffOpt.ForwardObjectiveSensitivity()),
)
function MOI.get(::Model, ::DiffOpt.ForwardObjectiveValue)
return throw(MOI.UnsupportedAttribute(DiffOpt.ForwardObjectiveValue()))
end

"""
Expand Down
10 changes: 6 additions & 4 deletions src/diff_opt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -249,19 +249,21 @@ struct ForwardConstraintDual <: MOI.AbstractConstraintAttribute end
MOI.is_set_by_optimize(::ForwardConstraintDual) = true

"""
ForwardObjectiveSensitivity <: MOI.AbstractModelAttribute
ForwardObjectiveValue <: MOI.AbstractModelAttribute

A `MOI.AbstractModelAttribute` to get output objective sensitivity data from forward differentiation.

For instance, to get the sensitivity of the objective function with respect to the parameter perturbation, do the following:

```julia
MOI.get(model, DiffOpt.ForwardObjectiveSensitivity())
MOI.get(model, DiffOpt.ForwardObjectiveValue())
```
"""
struct ForwardObjectiveSensitivity <: MOI.AbstractModelAttribute end
struct ForwardObjectiveValue <: MOI.AbstractModelAttribute end

MOI.is_set_by_optimize(::ForwardObjectiveSensitivity) = true
MOI.is_set_by_optimize(::ForwardObjectiveValue) = true

Base.@deprecate_binding ForwardObjectiveSensitivity ForwardObjectiveValue

"""
ReverseObjectiveFunction <: MOI.AbstractModelAttribute
Expand Down
4 changes: 2 additions & 2 deletions src/jump_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ end

Get the value of the objective output sensitivity for forward mode.

Equivalent to `get_attribute(model, DiffOpt.ForwardObjectiveSensitivity())`.
Equivalent to `get_attribute(model, DiffOpt.ForwardObjectiveValue())`.
"""
function get_forward_objective(model::JuMP.Model)
return MOI.get(model, ForwardObjectiveSensitivity())
return MOI.get(model, ForwardObjectiveValue())
end

"""
Expand Down
2 changes: 1 addition & 1 deletion src/moi_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ function MOI.get(
)
end

function MOI.get(model::Optimizer, attr::ForwardObjectiveSensitivity)
function MOI.get(model::Optimizer, attr::ForwardObjectiveValue)
diff_model = _checked_diff(model, attr, :forward_differentiate!)
val = 0.0
try
Expand Down
6 changes: 3 additions & 3 deletions test/jump_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ function test_set_get_attribute()
optimize!(model)

# Forward via attributes: ForwardParameterValue (set),
# ForwardVariablePrimal (get), ForwardObjectiveSensitivity (get),
# ForwardVariablePrimal (get), ForwardObjectiveValue (get),
# ForwardConstraintDual (get).
DiffOpt.empty_input_sensitivities!(model)
set_attribute(p, DiffOpt.ForwardParameterValue(), 1.0)
Expand All @@ -456,7 +456,7 @@ function test_set_get_attribute()
RTOL
@test get_attribute(c1, DiffOpt.ForwardConstraintDual()) ≈ 1.75 atol = ATOL rtol =
RTOL
@test get_attribute(model, DiffOpt.ForwardObjectiveSensitivity()) ≈ 2.75 atol =
@test get_attribute(model, DiffOpt.ForwardObjectiveValue()) ≈ 2.75 atol =
ATOL rtol = RTOL

# Reverse via attributes: ReverseVariablePrimal (set),
Expand Down Expand Up @@ -546,7 +546,7 @@ function test_attribute_types()
@test DiffOpt.ReverseVariablePrimal() isa MOI.AbstractVariableAttribute
@test DiffOpt.ForwardObjectiveFunction() isa MOI.AbstractModelAttribute
@test DiffOpt.ReverseObjectiveFunction() isa MOI.AbstractModelAttribute
@test DiffOpt.ForwardObjectiveSensitivity() isa MOI.AbstractModelAttribute
@test DiffOpt.ForwardObjectiveValue() isa MOI.AbstractModelAttribute
@test DiffOpt.ReverseObjectiveValue() isa MOI.AbstractModelAttribute
@test DiffOpt.ForwardConstraintFunction() isa
MOI.AbstractConstraintAttribute
Expand Down
24 changes: 12 additions & 12 deletions test/nlp_program.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ function test_analytical_simple(; P = 2) # Number of parameters
DiffOpt.forward_differentiate!(m)

# test Objective Sensitivity wrt parameters
df_dp = MOI.get(m, DiffOpt.ForwardObjectiveSensitivity())
df_dp = MOI.get(m, DiffOpt.ForwardObjectiveValue())
@test isapprox(df_dp, dot(dual.(con), Δp); atol = 1e-4)
@test all(isapprox.(dual.(ParameterRef.(p)), dual.(con); atol = 1e-8))

Expand Down Expand Up @@ -642,7 +642,7 @@ end
=#
################################################

function test_ObjectiveSensitivity_model1()
function test_ObjectiveValue_model1()
# Model 1
model = Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
set_silent(model)
Expand All @@ -669,7 +669,7 @@ function test_ObjectiveSensitivity_model1()
DiffOpt.forward_differentiate!(model)

# Test Objective Sensitivity wrt parameters
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveSensitivity())
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveValue())
df = -2cos(p_val) / sin(p_val)^2
@test isapprox(df_dp, df * Δp; atol = 1e-4)

Expand All @@ -678,7 +678,7 @@ function test_ObjectiveSensitivity_model1()

# Test both obj and solution inputs
Δf = 0.5
MOI.set(model, DiffOpt.ReverseObjectiveSensitivity(), Δf)
MOI.set(model, DiffOpt.ReverseObjectiveValue(), Δf)
MOI.set(model, DiffOpt.ReverseVariablePrimal(), x, Δp)

msg = "Computing reverse differentiation with both solution sensitivities and objective sensitivities. Set `DiffOpt.AllowObjectiveAndSolutionInput()` to `true` to silence this warning."
Expand Down Expand Up @@ -711,7 +711,7 @@ function test_ObjectiveSensitivity_model1()

# Set Reverse Objective Sensitivity
Δf = 0.5
MOI.set(model, DiffOpt.ReverseObjectiveSensitivity(), Δf)
MOI.set(model, DiffOpt.ReverseObjectiveValue(), Δf)

# Compute derivatives
DiffOpt.reverse_differentiate!(model)
Expand All @@ -722,7 +722,7 @@ function test_ObjectiveSensitivity_model1()
@test isapprox(dp, df * Δf; atol = 1e-4)
end

function test_ObjectiveSensitivity_model2()
function test_ObjectiveValue_model2()
# Model 2
model = Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
set_silent(model)
Expand All @@ -749,15 +749,15 @@ function test_ObjectiveSensitivity_model2()
DiffOpt.forward_differentiate!(model)

# Test Objective Sensitivity wrt parameters
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveSensitivity())
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveValue())
@test isapprox(df_dp, -0.3; atol = 1e-4)

# Clean up
DiffOpt.empty_input_sensitivities!(model)

# Set Reverse Objective Sensitivity
Δf = 0.5
MOI.set(model, DiffOpt.ReverseObjectiveSensitivity(), Δf)
MOI.set(model, DiffOpt.ReverseObjectiveValue(), Δf)

# Compute derivatives
DiffOpt.reverse_differentiate!(model)
Expand All @@ -768,7 +768,7 @@ function test_ObjectiveSensitivity_model2()
@test isapprox(dp, -1.5; atol = 1e-4)
end

function test_ObjectiveSensitivity_direct_param_contrib()
function test_ObjectiveValue_direct_param_contrib()
model = DiffOpt.nonlinear_diff_model(Ipopt.Optimizer)
set_silent(model)

Expand All @@ -784,7 +784,7 @@ function test_ObjectiveSensitivity_direct_param_contrib()
DiffOpt.set_forward_parameter(model, p, Δp)
DiffOpt.forward_differentiate!(model)

df_dp = MOI.get(model, DiffOpt.ForwardObjectiveSensitivity())
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveValue())
@test isapprox(df_dp, 2 * p_val * Δp, atol = 1e-8) # ≈ 0.6 for p=3

ε = 1e-6
Expand All @@ -802,7 +802,7 @@ function test_ObjectiveSensitivity_direct_param_contrib()

@test isapprox(df_dp, df_dp_fd, atol = 1e-4)
end
function test_ObjectiveSensitivity_subset_parameters()
function test_ObjectiveValue_subset_parameters()
# Model with 10 parameters, differentiate only w.r.t. 3rd and 7th
model = Model(() -> DiffOpt.diff_optimizer(Ipopt.Optimizer))
set_silent(model)
Expand Down Expand Up @@ -830,7 +830,7 @@ function test_ObjectiveSensitivity_subset_parameters()
DiffOpt.forward_differentiate!(model)

# Objective sensitivity should equal sum over selected params only
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveSensitivity())
df_dp = MOI.get(model, DiffOpt.ForwardObjectiveValue())
@test isapprox(df_dp, 0.007109293; atol = 1e-4)
end

Expand Down
15 changes: 7 additions & 8 deletions test/solver_native_diff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ function MOI.set(
return
end

function MOI.set(s::EqQPSolver, ::DiffOpt.ReverseObjectiveSensitivity, value)
function MOI.set(s::EqQPSolver, ::DiffOpt.ReverseObjectiveValue, value)
s.rev_dobj = value
return
end
Expand Down Expand Up @@ -516,8 +516,8 @@ function MOI.get(s::EqQPSolver, ::DiffOpt.ForwardConstraintDual, ci::EQ_CI)
return s.dnu_fwd[ci.value]
end

# ForwardObjectiveSensitivity
function MOI.get(s::EqQPSolver, ::DiffOpt.ForwardObjectiveSensitivity)
# ForwardObjectiveValue
function MOI.get(s::EqQPSolver, ::DiffOpt.ForwardObjectiveValue)
return s.fwd_obj_sensitivity
end

Expand Down Expand Up @@ -870,13 +870,13 @@ function test_three_variable_problem()
end
end

# ── Test reverse with dobj seed (ReverseObjectiveSensitivity) ─────────────
# ── Test reverse with dobj seed (ReverseObjectiveValue) ─────────────

function test_reverse_dobj_seed()
model, x1, x2, c1 = _setup_model()

# Set dobj = 1.0: sensitivity of the objective value w.r.t. problem data
MOI.set(model, DiffOpt.ReverseObjectiveSensitivity(), 1.0)
MOI.set(model, DiffOpt.ReverseObjectiveValue(), 1.0)
DiffOpt.reverse_differentiate!(model)

# x* = [0, 1], nu* = [-3], Q = I, c = [3, 2]
Expand Down Expand Up @@ -908,8 +908,7 @@ function test_forward_objective_sensitivity()
# dobj/dt = (Qx + c)'dx + dc'x = [3,3]'*dx + [1,0]'*[0,1]
expected = dot([3.0, 3.0], fwd[1:2]) + dot([1.0, 0.0], [0.0, 1.0])

@test MOI.get(model, DiffOpt.ForwardObjectiveSensitivity()) ≈ expected atol =
ATOL
@test MOI.get(model, DiffOpt.ForwardObjectiveValue()) ≈ expected atol = ATOL
end

# ── Test forward twice (re-differentiation in forward mode) ──────────────
Expand Down Expand Up @@ -979,7 +978,7 @@ function test_reverse_dobj_and_dx_seed()
model, x1, x2, c1 = _setup_model()

MOI.set(model, DiffOpt.AllowObjectiveAndSolutionInput(), true)
MOI.set(model, DiffOpt.ReverseObjectiveSensitivity(), 1.0)
MOI.set(model, DiffOpt.ReverseObjectiveValue(), 1.0)
MOI.set(model, DiffOpt.ReverseVariablePrimal(), x1, 1.0)
DiffOpt.reverse_differentiate!(model)

Expand Down
Loading