From 48d0d813d482350421afa754935d9bddd01183f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 6 Apr 2020 12:24:20 +0200 Subject: [PATCH 1/4] Fix MOI.supports and MOI.supports_constraint for DualOptimizer --- src/MOI_wrapper.jl | 84 +++++++++++++++++++++----------- src/add_dual_cone_constraint.jl | 23 +++++---- src/dual_equality_constraints.jl | 40 +++++++-------- src/dual_model_variables.jl | 51 ++++++++----------- src/dual_sets.jl | 19 ++++++-- src/dualize.jl | 2 +- src/objective_coefficients.jl | 30 ++++++------ 7 files changed, 139 insertions(+), 110 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c74abb8..de8ff3e 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -2,20 +2,6 @@ export DualOptimizer, dual_optimizer dual_optimizer(optimizer_constructor) = () -> DualOptimizer(MOI.instantiate(optimizer_constructor)) -# Supported Functions -const SF = Union{MOI.SingleVariable, - MOI.ScalarAffineFunction{Float64}, - MOI.VectorOfVariables, - MOI.VectorAffineFunction{Float64}} - -# Supported Sets -const SS = Union{MOI.EqualTo{Float64}, MOI.GreaterThan{Float64}, MOI.LessThan{Float64}, - MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, - MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, - MOI.ExponentialCone, MOI.DualExponentialCone, - MOI.PowerCone, MOI.DualPowerCone, - MOI.PositiveSemidefiniteConeTriangle} - struct DualOptimizer{T, OT <: MOI.ModelLike} <: MOI.AbstractOptimizer dual_problem::DualProblem{T, OT} @@ -65,21 +51,63 @@ function DualOptimizer() end function MOI.supports(::DualOptimizer, - ::Union{MOI.ObjectiveSense, - MOI.ObjectiveFunction{MOI.SingleVariable}, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, - MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}, - }) + ::MOI.ObjectiveSense) return true end - -function MOI.supports_constraint(optimizer::DualOptimizer, F::Type{<:SF}, S::Type{<:SS}) - return MOI.supports_constraint(optimizer.dual_problem.dual_model, F, S) +function MOI.supports(optimizer::DualOptimizer{T}, + ::MOI.ObjectiveFunction{F}) where {T, F} + # If the objective function is `MOI.SingleVariable` or `MOI.ScalarAffineFunction`, + # a `MOI.ScalarAffineFunction` is set as objective function for the dual problem. + # If it is `MOI.ScalarQuadraticFunction` , a `MOI.ScalarQuadraticFunction` is set as objective function for the dual problem. + G = F <: MOI.ScalarQuadraticFunction ? MOI.ScalarQuadraticFunction{T} : MOI.ScalarAffineFunction{T} + return supported_obj(F) && MOI.supports(optimizer.dual_problem.dual_model, MOI.ObjectiveFunction{G}()) +end + +function MOI.supports_constraint( + optimizer::DualOptimizer{T}, + F::Type{<:Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}}}, + S::Type{<:MOI.AbstractScalarSet}) where T + D = try + D = dual_set_type(S) + catch + return false # The fallback of `dual_set_type` throws an error. + end + if D <: MOI.AbstractVectorSet # The dual of `EqualTo` is `Reals` + return MOI.supports_add_constrained_variables(optimizer.dual_problem.dual_model, D) + else + return MOI.supports_add_constrained_variable(optimizer.dual_problem.dual_model, D) + end end -function MOI.supports_constraint(::DualOptimizer, ::Type{MOI.AbstractFunction}, ::Type{MOI.AbstractSet}) - return false -end +function MOI.supports_constraint( + optimizer::DualOptimizer{T}, + F::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, + S::Type{<:MOI.AbstractVectorSet}) where T + D = try + D = dual_set_type(S) + catch + return false # The fallback of `dual_set_type` throws an error. + end + return MOI.supports_add_constrained_variables(optimizer.dual_problem.dual_model, D) +end + +# TODO add this when constrained variables are implemented +#function MOI.supports_add_constrained_variables( +# optimizer::DualOptimizer{T}, S::Type{MOI.Reals}) where T +# return MOI.supports_constraint(optimizer.dual_problem.dual_model, +# MOI.ScalarAffineFunction{T}, +# MOI.EqualTo{T}) # If it was `MOI.Zeros`, we would not need this method as special case of the one below +#end +#function MOI.supports_add_constrained_variables( +# optimizer::DualOptimizer{T}, S::Type{<:MOI.AbstractVectorSet}) where T +# D = try +# D = dual_set_type(S) +# catch +# return false # The fallback of `dual_set_type` throws an error. +# end +# return MOI.supports_constraint(optimizer.dual_problem.dual_model, +# MOI.VectorAffineFunction{T}, MOI.dual_set_type(S)) +#end function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike; kwargs...) # Dualize the original problem @@ -171,13 +199,13 @@ function MOI.get(optimizer::DualOptimizer, ::MOI.ConstraintPrimal, return MOI.get(optimizer.dual_problem.dual_model, MOI.ConstraintDual(), ci_dual_problem) - primal_ci_constant end -function MOI.get(optimizer::DualOptimizer, ::MOI.ConstraintPrimal, - ci::CI{F,S}) where {F <: MOI.AbstractVectorFunction, S <: MOI.AbstractVectorSet} +function MOI.get(optimizer::DualOptimizer{T}, ::MOI.ConstraintPrimal, + ci::CI{F,S}) where {T, F <: MOI.AbstractVectorFunction, S <: MOI.AbstractVectorSet} # If it has no key than there is no dual constraint if !haskey(optimizer.dual_problem.primal_dual_map.primal_con_dual_con, ci) # The number of dual variable associated with the primal constraint is the ci dimension ci_dimension = length(get_vis_dual_problem(optimizer, ci)) - return zeros(Float64, ci_dimension) + return zeros(T, ci_dimension) end ci_dual_problem = get_ci_dual_problem(optimizer, ci) return MOI.get(optimizer.dual_problem.dual_model, MOI.ConstraintDual(), ci_dual_problem) diff --git a/src/add_dual_cone_constraint.jl b/src/add_dual_cone_constraint.jl index 3351022..33c4371 100644 --- a/src/add_dual_cone_constraint.jl +++ b/src/add_dual_cone_constraint.jl @@ -1,20 +1,23 @@ -function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, vi::Vector{VI}, +function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::CI{F, S}) where {F <: MOI.AbstractScalarFunction, S <: MOI.AbstractScalarSet} - # In this case vi should have only one entry - return MOI.add_constraint(dual_model, SVF(vi[1]), MOI.dual_set(get_set(primal_model, ci))) + vi, con_index = MOI.add_constrained_variable(dual_model, MOI.dual_set(get_set(primal_model, ci))) + return [vi], con_index end -function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, vi::Vector{VI}, +function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::CI{F, MOI.EqualTo{T}}) where {T, F <: MOI.AbstractScalarFunction} - return + vi = MOI.add_variable(dual_model) + return [vi], nothing end -function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, vis::Vector{VI}, +function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::CI{F, S}) where {F <: MOI.AbstractVectorFunction, S <: MOI.AbstractVectorSet} - return MOI.add_constraint(dual_model, VVF(vis), MOI.dual_set(get_set(primal_model, ci))) + return MOI.add_constrained_variables(dual_model, MOI.dual_set(get_set(primal_model, ci))) end -function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, vis::Vector{VI}, +function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::CI{F, MOI.Zeros}) where {F <: MOI.AbstractVectorFunction} - return -end \ No newline at end of file + # Add as many variables as the dimension of the constraint + vis = MOI.add_variables(dual_model, get_ci_row_dimension(primal_model, ci)) + return vis, nothing +end diff --git a/src/dual_equality_constraints.jl b/src/dual_equality_constraints.jl index 9fc009b..d090d6e 100644 --- a/src/dual_equality_constraints.jl +++ b/src/dual_equality_constraints.jl @@ -11,7 +11,7 @@ function add_dual_equality_constraints(dual_model::MOI.ModelLike, primal_model:: # Loop at every constraint to get the scalar affine terms scalar_affine_terms = get_scalar_affine_terms(primal_model, - primal_dual_map.primal_con_dual_var, + primal_dual_map.primal_con_dual_var, all_variables, con_types, T) # get RHS from objective coeficients @@ -32,9 +32,9 @@ function add_dual_equality_constraints(dual_model::MOI.ModelLike, primal_model:: MOI.ScalarAffineFunction(scalar_affine_terms[primal_vi], zero(T)), MOI.EqualTo(sense_change * get(scalar_terms, primal_vi, zero(T)))) #Set constraint name with the name of the associated priaml variable - if !is_empty(dual_names) - set_dual_constraint_name(dual_model, primal_model, primal_vi, dual_ci, - dual_names.dual_constraint_name_prefix) + if !is_empty(dual_names) + set_dual_constraint_name(dual_model, primal_model, primal_vi, dual_ci, + dual_names.dual_constraint_name_prefix) end # Add primal variable to dual contraint to the link dictionary push!(primal_dual_map.primal_var_dual_con, primal_vi => dual_ci) @@ -79,11 +79,11 @@ function add_scalar_affine_terms_from_quad_params( end end -function set_dual_constraint_name(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, +function set_dual_constraint_name(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, primal_vi::VI, dual_ci::CI, prefix::String) - MOI.set(dual_model, MOI.ConstraintName(), dual_ci, + MOI.set(dual_model, MOI.ConstraintName(), dual_ci, prefix*MOI.get(primal_model, MOI.VariableName(), primal_vi)) - return + return end function get_scalar_terms(primal_model::MOI.ModelLike, @@ -106,7 +106,7 @@ function get_scalar_affine_terms(primal_model::MOI.ModelLike, variables::Vector{VI}, con_types::Vector{Tuple{DataType, DataType}}, T::Type) - + scalar_affine_terms = Dict{VI,Vector{MOI.ScalarAffineTerm{T}}}() for vi in variables scalar_affine_terms[vi] = MOI.ScalarAffineTerm{T}[] @@ -114,8 +114,8 @@ function get_scalar_affine_terms(primal_model::MOI.ModelLike, for (F, S) in con_types primal_cis = MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}()) # Constraints of type {F, S} for ci in primal_cis - fill_scalar_affine_terms!(scalar_affine_terms, primal_con_dual_var, - primal_model, ci) + fill_scalar_affine_terms!(scalar_affine_terms, primal_con_dual_var, + primal_model, ci) end end return scalar_affine_terms @@ -132,7 +132,7 @@ end function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}}, primal_con_dual_var::Dict{CI, Vector{VI}}, - primal_model::MOI.ModelLike, ci::CI{SAF{T}, S}, + primal_model::MOI.ModelLike, ci::CI{SAF{T}, S}, ) where {T, S <: Union{MOI.GreaterThan{T}, MOI.LessThan{T}, MOI.EqualTo{T}}} @@ -143,12 +143,12 @@ function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.Scala push_to_scalar_affine_terms!(scalar_affine_terms[term.variable_index], MOI.coefficient(term), dual_vi) end - return + return end function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}}, primal_con_dual_var::Dict{CI, Vector{VI}}, - primal_model::MOI.ModelLike, ci::CI{SVF, S}, + primal_model::MOI.ModelLike, ci::CI{SVF, S}, ) where {T, S <: Union{MOI.GreaterThan{T}, MOI.LessThan{T}, MOI.EqualTo{T}}} @@ -156,12 +156,12 @@ function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.Scala moi_function = get_function(primal_model, ci) dual_vi = primal_con_dual_var[ci][1] # In this case we only have one vi push_to_scalar_affine_terms!(scalar_affine_terms[moi_function.variable], one(T), dual_vi) - return + return end function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}}, primal_con_dual_var::Dict{CI, Vector{VI}}, - primal_model::MOI.ModelLike, ci::CI{VAF{T}, S}, + primal_model::MOI.ModelLike, ci::CI{VAF{T}, S}, ) where {T, S <: MOI.AbstractVectorSet} moi_function = get_function(primal_model, ci) @@ -170,7 +170,7 @@ function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.Scala dual_vi = primal_con_dual_var[ci][term.output_index] # term.output_index is the row of the VAF, # it corresponds to the dual variable associated with this constraint - push_to_scalar_affine_terms!(scalar_affine_terms[term.scalar_term.variable_index], + push_to_scalar_affine_terms!(scalar_affine_terms[term.scalar_term.variable_index], set_dot(term.output_index, set, T)*MOI.coefficient(term), dual_vi) end return @@ -178,17 +178,17 @@ end function fill_scalar_affine_terms!(scalar_affine_terms::Dict{VI,Vector{MOI.ScalarAffineTerm{T}}}, primal_con_dual_var::Dict{CI, Vector{VI}}, - primal_model::MOI.ModelLike, ci::CI{VVF, S}, + primal_model::MOI.ModelLike, ci::CI{VVF, S}, ) where {T, S <: MOI.AbstractVectorSet} moi_function = get_function(primal_model, ci) set = get_set(primal_model, ci) for (i, variable) in enumerate(moi_function.variables) dual_vi = primal_con_dual_var[ci][i] - push_to_scalar_affine_terms!(scalar_affine_terms[variable], + push_to_scalar_affine_terms!(scalar_affine_terms[variable], set_dot(i, set, T)*one(T), dual_vi) end - return + return end function set_dot(i::Int, s::MOI.AbstractVectorSet, T::Type) @@ -198,4 +198,4 @@ function set_dot(i::Int, s::MOI.AbstractVectorSet, T::Type) end function set_dot(i::Int, s::MOI.AbstractScalarSet, T::Type) return one(T) -end \ No newline at end of file +end diff --git a/src/dual_model_variables.jl b/src/dual_model_variables.jl index b9df19d..ba56a0f 100644 --- a/src/dual_model_variables.jl +++ b/src/dual_model_variables.jl @@ -16,14 +16,14 @@ function add_dual_vars_in_dual_cones(dual_model::MOI.ModelLike, primal_model::MO 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{CI, Vector{T}}, +# Utils for primal_con_constants dict +function push_to_primal_con_constants!(primal_model::MOI.ModelLike, primal_con_constants::Dict{CI, Vector{T}}, ci::CI{F, S}) where {T, F <: MOI.AbstractScalarFunction, S <: MOI.AbstractScalarSet} push!(primal_con_constants, ci => get_scalar_term(primal_model, ci)) - return + return end -function push_to_primal_con_constants!(primal_model::MOI.ModelLike, primal_con_constants::Dict{CI, Vector{T}}, +function push_to_primal_con_constants!(primal_model::MOI.ModelLike, primal_con_constants::Dict{CI, Vector{T}}, ci::CI{F, S}) where {T, F <: MOI.AbstractVectorFunction, S <: MOI.AbstractVectorSet} return # No constants need to be passed to the DualOptimizer in this case, don't need to push zero to the dict end @@ -31,7 +31,7 @@ end # Utils for primal_con_dual_con dict function push_to_primal_con_dual_con!(primal_con_dual_con::Dict{CI, CI}, ci::CI, ci_dual::CI) push!(primal_con_dual_con, ci => ci_dual) - return + return end function push_to_primal_con_dual_con!(primal_con_dual_con::Dict{CI, CI}, ci::CI, ci_dual::Nothing) @@ -46,7 +46,7 @@ function push_to_dual_obj_aff_terms!(primal_model::MOI.ModelLike, dual_obj_affin if !iszero(value) # If value is different than 0 add to the dictionary push!(dual_obj_affine_terms, vi => value) end - return + return end function push_to_dual_obj_aff_terms!(primal_model::MOI.ModelLike, dual_obj_affine_terms::Dict{VI, T}, vi::VI, @@ -54,14 +54,13 @@ function push_to_dual_obj_aff_terms!(primal_model::MOI.ModelLike, dual_obj_affin return # It is zero so don't push to the dual_obj_affine_terms end -function _add_dual_variable(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, dual_names::DualNames, - primal_con_dual_var::Dict{CI, Vector{VI}}, dual_obj_affine_terms::Dict{VI, T}, - ci::CI{F, S}) where {T, F <: MOI.AbstractFunction, - S <: MOI.AbstractSet} +function add_dual_variable( + dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, + dual_names::DualNames, primal_con_dual_var::Dict{CI, Vector{VI}}, + dual_obj_affine_terms::Dict{VI, T}, ci::CI{F, S} +) where {T, F <: MOI.AbstractFunction, S <: MOI.AbstractSet} - # Change to add_constrained_varibales in MOI 0.9.0 - # because of https://github.com/guilhermebodin/Dualization.jl/issues/9 - vis = MOI.add_variables(dual_model, get_ci_row_dimension(primal_model, ci)) # Add as many variables as the dimension of the constraint + vis, con_index = add_dual_cone_constraint(dual_model, primal_model, ci) push!(primal_con_dual_var, ci => vis) # Add the map of the added dual variable to the relationated constraint # Get constraint name ci_name = MOI.get(primal_model, MOI.ConstraintName(), ci) @@ -69,26 +68,16 @@ function _add_dual_variable(dual_model::MOI.ModelLike, primal_model::MOI.ModelLi for (i, vi) in enumerate(vis) push_to_dual_obj_aff_terms!(primal_model, dual_obj_affine_terms, vi, ci, i) if !is_empty(dual_names) - set_dual_variable_name(dual_model, vi, i, ci_name, + set_dual_variable_name(dual_model, vi, i, ci_name, dual_names.dual_variable_name_prefix) end end - return vis -end - -function add_dual_variable(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, dual_names::DualNames, - primal_con_dual_var::Dict{CI, Vector{VI}}, dual_obj_affine_terms::Dict{VI, T}, - ci::CI{F, S}) where {T, F <: MOI.AbstractFunction, - S <: MOI.AbstractSet} - - vis = _add_dual_variable(dual_model, primal_model, dual_names, - primal_con_dual_var, dual_obj_affine_terms, ci) - return add_dual_cone_constraint(dual_model, primal_model, vis, ci) + return con_index end function set_dual_variable_name(dual_model::MOI.ModelLike, vi::VI, i::Int, ci_name::String, prefix::String) MOI.set(dual_model, MOI.VariableName(), vi, prefix*ci_name*"_$i") - return + return end function add_primal_parameter_vars(dual_model::MOI.ModelLike, @@ -135,14 +124,14 @@ end # Save mapping between primal parameter and dual parameter function push_to_primal_parameter!(primal_parameter::Dict{VI, VI}, vi::VI, vi_dual::VI) push!(primal_parameter, vi => vi_dual) - return + return end # Add name to parameter variable function set_parameter_variable_name(dual_model::MOI.ModelLike, vi::VI, 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 + return end function add_quadratic_slack_vars(dual_model::MOI.ModelLike, @@ -174,12 +163,12 @@ end # Save mapping between primal variable and dual quadratic slack function push_to_quad_slack!(dual_quad_slack::Dict{VI, VI}, vi::VI, vi_dual::VI) push!(dual_quad_slack, vi => vi_dual) - return + return end # set name for dual quadratic slack function set_quad_slack_name(dual_model::MOI.ModelLike, vi::VI, vi_name::String, dual_names) prefix = dual_names.quadratic_slack_name_prefix == "" ? "quadslack_" : dual_names.quadratic_slack_name_prefix MOI.set(dual_model, MOI.VariableName(), vi, prefix*vi_name) - return -end \ No newline at end of file + return +end diff --git a/src/dual_sets.jl b/src/dual_sets.jl index eb4af40..2a6dfaf 100644 --- a/src/dual_sets.jl +++ b/src/dual_sets.jl @@ -1,14 +1,23 @@ export dual_set -# Additional dual_set -function dual_set(::MOI.GreaterThan{T}) where T +# Additional dual_set +function MOI.dual_set(::MOI.GreaterThan{T}) where T return MOI.GreaterThan(zero(T)) end +function MOI.dual_set_type(::Type{MOI.GreaterThan{T}}) where T + return MOI.GreaterThan{T} +end -function dual_set(::MOI.LessThan{T}) where T +function MOI.dual_set(::MOI.LessThan{T}) where T return MOI.LessThan(zero(T)) end +function MOI.dual_set_type(::Type{MOI.LessThan{T}}) where T + return MOI.GreaterThan{T} +end -function dual_set(::MOI.EqualTo{T}) where T +function MOI.dual_set(::MOI.EqualTo{T}) where T return # Maybe return Reals in the future -end \ No newline at end of file +end +function MOI.dual_set_type(::Type{<:MOI.EqualTo}) + return MOI.Reals +end diff --git a/src/dualize.jl b/src/dualize.jl index 1f90819..e618e50 100644 --- a/src/dualize.jl +++ b/src/dualize.jl @@ -30,7 +30,7 @@ function dualize(primal_model::MOI.ModelLike, dual_problem::DualProblem{T}, set_dual_model_sense(dual_problem.dual_model, primal_model) # Get Primal Objective Coefficients - primal_objective = get_primal_objective(primal_model, variable_parameters) + primal_objective = get_primal_objective(primal_model, variable_parameters, T) # Add variables to the dual model and their dual cone constraint. # Return a dictionary from dual variables to primal constraints constants (obj coef of dual var) diff --git a/src/objective_coefficients.jl b/src/objective_coefficients.jl index 2c8d785..b1ed427 100644 --- a/src/objective_coefficients.jl +++ b/src/objective_coefficients.jl @@ -15,15 +15,15 @@ function set_dual_model_sense(dual_model::MOI.ModelLike, primal_model::MOI.Model return end -function _scalar_quadratic_function(func::MOI.ScalarQuadraticFunction{T}) where T +function _scalar_quadratic_function(func::MOI.ScalarQuadraticFunction{T}, ::Type{T}) where T return MOIU.canonical(func) end -function _scalar_quadratic_function(func::MOI.ScalarAffineFunction{T}) where T +function _scalar_quadratic_function(func::MOI.ScalarAffineFunction{T}, ::Type{T}) where T return _scalar_quadratic_function( - SQF{T}(func.terms, MOI.ScalarQuadraticTerm{T}[], func.constant)) + SQF{T}(func.terms, MOI.ScalarQuadraticTerm{T}[], func.constant), T) end -function _scalar_quadratic_function(func::MOI.SingleVariable) - return _scalar_quadratic_function(SAF{Float64}(func)) +function _scalar_quadratic_function(func::MOI.SingleVariable, T::Type) + return _scalar_quadratic_function(SAF{T}(func), T) end # Primals @@ -38,7 +38,7 @@ mutable struct PrimalObjective{T} obj_parametric::Union{SQF{T},Nothing} function PrimalObjective{T}(obj) where T - canonical_obj = _scalar_quadratic_function(obj) + canonical_obj = _scalar_quadratic_function(obj, T) # if isempty(canonical_obj.terms) # error("Dualization does not support models with no variables in the objective function.") # end @@ -75,18 +75,18 @@ function get_affine_terms(objective::Objective{T}) where T return objective.obj.affine_terms end -function get_primal_objective(primal_model::MOI.ModelLike) - T = MOI.get(primal_model, MOI.ObjectiveFunctionType()) - return _get_primal_objective(MOI.get(primal_model, MOI.ObjectiveFunction{T}())) +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)# where T - return PrimalObjective{Float64}(obj_fun) +function _get_primal_objective(obj_fun, T::Type) + return PrimalObjective{T}(obj_fun) end # allow removing variables from objective function -function get_primal_objective(primal_model::MOI.ModelLike, variable_parameters::Vector{VI}) - p_obj = get_primal_objective(primal_model) +function get_primal_objective(primal_model::MOI.ModelLike, variable_parameters::Vector{VI}, T::Type) + p_obj = get_primal_objective(primal_model, T) if length(variable_parameters) > 0 vars_func, quad_cross_params, params_func = split_variables(p_obj.obj, variable_parameters) p_obj.obj = vars_func @@ -158,10 +158,10 @@ function set_dual_objective(dual_model::MOI.ModelLike, if MOIU.number_of_quadratic_terms(T, raw_obj) > 0 MOI.set(dual_model, MOI.ObjectiveFunction{SQF{T}}(), raw_obj) else - MOI.set(dual_model, MOI.ObjectiveFunction{SAF{T}}(), + MOI.set(dual_model, MOI.ObjectiveFunction{SAF{T}}(), SAF{T}(raw_obj.affine_terms, raw_obj.constant)) end - return + return end """ From e2a54d42cef76dd6a64d97d8794684b02e29be8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 6 Apr 2020 19:36:11 +0200 Subject: [PATCH 2/4] Address comment --- src/MOI_wrapper.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index de8ff3e..9661756 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -68,7 +68,7 @@ function MOI.supports_constraint( F::Type{<:Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}}}, S::Type{<:MOI.AbstractScalarSet}) where T D = try - D = dual_set_type(S) + dual_set_type(S) catch return false # The fallback of `dual_set_type` throws an error. end @@ -84,7 +84,7 @@ function MOI.supports_constraint( F::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, S::Type{<:MOI.AbstractVectorSet}) where T D = try - D = dual_set_type(S) + dual_set_type(S) catch return false # The fallback of `dual_set_type` throws an error. end @@ -101,7 +101,7 @@ end #function MOI.supports_add_constrained_variables( # optimizer::DualOptimizer{T}, S::Type{<:MOI.AbstractVectorSet}) where T # D = try -# D = dual_set_type(S) +# dual_set_type(S) # catch # return false # The fallback of `dual_set_type` throws an error. # end From 88c78b509c310b82f537305e4d51ce5f1c4d582c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 7 Apr 2020 12:05:44 +0200 Subject: [PATCH 3/4] Add _dual_set and _dual_set_type --- src/Dualization.jl | 2 - src/MOI_wrapper.jl | 23 ++++----- src/add_dual_cone_constraint.jl | 4 +- src/dual_sets.jl | 24 ++++++--- src/supported.jl | 51 ++---------------- test/Tests/test_dual_sets.jl | 92 +++++++++++++++++---------------- 6 files changed, 80 insertions(+), 116 deletions(-) diff --git a/src/Dualization.jl b/src/Dualization.jl index 512784c..9f09371 100644 --- a/src/Dualization.jl +++ b/src/Dualization.jl @@ -15,8 +15,6 @@ const VQF{T} = MOI.VectorQuadraticFunction{T} const VI = MOI.VariableIndex const CI = MOI.ConstraintIndex -import MathOptInterface: dual_set - include("structures.jl") include("utils.jl") include("dual_sets.jl") diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 9661756..1d3e57e 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -67,10 +67,9 @@ function MOI.supports_constraint( optimizer::DualOptimizer{T}, F::Type{<:Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}}}, S::Type{<:MOI.AbstractScalarSet}) where T - D = try - dual_set_type(S) - catch - return false # The fallback of `dual_set_type` throws an error. + D = _dual_set_type(S) + if D === nothing + return false end if D <: MOI.AbstractVectorSet # The dual of `EqualTo` is `Reals` return MOI.supports_add_constrained_variables(optimizer.dual_problem.dual_model, D) @@ -83,10 +82,9 @@ function MOI.supports_constraint( optimizer::DualOptimizer{T}, F::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, S::Type{<:MOI.AbstractVectorSet}) where T - D = try - dual_set_type(S) - catch - return false # The fallback of `dual_set_type` throws an error. + D = _dual_set_type(S) + if D === nothing + return false end return MOI.supports_add_constrained_variables(optimizer.dual_problem.dual_model, D) end @@ -100,13 +98,12 @@ end #end #function MOI.supports_add_constrained_variables( # optimizer::DualOptimizer{T}, S::Type{<:MOI.AbstractVectorSet}) where T -# D = try -# dual_set_type(S) -# catch -# return false # The fallback of `dual_set_type` throws an error. +# D = _dual_set_type(S) +# if D === nothing +# return false # end # return MOI.supports_constraint(optimizer.dual_problem.dual_model, -# MOI.VectorAffineFunction{T}, MOI.dual_set_type(S)) +# MOI.VectorAffineFunction{T}, D) #end function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike; kwargs...) diff --git a/src/add_dual_cone_constraint.jl b/src/add_dual_cone_constraint.jl index 33c4371..21a5845 100644 --- a/src/add_dual_cone_constraint.jl +++ b/src/add_dual_cone_constraint.jl @@ -1,6 +1,6 @@ function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::CI{F, S}) where {F <: MOI.AbstractScalarFunction, S <: MOI.AbstractScalarSet} - vi, con_index = MOI.add_constrained_variable(dual_model, MOI.dual_set(get_set(primal_model, ci))) + vi, con_index = MOI.add_constrained_variable(dual_model, _dual_set(get_set(primal_model, ci))) return [vi], con_index end @@ -12,7 +12,7 @@ end function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, ci::CI{F, S}) where {F <: MOI.AbstractVectorFunction, S <: MOI.AbstractVectorSet} - return MOI.add_constrained_variables(dual_model, MOI.dual_set(get_set(primal_model, ci))) + return MOI.add_constrained_variables(dual_model, _dual_set(get_set(primal_model, ci))) end function add_dual_cone_constraint(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, diff --git a/src/dual_sets.jl b/src/dual_sets.jl index 2a6dfaf..666aeb0 100644 --- a/src/dual_sets.jl +++ b/src/dual_sets.jl @@ -1,23 +1,31 @@ -export dual_set +# We define `_dual_set` instead as adding methods for `MOI.dual_set` on MOI sets is type piracy. +_dual_set(set::MOI.AbstractSet) = MOI.dual_set(set) +function _dual_set_type(S::Type) + return try + MOI.dual_set_type(S) + catch + return nothing # The fallback of `dual_set_type` throws an error. + end +end + -# Additional dual_set -function MOI.dual_set(::MOI.GreaterThan{T}) where T +function _dual_set(::MOI.GreaterThan{T}) where T return MOI.GreaterThan(zero(T)) end -function MOI.dual_set_type(::Type{MOI.GreaterThan{T}}) where T +function _dual_set_type(::Type{MOI.GreaterThan{T}}) where T return MOI.GreaterThan{T} end -function MOI.dual_set(::MOI.LessThan{T}) where T +function _dual_set(::MOI.LessThan{T}) where T return MOI.LessThan(zero(T)) end -function MOI.dual_set_type(::Type{MOI.LessThan{T}}) where T +function _dual_set_type(::Type{MOI.LessThan{T}}) where T return MOI.GreaterThan{T} end -function MOI.dual_set(::MOI.EqualTo{T}) where T +function _dual_set(::MOI.EqualTo{T}) where T return # Maybe return Reals in the future end -function MOI.dual_set_type(::Type{<:MOI.EqualTo}) +function _dual_set_type(::Type{<:MOI.EqualTo}) return MOI.Reals end diff --git a/src/supported.jl b/src/supported.jl index 2bd92d5..c522235 100644 --- a/src/supported.jl +++ b/src/supported.jl @@ -12,52 +12,9 @@ function supported_constraints(con_types::Vector{Tuple{DataType, DataType}}) return end -# General case -supported_constraint(::DataType, ::DataType) = false -# List of supported constraints -# SVF - Linear -supported_constraint(::Type{SVF}, ::Type{<:MOI.GreaterThan}) = true -supported_constraint(::Type{SVF}, ::Type{<:MOI.LessThan}) = true -supported_constraint(::Type{SVF}, ::Type{<:MOI.EqualTo}) = true -# SAF - Linear -supported_constraint(::Type{SAF{T}}, ::Type{MOI.GreaterThan{T}}) where T = true -supported_constraint(::Type{SAF{T}}, ::Type{MOI.LessThan{T}}) where T = true -supported_constraint(::Type{SAF{T}}, ::Type{MOI.EqualTo{T}}) where T = true -# VVF - Linear -supported_constraint(::Type{VVF}, ::Type{MOI.Nonpositives}) = true -supported_constraint(::Type{VVF}, ::Type{MOI.Nonnegatives}) = true -supported_constraint(::Type{VVF}, ::Type{MOI.Zeros}) = true -# VAF - Linear -supported_constraint(::Type{<:VAF}, ::Type{MOI.Nonpositives}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.Nonnegatives}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.Zeros}) = true -# SOC -supported_constraint(::Type{VVF}, ::Type{MOI.SecondOrderCone}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.SecondOrderCone}) = true -# RotatedSOC -supported_constraint(::Type{VVF}, ::Type{MOI.RotatedSecondOrderCone}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.RotatedSecondOrderCone}) = true -# SDP Triangle -supported_constraint(::Type{VVF}, ::Type{MOI.PositiveSemidefiniteConeTriangle}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.PositiveSemidefiniteConeTriangle}) = true -# ExponentialCone -supported_constraint(::Type{VVF}, ::Type{MOI.ExponentialCone}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.ExponentialCone}) = true -# DualExponentialCone -supported_constraint(::Type{VVF}, ::Type{MOI.DualExponentialCone}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.DualExponentialCone}) = true -# PowerCone -supported_constraint(::Type{VVF}, ::Type{<:MOI.PowerCone}) = true -supported_constraint(::Type{VAF{T}}, ::Type{MOI.PowerCone{T}}) where T = true -# DualPowerCone -supported_constraint(::Type{VVF}, ::Type{<:MOI.DualPowerCone}) = true -supported_constraint(::Type{VAF{T}}, ::Type{MOI.DualPowerCone{T}}) where T = true -# NormOneCone -supported_constraint(::Type{VVF}, ::Type{MOI.NormOneCone}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.NormOneCone}) = true -# NormInfinityCone -supported_constraint(::Type{VVF}, ::Type{MOI.NormInfinityCone}) = true -supported_constraint(::Type{<:VAF}, ::Type{MOI.NormInfinityCone}) = true +supported_constraint(::Type, ::Type) = false +supported_constraint(::Type{<:Union{MOI.SingleVariable, MOI.ScalarAffineFunction}}, S::Type{<:MOI.AbstractScalarSet}) = _dual_set_type(S) !== nothing +supported_constraint(::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction}}, S::Type{<:MOI.AbstractVectorSet}) = _dual_set_type(S) !== nothing """ supported_objective(primal_model::MOI.ModelLike) @@ -73,7 +30,7 @@ function supported_objective(primal_model::MOI.ModelLike) end # General case -supported_obj(::Any) = false +supported_obj(::Type) = false # List of supported objective functions supported_obj(::Type{SVF}) = true supported_obj(::Type{<:SAF}) = true diff --git a/test/Tests/test_dual_sets.jl b/test/Tests/test_dual_sets.jl index 904c6c7..3030b89 100644 --- a/test/Tests/test_dual_sets.jl +++ b/test/Tests/test_dual_sets.jl @@ -1,4 +1,7 @@ @testset "dual_sets.jl" begin + @test Dualization._dual_set_type(MOI.Integer) === nothing + @test Dualization._dual_set_type(MOI.ZeroOne) === nothing + greater_than_Float64 = MOI.GreaterThan(0.0) greater_than_Int64 = MOI.GreaterThan(0) less_than_Float64 = MOI.LessThan(0.0) @@ -11,77 +14,78 @@ zeros_4 = MOI.Zeros(4) # GreaterThan - @test MOI.dual_set(MOI.GreaterThan(0.0)) == greater_than_Float64 - @test MOI.dual_set(MOI.GreaterThan(1.0)) == greater_than_Float64 - @test MOI.dual_set(MOI.GreaterThan(-1.0)) == greater_than_Float64 - @test MOI.dual_set(MOI.GreaterThan(0)) == greater_than_Int64 - @test MOI.dual_set(MOI.GreaterThan(1)) == greater_than_Int64 - @test MOI.dual_set(MOI.GreaterThan(-1)) == greater_than_Int64 + @test Dualization._dual_set(MOI.GreaterThan(0.0)) == greater_than_Float64 + @test Dualization._dual_set(MOI.GreaterThan(1.0)) == greater_than_Float64 + @test Dualization._dual_set(MOI.GreaterThan(-1.0)) == greater_than_Float64 + @test Dualization._dual_set(MOI.GreaterThan(0)) == greater_than_Int64 + @test Dualization._dual_set(MOI.GreaterThan(1)) == greater_than_Int64 + @test Dualization._dual_set(MOI.GreaterThan(-1)) == greater_than_Int64 # LessThan - @test MOI.dual_set(MOI.LessThan(0.0)) == less_than_Float64 - @test MOI.dual_set(MOI.LessThan(1.0)) == less_than_Float64 - @test MOI.dual_set(MOI.LessThan(-1.0)) == less_than_Float64 - @test MOI.dual_set(MOI.LessThan(0)) == less_than_Int64 - @test MOI.dual_set(MOI.LessThan(1)) == less_than_Int64 - @test MOI.dual_set(MOI.LessThan(-1)) == less_than_Int64 + @test Dualization._dual_set(MOI.LessThan(0.0)) == less_than_Float64 + @test Dualization._dual_set(MOI.LessThan(1.0)) == less_than_Float64 + @test Dualization._dual_set(MOI.LessThan(-1.0)) == less_than_Float64 + @test Dualization._dual_set(MOI.LessThan(0)) == less_than_Int64 + @test Dualization._dual_set(MOI.LessThan(1)) == less_than_Int64 + @test Dualization._dual_set(MOI.LessThan(-1)) == less_than_Int64 # EqualTo - @test MOI.dual_set(MOI.EqualTo(0.0)) == nothing - @test MOI.dual_set(MOI.EqualTo(1.0)) == nothing - @test MOI.dual_set(MOI.EqualTo(-1.0)) == nothing - @test MOI.dual_set(MOI.EqualTo(0)) == nothing - @test MOI.dual_set(MOI.EqualTo(1)) == nothing - @test MOI.dual_set(MOI.EqualTo(-1)) == nothing + @test Dualization._dual_set(MOI.EqualTo(0.0)) == nothing + @test Dualization._dual_set(MOI.EqualTo(1.0)) == nothing + @test Dualization._dual_set(MOI.EqualTo(-1.0)) == nothing + @test Dualization._dual_set(MOI.EqualTo(0)) == nothing + @test Dualization._dual_set(MOI.EqualTo(1)) == nothing + @test Dualization._dual_set(MOI.EqualTo(-1)) == nothing # Nonpositives - @test MOI.dual_set(nonpositives_3) == nonpositives_3 - @test MOI.dual_set(nonpositives_4) == nonpositives_4 - + @test Dualization._dual_set(nonpositives_3) == nonpositives_3 + @test Dualization._dual_set(nonpositives_4) == nonpositives_4 + # Nonnegatives - @test MOI.dual_set(nonnegatives_3) == nonnegatives_3 - @test MOI.dual_set(nonnegatives_4) == nonnegatives_4 + @test Dualization._dual_set(nonnegatives_3) == nonnegatives_3 + @test Dualization._dual_set(nonnegatives_4) == nonnegatives_4 # Zeros - @test MOI.dual_set(zeros_3) == MOI.Reals(3) - @test MOI.dual_set(zeros_4) == MOI.Reals(4) + @test Dualization._dual_set(zeros_3) == MOI.Reals(3) + @test Dualization._dual_set(zeros_4) == MOI.Reals(4) #SOC soc = MOI.SecondOrderCone(2) soc3 = MOI.SecondOrderCone(3) - @test MOI.dual_set(soc) == soc - @test MOI.dual_set(soc) != soc3 - @test MOI.dual_set(soc3) == soc3 + @test Dualization._dual_set(soc) == soc + @test Dualization._dual_set(soc) != soc3 + @test Dualization._dual_set(soc3) == soc3 #RSOC rsoc = MOI.RotatedSecondOrderCone(2) rsoc3 = MOI.RotatedSecondOrderCone(3) - @test MOI.dual_set(rsoc) == rsoc - @test MOI.dual_set(rsoc) != rsoc3 - @test MOI.dual_set(rsoc3) == rsoc3 + @test Dualization._dual_set(rsoc) == rsoc + @test Dualization._dual_set(rsoc) != rsoc3 + @test Dualization._dual_set(rsoc3) == rsoc3 #PSD + @test Dualization._dual_set_type(MOI.PositiveSemidefiniteConeTriangle) == MOI.PositiveSemidefiniteConeTriangle psd = MOI.PositiveSemidefiniteConeTriangle(2) psd3 = MOI.PositiveSemidefiniteConeTriangle(3) - @test MOI.dual_set(psd) == psd - @test MOI.dual_set(psd) != psd3 - @test MOI.dual_set(psd3) == psd3 + @test Dualization._dual_set(psd) == psd + @test Dualization._dual_set(psd) != psd3 + @test Dualization._dual_set(psd3) == psd3 # Exponential exp = MOI.ExponentialCone() dual_exp = MOI.DualExponentialCone() - @test MOI.dual_set(exp) == dual_exp - @test MOI.dual_set(exp) != exp - @test MOI.dual_set(dual_exp) == exp - @test MOI.dual_set(dual_exp) != dual_exp + @test Dualization._dual_set(exp) == dual_exp + @test Dualization._dual_set(exp) != exp + @test Dualization._dual_set(dual_exp) == exp + @test Dualization._dual_set(dual_exp) != dual_exp # Power pow = MOI.PowerCone(0.3) pow4 = MOI.PowerCone(0.4) dual_pow = MOI.DualPowerCone(0.3) - @test MOI.dual_set(pow) == dual_pow - @test MOI.dual_set(pow) != pow - @test MOI.dual_set(dual_pow) == pow - @test MOI.dual_set(dual_pow) != pow4 - @test MOI.dual_set(dual_pow) != dual_pow -end \ No newline at end of file + @test Dualization._dual_set(pow) == dual_pow + @test Dualization._dual_set(pow) != pow + @test Dualization._dual_set(dual_pow) == pow + @test Dualization._dual_set(dual_pow) != pow4 + @test Dualization._dual_set(dual_pow) != dual_pow +end From 01419d8c3a6610c7ba91b9314e603900d6d5d3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 7 Apr 2020 22:01:16 +0200 Subject: [PATCH 4/4] Exclude failing test for CSDP --- test/Tests/test_MOI_wrapper.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/Tests/test_MOI_wrapper.jl b/test/Tests/test_MOI_wrapper.jl index 910fa69..e1f7c25 100644 --- a/test/Tests/test_MOI_wrapper.jl +++ b/test/Tests/test_MOI_wrapper.jl @@ -12,7 +12,7 @@ "linear12", # Asks for infeasibility ray "linear13", # Feasibility problem "linear15" # Feasibility when written in the canonical form - ]) + ]) end end @@ -22,10 +22,11 @@ conic_cached = MOIU.CachingOptimizer(conic_cache, opt) conic_bridged = MOIB.full_bridge_optimizer(conic_cached, Float64) - @testset "coninc linear, soc, rsoc and sdp test" begin + @testset "conic linear, soc, rsoc and sdp test" begin MOIT.contconictest(conic_bridged, conic_config, ["lin3", # Feasibility problem "lin4", # Feasibility problem + "geomean3f", "geomean3v", # CSDP does not converge after https://github.com/JuliaOpt/Dualization.jl/pull/86 "normone2", # Feasibility problem "norminf2", # Feasibility problem "soc3", # Feasibility problem