From bb9630bcbe6fca76a6d92ebab92046955e18a754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 27 Feb 2020 12:18:20 +0100 Subject: [PATCH] Fix MockOptimizer for nonone result index --- src/Utilities/mockoptimizer.jl | 139 ++++++++++++++++++-------------- src/Utilities/results.jl | 8 +- test/Utilities/mockoptimizer.jl | 48 +++++++++-- 3 files changed, 124 insertions(+), 71 deletions(-) diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index cf7246af3c..5f73ba77fe 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -31,20 +31,20 @@ mutable struct MockOptimizer{MT<:MOI.ModelLike} <: MOI.AbstractOptimizer # Computes `ObjectiveValue` by evaluating the `ObjectiveFunction` with # `VariablePrimal`. See `get_fallback`. eval_objective_value::Bool - objective_value::Float64 # set this using MOI.set(model, MOI.ObjectiveValue(), value) + objective_value::Dict{Int,Float64} # set this using MOI.set(model, MOI.ObjectiveValue(), value) # Computes `DualObjectiveValue` using `get_fallback` eval_dual_objective_value::Bool - dual_objective_value::Float64 # set this using MOI.set(model, MOI.DualObjectiveValue(), value) - primalstatus::MOI.ResultStatusCode - dualstatus::MOI.ResultStatusCode - varprimal::Dict{MOI.VariableIndex,Float64} + dual_objective_value::Dict{Int,Float64} # set this using MOI.set(model, MOI.DualObjectiveValue(), value) + primal_status::Dict{Int,MOI.ResultStatusCode} + dual_status::Dict{Int,MOI.ResultStatusCode} + varprimal::Dict{MOI.VariableIndex,Dict{Int,Float64}} callback_variable_primal::Dict{MOI.VariableIndex, Float64} # Computes `ConstraintDual` of constraints with `SingleVariable` or # `VectorOfVariables` functions by evaluating the `ConstraintDual` of # constraints having the variable in the function. See `get_fallback`. eval_variable_constraint_dual::Bool - condual::Dict{MOI.ConstraintIndex,Any} - con_basis::Dict{MOI.ConstraintIndex,MOI.BasisStatusCode} + condual::Dict{MOI.ConstraintIndex,Dict{Int,Any}} + con_basis::Dict{MOI.ConstraintIndex,Dict{Int,MOI.BasisStatusCode}} # The attributes set by `MOI.optimize!` cannot be set to `model`. # We detect them with `is_set_by_optimize` and store them in the following: optimizer_attributes::Dict{MOI.AbstractOptimizerAttribute, Any} @@ -83,16 +83,16 @@ function MockOptimizer(inner_model::MOI.ModelLike; supports_names=true, 1, MOI.OPTIMIZE_NOT_CALLED, eval_objective_value, - NaN, + Dict{Int,Float64}(), eval_dual_objective_value, - NaN, - MOI.NO_SOLUTION, - MOI.NO_SOLUTION, - Dict{MOI.VariableIndex,Float64}(), + Dict{Int,Float64}(), + Dict{Int,MOI.ResultStatusCode}(), + Dict{Int,MOI.ResultStatusCode}(), + Dict{MOI.VariableIndex,Dict{Int,Float64}}(), Dict{MOI.VariableIndex,Float64}(), eval_variable_constraint_dual, - Dict{MOI.ConstraintIndex,Any}(), - Dict{MOI.ConstraintIndex,MOI.BasisStatusCode}(), + Dict{MOI.ConstraintIndex,Dict{Int,Any}}(), + Dict{MOI.ConstraintIndex,Dict{Int,MOI.BasisStatusCode}}(), Dict{MOI.AbstractOptimizerAttribute, Any}(), Dict{MOI.AbstractModelAttribute, Any}(), Dict{MOI.AbstractSubmittable, Vector{Tuple}}()) @@ -153,10 +153,18 @@ end MOI.supports(mock::MockOptimizer, ::MockModelAttribute) = true MOI.set(mock::MockOptimizer, ::MOI.TerminationStatus, value::MOI.TerminationStatusCode) = (mock.terminationstatus = value) -MOI.set(mock::MockOptimizer, ::MOI.ObjectiveValue, value::Real) = (mock.objective_value = value) -MOI.set(mock::MockOptimizer, ::MOI.DualObjectiveValue, value::Real) = (mock.dual_objective_value = value) -MOI.set(mock::MockOptimizer, ::MOI.PrimalStatus, value::MOI.ResultStatusCode) = (mock.primalstatus = value) -MOI.set(mock::MockOptimizer, ::MOI.DualStatus, value::MOI.ResultStatusCode) = (mock.dualstatus = value) +function MOI.set(mock::MockOptimizer, attr::MOI.ObjectiveValue, value::Real) + mock.objective_value[attr.result_index] = value +end +function MOI.set(mock::MockOptimizer, attr::MOI.DualObjectiveValue, value::Real) + mock.dual_objective_value[attr.result_index] = value +end +function MOI.set(mock::MockOptimizer, attr::MOI.PrimalStatus, value::MOI.ResultStatusCode) + mock.primal_status[attr.N] = value +end +function MOI.set(mock::MockOptimizer, attr::MOI.DualStatus, value::MOI.ResultStatusCode) + mock.dual_status[attr.N] = value +end MOI.set(mock::MockOptimizer, ::MockModelAttribute, value::Integer) = (mock.attribute = value) function MOI.supports(mock::MockOptimizer, attr::MOI.AbstractOptimizerAttribute) # `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, idx::MOI.VariableIndex, value) MOI.set(mock.inner_model, attr, xor_index(idx), xor_indices(value)) end -function MOI.set(mock::MockOptimizer, ::MOI.VariablePrimal, +function MOI.set(mock::MockOptimizer, attr::MOI.VariablePrimal, idx::MOI.VariableIndex, value) - mock.varprimal[xor_index(idx)] = value + _safe_set_result(mock.varprimal, attr, idx, value) end function MOI.set(mock::MockOptimizer, ::MOI.CallbackVariablePrimal, idx::MOI.VariableIndex, value) @@ -208,13 +216,13 @@ function MOI.set(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex, value) mock.conattribute[xor_index(idx)] = value end -function MOI.set(mock::MockOptimizer, ::MOI.ConstraintDual, +function MOI.set(mock::MockOptimizer, attr::MOI.ConstraintDual, idx::MOI.ConstraintIndex, value) - mock.condual[xor_index(idx)] = value + _safe_set_result(mock.condual, attr, idx, value) end -function MOI.set(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, +function MOI.set(mock::MockOptimizer, attr::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex, value) - mock.con_basis[xor_index(idx)] = value + _safe_set_result(mock.con_basis, attr, idx, value) end MOI.get(mock::MockOptimizer, ::MOI.RawSolver) = mock @@ -295,7 +303,7 @@ function MOI.get(mock::MockOptimizer, attr::MOI.ObjectiveValue) if mock.eval_objective_value return get_fallback(mock, attr) else - return mock.objective_value + return get(mock.objective_value, attr.result_index, NaN) end end function MOI.get(mock::MockOptimizer, attr::MOI.DualObjectiveValue) @@ -303,21 +311,21 @@ function MOI.get(mock::MockOptimizer, attr::MOI.DualObjectiveValue) if mock.eval_dual_objective_value return get_fallback(mock, attr, Float64) else - return mock.dual_objective_value + return get(mock.dual_objective_value, attr.result_index, NaN) end end function MOI.get(mock::MockOptimizer, attr::MOI.PrimalStatus) if attr.N > mock.result_count return MOI.NO_SOLUTION else - return mock.primalstatus + return get(mock.primal_status, attr.N, MOI.NO_SOLUTION) end end function MOI.get(mock::MockOptimizer, attr::MOI.DualStatus) if attr.N > mock.result_count return MOI.NO_SOLUTION else - return mock.dualstatus + return get(mock.dual_status, attr.N, MOI.NO_SOLUTION) end end MOI.get(mock::MockOptimizer, ::MockModelAttribute) = mock.attribute @@ -332,14 +340,8 @@ function MOI.get( mock::MockOptimizer, attr::MOI.VariablePrimal, idx::MOI.VariableIndex ) MOI.check_result_index_bounds(mock, attr) - primal = get(mock.varprimal, xor_index(idx), nothing) - if primal !== nothing - return primal - elseif MOI.is_valid(mock, idx) - error("No mock primal is set for variable `", idx, "`.") - else - throw(MOI.InvalidIndex(idx)) - end + MOI.throw_if_not_valid(mock, idx) + return _safe_get_result(mock.varprimal, attr, idx, "primal") end function MOI.get( @@ -379,23 +381,42 @@ function MOI.get( mock::MockOptimizer, attr::MOI.ConstraintDual, idx::MOI.ConstraintIndex{F} ) where {F} MOI.check_result_index_bounds(mock, attr) + MOI.throw_if_not_valid(mock, idx) if mock.eval_variable_constraint_dual && (F == MOI.SingleVariable || F == MOI.VectorOfVariables) return get_fallback(mock, attr, idx) else - dual = get(mock.condual, xor_index(idx), nothing) - if dual === nothing - if MOI.is_valid(mock, idx) - error("No mock dual is set for constraint `", idx, "`.") - else - throw(MOI.InvalidIndex(idx)) - end - end - return dual + return _safe_get_result(mock.condual, attr, idx, "dual") end end MOI.get(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex) = mock.conattribute[xor_index(idx)] -MOI.get(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex) = mock.con_basis[xor_index(idx)] +function MOI.get(mock::MockOptimizer, attr::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex) + MOI.check_result_index_bounds(mock, attr) + MOI.throw_if_not_valid(mock, idx) + return _safe_get_result(mock.con_basis, attr, idx, "basis status") +end + +function _safe_set_result(dict::Dict{K,V}, attr::MOI.AnyAttribute, index::K, + value) where {K, V} + xored = xor_index(index) + if !haskey(dict, xored) + dict[xored] = V() + end + dict[xored][MOI._result_index_field(attr)] = value +end +function _safe_get_result(dict::Dict, attr::MOI.AnyAttribute, index::MOI.Index, + name::String) + index_name = index isa MOI.VariableIndex ? "variable" : "constraint" + result_to_value = get(dict, xor_index(index), nothing) + if result_to_value === nothing + error("No mock $name is set for ", index_name, " `", index, "`.") + end + value = get(result_to_value, MOI._result_index_field(attr), nothing) + if value === nothing + error("No mock $name is set for ", index_name, " `", index, "` at result index `", MOI._result_index_field(attr), "`.") + end + return value +end MOI.get(::MockOptimizer, ::MOI.SolverName) = "Mock" @@ -408,10 +429,10 @@ function MOI.empty!(mock::MockOptimizer) mock.hasprimal = false mock.hasdual = false mock.terminationstatus = MOI.OPTIMIZE_NOT_CALLED - mock.objective_value = NaN - mock.dual_objective_value = NaN - mock.primalstatus = MOI.NO_SOLUTION - mock.dualstatus = MOI.NO_SOLUTION + empty!(mock.objective_value) + empty!(mock.dual_objective_value) + empty!(mock.primal_status) + empty!(mock.dual_status) empty!(mock.varprimal) empty!(mock.callback_variable_primal) empty!(mock.condual) @@ -429,10 +450,10 @@ function MOI.is_empty(mock::MockOptimizer) return MOI.is_empty(mock.inner_model) && mock.attribute == 0 && !mock.solved && !mock.hasprimal && !mock.hasdual && mock.terminationstatus == MOI.OPTIMIZE_NOT_CALLED && - isnan(mock.objective_value) && - isnan(mock.dual_objective_value) && - mock.primalstatus == MOI.NO_SOLUTION && - mock.dualstatus == MOI.NO_SOLUTION && + isempty(mock.objective_value) && + isempty(mock.dual_objective_value) && + isempty(mock.primal_status) && + isempty(mock.dual_status) && isempty(mock.con_basis) && isempty(mock.optimizer_attributes) && isempty(mock.model_attributes) && isempty(mock.submitted) end @@ -552,13 +573,13 @@ end rec_mock_optimize(mock::MockOptimizer, opt::Function) = opt """ - mock_optimize!(mock::MockOptimizer, termstatus::MOI.TerminationStatusCode, (primstatus::MOI.ResultStatusCode, varprim::Vector), dualstatus::MOI.ResultStatusCode, conduals::Pair...) + mock_optimize!(mock::MockOptimizer, termstatus::MOI.TerminationStatusCode, (primstatus::MOI.ResultStatusCode, varprim::Vector), dual_status::MOI.ResultStatusCode, conduals::Pair...) -Sets the termination status of `mock` to `termstatus` and the primal (resp. dual) status to `primstatus` (resp. `dualstatus`). +Sets the termination status of `mock` to `termstatus` and the primal (resp. dual) status to `primstatus` (resp. `dual_status`). The primal values of the variables in the order returned by `ListOfVariableIndices` are set to `varprim`. If `termstatus` is missing, it is assumed to be `MOI.OPTIMAL`. If `primstatus` is missing, it is assumed to be `MOI.FEASIBLE_POINT`. -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`. +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`. 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}`. 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}`. """ @@ -598,8 +619,8 @@ function mock_varprimal!(mock::MockOptimizer, varprim::Vector) end # Dual -function mock_dual!(mock::MockOptimizer, dualstatus::MOI.ResultStatusCode, conduals::Pair...) - MOI.set(mock, MOI.DualStatus(), dualstatus) +function mock_dual!(mock::MockOptimizer, dual_status::MOI.ResultStatusCode, conduals::Pair...) + MOI.set(mock, MOI.DualStatus(), dual_status) mock_condual!(mock, conduals...) end # Default dual status diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index c0b8e5d488..1edb7d1624 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -16,11 +16,11 @@ Compute the objective function value using the `VariablePrimal` results and the `ObjectiveFunction` value. """ -function get_fallback(model::MOI.ModelLike, ::MOI.ObjectiveValue) +function get_fallback(model::MOI.ModelLike, attr::MOI.ObjectiveValue) F = MOI.get(model, MOI.ObjectiveFunctionType()) f = MOI.get(model, MOI.ObjectiveFunction{F}()) # TODO do not include constant if primal solution is a ray - return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) + return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(attr.result_index), vi), f) end function constraint_constant(model::MOI.ModelLike, @@ -145,11 +145,11 @@ end Compute the value of the function of the constraint of index `constraint_index` using the `VariablePrimal` results and the `ConstraintFunction` values. """ -function get_fallback(model::MOI.ModelLike, ::MOI.ConstraintPrimal, +function get_fallback(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, idx::MOI.ConstraintIndex) f = MOI.get(model, MOI.ConstraintFunction(), idx) # TODO do not include constant if primal solution is a ray - return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) + return eval_variables(vi -> MOI.get(model, MOI.VariablePrimal(attr.N), vi), f) end ################ Constraint Dual for Variable-wise constraints ################# diff --git a/test/Utilities/mockoptimizer.jl b/test/Utilities/mockoptimizer.jl index a1b832d797..70c55a0bbc 100644 --- a/test/Utilities/mockoptimizer.jl +++ b/test/Utilities/mockoptimizer.jl @@ -61,6 +61,7 @@ end v = MOI.add_variables(optimizer, 2) c1 = MOI.add_constraint(optimizer, MOI.SingleVariable(v[1]), MOI.GreaterThan(1.0)) soc = MOI.add_constraint(optimizer, MOI.VectorOfVariables(v), MOI.SecondOrderCone(2)) + MOI.set(optimizer, MOI.ObjectiveFunction{MOI.SingleVariable}(), MOI.SingleVariable(v[1])) MOI.set(optimizer, MOI.ResultCount(), 1) @test_throws( ErrorException("No mock primal is set for variable `$(v[1])`."), @@ -80,24 +81,55 @@ end # TODO: Provide a more compact API for this. MOI.set(optimizer, MOI.TerminationStatus(), MOI.OPTIMAL) MOI.set(optimizer, MOI.ObjectiveValue(), 1.0) - MOI.set(optimizer, MOI.ResultCount(), 1) + MOI.set(optimizer, MOI.DualObjectiveValue(2), 2.0) + MOI.set(optimizer, MOI.ResultCount(), 2) MOI.set(optimizer, MOI.PrimalStatus(), MOI.FEASIBLE_POINT) - MOI.set(optimizer, MOI.DualStatus(), MOI.FEASIBLE_POINT) + MOI.set(optimizer, MOI.DualStatus(2), MOI.FEASIBLE_POINT) MOI.set(optimizer, MOI.VariablePrimal(), v, [1.0, 2.0]) MOI.set(optimizer, MOI.VariablePrimal(), v[1], 3.0) - MOI.set(optimizer, MOI.ConstraintDual(), c1, 5.9) - MOI.set(optimizer, MOI.ConstraintDual(), soc, [1.0,2.0]) + MOI.set(optimizer, MOI.ConstraintDual(2), c1, 5.9) + MOI.set(optimizer, MOI.ConstraintDual(2), soc, [1.0,2.0]) MOI.optimize!(optimizer) @test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL - @test MOI.get(optimizer, MOI.ResultCount()) == 1 + @test MOI.get(optimizer, MOI.ResultCount()) == 2 @test MOI.get(optimizer, MOI.ObjectiveValue()) == 1.0 + @test isnan(MOI.get(optimizer, MOI.ObjectiveValue(2))) + optimizer.eval_objective_value = true + @test MOI.get(optimizer, MOI.ObjectiveValue()) == 3.0 + @test_throws( + ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."), + MOI.get(optimizer, MOI.ObjectiveValue(2)) + ) + @test_throws( + ErrorException("No mock dual is set for constraint `$c1` at result index `1`."), + MOI.get(optimizer, MOI.DualObjectiveValue()) + ) + @test MOI.get(optimizer, MOI.DualObjectiveValue(2)) == 5.9 @test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - @test MOI.get(optimizer, MOI.DualStatus()) == MOI.FEASIBLE_POINT + @test MOI.get(optimizer, MOI.DualStatus(2)) == MOI.FEASIBLE_POINT @test MOI.get(optimizer, MOI.VariablePrimal(), v) == [3.0, 2.0] @test MOI.get(optimizer, MOI.VariablePrimal(), v[1]) == 3.0 - @test MOI.get(optimizer, MOI.ConstraintDual(), c1) == 5.9 - @test MOI.get(optimizer, MOI.ConstraintDual(), soc) == [1.0,2.0] + @test_throws( + ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."), + MOI.get(optimizer, MOI.VariablePrimal(2), v[1]) + ) + @test MOI.get(optimizer, MOI.ConstraintPrimal(), c1) == 3.0 + @test MOI.get(optimizer, MOI.ConstraintPrimal(), soc) == [3.0, 2.0] + @test_throws( + ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."), + @show MOI.get(optimizer, MOI.ConstraintPrimal(2), c1) + ) + @test_throws( + ErrorException("No mock primal is set for variable `$(v[1])` at result index `2`."), + MOI.get(optimizer, MOI.ConstraintPrimal(2), soc) + ) + @test MOI.get(optimizer, MOI.ConstraintDual(2), c1) == 5.9 + @test MOI.get(optimizer, MOI.ConstraintDual(2), soc) == [1.0,2.0] + @test_throws( + ErrorException("No mock dual is set for constraint `$c1` at result index `1`."), + MOI.get(optimizer, MOI.ConstraintDual(1), c1) + ) end @testset "Delete" begin