diff --git a/docs/src/apireference.md b/docs/src/apireference.md index b99db0f704..177f3292d1 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -80,6 +80,7 @@ List of attributes useful for optimizers RawSolver ResultCount ObjectiveFunction +ObjectiveFunctionType ObjectiveValue ObjectiveBound RelativeGap diff --git a/src/Test/contconic.jl b/src/Test/contconic.jl index 3fc165591e..4bcb4513b6 100644 --- a/src/Test/contconic.jl +++ b/src/Test/contconic.jl @@ -1277,7 +1277,7 @@ function _psd0test(model::MOI.ModelLike, vecofvars::Bool, psdcone, config::TestC @test MOI.canget(model, MOI.ConstraintDual(), typeof(c)) @test MOI.get(model, MOI.ConstraintDual(), c) ≈ 2 atol=atol rtol=rtol - cXv = square ? [1, -1, -1, 1] : [1, -1, 1] + cXv = square ? [1, -2, 0, 1] : [1, -1, 1] @test MOI.canget(model, MOI.ConstraintDual(), typeof(cX)) @test MOI.get(model, MOI.ConstraintDual(), cX) ≈ cXv atol=atol rtol=rtol end diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index 2282c406a7..9f4d75c625 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -1,6 +1,7 @@ module Utilities using Compat # For firstindex, lastindex and Nothing +using Compat.LinearAlgebra # For dot using MathOptInterface const MOI = MathOptInterface @@ -20,6 +21,7 @@ const CI{F,S} = MOI.ConstraintIndex{F,S} include("functions.jl") include("sets.jl") include("copy.jl") +include("results.jl") include("model.jl") include("parser.jl") diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index 89a5c73a2a..747153a009 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -26,12 +26,18 @@ mutable struct MockOptimizer{MT<:MOI.ModelLike} <: MOI.AbstractOptimizer hasdual::Bool terminationstatus::MOI.TerminationStatusCode resultcount::Int - evalobjective::Bool # Computes ObjectiveValue by evaluation ObjectiveFunction with VariablePrimal + # Computes `ObjectiveValue` by evaluating the `ObjectiveFunction` with + # `VariablePrimal`. See `get_fallback`. + eval_objective_value::Bool objectivevalue::Float64 objectivebound::Float64 # set this using MOI.set!(model, MOI.ObjectiveBound(), value) primalstatus::MOI.ResultStatusCode dualstatus::MOI.ResultStatusCode varprimal::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} end @@ -42,27 +48,31 @@ xor_index(vi::VI) = VI(xor(vi.value, internal_xor_mask)) xor_index(ci::CI{F,S}) where {F,S} = CI{F,S}(xor(ci.value, internal_xor_mask)) xor_variables(f) = mapvariables(xor_index, f) -MockOptimizer(inner_model::MOI.ModelLike; needsallocateload=false, evalobjective=true) = - MockOptimizer(inner_model, - 0, - Dict{MOI.VariableIndex,Int}(), - Dict{MOI.ConstraintIndex,Int}(), - needsallocateload, - true, - true, - (::MockOptimizer) -> begin end, - false, - false, - false, - MOI.Success, - 0, - evalobjective, - NaN, - NaN, - MOI.UnknownResultStatus, - MOI.UnknownResultStatus, - Dict{MOI.VariableIndex,Float64}(), - Dict{MOI.ConstraintIndex,Any}()) +function MockOptimizer(inner_model::MOI.ModelLike; needsallocateload=false, + eval_objective_value=true, + eval_variable_constraint_dual=true) + return MockOptimizer(inner_model, + 0, + Dict{MOI.VariableIndex,Int}(), + Dict{MOI.ConstraintIndex,Int}(), + needsallocateload, + true, + true, + (::MockOptimizer) -> begin end, + false, + false, + false, + MOI.Success, + 0, + eval_objective_value, + NaN, + NaN, + MOI.UnknownResultStatus, + MOI.UnknownResultStatus, + Dict{MOI.VariableIndex,Float64}(), + eval_variable_constraint_dual, + Dict{MOI.ConstraintIndex,Any}()) +end function MOI.addvariable!(mock::MockOptimizer) if mock.canaddvar @@ -154,17 +164,11 @@ MOI.get(b::MockOptimizer, IdxT::Type{<:MOI.Index}, name::String) = xor_index(MOI MOI.get(mock::MockOptimizer, ::MOI.ResultCount) = mock.resultcount MOI.get(mock::MockOptimizer, ::MOI.TerminationStatus) = mock.terminationstatus -# Gets the ObjectiveFunction attribute set to mock, i.e. the type of the scalar function is unknown -_getobjfunattr() = error("Objective Function not set") -_getobjfunattr(objfun::MOI.ObjectiveFunction, args...) = objfun -_getobjfunattr(::MOI.AbstractModelAttribute, args...) = _getobjfunattr(args...) -_getobjfunattr(mock::MockOptimizer) = _getobjfunattr(MOI.get(mock, MOI.ListOfModelAttributesSet())...) -function MOI.get(mock::MockOptimizer, ::MOI.ObjectiveValue) - if mock.evalobjective - f = MOI.get(mock, _getobjfunattr(mock)) - evalvariables(vi -> MOI.get(mock, MOI.VariablePrimal(), vi), f) +function MOI.get(mock::MockOptimizer, attr::MOI.ObjectiveValue) + if mock.eval_objective_value + return get_fallback(mock, attr) else - mock.objectivevalue + return mock.objectivevalue end end MOI.get(mock::MockOptimizer, ::MOI.PrimalStatus) = mock.primalstatus @@ -174,12 +178,20 @@ MOI.get(mock::MockOptimizer, ::MockModelAttribute) = mock.attribute MOI.get(mock::MockOptimizer, attr::MOI.AbstractVariableAttribute, idx::MOI.VariableIndex) = MOI.get(mock.inner_model, attr, xor_index(idx)) MOI.get(mock::MockOptimizer, ::MockVariableAttribute, idx::MOI.VariableIndex) = mock.varattribute[xor_index(idx)] MOI.get(mock::MockOptimizer, ::MOI.VariablePrimal, idx::MOI.VariableIndex) = mock.varprimal[xor_index(idx)] -function MOI.get(mock::MockOptimizer, ::MOI.ConstraintPrimal, idx::MOI.ConstraintIndex) - f = MOI.get(mock, MOI.ConstraintFunction(), idx) - evalvariables(vi -> MOI.get(mock, MOI.VariablePrimal(), vi), f) +function MOI.get(mock::MockOptimizer, attr::MOI.ConstraintPrimal, + idx::MOI.ConstraintIndex) + return get_fallback(mock, attr, idx) end MOI.get(mock::MockOptimizer, attr::MOI.AbstractConstraintAttribute, idx::MOI.ConstraintIndex) = MOI.get(mock.inner_model, attr, xor_index(idx)) -MOI.get(mock::MockOptimizer, ::MOI.ConstraintDual, idx::MOI.ConstraintIndex) = mock.condual[xor_index(idx)] +function MOI.get(mock::MockOptimizer, attr::MOI.ConstraintDual, + idx::MOI.ConstraintIndex{F}) where F + if mock.eval_variable_constraint_dual && + (F == MOI.SingleVariable || F == MOI.VectorOfVariables) + return get_fallback(mock, attr, idx) + else + return mock.condual[xor_index(idx)] + end +end MOI.get(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex) = mock.conattribute[xor_index(idx)] MOI.supports(mock::MockOptimizer, ::MOI.ObjectiveBound) = true diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 274eb28fe2..d0a1c608dc 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -226,6 +226,10 @@ function MOI.set!(model::AbstractModel, ::MOI.ObjectiveSense, sense::MOI.Optimiz model.senseset = true model.sense = sense end +MOI.canget(model::AbstractModel, ::MOI.ObjectiveFunctionType) = true +function MOI.get(model::AbstractModel, ::MOI.ObjectiveFunctionType) + return MOI.typeof(model.objective) +end MOI.canget(model::AbstractModel, ::MOI.ObjectiveFunction{T}) where T = model.objectiveset && typeof(model.objective) == T function MOI.get(model::AbstractModel, ::MOI.ObjectiveFunction{T})::T where T if typeof(model.objective) != T diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl new file mode 100644 index 0000000000..01e80e0bee --- /dev/null +++ b/src/Utilities/results.jl @@ -0,0 +1,332 @@ +# This file contains the implementation of different methods for the +# `get_fallback` function. These methods can be used by solver wrappers as +# fallbacks for implementing the `get` method when the solver API does not +# provide the required result. For instance, if the solver does not provide the +# value of the constraints, the solver wrapper can write +# ```julia +# function MOI.get(model::Optimizer, attr::MOI.ConstraintPrimal, +# ci::MOI.ConstraintIndex) +# return MOIU.get_fallback(model, attr, ci) +# end +# ``` + +""" + get_fallback(model::MOI.ModelLike, ::MOI.ObjectiveValue) + +Compute the objective function value using the `VariablePrimal` results and +the `ObjectiveFunction` value. +""" +function get_fallback(model::MOI.ModelLike, ::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 evalvariables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) +end + +""" + get_fallback(model::MOI.ModelLike, ::MOI.ConstraintPrimal, + constraint_index::MOI.ConstraintIndex) + +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, + idx::MOI.ConstraintIndex) + f = MOI.get(model, MOI.ConstraintFunction(), idx) + # TODO do not include constant if primal solution is a ray + return evalvariables(vi -> MOI.get(model, MOI.VariablePrimal(), vi), f) +end + +################ Constraint Dual for Variable-wise constraints ################# +# +# In the primal we have +# min a_0' x + b_0 +# A_i x + b_i in C_i for all i +# In the dual we have +# max b_0 - sum b_i' y +# a_0 - sum A_i* y_i = 0 +# y_i in C_i* for all i +# where A_i* is the adjoint operator of the linear operator A_i. That is, A* +# is the linear operator such that +# ⟨A x, y⟩_{C_i} = ⟨x, A* y⟩_Rn +# where +# * ⟨., .⟩_Rn is the standard scalar product over Rn: ⟨., .⟩_Rn and +# * ⟨., .⟩_{C_i} is the scalar product `set_dot` defined for the set C_i +# +# Suppose we want to get the constraint variable of a variable-wise constraint: +# A_j x in C_j +# where A_j is zero except on a submatrix which is the identity. We have +# A_j* y_j = a_0 - sum_(i != j) A_i* y_i +# Thus to get the dual y_j, we simply have to compute the right-hand side and +# then invert A_j*. To get the kth element of A_i* y_i we need to compute +# ⟨e_k, A_i* y_i⟩_Rn = ⟨A_i e_k, y_i⟩_{C_i}. A_i e_k is computed using +# `variable_coefficient` and then it is combined with the dual y_i with +# `set_dot`. +# Once A_j* y_j is obtained, we invert A_j* with `dot_coefficients`. + +function variable_coefficient(func::MOI.ScalarAffineFunction{T}, + vi::MOI.VariableIndex) where T + coef = zero(T) + for term in func.terms + if term.variable_index == vi + coef += term.coefficient + end + end + return coef +end +function variable_coefficient(func::MOI.VectorAffineFunction{T}, + vi::MOI.VariableIndex) where T + coef = zeros(T, MOI.output_dimension(func)) + for vector_term in func.terms + term = vector_term.scalar_term + if term.variable_index == vi + coef[vector_term.output_index] += term.coefficient + end + end + return coef +end + +""" + variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + vi::MOI.VariableIndex, + ci::MOI.ConstraintIndex{<:Union{MOI.ScalarAffineFunction, + MOI.VectorAffineFunction}) + +Return dual of the constraint of index `ci` multiplied by the coefficient of +`vi` in the `MOI.ConstraintFunction`. +""" +function variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + vi::MOI.VariableIndex, + ci::MOI.ConstraintIndex{<:MOI.VectorAffineFunction}) + func = MOI.get(model, MOI.ConstraintFunction(), ci) + set = MOI.get(model, MOI.ConstraintSet(), ci) + coef = variable_coefficient(func, vi) + dual = MOI.get(model, attr, ci) + return set_dot(coef, dual, set) +end +function variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + vi::MOI.VariableIndex, + ci::MOI.ConstraintIndex{<:MOI.ScalarAffineFunction}) + func = MOI.get(model, MOI.ConstraintFunction(), ci) + coef = variable_coefficient(func, vi) + dual = MOI.get(model, attr, ci) + return coef * dual +end + +""" + variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex, + vi::MOI.VariableIndex, + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) + +Return sum of the the dual of the `F`-in-`S` constraints except `ci` multiplied +by the coefficient of `vi` in the `MOI.ConstraintFunction`. It errors if another +variable-wise constraint different than `ci` uses `vi`. +""" +function variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex, + vi::MOI.VariableIndex, + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) + dual = 0.0 + for constraint_index in MOI.get(model, MOI.ListOfConstraintIndices{F, S}()) + dual += variable_dual(model, attr, vi, constraint_index) + end + return dual +end +function variable_dual(model::MOI.ModelLike, + ::MOI.ConstraintDual, + ci::MOI.ConstraintIndex, + vi::MOI.VariableIndex, + F::Type{<:Union{MOI.SingleVariable, + MOI.VectorOfVariables}}, + S::Type{<:MOI.AbstractSet}) + for constraint_index in MOI.get(model, MOI.ListOfConstraintIndices{F, S}()) + if constraint_index != ci + func = MOI.get(model, MOI.ConstraintFunction(), constraint_index) + if (F == MOI.SingleVariable && func.variable == vi) || + (F == MOI.VectorOfVariables && vi in func.variables) + error("Fallback getter for variable constraint dual does not", + "support other variable-wise constraints on the variable.", + "Please report this issue to the solver wrapper package.") + end + end + end + return 0.0 +end +function variable_dual(::MOI.ModelLike, + ::MOI.ConstraintDual, + ::MOI.ConstraintIndex, + ::MOI.VariableIndex, + ::Type{<:Union{MOI.ScalarQuadraticFunction, + MOI.VectorQuadraticFunction}}, + ::Type{<:MOI.AbstractSet}) + error("Fallback getter for variable constraint dual only supports affine", + "constraint functions.", + "Please report this issue to the solver wrapper package.") +end + +""" + variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex, + vi::MOI.VariableIndex) + +Return the dual of the variable `vi` by using the duals of constraints +of index different than `ci`. It errors if another variable-wise constraint +different than `ci` uses `vi`. +""" +function variable_dual(model::MOI.ModelLike, + attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex, + vi::MOI.VariableIndex) + status = MOI.get(model, MOI.DualStatus()) + ray = status == MOI.InfeasibilityCertificate || + status == MOI.NearlyInfeasibilityCertificate + dual = 0.0 + if !ray + sense = MOI.get(model, MOI.ObjectiveSense()) + # Dual definition for maximization problem corresponds to dual + # definition for minimization problem with flipped objectived in MOI + sign = sense == MOI.MaxSense ? -1.0 : 1.0 + F = MOI.get(model, MOI.ObjectiveFunctionType()) + obj_attr = MOI.ObjectiveFunction{F}() + if F == MOI.SingleVariable + if MOI.get(model, obj_attr).variable == vi + dual += sign + end + elseif F <: MOI.ScalarAffineFunction + f = MOI.get(model, obj_attr) + dual += sign * variable_coefficient(f, vi) + else + error("Fallback getter for variable constraint dual only supports", + "affine objective function.", + "Please report this issue to the solver wrapper package.") + end + end + for FS in MOI.get(model, MOI.ListOfConstraints()) + dual -= variable_dual(model, attr, ci, vi, FS[1], FS[2]) + end + return dual +end + +""" + variable_dual(model::MOI.ModelLike, attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{F}, + func::F) where F <: Union{MOI.SingleVariable, + MOI.VectorOfVariables} + +Return the dual of the constraint of index `ci` for which the value of the +`MOI.ConstraintFunction` attribute is `func`. +""" +function variable_dual(model::MOI.ModelLike, attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{MOI.SingleVariable}, + func::MOI.SingleVariable) + return variable_dual(model, attr, ci, func.variable) +end +function variable_dual(model::MOI.ModelLike, attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables}, + func::MOI.VectorOfVariables) + dual = map(vi -> variable_dual(model, attr, ci, vi), func.variables) + set = MOI.get(model, MOI.ConstraintSet(), ci) + return dot_coefficients(dual, set) +end + +""" + get_fallback(model::MOI.ModelLike, attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{Union{MOI.SingleVariable, + MOI.VectorOfVariables}}) + +Compute the dual of the constraint of index `ci` using the `ConstraintDual` of +other constraints and the `ConstraintFunction` values. Throws an error if some +constraints are quadratic or if there is one another `MOI.SingleVariable`-in-`S` +or `MOI.VectorOfVariables`-in-`S` constraint with one of the variables in the +function of the constraint `ci`. +""" +function get_fallback(model::MOI.ModelLike, attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{<:Union{MOI.SingleVariable, + MOI.VectorOfVariables}}) + func = MOI.get(model, MOI.ConstraintFunction(), ci) + return variable_dual(model, attr, ci, func) +end + +# Scalar product. Any vector set defined that does not use the standard scalar +# product between vectors of ``R^n`` should redefine `set_dot` and +# `dot_coefficients`. + +""" + set_dot(x::Vector, y::Vector, set::AbstractVectorSet) + +Return the scalar product between a vector `x` of the set `set` and a vector +`y` of the dual of the set `s`. +""" +function set_dot(x::Vector, y::Vector, set::MOI.AbstractVectorSet) + return dot(x, y) +end + +function triangle_dot(x::Vector{T}, y::Vector{T}, dim::Int, offset::Int) where T + result = zero(T) + k = offset + for i in 1:dim + for j in 1:i + k += 1 + if i == j + result += x[k] * y[k] + else + result += 2 * x[k] * y[k] + end + end + end + return result +end + +function set_dot(x::Vector, y::Vector, + set::MOI.PositiveSemidefiniteConeTriangle) + return triangle_dot(x, y, set.side_dimension, 0) +end + +function set_dot(x::Vector, y::Vector, set::Union{MOI.LogDetConeTriangle, + MOI.RootDetConeTriangle}) + return x[1] * y[1] + triangle_dot(x, y, set.side_dimension, 1) +end + +""" + dot_coefficients(a::Vector, set::AbstractVectorSet) + +Return the vector `b` such that for all vector `x` of the set `set`, +`set_dot(b, x, set)` is equal to `dot(a, x)`. +""" +function dot_coefficients(a::Vector, set::MOI.AbstractVectorSet) + return a +end + +function triangle_coefficients!(b::Vector{T}, dim::Int, offset::Int) where T + k = offset + for i in 1:dim + for j in 1:i + k += 1 + if i != j + b[k] /= 2 + end + end + end +end + +function dot_coefficients(a::Vector, set::MOI.PositiveSemidefiniteConeTriangle) + b = copy(a) + triangle_coefficients!(b, set.side_dimension, 0) + return b +end + +function dot_coefficients(a::Vector, set::Union{MOI.LogDetConeTriangle, + MOI.RootDetConeTriangle}) + b = copy(a) + triangle_coefficients!(b, set.side_dimension, 1) + return b +end diff --git a/src/attributes.jl b/src/attributes.jl index 502054753e..8e28540255 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -380,6 +380,24 @@ it has non-integer coefficient and `F` is `ScalarAffineFunction{Int}`. """ struct ObjectiveFunction{F<:AbstractScalarFunction} <: AbstractModelAttribute end +""" + ObjectiveFunctionType() + +A model attribute for the type `F` of the objective function set using the +`ObjectiveFunction{F}` attribute. + +## Examples + +In the following code, `attr` should be equal to `MOI.SingleVariable`: +```julia +x = MOI.addvariable!(model) +MOI.set!(model, MOI.ObjectiveFunction{MOI.SingleVariable}(), + MOI.SingleVariable(x)) +attr = MOI.get(model, MOI.ObjectiveFunctionType()) +``` +""" +struct ObjectiveFunctionType <: AbstractModelAttribute end + ## Optimizer attributes """ diff --git a/src/sets.jl b/src/sets.jl index e4d50240a7..49128f8620 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -349,6 +349,7 @@ struct RootDetConeSquare <: AbstractVectorSet end dimension(s::Union{LogDetConeTriangle, RootDetConeTriangle}) = 1 + div(s.side_dimension * (s.side_dimension + 1), 2) + dimension(s::Union{LogDetConeSquare, RootDetConeSquare}) = 1 + s.side_dimension^2 """ diff --git a/test/Test/contconic.jl b/test/Test/contconic.jl index 67c2b92519..5d57391ae1 100644 --- a/test/Test/contconic.jl +++ b/test/Test/contconic.jl @@ -4,7 +4,6 @@ @testset "Linear" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0], - (MOI.VectorOfVariables, MOI.Nonnegatives) => [[0, 2, 0]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]]) MOIT.lin1vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0], @@ -12,9 +11,6 @@ (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-3, -1]]) MOIT.lin1ftest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, -3, 16, 0], - (MOI.VectorOfVariables, MOI.Nonnegatives) => [[0]], - (MOI.VectorOfVariables, MOI.Nonpositives) => [[0]], - (MOI.VectorOfVariables, MOI.Zeros) => [[7]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[7, 2, -4]]) MOIT.lin2vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, -3, 16, 0], @@ -37,7 +33,6 @@ end @testset "SOC" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], - (MOI.VectorOfVariables, MOI.SecondOrderCone) => [[√2, -1, -1]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) MOIT.soc1vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], @@ -57,15 +52,17 @@ mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.InfeasiblePoint, MOI.InfeasibilityCertificate) MOIT.soc3test(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 2/√5, 1/√5, 2/√5, 1/√5], - (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√5, -2.0, -1.0]], - (MOI.VectorOfVariables, MOI.SecondOrderCone) => [[√5, -2.0, -1.0]]) + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√5, -2.0, -1.0]]) MOIT.soc4test(mock, config) end @testset "RSOC" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2, 1/√2, 0.5, 1.0], (MOI.SingleVariable, MOI.EqualTo{Float64}) => [-√2, -1/√2], (MOI.VectorOfVariables, MOI.RotatedSecondOrderCone) => [[√2, 1/√2, -1.0, -1.0]]) + # double variable bounds on a and b variables + mock.eval_variable_constraint_dual = false MOIT.rotatedsoc1vtest(mock, config) + mock.eval_variable_constraint_dual = true mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2, 1/√2], (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[√2, 1/√2, -1.0, -1.0]]) MOIT.rotatedsoc1ftest(mock, config) @@ -74,7 +71,10 @@ (MOI.SingleVariable, MOI.EqualTo{Float64}) => [-1], (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1], (MOI.VectorOfVariables , MOI.RotatedSecondOrderCone) => [[1, 1, -1]]) + # double variable bounds on x, y, z variables + mock.eval_variable_constraint_dual = false MOIT.rotatedsoc2test(mock, config) + mock.eval_variable_constraint_dual = true n = 2 ub = 3.0 mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0; zeros(n-1); ub; √ub; ones(2)], @@ -83,7 +83,10 @@ (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0], (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1/(2*√ub)], (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => [[√ub/(2*√2); √ub/(2*√2); -√ub/2; zeros(n-1)], [√ub/√2, 1/√(2*ub), -1.0]]) + # double variable bounds on u + mock.eval_variable_constraint_dual = false MOIT.rotatedsoc3test(mock, config) + mock.eval_variable_constraint_dual = true end @testset "GeoMean" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4)) @@ -91,7 +94,6 @@ end @testset "Exponential" begin mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1., 2., 2exp(1/2)], - (MOI.VectorOfVariables, MOI.ExponentialCone) => [[-exp(1/2), -exp(1/2)/2, 1.]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [1 + exp(1/2), 1 + exp(1/2)/2]) MOIT.exp1vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1., 2., 2exp(1/2)], @@ -106,14 +108,12 @@ MOIT.exp2test(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [log(5), 5.], (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0.], - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1/5], (MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [[-1., log(5)-1, 1/5]]) MOIT.exp3test(mock, config) end @testset "PSD" begin # PSD0 mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(3), - (MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle) => [[1, -1, 1]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2]) MOIT.psdt0vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(3), @@ -121,11 +121,10 @@ (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2]) MOIT.psdt0ftest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4), - (MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeSquare) => [[1, -1, -1, 1]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2]) MOIT.psds0vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4), - (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeSquare) => [[1, -1, -1, 1]], + (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeSquare) => [[1, -2, 0, 1]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2]) MOIT.psds0ftest(mock, config) # PSD1 @@ -145,25 +144,19 @@ cX2 = -y2 cXv = [cX0, cX1, cX0, cX2, cX1, cX0] mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [Xv; xv], - (MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle) => [cXv], - (MOI.VectorOfVariables, MOI.SecondOrderCone) => [[1-y1, -y2, -y2]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [y1, y2]) MOIT.psdt1vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [Xv; xv], (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [cXv], - (MOI.VectorOfVariables, MOI.SecondOrderCone) => [[1-y1, -y2, -y2]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [y1, y2]) MOIT.psdt1ftest(mock, config) Xv = [α^2, α*β, α^2, α*β, β^2, α*β, α^2, α*β, α^2] cXv = [cX0, cX1, cX2, cX1, cX0, cX1, cX2, cX1, cX0] mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [Xv; xv], - (MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeSquare) => [cXv], - (MOI.VectorOfVariables, MOI.SecondOrderCone) => [[1-y1, -y2, -y2]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [y1, y2]) MOIT.psds1vtest(mock, config) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [Xv; xv], (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeSquare) => [cXv], - (MOI.VectorOfVariables, MOI.SecondOrderCone) => [[1-y1, -y2, -y2]], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [y1, y2]) MOIT.psds1ftest(mock, config) # PSD2 diff --git a/test/Test/contlinear.jl b/test/Test/contlinear.jl index 0963ea2289..d0d3dcc026 100644 --- a/test/Test/contlinear.jl +++ b/test/Test/contlinear.jl @@ -6,23 +6,18 @@ function set_mock_optimize_linear1Test!(mock) MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0, 1]), + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0, 1]), + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 1], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-2], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1, 2, 0]), + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-2]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-1, 0, 2]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2, 0, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 2, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 1, 0], (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-1.5], - (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0.5], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0, 0], - (MOI.SingleVariable, MOI.EqualTo{Float64}) => [1.5])) + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0.5])) end set_mock_optimize_linear1Test!(mock) MOIT.linear1test(mock, config) @@ -30,8 +25,7 @@ MOIT.linear1test(mock, config_no_lhs_modif) MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0], - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0, 1])) + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1])) MOIT.linear2test(mock, config) MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [3]), @@ -104,8 +98,7 @@ MOIT.linear11test(mock, config) MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, tuple(), - (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1, -1], - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2, -2])) + (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1, -1])) MOIT.linear12test(mock, config) MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.InfeasibleNoResult)) @@ -123,7 +116,10 @@ (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1], (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1], (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0])) + # linear14 has double variable bounds for the z variable + mock.eval_variable_constraint_dual = false MOIT.linear14test(mock, config) + mock.eval_variable_constraint_dual = true MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.0], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [0.0, 0.0])) diff --git a/test/Test/unit.jl b/test/Test/unit.jl index 6cea334216..98671198fb 100644 --- a/test/Test/unit.jl +++ b/test/Test/unit.jl @@ -30,8 +30,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [1]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0] + MOI.FeasiblePoint ) ) MOIT.solve_blank_obj(mock, config) @@ -41,8 +40,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [1]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [2.0] + MOI.FeasiblePoint ) ) MOIT.solve_constant_obj(mock, config) @@ -52,8 +50,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [1]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0] + MOI.FeasiblePoint ) ) MOIT.solve_singlevariable_obj(mock, config) @@ -68,7 +65,10 @@ end (MOI.SingleVariable, MOI.LessThan{Float64}) => [0.0] ) ) + # x has two variable constraints + mock.eval_variable_constraint_dual = false MOIT.solve_with_lowerbound(mock, config) + mock.eval_variable_constraint_dual = true end @testset "solve_with_upperbound" begin MOIU.set_mock_optimize!(mock, @@ -80,7 +80,10 @@ end (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [0.0] ) ) + # x has two variable constraints + mock.eval_variable_constraint_dual = false MOIT.solve_with_upperbound(mock, config) + mock.eval_variable_constraint_dual = true end @testset "solve_affine_lessthan" begin MOIU.set_mock_optimize!(mock, @@ -167,8 +170,7 @@ end (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [1]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [3.0] + MOI.FeasiblePoint ) ) MOIT.solve_duplicate_terms_obj(mock, config) @@ -241,13 +243,11 @@ end MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [1.0]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1.0] + MOI.FeasiblePoint ), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [2.0]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1.0] + MOI.FeasiblePoint ) ) MOIT.solve_set_singlevariable_lessthan(mock, config) @@ -256,13 +256,11 @@ end MOIU.set_mock_optimize!(mock, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [1.0]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.LessThan{Float64}) => [-1.0] + MOI.FeasiblePoint ), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, MOI.Success, (MOI.FeasiblePoint, [2.0]), - MOI.FeasiblePoint, - (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1.0] + MOI.FeasiblePoint ) ) MOIT.solve_transform_singlevariable_lessthan(mock, config) diff --git a/test/mockoptimizer.jl b/test/mockoptimizer.jl index 8621ef8454..470a482b7d 100644 --- a/test/mockoptimizer.jl +++ b/test/mockoptimizer.jl @@ -49,7 +49,9 @@ end end @testset "Mock optimizer optimizer solve with result" begin - optimizer = MOIU.MockOptimizer(ModelForMock{Float64}(), evalobjective=false) + optimizer = MOIU.MockOptimizer(ModelForMock{Float64}(), + eval_objective_value=false, + eval_variable_constraint_dual=false) v = MOI.addvariables!(optimizer, 2) c1 = MOI.addconstraint!(optimizer, MOI.SingleVariable(v[1]), MOI.GreaterThan(1.0))