Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/Bridges/lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,22 @@ function node(
@nospecialize(b::LazyBridgeOptimizer),
@nospecialize(S::Type{<:MOI.AbstractSet}),
)
# If we support the set, the node is 0.
# If we support the set, the node is 0 unless the inner model reports a
# non-zero `VariableBridgingCost` (which can happen when the inner model is
# itself a bridge optimizer that needs to bridge `S`).
if (
S <: MOI.AbstractScalarSet &&
MOI.supports_add_constrained_variable(b.model, S)
) || (
S <: MOI.AbstractVectorSet &&
MOI.supports_add_constrained_variables(b.model, S)
)
return VariableNode(0)
inner_cost = MOI.get(b.model, MOI.VariableBridgingCost{S}())::Float64
if iszero(inner_cost)
return VariableNode(0)
end
else
inner_cost = nothing
end
# If (S,) is stored in .variable_node, we've already added the node
# previously.
Expand All @@ -275,6 +282,13 @@ function node(
variable_node = add_node(b.graph, VariableNode)
b.variable_node[(S,)] = variable_node
push!(b.variable_types, (S,))
if !isnothing(inner_cost)
# The inner model supports `S` but with a non-zero bridging cost.
# Create a leaf node whose distance is `inner_cost` so that bridges
# that emit constrained variables in `S` account for it.
b.graph.variable_dist[variable_node.index] = inner_cost
return variable_node
end
F = MOI.Utilities.variable_function_type(S)
if is_bridged(b, MOI.Reals)
# The solver doesn't support adding free variables.
Expand Down Expand Up @@ -315,9 +329,17 @@ function node(
@nospecialize(F::Type{<:MOI.AbstractFunction}),
@nospecialize(S::Type{<:MOI.AbstractSet}),
)
# If we support the constraint type, the node is 0.
# If we support the constraint type, the node is 0 unless the inner model
# reports a non-zero `ConstraintBridgingCost` (which can happen when the
# inner model is itself a bridge optimizer that needs to bridge `F`-in-`S`).
if MOI.supports_constraint(b.model, F, S)
return ConstraintNode(0)
inner_cost =
MOI.get(b.model, MOI.ConstraintBridgingCost{F,S}())::Float64
if iszero(inner_cost)
return ConstraintNode(0)
end
else
inner_cost = nothing
end
# If (F, S) is stored in .constraint_node, we've already added the node
# previously.
Expand All @@ -329,6 +351,13 @@ function node(
constraint_node = add_node(b.graph, ConstraintNode)
b.constraint_node[(F, S)] = constraint_node
push!(b.constraint_types, (F, S))
if !isnothing(inner_cost)
# The inner model supports `F`-in-`S` but with a non-zero bridging cost.
# Create a leaf node whose distance is `inner_cost` so that bridges
# that emit `F`-in-`S` constraints account for it.
b.graph.constraint_dist[constraint_node.index] = inner_cost
return constraint_node
end
for (i, BT) in enumerate(b.constraint_bridge_types)
if MOI.supports_constraint(BT, F, S)
edge = _edge(b, i, Constraint.concrete_bridge_type(BT, F, S))::Edge
Expand Down
8 changes: 8 additions & 0 deletions src/FileFormats/NL/NL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,14 @@ function MOI.get(model::Model, attr::MOI.AbstractModelAttribute)
return MOI.get(inner, attr)
end

# It doesn't need the inner model
function MOI.get(
model::Model,
attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost},
)
return MOI.get_fallback(model, attr)
end

function MOI.get(
model::Model,
attr::MOI.AbstractConstraintAttribute,
Expand Down
52 changes: 52 additions & 0 deletions src/Test/test_attribute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,55 @@ function test_attribute_unsupported_constraint(model::MOI.ModelLike, ::Config)
end

version_added(::typeof(test_attribute_unsupported_constraint)) = v"1.9.0"

"""
test_attribute_VariableBridgingCost(model::MOI.ModelLike, config::Config)

Test that, for every set `S` that the model claims to support via
`supports_add_constrained_variable(s)`, the corresponding
[`MOI.VariableBridgingCost`](@ref) attribute returns a finite value.

This is the variable-side analog of the `ConstraintBridgingCost` check in
`_basic_constraint_test_helper`.

The fallback works for most model but it may need custom method for some MOI
layers (see https://github.com/jump-dev/MathOptInterface.jl/pull/3001#issuecomment-4468198935).

This test is here to catch that.
"""
function test_attribute_VariableBridgingCost(
model::MOI.ModelLike,
::Config{T},
) where {T}
for S in Any[
MOI.GreaterThan{T},
MOI.LessThan{T},
MOI.EqualTo{T},
MOI.Interval{T},
MOI.Integer,
MOI.ZeroOne,
MOI.Semicontinuous{T},
MOI.Semiinteger{T},
]
if MOI.supports_add_constrained_variable(model, S)
@test MOI.get(model, MOI.VariableBridgingCost{S}()) < Inf
end
end
for S in Any[
MOI.Reals,
MOI.Zeros,
MOI.Nonnegatives,
MOI.Nonpositives,
MOI.SecondOrderCone,
MOI.RotatedSecondOrderCone,
MOI.ExponentialCone,
MOI.PositiveSemidefiniteConeTriangle,
]
if MOI.supports_add_constrained_variables(model, S)
@test MOI.get(model, MOI.VariableBridgingCost{S}()) < Inf
end
end
return
end

version_added(::typeof(test_attribute_VariableBridgingCost)) = v"1.52.0"
10 changes: 10 additions & 0 deletions src/Test/test_basic_constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,16 @@ function _basic_constraint_test_helper(
###
@requires MOI.supports_constraint(model, F, S)
###
### Test MOI.ConstraintBridgingCost
###
# If `supports_constraint(F, S)` returns `true`, then the model must be
# able to handle that pair (possibly via bridging), so the bridging cost
# must be finite. The fallback works for most model but it may need
# custom method for some MOI layer (see
# https://github.com/jump-dev/MathOptInterface.jl/pull/3001#issuecomment-4468198935)
# This test is here to catch that.
@test MOI.get(model, MOI.ConstraintBridgingCost{F,S}()) < Inf
###
### Test MOI.NumberOfConstraints
###
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 0
Expand Down
10 changes: 10 additions & 0 deletions src/Utilities/cachingoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,16 @@ function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute)
return _get_model_attribute(model, attr)
end

function MOI.get(
model::CachingOptimizer,
attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost},
)::Float64
if state(model) == NO_OPTIMIZER
return MOI.get(model.model_cache, attr)
end
return MOI.get(model.optimizer, attr)
end

function MOI.get(
model::CachingOptimizer,
attr::MOI.TerminationStatus,
Expand Down
30 changes: 30 additions & 0 deletions src/Utilities/universalfallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,36 @@ function MOI.get(
return _get(uf, attr)
end

function MOI.get(
uf::UniversalFallback,
attr::MOI.VariableBridgingCost{S},
)::Float64 where {S<:MOI.AbstractScalarSet}
if MOI.supports_add_constrained_variable(uf.model, S)
return MOI.get(uf.model, attr)
end
return 0.0
end

function MOI.get(
uf::UniversalFallback,
attr::MOI.VariableBridgingCost{S},
)::Float64 where {S<:MOI.AbstractVectorSet}
if MOI.supports_add_constrained_variables(uf.model, S)
return MOI.get(uf.model, attr)
end
return 0.0
end

function MOI.get(
uf::UniversalFallback,
attr::MOI.ConstraintBridgingCost{F,S},
)::Float64 where {F,S}
if MOI.supports_constraint(uf.model, F, S)
return MOI.get(uf.model, attr)
end
return 0.0
end

function MOI.get(
uf::UniversalFallback,
attr::MOI.AbstractConstraintAttribute,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function test_basic(T)
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()),
)
bridged_mock = MOI.Bridges.Constraint.IntervalToHyperRectangle{T}(mock)
config = MOI.Test.Config()
config = MOI.Test.Config(T)
MOI.Test.runtests(
bridged_mock,
config,
Expand Down
Loading
Loading