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 c74abb8..1d3e57e 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,60 @@ 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 = _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) + 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 = _dual_set_type(S) + if D === nothing + return false + 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 = _dual_set_type(S) +# if D === nothing +# return false +# end +# return MOI.supports_constraint(optimizer.dual_problem.dual_model, +# MOI.VectorAffineFunction{T}, D) +#end function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike; kwargs...) # Dualize the original problem @@ -171,13 +196,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..21a5845 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, _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, _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..666aeb0 100644 --- a/src/dual_sets.jl +++ b/src/dual_sets.jl @@ -1,14 +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 dual_set(::MOI.GreaterThan{T}) where T +function _dual_set(::MOI.GreaterThan{T}) where T return MOI.GreaterThan(zero(T)) end +function _dual_set_type(::Type{MOI.GreaterThan{T}}) where T + return MOI.GreaterThan{T} +end -function dual_set(::MOI.LessThan{T}) where T +function _dual_set(::MOI.LessThan{T}) where T return MOI.LessThan(zero(T)) end +function _dual_set_type(::Type{MOI.LessThan{T}}) where T + return MOI.GreaterThan{T} +end -function dual_set(::MOI.EqualTo{T}) where T +function _dual_set(::MOI.EqualTo{T}) where T return # Maybe return Reals in the future -end \ No newline at end of file +end +function _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 """ 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_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 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