Skip to content

Commit ec2f856

Browse files
Adds support for VectorQuadraticFunction (#179)
* add ParametricVectorQuadraticFunction * use at_variable and remove unecessary calls * start addressing variable coefficients * update tests * fix bugs * fix bug * update add constraint * update test * update jump test * fix test * rm update * update add_constraint * attemp replace * update functions * update * update * working first set * update tol * update tol * finish * change tests names * Apply suggestions from code review --------- Co-authored-by: Joaquim <joaquimdgarcia@gmail.com>
1 parent 667cb15 commit ec2f856

7 files changed

Lines changed: 619 additions & 0 deletions

File tree

src/MOI_wrapper.jl

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ function _has_parameters(f::MOI.ScalarQuadraticFunction{T}) where {T}
5151
return false
5252
end
5353

54+
function _has_parameters(f::MOI.VectorQuadraticFunction)
55+
# quadratic part
56+
for qt in f.quadratic_terms
57+
if _is_parameter(qt.scalar_term.variable_1) || _is_parameter(qt.scalar_term.variable_2)
58+
return true
59+
end
60+
end
61+
# affine part
62+
for at in f.affine_terms
63+
if _is_parameter(at.scalar_term.variable)
64+
return true
65+
end
66+
end
67+
return false
68+
end
69+
5470
function _cache_multiplicative_params!(
5571
model::Optimizer{T},
5672
f::ParametricQuadraticFunction{T},
@@ -65,6 +81,21 @@ function _cache_multiplicative_params!(
6581
return
6682
end
6783

84+
function _cache_multiplicative_params!(
85+
model::Optimizer{T},
86+
f::ParametricVectorQuadraticFunction{T},
87+
) where {T}
88+
for term in f.pv
89+
push!(model.multiplicative_parameters_pv,
90+
term.scalar_term.variable_1.value)
91+
end
92+
for term in f.pp
93+
push!(model.multiplicative_parameters_pp, term.scalar_term.variable_1.value)
94+
push!(model.multiplicative_parameters_pp, term.scalar_term.variable_2.value)
95+
end
96+
return
97+
end
98+
6899
#
69100
# Empty
70101
#
@@ -88,6 +119,8 @@ function MOI.is_empty(model::Optimizer)
88119
isempty(model.quadratic_outer_to_inner) &&
89120
isempty(model.quadratic_constraint_cache) &&
90121
isempty(model.quadratic_constraint_cache_set) &&
122+
isempty(model.vector_quadratic_constraint_cache) &&
123+
isempty(model.vector_quadratic_constraint_cache_set) &&
91124
# obj
92125
model.affine_objective_cache === nothing &&
93126
model.quadratic_objective_cache === nothing &&
@@ -123,6 +156,8 @@ function MOI.empty!(model::Optimizer{T}) where {T}
123156
empty!(model.quadratic_outer_to_inner)
124157
empty!(model.quadratic_constraint_cache)
125158
empty!(model.quadratic_constraint_cache_set)
159+
empty!(model.vector_quadratic_constraint_cache)
160+
empty!(model.vector_quadratic_constraint_cache_set)
126161
# obj
127162
model.affine_objective_cache = nothing
128163
model.quadratic_objective_cache = nothing
@@ -538,6 +573,10 @@ function MOI.get(
538573
return _original_function(
539574
model.quadratic_constraint_cache[inner_ci],
540575
)
576+
elseif haskey(model.vector_quadratic_constraint_cache, inner_ci)
577+
return _original_function(
578+
model.vector_quadratic_constraint_cache[inner_ci],
579+
)
541580
else
542581
return convert(
543582
MOI.ScalarQuadraticFunction{T},
@@ -583,6 +622,9 @@ function MOI.get(
583622
if haskey(model.quadratic_outer_to_inner, ci)
584623
inner_ci = model.quadratic_outer_to_inner[ci]
585624
return model.quadratic_constraint_cache_set[inner_ci]
625+
elseif haskey(model.vector_quadratic_constraint_cache, ci)
626+
inner_ci = model.vector_quadratic_constraint_cache[ci]
627+
return model.vector_quadratic_constraint_cache_set[inner_ci]
586628
elseif haskey(model.affine_outer_to_inner, ci)
587629
inner_ci = model.affine_outer_to_inner[ci]
588630
return model.affine_constraint_cache_set[inner_ci]
@@ -858,6 +900,80 @@ function MOI.add_constraint(
858900
end
859901
end
860902

903+
function _is_vector_affine(f::MOI.VectorQuadraticFunction{T}) where {T}
904+
return isempty(f.quadratic_terms)
905+
end
906+
907+
function _is_vector_affine(::MOI.VectorAffineFunction{T}) where {T}
908+
return true # VectorAffineFunction is always affine
909+
end
910+
911+
function _add_constraint_with_parameters_on_function(
912+
model::Optimizer,
913+
f::MOI.VectorQuadraticFunction{T},
914+
set::S,
915+
) where {T,S}
916+
# Create parametric vector quadratic function
917+
pf = ParametricVectorQuadraticFunction(f)
918+
_cache_multiplicative_params!(model, pf)
919+
_update_cache!(pf, model)
920+
921+
# Get the current function after parameter substitution
922+
func = _current_function(pf)
923+
if !_is_vector_affine(func)
924+
fq = func
925+
inner_ci = MOI.add_constraint(model.optimizer, fq, set)
926+
model.last_quad_add_added += 1
927+
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
928+
model.last_quad_add_added,
929+
)
930+
model.quadratic_outer_to_inner[outer_ci] = inner_ci
931+
model.constraint_outer_to_inner[outer_ci] = inner_ci
932+
else
933+
fa = MOI.VectorAffineFunction(func.affine_terms, func.constants)
934+
inner_ci = MOI.add_constraint(model.optimizer, fa, set)
935+
model.last_quad_add_added += 1
936+
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
937+
model.last_quad_add_added,
938+
)
939+
# This part is used to remember that ci came from a quadratic function
940+
# It is particularly useful because sometimes the constraint mutates
941+
model.quadratic_outer_to_inner[outer_ci] = inner_ci
942+
model.constraint_outer_to_inner[outer_ci] = inner_ci
943+
end
944+
model.vector_quadratic_constraint_cache[inner_ci] = pf
945+
model.vector_quadratic_constraint_cache_set[inner_ci] = set
946+
return outer_ci
947+
end
948+
949+
function MOI.add_constraint(
950+
model::Optimizer,
951+
f::MOI.VectorQuadraticFunction{T},
952+
set::MOI.AbstractVectorSet,
953+
) where {T}
954+
if !_has_parameters(f)
955+
return _add_constraint_direct_and_cache_map!(model, f, set)
956+
else
957+
return _add_constraint_with_parameters_on_function(model, f, set)
958+
end
959+
end
960+
961+
function MOI.delete(
962+
model::Optimizer,
963+
c::MOI.ConstraintIndex{F,S},
964+
) where {F<:MOI.VectorQuadraticFunction,S<:MOI.AbstractSet}
965+
ci_inner = model.constraint_outer_to_inner[c]
966+
if haskey(model.quadratic_constraint_cache, ci_inner)
967+
delete!(model.quadratic_constraint_cache, ci_inner)
968+
delete!(model.quadratic_constraint_cache_set, ci_inner)
969+
MOI.delete(model.optimizer, ci_inner)
970+
else
971+
MOI.delete(model.optimizer, c)
972+
end
973+
delete!(model.constraint_outer_to_inner, c)
974+
return
975+
end
976+
861977
function MOI.delete(
862978
model::Optimizer,
863979
c::MOI.ConstraintIndex{F,S},
@@ -1411,6 +1527,13 @@ function MOI.get(
14111527
return model.quadratic_constraint_cache[F, S]
14121528
end
14131529

1530+
function MOI.get(
1531+
model::Optimizer,
1532+
::DictOfParametricConstraintIndicesAndFunctions{F,S,P},
1533+
) where {F,S,P<:ParametricVectorQuadraticFunction}
1534+
return model.vector_quadratic_constraint_cache[F, S]
1535+
end
1536+
14141537
"""
14151538
NumberOfPureVariables
14161539

src/ParametricOptInterface.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
137137
quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}}
138138
# Store original constraint set (inner key)
139139
quadratic_constraint_cache_set::DoubleDict{MOI.AbstractScalarSet}
140+
# Vector quadratic function data
141+
vector_quadratic_constraint_cache::DoubleDict{ParametricVectorQuadraticFunction{T}}
142+
# Store original constraint set (inner key)
143+
vector_quadratic_constraint_cache_set::DoubleDict{MOI.AbstractVectorSet}
140144

141145
# objective function data
142146
# Clever cache of data (at most one can be !== nothing)
@@ -209,6 +213,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
209213
DoubleDict{MOI.ConstraintIndex}(),
210214
DoubleDict{ParametricQuadraticFunction{T}}(),
211215
DoubleDict{MOI.AbstractScalarSet}(),
216+
DoubleDict{ParametricVectorQuadraticFunction{T}}(),
217+
DoubleDict{MOI.AbstractVectorSet}(),
212218
# objective
213219
nothing,
214220
nothing,

src/duals.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function _compute_dual_of_parameters!(model::Optimizer{T}) where {T}
99
_update_duals_from_affine_constraints!(model)
1010
_update_duals_from_vector_affine_constraints!(model)
1111
_update_duals_from_quadratic_constraints!(model)
12+
_update_duals_from_vector_quadratic_constraints!(model)
1213
if model.affine_objective_cache !== nothing
1314
_update_duals_from_objective!(model, model.affine_objective_cache)
1415
end
@@ -174,3 +175,33 @@ function _is_additive(model::Optimizer, cp::MOI.ConstraintIndex)
174175
end
175176
return true
176177
end
178+
179+
function _update_duals_from_vector_quadratic_constraints!(model::Optimizer)
180+
for (F, S) in keys(model.vector_quadratic_constraint_cache.dict)
181+
vector_quadratic_constraint_cache_inner = model.vector_quadratic_constraint_cache[F, S]
182+
_compute_parameters_in_ci!(model, vector_quadratic_constraint_cache_inner)
183+
end
184+
return
185+
end
186+
187+
function _compute_parameters_in_ci!(
188+
model::Optimizer{T},
189+
pf::ParametricVectorQuadraticFunction{T},
190+
ci::MOI.ConstraintIndex{F,S},
191+
) where {F,S,T}
192+
cons_dual = MOI.get(model.optimizer, MOI.ConstraintDual(), ci)
193+
for term in pf.p
194+
model.dual_value_of_parameters[p_val(term.scalar_term.variable)] -=
195+
cons_dual[term.output_index] * term.scalar_term.coefficient
196+
end
197+
for term in pf.pp
198+
coef = ifelse(term.scalar_term.variable_1 == term.scalar_term.variable_2, T(1 // 2), T(1))
199+
model.dual_value_of_parameters[p_val(term.scalar_term.variable_1)] -=
200+
coef * cons_dual[term.output_index] * term.scalar_term.coefficient *
201+
MOI.get(model, ParameterValue(), term.scalar_term.variable_2)
202+
model.dual_value_of_parameters[p_val(term.scalar_term.variable_2)] -=
203+
coef * cons_dual[term.output_index] * term.scalar_term.coefficient *
204+
MOI.get(model, ParameterValue(), term.scalar_term.variable_1)
205+
end
206+
return
207+
end

0 commit comments

Comments
 (0)