Skip to content

Commit a1db9f4

Browse files
authored
Merge pull request #823 from JuliaOpt/bl/variable_vectorize_bridge
Add Variable.VectorizeBridge
2 parents a04646c + 8034332 commit a1db9f4

File tree

5 files changed

+293
-7
lines changed

5 files changed

+293
-7
lines changed

src/Bridges/Variable/Variable.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ include("zeros.jl")
1919
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}
2020
include("flip_sign.jl")
2121
const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT}
22+
include("vectorize.jl")
23+
const Vectorize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorizeBridge{T}, OT}
2224
include("rsoc_to_psd.jl")
2325
const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}, OT}
2426

src/Bridges/Variable/vectorize.jl

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""
2+
VectorizeBridge{T, S}
3+
4+
Transforms a constrained variable in `scalar_set_type(S, T)` where
5+
`S <: VectorLinearSet` into a constrained vector of one variable in `S`. For
6+
instance, `VectorizeBridge{Float64, MOI.Nonnegatives}` transforms a constrained
7+
variable in `MOI.GreaterThan{Float64}` into a constrained vector of one
8+
variable in `MOI.Nonnegatives`.
9+
"""
10+
mutable struct VectorizeBridge{T, S} <: AbstractBridge
11+
variable::MOI.VariableIndex
12+
vector_constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, S}
13+
set_constant::T # constant in scalar set
14+
end
15+
function bridge_constrained_variable(
16+
::Type{VectorizeBridge{T, S}},
17+
model::MOI.ModelLike, set::MOIU.ScalarLinearSet{T}) where {T, S}
18+
set_constant = MOI.constant(set)
19+
variables, vector_constraint = MOI.add_constrained_variables(model, S(1))
20+
return VectorizeBridge{T, S}(variables[1], vector_constraint, set_constant)
21+
end
22+
23+
function supports_constrained_variable(
24+
::Type{VectorizeBridge{T}}, ::Type{<:MOIU.ScalarLinearSet{T}}) where T
25+
return true
26+
end
27+
function MOIB.added_constrained_variable_types(::Type{VectorizeBridge{T, S}}) where {T, S}
28+
return [(S,)]
29+
end
30+
function MOIB.added_constraint_types(::Type{<:VectorizeBridge})
31+
return Tuple{DataType, DataType}[]
32+
end
33+
function concrete_bridge_type(::Type{<:VectorizeBridge{T}},
34+
S::Type{<:MOIU.ScalarLinearSet{T}}) where T
35+
return VectorizeBridge{T, MOIU.vector_set_type(S)}
36+
end
37+
38+
# Attributes, Bridge acting as a model
39+
function MOI.get(bridge::VectorizeBridge, ::MOI.NumberOfVariables)
40+
return 1
41+
end
42+
function MOI.get(bridge::VectorizeBridge, ::MOI.ListOfVariableIndices)
43+
return [bridge.variable]
44+
end
45+
function MOI.get(::VectorizeBridge{T, S},
46+
::MOI.NumberOfConstraints{MOI.VectorOfVariables, S}) where {T, S}
47+
return 1
48+
end
49+
function MOI.get(bridge::VectorizeBridge{T, S},
50+
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}) where {T, S}
51+
return [bridge.vector_constraint]
52+
end
53+
54+
# References
55+
function MOI.delete(model::MOI.ModelLike, bridge::VectorizeBridge)
56+
MOI.delete(model, bridge.variable)
57+
end
58+
59+
# Attributes, Bridge acting as a constraint
60+
61+
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet,
62+
bridge::VectorizeBridge{T, S}) where {T, S}
63+
return MOIU.scalar_set_type(S, T)(bridge.set_constant)
64+
end
65+
66+
function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet,
67+
bridge::VectorizeBridge, new_set::MOIU.ScalarLinearSet)
68+
# This would require modifing any constraint which uses the bridged
69+
# variable.
70+
throw(MOI.SetAttributeNotAllowed(attr,
71+
"The variable `$(bridge.variable)` is bridged by the `VectorizeBridge`."))
72+
end
73+
74+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
75+
bridge::VectorizeBridge)
76+
x = MOI.get(model, attr, bridge.vector_constraint)
77+
@assert length(x) == 1
78+
y = x[1]
79+
if !MOIU.is_ray(MOI.get(model, MOI.PrimalStatus(attr.N)))
80+
# If it is an infeasibility certificate, it is a ray and satisfies the
81+
# homogenized problem, see https://github.com/JuliaOpt/MathOptInterface.jl/issues/433
82+
# Otherwise, we need to add the set constant since the ConstraintPrimal
83+
# is defined as the value of the function and the set_constant was
84+
# removed from the original function
85+
y += bridge.set_constant
86+
end
87+
return y
88+
end
89+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
90+
bridge::VectorizeBridge)
91+
x = MOI.get(model, attr, bridge.vector_constraint)
92+
@assert length(x) == 1
93+
return x[1]
94+
end
95+
96+
function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
97+
bridge::VectorizeBridge)
98+
value = MOI.get(model, attr, bridge.variable)
99+
if !MOIU.is_ray(MOI.get(model, MOI.PrimalStatus(attr.N)))
100+
value += bridge.set_constant
101+
end
102+
return value
103+
end
104+
105+
function MOIB.bridged_function(bridge::VectorizeBridge{T}) where T
106+
func = MOI.SingleVariable(bridge.variable)
107+
return MOIU.operate(+, T, func, bridge.set_constant)
108+
end
109+
function unbridged_map(bridge::VectorizeBridge{T}, vi::MOI.VariableIndex) where T
110+
func = MOIU.operate(-, T, MOI.SingleVariable(vi),
111+
bridge.set_constant)
112+
return (bridge.variable => func,)
113+
end

src/Bridges/bridge_optimizer.jl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -999,13 +999,11 @@ scalar.
999999
function unbridged_constraint_function end
10001000

10011001
function unbridged_constraint_function(
1002-
b::AbstractBridgeOptimizer,
1003-
func::Union{MOI.AbstractVectorSet, MOI.SingleVariable}
1004-
)
1002+
b::AbstractBridgeOptimizer, func::MOI.AbstractVectorFunction)
10051003
return unbridged_function(b, func)
10061004
end
1007-
function unbridged_constraint_function(b::AbstractBridgeOptimizer,
1008-
func::MOI.AbstractScalarFunction)
1005+
function unbridged_constraint_function(
1006+
b::AbstractBridgeOptimizer, func::MOI.AbstractScalarFunction)
10091007
if !Variable.has_bridges(Variable.bridges(b))
10101008
return func
10111009
end
@@ -1017,8 +1015,8 @@ function unbridged_constraint_function(b::AbstractBridgeOptimizer,
10171015
return f
10181016
end
10191017
function unbridged_constraint_function(
1020-
::AbstractBridgeOptimizer, func::MOI.AbstractVectorFunction)
1021-
return func
1018+
b::AbstractBridgeOptimizer, func::MOI.SingleVariable)
1019+
return unbridged_function(b, func)
10221020
end
10231021

10241022

test/Bridges/Variable/Variable.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ end
88
@testset "FlipSign" begin
99
include("flip_sign.jl")
1010
end
11+
@testset "Vectorize" begin
12+
include("vectorize.jl")
13+
end
1114
@testset "RSOCtoPSD" begin
1215
include("rsoc_to_psd.jl")
1316
end

test/Bridges/Variable/vectorize.jl

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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.Vectorize{Float64}(mock)
15+
16+
@testset "get scalar constraint" begin
17+
x, cx = MOI.add_constrained_variable(bridged_mock, MOI.GreaterThan(1.0))
18+
fx = MOI.SingleVariable(x)
19+
func = 2.0 * fx
20+
set = MOI.GreaterThan(5.0)
21+
err = MOI.ScalarFunctionConstantNotZero{
22+
Float64, typeof(func), typeof(set)}(1.0)
23+
@test_throws err MOI.add_constraint(bridged_mock, func + 1.0, set)
24+
25+
c = MOI.add_constraint(bridged_mock, func, set)
26+
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), c) func
27+
@test MOI.get(bridged_mock, MOI.ConstraintSet(), c) == set
28+
MOI.set(bridged_mock, MOI.ConstraintName(), c, "c")
29+
30+
@testset "Mock model" begin
31+
MOI.set(mock, MOI.VariableName(),
32+
MOI.get(mock, MOI.ListOfVariableIndices()), ["y"])
33+
MOI.set(mock, MOI.ConstraintName(),
34+
MOI.get(mock, MOI.ListOfConstraintIndices{
35+
MOI.VectorOfVariables, MOI.Nonnegatives}()),
36+
["cy"])
37+
s = """
38+
variables: y
39+
cy: [y] in MathOptInterface.Nonnegatives(1)
40+
c: 2.0y >= 3.0
41+
"""
42+
model = MOIU.Model{Float64}()
43+
MOIU.loadfromstring!(model, s)
44+
MOIU.test_models_equal(mock, model, ["y"], ["cy", "c"])
45+
end
46+
@testset "Bridged model" begin
47+
MOI.set(bridged_mock, MOI.VariableName(), x, "x")
48+
MOI.set(bridged_mock, MOI.ConstraintName(), cx, "cx")
49+
s = """
50+
variables: x
51+
cx: x >= 1.0
52+
c: 2.0x >= 5.0
53+
"""
54+
model = MOIU.Model{Float64}()
55+
MOIU.loadfromstring!(model, s)
56+
MOIU.test_models_equal(bridged_mock, model, ["x"], ["cx", "c"])
57+
end
58+
end
59+
60+
@testset "exp3 with add_constrained_variable for `y`" begin
61+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [log(5), 0.0],
62+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0.0],
63+
(MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [[-1.0, log(5)-1, 1/5]])
64+
65+
MOI.empty!(bridged_mock)
66+
x = MOI.add_variable(bridged_mock)
67+
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 1
68+
fx = MOI.SingleVariable(x)
69+
xc = MOI.add_constraint(bridged_mock, 2.0fx, MOI.LessThan(4.0))
70+
y, yc = MOI.add_constrained_variable(bridged_mock, MOI.LessThan(5.0))
71+
@test yc.value == y.value == -1
72+
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 2
73+
@test length(MOI.get(bridged_mock, MOI.ListOfVariableIndices())) == 2
74+
@test Set(MOI.get(bridged_mock, MOI.ListOfVariableIndices())) == Set([x, y])
75+
fy = MOI.SingleVariable(y)
76+
ec = MOI.add_constraint(bridged_mock,
77+
MOIU.operate(vcat, Float64, fx, 1.0, fy),
78+
MOI.ExponentialCone())
79+
80+
MOI.optimize!(bridged_mock)
81+
@test MOI.get(bridged_mock, MOI.VariablePrimal(), x) log(5)
82+
@test MOI.get(bridged_mock, MOI.VariablePrimal(), y) 5.0
83+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), xc) 2log(5)
84+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), yc) 5
85+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), ec) [log(5), 1., 5.0]
86+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), xc) 0.0
87+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), yc) -1/5
88+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), ec) [-1., log(5)-1, 1/5]
89+
90+
err = ErrorException(
91+
"Cannot add two `SingleVariable`-in-`MathOptInterface.LessThan{Float64}`" *
92+
" on the same variable MathOptInterface.VariableIndex(-1)."
93+
)
94+
@test_throws err MOI.add_constraint(bridged_mock, MOI.SingleVariable(y), MOI.LessThan(4.0))
95+
96+
cis = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
97+
MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone}())
98+
@test length(cis) == 1
99+
100+
@testset "get `UnknownVariableAttribute``" begin
101+
err = ArgumentError(
102+
"Variable bridge of type `MathOptInterface.Bridges.Variable.VectorizeBridge{Float64,MathOptInterface.Nonpositives}`" *
103+
" does not support accessing the attribute `MathOptInterface.Test.UnknownVariableAttribute()`."
104+
)
105+
@test_throws err MOI.get(bridged_mock, MOIT.UnknownVariableAttribute(), y)
106+
end
107+
108+
@testset "set `ConstraintSet`" begin
109+
ci = MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}}(y.value)
110+
attr = MOI.ConstraintSet()
111+
err = MOI.SetAttributeNotAllowed(attr,
112+
"The variable `MathOptInterface.VariableIndex(12345676)` is bridged by the `VectorizeBridge`.")
113+
@test_throws err MOI.set(bridged_mock, attr, ci, MOI.LessThan(4.0))
114+
end
115+
116+
@testset "MultirowChange" begin
117+
change = MOI.MultirowChange(y, [(3, 0.0)])
118+
message = "The change MathOptInterface.MultirowChange{Float64}(MathOptInterface.VariableIndex(-1), Tuple{Int64,Float64}[(3, 0.0)])" *
119+
" contains variables bridged into a function with nonzero constant."
120+
err = MOI.ModifyConstraintNotAllowed(cis[1], change, message)
121+
@test_throws err MOI.modify(bridged_mock, cis[1], change)
122+
end
123+
124+
@testset "ScalarCoefficientChange" begin
125+
change = MOI.ScalarCoefficientChange(y, 0.0)
126+
attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()
127+
message = "The change MathOptInterface.ScalarCoefficientChange{Float64}(MathOptInterface.VariableIndex(-1), 0.0)" *
128+
" contains variables bridged into a function with nonzero constant."
129+
err = MOI.ModifyObjectiveNotAllowed(change, message)
130+
@test_throws err MOI.modify(bridged_mock, attr, change)
131+
end
132+
133+
MOI.set(bridged_mock, MOI.VariableName(), x, "x")
134+
MOI.set(bridged_mock, MOI.ConstraintName(), xc, "xc")
135+
MOI.set(bridged_mock, MOI.ConstraintName(), ec, "ec")
136+
@testset "Mock model" begin
137+
MOI.set(mock, MOI.VariableName(),
138+
MOI.get(mock, MOI.ListOfVariableIndices())[2], "z")
139+
MOI.set(mock, MOI.ConstraintName(),
140+
MOI.get(mock, MOI.ListOfConstraintIndices{
141+
MOI.VectorOfVariables, MOI.Nonpositives}()),
142+
["zc"])
143+
s = """
144+
variables: x, z
145+
zc: [z] in MathOptInterface.Nonpositives(1)
146+
xc: 2.0x <= 4.0
147+
ec: [x, 1.0, z + 5.0] in MathOptInterface.ExponentialCone()
148+
"""
149+
model = MOIU.Model{Float64}()
150+
MOIU.loadfromstring!(model, s)
151+
MOIU.test_models_equal(mock, model, ["x", "z"], ["zc", "xc", "ec"])
152+
end
153+
@testset "Bridged model" begin
154+
MOI.set(bridged_mock, MOI.VariableName(), y, "y")
155+
MOI.set(bridged_mock, MOI.ConstraintName(), yc, "yc")
156+
s = """
157+
variables: x, y
158+
yc: y <= 5.0
159+
xc: 2.0x <= 4.0
160+
ec: [x, 1.0, y] in MathOptInterface.ExponentialCone()
161+
"""
162+
model = MOIU.Model{Float64}()
163+
MOIU.loadfromstring!(model, s)
164+
MOIU.test_models_equal(bridged_mock, model, ["x", "y"], ["yc", "xc", "ec"])
165+
end
166+
167+
test_delete_bridged_variable(bridged_mock, y, MOI.LessThan{Float64}, 2, (
168+
(MOI.VectorOfVariables, MOI.Nonpositives, 0),
169+
))
170+
end

0 commit comments

Comments
 (0)