diff --git a/src/Utilities/penalty_relaxation.jl b/src/Utilities/penalty_relaxation.jl index 9c23a796cc..4de8868f7e 100644 --- a/src/Utilities/penalty_relaxation.jl +++ b/src/Utilities/penalty_relaxation.jl @@ -187,9 +187,51 @@ function _relax_constraint( return [x] end +function _slack_vector(set::MOI.AbstractVectorSet) + ret = Pair{Int,Int}[] + for i in 1:MOI.dimension(set) + push!(ret, i => +1) + push!(ret, i => -1) + end + return ret +end + +_slack_vector(set::MOI.Nonnegatives) = [i => +1 for i in 1:MOI.dimension(set)] +_slack_vector(set::MOI.Nonpositives) = [i => -1 for i in 1:MOI.dimension(set)] +_slack_vector(::MOI.SecondOrderCone) = [1 => +1] +_slack_vector(::MOI.RotatedSecondOrderCone) = [1 => +1, 2 => +1] +_slack_vector(::MOI.NormOneCone) = [1 => +1] +_slack_vector(::MOI.NormInfinityCone) = [1 => +1] +_slack_vector(::MOI.NormCone) = [1 => +1] +_slack_vector(::MOI.NormSpectralCone) = [1 => +1] +_slack_vector(::MOI.NormNuclearCone) = [1 => +1] +_slack_vector(::MOI.RootDetConeTriangle) = [1 => -1] +_slack_vector(::MOI.LogDetConeTriangle) = [1 => -1, 2 => +1] + +function _slack_vector(set::MOI.GeometricMeanCone) + ret = [i => +1 for i in 1:MOI.dimension(set)] + ret[1] = (1 => -1) + return ret +end + +function _relax_constraint( + ::Type{T}, + model::MOI.ModelLike, + ci::MOI.ConstraintIndex{F,<:MOI.AbstractVectorSet}, +) where {T,F<:Union{MOI.VectorAffineFunction{T},MOI.VectorQuadraticFunction{T}}} + set = MOI.get(model, MOI.ConstraintSet(), ci) + slack = _slack_vector(set) + y, _ = MOI.add_constrained_variables(model, MOI.Nonnegatives(length(slack))) + for (i, (row, weight)) in enumerate(slack) + change = MOI.MultirowChange(y[i], [(Int64(row), weight * one(T))]) + MOI.modify(model, ci, change) + end + return y +end + function MOI.modify( model::MOI.ModelLike, - ci::MOI.ConstraintIndex{F,<:MOI.AbstractScalarSet}, + ci::MOI.ConstraintIndex{F}, relax::ScalarPenaltyRelaxation{T}, ) where { T, @@ -197,6 +239,8 @@ function MOI.modify( MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}, MOI.ScalarNonlinearFunction, + MOI.VectorAffineFunction{T}, + MOI.VectorQuadraticFunction{T}, }, } x = _relax_constraint(T, model, ci) diff --git a/test/Utilities/test_penalty_relaxation.jl b/test/Utilities/test_penalty_relaxation.jl index 4b19485347..7a91e7c779 100644 --- a/test/Utilities/test_penalty_relaxation.jl +++ b/test/Utilities/test_penalty_relaxation.jl @@ -475,6 +475,258 @@ function test_scalar_penalty_relaxation_vector_objective() return end +function test_relax_vector_epigraph_cones() + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in SecondOrderCone(3) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x + 1.0 * a, y, z] in SecondOrderCone(3) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in RotatedSecondOrderCone(3) + """, + """ + variables: x, y, z, a, b + minobjective: 1.0 * a + 1.0 * b + c1: [1.0 * x + 1.0 * a, y + 1.0 * b, z] in RotatedSecondOrderCone(3) + [a, b] in Nonnegatives(2) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in NormOneCone(3) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x + 1.0 * a, y, z] in NormOneCone(3) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in NormInfinityCone(3) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x + 1.0 * a, y, z] in NormInfinityCone(3) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in NormCone(1.5, 3) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x + 1.0 * a, y, z] in NormCone(1.5, 3) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in NormSpectralCone(1, 2) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x + 1.0 * a, y, z] in NormSpectralCone(1, 2) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in NormNuclearCone(1, 2) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x + 1.0 * a, y, z] in NormNuclearCone(1, 2) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x * x, 1.0 * y, 1.0 * z] in NormCone(1.5, 3) + """, + """ + variables: x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * x * x + 1.0 * a, 1.0 * y, 1.0 * z] in NormCone(1.5, 3) + [a] in Nonnegatives(1) + """, + ) + return +end + +function test_relax_vector_hypograph_cones() + _test_roundtrip( + """ + variables: t, x, y, z + c1: [1.0 * t, x, y, z] in RootDetConeTriangle(2) + """, + """ + variables: t, x, y, z, a + minobjective: 1.0 * a + c1: [1.0 * t + -1.0 * a, x, y, z] in RootDetConeTriangle(2) + [a] in Nonnegatives(1) + """, + ) + _test_roundtrip( + """ + variables: t, u, x, y, z + c1: [1.0 * t, u, x, y, z] in LogDetConeTriangle(2) + """, + """ + variables: t, u, x, y, z, a, b + minobjective: 1.0 * a + 1.0 * b + c1: [1.0 * t + -1.0 * a, u + 1.0 * b, x, y, z] in LogDetConeTriangle(2) + [a, b] in Nonnegatives(2) + """, + ) + return +end + +function test_relax_nonnegatives() + _test_roundtrip( + """ + variables: x, y + c1: [1.0 * x, y] in Nonnegatives(2) + """, + """ + variables: x, y, a, b + minobjective: 1.0 * a + 1.0 * b + c1: [1.0 * x + 1.0 * a, y + 1.0 * b] in Nonnegatives(2) + [a, b] in Nonnegatives(2) + """, + ) + return +end + +function test_relax_nonpositives() + _test_roundtrip( + """ + variables: x, y + c1: [1.0 * x, y] in Nonpositives(2) + """, + """ + variables: x, y, a, b + minobjective: 1.0 * a + 1.0 * b + c1: [1.0 * x + -1.0 * a, y + -1.0 * b] in Nonpositives(2) + [a, b] in Nonnegatives(2) + """, + ) + return +end + +function test_relax_geometric_mean_cone() + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in GeometricMeanCone(3) + """, + """ + variables: x, y, z, a, b, c + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + c1: [1.0 * x + -1.0 * a, y + 1.0 * b, z + 1.0 * c] in GeometricMeanCone(3) + [a, b, c] in Nonnegatives(3) + """, + ) + return +end + +function test_relax_both_sides() + _test_roundtrip( + """ + variables: x, y + c1: [1.0 * x, y] in Zeros(2) + """, + """ + variables: x, y, a, b, c, d + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d] in Zeros(2) + [a, b, c, d] in Nonnegatives(4) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in ExponentialCone() + """, + """ + variables: x, y, z, a, b, c, d, e, f + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f + c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d, z + 1.0 * e + -1.0 * f] in ExponentialCone() + [a, b, c, d, e, f] in Nonnegatives(6) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in DualExponentialCone() + """, + """ + variables: x, y, z, a, b, c, d, e, f + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f + c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d, z + 1.0 * e + -1.0 * f] in DualExponentialCone() + [a, b, c, d, e, f] in Nonnegatives(6) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in PowerCone(0.5) + """, + """ + variables: x, y, z, a, b, c, d, e, f + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f + c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d, z + 1.0 * e + -1.0 * f] in PowerCone(0.5) + [a, b, c, d, e, f] in Nonnegatives(6) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in DualPowerCone(0.5) + """, + """ + variables: x, y, z, a, b, c, d, e, f + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f + c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d, z + 1.0 * e + -1.0 * f] in DualPowerCone(0.5) + [a, b, c, d, e, f] in Nonnegatives(6) + """, + ) + _test_roundtrip( + """ + variables: x, y, z + c1: [1.0 * x, y, z] in HyperRectangle([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]) + """, + """ + variables: x, y, z, a, b, c, d, e, f + minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f + c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d, z + 1.0 * e + -1.0 * f] in HyperRectangle([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]) + [a, b, c, d, e, f] in Nonnegatives(6) + """, + ) + return +end + end # module TestPenaltyRelaxation.runtests()