diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index c22a5822ef..001290278d 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -45,6 +45,8 @@ function full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T cache = MOIU.UniversalFallback(AllBridgedConstraints{T}()) bridgedmodel = MOIB.LazyBridgeOptimizer(model, cache) add_bridge(bridgedmodel, MOIB.VectorizeBridge{T}) + add_bridge(bridgedmodel, MOIB.ScalarSlackBridge{T}) + add_bridge(bridgedmodel, MOIB.VectorSlackBridge{T}) add_bridge(bridgedmodel, MOIB.SplitIntervalBridge{T}) add_bridge(bridgedmodel, MOIB.QuadtoSOCBridge{T}) add_bridge(bridgedmodel, MOIB.GeoMeanBridge{T}) @@ -59,6 +61,16 @@ end include("vectorizebridge.jl") @bridge Vectorize VectorizeBridge () (MOI.EqualTo, MOI.LessThan, MOI.GreaterThan,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () +include("slackbridge.jl") +@bridge ScalarSlack ScalarSlackBridge () (MOI.Interval, MOI.LessThan, MOI.GreaterThan) () () () (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () +@bridge(VectorSlack, VectorSlackBridge, (), (), + (MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, + MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, + MOI.PositiveSemidefiniteConeSquare, MOI.LogDetConeTriangle, + MOI.RootDetConeTriangle), + (MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2), (), (), (), + (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) + ) include("intervalbridge.jl") @bridge SplitInterval SplitIntervalBridge () (MOI.Interval,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () include("rsocbridge.jl") diff --git a/src/Bridges/bridgeoptimizer.jl b/src/Bridges/bridgeoptimizer.jl index 39aa630d07..95dcc57627 100644 --- a/src/Bridges/bridgeoptimizer.jl +++ b/src/Bridges/bridgeoptimizer.jl @@ -342,3 +342,5 @@ end # Variables MOI.add_variable(b::AbstractBridgeOptimizer) = MOI.add_variable(b.model) MOI.add_variables(b::AbstractBridgeOptimizer, n) = MOI.add_variables(b.model, n) + +# TODO add transform \ No newline at end of file diff --git a/src/Bridges/slackbridge.jl b/src/Bridges/slackbridge.jl new file mode 100644 index 0000000000..9a290dee73 --- /dev/null +++ b/src/Bridges/slackbridge.jl @@ -0,0 +1,176 @@ +# scalar version + +""" + ScalarSlackBridge{T, F, S} + +The `ScalarSlackBridge` converts a constraint `G`-in-`S` where `G` is a function different +from `SingleVariable` into the constraints `F`-in-`EqualTo{T}` and `SingleVariable`-in-`S`. +`F` is the result of subtracting a `SingleVariable` from `G`. +Tipically `G` is the same as `F`, but that is not mandatory. +""" +struct ScalarSlackBridge{T, F, S} <: AbstractBridge + slack::MOI.VariableIndex + slack_in_set::CI{MOI.SingleVariable, S} + equality::CI{F, MOI.EqualTo{T}} +end +function ScalarSlackBridge{T, F, S}(model, f::MOI.AbstractScalarFunction, s::S) where {T, F, S} + slack = MOI.add_variable(model) + new_f = MOIU.operate(-, T, f, MOI.SingleVariable(slack)) + slack_in_set = MOI.add_constraint(model, MOI.SingleVariable(slack), s) + equality = MOI.add_constraint(model, new_f, MOI.EqualTo(0.0)) + return ScalarSlackBridge{T, F, S}(slack, slack_in_set, equality) +end + +# start allowing everything (scalar) +MOI.supports_constraint(::Type{ScalarSlackBridge{T, F, S}}, + ::Type{<:MOI.AbstractScalarFunction}, + ::Type{<:MOI.AbstractScalarSet}) where {T, F, S} = true +# then restrict (careful with ambiguity) +MOI.supports_constraint(::Type{ScalarSlackBridge{T, F, S}}, + ::Type{<:MOI.SingleVariable}, + ::Type{<:MOI.EqualTo}) where {T, F, S} = false +MOI.supports_constraint(::Type{ScalarSlackBridge{T, F, S}}, + ::Type{<:MOI.SingleVariable}, + ::Type{<:MOI.AbstractScalarSet}) where {T, F, S} = false +MOI.supports_constraint(::Type{ScalarSlackBridge{T, F, S}}, + ::Type{<:MOI.AbstractScalarFunction}, + ::Type{<:MOI.EqualTo}) where {T, F, S} = false +function added_constraint_types(::Type{ScalarSlackBridge{T, F, S}}) where {T, F, S} + return [(F, MOI.EqualTo{T}), (MOI.SingleVariable, S)] +end +function concrete_bridge_type(::Type{<:ScalarSlackBridge{T}}, + F::Type{<:MOI.AbstractScalarFunction}, + S::Type{<:MOI.AbstractScalarSet}) where T + F2 = MOIU.promote_operation(-, T, F, MOI.SingleVariable) + return ScalarSlackBridge{T, F2, S} +end + +# Attributes, Bridge acting as an model +MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.NumberOfVariables) where {T, F, S} = 1 +MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.NumberOfConstraints{F, MOI.EqualTo{T}}) where {T, F, S} = 1 +MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.NumberOfConstraints{MOI.SingleVariable, S}) where {T, F, S} = 1 +MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.ListOfConstraintIndices{F, MOI.EqualTo{T}}) where {T, F, S} = [b.equality] +MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.ListOfConstraintIndices{MOI.SingleVariable, S}) where {T, F, S} = [b.slack_in_set] + +# Indices +function MOI.delete(model::MOI.ModelLike, c::ScalarSlackBridge) + MOI.delete(model, c.equality) + MOI.delete(model, c.slack_in_set) + MOI.delete(model, c.slack) + return +end + +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, c::ScalarSlackBridge) + # due to equality, slack should have the same value as original affine function + return MOI.get(model, attr, c.slack_in_set) +end +function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::ScalarSlackBridge) + # The dual constraint on slack (since it is free) is + # -dual_slack_in_set + dual_equality = 0 so the two duals are + # equal and we can return either one of them. + return MOI.get(model, a, c.slack_in_set) +end + +# Constraints +function MOI.modify(model::MOI.ModelLike, c::ScalarSlackBridge, change::MOI.AbstractFunctionModification) + MOI.modify(model, c.equality, change) +end + +function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintFunction, + c::ScalarSlackBridge{T, F, S}, func::F) where {T, F, S} + new_func = MOIU.operate(-, T, func, MOI.SingleVariable(c.slack)) + MOI.set(model, MOI.ConstraintFunction(), c.equality, new_func) +end + +function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::ScalarSlackBridge{T, F, S}, change::S) where {T, F, S} + MOI.set(model, MOI.ConstraintSet(), c.slack_in_set, change) +end + +# vector version + +""" + VectorSlackBridge{T, F, S} + +The `VectorSlackBridge` converts a constraint `G`-in-`S` where `G` is a function different +from `VectorOfVariables` into the constraints `F`in-`Zeros` and `VectorOfVariables`-in-`S`. +`F` is the result of subtracting a `VectorOfVariables` from `G`. +Tipically `G` is the same as `F`, but that is not mandatory. +""" +struct VectorSlackBridge{T, F, S} <: AbstractBridge + slacks::Vector{MOI.VariableIndex} + slacks_in_set::CI{MOI.VectorOfVariables, S} + equality::CI{F, MOI.Zeros} +end +function VectorSlackBridge{T, F, S}(model, f::MOI.AbstractVectorFunction, s::S) where {T, F, S} + d = MOI.dimension(s) + slacks = MOI.add_variables(model, d) + new_f = MOIU.operate(-, T, f, MOI.VectorAffineFunction{T}(MOI.VectorOfVariables(slacks))) + slacks_in_set = MOI.add_constraint(model, MOI.VectorOfVariables(slacks), s) + equality = MOI.add_constraint(model, new_f, MOI.Zeros(d)) + return VectorSlackBridge{T, F, S}(slacks, slacks_in_set, equality) +end + +MOI.supports_constraint(::Type{VectorSlackBridge{T, F, S}}, + ::Type{<:MOI.AbstractVectorFunction}, + ::Type{<:MOI.AbstractVectorSet}) where {T, F, S} = true +MOI.supports_constraint(::Type{VectorSlackBridge{T, F, S}}, + ::Type{<:MOI.VectorOfVariables}, + ::Type{<:MOI.Zeros}) where {T, F, S} = false +MOI.supports_constraint(::Type{VectorSlackBridge{T, F, S}}, + ::Type{<:MOI.AbstractVectorFunction}, + ::Type{<:MOI.Zeros}) where {T, F, S} = false +MOI.supports_constraint(::Type{VectorSlackBridge{T, F, S}}, + ::Type{<:MOI.VectorOfVariables}, + ::Type{<:MOI.AbstractVectorSet}) where {T, F, S} = false +function added_constraint_types(::Type{VectorSlackBridge{T, F, S}}) where {T, F<:MOI.AbstractVectorFunction, S} + return [(F, MOI.Zeros), (MOI.VectorOfVariables, S)] +end +function concrete_bridge_type(::Type{<:VectorSlackBridge{T}}, + F::Type{<:MOI.AbstractVectorFunction}, + S::Type{<:MOI.AbstractVectorSet}) where T + F2 = MOIU.promote_operation(-, T, F, MOI.VectorOfVariables) + return VectorSlackBridge{T, F2, S} +end + +# Attributes, Bridge acting as an model +MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.NumberOfVariables) where {T, F, S} = length(b.slacks) +MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.NumberOfConstraints{F, MOI.Zeros}) where {T, F, S} = 1 +MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.NumberOfConstraints{MOI.VectorOfVariables, S}) where {T, F, S} = 1 +MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.ListOfConstraintIndices{F, MOI.Zeros}) where {T, F, S} = [b.equality] +MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}) where {T, F, S} = [b.slacks_in_set] + +# Indices +function MOI.delete(model::MOI.ModelLike, c::VectorSlackBridge) + MOI.delete(model, c.equality) + MOI.delete(model, c.slacks_in_set) + MOI.delete(model, c.slacks) + return +end + +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, c::VectorSlackBridge) + # due to equality, slacks should have the same value as original affine function + return MOI.get(model, attr, c.slacks_in_set) +end +function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::VectorSlackBridge) + # The dual constraint on slack (since it is free) is + # -dual_slack_in_set + dual_equality = 0 so the two duals are + # equal and we can return either one of them. + return MOI.get(model, a, c.slacks_in_set) +end + +# Constraints +function MOI.modify(model::MOI.ModelLike, c::VectorSlackBridge, change::MOI.AbstractFunctionModification) + MOI.modify(model, c.equality, change) +end + +function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintFunction, + c::VectorSlackBridge{T, F, S}, func::F) where {T, F, S} + new_func = MOIU.operate(-, T, func, MOI.VectorAffineFunction{T}(MOI.VectorOfVariables(c.slacks))) + MOI.set(model, MOI.ConstraintFunction(), c.equality, new_func) +end + +function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::VectorSlackBridge{T,F,S}, change::S) where {T, F, S} + MOI.set(model, MOI.ConstraintSet(), c.slacks_in_set, change) +end diff --git a/src/Test/contlinear.jl b/src/Test/contlinear.jl index 99c7c0d4d4..e311068055 100644 --- a/src/Test/contlinear.jl +++ b/src/Test/contlinear.jl @@ -783,6 +783,23 @@ function linear7test(model::MOI.ModelLike, config::TestConfig) atol = config.atol rtol = config.rtol + # Min x - y + # s.t. bx <= x (c1) + # y <= by (c2) + # + # or, in more detail, + # + # Min 1 x - 1 y + # s.t. - 1 x <= - bx (z) (c1) + # 1 y <= by (w) (c2) + # + # with generic dual + # + # Max - bx z + by w + # s.t. - z == - 1 (c1) + # w == 1 (c2) + # i.e. z == w == 1 + @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) @test MOI.supports(model, MOI.ObjectiveSense()) @@ -1171,9 +1188,36 @@ function linear11test(model::MOI.ModelLike, config::TestConfig) atol = config.atol rtol = config.rtol # simple 2 variable, 1 constraint problem + # + # starts with + # # min x + y # st x + y >= 1 # x + y >= 2 + # sol: x+y = 2 (degenerate) + # + # with dual + # + # max w + 2z + # st w + z == 1 + # w + z == 1 + # w, z >= 0 + # sol: z = 1, w = 0 + # + # tranforms problem into: + # + # min x + y + # st x + y >= 1 + # x + y <= 2 + # sol: x+y = 1 (degenerate) + # + # with dual + # + # max w + 2z + # st w + z == 1 + # w + z == 1 + # w >= 0, z <= 0 + # sol: w = 1, z = 0 @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index c8091464b4..f31bb17a52 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -594,6 +594,12 @@ function operate_term(::typeof(-), term::MOI.ScalarQuadraticTerm) term.variable_index_1, term.variable_index_2) end +function operate_term(::typeof(-), term::MOI.VectorAffineTerm) + return MOI.VectorAffineTerm(term.output_index, operate_term(-, term.scalar_term)) +end +function operate_term(::typeof(-), term::MOI.VectorQuadraticTerm) + return MOI.VectorQuadraticTerm(term.output_index, operate_term(-, term.scalar_term)) +end function operate_term(::typeof(*), α::T, t::MOI.ScalarAffineTerm{T}) where T MOI.ScalarAffineTerm(α * t.coefficient, t.variable_index) @@ -608,9 +614,31 @@ function operate_term(::typeof(*), t1::MOI.ScalarAffineTerm, t2.variable_index) end +function operate_term(::typeof(*), α::T, t::MOI.VectorAffineTerm{T}) where T + MOI.VectorAffineTerm(t.output_index, operate_term(*, α, t.scalar_term)) +end +function operate_term(::typeof(*), α::T, t::MOI.VectorQuadraticTerm{T}) where T + MOI.VectorQuadraticTerm(t.output_index, operate_term(*, α, t.scalar_term)) +end +function operate_term(::typeof(*), t1::MOI.VectorAffineTerm, + t2::MOI.VectorAffineTerm) + @assert t1.output_index == t2.output_index + MOI.VectorQuadraticTerm(t1.output_index, operate_term(*, t1.scalar_term, t2.scalar_term)) +end + function operate_term(::typeof(/), t::MOI.ScalarAffineTerm{T}, α::T) where T MOI.ScalarAffineTerm(t.coefficient / α, t.variable_index) end +function operate_term(::typeof(/), t::MOI.ScalarQuadraticTerm{T}, α::T) where T + MOI.ScalarQuadraticTerm(t.coefficient / α, t.variable_index_1, + t.variable_index_2) +end +function operate_term(::typeof(/), t::MOI.VectorAffineTerm{T}, α::T) where T + MOI.VectorAffineTerm(t.output_index, operate_term(/, t.scalar_term, α)) +end +function operate_term(::typeof(/), t::MOI.VectorQuadraticTerm{T}, α::T) where T + MOI.VectorQuadraticTerm(t.output_index, operate_term(/, t.scalar_term, α)) +end # Avoid a copy in the case of + function operate_terms(::typeof(+), @@ -618,16 +646,26 @@ function operate_terms(::typeof(+), MOI.ScalarQuadraticTerm}}) return terms end +function operate_terms(::typeof(+), + terms::Vector{<:Union{MOI.VectorAffineTerm, + MOI.VectorQuadraticTerm}}) + return terms +end function operate_terms(::typeof(-), terms::Vector{<:Union{MOI.ScalarAffineTerm, MOI.ScalarQuadraticTerm}}) return map(term -> operate_term(-, term), terms) end +function operate_terms(::typeof(-), + terms::Vector{<:Union{MOI.VectorAffineTerm, + MOI.VectorQuadraticTerm}}) + return map(term -> operate_term(-, term), terms) +end -function map_terms!(op, func::MOI.ScalarAffineFunction) +function map_terms!(op, func::Union{MOI.ScalarAffineFunction, MOI.VectorAffineFunction}) map!(op, func.terms, func.terms) end -function map_terms!(op, func::MOI.ScalarQuadraticFunction) +function map_terms!(op, func::Union{MOI.ScalarQuadraticFunction, MOI.VectorQuadraticFunction}) map!(op, func.affine_terms, func.affine_terms) map!(op, func.quadratic_terms, func.quadratic_terms) end @@ -642,8 +680,19 @@ const ScalarQuadraticLike{T} = Union{ScalarAffineLike{T}, MOI.ScalarQuadraticFun const ScalarLike{T} = Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}} +# Functions convertible to a VectorAffineFunction +const VectorAffineLike{T} = Union{Vector{T}, MOI.VectorOfVariables, MOI.VectorAffineFunction{T}} +# Functions convertible to a VectorQuadraticFunction +const VectorQuadraticLike{T} = Union{VectorAffineLike{T}, MOI.VectorQuadraticFunction{T}} + +# Used for overloading Base operator functions so `T` is not in the union to +# avoid overloading e.g. `+(::Float64, ::Float64)` +const VectorLike{T} = Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}, + MOI.VectorQuadraticFunction{T}} + ###################################### +/- ##################################### ## promote_operation + function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, ::Type{<:ScalarAffineLike{T}}, ::Type{<:ScalarAffineLike{T}}) where T @@ -807,6 +856,210 @@ function Base.:-(α::T, f::ScalarLike{T}) where T return operate(-, T, α, f) end +# Vector +/- +############################################################################### +# function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, +# ::Type{<:VectorAffineLike{T}}) where T +# return MOI.VectorAffineFunction{T} +# end +function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, + ::Type{<:VectorAffineLike{T}}, + ::Type{<:VectorAffineLike{T}}) where T + return MOI.VectorAffineFunction{T} +end +function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, + ::Type{<:VectorQuadraticLike{T}}, + ::Type{<:VectorQuadraticLike{T}}) where T + return MOI.VectorQuadraticFunction{T} +end + +# Vector Variable +/- ... +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorOfVariables, + g::VectorQuadraticLike) where T + return operate(op, T, f, g) +end +# Vector Affine +/-! ... +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}, + g::Vector{T}) where T + @assert MOI.output_dimension(f) == length(g) + f.constants .= op.(f.constants, g) + return f +end +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}, + g::MOI.VectorOfVariables) where T + d = MOI.output_dimension(g) + @assert MOI.output_dimension(f) == d + append!(f.terms, MOI.VectorAffineTerm.( + collect(1:d), + MOI.ScalarAffineTerm.(op(one(T)), g.variables))) + return f +end +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}, + g::MOI.VectorAffineFunction{T}) where T + append!(f.terms, operate_terms(op, g.terms)) + f.constants .= op.(f.constants, g.constants) + return f +end +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}, + g::MOI.VectorQuadraticFunction{T}) where T + return operate(op, T, f, g) +end +# Vector Quadratic +/-! ... +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorQuadraticFunction{T}, + g::Vector{T}) where T + @assert MOI.output_dimension(f) == length(g) + f.constants .= op.(f.constants, g) + return f +end +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorQuadraticFunction{T}, + g::MOI.VectorOfVariables) where T + d = MOI.output_dimension(g) + @assert MOI.output_dimension(f) == d + append!(f.affine_terms, MOI.VectorAffineTerm.(collect(1:d), MOI.ScalarAffineTerm.(op(one(T)), g.variables))) + return f +end +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorQuadraticFunction{T}, + g::MOI.VectorAffineFunction{T}) where T + append!(f.affine_terms, operate_terms(op, g.terms)) + f.constants .= op.(f.constants, g.constants) + return f +end +function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorQuadraticFunction{T}, + g::MOI.VectorQuadraticFunction{T}) where T + append!(f.affine_terms, operate_terms(op, g.affine_terms)) + append!(f.quadratic_terms, operate_terms(op, g.quadratic_terms)) + f.constants .= op.(f.constants, g.constants) + return f +end + +## operate +# + with at least 3 arguments, can use in-place as the user cannot use +# intermediate results +# overload +# function operate(op::typeof(+), ::Type{T}, f, g, h, args...) where T +# return operate!(+, T, operate(+, T, f, g), h, args...) +# end + +# function operate(op::typeof(+), ::Type{T}, f::VectorOfVariables) where T +# return f +# end + +function operate(op::typeof(-), ::Type{T}, f::MOI.VectorOfVariables) where T + d = MOI.output_dimension(f) + return MOI.VectorAffineFunction{T}( + MOI.VectorAffineTerm.( + collect(1:d), + MOI.ScalarAffineTerm.(-one(T), f.variables)), + fill(zero(T),d)) +end + +# Vector number +/- ... +function operate(op::typeof(+), ::Type{T}, α::Vector{T}, f::VectorLike{T}) where T + return operate(op, T, f, α) +end +function operate(op::typeof(-), ::Type{T}, α::Vector{T}, f::VectorLike{T}) where T + return operate!(+, T, operate(-, T, f), α) +end + +# Vector Variable +/- ... +function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorOfVariables, α::Vector{T}) where T + d = MOI.output_dimension(f) + @assert length(α) == d + return MOI.VectorAffineFunction{T}( + MOI.VectorAffineTerm.( + collect(1:d), + MOI.ScalarAffineTerm.(one(T), f.variables)), + op.(α)) +end +function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorOfVariables, + g::MOI.VectorOfVariables) where T + d = MOI.output_dimension(f) + @assert MOI.output_dimension(g) == d + return MOI.VectorAffineFunction{T}( + vcat( + MOI.VectorAffineTerm.( + collect(1:d), + MOI.ScalarAffineTerm.(one(T), f.variables)), + MOI.VectorAffineTerm.( + collect(1:d), + MOI.ScalarAffineTerm.(op(one(T)), g.variables))), + fill(zero(T),d)) +end +function operate(op::typeof(+), ::Type{T}, + f::MOI.VectorOfVariables, + g::Union{MOI.VectorAffineFunction{T}, + MOI.VectorQuadraticFunction{T}}) where T + return operate(op, T, g, f) +end +function operate(op::typeof(-), ::Type{T}, + f::MOI.VectorOfVariables, + g::Union{MOI.VectorAffineFunction{T}, + MOI.VectorQuadraticFunction{T}}) where T + return operate!(+, T, operate(-, T, g), f) +end +# Vector Affine +/- ... +function operate(op::Union{typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}) where T + return MOI.VectorAffineFunction(operate_terms(op, f.terms), + op.(f.constants)) +end +function operate(op::Union{typeof(-)}, ::Type{T}, + f::MOI.VectorQuadraticFunction{T}) where T + return MOI.VectorQuadraticFunction( + operate_terms(op, f.affine_terms), + operate_terms(op, f.quadratic_terms), + op.(f.constants)) +end +function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}, + g::VectorAffineLike{T}) where T + return operate!(op, T, copy(f), g) +end +function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorAffineFunction{T}, + g::MOI.VectorQuadraticFunction{T}) where T + MOI.VectorQuadraticFunction([f.terms; operate_terms(op, g.affine_terms)], + operate_terms(op, g.quadratic_terms), + op.(f.constants, g.constants)) +end + +# Vector Quadratic +/- ... +function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, + f::MOI.VectorQuadraticFunction{T}, + g::VectorQuadraticLike{T}) where T + operate!(op, T, copy(f), g) +end + +function Base.:+(args::VectorLike{T}...) where T + return operate(+, T, args...) +end +function Base.:+(α::Vector{T}, f::VectorLike{T}...) where T + return operate(+, T, α, f...) +end +function Base.:+(f::VectorLike{T}, α::Vector{T}) where T + return operate(+, T, f, α) +end +function Base.:-(args::VectorLike{T}...) where T + return operate(-, T, args...) +end +function Base.:-(f::VectorLike{T}, α::Vector{T}) where T + return operate(-, T, f, α) +end +function Base.:-(α::Vector{T}, f::VectorLike{T}) where T + return operate(-, T, α, f) +end + ####################################### * ###################################### function promote_operation(::typeof(*), ::Type{T}, ::Type{T}, ::Type{<:Union{MOI.SingleVariable, diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index bf71cb4bf1..2821d32d19 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -491,4 +491,98 @@ ) end end -end + + @testset "Vector operate tests" begin + w = MOI.VariableIndex(0) + x = MOI.VariableIndex(1) + y = MOI.VariableIndex(2) + + @testset "Vector terms tests" begin + at = MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(10.0, x)) + qt = MOI.VectorQuadraticTerm(3, MOI.ScalarQuadraticTerm(6.0, x, y)) + @test MOIU.operate_term(*, 3.0, at) == + MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(30.0, x)) + @test MOIU.operate_term(*, at, at) == + MOI.VectorQuadraticTerm(3, MOI.ScalarQuadraticTerm(100.0, x, x)) + @test MOIU.operate_term(*, 3.0, qt) == + MOI.VectorQuadraticTerm(3, MOI.ScalarQuadraticTerm(18.0, x, y)) + @test MOIU.operate_term(/, at, 2.0) == + MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(5.0, x)) + @test MOIU.operate_term(/, qt, 3.0) == + MOI.VectorQuadraticTerm(3, MOI.ScalarQuadraticTerm(2.0, x, y)) + end + + @testset "Vector promote tests" begin + T = Float64 + for t1 in [MOI.VectorAffineFunction{T}, MOI.VectorOfVariables, Vector{T}] + for t2 in [MOI.VectorAffineFunction{T}, MOI.VectorOfVariables] + @test MOIU.promote_operation(+, T, t1, t2) == MOI.VectorAffineFunction{T} + @test MOIU.promote_operation(-, T, t1, t2) == MOI.VectorAffineFunction{T} + end + end + for t1 in [MOI.VectorQuadraticFunction{T}, MOI.VectorAffineFunction{T}, + MOI.VectorOfVariables, Vector{T}] + for t2 in [MOI.VectorQuadraticFunction{T}] + @test MOIU.promote_operation(+, T, t1, t2) == MOI.VectorQuadraticFunction{T} + @test MOIU.promote_operation(-, T, t1, t2) == MOI.VectorQuadraticFunction{T} + end + end + end + + α = [1, 2, 3] + v = MOI.VectorOfVariables([y, w, y]) + g = MOI.VectorAffineFunction( + MOI.VectorAffineTerm.([3, 1], + MOI.ScalarAffineTerm.([5, 2], [y, x])), + [3, 1, 4]) + f = MOI.VectorQuadraticFunction( + MOI.VectorAffineTerm.([1, 2, 2], + MOI.ScalarAffineTerm.([3, 1, 2], [x, x, y])), + MOI.VectorQuadraticTerm.([1, 1, 2], + MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y])), + [7, 3, 4]) + v_plus_g = MOI.VectorAffineFunction( + MOI.VectorAffineTerm.([3, 1, 1, 2, 3], + MOI.ScalarAffineTerm.([5, 2, 1, 1, 1], [y, x, y, w, y])), + [3, 1, 4]) + g_plus_α = MOI.VectorAffineFunction( + MOI.VectorAffineTerm.([3, 1], + MOI.ScalarAffineTerm.([5, 2], [y, x])), + [4, 3, 7]) + α_minus_v = MOI.VectorAffineFunction( + MOI.VectorAffineTerm.([1, 2, 3], + MOI.ScalarAffineTerm.([-1, -1, -1], [y, w, y])), + [1, 2, 3]) + v_minus_v_plus_v = MOI.VectorAffineFunction( + MOI.VectorAffineTerm.([1, 2, 3, 1, 2, 3, 1, 2, 3], + MOI.ScalarAffineTerm.([1, 1, 1, -1, -1, -1, 1, 1, 1], + [y, w, y, y, w, y, y, w, y])), + [0, 0, 0]) + f_plus_α = MOI.VectorQuadraticFunction( + MOI.VectorAffineTerm.([1, 2, 2], + MOI.ScalarAffineTerm.([3, 1, 2], [x, x, y])), + MOI.VectorQuadraticTerm.([1, 1, 2], + MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y])), + [8, 5, 7]) + f_minus_g = MOI.VectorQuadraticFunction( + MOI.VectorAffineTerm.([1, 2, 2, 3, 1], + MOI.ScalarAffineTerm.([3, 1, 2, -5, -2], [x, x, y, y, x])), + MOI.VectorQuadraticTerm.([1, 1, 2], + MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y])), + [4, 2, 0]) + @test v + g ≈ v_plus_g + @test g + α ≈ g_plus_α + @test α + g ≈ g_plus_α + @test α - v ≈ α_minus_v + @test MOIU.operate(+, Int, MOIU.operate(-, Int, v, v), v) ≈ v_minus_v_plus_v + @test f + α ≈ f_plus_α + @test f - g ≈ f_minus_g + @test f - f + f - g ≈ f_minus_g + @test v + f + α - v ≈ f_plus_α + @test v - f - α - v ≈ - f_plus_α + @test MOIU.operate!(-, Int, v, f) - v ≈ - f + @test (g + v + g + v + f) - (v + g + v + g) ≈ f + @test v - α ≈ - α_minus_v + @test g - f ≈ - f_minus_g + end +end \ No newline at end of file diff --git a/test/bridge.jl b/test/bridge.jl index c8a484830d..ba654d7b5f 100644 --- a/test/bridge.jl +++ b/test/bridge.jl @@ -1,15 +1,15 @@ # Model not supporting Interval MOIU.@model(SimpleModel, (), - (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan), + (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, MOI.PositiveSemidefiniteConeTriangle, MOI.ExponentialCone), - (), + (MOI.PowerCone, MOI.DualPowerCone), (MOI.SingleVariable,), (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction), (MOI.VectorOfVariables,), - (MOI.VectorAffineFunction,)) + (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)) function test_noc(bridged_mock, F, S, n) @test MOI.get(bridged_mock, MOI.NumberOfConstraints{F, S}()) == n @@ -341,7 +341,135 @@ end @test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) ≈ newf test_delete_bridge(bridged_mock, ci, 2, ((MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0), (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 0))) - end + end + @testset "Scalar slack" begin + MOI.empty!(mock) + bridgedmock = MOIB.ScalarSlack{Float64}(mock) + x = MOI.add_variable(bridgedmock) + y = MOI.add_variable(bridgedmock) + f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([1.0, 2.0], [x, y]), 0.0) + ci = MOI.add_constraint(bridgedmock, f, MOI.GreaterThan(0.0)) + @test MOI.get(bridgedmock, MOI.ConstraintFunction(), ci) ≈ f + newf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([2.0, 1.0], [x, y]), 0.0) + MOI.set(bridgedmock, MOI.ConstraintFunction(), ci, newf) + @test MOI.get(bridgedmock, MOI.ConstraintFunction(), ci) ≈ newf + @test MOI.get(bridgedmock, MOI.ConstraintSet(), ci) == MOI.GreaterThan(0.0) + MOI.set(bridgedmock, MOI.ConstraintSet(), ci, MOI.GreaterThan(1.0)) + @test MOI.get(bridgedmock, MOI.ConstraintSet(), ci) == MOI.GreaterThan(1.0) + MOI.modify(bridgedmock, ci, MOI.ScalarConstantChange{Float64}(1.0)) + @test MOI.get(bridgedmock, MOI.ConstraintFunction(), ci) ≈ + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([2.0, 1.0], [x, y]), 1.0) + test_delete_bridge(bridgedmock, ci, 2, ((MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64}, 0),)) + + MOIT.basic_constraint_tests(bridgedmock, config, + include=[(F, S) for + F in [MOI.ScalarAffineFunction{Float64}, + MOI.ScalarQuadraticFunction{Float64}], + S in [MOI.GreaterThan{Float64}, + MOI.LessThan{Float64}] + ]) + + # There are extra variables due to the bridge + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 2.0, 2.0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 0.5, 1.0, 1.0], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [1, 0], + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [1], + (MOI.SingleVariable, MOI.LessThan{Float64}) => [0])) + MOIT.linear11test(bridgedmock, MOIT.TestConfig(duals = false)) + + c1 = MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()) + @test length(c1) == 1 + @test MOI.get(bridgedmock, MOI.ConstraintPrimal(), c1[]) ≈ 1.0 + @test MOI.get(bridgedmock, MOI.ConstraintDual(), c1[]) ≈ 1.0 + c2 = MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}()) + @test length(c2) == 1 + @test MOI.get(bridgedmock, MOI.ConstraintPrimal(), c2[]) ≈ 1.0 + @test MOI.get(bridgedmock, MOI.ConstraintDual(), c2[]) ≈ 0.0 + + loc = MOI.get(bridgedmock, MOI.ListOfConstraints()) + @test length(loc) == 2 + @test (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) in loc + @test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in loc + loc = MOI.get(mock, MOI.ListOfConstraints()) + @test length(loc) == 3 + @test (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) in loc + @test (MOI.SingleVariable, MOI.LessThan{Float64}) in loc + @test (MOI.SingleVariable, MOI.GreaterThan{Float64}) in loc + + for T in [Int, Float64], S in [MOI.GreaterThan{T}, MOI.GreaterThan{T}] + for F in [MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}] + @test MOIB.added_constraint_types(MOIB.ScalarSlackBridge{T, F, S}) == [(F, MOI.EqualTo{T}), (MOI.SingleVariable, S)] + end + end + end + + @testset "Vector slack" begin + MOI.empty!(mock) + bridgedmock = MOIB.VectorSlack{Float64}(mock) + x = MOI.add_variable(bridgedmock) + y = MOI.add_variable(bridgedmock) + f = MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.([1.0, 2.0], [x, y])), [0.0]) + ci = MOI.add_constraint(bridgedmock, f, MOI.Nonpositives(1)) + @test MOI.get(bridgedmock, MOI.ConstraintFunction(), ci) ≈ f + newf = MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.([2.0, 1.0], [x, y])), [0.0]) + MOI.set(bridgedmock, MOI.ConstraintFunction(), ci, newf) + @test MOI.get(bridgedmock, MOI.ConstraintFunction(), ci) ≈ newf + @test MOI.get(bridgedmock, MOI.ConstraintSet(), ci) == MOI.Nonpositives(1) + MOI.modify(bridgedmock, ci, MOI.VectorConstantChange([1.0])) + @test MOI.get(bridgedmock, MOI.ConstraintFunction(), ci) ≈ + MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.([2.0, 1.0], [x, y])), [1.0]) + test_delete_bridge(bridgedmock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.Zeros, 0),)) + + fp = MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,2,3], MOI.ScalarAffineTerm.([1.0, 2.0, 3.0], [x, y, y])), [0.0, 0.0, 0.0]) + cp = MOI.add_constraint(bridgedmock, fp, MOI.PowerCone(0.1)) + @test MOI.get(bridgedmock, MOI.ConstraintSet(), cp) == MOI.PowerCone(0.1) + MOI.set(bridgedmock, MOI.ConstraintSet(), cp, MOI.PowerCone(0.2)) + @test MOI.get(bridgedmock, MOI.ConstraintSet(), cp) == MOI.PowerCone(0.2) + + MOIT.basic_constraint_tests(bridgedmock, config, + include=[(F, S) for + F in [MOI.VectorAffineFunction{Float64}, + MOI.VectorQuadraticFunction{Float64}], + S in [MOI.Nonnegatives, + MOI.Nonpositives] + ]) + + # There are extra variables due to the bridge + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0, 0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0, 100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100, 100, -100], + (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[1.], [1.]], + (MOI.VectorOfVariables, MOI.Nonnegatives) => [[1.]], + (MOI.VectorOfVariables, MOI.Nonpositives) => [[1.]])) + MOIT.linear7test(bridgedmock, config) + + c1 = MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}()) + @test length(c1) == 1 + @test MOI.get(bridgedmock, MOI.ConstraintPrimal(), c1[]) ≈ [100.0] + @test MOI.get(bridgedmock, MOI.ConstraintDual(), c1[]) ≈ [1.0] + c2 = MOI.get(bridgedmock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonpositives}()) + @test length(c2) == 1 + @test MOI.get(bridgedmock, MOI.ConstraintPrimal(), c2[]) ≈ [-100.0] + @test MOI.get(bridgedmock, MOI.ConstraintDual(), c2[]) ≈ [1.0] + + loc = MOI.get(bridgedmock, MOI.ListOfConstraints()) + @test length(loc) == 2 + @test (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) in loc + @test (MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) in loc + loc = MOI.get(mock, MOI.ListOfConstraints()) + @test length(loc) == 3 + @test (MOI.VectorAffineFunction{Float64}, MOI.Zeros) in loc + @test (MOI.VectorOfVariables, MOI.Nonnegatives) in loc + @test (MOI.VectorOfVariables, MOI.Nonpositives) in loc + + for T in [Int, Float64], S in [MOI.Nonnegatives, MOI.Nonpositives] + for F in [MOI.VectorAffineFunction{T}, MOI.VectorQuadraticFunction{T}] + @test MOIB.added_constraint_types(MOIB.VectorSlackBridge{T, F, S}) == [(F, MOI.Zeros), (MOI.VectorOfVariables, S)] + end + end + end @testset "RSOC" begin bridged_mock = MOIB.RSOC{Float64}(mock)