Skip to content

Commit 0001bcd

Browse files
authored
[Utilities] simplify _dual_objective_value (#2826)
1 parent 4a2641b commit 0001bcd

File tree

3 files changed

+170
-108
lines changed

3 files changed

+170
-108
lines changed

src/Utilities/results.jl

Lines changed: 66 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -52,112 +52,6 @@ end
5252

5353
# MOI.DualObjectiveValue
5454

55-
function _constraint_constant(
56-
model::MOI.ModelLike,
57-
ci::MOI.ConstraintIndex{
58-
<:MOI.AbstractVectorFunction,
59-
<:MOI.AbstractVectorSet,
60-
},
61-
::Type{T},
62-
) where {T}
63-
return MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T)
64-
end
65-
66-
function _constraint_constant(
67-
model::MOI.ModelLike,
68-
ci::MOI.ConstraintIndex{
69-
<:MOI.AbstractScalarFunction,
70-
<:MOI.AbstractScalarSet,
71-
},
72-
::Type{T},
73-
) where {T}
74-
return MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T) -
75-
MOI.constant(MOI.get(model, MOI.ConstraintSet(), ci))
76-
end
77-
78-
function _dual_objective_value(
79-
model::MOI.ModelLike,
80-
ci::MOI.ConstraintIndex,
81-
::Type{T},
82-
result_index::Integer,
83-
) where {T}
84-
return set_dot(
85-
_constraint_constant(model, ci, T),
86-
MOI.get(model, MOI.ConstraintDual(result_index), ci),
87-
MOI.get(model, MOI.ConstraintSet(), ci),
88-
)
89-
end
90-
91-
"""
92-
Given lower <= f(x) <= upper [dual], return the expression to be multiplied by
93-
the dual variable. This is one of the following cases:
94-
95-
1. f(x) - lower: if `lower > -Inf` and the lower bound is binding (either no
96-
`upper` or `dual > 0`)
97-
2. f(x) - upper: if `upper < Inf` and the upper bound is binding (either no
98-
`lower` or `dual < 0`)
99-
3. f(x): if `lower = -Inf` and `upper = Inf` or `dual = 0`
100-
"""
101-
function _constant_minus_bound(constant, lower, upper, dual)
102-
if isfinite(lower) && (!isfinite(upper) || dual > zero(dual))
103-
return constant - lower
104-
elseif isfinite(upper) && (!isfinite(lower) || dual < zero(dual))
105-
return constant - upper
106-
else
107-
return constant
108-
end
109-
end
110-
111-
function _dual_objective_value(
112-
model::MOI.ModelLike,
113-
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,<:MOI.Interval},
114-
::Type{T},
115-
result_index::Integer,
116-
) where {T}
117-
constant = MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T)
118-
set = MOI.get(model, MOI.ConstraintSet(), ci)
119-
dual = MOI.get(model, MOI.ConstraintDual(result_index), ci)
120-
constant = _constant_minus_bound(constant, set.lower, set.upper, dual)
121-
return set_dot(constant, dual, set)
122-
end
123-
124-
function _dual_objective_value(
125-
model::MOI.ModelLike,
126-
ci::MOI.ConstraintIndex{<:MOI.AbstractVectorFunction,<:MOI.HyperRectangle},
127-
::Type{T},
128-
result_index::Integer,
129-
) where {T}
130-
func_constant =
131-
MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T)
132-
set = MOI.get(model, MOI.ConstraintSet(), ci)
133-
dual = MOI.get(model, MOI.ConstraintDual(result_index), ci)
134-
constants = map(enumerate(func_constant)) do (i, c)
135-
return _constant_minus_bound(c, set.lower[i], set.upper[i], dual[i])
136-
end
137-
return set_dot(constants, dual, set)
138-
end
139-
140-
function _dual_objective_value(
141-
model::MOI.ModelLike,
142-
::Type{F},
143-
::Type{S},
144-
::Type{T},
145-
result_index::Integer,
146-
) where {T,F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
147-
value = zero(T)
148-
if F == variable_function_type(S) && !_has_constant(S)
149-
return value # Shortcut
150-
end
151-
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
152-
value += _dual_objective_value(model, ci, T, result_index)
153-
end
154-
return value
155-
end
156-
157-
_has_constant(::Type{<:MOI.AbstractScalarSet}) = true
158-
_has_constant(::Type{<:MOI.AbstractVectorSet}) = false
159-
_has_constant(::Type{<:MOI.HyperRectangle}) = true
160-
16155
"""
16256
get_fallback(
16357
model::MOI.ModelLike,
@@ -192,6 +86,72 @@ function get_fallback(
19286
return value::T
19387
end
19488

89+
function _dual_objective_value(
90+
model::MOI.ModelLike,
91+
::Type{F},
92+
::Type{S},
93+
::Type{T},
94+
result_index::Integer,
95+
)::T where {T,F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
96+
value = zero(T)
97+
if F == variable_function_type(S) && !_variable_set_in_dual_objective(S)
98+
# Early return. This is a constraint like x in R_+, so no contribution
99+
# appears in the dual objective.
100+
return value
101+
end
102+
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
103+
constant = MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T)
104+
set = MOI.get(model, MOI.ConstraintSet(), ci)
105+
dual = MOI.get(model, MOI.ConstraintDual(result_index), ci)
106+
value += _dual_objective_dot(constant, dual, set)
107+
end
108+
return value
109+
end
110+
111+
_variable_set_in_dual_objective(::Type{<:MOI.AbstractSet}) = false
112+
113+
_variable_set_in_dual_objective(::Type{<:MOI.EqualTo}) = true
114+
115+
_variable_set_in_dual_objective(::Type{<:MOI.GreaterThan}) = true
116+
117+
_variable_set_in_dual_objective(::Type{<:MOI.LessThan}) = true
118+
119+
_variable_set_in_dual_objective(::Type{<:MOI.Interval}) = true
120+
121+
_variable_set_in_dual_objective(::Type{<:MOI.HyperRectangle}) = true
122+
123+
_dual_objective_dot(x, y, set) = set_dot(x, y, set)
124+
125+
_dual_objective_dot(x, y, set::MOI.EqualTo) = (x - set.value) * y
126+
127+
_dual_objective_dot(x, y, set::MOI.LessThan) = (x - set.upper) * y
128+
129+
_dual_objective_dot(x, y, set::MOI.GreaterThan) = (x - set.lower) * y
130+
131+
function _dual_objective_dot(x, y, set::MOI.Interval)
132+
if isfinite(set.lower) && (!isfinite(set.upper) || y > zero(y))
133+
return (x - set.lower) * y
134+
elseif isfinite(set.upper) && (!isfinite(set.lower) || y < zero(y))
135+
return (x - set.upper) * y
136+
end
137+
return x * y
138+
end
139+
140+
function _dual_objective_dot(x, y, set::MOI.HyperRectangle)
141+
@assert length(x) == length(y) == MOI.dimension(set)
142+
ret = zero(eltype(x))
143+
for (xi, yi, li, ui) in zip(x, y, set.lower, set.upper)
144+
if isfinite(li) && (!isfinite(ui) || yi > zero(yi))
145+
ret += (xi - li) * yi
146+
elseif isfinite(ui) && (!isfinite(li) || yi < zero(yi))
147+
ret += (xi - ui) * yi
148+
else
149+
ret += xi * yi
150+
end
151+
end
152+
return ret
153+
end
154+
195155
# MOI.ConstraintPrimal
196156

197157
"""

test/Bridges/Constraint/NormSpectralBridge.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ function test_NormNuclear()
197197
mock,
198198
var_primal,
199199
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) =>
200-
[[1.0]],
200+
[1.0],
201201
(
202202
MOI.VectorAffineFunction{Float64},
203203
MOI.PositiveSemidefiniteConeTriangle,

test/Utilities/results.jl

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,57 @@ function _test_hyperrectangle(T)
5151
return
5252
end
5353

54+
function test_dual_objective_value_open_interval_Interval_variable_index()
55+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
56+
model = MOI.Utilities.MockOptimizer(
57+
inner;
58+
eval_variable_constraint_dual = false,
59+
)
60+
# -Inf <= x[1] <= Inf
61+
# -Inf <= x[2] <= 2.1
62+
# -2.2 <= x[3] <= Inf
63+
# -2.3 <= x[4] <= 2.4
64+
x = MOI.add_variables(model, 4)
65+
set = MOI.Interval.([-Inf, -Inf, -2.2, -2.3], [Inf, 2.1, Inf, 2.4])
66+
c = MOI.add_constraint.(model, x, set)
67+
for (dual, obj) in [
68+
[0.0, 0.0, 0.0, 0.0] => 0.0,
69+
# d[1]
70+
[-2.0, 0.0, 0.0, 0.0] => 0.0,
71+
[-1.0, 0.0, 0.0, 0.0] => 0.0,
72+
[1.0, 0.0, 0.0, 0.0] => 0.0,
73+
[2.0, 0.0, 0.0, 0.0] => 0.0,
74+
# d[2]: -(-2.1) = 2.1
75+
[0.0, -2.0, 0.0, 0.0] => -4.2,
76+
[0.0, -1.0, 0.0, 0.0] => -2.1,
77+
[0.0, 1.0, 0.0, 0.0] => 2.1,
78+
[0.0, 2.0, 0.0, 0.0] => 4.2,
79+
# d[3]: -(- -2.2) = -2.2
80+
[0.0, 0.0, -2.0, 0.0] => 4.4,
81+
[0.0, 0.0, -1.0, 0.0] => 2.2,
82+
[0.0, 0.0, 1.0, 0.0] => -2.2,
83+
[0.0, 0.0, 2.0, 0.0] => -4.4,
84+
# d[4]: -(- -2.3) = -2.3
85+
# d[4]: -(- 2.4) = 2.4
86+
[0.0, 0.0, 0.0, -2.0] => -4.8,
87+
[0.0, 0.0, 0.0, -1.0] => -2.4,
88+
[0.0, 0.0, 0.0, 1.0] => -2.3,
89+
[0.0, 0.0, 0.0, 2.0] => -4.6,
90+
#
91+
[1.0, 1.0, 1.0, 1.0] => -2.4,
92+
[-1.0, -1.0, -1.0, -1.0] => -2.3,
93+
]
94+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
95+
MOI.set.(model, MOI.ConstraintDual(), c, dual)
96+
d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64)
97+
@test isapprox(d, obj)
98+
MOI.set.(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
99+
d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64)
100+
@test isapprox(d, -obj)
101+
end
102+
return
103+
end
104+
54105
function test_dual_objective_value_open_interval_Interval()
55106
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
56107
model = MOI.Utilities.MockOptimizer(inner)
@@ -93,7 +144,58 @@ function test_dual_objective_value_open_interval_Interval()
93144
MOI.set.(model, MOI.ConstraintDual(), c, dual)
94145
d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64)
95146
@test isapprox(d, obj)
96-
MOI.set.(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
147+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
148+
d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64)
149+
@test isapprox(d, -obj)
150+
end
151+
return
152+
end
153+
154+
function test_dual_objective_value_open_interval_Hyperrectangle_variable_index()
155+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
156+
model = MOI.Utilities.MockOptimizer(
157+
inner;
158+
eval_variable_constraint_dual = false,
159+
)
160+
# -Inf <= x[1] <= Inf
161+
# -Inf <= x[2] <= 2.1
162+
# -2.2 <= x[3] <= Inf
163+
# -2.3 <= x[4] <= 2.4
164+
x = MOI.add_variables(model, 4)
165+
set = MOI.HyperRectangle([-Inf, -Inf, -2.2, -2.3], [Inf, 2.1, Inf, 2.4])
166+
c = MOI.add_constraint(model, MOI.VectorOfVariables(x), set)
167+
for (dual, obj) in [
168+
[0.0, 0.0, 0.0, 0.0] => 0.0,
169+
# d[1]
170+
[-2.0, 0.0, 0.0, 0.0] => 0.0,
171+
[-1.0, 0.0, 0.0, 0.0] => 0.0,
172+
[1.0, 0.0, 0.0, 0.0] => 0.0,
173+
[2.0, 0.0, 0.0, 0.0] => 0.0,
174+
# d[2]: -(-2.1) = 2.1
175+
[0.0, -2.0, 0.0, 0.0] => -4.2,
176+
[0.0, -1.0, 0.0, 0.0] => -2.1,
177+
[0.0, 1.0, 0.0, 0.0] => 2.1,
178+
[0.0, 2.0, 0.0, 0.0] => 4.2,
179+
# d[3]: -(- -2.2) = -2.2
180+
[0.0, 0.0, -2.0, 0.0] => 4.4,
181+
[0.0, 0.0, -1.0, 0.0] => 2.2,
182+
[0.0, 0.0, 1.0, 0.0] => -2.2,
183+
[0.0, 0.0, 2.0, 0.0] => -4.4,
184+
# d[4]: -(- -2.3) = -2.3
185+
# d[4]: -(- 2.4) = 2.4
186+
[0.0, 0.0, 0.0, -2.0] => -4.8,
187+
[0.0, 0.0, 0.0, -1.0] => -2.4,
188+
[0.0, 0.0, 0.0, 1.0] => -2.3,
189+
[0.0, 0.0, 0.0, 2.0] => -4.6,
190+
#
191+
[1.0, 1.0, 1.0, 1.0] => -2.4,
192+
[-1.0, -1.0, -1.0, -1.0] => -2.3,
193+
]
194+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
195+
MOI.set(model, MOI.ConstraintDual(), c, dual)
196+
d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64)
197+
@test isapprox(d, obj)
198+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
97199
d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64)
98200
@test isapprox(d, -obj)
99201
end

0 commit comments

Comments
 (0)