Skip to content

Commit 9d0731c

Browse files
committed
Add model tests and create only nonnegative variables
1 parent 18147ef commit 9d0731c

File tree

2 files changed

+123
-56
lines changed

2 files changed

+123
-56
lines changed

src/Bridges/Variable/free.jl

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,38 @@
11
"""
22
FreeBridge{T} <: Bridges.Variable.AbstractBridge
33
4-
Transforms constrained variables in [`MathOptInterface.Reals`](@ref)
5-
to the sum of constrained variables in [`MathOptInterface.Nonnegatives`](@ref)
6-
and constrained variables in [`MathOptInterface.Nonpositives`](@ref).
4+
Transforms constrained variables in [`MOI.Reals`](@ref) to the difference of
5+
constrained variables in [`MOI.Nonnegatives`](@ref).
76
"""
87
struct FreeBridge{T} <: AbstractBridge
9-
nonneg_variables::Vector{MOI.VariableIndex}
10-
nonneg_constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Nonnegatives}
11-
nonpos_variables::Vector{MOI.VariableIndex}
12-
nonpos_constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Nonpositives}
8+
variables::Vector{MOI.VariableIndex}
9+
constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.Nonnegatives}
1310
end
1411
function bridge_constrained_variable(::Type{FreeBridge{T}},
1512
model::MOI.ModelLike,
1613
set::MOI.Reals) where T
17-
nonneg_variables, nonneg_constraint = MOI.add_constrained_variables(
18-
model, MOI.Nonnegatives(MOI.dimension(set)))
19-
nonpos_variables, nonpos_constraint = MOI.add_constrained_variables(
20-
model, MOI.Nonpositives(MOI.dimension(set)))
21-
return FreeBridge{T}(nonneg_variables, nonneg_constraint,
22-
nonpos_variables, nonpos_constraint)
14+
variables, constraint = MOI.add_constrained_variables(
15+
model, MOI.Nonnegatives(2MOI.dimension(set)))
16+
return FreeBridge{T}(variables, constraint)
2317
end
2418

2519
function supports_constrained_variable(
2620
::Type{<:FreeBridge}, ::Type{MOI.Reals})
2721
return true
2822
end
2923
function MOIB.added_constrained_variable_types(::Type{<:FreeBridge})
30-
return [(MOI.Nonnegatives,), (MOI.Nonpositives,)]
24+
return [(MOI.Nonnegatives,)]
3125
end
3226
function MOIB.added_constraint_types(::Type{FreeBridge{T}}) where T
3327
return Tuple{DataType, DataType}[]
3428
end
3529

3630
# Attributes, Bridge acting as a model
3731
function MOI.get(bridge::FreeBridge, ::MOI.NumberOfVariables)
38-
return length(bridge.nonneg_variables) + length(bridge.nonpos_variables)
32+
return length(bridge.variables)
3933
end
4034
function MOI.get(bridge::FreeBridge, ::MOI.ListOfVariableIndices)
41-
return vcat(bridge.nonneg_variables, bridge.nonpos_variables)
35+
return vcat(bridge.variables)
4236
end
4337
function MOI.get(bridge::FreeBridge,
4438
::MOI.NumberOfConstraints{MOI.VectorOfVariables,
@@ -48,80 +42,76 @@ end
4842
function MOI.get(bridge::FreeBridge,
4943
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
5044
MOI.Nonnegatives})
51-
return [bridge.nonneg_constraint]
52-
end
53-
function MOI.get(bridge::FreeBridge,
54-
::MOI.NumberOfConstraints{MOI.VectorOfVariables,
55-
MOI.Nonpositives})
56-
return 1
57-
end
58-
function MOI.get(bridge::FreeBridge,
59-
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
60-
MOI.Nonpositives})
61-
return [bridge.nonpos_constraint]
45+
return [bridge.constraint]
6246
end
6347

6448
# References
6549
function MOI.delete(model::MOI.ModelLike, bridge::FreeBridge)
66-
MOI.delete(model, bridge.nonneg_variables)
67-
MOI.delete(model, bridge.nonpos_variables)
50+
MOI.delete(model, bridge.variables)
6851
end
6952

7053
function MOI.delete(model::MOI.ModelLike, bridge::FreeBridge, i::IndexInVector)
71-
MOI.delete(model, bridge.nonneg_variables[i.value])
72-
deleteat!(bridge.nonneg_variables, i.value)
73-
MOI.delete(model, bridge.nonpos_variables[i.value])
74-
deleteat!(bridge.nonpos_variables, i.value)
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])
7558
end
7659

7760

7861
# Attributes, Bridge acting as a constraint
7962

8063
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
8164
bridge::FreeBridge{T}) where T
82-
return MOI.get(model, attr, bridge.nonneg_constraint) +
83-
MOI.get(model, attr, bridge.nonpos_constraint)
65+
n = div(length(bridge.variables), 2)
66+
primal = MOI.get(model, attr, bridge.constraint)
67+
return primal[1:n] - primal[n .+ (1:n)]
8468
end
85-
# The transformation is x_free = [I I] * [x_nonneg; x_nonpos]
69+
# The transformation is x_free = [I -I] * x
8670
# so the transformation of the dual is
87-
# [y_nonneg; y_nonpos] = [I; I] * y_free
71+
# y = [I; -I] * y_free
8872
# that is
89-
# y_nonneg = y_nonpos = y_free
90-
# We can therefore take either of them, let's take y_nonneg.
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]`.
9175
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
9276
bridge::FreeBridge{T}) where T
93-
return MOI.get(model, attr, bridge.nonneg_constraint)
77+
n = div(length(bridge.variables), 2)
78+
return MOI.get(model, attr, bridge.constraint)[1:n]
9479
end
9580

9681
function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
9782
bridge::FreeBridge{T}, i::IndexInVector) where T
98-
return MOI.get(model, attr, bridge.nonneg_variables[i.value]) +
99-
MOI.get(model, attr, bridge.nonpos_variables[i.value])
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])
10086
end
10187

10288
function MOIB.bridged_function(bridge::FreeBridge{T}, i::IndexInVector) where T
103-
return MOIU.operate(+, T, MOI.SingleVariable(bridge.nonneg_variables[i.value]),
104-
MOI.SingleVariable(bridge.nonpos_variables[i.value]))
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]))
10592
end
106-
# x_free has been replaced by x_nonneg + x_nonpos.
107-
# To undo it we replace x_nonneg by x_free and x_nonpos by 0.
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.
10895
function unbridged_map(bridge::FreeBridge{T}, vi::MOI.VariableIndex,
10996
i::IndexInVector) where T
11097
sv = MOI.SingleVariable(vi)
98+
# `unbridged_map` is required to return a `MOI.ScalarAffineFunction`.
11199
func = convert(MOI.ScalarAffineFunction{T}, sv)
112-
return bridge.nonneg_variables[i.value] => func,
113-
bridge.nonpos_variables[i.value] => zero(MOI.ScalarAffineFunction{T})
100+
n = div(length(bridge.variables), 2)
101+
return bridge.variables[i.value] => func,
102+
bridge.variables[n + i.value] => zero(MOI.ScalarAffineFunction{T})
114103
end
115104

116105
function MOI.set(model::MOI.ModelLike, attr::MOI.VariablePrimalStart,
117106
bridge::FreeBridge, value, i::IndexInVector)
118107
if value < 0
119108
nonneg = zero(value)
120-
nonpos = value
109+
nonpos = -value
121110
else
122111
nonneg = value
123112
nonpos = zero(value)
124113
end
125-
MOI.set(model, attr, bridge.nonneg_variables[i.value], nonneg)
126-
MOI.set(model, attr, bridge.nonpos_variables[i.value], nonpos)
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)
127117
end

test/Bridges/Variable/free.jl

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,47 @@ bridged_mock = MOIB.Variable.Free{Float64}(mock)
2323
)
2424
)
2525
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
2660
end
2761

2862
@testset "Linear6" begin
2963
MOIU.set_mock_optimize!(mock,
3064
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]),
3165
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 0]),
32-
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, -100],
66+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 100],
3367
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0],
3468
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0]
3569
))
@@ -67,7 +101,7 @@ end
67101
MOIU.set_mock_optimize!(mock,
68102
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]),
69103
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 0]),
70-
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, -100],
104+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 0, 100],
71105
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[1.0]],
72106
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-1.0]]
73107
))
@@ -87,18 +121,61 @@ end
87121
@test MOI.supports(bridged_mock, MOI.VariablePrimalStart(), MOI.VariableIndex)
88122
MOI.set(bridged_mock, MOI.VariablePrimalStart(), [x, y], [1.0, -1.0])
89123
xa, xb, ya, yb = MOI.get(mock, MOI.ListOfVariableIndices())
90-
@test MOI.get(mock, MOI.VariablePrimalStart(), [xa, xb, ya, yb]) == [1.0, 0.0, 0.0, -1.0]
124+
@test MOI.get(mock, MOI.VariablePrimalStart(), [xa, xb, ya, yb]) == [1.0, 0.0, 0.0, 1.0]
91125
end
92126

93127
@testset "Linear11" begin
94128
MOIU.set_mock_optimize!(mock,
95-
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 1.0, 0.0]),
96-
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.0, 0.5, 0.0]))
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]))
97131
MOIT.linear11test(bridged_mock, config)
98132

99133
vis = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
100134
@test vis == MOI.VariableIndex.([-1, -2])
101135

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+
102179
test_delete_bridged_variable(bridged_mock, vis[1], MOI.Reals, 2, (
103180
(MOI.VectorOfVariables, MOI.Nonnegatives, 0),
104181
(MOI.VectorOfVariables, MOI.Nonpositives, 0)

0 commit comments

Comments
 (0)