Skip to content
Merged
76 changes: 53 additions & 23 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ function MOI.is_empty(model::Optimizer)
isempty(model.affine_constraint_cache_set) &&
# quad ctr
model.last_quad_add_added == 0 &&
model.last_vec_quad_add_added == 0 &&
isempty(model.quadratic_outer_to_inner) &&
isempty(model.vector_quadratic_outer_to_inner) &&
isempty(model.quadratic_constraint_cache) &&
isempty(model.quadratic_constraint_cache_set) &&
isempty(model.vector_quadratic_constraint_cache) &&
Expand Down Expand Up @@ -162,7 +164,9 @@ function MOI.empty!(model::Optimizer{T}) where {T}
empty!(model.affine_constraint_cache_set)
# quad ctr
model.last_quad_add_added = 0
model.last_vec_quad_add_added = 0
empty!(model.quadratic_outer_to_inner)
empty!(model.vector_quadratic_outer_to_inner)
empty!(model.quadratic_constraint_cache)
empty!(model.quadratic_constraint_cache_set)
empty!(model.vector_quadratic_constraint_cache)
Expand Down Expand Up @@ -513,11 +517,16 @@ end
function MOI.set(
model::Optimizer,
attr::MOI.ConstraintName,
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S},
c::MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S},
name::String,
) where {T,S<:MOI.AbstractSet}
if haskey(model.affine_outer_to_inner, c)
MOI.set(model.optimizer, attr, model.affine_outer_to_inner[c], name)
if haskey(model.vector_quadratic_outer_to_inner, c)
MOI.set(
model.optimizer,
attr,
model.vector_quadratic_outer_to_inner[c],
name,
)
else
MOI.set(model.optimizer, attr, c, name)
end
Expand All @@ -527,10 +536,14 @@ end
function MOI.set(
model::Optimizer,
attr::MOI.ConstraintName,
c::MOI.ConstraintIndex,
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S},
name::String,
)
MOI.set(model.optimizer, attr, c, name)
) where {T,S<:MOI.AbstractSet}
if haskey(model.affine_outer_to_inner, c)
MOI.set(model.optimizer, attr, model.affine_outer_to_inner[c], name)
else
MOI.set(model.optimizer, attr, c, name)
end
return
end

Expand All @@ -549,9 +562,17 @@ end
function MOI.get(
model::Optimizer,
attr::MOI.ConstraintName,
c::MOI.ConstraintIndex,
)
return MOI.get(model.optimizer, attr, c)
c::MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S},
) where {T,S<:MOI.AbstractSet}
if haskey(model.vector_quadratic_outer_to_inner, c)
return MOI.get(
model.optimizer,
attr,
model.vector_quadratic_outer_to_inner[c],
)
else
return MOI.get(model.optimizer, attr, c)
end
end

function MOI.get(
Expand Down Expand Up @@ -606,13 +627,21 @@ function MOI.get(
return _original_function(
model.quadratic_constraint_cache[inner_ci],
)
elseif haskey(model.vector_quadratic_constraint_cache, inner_ci)
else
return convert(
MOI.ScalarQuadraticFunction{T},
MOI.get(model.optimizer, attr, inner_ci),
)
end
elseif haskey(model.vector_quadratic_outer_to_inner, ci)
inner_ci = model.vector_quadratic_outer_to_inner[ci]
if haskey(model.vector_quadratic_constraint_cache, inner_ci)
return _original_function(
model.vector_quadratic_constraint_cache[inner_ci],
)
else
return convert(
MOI.ScalarQuadraticFunction{T},
MOI.VectorQuadraticFunction{T},
MOI.get(model.optimizer, attr, inner_ci),
)
end
Expand Down Expand Up @@ -655,8 +684,8 @@ 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]
elseif haskey(model.vector_quadratic_outer_to_inner, ci)
inner_ci = model.vector_quadratic_outer_to_inner[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]
Expand Down Expand Up @@ -956,22 +985,22 @@ function _add_constraint_with_parameters_on_function(
if !_is_vector_affine(func)
fq = func
inner_ci = MOI.add_constraint(model.optimizer, fq, set)
model.last_quad_add_added += 1
model.last_vec_quad_add_added += 1
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
model.last_quad_add_added,
model.last_vec_quad_add_added,
)
model.quadratic_outer_to_inner[outer_ci] = inner_ci
model.vector_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
model.last_vec_quad_add_added += 1
outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}(
model.last_quad_add_added,
model.last_vec_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.vector_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
Expand All @@ -995,10 +1024,11 @@ 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)
if haskey(model.vector_quadratic_outer_to_inner, c)
ci_inner = model.vector_quadratic_outer_to_inner[c]
delete!(model.vector_quadratic_outer_to_inner, c)
delete!(model.vector_quadratic_constraint_cache, ci_inner)
delete!(model.vector_quadratic_constraint_cache_set, ci_inner)
MOI.delete(model.optimizer, ci_inner)
else
MOI.delete(model.optimizer, c)
Expand Down
4 changes: 4 additions & 0 deletions src/ParametricOptInterface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,11 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer

# quadratic constraitn data
last_quad_add_added::Int64
last_vec_quad_add_added::Int64
# Store the map for SQFs (some might be transformed into SAF)
# for instance p*p + var -> ScalarAffine(var)
quadratic_outer_to_inner::DoubleDict{MOI.ConstraintIndex}
vector_quadratic_outer_to_inner::DoubleDict{MOI.ConstraintIndex}
# Clever cache of data (inner key)
quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}}
# Store original constraint set (inner key)
Expand Down Expand Up @@ -212,6 +214,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
DoubleDict{MOI.AbstractScalarSet}(),
# quadratic constraint
0,
0,
DoubleDict{MOI.ConstraintIndex}(),
DoubleDict{MOI.ConstraintIndex}(),
DoubleDict{ParametricQuadraticFunction{T}}(),
DoubleDict{MOI.AbstractScalarSet}(),
Expand Down
95 changes: 49 additions & 46 deletions src/parametric_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -705,57 +705,60 @@ function _parametric_affine_terms(
base + term.scalar_term.coefficient * model.parameters[p_idx_val]
end

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

return param_terms_dict
end

function _delta_parametric_affine_terms(
model,
f::ParametricVectorQuadraticFunction{T},
) where {T}
delta_terms = Dict{Tuple{Int,MOI.VariableIndex},T}()

# Handle parameter-variable quadratic terms (px) that become affine (x) when p is updated
for term in f.pv
p_idx_val = p_idx(term.scalar_term.variable_1)
var = term.scalar_term.variable_2
output_idx = term.output_index

if haskey(model.updated_parameters, p_idx_val) &&
!isnan(model.updated_parameters[p_idx_val])
old_param_val = model.parameters[p_idx_val]
new_param_val = model.updated_parameters[p_idx_val]
delta_coef =
term.scalar_term.coefficient * (new_param_val - old_param_val)

key = (output_idx, var)
current_delta = get(delta_terms, key, zero(T))
delta_terms[key] = current_delta + delta_coef
end
end

# Handle parameter-only affine terms
for term in f.p
p_idx_val = p_idx(term.scalar_term.variable)
output_idx = term.output_index

if haskey(model.updated_parameters, p_idx_val) &&
!isnan(model.updated_parameters[p_idx_val])
old_param_val = model.parameters[p_idx_val]
new_param_val = model.updated_parameters[p_idx_val]

# This becomes a constant change, not an affine term change
# We'll handle this in the constant update function
end
end

return delta_terms
end
# TODO: USED once we update _update_vector_quadratic_constraints!
# function _delta_parametric_affine_terms(
# model,
# f::ParametricVectorQuadraticFunction{T},
# ) where {T}
# delta_terms = Dict{Tuple{Int,MOI.VariableIndex},T}()

# # Handle parameter-variable quadratic terms (px) that become affine (x) when p is updated
# for term in f.pv
# p_idx_val = p_idx(term.scalar_term.variable_1)
# var = term.scalar_term.variable_2
# output_idx = term.output_index

# if haskey(model.updated_parameters, p_idx_val) &&
# !isnan(model.updated_parameters[p_idx_val])
# old_param_val = model.parameters[p_idx_val]
# new_param_val = model.updated_parameters[p_idx_val]
# delta_coef =
# term.scalar_term.coefficient * (new_param_val - old_param_val)

# key = (output_idx, var)
# current_delta = get(delta_terms, key, zero(T))
# delta_terms[key] = current_delta + delta_coef
# end
# end

# # Handle parameter-only affine terms
# for term in f.p
# p_idx_val = p_idx(term.scalar_term.variable)
# output_idx = term.output_index

# if haskey(model.updated_parameters, p_idx_val) &&
# !isnan(model.updated_parameters[p_idx_val])
# old_param_val = model.parameters[p_idx_val]
# new_param_val = model.updated_parameters[p_idx_val]

# # This becomes a constant change, not an affine term change
# # We'll handle this in the constant update function
# end
# end

# return delta_terms
# end

function _update_cache!(
f::ParametricVectorQuadraticFunction{T},
Expand Down
91 changes: 47 additions & 44 deletions src/update_parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -330,58 +330,61 @@ function _update_vector_quadratic_constraints!(model::Optimizer)
return
end

function _delta_parametric_constant(
model,
f::ParametricVectorQuadraticFunction{T},
) where {T}
delta_constants = zeros(T, length(f.current_constant))

# Handle parameter-only affine terms
for term in f.p
p_idx_val = p_idx(term.scalar_term.variable)
output_idx = term.output_index

if !isnan(model.updated_parameters[p_idx_val])
old_param_val = model.parameters[p_idx_val]
new_param_val = model.updated_parameters[p_idx_val]
delta_constants[output_idx] +=
term.scalar_term.coefficient * (new_param_val - old_param_val)
end
end
# TODO: USED once we update _update_vector_quadratic_constraints!
# function _delta_parametric_constant(
# model,
# f::ParametricVectorQuadraticFunction{T},
# ) where {T}
# delta_constants = zeros(T, length(f.current_constant))

# Handle parameter-parameter quadratic terms
for term in f.pp
idx = term.output_index
var1 = term.scalar_term.variable_1
var2 = term.scalar_term.variable_2
p1 = p_idx(var1)
p2 = p_idx(var2)

if !isnan(model.updated_parameters[p1]) ||
!isnan(model.updated_parameters[p2])
old_val1 = model.parameters[p1]
old_val2 = model.parameters[p2]
new_val1 =
!isnan(model.updated_parameters[p1]) ?
model.updated_parameters[p1] : old_val1
new_val2 =
!isnan(model.updated_parameters[p2]) ?
model.updated_parameters[p2] : old_val2

coef = term.scalar_term.coefficient / (var1 == var2 ? 2 : 1)
delta_constants[idx] +=
coef * (new_val1 * new_val2 - old_val1 * old_val2)
end
end
# # Handle parameter-only affine terms
# for term in f.p
# p_idx_val = p_idx(term.scalar_term.variable)
# output_idx = term.output_index

return delta_constants
end
# if !isnan(model.updated_parameters[p_idx_val])
# old_param_val = model.parameters[p_idx_val]
# new_param_val = model.updated_parameters[p_idx_val]
# delta_constants[output_idx] +=
# term.scalar_term.coefficient * (new_param_val - old_param_val)
# end
# end

# # Handle parameter-parameter quadratic terms
# for term in f.pp
# idx = term.output_index
# var1 = term.scalar_term.variable_1
# var2 = term.scalar_term.variable_2
# p1 = p_idx(var1)
# p2 = p_idx(var2)

# if !isnan(model.updated_parameters[p1]) ||
# !isnan(model.updated_parameters[p2])
# old_val1 = model.parameters[p1]
# old_val2 = model.parameters[p2]
# new_val1 =
# !isnan(model.updated_parameters[p1]) ?
# model.updated_parameters[p1] : old_val1
# new_val2 =
# !isnan(model.updated_parameters[p2]) ?
# model.updated_parameters[p2] : old_val2

# coef = term.scalar_term.coefficient / (var1 == var2 ? 2 : 1)
# delta_constants[idx] +=
# coef * (new_val1 * new_val2 - old_val1 * old_val2)
# end
# end

# return delta_constants
# end

# TODO: Update once MOI.VectorConstantChange is implemented
function _update_vector_quadratic_constraints!(
model::Optimizer,
vector_quadratic_constraint_cache_inner::DoubleDictInner{F,S,V},
) where {F,S,V}
for (inner_ci, pf) in vector_quadratic_constraint_cache_inner

# delta_constants = _delta_parametric_constant(model, pf)
# if !iszero(delta_constants)
# pf.current_constant .+= delta_constants
Expand Down
Loading
Loading