Skip to content

Commit 25f9a33

Browse files
Fix key vector quadratic and add tests (#184)
* fix key vector quadratic * add new tests and fix affine cache * format * test constraint name set and get * add no parameter problem * fix test name * fix comment * force has_parameter and !_is_vector_affine * update coverage * remove useless function * update tests coverage * format and rm useless functions * improve tests to force coverage improvement * fix typo
1 parent 979274d commit 25f9a33

6 files changed

Lines changed: 370 additions & 116 deletions

File tree

src/MOI_wrapper.jl

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ function MOI.is_empty(model::Optimizer)
125125
isempty(model.affine_constraint_cache_set) &&
126126
# quad ctr
127127
model.last_quad_add_added == 0 &&
128+
model.last_vec_quad_add_added == 0 &&
128129
isempty(model.quadratic_outer_to_inner) &&
130+
isempty(model.vector_quadratic_outer_to_inner) &&
129131
isempty(model.quadratic_constraint_cache) &&
130132
isempty(model.quadratic_constraint_cache_set) &&
131133
isempty(model.vector_quadratic_constraint_cache) &&
@@ -162,7 +164,9 @@ function MOI.empty!(model::Optimizer{T}) where {T}
162164
empty!(model.affine_constraint_cache_set)
163165
# quad ctr
164166
model.last_quad_add_added = 0
167+
model.last_vec_quad_add_added = 0
165168
empty!(model.quadratic_outer_to_inner)
169+
empty!(model.vector_quadratic_outer_to_inner)
166170
empty!(model.quadratic_constraint_cache)
167171
empty!(model.quadratic_constraint_cache_set)
168172
empty!(model.vector_quadratic_constraint_cache)
@@ -526,11 +530,16 @@ end
526530
function MOI.set(
527531
model::Optimizer,
528532
attr::MOI.ConstraintName,
529-
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S},
533+
c::MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S},
530534
name::String,
531535
) where {T,S<:MOI.AbstractSet}
532-
if haskey(model.affine_outer_to_inner, c)
533-
MOI.set(model.optimizer, attr, model.affine_outer_to_inner[c], name)
536+
if haskey(model.vector_quadratic_outer_to_inner, c)
537+
MOI.set(
538+
model.optimizer,
539+
attr,
540+
model.vector_quadratic_outer_to_inner[c],
541+
name,
542+
)
534543
else
535544
MOI.set(model.optimizer, attr, c, name)
536545
end
@@ -540,10 +549,14 @@ end
540549
function MOI.set(
541550
model::Optimizer,
542551
attr::MOI.ConstraintName,
543-
c::MOI.ConstraintIndex,
552+
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S},
544553
name::String,
545-
)
546-
MOI.set(model.optimizer, attr, c, name)
554+
) where {T,S<:MOI.AbstractSet}
555+
if haskey(model.affine_outer_to_inner, c)
556+
MOI.set(model.optimizer, attr, model.affine_outer_to_inner[c], name)
557+
else
558+
MOI.set(model.optimizer, attr, c, name)
559+
end
547560
return
548561
end
549562

@@ -562,9 +575,17 @@ end
562575
function MOI.get(
563576
model::Optimizer,
564577
attr::MOI.ConstraintName,
565-
c::MOI.ConstraintIndex,
566-
)
567-
return MOI.get(model.optimizer, attr, c)
578+
c::MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S},
579+
) where {T,S<:MOI.AbstractSet}
580+
if haskey(model.vector_quadratic_outer_to_inner, c)
581+
return MOI.get(
582+
model.optimizer,
583+
attr,
584+
model.vector_quadratic_outer_to_inner[c],
585+
)
586+
else
587+
return MOI.get(model.optimizer, attr, c)
588+
end
568589
end
569590

570591
function MOI.get(
@@ -619,13 +640,21 @@ function MOI.get(
619640
return _original_function(
620641
model.quadratic_constraint_cache[inner_ci],
621642
)
622-
elseif haskey(model.vector_quadratic_constraint_cache, inner_ci)
643+
else
644+
return convert(
645+
MOI.ScalarQuadraticFunction{T},
646+
MOI.get(model.optimizer, attr, inner_ci),
647+
)
648+
end
649+
elseif haskey(model.vector_quadratic_outer_to_inner, ci)
650+
inner_ci = model.vector_quadratic_outer_to_inner[ci]
651+
if haskey(model.vector_quadratic_constraint_cache, inner_ci)
623652
return _original_function(
624653
model.vector_quadratic_constraint_cache[inner_ci],
625654
)
626655
else
627656
return convert(
628-
MOI.ScalarQuadraticFunction{T},
657+
MOI.VectorQuadraticFunction{T},
629658
MOI.get(model.optimizer, attr, inner_ci),
630659
)
631660
end
@@ -668,8 +697,8 @@ function MOI.get(
668697
if haskey(model.quadratic_outer_to_inner, ci)
669698
inner_ci = model.quadratic_outer_to_inner[ci]
670699
return model.quadratic_constraint_cache_set[inner_ci]
671-
elseif haskey(model.vector_quadratic_constraint_cache, ci)
672-
inner_ci = model.vector_quadratic_constraint_cache[ci]
700+
elseif haskey(model.vector_quadratic_outer_to_inner, ci)
701+
inner_ci = model.vector_quadratic_outer_to_inner[ci]
673702
return model.vector_quadratic_constraint_cache_set[inner_ci]
674703
elseif haskey(model.affine_outer_to_inner, ci)
675704
inner_ci = model.affine_outer_to_inner[ci]
@@ -969,22 +998,22 @@ function _add_constraint_with_parameters_on_function(
969998
if !_is_vector_affine(func)
970999
fq = func
9711000
inner_ci = MOI.add_constraint(model.optimizer, fq, set)
972-
model.last_quad_add_added += 1
1001+
model.last_vec_quad_add_added += 1
9731002
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
974-
model.last_quad_add_added,
1003+
model.last_vec_quad_add_added,
9751004
)
976-
model.quadratic_outer_to_inner[outer_ci] = inner_ci
1005+
model.vector_quadratic_outer_to_inner[outer_ci] = inner_ci
9771006
model.constraint_outer_to_inner[outer_ci] = inner_ci
9781007
else
9791008
fa = MOI.VectorAffineFunction(func.affine_terms, func.constants)
9801009
inner_ci = MOI.add_constraint(model.optimizer, fa, set)
981-
model.last_quad_add_added += 1
1010+
model.last_vec_quad_add_added += 1
9821011
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
983-
model.last_quad_add_added,
1012+
model.last_vec_quad_add_added,
9841013
)
9851014
# This part is used to remember that ci came from a quadratic function
9861015
# It is particularly useful because sometimes the constraint mutates
987-
model.quadratic_outer_to_inner[outer_ci] = inner_ci
1016+
model.vector_quadratic_outer_to_inner[outer_ci] = inner_ci
9881017
model.constraint_outer_to_inner[outer_ci] = inner_ci
9891018
end
9901019
model.vector_quadratic_constraint_cache[inner_ci] = pf
@@ -1008,10 +1037,11 @@ function MOI.delete(
10081037
model::Optimizer,
10091038
c::MOI.ConstraintIndex{F,S},
10101039
) where {F<:MOI.VectorQuadraticFunction,S<:MOI.AbstractSet}
1011-
ci_inner = model.constraint_outer_to_inner[c]
1012-
if haskey(model.quadratic_constraint_cache, ci_inner)
1013-
delete!(model.quadratic_constraint_cache, ci_inner)
1014-
delete!(model.quadratic_constraint_cache_set, ci_inner)
1040+
if haskey(model.vector_quadratic_outer_to_inner, c)
1041+
ci_inner = model.vector_quadratic_outer_to_inner[c]
1042+
delete!(model.vector_quadratic_outer_to_inner, c)
1043+
delete!(model.vector_quadratic_constraint_cache, ci_inner)
1044+
delete!(model.vector_quadratic_constraint_cache_set, ci_inner)
10151045
MOI.delete(model.optimizer, ci_inner)
10161046
else
10171047
MOI.delete(model.optimizer, c)

src/ParametricOptInterface.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
130130

131131
# quadratic constraitn data
132132
last_quad_add_added::Int64
133+
last_vec_quad_add_added::Int64
133134
# Store the map for SQFs (some might be transformed into SAF)
134135
# for instance p*p + var -> ScalarAffine(var)
135136
quadratic_outer_to_inner::DoubleDict{MOI.ConstraintIndex}
137+
vector_quadratic_outer_to_inner::DoubleDict{MOI.ConstraintIndex}
136138
# Clever cache of data (inner key)
137139
quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}}
138140
# Store original constraint set (inner key)
@@ -212,6 +214,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
212214
DoubleDict{MOI.AbstractScalarSet}(),
213215
# quadratic constraint
214216
0,
217+
0,
218+
DoubleDict{MOI.ConstraintIndex}(),
215219
DoubleDict{MOI.ConstraintIndex}(),
216220
DoubleDict{ParametricQuadraticFunction{T}}(),
217221
DoubleDict{MOI.AbstractScalarSet}(),

src/parametric_functions.jl

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -705,57 +705,60 @@ function _parametric_affine_terms(
705705
base + term.scalar_term.coefficient * model.parameters[p_idx_val]
706706
end
707707

708-
for (term, coef) in f.affine_data
709-
output_idx = term.output_index
710-
var = term.scalar_term.variable
711-
param_terms_dict[(var, output_idx)] = coef
708+
# TODO: check if affine data should only contains variables that appear in pv
709+
for (var, coef) in f.affine_data
710+
if !haskey(param_terms_dict, var)
711+
param_terms_dict[var] = zero(T)
712+
end
713+
param_terms_dict[var] += coef
712714
end
713715

714716
return param_terms_dict
715717
end
716718

717-
function _delta_parametric_affine_terms(
718-
model,
719-
f::ParametricVectorQuadraticFunction{T},
720-
) where {T}
721-
delta_terms = Dict{Tuple{Int,MOI.VariableIndex},T}()
722-
723-
# Handle parameter-variable quadratic terms (px) that become affine (x) when p is updated
724-
for term in f.pv
725-
p_idx_val = p_idx(term.scalar_term.variable_1)
726-
var = term.scalar_term.variable_2
727-
output_idx = term.output_index
728-
729-
if haskey(model.updated_parameters, p_idx_val) &&
730-
!isnan(model.updated_parameters[p_idx_val])
731-
old_param_val = model.parameters[p_idx_val]
732-
new_param_val = model.updated_parameters[p_idx_val]
733-
delta_coef =
734-
term.scalar_term.coefficient * (new_param_val - old_param_val)
735-
736-
key = (output_idx, var)
737-
current_delta = get(delta_terms, key, zero(T))
738-
delta_terms[key] = current_delta + delta_coef
739-
end
740-
end
741-
742-
# Handle parameter-only affine terms
743-
for term in f.p
744-
p_idx_val = p_idx(term.scalar_term.variable)
745-
output_idx = term.output_index
746-
747-
if haskey(model.updated_parameters, p_idx_val) &&
748-
!isnan(model.updated_parameters[p_idx_val])
749-
old_param_val = model.parameters[p_idx_val]
750-
new_param_val = model.updated_parameters[p_idx_val]
751-
752-
# This becomes a constant change, not an affine term change
753-
# We'll handle this in the constant update function
754-
end
755-
end
756-
757-
return delta_terms
758-
end
719+
# TODO: USED once we update _update_vector_quadratic_constraints!
720+
# function _delta_parametric_affine_terms(
721+
# model,
722+
# f::ParametricVectorQuadraticFunction{T},
723+
# ) where {T}
724+
# delta_terms = Dict{Tuple{Int,MOI.VariableIndex},T}()
725+
726+
# # Handle parameter-variable quadratic terms (px) that become affine (x) when p is updated
727+
# for term in f.pv
728+
# p_idx_val = p_idx(term.scalar_term.variable_1)
729+
# var = term.scalar_term.variable_2
730+
# output_idx = term.output_index
731+
732+
# if haskey(model.updated_parameters, p_idx_val) &&
733+
# !isnan(model.updated_parameters[p_idx_val])
734+
# old_param_val = model.parameters[p_idx_val]
735+
# new_param_val = model.updated_parameters[p_idx_val]
736+
# delta_coef =
737+
# term.scalar_term.coefficient * (new_param_val - old_param_val)
738+
739+
# key = (output_idx, var)
740+
# current_delta = get(delta_terms, key, zero(T))
741+
# delta_terms[key] = current_delta + delta_coef
742+
# end
743+
# end
744+
745+
# # Handle parameter-only affine terms
746+
# for term in f.p
747+
# p_idx_val = p_idx(term.scalar_term.variable)
748+
# output_idx = term.output_index
749+
750+
# if haskey(model.updated_parameters, p_idx_val) &&
751+
# !isnan(model.updated_parameters[p_idx_val])
752+
# old_param_val = model.parameters[p_idx_val]
753+
# new_param_val = model.updated_parameters[p_idx_val]
754+
755+
# # This becomes a constant change, not an affine term change
756+
# # We'll handle this in the constant update function
757+
# end
758+
# end
759+
760+
# return delta_terms
761+
# end
759762

760763
function _update_cache!(
761764
f::ParametricVectorQuadraticFunction{T},

src/update_parameters.jl

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -330,58 +330,61 @@ function _update_vector_quadratic_constraints!(model::Optimizer)
330330
return
331331
end
332332

333-
function _delta_parametric_constant(
334-
model,
335-
f::ParametricVectorQuadraticFunction{T},
336-
) where {T}
337-
delta_constants = zeros(T, length(f.current_constant))
338-
339-
# Handle parameter-only affine terms
340-
for term in f.p
341-
p_idx_val = p_idx(term.scalar_term.variable)
342-
output_idx = term.output_index
343-
344-
if !isnan(model.updated_parameters[p_idx_val])
345-
old_param_val = model.parameters[p_idx_val]
346-
new_param_val = model.updated_parameters[p_idx_val]
347-
delta_constants[output_idx] +=
348-
term.scalar_term.coefficient * (new_param_val - old_param_val)
349-
end
350-
end
333+
# TODO: USED once we update _update_vector_quadratic_constraints!
334+
# function _delta_parametric_constant(
335+
# model,
336+
# f::ParametricVectorQuadraticFunction{T},
337+
# ) where {T}
338+
# delta_constants = zeros(T, length(f.current_constant))
351339

352-
# Handle parameter-parameter quadratic terms
353-
for term in f.pp
354-
idx = term.output_index
355-
var1 = term.scalar_term.variable_1
356-
var2 = term.scalar_term.variable_2
357-
p1 = p_idx(var1)
358-
p2 = p_idx(var2)
359-
360-
if !isnan(model.updated_parameters[p1]) ||
361-
!isnan(model.updated_parameters[p2])
362-
old_val1 = model.parameters[p1]
363-
old_val2 = model.parameters[p2]
364-
new_val1 =
365-
!isnan(model.updated_parameters[p1]) ?
366-
model.updated_parameters[p1] : old_val1
367-
new_val2 =
368-
!isnan(model.updated_parameters[p2]) ?
369-
model.updated_parameters[p2] : old_val2
370-
371-
coef = term.scalar_term.coefficient / (var1 == var2 ? 2 : 1)
372-
delta_constants[idx] +=
373-
coef * (new_val1 * new_val2 - old_val1 * old_val2)
374-
end
375-
end
340+
# # Handle parameter-only affine terms
341+
# for term in f.p
342+
# p_idx_val = p_idx(term.scalar_term.variable)
343+
# output_idx = term.output_index
376344

377-
return delta_constants
378-
end
345+
# if !isnan(model.updated_parameters[p_idx_val])
346+
# old_param_val = model.parameters[p_idx_val]
347+
# new_param_val = model.updated_parameters[p_idx_val]
348+
# delta_constants[output_idx] +=
349+
# term.scalar_term.coefficient * (new_param_val - old_param_val)
350+
# end
351+
# end
379352

353+
# # Handle parameter-parameter quadratic terms
354+
# for term in f.pp
355+
# idx = term.output_index
356+
# var1 = term.scalar_term.variable_1
357+
# var2 = term.scalar_term.variable_2
358+
# p1 = p_idx(var1)
359+
# p2 = p_idx(var2)
360+
361+
# if !isnan(model.updated_parameters[p1]) ||
362+
# !isnan(model.updated_parameters[p2])
363+
# old_val1 = model.parameters[p1]
364+
# old_val2 = model.parameters[p2]
365+
# new_val1 =
366+
# !isnan(model.updated_parameters[p1]) ?
367+
# model.updated_parameters[p1] : old_val1
368+
# new_val2 =
369+
# !isnan(model.updated_parameters[p2]) ?
370+
# model.updated_parameters[p2] : old_val2
371+
372+
# coef = term.scalar_term.coefficient / (var1 == var2 ? 2 : 1)
373+
# delta_constants[idx] +=
374+
# coef * (new_val1 * new_val2 - old_val1 * old_val2)
375+
# end
376+
# end
377+
378+
# return delta_constants
379+
# end
380+
381+
# TODO: Update once MOI.VectorConstantChange is implemented
380382
function _update_vector_quadratic_constraints!(
381383
model::Optimizer,
382384
vector_quadratic_constraint_cache_inner::DoubleDictInner{F,S,V},
383385
) where {F,S,V}
384386
for (inner_ci, pf) in vector_quadratic_constraint_cache_inner
387+
385388
# delta_constants = _delta_parametric_constant(model, pf)
386389
# if !iszero(delta_constants)
387390
# pf.current_constant .+= delta_constants

0 commit comments

Comments
 (0)