Skip to content

Commit f0e9fef

Browse files
blegatodow
andauthored
Fix various getters for VariableBridgingCost and ConstraintBridgingCost (#3004)
Co-authored-by: Oscar Dowson <o.dowson@gmail.com>
1 parent b98b476 commit f0e9fef

9 files changed

Lines changed: 182 additions & 1 deletion

File tree

src/FileFormats/NL/NL.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,14 @@ function MOI.get(model::Model, attr::MOI.AbstractModelAttribute)
880880
return MOI.get(inner, attr)
881881
end
882882

883+
# It doesn't need the inner model
884+
function MOI.get(
885+
model::Model,
886+
attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost},
887+
)
888+
return MOI.get_fallback(model, attr)
889+
end
890+
883891
function MOI.get(
884892
model::Model,
885893
attr::MOI.AbstractConstraintAttribute,

src/Test/test_attribute.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,55 @@ function test_attribute_unsupported_constraint(model::MOI.ModelLike, ::Config)
393393
end
394394

395395
version_added(::typeof(test_attribute_unsupported_constraint)) = v"1.9.0"
396+
397+
"""
398+
test_attribute_VariableBridgingCost(model::MOI.ModelLike, config::Config)
399+
400+
Test that, for every set `S` that the model claims to support via
401+
`supports_add_constrained_variable(s)`, the corresponding
402+
[`MOI.VariableBridgingCost`](@ref) attribute returns a finite value.
403+
404+
This is the variable-side analog of the `ConstraintBridgingCost` check in
405+
`_basic_constraint_test_helper`.
406+
407+
The fallback works for most model but it may need custom method for some MOI
408+
layers (see https://github.com/jump-dev/MathOptInterface.jl/pull/3001#issuecomment-4468198935).
409+
410+
This test is here to catch that.
411+
"""
412+
function test_attribute_VariableBridgingCost(
413+
model::MOI.ModelLike,
414+
::Config{T},
415+
) where {T}
416+
for S in Any[
417+
MOI.GreaterThan{T},
418+
MOI.LessThan{T},
419+
MOI.EqualTo{T},
420+
MOI.Interval{T},
421+
MOI.Integer,
422+
MOI.ZeroOne,
423+
MOI.Semicontinuous{T},
424+
MOI.Semiinteger{T},
425+
]
426+
if MOI.supports_add_constrained_variable(model, S)
427+
@test MOI.get(model, MOI.VariableBridgingCost{S}()) < Inf
428+
end
429+
end
430+
for S in Any[
431+
MOI.Reals,
432+
MOI.Zeros,
433+
MOI.Nonnegatives,
434+
MOI.Nonpositives,
435+
MOI.SecondOrderCone,
436+
MOI.RotatedSecondOrderCone,
437+
MOI.ExponentialCone,
438+
MOI.PositiveSemidefiniteConeTriangle,
439+
]
440+
if MOI.supports_add_constrained_variables(model, S)
441+
@test MOI.get(model, MOI.VariableBridgingCost{S}()) < Inf
442+
end
443+
end
444+
return
445+
end
446+
447+
version_added(::typeof(test_attribute_VariableBridgingCost)) = v"1.52.0"

src/Test/test_basic_constraint.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,16 @@ function _basic_constraint_test_helper(
255255
###
256256
@requires MOI.supports_constraint(model, F, S)
257257
###
258+
### Test MOI.ConstraintBridgingCost
259+
###
260+
# If `supports_constraint(F, S)` returns `true`, then the model must be
261+
# able to handle that pair (possibly via bridging), so the bridging cost
262+
# must be finite. The fallback works for most model but it may need
263+
# custom method for some MOI layer (see
264+
# https://github.com/jump-dev/MathOptInterface.jl/pull/3001#issuecomment-4468198935)
265+
# This test is here to catch that.
266+
@test MOI.get(model, MOI.ConstraintBridgingCost{F,S}()) < Inf
267+
###
258268
### Test MOI.NumberOfConstraints
259269
###
260270
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 0

src/Utilities/cachingoptimizer.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,16 @@ function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute)
897897
return _get_model_attribute(model, attr)
898898
end
899899

900+
function MOI.get(
901+
model::CachingOptimizer,
902+
attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost},
903+
)::Float64
904+
if state(model) == NO_OPTIMIZER
905+
return MOI.get(model.model_cache, attr)
906+
end
907+
return MOI.get(model.optimizer, attr)
908+
end
909+
900910
function MOI.get(
901911
model::CachingOptimizer,
902912
attr::MOI.TerminationStatus,

src/Utilities/universalfallback.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,36 @@ function MOI.get(
372372
return _get(uf, attr)
373373
end
374374

375+
function MOI.get(
376+
uf::UniversalFallback,
377+
attr::MOI.ConstraintBridgingCost{F,S},
378+
) where {F,S}
379+
if MOI.supports_constraint(uf.model, F, S)
380+
return MOI.get(uf.model, attr)
381+
end
382+
return 0.0
383+
end
384+
385+
function MOI.get(
386+
uf::UniversalFallback,
387+
attr::MOI.VariableBridgingCost{S},
388+
) where {S<:MOI.AbstractScalarSet}
389+
if MOI.supports_add_constrained_variable(uf.model, S)
390+
return MOI.get(uf.model, attr)
391+
end
392+
return 0.0
393+
end
394+
395+
function MOI.get(
396+
uf::UniversalFallback,
397+
attr::MOI.VariableBridgingCost{S},
398+
) where {S<:MOI.AbstractVectorSet}
399+
if MOI.supports_add_constrained_variables(uf.model, S)
400+
return MOI.get(uf.model, attr)
401+
end
402+
return 0.0
403+
end
404+
375405
function MOI.get(
376406
uf::UniversalFallback,
377407
attr::MOI.AbstractConstraintAttribute,

test/Bridges/Constraint/test_IntervalToHyperRectangleBridge.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function test_basic(T)
3838
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()),
3939
)
4040
bridged_mock = MOI.Bridges.Constraint.IntervalToHyperRectangle{T}(mock)
41-
config = MOI.Test.Config()
41+
config = MOI.Test.Config(T)
4242
MOI.Test.runtests(
4343
bridged_mock,
4444
config,

test/FileFormats/NL/test_NL.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,17 @@ function test_unsupported_kwarg()
15421542
return
15431543
end
15441544

1545+
function test_VariableBridgingCost()
1546+
model = NL.Model()
1547+
attr = MOI.VariableBridgingCost{MOI.LessThan{Float64}}()
1548+
@test MOI.get(model, attr) == 0
1549+
attr = MOI.ConstraintBridgingCost{MOI.VariableIndex,MOI.LessThan{Float64}}()
1550+
@test MOI.get(model, attr) == 0
1551+
attr = MOI.VariableBridgingCost{MOI.SecondOrderCone}()
1552+
@test isinf(MOI.get(model, attr))
1553+
return
1554+
end
1555+
15451556
end
15461557

15471558
TestNLModel.runtests()

test/Utilities/test_cachingoptimizer.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,24 @@ function test_rethrow_set_model_attribute()
14651465
return
14661466
end
14671467

1468+
function test_BridgingCost_NO_OPTIMIZER()
1469+
cache = MOI.Utilities.Model{Float64}()
1470+
model = MOI.Utilities.CachingOptimizer(cache, MOI.Utilities.AUTOMATIC)
1471+
@test MOI.Utilities.state(model) == MOI.Utilities.NO_OPTIMIZER
1472+
@test MOI.get(model, MOI.VariableBridgingCost{MOI.LessThan{Float64}}()) ==
1473+
0.0
1474+
@test MOI.get(model, MOI.VariableBridgingCost{MOI.LessThan{Int}}()) == Inf
1475+
@test MOI.get(
1476+
model,
1477+
MOI.ConstraintBridgingCost{MOI.VariableIndex,MOI.LessThan{Float64}}(),
1478+
) == 0.0
1479+
@test MOI.get(
1480+
model,
1481+
MOI.ConstraintBridgingCost{MOI.VariableIndex,MOI.LessThan{Int}}(),
1482+
) == Inf
1483+
return
1484+
end
1485+
14681486
end # module
14691487

14701488
TestCachingOptimizer.runtests()

test/Utilities/test_universalfallback.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,48 @@ function test_set_inner_constraint_attribute()
530530
return
531531
end
532532

533+
function test_bridging_cost_consistent_with_supports()
534+
# `UniversalFallback.supports_constraint` and
535+
# `supports_add_constrained_variable(s)` accept absolutely anything by
536+
# forwarding through their `is_bridged`-style catch-all. The bridging-cost
537+
# attributes must agree: they should never return `Inf` for a pair that
538+
# `supports_*` claims to support, because that would make
539+
# `LazyBridgeOptimizer` treat the node as unreachable and break graph
540+
# construction. Use `Model{BigFloat}` so the inner model genuinely does
541+
# not support `*Cone{Float64}`, exercising the case where
542+
# `UniversalFallback` extends support beyond the inner.
543+
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{BigFloat}())
544+
for (F, S) in (
545+
# inner supports natively
546+
(MOI.ScalarAffineFunction{BigFloat}, MOI.LessThan{BigFloat}),
547+
(MOI.VectorOfVariables, MOI.PowerCone{BigFloat}),
548+
# inner does not support; UF stores in its own dict
549+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}),
550+
(MOI.VectorOfVariables, MOI.PowerCone{Float64}),
551+
(MOI.VectorAffineFunction{BigFloat}, MOI.Test.UnknownVectorSet),
552+
)
553+
@test MOI.supports_constraint(model, F, S)
554+
@test MOI.get(model, MOI.ConstraintBridgingCost{F,S}()) < Inf
555+
end
556+
for S in (
557+
MOI.GreaterThan{BigFloat},
558+
MOI.Integer,
559+
MOI.GreaterThan{Float64}, # not natively supported by Model{BigFloat}
560+
)
561+
@test MOI.supports_add_constrained_variable(model, S)
562+
@test MOI.get(model, MOI.VariableBridgingCost{S}()) < Inf
563+
end
564+
for S in (
565+
MOI.Nonnegatives,
566+
MOI.PowerCone{BigFloat},
567+
MOI.PowerCone{Float64}, # not natively supported by Model{BigFloat}
568+
)
569+
@test MOI.supports_add_constrained_variables(model, S)
570+
@test MOI.get(model, MOI.VariableBridgingCost{S}()) < Inf
571+
end
572+
return
573+
end
574+
533575
end # module
534576

535577
TestUniversalFallback.runtests()

0 commit comments

Comments
 (0)