Skip to content
Draft
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
43 changes: 39 additions & 4 deletions src/Bridges/lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,32 @@ 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
# 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.
cached = get(b.variable_node, (S,), nothing)
if cached !== nothing
return cached
end
new_node = add_node(b.graph, VariableNode)
b.variable_node[(S,)] = new_node
push!(b.variable_types, (S,))
b.graph.variable_dist[new_node.index] = inner_cost
return new_node
end
# If (S,) is stored in .variable_node, we've already added the node
# previously.
Expand Down Expand Up @@ -315,9 +332,27 @@ 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
# 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.
cached = get(b.constraint_node, (F, S), nothing)
if cached !== nothing
return cached
end
new_node = add_node(b.graph, ConstraintNode)
b.constraint_node[(F, S)] = new_node
push!(b.constraint_types, (F, S))
b.graph.constraint_dist[new_node.index] = inner_cost
return new_node
end
# If (F, S) is stored in .constraint_node, we've already added the node
# previously.
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
40 changes: 40 additions & 0 deletions test/Bridges/General/test_lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2477,6 +2477,46 @@ function test_issue_2870_relative_entropy()
return
end

MOI.Utilities.@model(
NonnegOnlyModel,
(),
(),
(MOI.Nonnegatives,),
(),
(),
(),
(MOI.VectorOfVariables,),
(MOI.VectorAffineFunction,)
)

function test_nested_lazy_bridge_optimizer_cost()
# When the inner model is itself a `LazyBridgeOptimizer` that needs to
# bridge a set, the outer `LazyBridgeOptimizer` must take the inner
# bridging cost into account when computing edge costs in its own graph,
# not assume zero cost just because the inner reports `supports`.
T = Float64
# Solver supporting only `Nonnegatives`-constrained variables.
inner = MOI.Bridges.LazyBridgeOptimizer(NonnegOnlyModel{T}())
MOI.Bridges.add_bridge(
inner,
MOI.Bridges.Variable.NonposToNonnegBridge{T},
)
@test MOI.get(inner, MOI.VariableBridgingCost{MOI.Nonnegatives}()) == 0.0
@test MOI.get(inner, MOI.VariableBridgingCost{MOI.Nonpositives}()) == 1.0
cache = MOI.Utilities.CachingOptimizer(
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()),
inner,
)
@test MOI.get(cache, MOI.VariableBridgingCost{MOI.Nonpositives}()) == 1.0
outer = MOI.Bridges.LazyBridgeOptimizer(cache)
@test MOI.get(outer, MOI.VariableBridgingCost{MOI.Nonpositives}()) == 1.0
@test MOI.Bridges.bridging_cost(
outer.graph,
MOI.Bridges.node(outer, MOI.Nonpositives),
) == 1.0
return
end

end # module

TestBridgesLazyBridgeOptimizer.runtests()
Loading