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
95 changes: 94 additions & 1 deletion src/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ julia> MOI.Bridges.runtests(
end,
)
Test Summary: | Pass Total Time
Bridges.runtests | 32 32 0.8s
Bridges.runtests | 32 32 0.0s
Comment thread
odow marked this conversation as resolved.
Resolved
```
"""
function runtests(args...; kwargs...)
Expand All @@ -294,12 +294,100 @@ function runtests(args...; kwargs...)
return
end

# This function tests that ConstraintDual is implemented correctly for a bridge
# by comparing Lagrangian values of the inner and outer models at corresponding
# primal points. Evaluating the functions also works for nonlinear expressions.
#
# For this test to be enabled, the bridge should implement `MOI.supports` and
# `MOI.set` for `ConstraintDual`. The easiest way to achieve this is with
# `Union{ConstraintDual,ConstraintDualStart}`.
function _test_dual(
Bridge::Type{<:AbstractBridge},
input_fn::Function;
dual,
eltype,
model_eltype,
)
if Bridge <: Objective.AbstractBridge
return
end
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{model_eltype}())
mock = MOI.Utilities.MockOptimizer(
inner;
eval_variable_constraint_dual = false,
)
model = _bridged_model(Bridge{eltype}, mock)
input_fn(model)
final_touch(model)
final_touch(model)
MOI.set(mock, MOI.PrimalStatus(), MOI.FEASIBLE_POINT)
# Use an integer point so integer-coefficient vector functions can be
# evaluated without inexact conversions. Keep auxiliary primals at zero.
primal = 2 * one(model_eltype)
x = MOI.get(mock, MOI.ListOfVariableIndices())
MOI.set(mock, MOI.VariablePrimal(), x, fill(zero(primal), length(x)))
if Bridge <: Variable.AbstractBridge
MOI.set(mock, MOI.VariablePrimal(), x, fill(primal, length(x)))
else
x = MOI.get(model, MOI.ListOfVariableIndices())
MOI.set(model, MOI.VariablePrimal(), x, fill(primal, length(x)))
end
MOI.set(mock, MOI.DualStatus(), MOI.FEASIBLE_POINT)
# Some bridges leave auxiliary constraint duals unspecified.
for (F, S) in MOI.get(mock, MOI.ListOfConstraintTypesPresent())
for ci in MOI.get(mock, MOI.ListOfConstraintIndices{F,S}())
set = MOI.get(mock, MOI.ConstraintSet(), ci)
MOI.set(
mock,
MOI.ConstraintDual(),
ci,
_fake_start(zero(model_eltype), set),
)
end
end
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
if !MOI.supports(model, MOI.ConstraintDual(), MOI.ConstraintIndex{F,S})
return
end
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
set = MOI.get(model, MOI.ConstraintSet(), ci)
value = _fake_start(dual, set)
MOI.set(model, MOI.ConstraintDual(), ci, value)
Test.@test MOI.get(model, MOI.ConstraintDual(), ci) ≈ value
end
end
Test.@test isapprox(
_test_lagrangian_value(model),
_test_lagrangian_value(mock);
atol = 1e-6,
)
return
end

function _test_lagrangian_value(model::MOI.ModelLike)
value = MOI.Utilities.get_fallback(model, MOI.ObjectiveValue())
sign = MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE ? 1 : -1
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
f = MOI.get(model, MOI.ConstraintFunction(), ci)
primal = MOI.Utilities.eval_variables(model, f) do vi
return MOI.get(model, MOI.VariablePrimal(), vi)
end
set = MOI.get(model, MOI.ConstraintSet(), ci)
dual = MOI.get(model, MOI.ConstraintDual(), ci)
value += sign * MOI.Utilities._dual_objective_dot(primal, dual, set)
end
end
return value
end

function _runtests(
Bridge::Type{<:AbstractBridge},
input_fn::Function,
output_fn::Function;
variable_start = 1.2,
constraint_start = 1.2,
dual = constraint_start,
eltype = Float64,
model_eltype = eltype,
print_inner_model::Bool = false,
Expand Down Expand Up @@ -404,6 +492,11 @@ function _runtests(
Test.@testset "Test delete" begin # COV_EXCL_LINE
_test_delete(Bridge, model, inner)
end
if !isnothing(dual) && !cannot_unbridge
Test.@testset "Test ConstraintDual" begin # COV_EXCL_LINE
_test_dual(Bridge, input_fn; dual, eltype, model_eltype)
end
end
return
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ function conversion_cost(
# This is most useful for conic solvers, which often support
# VectorAffineFunction-in-S, but not VectorOfVariables-in-S.
#
# The value in this function controls the cost of the transformation
# The value in this function controls the cost of the transformation
# VectorOfVariables-in-S -> VectorAffineFunction-in-S. When we had the
# previous cost of `1.0`, there might be an alternative bridge
# VectorOfVariables-in-S -> VectorAffineFunction-in-S2 which also has a cost
Expand Down
6 changes: 3 additions & 3 deletions src/Bridges/Constraint/bridges/GeoMeanToPowerBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
::Type{GeoMeanToPowerBridge{T,F}},
) where {T,F}
return MOI.supports(model, attr, MOI.ConstraintIndex{F,MOI.PowerCone{T}})
Expand Down Expand Up @@ -293,7 +293,7 @@ end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::GeoMeanToPowerBridge{T,F},
start::AbstractVector{T},
) where {T,F}
Expand Down Expand Up @@ -327,7 +327,7 @@ end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::GeoMeanToPowerBridge,
::Nothing,
)
Expand Down
19 changes: 9 additions & 10 deletions src/Bridges/Constraint/bridges/GeoMeantoRelEntrBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
attr::Union{
MOI.ConstraintPrimalStart,
MOI.ConstraintDualStart,
MOI.ConstraintDual,
},
::Type{GeoMeantoRelEntrBridge{T,F,G,H}},
) where {T,F,G,H}
ci_1 = MOI.ConstraintIndex{F,MOI.Nonnegatives}
Expand Down Expand Up @@ -277,28 +281,23 @@ end
# Note log.(w/geomean(w)) = log.(w) .- sum(log, w) / n.
function MOI.set(
model::MOI.ModelLike,
::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::GeoMeantoRelEntrBridge,
value,
)
d = length(value) - 1
u = value[1]
w = value[2:end]
MOI.set(model, MOI.ConstraintDualStart(), bridge.nn_index, [-u])
MOI.set(model, attr, bridge.nn_index, [-u])
relentr_dual =
vcat(-u / d, value[2:end], u / d * (log.(w) .+ (1 - sum(log, w) / d)))
MOI.set(
model,
MOI.ConstraintDualStart(),
bridge.relentr_index,
relentr_dual,
)
MOI.set(model, attr, bridge.relentr_index, relentr_dual)
return
end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::GeoMeantoRelEntrBridge,
::Nothing,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
attr::Union{
MOI.ConstraintPrimalStart,
MOI.ConstraintDualStart,
MOI.ConstraintDual,
},
::Type{IntervalToHyperRectangleBridge{T,F,G}},
) where {T,F,G}
return MOI.supports(
Expand Down Expand Up @@ -157,7 +161,7 @@ end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::IntervalToHyperRectangleBridge,
value,
)
Expand Down
38 changes: 30 additions & 8 deletions src/Bridges/Constraint/bridges/NormNuclearBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDual},
::Type{NormSpectralBridge{T,F,G}},
) where {T,F,G}
return MOI.supports(
Expand Down Expand Up @@ -215,6 +215,28 @@ function MOI.get(
return vcat(t, X)
end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDual,
bridge::NormSpectralBridge{T},
value::AbstractVector,
) where {T}
column_dim = bridge.column_dim
side_dim = bridge.row_dim + column_dim
dual = zeros(T, div(side_dim * (side_dim + 1), 2))
# Invert the trace and the doubled off-diagonal entries in the getter.
for i in 1:side_dim
dual[MOI.Utilities.trimap(i, i)] = value[1] / side_dim
end
k = 2
for j in 1:column_dim, i in (column_dim+1):side_dim
dual[MOI.Utilities.trimap(i, j)] = value[k] / 2
k += 1
end
MOI.set(model, attr, bridge.psd_index, dual)
return
end

"""
NormNuclearBridge{T,F,G,H} <: Bridges.Constraint.AbstractBridge

Expand Down Expand Up @@ -444,7 +466,7 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
::Type{NormNuclearBridge{T,F,G,H}},
) where {T,F,G,H}
ci_1 = MOI.ConstraintIndex{F,MOI.GreaterThan{T}}
Expand Down Expand Up @@ -494,11 +516,11 @@ end

function MOI.set(
model::MOI.ModelLike,
::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::NormNuclearBridge{T},
value,
) where {T}
MOI.set(model, MOI.ConstraintDualStart(), bridge.ge_index, value[1])
MOI.set(model, attr, bridge.ge_index, value[1])
column_dim = bridge.column_dim
side_dim = bridge.row_dim + column_dim
dual = zeros(T, div(side_dim * (side_dim + 1), 2))
Expand All @@ -510,17 +532,17 @@ function MOI.set(
dual[MOI.Utilities.trimap(i, j)] = value[X_idx] / 2
X_idx += 1
end
MOI.set(model, MOI.ConstraintDualStart(), bridge.psd_index, dual)
MOI.set(model, attr, bridge.psd_index, dual)
return
end

function MOI.set(
model::MOI.ModelLike,
::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::NormNuclearBridge{T},
::Nothing,
) where {T}
MOI.set(model, MOI.ConstraintDualStart(), bridge.ge_index, nothing)
MOI.set(model, MOI.ConstraintDualStart(), bridge.psd_index, nothing)
MOI.set(model, attr, bridge.ge_index, nothing)
MOI.set(model, attr, bridge.psd_index, nothing)
return
end
14 changes: 9 additions & 5 deletions src/Bridges/Constraint/bridges/NormOneBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
attr::Union{
MOI.ConstraintPrimalStart,
MOI.ConstraintDualStart,
MOI.ConstraintDual,
},
::Type{NormOneBridge{T,F,G}},
) where {T,F,G}
return MOI.supports(model, attr, MOI.ConstraintIndex{F,MOI.Nonnegatives})
Expand Down Expand Up @@ -219,7 +223,7 @@ end
# depending on the sense of `value[1 + i]`.
function MOI.set(
model::MOI.ModelLike,
::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::NormOneBridge,
value,
)
Expand All @@ -233,16 +237,16 @@ function MOI.set(
nn_dual[1+d+i] = value[1+i]
end
end
MOI.set(model, MOI.ConstraintDualStart(), bridge.nn_index, nn_dual)
MOI.set(model, attr, bridge.nn_index, nn_dual)
return
end

function MOI.set(
model::MOI.ModelLike,
::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::NormOneBridge,
::Nothing,
)
MOI.set(model, MOI.ConstraintDualStart(), bridge.nn_index, nothing)
MOI.set(model, attr, bridge.nn_index, nothing)
return
end
6 changes: 5 additions & 1 deletion src/Bridges/Constraint/bridges/NumberConversionBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
attr::Union{
MOI.ConstraintPrimalStart,
MOI.ConstraintDualStart,
MOI.ConstraintDual,
},
::Type{NumberConversionBridge{T,F1,S1,F2,S2}},
) where {T,F1,S1,F2,S2}
return MOI.supports(model, attr, MOI.ConstraintIndex{F2,S2})
Expand Down
10 changes: 7 additions & 3 deletions src/Bridges/Constraint/bridges/RelativeEntropyBridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ end

function MOI.supports(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
attr::Union{
MOI.ConstraintPrimalStart,
MOI.ConstraintDualStart,
MOI.ConstraintDual,
},
::Type{RelativeEntropyBridge{T,F,G,H}},
) where {T,F,G,H}
ci_1 = MOI.ConstraintIndex{F,MOI.GreaterThan{T}}
Expand Down Expand Up @@ -275,7 +279,7 @@ end
# r_i = exp(W(-w_i / (-v_i * e))) * (-v_i * e), but this is more complicated.
function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::RelativeEntropyBridge,
value,
)
Expand All @@ -289,7 +293,7 @@ end

function MOI.set(
model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
bridge::RelativeEntropyBridge,
::Nothing,
)
Expand Down
Loading
Loading