Skip to content

Commit 837d879

Browse files
authored
Merge pull request #473 from JuliaOpt/bl/objbound
Add DualObjectiveValue
2 parents 681f4c3 + 1e9be17 commit 837d879

File tree

9 files changed

+249
-27
lines changed

9 files changed

+249
-27
lines changed

docs/src/apireference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ ResultCount
100100
ObjectiveFunction
101101
ObjectiveFunctionType
102102
ObjectiveValue
103+
DualObjectiveValue
103104
ObjectiveBound
104105
RelativeGap
105106
SolveTime

src/Test/contconic.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ function _lin1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool)
5858
end
5959

6060
@test MOI.get(model, MOI.ObjectiveValue()) -11 atol=atol rtol=rtol
61+
if config.duals
62+
@test MOI.get(model, MOI.DualObjectiveValue()) -11 atol=atol rtol=rtol
63+
end
6164

6265
@test MOI.get(model, MOI.VariablePrimal(), v) [1, 0, 2] atol=atol rtol=rtol
6366

@@ -155,6 +158,9 @@ function _lin2test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool)
155158
end
156159

157160
@test MOI.get(model, MOI.ObjectiveValue()) -82 atol=atol rtol=rtol
161+
if config.duals
162+
@test MOI.get(model, MOI.DualObjectiveValue()) -82 atol=atol rtol=rtol
163+
end
158164

159165
@test MOI.get(model, MOI.VariablePrimal(), x) -4 atol=atol rtol=rtol
160166
@test MOI.get(model, MOI.VariablePrimal(), y) -3 atol=atol rtol=rtol
@@ -343,6 +349,9 @@ function _soc1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool)
343349
end
344350

345351
@test MOI.get(model, MOI.ObjectiveValue()) 2 atol=atol rtol=rtol
352+
if config.duals
353+
@test MOI.get(model, MOI.DualObjectiveValue()) 2 atol=atol rtol=rtol
354+
end
346355

347356
@test MOI.get(model, MOI.VariablePrimal(), x) 1 atol=atol rtol=rtol
348357
@test MOI.get(model, MOI.VariablePrimal(), y) 1/√2 atol=atol rtol=rtol
@@ -418,6 +427,9 @@ function _soc2test(model::MOI.ModelLike, config::TestConfig, nonneg::Bool)
418427
end
419428

420429
@test MOI.get(model, MOI.ObjectiveValue()) -1/√2 atol=atol rtol=rtol
430+
if config.duals
431+
@test MOI.get(model, MOI.DualObjectiveValue()) -1/√2 atol=atol rtol=rtol
432+
end
421433

422434
@test MOI.get(model, MOI.VariablePrimal(), x) -1/√2 atol=atol rtol=rtol
423435
@test MOI.get(model, MOI.VariablePrimal(), y) 1/√2 atol=atol rtol=rtol
@@ -537,6 +549,9 @@ function soc4test(model::MOI.ModelLike, config::TestConfig)
537549
end
538550

539551
@test MOI.get(model, MOI.ObjectiveValue()) -√5 atol=atol rtol=rtol
552+
if config.duals
553+
@test MOI.get(model, MOI.DualObjectiveValue()) -√5 atol=atol rtol=rtol
554+
end
540555

541556
@test MOI.get(model, MOI.VariablePrimal(), x) [1.0, 2/√5, 1/√5, 2/√5, 1/√5] atol=atol rtol=rtol
542557

@@ -619,6 +634,9 @@ function _rotatedsoc1test(model::MOI.ModelLike, config::TestConfig, abvars::Bool
619634
end
620635

621636
@test MOI.get(model, MOI.ObjectiveValue()) 2 atol=atol rtol=rtol
637+
if config.duals
638+
@test MOI.get(model, MOI.DualObjectiveValue()) 2 atol=atol rtol=rtol
639+
end
622640

623641
if abvars
624642
@test MOI.get(model, MOI.VariablePrimal(), a) 0.5 atol=atol rtol=rtol
@@ -787,6 +805,9 @@ function rotatedsoc3test(model::MOI.ModelLike, config::TestConfig; n=2, ub=3.0)
787805
end
788806

789807
@test MOI.get(model, MOI.ObjectiveValue()) ub atol=atol rtol=rtol
808+
if config.duals
809+
@test MOI.get(model, MOI.DualObjectiveValue()) ub atol=atol rtol=rtol
810+
end
790811

791812
@test MOI.get(model, MOI.VariablePrimal(), x) [1.0; zeros(n-1)] atol=atol rtol=rtol
792813
@test MOI.get(model, MOI.VariablePrimal(), u) ub atol=atol rtol=rtol
@@ -947,6 +968,10 @@ function _exp1test(model::MOI.ModelLike, config::TestConfig, vecofvars::Bool)
947968
end
948969

949970
@test MOI.get(model, MOI.ObjectiveValue()) 3 + 2exp(1/2) atol=atol rtol=rtol
971+
if config.duals
972+
@test MOI.get(model, MOI.DualObjectiveValue()) 3 + 2exp(1/2) atol=atol rtol=rtol
973+
end
974+
950975
@test MOI.get(model, MOI.VariablePrimal(), v) [1., 2., 2exp(1/2)] atol=atol rtol=rtol
951976

952977
@test MOI.get(model, MOI.ConstraintPrimal(), vc) [1., 2., 2exp(1/2)] atol=atol rtol=rtol
@@ -1013,6 +1038,9 @@ function exp2test(model::MOI.ModelLike, config::TestConfig)
10131038
end
10141039

10151040
@test MOI.get(model, MOI.ObjectiveValue()) exp(-0.3) atol=atol rtol=rtol
1041+
if config.duals
1042+
@test MOI.get(model, MOI.DualObjectiveValue()) exp(-0.3) atol=atol rtol=rtol
1043+
end
10161044

10171045
@test MOI.get(model, MOI.VariablePrimal(), v) [0., -0.3, 0., exp(-0.3), exp(-0.3), exp(-0.3), 0., 1.0, 0.] atol=atol rtol=rtol
10181046

@@ -1078,6 +1106,9 @@ function exp3test(model::MOI.ModelLike, config::TestConfig)
10781106
end
10791107

10801108
@test MOI.get(model, MOI.ObjectiveValue()) log(5) atol=atol rtol=rtol
1109+
if config.duals
1110+
@test MOI.get(model, MOI.DualObjectiveValue()) log(5) atol=atol rtol=rtol
1111+
end
10811112

10821113
@test MOI.get(model, MOI.VariablePrimal(), x) log(5) atol=atol rtol=rtol
10831114
@test MOI.get(model, MOI.VariablePrimal(), y) 5. atol=atol rtol=rtol
@@ -1157,6 +1188,9 @@ function _psd0test(model::MOI.ModelLike, vecofvars::Bool, psdcone, config::TestC
11571188
end
11581189

11591190
@test MOI.get(model, MOI.ObjectiveValue()) 2 atol=atol rtol=rtol
1191+
if config.duals
1192+
@test MOI.get(model, MOI.DualObjectiveValue()) 2 atol=atol rtol=rtol
1193+
end
11601194

11611195
Xv = square ? ones(4) : ones(3)
11621196
@test MOI.get(model, MOI.VariablePrimal(), X) Xv atol=atol rtol=rtol
@@ -1302,6 +1336,9 @@ function _psd1test(model::MOI.ModelLike, vecofvars::Bool, psdcone, config::TestC
13021336
end
13031337

13041338
@test MOI.get(model, MOI.ObjectiveValue()) obj atol=atol rtol=rtol
1339+
if config.duals
1340+
@test MOI.get(model, MOI.DualObjectiveValue()) obj atol=atol rtol=rtol
1341+
end
13051342

13061343
Xv = square ?^2, α*β, α^2, α*β, β^2, α*β, α^2, α*β, α^2] :^2, α*β, β^2, α^2, α*β, α^2]
13071344
xv = [2*x2, x2, x2]

src/Test/contlinear.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function linear1test(model::MOI.ModelLike, config::TestConfig)
7979

8080
if config.duals
8181
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
82+
@test MOI.get(model, MOI.DualObjectiveValue()) -1 atol=atol rtol=rtol
8283
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
8384

8485
# reduced costs
@@ -112,6 +113,7 @@ function linear1test(model::MOI.ModelLike, config::TestConfig)
112113

113114
if config.duals
114115
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
116+
@test MOI.get(model, MOI.DualObjectiveValue()) 1 atol=atol rtol=rtol
115117
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
116118

117119
@test MOI.get(model, MOI.ConstraintDual(), vc1) 0 atol=atol rtol=rtol
@@ -171,6 +173,7 @@ function linear1test(model::MOI.ModelLike, config::TestConfig)
171173

172174
if config.duals
173175
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
176+
@test MOI.get(model, MOI.DualObjectiveValue()) 2 atol=atol rtol=rtol
174177
@test MOI.get(model, MOI.ConstraintDual(), c) -2 atol=atol rtol=rtol
175178

176179
@test MOI.get(model, MOI.ConstraintDual(), vc1) 1 atol=atol rtol=rtol
@@ -198,6 +201,11 @@ function linear1test(model::MOI.ModelLike, config::TestConfig)
198201
@test MOI.get(model, MOI.ObjectiveValue()) 3 atol=atol rtol=rtol
199202

200203
@test MOI.get(model, MOI.VariablePrimal(), v) [-1, 0, 2] atol=atol rtol=rtol
204+
205+
if config.duals
206+
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
207+
@test MOI.get(model, MOI.DualObjectiveValue()) 3 atol=atol rtol=rtol
208+
end
201209
end
202210

203211
# put lb of x back to 0 and fix z to zero to get :
@@ -317,6 +325,7 @@ function linear1test(model::MOI.ModelLike, config::TestConfig)
317325

318326
if config.duals
319327
@test MOI.get(model, MOI.DualStatus(1)) == MOI.FEASIBLE_POINT
328+
@test MOI.get(model, MOI.DualObjectiveValue()) 3 atol=atol rtol=rtol
320329

321330
@test MOI.get(model, MOI.ConstraintDual(), c) -1.5 atol=atol rtol=rtol
322331
@test MOI.get(model, MOI.ConstraintDual(), c2) 0.5 atol=atol rtol=rtol
@@ -405,6 +414,7 @@ function linear2test(model::MOI.ModelLike, config::TestConfig)
405414

406415
if config.duals
407416
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
417+
@test MOI.get(model, MOI.DualObjectiveValue()) -1 atol=atol rtol=rtol
408418
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
409419

410420
# reduced costs
@@ -1186,6 +1196,7 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
11861196
if config.duals
11871197
@test MOI.get(model, MOI.ResultCount()) >= 1
11881198
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
1199+
@test MOI.get(model, MOI.DualObjectiveValue()) 10.0 atol=atol rtol=rtol
11891200
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
11901201
end
11911202

@@ -1209,6 +1220,7 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
12091220
@test MOI.get(model, MOI.ConstraintPrimal(), c) 5 atol=atol rtol=rtol
12101221

12111222
if config.duals
1223+
@test MOI.get(model, MOI.DualObjectiveValue()) 5.0 atol=atol rtol=rtol
12121224
@test MOI.get(model, MOI.ResultCount()) >= 1
12131225
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
12141226
@test MOI.get(model, MOI.ConstraintDual(), c) 1 atol=atol rtol=rtol
@@ -1242,6 +1254,13 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
12421254
MOI.get(model, MOI.ConstraintBasisStatus(), vc[2])== MOI.BASIC)
12431255
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC_AT_LOWER
12441256
end
1257+
1258+
if config.duals
1259+
@test MOI.get(model, MOI.ResultCount()) >= 1
1260+
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
1261+
@test MOI.get(model, MOI.DualObjectiveValue()) 2.0 atol=atol rtol=rtol
1262+
@test MOI.get(model, MOI.ConstraintDual(), c) 1 atol=atol rtol=rtol
1263+
end
12451264
end
12461265

12471266
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0))
@@ -1558,6 +1577,7 @@ function linear14test(model::MOI.ModelLike, config::TestConfig)
15581577

15591578
if config.duals
15601579
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
1580+
@test MOI.get(model, MOI.DualObjectiveValue()) 8 atol=atol rtol=rtol
15611581
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
15621582

15631583
# reduced costs
@@ -1594,6 +1614,7 @@ function linear14test(model::MOI.ModelLike, config::TestConfig)
15941614

15951615
if config.duals
15961616
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
1617+
@test MOI.get(model, MOI.DualObjectiveValue()) 6 atol=atol rtol=rtol
15971618
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
15981619

15991620
# reduced costs
@@ -1646,6 +1667,11 @@ function linear15test(model::MOI.ModelLike, config::TestConfig)
16461667
@test MOI.get(model, MOI.ObjectiveValue()) 0 atol=atol rtol=rtol
16471668

16481669
@test MOI.get(model, MOI.VariablePrimal(), x[1]) 0 atol=atol rtol=rtol
1670+
1671+
if config.duals
1672+
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
1673+
@test MOI.get(model, MOI.DualObjectiveValue()) 0 atol=atol rtol=rtol
1674+
end
16491675
end
16501676
end
16511677

src/Utilities/mockoptimizer.jl

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ mutable struct MockOptimizer{MT<:MOI.ModelLike} <: MOI.AbstractOptimizer
3131
# Computes `ObjectiveValue` by evaluating the `ObjectiveFunction` with
3232
# `VariablePrimal`. See `get_fallback`.
3333
eval_objective_value::Bool
34-
objectivevalue::Float64
35-
objectivebound::Float64 # set this using MOI.set(model, MOI.ObjectiveBound(), value)
34+
objective_value::Float64 # set this using MOI.set(model, MOI.ObjectiveValue(), value)
35+
# Computes `DualObjectiveValue` using `get_fallback`
36+
eval_dual_objective_value::Bool
37+
dual_objective_value::Float64 # set this using MOI.set(model, MOI.DualObjectiveValue(), value)
38+
objective_bound::Float64 # set this using MOI.set(model, MOI.ObjectiveBound(), value)
3639
primalstatus::MOI.ResultStatusCode
3740
dualstatus::MOI.ResultStatusCode
3841
varprimal::Dict{MOI.VariableIndex,Float64}
@@ -54,6 +57,7 @@ xor_variables(f) = mapvariables(xor_index, f)
5457
function MockOptimizer(inner_model::MOI.ModelLike; supports_names=true,
5558
needs_allocate_load=false,
5659
eval_objective_value=true,
60+
eval_dual_objective_value=true,
5761
eval_variable_constraint_dual=true)
5862
return MockOptimizer(inner_model,
5963
0,
@@ -73,6 +77,8 @@ function MockOptimizer(inner_model::MOI.ModelLike; supports_names=true,
7377
0,
7478
eval_objective_value,
7579
NaN,
80+
eval_dual_objective_value,
81+
NaN,
7682
NaN,
7783
MOI.NO_SOLUTION,
7884
MOI.NO_SOLUTION,
@@ -141,10 +147,11 @@ function MOI.supports(mock::MockOptimizer,
141147
return MOI.supports(mock.inner_model, attr, IdxT)
142148
end
143149

144-
MOI.supports(mock::MockOptimizer, ::Union{MOI.ResultCount,MOI.TerminationStatus,MOI.ObjectiveValue,MOI.PrimalStatus,MOI.DualStatus,MockModelAttribute}) = true
150+
MOI.supports(mock::MockOptimizer, ::MockModelAttribute) = true
145151
MOI.set(mock::MockOptimizer, ::MOI.ResultCount, value::Integer) = (mock.resultcount = value)
146152
MOI.set(mock::MockOptimizer, ::MOI.TerminationStatus, value::MOI.TerminationStatusCode) = (mock.terminationstatus = value)
147-
MOI.set(mock::MockOptimizer, ::MOI.ObjectiveValue, value::Real) = (mock.objectivevalue = value)
153+
MOI.set(mock::MockOptimizer, ::MOI.ObjectiveValue, value::Real) = (mock.objective_value = value)
154+
MOI.set(mock::MockOptimizer, ::MOI.DualObjectiveValue, value::Real) = (mock.dual_objective_value = value)
148155
MOI.set(mock::MockOptimizer, ::MOI.PrimalStatus, value::MOI.ResultStatusCode) = (mock.primalstatus = value)
149156
MOI.set(mock::MockOptimizer, ::MOI.DualStatus, value::MOI.ResultStatusCode) = (mock.dualstatus = value)
150157
MOI.set(mock::MockOptimizer, ::MockModelAttribute, value::Integer) = (mock.attribute = value)
@@ -237,7 +244,14 @@ function MOI.get(mock::MockOptimizer, attr::MOI.ObjectiveValue)
237244
if mock.eval_objective_value
238245
return get_fallback(mock, attr)
239246
else
240-
return mock.objectivevalue
247+
return mock.objective_value
248+
end
249+
end
250+
function MOI.get(mock::MockOptimizer, attr::MOI.DualObjectiveValue)
251+
if mock.eval_dual_objective_value
252+
return get_fallback(mock, attr, Float64)
253+
else
254+
return mock.dual_objective_value
241255
end
242256
end
243257
MOI.get(mock::MockOptimizer, ::MOI.PrimalStatus) = mock.primalstatus
@@ -283,10 +297,9 @@ end
283297
MOI.get(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex) = mock.conattribute[xor_index(idx)]
284298
MOI.get(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex) = mock.con_basis[xor_index(idx)]
285299

286-
MOI.supports(mock::MockOptimizer, ::MOI.ObjectiveBound) = true
287-
MOI.get(mock::MockOptimizer, ::MOI.ObjectiveBound) = mock.objectivebound
300+
MOI.get(mock::MockOptimizer, ::MOI.ObjectiveBound) = mock.objective_bound
288301
function MOI.set(mock::MockOptimizer, ::MOI.ObjectiveBound, value::Float64)
289-
mock.objectivebound = value
302+
mock.objective_bound = value
290303
end
291304

292305
MOI.get(::MockOptimizer, ::MOI.SolverName) = "Mock"
@@ -301,8 +314,9 @@ function MOI.empty!(mock::MockOptimizer)
301314
mock.hasdual = false
302315
mock.terminationstatus = MOI.OPTIMIZE_NOT_CALLED
303316
mock.resultcount = 0
304-
mock.objectivevalue = NaN
305-
mock.objectivebound = NaN
317+
mock.objective_value = NaN
318+
mock.dual_objective_value = NaN
319+
mock.objective_bound = NaN
306320
mock.primalstatus = MOI.NO_SOLUTION
307321
mock.dualstatus = MOI.NO_SOLUTION
308322
mock.varprimal = Dict{MOI.VariableIndex,Float64}()
@@ -318,8 +332,8 @@ function MOI.is_empty(mock::MockOptimizer)
318332
return MOI.is_empty(mock.inner_model) && mock.attribute == 0 &&
319333
!mock.solved && !mock.hasprimal && !mock.hasdual &&
320334
mock.terminationstatus == MOI.OPTIMIZE_NOT_CALLED &&
321-
mock.resultcount == 0 && isnan(mock.objectivevalue) &&
322-
isnan(mock.objectivebound) &&
335+
mock.resultcount == 0 && isnan(mock.objective_value) &&
336+
isnan(mock.dual_objective_value) && isnan(mock.objective_bound) &&
323337
mock.primalstatus == MOI.NO_SOLUTION &&
324338
mock.dualstatus == MOI.NO_SOLUTION &&
325339
isempty(mock.con_basis)

0 commit comments

Comments
 (0)