Skip to content

Commit 5e90c4a

Browse files
committed
Fix ConstraintFunction and ConstraintSet for scalar constraint with variable bridges
1 parent ecac62f commit 5e90c4a

File tree

2 files changed

+148
-50
lines changed

2 files changed

+148
-50
lines changed

src/Bridges/bridge_optimizer.jl

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,18 +498,59 @@ function MOI.set(b::AbstractBridgeOptimizer,
498498
end
499499

500500
# Constraint attributes
501-
function MOI.get(b::AbstractBridgeOptimizer,
502-
attr::MOI.AbstractConstraintAttribute, ci::MOI.ConstraintIndex)
501+
function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintFunction,
502+
ci::MOI.ConstraintIndex)
503503
if is_bridged(b, ci)
504504
MOI.throw_if_not_valid(b, ci)
505505
br = bridge(b, ci)
506-
if attr isa MOI.ConstraintFunction && br isa Variable.AbstractBridge
506+
if br isa Variable.AbstractBridge
507507
return Variable.function_for(Variable.bridges(b), ci)
508508
end
509509
func = MOI.get(b, attr, br)
510510
else
511511
func = MOI.get(b.model, attr, ci)
512512
end
513+
return unbridged_constraint_function(b, func)
514+
end
515+
function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintSet,
516+
ci::MOI.ConstraintIndex{<:Union{MOI.AbstractVectorFunction,
517+
MOI.SingleVariable}})
518+
return if is_bridged(b, ci)
519+
MOI.throw_if_not_valid(b, ci)
520+
MOI.get(b, attr, bridge(b, ci))
521+
else
522+
MOI.get(b.model, attr, ci)
523+
end
524+
end
525+
function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintSet,
526+
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction})
527+
if is_bridged(b, ci)
528+
MOI.throw_if_not_valid(b, ci)
529+
set = MOI.get(b, attr, bridge(b, ci))
530+
else
531+
set = MOI.get(b.model, attr, ci)
532+
end
533+
if Variable.has_bridges(Variable.bridges(b))
534+
# The function constant of the bridged function was moved to the set,
535+
# we need to remove it.
536+
if is_bridged(b, ci)
537+
func = MOI.get(b, MOI.ConstraintFunction(), bridge(b, c))
538+
else
539+
func = MOI.get(b.model, MOI.ConstraintFunction(), ci)
540+
end
541+
f = unbridged_function(b, func)
542+
set = MOIU.shift_constant(set, -MOI.constant(f))
543+
end
544+
return set
545+
end
546+
function MOI.get(b::AbstractBridgeOptimizer,
547+
attr::MOI.AbstractConstraintAttribute, ci::MOI.ConstraintIndex)
548+
if is_bridged(b, ci)
549+
MOI.throw_if_not_valid(b, ci)
550+
func = MOI.get(b, attr, bridge(b, ci))
551+
else
552+
func = MOI.get(b.model, attr, ci)
553+
end
513554
return unbridged_function(b, func)
514555
end
515556
function MOI.supports(b::AbstractBridgeOptimizer,
@@ -668,6 +709,16 @@ function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction,
668709
return Constraint.add_key_for_bridge(Constraint.bridges(b), bridge, f, s)
669710
end
670711
else
712+
if f isa MOI.AbstractScalarFunction
713+
constant = MOI.constant(f)
714+
if !iszero(constant)
715+
# We use the fact that the initial function constant was zero to
716+
# implement getters for `MOI.ConstraintFunction` and
717+
# `MOI.ConstraintSet`.
718+
throw(MOI.ScalarFunctionConstantNotZero{
719+
typeof(constant), typeof(f), typeof(s)}(constant))
720+
end
721+
end
671722
f = bridged_function(b, f)::typeof(f)
672723
f, s = MOIU.normalize_constant(f, s)
673724
end
@@ -925,7 +976,37 @@ function unbridged_function(bridge::AbstractBridgeOptimizer,
925976
func::Union{MOI.SingleVariable, MOI.VectorOfVariables})
926977
return func # bridged variables are not allowed in non-bridged constraints
927978
end
928-
unbridged_function(bridge::AbstractBridgeOptimizer, value) = value
979+
unbridged_function(::AbstractBridgeOptimizer, value) = value
980+
981+
"""
982+
unbridged_constraint_function(
983+
b::AbstractBridgeOptimizer,
984+
func::MOI.AbstractFunction
985+
)
986+
987+
Similar to `unbridged_function(b, func)` but zero the function constant if it is
988+
scalar.
989+
"""
990+
function unbridged_constraint_function end
991+
992+
function unbridged_constraint_function(
993+
b::AbstractBridgeOptimizer,
994+
func::Union{MOI.AbstractVectorSet, MOI.SingleVariable}
995+
)
996+
return unbridged_function(b, func)
997+
end
998+
function unbridged_constraint_function(b::AbstractBridgeOptimizer,
999+
func::MOI.AbstractScalarFunction)
1000+
if !Variable.has_bridges(Variable.bridges(b))
1001+
return func
1002+
end
1003+
f = unbridged_function(b, func)
1004+
if !iszero(MOI.constant(f))
1005+
f = copy(f)
1006+
f.constant = zero(f.constant)
1007+
end
1008+
return f
1009+
end
9291010

9301011

9311012
# TODO add transform

test/Bridges/Variable/vectorize.jl

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,58 +13,75 @@ config = MOIT.TestConfig()
1313

1414
bridged_mock = MOIB.Variable.Vectorize{Float64}(mock)
1515

16-
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [log(5), 0.0],
17-
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0.0],
18-
(MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [[-1.0, log(5)-1, 1/5]])
19-
MOIT.exp3test(bridged_mock, config)
16+
@testset "get scalar constraint" begin
17+
x, cx = MOI.add_constrained_variable(bridged_mock, MOI.GreaterThan(1.0))
18+
fx = MOI.SingleVariable(x)
19+
func = 2.0 * fx
20+
set = MOI.GreaterThan(5.0)
21+
err = MOI.ScalarFunctionConstantNotZero{
22+
Float64, typeof(func), typeof(set)}(1.0)
23+
@test_throws err MOI.add_constraint(bridged_mock, func + 1.0, set)
2024

21-
vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
22-
@test length(vis) == 2
23-
y = vis[2]
25+
c = MOI.add_constraint(bridged_mock, func, set)
26+
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), c) func
27+
@test MOI.get(bridged_mock, MOI.ConstraintSet(), c) == set
28+
end
2429

25-
err = ErrorException(
26-
"Cannot add two `SingleVariable`-in-`MathOptInterface.LessThan{Float64}`" *
27-
" on the same variable MathOptInterface.VariableIndex(-1)."
28-
)
29-
@test_throws err MOI.add_constraint(bridged_mock, MOI.SingleVariable(y), MOI.LessThan(4.0))
30+
@testset "exp3" begin
31+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [log(5), 0.0],
32+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0.0],
33+
(MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [[-1.0, log(5)-1, 1/5]])
34+
MOIT.exp3test(bridged_mock, config)
3035

31-
cis = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
32-
MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone}())
33-
@test length(cis) == 1
36+
vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
37+
@test length(vis) == 2
38+
y = vis[2]
39+
@test y.value == -1
3440

35-
@testset "get `UnknownVariableAttribute``" begin
36-
err = ArgumentError(
37-
"Variable bridge of type `MathOptInterface.Bridges.Variable.VectorizeBridge{Float64,MathOptInterface.Nonpositives}`" *
38-
" does not support accessing the attribute `MathOptInterface.Test.UnknownVariableAttribute()`."
41+
err = ErrorException(
42+
"Cannot add two `SingleVariable`-in-`MathOptInterface.LessThan{Float64}`" *
43+
" on the same variable MathOptInterface.VariableIndex(-1)."
3944
)
40-
@test_throws err MOI.get(bridged_mock, MOIT.UnknownVariableAttribute(), y)
41-
end
45+
@test_throws err MOI.add_constraint(bridged_mock, MOI.SingleVariable(y), MOI.LessThan(4.0))
4246

43-
@testset "set `ConstraintSet`" begin
44-
ci = MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}}(y.value)
45-
attr = MOI.ConstraintSet()
46-
err = MOI.SetAttributeNotAllowed(attr,
47-
"The variable `MathOptInterface.VariableIndex(12345676)` is bridged by the `VectorizeBridge`.")
48-
@test_throws err MOI.set(bridged_mock, attr, ci, MOI.LessThan(4.0))
49-
end
47+
cis = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
48+
MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone}())
49+
@test length(cis) == 1
5050

51-
@testset "MultirowChange" begin
52-
change = MOI.MultirowChange(y, [(3, 0.0)])
53-
message = "The change MathOptInterface.MultirowChange{Float64}(MathOptInterface.VariableIndex(-1), Tuple{Int64,Float64}[(3, 0.0)])" *
54-
" contains variables bridged into a function with nonzero constant."
55-
err = MOI.ModifyConstraintNotAllowed(cis[1], change, message)
56-
@test_throws err MOI.modify(bridged_mock, cis[1], change)
57-
end
51+
@testset "get `UnknownVariableAttribute``" begin
52+
err = ArgumentError(
53+
"Variable bridge of type `MathOptInterface.Bridges.Variable.VectorizeBridge{Float64,MathOptInterface.Nonpositives}`" *
54+
" does not support accessing the attribute `MathOptInterface.Test.UnknownVariableAttribute()`."
55+
)
56+
@test_throws err MOI.get(bridged_mock, MOIT.UnknownVariableAttribute(), y)
57+
end
5858

59-
@testset "ScalarCoefficientChange" begin
60-
change = MOI.ScalarCoefficientChange(y, 0.0)
61-
attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()
62-
message = "The change MathOptInterface.ScalarCoefficientChange{Float64}(MathOptInterface.VariableIndex(-1), 0.0)" *
63-
" contains variables bridged into a function with nonzero constant."
64-
err = MOI.ModifyObjectiveNotAllowed(change, message)
65-
@test_throws err MOI.modify(bridged_mock, attr, change)
66-
end
59+
@testset "set `ConstraintSet`" begin
60+
ci = MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}}(y.value)
61+
attr = MOI.ConstraintSet()
62+
err = MOI.SetAttributeNotAllowed(attr,
63+
"The variable `MathOptInterface.VariableIndex(12345676)` is bridged by the `VectorizeBridge`.")
64+
@test_throws err MOI.set(bridged_mock, attr, ci, MOI.LessThan(4.0))
65+
end
66+
67+
@testset "MultirowChange" begin
68+
change = MOI.MultirowChange(y, [(3, 0.0)])
69+
message = "The change MathOptInterface.MultirowChange{Float64}(MathOptInterface.VariableIndex(-1), Tuple{Int64,Float64}[(3, 0.0)])" *
70+
" contains variables bridged into a function with nonzero constant."
71+
err = MOI.ModifyConstraintNotAllowed(cis[1], change, message)
72+
@test_throws err MOI.modify(bridged_mock, cis[1], change)
73+
end
6774

68-
test_delete_bridged_variable(bridged_mock, y, MOI.LessThan{Float64}, 2, (
69-
(MOI.VectorOfVariables, MOI.Nonpositives, 0),
70-
))
75+
@testset "ScalarCoefficientChange" begin
76+
change = MOI.ScalarCoefficientChange(y, 0.0)
77+
attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()
78+
message = "The change MathOptInterface.ScalarCoefficientChange{Float64}(MathOptInterface.VariableIndex(-1), 0.0)" *
79+
" contains variables bridged into a function with nonzero constant."
80+
err = MOI.ModifyObjectiveNotAllowed(change, message)
81+
@test_throws err MOI.modify(bridged_mock, attr, change)
82+
end
83+
84+
test_delete_bridged_variable(bridged_mock, y, MOI.LessThan{Float64}, 2, (
85+
(MOI.VectorOfVariables, MOI.Nonpositives, 0),
86+
))
87+
end

0 commit comments

Comments
 (0)