Skip to content

Commit 22645a9

Browse files
authored
Merge pull request #1039 from JuliaOpt/bl/mock_result_index
Fix MockOptimizer for nonone result index
2 parents d12a510 + bb9630b commit 22645a9

3 files changed

Lines changed: 124 additions & 71 deletions

File tree

src/Utilities/mockoptimizer.jl

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,20 @@ 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-
objective_value::Float64 # set this using MOI.set(model, MOI.ObjectiveValue(), value)
34+
objective_value::Dict{Int,Float64} # set this using MOI.set(model, MOI.ObjectiveValue(), value)
3535
# Computes `DualObjectiveValue` using `get_fallback`
3636
eval_dual_objective_value::Bool
37-
dual_objective_value::Float64 # set this using MOI.set(model, MOI.DualObjectiveValue(), value)
38-
primalstatus::MOI.ResultStatusCode
39-
dualstatus::MOI.ResultStatusCode
40-
varprimal::Dict{MOI.VariableIndex,Float64}
37+
dual_objective_value::Dict{Int,Float64} # set this using MOI.set(model, MOI.DualObjectiveValue(), value)
38+
primal_status::Dict{Int,MOI.ResultStatusCode}
39+
dual_status::Dict{Int,MOI.ResultStatusCode}
40+
varprimal::Dict{MOI.VariableIndex,Dict{Int,Float64}}
4141
callback_variable_primal::Dict{MOI.VariableIndex, Float64}
4242
# Computes `ConstraintDual` of constraints with `SingleVariable` or
4343
# `VectorOfVariables` functions by evaluating the `ConstraintDual` of
4444
# constraints having the variable in the function. See `get_fallback`.
4545
eval_variable_constraint_dual::Bool
46-
condual::Dict{MOI.ConstraintIndex,Any}
47-
con_basis::Dict{MOI.ConstraintIndex,MOI.BasisStatusCode}
46+
condual::Dict{MOI.ConstraintIndex,Dict{Int,Any}}
47+
con_basis::Dict{MOI.ConstraintIndex,Dict{Int,MOI.BasisStatusCode}}
4848
# The attributes set by `MOI.optimize!` cannot be set to `model`.
4949
# We detect them with `is_set_by_optimize` and store them in the following:
5050
optimizer_attributes::Dict{MOI.AbstractOptimizerAttribute, Any}
@@ -83,16 +83,16 @@ function MockOptimizer(inner_model::MOI.ModelLike; supports_names=true,
8383
1,
8484
MOI.OPTIMIZE_NOT_CALLED,
8585
eval_objective_value,
86-
NaN,
86+
Dict{Int,Float64}(),
8787
eval_dual_objective_value,
88-
NaN,
89-
MOI.NO_SOLUTION,
90-
MOI.NO_SOLUTION,
91-
Dict{MOI.VariableIndex,Float64}(),
88+
Dict{Int,Float64}(),
89+
Dict{Int,MOI.ResultStatusCode}(),
90+
Dict{Int,MOI.ResultStatusCode}(),
91+
Dict{MOI.VariableIndex,Dict{Int,Float64}}(),
9292
Dict{MOI.VariableIndex,Float64}(),
9393
eval_variable_constraint_dual,
94-
Dict{MOI.ConstraintIndex,Any}(),
95-
Dict{MOI.ConstraintIndex,MOI.BasisStatusCode}(),
94+
Dict{MOI.ConstraintIndex,Dict{Int,Any}}(),
95+
Dict{MOI.ConstraintIndex,Dict{Int,MOI.BasisStatusCode}}(),
9696
Dict{MOI.AbstractOptimizerAttribute, Any}(),
9797
Dict{MOI.AbstractModelAttribute, Any}(),
9898
Dict{MOI.AbstractSubmittable, Vector{Tuple}}())
@@ -153,10 +153,18 @@ end
153153

154154
MOI.supports(mock::MockOptimizer, ::MockModelAttribute) = true
155155
MOI.set(mock::MockOptimizer, ::MOI.TerminationStatus, value::MOI.TerminationStatusCode) = (mock.terminationstatus = value)
156-
MOI.set(mock::MockOptimizer, ::MOI.ObjectiveValue, value::Real) = (mock.objective_value = value)
157-
MOI.set(mock::MockOptimizer, ::MOI.DualObjectiveValue, value::Real) = (mock.dual_objective_value = value)
158-
MOI.set(mock::MockOptimizer, ::MOI.PrimalStatus, value::MOI.ResultStatusCode) = (mock.primalstatus = value)
159-
MOI.set(mock::MockOptimizer, ::MOI.DualStatus, value::MOI.ResultStatusCode) = (mock.dualstatus = value)
156+
function MOI.set(mock::MockOptimizer, attr::MOI.ObjectiveValue, value::Real)
157+
mock.objective_value[attr.result_index] = value
158+
end
159+
function MOI.set(mock::MockOptimizer, attr::MOI.DualObjectiveValue, value::Real)
160+
mock.dual_objective_value[attr.result_index] = value
161+
end
162+
function MOI.set(mock::MockOptimizer, attr::MOI.PrimalStatus, value::MOI.ResultStatusCode)
163+
mock.primal_status[attr.N] = value
164+
end
165+
function MOI.set(mock::MockOptimizer, attr::MOI.DualStatus, value::MOI.ResultStatusCode)
166+
mock.dual_status[attr.N] = value
167+
end
160168
MOI.set(mock::MockOptimizer, ::MockModelAttribute, value::Integer) = (mock.attribute = value)
161169
function MOI.supports(mock::MockOptimizer, attr::MOI.AbstractOptimizerAttribute)
162170
# `supports` is not defined if `is_set_by_optimize(attr)` so we pass it to
@@ -188,9 +196,9 @@ function MOI.set(mock::MockOptimizer, attr::MOI.AbstractVariableAttribute,
188196
idx::MOI.VariableIndex, value)
189197
MOI.set(mock.inner_model, attr, xor_index(idx), xor_indices(value))
190198
end
191-
function MOI.set(mock::MockOptimizer, ::MOI.VariablePrimal,
199+
function MOI.set(mock::MockOptimizer, attr::MOI.VariablePrimal,
192200
idx::MOI.VariableIndex, value)
193-
mock.varprimal[xor_index(idx)] = value
201+
_safe_set_result(mock.varprimal, attr, idx, value)
194202
end
195203
function MOI.set(mock::MockOptimizer, ::MOI.CallbackVariablePrimal,
196204
idx::MOI.VariableIndex, value)
@@ -208,13 +216,13 @@ function MOI.set(mock::MockOptimizer, ::MockConstraintAttribute,
208216
idx::MOI.ConstraintIndex, value)
209217
mock.conattribute[xor_index(idx)] = value
210218
end
211-
function MOI.set(mock::MockOptimizer, ::MOI.ConstraintDual,
219+
function MOI.set(mock::MockOptimizer, attr::MOI.ConstraintDual,
212220
idx::MOI.ConstraintIndex, value)
213-
mock.condual[xor_index(idx)] = value
221+
_safe_set_result(mock.condual, attr, idx, value)
214222
end
215-
function MOI.set(mock::MockOptimizer, ::MOI.ConstraintBasisStatus,
223+
function MOI.set(mock::MockOptimizer, attr::MOI.ConstraintBasisStatus,
216224
idx::MOI.ConstraintIndex, value)
217-
mock.con_basis[xor_index(idx)] = value
225+
_safe_set_result(mock.con_basis, attr, idx, value)
218226
end
219227

220228
MOI.get(mock::MockOptimizer, ::MOI.RawSolver) = mock
@@ -295,29 +303,29 @@ function MOI.get(mock::MockOptimizer, attr::MOI.ObjectiveValue)
295303
if mock.eval_objective_value
296304
return get_fallback(mock, attr)
297305
else
298-
return mock.objective_value
306+
return get(mock.objective_value, attr.result_index, NaN)
299307
end
300308
end
301309
function MOI.get(mock::MockOptimizer, attr::MOI.DualObjectiveValue)
302310
MOI.check_result_index_bounds(mock, attr)
303311
if mock.eval_dual_objective_value
304312
return get_fallback(mock, attr, Float64)
305313
else
306-
return mock.dual_objective_value
314+
return get(mock.dual_objective_value, attr.result_index, NaN)
307315
end
308316
end
309317
function MOI.get(mock::MockOptimizer, attr::MOI.PrimalStatus)
310318
if attr.N > mock.result_count
311319
return MOI.NO_SOLUTION
312320
else
313-
return mock.primalstatus
321+
return get(mock.primal_status, attr.N, MOI.NO_SOLUTION)
314322
end
315323
end
316324
function MOI.get(mock::MockOptimizer, attr::MOI.DualStatus)
317325
if attr.N > mock.result_count
318326
return MOI.NO_SOLUTION
319327
else
320-
return mock.dualstatus
328+
return get(mock.dual_status, attr.N, MOI.NO_SOLUTION)
321329
end
322330
end
323331
MOI.get(mock::MockOptimizer, ::MockModelAttribute) = mock.attribute
@@ -332,14 +340,8 @@ function MOI.get(
332340
mock::MockOptimizer, attr::MOI.VariablePrimal, idx::MOI.VariableIndex
333341
)
334342
MOI.check_result_index_bounds(mock, attr)
335-
primal = get(mock.varprimal, xor_index(idx), nothing)
336-
if primal !== nothing
337-
return primal
338-
elseif MOI.is_valid(mock, idx)
339-
error("No mock primal is set for variable `", idx, "`.")
340-
else
341-
throw(MOI.InvalidIndex(idx))
342-
end
343+
MOI.throw_if_not_valid(mock, idx)
344+
return _safe_get_result(mock.varprimal, attr, idx, "primal")
343345
end
344346

345347
function MOI.get(
@@ -379,23 +381,42 @@ function MOI.get(
379381
mock::MockOptimizer, attr::MOI.ConstraintDual, idx::MOI.ConstraintIndex{F}
380382
) where {F}
381383
MOI.check_result_index_bounds(mock, attr)
384+
MOI.throw_if_not_valid(mock, idx)
382385
if mock.eval_variable_constraint_dual &&
383386
(F == MOI.SingleVariable || F == MOI.VectorOfVariables)
384387
return get_fallback(mock, attr, idx)
385388
else
386-
dual = get(mock.condual, xor_index(idx), nothing)
387-
if dual === nothing
388-
if MOI.is_valid(mock, idx)
389-
error("No mock dual is set for constraint `", idx, "`.")
390-
else
391-
throw(MOI.InvalidIndex(idx))
392-
end
393-
end
394-
return dual
389+
return _safe_get_result(mock.condual, attr, idx, "dual")
395390
end
396391
end
397392
MOI.get(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex) = mock.conattribute[xor_index(idx)]
398-
MOI.get(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex) = mock.con_basis[xor_index(idx)]
393+
function MOI.get(mock::MockOptimizer, attr::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex)
394+
MOI.check_result_index_bounds(mock, attr)
395+
MOI.throw_if_not_valid(mock, idx)
396+
return _safe_get_result(mock.con_basis, attr, idx, "basis status")
397+
end
398+
399+
function _safe_set_result(dict::Dict{K,V}, attr::MOI.AnyAttribute, index::K,
400+
value) where {K, V}
401+
xored = xor_index(index)
402+
if !haskey(dict, xored)
403+
dict[xored] = V()
404+
end
405+
dict[xored][MOI._result_index_field(attr)] = value
406+
end
407+
function _safe_get_result(dict::Dict, attr::MOI.AnyAttribute, index::MOI.Index,
408+
name::String)
409+
index_name = index isa MOI.VariableIndex ? "variable" : "constraint"
410+
result_to_value = get(dict, xor_index(index), nothing)
411+
if result_to_value === nothing
412+
error("No mock $name is set for ", index_name, " `", index, "`.")
413+
end
414+
value = get(result_to_value, MOI._result_index_field(attr), nothing)
415+
if value === nothing
416+
error("No mock $name is set for ", index_name, " `", index, "` at result index `", MOI._result_index_field(attr), "`.")
417+
end
418+
return value
419+
end
399420

400421
MOI.get(::MockOptimizer, ::MOI.SolverName) = "Mock"
401422

@@ -408,10 +429,10 @@ function MOI.empty!(mock::MockOptimizer)
408429
mock.hasprimal = false
409430
mock.hasdual = false
410431
mock.terminationstatus = MOI.OPTIMIZE_NOT_CALLED
411-
mock.objective_value = NaN
412-
mock.dual_objective_value = NaN
413-
mock.primalstatus = MOI.NO_SOLUTION
414-
mock.dualstatus = MOI.NO_SOLUTION
432+
empty!(mock.objective_value)
433+
empty!(mock.dual_objective_value)
434+
empty!(mock.primal_status)
435+
empty!(mock.dual_status)
415436
empty!(mock.varprimal)
416437
empty!(mock.callback_variable_primal)
417438
empty!(mock.condual)
@@ -429,10 +450,10 @@ function MOI.is_empty(mock::MockOptimizer)
429450
return MOI.is_empty(mock.inner_model) && mock.attribute == 0 &&
430451
!mock.solved && !mock.hasprimal && !mock.hasdual &&
431452
mock.terminationstatus == MOI.OPTIMIZE_NOT_CALLED &&
432-
isnan(mock.objective_value) &&
433-
isnan(mock.dual_objective_value) &&
434-
mock.primalstatus == MOI.NO_SOLUTION &&
435-
mock.dualstatus == MOI.NO_SOLUTION &&
453+
isempty(mock.objective_value) &&
454+
isempty(mock.dual_objective_value) &&
455+
isempty(mock.primal_status) &&
456+
isempty(mock.dual_status) &&
436457
isempty(mock.con_basis) && isempty(mock.optimizer_attributes) &&
437458
isempty(mock.model_attributes) && isempty(mock.submitted)
438459
end
@@ -552,13 +573,13 @@ end
552573
rec_mock_optimize(mock::MockOptimizer, opt::Function) = opt
553574

554575
"""
555-
mock_optimize!(mock::MockOptimizer, termstatus::MOI.TerminationStatusCode, (primstatus::MOI.ResultStatusCode, varprim::Vector), dualstatus::MOI.ResultStatusCode, conduals::Pair...)
576+
mock_optimize!(mock::MockOptimizer, termstatus::MOI.TerminationStatusCode, (primstatus::MOI.ResultStatusCode, varprim::Vector), dual_status::MOI.ResultStatusCode, conduals::Pair...)
556577
557-
Sets the termination status of `mock` to `termstatus` and the primal (resp. dual) status to `primstatus` (resp. `dualstatus`).
578+
Sets the termination status of `mock` to `termstatus` and the primal (resp. dual) status to `primstatus` (resp. `dual_status`).
558579
The primal values of the variables in the order returned by `ListOfVariableIndices` are set to `varprim`.
559580
If `termstatus` is missing, it is assumed to be `MOI.OPTIMAL`.
560581
If `primstatus` is missing, it is assumed to be `MOI.FEASIBLE_POINT`.
561-
If `dualstatus` is missing, it is assumed to be `MOI.FEASIBLE_POINT` if there is a primal solution and `primstatus` is not `MOI.INFEASIBLE_POINT`, otherwise it is `MOI.INFEASIBILITY_CERTIFICATE`.
582+
If `dual_status` is missing, it is assumed to be `MOI.FEASIBLE_POINT` if there is a primal solution and `primstatus` is not `MOI.INFEASIBLE_POINT`, otherwise it is `MOI.INFEASIBILITY_CERTIFICATE`.
562583
The dual values are set to the values specified by `conduals`. Each pair is of the form `(F,S)=>[...]` where `[...]` is the the vector of dual values for the constraints `F`-in-`S` in the order returned by `ListOfConstraintIndices{F,S}`.
563584
The bases status are set to the status specified by `con_basis`. A vector of pairs, each of the form `(F,S)=>[...]`, where `[...]` is the the vector of basis status for the constraints `F`-in-`S` in the order returned by `ListOfConstraintIndices{F,S}`.
564585
"""
@@ -598,8 +619,8 @@ function mock_varprimal!(mock::MockOptimizer, varprim::Vector)
598619
end
599620

600621
# Dual
601-
function mock_dual!(mock::MockOptimizer, dualstatus::MOI.ResultStatusCode, conduals::Pair...)
602-
MOI.set(mock, MOI.DualStatus(), dualstatus)
622+
function mock_dual!(mock::MockOptimizer, dual_status::MOI.ResultStatusCode, conduals::Pair...)
623+
MOI.set(mock, MOI.DualStatus(), dual_status)
603624
mock_condual!(mock, conduals...)
604625
end
605626
# Default dual status

src/Utilities/results.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
Compute the objective function value using the `VariablePrimal` results and
1717
the `ObjectiveFunction` value.
1818
"""
19-
function get_fallback(model::MOI.ModelLike, ::MOI.ObjectiveValue)
19+
function get_fallback(model::MOI.ModelLike, attr::MOI.ObjectiveValue)
2020
F = MOI.get(model, MOI.ObjectiveFunctionType())
2121
f = MOI.get(model, MOI.ObjectiveFunction{F}())
2222
# TODO do not include constant if primal solution is a ray
23-
return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f)
23+
return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(attr.result_index), vi), f)
2424
end
2525

2626
function constraint_constant(model::MOI.ModelLike,
@@ -145,11 +145,11 @@ end
145145
Compute the value of the function of the constraint of index `constraint_index`
146146
using the `VariablePrimal` results and the `ConstraintFunction` values.
147147
"""
148-
function get_fallback(model::MOI.ModelLike, ::MOI.ConstraintPrimal,
148+
function get_fallback(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
149149
idx::MOI.ConstraintIndex)
150150
f = MOI.get(model, MOI.ConstraintFunction(), idx)
151151
# TODO do not include constant if primal solution is a ray
152-
return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f)
152+
return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(attr.N), vi), f)
153153
end
154154

155155
################ Constraint Dual for Variable-wise constraints #################

test/Utilities/mockoptimizer.jl

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ end
6161
v = MOI.add_variables(optimizer, 2)
6262
c1 = MOI.add_constraint(optimizer, MOI.SingleVariable(v[1]), MOI.GreaterThan(1.0))
6363
soc = MOI.add_constraint(optimizer, MOI.VectorOfVariables(v), MOI.SecondOrderCone(2))
64+
MOI.set(optimizer, MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(v[1]))
6465
MOI.set(optimizer, MOI.ResultCount(), 1)
6566
@test_throws(
6667
ErrorException("No mock primal is set for variable `$(v[1])`."),
@@ -80,24 +81,55 @@ end
8081
# TODO: Provide a more compact API for this.
8182
MOI.set(optimizer, MOI.TerminationStatus(), MOI.OPTIMAL)
8283
MOI.set(optimizer, MOI.ObjectiveValue(), 1.0)
83-
MOI.set(optimizer, MOI.ResultCount(), 1)
84+
MOI.set(optimizer, MOI.DualObjectiveValue(2), 2.0)
85+
MOI.set(optimizer, MOI.ResultCount(), 2)
8486
MOI.set(optimizer, MOI.PrimalStatus(), MOI.FEASIBLE_POINT)
85-
MOI.set(optimizer, MOI.DualStatus(), MOI.FEASIBLE_POINT)
87+
MOI.set(optimizer, MOI.DualStatus(2), MOI.FEASIBLE_POINT)
8688
MOI.set(optimizer, MOI.VariablePrimal(), v, [1.0, 2.0])
8789
MOI.set(optimizer, MOI.VariablePrimal(), v[1], 3.0)
88-
MOI.set(optimizer, MOI.ConstraintDual(), c1, 5.9)
89-
MOI.set(optimizer, MOI.ConstraintDual(), soc, [1.0,2.0])
90+
MOI.set(optimizer, MOI.ConstraintDual(2), c1, 5.9)
91+
MOI.set(optimizer, MOI.ConstraintDual(2), soc, [1.0,2.0])
9092

9193
MOI.optimize!(optimizer)
9294
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
93-
@test MOI.get(optimizer, MOI.ResultCount()) == 1
95+
@test MOI.get(optimizer, MOI.ResultCount()) == 2
9496
@test MOI.get(optimizer, MOI.ObjectiveValue()) == 1.0
97+
@test isnan(MOI.get(optimizer, MOI.ObjectiveValue(2)))
98+
optimizer.eval_objective_value = true
99+
@test MOI.get(optimizer, MOI.ObjectiveValue()) == 3.0
100+
@test_throws(
101+
ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."),
102+
MOI.get(optimizer, MOI.ObjectiveValue(2))
103+
)
104+
@test_throws(
105+
ErrorException("No mock dual is set for constraint `$c1` at result index `1`."),
106+
MOI.get(optimizer, MOI.DualObjectiveValue())
107+
)
108+
@test MOI.get(optimizer, MOI.DualObjectiveValue(2)) == 5.9
95109
@test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
96-
@test MOI.get(optimizer, MOI.DualStatus()) == MOI.FEASIBLE_POINT
110+
@test MOI.get(optimizer, MOI.DualStatus(2)) == MOI.FEASIBLE_POINT
97111
@test MOI.get(optimizer, MOI.VariablePrimal(), v) == [3.0, 2.0]
98112
@test MOI.get(optimizer, MOI.VariablePrimal(), v[1]) == 3.0
99-
@test MOI.get(optimizer, MOI.ConstraintDual(), c1) == 5.9
100-
@test MOI.get(optimizer, MOI.ConstraintDual(), soc) == [1.0,2.0]
113+
@test_throws(
114+
ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."),
115+
MOI.get(optimizer, MOI.VariablePrimal(2), v[1])
116+
)
117+
@test MOI.get(optimizer, MOI.ConstraintPrimal(), c1) == 3.0
118+
@test MOI.get(optimizer, MOI.ConstraintPrimal(), soc) == [3.0, 2.0]
119+
@test_throws(
120+
ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."),
121+
@show MOI.get(optimizer, MOI.ConstraintPrimal(2), c1)
122+
)
123+
@test_throws(
124+
ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."),
125+
MOI.get(optimizer, MOI.ConstraintPrimal(2), soc)
126+
)
127+
@test MOI.get(optimizer, MOI.ConstraintDual(2), c1) == 5.9
128+
@test MOI.get(optimizer, MOI.ConstraintDual(2), soc) == [1.0,2.0]
129+
@test_throws(
130+
ErrorException("No mock dual is set for constraint `$c1` at result index `1`."),
131+
MOI.get(optimizer, MOI.ConstraintDual(1), c1)
132+
)
101133
end
102134

103135
@testset "Delete" begin

0 commit comments

Comments
 (0)