diff --git a/src/constrained_variables.jl b/src/constrained_variables.jl index 4ecc19d..681a250 100644 --- a/src/constrained_variables.jl +++ b/src/constrained_variables.jl @@ -83,3 +83,16 @@ function _select_constrained_variables( end return end + +function _get_parameter_variables(::PrimalDualMap{T}, primal_model) where {T} + cis = MOI.get( + primal_model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Parameter{T}}(), + ) + parameters = Set{MOI.VariableIndex}() + for ci in cis + vi = MOI.get(primal_model, MOI.ConstraintFunction(), ci) + push!(parameters, vi) + end + return parameters +end diff --git a/src/dual_equality_constraints.jl b/src/dual_equality_constraints.jl index 6e68e20..098091a 100644 --- a/src/dual_equality_constraints.jl +++ b/src/dual_equality_constraints.jl @@ -410,6 +410,15 @@ function _fill_scalar_affine_terms!( return end +function _fill_scalar_affine_terms!( + ::Dict{MOI.VariableIndex,Vector{MOI.ScalarAffineTerm{T}}}, + primal_constraint_data, + ::MOI.ModelLike, + ::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}}, +) where {T} + return +end + function _fill_scalar_affine_terms!( scalar_affine_terms::Dict{ MOI.VariableIndex, diff --git a/src/dual_model_variables.jl b/src/dual_model_variables.jl index fe44042..ae3a589 100644 --- a/src/dual_model_variables.jl +++ b/src/dual_model_variables.jl @@ -10,8 +10,10 @@ function _add_dual_vars_in_dual_cones( primal_dual_map::PrimalDualMap{T}, dual_names::DualNames, con_types::Vector{Tuple{Type,Type}}, + variable_parameters, ) where {T} dual_obj_affine_terms = Dict{MOI.VariableIndex,T}() + parameters_set = Set(variable_parameters) for (F, S) in con_types _add_dual_vars_in_dual_cones( dual_obj_affine_terms, @@ -21,6 +23,7 @@ function _add_dual_vars_in_dual_cones( dual_names, F, S, + parameters_set, ) end return dual_obj_affine_terms @@ -34,13 +37,22 @@ function _add_dual_vars_in_dual_cones( dual_names::DualNames, ::Type{F}, ::Type{S}, + parameters_set, ) 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 haskey(primal_dual_map.primal_constrained_variables, ci) - # primal constraints that are the main constraints of - # constrained variables have no dual variable associated - # bacause they associated with dual constraints + if haskey(primal_dual_map.primal_constrained_variables, ci) || + # primal constraints that are the main constraints of + # constrained variables have no dual variable associated + # bacause they associated with dual constraints + ( + F <: MOI.VariableIndex && + MOI.get(primal_model, MOI.ConstraintFunction(), ci) in + parameters_set + ) + # if a parameter is constrained, either because the set is + # Parameter or because it was user defined parameter its constraint + # will lead to a useless dual variable. continue end # Add dual variable to dual cone diff --git a/src/dualize.jl b/src/dualize.jl index 9c490c9..fcc8518 100644 --- a/src/dualize.jl +++ b/src/dualize.jl @@ -69,7 +69,7 @@ function dualize( primal_model::MOI.ModelLike, dual_problem::DualProblem{T}, dual_names::DualNames, - variable_parameters::Vector{MOI.VariableIndex}, + _variable_parameters::Vector{MOI.VariableIndex}, ignore_objective::Bool, consider_constrained_variables::Bool, ) where {T} @@ -80,6 +80,15 @@ function dualize( con_types = MOI.get(primal_model, MOI.ListOfConstraintTypesPresent()) supported_constraints(con_types) # Errors if constraint cant be dualized + # merge user listed parameters and MOI.VariableIndex-in-MOI.Parameter{T} + moi_parameters = + _get_parameter_variables(dual_problem.primal_dual_map, primal_model) + all_parameters = Set{MOI.VariableIndex}(_variable_parameters) + for p in moi_parameters + push!(all_parameters, p) + end + variable_parameters = collect(all_parameters) + # Set the dual model objective sense _set_dual_model_sense(dual_problem.dual_model, primal_model) @@ -98,6 +107,8 @@ function dualize( # the respective primal variable will not be a constrained variable (with # respect to that constraint). # Constrained variables are registered in `primal_constrained_variables`. + # Since MOI.VariableIndex-in-MOI.Parameter{T} are in the variable_parameters + # list, they are not considered constrained variables, they are parameters. if consider_constrained_variables _select_constrained_variables( dual_problem, @@ -132,6 +143,7 @@ function dualize( dual_problem.primal_dual_map, dual_names, con_types, + variable_parameters, ) # Creates variables in the dual problem that represent parameters in the diff --git a/src/supported.jl b/src/supported.jl index eb2c62c..f87ca00 100644 --- a/src/supported.jl +++ b/src/supported.jl @@ -28,6 +28,13 @@ end supported_constraint(::Type, ::Type) = false +function supported_constraint( + ::Type{MOI.VariableIndex}, + S::Type{MOI.Parameter{T}}, +) where {T} + return true +end + function supported_constraint( ::Type{<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction}}, S::Type{<:MOI.AbstractScalarSet}, diff --git a/test/Tests/test_partial_dual_linear.jl b/test/Tests/test_partial_dual_linear.jl index 20455cd..9a3a5db 100644 --- a/test/Tests/test_partial_dual_linear.jl +++ b/test/Tests/test_partial_dual_linear.jl @@ -106,14 +106,13 @@ s.t. 2x1 + x2 - 3 <= 0 :y_2 x1 + 2x2 - 3 <= 0 :y_3 - x1 >= 1 :y_1 + x1 >= 1 :no dual here x2 >= 0 ignore x_1 during dualization dual obj ignored s.t. -y_2 - 2y_3 >= 3 :x_2 - y_1 >= 0 y_2 <= 0 y_3 <= 0 =# @@ -126,11 +125,10 @@ dual_model = dual.dual_model primal_dual_map = dual.primal_dual_map - @test MOI.get(dual_model, MOI.NumberOfVariables()) == 3 + @test MOI.get(dual_model, MOI.NumberOfVariables()) == 2 list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) @test Set(list_of_cons) == Set( [ - (MOI.VariableIndex, MOI.GreaterThan{Float64}) (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) (MOI.VectorOfVariables, MOI.Nonpositives) ], @@ -141,7 +139,7 @@ MOI.VariableIndex, MOI.GreaterThan{Float64}, }(), - ) == 1 + ) == 0 @test MOI.get( dual_model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonpositives}(), @@ -185,8 +183,7 @@ MOI.GreaterThan{Float64}, }(), ) - @test primal_constraint_data[vgt1].dual_variables == - [MOI.VariableIndex(3)] + @test !haskey(primal_constraint_data, vgt1) # as this was not dualized for (vi, data) in primal_dual_map.primal_variable_data vi = MOI.VariableIndex(2) @@ -203,7 +200,7 @@ min 4x_3 + 5 s.t. x_1 + 2x_2 + x_3 <= 20 :y_3 - x_1 <= 1 :y_1 + x_1 <= 1 : # no dual here: y_1 x_2 <= 3 :y_2 ignoring x_1 and x_3 dual @@ -212,7 +209,7 @@ # y_1 + y_3 == 0 :x_1 y_2 + 2y_3 == 0 :x_2 # y_3 == 4.0 :x_3 - y_1 <= 0 + # y_1 <= 0 y_2 <= 0 y_3 <= 0 =# @@ -228,7 +225,7 @@ dual_model = dual.dual_model primal_dual_map = dual.primal_dual_map - @test MOI.get(dual_model, MOI.NumberOfVariables()) == 3 + @test MOI.get(dual_model, MOI.NumberOfVariables()) == 2 list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) @test Set(list_of_cons) == Set( [ @@ -239,7 +236,7 @@ @test MOI.get( dual_model, MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(), - ) == 3 + ) == 2 @test MOI.get( dual_model, MOI.NumberOfConstraints{ @@ -278,13 +275,118 @@ MOI.LessThan{Float64}, }( 2, - )].dual_variables == [MOI.VariableIndex(3)] + )].dual_variables == [MOI.VariableIndex(2)] + @test !haskey( + primal_constraint_data, + MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}(1), + ) @test primal_constraint_data[MOI.ConstraintIndex{ - MOI.VariableIndex, + MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, }( 1, + )].dual_variables == [MOI.VariableIndex(1)] + + primal_variable_data = primal_dual_map.primal_variable_data + @test primal_variable_data[MOI.VariableIndex(2)].dual_constraint == + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ) + end + + @testset "lp12_test - x_1 ignored x_3 moi_param" begin + #= + primal + min 4x_3 + 5 + s.t. + x_1 + 2x_2 + x_3 <= 20 :y_3 + x_1 <= 1 : # no dual here: y_1 + x_2 <= 3 :y_2 + ignoring x_1 and x_3 + dual + obj ignored + s.t. + # y_1 + y_3 == 0 :x_1 + y_2 + 2y_3 == 0 :x_2 + # y_3 == 4.0 :x_3 + # y_1 <= 0 + y_2 <= 0 + y_3 <= 0 + =# + primal_model = lp12_test() + MOI.add_constraint( + primal_model, + MOI.VariableIndex(3), + MOI.Parameter{Float64}(0.0), + ) + dual = Dualization.dualize( + primal_model, + variable_parameters = MOI.VariableIndex[MOI.VariableIndex(1), + # MOI.VariableIndex(3), # as a param + ], + ignore_objective = true, + ) + dual_model = dual.dual_model + primal_dual_map = dual.primal_dual_map + + @test MOI.get(dual_model, MOI.NumberOfVariables()) == 2 + list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) + @test Set(list_of_cons) == Set( + [ + (MOI.VariableIndex, MOI.LessThan{Float64}) + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) + ], + ) + @test MOI.get( + dual_model, + MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(), + ) == 2 + @test MOI.get( + dual_model, + MOI.NumberOfConstraints{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + ) == 1 + + eq_con2_fun = MOI.get( + dual_model, + MOI.ConstraintFunction(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ), + ) + eq_con2_set = MOI.get( + dual_model, + MOI.ConstraintSet(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ), + ) + @test MOI.coefficient.(eq_con2_fun.terms) == [2.0; 1.0] + @test MOI.constant.(eq_con2_fun) == 0.0 + @test MOI.constant(eq_con2_set) == 0.0 + + primal_constraint_data = primal_dual_map.primal_constraint_data + @test primal_constraint_data[MOI.ConstraintIndex{ + MOI.VariableIndex, + MOI.LessThan{Float64}, + }( + 2, )].dual_variables == [MOI.VariableIndex(2)] + @test !haskey( + primal_constraint_data, + MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}(1), + ) @test primal_constraint_data[MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, diff --git a/test/Tests/test_partial_dual_quadratic.jl b/test/Tests/test_partial_dual_quadratic.jl index 9621345..a064570 100644 --- a/test/Tests/test_partial_dual_quadratic.jl +++ b/test/Tests/test_partial_dual_quadratic.jl @@ -147,35 +147,40 @@ @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 + @testset "qp1_test - z moi_param" begin #= - min 2 x^2 + y^2 + xy + x + y + 1 + min x^2 + xy + y^2 + yz + z^2 s.t. - x + y = 1 (a) - x >= 0 (b) - y >= 0 (c) + x + 2y + 3z >= 4 (a) + x + y >= 1 (b) + x,y,z \in R dual - max 1 + y + a - y a - y c - 2 w1^2 + y^2 + max 4a - 3z a + b - w1^2 - w1 w2 - w2^2 + z^2 s.t. - - a - b + y + 4w1 + 1 = 0 - a \in R + - a - b + 2 w1 + 1 w2 = 0 + -2a - b + 1 w1 + 2 w2 + z = 0 + a >= 0 b >= 0 - c >= 0 =# - primal_model = qp2_test() + primal_model = qp1_test() + MOI.add_constraint( + primal_model, + MOI.VariableIndex(3), + MOI.Parameter{Float64}(1.0), + ) dual = Dualization.dualize( primal_model, - variable_parameters = MOI.VariableIndex[MOI.VariableIndex(2)], + # variable_parameters = MOI.VariableIndex[MOI.VariableIndex(3)], ) dual_model = dual.dual_model primal_dual_map = dual.primal_dual_map - @test MOI.get(dual_model, MOI.NumberOfVariables()) == 4 + @test MOI.get(dual_model, MOI.NumberOfVariables()) == 2 + 2 + 1 list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) @test Set(list_of_cons) == Set( [ (MOI.VariableIndex, MOI.GreaterThan{Float64}) - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) ], ) @test MOI.get( @@ -184,7 +189,149 @@ MOI.VariableIndex, MOI.GreaterThan{Float64}, }(), - ) == 1 + ) == 2 + @test MOI.get( + dual_model, + MOI.NumberOfConstraints{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }(), + ) == 2 + obj_type = MOI.get(dual_model, MOI.ObjectiveFunctionType()) + @test obj_type == MOI.ScalarQuadraticFunction{Float64} + obj = MOI.get(dual_model, MOI.ObjectiveFunction{obj_type}()) + @test MOI.constant(obj) == 0.0 + @test MOI.coefficient.(obj.affine_terms) == [1.0; 4.0] + @test MOI.coefficient.(obj.quadratic_terms) == + [-2.0; -1.0; -2.0; 2.0; -3.0] + + eq_con1_fun = MOI.get( + dual_model, + MOI.ConstraintFunction(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ), + ) + eq_con1_set = MOI.get( + dual_model, + MOI.ConstraintSet(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ), + ) + @test MOI.coefficient.(eq_con1_fun.terms) == [1.0; 1.0; -2.0; -1.0] + @test MOI.constant.(eq_con1_fun) == 0.0 + @test MOI.constant(eq_con1_set) == 0.0 + eq_con2_fun = MOI.get( + dual_model, + MOI.ConstraintFunction(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 2, + ), + ) + eq_con2_set = MOI.get( + dual_model, + MOI.ConstraintSet(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 2, + ), + ) + @test MOI.coefficient.(eq_con2_fun.terms) == + [2.0; 1.0; -1.0; -1.0; -2.0] + @test MOI.constant.(eq_con2_fun) == 0.0 + @test MOI.constant(eq_con2_set) == 0.0 + + primal_constraint_data = primal_dual_map.primal_constraint_data + @test primal_constraint_data[MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + }( + 1, + )].dual_variables == [MOI.VariableIndex(1)] + @test primal_constraint_data[MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + }( + 2, + )].dual_variables == [MOI.VariableIndex(2)] + + primal_variable_data = primal_dual_map.primal_variable_data + @test primal_variable_data[MOI.VariableIndex(1)].dual_constraint == + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 1, + ) + @test primal_variable_data[MOI.VariableIndex(2)].dual_constraint == + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }( + 2, + ) + + 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_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_in_quad_obj_to_dual_slack_var[MOI.VariableIndex(2)] == + MOI.VariableIndex(2 + 1 + 2) + end + @testset "qp2_test - ignore y" begin + #= + min 2 x^2 + y^2 + xy + x + y + 1 + s.t. + x + y = 1 (a) + x >= 0 (b) # no dual becaus is interpreted as a constrainted var + y >= 0 (c) # ignored so no dual + dual + # max 1 + y + a - y a - y c - 2 w1^2 + y^2 + max 1 + y + a - y a - 2 w1^2 + y^2 + s.t. + - a - b + y + 4w1 + 1 = 0 # - a + y + 4w1 + 1 >= 0 + a \in R + # b >= 0 # does no exist as is a constrained variable con + # c >= 0 # ignored so no dual + =# + primal_model = qp2_test() + dual = Dualization.dualize( + primal_model, + variable_parameters = MOI.VariableIndex[MOI.VariableIndex(2)], + ) + dual_model = dual.dual_model + primal_dual_map = dual.primal_dual_map + + @test MOI.get(dual_model, MOI.NumberOfVariables()) == 3 + list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) + @test Set(list_of_cons) == Set([( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + )],) + @test MOI.get( + dual_model, + MOI.NumberOfConstraints{ + MOI.VariableIndex, + MOI.GreaterThan{Float64}, + }(), + ) == 0 @test MOI.get( dual_model, MOI.NumberOfConstraints{ @@ -197,7 +344,7 @@ obj = MOI.get(dual_model, MOI.ObjectiveFunction{obj_type}()) @test MOI.constant(obj) == 1.0 @test MOI.coefficient.(obj.affine_terms) == [1.0; 1.0] - @test MOI.coefficient.(obj.quadratic_terms) == [-4.0; 2.0; -1.0; -1.0] + @test MOI.coefficient.(obj.quadratic_terms) == [-4.0; 2.0; -1.0] eq_con1_fun = MOI.get( dual_model, @@ -234,12 +381,10 @@ primal_constraint_data, MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(1), ) - @test primal_constraint_data[MOI.ConstraintIndex{ - MOI.VariableIndex, - MOI.GreaterThan{Float64}, - }( - 2, - )].dual_variables == [MOI.VariableIndex(2)] + @test !haskey( + primal_constraint_data, + MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(2), + ) for (vi, data) in primal_dual_map.primal_variable_data @test vi == MOI.VariableIndex(1) @@ -258,12 +403,12 @@ 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) + MOI.VariableIndex(2) 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) + MOI.VariableIndex(3) end @testset "qp2_test - ignore y - no obj" begin #= @@ -289,21 +434,19 @@ dual_model = dual.dual_model primal_dual_map = dual.primal_dual_map - @test MOI.get(dual_model, MOI.NumberOfVariables()) == 4 + @test MOI.get(dual_model, MOI.NumberOfVariables()) == 3 list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent()) - @test Set(list_of_cons) == Set( - [ - (MOI.VariableIndex, MOI.GreaterThan{Float64}) - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) - ], - ) + @test Set(list_of_cons) == Set([( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + )],) @test MOI.get( dual_model, MOI.NumberOfConstraints{ MOI.VariableIndex, MOI.GreaterThan{Float64}, }(), - ) == 1 + ) == 0 @test MOI.get( dual_model, MOI.NumberOfConstraints{ @@ -352,12 +495,10 @@ primal_constraint_data, MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(1), )) - @test primal_constraint_data[MOI.ConstraintIndex{ - MOI.VariableIndex, - MOI.GreaterThan{Float64}, - }( - 2, - )].dual_variables == [MOI.VariableIndex(2)] + @test !haskey( + primal_constraint_data, + MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{Float64}}(2), + ) for (vi, data) in primal_dual_map.primal_variable_data @test vi == MOI.VariableIndex(1) @@ -376,11 +517,11 @@ 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) + MOI.VariableIndex(2) 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) + MOI.VariableIndex(3) end end