Skip to content

Adds support for VectorQuadraticFunction #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: jg/newdo
Choose a base branch
from
Open
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
123 changes: 123 additions & 0 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ function _has_parameters(f::MOI.ScalarQuadraticFunction{T}) where {T}
return false
end

function _has_parameters(f::MOI.VectorQuadraticFunction)
# quadratic part
for qt in f.quadratic_terms
if _is_parameter(qt.scalar_term.variable_1) || _is_parameter(qt.scalar_term.variable_2)
return true
end
end
# affine part
for at in f.affine_terms
if _is_parameter(at.scalar_term.variable)
return true
end
end
return false
end

function _cache_multiplicative_params!(
model::Optimizer{T},
f::ParametricQuadraticFunction{T},
Expand All @@ -65,6 +81,21 @@ function _cache_multiplicative_params!(
return
end

function _cache_multiplicative_params!(
model::Optimizer{T},
f::ParametricVectorQuadraticFunction{T},
) where {T}
for term in f.pv
push!(model.multiplicative_parameters_pv,
term.scalar_term.variable_1.value)
end
for term in f.pp
push!(model.multiplicative_parameters_pp, term.scalar_term.variable_1.value)
push!(model.multiplicative_parameters_pp, term.scalar_term.variable_2.value)
end
return
end

#
# Empty
#
Expand All @@ -88,6 +119,8 @@ function MOI.is_empty(model::Optimizer)
isempty(model.quadratic_outer_to_inner) &&
isempty(model.quadratic_constraint_cache) &&
isempty(model.quadratic_constraint_cache_set) &&
isempty(model.vector_quadratic_constraint_cache) &&
isempty(model.vector_quadratic_constraint_cache_set) &&
# obj
model.affine_objective_cache === nothing &&
model.quadratic_objective_cache === nothing &&
Expand Down Expand Up @@ -123,6 +156,8 @@ function MOI.empty!(model::Optimizer{T}) where {T}
empty!(model.quadratic_outer_to_inner)
empty!(model.quadratic_constraint_cache)
empty!(model.quadratic_constraint_cache_set)
empty!(model.vector_quadratic_constraint_cache)
empty!(model.vector_quadratic_constraint_cache_set)
# obj
model.affine_objective_cache = nothing
model.quadratic_objective_cache = nothing
Expand Down Expand Up @@ -538,6 +573,10 @@ function MOI.get(
return _original_function(
model.quadratic_constraint_cache[inner_ci],
)
elseif haskey(model.vector_quadratic_constraint_cache, inner_ci)
return _original_function(
model.vector_quadratic_constraint_cache[inner_ci],
)
else
return convert(
MOI.ScalarQuadraticFunction{T},
Expand Down Expand Up @@ -583,6 +622,9 @@ function MOI.get(
if haskey(model.quadratic_outer_to_inner, ci)
inner_ci = model.quadratic_outer_to_inner[ci]
return model.quadratic_constraint_cache_set[inner_ci]
elseif haskey(model.vector_quadratic_constraint_cache, ci)
inner_ci = model.vector_quadratic_constraint_cache[ci]
return model.vector_quadratic_constraint_cache_set[inner_ci]
elseif haskey(model.affine_outer_to_inner, ci)
inner_ci = model.affine_outer_to_inner[ci]
return model.affine_constraint_cache_set[inner_ci]
Expand Down Expand Up @@ -858,6 +900,80 @@ function MOI.add_constraint(
end
end

function _is_vector_affine(f::MOI.VectorQuadraticFunction{T}) where {T}
return isempty(f.quadratic_terms)
end

function _is_vector_affine(::MOI.VectorAffineFunction{T}) where {T}
return true # VectorAffineFunction is always affine
end

function _add_constraint_with_parameters_on_function(
model::Optimizer,
f::MOI.VectorQuadraticFunction{T},
set::S,
) where {T,S}
# Create parametric vector quadratic function
pf = ParametricVectorQuadraticFunction(f)
_cache_multiplicative_params!(model, pf)
_update_cache!(pf, model)

# Get the current function after parameter substitution
func = _current_function(pf)
if !_is_vector_affine(func)
fq = func
inner_ci = MOI.add_constraint(model.optimizer, fq, set)
model.last_quad_add_added += 1
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
model.last_quad_add_added,
)
model.quadratic_outer_to_inner[outer_ci] = inner_ci
model.constraint_outer_to_inner[outer_ci] = inner_ci
else
fa = MOI.VectorAffineFunction(func.affine_terms, func.constants)
inner_ci = MOI.add_constraint(model.optimizer, fa, set)
model.last_quad_add_added += 1
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
model.last_quad_add_added,
)
# This part is used to remember that ci came from a quadratic function
# It is particularly useful because sometimes the constraint mutates
model.quadratic_outer_to_inner[outer_ci] = inner_ci
model.constraint_outer_to_inner[outer_ci] = inner_ci
end
model.vector_quadratic_constraint_cache[inner_ci] = pf
model.vector_quadratic_constraint_cache_set[inner_ci] = set
return outer_ci
end

function MOI.add_constraint(
model::Optimizer,
f::MOI.VectorQuadraticFunction{T},
set::MOI.AbstractVectorSet,
) where {T}
if !_has_parameters(f)
return _add_constraint_direct_and_cache_map!(model, f, set)
else
return _add_constraint_with_parameters_on_function(model, f, set)
end
end

function MOI.delete(
model::Optimizer,
c::MOI.ConstraintIndex{F,S},
) where {F<:MOI.VectorQuadraticFunction,S<:MOI.AbstractSet}
ci_inner = model.constraint_outer_to_inner[c]
if haskey(model.quadratic_constraint_cache, ci_inner)
delete!(model.quadratic_constraint_cache, ci_inner)
delete!(model.quadratic_constraint_cache_set, ci_inner)
MOI.delete(model.optimizer, ci_inner)
else
MOI.delete(model.optimizer, c)
end
delete!(model.constraint_outer_to_inner, c)
return
end

function MOI.delete(
model::Optimizer,
c::MOI.ConstraintIndex{F,S},
Expand Down Expand Up @@ -1411,6 +1527,13 @@ function MOI.get(
return model.quadratic_constraint_cache[F, S]
end

function MOI.get(
model::Optimizer,
::DictOfParametricConstraintIndicesAndFunctions{F,S,P},
) where {F,S,P<:ParametricVectorQuadraticFunction}
return model.vector_quadratic_constraint_cache[F, S]
end

"""
NumberOfPureVariables

Expand Down
6 changes: 6 additions & 0 deletions src/ParametricOptInterface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}}
# Store original constraint set (inner key)
quadratic_constraint_cache_set::DoubleDict{MOI.AbstractScalarSet}
# Vector quadratic function data
vector_quadratic_constraint_cache::DoubleDict{ParametricVectorQuadraticFunction{T}}
# Store original constraint set (inner key)
vector_quadratic_constraint_cache_set::DoubleDict{MOI.AbstractVectorSet}

# objective function data
# Clever cache of data (at most one can be !== nothing)
Expand Down Expand Up @@ -209,6 +213,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
DoubleDict{MOI.ConstraintIndex}(),
DoubleDict{ParametricQuadraticFunction{T}}(),
DoubleDict{MOI.AbstractScalarSet}(),
DoubleDict{ParametricVectorQuadraticFunction{T}}(),
DoubleDict{MOI.AbstractVectorSet}(),
# objective
nothing,
nothing,
Expand Down
31 changes: 31 additions & 0 deletions src/duals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function _compute_dual_of_parameters!(model::Optimizer{T}) where {T}
_update_duals_from_affine_constraints!(model)
_update_duals_from_vector_affine_constraints!(model)
_update_duals_from_quadratic_constraints!(model)
_update_duals_from_vector_quadratic_constraints!(model)
if model.affine_objective_cache !== nothing
_update_duals_from_objective!(model, model.affine_objective_cache)
end
Expand Down Expand Up @@ -174,3 +175,33 @@ function _is_additive(model::Optimizer, cp::MOI.ConstraintIndex)
end
return true
end

function _update_duals_from_vector_quadratic_constraints!(model::Optimizer)
for (F, S) in keys(model.vector_quadratic_constraint_cache.dict)
vector_quadratic_constraint_cache_inner = model.vector_quadratic_constraint_cache[F, S]
_compute_parameters_in_ci!(model, vector_quadratic_constraint_cache_inner)
end
return
end

function _compute_parameters_in_ci!(
model::Optimizer{T},
pf::ParametricVectorQuadraticFunction{T},
ci::MOI.ConstraintIndex{F,S},
) where {F,S,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
for term in pf.pp
coef = ifelse(term.scalar_term.variable_1 == term.scalar_term.variable_2, T(1 // 2), T(1))
model.dual_value_of_parameters[p_val(term.scalar_term.variable_1)] -=
coef * cons_dual[term.output_index] * term.scalar_term.coefficient *
MOI.get(model, ParameterValue(), term.scalar_term.variable_2)
model.dual_value_of_parameters[p_val(term.scalar_term.variable_2)] -=
coef * cons_dual[term.output_index] * term.scalar_term.coefficient *
MOI.get(model, ParameterValue(), term.scalar_term.variable_1)
end
return
end
Loading
Loading