Skip to content

Commit 368153a

Browse files
committed
Add Variable.ZerosBridge
1 parent 42cdc34 commit 368153a

File tree

7 files changed

+269
-6
lines changed

7 files changed

+269
-6
lines changed

src/Bridges/Variable/Variable.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ include("map.jl")
1414
# Bridge optimizer bridging a specific variable bridge
1515
include("single_bridge_optimizer.jl")
1616

17+
# Variable bridges
18+
include("zeros.jl")
19+
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}
20+
1721
end

src/Bridges/Variable/zeros.jl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
ZerosBridge{T} <: Bridges.Variable.AbstractBridge
3+
4+
Transforms constrained variables in [`MathOptInterface.Zeros`](@ref) to zeros,
5+
which ends up creating no variables in the underlying model.
6+
The bridged variables are therefore similar to parameters with zero values.
7+
Parameters with non-zero value can be created with constrained variables in
8+
[`MOI.EqualTo`](@ref) by combining a [`VectorizeBridge`](@ref) and this bridge.
9+
The functions cannot be unbridged, given a function, we cannot determine, if
10+
the bridged variables were use.
11+
The dual values cannot be determined by the bridge but they can be determined
12+
by the bridged optimizer using [`MathOptInterface.Utilities.get_fallback`](@ref)
13+
if a `CachingOptimizer` is used (since `ConstraintFunction` cannot be got
14+
as functions cannot be unbridged).
15+
"""
16+
struct ZerosBridge{T} <: AbstractBridge
17+
n::Int # Number of variables
18+
end
19+
function bridge_constrained_variable(::Type{ZerosBridge{T}},
20+
model::MOI.ModelLike,
21+
set::MOI.Zeros) where T
22+
return ZerosBridge{T}(MOI.dimension(set))
23+
end
24+
25+
function supports_constrained_variable(
26+
::Type{<:ZerosBridge}, ::Type{MOI.Zeros})
27+
return true
28+
end
29+
function MOIB.added_constrained_variable_types(::Type{<:ZerosBridge})
30+
return Tuple{DataType}[]
31+
end
32+
function MOIB.added_constraint_types(::Type{<:ZerosBridge})
33+
return Tuple{DataType, DataType}[]
34+
end
35+
36+
# Attributes, Bridge acting as a model
37+
MOI.get(bridge::ZerosBridge, ::MOI.NumberOfVariables) = 0
38+
function MOI.get(bridge::ZerosBridge, ::MOI.ListOfVariableIndices)
39+
return MOI.VariableIndex[]
40+
end
41+
42+
# References
43+
function MOI.delete(::MOI.ModelLike, ::ZerosBridge) end
44+
45+
# Attributes, Bridge acting as a constraint
46+
47+
function MOI.get(::MOI.ModelLike, ::MOI.ConstraintPrimal,
48+
bridge::ZerosBridge{T}) where T
49+
return zeros(T, bridge.n)
50+
end
51+
52+
function MOI.get(::MOI.ModelLike, ::MOI.VariablePrimal,
53+
::ZerosBridge{T}, ::IndexInVector) where T
54+
return zero(T)
55+
end
56+
57+
function MOIB.bridged_function(::ZerosBridge{T}, ::IndexInVector) where T
58+
return zero(MOI.ScalarAffineFunction{T})
59+
end
60+
function unbridged_map(::ZerosBridge, ::MOI.VariableIndex,
61+
::IndexInVector)
62+
return nothing
63+
end

src/Bridges/bridge_optimizer.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,13 @@ function bridged_function(b::AbstractBridgeOptimizer,
931931
end
932932
return func
933933
end
934+
# Shortcut to avoid `Variable.throw_if_cannot_unbridge(Variable.bridges(b))`
935+
function bridge_function(
936+
::AbstractBridgeOptimizer,
937+
value::Union{Number, Enum, AbstractArray{<:Union{Number, Enum}}})
938+
return value
939+
end
940+
934941

935942
"""
936943
unbridged_variable_function(b::AbstractBridgeOptimizer,
@@ -975,6 +982,12 @@ function unbridged_function(bridge::AbstractBridgeOptimizer,
975982
func::Union{MOI.SingleVariable, MOI.VectorOfVariables})
976983
return func # bridged variables are not allowed in non-bridged constraints
977984
end
985+
# Shortcut to avoid `Variable.throw_if_cannot_unbridge(Variable.bridges(b))`
986+
function unbridged_function(
987+
::AbstractBridgeOptimizer,
988+
value::Union{Number, Enum, AbstractArray{<:Union{Number, Enum}}})
989+
return value
990+
end
978991

979992
"""
980993
unbridged_constraint_function(

test/Bridges/Bridges.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ end
66
@testset "LazyBridgeOptimizer" begin
77
include("lazy_bridge_optimizer.jl")
88
end
9+
@testset "Variable bridges" begin
10+
include("Variable/Variable.jl")
11+
end
912
@testset "Constraint bridges" begin
1013
include("Constraint/Constraint.jl")
1114
end

test/Bridges/Variable/Variable.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
using Test
12
@testset "Map" begin
23
include("map.jl")
34
end
5+
@testset "Zeros" begin
6+
include("zeros.jl")
7+
end

test/Bridges/Variable/zeros.jl

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Test
2+
3+
using MathOptInterface
4+
const MOI = MathOptInterface
5+
const MOIT = MathOptInterface.Test
6+
const MOIU = MathOptInterface.Utilities
7+
const MOIB = MathOptInterface.Bridges
8+
9+
include("../utilities.jl")
10+
11+
mock = MOIU.MockOptimizer(MOIU.Model{Float64}())
12+
config = MOIT.TestConfig()
13+
14+
bridged_mock = MOIB.Variable.Zeros{Float64}(mock)
15+
16+
x, cx = MOI.add_constrained_variable(bridged_mock, MOI.GreaterThan(0.0))
17+
yz, cyz = MOI.add_constrained_variables(bridged_mock, MOI.Zeros(2))
18+
y, z = yz
19+
fx = MOI.SingleVariable(x)
20+
fy = MOI.SingleVariable(y)
21+
fz = MOI.SingleVariable(z)
22+
c1, c2 = MOI.add_constraints(
23+
bridged_mock, [1.0fy + 1.0fz, 1.0fx + 1.0fy + 1.0fz],
24+
[MOI.EqualTo(0.0), MOI.GreaterThan(1.0)]
25+
)
26+
#c2 = MOI.add_constraint(bridged_mock, , )
27+
MOI.set(bridged_mock, MOI.ObjectiveSense(), MOI.MIN_SENSE)
28+
obj = 1.0fx - 1.0fy - 1.0fz
29+
MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(obj)}(), obj)
30+
31+
@test MOIB.Variable.unbridged_map(MOIB.bridge(bridged_mock, y), y, MOIB.Variable.IndexInVector(1)) === nothing
32+
@test MOIB.Variable.unbridged_map(MOIB.bridge(bridged_mock, z), z, MOIB.Variable.IndexInVector(2)) === nothing
33+
34+
err = ErrorException(
35+
"Cannot delete constraint index of bridged constrained variables. Delete" *
36+
" the scalar variable or the vector of variables instead."
37+
)
38+
@test_throws err MOI.delete(bridged_mock, cyz)
39+
40+
err = ErrorException(
41+
"Cannot add two `VectorOfVariables`-in-`MathOptInterface.Zeros` on the" *
42+
" same first variable MathOptInterface.VariableIndex(-1)."
43+
)
44+
@test_throws err MOI.add_constraint(bridged_mock, MOI.VectorOfVariables(yz), MOI.Zeros(2))
45+
46+
err = ErrorException(
47+
"Cannot `VectorOfVariables`-in-`MathOptInterface.Zeros` for" *
48+
" which some variables are bridged but not the first one" *
49+
" `MathOptInterface.VariableIndex(12345679)`."
50+
)
51+
@test_throws err MOI.add_constraint(bridged_mock, MOI.VectorOfVariables([x, y]), MOI.Zeros(2))
52+
53+
err = ErrorException(
54+
"Cannot unbridge function because some variables are bridged by" *
55+
" variable bridges that do not support reverse mapping, e.g.," *
56+
" `ZerosBridge`."
57+
)
58+
@test_throws err MOI.get(bridged_mock, MOI.ObjectiveFunction{typeof(obj)}())
59+
# With `c1`, the function does not contain any variable so it tests that it
60+
# also throws an error even if it never calls `variable_unbridged_function`.
61+
@test_throws err MOI.get(bridged_mock, MOI.ConstraintFunction(), c1)
62+
@test_throws err MOI.get(bridged_mock, MOI.ConstraintFunction(), c2)
63+
64+
err = ArgumentError(
65+
"Variable bridge of type `MathOptInterface.Bridges.Variable.ZerosBridge{Float64}`" *
66+
" does not support accessing the attribute `MathOptInterface.Test.UnknownVariableAttribute()`."
67+
)
68+
@test_throws err MOI.get(bridged_mock, MOIT.UnknownVariableAttribute(), y)
69+
70+
@testset "Results" begin
71+
MOIU.set_mock_optimize!(mock,
72+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
73+
mock, [1.0],
74+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => 0.0,
75+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => 1.0)
76+
)
77+
MOI.optimize!(bridged_mock)
78+
@test MOI.get(bridged_mock, MOI.VariablePrimal(), x) == 1.0
79+
@test MOI.get(bridged_mock, MOI.VariablePrimal(), y) == 0.0
80+
@test MOI.get(bridged_mock, MOI.VariablePrimal(), z) == 0.0
81+
82+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cyz) == zeros(2)
83+
84+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cx) == 0.0
85+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), c1) == 0.0
86+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), c2) == 1.0
87+
88+
err = ArgumentError(
89+
"Bridge of type `MathOptInterface.Bridges.Variable.ZerosBridge{Float64}`" *
90+
" does not support accessing the attribute" *
91+
" `MathOptInterface.ConstraintDual(1)`."
92+
)
93+
@test_throws err MOI.get(bridged_mock, MOI.ConstraintDual(), cyz)
94+
end
95+
96+
@testset "Query" begin
97+
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), cyz).variables == yz
98+
@test MOI.get(mock, MOI.NumberOfVariables()) == 1
99+
@test MOI.get(mock, MOI.ListOfVariableIndices()) == [x]
100+
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 3
101+
@test MOI.get(bridged_mock, MOI.ListOfVariableIndices()) == [x, y, z]
102+
@test MOI.get(mock, MOI.NumberOfConstraints{MOI.VectorOfVariables, MOI.Zeros}()) == 0
103+
@test MOI.get(bridged_mock, MOI.NumberOfConstraints{MOI.VectorOfVariables, MOI.Zeros}()) == 1
104+
@test MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, MOI.Zeros}()) == [cyz]
105+
end
106+
107+
@testset "SingleVariable objective" begin
108+
err = ErrorException("Using bridged variable in `SingleVariable` function.")
109+
@test_throws err MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(fy)}(), fy)
110+
MOI.set(bridged_mock, MOI.ObjectiveFunction{typeof(fx)}(), fx)
111+
@test MOI.get(bridged_mock, MOI.ObjectiveFunction{typeof(fx)}()) == fx
112+
end
113+
114+
@testset "Delete" begin
115+
test_delete_bridged_variables(bridged_mock, yz, MOI.Zeros, 3, (
116+
(MOI.SingleVariable, MOI.GreaterThan{Float64}, 1),
117+
))
118+
@test MOI.is_valid(bridged_mock, x)
119+
@test !MOI.is_valid(bridged_mock, y)
120+
@test !MOI.is_valid(bridged_mock, z)
121+
end

test/Bridges/utilities.jl

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ function test_delete_bridge(
1919
end
2020
@test MOI.is_valid(m, ci)
2121
MOI.delete(m, ci)
22-
@test_throws MOI.InvalidIndex{typeof(ci)} MOI.delete(m, ci)
23-
try
24-
MOI.delete(m, ci)
25-
catch err
26-
@test err.index == ci
27-
end
22+
@test_throws MOI.InvalidIndex(ci) MOI.delete(m, ci)
2823
@test !MOI.is_valid(m, ci)
2924
@test num_bridges() == start_num_bridges - used_bridges
3025
test_noc(m, F, S, num_bridged - 1)
@@ -34,3 +29,63 @@ function test_delete_bridge(
3429
test_noc(m, noc...)
3530
end
3631
end
32+
function test_delete_bridged_variable(
33+
m::MOIB.AbstractBridgeOptimizer, vi::MOI.VariableIndex, S::Type,
34+
nvars::Int, nocs::Tuple; used_bridges = 1, num_bridged = 1, used_constraints = 1)
35+
function num_bridges()
36+
return count(bridge -> true, values(MOIB.Variable.bridges(m)))
37+
end
38+
start_num_bridges = num_bridges()
39+
@test MOI.get(m, MOI.NumberOfVariables()) == nvars
40+
@test length(MOI.get(m, MOI.ListOfVariableIndices())) == nvars
41+
if S != MOI.Reals
42+
F = S <: MOI.AbstractScalarSet ? MOI.SingleVariable : MOI.VectorOfVariables
43+
test_noc(m, F, S, num_bridged)
44+
end
45+
for noc in nocs
46+
test_noc(m, noc...)
47+
end
48+
@test MOI.is_valid(m, vi)
49+
MOI.delete(m, vi)
50+
@test_throws MOI.InvalidIndex(vi) MOI.delete(m, vi)
51+
@test !MOI.is_valid(m, vi)
52+
@test num_bridges() == start_num_bridges - used_bridges
53+
if S != MOI.Reals
54+
test_noc(m, F, S, num_bridged - used_constraints)
55+
end
56+
@test MOI.get(m, MOI.NumberOfVariables()) == nvars - 1
57+
@test length(MOI.get(m, MOI.ListOfVariableIndices())) == nvars - 1
58+
for noc in nocs
59+
test_noc(m, noc...)
60+
end
61+
end
62+
function test_delete_bridged_variables(
63+
m::MOIB.AbstractBridgeOptimizer, vis::Vector{MOI.VariableIndex}, S::Type,
64+
nvars::Int, nocs::Tuple; used_bridges = 1, num_bridged = 1)
65+
function num_bridges()
66+
return count(bridge -> true, values(MOIB.Variable.bridges(m)))
67+
end
68+
start_num_bridges = num_bridges()
69+
@test MOI.get(m, MOI.NumberOfVariables()) == nvars
70+
@test length(MOI.get(m, MOI.ListOfVariableIndices())) == nvars
71+
if S != MOI.Reals
72+
F = S <: MOI.AbstractScalarSet ? MOI.SingleVariable : MOI.VectorOfVariables
73+
test_noc(m, F, S, num_bridged)
74+
end
75+
for noc in nocs
76+
test_noc(m, noc...)
77+
end
78+
@test all(vi -> MOI.is_valid(m, vi), vis)
79+
MOI.delete(m, vis)
80+
@test_throws MOI.InvalidIndex(vis[1]) MOI.delete(m, vis)
81+
@test all(vi -> !MOI.is_valid(m, vi), vis)
82+
@test num_bridges() == start_num_bridges - used_bridges
83+
if S != MOI.Reals
84+
test_noc(m, F, S, num_bridged - 1)
85+
end
86+
@test MOI.get(m, MOI.NumberOfVariables()) == nvars - length(vis)
87+
@test length(MOI.get(m, MOI.ListOfVariableIndices())) == nvars - length(vis)
88+
for noc in nocs
89+
test_noc(m, noc...)
90+
end
91+
end

0 commit comments

Comments
 (0)