diff --git a/NEWS.md b/NEWS.md index 7c4dee3c5b..79d3f82791 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,11 @@ MathOptInterface (MOI) release notes ==================================== +v0.9.1 (?) +--------------------- + +- L_1 and L_∞ norm epigraph cones and corresponding bridges to LP were added (#818). + v0.9.0 (August 13, 2019) --------------------- diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 5041121052..e7b9988713 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -38,6 +38,9 @@ include("rsoc.jl") const RSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCBridge{T}, OT} include("quad_to_soc.jl") const QuadtoSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadtoSOCBridge{T}, OT} +include("norm_to_lp.jl") +const NormInfinity{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NormInfinityBridge{T}, OT} +const NormOne{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NormOneBridge{T}, OT} include("geomean.jl") const GeoMean{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GeoMeanBridge{T}, OT} include("square.jl") @@ -69,6 +72,8 @@ function add_all_bridges(bridged_model, T::Type) MOIB.add_bridge(bridged_model, VectorFunctionizeBridge{T}) MOIB.add_bridge(bridged_model, SplitIntervalBridge{T}) MOIB.add_bridge(bridged_model, QuadtoSOCBridge{T}) + MOIB.add_bridge(bridged_model, NormInfinityBridge{T}) + MOIB.add_bridge(bridged_model, NormOneBridge{T}) MOIB.add_bridge(bridged_model, GeoMeanBridge{T}) MOIB.add_bridge(bridged_model, SquareBridge{T}) MOIB.add_bridge(bridged_model, LogDetBridge{T}) diff --git a/src/Bridges/Constraint/norm_to_lp.jl b/src/Bridges/Constraint/norm_to_lp.jl new file mode 100644 index 0000000000..729bfce602 --- /dev/null +++ b/src/Bridges/Constraint/norm_to_lp.jl @@ -0,0 +1,151 @@ +""" + NormInfinityBridge{T} + +The `NormInfinityCone` is representable with LP constraints, since +``t \\ge \\max_i \\lvert x_i \\rvert`` if and only if +``t \\ge x_i`` and ``t \\ge -x_i`` for all ``i``. +""" +struct NormInfinityBridge{T, F, G} <: AbstractBridge + nn_index::CI{F, MOI.Nonnegatives} +end +function bridge_constraint(::Type{NormInfinityBridge{T, F, G}}, model::MOI.ModelLike, f::MOI.AbstractVectorFunction, s::MOI.NormInfinityCone) where {T, F, G} + f_scalars = MOIU.eachscalar(f) + t = f_scalars[1] + d = MOI.dimension(s) + lb = f_scalars[2:d] + ub = MOIU.operate(-, T, lb) + f_new = MOIU.operate(vcat, T, ub, lb) + for i in 1:MOI.output_dimension(f_new) + MOIU.operate_output_index!(+, T, i, f_new, t) + end + nn_index = MOI.add_constraint(model, f_new, MOI.Nonnegatives(MOI.output_dimension(f_new))) + return NormInfinityBridge{T, F, G}(nn_index) +end + +MOI.supports_constraint(::Type{NormInfinityBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormInfinityCone}) where T = true +MOIB.added_constrained_variable_types(::Type{<:NormInfinityBridge}) = Tuple{DataType}[] +MOIB.added_constraint_types(::Type{NormInfinityBridge{T, F, G}}) where {T, F, G} = [(F, MOI.Nonnegatives)] +function concrete_bridge_type(::Type{<:NormInfinityBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormInfinityCone}) where T + F = MOIU.promote_operation(+, T, G, G) + return NormInfinityBridge{T, F, G} +end + +# Attributes, Bridge acting as a model +MOI.get(b::NormInfinityBridge{T, F, G}, ::MOI.NumberOfConstraints{F, MOI.Nonnegatives}) where {T, F, G} = 1 +MOI.get(b::NormInfinityBridge{T, F, G}, ::MOI.ListOfConstraintIndices{F, MOI.Nonnegatives}) where {T, F, G} = [b.nn_index] + +# References +MOI.delete(model::MOI.ModelLike, c::NormInfinityBridge) = MOI.delete(model, c.nn_index) + +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintFunction, c::NormInfinityBridge{T, F, G}) where {T, F, G} + nn_func = MOIU.eachscalar(MOI.get(model, MOI.ConstraintFunction(), c.nn_index)) + t = MOIU.operate!(/, T, sum(nn_func), T(length(nn_func))) + d = div(length(nn_func), 2) + x = MOIU.operate!(/, T, MOIU.operate!(-, T, nn_func[(d + 1):end], nn_func[1:d]), T(2)) + return MOIU.convert_approx(G, MOIU.operate(vcat, T, t, x)) +end +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, c::NormInfinityBridge) + dim = 1 + div(MOI.dimension(MOI.get(model, MOI.ConstraintSet(), c.nn_index)), 2) + return MOI.NormInfinityCone(dim) +end +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintPrimal, c::NormInfinityBridge) + nn_primal = MOI.get(model, MOI.ConstraintPrimal(), c.nn_index) + t = sum(nn_primal) / length(nn_primal) + d = div(length(nn_primal), 2) + x = (nn_primal[(d + 1):end] - nn_primal[1:d]) / 2 + return vcat(t, x) +end +# Given a_i is dual on t - x_i >= 0 and b_i is dual on t + x_i >= 0, +# the dual on (t, x) in NormInfinityCone is (u, v) in NormOneCone, where +# v_i = -a_i + b_i and u = sum(a) + sum(b). +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintDual, c::NormInfinityBridge) + nn_dual = MOI.get(model, MOI.ConstraintDual(), c.nn_index) + t = sum(nn_dual) + d = div(length(nn_dual), 2) + x = (nn_dual[(d + 1):end] - nn_dual[1:d]) + return vcat(t, x) +end + +""" + NormOneBridge{T} + +The `NormOneCone` is representable with LP constraints, since +``t \\ge \\sum_i \\lvert x_i \\rvert`` if and only if there exists a vector y such that +``t \\ge \\sum_i y_i`` and ``y_i \\ge x_i``, ``y_i \\ge -x_i`` for all ``i``. +""" +struct NormOneBridge{T, F, G, H} <: AbstractBridge + y::Vector{MOI.VariableIndex} + ge_index::CI{F, MOI.GreaterThan{T}} + nn_index::CI{G, MOI.Nonnegatives} +end +function bridge_constraint(::Type{NormOneBridge{T, F, G, H}}, model::MOI.ModelLike, f::MOI.AbstractVectorFunction, s::MOI.NormOneCone) where {T, F, G, H} + f_scalars = MOIU.eachscalar(f) + d = MOI.dimension(s) + y = MOI.add_variables(model, d - 1) + ge_index = MOIU.normalize_and_add_constraint(model, MOIU.operate(-, T, f_scalars[1], MOIU.operate(sum, T, y)), MOI.GreaterThan(zero(T)), allow_modify_function=true) + lb = f_scalars[2:d] + ub = MOIU.operate(-, T, lb) + lb = MOIU.operate!(+, T, lb, MOI.VectorOfVariables(y)) + ub = MOIU.operate!(+, T, ub, MOI.VectorOfVariables(y)) + f_new = MOIU.operate(vcat, T, ub, lb) + nn_index = MOI.add_constraint(model, f_new, MOI.Nonnegatives(2d - 2)) + return NormOneBridge{T, F, G, H}(y, ge_index, nn_index) +end + +MOI.supports_constraint(::Type{NormOneBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormOneCone}) where T = true +MOIB.added_constrained_variable_types(::Type{<:NormOneBridge}) = Tuple{DataType}[] +MOIB.added_constraint_types(::Type{NormOneBridge{T, F, G, H}}) where {T, F, G, H} = [(F, MOI.GreaterThan{T}), (G, MOI.Nonnegatives)] +function concrete_bridge_type(::Type{<:NormOneBridge{T}}, H::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormOneCone}) where T + S = MOIU.scalar_type(H) + F = MOIU.promote_operation(+, T, S, S) + G = MOIU.promote_operation(+, T, H, H) + return NormOneBridge{T, F, G, H} +end + +# Attributes, Bridge acting as a model +MOI.get(b::NormOneBridge, ::MOI.NumberOfVariables) = length(b.y) +MOI.get(b::NormOneBridge, ::MOI.ListOfVariableIndices) = b.y +MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.NumberOfConstraints{F, MOI.GreaterThan{T}}) where {T, F, G, H} = 1 +MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.NumberOfConstraints{G, MOI.Nonnegatives}) where {T, F, G, H} = 1 +MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.ListOfConstraintIndices{F, MOI.GreaterThan{T}}) where {T, F, G, H} = [b.ge_index] +MOI.get(b::NormOneBridge{T, F, G, H}, ::MOI.ListOfConstraintIndices{G, MOI.Nonnegatives}) where {T, F, G, H} = [b.nn_index] + +# References +function MOI.delete(model::MOI.ModelLike, c::NormOneBridge) + MOI.delete(model, c.nn_index) + MOI.delete(model, c.ge_index) + MOI.delete(model, c.y) +end + +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintFunction, c::NormOneBridge{T, F, G, H}) where {T, F, G, H} + ge_func = MOI.get(model, MOI.ConstraintFunction(), c.ge_index) + nn_func = MOIU.eachscalar(MOI.get(model, MOI.ConstraintFunction(), c.nn_index)) + t = MOIU.operate!(+, T, ge_func, MOIU.operate!(/, T, sum(nn_func), T(2))) + d = div(length(nn_func), 2) + x = MOIU.operate!(/, T, MOIU.operate!(-, T, nn_func[(d + 1):end], nn_func[1:d]), T(2)) + return MOIU.convert_approx(H, MOIU.remove_variable(MOIU.operate(vcat, T, t, x), c.y)) +end +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, c::NormOneBridge) + dim = 1 + div(MOI.dimension(MOI.get(model, MOI.ConstraintSet(), c.nn_index)), 2) + return MOI.NormOneCone(dim) +end +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintPrimal, c::NormOneBridge) + ge_primal = MOI.get(model, MOI.ConstraintPrimal(), c.ge_index) + nn_primal = MOI.get(model, MOI.ConstraintPrimal(), c.nn_index) + t = ge_primal + sum(nn_primal) / 2 + d = length(c.y) + x = (nn_primal[(d + 1):end] - nn_primal[1:d]) / 2 + return vcat(t, x) +end +# Given a_i is dual on y_i - x_i >= 0 and b_i is dual on y_i + x_i >= 0 and c is dual on t - sum(y) >= 0, +# the dual on (t, x) in NormOneCone is (u, v) in NormInfinityCone, where +# v_i = -a_i + b_i and u = c. +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintDual, c::NormOneBridge) + t = MOI.get(model, MOI.ConstraintDual(), c.ge_index) + nn_dual = MOI.get(model, MOI.ConstraintDual(), c.nn_index) + d = div(length(nn_dual), 2) + x = (nn_dual[(d + 1):end] - nn_dual[1:d]) + return vcat(t, x) +end diff --git a/src/Test/UnitTests/basic_constraint_tests.jl b/src/Test/UnitTests/basic_constraint_tests.jl index 3456e22e7f..45be86fc8b 100644 --- a/src/Test/UnitTests/basic_constraint_tests.jl +++ b/src/Test/UnitTests/basic_constraint_tests.jl @@ -34,6 +34,8 @@ const BasicConstraintTests = Dict( (MOI.VectorOfVariables, MOI.Nonpositives) => ( dummy_vectorofvariables, 2, MOI.Nonpositives(2) ), (MOI.VectorOfVariables, MOI.Nonnegatives) => ( dummy_vectorofvariables, 2, MOI.Nonnegatives(2) ), + (MOI.VectorOfVariables, MOI.NormInfinityCone) => ( dummy_vectorofvariables, 3, MOI.NormInfinityCone(3) ), + (MOI.VectorOfVariables, MOI.NormOneCone) => ( dummy_vectorofvariables, 3, MOI.NormOneCone(3) ), (MOI.VectorOfVariables, MOI.SecondOrderCone) => ( dummy_vectorofvariables, 3, MOI.SecondOrderCone(3) ), (MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) => ( dummy_vectorofvariables, 3, MOI.RotatedSecondOrderCone(3) ), (MOI.VectorOfVariables, MOI.GeometricMeanCone) => ( dummy_vectorofvariables, 3, MOI.GeometricMeanCone(3) ), @@ -63,6 +65,8 @@ const BasicConstraintTests = Dict( (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_affine, 2, MOI.Nonpositives(2) ), (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_affine, 2, MOI.Nonnegatives(2) ), + (MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) => ( dummy_vector_affine, 3, MOI.NormInfinityCone(3) ), + (MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) => ( dummy_vector_affine, 3, MOI.NormOneCone(3) ), (MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => ( dummy_vector_affine, 3, MOI.SecondOrderCone(3) ), (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => ( dummy_vector_affine, 3, MOI.RotatedSecondOrderCone(3) ), @@ -70,6 +74,8 @@ const BasicConstraintTests = Dict( (MOI.VectorQuadraticFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_quadratic, 2, MOI.Nonpositives(2) ), (MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_quadratic, 2, MOI.Nonnegatives(2) ), + (MOI.VectorQuadraticFunction{Float64}, MOI.NormInfinityCone) => ( dummy_vector_quadratic, 3, MOI.NormInfinityCone(3) ), + (MOI.VectorQuadraticFunction{Float64}, MOI.NormOneCone) => ( dummy_vector_quadratic, 3, MOI.NormOneCone(3) ), (MOI.VectorQuadraticFunction{Float64}, MOI.SecondOrderCone) => ( dummy_vector_quadratic, 3, MOI.SecondOrderCone(3) ), (MOI.VectorQuadraticFunction{Float64}, MOI.RotatedSecondOrderCone) => ( dummy_vector_quadratic, 3, MOI.RotatedSecondOrderCone(3) ) ) diff --git a/src/Test/contconic.jl b/src/Test/contconic.jl index 7a44bab367..1a4b19cb04 100644 --- a/src/Test/contconic.jl +++ b/src/Test/contconic.jl @@ -302,12 +302,284 @@ const lintests = Dict("lin1v" => lin1vtest, @moitestset lin +function _norminf1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool) + atol = config.atol + rtol = config.rtol + # Problem NormInf1 + # max 0x + 1y + 1z + # st x == 1 + # y == 1/2 + # x >= ||(y,z)||_∞ + + @test MOIU.supports_default_copy_to(model, #=copy_names=# false) + @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + @test MOI.supports(model, MOI.ObjectiveSense()) + if vecofvars + @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) + else + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros) + end + @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.NormInfinityCone) + + MOI.empty!(model) + @test MOI.is_empty(model) + + x,y,z = MOI.add_variables(model, 3) + + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [y,z]), 0.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + + ceq1 = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x))], [-1.0]), MOI.Zeros(1)) + ceq2 = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y))], [-0.5]), MOI.Zeros(1)) + vov = MOI.VectorOfVariables([x,y,z]) + if vecofvars + ccone = MOI.add_constraint(model, vov, MOI.NormInfinityCone(3)) + else + ccone = MOI.add_constraint(model, MOI.VectorAffineFunction{Float64}(vov), MOI.NormInfinityCone(3)) + end + + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 2 + @test MOI.get(model, MOI.NumberOfConstraints{vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64},MOI.NormInfinityCone}()) == 1 + loc = MOI.get(model, MOI.ListOfConstraints()) + @test length(loc) == 2 + @test (MOI.VectorAffineFunction{Float64},MOI.Zeros) in loc + @test (vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) in loc + + if config.solve + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + + MOI.optimize!(model) + + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + if config.duals + @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT + end + + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1.5 atol=atol rtol=rtol + if config.duals + @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 1.5 atol=atol rtol=rtol + end + + @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 0.5 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), z) ≈ 1 atol=atol rtol=rtol + + @test MOI.get(model, MOI.ConstraintPrimal(), ceq1) ≈ [0] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintPrimal(), ceq2) ≈ [0] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintPrimal(), ccone) ≈ [1.0, 0.5, 1.0] atol=atol rtol=rtol + + if config.duals + @test MOI.get(model, MOI.ConstraintDual(), ceq1) ≈ [-1] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintDual(), ceq2) ≈ [-1] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintDual(), ccone) ≈ [1.0, 0.0, -1.0] atol=atol rtol=rtol + end + end +end + +norminf1vtest(model::MOI.ModelLike, config::TestConfig) = _norminf1test(model, config, true) +norminf1ftest(model::MOI.ModelLike, config::TestConfig) = _norminf1test(model, config, false) + +function norminf2test(model::MOI.ModelLike, config::TestConfig) + atol = config.atol + rtol = config.rtol + # Problem NormInf2 - Infeasible + # min 0 + # s.t. y ≥ 2 + # x ≤ 1 + # |y| ≤ x + # in conic form: + # min 0 + # s.t. -2 + y ∈ R₊ + # -1 + x ∈ R₋ + # (x,y) ∈ NormInf₂ + + @test MOIU.supports_default_copy_to(model, #=copy_names=# false) + @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + @test MOI.supports(model, MOI.ObjectiveSense()) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone) + + MOI.empty!(model) + @test MOI.is_empty(model) + + x,y = MOI.add_variables(model, 2) + + MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y))], [-2.0]), MOI.Nonnegatives(1)) + MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x))], [-1.0]), MOI.Nonpositives(1)) + MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,2], MOI.ScalarAffineTerm.(1.0, [x,y])), zeros(2)), MOI.NormInfinityCone(2)) + + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Nonnegatives}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Nonpositives}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.NormInfinityCone}()) == 1 + + if config.solve + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + + MOI.optimize!(model) + + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + + @test MOI.get(model, MOI.PrimalStatus()) in (MOI.NO_SOLUTION, + MOI.INFEASIBLE_POINT) + if config.duals + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + end + + # TODO test dual feasibility and objective sign + end +end + +const norminftests = Dict("norminf1v" => norminf1vtest, + "norminf1f" => norminf1ftest, + "norminf2" => norminf2test) + +@moitestset norminf + +function _normone1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool) + atol = config.atol + rtol = config.rtol + # Problem NormOne1 + # max 0x + 1y + 1z + # st x == 1 + # y == 1/2 + # x >= ||(y,z)||_1 + + @test MOIU.supports_default_copy_to(model, #=copy_names=# false) + @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + @test MOI.supports(model, MOI.ObjectiveSense()) + if vecofvars + @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros) + else + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros) + end + @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.NormOneCone) + + MOI.empty!(model) + @test MOI.is_empty(model) + + x,y,z = MOI.add_variables(model, 3) + + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], [y,z]), 0.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + + ceq1 = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x))], [-1.0]), MOI.Zeros(1)) + ceq2 = MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y))], [-0.5]), MOI.Zeros(1)) + vov = MOI.VectorOfVariables([x,y,z]) + if vecofvars + ccone = MOI.add_constraint(model, vov, MOI.NormOneCone(3)) + else + ccone = MOI.add_constraint(model, MOI.VectorAffineFunction{Float64}(vov), MOI.NormOneCone(3)) + end + + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 2 + @test MOI.get(model, MOI.NumberOfConstraints{vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64},MOI.NormOneCone}()) == 1 + loc = MOI.get(model, MOI.ListOfConstraints()) + @test length(loc) == 2 + @test (MOI.VectorAffineFunction{Float64},MOI.Zeros) in loc + @test (vecofvars ? MOI.VectorOfVariables : MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) in loc + + if config.solve + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + + MOI.optimize!(model) + + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + if config.duals + @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT + end + + @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1 atol=atol rtol=rtol + if config.duals + @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 1 atol=atol rtol=rtol + end + + @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 0.5 atol=atol rtol=rtol + @test MOI.get(model, MOI.VariablePrimal(), z) ≈ 0.5 atol=atol rtol=rtol + + @test MOI.get(model, MOI.ConstraintPrimal(), ceq1) ≈ [0] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintPrimal(), ceq2) ≈ [0] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintPrimal(), ccone) ≈ [1.0, 0.5, 0.5] atol=atol rtol=rtol + + if config.duals + @test MOI.get(model, MOI.ConstraintDual(), ceq1) ≈ [-1] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintDual(), ceq2) ≈ [0] atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintDual(), ccone) ≈ [1.0, -1.0, -1.0] atol=atol rtol=rtol + end + end +end + +normone1vtest(model::MOI.ModelLike, config::TestConfig) = _normone1test(model, config, true) +normone1ftest(model::MOI.ModelLike, config::TestConfig) = _normone1test(model, config, false) + +function normone2test(model::MOI.ModelLike, config::TestConfig) + atol = config.atol + rtol = config.rtol + # Problem NormOne2 - Infeasible + # min 0 + # s.t. y ≥ 2 + # x ≤ 1 + # |y| ≤ x + # in conic form: + # min 0 + # s.t. -2 + y ∈ R₊ + # -1 + x ∈ R₋ + # (x,y) ∈ NormOne₂ + + @test MOIU.supports_default_copy_to(model, #=copy_names=# false) + @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + @test MOI.supports(model, MOI.ObjectiveSense()) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) + @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.NormOneCone) + + MOI.empty!(model) + @test MOI.is_empty(model) + + x,y = MOI.add_variables(model, 2) + + MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y))], [-2.0]), MOI.Nonnegatives(1)) + MOI.add_constraint(model, MOI.VectorAffineFunction([MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x))], [-1.0]), MOI.Nonpositives(1)) + MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,2], MOI.ScalarAffineTerm.(1.0, [x,y])), zeros(2)), MOI.NormOneCone(2)) + + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Nonnegatives}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Nonpositives}()) == 1 + @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.NormOneCone}()) == 1 + + if config.solve + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + + MOI.optimize!(model) + + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + + @test MOI.get(model, MOI.PrimalStatus()) in (MOI.NO_SOLUTION, + MOI.INFEASIBLE_POINT) + if config.duals + @test MOI.get(model, MOI.DualStatus()) == MOI.INFEASIBILITY_CERTIFICATE + end + + # TODO test dual feasibility and objective sign + end +end + +const normonetests = Dict("normone1v" => normone1vtest, + "normone1f" => normone1ftest, + "normone2" => normone2test) + +@moitestset normone + function _soc1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool) atol = config.atol rtol = config.rtol # Problem SOC1 # max 0x + 1y + 1z - # st x == 1 + # st x == 1 # x >= ||(y,z)|| @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @@ -1772,6 +2044,8 @@ const rootdettests = Dict("rootdett" => rootdetttest, @moitestset rootdet true const contconictests = Dict("lin" => lintest, + "norminf" => norminftest, + "normone" => normonetest, "soc" => soctest, "rsoc" => rsoctest, "geomean" => geomeantest, diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index c69bd04ce3..2a80b8b02f 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -341,7 +341,7 @@ Sums the coefficients of `t1` and `t2` and returns an output `MOI.VectorAffineTe function unsafe_add(t1::VT, t2::VT) where VT <: Union{MOI.VectorAffineTerm, MOI.VectorQuadraticTerm} scalar_term = unsafe_add(t1.scalar_term, t2.scalar_term) - return MOI.VectorAffineTerm(t1.output_index, scalar_term) + return VT(t1.output_index, scalar_term) end """ diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 165ebe50be..77cc4747ad 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -1010,6 +1010,7 @@ const LessThanIndicatorSetZero{T} = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO, MOI.L (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval, MOI.Semicontinuous, MOI.Semiinteger), (MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, + MOI.NormInfinityCone, MOI.NormOneCone, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, MOI.ExponentialCone, MOI.DualExponentialCone, MOI.PositiveSemidefiniteConeTriangle, MOI.PositiveSemidefiniteConeSquare, diff --git a/src/sets.jl b/src/sets.jl index bcbabb6303..6e8575a283 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -35,7 +35,7 @@ function dimension end """ dual_set(s::AbstractSet) -Return the dual set of `s`, that is the dual cone of the set. This follows the +Return the dual set of `s`, that is the dual cone of the set. This follows the definition of duality discussed in [Duals](@ref). See [Dual cone](https://en.wikipedia.org/wiki/Dual_cone_and_polar_cone) for more information. If the dual cone is not defined it returns an error. @@ -184,10 +184,32 @@ constant(s::EqualTo) = s.value constant(s::GreaterThan) = s.lower constant(s::LessThan) = s.upper +""" + NormInfinityCone(dimension) + +The ``\\ell_\\infty``-norm cone ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\lVert x \\rVert_\\infty = \\max_i \\lvert x_i \\rvert \\}`` of dimension `dimension`. +""" +struct NormInfinityCone <: AbstractVectorSet + dimension::Int +end + +dual_set(s::NormInfinityCone) = NormOneCone(dimension(s)) + +""" + NormOneCone(dimension) + +The ``\\ell_1``-norm cone ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\lVert x \\rVert_\\infty_1 = \\sum_i \\lvert x_i \\rvert \\}`` of dimension `dimension`. +""" +struct NormOneCone <: AbstractVectorSet + dimension::Int +end + +dual_set(s::NormOneCone) = NormInfinityCone(dimension(s)) + """ SecondOrderCone(dimension) -The second-order cone (or Lorenz cone) ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge || x ||_2 \\}`` of dimension `dimension`. +The second-order cone (or Lorenz cone or ``\\ell_2``-norm cone) ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\lVert x \\rVert_2 \\}`` of dimension `dimension`. """ struct SecondOrderCone <: AbstractVectorSet dimension::Int @@ -198,7 +220,7 @@ dual_set(s::SecondOrderCone) = copy(s) """ RotatedSecondOrderCone(dimension) -The rotated second-order cone ``\\{ (t,u,x) \\in \\mathbb{R}^{dimension} : 2tu \\ge || x ||_2^2, t,u \\ge 0 \\}`` of dimension `dimension`. +The rotated second-order cone ``\\{ (t,u,x) \\in \\mathbb{R}^{dimension} : 2tu \\ge \\lVert x \\rVert_2^2, t,u \\ge 0 \\}`` of dimension `dimension`. """ struct RotatedSecondOrderCone <: AbstractVectorSet dimension::Int @@ -636,6 +658,7 @@ end # isbits types, nothing to copy function Base.copy(set::Union{Reals, Zeros, Nonnegatives, Nonpositives, GreaterThan, LessThan, EqualTo, Interval, + NormInfinityCone, NormOneCone, SecondOrderCone, RotatedSecondOrderCone, GeometricMeanCone, ExponentialCone, DualExponentialCone, PowerCone, DualPowerCone, diff --git a/test/Bridges/Constraint/Constraint.jl b/test/Bridges/Constraint/Constraint.jl index dbce4c1017..e24b3af367 100644 --- a/test/Bridges/Constraint/Constraint.jl +++ b/test/Bridges/Constraint/Constraint.jl @@ -10,6 +10,7 @@ include("functionize.jl") include("interval.jl") include("rsoc.jl") include("quad_to_soc.jl") +include("norm_to_lp.jl") include("geomean.jl") include("square.jl") include("det.jl") diff --git a/test/Bridges/Constraint/norm_to_lp.jl b/test/Bridges/Constraint/norm_to_lp.jl new file mode 100644 index 0000000000..f80c2312ef --- /dev/null +++ b/test/Bridges/Constraint/norm_to_lp.jl @@ -0,0 +1,149 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("../utilities.jl") + +mock = MOIU.MockOptimizer(MOIU.Model{Float64}()) +config = MOIT.TestConfig() + +@testset "NormInfinity" begin + bridged_mock = MOIB.Constraint.NormInfinity{Float64}(mock) + + MOIT.basic_constraint_tests(bridged_mock, config, + include = [(F, MOI.NormInfinityCone) for F in [ + MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} + ]]) + + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.5, 1.0], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[0.0, 1.0, 0.0, 0.0]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [-1]]) + + MOIT.norminf1vtest(bridged_mock, config) + MOIT.norminf1ftest(bridged_mock, config) + + @testset "Test mock model" begin + var_names = ["x", "y", "z"] + MOI.set(mock, MOI.VariableName(), MOI.get(mock, MOI.ListOfVariableIndices()), var_names) + nonneg = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}()) + @test length(nonneg) == 1 + MOI.set(mock, MOI.ConstraintName(), nonneg[1], "nonneg") + zeros = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()) + @test length(zeros) == 2 + MOI.set(mock, MOI.ConstraintName(), zeros[1], "x_eq") + MOI.set(mock, MOI.ConstraintName(), zeros[2], "y_eq") + + s = """ + variables: x, y, z + nonneg: [x + -1.0y, x + -1.0z, x + y, x + z] in MathOptInterface.Nonnegatives(4) + x_eq: [-1.0 + x] in MathOptInterface.Zeros(1) + y_eq: [-0.5 + y] in MathOptInterface.Zeros(1) + maxobjective: y + z + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(mock, model, var_names, ["nonneg", "x_eq", "y_eq"]) + end + + @testset "Test bridged model" begin + var_names = ["x", "y", "z"] + MOI.set(bridged_mock, MOI.VariableName(), MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names) + norminf = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone}()) + @test length(norminf) == 1 + MOI.set(bridged_mock, MOI.ConstraintName(), norminf[1], "norminf") + zeros = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()) + @test length(zeros) == 2 + MOI.set(bridged_mock, MOI.ConstraintName(), zeros[1], "x_eq") + MOI.set(bridged_mock, MOI.ConstraintName(), zeros[2], "y_eq") + + s = """ + variables: x, y, z + norminf: [1.0x, y, z] in MathOptInterface.NormInfinityCone(3) + x_eq: [-1.0 + x] in MathOptInterface.Zeros(1) + y_eq: [-0.5 + y] in MathOptInterface.Zeros(1) + maxobjective: y + z + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(bridged_mock, model, var_names, ["norminf", "x_eq", "y_eq"]) + end + + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.NormInfinityCone}())) + test_delete_bridge(bridged_mock, ci, 3, ((MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0),)) +end + +@testset "NormOne" begin + bridged_mock = MOIB.Constraint.NormOne{Float64}(mock) + + MOIT.basic_constraint_tests(bridged_mock, config, + include = [(F, MOI.NormOneCone) for F in [ + MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} + ]]) + + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.5, 0.5, 0.5, 0.5], + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[1.0, 1.0, 0.0, 0.0]], + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [[1.0]], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-1], [0]]) + + MOIT.normone1vtest(bridged_mock, config) + MOIT.normone1ftest(bridged_mock, config) + + @testset "Test mock model" begin + var_names = ["x", "y", "z", "u", "v"] + MOI.set(mock, MOI.VariableName(), MOI.get(mock, MOI.ListOfVariableIndices()), var_names) + nonneg = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}()) + @test length(nonneg) == 1 + MOI.set(mock, MOI.ConstraintName(), nonneg[1], "nonneg") + greater = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) + @test length(greater) == 1 + MOI.set(mock, MOI.ConstraintName(), greater[1], "greater") + zeros = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()) + @test length(zeros) == 2 + MOI.set(mock, MOI.ConstraintName(), zeros[1], "x_eq") + MOI.set(mock, MOI.ConstraintName(), zeros[2], "y_eq") + + s = """ + variables: x, y, z, u, v + nonneg: [u + -1.0y, v + -1.0z, u + y, v + z] in MathOptInterface.Nonnegatives(4) + greater: x + -1.0u + -1.0v >= 0.0 + x_eq: [-1.0 + x] in MathOptInterface.Zeros(1) + y_eq: [-0.5 + y] in MathOptInterface.Zeros(1) + maxobjective: y + z + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(mock, model, var_names, ["nonneg", "greater", "x_eq", "y_eq"]) + end + + @testset "Test bridged model" begin + var_names = ["x", "y", "z"] + MOI.set(bridged_mock, MOI.VariableName(), MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names) + normone = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.NormOneCone}()) + @test length(normone) == 1 + MOI.set(bridged_mock, MOI.ConstraintName(), normone[1], "normone") + zeros = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()) + @test length(zeros) == 2 + MOI.set(bridged_mock, MOI.ConstraintName(), zeros[1], "x_eq") + MOI.set(bridged_mock, MOI.ConstraintName(), zeros[2], "y_eq") + + s = """ + variables: x, y, z + normone: [1.0x, y, z] in MathOptInterface.NormOneCone(3) + x_eq: [-1.0 + x] in MathOptInterface.Zeros(1) + y_eq: [-0.5 + y] in MathOptInterface.Zeros(1) + maxobjective: y + z + """ + model = MOIU.Model{Float64}() + MOIU.loadfromstring!(model, s) + MOIU.test_models_equal(bridged_mock, model, var_names, ["normone", "x_eq", "y_eq"]) + end + + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.NormOneCone}())) + test_delete_bridge(bridged_mock, ci, 3, ( + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0), + (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0))) +end diff --git a/test/Bridges/bridge_optimizer.jl b/test/Bridges/bridge_optimizer.jl index d4fe80c14b..553f99e87f 100644 --- a/test/Bridges/bridge_optimizer.jl +++ b/test/Bridges/bridge_optimizer.jl @@ -12,7 +12,8 @@ include("utilities.jl") MOIU.@model(NoIntervalModel, (), (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), - (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, + (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, + MOI.NormInfinityCone, MOI.NormOneCone, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, MOI.PositiveSemidefiniteConeTriangle, MOI.ExponentialCone), (MOI.PowerCone, MOI.DualPowerCone), diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index 8c42816bd1..af035bb356 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -159,6 +159,7 @@ MOIU.@model(NoRSOCModel, (), (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan), (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, + MOI.NormInfinityCone, MOI.NormOneCone, MOI.ExponentialCone, MOI.PositiveSemidefiniteConeTriangle), (MOI.PowerCone,), (), @@ -202,6 +203,7 @@ MOIU.@model(ModelNoVAFinSOC, (), (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, + MOI.NormInfinityCone, MOI.NormOneCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, MOI.PositiveSemidefiniteConeTriangle, MOI.ExponentialCone), (MOI.PowerCone, MOI.DualPowerCone), @@ -243,6 +245,7 @@ MOIU.@model(ModelNoZeroIndicator, (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval, MOI.Semicontinuous, MOI.Semiinteger), (MOI.Reals, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, + MOI.NormInfinityCone, MOI.NormOneCone, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, MOI.ExponentialCone, MOI.DualExponentialCone, MOI.PositiveSemidefiniteConeTriangle, MOI.PositiveSemidefiniteConeSquare, diff --git a/test/Utilities/sets.jl b/test/Utilities/sets.jl index e2db3594fc..9397b7f59e 100644 --- a/test/Utilities/sets.jl +++ b/test/Utilities/sets.jl @@ -16,6 +16,8 @@ end @testset "Dimension" begin @test MOI.dimension(MOI.EqualTo(3.0)) === 1 @test MOI.dimension(MOI.Reals(8)) === 8 + @test MOI.dimension(MOI.NormInfinityCone(5)) === 5 + @test MOI.dimension(MOI.NormOneCone(5)) === 5 @test MOI.dimension(MOI.DualExponentialCone()) === 3 @test MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(4)) === 10 @test MOI.dimension(MOI.PositiveSemidefiniteConeSquare(5)) === 25 @@ -50,6 +52,15 @@ end @test MOI.dual_set(zeros4) == reals4 @test MOI.dual_set(reals4) == zeros4 @test MOI.dual_set(zeros4) != reals3 + # Norm-1 and norm-∞ cones + norminf2 = MOI.NormInfinityCone(2) + norminf3 = MOI.NormInfinityCone(3) + normone2 = MOI.NormOneCone(2) + normone3 = MOI.NormOneCone(3) + @test MOI.dual_set(norminf2) == normone2 + @test MOI.dual_set(normone2) == norminf2 + @test MOI.dual_set(norminf2) != normone3 + @test MOI.dual_set(normone2) != norminf3 #SOC soc2 = MOI.SecondOrderCone(2) soc3 = MOI.SecondOrderCone(3) @@ -118,7 +129,7 @@ end @test MOIU. set_dot(vec, vec, MOI.LogDetConeTriangle(3)) == 0 vec[5] = 1 @test MOIU.set_dot(vec, vec, MOI.LogDetConeTriangle(3)) == 1 - + sp_vec = spzeros(6) @test MOIU.set_dot(sp_vec, sp_vec, MOI.SecondOrderCone(6)) == 0 @test MOIU.set_dot(sp_vec, sp_vec, MOI.PositiveSemidefiniteConeTriangle(3)) == 0