Skip to content

Commit 6a38088

Browse files
authored
Support MOI.Parameter and dont add useless duals from parameters (#185)
1 parent 8482c9f commit 6a38088

7 files changed

+353
-57
lines changed

src/constrained_variables.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,16 @@ function _select_constrained_variables(
8383
end
8484
return
8585
end
86+
87+
function _get_parameter_variables(::PrimalDualMap{T}, primal_model) where {T}
88+
cis = MOI.get(
89+
primal_model,
90+
MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Parameter{T}}(),
91+
)
92+
parameters = Set{MOI.VariableIndex}()
93+
for ci in cis
94+
vi = MOI.get(primal_model, MOI.ConstraintFunction(), ci)
95+
push!(parameters, vi)
96+
end
97+
return parameters
98+
end

src/dual_equality_constraints.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,15 @@ function _fill_scalar_affine_terms!(
410410
return
411411
end
412412

413+
function _fill_scalar_affine_terms!(
414+
::Dict{MOI.VariableIndex,Vector{MOI.ScalarAffineTerm{T}}},
415+
primal_constraint_data,
416+
::MOI.ModelLike,
417+
::MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}},
418+
) where {T}
419+
return
420+
end
421+
413422
function _fill_scalar_affine_terms!(
414423
scalar_affine_terms::Dict{
415424
MOI.VariableIndex,

src/dual_model_variables.jl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ function _add_dual_vars_in_dual_cones(
1010
primal_dual_map::PrimalDualMap{T},
1111
dual_names::DualNames,
1212
con_types::Vector{Tuple{Type,Type}},
13+
variable_parameters,
1314
) where {T}
1415
dual_obj_affine_terms = Dict{MOI.VariableIndex,T}()
16+
parameters_set = Set(variable_parameters)
1517
for (F, S) in con_types
1618
_add_dual_vars_in_dual_cones(
1719
dual_obj_affine_terms,
@@ -21,6 +23,7 @@ function _add_dual_vars_in_dual_cones(
2123
dual_names,
2224
F,
2325
S,
26+
parameters_set,
2427
)
2528
end
2629
return dual_obj_affine_terms
@@ -34,13 +37,22 @@ function _add_dual_vars_in_dual_cones(
3437
dual_names::DualNames,
3538
::Type{F},
3639
::Type{S},
40+
parameters_set,
3741
) where {T,F,S}
3842
for ci in MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}())
3943
# If `F` not one of these two, we can skip the `haskey` check.
40-
if haskey(primal_dual_map.primal_constrained_variables, ci)
41-
# primal constraints that are the main constraints of
42-
# constrained variables have no dual variable associated
43-
# bacause they associated with dual constraints
44+
if haskey(primal_dual_map.primal_constrained_variables, ci) ||
45+
# primal constraints that are the main constraints of
46+
# constrained variables have no dual variable associated
47+
# bacause they associated with dual constraints
48+
(
49+
F <: MOI.VariableIndex &&
50+
MOI.get(primal_model, MOI.ConstraintFunction(), ci) in
51+
parameters_set
52+
)
53+
# if a parameter is constrained, either because the set is
54+
# Parameter or because it was user defined parameter its constraint
55+
# will lead to a useless dual variable.
4456
continue
4557
end
4658
# Add dual variable to dual cone

src/dualize.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function dualize(
6969
primal_model::MOI.ModelLike,
7070
dual_problem::DualProblem{T},
7171
dual_names::DualNames,
72-
variable_parameters::Vector{MOI.VariableIndex},
72+
_variable_parameters::Vector{MOI.VariableIndex},
7373
ignore_objective::Bool,
7474
consider_constrained_variables::Bool,
7575
) where {T}
@@ -80,6 +80,15 @@ function dualize(
8080
con_types = MOI.get(primal_model, MOI.ListOfConstraintTypesPresent())
8181
supported_constraints(con_types) # Errors if constraint cant be dualized
8282

83+
# merge user listed parameters and MOI.VariableIndex-in-MOI.Parameter{T}
84+
moi_parameters =
85+
_get_parameter_variables(dual_problem.primal_dual_map, primal_model)
86+
all_parameters = Set{MOI.VariableIndex}(_variable_parameters)
87+
for p in moi_parameters
88+
push!(all_parameters, p)
89+
end
90+
variable_parameters = collect(all_parameters)
91+
8392
# Set the dual model objective sense
8493
_set_dual_model_sense(dual_problem.dual_model, primal_model)
8594

@@ -98,6 +107,8 @@ function dualize(
98107
# the respective primal variable will not be a constrained variable (with
99108
# respect to that constraint).
100109
# Constrained variables are registered in `primal_constrained_variables`.
110+
# Since MOI.VariableIndex-in-MOI.Parameter{T} are in the variable_parameters
111+
# list, they are not considered constrained variables, they are parameters.
101112
if consider_constrained_variables
102113
_select_constrained_variables(
103114
dual_problem,
@@ -132,6 +143,7 @@ function dualize(
132143
dual_problem.primal_dual_map,
133144
dual_names,
134145
con_types,
146+
variable_parameters,
135147
)
136148

137149
# Creates variables in the dual problem that represent parameters in the

src/supported.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ end
2828

2929
supported_constraint(::Type, ::Type) = false
3030

31+
function supported_constraint(
32+
::Type{MOI.VariableIndex},
33+
S::Type{MOI.Parameter{T}},
34+
) where {T}
35+
return true
36+
end
37+
3138
function supported_constraint(
3239
::Type{<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction}},
3340
S::Type{<:MOI.AbstractScalarSet},

test/Tests/test_partial_dual_linear.jl

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,13 @@
106106
s.t.
107107
2x1 + x2 - 3 <= 0 :y_2
108108
x1 + 2x2 - 3 <= 0 :y_3
109-
x1 >= 1 :y_1
109+
x1 >= 1 :no dual here
110110
x2 >= 0
111111
ignore x_1 during dualization
112112
dual
113113
obj ignored
114114
s.t.
115115
-y_2 - 2y_3 >= 3 :x_2
116-
y_1 >= 0
117116
y_2 <= 0
118117
y_3 <= 0
119118
=#
@@ -126,11 +125,10 @@
126125
dual_model = dual.dual_model
127126
primal_dual_map = dual.primal_dual_map
128127

129-
@test MOI.get(dual_model, MOI.NumberOfVariables()) == 3
128+
@test MOI.get(dual_model, MOI.NumberOfVariables()) == 2
130129
list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent())
131130
@test Set(list_of_cons) == Set(
132131
[
133-
(MOI.VariableIndex, MOI.GreaterThan{Float64})
134132
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})
135133
(MOI.VectorOfVariables, MOI.Nonpositives)
136134
],
@@ -141,7 +139,7 @@
141139
MOI.VariableIndex,
142140
MOI.GreaterThan{Float64},
143141
}(),
144-
) == 1
142+
) == 0
145143
@test MOI.get(
146144
dual_model,
147145
MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonpositives}(),
@@ -185,8 +183,7 @@
185183
MOI.GreaterThan{Float64},
186184
}(),
187185
)
188-
@test primal_constraint_data[vgt1].dual_variables ==
189-
[MOI.VariableIndex(3)]
186+
@test !haskey(primal_constraint_data, vgt1) # as this was not dualized
190187

191188
for (vi, data) in primal_dual_map.primal_variable_data
192189
vi = MOI.VariableIndex(2)
@@ -203,7 +200,7 @@
203200
min 4x_3 + 5
204201
s.t.
205202
x_1 + 2x_2 + x_3 <= 20 :y_3
206-
x_1 <= 1 :y_1
203+
x_1 <= 1 : # no dual here: y_1
207204
x_2 <= 3 :y_2
208205
ignoring x_1 and x_3
209206
dual
@@ -212,7 +209,7 @@
212209
# y_1 + y_3 == 0 :x_1
213210
y_2 + 2y_3 == 0 :x_2
214211
# y_3 == 4.0 :x_3
215-
y_1 <= 0
212+
# y_1 <= 0
216213
y_2 <= 0
217214
y_3 <= 0
218215
=#
@@ -228,7 +225,7 @@
228225
dual_model = dual.dual_model
229226
primal_dual_map = dual.primal_dual_map
230227

231-
@test MOI.get(dual_model, MOI.NumberOfVariables()) == 3
228+
@test MOI.get(dual_model, MOI.NumberOfVariables()) == 2
232229
list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent())
233230
@test Set(list_of_cons) == Set(
234231
[
@@ -239,7 +236,7 @@
239236
@test MOI.get(
240237
dual_model,
241238
MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(),
242-
) == 3
239+
) == 2
243240
@test MOI.get(
244241
dual_model,
245242
MOI.NumberOfConstraints{
@@ -278,13 +275,118 @@
278275
MOI.LessThan{Float64},
279276
}(
280277
2,
281-
)].dual_variables == [MOI.VariableIndex(3)]
278+
)].dual_variables == [MOI.VariableIndex(2)]
279+
@test !haskey(
280+
primal_constraint_data,
281+
MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}(1),
282+
)
282283
@test primal_constraint_data[MOI.ConstraintIndex{
283-
MOI.VariableIndex,
284+
MOI.ScalarAffineFunction{Float64},
284285
MOI.LessThan{Float64},
285286
}(
286287
1,
288+
)].dual_variables == [MOI.VariableIndex(1)]
289+
290+
primal_variable_data = primal_dual_map.primal_variable_data
291+
@test primal_variable_data[MOI.VariableIndex(2)].dual_constraint ==
292+
MOI.ConstraintIndex{
293+
MOI.ScalarAffineFunction{Float64},
294+
MOI.EqualTo{Float64},
295+
}(
296+
1,
297+
)
298+
end
299+
300+
@testset "lp12_test - x_1 ignored x_3 moi_param" begin
301+
#=
302+
primal
303+
min 4x_3 + 5
304+
s.t.
305+
x_1 + 2x_2 + x_3 <= 20 :y_3
306+
x_1 <= 1 : # no dual here: y_1
307+
x_2 <= 3 :y_2
308+
ignoring x_1 and x_3
309+
dual
310+
obj ignored
311+
s.t.
312+
# y_1 + y_3 == 0 :x_1
313+
y_2 + 2y_3 == 0 :x_2
314+
# y_3 == 4.0 :x_3
315+
# y_1 <= 0
316+
y_2 <= 0
317+
y_3 <= 0
318+
=#
319+
primal_model = lp12_test()
320+
MOI.add_constraint(
321+
primal_model,
322+
MOI.VariableIndex(3),
323+
MOI.Parameter{Float64}(0.0),
324+
)
325+
dual = Dualization.dualize(
326+
primal_model,
327+
variable_parameters = MOI.VariableIndex[MOI.VariableIndex(1),
328+
# MOI.VariableIndex(3), # as a param
329+
],
330+
ignore_objective = true,
331+
)
332+
dual_model = dual.dual_model
333+
primal_dual_map = dual.primal_dual_map
334+
335+
@test MOI.get(dual_model, MOI.NumberOfVariables()) == 2
336+
list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent())
337+
@test Set(list_of_cons) == Set(
338+
[
339+
(MOI.VariableIndex, MOI.LessThan{Float64})
340+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64})
341+
],
342+
)
343+
@test MOI.get(
344+
dual_model,
345+
MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(),
346+
) == 2
347+
@test MOI.get(
348+
dual_model,
349+
MOI.NumberOfConstraints{
350+
MOI.ScalarAffineFunction{Float64},
351+
MOI.EqualTo{Float64},
352+
}(),
353+
) == 1
354+
355+
eq_con2_fun = MOI.get(
356+
dual_model,
357+
MOI.ConstraintFunction(),
358+
MOI.ConstraintIndex{
359+
MOI.ScalarAffineFunction{Float64},
360+
MOI.EqualTo{Float64},
361+
}(
362+
1,
363+
),
364+
)
365+
eq_con2_set = MOI.get(
366+
dual_model,
367+
MOI.ConstraintSet(),
368+
MOI.ConstraintIndex{
369+
MOI.ScalarAffineFunction{Float64},
370+
MOI.EqualTo{Float64},
371+
}(
372+
1,
373+
),
374+
)
375+
@test MOI.coefficient.(eq_con2_fun.terms) == [2.0; 1.0]
376+
@test MOI.constant.(eq_con2_fun) == 0.0
377+
@test MOI.constant(eq_con2_set) == 0.0
378+
379+
primal_constraint_data = primal_dual_map.primal_constraint_data
380+
@test primal_constraint_data[MOI.ConstraintIndex{
381+
MOI.VariableIndex,
382+
MOI.LessThan{Float64},
383+
}(
384+
2,
287385
)].dual_variables == [MOI.VariableIndex(2)]
386+
@test !haskey(
387+
primal_constraint_data,
388+
MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{Float64}}(1),
389+
)
288390
@test primal_constraint_data[MOI.ConstraintIndex{
289391
MOI.ScalarAffineFunction{Float64},
290392
MOI.LessThan{Float64},

0 commit comments

Comments
 (0)