Skip to content

Commit 9884cfa

Browse files
authored
[Utilities] add support for relaxing vector constraints (#2966)
1 parent b53079c commit 9884cfa

File tree

2 files changed

+297
-1
lines changed

2 files changed

+297
-1
lines changed

src/Utilities/penalty_relaxation.jl

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,60 @@ function _relax_constraint(
187187
return [x]
188188
end
189189

190+
function _slack_vector(set::MOI.AbstractVectorSet)
191+
ret = Pair{Int,Int}[]
192+
for i in 1:MOI.dimension(set)
193+
push!(ret, i => +1)
194+
push!(ret, i => -1)
195+
end
196+
return ret
197+
end
198+
199+
_slack_vector(set::MOI.Nonnegatives) = [i => +1 for i in 1:MOI.dimension(set)]
200+
_slack_vector(set::MOI.Nonpositives) = [i => -1 for i in 1:MOI.dimension(set)]
201+
_slack_vector(::MOI.SecondOrderCone) = [1 => +1]
202+
_slack_vector(::MOI.RotatedSecondOrderCone) = [1 => +1, 2 => +1]
203+
_slack_vector(::MOI.NormOneCone) = [1 => +1]
204+
_slack_vector(::MOI.NormInfinityCone) = [1 => +1]
205+
_slack_vector(::MOI.NormCone) = [1 => +1]
206+
_slack_vector(::MOI.NormSpectralCone) = [1 => +1]
207+
_slack_vector(::MOI.NormNuclearCone) = [1 => +1]
208+
_slack_vector(::MOI.RootDetConeTriangle) = [1 => -1]
209+
_slack_vector(::MOI.LogDetConeTriangle) = [1 => -1, 2 => +1]
210+
211+
function _slack_vector(set::MOI.GeometricMeanCone)
212+
ret = [i => +1 for i in 1:MOI.dimension(set)]
213+
ret[1] = (1 => -1)
214+
return ret
215+
end
216+
217+
function _relax_constraint(
218+
::Type{T},
219+
model::MOI.ModelLike,
220+
ci::MOI.ConstraintIndex{F,<:MOI.AbstractVectorSet},
221+
) where {T,F<:Union{MOI.VectorAffineFunction{T},MOI.VectorQuadraticFunction{T}}}
222+
set = MOI.get(model, MOI.ConstraintSet(), ci)
223+
slack = _slack_vector(set)
224+
y, _ = MOI.add_constrained_variables(model, MOI.Nonnegatives(length(slack)))
225+
for (i, (row, weight)) in enumerate(slack)
226+
change = MOI.MultirowChange(y[i], [(Int64(row), weight * one(T))])
227+
MOI.modify(model, ci, change)
228+
end
229+
return y
230+
end
231+
190232
function MOI.modify(
191233
model::MOI.ModelLike,
192-
ci::MOI.ConstraintIndex{F,<:MOI.AbstractScalarSet},
234+
ci::MOI.ConstraintIndex{F},
193235
relax::ScalarPenaltyRelaxation{T},
194236
) where {
195237
T,
196238
F<:Union{
197239
MOI.ScalarAffineFunction{T},
198240
MOI.ScalarQuadraticFunction{T},
199241
MOI.ScalarNonlinearFunction,
242+
MOI.VectorAffineFunction{T},
243+
MOI.VectorQuadraticFunction{T},
200244
},
201245
}
202246
x = _relax_constraint(T, model, ci)

test/Utilities/test_penalty_relaxation.jl

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,258 @@ function test_scalar_penalty_relaxation_vector_objective()
475475
return
476476
end
477477

478+
function test_relax_vector_epigraph_cones()
479+
_test_roundtrip(
480+
"""
481+
variables: x, y, z
482+
c1: [1.0 * x, y, z] in SecondOrderCone(3)
483+
""",
484+
"""
485+
variables: x, y, z, a
486+
minobjective: 1.0 * a
487+
c1: [1.0 * x + 1.0 * a, y, z] in SecondOrderCone(3)
488+
[a] in Nonnegatives(1)
489+
""",
490+
)
491+
_test_roundtrip(
492+
"""
493+
variables: x, y, z
494+
c1: [1.0 * x, y, z] in RotatedSecondOrderCone(3)
495+
""",
496+
"""
497+
variables: x, y, z, a, b
498+
minobjective: 1.0 * a + 1.0 * b
499+
c1: [1.0 * x + 1.0 * a, y + 1.0 * b, z] in RotatedSecondOrderCone(3)
500+
[a, b] in Nonnegatives(2)
501+
""",
502+
)
503+
_test_roundtrip(
504+
"""
505+
variables: x, y, z
506+
c1: [1.0 * x, y, z] in NormOneCone(3)
507+
""",
508+
"""
509+
variables: x, y, z, a
510+
minobjective: 1.0 * a
511+
c1: [1.0 * x + 1.0 * a, y, z] in NormOneCone(3)
512+
[a] in Nonnegatives(1)
513+
""",
514+
)
515+
_test_roundtrip(
516+
"""
517+
variables: x, y, z
518+
c1: [1.0 * x, y, z] in NormInfinityCone(3)
519+
""",
520+
"""
521+
variables: x, y, z, a
522+
minobjective: 1.0 * a
523+
c1: [1.0 * x + 1.0 * a, y, z] in NormInfinityCone(3)
524+
[a] in Nonnegatives(1)
525+
""",
526+
)
527+
_test_roundtrip(
528+
"""
529+
variables: x, y, z
530+
c1: [1.0 * x, y, z] in NormCone(1.5, 3)
531+
""",
532+
"""
533+
variables: x, y, z, a
534+
minobjective: 1.0 * a
535+
c1: [1.0 * x + 1.0 * a, y, z] in NormCone(1.5, 3)
536+
[a] in Nonnegatives(1)
537+
""",
538+
)
539+
_test_roundtrip(
540+
"""
541+
variables: x, y, z
542+
c1: [1.0 * x, y, z] in NormSpectralCone(1, 2)
543+
""",
544+
"""
545+
variables: x, y, z, a
546+
minobjective: 1.0 * a
547+
c1: [1.0 * x + 1.0 * a, y, z] in NormSpectralCone(1, 2)
548+
[a] in Nonnegatives(1)
549+
""",
550+
)
551+
_test_roundtrip(
552+
"""
553+
variables: x, y, z
554+
c1: [1.0 * x, y, z] in NormNuclearCone(1, 2)
555+
""",
556+
"""
557+
variables: x, y, z, a
558+
minobjective: 1.0 * a
559+
c1: [1.0 * x + 1.0 * a, y, z] in NormNuclearCone(1, 2)
560+
[a] in Nonnegatives(1)
561+
""",
562+
)
563+
_test_roundtrip(
564+
"""
565+
variables: x, y, z
566+
c1: [1.0 * x * x, 1.0 * y, 1.0 * z] in NormCone(1.5, 3)
567+
""",
568+
"""
569+
variables: x, y, z, a
570+
minobjective: 1.0 * a
571+
c1: [1.0 * x * x + 1.0 * a, 1.0 * y, 1.0 * z] in NormCone(1.5, 3)
572+
[a] in Nonnegatives(1)
573+
""",
574+
)
575+
return
576+
end
577+
578+
function test_relax_vector_hypograph_cones()
579+
_test_roundtrip(
580+
"""
581+
variables: t, x, y, z
582+
c1: [1.0 * t, x, y, z] in RootDetConeTriangle(2)
583+
""",
584+
"""
585+
variables: t, x, y, z, a
586+
minobjective: 1.0 * a
587+
c1: [1.0 * t + -1.0 * a, x, y, z] in RootDetConeTriangle(2)
588+
[a] in Nonnegatives(1)
589+
""",
590+
)
591+
_test_roundtrip(
592+
"""
593+
variables: t, u, x, y, z
594+
c1: [1.0 * t, u, x, y, z] in LogDetConeTriangle(2)
595+
""",
596+
"""
597+
variables: t, u, x, y, z, a, b
598+
minobjective: 1.0 * a + 1.0 * b
599+
c1: [1.0 * t + -1.0 * a, u + 1.0 * b, x, y, z] in LogDetConeTriangle(2)
600+
[a, b] in Nonnegatives(2)
601+
""",
602+
)
603+
return
604+
end
605+
606+
function test_relax_nonnegatives()
607+
_test_roundtrip(
608+
"""
609+
variables: x, y
610+
c1: [1.0 * x, y] in Nonnegatives(2)
611+
""",
612+
"""
613+
variables: x, y, a, b
614+
minobjective: 1.0 * a + 1.0 * b
615+
c1: [1.0 * x + 1.0 * a, y + 1.0 * b] in Nonnegatives(2)
616+
[a, b] in Nonnegatives(2)
617+
""",
618+
)
619+
return
620+
end
621+
622+
function test_relax_nonpositives()
623+
_test_roundtrip(
624+
"""
625+
variables: x, y
626+
c1: [1.0 * x, y] in Nonpositives(2)
627+
""",
628+
"""
629+
variables: x, y, a, b
630+
minobjective: 1.0 * a + 1.0 * b
631+
c1: [1.0 * x + -1.0 * a, y + -1.0 * b] in Nonpositives(2)
632+
[a, b] in Nonnegatives(2)
633+
""",
634+
)
635+
return
636+
end
637+
638+
function test_relax_geometric_mean_cone()
639+
_test_roundtrip(
640+
"""
641+
variables: x, y, z
642+
c1: [1.0 * x, y, z] in GeometricMeanCone(3)
643+
""",
644+
"""
645+
variables: x, y, z, a, b, c
646+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c
647+
c1: [1.0 * x + -1.0 * a, y + 1.0 * b, z + 1.0 * c] in GeometricMeanCone(3)
648+
[a, b, c] in Nonnegatives(3)
649+
""",
650+
)
651+
return
652+
end
653+
654+
function test_relax_both_sides()
655+
_test_roundtrip(
656+
"""
657+
variables: x, y
658+
c1: [1.0 * x, y] in Zeros(2)
659+
""",
660+
"""
661+
variables: x, y, a, b, c, d
662+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d
663+
c1: [1.0 * x + 1.0 * a + -1.0 * b, y + 1.0 * c + -1.0 * d] in Zeros(2)
664+
[a, b, c, d] in Nonnegatives(4)
665+
""",
666+
)
667+
_test_roundtrip(
668+
"""
669+
variables: x, y, z
670+
c1: [1.0 * x, y, z] in ExponentialCone()
671+
""",
672+
"""
673+
variables: x, y, z, a, b, c, d, e, f
674+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f
675+
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()
676+
[a, b, c, d, e, f] in Nonnegatives(6)
677+
""",
678+
)
679+
_test_roundtrip(
680+
"""
681+
variables: x, y, z
682+
c1: [1.0 * x, y, z] in DualExponentialCone()
683+
""",
684+
"""
685+
variables: x, y, z, a, b, c, d, e, f
686+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f
687+
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()
688+
[a, b, c, d, e, f] in Nonnegatives(6)
689+
""",
690+
)
691+
_test_roundtrip(
692+
"""
693+
variables: x, y, z
694+
c1: [1.0 * x, y, z] in PowerCone(0.5)
695+
""",
696+
"""
697+
variables: x, y, z, a, b, c, d, e, f
698+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f
699+
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)
700+
[a, b, c, d, e, f] in Nonnegatives(6)
701+
""",
702+
)
703+
_test_roundtrip(
704+
"""
705+
variables: x, y, z
706+
c1: [1.0 * x, y, z] in DualPowerCone(0.5)
707+
""",
708+
"""
709+
variables: x, y, z, a, b, c, d, e, f
710+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f
711+
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)
712+
[a, b, c, d, e, f] in Nonnegatives(6)
713+
""",
714+
)
715+
_test_roundtrip(
716+
"""
717+
variables: x, y, z
718+
c1: [1.0 * x, y, z] in HyperRectangle([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
719+
""",
720+
"""
721+
variables: x, y, z, a, b, c, d, e, f
722+
minobjective: 1.0 * a + 1.0 * b + 1.0 * c + 1.0 * d + 1.0 * e + 1.0 * f
723+
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])
724+
[a, b, c, d, e, f] in Nonnegatives(6)
725+
""",
726+
)
727+
return
728+
end
729+
478730
end # module
479731

480732
TestPenaltyRelaxation.runtests()

0 commit comments

Comments
 (0)