Skip to content

Commit b852738

Browse files
authored
Fix issue #2452 (#2464)
1 parent 76c446b commit b852738

File tree

3 files changed

+132
-11
lines changed

3 files changed

+132
-11
lines changed

src/Bridges/bridge_optimizer.jl

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,8 +1456,8 @@ end
14561456
function MOI.get(
14571457
b::AbstractBridgeOptimizer,
14581458
attr::MOI.ConstraintSet,
1459-
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction},
1460-
)
1459+
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,S},
1460+
) where {S}
14611461
set = if is_bridged(b, ci)
14621462
MOI.throw_if_not_valid(b, ci)
14631463
call_in_context(MOI.get, b, ci, attr)
@@ -1467,17 +1467,31 @@ function MOI.get(
14671467
# This is a scalar function, so if there are variable bridges, it might
14681468
# contain constants that have been moved into the set.
14691469
if !Variable.has_bridges(Variable.bridges(b))
1470+
# If there are no variable bridges, return the set.
1471+
return set
1472+
elseif !MOI.Utilities.supports_shift_constant(S)
1473+
# If it doesn't support shift_constant, then return the set
14701474
return set
14711475
end
1472-
# The function constant of the bridged function was moved to the set,
1473-
# we need to remove it.
1474-
func = if is_bridged(b, ci)
1475-
call_in_context(MOI.get, b, ci, MOI.ConstraintFunction())
1476-
else
1477-
MOI.get(b.model, MOI.ConstraintFunction(), ci)
1478-
end
1479-
f = unbridged_function(b, func)
1480-
return MOI.Utilities.shift_constant(set, -MOI.constant(f))
1476+
# When the constraint is added with function `f` and set `set_f`,
1477+
# the function is bridged into `g` with set `set` by
1478+
# `g, set = bridged_constraint_function(b, f, set_f)`.
1479+
# By doing so, the function constant of the bridged function (if it exists)
1480+
# was moved to `set`, we need to remove it to recover `set_f`.
1481+
# The function `f` contains the variables in the context of `ci`
1482+
# (which should match `Variable.bridges(b).current_context` since no code
1483+
# outside of that context has references to `ci`) and
1484+
# the constraint `g` contains the variables of `b.model`.
1485+
# The following line recovers `f` in the context of
1486+
# `Variables.map(b).current_context`.
1487+
f = MOI.get(b, MOI.ConstraintFunction(), ci)
1488+
# We need to substitute the variable bridges to recover the function `g`
1489+
# that was given at the creation of the bridge.
1490+
g = bridged_function(b, f)
1491+
# Since `bridged_constraint_function(b, f, set_f)` used
1492+
# `set = shift_constant(set_f, -MOI.constant(g))`, we need
1493+
# to do the opposite to recover `set_f`.
1494+
return MOI.Utilities.shift_constant(set, MOI.constant(g))
14811495
end
14821496

14831497
## Other constraint attributes

src/Test/test_modification.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,3 +1061,18 @@ function test_modification_constraint_scalarquadraticcoefficientchange(
10611061
@test (MOI.get(model, MOI.ConstraintFunction(), c), g, config)
10621062
return
10631063
end
1064+
1065+
function test_modification_mathoptinterface_issue_2452(
1066+
model::MOI.ModelLike,
1067+
config::Config{T},
1068+
) where {T}
1069+
F, S = MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}
1070+
@requires MOI.supports_constraint(model, F, S)
1071+
x, _ = MOI.add_constrained_variable(model, MOI.GreaterThan(T(1)))
1072+
c = MOI.add_constraint(model, T(2) * x, MOI.EqualTo(T(3)))
1073+
@test (MOI.get(model, MOI.ConstraintFunction(), c), T(2) * x, config)
1074+
@test MOI.get(model, MOI.ConstraintSet(), c) == MOI.EqualTo(T(3))
1075+
MOI.set(model, MOI.ConstraintSet(), c, MOI.EqualTo(T(2)))
1076+
@test MOI.get(model, MOI.ConstraintSet(), c) == MOI.EqualTo(T(2))
1077+
return
1078+
end

test/Bridges/bridge_optimizer.jl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,98 @@ function test_cannot_unbridge_variable_function()
11871187
return
11881188
end
11891189

1190+
MOI.Utilities.@model(
1191+
Model2452,
1192+
(),
1193+
(),
1194+
(MOI.Nonnegatives, MOI.Zeros),
1195+
(),
1196+
(),
1197+
(),
1198+
(MOI.VectorOfVariables,),
1199+
(MOI.VectorAffineFunction,)
1200+
)
1201+
1202+
function MOI.supports_constraint(
1203+
::Model2452{T},
1204+
::Type{MOI.VariableIndex},
1205+
::Type{
1206+
<:Union{
1207+
MOI.GreaterThan{T},
1208+
MOI.LessThan{T},
1209+
MOI.EqualTo{T},
1210+
MOI.Interval{T},
1211+
MOI.ZeroOne,
1212+
},
1213+
},
1214+
) where {T}
1215+
return false
1216+
end
1217+
1218+
function MOI.supports_constraint(
1219+
::Model2452{T},
1220+
::Type{MOI.VectorOfVariables},
1221+
::Type{MOI.Reals},
1222+
) where {T}
1223+
return false
1224+
end
1225+
1226+
function test_issue_2452_multiple_variable_bridges()
1227+
src = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
1228+
x = MOI.add_variable(src)
1229+
MOI.add_constraint(src, x, MOI.LessThan(1.0))
1230+
c = MOI.add_constraint(src, 2.0 * x, MOI.EqualTo(3.0))
1231+
dest = MOI.instantiate(Model2452{Float64}; with_bridge_type = Float64)
1232+
index_map = MOI.copy_to(dest, src)
1233+
set = MOI.get(dest, MOI.ConstraintSet(), index_map[c])
1234+
@test set == MOI.EqualTo(3.0)
1235+
MOI.set(dest, MOI.ConstraintSet(), index_map[c], set)
1236+
@test MOI.get(dest, MOI.ConstraintSet(), index_map[c]) == set
1237+
new_set = MOI.EqualTo(2.0)
1238+
MOI.set(dest, MOI.ConstraintSet(), index_map[c], new_set)
1239+
@test MOI.get(dest, MOI.ConstraintSet(), index_map[c]) == new_set
1240+
return
1241+
end
1242+
1243+
function test_issue_2452()
1244+
src = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
1245+
x = MOI.add_variable(src)
1246+
MOI.add_constraint(src, x, MOI.GreaterThan(1.0))
1247+
c = MOI.add_constraint(src, 2.0 * x, MOI.EqualTo(3.0))
1248+
dest = MOI.instantiate(Model2452{Float64}; with_bridge_type = Float64)
1249+
index_map = MOI.copy_to(dest, src)
1250+
set = MOI.get(dest, MOI.ConstraintSet(), index_map[c])
1251+
@test set == MOI.EqualTo(3.0)
1252+
MOI.set(dest, MOI.ConstraintSet(), index_map[c], set)
1253+
@test MOI.get(dest, MOI.ConstraintSet(), index_map[c]) == set
1254+
new_set = MOI.EqualTo(2.0)
1255+
MOI.set(dest, MOI.ConstraintSet(), index_map[c], new_set)
1256+
@test MOI.get(dest, MOI.ConstraintSet(), index_map[c]) == new_set
1257+
return
1258+
end
1259+
1260+
function test_issue_2452_with_constant()
1261+
src = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
1262+
x = MOI.add_variable(src)
1263+
MOI.add_constraint(src, x, MOI.GreaterThan(1.0))
1264+
MOI.add_constraint(src, 2.0 * x + 1.0, MOI.EqualTo(3.0))
1265+
dest = MOI.instantiate(Model2452{Float64}; with_bridge_type = Float64)
1266+
@test_throws MOI.ScalarFunctionConstantNotZero MOI.copy_to(dest, src)
1267+
return
1268+
end
1269+
1270+
function test_issue_2452_integer()
1271+
src = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
1272+
x = MOI.add_variable(src)
1273+
MOI.add_constraint(src, x, MOI.GreaterThan(1.0))
1274+
y = MOI.add_variable(src)
1275+
c = MOI.add_constraint(src, 1.0 * y, MOI.Integer())
1276+
dest = MOI.instantiate(Model2452{Float64}; with_bridge_type = Float64)
1277+
index_map = MOI.copy_to(dest, src)
1278+
@test MOI.get(dest, MOI.ConstraintSet(), index_map[c]) == MOI.Integer()
1279+
return
1280+
end
1281+
11901282
end # module
11911283

11921284
TestBridgeOptimizer.runtests()

0 commit comments

Comments
 (0)