Skip to content

Commit 86335c5

Browse files
authored
Merge pull request #824 from JuliaOpt/bl/free_bridge
Add Variable.FreeBridge
2 parents a1db9f4 + 9d0731c commit 86335c5

File tree

4 files changed

+309
-0
lines changed

4 files changed

+309
-0
lines changed

src/Bridges/Variable/Variable.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ include("single_bridge_optimizer.jl")
1717
# Variable bridges
1818
include("zeros.jl")
1919
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}
20+
include("free.jl")
21+
const Free{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{FreeBridge{T}, OT}
2022
include("flip_sign.jl")
2123
const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT}
2224
include("vectorize.jl")

src/Bridges/Variable/free.jl

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
FreeBridge{T} <: Bridges.Variable.AbstractBridge
3+
4+
Transforms constrained variables in [`MOI.Reals`](@ref) to the difference of
5+
constrained variables in [`MOI.Nonnegatives`](@ref).
6+
"""
7+
struct FreeBridge{T} <: AbstractBridge
8+
variables::Vector{MOI.VariableIndex}
9+
constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Nonnegatives}
10+
end
11+
function bridge_constrained_variable(::Type{FreeBridge{T}},
12+
model::MOI.ModelLike,
13+
set::MOI.Reals) where T
14+
variables, constraint = MOI.add_constrained_variables(
15+
model, MOI.Nonnegatives(2MOI.dimension(set)))
16+
return FreeBridge{T}(variables, constraint)
17+
end
18+
19+
function supports_constrained_variable(
20+
::Type{<:FreeBridge}, ::Type{MOI.Reals})
21+
return true
22+
end
23+
function MOIB.added_constrained_variable_types(::Type{<:FreeBridge})
24+
return [(MOI.Nonnegatives,)]
25+
end
26+
function MOIB.added_constraint_types(::Type{FreeBridge{T}}) where T
27+
return Tuple{DataType, DataType}[]
28+
end
29+
30+
# Attributes, Bridge acting as a model
31+
function MOI.get(bridge::FreeBridge, ::MOI.NumberOfVariables)
32+
return length(bridge.variables)
33+
end
34+
function MOI.get(bridge::FreeBridge, ::MOI.ListOfVariableIndices)
35+
return vcat(bridge.variables)
36+
end
37+
function MOI.get(bridge::FreeBridge,
38+
::MOI.NumberOfConstraints{MOI.VectorOfVariables,
39+
MOI.Nonnegatives})
40+
return 1
41+
end
42+
function MOI.get(bridge::FreeBridge,
43+
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
44+
MOI.Nonnegatives})
45+
return [bridge.constraint]
46+
end
47+
48+
# References
49+
function MOI.delete(model::MOI.ModelLike, bridge::FreeBridge)
50+
MOI.delete(model, bridge.variables)
51+
end
52+
53+
function MOI.delete(model::MOI.ModelLike, bridge::FreeBridge, i::IndexInVector)
54+
n = div(length(bridge.variables), 2)
55+
MOI.delete(model, bridge.variables[i.value])
56+
MOI.delete(model, bridge.variables[n + i.value])
57+
deleteat!(bridge.variables, [i.value, n + i.value])
58+
end
59+
60+
61+
# Attributes, Bridge acting as a constraint
62+
63+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
64+
bridge::FreeBridge{T}) where T
65+
n = div(length(bridge.variables), 2)
66+
primal = MOI.get(model, attr, bridge.constraint)
67+
return primal[1:n] - primal[n .+ (1:n)]
68+
end
69+
# The transformation is x_free = [I -I] * x
70+
# so the transformation of the dual is
71+
# y = [I; -I] * y_free
72+
# that is
73+
# y[1:n] = -y[n .+ (1:n)] = y_free
74+
# We can therefore compute `y_free` from either of them, let's take `y[1:n]`.
75+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
76+
bridge::FreeBridge{T}) where T
77+
n = div(length(bridge.variables), 2)
78+
return MOI.get(model, attr, bridge.constraint)[1:n]
79+
end
80+
81+
function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
82+
bridge::FreeBridge{T}, i::IndexInVector) where T
83+
n = div(length(bridge.variables), 2)
84+
return MOI.get(model, attr, bridge.variables[i.value]) -
85+
MOI.get(model, attr, bridge.variables[n + i.value])
86+
end
87+
88+
function MOIB.bridged_function(bridge::FreeBridge{T}, i::IndexInVector) where T
89+
n = div(length(bridge.variables), 2)
90+
return MOIU.operate(-, T, MOI.SingleVariable(bridge.variables[i.value]),
91+
MOI.SingleVariable(bridge.variables[n + i.value]))
92+
end
93+
# x_free has been replaced by x[i] - x[n + i].
94+
# To undo it we replace x[i] by x_free and x[n + i] by 0.
95+
function unbridged_map(bridge::FreeBridge{T}, vi::MOI.VariableIndex,
96+
i::IndexInVector) where T
97+
sv = MOI.SingleVariable(vi)
98+
# `unbridged_map` is required to return a `MOI.ScalarAffineFunction`.
99+
func = convert(MOI.ScalarAffineFunction{T}, sv)
100+
n = div(length(bridge.variables), 2)
101+
return bridge.variables[i.value] => func,
102+
bridge.variables[n + i.value] => zero(MOI.ScalarAffineFunction{T})
103+
end
104+
105+
function MOI.set(model::MOI.ModelLike, attr::MOI.VariablePrimalStart,
106+
bridge::FreeBridge, value, i::IndexInVector)
107+
if value < 0
108+
nonneg = zero(value)
109+
nonpos = -value
110+
else
111+
nonneg = value
112+
nonpos = zero(value)
113+
end
114+
n = div(length(bridge.variables), 2)
115+
MOI.set(model, attr, bridge.variables[i.value], nonneg)
116+
MOI.set(model, attr, bridge.variables[n + i.value], nonpos)
117+
end

test/Bridges/Variable/Variable.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ end
55
@testset "Zeros" begin
66
include("zeros.jl")
77
end
8+
@testset "Free" begin
9+
include("free.jl")
10+
end
811
@testset "FlipSign" begin
912
include("flip_sign.jl")
1013
end

test/Bridges/Variable/free.jl

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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.UniversalFallback(MOIU.Model{Float64}()))
12+
config = MOIT.TestConfig()
13+
14+
bridged_mock = MOIB.Variable.Free{Float64}(mock)
15+
16+
@testset "solve_multirow_vectoraffine_nonpos" begin
17+
MOIU.set_mock_optimize!(mock,
18+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
19+
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.5, 0.0])
20+
),
21+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
22+
MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.25, 0.0])
23+
)
24+
)
25+
MOIT.solve_multirow_vectoraffine_nonpos(bridged_mock, config)
26+
27+
MOI.set(bridged_mock, MOI.ConstraintName(),
28+
MOI.get(mock, MOI.ListOfConstraintIndices{
29+
MOI.VectorAffineFunction{Float64}, MOI.Nonpositives}()),
30+
["c"])
31+
32+
@testset "Mock model" begin
33+
var_names = ["xpos", "xneg"]
34+
MOI.set(mock, MOI.VariableName(),
35+
MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
36+
MOI.set(bridged_mock, MOI.ConstraintName(),
37+
MOI.get(mock, MOI.ListOfConstraintIndices{
38+
MOI.VectorOfVariables, MOI.Nonnegatives}()),
39+
["nonneg"])
40+
s = """
41+
variables: xpos, xneg
42+
nonneg: [xpos, xneg] in MathOptInterface.Nonnegatives(2)
43+
c: [4.0xpos + -4.0xneg + -1.0, 3.0xpos + -3.0xneg + -1.0] in MathOptInterface.Nonpositives(2)
44+
maxobjective: xpos + -1.0xneg
45+
"""
46+
model = MOIU.Model{Float64}()
47+
MOIU.loadfromstring!(model, s)
48+
MOIU.test_models_equal(mock, model, var_names, ["nonneg", "c"])
49+
end
50+
@testset "Bridged model" begin
51+
s = """
52+
variables: x
53+
c: [4.0x + -1.0, 3.0x + -1.0] in MathOptInterface.Nonpositives(2)
54+
maxobjective: 1.0x
55+
"""
56+
model = MOIU.Model{Float64}()
57+
MOIU.loadfromstring!(model, s)
58+
MOIU.test_models_equal(bridged_mock, model, ["x"], ["c"])
59+
end
60+
end
61+
62+
@testset "Linear6" begin
63+
MOIU.set_mock_optimize!(mock,
64+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]),
65+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 0]),
66+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 100],
67+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0],
68+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0]
69+
))
70+
MOIT.linear6test(bridged_mock, config)
71+
72+
loc = MOI.get(bridged_mock, MOI.ListOfConstraints())
73+
@test length(loc) == 2
74+
@test !((MOI.VectorOfVariables, MOI.Reals) in loc)
75+
@test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in loc
76+
@test (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) in loc
77+
@test MOI.get(mock, MOI.NumberOfVariables()) == 4
78+
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 2
79+
vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
80+
@test vis == MOI.VariableIndex.([-1, -2])
81+
82+
cx = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(vis[1].value)
83+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cx) == [100.0]
84+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cx) == [0.0]
85+
cy = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(vis[2].value)
86+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cy) == [-100.0]
87+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cy) == [0.0]
88+
89+
test_delete_bridged_variable(bridged_mock, vis[1], MOI.Reals, 2, (
90+
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
91+
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
92+
))
93+
test_delete_bridged_variable(bridged_mock, vis[2], MOI.Reals, 1, (
94+
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
95+
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
96+
))
97+
end
98+
99+
@testset "Linear7" begin
100+
function set_mock_optimize_linear7Test!(mock)
101+
MOIU.set_mock_optimize!(mock,
102+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]),
103+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 0]),
104+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 100],
105+
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[1.0]],
106+
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-1.0]]
107+
))
108+
end
109+
set_mock_optimize_linear7Test!(mock)
110+
MOIT.linear7test(bridged_mock, config)
111+
112+
x, y = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
113+
114+
cx = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(x.value)
115+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cx) == [100.0]
116+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cx) == [0.0]
117+
cy = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Reals}(y.value)
118+
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), cy) == [-100.0]
119+
@test MOI.get(bridged_mock, MOI.ConstraintDual(), cy) == [0.0]
120+
121+
@test MOI.supports(bridged_mock, MOI.VariablePrimalStart(), MOI.VariableIndex)
122+
MOI.set(bridged_mock, MOI.VariablePrimalStart(), [x, y], [1.0, -1.0])
123+
xa, xb, ya, yb = MOI.get(mock, MOI.ListOfVariableIndices())
124+
@test MOI.get(mock, MOI.VariablePrimalStart(), [xa, xb, ya, yb]) == [1.0, 0.0, 0.0, 1.0]
125+
end
126+
127+
@testset "Linear11" begin
128+
MOIU.set_mock_optimize!(mock,
129+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 0.0, 0.0]),
130+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.5, 0.0, 0.0]))
131+
MOIT.linear11test(bridged_mock, config)
132+
133+
vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
134+
@test vis == MOI.VariableIndex.([-1, -2])
135+
136+
MOI.set(bridged_mock, MOI.ConstraintName(),
137+
MOI.get(mock, MOI.ListOfConstraintIndices{
138+
MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()),
139+
["c1"])
140+
MOI.set(bridged_mock, MOI.ConstraintName(),
141+
MOI.get(mock, MOI.ListOfConstraintIndices{
142+
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}()),
143+
["c2"])
144+
145+
@testset "Mock model" begin
146+
var_names = ["v1pos", "v2pos", "v1neg", "v2neg"]
147+
MOI.set(mock, MOI.VariableName(),
148+
MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
149+
MOI.set(bridged_mock, MOI.ConstraintName(),
150+
MOI.get(mock, MOI.ListOfConstraintIndices{
151+
MOI.VectorOfVariables, MOI.Nonnegatives}()),
152+
["nonneg"])
153+
s = """
154+
variables: v1pos, v2pos, v1neg, v2neg
155+
nonneg: [v1pos, v2pos, v1neg, v2neg] in MathOptInterface.Nonnegatives(4)
156+
c1: v1pos + -1.0v1neg + v2pos + -1.0v2neg >= 1.0
157+
c2: v1pos + -1.0v1neg + v2pos + -1.0v2neg <= 2.0
158+
minobjective: v1pos + -1.0v1neg + v2pos + -1.0v2neg
159+
"""
160+
model = MOIU.Model{Float64}()
161+
MOIU.loadfromstring!(model, s)
162+
MOIU.test_models_equal(mock, model, var_names, ["nonneg", "c1", "c2"])
163+
end
164+
@testset "Bridged model" begin
165+
var_names = ["v1", "v2"]
166+
MOI.set(bridged_mock, MOI.VariableName(),
167+
vis, var_names)
168+
s = """
169+
variables: v1, v2
170+
c1: v1 + v2 >= 1.0
171+
c2: v1 + v2 <= 2.0
172+
minobjective: v1 + v2
173+
"""
174+
model = MOIU.Model{Float64}()
175+
MOIU.loadfromstring!(model, s)
176+
MOIU.test_models_equal(bridged_mock, model, var_names, ["c1", "c2"])
177+
end
178+
179+
test_delete_bridged_variable(bridged_mock, vis[1], MOI.Reals, 2, (
180+
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
181+
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
182+
), used_bridges = 0, used_constraints = 0)
183+
test_delete_bridged_variable(bridged_mock, vis[2], MOI.Reals, 1, (
184+
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
185+
(MOI.VectorOfVariables, MOI.Nonpositives, 0)
186+
))
187+
end

0 commit comments

Comments
 (0)