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
2 changes: 1 addition & 1 deletion docs/src/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ MOI.set(optimizer, POI.ParameterValue(), y, POI.Parameter(2.0))

### Retrieving the dual of a parameter

Given an optimized model, one can calculate the dual associated to a parameter, **as long as it is an additive term in the constraints or objective**.
Given an optimized model, one can compute the dual associated to a parameter, **as long as it is an additive term in the constraints or objective**.
One can do so by getting the `MOI.ConstraintDual` attribute of the parameter's `MOI.ConstraintIndex`:

```julia
Expand Down
1,025 changes: 459 additions & 566 deletions src/ParametricOptInterface.jl

Large diffs are not rendered by default.

179 changes: 60 additions & 119 deletions src/duals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,154 +3,95 @@
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

function create_param_dual_cum_sum(model::Optimizer)
return zeros(model.number_of_parameters_in_model)
end

function calculate_dual_of_parameters(model::Optimizer)
param_dual_cum_sum = create_param_dual_cum_sum(model)
update_duals_with_affine_constraint_cache!(param_dual_cum_sum, model)
update_duals_with_quadratic_constraint_cache!(param_dual_cum_sum, model)
update_duals_in_affine_objective!(
param_dual_cum_sum,
model.affine_objective_cache,
)
update_duals_in_quadratic_objective!(
param_dual_cum_sum,
model.quadratic_objective_cache_pc,
)
empty_and_feed_duals_to_model(model, param_dual_cum_sum)
return model
function compute_dual_of_parameters!(model::Optimizer{T}) where {T}
model.dual_value_of_parameters =
zeros(T, model.number_of_parameters_in_model)
update_duals_from_affine_constraints!(model)
update_duals_from_vector_affine_constraints!(model)
update_duals_from_quadratic_constraints!(model)
if model.affine_objective_cache !== nothing
update_duals_from_objective!(model, model.affine_objective_cache)
end
if model.quadratic_objective_cache !== nothing
update_duals_from_objective!(model, model.quadratic_objective_cache)
end
return
end

function update_duals_with_affine_constraint_cache!(
param_dual_cum_sum::Vector{Float64},
model::Optimizer,
)
function update_duals_from_affine_constraints!(model::Optimizer)
for (F, S) in keys(model.affine_constraint_cache.dict)
affine_constraint_cache_inner = model.affine_constraint_cache[F, S]
affine_added_cache_inner = model.affine_added_cache[F, S]
if !isempty(affine_constraint_cache_inner)
update_duals_with_affine_constraint_cache!(
param_dual_cum_sum,
model.optimizer,
affine_constraint_cache_inner,
affine_added_cache_inner,
)
end
# barrier for type instability
compute_parameters_in_ci!(model, affine_constraint_cache_inner)
end
return
end

function update_duals_with_affine_constraint_cache!(
param_dual_cum_sum::Vector{Float64},
optimizer::OT,
affine_constraint_cache_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{
F,
S,
V1,
},
affine_added_cache_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{F,S,V2},
) where {OT,F,S,V1,V2}
for (ci, param_array) in affine_constraint_cache_inner
calculate_parameters_in_ci!(
param_dual_cum_sum,
optimizer,
param_array,
affine_added_cache_inner[ci],
)
function update_duals_from_vector_affine_constraints!(model::Optimizer)
for (F, S) in keys(model.vector_affine_constraint_cache.dict)
vector_affine_constraint_cache_inner =
model.vector_affine_constraint_cache[F, S]
# barrier for type instability
compute_parameters_in_ci!(model, vector_affine_constraint_cache_inner)
end
return
end

function update_duals_with_quadratic_constraint_cache!(
param_dual_cum_sum::Vector{Float64},
model::Optimizer,
)
for (F, S) in keys(model.quadratic_constraint_cache_pc.dict)
quadratic_constraint_cache_pc_inner =
model.quadratic_constraint_cache_pc[F, S]
if !isempty(quadratic_constraint_cache_pc_inner)
update_duals_with_quadratic_constraint_cache!(
param_dual_cum_sum,
model,
quadratic_constraint_cache_pc_inner,
)
end
function update_duals_from_quadratic_constraints!(model::Optimizer)
for (F, S) in keys(model.quadratic_constraint_cache.dict)
quadratic_constraint_cache_inner =
model.quadratic_constraint_cache[F, S]
# barrier for type instability
compute_parameters_in_ci!(model, quadratic_constraint_cache_inner)
end
return
end

function update_duals_with_quadratic_constraint_cache!(
param_dual_cum_sum::Vector{Float64},
model::Optimizer,
quadratic_constraint_cache_pc_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{
F,
S,
V,
},
) where {F,S,V}
for (poi_ci, param_array) in quadratic_constraint_cache_pc_inner
moi_ci = model.quadratic_added_cache[poi_ci]
calculate_parameters_in_ci!(
param_dual_cum_sum,
model.optimizer,
param_array,
moi_ci,
)
function compute_parameters_in_ci!(
model::OT,
constraint_cache_inner::DoubleDictInner{F,S,V},
) where {OT,F,S,V}
for (inner_ci, pf) in constraint_cache_inner
compute_parameters_in_ci!(model, pf, inner_ci)
end
return
end

function calculate_parameters_in_ci!(
param_dual_cum_sum::Vector{Float64},
optimizer::OT,
param_array::Vector{MOI.ScalarAffineTerm{T}},
ci::CI,
) where {OT,CI,T}
cons_dual = MOI.get(optimizer, MOI.ConstraintDual(), ci)

for param in param_array
param_dual_cum_sum[param.variable.value-PARAMETER_INDEX_THRESHOLD] -=
cons_dual * param.coefficient
function compute_parameters_in_ci!(
model::Optimizer{T},
pf,
ci::MOI.ConstraintIndex{F,S},
) where {F,S} where {T}
cons_dual = MOI.get(model.optimizer, MOI.ConstraintDual(), ci)
for term in pf.p
model.dual_value_of_parameters[p_val(term.variable)] -=
cons_dual * term.coefficient
end
return
end

function update_duals_in_affine_objective!(
param_dual_cum_sum::Vector{Float64},
affine_objective_cache::Vector{MOI.ScalarAffineTerm{T}},
) where {T}
for param in affine_objective_cache
param_dual_cum_sum[param.variable.value-PARAMETER_INDEX_THRESHOLD] -=
param.coefficient
function compute_parameters_in_ci!(
model::Optimizer{T},
pf::ParametricVectorAffineFunction{T},
ci::MOI.ConstraintIndex{F,S},
) where {F<:MOI.VectorAffineFunction{T},S} where {T}
cons_dual = MOI.get(model.optimizer, MOI.ConstraintDual(), ci)
for term in pf.p
model.dual_value_of_parameters[p_val(term.scalar_term.variable)] -=
cons_dual[term.output_index] * term.scalar_term.coefficient
end
return
end

function update_duals_in_quadratic_objective!(
param_dual_cum_sum::Vector{Float64},
quadratic_objective_cache_pc::Vector{MOI.ScalarAffineTerm{T}},
) where {T}
for param in quadratic_objective_cache_pc
param_dual_cum_sum[param.variable.value-PARAMETER_INDEX_THRESHOLD] -=
# this one seem to be the same as the next
function update_duals_from_objective!(model::Optimizer{T}, pf) where {T}
for param in pf.p
model.dual_value_of_parameters[p_val(param.variable)] -=
param.coefficient
end
return
end

function empty_and_feed_duals_to_model(
model::Optimizer,
param_dual_cum_sum::Vector{Float64},
)
empty!(model.dual_value_of_parameters)
model.dual_value_of_parameters = zeros(model.number_of_parameters_in_model)
for (vi_val_minus_threshold, param_dual) in enumerate(param_dual_cum_sum)
model.dual_value_of_parameters[vi_val_minus_threshold] = param_dual
end
return
end

"""
ParameterDual <: MOI.AbstractVariableAttribute

Expand All @@ -171,9 +112,9 @@ function MOI.get(model::Optimizer, ::ParameterDual, v::MOI.VariableIndex)
model,
MOI.ConstraintIndex{MOI.VariableIndex,Parameter}(v.value),
)
error("Cannot calculate the dual of a multiplicative parameter")
error("Cannot compute the dual of a multiplicative parameter")
end
return model.dual_value_of_parameters[v.value-PARAMETER_INDEX_THRESHOLD]
return model.dual_value_of_parameters[p_val(v)]
end

function MOI.get(
Expand All @@ -182,9 +123,9 @@ function MOI.get(
cp::MOI.ConstraintIndex{MOI.VariableIndex,Parameter},
)
if !is_additive(model, cp)
error("Cannot calculate the dual of a multiplicative parameter")
error("Cannot compute the dual of a multiplicative parameter")
end
return model.dual_value_of_parameters[cp.value-PARAMETER_INDEX_THRESHOLD]
return model.dual_value_of_parameters[p_val(cp)]
end

function is_additive(model::Optimizer, cp::MOI.ConstraintIndex)
Expand Down
Loading