Skip to content

Commit 91fbd57

Browse files
guilhermebodinjoaquimgodow
authored
Remove error from feasibility sense (#162)
* Remove error from feasibility sense * add kwarg * fix typo * fix test * Update src/objective_coefficients.jl Co-authored-by: Oscar Dowson <odow@users.noreply.github.com> * add flag to dual optimizer --------- Co-authored-by: joaquimg <joaquimdgarcia@gmail.com> Co-authored-by: Oscar Dowson <odow@users.noreply.github.com>
1 parent 6a38088 commit 91fbd57

7 files changed

+132
-19
lines changed

src/MOI_wrapper.jl

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ export DualOptimizer, dual_optimizer
88
function dual_optimizer(
99
optimizer_constructor;
1010
coefficient_type::Type{T} = Float64,
11+
kwargs...,
1112
) where {T<:Number}
12-
return () -> DualOptimizer{T}(MOI.instantiate(optimizer_constructor))
13+
return () ->
14+
DualOptimizer{T}(MOI.instantiate(optimizer_constructor), kwargs...)
1315
end
1416

1517
struct DualOptimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer
1618
dual_problem::DualProblem{T,OT}
19+
assume_min_if_feasibility::Bool
1720

1821
function DualOptimizer{T,OT}(
19-
dual_problem::DualProblem{T,OT},
22+
dual_problem::DualProblem{T,OT};
23+
assume_min_if_feasibility::Bool = false,
2024
) where {T,OT<:MOI.ModelLike}
21-
return new{T,OT}(dual_problem)
25+
return new{T,OT}(dual_problem, assume_min_if_feasibility)
2226
end
2327
end
2428

@@ -50,11 +54,14 @@ CachingOptimizer state: EMPTY_OPTIMIZER
5054
Solver name: Dual model with HiGHS attached
5155
```
5256
"""
53-
function DualOptimizer(dual_optimizer::OT) where {OT<:MOI.ModelLike}
54-
return DualOptimizer{Float64}(dual_optimizer)
57+
function DualOptimizer(dual_optimizer::OT; kwargs...) where {OT<:MOI.ModelLike}
58+
return DualOptimizer{Float64}(dual_optimizer, kwargs...)
5559
end
5660

57-
function DualOptimizer{T}(dual_optimizer::OT) where {T,OT<:MOI.ModelLike}
61+
function DualOptimizer{T}(
62+
dual_optimizer::OT;
63+
kwargs...,
64+
) where {T,OT<:MOI.ModelLike}
5865
dual_problem = DualProblem{T}(
5966
MOI.Bridges.full_bridge_optimizer(
6067
MOI.Utilities.CachingOptimizer(
@@ -67,7 +74,7 @@ function DualOptimizer{T}(dual_optimizer::OT) where {T,OT<:MOI.ModelLike}
6774
# discover the type of
6875
# MOI.Utilities.CachingOptimizer(DualizableModel{T}(), dual_optimizer)
6976
OptimizerType = typeof(dual_problem.dual_model)
70-
return DualOptimizer{T,OptimizerType}(dual_problem)
77+
return DualOptimizer{T,OptimizerType}(dual_problem, kwargs...)
7178
end
7279

7380
DualOptimizer() = error("DualOptimizer must have a solver attached")
@@ -205,7 +212,11 @@ function MOI.supports_add_constrained_variables(
205212
end
206213

207214
function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike)
208-
dualize(src, dest.dual_problem)
215+
dualize(
216+
src,
217+
dest.dual_problem,
218+
assume_min_if_feasibility = dest.assume_min_if_feasibility,
219+
)
209220
idx_map = MOI.Utilities.IndexMap()
210221
for vi in MOI.get(src, MOI.ListOfVariableIndices())
211222
setindex!(idx_map, vi, vi)

src/dualize.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ On each of these methods, the user can provide the following keyword arguments:
4444
* `ignore_objective`: a boolean indicating if the objective function should be
4545
added to the dual model. This is also useful for bi-level modelling, where
4646
the second level model is represented as a KKT in the upper level model.
47+
48+
* `assume_min_if_feasibility`: a boolean indicating if the objective function
49+
is of type `MOI.FEASIBILITY_SENSE` then the objective is treated as
50+
`MOI.MIN_SENSE`. Therefore, the dual will have a `MOI.MAX_SENSE` objective.
51+
This is set to false by default, to warn users about the outcome.
4752
"""
4853
function dualize end
4954

@@ -54,6 +59,7 @@ function dualize(
5459
variable_parameters::Vector{MOI.VariableIndex} = MOI.VariableIndex[],
5560
ignore_objective::Bool = false,
5661
consider_constrained_variables::Bool = true,
62+
assume_min_if_feasibility::Bool = false,
5763
)
5864
return dualize(
5965
primal_model,
@@ -62,6 +68,7 @@ function dualize(
6268
variable_parameters,
6369
ignore_objective,
6470
consider_constrained_variables,
71+
assume_min_if_feasibility,
6572
)
6673
end
6774

@@ -72,6 +79,7 @@ function dualize(
7279
_variable_parameters::Vector{MOI.VariableIndex},
7380
ignore_objective::Bool,
7481
consider_constrained_variables::Bool,
82+
assume_min_if_feasibility::Bool = false,
7583
) where {T}
7684
# Throws an error if objective function cannot be dualized
7785
supported_objective(primal_model)
@@ -90,7 +98,11 @@ function dualize(
9098
variable_parameters = collect(all_parameters)
9199

92100
# Set the dual model objective sense
93-
_set_dual_model_sense(dual_problem.dual_model, primal_model)
101+
_set_dual_model_sense(
102+
dual_problem.dual_model,
103+
primal_model,
104+
assume_min_if_feasibility,
105+
)
94106

95107
# Get primal objective in quadratic form
96108
# terms already split considering parameters

src/objective_coefficients.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Set the dual model objective sense.
1111
function _set_dual_model_sense(
1212
dual_model::MOI.ModelLike,
1313
primal_model::MOI.ModelLike,
14+
assume_min_if_feasibility::Bool,
1415
)::Nothing
1516
# Get model sense
1617
primal_sense = MOI.get(primal_model, MOI.ObjectiveSense())
@@ -19,8 +20,19 @@ function _set_dual_model_sense(
1920
MOI.MAX_SENSE
2021
elseif primal_sense == MOI.MAX_SENSE
2122
MOI.MIN_SENSE
22-
else # primal_sense == MOI.FEASIBILITY_SENSE
23-
error(primal_sense, " is not supported")
23+
elseif primal_sense == MOI.FEASIBILITY_SENSE && assume_min_if_feasibility
24+
# We assume fesibility sense is a Min 0
25+
# so the dual would be Max ...
26+
MOI.MAX_SENSE
27+
else
28+
error(
29+
"Expected objective sense to be either MIN_SENSE or MAX_SENSE, " *
30+
"got FEASIBILITY_SENSE. It is not possible to decide how to " *
31+
"dualize. Set the sense to either MIN_SENSE or MAX_SENSE to " *
32+
"proceed. Alternatively, set the keyword argument " *
33+
"`assume_min_if_feasibility` to true to assume the dual model " *
34+
"is a minimization problem without setting the sense.",
35+
)
2436
end
2537
MOI.set(dual_model, MOI.ObjectiveSense(), dual_sense)
2638
return
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
function feasibility_1_test()
2+
#=
3+
min 0
4+
s.t.
5+
x_1 + 2x_2 <= 3
6+
x_1 >= 3
7+
=#
8+
model = TestModel{Float64}()
9+
10+
X = MOI.add_variables(model, 2)
11+
12+
MOI.add_constraint(
13+
model,
14+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 2.0], X), 0.0),
15+
MOI.LessThan(3.0),
16+
)
17+
18+
MOI.add_constraint(model, X[1], MOI.GreaterThan(3.0))
19+
20+
return model
21+
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
@testset "linear problems" begin
2+
@testset "feasibility_1_test" begin
3+
#=
4+
primal
5+
min 0
6+
s.t.
7+
x_1 >= 3 :y_2
8+
x_1 + 2x_2 <= 3 :y_3
9+
dual
10+
max 3y_2 + 3y_3
11+
s.t.
12+
y_2 >= 0
13+
y_3 <= 0
14+
y_2 + y_3 == 0 :x_1
15+
2y_3 == 0 :x_2
16+
=#
17+
primal_model = feasibility_1_test()
18+
19+
# fail due no objective sense
20+
@test_throws ErrorException Dualization.dualize(primal_model)
21+
22+
dual =
23+
Dualization.dualize(primal_model, assume_min_if_feasibility = true)
24+
dual_model = dual.dual_model
25+
primal_dual_map = dual.primal_dual_map
26+
27+
@test MOI.get(dual_model, MOI.NumberOfVariables()) == 2
28+
list_of_cons = MOI.get(dual_model, MOI.ListOfConstraintTypesPresent())
29+
@test Set(list_of_cons) == Set(
30+
[
31+
(MOI.VariableIndex, MOI.GreaterThan{Float64})
32+
(MOI.VariableIndex, MOI.LessThan{Float64})
33+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64})
34+
],
35+
)
36+
@test MOI.get(
37+
dual_model,
38+
MOI.NumberOfConstraints{
39+
MOI.VariableIndex,
40+
MOI.GreaterThan{Float64},
41+
}(),
42+
) == 1
43+
@test MOI.get(
44+
dual_model,
45+
MOI.NumberOfConstraints{MOI.VariableIndex,MOI.LessThan{Float64}}(),
46+
) == 1
47+
@test MOI.get(
48+
dual_model,
49+
MOI.NumberOfConstraints{
50+
MOI.ScalarAffineFunction{Float64},
51+
MOI.EqualTo{Float64},
52+
}(),
53+
) == 2
54+
obj_type = MOI.get(dual_model, MOI.ObjectiveFunctionType())
55+
@test obj_type == MOI.ScalarAffineFunction{Float64}
56+
obj = MOI.get(dual_model, MOI.ObjectiveFunction{obj_type}())
57+
@test MOI.constant(obj) == 0.0
58+
@test MOI.coefficient.(obj.terms) == [3.0; 3.0]
59+
end
60+
end

test/Tests/test_objective_coefficients.jl

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,15 @@
44
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
55

66
@testset "objective_coefficients.jl" begin
7-
@testset "_set_dual_model_sense" begin
8-
# ERROR: FEASIBILITY_SENSE is not supported
9-
# @test_throws ErrorException Dualization._set_dual_model_sense(lp11_test(), lp11_test())
10-
# obj = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(0.0, MOI.VariableIndex(1))], 0.0)
11-
# @test_throws ErrorException Dualization._PrimalObjective{Float64}(obj)
12-
7+
@testset "set_dual_model_sense" begin
138
model = lp1_test()
149
@test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE
15-
Dualization._set_dual_model_sense(model, model)
10+
Dualization._set_dual_model_sense(model, model, false)
1611
@test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
1712

1813
model = lp4_test()
1914
@test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
20-
Dualization._set_dual_model_sense(model, model)
15+
Dualization._set_dual_model_sense(model, model, false)
2116
@test MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE
2217
end
2318
end

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ include("Problems/RSOC/rsoc_problems.jl")
5454
include("Problems/SDP/sdp_triangle_problems.jl")
5555
include("Problems/Exponential/exponential_cone_problems.jl")
5656
include("Problems/Power/power_cone_problems.jl")
57+
include("Problems/Feasibility/feasibility_problems.jl")
5758

5859
# Run tests to travis ci
5960
include("Tests/test_structures.jl")
@@ -63,6 +64,7 @@ include("Tests/test_dual_model_variables.jl")
6364
include("Tests/test_dual_sets.jl")
6465
include("Tests/test_dualize_conic_linear.jl")
6566
include("Tests/test_dualize_linear.jl")
67+
include("Tests/test_dualize_feasibility.jl")
6668
include("Tests/test_dualize_soc.jl")
6769
include("Tests/test_dualize_rsoc.jl")
6870
include("Tests/test_dualize_sdp.jl")

0 commit comments

Comments
 (0)