diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 3b02cccb..504b04e2 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -88,7 +88,7 @@ function MOI.supports( else MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}() end - return supported_obj(F) && + return supported_objective(F) && MOI.supports(optimizer.dual_problem.dual_model, attr) end @@ -159,15 +159,16 @@ function MOI.modify( constant = -constant end vi = obj_change.variable - if haskey(primal_dual_map.constrained_var_idx, vi) - ci_primal, index = primal_dual_map.constrained_var_idx[vi] - ci_dual = primal_dual_map.constrained_var_dual[ci_primal] + if haskey(primal_dual_map.primal_convar_to_primal_convarcon_and_index, vi) + ci_primal, index = + primal_dual_map.primal_convar_to_primal_convarcon_and_index[vi] + ci_dual = primal_dual_map.primal_convarcon_to_dual_con[ci_primal] if ci_dual === NO_CONSTRAINT return end constant = -constant else - ci_dual = primal_dual_map.primal_var_dual_con[vi] + ci_dual = primal_dual_map.primal_var_to_dual_con[vi] index = 1 end _change_constant( @@ -188,7 +189,8 @@ function MOI.supports_add_constrained_variables( MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}, ) - # If `_dual_set_type(MOI.Reals)` was `MOI.Zeros`, we would not need this method as special case of the one below + # If `_dual_set_type(MOI.Reals)` was `MOI.Zeros`, + # we would not need this method as special case of the one below end function MOI.supports_add_constrained_variables( @@ -235,129 +237,56 @@ function MOI.empty!(optimizer::DualOptimizer) return end -# MOI.get auxiliary functions -function get_ci_dual_problem(optimizer::DualOptimizer, vi::MOI.VariableIndex) - return optimizer.dual_problem.primal_dual_map.primal_var_dual_con[vi] -end - -function get_ci_dual_problem(optimizer::DualOptimizer, ci::MOI.ConstraintIndex) - return optimizer.dual_problem.primal_dual_map.primal_con_dual_con[ci] -end - -function get_primal_ci_constant( - optimizer::DualOptimizer, - ci::MOI.ConstraintIndex, -) - return first(get_primal_ci_constants(optimizer, ci)) -end - -function get_primal_ci_constants( - optimizer::DualOptimizer, - ci::MOI.ConstraintIndex, -) - return optimizer.dual_problem.primal_dual_map.primal_con_constants[ci] -end - -function get_vi_dual_problem(optimizer::DualOptimizer, ci::MOI.ConstraintIndex) - return first(get_vis_dual_problem(optimizer, ci)) -end - -function get_vis_dual_problem(optimizer::DualOptimizer, ci::MOI.ConstraintIndex) - return optimizer.dual_problem.primal_dual_map.primal_con_dual_var[ci] -end - function MOI.get(optimizer::DualOptimizer, ::MOI.SolverName) name = MOI.get(optimizer.dual_problem.dual_model, MOI.SolverName()) return "Dual model with $name attached" end -function _get( - ::DualOptimizer{T}, - ::MOI.AbstractConstraintAttribute, - ::MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}, - ::MOI.ConstraintIndex{Nothing,Nothing}, -) where {T} - return zero(T) -end - -function _get( - optimizer::DualOptimizer, - attr::MOI.AbstractConstraintAttribute, - ::MOI.ConstraintIndex, - ci::MOI.ConstraintIndex, -) - return MOI.get(optimizer.dual_problem.dual_model, attr, ci) -end - -function _get( - optimizer::DualOptimizer{T}, - ::MOI.AbstractConstraintAttribute, - ci_primal::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Zeros}, - ::MOI.ConstraintIndex{Nothing,Nothing}, -) where {T} - n = MOI.output_dimension( - optimizer.dual_problem.primal_dual_map.constrained_var_zero[ci_primal], - ) - return zeros(T, n) -end - -function _get_at_index( - optimizer::DualOptimizer, - attr::MOI.AbstractConstraintAttribute, - ci_primal::MOI.ConstraintIndex{MOI.VariableIndex}, - ci_dual::MOI.ConstraintIndex, - idx, -) - @assert isone(idx) - return _get(optimizer, attr, ci_primal, ci_dual) -end - -function _get_at_index( - optimizer::DualOptimizer, - attr::MOI.AbstractConstraintAttribute, - ci_primal::MOI.ConstraintIndex{MOI.VectorOfVariables}, - ci_dual::MOI.ConstraintIndex, - idx, -) - return _get(optimizer, attr, ci_primal, ci_dual)[idx] -end - function MOI.get( - optimizer::DualOptimizer, + optimizer::DualOptimizer{T}, ::MOI.VariablePrimal, vi::MOI.VariableIndex, -) +) where {T} primal_dual_map = optimizer.dual_problem.primal_dual_map - if haskey(primal_dual_map.constrained_var_idx, vi) - ci_primal, idx = primal_dual_map.constrained_var_idx[vi] - ci_dual = primal_dual_map.constrained_var_dual[ci_primal] - return _get_at_index( - optimizer, - MOI.ConstraintDual(), - ci_primal, - ci_dual, - idx, - ) + if haskey(primal_dual_map.primal_convar_to_primal_convarcon_and_index, vi) + ci_primal, idx = + primal_dual_map.primal_convar_to_primal_convarcon_and_index[vi] + ci_dual = primal_dual_map.primal_convarcon_to_dual_con[ci_primal] + if ci_dual === NO_CONSTRAINT + return zero(T) + elseif ci_dual isa MOI.ConstraintIndex{<:MOI.AbstractVectorFunction} + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintDual(), + ci_dual, + )[idx] + else + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintDual(), + ci_dual, + ) + end else return -MOI.get( optimizer.dual_problem.dual_model, MOI.ConstraintDual(), - get_ci_dual_problem(optimizer, vi), + primal_dual_map.primal_var_to_dual_con[vi], ) end end function MOI.get( optimizer::DualOptimizer, - attr::MOI.ConstraintDual, + ::MOI.ConstraintDual, ci::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} primal_dual_map = optimizer.dual_problem.primal_dual_map - if haskey(primal_dual_map.constrained_var_dual, ci) - ci_dual = primal_dual_map.constrained_var_dual[ci] + if haskey(primal_dual_map.primal_convarcon_to_dual_con, ci) + ci_dual = primal_dual_map.primal_convarcon_to_dual_con[ci] if ci_dual === NO_CONSTRAINT return MOI.Utilities.eval_variables( - primal_dual_map.constrained_var_zero[ci], + primal_dual_map.primal_convarcon_to_dual_function[ci], ) do vi return MOI.get( optimizer.dual_problem.dual_model, @@ -380,7 +309,7 @@ function MOI.get( return MOI.get( optimizer.dual_problem.dual_model, MOI.VariablePrimal(), - get_vi_dual_problem(optimizer, ci), + primal_dual_map.primal_con_to_dual_var_vec[ci][], ) end end @@ -391,11 +320,11 @@ function MOI.get( ci::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet} primal_dual_map = optimizer.dual_problem.primal_dual_map - if haskey(primal_dual_map.constrained_var_dual, ci) - ci_dual = primal_dual_map.constrained_var_dual[ci] + if haskey(primal_dual_map.primal_convarcon_to_dual_con, ci) + ci_dual = primal_dual_map.primal_convarcon_to_dual_con[ci] if ci_dual === NO_CONSTRAINT return MOI.Utilities.eval_variables( - primal_dual_map.constrained_var_zero[ci], + primal_dual_map.primal_convarcon_to_dual_function[ci], ) do vi return MOI.get( optimizer.dual_problem.dual_model, @@ -407,37 +336,42 @@ function MOI.get( return MOI.get( optimizer.dual_problem.dual_model, MOI.ConstraintPrimal(), - primal_dual_map.constrained_var_dual[ci], + primal_dual_map.primal_convarcon_to_dual_con[ci], ) else return MOI.get.( optimizer.dual_problem.dual_model, MOI.VariablePrimal(), - get_vis_dual_problem(optimizer, ci), + primal_dual_map.primal_con_to_dual_var_vec[ci], ) end end function MOI.get( - optimizer::DualOptimizer, + optimizer::DualOptimizer{T}, ::MOI.ConstraintPrimal, ci::MOI.ConstraintIndex{F,S}, -) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} +) where {T,F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} primal_dual_map = optimizer.dual_problem.primal_dual_map - if haskey(primal_dual_map.constrained_var_dual, ci) - return _get( - optimizer, - MOI.ConstraintDual(), - ci, - primal_dual_map.constrained_var_dual[ci], - ) + if haskey(primal_dual_map.primal_convarcon_to_dual_con, ci) + ci_dual = primal_dual_map.primal_convarcon_to_dual_con[ci] + if ci_dual === NO_CONSTRAINT + return zero(T) + else + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintDual(), + ci_dual, + ) + end else - primal_ci_constant = get_primal_ci_constant(optimizer, ci) + primal_ci_constant = + primal_dual_map.primal_con_to_primal_constants_vec[ci][1] # If it has no key then there is no dual constraint - if !haskey(primal_dual_map.primal_con_dual_con, ci) + if !haskey(primal_dual_map.primal_con_to_dual_convarcon, ci) return -primal_ci_constant end - ci_dual_problem = get_ci_dual_problem(optimizer, ci) + ci_dual_problem = primal_dual_map.primal_con_to_dual_convarcon[ci] return MOI.get( optimizer.dual_problem.dual_model, MOI.ConstraintDual(), @@ -452,21 +386,29 @@ function MOI.get( ci::MOI.ConstraintIndex{F,S}, ) where {T,F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet} primal_dual_map = optimizer.dual_problem.primal_dual_map - if haskey(primal_dual_map.constrained_var_dual, ci) - return _get( - optimizer, - MOI.ConstraintDual(), - ci, - primal_dual_map.constrained_var_dual[ci], - ) + if haskey(primal_dual_map.primal_convarcon_to_dual_con, ci) + ci_dual = primal_dual_map.primal_convarcon_to_dual_con[ci] + if ci_dual === NO_CONSTRAINT + n = MOI.output_dimension( + primal_dual_map.primal_convarcon_to_dual_function[ci], + ) + return zeros(T, n) + else + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintDual(), + ci_dual, + ) + end else # If it has no key then there is no dual constraint - if !haskey(primal_dual_map.primal_con_dual_con, ci) + if !haskey(primal_dual_map.primal_con_to_dual_convarcon, ci) # The number of dual variable associated with the primal constraint is the ci dimension - ci_dimension = length(get_vis_dual_problem(optimizer, ci)) + ci_dimension = + length(primal_dual_map.primal_con_to_dual_var_vec[ci]) return zeros(T, ci_dimension) end - ci_dual_problem = get_ci_dual_problem(optimizer, ci) + ci_dual_problem = primal_dual_map.primal_con_to_dual_convarcon[ci] return MOI.get( optimizer.dual_problem.dual_model, MOI.ConstraintDual(), @@ -476,12 +418,12 @@ function MOI.get( end function MOI.get(optimizer::DualOptimizer, ::MOI.TerminationStatus) - return dual_status( + return _dual_status( MOI.get(optimizer.dual_problem.dual_model, MOI.TerminationStatus()), ) end -function dual_status(term::MOI.TerminationStatusCode) +function _dual_status(term::MOI.TerminationStatusCode) if term == MOI.INFEASIBLE return MOI.DUAL_INFEASIBLE elseif term == MOI.DUAL_INFEASIBLE diff --git a/src/add_dual_cone_constraint.jl b/src/add_dual_cone_constraint.jl index dd0f4ce7..64303beb 100644 --- a/src/add_dual_cone_constraint.jl +++ b/src/add_dual_cone_constraint.jl @@ -3,44 +3,60 @@ # 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 add_dual_cone_constraint( +""" + _add_dual_cone_constraint(primal_model, dual_model, ci) + +Add variables to the dual models. These variables are associated with primal +constraints. If the primal constraint is of type +function-in-`MOI.Zeros` or function-in-`MOI.EqualTo(zero(T))`, then the dual +variable are unconstrained. Otherwise, the dual variables are created as +constrained variables in the dual model. + +The function returns a tuple: the first element is a vector dual variables, +and the second element is the constraint index of the dual constraint. If the +dual variable in not a constrained variable, the second element is `nothing`. +""" +function _add_dual_cone_constraint( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} vi, con_index = MOI.add_constrained_variable( dual_model, - _dual_set(get_set(primal_model, ci)), + _dual_set(MOI.get(primal_model, MOI.ConstraintSet(), ci)), ) return [vi], con_index end -function add_dual_cone_constraint( +function _add_dual_cone_constraint( dual_model::MOI.ModelLike, - primal_model::MOI.ModelLike, + ::MOI.ModelLike, ci::MOI.ConstraintIndex{F,MOI.EqualTo{T}}, ) where {T,F<:MOI.AbstractScalarFunction} vi = MOI.add_variable(dual_model) return [vi], nothing end -function add_dual_cone_constraint( +function _add_dual_cone_constraint( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet} return MOI.add_constrained_variables( dual_model, - _dual_set(get_set(primal_model, ci)), + _dual_set(MOI.get(primal_model, MOI.ConstraintSet(), ci)), ) end -function add_dual_cone_constraint( +function _add_dual_cone_constraint( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{F,MOI.Zeros}, ) where {F<:MOI.AbstractVectorFunction} # Add as many variables as the dimension of the constraint - vis = MOI.add_variables(dual_model, get_ci_row_dimension(primal_model, ci)) + dim = MOI.output_dimension( + MOI.get(primal_model, MOI.ConstraintFunction(), ci), + ) + vis = MOI.add_variables(dual_model, dim) return vis, nothing end diff --git a/src/constrained_variables.jl b/src/constrained_variables.jl index 7b9dfdca..7e86cf15 100644 --- a/src/constrained_variables.jl +++ b/src/constrained_variables.jl @@ -3,7 +3,7 @@ # 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 add_constrained_variables( +function _select_constrained_variables( dual_problem, primal_model, variable_parameters, @@ -15,21 +15,12 @@ function add_constrained_variables( ) params = Set(variable_parameters) for S in single_or_vector_variables_types - if S <: MOI.AbstractVectorSet - _add_constrained_variables( - dual_problem.primal_dual_map, - primal_model, - S, - params, - ) - elseif S <: MOI.AbstractScalarSet - _add_constrained_variable( - dual_problem.primal_dual_map, - primal_model, - S, - params, - ) - end + _select_constrained_variables( + dual_problem.primal_dual_map, + primal_model, + S, + params, + ) end return end @@ -37,7 +28,7 @@ end const NO_CONSTRAINT = MOI.ConstraintIndex{Nothing,Nothing}(0) # Function barrier for the type instability of `F` and `S`. -function _add_constrained_variables( +function _select_constrained_variables( m::PrimalDualMap, primal_model, ::Type{S}, @@ -49,22 +40,27 @@ function _add_constrained_variables( ) for ci in cis f = MOI.get(primal_model, MOI.ConstraintFunction(), ci) + # try to add variables as constrained variables if all( - vi -> !haskey(m.constrained_var_idx, vi) && !(vi in params), + # no element of the VectorOfVariables is a constrained variable + # and not a parameter + vi -> + !haskey(m.primal_convar_to_primal_convarcon_and_index, vi) && + !(vi in params), f.variables, ) for (i, vi) in enumerate(f.variables) - m.constrained_var_idx[vi] = (ci, i) + m.primal_convar_to_primal_convarcon_and_index[vi] = (ci, i) end # Placeholder to indicate this constraint is part of constrained variables, # it will be replaced later with a dual constraints - m.constrained_var_dual[ci] = NO_CONSTRAINT + m.primal_convarcon_to_dual_con[ci] = NO_CONSTRAINT end end return end -function _add_constrained_variable( +function _select_constrained_variables( m::PrimalDualMap, primal_model, ::Type{S}, @@ -76,15 +72,18 @@ function _add_constrained_variable( ) for ci in cis f = MOI.get(primal_model, MOI.ConstraintFunction(), ci) - if !haskey(m.constrained_var_idx, f) && !(f in params) + # no element of the VectorOfVariables is a constrained variable + # and not a parameter + if !haskey(m.primal_convar_to_primal_convarcon_and_index, f) && + !(f in params) set = MOI.get(primal_model, MOI.ConstraintSet(), ci) if !iszero(MOI.constant(set)) continue end - m.constrained_var_idx[f] = (ci, 1) + m.primal_convar_to_primal_convarcon_and_index[f] = (ci, 1) # Placeholder to indicate this constraint is part of constrained variables, # it will be replaced later with a dual constraints - m.constrained_var_dual[ci] = NO_CONSTRAINT + m.primal_convarcon_to_dual_con[ci] = NO_CONSTRAINT end end return diff --git a/src/dual_equality_constraints.jl b/src/dual_equality_constraints.jl index 44dd41ae..e6e5d133 100644 --- a/src/dual_equality_constraints.jl +++ b/src/dual_equality_constraints.jl @@ -3,12 +3,12 @@ # 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 add_dual_equality_constraints( +function _add_dual_equality_constraints( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, primal_dual_map::PrimalDualMap, dual_names::DualNames, - primal_objective::PrimalObjective{T}, + primal_objective::_PrimalObjective{T}, con_types::Vector{Tuple{Type,Type}}, variable_parameters::Vector{MOI.VariableIndex}, ) where {T} @@ -22,27 +22,28 @@ function add_dual_equality_constraints( non_parameter_variables = setdiff(all_variables, variable_parameters) # Loop at every constraint to collect the scalar affine terms in the - # `scalar_affine_terms` list. + # `scalar_affine_terms` list (a dics mapping variable index to + # a scalar affine function). # TODO: flip these signs a priorie instead of require post processing later - scalar_affine_terms = get_scalar_affine_terms( + scalar_affine_terms = _get_scalar_affine_terms( primal_model, - primal_dual_map.primal_con_dual_var, + primal_dual_map.primal_con_to_dual_var_vec, all_variables, con_types, T, ) # get constants (rhs) of dual constraints from primal objective coefficients - scalar_terms = get_scalar_terms(primal_objective) + scalar_terms = _get_dual_constraint_constants(primal_objective) # Collect affine terms of dual constraints that come from the quadratic # part of the primal objective function, and add them into # `scalar_affine_terms`. # These terms are added with flipped signs (because the sign will be flipped again). # TODO: unflip these signs - add_scalar_affine_terms_from_quad_obj( + _add_scalar_affine_terms_from_quad_obj( scalar_affine_terms, - primal_dual_map.primal_var_dual_quad_slack, + primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var, primal_objective, sense_change, ) @@ -50,15 +51,15 @@ function add_dual_equality_constraints( # terms from mixing variables and parameters # These terms are added with flipped signs (because the sign will be flipped again). # TODO: unflip these signs - add_scalar_affine_terms_from_quad_params( + _add_scalar_affine_terms_from_quad_params( scalar_affine_terms, - primal_dual_map.primal_parameter, + primal_dual_map.primal_parameter_to_dual_parameter, primal_objective, sense_change, ) # Constrained variables - for ci in keys(primal_dual_map.constrained_var_dual) + for ci in keys(primal_dual_map.primal_convarcon_to_dual_con) # Add constraints associated with constrained variables # These are constraints that will not be regular equality constraints # they will be function-in-set, where set is the dual set of the @@ -67,8 +68,8 @@ function add_dual_equality_constraints( _add_constrained_variable_constraint( dual_model, primal_model, - primal_dual_map.constrained_var_zero, - primal_dual_map.constrained_var_dual, + primal_dual_map.primal_convarcon_to_dual_function, + primal_dual_map.primal_convarcon_to_dual_con, ci, scalar_affine_terms, scalar_terms, @@ -80,7 +81,10 @@ function add_dual_equality_constraints( # Free variables for primal_vi in non_parameter_variables - if haskey(primal_dual_map.constrained_var_idx, primal_vi) + if haskey( + primal_dual_map.primal_convar_to_primal_convarcon_and_index, + primal_vi, + ) continue # constrained variable end # Add equality constraint @@ -100,7 +104,7 @@ function add_dual_equality_constraints( ) # Set constraint name with the name of the associated priaml variable if !is_empty(dual_names) - set_dual_constraint_name( + _set_dual_constraint_name( dual_model, primal_model, primal_vi, @@ -109,7 +113,7 @@ function add_dual_equality_constraints( ) end # Add primal variable to dual contraint to the link dictionary - push!(primal_dual_map.primal_var_dual_con, primal_vi => dual_ci) + primal_dual_map.primal_var_to_dual_con[primal_vi] = dual_ci end return scalar_affine_terms end @@ -224,7 +228,7 @@ function _add_constrained_variable_constraint( set_dual, ) if !is_empty(dual_names) - set_dual_constraint_name( + _set_dual_constraint_name( dual_model, primal_model, primal_vi, @@ -235,32 +239,37 @@ function _add_constrained_variable_constraint( return end -function add_scalar_affine_terms_from_quad_obj( +function _add_scalar_affine_terms_from_quad_obj( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_var_dual_quad_slack::Dict{MOI.VariableIndex,MOI.VariableIndex}, - primal_objective::PrimalObjective{T}, + primal_var_in_quad_obj_to_dual_slack_var::Dict{ + MOI.VariableIndex, + MOI.VariableIndex, + }, + primal_objective::_PrimalObjective{T}, sense_change::T, ) where {T} for term in primal_objective.obj.quadratic_terms if term.variable_1 == term.variable_2 - dual_vi = primal_var_dual_quad_slack[term.variable_1] - push_to_scalar_affine_terms!( + dual_vi = primal_var_in_quad_obj_to_dual_slack_var[term.variable_1] + _push_to_scalar_affine_terms!( scalar_affine_terms[term.variable_1], -sense_change * MOI.coefficient(term), dual_vi, ) else - dual_vi_1 = primal_var_dual_quad_slack[term.variable_1] - push_to_scalar_affine_terms!( + dual_vi_1 = + primal_var_in_quad_obj_to_dual_slack_var[term.variable_1] + _push_to_scalar_affine_terms!( scalar_affine_terms[term.variable_2], -sense_change * MOI.coefficient(term), dual_vi_1, ) - dual_vi_2 = primal_var_dual_quad_slack[term.variable_2] - push_to_scalar_affine_terms!( + dual_vi_2 = + primal_var_in_quad_obj_to_dual_slack_var[term.variable_2] + _push_to_scalar_affine_terms!( scalar_affine_terms[term.variable_1], -sense_change * MOI.coefficient(term), dual_vi_2, @@ -270,19 +279,22 @@ function add_scalar_affine_terms_from_quad_obj( return end -function add_scalar_affine_terms_from_quad_params( +function _add_scalar_affine_terms_from_quad_params( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_parameter::Dict{MOI.VariableIndex,MOI.VariableIndex}, - primal_objective::PrimalObjective{T}, + primal_parameter_to_dual_parameter::Dict{ + MOI.VariableIndex, + MOI.VariableIndex, + }, + primal_objective::_PrimalObjective{T}, sense_change::T, ) where {T} for (key, val) in primal_objective.quad_cross_parameters for term in val - dual_vi = primal_parameter[term.variable] - push_to_scalar_affine_terms!( + dual_vi = primal_parameter_to_dual_parameter[term.variable] + _push_to_scalar_affine_terms!( scalar_affine_terms[key], -sense_change * MOI.coefficient(term), dual_vi, @@ -291,7 +303,7 @@ function add_scalar_affine_terms_from_quad_params( end end -function set_dual_constraint_name( +function _set_dual_constraint_name( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, primal_vi::MOI.VariableIndex, @@ -307,9 +319,11 @@ function set_dual_constraint_name( return end -function get_scalar_terms(primal_objective::PrimalObjective{T}) where {T} +function _get_dual_constraint_constants( + primal_objective::_PrimalObjective{T}, +) where {T} scalar_terms = Dict{MOI.VariableIndex,T}() - for term in get_affine_terms(primal_objective) + for term in primal_objective.obj.affine_terms if haskey(scalar_terms, term.variable) scalar_terms[term.variable] += MOI.coefficient(term) else @@ -319,20 +333,24 @@ function get_scalar_terms(primal_objective::PrimalObjective{T}) where {T} return scalar_terms end -function fill_scalar_affine_terms!( +# function barrier +function _fill_scalar_affine_terms!( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_con_to_dual_var_vec::Dict{ + MOI.ConstraintIndex, + Vector{MOI.VariableIndex}, + }, primal_model::MOI.ModelLike, ::Type{F}, ::Type{S}, ) where {T,F,S} for ci in MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}()) - fill_scalar_affine_terms!( + _fill_scalar_affine_terms!( scalar_affine_terms, - primal_con_dual_var, + primal_con_to_dual_var_vec, primal_model, ci, ) @@ -340,9 +358,12 @@ function fill_scalar_affine_terms!( return end -function get_scalar_affine_terms( +function _get_scalar_affine_terms( primal_model::MOI.ModelLike, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_con_to_dual_var_vec::Dict{ + MOI.ConstraintIndex, + Vector{MOI.VariableIndex}, + }, variables::Vector{MOI.VariableIndex}, con_types::Vector{Tuple{Type,Type}}, ::Type{T}, @@ -352,9 +373,9 @@ function get_scalar_affine_terms( vi => MOI.ScalarAffineTerm{T}[] for vi in variables ) for (F, S) in con_types - fill_scalar_affine_terms!( + _fill_scalar_affine_terms!( scalar_affine_terms, - primal_con_dual_var, + primal_con_to_dual_var_vec, primal_model, F, S, @@ -363,7 +384,7 @@ function get_scalar_affine_terms( return scalar_affine_terms end -function push_to_scalar_affine_terms!( +function _push_to_scalar_affine_terms!( scalar_affine_terms::Vector{MOI.ScalarAffineTerm{T}}, affine_term::T, vi::MOI.VariableIndex, @@ -374,19 +395,22 @@ function push_to_scalar_affine_terms!( return end -function fill_scalar_affine_terms!( +function _fill_scalar_affine_terms!( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_con_to_dual_var_vec::Dict{ + MOI.ConstraintIndex, + Vector{MOI.VariableIndex}, + }, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}, ) where {T,S<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}} - moi_function = get_function(primal_model, ci) + moi_function = MOI.get(primal_model, MOI.ConstraintFunction(), ci) for term in moi_function.terms - dual_vi = primal_con_dual_var[ci][1] # In this case we only have one vi - push_to_scalar_affine_terms!( + dual_vi = primal_con_to_dual_var_vec[ci][1] # In this case we only have one vi + _push_to_scalar_affine_terms!( scalar_affine_terms[term.variable], MOI.coefficient(term), dual_vi, @@ -395,25 +419,28 @@ function fill_scalar_affine_terms!( return end -function fill_scalar_affine_terms!( +function _fill_scalar_affine_terms!( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_con_to_dual_var_vec::Dict{ + MOI.ConstraintIndex, + Vector{MOI.VariableIndex}, + }, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, ) where {T,S<:Union{MOI.GreaterThan{T},MOI.LessThan{T},MOI.EqualTo{T}}} - dual_var = get(primal_con_dual_var, ci, nothing) + dual_var = get(primal_con_to_dual_var_vec, ci, nothing) if dual_var === nothing # No variables created as the primal constraint is the constraint # of a constrained variable. Hence, its duality information goes to # the dual constraint associated to that primal variable. return end - moi_function = get_function(primal_model, ci) + moi_function = MOI.get(primal_model, MOI.ConstraintFunction(), ci) dual_vi = dual_var[1] # In this case we only have one vi - push_to_scalar_affine_terms!( + _push_to_scalar_affine_terms!( scalar_affine_terms[moi_function], one(T), dual_vi, @@ -421,22 +448,25 @@ function fill_scalar_affine_terms!( return end -function fill_scalar_affine_terms!( +function _fill_scalar_affine_terms!( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_con_to_dual_var_vec::Dict{ + MOI.ConstraintIndex, + Vector{MOI.VariableIndex}, + }, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{MOI.VectorAffineFunction{T},S}, ) where {T,S<:MOI.AbstractVectorSet} - moi_function = get_function(primal_model, ci) - set = get_set(primal_model, ci) + moi_function = MOI.get(primal_model, MOI.ConstraintFunction(), ci) + set = MOI.get(primal_model, MOI.ConstraintSet(), ci) for term in moi_function.terms - dual_vi = primal_con_dual_var[ci][term.output_index] + dual_vi = primal_con_to_dual_var_vec[ci][term.output_index] # term.output_index is the row of the MOI.VectorAffineFunction, # it corresponds to the dual variable associated with this constraint - push_to_scalar_affine_terms!( + _push_to_scalar_affine_terms!( scalar_affine_terms[term.scalar_term.variable], set_dot(term.output_index, set, T) * MOI.coefficient(term), dual_vi, @@ -445,27 +475,30 @@ function fill_scalar_affine_terms!( return end -function fill_scalar_affine_terms!( +function _fill_scalar_affine_terms!( scalar_affine_terms::Dict{ MOI.VariableIndex, Vector{MOI.ScalarAffineTerm{T}}, }, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_con_to_dual_var_vec::Dict{ + MOI.ConstraintIndex, + Vector{MOI.VariableIndex}, + }, primal_model::MOI.ModelLike, ci::MOI.ConstraintIndex{MOI.VectorOfVariables,S}, ) where {T,S<:MOI.AbstractVectorSet} - dual_vars = get(primal_con_dual_var, ci, nothing) + dual_vars = get(primal_con_to_dual_var_vec, ci, nothing) if dual_vars === nothing # No variables created as the primal constraint is the constraint # of a constrained variable. Hence, its duality information goes to # the dual constraint associated to that primal variable. return end - moi_function = get_function(primal_model, ci) - set = get_set(primal_model, ci) + moi_function = MOI.get(primal_model, MOI.ConstraintFunction(), ci) + set = MOI.get(primal_model, MOI.ConstraintSet(), ci) for (i, variable) in enumerate(moi_function.variables) dual_vi = dual_vars[i] - push_to_scalar_affine_terms!( + _push_to_scalar_affine_terms!( scalar_affine_terms[variable], set_dot(i, set, T) * one(T), dual_vi, @@ -474,33 +507,33 @@ function fill_scalar_affine_terms!( return end -struct CanonicalVector{T} <: AbstractVector{T} +struct _CanonicalVector{T} <: AbstractVector{T} index::Int n::Int end -Base.eltype(::Type{CanonicalVector{T}}) where {T} = T +Base.eltype(::Type{_CanonicalVector{T}}) where {T} = T -Base.length(v::CanonicalVector) = v.n +Base.length(v::_CanonicalVector) = v.n -Base.size(v::CanonicalVector) = (v.n,) +Base.size(v::_CanonicalVector) = (v.n,) -function Base.getindex(v::CanonicalVector{T}, i::Integer) where {T} +function Base.getindex(v::_CanonicalVector{T}, i::Integer) where {T} return convert(T, i == v.index) end # This is much faster than the default implementation that goes # through all entries even if only one is nonzero. function LinearAlgebra.dot( - x::CanonicalVector{T}, - y::CanonicalVector{T}, + x::_CanonicalVector{T}, + y::_CanonicalVector{T}, ) where {T} return convert(T, x.index == y.index) end function MOI.Utilities.triangle_dot( - x::CanonicalVector{T}, - y::CanonicalVector{T}, + x::_CanonicalVector{T}, + y::_CanonicalVector{T}, dim::Int, offset::Int, ) where {T} @@ -514,7 +547,7 @@ function MOI.Utilities.triangle_dot( end function set_dot(i::Integer, s::MOI.AbstractVectorSet, T::Type) - vec = CanonicalVector{T}(i, MOI.dimension(s)) + vec = _CanonicalVector{T}(i, MOI.dimension(s)) return MOI.Utilities.set_dot(vec, vec, s) end diff --git a/src/dual_model_variables.jl b/src/dual_model_variables.jl index ab43a11b..bbe6e19a 100644 --- a/src/dual_model_variables.jl +++ b/src/dual_model_variables.jl @@ -3,47 +3,8 @@ # 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. +# barrier function function _add_dual_vars_in_dual_cones( - dual_obj_affine_terms::Dict{MOI.VariableIndex,T}, - dual_model::MOI.ModelLike, - primal_model::MOI.ModelLike, - primal_dual_map::PrimalDualMap{T}, - dual_names::DualNames, - ::Type{F}, - ::Type{S}, -) where {T,F,S} - for ci in MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}()) - # If `F` not one of these two, we can skip the `in` check. - if (F === MOI.VectorOfVariables || F === MOI.VariableIndex) && - haskey(primal_dual_map.constrained_var_dual, ci) - continue - end - # Add dual variable to dual cone - # Fill a dual objective dictionary - # Fill the primal_con_dual_var dictionary - ci_dual = add_dual_variable( - dual_model, - primal_model, - dual_names, - primal_dual_map.primal_con_dual_var, - dual_obj_affine_terms, - ci, - ) - push_to_primal_con_dual_con!( - primal_dual_map.primal_con_dual_con, - ci, - ci_dual, - ) - push_to_primal_con_constants!( - primal_model, - primal_dual_map.primal_con_constants, - ci, - ) - end - return -end - -function add_dual_vars_in_dual_cones( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, primal_dual_map::PrimalDualMap{T}, @@ -65,124 +26,78 @@ function add_dual_vars_in_dual_cones( return dual_obj_affine_terms end -# Utils for primal_con_constants dict -function push_to_primal_con_constants!( - primal_model::MOI.ModelLike, - primal_con_constants::Dict{MOI.ConstraintIndex,Vector{T}}, - ci::MOI.ConstraintIndex{F,S}, -) where {T,F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} - push!(primal_con_constants, ci => get_scalar_term(primal_model, ci)) - return -end - -function push_to_primal_con_constants!( - primal_model::MOI.ModelLike, - primal_con_constants::Dict{MOI.ConstraintIndex,Vector{T}}, - ci::MOI.ConstraintIndex{F,S}, -) where {T,F<:MOI.AbstractVectorFunction,S<:MOI.AbstractVectorSet} - # No constants need to be passed to the DualOptimizer in this case, - # because the VectorSet's do not have constants inside them that - # will need to be used in result queries as some ScalarSet's might. - # Hence, don't need to push zero to the dict. - return -end - -# Utils for primal_con_dual_con dict -function push_to_primal_con_dual_con!( - primal_con_dual_con::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}, - ci::MOI.ConstraintIndex, - ci_dual::MOI.ConstraintIndex, -) - push!(primal_con_dual_con, ci => ci_dual) - return -end - -function push_to_primal_con_dual_con!( - primal_con_dual_con::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}, - ci::MOI.ConstraintIndex, - ci_dual::Nothing, -) - return # Don't put in the dict a nothing value -end - -# Utils for dual_obj_affine_terms dict -function push_to_dual_obj_aff_terms!( - primal_model::MOI.ModelLike, +function _add_dual_vars_in_dual_cones( dual_obj_affine_terms::Dict{MOI.VariableIndex,T}, - vi::MOI.VariableIndex, - func::MOI.AbstractFunction, - set::MOI.AbstractSet, - i::Int, -) where {T} - value = set_dot(i, set, T) * get_scalar_term(func, set, i) - if !iszero(value) # If value is different than 0 add to the dictionary - push!(dual_obj_affine_terms, vi => value) + dual_model::MOI.ModelLike, + primal_model::MOI.ModelLike, + primal_dual_map::PrimalDualMap{T}, + dual_names::DualNames, + ::Type{F}, + ::Type{S}, +) where {T,F,S} + for ci in MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}()) + # If `F` not one of these two, we can skip the `haskey` check. + if (F === MOI.VectorOfVariables || F === MOI.VariableIndex) && + haskey(primal_dual_map.primal_convarcon_to_dual_con, ci) + # primal constraints that are the main constraints of + # constrained variables have no dual variable associated + # bacause they associated with dual constraints + continue + end + # Add dual variable to dual cone + # Fill a dual objective dictionary + # Fill the primal_dual_map info + _add_dual_variable( + dual_model, + primal_model, + dual_names, + primal_dual_map, + dual_obj_affine_terms, + ci, + ) end return end -function push_to_dual_obj_aff_terms!( - ::MOI.ModelLike, - ::Dict{MOI.VariableIndex}, - ::MOI.VariableIndex, - ::MOI.VectorOfVariables, - ::MOI.AbstractVectorSet, - i::Int, -) - return # It is zero so don't push to the dual_obj_affine_terms -end - -function add_dual_variable( +function _add_dual_variable( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, dual_names::DualNames, - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}, + primal_dual_map::PrimalDualMap, dual_obj_affine_terms::Dict{MOI.VariableIndex,T}, ci::MOI.ConstraintIndex{F,S}, ) where {T,F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - vis, con_index = add_dual_cone_constraint(dual_model, primal_model, ci) + vis, con_index = _add_dual_cone_constraint(dual_model, primal_model, ci) # Add the map of the added dual variable to the relationated constraint - push!(primal_con_dual_var, ci => vis) + primal_dual_map.primal_con_to_dual_var_vec[ci] = vis # Get constraint name ci_name = MOI.get(primal_model, MOI.ConstraintName(), ci) # Add each vi to the dictionary - func = get_function(primal_model, ci) - set = get_set(primal_model, ci) + func = MOI.get(primal_model, MOI.ConstraintFunction(), ci) + set = MOI.get(primal_model, MOI.ConstraintSet(), ci) for (i, vi) in enumerate(vis) - push_to_dual_obj_aff_terms!( - primal_model, - dual_obj_affine_terms, - vi, - func, - set, - i, - ) - if !is_empty(dual_names) - set_dual_variable_name( - dual_model, - vi, - i, - ci_name, - dual_names.dual_variable_name_prefix, - ) + if !(F <: MOI.VectorOfVariables) + value = set_dot(i, set, T) * _get_normalized_constant(func, set, i) + if !iszero(value) + dual_obj_affine_terms[vi] = value + end + end + if !is_empty(dual_names) && !isempty(ci_name) + pre = dual_names.dual_variable_name_prefix + MOI.set(dual_model, MOI.VariableName(), vi, pre * ci_name * "_$i") end end - return con_index -end - -function set_dual_variable_name( - dual_model::MOI.ModelLike, - vi::MOI.VariableIndex, - i::Int, - ci_name::String, - prefix::String, -) - isempty(ci_name) && return - MOI.set(dual_model, MOI.VariableName(), vi, prefix * ci_name * "_$i") + if con_index !== nothing + primal_dual_map.primal_con_to_dual_convarcon[ci] = con_index + end + if F <: MOI.AbstractScalarFunction + primal_dual_map.primal_con_to_primal_constants_vec[ci] = + _get_normalized_constant(primal_model, ci) + end return end -function add_primal_parameter_vars( +function _add_primal_parameter_vars( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, primal_dual_map::PrimalDualMap{T}, @@ -193,87 +108,35 @@ function add_primal_parameter_vars( ) where {T} # if objective is ignored we only need parameters that appear in the # quadratic objective - if ignore_objective + parameters = if ignore_objective # only crossed terms (parameter times primal variable) of the objective # are required - added = Set{MOI.VariableIndex}() + to_add = Set{MOI.VariableIndex}() for vec in values(primal_objective.quad_cross_parameters) for term in vec - ind = term.variable - if ind in added - # do nothing - else - push!(added, ind) - vi = MOI.add_variable(dual_model) - push_to_primal_parameter!( - primal_dual_map.primal_parameter, - ind, - vi, - ) - # set name - if !is_empty(dual_names) - vi_name = MOI.get(primal_model, MOI.VariableName(), ind) - set_parameter_variable_name( - dual_model, - vi, - vi_name, - dual_names, - ) - end - end + push!(to_add, term.variable) end end - elseif length(variable_parameters) > 0 - vis = MOI.add_variables(dual_model, length(variable_parameters)) - for i in eachindex(vis) - push_to_primal_parameter!( - primal_dual_map.primal_parameter, - variable_parameters[i], - vis[i], - ) - if !is_empty(dual_names) - vi_name = MOI.get( - primal_model, - MOI.VariableName(), - variable_parameters[i], - ) - set_parameter_variable_name( - dual_model, - vis[i], - vi_name, - dual_names, - ) - end + collect(to_add) + else + variable_parameters + end + vis = MOI.add_variables(dual_model, length(parameters)) + for i in eachindex(vis) + primal_dual_map.primal_parameter_to_dual_parameter[parameters[i]] = + vis[i] + if !is_empty(dual_names) + vi_name = MOI.get(primal_model, MOI.VariableName(), parameters[i]) + prefix = + dual_names.parameter_name_prefix == "" ? "param_" : + dual_names.parameter_name_prefix + MOI.set(dual_model, MOI.VariableName(), vi, prefix * vi_name) end end return end -# Save mapping between primal parameter and dual parameter -function push_to_primal_parameter!( - primal_parameter::Dict{MOI.VariableIndex,MOI.VariableIndex}, - vi::MOI.VariableIndex, - vi_dual::MOI.VariableIndex, -) - push!(primal_parameter, vi => vi_dual) - return -end - -# Add name to parameter variable -function set_parameter_variable_name( - dual_model::MOI.ModelLike, - vi::MOI.VariableIndex, - vi_name::String, - dual_names, -) - prefix = - dual_names.parameter_name_prefix == "" ? "param_" : - dual_names.parameter_name_prefix - MOI.set(dual_model, MOI.VariableName(), vi, prefix * vi_name) - return -end - -function add_quadratic_slack_vars( +function _add_quadratic_slack_vars( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, primal_dual_map::PrimalDualMap{T}, @@ -286,48 +149,19 @@ function add_quadratic_slack_vars( for term in primal_objective.obj.quadratic_terms for ind in [term.variable_1, term.variable_2] if ind in added - #do nothing - else - push!(added, ind) - vi = MOI.add_variable(dual_model) - push_to_quad_slack!( - primal_dual_map.primal_var_dual_quad_slack, - ind, - vi, - ) - # set name - if !is_empty(dual_names) - vi_name = MOI.get(primal_model, MOI.VariableName(), ind) - set_quad_slack_name(dual_model, vi, vi_name, dual_names) - end + continue + end + push!(added, ind) + vi = MOI.add_variable(dual_model) + primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var[ind] = vi + if !is_empty(dual_names) + name = MOI.get(primal_model, MOI.VariableName(), ind) + prefix = + dual_names.quadratic_slack_name_prefix == "" ? + "quadslack_" : dual_names.quadratic_slack_name_prefix + MOI.set(dual_model, MOI.VariableName(), vi, prefix * name) end end end return end - -# Save mapping between primal variable and dual quadratic slack -function push_to_quad_slack!( - dual_quad_slack::Dict{MOI.VariableIndex,MOI.VariableIndex}, - vi::MOI.VariableIndex, - vi_dual::MOI.VariableIndex, -) - push!(dual_quad_slack, vi => vi_dual) - return -end - -# set name for dual quadratic slack -function set_quad_slack_name( - dual_model::MOI.ModelLike, - vi::MOI.VariableIndex, - vi_name::String, - dual_names, -) - prefix = if dual_names.quadratic_slack_name_prefix == "" - "quadslack_" - else - dual_names.quadratic_slack_name_prefix - end - MOI.set(dual_model, MOI.VariableName(), vi, prefix * vi_name) - return -end diff --git a/src/dualize.jl b/src/dualize.jl index ae2adf4c..cbb7cdf0 100644 --- a/src/dualize.jl +++ b/src/dualize.jl @@ -81,23 +81,23 @@ function dualize( supported_constraints(con_types) # Errors if constraint cant be dualized # Set the dual model objective sense - set_dual_model_sense(dual_problem.dual_model, primal_model) + _set_dual_model_sense(dual_problem.dual_model, primal_model) # Get primal objective in quadratic form # terms already split considering parameters primal_objective = - get_primal_objective(primal_model, variable_parameters, T) + _get_primal_objective(primal_model, variable_parameters, T) # Cache information of which primal variables are `constrained_variables` - # creating a map: constrained_var_idx, from original primal vars to original + # creating a map: primal_convar_to_primal_convarcon_and_index, from original primal vars to original # constrains and their internal index (if vector constrains), 1 otherwise. - # Also, initializes the map: `constrained_var_dual`, from original primal ci + # Also, initializes the map: `primal_convarcon_to_dual_con`, from original primal ci # to the dual constraint (latter is initilized as empty at this point). # If the Set constant of a MOI.VariableIndex-in-Set constraint is non-zero, # the respective primal variable will not be a constrained variable (with # respect to that constraint). if consider_constrained_variables - add_constrained_variables( + _select_constrained_variables( dual_problem, primal_model, variable_parameters, @@ -112,19 +112,19 @@ function dualize( # * creates the dual variable associated with the primal constraint # * fills `dual_obj_affine_terms`, since we are already looping through # all constraints that might have constants. - # * fills `primal_con_dual_var` mapping the primal constraint and the dual + # * fills `primal_con_to_dual_var_vec` mapping the primal constraint and the dual # variable - # * fills `primal_con_dual_con` to map the primal constraint to a + # * fills `primal_con_to_dual_convarcon` to map the primal constraint to a # constraint in the dual variable (if there is such constraint the dual # dual variable is said to be constrained). If the primal constraint's set # is EqualTo or Zeros, no constraint is added in the dual variable (the # dual variable is said to be free). - # * fills `primal_con_constants` mapping primal constraints to their + # * fills `primal_con_to_primal_constants_vec` mapping primal constraints to their # respective constants, which might be inside the set. # this map is used in `MOI.get(::DualOptimizer,::MOI.ConstraintPrimal,ci)` # that requires extra information in the case that the scalar set # constrains a constant (EqualtTo, GreaterThan, LessThan) - dual_obj_affine_terms = add_dual_vars_in_dual_cones( + dual_obj_affine_terms = _add_dual_vars_in_dual_cones( dual_problem.dual_model, primal_model, dual_problem.primal_dual_map, @@ -134,9 +134,9 @@ function dualize( # Creates variables in the dual problem that represent parameters in the # primal model. - # Fills `primal_parameter` mapping parameters in the primal to parameters + # Fills `primal_parameter_to_dual_parameter` mapping parameters in the primal to parameters # in the dual model. - add_primal_parameter_vars( + _add_primal_parameter_vars( dual_problem.dual_model, primal_model, dual_problem.primal_dual_map, @@ -149,9 +149,9 @@ function dualize( # Add dual slack variables that are associated to the primal quadratic terms # All primal variables that appear in the objective products will have an # associated dual slack variable that is created here. - # also, `primal_var_dual_quad_slack` is filled, mapping primal variables + # also, `primal_var_in_quad_obj_to_dual_slack_var` is filled, mapping primal variables # (that appear in quadritc objective terms) to dual "slack" variables. - add_quadratic_slack_vars( + _add_quadratic_slack_vars( dual_problem.dual_model, primal_model, dual_problem.primal_dual_map, @@ -166,8 +166,8 @@ function dualize( # Also, fills the link dictionary. # returns `scalar_affine_terms` # because the terms associated to variables that are parameters will be used - # in `get_dual_objective` - scalar_affine_terms = add_dual_equality_constraints( + # in `_get_dual_objective` + scalar_affine_terms = _add_dual_equality_constraints( dual_problem.dual_model, primal_model, dual_problem.primal_dual_map, @@ -181,7 +181,7 @@ function dualize( # do not add objective else # Fill Dual Objective Coefficients Struct - dual_objective = get_dual_objective( + dual_objective = _get_dual_objective( dual_problem, dual_obj_affine_terms, primal_objective, @@ -189,7 +189,7 @@ function dualize( variable_parameters, ) # Add dual objective to the model - set_dual_objective(dual_problem.dual_model, dual_objective) + _set_dual_objective(dual_problem.dual_model, dual_objective) end return dual_problem end diff --git a/src/objective_coefficients.jl b/src/objective_coefficients.jl index be5c04ba..f227be14 100644 --- a/src/objective_coefficients.jl +++ b/src/objective_coefficients.jl @@ -4,11 +4,11 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. """ - set_dual_model_sense!(dual_model::MOI.ModelLike, model::MOI.ModelLike) + _set_dual_model_sense(dual_model::MOI.ModelLike, model::MOI.ModelLike) Set the dual model objective sense. """ -function set_dual_model_sense( +function _set_dual_model_sense( dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, )::Nothing @@ -32,6 +32,7 @@ function _scalar_quadratic_function( ) where {T} return MOI.Utilities.canonical(func) end + function _scalar_quadratic_function( func::MOI.ScalarAffineFunction{T}, ::Type{T}, @@ -54,11 +55,11 @@ function _scalar_quadratic_function(func::MOI.VariableIndex, T::Type) end """ - PrimalObjective{T} + _PrimalObjective{T} Primal objective is defined as a `MOI.ScalarAffineFunction` """ -mutable struct PrimalObjective{T} +mutable struct _PrimalObjective{T} obj::MOI.ScalarQuadraticFunction{T} quad_cross_parameters::Dict{ MOI.VariableIndex, @@ -66,7 +67,7 @@ mutable struct PrimalObjective{T} } obj_parametric::Union{MOI.ScalarQuadraticFunction{T},Nothing} - function PrimalObjective{T}(obj) where {T} + function _PrimalObjective{T}(obj) where {T} canonical_obj = _scalar_quadratic_function(obj, T) quad_cross_parameters = Dict{MOI.VariableIndex,Vector{MOI.ScalarAffineTerm{T}}}() @@ -75,45 +76,26 @@ mutable struct PrimalObjective{T} end """ - DualObjective{T} + _DualObjective{T} Dual objective is defined as a `MOI.ScalarAffineFunction`. """ -mutable struct DualObjective{T} +mutable struct _DualObjective{T} obj::MOI.ScalarQuadraticFunction{T} end -const Objective{T} = Union{PrimalObjective{T},DualObjective{T}} - -function get_raw_obj(objective::Objective{T}) where {T} - return objective.obj -end -function get_affine_terms(objective::Objective{T}) where {T} - return objective.obj.affine_terms -end - -function get_primal_objective(primal_model::MOI.ModelLike, T::Type = Float64) - F = MOI.get(primal_model, MOI.ObjectiveFunctionType()) - return _get_primal_objective( - MOI.get(primal_model, MOI.ObjectiveFunction{F}()), - T, - ) -end - -function _get_primal_objective(obj_fun, T::Type) - return PrimalObjective{T}(obj_fun) -end - # allow removing variables from objective function -function get_primal_objective( +function _get_primal_objective( primal_model::MOI.ModelLike, variable_parameters::Vector{MOI.VariableIndex}, T::Type, ) - p_obj = get_primal_objective(primal_model, T) + F = MOI.get(primal_model, MOI.ObjectiveFunctionType()) + p_obj = + _PrimalObjective{T}(MOI.get(primal_model, MOI.ObjectiveFunction{F}())) if length(variable_parameters) > 0 vars_func, quad_cross_params, params_func = - split_variables(p_obj.obj, variable_parameters) + _split_variables(p_obj.obj, variable_parameters) p_obj.obj = vars_func p_obj.quad_cross_parameters = quad_cross_params p_obj.obj_parametric = params_func @@ -121,7 +103,7 @@ function get_primal_objective( return p_obj end -function split_variables( +function _split_variables( func::MOI.ScalarQuadraticFunction{T}, variable_parameters::Vector{MOI.VariableIndex}, ) where {T} @@ -148,9 +130,9 @@ function split_variables( if is_param_1 && is_param_2 push!(quad_params, term) elseif is_param_1 - push_affine_term(quad_cross_params, term, false) + _push_affine_term(quad_cross_params, term, false) elseif is_param_2 - push_affine_term(quad_cross_params, term, true) + _push_affine_term(quad_cross_params, term, true) else push!(quad_vars, term) end @@ -164,7 +146,7 @@ function split_variables( return variables_func, quad_cross_params, parameters_func end -function push_affine_term( +function _push_affine_term( dic, term::MOI.ScalarQuadraticTerm{T}, var_is_first::Bool, @@ -182,19 +164,19 @@ function push_affine_term( end """ - set_dual_objective( + _set_dual_objective( dual_model::MOI.ModelLike, - dual_objective::DualObjective{T}, + dual_objective::_DualObjective{T}, )::Nothing where {T} Add the objective function to the dual model. """ -function set_dual_objective( +function _set_dual_objective( dual_model::MOI.ModelLike, - dual_objective::DualObjective{T}, + dual_objective::_DualObjective{T}, )::Nothing where {T} # Set dual model objective function - raw_obj = get_raw_obj(dual_objective) + raw_obj = dual_objective.obj if MOI.Utilities.number_of_quadratic_terms(T, raw_obj) > 0 MOI.set( dual_model, @@ -212,21 +194,21 @@ function set_dual_objective( end """ - get_dual_objective( + _get_dual_objective( dual_model::MOI.ModelLike, dual_obj_affine_terms::Dict, - primal_objective::PrimalObjective{T}, - )::DualObjective{T} where {T} + primal_objective::_PrimalObjective{T}, + )::_DualObjective{T} where {T} build the dual model objective function from the primal model. """ -function get_dual_objective( +function _get_dual_objective( dual_problem, dual_obj_affine_terms::Dict, - primal_objective::PrimalObjective{T}, + primal_objective::_PrimalObjective{T}, scalar_affine_terms, variable_parameters, -)::DualObjective{T} where {T} +)::_DualObjective{T} where {T} dual_model = dual_problem.dual_model map = dual_problem.primal_dual_map sense_change = ifelse( @@ -256,14 +238,14 @@ function get_dual_objective( quad_terms, MOI.ScalarQuadraticTerm{T}( -MOI.coefficient(term), - map.primal_var_dual_quad_slack[term.variable_1], - map.primal_var_dual_quad_slack[term.variable_2], + map.primal_var_in_quad_obj_to_dual_slack_var[term.variable_1], + map.primal_var_in_quad_obj_to_dual_slack_var[term.variable_2], ), ) end # parametric part - # if some varaibles were marked to be parameters then their final + # if some variables were marked to be parameters then their final # processing occurs here. if nothing !== primal_objective.obj_parametric @@ -275,7 +257,7 @@ function get_dual_objective( lin_terms, MOI.ScalarAffineTerm{T}( MOI.coefficient(term), - map.primal_parameter[term.variable], + map.primal_parameter_to_dual_parameter[term.variable], ), ) end @@ -288,8 +270,8 @@ function get_dual_objective( quad_terms, MOI.ScalarQuadraticTerm{T}( MOI.coefficient(term), - map.primal_parameter[term.variable_1], - map.primal_parameter[term.variable_2], + map.primal_parameter_to_dual_parameter[term.variable_1], + map.primal_parameter_to_dual_parameter[term.variable_2], ), ) end @@ -301,7 +283,7 @@ function get_dual_objective( # and, thus, the go to the obj of the dual. # TODO? set_dot for vi in variable_parameters - param = map.primal_parameter[vi] + param = map.primal_parameter_to_dual_parameter[vi] for term in scalar_affine_terms[vi] push!( quad_terms, @@ -318,7 +300,7 @@ function get_dual_objective( saf_dual_objective = MOI.ScalarQuadraticFunction{T}( quad_terms, lin_terms, - MOI.constant(get_raw_obj(primal_objective)), + MOI.constant(primal_objective.obj), ) - return DualObjective{T}(saf_dual_objective) + return _DualObjective{T}(saf_dual_objective) end diff --git a/src/structures.jl b/src/structures.jl index 4f458f22..5b5c7710 100644 --- a/src/structures.jl +++ b/src/structures.jl @@ -30,106 +30,195 @@ MOI.Utilities.@model( Maps information from all structures of the primal to the dual model. - * `constrained_var_idx::Dict{MOI.VariableIndex,Tuple{MOI.ConstraintIndex,Int}}`: - maps original primal constrained variables to their primal original +The following abbreviations are used in the maps: + +* `var`: variable index +* `con`: constraint index +* `convar`: constrained variable, variable index +* `convarcon`: constrained variable, constraint index + +Main maps: + + * `primal_convar_to_primal_convarcon_and_index::Dict{MOI.VariableIndex,Tuple{MOI.ConstraintIndex,Int}}`: + maps primal constrained variables to their primal constraints (the special ones that makes them constrained variables) and - their internal index (if vector constraints, VectorOfVariables-in-Set), 1 - otherwise (VariableIndex-in-Set). + their internal index from 1 to dimension(set) (if vector constraints: + VectorOfVariables-in-Set), 1 otherwise (scalar: VariableIndex-in-Set). - * `constrained_var_dual::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}`: maps - the original primal constraint index of constrained variables to the dual - model's constraint index of the associated dual constraint. + note: this set is important to dualization can keep track of what it + decided to define as a constrained variable. + - consider index 0 to highlight scalars - * `constrained_var_zero::Dict{MOI.ConstraintIndex,Unions{MOI.ScalarAffineFunction,MOI.VectorAffineFunction}}`: - caches scalar affine functions or vector affine functions associated with - constrained variables of type `VectorOfVariables`-in-`Zeros` or - `VariableIndex`-in-`EqualTo(zero(T))` as their duals would be `func`-in-`Reals`, - which are "irrelevant" to the model. This information is cached for - completeness of the `DualOptimizer` for `get`ting `ConstraintDuals`. + * `primal_convarcon_to_dual_con::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}`: maps + the primal constraint index of constrained variables to the dual + model's constraint index of the associated dual constraint. This dual + constraint is a regular constraint (not a constrained variable constraint). + `VectorOfVariables`-in-`Zeros` and `VariableIndex`-in-`EqualTo(zero(T))` + are not in this map, as they are not dualized (See + primal_convarcon_to_dual_function). + + note: from the above two maps, we can get primal_convar_to_dual_con_and_index + + * `primal_var_to_dual_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}`: maps + "free" primal variables to their associated dual (equality) constraints. + Free variables as opposed to constrained variables. Note that Dualization + will select automatically which variables are free and which are + constrained. - * `primal_var_dual_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex}`: maps - "free" primal variables to their associated dual constraints. Free variables - as opposed to constrained variables. Note that Dualization will select - automatically which variables are free and which are constrained. + note: from the above three maps, we can get primal_var_to_dual_con_and_index - * `primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}`: + # TODO: idea, for simmetry, keep only + - primal_convarcon_to_dual_con (as is), analogous to primal_con_to_dual_convarcon + - primal_var_to_dual_con_and_index, analogous to primal_con_to_dual_var_vec + -- the second implicitly tracks which vars were decided to be convar's + + * `primal_con_to_dual_var_vec::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}`: maps primal constraint indices to vectors of dual variable indices. For scalar constraints those vectors will be single element vectors. + Primal constrained variables constraints (the main ones) are not in this + map. However, `VariableIndex`-in-set and `VectorOfVariables`-in-set might + appear in this map if they were not chosen as the main ones. + + note: possibly change this to: + primal_con_to_dual_var_vec_and_convarcon - * `primal_con_dual_con::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}`: maps - primal constraints to dual variable constraints (if there is such constraint - the dual dual variable is said to be constrained). If the primal + * `primal_con_to_dual_convarcon::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}`: + maps regular primal constraints to dual constrained variable. If the primal constraint's set is EqualTo or Zeros, no constraint is added in the dual variable (the dual variable is said to be free). + Primal constrained variables constraints (the main ones) are not in this + map. However, `VariableIndex`-in-set and `VectorOfVariables`-in-set might + appear in this map if they were not chosen as the main ones. + The keys are similar to the (# primal_con_to_dual_var_vec) map, except + that `VariableIndex`-in-`EqualTo(zero(T))` and `VectorOfVariables`-in-`Zeros` + are not in this map, as the dual constraint would belong to the `Reals` set, + and would be innocuous (hence, not added). + + Additional helper maps: - * `primal_con_constants::Dict{MOI.ConstraintIndex,Vector{T}}`: maps primal + * `primal_con_to_primal_constants_vec::Dict{MOI.ConstraintIndex,Vector{T}}`: maps primal constraints to their respective constants, which might be inside the set. This map is used in `MOI.get(::DualOptimizer,::MOI.ConstraintPrimal,ci)` - that requires extra information in the case that the scalar set constrains + that requires extra information in the case that the scalar set constains a constant (`EqualtTo`, `GreaterThan`, `LessThan`). - * `primal_parameter::Dict{MOI.VariableIndex,MOI.VariableIndex}`: maps + * `primal_parameter_to_dual_parameter::Dict{MOI.VariableIndex,MOI.VariableIndex}`: maps parameters in the primal to parameters in the dual model. - * `primal_var_dual_quad_slack::Dict{MOI.VariableIndex,MOI.VariableIndex}`: + * `primal_convarcon_to_dual_function::Dict{MOI.ConstraintIndex,Unions{MOI.ScalarAffineFunction,MOI.VectorAffineFunction}}`: + caches scalar affine functions or vector affine functions associated with + constrained variables of type `VectorOfVariables`-in-`Zeros` or + `VariableIndex`-in-`EqualTo(zero(T))` as their duals would be `func`-in-`Reals`, + which are "irrelevant" to the model. This information is cached for + completeness of the `DualOptimizer` for `get`ting `ConstraintDuals`. + + * `primal_var_in_quad_obj_to_dual_slack_var::Dict{MOI.VariableIndex,MOI.VariableIndex}`: maps primal variables (that appear in quadratic objective terms) to dual - "slack" variables. + "slack" variables. These primal variables might appear in other maps. + Future name: primal_var_in_quad_obj_to_dual_slack_var """ mutable struct PrimalDualMap{T} - constrained_var_idx::Dict{MOI.VariableIndex,Tuple{MOI.ConstraintIndex,Int}} - constrained_var_dual::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex} - constrained_var_zero::Dict{ + primal_convar_to_primal_convarcon_and_index::Dict{ + MOI.VariableIndex, + Tuple{MOI.ConstraintIndex,Int}, + } + primal_convarcon_to_dual_con::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex} + primal_var_to_dual_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex} + primal_con_to_dual_var_vec::Dict{ MOI.ConstraintIndex, - Union{MOI.VectorAffineFunction{T},MOI.ScalarAffineFunction{T}}, + Vector{MOI.VariableIndex}, } - primal_var_dual_con::Dict{MOI.VariableIndex,MOI.ConstraintIndex} - primal_con_dual_var::Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}} - primal_con_dual_con::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex} - primal_con_constants::Dict{MOI.ConstraintIndex,Vector{T}} + primal_con_to_dual_convarcon::Dict{MOI.ConstraintIndex,MOI.ConstraintIndex} - primal_parameter::Dict{MOI.VariableIndex,MOI.VariableIndex} - primal_var_dual_quad_slack::Dict{MOI.VariableIndex,MOI.VariableIndex} + primal_con_to_primal_constants_vec::Dict{MOI.ConstraintIndex,Vector{T}} + primal_parameter_to_dual_parameter::Dict{ + MOI.VariableIndex, + MOI.VariableIndex, + } + primal_convarcon_to_dual_function::Dict{ + MOI.ConstraintIndex, + Union{MOI.VectorAffineFunction{T},MOI.ScalarAffineFunction{T}}, + } + primal_var_in_quad_obj_to_dual_slack_var::Dict{ + MOI.VariableIndex, + MOI.VariableIndex, + } function PrimalDualMap{T}() where {T} return new( Dict{MOI.VariableIndex,Tuple{MOI.ConstraintIndex,Int}}(), Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}(), - Dict{ - MOI.ConstraintIndex, - Union{MOI.VectorAffineFunction{T},MOI.ScalarAffineFunction{T}}, - }(), Dict{MOI.VariableIndex,MOI.ConstraintIndex}(), Dict{MOI.ConstraintIndex,Vector{MOI.VariableIndex}}(), Dict{MOI.ConstraintIndex,MOI.ConstraintIndex}(), Dict{MOI.ConstraintIndex,Vector{T}}(), Dict{MOI.VariableIndex,MOI.VariableIndex}(), + Dict{ + MOI.ConstraintIndex, + Union{MOI.VectorAffineFunction{T},MOI.ScalarAffineFunction{T}}, + }(), Dict{MOI.VariableIndex,MOI.VariableIndex}(), ) end end +function Base.getproperty(m::PrimalDualMap{T}, name::Symbol) where {T} + if name === :constrained_var_idx + @warn "constrained_var_idx field is deprecated, use primal_convar_to_primal_convarcon_and_index instead" + return getfield(m, :primal_convar_to_primal_convarcon_and_index) + elseif name === :constrained_var_dual + @warn "constrained_var_dual field is deprecated, use primal_convarcon_to_dual_con instead" + return getfield(m, :primal_convarcon_to_dual_con) + elseif name === :primal_var_dual_con + @warn "primal_var_dual_con field is deprecated, use primal_var_to_dual_con instead" + return getfield(m, :primal_var_to_dual_con) + elseif name === :primal_con_dual_var + @warn "primal_con_dual_var field is deprecated, use primal_con_to_dual_var_vec instead" + return getfield(m, :primal_con_to_dual_var_vec) + elseif name === :primal_con_dual_con + @warn "primal_con_dual_con field is deprecated, use primal_con_to_dual_convarcon instead" + return getfield(m, :primal_con_to_dual_convarcon) + elseif name === :primal_con_constants + @warn "primal_con_constants field is deprecated, use primal_con_to_primal_constants_vec instead" + return getfield(m, :primal_con_to_primal_constants_vec) + elseif name === :primal_parameter + @warn "primal_parameter field is deprecated, use primal_parameter_to_dual_parameter instead" + return getfield(m, :primal_parameter_to_dual_parameter) + elseif name === :constrained_var_zero + @warn "constrained_var_zero field is deprecated, use primal_convarcon_to_dual_function instead" + return getfield(m, :primal_convarcon_to_dual_function) + elseif name === :primal_var_dual_quad_slack + @warn "primal_var_dual_quad_slack field is deprecated, use primal_var_in_quad_obj_to_dual_slack_var instead" + return getfield(m, :primal_var_in_quad_obj_to_dual_slack_var) + else + return getfield(m, name) + end +end + function is_empty(primal_dual_map::PrimalDualMap{T}) where {T} - return isempty(primal_dual_map.constrained_var_idx) && - isempty(primal_dual_map.constrained_var_dual) && - isempty(primal_dual_map.constrained_var_zero) && - isempty(primal_dual_map.primal_var_dual_con) && - isempty(primal_dual_map.primal_con_dual_var) && - isempty(primal_dual_map.primal_con_dual_con) && - isempty(primal_dual_map.primal_con_constants) && - isempty(primal_dual_map.primal_parameter) && - isempty(primal_dual_map.primal_var_dual_quad_slack) + return isempty( + primal_dual_map.primal_convar_to_primal_convarcon_and_index, + ) && + isempty(primal_dual_map.primal_convarcon_to_dual_con) && + isempty(primal_dual_map.primal_convarcon_to_dual_function) && + isempty(primal_dual_map.primal_var_to_dual_con) && + isempty(primal_dual_map.primal_con_to_dual_var_vec) && + isempty(primal_dual_map.primal_con_to_dual_convarcon) && + isempty(primal_dual_map.primal_con_to_primal_constants_vec) && + isempty(primal_dual_map.primal_parameter_to_dual_parameter) && + isempty(primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var) end function empty!(primal_dual_map::PrimalDualMap) - Base.empty!(primal_dual_map.constrained_var_idx) - Base.empty!(primal_dual_map.constrained_var_dual) - Base.empty!(primal_dual_map.constrained_var_zero) - Base.empty!(primal_dual_map.primal_var_dual_con) - Base.empty!(primal_dual_map.primal_con_dual_var) - Base.empty!(primal_dual_map.primal_con_dual_con) - Base.empty!(primal_dual_map.primal_con_constants) - Base.empty!(primal_dual_map.primal_parameter) - Base.empty!(primal_dual_map.primal_var_dual_quad_slack) + Base.empty!(primal_dual_map.primal_convar_to_primal_convarcon_and_index) + Base.empty!(primal_dual_map.primal_convarcon_to_dual_con) + Base.empty!(primal_dual_map.primal_convarcon_to_dual_function) + Base.empty!(primal_dual_map.primal_var_to_dual_con) + Base.empty!(primal_dual_map.primal_con_to_dual_var_vec) + Base.empty!(primal_dual_map.primal_con_to_dual_convarcon) + Base.empty!(primal_dual_map.primal_con_to_primal_constants_vec) + Base.empty!(primal_dual_map.primal_parameter_to_dual_parameter) + Base.empty!(primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var) return end diff --git a/src/supported.jl b/src/supported.jl index 093fe03c..eb2c62c3 100644 --- a/src/supported.jl +++ b/src/supported.jl @@ -8,6 +8,11 @@ Returns `true` if `Function-in-Set` is supported for Dualization and throws an error if it is not. + + supported_constraints(F::MOI.AbstractFunction, S::MOI.AbstractSet) + +Returns `true` if `Function-in-Set` is supported for Dualization and `false` +if it is not. """ function supported_constraints(con_types::Vector{Tuple{Type,Type}}) for (F, S) in con_types @@ -18,7 +23,7 @@ function supported_constraints(con_types::Vector{Tuple{Type,Type}}) ) end end - return + return true end supported_constraint(::Type, ::Type) = false @@ -42,19 +47,24 @@ end Returns `true` if `MOI.ObjectiveFunctionType()` is supported for Dualization and throws an error if it is not. + + supported_objective(obj_func_type::Type) + +Returns `true` if `obj_func_type` is supported for Dualization and throws an +error if it is not. """ function supported_objective(primal_model::MOI.ModelLike) obj_func_type = MOI.get(primal_model, MOI.ObjectiveFunctionType()) - if !supported_obj(obj_func_type) + if !supported_objective(obj_func_type) error("Objective functions of type $obj_func_type are not implemented") end - return + return true end -supported_obj(::Type) = false +supported_objective(::Type) = false -supported_obj(::Type{MOI.VariableIndex}) = true +supported_objective(::Type{MOI.VariableIndex}) = true -supported_obj(::Type{<:MOI.ScalarAffineFunction}) = true +supported_objective(::Type{<:MOI.ScalarAffineFunction}) = true -supported_obj(::Type{<:MOI.ScalarQuadraticFunction}) = true +supported_objective(::Type{<:MOI.ScalarQuadraticFunction}) = true diff --git a/src/utils.jl b/src/utils.jl index 6a7b9327..db4e47d2 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -4,37 +4,26 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. # Some useful wrappers -function get_function(model::MOI.ModelLike, ci::MOI.ConstraintIndex) - return MOI.get(model, MOI.ConstraintFunction(), ci) -end - -function get_set(model::MOI.ModelLike, ci::MOI.ConstraintIndex) - return MOI.get(model, MOI.ConstraintSet(), ci) -end - -function get_ci_row_dimension(model::MOI.ModelLike, ci::MOI.ConstraintIndex) - return MOI.output_dimension(get_function(model, ci)) -end -function get_scalar_term( +function _get_normalized_constant( model::MOI.ModelLike, ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, ) where {S<:MOI.AbstractScalarSet} - return [-MOI.constant(get_set(model, ci))] + return [-MOI.constant(MOI.get(model, MOI.ConstraintSet(), ci))] end -function get_scalar_term( +function _get_normalized_constant( model::MOI.ModelLike, ci::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} return [ - MOI.constant(get_function(model, ci)) - - MOI.constant(get_set(model, ci)), + MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci)) - + MOI.constant(MOI.get(model, MOI.ConstraintSet(), ci)), ] end # This is used to fill the dual objective dictionary -function get_scalar_term( +function _get_normalized_constant( func::MOI.AbstractVectorFunction, ::MOI.AbstractVectorSet, i::Int, @@ -43,7 +32,7 @@ function get_scalar_term( end # This is used to fill the dual objective dictionary -function get_scalar_term( +function _get_normalized_constant( ::MOI.VariableIndex, set::MOI.AbstractScalarSet, i::Int, @@ -52,7 +41,7 @@ function get_scalar_term( end # This is used to fill the dual objective dictionary -function get_scalar_term( +function _get_normalized_constant( func::MOI.AbstractScalarFunction, set::MOI.AbstractScalarSet, i::Int, diff --git a/test/Tests/test_MOI_wrapper.jl b/test/Tests/test_MOI_wrapper.jl index c8741a4a..d1831a7b 100644 --- a/test/Tests/test_MOI_wrapper.jl +++ b/test/Tests/test_MOI_wrapper.jl @@ -108,12 +108,12 @@ end end - @testset "dual_status" begin - @test Dualization.dual_status(MOI.INFEASIBLE) == MOI.DUAL_INFEASIBLE - @test Dualization.dual_status(MOI.DUAL_INFEASIBLE) == MOI.INFEASIBLE - @test Dualization.dual_status(MOI.ALMOST_INFEASIBLE) == + @testset "_dual_status" begin + @test Dualization._dual_status(MOI.INFEASIBLE) == MOI.DUAL_INFEASIBLE + @test Dualization._dual_status(MOI.DUAL_INFEASIBLE) == MOI.INFEASIBLE + @test Dualization._dual_status(MOI.ALMOST_INFEASIBLE) == MOI.ALMOST_DUAL_INFEASIBLE - @test Dualization.dual_status(MOI.ALMOST_DUAL_INFEASIBLE) == + @test Dualization._dual_status(MOI.ALMOST_DUAL_INFEASIBLE) == MOI.ALMOST_INFEASIBLE end diff --git a/test/Tests/test_dot.jl b/test/Tests/test_dot.jl index 9118fbc0..f795870a 100644 --- a/test/Tests/test_dot.jl +++ b/test/Tests/test_dot.jl @@ -4,17 +4,17 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. @testset "test_dot.jl" begin - a = Dualization.CanonicalVector{Int}(1, 3) + a = Dualization._CanonicalVector{Int}(1, 3) @test collect(a) == [1, 0, 0] - b = Dualization.CanonicalVector{Int}(2, 3) + b = Dualization._CanonicalVector{Int}(2, 3) @test collect(b) == [0, 1, 0] set = MOI.PositiveSemidefiniteConeTriangle(2) @test MOI.Utilities.set_dot(a, b, set) == 0 @test MOI.Utilities.set_dot(a, a, set) == 1 @test MOI.Utilities.set_dot(b, b, set) == 2 - a = Dualization.CanonicalVector{Int}(1, 4) - b = Dualization.CanonicalVector{Int}(2, 4) - c = Dualization.CanonicalVector{Int}(3, 4) + a = Dualization._CanonicalVector{Int}(1, 4) + b = Dualization._CanonicalVector{Int}(2, 4) + c = Dualization._CanonicalVector{Int}(3, 4) set = MOI.RootDetConeTriangle(2) @test MOI.Utilities.set_dot(a, b, set) == 0 @test MOI.Utilities.set_dot(b, c, set) == 0 diff --git a/test/Tests/test_dual_model_variables.jl b/test/Tests/test_dual_model_variables.jl index eb3c929b..b6e07199 100644 --- a/test/Tests/test_dual_model_variables.jl +++ b/test/Tests/test_dual_model_variables.jl @@ -2,41 +2,3 @@ # # 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. - -# Tests of this file not covered in other tests -@testset "dual_model_variables.jl" begin - @testset "push_to_dual_obj_aff_terms!" begin - primal_model = soc1_test() - dual_obj_affine_terms = Dict{MOI.VariableIndex,Float64}() - list = MOI.get( - primal_model, - MOI.ListOfConstraintIndices{ - MOI.VectorOfVariables, - MOI.SecondOrderCone, - }(), - ) - ci = first(list) - func = MOI.get(primal_model, MOI.ConstraintFunction(), ci) - set = MOI.get(primal_model, MOI.ConstraintSet(), ci) - Dualization.push_to_dual_obj_aff_terms!( - primal_model, - dual_obj_affine_terms, - MOI.VariableIndex(1), - func, - set, - 1, - ) - @test isempty(dual_obj_affine_terms) - end - - @testset "set_dual_variable_name" begin - primal_model = soc1_test() - vi = MOI.VariableIndex(1) - Dualization.set_dual_variable_name(primal_model, vi, 1, "con", "") - @test MOI.get(primal_model, MOI.VariableName(), vi) == "con_1" - Dualization.set_dual_variable_name(primal_model, vi, 2, "con", "") - @test MOI.get(primal_model, MOI.VariableName(), vi) == "con_2" - Dualization.set_dual_variable_name(primal_model, vi, 2, "con", "oi") - @test MOI.get(primal_model, MOI.VariableName(), vi) == "oicon_2" - end -end diff --git a/test/Tests/test_dual_names.jl b/test/Tests/test_dual_names.jl index 0fd6bfaf..e9b45f51 100644 --- a/test/Tests/test_dual_names.jl +++ b/test/Tests/test_dual_names.jl @@ -32,13 +32,13 @@ dual_model = dual_problem.dual_model primal_dual_map = dual_problem.primal_dual_map # Query variable names - vi_1 = primal_dual_map.primal_con_dual_var[MOI.ConstraintIndex{ + vi_1 = primal_dual_map.primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VariableIndex, MOI.GreaterThan{Float64}, }( 1, )][1] - vi_2 = primal_dual_map.primal_con_dual_var[MOI.ConstraintIndex{ + vi_2 = primal_dual_map.primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, }( @@ -47,8 +47,8 @@ @test MOI.get(dual_model, MOI.VariableName(), vi_1) == "" @test MOI.get(dual_model, MOI.VariableName(), vi_2) == "" # Query constraint names - ci_1 = primal_dual_map.primal_var_dual_con[MOI.VariableIndex(1)] - ci_2 = primal_dual_map.primal_var_dual_con[MOI.VariableIndex(2)] + ci_1 = primal_dual_map.primal_var_to_dual_con[MOI.VariableIndex(1)] + ci_2 = primal_dual_map.primal_var_to_dual_con[MOI.VariableIndex(2)] @test MOI.get(dual_model, MOI.ConstraintName(), ci_1) == "" @test MOI.get(dual_model, MOI.ConstraintName(), ci_2) == "" @@ -59,13 +59,13 @@ dual_model = dual_problem.dual_model primal_dual_map = dual_problem.primal_dual_map # Query variable names - vi_1 = primal_dual_map.primal_con_dual_var[MOI.ConstraintIndex{ + vi_1 = primal_dual_map.primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VariableIndex, MOI.GreaterThan{Float64}, }( 1, )][1] - vi_2 = primal_dual_map.primal_con_dual_var[MOI.ConstraintIndex{ + vi_2 = primal_dual_map.primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, }( @@ -73,8 +73,8 @@ )][1] @test MOI.get(dual_model, MOI.VariableName(), vi_2) == "dualvar_lessthan_1" # Query constraint names - ci_1 = primal_dual_map.primal_var_dual_con[MOI.VariableIndex(1)] - ci_2 = primal_dual_map.primal_var_dual_con[MOI.VariableIndex(2)] + ci_1 = primal_dual_map.primal_var_to_dual_con[MOI.VariableIndex(1)] + ci_2 = primal_dual_map.primal_var_to_dual_con[MOI.VariableIndex(2)] @test MOI.get(dual_model, MOI.ConstraintName(), ci_1) == "dualcon_x1" @test MOI.get(dual_model, MOI.ConstraintName(), ci_2) == "dualcon_x2" end diff --git a/test/Tests/test_dualize_conic_linear.jl b/test/Tests/test_dualize_conic_linear.jl index be8e34d4..e480e686 100644 --- a/test/Tests/test_dualize_conic_linear.jl +++ b/test/Tests/test_dualize_conic_linear.jl @@ -55,7 +55,7 @@ @test MOI.get(dual_model, MOI.ConstraintSet(), ci) == MOI.Nonnegatives(3) - primal_con_dual_var = primal_dual_map.primal_con_dual_var + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec ci_zero = first( MOI.get( primal_model, @@ -65,7 +65,7 @@ }(), ), ) - @test primal_con_dual_var[ci_zero] == MOI.VariableIndex.(1:2) + @test primal_con_to_dual_var_vec[ci_zero] == MOI.VariableIndex.(1:2) ci_nneg = first( MOI.get( primal_model, @@ -75,13 +75,14 @@ }(), ), ) - @test !haskey(primal_con_dual_var, ci_nneg) - @test primal_dual_map.constrained_var_dual[ci_nneg] == ci + @test !haskey(primal_con_to_dual_var_vec, ci_nneg) + @test primal_dual_map.primal_convarcon_to_dual_con[ci_nneg] == ci for i in 1:3 vi = MOI.VariableIndex(i) - @test !haskey(primal_dual_map.primal_var_dual_con, vi) - @test primal_dual_map.constrained_var_idx[vi] == (ci_nneg, i) + @test !haskey(primal_dual_map.primal_var_to_dual_con, vi) + @test primal_dual_map.primal_convar_to_primal_convarcon_and_index[vi] == + (ci_nneg, i) end end @@ -187,14 +188,14 @@ @test MOI.coefficient.(eq_con3_fun.terms) == -[1.0] @test MOI.constant(eq_con3_fun) == [-4.0] - primal_con_dual_var = primal_dual_map.primal_con_dual_var + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec ci_zero = first( MOI.get( primal_model, MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.Zeros}(), ), ) - @test !haskey(primal_con_dual_var, ci_zero) + @test !haskey(primal_con_to_dual_var_vec, ci_zero) ci_nneg = first( MOI.get( primal_model, @@ -204,8 +205,8 @@ }(), ), ) - @test !haskey(primal_con_dual_var, ci_nneg) - @test primal_dual_map.constrained_var_dual[ci_nneg] == ci_nn + @test !haskey(primal_con_to_dual_var_vec, ci_nneg) + @test primal_dual_map.primal_convarcon_to_dual_con[ci_nneg] == ci_nn ci_npos = first( MOI.get( primal_model, @@ -215,8 +216,8 @@ }(), ), ) - @test !haskey(primal_con_dual_var, ci_npos) - @test primal_dual_map.constrained_var_dual[ci_npos] == ci_np + @test !haskey(primal_con_to_dual_var_vec, ci_npos) + @test primal_dual_map.primal_convarcon_to_dual_con[ci_npos] == ci_np ci_aff_zero = first( MOI.get( primal_model, @@ -226,19 +227,22 @@ }(), ), ) - @test primal_con_dual_var[ci_aff_zero] == MOI.VariableIndex.(1:3) + @test primal_con_to_dual_var_vec[ci_aff_zero] == MOI.VariableIndex.(1:3) - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == ci_eq + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == ci_eq for i in 2:4 vi = MOI.VariableIndex(i) - @test !haskey(primal_var_dual_con, vi) + @test !haskey(primal_var_to_dual_con, vi) end - @test primal_dual_map.constrained_var_idx[MOI.VariableIndex(2)] == - (ci_npos, 1) - @test primal_dual_map.constrained_var_idx[MOI.VariableIndex(3)] == - (ci_nneg, 1) - @test primal_dual_map.constrained_var_idx[MOI.VariableIndex(4)] == - (ci_zero, 1) + @test primal_dual_map.primal_convar_to_primal_convarcon_and_index[MOI.VariableIndex( + 2, + )] == (ci_npos, 1) + @test primal_dual_map.primal_convar_to_primal_convarcon_and_index[MOI.VariableIndex( + 3, + )] == (ci_nneg, 1) + @test primal_dual_map.primal_convar_to_primal_convarcon_and_index[MOI.VariableIndex( + 4, + )] == (ci_zero, 1) end end diff --git a/test/Tests/test_dualize_exponential.jl b/test/Tests/test_dualize_exponential.jl index 0a88e089..0e8aee9f 100644 --- a/test/Tests/test_dualize_exponential.jl +++ b/test/Tests/test_dualize_exponential.jl @@ -125,19 +125,19 @@ ) @test dual_exp_con.variables == MOI.VariableIndex.(3:5) - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[eq_con1] == [MOI.VariableIndex(1)] - @test primal_con_dual_var[eq_con2] == [MOI.VariableIndex(2)] - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[eq_con1] == [MOI.VariableIndex(1)] + @test primal_con_to_dual_var_vec[eq_con2] == [MOI.VariableIndex(2)] + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone, }( 1, )] == MOI.VariableIndex.(3:5) - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == eq_con1 - @test primal_var_dual_con[MOI.VariableIndex(2)] == eq_con2 - @test primal_var_dual_con[MOI.VariableIndex(3)] == eq_con3 + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == eq_con1 + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == eq_con2 + @test primal_var_to_dual_con[MOI.VariableIndex(3)] == eq_con3 end end diff --git a/test/Tests/test_dualize_power.jl b/test/Tests/test_dualize_power.jl index 5b1b5769..2d79afd5 100644 --- a/test/Tests/test_dualize_power.jl +++ b/test/Tests/test_dualize_power.jl @@ -125,13 +125,13 @@ dual_pow_con = MOI.get(dual_model, MOI.ConstraintFunction(), pow_con) - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[eq_con1] == [MOI.VariableIndex(1)] - @test primal_con_dual_var[eq_con2] == [MOI.VariableIndex(2)] + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[eq_con1] == [MOI.VariableIndex(1)] + @test primal_con_to_dual_var_vec[eq_con2] == [MOI.VariableIndex(2)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == eq_con1 - @test primal_var_dual_con[MOI.VariableIndex(2)] == eq_con2 - @test primal_var_dual_con[MOI.VariableIndex(3)] == eq_con3 + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == eq_con1 + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == eq_con2 + @test primal_var_to_dual_con[MOI.VariableIndex(3)] == eq_con3 end end diff --git a/test/Tests/test_dualize_quadratic.jl b/test/Tests/test_dualize_quadratic.jl index a65eaaf0..ee6187ef 100644 --- a/test/Tests/test_dualize_quadratic.jl +++ b/test/Tests/test_dualize_quadratic.jl @@ -124,46 +124,50 @@ @test MOI.constant.(eq_con3_fun) == 0.0 @test MOI.constant(eq_con3_set) == 0.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, }( 1, )] == [MOI.VariableIndex(1)] - @test primal_con_dual_var[MOI.ConstraintIndex{ + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, }( 2, )] == [MOI.VariableIndex(2)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == MOI.ConstraintIndex{ + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 1, ) - @test primal_var_dual_con[MOI.VariableIndex(2)] == MOI.ConstraintIndex{ + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 2, ) - @test primal_var_dual_con[MOI.VariableIndex(3)] == MOI.ConstraintIndex{ + @test primal_var_to_dual_con[MOI.VariableIndex(3)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 3, ) - primal_var_dual_quad_slack = primal_dual_map.primal_var_dual_quad_slack - @test primal_var_dual_quad_slack[MOI.VariableIndex(1)] == + primal_var_in_quad_obj_to_dual_slack_var = + primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(1)] == MOI.VariableIndex(2 + 1) - @test primal_var_dual_quad_slack[MOI.VariableIndex(2)] == + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(2)] == MOI.VariableIndex(2 + 2) - @test primal_var_dual_quad_slack[MOI.VariableIndex(3)] == + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(3)] == MOI.VariableIndex(2 + 3) end @testset "qp2_test" begin diff --git a/test/Tests/test_dualize_rsoc.jl b/test/Tests/test_dualize_rsoc.jl index d8a16d5a..8f11a196 100644 --- a/test/Tests/test_dualize_rsoc.jl +++ b/test/Tests/test_dualize_rsoc.jl @@ -125,16 +125,16 @@ ) @test rsoc_con.variables == MOI.VariableIndex.(1:4) - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, }( 1, )] == MOI.VariableIndex.(1:4) - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == eq_con1 - @test primal_var_dual_con[MOI.VariableIndex(2)] == eq_con2 + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == eq_con1 + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == eq_con2 end end diff --git a/test/Tests/test_dualize_sdp.jl b/test/Tests/test_dualize_sdp.jl index 9bec7416..0afb2f75 100644 --- a/test/Tests/test_dualize_sdp.jl +++ b/test/Tests/test_dualize_sdp.jl @@ -125,18 +125,18 @@ sdp_con = MOI.get(dual_model, MOI.ConstraintFunction(), spd_con) - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[eq_con1] == [MOI.VariableIndex(1)] - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[eq_con1] == [MOI.VariableIndex(1)] + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, }( 1, )] == MOI.VariableIndex.(2:4) - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == eq_con1 - @test primal_var_dual_con[MOI.VariableIndex(2)] == eq_con2 - @test primal_var_dual_con[MOI.VariableIndex(3)] == eq_con3 + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == eq_con1 + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == eq_con2 + @test primal_var_to_dual_con[MOI.VariableIndex(3)] == eq_con3 end end diff --git a/test/Tests/test_dualize_soc.jl b/test/Tests/test_dualize_soc.jl index 130a7d32..91b0129b 100644 --- a/test/Tests/test_dualize_soc.jl +++ b/test/Tests/test_dualize_soc.jl @@ -114,8 +114,8 @@ ) @test soc_con.variables == MOI.VariableIndex.(2:4) - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VectorAffineFunction{Float64}, MOI.Zeros, }( @@ -130,12 +130,12 @@ }(), ), ) - @test primal_con_dual_var[primal_soc_con] == + @test primal_con_to_dual_var_vec[primal_soc_con] == [MOI.VariableIndex(2); MOI.VariableIndex(3); MOI.VariableIndex(4)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == eq_con1 - @test primal_var_dual_con[MOI.VariableIndex(2)] == eq_con2 - @test primal_var_dual_con[MOI.VariableIndex(3)] == eq_con3 + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == eq_con1 + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == eq_con2 + @test primal_var_to_dual_con[MOI.VariableIndex(3)] == eq_con3 end end diff --git a/test/Tests/test_objective_coefficients.jl b/test/Tests/test_objective_coefficients.jl index d72cb8db..68636978 100644 --- a/test/Tests/test_objective_coefficients.jl +++ b/test/Tests/test_objective_coefficients.jl @@ -4,38 +4,20 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. @testset "objective_coefficients.jl" begin - @testset "set_dual_model_sense" begin + @testset "_set_dual_model_sense" begin # ERROR: FEASIBILITY_SENSE is not supported - # @test_throws ErrorException Dualization.set_dual_model_sense(lp11_test(), lp11_test()) + # @test_throws ErrorException Dualization._set_dual_model_sense(lp11_test(), lp11_test()) # obj = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(0.0, MOI.VariableIndex(1))], 0.0) - # @test_throws ErrorException Dualization.PrimalObjective{Float64}(obj) + # @test_throws ErrorException Dualization._PrimalObjective{Float64}(obj) model = lp1_test() @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE - Dualization.set_dual_model_sense(model, model) + Dualization._set_dual_model_sense(model, model) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE model = lp4_test() @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE - Dualization.set_dual_model_sense(model, model) + Dualization._set_dual_model_sense(model, model) @test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE end - - @testset "get_primal_objective" begin - model = lp1_test() - primal_objective = Dualization.get_primal_objective(model) - - @test Dualization.get_affine_terms(primal_objective)[1] == - MOI.ScalarAffineTerm{Float64}(-4.0, MOI.VariableIndex(2)) - @test MOI.constant(Dualization.get_raw_obj(primal_objective)) == -1.0 - - model = lp10_test() - - @test model.objective.single_variable == MOI.VariableIndex(1) - primal_objective = Dualization.get_primal_objective(model) - @test Dualization.get_affine_terms( - Dualization.get_primal_objective(model), - )[1] == MOI.ScalarAffineTerm{Float64}(1.0, MOI.VariableIndex(1)) - @test MOI.constant(Dualization.get_raw_obj(primal_objective)) == 0.0 - end end diff --git a/test/Tests/test_partial_dual_linear.jl b/test/Tests/test_partial_dual_linear.jl index 6e4bb0a6..b5302b13 100644 --- a/test/Tests/test_partial_dual_linear.jl +++ b/test/Tests/test_partial_dual_linear.jl @@ -81,16 +81,17 @@ @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == 0.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, }( 1, )] == [MOI.VariableIndex(1)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == MOI.ConstraintIndex{ + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( @@ -167,7 +168,7 @@ @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 3.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec vaf_npos, = MOI.get( primal_model, MOI.ListOfConstraintIndices{ @@ -175,7 +176,7 @@ MOI.Nonpositives, }(), ) - @test primal_con_dual_var[vaf_npos] == + @test primal_con_to_dual_var_vec[vaf_npos] == [MOI.VariableIndex(1); MOI.VariableIndex(2)] vgt, = MOI.get( primal_model, @@ -184,10 +185,10 @@ MOI.GreaterThan{Float64}, }(), ) - @test primal_con_dual_var[vgt] == [MOI.VariableIndex(3)] + @test primal_con_to_dual_var_vec[vgt] == [MOI.VariableIndex(3)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test isempty(primal_var_dual_con) + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test isempty(primal_var_to_dual_con) end @testset "lp12_test - x_1 and x_3 ignored" begin @@ -265,28 +266,29 @@ @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VariableIndex, MOI.LessThan{Float64}, }( 2, )] == [MOI.VariableIndex(3)] - @test primal_con_dual_var[MOI.ConstraintIndex{ + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VariableIndex, MOI.LessThan{Float64}, }( 1, )] == [MOI.VariableIndex(2)] - @test primal_con_dual_var[MOI.ConstraintIndex{ + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, }( 1, )] == [MOI.VariableIndex(1)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(2)] == MOI.ConstraintIndex{ + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( diff --git a/test/Tests/test_partial_dual_quadratic.jl b/test/Tests/test_partial_dual_quadratic.jl index ed52270c..6c47482c 100644 --- a/test/Tests/test_partial_dual_quadratic.jl +++ b/test/Tests/test_partial_dual_quadratic.jl @@ -105,41 +105,46 @@ @test MOI.constant.(eq_con2_fun) == 0.0 @test MOI.constant(eq_con2_set) == 0.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, }( 1, )] == [MOI.VariableIndex(1)] - @test primal_con_dual_var[MOI.ConstraintIndex{ + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, }( 2, )] == [MOI.VariableIndex(2)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test primal_var_dual_con[MOI.VariableIndex(1)] == MOI.ConstraintIndex{ + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test primal_var_to_dual_con[MOI.VariableIndex(1)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 1, ) - @test primal_var_dual_con[MOI.VariableIndex(2)] == MOI.ConstraintIndex{ + @test primal_var_to_dual_con[MOI.VariableIndex(2)] == + MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 2, ) - primal_parameter = primal_dual_map.primal_parameter - @test primal_parameter[MOI.VariableIndex(3)] == MOI.VariableIndex(2 + 1) + primal_parameter_to_dual_parameter = + primal_dual_map.primal_parameter_to_dual_parameter + @test primal_parameter_to_dual_parameter[MOI.VariableIndex(3)] == + MOI.VariableIndex(2 + 1) - primal_var_dual_quad_slack = primal_dual_map.primal_var_dual_quad_slack - @test primal_var_dual_quad_slack[MOI.VariableIndex(1)] == + primal_var_in_quad_obj_to_dual_slack_var = + primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(1)] == MOI.VariableIndex(2 + 1 + 1) - @test primal_var_dual_quad_slack[MOI.VariableIndex(2)] == + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(2)] == MOI.VariableIndex(2 + 1 + 2) end @testset "qp2_test - ignore y" begin @@ -218,32 +223,35 @@ @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == -1.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 1, )] == [MOI.VariableIndex(1)] @test !haskey( - primal_con_dual_var, + primal_con_to_dual_var_vec, MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(1), ) - @test primal_con_dual_var[MOI.ConstraintIndex{ + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VariableIndex, MOI.GreaterThan{Float64}, }( 2, )] == [MOI.VariableIndex(2)] - primal_var_dual_con = primal_dual_map.primal_var_dual_con - @test isempty(primal_var_dual_con) + primal_var_to_dual_con = primal_dual_map.primal_var_to_dual_con + @test isempty(primal_var_to_dual_con) - primal_parameter = primal_dual_map.primal_parameter - @test primal_parameter[MOI.VariableIndex(2)] == MOI.VariableIndex(3) + primal_parameter_to_dual_parameter = + primal_dual_map.primal_parameter_to_dual_parameter + @test primal_parameter_to_dual_parameter[MOI.VariableIndex(2)] == + MOI.VariableIndex(3) - primal_var_dual_quad_slack = primal_dual_map.primal_var_dual_quad_slack - @test primal_var_dual_quad_slack[MOI.VariableIndex(1)] == + primal_var_in_quad_obj_to_dual_slack_var = + primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(1)] == MOI.VariableIndex(4) end @testset "qp2_test - ignore y - no obj" begin @@ -322,31 +330,34 @@ @test MOI.constant.(eq_con1_fun) == 0.0 @test MOI.constant(eq_con1_set) == -1.0 - primal_con_dual_var = primal_dual_map.primal_con_dual_var - @test primal_con_dual_var[MOI.ConstraintIndex{ + primal_con_to_dual_var_vec = primal_dual_map.primal_con_to_dual_var_vec + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, }( 1, )] == [MOI.VariableIndex(1)] @test !(haskey( - primal_con_dual_var, + primal_con_to_dual_var_vec, MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(1), )) - @test primal_con_dual_var[MOI.ConstraintIndex{ + @test primal_con_to_dual_var_vec[MOI.ConstraintIndex{ MOI.VariableIndex, MOI.GreaterThan{Float64}, }( 2, )] == [MOI.VariableIndex(2)] - @test isempty(primal_dual_map.primal_var_dual_con) + @test isempty(primal_dual_map.primal_var_to_dual_con) - primal_parameter = primal_dual_map.primal_parameter - @test primal_parameter[MOI.VariableIndex(2)] == MOI.VariableIndex(3) + primal_parameter_to_dual_parameter = + primal_dual_map.primal_parameter_to_dual_parameter + @test primal_parameter_to_dual_parameter[MOI.VariableIndex(2)] == + MOI.VariableIndex(3) - primal_var_dual_quad_slack = primal_dual_map.primal_var_dual_quad_slack - @test primal_var_dual_quad_slack[MOI.VariableIndex(1)] == + primal_var_in_quad_obj_to_dual_slack_var = + primal_dual_map.primal_var_in_quad_obj_to_dual_slack_var + @test primal_var_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(1)] == MOI.VariableIndex(4) end end diff --git a/test/Tests/test_structures.jl b/test/Tests/test_structures.jl index 13e07101..be1a244d 100644 --- a/test/Tests/test_structures.jl +++ b/test/Tests/test_structures.jl @@ -7,7 +7,7 @@ primal_dual_map = Dualization.PrimalDualMap{Float64}() @test Dualization.is_empty(primal_dual_map) push!( - primal_dual_map.primal_var_dual_con, + primal_dual_map.primal_var_to_dual_con, MOI.VariableIndex(1) => MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo}(1), ) diff --git a/test/Tests/test_supported.jl b/test/Tests/test_supported.jl index 96569709..6d48f749 100644 --- a/test/Tests/test_supported.jl +++ b/test/Tests/test_supported.jl @@ -11,9 +11,9 @@ obj_typ_svf = MOI.get(lp1_test(), MOI.ObjectiveFunctionType()) obj_typ_saf = MOI.get(lp10_test(), MOI.ObjectiveFunctionType()) obj_typ_qp = MOI.get(qp1_test(), MOI.ObjectiveFunctionType()) - @test Dualization.supported_obj(obj_typ_svf) - @test Dualization.supported_obj(obj_typ_saf) - @test Dualization.supported_obj(obj_typ_qp) + @test Dualization.supported_objective(obj_typ_svf) + @test Dualization.supported_objective(obj_typ_saf) + @test Dualization.supported_objective(obj_typ_qp) end @testset "supported_constraints" begin diff --git a/test/optimize_abstract_models.jl b/test/optimize_abstract_models.jl index 2b7776ac..04823c6b 100644 --- a/test/optimize_abstract_models.jl +++ b/test/optimize_abstract_models.jl @@ -7,10 +7,7 @@ Attach an MOI.ModelLike to an optimizer, solve it and retrieve the termination status and objective value """ -function solve_abstract_model( - model::MOI.ModelLike, - optimizer_constructor, -) where {T} +function solve_abstract_model(model::MOI.ModelLike, optimizer_constructor) JuMP_model = JuMP.Model() MOI.copy_to(JuMP.backend(JuMP_model), model) set_optimizer(JuMP_model, optimizer_constructor)