From 3d2f4237e2b8a613692a09f0643b3f611ae6be83 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 9 Apr 2023 23:39:49 -0300 Subject: [PATCH 01/13] add update without breaking --- src/ParametricOptInterface.jl | 248 ++++++++++++++--------- src/duals.jl | 17 +- src/update_parameters.jl | 86 ++------ src/utils.jl | 360 ++++++++++++++++++++++++++++++---- 4 files changed, 492 insertions(+), 219 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 7382636e..1749fafe 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -9,7 +9,7 @@ using MathOptInterface const MOI = MathOptInterface -const PARAMETER_INDEX_THRESHOLD = 1_000_000_000_000_000_000 +const PARAMETER_INDEX_THRESHOLD = Int64(4_611_686_018_427_387_904) # div(typemax(Int64),2)+1 @enum ConstraintsInterpretationCode ONLY_CONSTRAINTS ONLY_BOUNDS BOUNDS_AND_CONSTRAINTS @@ -47,6 +47,70 @@ function p_idx(vi::MOI.VariableIndex) return ParameterIndex(vi.value - PARAMETER_INDEX_THRESHOLD) end +const ParamTo{T} = MOI.Utilities.CleverDicts.CleverDict{ + ParameterIndex, + T, + typeof(MOI.Utilities.CleverDicts.key_to_index), + typeof(MOI.Utilities.CleverDicts.index_to_key), +} +const VariableMap = MOI.Utilities.CleverDicts.CleverDict{ + MOI.VariableIndex, + MOI.VariableIndex, + typeof(MOI.Utilities.CleverDicts.key_to_index), + typeof(MOI.Utilities.CleverDicts.index_to_key), +} + +const DoubleDict{T} = MOI.Utilities.DoubleDicts.DoubleDict{T} +const DoubleDictInner{F,S,T} = MOI.Utilities.DoubleDicts.DoubleDictInner{F,S,T} + +mutable struct ParametricQuadraticFunction{T} + # helper to efficiently update affine terms + affine_data::Dict{MOI.VariableIndex,T} + # constant * parameter * variable (in this order) + pv::Vector{MOI.ScalarQuadraticTerm{T}} + # constant * parameter * parameter + pp::Vector{MOI.ScalarQuadraticTerm{T}} + # constant * variable * variable + vv::Vector{MOI.ScalarQuadraticTerm{T}} + # constant * parameter + p::Vector{MOI.ScalarAffineTerm{T}} + # constant * variable + v::Vector{MOI.ScalarAffineTerm{T}} + # constant (includes the set constant TODO!!!!) + c::T + # + # cache data that is inside the solver to avoid slow getters + current_terms_with_p::Dict{MOI.VariableIndex,T} + current_constant::T + # computed on runtime + # updated_terms_with_p::Dict{MOI.VariableIndex,T} + # updated_constant::T + #= + function ParametricQuadraticFunction{T}() + return new( + zero(T), + MOI.ScalarAffineTerm{T}[], + MOI.ScalarAffineTerm{T}[], + Dict{MOI.VariableIndex,T}(), + MOI.ScalarQuadraticTerm{T}[], + MOI.ScalarQuadraticTerm{T}[], + MOI.ScalarQuadraticTerm{T}[], + ) + end + =# +end + +mutable struct ParametricAffineFunction{T} + # constant * parameter + p::Vector{MOI.ScalarAffineTerm{T}} + # constant * variable + v::Vector{MOI.ScalarAffineTerm{T}} + # constant + c::T + # cache to avoid slow getters + current_constant::T +end + """ Optimizer{T, OT <: MOI.ModelLike} <: MOI.AbstractOptimizer @@ -74,68 +138,57 @@ ParametricOptInterface.Optimizer{Float64,GLPK.Optimizer} """ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer optimizer::OT - parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - } + parameters::ParamTo{T} parameters_name::Dict{MOI.VariableIndex,String} # The updated_parameters dictionary has the same dimension of the # parameters dictionary and if the value stored is a NaN is means # that the parameter has not been updated. - updated_parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - } - variables::MOI.Utilities.CleverDicts.CleverDict{ - MOI.VariableIndex, - MOI.VariableIndex, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - } + updated_parameters::ParamTo{T} + variables::VariableMap last_variable_index_added::Int64 last_parameter_index_added::Int64 # Store the constraint function and set passed to POI by MOI - original_constraint_function_and_set_cache::Dict{ + # REM + original_constraint_data::Dict{ MOI.ConstraintIndex, Tuple{MOI.AbstractFunction,MOI.AbstractSet}, } # Store the map for SAFs that might be transformed into VI - affine_added_cache::MOI.Utilities.DoubleDicts.DoubleDict{ - MOI.ConstraintIndex, - } + affine_added_cache::DoubleDict{MOI.ConstraintIndex} last_affine_added::Int64 + # Store reference to parameters of affine constraints with parameters: v + p - affine_constraint_cache::MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.ScalarAffineTerm{Float64}}, - } + # REM + affine_constraint_cache::DoubleDict{Vector{MOI.ScalarAffineTerm{Float64}}} # Store constraint set - affine_constraint_cache_set::MOI.Utilities.DoubleDicts.DoubleDict{ - MOI.AbstractScalarSet, - } + affine_constraint_cache_set::DoubleDict{MOI.AbstractScalarSet} + + # replaces above + affine_constraint_cache2::DoubleDict{ParametricAffineFunction{T}} + # replaces below + quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}} + objective_cache::Union{Nothing,ParametricQuadraticFunction{T}} + # Store reference quadratic constraints with parameter * variable constraints: p * v - quadratic_constraint_cache_pv::MOI.Utilities.DoubleDicts.DoubleDict{ + # REM + quadratic_constraint_cache_pv::DoubleDict{ Vector{MOI.ScalarQuadraticTerm{Float64}}, } - # Store reference quadratic constraints with parameter * variable constraints: p * v - quadratic_constraint_cache_pp::MOI.Utilities.DoubleDicts.DoubleDict{ + # Store reference quadratic constraints with parameter * parameter constraints: p * v + # REM + quadratic_constraint_cache_pp::DoubleDict{ Vector{MOI.ScalarQuadraticTerm{Float64}}, } # Store constraint set - quadratic_constraint_cache_pp_set::MOI.Utilities.DoubleDicts.DoubleDict{ - MOI.AbstractScalarSet, - } + # REM + quadratic_constraint_cache_pp_set::DoubleDict{MOI.AbstractScalarSet} # Store reference to constraints with quad_variable_term + affine_with_parameters: v * v + p - quadratic_constraint_cache_pc::MOI.Utilities.DoubleDicts.DoubleDict{ + # REM + quadratic_constraint_cache_pc::DoubleDict{ Vector{MOI.ScalarAffineTerm{Float64}}, } # Store constraint set - quadratic_constraint_cache_pc_set::MOI.Utilities.DoubleDicts.DoubleDict{ - MOI.AbstractScalarSet, - } + quadratic_constraint_cache_pc_set::DoubleDict{MOI.AbstractScalarSet} # Store the reference to variables in the scalar affine part that are # multiplied by parameters in the scalar quadratic terms. # i.e. @@ -144,30 +197,39 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # When we need to update the constraint coefficient after updating the parameter # we must do (new_p_1 + 2.0) * v_1 # This cache is storing the 2.0 * v_1 part. - quadratic_constraint_variables_associated_to_parameters_cache::MOI.Utilities.DoubleDicts.DoubleDict{ + # REM + variables_multiplied_by_parameters::DoubleDict{ Vector{MOI.ScalarAffineTerm{T}}, } # Store the map for SQFs that might be transformed into SAF # for instance p*p + var -> ScalarAffine(var) - quadratic_added_cache::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex} + moi_quadratic_to_poi_affine_map::Dict{ + MOI.ConstraintIndex, + MOI.ConstraintIndex, + } # Store parametric expressions for product of variables quadratic_objective_cache_product::Dict{ Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, } last_quad_add_added::Int64 - vector_constraint_cache::MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.VectorAffineTerm{Float64}}, - } + vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{Float64}}} # Ditto from the constraint caches but for the objective function + # REM original_objective_function::MOI.AbstractFunction + # REM affine_objective_cache::Vector{MOI.ScalarAffineTerm{T}} + # REM quadratic_objective_cache_pv::Vector{MOI.ScalarQuadraticTerm{T}} + # REM quadratic_objective_cache_pp::Vector{MOI.ScalarQuadraticTerm{T}} + # REM quadratic_objective_cache_pc::Vector{MOI.ScalarAffineTerm{T}} + # REM quadratic_objective_variables_associated_to_parameters_cache::Vector{ MOI.ScalarAffineTerm{T}, } + # multiplicative_parameters::Set{Int64} dual_value_of_parameters::Vector{Float64} evaluate_duals::Bool @@ -179,14 +241,15 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer evaluate_duals::Bool = true, save_original_objective_and_constraints::Bool = true, ) where {OT} + T = Float64 return new{Float64,OT}( optimizer, - MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,Float64}( + MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,T}( MOI.Utilities.CleverDicts.key_to_index, MOI.Utilities.CleverDicts.index_to_key, ), Dict{MOI.VariableIndex,String}(), - MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,Float64}( + MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,T}( MOI.Utilities.CleverDicts.key_to_index, MOI.Utilities.CleverDicts.index_to_key, ), @@ -203,43 +266,34 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer MOI.ConstraintIndex, Tuple{MOI.AbstractFunction,MOI.AbstractSet}, }(), - MOI.Utilities.DoubleDicts.DoubleDict{MOI.ConstraintIndex}(), + DoubleDict{MOI.ConstraintIndex}(), 0, - MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.ScalarAffineTerm{Float64}}, - }(), - MOI.Utilities.DoubleDicts.DoubleDict{MOI.AbstractScalarSet}(), - MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.ScalarQuadraticTerm{Float64}}, - }(), - MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.ScalarQuadraticTerm{Float64}}, - }(), - MOI.Utilities.DoubleDicts.DoubleDict{MOI.AbstractScalarSet}(), - MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.ScalarAffineTerm{Float64}}, - }(), - MOI.Utilities.DoubleDicts.DoubleDict{MOI.AbstractScalarSet}(), - MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.ScalarAffineTerm{Float64}}, - }(), + DoubleDict{Vector{MOI.ScalarAffineTerm{T}}}(), + DoubleDict{MOI.AbstractScalarSet}(), + DoubleDict{ParametricAffineFunction{T}}(), + DoubleDict{ParametricQuadraticFunction{T}}(), + nothing, + DoubleDict{Vector{MOI.ScalarQuadraticTerm{T}}}(), + DoubleDict{Vector{MOI.ScalarQuadraticTerm{T}}}(), + DoubleDict{MOI.AbstractScalarSet}(), + DoubleDict{Vector{MOI.ScalarAffineTerm{T}}}(), + DoubleDict{MOI.AbstractScalarSet}(), + DoubleDict{Vector{MOI.ScalarAffineTerm{T}}}(), Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}(), Dict{ Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, }(), 0, - MOI.Utilities.DoubleDicts.DoubleDict{ - Vector{MOI.VectorAffineTerm{Float64}}, - }(), + DoubleDict{Vector{MOI.VectorAffineTerm{T}}}(), MOI.VariableIndex(-1), - Vector{MOI.ScalarAffineTerm{Float64}}(), - Vector{MOI.ScalarQuadraticTerm{Float64}}(), - Vector{MOI.ScalarQuadraticTerm{Float64}}(), - Vector{MOI.ScalarAffineTerm{Float64}}(), - Vector{MOI.ScalarAffineTerm{Float64}}(), + Vector{MOI.ScalarAffineTerm{T}}(), + Vector{MOI.ScalarQuadraticTerm{T}}(), + Vector{MOI.ScalarQuadraticTerm{T}}(), + Vector{MOI.ScalarAffineTerm{T}}(), + Vector{MOI.ScalarAffineTerm{T}}(), Set{Int64}(), - Vector{Float64}(), + Vector{T}(), evaluate_duals, 0, ONLY_CONSTRAINTS, @@ -261,20 +315,21 @@ function MOI.is_empty(model::Optimizer) isempty(model.variables) && model.last_variable_index_added == 0 && model.last_parameter_index_added == PARAMETER_INDEX_THRESHOLD && - isempty(model.original_constraint_function_and_set_cache) && + isempty(model.original_constraint_data) && isempty(model.affine_added_cache) && model.last_affine_added == 0 && isempty(model.affine_constraint_cache) && isempty(model.affine_constraint_cache_set) && + isempty(model.quadratic_constraint_cache) && isempty(model.quadratic_constraint_cache_pv) && isempty(model.quadratic_constraint_cache_pp) && isempty(model.quadratic_constraint_cache_pp_set) && isempty(model.quadratic_constraint_cache_pc) && isempty(model.quadratic_constraint_cache_pc_set) && isempty( - model.quadratic_constraint_variables_associated_to_parameters_cache, + model.variables_multiplied_by_parameters, ) && - isempty(model.quadratic_added_cache) && + isempty(model.moi_quadratic_to_poi_affine_map) && isempty(model.quadratic_objective_cache_product) && model.last_quad_add_added == 0 && model.original_objective_function == MOI.VariableIndex(-1) && @@ -453,18 +508,19 @@ function MOI.empty!(model::Optimizer{T}) where {T} empty!(model.variables) model.last_variable_index_added = 0 model.last_parameter_index_added = PARAMETER_INDEX_THRESHOLD - empty!(model.original_constraint_function_and_set_cache) + empty!(model.original_constraint_data) empty!(model.affine_added_cache) model.last_affine_added = 0 empty!(model.affine_constraint_cache) empty!(model.affine_constraint_cache_set) + empty!(model.quadratic_constraint_cache) empty!(model.quadratic_constraint_cache_pv) empty!(model.quadratic_constraint_cache_pp) empty!(model.quadratic_constraint_cache_pp_set) empty!(model.quadratic_constraint_cache_pc) empty!(model.quadratic_constraint_cache_pc_set) - empty!(model.quadratic_constraint_variables_associated_to_parameters_cache) - empty!(model.quadratic_added_cache) + empty!(model.variables_multiplied_by_parameters) + empty!(model.moi_quadratic_to_poi_affine_map) empty!(model.quadratic_objective_cache_product) model.last_quad_add_added = 0 model.original_objective_function = MOI.VariableIndex(-1) @@ -523,8 +579,8 @@ function MOI.set( c::MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}, name::String, ) where {T,S<:MOI.AbstractSet} - if haskey(model.quadratic_added_cache, c) - MOI.set(model.optimizer, attr, model.quadratic_added_cache[c], name) + if haskey(model.moi_quadratic_to_poi_affine_map, c) + MOI.set(model.optimizer, attr, model.moi_quadratic_to_poi_affine_map[c], name) else MOI.set(model.optimizer, attr, c, name) end @@ -560,8 +616,8 @@ function MOI.get( attr::MOI.ConstraintName, c::MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}, ) where {T,S<:MOI.AbstractSet} - if haskey(model.quadratic_added_cache, c) - return MOI.get(model.optimizer, attr, model.quadratic_added_cache[c]) + if haskey(model.moi_quadratic_to_poi_affine_map, c) + return MOI.get(model.optimizer, attr, model.moi_quadratic_to_poi_affine_map[c]) else return MOI.get(model.optimizer, attr, c) end @@ -620,8 +676,8 @@ function MOI.get( attr::MOI.ConstraintFunction, ci::MOI.ConstraintIndex{F,S}, ) where {F,S} - if haskey(model.original_constraint_function_and_set_cache, ci) - return model.original_constraint_function_and_set_cache[ci][1] + if haskey(model.original_constraint_data, ci) + return model.original_constraint_data[ci][1] else return MOI.get(model.optimizer, attr, ci) end @@ -688,8 +744,8 @@ function MOI.get( attr::MOI.ConstraintSet, ci::MOI.ConstraintIndex{F,S}, ) where {F,S} - if haskey(model.original_constraint_function_and_set_cache, ci) - return model.original_constraint_function_and_set_cache[ci][2] + if haskey(model.original_constraint_data, ci) + return model.original_constraint_data[ci][2] else MOI.throw_if_not_valid(model, ci) return MOI.get(model.optimizer, attr, ci) @@ -731,16 +787,16 @@ end # In the AbstractBridgeOptimizer, we collect all the possible constraint types and them filter with NumberOfConstraints. # If NumberOfConstraints is zero then we remove it from the list. -# Here, you can look over keys(quadratic_added_cache) and add the F-S types of all the keys in constraints. +# Here, you can look over keys(moi_quadratic_to_poi_affine_map) and add the F-S types of all the keys in constraints. # To implement NumberOfConstraints, you call NumberOfConstraints to the inner optimizer. -# Then you remove the number of constraints of that that in values(quadratic_added_cache) +# Then you remove the number of constraints of that that in values(moi_quadratic_to_poi_affine_map) function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) inner_ctrs = MOI.get(model.optimizer, MOI.ListOfConstraintTypesPresent()) if !has_quadratic_constraint_caches(model) return inner_ctrs end - cache_keys = collect(keys(model.quadratic_added_cache)) + cache_keys = collect(keys(model.moi_quadratic_to_poi_affine_map)) constraints = Set{Tuple{DataType,DataType}}() for (F, S) in inner_ctrs @@ -801,7 +857,7 @@ function MOI.get( :quadratic_constraint_cache_pp, :quadratic_constraint_cache_pv, # JD: Check if this applies here - # :quadratic_constraint_variables_associated_to_parameters_cache + # :variables_multiplied_by_parameters ] for field in quadratic_caches @@ -884,7 +940,7 @@ function add_constraint_with_parameters_on_function( end end if model.save_original_objective_and_constraints - model.original_constraint_function_and_set_cache[poi_ci] = (f, set) + model.original_constraint_data[poi_ci] = (f, set) end return poi_ci end @@ -1271,7 +1327,7 @@ function add_constraint_with_parameters_on_function( ) model.vector_constraint_cache[ci] = params if model.save_original_objective_and_constraints - model.original_constraint_function_and_set_cache[ci] = (f, set) + model.original_constraint_data[ci] = (f, set) end return ci end @@ -1320,9 +1376,9 @@ function add_constraint_with_parameters_on_function( new_ci = MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}( model.last_quad_add_added, ) - model.quadratic_added_cache[new_ci] = ci + model.moi_quadratic_to_poi_affine_map[new_ci] = ci if model.save_original_objective_and_constraints - model.original_constraint_function_and_set_cache[new_ci] = (f, set) + model.original_constraint_data[new_ci] = (f, set) end fill_quadratic_constraint_caches!( model, diff --git a/src/duals.jl b/src/duals.jl index 464839cc..2725a3d4 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -45,12 +45,8 @@ 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}, + affine_constraint_cache_inner::DoubleDictInner{F,S,V1}, + affine_added_cache_inner::DoubleDictInner{F,S,V2}, ) where {OT,F,S,V1,V2} for (ci, param_array) in affine_constraint_cache_inner calculate_parameters_in_ci!( @@ -84,14 +80,10 @@ 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, - }, + quadratic_constraint_cache_pc_inner::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] + moi_ci = model.moi_quadratic_to_poi_affine_map[poi_ci] calculate_parameters_in_ci!( param_dual_cum_sum, model.optimizer, @@ -117,6 +109,7 @@ function calculate_parameters_in_ci!( return end +# this one seem to be the same as the next function update_duals_in_affine_objective!( param_dual_cum_sum::Vector{Float64}, affine_objective_cache::Vector{MOI.ScalarAffineTerm{T}}, diff --git a/src/update_parameters.jl b/src/update_parameters.jl index a82ecc1c..34743f5d 100644 --- a/src/update_parameters.jl +++ b/src/update_parameters.jl @@ -42,29 +42,11 @@ end function update_parameter_in_affine_constraints!( optimizer::OT, - parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, - updated_parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, - affine_constraint_cache_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{ - F, - S, - V1, - }, - affine_constraint_cache_set_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{ - F, - S, - MOI.AbstractScalarSet, - }, - affine_added_cache_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{F,S,V2}, + parameters::ParamTo{T}, + updated_parameters::ParamTo{T}, + affine_constraint_cache_inner::DoubleDictInner{F,S,V1}, + affine_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, + affine_added_cache_inner::DoubleDictInner{F,S,V2}, ) where {OT,T,F,S,V1,V2} for (ci, param_array) in affine_constraint_cache_inner new_set = update_parameter_in_affine_constraints!( @@ -84,18 +66,8 @@ function update_parameter_in_affine_constraints!( optimizer::OT, ci::CI, param_array::Vector{MOI.ScalarAffineTerm{T}}, - parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, - updated_parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, + parameters::ParamTo{T}, + updated_parameters::ParamTo{T}, set::S, ) where {OT,T,CI,S} param_constant = zero(T) @@ -158,7 +130,7 @@ function update_parameter_in_quadratic_constraints_pc!(model::Optimizer) MOI.set( model.optimizer, MOI.ConstraintSet(), - model.quadratic_added_cache[ci], + model.moi_quadratic_to_poi_affine_map[ci], new_set, ) model.quadratic_constraint_cache_pc_set[ci] = new_set @@ -230,7 +202,7 @@ function update_parameter_in_quadratic_constraints_pp!(model::Optimizer) MOI.set( model.optimizer, MOI.ConstraintSet(), - model.quadratic_added_cache[ci], + model.moi_quadratic_to_poi_affine_map[ci], new_set, ) model.quadratic_constraint_cache_pp_set[ci] = new_set @@ -305,11 +277,11 @@ function update_parameter_in_quadratic_constraints_pv!(model::Optimizer) end end if haskey( - model.quadratic_constraint_variables_associated_to_parameters_cache, + model.variables_multiplied_by_parameters, ci, ) for aff_term in - model.quadratic_constraint_variables_associated_to_parameters_cache[ci] + model.variables_multiplied_by_parameters[ci] coef = aff_term.coefficient if haskey(new_coeff_per_variable, aff_term.variable) new_coeff_per_variable[aff_term.variable] += coef @@ -318,7 +290,7 @@ function update_parameter_in_quadratic_constraints_pv!(model::Optimizer) end end end - old_ci = model.quadratic_added_cache[ci] + old_ci = model.moi_quadratic_to_poi_affine_map[ci] changes = Vector{MOI.ScalarCoefficientChange}( undef, length(new_coeff_per_variable), @@ -400,23 +372,9 @@ end function update_parameter_in_vector_affine_constraints!( optimizer::OT, - parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, - updated_parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, - vector_constraint_cache_inner::MOI.Utilities.DoubleDicts.DoubleDictInner{ - F, - S, - V, - }, + parameters::ParamTo{T}, + updated_parameters::ParamTo{T}, + vector_constraint_cache_inner::DoubleDictInner{F,S,V}, ) where {OT,T,F,S,V} for (ci, param_array) in vector_constraint_cache_inner update_parameter_in_vector_affine_constraints!( @@ -435,18 +393,8 @@ function update_parameter_in_vector_affine_constraints!( optimizer::OT, ci::CI, param_array::Vector{MOI.VectorAffineTerm{T}}, - parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, - updated_parameters::MOI.Utilities.CleverDicts.CleverDict{ - ParameterIndex, - T, - typeof(MOI.Utilities.CleverDicts.key_to_index), - typeof(MOI.Utilities.CleverDicts.index_to_key), - }, + parameters::ParamTo{T}, + updated_parameters::ParamTo{T}, ) where {OT,T,CI} cf = MOI.get(optimizer, MOI.ConstraintFunction(), ci) diff --git a/src/utils.jl b/src/utils.jl index a174bbab..086374d9 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -26,7 +26,7 @@ function is_variable_in_model(model::Optimizer, v::MOI.VariableIndex) end function has_quadratic_constraint_caches(model::Optimizer) - return !isempty(model.quadratic_added_cache) + return !isempty(model.moi_quadratic_to_poi_affine_map) end function function_has_parameters( @@ -125,6 +125,280 @@ function count_scalar_affine_terms_types( return num_vars, num_params, num_vars_associated_to_params end +function is_variable(v::MOI.VariableIndex) + return v.index < PARAMETER_INDEX_THRESHOLD +end + +function count_scalar_affine_terms_types( + terms::Vector{MOI.ScalarAffineTerm{T}}, +) where {T} + num_vars = 0 + num_params = 0 + for term in terms + if is_variable(term.variable) + num_vars += 1 + else + num_params += 1 + end + end + return num_vars, num_params +end + +function split_affine_terms(terms::Vector{MOI.ScalarAffineTerm{T}}) where {T} + num_v, num_p = count_scalar_affine_terms_types(terms) + v = Vector{MOI.ScalarAffineTerm{T}}(undef, num_v) + p = Vector{MOI.ScalarAffineTerm{T}}(undef, num_p) + i_v = 1 + i_p = 1 + for term in terms + if is_variable(term.variable) + v[i_v] = term + i_v += 1 + else + p[i_p] = term + i_p += 1 + end + end + return v, p +end + +function ParametricAffineFunction( + f::MOI.ScalarAffineFunction{T} +) where {T} + v, p = split_affine_terms(f.terms) + return ParametricAffineFunction{T}( + p, + v, + f.constant, + zero(T), + ) +end + +function parametric_constant( + model::Optimizer, + f::ParametricAffineFunction{T}, +) where {T} + param_constant = f.c + for term in f.p + param_constant += + term.coefficient * model.parameters[p_idx(term.variable)] + end + return param_constant +end + +function delta_parametric_constant( + model::Optimizer, + f::ParametricAffineFunction{T}, +) where {T} + delta_constant = zero(T) + for term in f.p + p = p_idx(term.variable) + if !isnan(model.updated_parameters[p]) + delta_constant += term.coefficient * + (model.updated_parameters[p] - model.parameters[p]) + end + end + return delta_constant +end + +function count_scalar_quadratic_terms_types( + terms::Vector{MOI.ScalarQuadraticTerm{T}}, +) where {T} + num_vv = 0 + num_pp = 0 + num_pv = 0 + for term in terms + if is_variable(term.variable_1) + if is_variable(term.variable_2) + num_vv += 1 + else + num_pv += 1 + end + else + if is_variable(term.variable_2) + num_pv += 1 + else + num_pp += 1 + end + end + end + return num_vv, num_pp, num_pv +end + +function split_quadratic_terms( + terms::Vector{MOI.ScalarQuadraticTerm{T}}, +) where {T} + num_vv, num_pp, num_pv = count_scalar_quadratic_terms_types(terms) + pp = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_pp) # parameter x parameter + pv = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_pv) # parameter (as a variable) x variable + vv = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_vv) # variable x variable + i_vv = 1 + i_pp = 1 + i_pv = 1 + for term in terms + if is_variable(term.variable_1) + if is_variable(term.variable_2) + vv[i_vv] = term + i_vv += 1 + else + pv[i_pv] = MOI.ScalarQuadraticTerm( + term.coefficient, + term.variable_2, + term.variable_1, + ) + i_pv += 1 + end + else + if is_variable(term.variable_2) + pv[i_pv] = term + i_pv += 1 + else + pp[i_pp] = term + i_pp += 1 + end + end + end + return pv, pp, vv +end + +function ParametricQuadraticFunction( + f::MOI.ScalarQuadraticFunction{T} +) where {T} + v, p = split_affine_terms(f.affine_terms) + pv, pp, vv = split_affine_terms(f.quadratic_terms) + + # find variables related to parameters + # so that we only cache the important part of the v (affine part) + v_in_pv = Set{MOI.VariableIndex}() + sizehint!(v_in_pv, length(pv)) + for term in pv + push!(v_in_pv, term.variable_2) + end + affine_data = Dict{MOI.VariableIndex,T}() + sizehint!(affine_data, length(v_in_pv)) + for term in v + if term.variable in v_in_pv + base = get(affine_data, term.variable, zero(T)) + affine_data[term.variable] = term.coefficient + base + end + end + + return ParametricQuadraticFunction{T}( + affine_data, + pv, + pp, + vv, + p, + v, + f.constant, + Dict{MOI.VariableIndex,T}(), + zero(T), + ) +end + +function parametric_constant( + model::Optimizer, + f::ParametricQuadraticFunction{T}, +) where {T} + param_constant = f.c + for term in f.p + param_constant += + term.coefficient * model.parameters[p_idx(term.variable)] + end + for term in f.pp + param_constant += + term.coefficient * model.parameters[p_idx(term.variable_1)] * + model.parameters[p_idx(term.variable_2)] + end + return param_constant +end + +function delta_parametric_constant( + model::Optimizer, + f::ParametricQuadraticFunction{T}, +) where {T} + delta_constant = zero(T) + for term in f.p + p = p_idx(term.variable) + if !isnan(model.updated_parameters[p]) + delta_constant += + term.coefficient * ( + model.updated_parameters[p] - model.parameters[p] + ) + end + end + for term in f.pp + p1 = p_idx(term.variable_1) + delta_1 = if !isnan(model.updated_parameters[p1]) + model.updated_parameters[p1] - model.parameters[p1] + else + 0.0 + end + p2 = p_idx(term.variable_2) + delta_2 = if !isnan(model.updated_parameters[p2]) + model.updated_parameters[p2] - model.parameters[p2] + else + 0.0 + end + delta_constant += term.coefficient * delta_1 * delta_2 + end + return delta_constant +end + +function parametric_affine_terms( + model::Optimizer, + f::ParametricQuadraticFunction{T}, +) where {T} + param_terms_dict = Dict{MOI.VariableIndex,T}() + sizehint!(param_terms_dict, length(f.pv)) + # remember a variable may appear more than once in pv + for term in f.pv + base = get(param_terms_dict, term.variable_2, zero(T)) + param_terms_dict[term.variable_2] = base + term.coefficient * + model.parameters[p_idx(term.variable_1)] + end + # by definition affin data only contains variables that appear in pv + for (var, coef) in f.affine_data + param_terms_dict[var] += coef + end + return param_terms_dict +end + +function delta_parametric_affine_terms( + model::Optimizer, + f::ParametricQuadraticFunction{T}, +) where {T} + delta_terms_dict = Dict{MOI.VariableIndex,T}() + sizehint!(delta_terms_dict, length(f.pv)) + # remember a variable may appear more than once in pv + for term in f.pv + p = p_idx(term.variable_1) + if !isnan(model.updated_parameters[p]) + base = get(delta_terms_dict, term.variable_2, zero(T)) + delta_terms_dict[term.variable_2] = base + term.coefficient * + (model.updated_parameters[p2] - model.parameters[p]) + end + end + return delta_terms_dict +end + +function move_set_constant_to_function( + f::MOI.AbstractScalarFunction, + s::Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}}, +) where {T} + c = constant(s) + new_f = copy(f) + new_f.constant -= c + return new_f +end + +function move_set_constant_to_function( + f::MOI.AbstractScalarFunction, + s::MOI.AbstractScalarSet, +) + return f +end + function separate_possible_terms_and_calculate_parameter_constant( model::Optimizer, terms::Vector{MOI.ScalarAffineTerm{T}}, @@ -137,6 +411,7 @@ function separate_possible_terms_and_calculate_parameter_constant( i_params = 1 for term in terms + # TODO: do we really need this checks? if is_variable_in_model(model, term.variable) vars[i_vars] = term i_vars += 1 @@ -201,44 +476,44 @@ function count_scalar_quadratic_terms_types( model::Optimizer, terms::Vector{MOI.ScalarQuadraticTerm{T}}, ) where {T} - num_quad_vars = 0 - num_quad_params = 0 - num_quad_aff_vars = 0 + num_vv = 0 + num_pp = 0 + num_pv = 0 for term in terms if is_variable_in_model(model, term.variable_1) && is_variable_in_model(model, term.variable_2) - num_quad_vars += 1 + num_vv += 1 elseif is_variable_in_model(model, term.variable_1) && is_parameter_in_model(model, term.variable_2) - num_quad_aff_vars += 1 + num_pv += 1 elseif is_parameter_in_model(model, term.variable_1) && is_variable_in_model(model, term.variable_2) - num_quad_aff_vars += 1 + num_pv += 1 else - num_quad_params += 1 + num_pp += 1 end end - return num_quad_vars, num_quad_params, num_quad_aff_vars + return num_vv, num_pp, num_pv end function separate_possible_terms_and_calculate_parameter_constant( model::Optimizer, terms::Vector{MOI.ScalarQuadraticTerm{T}}, ) where {T} - num_quad_vars, num_quad_params, num_quad_aff_vars = + num_vv, num_pp, num_pv = count_scalar_quadratic_terms_types(model, terms) - quad_params = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_quad_params) # parameter x parameter - quad_aff_vars = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_quad_aff_vars) # parameter (as a variable) x variable - quad_vars = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_quad_vars) # variable x variable - aff_terms = Vector{MOI.ScalarAffineTerm{T}}(undef, num_quad_aff_vars) # parameter (as a number) x variable + pp = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_pp) # parameter x parameter + pv = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_pv) # parameter (as a variable) x variable + vv = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_vv) # variable x variable + aff_terms = Vector{MOI.ScalarAffineTerm{T}}(undef, num_pv) # parameter (as a number) x variable variables_associated_to_parameters = - Vector{MOI.VariableIndex}(undef, num_quad_aff_vars) + Vector{MOI.VariableIndex}(undef, num_pv) quad_param_constant = zero(T) - i_quad_vars = 1 - i_quad_params = 1 - i_quad_aff_vars = 1 + i_vv = 1 + i_pp = 1 + i_pv = 1 # When we have a parameter x variable or a variable x parameter the convention is to rewrite # the SQT with parameter as variable_index_1 and variable as variable_index_2 @@ -247,47 +522,48 @@ function separate_possible_terms_and_calculate_parameter_constant( is_variable_in_model(model, term.variable_1) && is_variable_in_model(model, term.variable_2) ) - quad_vars[i_quad_vars] = term # if there are only variables, it remains a quadratic term - i_quad_vars += 1 + vv[i_vv] = term # if there are only variables, it remains a quadratic term + i_vv += 1 elseif ( is_parameter_in_model(model, term.variable_1) && is_variable_in_model(model, term.variable_2) ) - quad_aff_vars[i_quad_aff_vars] = term - variables_associated_to_parameters[i_quad_aff_vars] = + pv[i_pv] = term + variables_associated_to_parameters[i_pv] = term.variable_2 - aff_terms[i_quad_aff_vars] = MOI.ScalarAffineTerm( + aff_terms[i_pv] = MOI.ScalarAffineTerm( term.coefficient * model.parameters[p_idx(term.variable_1)], term.variable_2, ) - model.evaluate_duals && + if model.evaluate_duals push!(model.multiplicative_parameters, term.variable_1.value) - i_quad_aff_vars += 1 + end + i_pv += 1 elseif ( is_variable_in_model(model, term.variable_1) && is_parameter_in_model(model, term.variable_2) ) # Check convention defined above. We use the convention to know decide who is a variable and who is # a parameter withou having to recheck which is which. - quad_aff_vars[i_quad_aff_vars] = MOI.ScalarQuadraticTerm( + pv[i_pv] = MOI.ScalarQuadraticTerm( term.coefficient, term.variable_2, term.variable_1, ) - variables_associated_to_parameters[i_quad_aff_vars] = + variables_associated_to_parameters[i_pv] = term.variable_1 - aff_terms[i_quad_aff_vars] = MOI.ScalarAffineTerm( + aff_terms[i_pv] = MOI.ScalarAffineTerm( term.coefficient * model.parameters[p_idx(term.variable_2)], term.variable_1, ) model.evaluate_duals && push!(model.multiplicative_parameters, term.variable_2.value) - i_quad_aff_vars += 1 + i_pv += 1 elseif ( is_parameter_in_model(model, term.variable_1) && is_parameter_in_model(model, term.variable_2) ) - quad_params[i_quad_params] = term + pp[i_pp] = term model.evaluate_duals && push!(model.multiplicative_parameters, term.variable_1.value) model.evaluate_duals && @@ -296,7 +572,7 @@ function separate_possible_terms_and_calculate_parameter_constant( term.coefficient * model.parameters[p_idx(term.variable_1)] * model.parameters[p_idx(term.variable_2)] - i_quad_params += 1 + i_pp += 1 else throw( ErrorException( @@ -305,9 +581,9 @@ function separate_possible_terms_and_calculate_parameter_constant( ) end end - return quad_vars, - quad_aff_vars, - quad_params, + return vv, + pv, + pp, aff_terms, variables_associated_to_parameters, quad_param_constant @@ -316,19 +592,19 @@ end function fill_quadratic_constraint_caches!( model::Optimizer, new_ci::MOI.ConstraintIndex, - quad_aff_vars::Vector{MOI.ScalarQuadraticTerm{T}}, - quad_params::Vector{MOI.ScalarQuadraticTerm{T}}, + pv::Vector{MOI.ScalarQuadraticTerm{T}}, + pp::Vector{MOI.ScalarQuadraticTerm{T}}, aff_params::Vector{MOI.ScalarAffineTerm{T}}, terms_with_variables_associated_to_parameters::Vector{ MOI.ScalarAffineTerm{T}, }, ci::MOI.ConstraintIndex, ) where {T,S} - if !isempty(quad_aff_vars) - model.quadratic_constraint_cache_pv[new_ci] = quad_aff_vars + if !isempty(pv) + model.quadratic_constraint_cache_pv[new_ci] = pv end - if !isempty(quad_params) - model.quadratic_constraint_cache_pp[new_ci] = quad_params + if !isempty(pp) + model.quadratic_constraint_cache_pp[new_ci] = pp model.quadratic_constraint_cache_pp_set[new_ci] = MOI.get(model.optimizer, MOI.ConstraintSet(), ci) end @@ -338,7 +614,7 @@ function fill_quadratic_constraint_caches!( MOI.get(model.optimizer, MOI.ConstraintSet(), ci) end if !isempty(terms_with_variables_associated_to_parameters) - model.quadratic_constraint_variables_associated_to_parameters_cache[new_ci] = + model.variables_multiplied_by_parameters[new_ci] = terms_with_variables_associated_to_parameters end return nothing @@ -348,7 +624,7 @@ function quadratic_constraint_cache_map_check( model::Optimizer, idx::MOI.ConstraintIndex{F,S}, ) where {F,S} - cached_constraints = values(model.quadratic_added_cache) + cached_constraints = values(model.moi_quadratic_to_poi_affine_map) # Using this becuase some custom brodcast method throws errors if # inner_idex .∈ cached_constraints is used return idx ∈ cached_constraints From 8d4e0a6c363301e81906ed212a727e53eaf737cb Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 12 Apr 2023 00:58:30 -0300 Subject: [PATCH 02/13] large refactor --- src/ParametricOptInterface.jl | 957 +++++++++++++++------------------- src/duals.jl | 164 ++---- src/update_parameters.jl | 463 ++++++---------- src/utils.jl | 404 +++++--------- test/jump_tests.jl | 231 ++++---- test/moi_tests.jl | 2 +- test/runtests.jl | 4 + 7 files changed, 880 insertions(+), 1345 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 1749fafe..edbb6ac2 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -13,6 +13,8 @@ const PARAMETER_INDEX_THRESHOLD = Int64(4_611_686_018_427_387_904) # div(typemax @enum ConstraintsInterpretationCode ONLY_CONSTRAINTS ONLY_BOUNDS BOUNDS_AND_CONSTRAINTS +const SIMPLE_SCALAR_SETS{T} = Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}} + """ Parameter(val::Float64) @@ -43,9 +45,15 @@ end function MOI.Utilities.CleverDicts.key_to_index(key::ParameterIndex) return key.index end -function p_idx(vi::MOI.VariableIndex) +function p_idx(vi::MOI.VariableIndex)::ParameterIndex return ParameterIndex(vi.value - PARAMETER_INDEX_THRESHOLD) end +function p_val(vi::MOI.VariableIndex)::Int64 + return vi.value - PARAMETER_INDEX_THRESHOLD +end +function p_val(ci::MOI.ConstraintIndex)::Int64 + return ci.value - PARAMETER_INDEX_THRESHOLD +end const ParamTo{T} = MOI.Utilities.CleverDicts.CleverDict{ ParameterIndex, @@ -66,6 +74,7 @@ const DoubleDictInner{F,S,T} = MOI.Utilities.DoubleDicts.DoubleDictInner{F,S,T} mutable struct ParametricQuadraticFunction{T} # helper to efficiently update affine terms affine_data::Dict{MOI.VariableIndex,T} + affine_data_np::Dict{MOI.VariableIndex,T} # constant * parameter * variable (in this order) pv::Vector{MOI.ScalarQuadraticTerm{T}} # constant * parameter * parameter @@ -76,28 +85,16 @@ mutable struct ParametricQuadraticFunction{T} p::Vector{MOI.ScalarAffineTerm{T}} # constant * variable v::Vector{MOI.ScalarAffineTerm{T}} - # constant (includes the set constant TODO!!!!) + # constant (does not include the set constant) c::T - # + # to avoid unnecessary lookups in updates + set_constant::T # cache data that is inside the solver to avoid slow getters current_terms_with_p::Dict{MOI.VariableIndex,T} current_constant::T # computed on runtime # updated_terms_with_p::Dict{MOI.VariableIndex,T} # updated_constant::T - #= - function ParametricQuadraticFunction{T}() - return new( - zero(T), - MOI.ScalarAffineTerm{T}[], - MOI.ScalarAffineTerm{T}[], - Dict{MOI.VariableIndex,T}(), - MOI.ScalarQuadraticTerm{T}[], - MOI.ScalarQuadraticTerm{T}[], - MOI.ScalarQuadraticTerm{T}[], - ) - end - =# end mutable struct ParametricAffineFunction{T} @@ -107,6 +104,8 @@ mutable struct ParametricAffineFunction{T} v::Vector{MOI.ScalarAffineTerm{T}} # constant c::T + # to avoid unnecessary lookups in updates + set_constant::T # cache to avoid slow getters current_constant::T end @@ -147,91 +146,50 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer variables::VariableMap last_variable_index_added::Int64 last_parameter_index_added::Int64 - # Store the constraint function and set passed to POI by MOI - # REM - original_constraint_data::Dict{ - MOI.ConstraintIndex, - Tuple{MOI.AbstractFunction,MOI.AbstractSet}, - } - # Store the map for SAFs that might be transformed into VI - affine_added_cache::DoubleDict{MOI.ConstraintIndex} - last_affine_added::Int64 - # Store reference to parameters of affine constraints with parameters: v + p - # REM - affine_constraint_cache::DoubleDict{Vector{MOI.ScalarAffineTerm{Float64}}} - # Store constraint set + # affine constraint data + last_affine_added::Int64 + # Store the map for SAFs (some might be transformed into VI) + affine_outer_to_inner::DoubleDict{MOI.ConstraintIndex} + # Clever cache of data (inner key) + affine_constraint_cache::DoubleDict{ParametricAffineFunction{T}} + # Store original constraint set (inner key) affine_constraint_cache_set::DoubleDict{MOI.AbstractScalarSet} - # replaces above - affine_constraint_cache2::DoubleDict{ParametricAffineFunction{T}} - # replaces below - quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}} - objective_cache::Union{Nothing,ParametricQuadraticFunction{T}} - - # Store reference quadratic constraints with parameter * variable constraints: p * v - # REM - quadratic_constraint_cache_pv::DoubleDict{ - Vector{MOI.ScalarQuadraticTerm{Float64}}, - } - # Store reference quadratic constraints with parameter * parameter constraints: p * v - # REM - quadratic_constraint_cache_pp::DoubleDict{ - Vector{MOI.ScalarQuadraticTerm{Float64}}, - } - # Store constraint set - # REM - quadratic_constraint_cache_pp_set::DoubleDict{MOI.AbstractScalarSet} - # Store reference to constraints with quad_variable_term + affine_with_parameters: v * v + p - # REM - quadratic_constraint_cache_pc::DoubleDict{ - Vector{MOI.ScalarAffineTerm{Float64}}, - } - # Store constraint set - quadratic_constraint_cache_pc_set::DoubleDict{MOI.AbstractScalarSet} - # Store the reference to variables in the scalar affine part that are - # multiplied by parameters in the scalar quadratic terms. - # i.e. - # If we have a constraint function with both scalar quadratic terms and - # scalar affine terms such as p_1 * v_1 + 2.0 * v_1 - # When we need to update the constraint coefficient after updating the parameter - # we must do (new_p_1 + 2.0) * v_1 - # This cache is storing the 2.0 * v_1 part. - # REM - variables_multiplied_by_parameters::DoubleDict{ - Vector{MOI.ScalarAffineTerm{T}}, - } - # Store the map for SQFs that might be transformed into SAF + # quadratic constraitn data + last_quad_add_added::Int64 + # Store the map for SQFs (some might be transformed into SAF) # for instance p*p + var -> ScalarAffine(var) - moi_quadratic_to_poi_affine_map::Dict{ - MOI.ConstraintIndex, - MOI.ConstraintIndex, + 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) + quadratic_constraint_cache_set::DoubleDict{MOI.AbstractScalarSet} + + # objective function data + # Clever cache of data (at most one can be !== nothing) + affine_objective_cache::Union{Nothing,ParametricAffineFunction{T}} + quadratic_objective_cache::Union{Nothing,ParametricQuadraticFunction{T}} + original_objective_cache::Union{ + Nothing, + MOI.VariableIndex, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, } # Store parametric expressions for product of variables quadratic_objective_cache_product::Dict{ Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, } - last_quad_add_added::Int64 - vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{Float64}}} - # Ditto from the constraint caches but for the objective function - # REM - original_objective_function::MOI.AbstractFunction - # REM - affine_objective_cache::Vector{MOI.ScalarAffineTerm{T}} - # REM - quadratic_objective_cache_pv::Vector{MOI.ScalarQuadraticTerm{T}} - # REM - quadratic_objective_cache_pp::Vector{MOI.ScalarQuadraticTerm{T}} - # REM - quadratic_objective_cache_pc::Vector{MOI.ScalarAffineTerm{T}} - # REM - quadratic_objective_variables_associated_to_parameters_cache::Vector{ - MOI.ScalarAffineTerm{T}, - } + + # vector affine function data + vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{T}}} + # multiplicative_parameters::Set{Int64} - dual_value_of_parameters::Vector{Float64} + dual_value_of_parameters::Vector{T} + + # params evaluate_duals::Bool number_of_parameters_in_model::Int64 constraints_interpretation::ConstraintsInterpretationCode @@ -242,7 +200,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer save_original_objective_and_constraints::Bool = true, ) where {OT} T = Float64 - return new{Float64,OT}( + return new{T,OT}( optimizer, MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,T}( MOI.Utilities.CleverDicts.key_to_index, @@ -262,36 +220,27 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer ), 0, PARAMETER_INDEX_THRESHOLD, - Dict{ - MOI.ConstraintIndex, - Tuple{MOI.AbstractFunction,MOI.AbstractSet}, - }(), - DoubleDict{MOI.ConstraintIndex}(), + # affine constraint 0, - DoubleDict{Vector{MOI.ScalarAffineTerm{T}}}(), - DoubleDict{MOI.AbstractScalarSet}(), + DoubleDict{MOI.ConstraintIndex}(), DoubleDict{ParametricAffineFunction{T}}(), - DoubleDict{ParametricQuadraticFunction{T}}(), - nothing, - DoubleDict{Vector{MOI.ScalarQuadraticTerm{T}}}(), - DoubleDict{Vector{MOI.ScalarQuadraticTerm{T}}}(), DoubleDict{MOI.AbstractScalarSet}(), - DoubleDict{Vector{MOI.ScalarAffineTerm{T}}}(), + # quadratic constraint + 0, + DoubleDict{MOI.ConstraintIndex}(), + DoubleDict{ParametricQuadraticFunction{T}}(), DoubleDict{MOI.AbstractScalarSet}(), - DoubleDict{Vector{MOI.ScalarAffineTerm{T}}}(), - Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}(), + # objective + nothing, + nothing, + nothing, Dict{ Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, }(), - 0, + # vec affine DoubleDict{Vector{MOI.VectorAffineTerm{T}}}(), - MOI.VariableIndex(-1), - Vector{MOI.ScalarAffineTerm{T}}(), - Vector{MOI.ScalarQuadraticTerm{T}}(), - Vector{MOI.ScalarQuadraticTerm{T}}(), - Vector{MOI.ScalarAffineTerm{T}}(), - Vector{MOI.ScalarAffineTerm{T}}(), + # other Set{Int64}(), Vector{T}(), evaluate_duals, @@ -308,40 +257,66 @@ include("update_parameters.jl") function MOI.is_empty(model::Optimizer) return MOI.is_empty(model.optimizer) && - isempty(model.parameters) && - isempty(model.parameters_name) && - isempty(model.variables) && - isempty(model.updated_parameters) && - isempty(model.variables) && - model.last_variable_index_added == 0 && - model.last_parameter_index_added == PARAMETER_INDEX_THRESHOLD && - isempty(model.original_constraint_data) && - isempty(model.affine_added_cache) && - model.last_affine_added == 0 && - isempty(model.affine_constraint_cache) && - isempty(model.affine_constraint_cache_set) && - isempty(model.quadratic_constraint_cache) && - isempty(model.quadratic_constraint_cache_pv) && - isempty(model.quadratic_constraint_cache_pp) && - isempty(model.quadratic_constraint_cache_pp_set) && - isempty(model.quadratic_constraint_cache_pc) && - isempty(model.quadratic_constraint_cache_pc_set) && - isempty( - model.variables_multiplied_by_parameters, - ) && - isempty(model.moi_quadratic_to_poi_affine_map) && - isempty(model.quadratic_objective_cache_product) && - model.last_quad_add_added == 0 && - model.original_objective_function == MOI.VariableIndex(-1) && - isempty(model.affine_objective_cache) && - isempty(model.quadratic_objective_cache_pv) && - isempty(model.quadratic_objective_cache_pp) && - isempty(model.quadratic_objective_cache_pc) && - isempty( - model.quadratic_objective_variables_associated_to_parameters_cache, - ) && - isempty(model.dual_value_of_parameters) && - model.number_of_parameters_in_model == 0 + isempty(model.parameters) && + isempty(model.parameters_name) && + isempty(model.updated_parameters) && + isempty(model.variables) && + model.last_variable_index_added == 0 && + model.last_parameter_index_added == PARAMETER_INDEX_THRESHOLD && + # affine ctr + model.last_affine_added == 0 && + isempty(model.affine_outer_to_inner) && + isempty(model.affine_constraint_cache) && + isempty(model.affine_constraint_cache_set) && + # quad ctr + model.last_quad_add_added == 0 && + isempty(model.quadratic_outer_to_inner) && + isempty(model.quadratic_constraint_cache) && + isempty(model.quadratic_constraint_cache_set) && + # obj + model.affine_objective_cache === nothing && + model.quadratic_objective_cache === nothing && + model.original_objective_cache === nothing && + isempty(model.quadratic_objective_cache_product) && + # + isempty(model.vector_constraint_cache) && + # + isempty(model.multiplicative_parameters) && + isempty(model.dual_value_of_parameters) && + model.number_of_parameters_in_model == 0 +end + +function MOI.empty!(model::Optimizer{T}) where {T} + MOI.empty!(model.optimizer) + empty!(model.parameters) + empty!(model.parameters_name) + empty!(model.updated_parameters) + empty!(model.variables) + model.last_variable_index_added = 0 + model.last_parameter_index_added = PARAMETER_INDEX_THRESHOLD + # affine ctr + model.last_affine_added = 0 + empty!(model.affine_outer_to_inner) + empty!(model.affine_constraint_cache) + empty!(model.affine_constraint_cache_set) + # quad ctr + model.last_quad_add_added = 0 + empty!(model.quadratic_outer_to_inner) + empty!(model.quadratic_constraint_cache) + empty!(model.quadratic_constraint_cache_set) + # obj + model.affine_objective_cache = nothing + model.quadratic_objective_cache = nothing + model.original_objective_cache = nothing + empty!(model.quadratic_objective_cache_product) + # + empty!(model.vector_constraint_cache) + # + empty!(model.multiplicative_parameters) + empty!(model.dual_value_of_parameters) + # + model.number_of_parameters_in_model = 0 + return end function MOI.supports_constraint( @@ -392,14 +367,6 @@ function MOI.supports( return MOI.supports(model.optimizer, attr) end -function MOI.supports(model::Optimizer, ::MOI.NLPBlock) - return MOI.supports(model.optimizer, MOI.NLPBlock()) -end - -function MOI.set(model::Optimizer, ::MOI.NLPBlock, nlp_data::MOI.NLPBlockData) - return MOI.set(model.optimizer, MOI.NLPBlock(), nlp_data) -end - function MOI.supports( model::Optimizer, ::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}, @@ -410,6 +377,14 @@ function MOI.supports( ) end +function MOI.supports(model::Optimizer, ::MOI.NLPBlock) + return MOI.supports(model.optimizer, MOI.NLPBlock()) +end + +function MOI.set(model::Optimizer, ::MOI.NLPBlock, nlp_data::MOI.NLPBlockData) + return MOI.set(model.optimizer, MOI.NLPBlock(), nlp_data) +end + function MOI.supports_incremental_interface(model::Optimizer) return MOI.supports_incremental_interface(model.optimizer) end @@ -440,12 +415,15 @@ end function _all_variables(model::Optimizer) return collect(keys(model.variables)) end + function _all_parameters(model::Optimizer) return collect(keys(model.parameters)) end + function MOI.get(model::Optimizer, ::MOI.ListOfVariableAttributesSet) return MOI.get(model.optimizer, MOI.ListOfVariableAttributesSet()) end + function MOI.get( model::Optimizer, ::MOI.ListOfConstraintAttributesSet{F,S}, @@ -457,6 +435,7 @@ function MOI.get( end return MOI.get(model.optimizer, MOI.ListOfConstraintAttributesSet{F,S}()) end + function MOI.set( model::Optimizer, ::MOI.ConstraintFunction, @@ -466,6 +445,7 @@ function MOI.set( MOI.set(model.optimizer, MOI.ConstraintFunction(), c, f) return end + function MOI.set( model::Optimizer, ::MOI.ConstraintSet, @@ -475,63 +455,33 @@ function MOI.set( MOI.set(model.optimizer, MOI.ConstraintSet(), c, s) return end + function MOI.modify( model::Optimizer, c::MOI.ConstraintIndex{F,S}, - chg::MOI.ScalarCoefficientChange{Float64}, -) where {F,S} - MOI.modify(model.optimizer, c, chg) - return -end -function MOI.modify( - model::Optimizer, - c::MOI.ObjectiveFunction{F}, - chg::MOI.ScalarCoefficientChange{Float64}, -) where {F<:MathOptInterface.AbstractScalarFunction} + chg::MOI.ScalarCoefficientChange{T}, +) where {F,S,T} + if haskey(model.quadratic_constraint_cache, c) || + haskey(model.affine_constraint_cache, c) + error("Parametric constraint cannot be modified") + end MOI.modify(model.optimizer, c, chg) + MOI.Utilities.modify_function(model.original_objective_cache) return end + function MOI.modify( model::Optimizer, c::MOI.ObjectiveFunction{F}, - chg::MOI.ScalarConstantChange{Float64}, -) where {F<:MathOptInterface.AbstractScalarFunction} + chg::MOI.ScalarConstantChange{T}, +) where {F<:MathOptInterface.AbstractScalarFunction,T} + if model.quadratic_objective_cache !== nothing || + model.affine_objective_cache !== nothing || + !isempty(model.quadratic_objective_cache_product) + error("Parametric objective cannot be modified") + end MOI.modify(model.optimizer, c, chg) - return -end - -function MOI.empty!(model::Optimizer{T}) where {T} - MOI.empty!(model.optimizer) - empty!(model.parameters) - empty!(model.parameters_name) - empty!(model.updated_parameters) - empty!(model.variables) - model.last_variable_index_added = 0 - model.last_parameter_index_added = PARAMETER_INDEX_THRESHOLD - empty!(model.original_constraint_data) - empty!(model.affine_added_cache) - model.last_affine_added = 0 - empty!(model.affine_constraint_cache) - empty!(model.affine_constraint_cache_set) - empty!(model.quadratic_constraint_cache) - empty!(model.quadratic_constraint_cache_pv) - empty!(model.quadratic_constraint_cache_pp) - empty!(model.quadratic_constraint_cache_pp_set) - empty!(model.quadratic_constraint_cache_pc) - empty!(model.quadratic_constraint_cache_pc_set) - empty!(model.variables_multiplied_by_parameters) - empty!(model.moi_quadratic_to_poi_affine_map) - empty!(model.quadratic_objective_cache_product) - model.last_quad_add_added = 0 - model.original_objective_function = MOI.VariableIndex(-1) - empty!(model.vector_constraint_cache) - empty!(model.affine_objective_cache) - empty!(model.quadratic_objective_cache_pv) - empty!(model.quadratic_objective_cache_pp) - empty!(model.quadratic_objective_cache_pc) - empty!(model.quadratic_objective_variables_associated_to_parameters_cache) - empty!(model.dual_value_of_parameters) - model.number_of_parameters_in_model = 0 + MOI.Utilities.modify_function(model.original_objective_cache) return end @@ -579,8 +529,8 @@ function MOI.set( c::MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}, name::String, ) where {T,S<:MOI.AbstractSet} - if haskey(model.moi_quadratic_to_poi_affine_map, c) - MOI.set(model.optimizer, attr, model.moi_quadratic_to_poi_affine_map[c], name) + if haskey(model.quadratic_outer_to_inner, c) + MOI.set(model.optimizer, attr, model.quadratic_outer_to_inner[c], name) else MOI.set(model.optimizer, attr, c, name) end @@ -593,8 +543,8 @@ function MOI.set( c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}, name::String, ) where {T,S<:MOI.AbstractSet} - if haskey(model.affine_added_cache, c) - MOI.set(model.optimizer, attr, model.affine_added_cache[c], name) + 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 @@ -616,20 +566,8 @@ function MOI.get( attr::MOI.ConstraintName, c::MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}, ) where {T,S<:MOI.AbstractSet} - if haskey(model.moi_quadratic_to_poi_affine_map, c) - return MOI.get(model.optimizer, attr, model.moi_quadratic_to_poi_affine_map[c]) - else - return MOI.get(model.optimizer, attr, c) - end -end - -function MOI.get( - model::Optimizer, - attr::MOI.ConstraintName, - c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}, -) where {T,S<:MOI.AbstractSet} - if haskey(model.affine_added_cache, c) - return MOI.get(model.optimizer, attr, model.affine_added_cache[c]) + if haskey(model.quadratic_outer_to_inner, c) + return MOI.get(model.optimizer, attr, model.quadratic_outer_to_inner[c]) else return MOI.get(model.optimizer, attr, c) end @@ -648,14 +586,18 @@ function MOI.get( attr::MOI.ConstraintName, c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}, ) where {T,S} - moi_ci = get(model.affine_added_cache, c, c) - # This SAF constraint was transformed into variable bound - if typeof(moi_ci) === MOI.ConstraintIndex{MOI.VariableIndex,S} - v = MOI.get(model.optimizer, MOI.ConstraintFunction(), moi_ci) - variable_name = MOI.get(model.optimizer, MOI.VariableName(), v) - return "ParametricBound_$(S)_$(variable_name)" + + if haskey(model.affine_outer_to_inner, c) + inner_ci = model.affine_outer_to_inner[c] + # This SAF constraint was transformed into variable bound + if typeof(inner_ci) === MOI.ConstraintIndex{MOI.VariableIndex,S} + v = MOI.get(model.optimizer, MOI.ConstraintFunction(), inner_ci) + variable_name = MOI.get(model.optimizer, MOI.VariableName(), v) + return "ParametricBound_$(S)_$(variable_name)" + end + return MOI.get(model.optimizer, attr, inner_ci) else - return MOI.get(model.optimizer, attr, moi_ci) + return MOI.get(model.optimizer, attr, c) end end @@ -676,9 +618,14 @@ function MOI.get( attr::MOI.ConstraintFunction, ci::MOI.ConstraintIndex{F,S}, ) where {F,S} - if haskey(model.original_constraint_data, ci) - return model.original_constraint_data[ci][1] + if haskey(model.quadratic_outer_to_inner, ci) + inner_ci = model.quadratic_outer_to_inner[ci] + return original_function(model.quadratic_constraint_cache[inner_ci]) + elseif haskey(model.affine_outer_to_inner, ci) + inner_ci = model.affine_outer_to_inner[ci] + return original_function(model.affine_constraint_cache[inner_ci]) else + MOI.throw_if_not_valid(model, ci) return MOI.get(model.optimizer, attr, ci) end end @@ -744,8 +691,12 @@ function MOI.get( attr::MOI.ConstraintSet, ci::MOI.ConstraintIndex{F,S}, ) where {F,S} - if haskey(model.original_constraint_data, ci) - return model.original_constraint_data[ci][2] + 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.affine_outer_to_inner, ci) + inner_ci = model.affine_outer_to_inner[ci] + return model.affine_constraint_cache_set[inner_ci] else MOI.throw_if_not_valid(model, ci) return MOI.get(model.optimizer, attr, ci) @@ -756,47 +707,71 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveSense) return MOI.get(model.optimizer, attr) end -function MOI.get(model::Optimizer, attr::MOI.ObjectiveFunctionType) - return typeof(model.original_objective_function) +function MOI.get(model::Optimizer{T}, attr::MOI.ObjectiveFunctionType) where {T} + if model.affine_objective_cache !== nothing + return MOI.ScalarAffineFunction{T} + elseif model.quadratic_objective_cache !== nothing + return MOI.ScalarQuadraticFunction{T} + end + return typeof(model.original_objective_cache) end function MOI.get( model::Optimizer, - attr::MOI.ObjectiveFunction{F}, -) where { - F<:Union{ - MOI.VariableIndex, - MOI.ScalarAffineFunction{T}, - MOI.ScalarQuadraticFunction{T}, - }, -} where {T} - if !function_has_parameters(model, model.original_objective_function) - return MOI.get(model.optimizer, attr) - else - if F === typeof(model.original_objective_function) - return model.original_objective_function - else - throw(InexactError) - end + attr::MOI.ObjectiveFunction{MOI.VariableIndex}, +) + if !(typeof(model.original_objective_cache) <: MOI.VariableIndex) + error("Objective function is of type $(typeof(model.original_objective_cache))") + end + return model.original_objective_cache +end + +function MOI.get( + model::Optimizer, + attr::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}, +) where {T} + if model.affine_objective_cache !== nothing + return original_function(model.affine_objective_cache) + end + if !(typeof(model.original_objective_cache) <: MOI.ScalarAffineFunction{T}) + error("Objective function is of type $(typeof(model.original_objective_cache))") + end + return model.original_objective_cache +end + +function MOI.get( + model::Optimizer, + attr::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}, +) where {T} + if model.quadratic_objective_cache !== nothing + return original_function(model.quadratic_objective_cache) end + if !( + typeof(model.original_objective_cache) <: + MOI.ScalarQuadraticFunction{T} + ) + error("Objective function is of type $(typeof(model.original_objective_cache))") + end + return model.original_objective_cache end function MOI.get(model::Optimizer, attr::MOI.ResultCount) return MOI.get(model.optimizer, attr) end +# TODO: cleanup # In the AbstractBridgeOptimizer, we collect all the possible constraint types and them filter with NumberOfConstraints. # If NumberOfConstraints is zero then we remove it from the list. -# Here, you can look over keys(moi_quadratic_to_poi_affine_map) and add the F-S types of all the keys in constraints. +# Here, you can look over keys(quadratic_outer_to_inner) and add the F-S types of all the keys in constraints. # To implement NumberOfConstraints, you call NumberOfConstraints to the inner optimizer. -# Then you remove the number of constraints of that that in values(moi_quadratic_to_poi_affine_map) +# Then you remove the number of constraints of that that in values(quadratic_outer_to_inner) function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) inner_ctrs = MOI.get(model.optimizer, MOI.ListOfConstraintTypesPresent()) if !has_quadratic_constraint_caches(model) return inner_ctrs end - cache_keys = collect(keys(model.moi_quadratic_to_poi_affine_map)) + cache_keys = collect(keys(model.quadratic_outer_to_inner)) constraints = Set{Tuple{DataType,DataType}}() for (F, S) in inner_ctrs @@ -807,7 +782,8 @@ function MOI.get(model::Optimizer, ::MOI.ListOfConstraintTypesPresent) for type in typeof.(cache_keys[cache_map_check]) push!(constraints, (type.parameters[1], type.parameters[2])) end - # If not all the constraints are chached then also push the original type + # If not all the constraints are cached then also push the original type + # since there was a function with no parameters of that type if !all(cache_map_check) push!(constraints, (F, S)) end @@ -847,23 +823,15 @@ function MOI.get( if MOI.supports_constraint(model.optimizer, F, S) inner_index = MOI.get(model.optimizer, MOI.ListOfConstraintIndices{F,S}()) - if !has_quadratic_constraint_caches(model) - return inner_index - end + end + if !has_quadratic_constraint_caches(model) + return inner_index end - quadratic_caches = [ - :quadratic_constraint_cache_pc, - :quadratic_constraint_cache_pp, - :quadratic_constraint_cache_pv, - # JD: Check if this applies here - # :variables_multiplied_by_parameters - ] - - for field in quadratic_caches - cache = getfield(model, field) - push!(inner_index, keys(cache)...) + for key in keys(model.quadratic_outer_to_inner) + push!(inner_index, key) end + return inner_index end @@ -903,7 +871,7 @@ function MOI.add_constraint( f::MOI.VariableIndex, set::MOI.AbstractScalarSet, ) - if is_parameter_in_model(model, f) + if !is_variable(f) error("Cannot constrain a parameter") elseif !is_variable_in_model(model, f) error("Variable not in the model") @@ -916,82 +884,70 @@ function add_constraint_with_parameters_on_function( f::MOI.ScalarAffineFunction{T}, set::S, ) where {T,S} - vars, params, param_constant = - separate_possible_terms_and_calculate_parameter_constant(model, f.terms) - model.last_affine_added += 1 + pf = ParametricAffineFunction(f) + cache_set_constant!(pf, set) if model.constraints_interpretation == ONLY_BOUNDS - if (length(vars) == 1) && isone(MOI.coefficient(vars[1])) - poi_ci = - add_vi_constraint(model, vars, params, param_constant, f, set) + if length(pf.v) == 1 && isone(MOI.coefficient(pf.v[])) + poi_ci = add_vi_constraint(model, pf, set) else error( "It was not possible to interpret this constraint as a variable bound.", ) end elseif model.constraints_interpretation == ONLY_CONSTRAINTS - poi_ci = add_saf_constraint(model, vars, params, param_constant, f, set) + poi_ci = add_saf_constraint(model, pf, set) elseif model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS - if (length(vars) == 1) && isone(MOI.coefficient(vars[1])) - poi_ci = - add_vi_constraint(model, vars, params, param_constant, f, set) + if length(pf.v) == 1 && isone(MOI.coefficient(pf.v[])) + poi_ci = add_vi_constraint(model, pf, set) else - poi_ci = - add_saf_constraint(model, vars, params, param_constant, f, set) + poi_ci = add_saf_constraint(model, pf, set) end end - if model.save_original_objective_and_constraints - model.original_constraint_data[poi_ci] = (f, set) - end return poi_ci end function add_saf_constraint( model::Optimizer, - vars::Vector{MOI.ScalarAffineTerm{T}}, - params::Vector{MOI.ScalarAffineTerm{T}}, - param_constant::T, - f::MOI.ScalarAffineFunction{T}, + pf::ParametricAffineFunction{T}, set::S, ) where {T,S} - moi_ci = MOI.Utilities.normalize_and_add_constraint( + update_cache!(pf, model) + inner_ci = MOI.Utilities.normalize_and_add_constraint( model.optimizer, - MOI.ScalarAffineFunction(vars, f.constant + param_constant), - set, + MOI.ScalarAffineFunction{T}(pf.v, 0.0), + set_with_new_constant(set, pf.current_constant), ) - poi_ci = create_new_poi_ci_and_save_affine_caches(model, params, moi_ci) - return poi_ci + model.last_affine_added += 1 + outer_ci = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}( + model.last_affine_added, + ) + model.affine_outer_to_inner[outer_ci] = inner_ci + # model.outer_to_inner_map[outer_ci] = inner_ci + model.affine_constraint_cache[inner_ci] = pf + model.affine_constraint_cache_set[inner_ci] = set + return outer_ci end function add_vi_constraint( model::Optimizer, - vars::Vector{MOI.ScalarAffineTerm{T}}, - params::Vector{MOI.ScalarAffineTerm{T}}, - param_constant::T, - f::MOI.ScalarAffineFunction{T}, + pf::ParametricAffineFunction{T}, set::S, ) where {T,S} - moi_ci = MOI.Utilities.normalize_and_add_constraint( + update_cache!(pf, model) + inner_ci = MOI.Utilities.normalize_and_add_constraint( model.optimizer, - MOI.VariableIndex(vars[1].variable.value), - update_constant!(set, f.constant + param_constant), + pf.v[].variable, + set_with_new_constant(set, pf.current_constant), ) - poi_ci = create_new_poi_ci_and_save_affine_caches(model, params, moi_ci) - return poi_ci -end - -function create_new_poi_ci_and_save_affine_caches( - model::Optimizer, - params::Vector{MOI.ScalarAffineTerm{T}}, - moi_ci::MOI.ConstraintIndex{F,S}, -) where {T,F,S} - poi_ci = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}( + model.last_affine_added += 1 + outer_ci = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}( model.last_affine_added, ) - model.affine_constraint_cache[poi_ci] = params - model.affine_constraint_cache_set[poi_ci] = - MOI.get(model.optimizer, MOI.ConstraintSet(), moi_ci) - model.affine_added_cache[poi_ci] = moi_ci - return poi_ci + model.affine_outer_to_inner[outer_ci] = inner_ci + # model.outer_to_inner_map[outer_ci] = inner_ci + model.affine_constraint_cache[inner_ci] = pf + model.affine_constraint_cache_set[inner_ci] = set + return outer_ci end function MOI.add_constraint( @@ -999,7 +955,7 @@ function MOI.add_constraint( f::MOI.ScalarAffineFunction{T}, set::MOI.AbstractScalarSet, ) where {T} - if !function_has_parameters(model, f) + if !function_has_parameters(f) return MOI.add_constraint(model.optimizer, f, set) else return add_constraint_with_parameters_on_function(model, f, set) @@ -1032,7 +988,7 @@ function MOI.set( model::Optimizer, attr::MOI.AbstractVariableAttribute, v::MOI.VariableIndex, - val::Float64, + val, ) if is_variable_in_model(model, v) MOI.set(model.optimizer, attr, v, val) @@ -1174,10 +1130,9 @@ function MOI.set( end function empty_objective_function_caches!(model::Optimizer) - empty!(model.affine_objective_cache) - empty!(model.quadratic_objective_cache_pv) - empty!(model.quadratic_objective_cache_pp) - empty!(model.quadratic_objective_cache_pc) + model.affine_objective_cache = nothing + model.quadratic_objective_cache = nothing + model.original_objective_cache = nothing return end @@ -1188,24 +1143,51 @@ function MOI.set( ) where {T} # clear previously defined objetive function cache empty_objective_function_caches!(model) - if !function_has_parameters(model, f) + if !function_has_parameters(f) MOI.set(model.optimizer, attr, f) else - vars, params, param_constant = - separate_possible_terms_and_calculate_parameter_constant( - model, - f.terms, - ) + pf = ParametricAffineFunction(f) + update_cache!(pf, model) MOI.set( model.optimizer, attr, - MOI.ScalarAffineFunction(vars, f.constant + param_constant), + current_function(pf), ) - model.affine_objective_cache = params + model.affine_objective_cache = pf end - if model.save_original_objective_and_constraints - model.original_objective_function = f + model.original_objective_cache = f + return +end + +function MOI.set( + model::Optimizer, + attr::MOI.ObjectiveFunction{F}, + f::F, +) where {F<:MOI.ScalarQuadraticFunction{T}} where {T} + # clear previously defined objetive function cache + empty_objective_function_caches!(model) + if !function_has_parameters(f) + MOI.set(model.optimizer, attr, f) + else + pf = ParametricQuadraticFunction(f) + cache_multiplicative_params!(model, pf) + update_cache!(pf, model) + func = current_function(pf) + MOI.set( + model.optimizer, + MOI.ObjectiveFunction{ + (is_affine(func) ? + MOI.ScalarAffineFunction{T} : + MOI.ScalarQuadraticFunction{T}) + }(), + # func, + (is_affine(func) ? + MOI.ScalarAffineFunction(func.affine_terms, func.constant) : + func), + ) + model.quadratic_objective_cache = pf end + model.original_objective_cache = f return end @@ -1214,15 +1196,14 @@ function MOI.set( attr::MOI.ObjectiveFunction, v::MOI.VariableIndex, ) - if is_parameter_in_model(model, v) + if is_parameter(v) error("Cannot use a parameter as objective function alone") elseif !is_variable_in_model(model, v) error("Variable not in the model") end - if model.save_original_objective_and_constraints - model.original_objective_function = v - end - return MOI.set(model.optimizer, attr, model.variables[v]) + MOI.set(model.optimizer, attr, model.variables[v]) + model.original_objective_cache = v + return end function MOI.set( @@ -1272,7 +1253,7 @@ function MOI.get( T, S<:MOI.AbstractScalarSet, } - moi_ci = get(model.affine_added_cache, c, c) + moi_ci = get(model.affine_outer_to_inner, c, c) return MOI.get(model.optimizer, attr, moi_ci) end @@ -1294,8 +1275,8 @@ function MOI.add_constraint( model::Optimizer, f::MOI.VectorOfVariables, set::MOI.AbstractVectorSet, -) where {T} - if function_has_parameters(model, f) +) + if function_has_parameters(f) error("VectorOfVariables does not allow parameters") end return MOI.add_constraint(model.optimizer, f, set) @@ -1306,7 +1287,7 @@ function MOI.add_constraint( f::MOI.VectorAffineFunction{T}, set::MOI.AbstractVectorSet, ) where {T} - if !function_has_parameters(model, f) + if !function_has_parameters(f) return MOI.add_constraint(model.optimizer, f, set) else return add_constraint_with_parameters_on_function(model, f, set) @@ -1318,6 +1299,7 @@ function add_constraint_with_parameters_on_function( f::MOI.VectorAffineFunction{T}, set::MOI.AbstractVectorSet, ) where {T} + error("TODO") vars, params, param_constants = separate_possible_terms_and_calculate_parameter_constant(model, f, set) ci = MOI.add_constraint( @@ -1326,70 +1308,44 @@ function add_constraint_with_parameters_on_function( set, ) model.vector_constraint_cache[ci] = params - if model.save_original_objective_and_constraints - model.original_constraint_data[ci] = (f, set) - end return ci end function add_constraint_with_parameters_on_function( model::Optimizer, f::MOI.ScalarQuadraticFunction{T}, - set::S, + s::S, ) where {T,S<:MOI.AbstractScalarSet} - ( - quad_vars, - quad_aff_vars, - quad_params, - aff_terms, - variables_associated_to_parameters, - quad_param_constant, - ) = separate_possible_terms_and_calculate_parameter_constant( - model, - f.quadratic_terms, - ) - - ( - aff_vars, - aff_params, - terms_with_variables_associated_to_parameters, - aff_param_constant, - ) = separate_possible_terms_and_calculate_parameter_constant( - model, - f.affine_terms, - variables_associated_to_parameters, - ) - - aff_terms = vcat(aff_terms, aff_vars) - const_term = f.constant + aff_param_constant + quad_param_constant - quad_terms = quad_vars - f_quad = if !isempty(quad_vars) - MOI.ScalarQuadraticFunction(quad_terms, aff_terms, const_term) + pf = ParametricQuadraticFunction(f) + cache_multiplicative_params!(model, pf) + cache_set_constant!(pf, s) + update_cache!(pf, model) + + func = current_function(pf) + f_quad = if !is_affine(func) + fq = func + inner_ci = MOI.Utilities.normalize_and_add_constraint(model.optimizer, fq, s) + model.last_quad_add_added += 1 + outer_ci = MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}( + model.last_quad_add_added, + ) + model.quadratic_outer_to_inner[outer_ci] = inner_ci + # model.outer_to_inner_map[outer_ci] = inner_ci else - MOI.ScalarAffineFunction(aff_terms, const_term) - end - model.last_quad_add_added += 1 - ci = - MOI.Utilities.normalize_and_add_constraint(model.optimizer, f_quad, set) - # This part is used to remember that ci came from a quadratic function - # It is particularly useful because sometimes the constraint mutates - new_ci = MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}( - model.last_quad_add_added, - ) - model.moi_quadratic_to_poi_affine_map[new_ci] = ci - if model.save_original_objective_and_constraints - model.original_constraint_data[new_ci] = (f, set) + fa = MOI.ScalarAffineFunction(func.affine_terms, func.constant) + inner_ci = MOI.Utilities.normalize_and_add_constraint(model.optimizer, fa, s) + model.last_quad_add_added += 1 + outer_ci = MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{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.outer_to_inner_map[outer_ci] = inner_ci end - fill_quadratic_constraint_caches!( - model, - new_ci, - quad_aff_vars, - quad_params, - aff_params, - terms_with_variables_associated_to_parameters, - ci, - ) - return new_ci + model.quadratic_constraint_cache[inner_ci] = pf + model.quadratic_constraint_cache_set[inner_ci] = s + return outer_ci end function MOI.add_constraint( @@ -1397,7 +1353,7 @@ function MOI.add_constraint( f::MOI.ScalarQuadraticFunction{T}, set::MOI.AbstractScalarSet, ) where {T} - if !function_has_parameters(model, f) + if !function_has_parameters(f) return MOI.add_constraint(model.optimizer, f, set) else return add_constraint_with_parameters_on_function(model, f, set) @@ -1428,6 +1384,7 @@ function MOI.delete( F<:Union{MOI.VariableIndex,MOI.VectorOfVariables,MOI.VectorAffineFunction}, S<:MOI.AbstractSet, } +error("TODO: MOI.VectorAffineFunction") MOI.delete(model.optimizer, c) return end @@ -1439,6 +1396,8 @@ function MOI.is_valid( F<:Union{MOI.VariableIndex,MOI.VectorOfVariables,MOI.VectorAffineFunction}, S<:MOI.AbstractSet, } +error("TODO: MOI.VectorAffineFunction") + return MOI.is_valid(model.optimizer, c) end @@ -1471,105 +1430,76 @@ function _evaluate_parametric_expression( return evaluated_parameter_expression end -function set_quadratic_product_in_obj!(model::Optimizer) - f = model.original_objective_function - F = typeof(f) - - if F <: MOI.VariableIndex - if f == MOI.VariableIndex(-1) - aff_vars = MOI.ScalarAffineTerm{Float64}[] - else - aff_vars = - MOI.ScalarAffineTerm{Float64}[MOI.ScalarAffineTerm{Float64}( - one(Float64), - f, - )] - end - aff_params = MOI.ScalarAffineTerm{Float64}[] - terms_with_variables_associated_to_parameters = - MOI.ScalarAffineTerm{Float64}[] - aff_param_constant = zero(Float64) - quad_vars = MOI.ScalarQuadraticTerm{Float64}[] - quad_aff_vars = MOI.ScalarQuadraticTerm{Float64}[] - quad_params = MOI.ScalarQuadraticTerm{Float64}[] - aff_terms = MOI.ScalarAffineTerm{Float64}[] - variables_associated_to_parameters = MOI.VariableIndex[] - quad_param_constant = zero(Float64) - constant = zero(Float64) - elseif F <: MOI.ScalarAffineFunction - num_vars, num_params = count_scalar_affine_terms_types(model, f.terms) - if num_vars == 0 && num_params == 0 - aff_vars = MOI.ScalarAffineTerm{Float64}[] - aff_params = MOI.ScalarAffineTerm{Float64}[] - terms_with_variables_associated_to_parameters = - MOI.ScalarAffineTerm{Float64}[] - aff_param_constant = zero(Float64) - else - (aff_vars, aff_params, aff_param_constant) = - separate_possible_terms_and_calculate_parameter_constant( - model, - f.terms, - ) - end - quad_vars = MOI.ScalarQuadraticTerm{Float64}[] - quad_aff_vars = MOI.ScalarQuadraticTerm{Float64}[] - quad_params = MOI.ScalarQuadraticTerm{Float64}[] - aff_terms = MOI.ScalarAffineTerm{Float64}[] - variables_associated_to_parameters = MOI.VariableIndex[] - quad_param_constant = zero(Float64) - constant = f.constant - elseif F <: MOI.ScalarQuadraticFunction - ( - quad_vars, - quad_aff_vars, - quad_params, - aff_terms, - variables_associated_to_parameters, - quad_param_constant, - ) = separate_possible_terms_and_calculate_parameter_constant( - model, - f.quadratic_terms, - ) - - ( - aff_vars, - aff_params, - terms_with_variables_associated_to_parameters, - aff_param_constant, - ) = separate_possible_terms_and_calculate_parameter_constant( - model, - f.affine_terms, - variables_associated_to_parameters, - ) - constant = f.constant +function set_quadratic_product_in_obj!(model::Optimizer{T}) where {T} + n = length(model.quadratic_objective_cache_product) + if n == 0 + return end - aff_terms = vcat(aff_terms, aff_vars) + f = if model.affine_objective_cache !== nothing + current_function(model.affine_objective_cache) + elseif model.quadratic_objective_cache !== nothing + current_function(model.quadratic_objective_cache) + else + model.original_objective_cache + end + F = typeof(f) - quadratic_prods_vector = MOI.ScalarQuadraticTerm{Float64}[] + quadratic_prods_vector = MOI.ScalarQuadraticTerm{T}[] + sizehint!(quadratic_prods_vector, n) - dict_vars_quad_prod = model.quadratic_objective_cache_product - for (prod_var, fparam) in dict_vars_quad_prod - x, y = prod_var + for ((x, y), fparam) in model.quadratic_objective_cache_product + # x, y = prod_var evaluated_fparam = _evaluate_parametric_expression(model, fparam) - push!( + push!(quadratic_prods_vector, + MOI.ScalarQuadraticTerm(evaluated_fparam, x, y)) + end + + f_new = if F <: MOI.VariableIndex + MOI.ScalarQuadraticFunction( quadratic_prods_vector, - MOI.ScalarQuadraticTerm(evaluated_fparam, x, y), + MOI.ScalarAffineTerm{T}[MOI.ScalarAffineTerm{T}(1.0, f)], + 0.0, + ) + elseif F <: MOI.ScalarAffineFunction{T} + MOI.ScalarQuadraticFunction( + quadratic_prods_vector, + f.terms, + f.constant, + ) + elseif F <: MOI.ScalarQuadraticFunction{T} + quadratic_terms = vcat(f.quadratic_terms, quadratic_prods_vector) + MOI.ScalarQuadraticFunction( + quadratic_terms, + f.affine_terms, + f.constant, ) end - quad_vars = vcat(quad_vars, quadratic_prods_vector) - const_term = constant + aff_param_constant + quad_param_constant - MOI.set( model.optimizer, - MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), - MOI.ScalarQuadraticFunction(quad_vars, aff_terms, const_term), + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}(), + f_new, ) return end +function MOI.set( + model::Optimizer, + ::QuadraticObjectiveCoef, + (x1, x2)::Tuple{MOI.VariableIndex,MOI.VariableIndex}, + ::Nothing, +) + if x1.value > x2.value + aux = x1 + x1 = x2 + x2 = aux + end + delete!(model.quadratic_objective_cache_product, (x1, x2)) + return +end + function MOI.set( model::Optimizer, ::QuadraticObjectiveCoef, @@ -1589,7 +1519,7 @@ function MOI.get( model::Optimizer, ::QuadraticObjectiveCoef, (x1, x2)::Tuple{MOI.VariableIndex,MOI.VariableIndex}, -) where {T} +) if x1.value > x2.value aux = x1 x1 = x2 @@ -1606,69 +1536,6 @@ function MOI.get( end end -function MOI.set( - model::Optimizer, - attr::MOI.ObjectiveFunction, - f::MOI.ScalarQuadraticFunction{T}, -) where {T} - # clear previously defined objetive function cache - empty_objective_function_caches!(model) - if model.save_original_objective_and_constraints - model.original_objective_function = f - end - if !function_has_parameters(model, f) - MOI.set(model.optimizer, attr, f) - return - end - ( - quad_vars, - quad_aff_vars, - quad_params, - aff_terms, - variables_associated_to_parameters, - quad_param_constant, - ) = separate_possible_terms_and_calculate_parameter_constant( - model, - f.quadratic_terms, - ) - - ( - aff_vars, - aff_params, - terms_with_variables_associated_to_parameters, - aff_param_constant, - ) = separate_possible_terms_and_calculate_parameter_constant( - model, - f.affine_terms, - variables_associated_to_parameters, - ) - - aff_terms = vcat(aff_terms, aff_vars) - const_term = f.constant + aff_param_constant + quad_param_constant - - if !isempty(quad_vars) - MOI.set( - model.optimizer, - attr, - MOI.ScalarQuadraticFunction(quad_vars, aff_terms, const_term), - ) - else - MOI.set( - model.optimizer, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), - MOI.ScalarAffineFunction(aff_terms, const_term), - ) - end - - model.quadratic_objective_cache_pv = quad_aff_vars - model.quadratic_objective_cache_pp = quad_params - model.quadratic_objective_cache_pc = aff_params - model.quadratic_objective_variables_associated_to_parameters_cache = - terms_with_variables_associated_to_parameters - - return -end - function _poi_default_copy_to(dest::T, src::MOI.ModelLike) where {T} if !MOI.supports_incremental_interface(dest) error("Model $(typeof(dest)) does not support copy_to.") @@ -1718,16 +1585,16 @@ function _poi_default_copy_to(dest::T, src::MOI.ModelLike) where {T} end function MOI.Utilities.default_copy_to( - dest::MOI.Bridges.LazyBridgeOptimizer{Optimizer{Float64,T}}, + dest::MOI.Bridges.LazyBridgeOptimizer{Optimizer{T,OT}}, src::MOI.ModelLike, -) where {T} +) where {T,OT} return _poi_default_copy_to(dest, src) end function MOI.Utilities.default_copy_to( - dest::Optimizer{Float64,T}, + dest::Optimizer{T,OT}, src::MOI.ModelLike, -) where {T} +) where {T,OT} return _poi_default_copy_to(dest, src) end @@ -1743,7 +1610,7 @@ function MOI.optimize!(model::Optimizer) model.evaluate_duals @warn "Dual solution not available, ignoring `evaluate_duals`" elseif model.evaluate_duals - calculate_dual_of_parameters(model) + compute_dual_of_parameters!(model) end return end diff --git a/src/duals.jl b/src/duals.jl index 2725a3d4..abbfda16 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -3,147 +3,79 @@ # 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_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::DoubleDictInner{F,S,V1}, - affine_added_cache_inner::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_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, -) - 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 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 update_duals_with_quadratic_constraint_cache!( - param_dual_cum_sum::Vector{Float64}, - model::Optimizer, - quadratic_constraint_cache_pc_inner::DoubleDictInner{F,S,V}, -) where {F,S,V} - for (poi_ci, param_array) in quadratic_constraint_cache_pc_inner - moi_ci = model.moi_quadratic_to_poi_affine_map[poi_ci] - calculate_parameters_in_ci!( - param_dual_cum_sum, - model.optimizer, - param_array, - moi_ci, - ) - end - return -end - -function calculate_parameters_in_ci!( - param_dual_cum_sum::Vector{Float64}, - optimizer::OT, - param_array::Vector{MOI.ScalarAffineTerm{T}}, +function compute_parameters_in_ci!( + model::Optimizer{T}, + pf, 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 +) where {CI,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 # this one seem to be the same as the next -function update_duals_in_affine_objective!( - param_dual_cum_sum::Vector{Float64}, - affine_objective_cache::Vector{MOI.ScalarAffineTerm{T}}, +function update_duals_from_objective!( + model::Optimizer{T}, + pf, ) where {T} - for param in affine_objective_cache - param_dual_cum_sum[param.variable.value-PARAMETER_INDEX_THRESHOLD] -= + for param in pf.p + model.dual_value_of_parameters[p_val(param.variable)] -= param.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] -= - 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 @@ -164,9 +96,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( @@ -175,9 +107,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(ci)] end function is_additive(model::Optimizer, cp::MOI.ConstraintIndex) diff --git a/src/update_parameters.jl b/src/update_parameters.jl index 34743f5d..beec3d5c 100644 --- a/src/update_parameters.jl +++ b/src/update_parameters.jl @@ -3,357 +3,223 @@ # 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 update_constant!(s::MOI.LessThan{T}, val::T) where {T} +function set_with_new_constant(s::MOI.LessThan{T}, val::T) where {T} return MOI.LessThan{T}(s.upper - val) end -function update_constant!(s::MOI.GreaterThan{T}, val::T) where {T} +function set_with_new_constant(s::MOI.GreaterThan{T}, val::T) where {T} return MOI.GreaterThan{T}(s.lower - val) end -function update_constant!(s::MOI.EqualTo{T}, val::T) where {T} +function set_with_new_constant(s::MOI.EqualTo{T}, val::T) where {T} return MOI.EqualTo{T}(s.value - val) end -function update_constant!(s::MOI.Interval{T}, val::T) where {T} +function set_with_new_constant(s::MOI.Interval{T}, val::T) where {T} return MOI.Interval{T}(s.lower - val, s.upper - val) end # Affine -function update_parameter_in_affine_constraints!(model::Optimizer) +# change to use only inner_ci all around so tha tupdates are faster +# modifications should not be used any ways, afterall we have param all around +function update_parametric_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_constraint_cache_set_inner = model.affine_constraint_cache_set[F, S] - affine_added_cache_inner = model.affine_added_cache[F, S] if !isempty(affine_constraint_cache_inner) - update_parameter_in_affine_constraints!( - model.optimizer, - model.parameters, - model.updated_parameters, + # barrier to avoid type instability of inner dicts + update_parametric_affine_constraints!( + model, affine_constraint_cache_inner, affine_constraint_cache_set_inner, - affine_added_cache_inner, ) end end - return model + return end -function update_parameter_in_affine_constraints!( - optimizer::OT, - parameters::ParamTo{T}, - updated_parameters::ParamTo{T}, - affine_constraint_cache_inner::DoubleDictInner{F,S,V1}, - affine_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, - affine_added_cache_inner::DoubleDictInner{F,S,V2}, -) where {OT,T,F,S,V1,V2} - for (ci, param_array) in affine_constraint_cache_inner - new_set = update_parameter_in_affine_constraints!( - optimizer, - affine_added_cache_inner[ci], - param_array, - parameters, - updated_parameters, - affine_constraint_cache_set_inner[ci], - ) - affine_constraint_cache_set_inner[ci] = new_set - end - return optimizer -end +# TODO: cache changes and then batch them instead -function update_parameter_in_affine_constraints!( - optimizer::OT, - ci::CI, - param_array::Vector{MOI.ScalarAffineTerm{T}}, - parameters::ParamTo{T}, - updated_parameters::ParamTo{T}, - set::S, -) where {OT,T,CI,S} - param_constant = zero(T) - for term in param_array - if !isnan(updated_parameters[p_idx(term.variable)]) - param_constant += - term.coefficient * ( - updated_parameters[p_idx(term.variable)] - - parameters[p_idx(term.variable)] - ) +function update_parametric_affine_constraints!( + model::Optimizer, + affine_constraint_cache_inner::DoubleDictInner{F,S,V}, + affine_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, +) where {F,S<:SIMPLE_SCALAR_SETS{T},V} where {T} + # cis = MOI.ConstraintIndex{F,S}[] + # sets = S[] + # sizehint!(cis, length(affine_constraint_cache_inner)) + # sizehint!(sets, length(affine_constraint_cache_inner)) + for (inner_ci, pf) in affine_constraint_cache_inner + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant += delta_constant + new_set = S(pf.set_constant - pf.current_constant) + # new_set = set_with_new_constant(set, param_constant) + MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set) + # push!(cis, inner_ci) + # push!(sets, new_set) end end - if param_constant != zero(Float64) - new_set = update_constant!(set, param_constant) - MOI.set(optimizer, MOI.ConstraintSet(), ci, new_set) - return new_set - end - return set + # if !isempty(cis) + # MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets) + # end + return end -function update_parameter_in_affine_objective!(model::Optimizer) - if !isempty(model.affine_objective_cache) - objective_constant = zero(Float64) - for term in model.affine_objective_cache - if !isnan(model.updated_parameters[p_idx(term.variable)]) - param_old = model.parameters[p_idx(term.variable)] - param_new = model.updated_parameters[p_idx(term.variable)] - aux = param_new - param_old - objective_constant += term.coefficient * aux - end - end - if !iszero(objective_constant) - F = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) - f = MOI.get(model.optimizer, MOI.ObjectiveFunction{F}()) - MOI.modify( - model.optimizer, - MOI.ObjectiveFunction{F}(), - MOI.ScalarConstantChange(f.constant + objective_constant), - ) +function update_parametric_affine_constraints!( + model::Optimizer, + affine_constraint_cache_inner::DoubleDictInner{F,S,V}, + affine_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, +) where {F,S<:MOI.Interval{T},V} where {T} + for (inner_ci, pf) in affine_constraint_cache_inner + set = affine_constraint_cache_set_inner[inner_ci]::S + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant += delta_constant + # new_set = S(pf.set_constant - pf.current_constant) + new_set = set_with_new_constant(set, pf.current_constant)::S + MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set) end end - return model + return end -function update_parameter_in_quadratic_constraints_pc!(model::Optimizer) - for (ci, fparam) in model.quadratic_constraint_cache_pc - param_constant = zero(Float64) - for term in fparam - if !isnan(model.updated_parameters[p_idx(term.variable)]) - param_constant += - term.coefficient * ( - model.updated_parameters[p_idx(term.variable)] - - model.parameters[p_idx(term.variable)] - ) - end - end - if param_constant != zero(Float64) - old_set = model.quadratic_constraint_cache_pc_set[ci] - new_set = update_constant!(old_set, param_constant) - MOI.set( - model.optimizer, - MOI.ConstraintSet(), - model.moi_quadratic_to_poi_affine_map[ci], - new_set, +function update_parametric_quadratic_constraints!(model::Optimizer) + for (F, S) in keys(model.quadratic_constraint_cache.dict) + quadratic_constraint_cache_inner = model.quadratic_constraint_cache[F, S] + quadratic_constraint_cache_set_inner = + model.quadratic_constraint_cache_set[F, S] + if !isempty(quadratic_constraint_cache_inner) + # barrier to avoid type instability of inner dicts + update_parametric_quadratic_constraints!( + model, + quadratic_constraint_cache_inner, + quadratic_constraint_cache_set_inner, ) - model.quadratic_constraint_cache_pc_set[ci] = new_set end end + return end -function update_parameter_in_quadratic_objective_pc!(model::Optimizer) - if !isempty(model.quadratic_objective_cache_pc) - objective_constant = zero(Float64) - for term in model.quadratic_objective_cache_pc - if !isnan(model.updated_parameters[p_idx(term.variable)]) - param_old = model.parameters[p_idx(term.variable)] - param_new = model.updated_parameters[p_idx(term.variable)] - aux = param_new - param_old - objective_constant += term.coefficient * aux - end - end - if !iszero(objective_constant) - F = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) - f = MOI.get(model.optimizer, MOI.ObjectiveFunction{F}()) - MOI.modify( - model.optimizer, - MOI.ObjectiveFunction{F}(), - MOI.ScalarConstantChange(f.constant + objective_constant), - ) - end +function affine_build_change_and_up_param_func( + pf::ParametricQuadraticFunction{T}, + delta_terms, +) where {T} + changes = Vector{MOI.ScalarCoefficientChange}( + undef, + length(delta_terms), + ) + i = 1 + for (var, coef) in delta_terms + base_coef = pf.current_terms_with_p[var] + new_coef = base_coef + coef + pf.current_terms_with_p[var] = new_coef + changes[i] = MOI.ScalarCoefficientChange(var, new_coef) + i += 1 end - return model + return changes end -function update_parameter_in_quadratic_constraints_pp!(model::Optimizer) - for (ci, fparam) in model.quadratic_constraint_cache_pp - param_constant = zero(Float64) - for term in fparam - if !isnan(model.updated_parameters[p_idx(term.variable_1)]) && - !isnan(model.updated_parameters[p_idx(term.variable_2)]) - param_constant += - term.coefficient * ( - ( - model.updated_parameters[p_idx(term.variable_1)] * - model.updated_parameters[p_idx(term.variable_2)] - ) - ( - model.parameters[p_idx(term.variable_1)] * - model.parameters[p_idx(term.variable_2)] - ) - ) - elseif !isnan(model.updated_parameters[p_idx(term.variable_1)]) - param_constant += - term.coefficient * - model.parameters[p_idx(term.variable_2)] * - ( - model.updated_parameters[p_idx(term.variable_1)] - - model.parameters[p_idx(term.variable_1)] - ) - elseif !isnan(model.updated_parameters[p_idx(term.variable_2)]) - param_constant += - term.coefficient * - model.parameters[p_idx(term.variable_1)] * - ( - model.updated_parameters[p_idx(term.variable_2)] - - model.parameters[p_idx(term.variable_2)] - ) - end +function update_parametric_quadratic_constraints!( + model::Optimizer, + quadratic_constraint_cache_inner::DoubleDictInner{F,S,V}, + quadratic_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, +) where {F,S<:SIMPLE_SCALAR_SETS{T},V} where {T} + # cis = MOI.ConstraintIndex{F,S}[] + # sets = S[] + # sizehint!(cis, length(quadratic_constraint_cache_inner)) + # sizehint!(sets, length(quadratic_constraint_cache_inner)) + for (inner_ci, pf) in quadratic_constraint_cache_inner + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant += delta_constant + new_set = S(pf.set_constant - pf.current_constant) + # new_set = set_with_new_constant(set, param_constant) + MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set) + # push!(cis, inner_ci) + # push!(sets, new_set) end - if param_constant != zero(Float64) - old_set = model.quadratic_constraint_cache_pp_set[ci] - new_set = update_constant!(old_set, param_constant) - MOI.set( - model.optimizer, - MOI.ConstraintSet(), - model.moi_quadratic_to_poi_affine_map[ci], - new_set, - ) - model.quadratic_constraint_cache_pp_set[ci] = new_set + delta_terms = delta_parametric_affine_terms(model, pf) + if !isempty(delta_terms) + changes = affine_build_change_and_up_param_func(pf, delta_terms) + cis = fill(inner_ci, length(changes)) + MOI.modify(model.optimizer, cis, changes) end end - return model + # if !isempty(cis) + # MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets) + # end + return end -function update_parameter_in_quadratic_objective_pp!(model::Optimizer) - if !isempty(model.quadratic_objective_cache_pp) - objective_constant = zero(Float64) - for term in model.quadratic_objective_cache_pp - if !isnan(model.updated_parameters[p_idx(term.variable_1)]) && - !isnan(model.updated_parameters[p_idx(term.variable_2)]) - objective_constant += - term.coefficient * ( - ( - model.updated_parameters[p_idx(term.variable_1)] * - model.updated_parameters[p_idx(term.variable_2)] - ) - ( - model.parameters[p_idx(term.variable_1)] * - model.parameters[p_idx(term.variable_2)] - ) - ) - elseif !isnan(model.updated_parameters[p_idx(term.variable_1)]) - objective_constant += - term.coefficient * - model.parameters[p_idx(term.variable_2)] * - ( - model.updated_parameters[p_idx(term.variable_1)] - - model.parameters[p_idx(term.variable_1)] - ) - elseif !isnan(model.updated_parameters[p_idx(term.variable_2)]) - objective_constant += - term.coefficient * - model.parameters[p_idx(term.variable_1)] * - ( - model.updated_parameters[p_idx(term.variable_2)] - - model.parameters[p_idx(term.variable_2)] - ) - end +function update_parametric_quadratic_constraints!( + model::Optimizer, + quadratic_constraint_cache_inner::DoubleDictInner{F,S,V}, + quadratic_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, +) where {F,S<:MOI.Interval{T},V} where {T} + for (inner_ci, pf) in quadratic_constraint_cache_inner + set = quadratic_constraint_cache_set_inner[inner_ci]::S + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant += delta_constant + # new_set = S(pf.set_constant - pf.current_constant) + new_set = set_with_new_constant(set, pf.current_constant)::S + MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set) end - if objective_constant != zero(Float64) - F = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) - f = MOI.get(model.optimizer, MOI.ObjectiveFunction{F}()) - MOI.modify( - model.optimizer, - MOI.ObjectiveFunction{F}(), - MOI.ScalarConstantChange(f.constant + objective_constant), - ) + delta_terms = delta_parametric_affine_terms(model, pf) + if !isempty(delta_terms) + changes = affine_build_change_and_up_param_func(pf, delta_terms) + cis = fill(inner_ci, length(changes)) + MOI.modify(model.optimizer, cis, changes) end end - return model + return end -function update_parameter_in_quadratic_constraints_pv!(model::Optimizer) - for (ci, quad_aff_vars) in model.quadratic_constraint_cache_pv - # We need this dictionary because we could have terms like - # p_1 * v_1 + p_2 * v_1 and we should add p_1 and p_2 on the update - new_coeff_per_variable = Dict{MOI.VariableIndex,Float64}() - for term in quad_aff_vars - # Here we use the convention that the parameter always comes as the first variables - # in the caches - if !isnan(model.updated_parameters[p_idx(term.variable_1)]) - coef = term.coefficient - param_new = model.updated_parameters[p_idx(term.variable_1)] - if haskey(new_coeff_per_variable, term.variable_2) - new_coeff_per_variable[term.variable_2] += param_new * coef - else - new_coeff_per_variable[term.variable_2] = param_new * coef - end - end - end - if haskey( - model.variables_multiplied_by_parameters, - ci, - ) - for aff_term in - model.variables_multiplied_by_parameters[ci] - coef = aff_term.coefficient - if haskey(new_coeff_per_variable, aff_term.variable) - new_coeff_per_variable[aff_term.variable] += coef - else - new_coeff_per_variable[aff_term.variable] = coef - end - end - end - old_ci = model.moi_quadratic_to_poi_affine_map[ci] - changes = Vector{MOI.ScalarCoefficientChange}( - undef, - length(new_coeff_per_variable), - ) - i = 1 - for (vi, value) in new_coeff_per_variable - changes[i] = MOI.ScalarCoefficientChange(vi, value) - i += 1 - end - # Make multiple changes at once. +function update_parametric_affine_objective!(model::Optimizer{T}) where {T} + if model.affine_objective_cache === nothing + return + end + pf = model.affine_objective_cache + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant += delta_constant + # F = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) MOI.modify( model.optimizer, - fill(old_ci, length(new_coeff_per_variable)), - changes, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), + MOI.ScalarConstantChange(pf.current_constant), ) end - return model + return end -function update_parameter_in_quadratic_objective_pv!(model::Optimizer) - if !isempty(model.quadratic_objective_cache_pv) - # We need this dictionary because we could have terms like - # p_1 * v_1 + p_2 * v_1 and we should add p_1 and p_2 on the update - new_coeff_per_variable = Dict{MOI.VariableIndex,Float64}() - for term in model.quadratic_objective_cache_pv - # Here we use the convention that the parameter always comes as the first variables - # in the caches - if !isnan(model.updated_parameters[p_idx(term.variable_1)]) - coef = term.coefficient - param_new = model.updated_parameters[p_idx(term.variable_1)] - if haskey(new_coeff_per_variable, term.variable_2) - new_coeff_per_variable[term.variable_2] += param_new * coef - else - new_coeff_per_variable[term.variable_2] = param_new * coef - end - end - end - - for aff_term in - model.quadratic_objective_variables_associated_to_parameters_cache - coef = aff_term.coefficient - if haskey(new_coeff_per_variable, aff_term.variable) - new_coeff_per_variable[aff_term.variable] += coef - else - new_coeff_per_variable[aff_term.variable] = coef - end - end - - F_pv = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) - changes = Vector{MOI.ScalarCoefficientChange}( - undef, - length(new_coeff_per_variable), +function update_parametric_quadratic_objective!(model::Optimizer{T}) where {T} + if model.quadratic_objective_cache === nothing + return + end + pf = model.quadratic_objective_cache + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant += delta_constant + F = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) + MOI.modify( + model.optimizer, + MOI.ObjectiveFunction{F}(), + MOI.ScalarConstantChange(pf.current_constant), ) - i = 1 - for (vi, value) in new_coeff_per_variable - changes[i] = MOI.ScalarCoefficientChange(vi, value) - i += 1 - end - MOI.modify(model.optimizer, MOI.ObjectiveFunction{F_pv}(), changes) end - return model + delta_terms = delta_parametric_affine_terms(model, pf) + if !isempty(delta_terms) + F = MOI.get(model.optimizer, MOI.ObjectiveFunctionType()) + changes = affine_build_change_and_up_param_func(pf, delta_terms) + MOI.modify(model.optimizer, MOI.ObjectiveFunction{F}(), changes) + end + return end +# TODO # Vector Affine function update_parameter_in_vector_affine_constraints!(model::Optimizer) for (F, S) in keys(model.vector_constraint_cache.dict) @@ -423,17 +289,14 @@ function update_parameter_in_vector_affine_constraints!( end function update_parameters!(model::Optimizer) - update_parameter_in_affine_constraints!(model) - update_parameter_in_affine_objective!(model) - update_parameter_in_quadratic_constraints_pc!(model) - update_parameter_in_quadratic_objective_pc!(model) - update_parameter_in_quadratic_constraints_pv!(model) - update_parameter_in_quadratic_objective_pv!(model) - update_parameter_in_quadratic_constraints_pp!(model) - update_parameter_in_quadratic_objective_pp!(model) - update_parameter_in_vector_affine_constraints!(model) + update_parametric_affine_constraints!(model) + update_parametric_quadratic_constraints!(model) + update_parametric_affine_objective!(model) + update_parametric_quadratic_objective!(model) + # update_parameter_in_vector_affine_constraints!(model) - # Update parameters and put NaN to indicate that the parameter has been updated + # Update parameters and put NaN to indicate that the parameter has been + # updated for (parameter_index, val) in model.updated_parameters if !isnan(val) model.parameters[parameter_index] = val @@ -441,5 +304,5 @@ function update_parameters!(model::Optimizer) end end - return model + return end diff --git a/src/utils.jl b/src/utils.jl index 086374d9..649503de 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -26,36 +26,30 @@ function is_variable_in_model(model::Optimizer, v::MOI.VariableIndex) end function has_quadratic_constraint_caches(model::Optimizer) - return !isempty(model.moi_quadratic_to_poi_affine_map) + return !isempty(model.quadratic_outer_to_inner) end -function function_has_parameters( - model::Optimizer, - f::MOI.ScalarAffineFunction{T}, -) where {T} +function function_has_parameters(f::MOI.ScalarAffineFunction{T}) where {T} for term in f.terms - if is_parameter_in_model(model, term.variable) + if is_parameter(term.variable) return true end end return false end -function function_has_parameters(model::Optimizer, f::MOI.VectorOfVariables) +function function_has_parameters(f::MOI.VectorOfVariables) for variable in f.variables - if is_parameter_in_model(model, variable) + if is_parameter(variable) return true end end return false end -function function_has_parameters( - model::Optimizer, - f::MOI.VectorAffineFunction{T}, -) where {T} +function function_has_parameters(f::MOI.VectorAffineFunction{T}) where {T} for term in f.terms - if is_parameter_in_model(model, term.scalar_term.variable) + if is_parameter(term.scalar_term.variable) return true end end @@ -63,19 +57,17 @@ function function_has_parameters( end function function_has_parameters( - model::Optimizer, f::MOI.ScalarQuadraticFunction{T}, ) where {T} - return function_affine_terms_has_parameters(model, f.affine_terms) || - function_quadratic_terms_has_parameters(model, f.quadratic_terms) + return function_affine_terms_has_parameters(f.affine_terms) || + function_quadratic_terms_has_parameters(f.quadratic_terms) end function function_affine_terms_has_parameters( - model::Optimizer, affine_terms::Vector{MOI.ScalarAffineTerm{T}}, ) where {T} for term in affine_terms - if is_parameter_in_model(model, term.variable) + if is_parameter(term.variable) return true end end @@ -83,50 +75,23 @@ function function_affine_terms_has_parameters( end function function_quadratic_terms_has_parameters( - model::Optimizer, quadratic_terms::Vector{MOI.ScalarQuadraticTerm{T}}, ) where {T} for term in quadratic_terms - if is_parameter_in_model(model, term.variable_1) || - is_parameter_in_model(model, term.variable_2) + if is_parameter(term.variable_1) || + is_parameter(term.variable_2) return true end end return false end -function count_scalar_affine_terms_types( - model::Optimizer, - terms::Vector{MOI.ScalarAffineTerm{T}}, -) where {T} - num_vars = count(x -> is_variable_in_model(model, x.variable), terms) - num_params = length(terms) - num_vars - return num_vars, num_params -end - -function count_scalar_affine_terms_types( - model::Optimizer, - terms::Vector{MOI.ScalarAffineTerm{T}}, - variables_associated_to_parameters::Vector{MOI.VariableIndex}, -) where {T} - num_vars = 0 - num_params = 0 - num_vars_associated_to_params = 0 - for term in terms - if is_variable_in_model(model, term.variable) - num_vars += 1 - if term.variable in variables_associated_to_parameters - num_vars_associated_to_params += 1 - end - else - num_params += 1 - end - end - return num_vars, num_params, num_vars_associated_to_params +function is_variable(v::MOI.VariableIndex) + return v.value < PARAMETER_INDEX_THRESHOLD end -function is_variable(v::MOI.VariableIndex) - return v.index < PARAMETER_INDEX_THRESHOLD +function is_parameter(v::MOI.VariableIndex) + return v.value > PARAMETER_INDEX_THRESHOLD end function count_scalar_affine_terms_types( @@ -169,15 +134,39 @@ function ParametricAffineFunction( return ParametricAffineFunction{T}( p, v, + zero(T), f.constant, zero(T), ) end +function original_function(f::ParametricAffineFunction{T}) where {T} + return MOI.ScalarAffineFunction{T}( + vcat(f.p, f.v), + f.c, + ) +end + +function current_function(f::ParametricAffineFunction{T}) where {T} + return MOI.ScalarAffineFunction{T}( + f.v, + f.current_constant, + ) +end + +function update_cache!( + f::ParametricAffineFunction{T}, + model::Optimizer, +) where {T} + f.current_constant = parametric_constant(model, f) + return nothing +end + function parametric_constant( model::Optimizer, f::ParametricAffineFunction{T}, ) where {T} + # do not add set_function here param_constant = f.c for term in f.p param_constant += @@ -265,7 +254,7 @@ function ParametricQuadraticFunction( f::MOI.ScalarQuadraticFunction{T} ) where {T} v, p = split_affine_terms(f.affine_terms) - pv, pp, vv = split_affine_terms(f.quadratic_terms) + pv, pp, vv = split_quadratic_terms(f.quadratic_terms) # find variables related to parameters # so that we only cache the important part of the v (affine part) @@ -276,30 +265,71 @@ function ParametricQuadraticFunction( end affine_data = Dict{MOI.VariableIndex,T}() sizehint!(affine_data, length(v_in_pv)) + affine_data_np = Dict{MOI.VariableIndex,T}() + sizehint!(affine_data, length(v)) for term in v if term.variable in v_in_pv base = get(affine_data, term.variable, zero(T)) affine_data[term.variable] = term.coefficient + base + else + base = get(affine_data_np, term.variable, zero(T)) + affine_data_np[term.variable] = term.coefficient + base end end return ParametricQuadraticFunction{T}( affine_data, + affine_data_np, pv, pp, vv, p, v, f.constant, + zero(T), Dict{MOI.VariableIndex,T}(), zero(T), ) end +function original_function(f::ParametricQuadraticFunction{T}) where {T} + return MOI.ScalarQuadraticFunction{T}( + vcat(f.pv, f.pp, f.vv), + vcat(f.p, f.v), + f.c, + ) +end + +function current_function(f::ParametricQuadraticFunction{T}) where {T} + affine = MOI.ScalarAffineTerm{T}[] + sizehint!(affine, length(f.current_terms_with_p) + length(f.affine_data_np)) + for (v, c) in f.current_terms_with_p + push!(affine, MOI.ScalarAffineTerm{T}(c, v)) + end + for (v, c) in f.affine_data_np + push!(affine, MOI.ScalarAffineTerm{T}(c, v)) + end + return MOI.ScalarQuadraticFunction{T}( + f.vv, + affine, + f.current_constant, + ) +end + +function update_cache!( + f::ParametricQuadraticFunction{T}, + model::Optimizer, +) where {T} + f.current_constant = parametric_constant(model, f) + f.current_terms_with_p = parametric_affine_terms(model, f) + return nothing +end + function parametric_constant( model::Optimizer, f::ParametricQuadraticFunction{T}, ) where {T} + # do not add set_function here param_constant = f.c for term in f.p param_constant += @@ -321,10 +351,8 @@ function delta_parametric_constant( for term in f.p p = p_idx(term.variable) if !isnan(model.updated_parameters[p]) - delta_constant += - term.coefficient * ( - model.updated_parameters[p] - model.parameters[p] - ) + delta_constant += term.coefficient * + (model.updated_parameters[p] - model.parameters[p]) end end for term in f.pp @@ -357,7 +385,7 @@ function parametric_affine_terms( param_terms_dict[term.variable_2] = base + term.coefficient * model.parameters[p_idx(term.variable_1)] end - # by definition affin data only contains variables that appear in pv + # by definition affine data only contains variables that appear in pv for (var, coef) in f.affine_data param_terms_dict[var] += coef end @@ -376,256 +404,86 @@ function delta_parametric_affine_terms( if !isnan(model.updated_parameters[p]) base = get(delta_terms_dict, term.variable_2, zero(T)) delta_terms_dict[term.variable_2] = base + term.coefficient * - (model.updated_parameters[p2] - model.parameters[p]) + (model.updated_parameters[p] - model.parameters[p]) end end return delta_terms_dict end -function move_set_constant_to_function( - f::MOI.AbstractScalarFunction, +function cache_set_constant!( + f::ParametricAffineFunction{T}, s::Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}}, ) where {T} - c = constant(s) - new_f = copy(f) - new_f.constant -= c - return new_f + f.set_constant = MOI.constant(s) + return end -function move_set_constant_to_function( - f::MOI.AbstractScalarFunction, +function cache_set_constant!( + f::ParametricAffineFunction{T}, s::MOI.AbstractScalarSet, -) - return f +) where {T} + return end -function separate_possible_terms_and_calculate_parameter_constant( - model::Optimizer, - terms::Vector{MOI.ScalarAffineTerm{T}}, +function cache_set_constant!( + f::ParametricQuadraticFunction{T}, + s::Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}}, ) where {T} - num_vars, num_params = count_scalar_affine_terms_types(model, terms) - vars = Vector{MOI.ScalarAffineTerm{T}}(undef, num_vars) - params = Vector{MOI.ScalarAffineTerm{T}}(undef, num_params) - param_constant = zero(T) - i_vars = 1 - i_params = 1 - - for term in terms - # TODO: do we really need this checks? - if is_variable_in_model(model, term.variable) - vars[i_vars] = term - i_vars += 1 - elseif is_parameter_in_model(model, term.variable) - params[i_params] = term - param_constant += - term.coefficient * model.parameters[p_idx(term.variable)] - i_params += 1 - else - error("Constraint uses a variable that is not in the model") - end - end - return vars, params, param_constant + f.set_constant = MOI.constant(s) + return end -# This version is used on SQFs -function separate_possible_terms_and_calculate_parameter_constant( - model::Optimizer, - terms::Vector{MOI.ScalarAffineTerm{T}}, - variables_associated_to_parameters::Vector{MOI.VariableIndex}, +function cache_set_constant!( + f::ParametricQuadraticFunction{T}, + s::MOI.AbstractScalarSet, ) where {T} - num_vars, num_params, num_vars_associated_to_params = - count_scalar_affine_terms_types( - model, - terms, - variables_associated_to_parameters, - ) - vars = Vector{MOI.ScalarAffineTerm{T}}(undef, num_vars) - params = Vector{MOI.ScalarAffineTerm{T}}(undef, num_params) - terms_with_variables_associated_to_parameters = - Vector{MOI.ScalarAffineTerm{T}}(undef, num_vars_associated_to_params) - param_constant = zero(T) - i_vars = 1 - i_params = 1 - i_vars_associated_to_params = 1 + return +end - for term in terms - if is_variable_in_model(model, term.variable) - vars[i_vars] = term - if term.variable in variables_associated_to_parameters - terms_with_variables_associated_to_parameters[i_vars_associated_to_params] = - term - i_vars_associated_to_params += 1 - end - i_vars += 1 - elseif is_parameter_in_model(model, term.variable) - params[i_params] = term - param_constant += - term.coefficient * model.parameters[p_idx(term.variable)] - i_params += 1 - else - error("Constraint uses a variable that is not in the model") - end - end - return vars, - params, - terms_with_variables_associated_to_parameters, - param_constant +function is_affine(::MOI.ScalarAffineFunction) + return true end -function count_scalar_quadratic_terms_types( - model::Optimizer, - terms::Vector{MOI.ScalarQuadraticTerm{T}}, -) where {T} - num_vv = 0 - num_pp = 0 - num_pv = 0 - for term in terms - if is_variable_in_model(model, term.variable_1) && - is_variable_in_model(model, term.variable_2) - num_vv += 1 - elseif is_variable_in_model(model, term.variable_1) && - is_parameter_in_model(model, term.variable_2) - num_pv += 1 - elseif is_parameter_in_model(model, term.variable_1) && - is_variable_in_model(model, term.variable_2) - num_pv += 1 - else - num_pp += 1 - end +function is_affine(f::MOI.ScalarQuadraticFunction) + if isempty(f.quadratic_terms) + return true end - return num_vv, num_pp, num_pv + return false end -function separate_possible_terms_and_calculate_parameter_constant( - model::Optimizer, - terms::Vector{MOI.ScalarQuadraticTerm{T}}, -) where {T} - num_vv, num_pp, num_pv = - count_scalar_quadratic_terms_types(model, terms) - - pp = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_pp) # parameter x parameter - pv = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_pv) # parameter (as a variable) x variable - vv = Vector{MOI.ScalarQuadraticTerm{T}}(undef, num_vv) # variable x variable - aff_terms = Vector{MOI.ScalarAffineTerm{T}}(undef, num_pv) # parameter (as a number) x variable - variables_associated_to_parameters = - Vector{MOI.VariableIndex}(undef, num_pv) - quad_param_constant = zero(T) - - i_vv = 1 - i_pp = 1 - i_pv = 1 +function is_affine(::ParametricAffineFunction) + return true +end - # When we have a parameter x variable or a variable x parameter the convention is to rewrite - # the SQT with parameter as variable_index_1 and variable as variable_index_2 - for term in terms - if ( - is_variable_in_model(model, term.variable_1) && - is_variable_in_model(model, term.variable_2) - ) - vv[i_vv] = term # if there are only variables, it remains a quadratic term - i_vv += 1 - elseif ( - is_parameter_in_model(model, term.variable_1) && - is_variable_in_model(model, term.variable_2) - ) - pv[i_pv] = term - variables_associated_to_parameters[i_pv] = - term.variable_2 - aff_terms[i_pv] = MOI.ScalarAffineTerm( - term.coefficient * model.parameters[p_idx(term.variable_1)], - term.variable_2, - ) - if model.evaluate_duals - push!(model.multiplicative_parameters, term.variable_1.value) - end - i_pv += 1 - elseif ( - is_variable_in_model(model, term.variable_1) && - is_parameter_in_model(model, term.variable_2) - ) - # Check convention defined above. We use the convention to know decide who is a variable and who is - # a parameter withou having to recheck which is which. - pv[i_pv] = MOI.ScalarQuadraticTerm( - term.coefficient, - term.variable_2, - term.variable_1, - ) - variables_associated_to_parameters[i_pv] = - term.variable_1 - aff_terms[i_pv] = MOI.ScalarAffineTerm( - term.coefficient * model.parameters[p_idx(term.variable_2)], - term.variable_1, - ) - model.evaluate_duals && - push!(model.multiplicative_parameters, term.variable_2.value) - i_pv += 1 - elseif ( - is_parameter_in_model(model, term.variable_1) && - is_parameter_in_model(model, term.variable_2) - ) - pp[i_pp] = term - model.evaluate_duals && - push!(model.multiplicative_parameters, term.variable_1.value) - model.evaluate_duals && - push!(model.multiplicative_parameters, term.variable_2.value) - quad_param_constant += - term.coefficient * - model.parameters[p_idx(term.variable_1)] * - model.parameters[p_idx(term.variable_2)] - i_pp += 1 - else - throw( - ErrorException( - "Constraint uses a variable or parameter that is not in the model", - ), - ) - end +function is_affine(f::ParametricQuadraticFunction) + if isempty(f.vv) + return true end - return vv, - pv, - pp, - aff_terms, - variables_associated_to_parameters, - quad_param_constant + return false end -function fill_quadratic_constraint_caches!( - model::Optimizer, - new_ci::MOI.ConstraintIndex, - pv::Vector{MOI.ScalarQuadraticTerm{T}}, - pp::Vector{MOI.ScalarQuadraticTerm{T}}, - aff_params::Vector{MOI.ScalarAffineTerm{T}}, - terms_with_variables_associated_to_parameters::Vector{ - MOI.ScalarAffineTerm{T}, - }, - ci::MOI.ConstraintIndex, -) where {T,S} - if !isempty(pv) - model.quadratic_constraint_cache_pv[new_ci] = pv - end - if !isempty(pp) - model.quadratic_constraint_cache_pp[new_ci] = pp - model.quadratic_constraint_cache_pp_set[new_ci] = - MOI.get(model.optimizer, MOI.ConstraintSet(), ci) - end - if !isempty(aff_params) - model.quadratic_constraint_cache_pc[new_ci] = aff_params - model.quadratic_constraint_cache_pc_set[new_ci] = - MOI.get(model.optimizer, MOI.ConstraintSet(), ci) +function cache_multiplicative_params!( + model::Optimizer{T}, + f::ParametricQuadraticFunction{T}, +) where {T} + for term in f.pv + push!(model.multiplicative_parameters, term.variable_2.value) end - if !isempty(terms_with_variables_associated_to_parameters) - model.variables_multiplied_by_parameters[new_ci] = - terms_with_variables_associated_to_parameters + # TODO compute these duals might be feasible + for term in f.pp + push!(model.multiplicative_parameters, term.variable_1.value) + push!(model.multiplicative_parameters, term.variable_2.value) end - return nothing + return end +# TODO: review comment function quadratic_constraint_cache_map_check( model::Optimizer, idx::MOI.ConstraintIndex{F,S}, ) where {F,S} - cached_constraints = values(model.moi_quadratic_to_poi_affine_map) - # Using this becuase some custom brodcast method throws errors if + cached_constraints = values(model.quadratic_outer_to_inner) + # Using this because some custom brodcast method throws errors if # inner_idex .∈ cached_constraints is used return idx ∈ cached_constraints end diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 7265fd4d..a78be2bf 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -108,89 +108,96 @@ function test_jump_constraintfunction_getter() c1 = @constraint(model, con, sum(x) + sum(p) >= 1) c2 = @constraint(model, conq, sum(x .* p) >= 1) c3 = @constraint(model, conqa, sum(x .* p) + x[1]^2 + x[1] + p[1] >= 1) - @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ - MOI.ScalarAffineFunction{Float64}( - [ - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(2)), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), - ), - ], - 0.0, - ) - @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ - MOI.ScalarQuadraticFunction{Float64}( - [ - MOI.ScalarQuadraticTerm{Float64}( - 1.0, - MOI.VariableIndex(1), - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - MOI.ScalarQuadraticTerm{Float64}( - 1.0, - MOI.VariableIndex(2), - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), - ), - ], - [], - 0.0, + @test MOI.Utilities.canonical( + MOI.get(model, MOI.ConstraintFunction(), c1) + ) ≈ + MOI.Utilities.canonical( + MOI.ScalarAffineFunction{Float64}( + [ + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(2)), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), + ), + ], + 0.0, + ) + ) + @test canonical_compare(MOI.get(model, MOI.ConstraintFunction(), c2), + MOI.ScalarQuadraticFunction{Float64}( + [ + MOI.ScalarQuadraticTerm{Float64}( + 1.0, + MOI.VariableIndex(1), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + MOI.ScalarQuadraticTerm{Float64}( + 1.0, + MOI.VariableIndex(2), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), + ), + ], + [], + 0.0, + ) ) - @test MOI.get(model, MOI.ConstraintFunction(), c3) ≈ - MOI.ScalarQuadraticFunction{Float64}( - [ - MOI.ScalarQuadraticTerm{Float64}( - 1.0, - MOI.VariableIndex(1), - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - MOI.ScalarQuadraticTerm{Float64}( - 1.0, - MOI.VariableIndex(2), - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), - ), - MOI.ScalarQuadraticTerm{Float64}( - 2.0, - MOI.VariableIndex(1), - MOI.VariableIndex(1), - ), - ], - [ - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - ], - 0.0, + @test canonical_compare(MOI.get(model, MOI.ConstraintFunction(), c3), + MOI.ScalarQuadraticFunction{Float64}( + [ + MOI.ScalarQuadraticTerm{Float64}( + 1.0, + MOI.VariableIndex(1), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + MOI.ScalarQuadraticTerm{Float64}( + 1.0, + MOI.VariableIndex(2), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), + ), + MOI.ScalarQuadraticTerm{Float64}( + 2.0, + MOI.VariableIndex(1), + MOI.VariableIndex(1), + ), + ], + [ + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + ], + 0.0, + ) ) o1 = @objective(model, Min, sum(x) + sum(p)) F = MOI.get(model, MOI.ObjectiveFunctionType()) - @test MOI.get(model, MOI.ObjectiveFunction{F}()) ≈ - MOI.ScalarAffineFunction{Float64}( - [ - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(2)), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), - ), - ], - 0.0, + @test canonical_compare(MOI.get(model, MOI.ObjectiveFunction{F}()), + MOI.ScalarAffineFunction{Float64}( + [ + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(2)), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), + ), + ], + 0.0, + ) ) o2 = @objective(model, Min, sum(x .* p) + 2) F = MOI.get(model, MOI.ObjectiveFunctionType()) - @test MOI.get(model, MOI.ObjectiveFunction{F}()) ≈ - MOI.ScalarQuadraticFunction{Float64}( + f = MOI.get(model, MOI.ObjectiveFunction{F}()) + f_ref = MOI.ScalarQuadraticFunction{Float64}( [ MOI.ScalarQuadraticTerm{Float64}( 1.0, @@ -206,35 +213,37 @@ function test_jump_constraintfunction_getter() [], 2.0, ) + @test canonical_compare(f, f_ref) o3 = @objective(model, Min, sum(x .* p) + x[1]^2 + x[1] + p[1]) F = MOI.get(model, MOI.ObjectiveFunctionType()) - @test MOI.get(model, MOI.ObjectiveFunction{F}()) ≈ - MOI.ScalarQuadraticFunction{Float64}( - [ - MOI.ScalarQuadraticTerm{Float64}( - 1.0, - MOI.VariableIndex(1), - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - MOI.ScalarQuadraticTerm{Float64}( - 1.0, - MOI.VariableIndex(2), - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), - ), - MOI.ScalarQuadraticTerm{Float64}( - 2.0, - MOI.VariableIndex(1), - MOI.VariableIndex(1), - ), - ], - [ - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - ], - 0.0, + @test canonical_compare(MOI.get(model, MOI.ObjectiveFunction{F}()), + MOI.ScalarQuadraticFunction{Float64}( + [ + MOI.ScalarQuadraticTerm{Float64}( + 1.0, + MOI.VariableIndex(1), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + MOI.ScalarQuadraticTerm{Float64}( + 1.0, + MOI.VariableIndex(2), + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), + ), + MOI.ScalarQuadraticTerm{Float64}( + 2.0, + MOI.VariableIndex(1), + MOI.VariableIndex(1), + ), + ], + [ + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + ], + 0.0, + ) ) return end @@ -865,16 +874,18 @@ function test_jump_direct_qp_objective() 2index(p) + 3, ) optimize!(model) - @test MOI.get( + @test canonical_compare(MOI.get( backend(model), POI.QuadraticObjectiveCoef(), (index(x), index(y)), - ) ≈ MOI.ScalarAffineFunction{Int64}( - MOI.ScalarAffineTerm{Int64}[MOI.ScalarAffineTerm{Int64}( - 2, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - )], - 3, + ), + MOI.ScalarAffineFunction{Int64}( + MOI.ScalarAffineTerm{Int64}[MOI.ScalarAffineTerm{Int64}( + 2, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + )], + 3, + ) ) @test objective_value(model) ≈ 32 / 3 atol = ATOL @test value(x) ≈ 4 / 3 atol = ATOL diff --git a/test/moi_tests.jl b/test/moi_tests.jl index b674058e..3aa4a779 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -129,7 +129,7 @@ function test_basic_special_cases_of_getters() ) MOI.set( optimizer, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), obj_func, ) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) diff --git a/test/runtests.jl b/test/runtests.jl index 4b7d393b..cbdff78f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,10 @@ const POI = ParametricOptInterface const ATOL = 1e-4 +function canonical_compare(f1, f2) + return MOI.Utilities.canonical(f1) ≈ MOI.Utilities.canonical(f2) +end + include("moi_tests.jl") include("jump_tests.jl") From d170da99d0516d2ba257510c5d43288dd31aedd9 Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 12 Apr 2023 01:05:09 -0300 Subject: [PATCH 03/13] warn vec affine --- src/ParametricOptInterface.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index edbb6ac2..646cfa74 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -1299,7 +1299,7 @@ function add_constraint_with_parameters_on_function( f::MOI.VectorAffineFunction{T}, set::MOI.AbstractVectorSet, ) where {T} - error("TODO") + @warn("TODO") vars, params, param_constants = separate_possible_terms_and_calculate_parameter_constant(model, f, set) ci = MOI.add_constraint( @@ -1384,7 +1384,7 @@ function MOI.delete( F<:Union{MOI.VariableIndex,MOI.VectorOfVariables,MOI.VectorAffineFunction}, S<:MOI.AbstractSet, } -error("TODO: MOI.VectorAffineFunction") +@warn("TODO: MOI.VectorAffineFunction") MOI.delete(model.optimizer, c) return end @@ -1396,7 +1396,7 @@ function MOI.is_valid( F<:Union{MOI.VariableIndex,MOI.VectorOfVariables,MOI.VectorAffineFunction}, S<:MOI.AbstractSet, } -error("TODO: MOI.VectorAffineFunction") +@warn("TODO: MOI.VectorAffineFunction") return MOI.is_valid(model.optimizer, c) end From 33820db28a9ffdff60076c68c540d21559699a66 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 13:03:58 -0300 Subject: [PATCH 04/13] add vector affine and fixes --- src/ParametricOptInterface.jl | 66 ++++++++++++------ src/duals.jl | 31 ++++++++- src/update_parameters.jl | 104 +++++++++------------------- src/utils.jl | 125 ++++++++++++++++++++++++++-------- 4 files changed, 207 insertions(+), 119 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 646cfa74..e6d921b1 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -110,6 +110,19 @@ mutable struct ParametricAffineFunction{T} current_constant::T end +mutable struct ParametricVectorAffineFunction{T} + # constant * parameter + p::Vector{MOI.VectorAffineTerm{T}} + # constant * variable + v::Vector{MOI.VectorAffineTerm{T}} + # constant + c::Vector{T} + # to avoid unnecessary lookups in updates + set_constant::Vector{T} + # cache to avoid slow getters + current_constant::Vector{T} +end + """ Optimizer{T, OT <: MOI.ModelLike} <: MOI.AbstractOptimizer @@ -183,7 +196,9 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer } # vector affine function data - vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{T}}} + # vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{T}}} + # Clever cache of data (inner key) + vector_affine_constraint_cache::DoubleDict{ParametricVectorAffineFunction{T}} # multiplicative_parameters::Set{Int64} @@ -239,7 +254,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer MOI.AbstractFunction, }(), # vec affine - DoubleDict{Vector{MOI.VectorAffineTerm{T}}}(), + # DoubleDict{Vector{MOI.VectorAffineTerm{T}}}(), + DoubleDict{ParametricVectorAffineFunction{T}}(), # other Set{Int64}(), Vector{T}(), @@ -279,7 +295,7 @@ function MOI.is_empty(model::Optimizer) model.original_objective_cache === nothing && isempty(model.quadratic_objective_cache_product) && # - isempty(model.vector_constraint_cache) && + isempty(model.vector_affine_constraint_cache) && # isempty(model.multiplicative_parameters) && isempty(model.dual_value_of_parameters) && @@ -310,7 +326,7 @@ function MOI.empty!(model::Optimizer{T}) where {T} model.original_objective_cache = nothing empty!(model.quadratic_objective_cache_product) # - empty!(model.vector_constraint_cache) + empty!(model.vector_affine_constraint_cache) # empty!(model.multiplicative_parameters) empty!(model.dual_value_of_parameters) @@ -466,14 +482,17 @@ function MOI.modify( error("Parametric constraint cannot be modified") end MOI.modify(model.optimizer, c, chg) - MOI.Utilities.modify_function(model.original_objective_cache) + MOI.Utilities.modify_function!(model.original_objective_cache, chg) return end function MOI.modify( model::Optimizer, c::MOI.ObjectiveFunction{F}, - chg::MOI.ScalarConstantChange{T}, + chg::Union{ + MOI.ScalarConstantChange{T}, + MOI.ScalarCoefficientChange{T}, + }, ) where {F<:MathOptInterface.AbstractScalarFunction,T} if model.quadratic_objective_cache !== nothing || model.affine_objective_cache !== nothing || @@ -481,7 +500,7 @@ function MOI.modify( error("Parametric objective cannot be modified") end MOI.modify(model.optimizer, c, chg) - MOI.Utilities.modify_function(model.original_objective_cache) + MOI.Utilities.modify_function!(model.original_objective_cache, chg) return end @@ -1155,7 +1174,7 @@ function MOI.set( ) model.affine_objective_cache = pf end - model.original_objective_cache = f + model.original_objective_cache = deepcopy(f) return end @@ -1299,16 +1318,16 @@ function add_constraint_with_parameters_on_function( f::MOI.VectorAffineFunction{T}, set::MOI.AbstractVectorSet, ) where {T} - @warn("TODO") - vars, params, param_constants = - separate_possible_terms_and_calculate_parameter_constant(model, f, set) - ci = MOI.add_constraint( + pf = ParametricVectorAffineFunction(f) + # cache_set_constant!(pf, set) # there is no constant is vector sets + update_cache!(pf, model) + inner_ci = MOI.add_constraint( model.optimizer, - MOI.VectorAffineFunction(vars, f.constants + param_constants), + current_function(pf), set, ) - model.vector_constraint_cache[ci] = params - return ci + model.vector_affine_constraint_cache[inner_ci] = pf + return inner_ci end function add_constraint_with_parameters_on_function( @@ -1381,14 +1400,25 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where { - F<:Union{MOI.VariableIndex,MOI.VectorOfVariables,MOI.VectorAffineFunction}, + F<:Union{MOI.VariableIndex,MOI.VectorOfVariables}, S<:MOI.AbstractSet, } -@warn("TODO: MOI.VectorAffineFunction") MOI.delete(model.optimizer, c) return end +function MOI.delete( + model::Optimizer, + c::MOI.ConstraintIndex{F,S}, +) where { + F<:MOI.VectorAffineFunction, + S<:MOI.AbstractSet, +} + MOI.delete(model.optimizer, c) + deleteat!(model.vector_affine_constraint_cache, c) + return +end + function MOI.is_valid( model::Optimizer, c::MOI.ConstraintIndex{F,S}, @@ -1396,8 +1426,6 @@ function MOI.is_valid( F<:Union{MOI.VariableIndex,MOI.VectorOfVariables,MOI.VectorAffineFunction}, S<:MOI.AbstractSet, } -@warn("TODO: MOI.VectorAffineFunction") - return MOI.is_valid(model.optimizer, c) end diff --git a/src/duals.jl b/src/duals.jl index abbfda16..d0164c68 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -6,6 +6,7 @@ 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) @@ -28,6 +29,19 @@ function update_duals_from_affine_constraints!(model::Optimizer) return end +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_from_quadratic_constraints!(model::Optimizer) for (F, S) in keys(model.quadratic_constraint_cache.dict) quadratic_constraint_cache_inner = @@ -54,8 +68,8 @@ end function compute_parameters_in_ci!( model::Optimizer{T}, pf, - ci::CI, -) where {CI,T} + 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)] -= @@ -64,6 +78,19 @@ function compute_parameters_in_ci!( return end +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 + # this one seem to be the same as the next function update_duals_from_objective!( model::Optimizer{T}, diff --git a/src/update_parameters.jl b/src/update_parameters.jl index beec3d5c..d1589624 100644 --- a/src/update_parameters.jl +++ b/src/update_parameters.jl @@ -85,6 +85,39 @@ function update_parametric_affine_constraints!( return end +function update_parametric_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] + if !isempty(vector_affine_constraint_cache_inner) + # barrier to avoid type instability of inner dicts + update_parametric_vector_affine_constraints!( + model, + vector_affine_constraint_cache_inner, + ) + end + end + return +end + +function update_parametric_vector_affine_constraints!( + model::Optimizer, + vector_affine_constraint_cache_inner::DoubleDictInner{F,S,V}, +) where {F<:MOI.VectorAffineFunction{T},S,V} where {T} + for (inner_ci, pf) in vector_affine_constraint_cache_inner + delta_constant = delta_parametric_constant(model, pf) + if !iszero(delta_constant) + pf.current_constant .+= delta_constant + MOI.modify( + model.optimizer, + inner_ci, + MOI.VectorConstantChange(pf.current_constant) + ) + end + end + return +end + function update_parametric_quadratic_constraints!(model::Optimizer) for (F, S) in keys(model.quadratic_constraint_cache.dict) quadratic_constraint_cache_inner = model.quadratic_constraint_cache[F, S] @@ -219,81 +252,12 @@ function update_parametric_quadratic_objective!(model::Optimizer{T}) where {T} return end -# TODO -# Vector Affine -function update_parameter_in_vector_affine_constraints!(model::Optimizer) - for (F, S) in keys(model.vector_constraint_cache.dict) - vector_constraint_cache_inner = model.vector_constraint_cache[F, S] - if !isempty(vector_constraint_cache_inner) - update_parameter_in_vector_affine_constraints!( - model.optimizer, - model.parameters, - model.updated_parameters, - vector_constraint_cache_inner, - ) - end - end - return model -end - -function update_parameter_in_vector_affine_constraints!( - optimizer::OT, - parameters::ParamTo{T}, - updated_parameters::ParamTo{T}, - vector_constraint_cache_inner::DoubleDictInner{F,S,V}, -) where {OT,T,F,S,V} - for (ci, param_array) in vector_constraint_cache_inner - update_parameter_in_vector_affine_constraints!( - optimizer, - ci, - param_array, - parameters, - updated_parameters, - ) - end - - return optimizer -end - -function update_parameter_in_vector_affine_constraints!( - optimizer::OT, - ci::CI, - param_array::Vector{MOI.VectorAffineTerm{T}}, - parameters::ParamTo{T}, - updated_parameters::ParamTo{T}, -) where {OT,T,CI} - cf = MOI.get(optimizer, MOI.ConstraintFunction(), ci) - - n_dims = length(cf.constants) - param_constants = zeros(T, n_dims) - - for term in param_array - vi = term.scalar_term.variable - - if !isnan(updated_parameters[p_idx(vi)]) - param_constants[term.output_index] = - term.scalar_term.coefficient * - (updated_parameters[p_idx(vi)] - parameters[p_idx(vi)]) - end - end - - if param_constants != zeros(T, n_dims) - MOI.modify( - optimizer, - ci, - MOI.VectorConstantChange(cf.constants + param_constants), - ) - end - - return ci -end - function update_parameters!(model::Optimizer) update_parametric_affine_constraints!(model) + update_parametric_vector_affine_constraints!(model) update_parametric_quadratic_constraints!(model) update_parametric_affine_objective!(model) update_parametric_quadratic_objective!(model) - # update_parameter_in_vector_affine_constraints!(model) # Update parameters and put NaN to indicate that the parameter has been # updated diff --git a/src/utils.jl b/src/utils.jl index 649503de..b8a0e8ed 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -134,9 +134,9 @@ function ParametricAffineFunction( return ParametricAffineFunction{T}( p, v, - zero(T), f.constant, zero(T), + zero(T), ) end @@ -190,6 +190,102 @@ function delta_parametric_constant( return delta_constant end +function count_vector_affine_terms_types( + terms::Vector{MOI.VectorAffineTerm{T}}, +) where {T} + num_vars = 0 + num_params = 0 + for term in terms + if is_variable(term.scalar_term.variable) + num_vars += 1 + else + num_params += 1 + end + end + return num_vars, num_params +end + +function split_affine_terms(terms::Vector{MOI.VectorAffineTerm{T}}) where {T} + num_v, num_p = count_vector_affine_terms_types(terms) + v = Vector{MOI.VectorAffineTerm{T}}(undef, num_v) + p = Vector{MOI.VectorAffineTerm{T}}(undef, num_p) + i_v = 1 + i_p = 1 + for term in terms + if is_variable(term.scalar_term.variable) + v[i_v] = term + i_v += 1 + else + p[i_p] = term + i_p += 1 + end + end + return v, p +end + +function ParametricVectorAffineFunction( + f::MOI.VectorAffineFunction{T} +) where {T} + v, p = split_affine_terms(f.terms) + return ParametricVectorAffineFunction{T}( + p, + v, + copy(f.constants), + zeros(T, length(f.constants)), + zeros(T, length(f.constants)), + ) +end + +function original_function(f::ParametricVectorAffineFunction{T}) where {T} + return MOI.VectorAffineFunction{T}( + vcat(f.p, f.v), + f.c, + ) +end + +function current_function(f::ParametricVectorAffineFunction{T}) where {T} + return MOI.VectorAffineFunction{T}( + f.v, + f.current_constant, + ) +end + +function update_cache!( + f::ParametricVectorAffineFunction{T}, + model::Optimizer, +) where {T} + f.current_constant = parametric_constant(model, f) + return nothing +end + +function parametric_constant( + model::Optimizer, + f::ParametricVectorAffineFunction{T}, +) where {T} + # do not add set_function here + param_constant = copy(f.c) + for term in f.p + param_constant[term.output_index] += term.scalar_term.coefficient * + model.parameters[p_idx(term.scalar_term.variable)] + end + return param_constant +end + +function delta_parametric_constant( + model::Optimizer, + f::ParametricVectorAffineFunction{T}, +) where {T} + delta_constant = zeros(T, length(f.c)) + for term in f.p + p = p_idx(term.scalar_term.variable) + if !isnan(model.updated_parameters[p]) + delta_constant[term.output_index] += term.scalar_term.coefficient * + (model.updated_parameters[p] - model.parameters[p]) + end + end + return delta_constant +end + function count_scalar_quadratic_terms_types( terms::Vector{MOI.ScalarQuadraticTerm{T}}, ) where {T} @@ -487,30 +583,3 @@ function quadratic_constraint_cache_map_check( # inner_idex .∈ cached_constraints is used return idx ∈ cached_constraints end - -# Vector Affine -function separate_possible_terms_and_calculate_parameter_constant( - model::Optimizer, - f::MOI.VectorAffineFunction{T}, - set::S, -) where {T,S<:MOI.AbstractVectorSet} - vars = MOI.VectorAffineTerm{T}[] - params = MOI.VectorAffineTerm{T}[] - n_dims = length(f.constants) - param_constants = zeros(T, n_dims) - for term in f.terms - oi = term.output_index - - if is_variable_in_model(model, term.scalar_term.variable) - push!(vars, term) - elseif is_parameter_in_model(model, term.scalar_term.variable) - push!(params, term) - param_constants[oi] += - term.scalar_term.coefficient * - model.parameters[p_idx(term.scalar_term.variable)] - else - error("Constraint uses a variable that is not in the model") - end - end - return vars, params, param_constants -end From a396c08d1387dcdddc7149fca3b05920e6db110d Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 14:04:11 -0300 Subject: [PATCH 05/13] improve obj handling --- src/ParametricOptInterface.jl | 77 ++++++++--------------------------- src/duals.jl | 2 +- test/moi_tests.jl | 3 ++ 3 files changed, 21 insertions(+), 61 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index e6d921b1..190d3fa4 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -183,12 +183,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # Clever cache of data (at most one can be !== nothing) affine_objective_cache::Union{Nothing,ParametricAffineFunction{T}} quadratic_objective_cache::Union{Nothing,ParametricQuadraticFunction{T}} - original_objective_cache::Union{ - Nothing, - MOI.VariableIndex, - MOI.ScalarAffineFunction{T}, - MOI.ScalarQuadraticFunction{T}, - } + original_objective_cache::MOI.Utilities.ObjectiveContainer{T} # Store parametric expressions for product of variables quadratic_objective_cache_product::Dict{ Tuple{MOI.VariableIndex,MOI.VariableIndex}, @@ -248,7 +243,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # objective nothing, nothing, - nothing, + # nothing, + MOI.Utilities.ObjectiveContainer{T}(), Dict{ Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, @@ -292,7 +288,7 @@ function MOI.is_empty(model::Optimizer) # obj model.affine_objective_cache === nothing && model.quadratic_objective_cache === nothing && - model.original_objective_cache === nothing && + MOI.is_empty(model.original_objective_cache) && isempty(model.quadratic_objective_cache_product) && # isempty(model.vector_affine_constraint_cache) && @@ -323,7 +319,7 @@ function MOI.empty!(model::Optimizer{T}) where {T} # obj model.affine_objective_cache = nothing model.quadratic_objective_cache = nothing - model.original_objective_cache = nothing + MOI.empty!(model.original_objective_cache) empty!(model.quadratic_objective_cache_product) # empty!(model.vector_affine_constraint_cache) @@ -482,7 +478,6 @@ function MOI.modify( error("Parametric constraint cannot be modified") end MOI.modify(model.optimizer, c, chg) - MOI.Utilities.modify_function!(model.original_objective_cache, chg) return end @@ -500,7 +495,7 @@ function MOI.modify( error("Parametric objective cannot be modified") end MOI.modify(model.optimizer, c, chg) - MOI.Utilities.modify_function!(model.original_objective_cache, chg) + MOI.modify(model.original_objective_cache, c, chg) return end @@ -727,51 +722,11 @@ function MOI.get(model::Optimizer, attr::MOI.ObjectiveSense) end function MOI.get(model::Optimizer{T}, attr::MOI.ObjectiveFunctionType) where {T} - if model.affine_objective_cache !== nothing - return MOI.ScalarAffineFunction{T} - elseif model.quadratic_objective_cache !== nothing - return MOI.ScalarQuadraticFunction{T} - end - return typeof(model.original_objective_cache) -end - -function MOI.get( - model::Optimizer, - attr::MOI.ObjectiveFunction{MOI.VariableIndex}, -) - if !(typeof(model.original_objective_cache) <: MOI.VariableIndex) - error("Objective function is of type $(typeof(model.original_objective_cache))") - end - return model.original_objective_cache -end - -function MOI.get( - model::Optimizer, - attr::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}, -) where {T} - if model.affine_objective_cache !== nothing - return original_function(model.affine_objective_cache) - end - if !(typeof(model.original_objective_cache) <: MOI.ScalarAffineFunction{T}) - error("Objective function is of type $(typeof(model.original_objective_cache))") - end - return model.original_objective_cache + return MOI.get(model.original_objective_cache, attr) end -function MOI.get( - model::Optimizer, - attr::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}, -) where {T} - if model.quadratic_objective_cache !== nothing - return original_function(model.quadratic_objective_cache) - end - if !( - typeof(model.original_objective_cache) <: - MOI.ScalarQuadraticFunction{T} - ) - error("Objective function is of type $(typeof(model.original_objective_cache))") - end - return model.original_objective_cache +function MOI.get(model::Optimizer, attr::MOI.ObjectiveFunction) + return MOI.get(model.original_objective_cache, attr) end function MOI.get(model::Optimizer, attr::MOI.ResultCount) @@ -1148,10 +1103,10 @@ function MOI.set( return model.constraints_interpretation = value end -function empty_objective_function_caches!(model::Optimizer) +function empty_objective_function_caches!(model::Optimizer{T}) where {T} model.affine_objective_cache = nothing model.quadratic_objective_cache = nothing - model.original_objective_cache = nothing + model.original_objective_cache = MOI.Utilities.ObjectiveContainer{T}() return end @@ -1174,7 +1129,7 @@ function MOI.set( ) model.affine_objective_cache = pf end - model.original_objective_cache = deepcopy(f) + MOI.set(model.original_objective_cache, attr, f) return end @@ -1206,7 +1161,7 @@ function MOI.set( ) model.quadratic_objective_cache = pf end - model.original_objective_cache = f + MOI.set(model.original_objective_cache, attr, f) return end @@ -1221,7 +1176,7 @@ function MOI.set( error("Variable not in the model") end MOI.set(model.optimizer, attr, model.variables[v]) - model.original_objective_cache = v + MOI.set(model.original_objective_cache, attr, v) return end @@ -1382,6 +1337,7 @@ end function MOI.delete(model::Optimizer, v::MOI.VariableIndex) delete!(model.variables, v) MOI.delete(model.optimizer, v) + MOI.delete(model.original_objective_cache, v) return end @@ -1469,7 +1425,8 @@ function set_quadratic_product_in_obj!(model::Optimizer{T}) where {T} elseif model.quadratic_objective_cache !== nothing current_function(model.quadratic_objective_cache) else - model.original_objective_cache + F = MOI.get(model.original_objective_cache, MOI.ObjectiveFunctionType()) + MOI.get(model.original_objective_cache, MOI.ObjectiveFunction{F}) end F = typeof(f) diff --git a/src/duals.jl b/src/duals.jl index d0164c68..5e05ee55 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -136,7 +136,7 @@ function MOI.get( if !is_additive(model, cp) error("Cannot compute the dual of a multiplicative parameter") end - return model.dual_value_of_parameters[p_val(ci)] + return model.dual_value_of_parameters[p_val(cp)] end function is_additive(model::Optimizer, cp::MOI.ConstraintIndex) diff --git a/test/moi_tests.jl b/test/moi_tests.jl index 3aa4a779..fe845800 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -266,6 +266,9 @@ function test_moi_ipopt() # - CachingOptimizer does not throw if optimizer not attached "test_model_copy_to_UnsupportedAttribute", "test_model_copy_to_UnsupportedConstraint", + # - POI throws a ErrorException if user tries to modify parametric + # functions + "test_objective_get_ObjectiveFunction_ScalarAffineFunction" ], ) return From f547ed7aabf5baca803fdbf25b3c47afb2c65f4b Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 14:21:26 -0300 Subject: [PATCH 06/13] fix typo --- src/ParametricOptInterface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 190d3fa4..a15ef043 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -1426,7 +1426,7 @@ function set_quadratic_product_in_obj!(model::Optimizer{T}) where {T} current_function(model.quadratic_objective_cache) else F = MOI.get(model.original_objective_cache, MOI.ObjectiveFunctionType()) - MOI.get(model.original_objective_cache, MOI.ObjectiveFunction{F}) + MOI.get(model.original_objective_cache, MOI.ObjectiveFunction{F}()) end F = typeof(f) From 226f762cbeb9219f3633df2522262b0f478ff0d9 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 16:03:49 -0300 Subject: [PATCH 07/13] fix quadratic and typos --- docs/src/manual.md | 2 +- src/ParametricOptInterface.jl | 10 +--------- src/utils.jl | 27 +++++++++++++++++---------- test/moi_tests.jl | 5 ++--- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/docs/src/manual.md b/docs/src/manual.md index f7303482..696aff6d 100644 --- a/docs/src/manual.md +++ b/docs/src/manual.md @@ -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 diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index a15ef043..d2ca7905 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -418,17 +418,9 @@ struct ListOfPureVariableIndices <: MOI.AbstractModelAttribute end struct ListOfParameterIndices <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::ListOfPureVariableIndices) - return _all_variables(model) -end -function MOI.get(model::Optimizer, ::ListOfParameterIndices) - return _all_parameters(model) -end - -function _all_variables(model::Optimizer) return collect(keys(model.variables)) end - -function _all_parameters(model::Optimizer) +function MOI.get(model::Optimizer, ::ListOfParameterIndices) return collect(keys(model.parameters)) end diff --git a/src/utils.jl b/src/utils.jl index b8a0e8ed..6fc84bbe 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -453,18 +453,25 @@ function delta_parametric_constant( end for term in f.pp p1 = p_idx(term.variable_1) - delta_1 = if !isnan(model.updated_parameters[p1]) - model.updated_parameters[p1] - model.parameters[p1] - else - 0.0 - end p2 = p_idx(term.variable_2) - delta_2 = if !isnan(model.updated_parameters[p2]) - model.updated_parameters[p2] - model.parameters[p2] - else - 0.0 + isnan_1 = isnan(model.updated_parameters[p1]) + isnan_2 = isnan(model.updated_parameters[p2]) + if !isnan_1 || !isnan_2 + new_1 = ifelse( + isnan_1, + model.parameters[p1], + model.updated_parameters[p1], + ) + new_2 = ifelse( + isnan_2, + model.parameters[p2], + model.updated_parameters[p2], + ) + delta_constant += term.coefficient * + ( + new_1 * new_2 - model.parameters[p1] * model.parameters[p2] + ) end - delta_constant += term.coefficient * delta_1 * delta_2 end return delta_constant end diff --git a/test/moi_tests.jl b/test/moi_tests.jl index fe845800..048efcc2 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -1318,7 +1318,6 @@ function test_qp_objective_parameter_times_parameter() opt_in = MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), ipopt) optimizer = POI.Optimizer(opt_in) - A = [0.0 1.0; 1.0 0.0] a = [1.0, 1.0] x = MOI.add_variables(optimizer, 2) for x_i in x @@ -1327,7 +1326,7 @@ function test_qp_objective_parameter_times_parameter() y, cy = MOI.add_constrained_variable(optimizer, POI.Parameter(1)) z, cz = MOI.add_constrained_variable(optimizer, POI.Parameter(1)) quad_terms = MOI.ScalarQuadraticTerm{Float64}[] - push!(quad_terms, MOI.ScalarQuadraticTerm(A[1, 2], y, z)) + push!(quad_terms, MOI.ScalarQuadraticTerm(1.0, y, z)) objective_function = MOI.ScalarQuadraticFunction( quad_terms, MOI.ScalarAffineTerm.(a, x), @@ -1347,7 +1346,7 @@ function test_qp_objective_parameter_times_parameter() atol = ATOL, ) err = ErrorException( - "Cannot calculate the dual of a multiplicative parameter", + "Cannot compute the dual of a multiplicative parameter", ) @test_throws err MOI.get(optimizer, MOI.ConstraintDual(), cy) @test_throws err MOI.get(optimizer, MOI.ConstraintDual(), cz) From f700e45736651f7d2622fa34013223986d5e5bf0 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 16:10:53 -0300 Subject: [PATCH 08/13] format --- src/ParametricOptInterface.jl | 142 ++++++++++++++++------------------ src/duals.jl | 23 ++---- src/update_parameters.jl | 34 +++++--- src/utils.jl | 79 +++++++------------ 4 files changed, 124 insertions(+), 154 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index d2ca7905..e25bdf7e 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -13,7 +13,8 @@ const PARAMETER_INDEX_THRESHOLD = Int64(4_611_686_018_427_387_904) # div(typemax @enum ConstraintsInterpretationCode ONLY_CONSTRAINTS ONLY_BOUNDS BOUNDS_AND_CONSTRAINTS -const SIMPLE_SCALAR_SETS{T} = Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}} +const SIMPLE_SCALAR_SETS{T} = + Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}} """ Parameter(val::Float64) @@ -193,7 +194,9 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # vector affine function data # vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{T}}} # Clever cache of data (inner key) - vector_affine_constraint_cache::DoubleDict{ParametricVectorAffineFunction{T}} + vector_affine_constraint_cache::DoubleDict{ + ParametricVectorAffineFunction{T}, + } # multiplicative_parameters::Set{Int64} @@ -269,33 +272,33 @@ include("update_parameters.jl") function MOI.is_empty(model::Optimizer) return MOI.is_empty(model.optimizer) && - isempty(model.parameters) && - isempty(model.parameters_name) && - isempty(model.updated_parameters) && - isempty(model.variables) && - model.last_variable_index_added == 0 && - model.last_parameter_index_added == PARAMETER_INDEX_THRESHOLD && - # affine ctr - model.last_affine_added == 0 && - isempty(model.affine_outer_to_inner) && - isempty(model.affine_constraint_cache) && - isempty(model.affine_constraint_cache_set) && - # quad ctr - model.last_quad_add_added == 0 && - isempty(model.quadratic_outer_to_inner) && - isempty(model.quadratic_constraint_cache) && - isempty(model.quadratic_constraint_cache_set) && - # obj - model.affine_objective_cache === nothing && - model.quadratic_objective_cache === nothing && - MOI.is_empty(model.original_objective_cache) && - isempty(model.quadratic_objective_cache_product) && - # - isempty(model.vector_affine_constraint_cache) && - # - isempty(model.multiplicative_parameters) && - isempty(model.dual_value_of_parameters) && - model.number_of_parameters_in_model == 0 + isempty(model.parameters) && + isempty(model.parameters_name) && + isempty(model.updated_parameters) && + isempty(model.variables) && + model.last_variable_index_added == 0 && + model.last_parameter_index_added == PARAMETER_INDEX_THRESHOLD && + # affine ctr + model.last_affine_added == 0 && + isempty(model.affine_outer_to_inner) && + isempty(model.affine_constraint_cache) && + isempty(model.affine_constraint_cache_set) && + # quad ctr + model.last_quad_add_added == 0 && + isempty(model.quadratic_outer_to_inner) && + isempty(model.quadratic_constraint_cache) && + isempty(model.quadratic_constraint_cache_set) && + # obj + model.affine_objective_cache === nothing && + model.quadratic_objective_cache === nothing && + MOI.is_empty(model.original_objective_cache) && + isempty(model.quadratic_objective_cache_product) && + # + isempty(model.vector_affine_constraint_cache) && + # + isempty(model.multiplicative_parameters) && + isempty(model.dual_value_of_parameters) && + model.number_of_parameters_in_model == 0 end function MOI.empty!(model::Optimizer{T}) where {T} @@ -466,7 +469,7 @@ function MOI.modify( chg::MOI.ScalarCoefficientChange{T}, ) where {F,S,T} if haskey(model.quadratic_constraint_cache, c) || - haskey(model.affine_constraint_cache, c) + haskey(model.affine_constraint_cache, c) error("Parametric constraint cannot be modified") end MOI.modify(model.optimizer, c, chg) @@ -476,14 +479,11 @@ end function MOI.modify( model::Optimizer, c::MOI.ObjectiveFunction{F}, - chg::Union{ - MOI.ScalarConstantChange{T}, - MOI.ScalarCoefficientChange{T}, - }, + chg::Union{MOI.ScalarConstantChange{T},MOI.ScalarCoefficientChange{T}}, ) where {F<:MathOptInterface.AbstractScalarFunction,T} if model.quadratic_objective_cache !== nothing || - model.affine_objective_cache !== nothing || - !isempty(model.quadratic_objective_cache_product) + model.affine_objective_cache !== nothing || + !isempty(model.quadratic_objective_cache_product) error("Parametric objective cannot be modified") end MOI.modify(model.optimizer, c, chg) @@ -592,7 +592,6 @@ function MOI.get( attr::MOI.ConstraintName, c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}, ) where {T,S} - if haskey(model.affine_outer_to_inner, c) inner_ci = model.affine_outer_to_inner[c] # This SAF constraint was transformed into variable bound @@ -1114,11 +1113,7 @@ function MOI.set( else pf = ParametricAffineFunction(f) update_cache!(pf, model) - MOI.set( - model.optimizer, - attr, - current_function(pf), - ) + MOI.set(model.optimizer, attr, current_function(pf)) model.affine_objective_cache = pf end MOI.set(model.original_objective_cache, attr, f) @@ -1141,15 +1136,16 @@ function MOI.set( func = current_function(pf) MOI.set( model.optimizer, - MOI.ObjectiveFunction{ - (is_affine(func) ? - MOI.ScalarAffineFunction{T} : - MOI.ScalarQuadraticFunction{T}) - }(), + MOI.ObjectiveFunction{( + is_affine(func) ? MOI.ScalarAffineFunction{T} : + MOI.ScalarQuadraticFunction{T} + )}(), # func, - (is_affine(func) ? + ( + is_affine(func) ? MOI.ScalarAffineFunction(func.affine_terms, func.constant) : - func), + func + ), ) model.quadratic_objective_cache = pf end @@ -1268,11 +1264,7 @@ function add_constraint_with_parameters_on_function( pf = ParametricVectorAffineFunction(f) # cache_set_constant!(pf, set) # there is no constant is vector sets update_cache!(pf, model) - inner_ci = MOI.add_constraint( - model.optimizer, - current_function(pf), - set, - ) + inner_ci = MOI.add_constraint(model.optimizer, current_function(pf), set) model.vector_affine_constraint_cache[inner_ci] = pf return inner_ci end @@ -1290,7 +1282,11 @@ function add_constraint_with_parameters_on_function( func = current_function(pf) f_quad = if !is_affine(func) fq = func - inner_ci = MOI.Utilities.normalize_and_add_constraint(model.optimizer, fq, s) + inner_ci = MOI.Utilities.normalize_and_add_constraint( + model.optimizer, + fq, + s, + ) model.last_quad_add_added += 1 outer_ci = MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}( model.last_quad_add_added, @@ -1299,7 +1295,11 @@ function add_constraint_with_parameters_on_function( # model.outer_to_inner_map[outer_ci] = inner_ci else fa = MOI.ScalarAffineFunction(func.affine_terms, func.constant) - inner_ci = MOI.Utilities.normalize_and_add_constraint(model.optimizer, fa, s) + inner_ci = MOI.Utilities.normalize_and_add_constraint( + model.optimizer, + fa, + s, + ) model.last_quad_add_added += 1 outer_ci = MOI.ConstraintIndex{MOI.ScalarQuadraticFunction{T},S}( model.last_quad_add_added, @@ -1347,10 +1347,7 @@ end function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, -) where { - F<:Union{MOI.VariableIndex,MOI.VectorOfVariables}, - S<:MOI.AbstractSet, -} +) where {F<:Union{MOI.VariableIndex,MOI.VectorOfVariables},S<:MOI.AbstractSet} MOI.delete(model.optimizer, c) return end @@ -1358,10 +1355,7 @@ end function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, -) where { - F<:MOI.VectorAffineFunction, - S<:MOI.AbstractSet, -} +) where {F<:MOI.VectorAffineFunction,S<:MOI.AbstractSet} MOI.delete(model.optimizer, c) deleteat!(model.vector_affine_constraint_cache, c) return @@ -1428,8 +1422,10 @@ function set_quadratic_product_in_obj!(model::Optimizer{T}) where {T} for ((x, y), fparam) in model.quadratic_objective_cache_product # x, y = prod_var evaluated_fparam = _evaluate_parametric_expression(model, fparam) - push!(quadratic_prods_vector, - MOI.ScalarQuadraticTerm(evaluated_fparam, x, y)) + push!( + quadratic_prods_vector, + MOI.ScalarQuadraticTerm(evaluated_fparam, x, y), + ) end f_new = if F <: MOI.VariableIndex @@ -1439,18 +1435,10 @@ function set_quadratic_product_in_obj!(model::Optimizer{T}) where {T} 0.0, ) elseif F <: MOI.ScalarAffineFunction{T} - MOI.ScalarQuadraticFunction( - quadratic_prods_vector, - f.terms, - f.constant, - ) - elseif F <: MOI.ScalarQuadraticFunction{T} + MOI.ScalarQuadraticFunction(quadratic_prods_vector, f.terms, f.constant) + elseif F <: MOI.ScalarQuadraticFunction{T} quadratic_terms = vcat(f.quadratic_terms, quadratic_prods_vector) - MOI.ScalarQuadraticFunction( - quadratic_terms, - f.affine_terms, - f.constant, - ) + MOI.ScalarQuadraticFunction(quadratic_terms, f.affine_terms, f.constant) end MOI.set( diff --git a/src/duals.jl b/src/duals.jl index 5e05ee55..5b368d04 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -4,7 +4,8 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. function compute_dual_of_parameters!(model::Optimizer{T}) where {T} - model.dual_value_of_parameters = zeros(T, model.number_of_parameters_in_model) + 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) @@ -21,10 +22,7 @@ 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] # barrier for type instability - compute_parameters_in_ci!( - model, - affine_constraint_cache_inner, - ) + compute_parameters_in_ci!(model, affine_constraint_cache_inner) end return end @@ -34,10 +32,7 @@ function update_duals_from_vector_affine_constraints!(model::Optimizer) 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, - ) + compute_parameters_in_ci!(model, vector_affine_constraint_cache_inner) end return end @@ -47,10 +42,7 @@ function update_duals_from_quadratic_constraints!(model::Optimizer) quadratic_constraint_cache_inner = model.quadratic_constraint_cache[F, S] # barrier for type instability - compute_parameters_in_ci!( - model, - quadratic_constraint_cache_inner, - ) + compute_parameters_in_ci!(model, quadratic_constraint_cache_inner) end return end @@ -92,10 +84,7 @@ function compute_parameters_in_ci!( end # this one seem to be the same as the next -function update_duals_from_objective!( - model::Optimizer{T}, - pf, -) where {T} +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 diff --git a/src/update_parameters.jl b/src/update_parameters.jl index d1589624..604c4e70 100644 --- a/src/update_parameters.jl +++ b/src/update_parameters.jl @@ -44,7 +44,11 @@ end function update_parametric_affine_constraints!( model::Optimizer, affine_constraint_cache_inner::DoubleDictInner{F,S,V}, - affine_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, + affine_constraint_cache_set_inner::DoubleDictInner{ + F, + S, + MOI.AbstractScalarSet, + }, ) where {F,S<:SIMPLE_SCALAR_SETS{T},V} where {T} # cis = MOI.ConstraintIndex{F,S}[] # sets = S[] @@ -70,7 +74,11 @@ end function update_parametric_affine_constraints!( model::Optimizer, affine_constraint_cache_inner::DoubleDictInner{F,S,V}, - affine_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, + affine_constraint_cache_set_inner::DoubleDictInner{ + F, + S, + MOI.AbstractScalarSet, + }, ) where {F,S<:MOI.Interval{T},V} where {T} for (inner_ci, pf) in affine_constraint_cache_inner set = affine_constraint_cache_set_inner[inner_ci]::S @@ -111,7 +119,7 @@ function update_parametric_vector_affine_constraints!( MOI.modify( model.optimizer, inner_ci, - MOI.VectorConstantChange(pf.current_constant) + MOI.VectorConstantChange(pf.current_constant), ) end end @@ -120,7 +128,8 @@ end function update_parametric_quadratic_constraints!(model::Optimizer) for (F, S) in keys(model.quadratic_constraint_cache.dict) - quadratic_constraint_cache_inner = model.quadratic_constraint_cache[F, S] + quadratic_constraint_cache_inner = + model.quadratic_constraint_cache[F, S] quadratic_constraint_cache_set_inner = model.quadratic_constraint_cache_set[F, S] if !isempty(quadratic_constraint_cache_inner) @@ -139,10 +148,7 @@ function affine_build_change_and_up_param_func( pf::ParametricQuadraticFunction{T}, delta_terms, ) where {T} - changes = Vector{MOI.ScalarCoefficientChange}( - undef, - length(delta_terms), - ) + changes = Vector{MOI.ScalarCoefficientChange}(undef, length(delta_terms)) i = 1 for (var, coef) in delta_terms base_coef = pf.current_terms_with_p[var] @@ -157,7 +163,11 @@ end function update_parametric_quadratic_constraints!( model::Optimizer, quadratic_constraint_cache_inner::DoubleDictInner{F,S,V}, - quadratic_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, + quadratic_constraint_cache_set_inner::DoubleDictInner{ + F, + S, + MOI.AbstractScalarSet, + }, ) where {F,S<:SIMPLE_SCALAR_SETS{T},V} where {T} # cis = MOI.ConstraintIndex{F,S}[] # sets = S[] @@ -189,7 +199,11 @@ end function update_parametric_quadratic_constraints!( model::Optimizer, quadratic_constraint_cache_inner::DoubleDictInner{F,S,V}, - quadratic_constraint_cache_set_inner::DoubleDictInner{F,S,MOI.AbstractScalarSet}, + quadratic_constraint_cache_set_inner::DoubleDictInner{ + F, + S, + MOI.AbstractScalarSet, + }, ) where {F,S<:MOI.Interval{T},V} where {T} for (inner_ci, pf) in quadratic_constraint_cache_inner set = quadratic_constraint_cache_set_inner[inner_ci]::S diff --git a/src/utils.jl b/src/utils.jl index 6fc84bbe..9faedf66 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -56,9 +56,7 @@ function function_has_parameters(f::MOI.VectorAffineFunction{T}) where {T} return false end -function function_has_parameters( - f::MOI.ScalarQuadraticFunction{T}, -) where {T} +function function_has_parameters(f::MOI.ScalarQuadraticFunction{T}) where {T} return function_affine_terms_has_parameters(f.affine_terms) || function_quadratic_terms_has_parameters(f.quadratic_terms) end @@ -78,8 +76,7 @@ function function_quadratic_terms_has_parameters( quadratic_terms::Vector{MOI.ScalarQuadraticTerm{T}}, ) where {T} for term in quadratic_terms - if is_parameter(term.variable_1) || - is_parameter(term.variable_2) + if is_parameter(term.variable_1) || is_parameter(term.variable_2) return true end end @@ -127,31 +124,17 @@ function split_affine_terms(terms::Vector{MOI.ScalarAffineTerm{T}}) where {T} return v, p end -function ParametricAffineFunction( - f::MOI.ScalarAffineFunction{T} -) where {T} +function ParametricAffineFunction(f::MOI.ScalarAffineFunction{T}) where {T} v, p = split_affine_terms(f.terms) - return ParametricAffineFunction{T}( - p, - v, - f.constant, - zero(T), - zero(T), - ) + return ParametricAffineFunction{T}(p, v, f.constant, zero(T), zero(T)) end function original_function(f::ParametricAffineFunction{T}) where {T} - return MOI.ScalarAffineFunction{T}( - vcat(f.p, f.v), - f.c, - ) + return MOI.ScalarAffineFunction{T}(vcat(f.p, f.v), f.c) end function current_function(f::ParametricAffineFunction{T}) where {T} - return MOI.ScalarAffineFunction{T}( - f.v, - f.current_constant, - ) + return MOI.ScalarAffineFunction{T}(f.v, f.current_constant) end function update_cache!( @@ -183,7 +166,8 @@ function delta_parametric_constant( for term in f.p p = p_idx(term.variable) if !isnan(model.updated_parameters[p]) - delta_constant += term.coefficient * + delta_constant += + term.coefficient * (model.updated_parameters[p] - model.parameters[p]) end end @@ -224,7 +208,7 @@ function split_affine_terms(terms::Vector{MOI.VectorAffineTerm{T}}) where {T} end function ParametricVectorAffineFunction( - f::MOI.VectorAffineFunction{T} + f::MOI.VectorAffineFunction{T}, ) where {T} v, p = split_affine_terms(f.terms) return ParametricVectorAffineFunction{T}( @@ -237,17 +221,11 @@ function ParametricVectorAffineFunction( end function original_function(f::ParametricVectorAffineFunction{T}) where {T} - return MOI.VectorAffineFunction{T}( - vcat(f.p, f.v), - f.c, - ) + return MOI.VectorAffineFunction{T}(vcat(f.p, f.v), f.c) end function current_function(f::ParametricVectorAffineFunction{T}) where {T} - return MOI.VectorAffineFunction{T}( - f.v, - f.current_constant, - ) + return MOI.VectorAffineFunction{T}(f.v, f.current_constant) end function update_cache!( @@ -265,7 +243,8 @@ function parametric_constant( # do not add set_function here param_constant = copy(f.c) for term in f.p - param_constant[term.output_index] += term.scalar_term.coefficient * + param_constant[term.output_index] += + term.scalar_term.coefficient * model.parameters[p_idx(term.scalar_term.variable)] end return param_constant @@ -279,7 +258,8 @@ function delta_parametric_constant( for term in f.p p = p_idx(term.scalar_term.variable) if !isnan(model.updated_parameters[p]) - delta_constant[term.output_index] += term.scalar_term.coefficient * + delta_constant[term.output_index] += + term.scalar_term.coefficient * (model.updated_parameters[p] - model.parameters[p]) end end @@ -347,7 +327,7 @@ function split_quadratic_terms( end function ParametricQuadraticFunction( - f::MOI.ScalarQuadraticFunction{T} + f::MOI.ScalarQuadraticFunction{T}, ) where {T} v, p = split_affine_terms(f.affine_terms) pv, pp, vv = split_quadratic_terms(f.quadratic_terms) @@ -405,11 +385,7 @@ function current_function(f::ParametricQuadraticFunction{T}) where {T} for (v, c) in f.affine_data_np push!(affine, MOI.ScalarAffineTerm{T}(c, v)) end - return MOI.ScalarQuadraticFunction{T}( - f.vv, - affine, - f.current_constant, - ) + return MOI.ScalarQuadraticFunction{T}(f.vv, affine, f.current_constant) end function update_cache!( @@ -433,7 +409,8 @@ function parametric_constant( end for term in f.pp param_constant += - term.coefficient * model.parameters[p_idx(term.variable_1)] * + term.coefficient * + model.parameters[p_idx(term.variable_1)] * model.parameters[p_idx(term.variable_2)] end return param_constant @@ -447,7 +424,8 @@ function delta_parametric_constant( for term in f.p p = p_idx(term.variable) if !isnan(model.updated_parameters[p]) - delta_constant += term.coefficient * + delta_constant += + term.coefficient * (model.updated_parameters[p] - model.parameters[p]) end end @@ -467,10 +445,9 @@ function delta_parametric_constant( model.parameters[p2], model.updated_parameters[p2], ) - delta_constant += term.coefficient * - ( - new_1 * new_2 - model.parameters[p1] * model.parameters[p2] - ) + delta_constant += + term.coefficient * + (new_1 * new_2 - model.parameters[p1] * model.parameters[p2]) end end return delta_constant @@ -485,8 +462,8 @@ function parametric_affine_terms( # remember a variable may appear more than once in pv for term in f.pv base = get(param_terms_dict, term.variable_2, zero(T)) - param_terms_dict[term.variable_2] = base + term.coefficient * - model.parameters[p_idx(term.variable_1)] + param_terms_dict[term.variable_2] = + base + term.coefficient * model.parameters[p_idx(term.variable_1)] end # by definition affine data only contains variables that appear in pv for (var, coef) in f.affine_data @@ -506,7 +483,9 @@ function delta_parametric_affine_terms( p = p_idx(term.variable_1) if !isnan(model.updated_parameters[p]) base = get(delta_terms_dict, term.variable_2, zero(T)) - delta_terms_dict[term.variable_2] = base + term.coefficient * + delta_terms_dict[term.variable_2] = + base + + term.coefficient * (model.updated_parameters[p] - model.parameters[p]) end end From 3d73b71a088ce1b8a162fd27d762dbb3882282ec Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 16:14:08 -0300 Subject: [PATCH 09/13] format test --- test/jump_tests.jl | 68 ++++++++++++++++++++++++---------------------- test/moi_tests.jl | 7 ++--- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index a78be2bf..1ffb4698 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -109,26 +109,26 @@ function test_jump_constraintfunction_getter() c2 = @constraint(model, conq, sum(x .* p) >= 1) c3 = @constraint(model, conqa, sum(x .* p) + x[1]^2 + x[1] + p[1] >= 1) @test MOI.Utilities.canonical( - MOI.get(model, MOI.ConstraintFunction(), c1) - ) ≈ - MOI.Utilities.canonical( - MOI.ScalarAffineFunction{Float64}( - [ - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), - MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(2)), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), - ), - MOI.ScalarAffineTerm{Float64}( - 1.0, - MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), - ), - ], - 0.0, - ) - ) - @test canonical_compare(MOI.get(model, MOI.ConstraintFunction(), c2), + MOI.get(model, MOI.ConstraintFunction(), c1), + ) ≈ MOI.Utilities.canonical( + MOI.ScalarAffineFunction{Float64}( + [ + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), + MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(2)), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), + ), + MOI.ScalarAffineTerm{Float64}( + 1.0, + MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 2), + ), + ], + 0.0, + ), + ) + @test canonical_compare( + MOI.get(model, MOI.ConstraintFunction(), c2), MOI.ScalarQuadraticFunction{Float64}( [ MOI.ScalarQuadraticTerm{Float64}( @@ -144,9 +144,10 @@ function test_jump_constraintfunction_getter() ], [], 0.0, - ) + ), ) - @test canonical_compare(MOI.get(model, MOI.ConstraintFunction(), c3), + @test canonical_compare( + MOI.get(model, MOI.ConstraintFunction(), c3), MOI.ScalarQuadraticFunction{Float64}( [ MOI.ScalarQuadraticTerm{Float64}( @@ -173,11 +174,12 @@ function test_jump_constraintfunction_getter() ), ], 0.0, - ) + ), ) o1 = @objective(model, Min, sum(x) + sum(p)) F = MOI.get(model, MOI.ObjectiveFunctionType()) - @test canonical_compare(MOI.get(model, MOI.ObjectiveFunction{F}()), + @test canonical_compare( + MOI.get(model, MOI.ObjectiveFunction{F}()), MOI.ScalarAffineFunction{Float64}( [ MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)), @@ -192,7 +194,7 @@ function test_jump_constraintfunction_getter() ), ], 0.0, - ) + ), ) o2 = @objective(model, Min, sum(x .* p) + 2) F = MOI.get(model, MOI.ObjectiveFunctionType()) @@ -216,7 +218,8 @@ function test_jump_constraintfunction_getter() @test canonical_compare(f, f_ref) o3 = @objective(model, Min, sum(x .* p) + x[1]^2 + x[1] + p[1]) F = MOI.get(model, MOI.ObjectiveFunctionType()) - @test canonical_compare(MOI.get(model, MOI.ObjectiveFunction{F}()), + @test canonical_compare( + MOI.get(model, MOI.ObjectiveFunction{F}()), MOI.ScalarQuadraticFunction{Float64}( [ MOI.ScalarQuadraticTerm{Float64}( @@ -243,7 +246,7 @@ function test_jump_constraintfunction_getter() ), ], 0.0, - ) + ), ) return end @@ -874,10 +877,11 @@ function test_jump_direct_qp_objective() 2index(p) + 3, ) optimize!(model) - @test canonical_compare(MOI.get( - backend(model), - POI.QuadraticObjectiveCoef(), - (index(x), index(y)), + @test canonical_compare( + MOI.get( + backend(model), + POI.QuadraticObjectiveCoef(), + (index(x), index(y)), ), MOI.ScalarAffineFunction{Int64}( MOI.ScalarAffineTerm{Int64}[MOI.ScalarAffineTerm{Int64}( @@ -885,7 +889,7 @@ function test_jump_direct_qp_objective() MOI.VariableIndex(POI.PARAMETER_INDEX_THRESHOLD + 1), )], 3, - ) + ), ) @test objective_value(model) ≈ 32 / 3 atol = ATOL @test value(x) ≈ 4 / 3 atol = ATOL diff --git a/test/moi_tests.jl b/test/moi_tests.jl index 048efcc2..cf5bd381 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -268,7 +268,7 @@ function test_moi_ipopt() "test_model_copy_to_UnsupportedConstraint", # - POI throws a ErrorException if user tries to modify parametric # functions - "test_objective_get_ObjectiveFunction_ScalarAffineFunction" + "test_objective_get_ObjectiveFunction_ScalarAffineFunction", ], ) return @@ -1345,9 +1345,8 @@ function test_qp_objective_parameter_times_parameter() 0.0, atol = ATOL, ) - err = ErrorException( - "Cannot compute the dual of a multiplicative parameter", - ) + err = + ErrorException("Cannot compute the dual of a multiplicative parameter") @test_throws err MOI.get(optimizer, MOI.ConstraintDual(), cy) @test_throws err MOI.get(optimizer, MOI.ConstraintDual(), cz) MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(2.0)) From f4681666465a89ac224c3b1923a7e22d2cb471e3 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 16:34:44 -0300 Subject: [PATCH 10/13] remove useless functions --- src/utils.jl | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 9faedf66..c6034b04 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -522,10 +522,6 @@ function cache_set_constant!( return end -function is_affine(::MOI.ScalarAffineFunction) - return true -end - function is_affine(f::MOI.ScalarQuadraticFunction) if isempty(f.quadratic_terms) return true @@ -533,17 +529,6 @@ function is_affine(f::MOI.ScalarQuadraticFunction) return false end -function is_affine(::ParametricAffineFunction) - return true -end - -function is_affine(f::ParametricQuadraticFunction) - if isempty(f.vv) - return true - end - return false -end - function cache_multiplicative_params!( model::Optimizer{T}, f::ParametricQuadraticFunction{T}, From ef0c55259f5de53e3114cd02339eaeece2685bd4 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 17:23:27 -0300 Subject: [PATCH 11/13] increase coverage and fir cubic coefs --- src/ParametricOptInterface.jl | 13 +++++--- test/jump_tests.jl | 58 +++++++++++++++++++++++++++++++++++ test/moi_tests.jl | 7 +++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index e25bdf7e..158852cb 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -190,6 +190,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, } + quadratic_objective_cache_product_changed::Bool # vector affine function data # vector_constraint_cache::DoubleDict{Vector{MOI.VectorAffineTerm{T}}} @@ -252,6 +253,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer Tuple{MOI.VariableIndex,MOI.VariableIndex}, MOI.AbstractFunction, }(), + false, # vec affine # DoubleDict{Vector{MOI.VectorAffineTerm{T}}}(), DoubleDict{ParametricVectorAffineFunction{T}}(), @@ -1402,9 +1404,6 @@ end function set_quadratic_product_in_obj!(model::Optimizer{T}) where {T} n = length(model.quadratic_objective_cache_product) - if n == 0 - return - end f = if model.affine_objective_cache !== nothing current_function(model.affine_objective_cache) @@ -1462,6 +1461,7 @@ function MOI.set( x2 = aux end delete!(model.quadratic_objective_cache_product, (x1, x2)) + model.quadratic_objective_cache_product_changed = true return end @@ -1477,6 +1477,7 @@ function MOI.set( x2 = aux end model.quadratic_objective_cache_product[(x1, x2)] = f_param + model.quadratic_objective_cache_product_changed = true return end @@ -1567,7 +1568,11 @@ function MOI.optimize!(model::Optimizer) if !isempty(model.updated_parameters) update_parameters!(model) end - if !isempty(model.quadratic_objective_cache_product) + if ( + !isempty(model.quadratic_objective_cache_product) || + model.quadratic_objective_cache_product_changed + ) + model.quadratic_objective_cache_product_changed = false set_quadratic_product_in_obj!(model) end MOI.optimize!(model.optimizer) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 1ffb4698..31217420 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -765,6 +765,7 @@ function test_jump_direct_vector_parameter_affine_nonnegatives() ) optimizer = POI.Optimizer(cached) model = direct_model(optimizer) + set_silent(model) @variable(model, x) @variable(model, y) @variable(model, t in POI.Parameter(5)) @@ -800,6 +801,7 @@ function test_jump_direct_vector_parameter_affine_nonpositives() ) optimizer = POI.Optimizer(cached) model = direct_model(optimizer) + set_silent(model) @variable(model, x) @variable(model, y) @variable(model, t in POI.Parameter(5)) @@ -840,6 +842,7 @@ function test_jump_direct_soc_parameters() ) optimizer = POI.Optimizer(cached) model = direct_model(optimizer) + set_silent(model) @variable(model, x) @variable(model, y) @variable(model, t) @@ -870,6 +873,10 @@ function test_jump_direct_qp_objective() @constraint(model, 2x + y <= 4) @constraint(model, x + 2y <= 4) @objective(model, Max, (x^2 + y^2) / 2) + optimize!(model) + @test objective_value(model) ≈ 16 / 9 atol = ATOL + @test value(x) ≈ 4 / 3 atol = ATOL + @test value(y) ≈ 4 / 3 atol = ATOL MOI.set( backend(model), POI.QuadraticObjectiveCoef(), @@ -899,6 +906,37 @@ function test_jump_direct_qp_objective() @test objective_value(model) ≈ 128 / 9 atol = ATOL @test value(x) ≈ 4 / 3 atol = ATOL @test value(y) ≈ 4 / 3 atol = ATOL + MOI.set( + backend(model), + POI.QuadraticObjectiveCoef(), + (index(x), index(y)), + nothing, + ) + optimize!(model) + @test objective_value(model) ≈ 16 / 9 atol = ATOL + @test value(x) ≈ 4 / 3 atol = ATOL + @test value(y) ≈ 4 / 3 atol = ATOL + # now in reverse order + MOI.set( + backend(model), + POI.QuadraticObjectiveCoef(), + (index(y), index(x)), + 2index(p) + 3, + ) + optimize!(model) + @test objective_value(model) ≈ 128 / 9 atol = ATOL + @test value(x) ≈ 4 / 3 atol = ATOL + @test value(y) ≈ 4 / 3 atol = ATOL + MOI.set( + backend(model), + POI.QuadraticObjectiveCoef(), + (index(y), index(x)), + nothing, + ) + optimize!(model) + @test objective_value(model) ≈ 16 / 9 atol = ATOL + @test value(x) ≈ 4 / 3 atol = ATOL + @test value(y) ≈ 4 / 3 atol = ATOL return end @@ -947,3 +985,23 @@ function test_jump_direct_rsoc_constraints() @test value(y) ≈ 2 atol = ATOL return end + +function test_jump_quadratic_interval() + optimizer = POI.Optimizer(GLPK.Optimizer()) + # model = direct_model(optimizer) + model = Model(() -> optimizer) + MOI.set(model, MOI.Silent(), true) + @variable(model, x >= 0) + @variable(model, y >= 0) + @variable(model, p in POI.Parameter(10.0)) + @constraint(model, -4 <= x - p * y <= -4) + @objective(model, Min, x + y) + optimize!(model) + @test value(x) ≈ 0 atol = ATOL + @test value(y) ≈ 0.4 atol = ATOL + MOI.set(model, POI.ParameterValue(), p, 20.0) + optimize!(model) + @test value(x) ≈ 0 atol = ATOL + @test value(y) ≈ 0.2 atol = ATOL + return +end diff --git a/test/moi_tests.jl b/test/moi_tests.jl index cf5bd381..ed79fa9d 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -94,6 +94,8 @@ function test_basic_tests() @test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MIN_SENSE @test MOI.get(optimizer, MOI.VariableName(), x[1]) == "" @test MOI.get(optimizer, MOI.ConstraintName(), c1) == "" + MOI.set(optimizer, MOI.ConstraintName(), c1, "ctr123") + @test MOI.get(optimizer, MOI.ConstraintName(), c1) == "ctr123" return end @@ -278,6 +280,7 @@ function test_moi_ListOfConstraintTypesPresent() N = 10 ipopt = Ipopt.Optimizer() model = POI.Optimizer(ipopt) + MOI.set(model, MOI.Silent(), true) x = MOI.add_variables(model, N / 2) y = first.( @@ -575,6 +578,7 @@ function test_vector_parameter_affine_nonnegatives() SCS.Optimizer(), ) model = POI.Optimizer(cached) + MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) t, ct = MOI.add_constrained_variable(model, POI.Parameter(5)) @@ -633,6 +637,7 @@ function test_vector_parameter_affine_nonpositives() Float64, ) model = POI.Optimizer(cached) + MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) y = MOI.add_variable(model) t, ct = MOI.add_constrained_variable(model, POI.Parameter(5)) @@ -696,6 +701,7 @@ function test_vector_soc_parameters() SCS.Optimizer(), ) model = POI.Optimizer(cached) + MOI.set(model, MOI.Silent(), true) x, y, t = MOI.add_variables(model, 3) p, cp = MOI.add_constrained_variable(model, POI.Parameter(0)) MOI.set( @@ -781,6 +787,7 @@ function test_vector_soc_no_parameters() Float64, ) model = POI.Optimizer(cached) + MOI.set(model, MOI.Silent(), true) x, y, t = MOI.add_variables(model, 3) MOI.set( model, From 6b567f3e49b1bc02ba89fdf2e070add699918071 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 17:34:00 -0300 Subject: [PATCH 12/13] add test --- test/jump_tests.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 31217420..8d00e839 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1005,3 +1005,14 @@ function test_jump_quadratic_interval() @test value(y) ≈ 0.2 atol = ATOL return end + +function test_affine_parametric_objective() + model = Model(() -> POI.Optimizer(GLPK.Optimizer())) + @variable(model, p in POI.Parameter(1.0)) + @variable(model, 0 <= x <= 1) + @objective(model, Max, (p + 0.5) * x) + optimize!(model) + @test value(x) ≈ 1.0 + @test objective_value(model) ≈ 1.5 + @test value(objective_function(model)) ≈ 1.5 +end From f1c4afd4d1942b33110e06634b75a352c8ac2e35 Mon Sep 17 00:00:00 2001 From: joaquim Date: Sun, 16 Apr 2023 17:49:59 -0300 Subject: [PATCH 13/13] add quad interval cached --- test/jump_tests.jl | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 8d00e839..a732df77 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -994,7 +994,8 @@ function test_jump_quadratic_interval() @variable(model, x >= 0) @variable(model, y >= 0) @variable(model, p in POI.Parameter(10.0)) - @constraint(model, -4 <= x - p * y <= -4) + @variable(model, q in POI.Parameter(4.0)) + @constraint(model, 0 <= x - p * y + q <= 0) @objective(model, Min, x + y) optimize!(model) @test value(x) ≈ 0 atol = ATOL @@ -1003,6 +1004,44 @@ function test_jump_quadratic_interval() optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.2 atol = ATOL + MOI.set(model, POI.ParameterValue(), q, 6.0) + optimize!(model) + @test value(x) ≈ 0 atol = ATOL + @test value(y) ≈ 0.3 atol = ATOL + return +end + +function test_jump_quadratic_interval_cached() + cached = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + GLPK.Optimizer(), + ), + Float64, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + # optimizer = POI.Optimizer(GLPK.Optimizer()) + # model = direct_model(optimizer) + # model = Model(() -> optimizer) + # MOI.set(model, MOI.Silent(), true) + @variable(model, x >= 0) + @variable(model, y >= 0) + @variable(model, p in POI.Parameter(10.0)) + @variable(model, q in POI.Parameter(4.0)) + @constraint(model, 0 <= x - p * y + q <= 0) + @objective(model, Min, x + y) + optimize!(model) + @test value(x) ≈ 0 atol = ATOL + @test value(y) ≈ 0.4 atol = ATOL + MOI.set(model, POI.ParameterValue(), p, 20.0) + optimize!(model) + @test value(x) ≈ 0 atol = ATOL + @test value(y) ≈ 0.2 atol = ATOL + MOI.set(model, POI.ParameterValue(), q, 6.0) + optimize!(model) + @test value(x) ≈ 0 atol = ATOL + @test value(y) ≈ 0.3 atol = ATOL return end