From 265fec6a8336612e1583ee9a517439c580b6792b Mon Sep 17 00:00:00 2001 From: Guilherme Bodin Date: Sat, 23 Feb 2019 10:00:48 -0300 Subject: [PATCH 1/5] added / to ScalarQuadraticFunction --- NEWS.md | 29 +++++ src/Bridges/Bridges.jl | 40 ++++--- src/Bridges/flip_sign_bridge.jl | 174 +++++++++++++++++++++++++++++ src/Bridges/lazybridgeoptimizer.jl | 9 +- src/Bridges/squarepsdbridge.jl | 8 +- src/Utilities/functions.jl | 172 ++++++++++++++++++++++------ src/error.jl | 10 +- src/functions.jl | 6 + test/Utilities/functions.jl | 140 ++++++++++++++++++----- test/bridge.jl | 160 +++++++++++++++++++++++++- test/errors.jl | 6 +- test/functions.jl | 14 +++ 12 files changed, 672 insertions(+), 96 deletions(-) create mode 100644 src/Bridges/flip_sign_bridge.jl diff --git a/NEWS.md b/NEWS.md index 9c2a4f0371..de7686a38c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,35 @@ MathOptInterface (MOI) release notes ==================================== +v0.8.2 (February 7, 2019) +------------------------- + +- Add `RawStatusString` attribute (#629). +- Do not set names to the optimizer but only to the cache in `CachingOptimizer` + (#638). +- Make scalar MOI functions act as scalars in broadcast (#646). +- Add function utilities: + * Implement `Base.zero` (#634), `Base.iszero` (#643), add missing arithmetic + operations (#644, #645) and fix division (#648). + * Add a `vectorize` function that turns a vector of `ScalarAffineFunction` + into a `VectorAffineFunction` (#642). +- Improve support for starting values: + * Show a warning in copy when starting values are not supported instead of + throwing an error (#630). + * Fix `UniversalFallback` for getting an variable or constraint attribute set to + no indices (#623). + * Add a test in `contlineartest` with partially set `VariablePrimalStart`. +- Bridges improvements: + * Fix `StackOverFlow` in `LazyBridgeOptimizer` when there is a cycle in the + graph of bridges. + * Add `Slack` bridges (#610, #650). + * Add `FlipSign` bridges (#658). +- Add tests with duplicate coefficients in `ScalarAffineFunction` and + `VectorAffineFunction` (#639). +- Use tolerance to compare `VariablePrimal` in `rotatedsoc1` test (#632). +- Use a zero constant in `ScalarAffineFunction` of constraints in `psdt2` + (#622). + v0.8.1 (January 7, 2019) ------------------------- diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index 577e48767e..21a4f124a7 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -27,7 +27,8 @@ include("lazybridgeoptimizer.jl") MOIU.@model(AllBridgedConstraints, (), (MOI.EqualTo, MOI.LessThan, MOI.GreaterThan, MOI.Interval,), - (MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, + (MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, + MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, MOI.PositiveSemidefiniteConeSquare, MOI.LogDetConeTriangle, MOI.RootDetConeTriangle), (), @@ -43,22 +44,31 @@ this package and for the coefficient type `T`. """ 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}) - add_bridge(bridgedmodel, MOIB.SquarePSDBridge{T}) - add_bridge(bridgedmodel, MOIB.LogDetBridge{T}) - add_bridge(bridgedmodel, MOIB.RootDetBridge{T}) - add_bridge(bridgedmodel, MOIB.RSOCBridge{T}) - add_bridge(bridgedmodel, MOIB.RSOCtoPSDBridge{T}) - add_bridge(bridgedmodel, MOIB.SOCtoPSDBridge{T}) - bridgedmodel + bridged_model = LazyBridgeOptimizer(model, cache) + add_bridge(bridged_model, GreaterToLessBridge{T}) + add_bridge(bridged_model, LessToGreaterBridge{T}) + add_bridge(bridged_model, NonnegToNonposBridge{T}) + add_bridge(bridged_model, NonposToNonnegBridge{T}) + add_bridge(bridged_model, VectorizeBridge{T}) + add_bridge(bridged_model, ScalarSlackBridge{T}) + add_bridge(bridged_model, VectorSlackBridge{T}) + add_bridge(bridged_model, SplitIntervalBridge{T}) + add_bridge(bridged_model, QuadtoSOCBridge{T}) + add_bridge(bridged_model, GeoMeanBridge{T}) + add_bridge(bridged_model, SquarePSDBridge{T}) + add_bridge(bridged_model, LogDetBridge{T}) + add_bridge(bridged_model, RootDetBridge{T}) + add_bridge(bridged_model, RSOCBridge{T}) + add_bridge(bridged_model, RSOCtoPSDBridge{T}) + add_bridge(bridged_model, SOCtoPSDBridge{T}) + return bridged_model end +include("flip_sign_bridge.jl") +@bridge GreaterToLess GreaterToLessBridge () (MOI.GreaterThan,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () +@bridge LessToGreater LessToGreaterBridge () (MOI.LessThan,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () +@bridge NonnegToNonpos NonnegToNonposBridge () () (MOI.Nonnegatives,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) +@bridge NonposToNonneg NonposToNonnegBridge () () (MOI.Nonpositives,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) include("vectorizebridge.jl") @bridge Vectorize VectorizeBridge () (MOI.EqualTo, MOI.LessThan, MOI.GreaterThan,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () include("slackbridge.jl") diff --git a/src/Bridges/flip_sign_bridge.jl b/src/Bridges/flip_sign_bridge.jl new file mode 100644 index 0000000000..c689460bf9 --- /dev/null +++ b/src/Bridges/flip_sign_bridge.jl @@ -0,0 +1,174 @@ +""" + FlipSignBridge{S1, S2, F} + +Bridge a `G`-in-`S1` constraint into an `F`-in-`S2` constraint by multiplying +the function by `-1` and taking the point reflection of the set across the +origin. The flipped `F`-in-`S` constraint is stored in the `flipped_constraint` +field by convention. +""" +abstract type FlipSignBridge{ + S1<:MOI.AbstractSet, S2<:MOI.AbstractSet, + F<:MOI.AbstractFunction} <: AbstractBridge end + +function MOI.supports_constraint(::Type{<:FlipSignBridge{S1}}, + ::Type{<:MOI.AbstractScalarFunction}, + ::Type{S1}) where {S1<:MOI.AbstractSet} + return true +end +function added_constraint_types( + ::Type{<:FlipSignBridge{S1, S2, F}}) where {S1, S2, F} + return [(F, S2)] +end + +# Attributes, Bridge acting as an model +function MOI.get(::FlipSignBridge{S1, S2, F}, + ::MOI.NumberOfConstraints{F, S2}) where {S1, S2, F} + return 1 +end +function MOI.get(bridge::FlipSignBridge{S1, S2, F}, + ::MOI.ListOfConstraintIndices{F, S2}) where {S1, S2, F} + return [bridge.flipped_constraint] +end + +# References +function MOI.delete(model::MOI.ModelLike, bridge::FlipSignBridge) + MOI.delete(model, bridge.flipped_constraint) +end + +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, + attr::Union{MOI.ConstraintPrimal, MOI.ConstraintDual}, + bridge::FlipSignBridge) + return -MOI.get(model, attr, bridge.flipped_constraint) +end + +function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, + change::MOI.ScalarCoefficientChange) + MOI.modify( + model, bridge.flipped_constraint, + MOI.ScalarCoefficientChange(change.variable, -change.new_coefficient)) +end + +function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, + change::MOI.MultirowChange{T}) where T + new_coefficients = Tuple{Int64, T}[ + (index, -coef) for (index, coef) in change.new_coefficients] + MOI.modify(model, bridge.flipped_constraint, + MOI.MultirowChange(change.variable, + new_coefficients)) +end +function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, + change::MOI.VectorConstantChange) + MOI.modify(model, bridge.flipped_constraint, + MOI.VectorConstantChange(-change.new_constant)) +end + +""" + GreaterToLessBridge{T, F<:MOI.AbstractScalarFunction} <: + FlipSignBridge{MOI.GreaterThan{T}, MOI.LessThan{T}, F} + +Transforms a `G`-in-`GreaterThan{T}` constraint into an `F`-in-`LessThan{T}` +constraint. +""" +struct GreaterToLessBridge{T, F<:MOI.AbstractScalarFunction} <: + FlipSignBridge{MOI.GreaterThan{T}, MOI.LessThan{T}, F} + flipped_constraint::CI{F, MOI.LessThan{T}} +end +function GreaterToLessBridge{T, F}(model::MOI.ModelLike, + g::MOI.AbstractScalarFunction, + s::MOI.GreaterThan) where {T, F} + f = MOIU.operate(-, T, g) + flipped_constraint = MOI.add_constraint(model, f, MOI.LessThan(-s.lower)) + return GreaterToLessBridge{T, F}(flipped_constraint) +end +function concrete_bridge_type(::Type{<:GreaterToLessBridge{T}}, + G::Type{<:MOI.AbstractScalarFunction}, + ::Type{MOI.GreaterThan{T}}) where T + F = MOIU.promote_operation(-, T, G) + return GreaterToLessBridge{T, F} +end +function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet, + bridge::GreaterToLessBridge, new_set::MOI.GreaterThan) + MOI.set(model, attr, bridge.flipped_constraint, + MOI.LessThan(-new_set.lower)) +end + +""" + LessToGreaterBridge{T, F<:MOI.AbstractScalarFunction} <: + FlipSignBridge{MOI.LessThan{T}, MOI.GreaterThan{T}, F} + +Transforms a `G`-in-`LessThan{T}` constraint into an `F`-in-`GreaterThan{T}` +constraint. +""" +struct LessToGreaterBridge{T, F<:MOI.AbstractScalarFunction} <: + FlipSignBridge{MOI.LessThan{T}, MOI.GreaterThan{T}, F} + flipped_constraint::CI{F, MOI.GreaterThan{T}} +end +function LessToGreaterBridge{T, F}(model::MOI.ModelLike, + g::MOI.AbstractScalarFunction, + s::MOI.LessThan) where {T, F} + f = MOIU.operate(-, T, g) + flipped_constraint = MOI.add_constraint(model, f, MOI.GreaterThan(-s.upper)) + return LessToGreaterBridge{T, F}(flipped_constraint) +end +function concrete_bridge_type(::Type{<:LessToGreaterBridge{T}}, + G::Type{<:MOI.AbstractScalarFunction}, + ::Type{MOI.LessThan{T}}) where T + F = MOIU.promote_operation(-, T, G) + return LessToGreaterBridge{T, F} +end +function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet, + bridge::LessToGreaterBridge, new_set::MOI.LessThan) + MOI.set(model, attr, bridge.flipped_constraint, + MOI.GreaterThan(-new_set.upper)) +end + +""" + NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction} + +Transforms a `G`-in-`Nonnegatives` constraint into a `F`-in-`Nonpositives` +constraint. +""" +struct NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction} <: + FlipSignBridge{MOI.Nonnegatives, MOI.Nonpositives, F} + flipped_constraint::CI{F, MOI.Nonpositives} +end +function NonnegToNonposBridge{T, F}(model::MOI.ModelLike, + g::MOI.AbstractVectorFunction, + s::MOI.Nonnegatives) where {T, F} + f = MOIU.operate(-, T, g) + flipped_constraint = MOI.add_constraint(model, f, + MOI.Nonpositives(s.dimension)) + return NonnegToNonposBridge{T, F}(flipped_constraint) +end +function concrete_bridge_type(::Type{<:NonnegToNonposBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.Nonnegatives}) where T + F = MOIU.promote_operation(-, T, G) + return NonnegToNonposBridge{T, F} +end + +""" + NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction} + +Transforms a `G`-in-`Nonpositives` constraint into a `F`-in-`Nonnegatives` +constraint. +""" +struct NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction} <: + FlipSignBridge{MOI.Nonpositives, MOI.Nonnegatives, F} + flipped_constraint::CI{F, MOI.Nonnegatives} +end +function NonposToNonnegBridge{T, F}(model::MOI.ModelLike, + g::MOI.AbstractVectorFunction, + s::MOI.Nonpositives) where {T, F} + f = MOIU.operate(-, T, g) + flipped_constraint = MOI.add_constraint(model, f, + MOI.Nonnegatives(s.dimension)) + return NonposToNonnegBridge{T, F}(flipped_constraint) +end +function concrete_bridge_type(::Type{<:NonposToNonnegBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.Nonpositives}) where T + F = MOIU.promote_operation(-, T, G) + return NonposToNonnegBridge{T, F} +end diff --git a/src/Bridges/lazybridgeoptimizer.jl b/src/Bridges/lazybridgeoptimizer.jl index 88c5ccd624..58437d2bff 100644 --- a/src/Bridges/lazybridgeoptimizer.jl +++ b/src/Bridges/lazybridgeoptimizer.jl @@ -43,7 +43,7 @@ function update_dist!(b::LazyBridgeOptimizer, constraints) changed = false for BT in b.bridgetypes for (F, S) in constraints - if MOI.supports_constraint(BT, F, S) && all(C -> MOI.supports_constraint(b, C[1], C[2]), added_constraint_types(BT, F, S)) + if MOI.supports_constraint(BT, F, S) && all(C -> supports_constraint_no_update(b, C[1], C[2]), added_constraint_types(BT, F, S)) # Number of bridges needed using BT dist = 1 + sum(C -> _dist(b, C[1], C[2]), added_constraint_types(BT, F, S)) # Is it better that what can currently be done ? @@ -59,7 +59,7 @@ function update_dist!(b::LazyBridgeOptimizer, constraints) end function fill_required_constraints!(required::Set{Tuple{DataType, DataType}}, b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) - if MOI.supports_constraint(b.model, F, S) || (F, S) in keys(b.best) + if supports_constraint_no_update(b, F, S) return # The constraint is supported end if (F, S) in required @@ -99,6 +99,11 @@ end function is_bridged(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) !MOI.supports_constraint(b.model, F, S) end +# Same as supports_constraint but do not trigger `update_constraint!`. This is +# used inside `update_constraint!`. +function supports_constraint_no_update(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) + return MOI.supports_constraint(b.model, F, S) || (F, S) in keys(b.best) +end function supports_bridging_constraint(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) update_constraint!(b, F, S) return (F, S) in keys(b.best) diff --git a/src/Bridges/squarepsdbridge.jl b/src/Bridges/squarepsdbridge.jl index 7434624725..9cf9e845e4 100644 --- a/src/Bridges/squarepsdbridge.jl +++ b/src/Bridges/squarepsdbridge.jl @@ -46,16 +46,16 @@ matrix. For instance, the constraint for the matrix ```math \\begin{pmatrix} - 1 & 1 + x & 2 - 3x\\ - 1 + x & 2 + x & 3 - x\\ + 1 & 1 + x & 2 - 3x\\\\ + 1 + x & 2 + x & 3 - x\\\\ 2 - 3x & 2 + x & 2x \\end{pmatrix} ``` to be PSD can be broken down to the constraint of the symmetric matrix ```math \\begin{pmatrix} - 1 & 1 + x & 2 - 3x\\ - \\cdot & 2 + x & 3 - x\\ + 1 & 1 + x & 2 - 3x\\\\ + \\cdot & 2 + x & 3 - x\\\\ \\cdot & \\cdot & 2x \\end{pmatrix} ``` diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index f31bb17a52..1d13543d03 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -353,11 +353,22 @@ function test_constraintnames_equal(model, constraintnames) end end -isapprox_zero(α::AbstractFloat, tol) = -tol < α < tol -isapprox_zero(α::Union{Integer, Rational}, tol) = iszero(α) -function isapprox_zero(t::Union{MOI.ScalarAffineTerm, - MOI.ScalarQuadraticTerm}, tol) - isapprox_zero(MOI.coefficient(t), tol) +""" + all_coefficients(p::Function, f::MOI.AbstractFunction) + +Determine whether predicate `p` returns `true` for all coefficients of `f`, +returning `false` as soon as the first coefficient of `f` for which `p` +returns `false` is encountered (short-circuiting). Similar to `all`. +""" +function all_coefficients end + +function all_coefficients(p::Function, f::MOI.ScalarAffineFunction) + return p(f.constant) && all(t -> p(MOI.coefficient(t)), f.terms) +end +function all_coefficients(p::Function, f::MOI.ScalarQuadraticFunction) + return p(f.constant) && + all(t -> p(MOI.coefficient(t)), f.affine_terms) && + all(t -> p(MOI.coefficient(t)), f.quadratic_terms) end """ @@ -379,16 +390,16 @@ then `isapprox_zero(f)` is `false` but `isapprox_zero(MOIU.canonical(f))` is """ function isapprox_zero end -function isapprox_zero(f::MOI.ScalarAffineFunction, tol) - isapprox_zero(f.constant, tol) && all(t -> isapprox_zero(t, tol), - f.terms) +isapprox_zero(α::AbstractFloat, tol) = -tol <= α <= tol +isapprox_zero(α::Union{Integer, Rational}, tol) = iszero(α) +function isapprox_zero(f::MOI.AbstractFunction, tol) + return all_coefficients(α -> isapprox_zero(α, tol), f) end -function isapprox_zero(f::MOI.ScalarQuadraticFunction, tol) - isapprox_zero(f.constant, tol) && - all(t -> isapprox_zero(t, tol), - f.affine_terms) && - all(t -> isapprox_zero(t, tol), - f.quadratic_terms) + +Base.iszero(f::MOI.SingleVariable) = false +function Base.iszero(f::Union{MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}}) where T + return all_coefficients(iszero, canonical(f)) end """ @@ -560,7 +571,8 @@ end args::Union{T, MOI.AbstractFunction}...)::MOI.AbstractFunction where T Returns an `MOI.AbstractFunction` representing the function resulting from the -operation `op(args...)`. No argument can be modified. +operation `op(args...)` on functions of coefficient type `T`. No argument can be +modified. """ function operate end @@ -569,8 +581,9 @@ function operate end args::Union{T, MOI.AbstractFunction}...)::MOI.AbstractFunction where T Returns an `MOI.AbstractFunction` representing the function resulting from the -operation `op(args...)`. The first argument can be modified. The return type -is the same than the method `operate(op, T, args...)` without `!`. +operation `op(args...)` on functions of coefficient type `T`. The first argument +can be modified. The return type is the same than the method +`operate(op, T, args...)` without `!`. """ function operate! end @@ -693,11 +706,19 @@ const VectorLike{T} = Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}, ###################################### +/- ##################################### ## promote_operation +function promote_operation(::typeof(-), ::Type{T}, + ::Type{<:ScalarAffineLike{T}}) where T + return MOI.ScalarAffineFunction{T} +end function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, ::Type{<:ScalarAffineLike{T}}, ::Type{<:ScalarAffineLike{T}}) where T return MOI.ScalarAffineFunction{T} end +function promote_operation(::typeof(-), ::Type{T}, + ::Type{<:ScalarQuadraticLike{T}}) where T + return MOI.ScalarQuadraticFunction{T} +end function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, ::Type{<:ScalarQuadraticLike{T}}, ::Type{<:ScalarQuadraticLike{T}}) where T @@ -778,6 +799,11 @@ function operate(op::typeof(+), ::Type{T}, f, g, h, args...) where T return operate!(+, T, operate(+, T, f, g), h, args...) end +# Unary + +function operate(op::typeof(+), ::Type{T}, f::MOI.AbstractFunction) where T + return f +end + # Scalar number +/- ... function operate(op::typeof(+), ::Type{T}, α::T, f::ScalarLike{T}) where T return operate(op, T, f, α) @@ -787,18 +813,22 @@ function operate(op::typeof(-), ::Type{T}, α::T, f::ScalarLike{T}) where T end # Scalar Variable +/- ... +function operate(::typeof(-), ::Type{T}, f::MOI.SingleVariable) where T + return MOI.ScalarAffineFunction{T}( + [MOI.ScalarAffineTerm(-one(T), f.variable)], zero(T)) +end function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, f::MOI.SingleVariable, α::T) where T - return MOI.ScalarAffineFunction{T}([MOI.ScalarAffineTerm(one(T), - f.variable)], - op(α)) + return MOI.ScalarAffineFunction{T}( + [MOI.ScalarAffineTerm(one(T), f.variable)], op(α)) end function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, f::MOI.SingleVariable, g::MOI.SingleVariable) where T - return MOI.ScalarAffineFunction{T}([MOI.ScalarAffineTerm(one(T), f.variable), - MOI.ScalarAffineTerm(op(one(T)), g.variable)], - zero(T)) + return MOI.ScalarAffineFunction{T}( + [MOI.ScalarAffineTerm(one(T), f.variable), + MOI.ScalarAffineTerm(op(one(T)), g.variable)], + zero(T)) end function operate(op::typeof(+), ::Type{T}, f::MOI.SingleVariable, @@ -831,6 +861,13 @@ function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, op(f.constant, g.constant)) end # Scalar Quadratic +/- ... +function operate(op::Union{typeof(-)}, ::Type{T}, + f::MOI.ScalarQuadraticFunction{T}) where T + return MOI.ScalarQuadraticFunction( + operate_terms(op, f.affine_terms), + operate_terms(op, f.quadratic_terms), + op(f.constant)) +end function operate(op::Union{typeof(+), typeof(-)}, ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, g::ScalarQuadraticLike{T}) where T @@ -858,10 +895,14 @@ end # Vector +/- ############################################################################### -# function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, -# ::Type{<:VectorAffineLike{T}}) where T -# return MOI.VectorAffineFunction{T} -# end +function promote_operation(::typeof(-), ::Type{T}, + ::Type{<:VectorAffineLike{T}}) where T + return MOI.VectorAffineFunction{T} +end +function promote_operation(::typeof(-), ::Type{T}, + ::Type{<:VectorQuadraticLike{T}}) where T + return MOI.VectorQuadraticFunction{T} +end function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T}, ::Type{<:VectorAffineLike{T}}, ::Type{<:VectorAffineLike{T}}) where T @@ -893,7 +934,7 @@ function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, d = MOI.output_dimension(g) @assert MOI.output_dimension(f) == d append!(f.terms, MOI.VectorAffineTerm.( - collect(1:d), + collect(1:d), MOI.ScalarAffineTerm.(op(one(T)), g.variables))) return f end @@ -1044,8 +1085,10 @@ 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...) +# Base.:+(α::Vector{T}, f::VectorLike{T}...) is too general as it also covers +# Base.:+(α::Vector) which is type piracy +function Base.:+(α::Vector{T}, f::VectorLike{T}, g::VectorLike{T}...) where T + return operate(+, T, α, f, g...) end function Base.:+(f::VectorLike{T}, α::Vector{T}) where T return operate(+, T, f, α) @@ -1158,7 +1201,9 @@ end function Base.:*(f::T, g::ScalarLike{T}) where T return operate(*, T, f, g) end - +function Base.:*(f::ScalarLike{T}, g::T) where T + return operate(*, T, g, f) +end ####################################### / ###################################### function promote_operation(::typeof(/), ::Type{T}, @@ -1192,7 +1237,18 @@ function operate!(::typeof(/), ::Type{T}, f::MOI.ScalarAffineFunction{T}, end function operate(::typeof(/), ::Type{T}, f::MOI.ScalarAffineFunction{T}, α::T) where T - return operate(/, T, copy(f), α) + return operate!(/, T, copy(f), α) +end + +function operate!(::typeof(/), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, + α::T) where T + map_terms!(term -> operate_term(/, term, α), f) + f.constant /= α + return f +end +function operate(::typeof(/), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, + α::T) where T + return operate!(/, T, copy(f), α) end function Base.:/(args::ScalarLike{T}...) where T @@ -1208,6 +1264,36 @@ function operate(::typeof(sum), ::Type{T}, vis::Vector{MOI.VariableIndex}) where end #################### Concatenation of MOI functions: `vcat` #################### +""" + fill_vector(vector::Vector, ::Type{T}, fill_func::Function, + dim_func::Function, funcs) where T + +Fill the vector `vector` with +`fill_func(vector, vector_offset, output_offset, func)` for each function `func` +in `funcs` where `vector_offset` (resp. `output_offset`) is the sum of +`dim_func(T, func)` (resp. `output_dim(T, func)`) of previous functions of +`func`. + + fill_vector(vector::Vector, ::Type{T}, vector_offset::Int, + output_offset::Int, fill_func::Function, + dim_func::Function, funcs...) where T + +Same than previous method but starting with possible nonzero `vector_offset` and +`output_offset`. +""" +function fill_vector end + +function fill_vector(vector::Vector, ::Type{T}, fill_func::Function, + dim_func::Function, funcs) where T + vector_offset = 0 + output_offset = 0 + for func in funcs + fill_func(vector, vector_offset, output_offset, func) + vector_offset += dim_func(T, func) + output_offset += output_dim(T, func) + end + @assert length(vector) == vector_offset +end function fill_vector(vector::Vector, ::Type, vector_offset::Int, output_offset::Int, fill_func::Function, dim_func::Function) @@ -1275,10 +1361,32 @@ function fill_constant(constant::Vector{T}, offset::Int, constant[offset .+ (1:n)] .= func.constants end +""" + vectorize(funcs::AbstractVector{MOI.ScalarAffineFunction{T}}) where T + +Returns the vector of scalar affine functions in the form of a +`MOI.VectorAffineFunction{T}`. +""" +function vectorize(funcs::AbstractVector{MOI.ScalarAffineFunction{T}}) where T + nterms = sum(func -> number_of_affine_terms(T, func), funcs) + out_dim = sum(func -> output_dim(T, func), funcs) + terms = Vector{MOI.VectorAffineTerm{T}}(undef, nterms) + constant = zeros(T, out_dim) + fill_vector(terms, T, fill_terms, number_of_affine_terms, funcs) + fill_vector(constant, T, fill_constant, output_dim, funcs) + return VAF(terms, constant) +end + function promote_operation(::typeof(vcat), ::Type{T}, ::Type{<:Union{ScalarAffineLike{T}, VVF, VAF{T}}}...) where T return VAF{T} end +function promote_operation( + ::typeof(vcat), ::Type{T}, + ::Type{<:Union{ScalarQuadraticLike{T}, VVF, VAF{T}, VQF{T}}}...) where T + return VQF{T} +end + function operate(::typeof(vcat), ::Type{T}, funcs::Union{ScalarAffineLike{T}, VVF, VAF{T}}...) where T nterms = sum(func -> number_of_affine_terms(T, func), funcs) diff --git a/src/error.jl b/src/error.jl index cd2eb55056..63c854c03f 100644 --- a/src/error.jl +++ b/src/error.jl @@ -8,13 +8,13 @@ abstract type UnsupportedError <: Exception end """ element_name(err::UnsupportedError) -Return the name of the element that is not supported +Return the name of the element that is not supported. """ function element_name end function Base.showerror(io::IO, err::UnsupportedError) print(io, typeof(err), ": ", element_name(err), - " is not supported by the the model") + " is not supported by the model") m = message(err) if Base.isempty(m) print(io, ".") @@ -56,9 +56,9 @@ end """ message(err::Union{UnsupportedError, NotAllowedError}) -Return a `String` containing a human-friendly explanation why the operation +Return a `String` containing a human-friendly explanation of why the operation is not supported/cannot be performed. It is printed in the error message if it -is not empty. By convention, it should be stored in the `message` field, it is -it the case, the `message` method does not have to be implemented. +is not empty. By convention, it should be stored in the `message` field; if +this is the case, the `message` method does not have to be implemented. """ message(err::Union{UnsupportedError, NotAllowedError}) = err.message diff --git a/src/functions.jl b/src/functions.jl index db194bc946..208140f942 100644 --- a/src/functions.jl +++ b/src/functions.jl @@ -23,6 +23,12 @@ Abstract supertype for scalar-valued function objects. abstract type AbstractScalarFunction <: AbstractFunction end output_dimension(::AbstractScalarFunction) = 1 +@static if VERSION >= v"0.7-" + # This allows to use `AbstractScalarFunction`s in broadcast calls without + # the need to embed it in a `Ref` + Base.broadcastable(f::AbstractScalarFunction) = Ref(f) +end + """ AbstractVectorFunction diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 2821d32d19..c02c45a12d 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -1,26 +1,59 @@ +using Compat +using Compat.Test +using MathOptInterface +const MOI = MathOptInterface +const MOIU = MOI.Utilities + @testset "Function tests" begin w = MOI.VariableIndex(0) x = MOI.VariableIndex(1) y = MOI.VariableIndex(2) z = MOI.VariableIndex(3) - @testset "operate vcat" begin - v = MOI.VectorOfVariables([y, w]) - f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2, 4], - [x, z]), 5) + @testset "Vectorization" begin g = MOI.VectorAffineFunction(MOI.VectorAffineTerm.([3, 1], MOI.ScalarAffineTerm.([5, 2], [y, x])), [3, 1, 4]) - wf = MOI.SingleVariable(w) - xf = MOI.SingleVariable(x) - @test MOIU.promote_operation(vcat, Int, typeof(wf), typeof(f), - typeof(v), Int, typeof(g), typeof(xf), - Int) == MOI.VectorAffineFunction{Int} - F = MOIU.operate(vcat, Int, wf, f, v, 3, g, xf, -4) - @test F.terms == MOI.VectorAffineTerm.([1, 2, 2, 3, 4, 8, 6, 9], - MOI.ScalarAffineTerm.([1, 2, 4, 1, 1, 5, 2, 1], - [w, x, z, y, w, y, x, x])) - @test F.constants == [0, 5, 0, 0, 3, 3, 1, 4, 0, -4] + @testset "vectorize" begin + g1 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2, x)], 3) + g2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Int}[], 1) + g3 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(5, y)], 4) + @test g ≈ MOIU.vectorize([g1, g2, g3]) + end + @testset "operate vcat" begin + v = MOI.VectorOfVariables([y, w]) + wf = MOI.SingleVariable(w) + xf = MOI.SingleVariable(x) + @testset "Variable" begin + # TODO #616 + end + f = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([2, 4], [x, z]), 5) + g = MOI.VectorAffineFunction( + MOI.VectorAffineTerm.([3, 1], MOI.ScalarAffineTerm.([5, 2], [y, x])), + [3, 1, 4]) + @testset "Affine" begin + @test MOIU.promote_operation(vcat, Int, typeof(wf), typeof(f), + typeof(v), Int, typeof(g), typeof(xf), + Int) == MOI.VectorAffineFunction{Int} + F = MOIU.operate(vcat, Int, wf, f, v, 3, g, xf, -4) + expected_terms = MOI.VectorAffineTerm.( + [1, 2, 2, 3, 4, 8, 6, 9], + MOI.ScalarAffineTerm.([1, 2, 4, 1, 1, 5, 2, 1], + [w, x, z, y, w, y, x, x])) + expected_constants = [0, 5, 0, 0, 3, 3, 1, 4, 0, -4] + F = MOIU.operate(vcat, Int, wf, f, v, 3, g, xf, -4) + @test F.terms == expected_terms + @test F.constants == expected_constants + end + @testset "Quadratic" begin + @test MOIU.promote_operation( + vcat, Int, MOI.VectorQuadraticFunction{Int}, typeof(wf), + typeof(f), typeof(v), Int, MOI.ScalarQuadraticFunction{Int}, + typeof(g), typeof(xf), Int) == MOI.VectorQuadraticFunction{Int} + # TODO + end + end end @testset "MultirowChange construction" begin chg1 = MOI.MultirowChange(w, [(Int32(2), 2.0), (Int32(1), 3.0)]) @@ -154,12 +187,27 @@ @test g.constant == 5 end @testset "Scalar" begin + @testset "Variable" begin + @testset "zero" begin + f = MOI.SingleVariable(MOI.VariableIndex(0)) + g = MOI.SingleVariable(MOI.VariableIndex(1)) + @test !iszero(f) + @test !iszero(g) + end + end @testset "Affine" begin @testset "zero" begin f = @inferred MOIU.zero(MOI.ScalarAffineFunction{Float64}) + @test iszero(f) @test MOIU.isapprox_zero(f, 1e-16) end @testset "promote_operation" begin + @test MOIU.promote_operation( + -, Int, MOI.SingleVariable + ) == MOI.ScalarAffineFunction{Int} + @test MOIU.promote_operation( + -, Int, MOI.ScalarAffineFunction{Int} + ) == MOI.ScalarAffineFunction{Int} @test MOIU.promote_operation(+, Float64, MOI.SingleVariable, MOI.SingleVariable) == MOI.ScalarAffineFunction{Float64} @test MOIU.promote_operation(+, Float64, @@ -169,7 +217,7 @@ MOI.ScalarAffineFunction{Int}, MOI.ScalarAffineFunction{Int}) == MOI.ScalarAffineFunction{Int} end - @testset "Comparison tolerance" begin + @testset "Comparison" begin @test MOIU.operate(+, Float64, MOI.SingleVariable(x), MOI.SingleVariable(z)) + 1.0 ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 1e-7, 1], [x, y, z]), 1.0) atol=1e-6 @@ -177,9 +225,16 @@ f2 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 1.0) @test f1 ≈ f2 atol=1e-6 fdiff = f1 - f2 - MOIU.canonicalize!(fdiff) - @test !MOIU.isapprox_zero(fdiff, 1e-8) - @test MOIU.isapprox_zero(fdiff, 1e-6) + @testset "With iszero" begin + @test !iszero(fdiff) + @test iszero(f1 - f1) + @test iszero(f2 - f2) + end + @testset "With tolerance" begin + MOIU.canonicalize!(fdiff) + @test !MOIU.isapprox_zero(fdiff, 1e-8) + @test MOIU.isapprox_zero(fdiff, 1e-6) + end end @testset "canonical" begin f = MOIU.canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2, 1, 3, -2, -3], [y, x, z, x, z]), 5)) @@ -204,14 +259,22 @@ g = convert(MOI.ScalarAffineFunction{Float64}, MOI.SingleVariable(x)) @test convert(MOI.SingleVariable, g) == MOI.SingleVariable(x) end - @testset "operate" begin + @testset "operate with Float64 coefficient type" begin + f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 4.0], + [x, y]), + 5.0) + @test f ≈ 2.0f / 2.0 + end + @testset "operate with Int coefficient type" begin f = MOIU.canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 3, 1, 2, -3, 2], [w, y, w, x, x, z]), 2) + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-1, -2, -2, 3, 2], [ y, z, w, x, y]), 3)) + @test f === +f @test f ≈ MOI.SingleVariable(x) + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 4], [x, y]), 5) - @test f ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 4], [x, y]), 5) + MOI.SingleVariable(x) + @test f ≈ f * 1 + @test f ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 2], [x, y]), 2) * 2 + 1 @test f ≈ MOI.SingleVariable(x) - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-1, -4], [x, y]), -5) @test f ≈ MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([3, 4], [x, y]), 5) - MOI.SingleVariable(x) end @@ -234,6 +297,9 @@ @test MOIU.isapprox_zero(f, 1e-16) end @testset "promote_operation" begin + @test MOIU.promote_operation( + -, Int, MOI.ScalarQuadraticFunction{Int} + ) == MOI.ScalarQuadraticFunction{Int} @test MOIU.promote_operation(+, Int, MOI.ScalarQuadraticFunction{Int}, MOI.ScalarQuadraticFunction{Int}) == MOI.ScalarQuadraticFunction{Int} @@ -264,14 +330,21 @@ f = 7 + 3fx + 1fx * fx + 2fy * fy + 3fx * fy MOIU.canonicalize!(f) @test MOI.output_dimension(f) == 1 - @testset "isapprox_zero" begin - @test !MOIU.isapprox_zero(f, 1e-8) - # Test isapprox_zero with zero terms - @test MOIU.isapprox_zero(0 * f, 1e-8) - g = 1.0fx * fy - (1 + 1e-6) * fy * fx - MOIU.canonicalize!(g) - @test MOIU.isapprox_zero(g, 1e-5) - @test !MOIU.isapprox_zero(g, 1e-7) + @testset "Comparison" begin + @testset "With iszero" begin + @test !iszero(f) + @test iszero(0 * f) + @test iszero(f - f) + end + @testset "With tolerance" begin + @test !MOIU.isapprox_zero(f, 1e-8) + # Test isapprox_zero with zero terms + @test MOIU.isapprox_zero(0 * f, 1e-8) + g = 1.0fx * fy - (1 + 1e-6) * fy * fx + MOIU.canonicalize!(g) + @test MOIU.isapprox_zero(g, 1e-5) + @test !MOIU.isapprox_zero(g, 1e-7) + end end @testset "convert" begin @test_throws InexactError convert(MOI.SingleVariable, f) @@ -282,8 +355,12 @@ end @testset "operate" begin @test f ≈ 7 + (fx + 2fy) * (1fx + fy) + 3fx + @test f ≈ -(-7 - 3fx) + (fx + 2fy) * (1fx + fy) + @test f ≈ -((fx + 2fy) * (MOIU.operate(-, Int, fx) - fy)) + 3fx + 7 @test f ≈ 7 + MOIU.operate(*, Int, fx, fx) + 3fx * (fy + 1) + 2fy * fy @test f ≈ (fx + 2) * (fx + 1) + (fy + 1) * (2fy + 3fx) + (5 - 3fx - 2fy) + @test (2.0*fx*fx + 2.0fx*fy + 2.0fx + 2.0)/2.0 ≈ 1.0fx*fx + 1.0fx*fy + 1.0fx + 1.0 + @test (2.0fx + 2.0)/2.0 ≈ 1.0fx + 1.0 @test f ≈ begin MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(3, x)], MOI.ScalarQuadraticTerm.([1], [x], [x]), 4) + @@ -527,6 +604,11 @@ @test MOIU.promote_operation(-, T, t1, t2) == MOI.VectorQuadraticFunction{T} end end + for t in [MOI.VectorOfVariables, MOI.VectorAffineFunction{T}] + @test MOIU.promote_operation(-, T, t) == MOI.VectorAffineFunction{T} + end + t = MOI.VectorQuadraticFunction{T} + @test MOIU.promote_operation(-, T, t) == MOI.VectorQuadraticFunction{T} end α = [1, 2, 3] @@ -585,4 +667,4 @@ @test v - α ≈ - α_minus_v @test g - f ≈ - f_minus_g end -end \ No newline at end of file +end diff --git a/test/bridge.jl b/test/bridge.jl index 16526c28d4..5ec6cf3f70 100644 --- a/test/bridge.jl +++ b/test/bridge.jl @@ -1,3 +1,12 @@ +using Compat +using Compat.Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + # Model not supporting Interval MOIU.@model(SimpleModel, (), @@ -131,6 +140,17 @@ MOIU.@model(NoRSOCModel, (MOI.VectorOfVariables,), (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)) +MOIU.@model(GreaterNonnegModel, + (), + (MOI.GreaterThan,), + (MOI.Nonnegatives,), + (), + (MOI.SingleVariable,), + (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction), + (MOI.VectorOfVariables,), + (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)) + + MOIU.@model(ModelNoVAFinSOC, (), (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), @@ -143,13 +163,28 @@ MOIU.@model(ModelNoVAFinSOC, (MOI.VectorOfVariables,), (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)) -MOI.supports_constraint(::ModelNoVAFinSOC{Float64}, - ::Type{MOI.VectorAffineFunction{Float64}}, +MOI.supports_constraint(::ModelNoVAFinSOC{Float64}, + ::Type{MOI.VectorAffineFunction{Float64}}, ::Type{MOI.SecondOrderCone}) = false +# Model supporting nothing +MOIU.@model NothingModel () () () () () () () () + @testset "LazyBridgeOptimizer" begin + @testset "Unsupported constraint with cycles" begin + # Test that `supports_constraint` works correctly when it is not + # supported but the bridges forms a cycle + mock = MOIU.MockOptimizer(NothingModel{Float64}()) + bridged = MOIB.full_bridge_optimizer(mock, Float64) + @test !MOI.supports_constraint( + bridged, MOI.SingleVariable, MOI.GreaterThan{Float64}) + @test !MOI.supports_constraint( + bridged, MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) + end + mock = MOIU.MockOptimizer(NoRSOCModel{Float64}()) - bridged_mock = MOIB.LazyBridgeOptimizer(mock, Model{Float64}()) + bridged_mock = MOIB.LazyBridgeOptimizer( + mock, MOIB.AllBridgedConstraints{Float64}()) @testset "UnsupportedConstraint when it cannot be bridged" begin x = MOI.add_variables(bridged_mock, 4) @@ -216,10 +251,17 @@ MOI.supports_constraint(::ModelNoVAFinSOC{Float64}, @testset "Supports" begin full_bridged_mock = MOIB.full_bridge_optimizer(mock, Float64) + greater_nonneg_mock = MOIU.MockOptimizer(GreaterNonnegModel{Float64}()) + full_bridged_greater_nonneg = MOIB.full_bridge_optimizer( + greater_nonneg_mock, Float64) for F in [MOI.SingleVariable, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}] @test MOI.supports_constraint(full_bridged_mock, F, MOI.Interval{Float64}) + @test !MOI.supports_constraint( + greater_nonneg_mock, F, MOI.LessThan{Float64}) + @test MOI.supports_constraint( + full_bridged_greater_nonneg, F, MOI.LessThan{Float64}) end for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64}] @@ -227,6 +269,10 @@ MOI.supports_constraint(::ModelNoVAFinSOC{Float64}, MOI.PositiveSemidefiniteConeSquare) @test MOI.supports_constraint(full_bridged_mock, F, MOI.GeometricMeanCone) + @test !MOI.supports_constraint( + greater_nonneg_mock, F, MOI.Nonpositives) + @test MOI.supports_constraint( + full_bridged_greater_nonneg, F, MOI.Nonnegatives) end for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}] # The bridges in this for loop do not support yet @@ -241,7 +287,7 @@ MOI.supports_constraint(::ModelNoVAFinSOC{Float64}, MOI.RootDetConeTriangle) end mock2 = MOIU.MockOptimizer(ModelNoVAFinSOC{Float64}()) - @test !MOI.supports_constraint(mock2, MOI.VectorAffineFunction{Float64}, + @test !MOI.supports_constraint(mock2, MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) full_bridged_mock2 = MOIB.full_bridge_optimizer(mock2, Float64) @test MOI.supports_constraint(full_bridged_mock2, MOI.VectorAffineFunction{Float64}, @@ -276,6 +322,108 @@ end mock = MOIU.MockOptimizer(SimpleModel{Float64}()) config = MOIT.TestConfig() + @testset "GreaterToLess" begin + bridged_mock = MOIB.GreaterToLess{Float64}(mock) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) + MOIT.linear6test(bridged_mock, config) + + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}())) + test_delete_bridge(bridged_mock, ci, 2, + ((MOI.ScalarAffineFunction{Float64}, + MOI.LessThan{Float64}, 1),)) + end + + @testset "LessToGreater" begin + bridged_mock = MOIB.LessToGreater{Float64}(mock) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0] + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [2.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0] + ) + ) + MOIT.solve_set_scalaraffine_lessthan(bridged_mock, config) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [1.0] + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.5]), + MOI.FEASIBLE_POINT, + (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0.5] + ) + ) + MOIT.solve_coef_scalaraffine_lessthan(bridged_mock, config) + + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())) + test_delete_bridge(bridged_mock, ci, 1, + ((MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, 0),)) + end + + @testset "NonnegToNonpos" begin + bridged_mock = MOIB.NonnegToNonpos{Float64}(mock) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) + MOIT.linear7test(bridged_mock, config) + + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}())) + test_delete_bridge(bridged_mock, ci, 2, + ((MOI.VectorAffineFunction{Float64}, + MOI.Nonpositives, 1),)) + end + + @testset "NonposToNonneg" begin + bridged_mock = MOIB.NonposToNonneg{Float64}(mock) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, 0]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [100, -100])) + MOIT.linear7test(bridged_mock, config) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.0, 0.0]) + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, + MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [1.0, 0.75]) + ) + ) + MOIT.solve_const_vectoraffine_nonpos(bridged_mock, config) + + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.5]) + ), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, MOI.OPTIMAL, (MOI.FEASIBLE_POINT, [0.25]) + ) + ) + MOIT.solve_multirow_vectoraffine_nonpos(bridged_mock, config) + + ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonpositives}())) + test_delete_bridge(bridged_mock, ci, 1, + ((MOI.VectorAffineFunction{Float64}, + MOI.Nonnegatives, 0),)) + end + @testset "Vectorize" begin bridged_mock = MOIB.Vectorize{Float64}(mock) @@ -379,7 +527,7 @@ end 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) ≈ + @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),)) @@ -439,7 +587,7 @@ end @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) ≈ + @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),)) diff --git a/test/errors.jl b/test/errors.jl index 79cc24267d..f7f7b1b352 100644 --- a/test/errors.jl +++ b/test/errors.jl @@ -37,7 +37,7 @@ catch err @test sprint(showerror, err) == "$MOI.UnsupportedConstraint{$MOI.SingleVariable,$MOI.EqualTo{$Int}}:" * " `$MOI.SingleVariable`-in-`$MOI.EqualTo{$Int}` constraints is" * - " not supported by the the model." + " not supported by the model." end @test_throws MOI.UnsupportedConstraint begin MOI.add_constraint(model, vi, MOI.EqualTo(0)) @@ -174,9 +174,9 @@ end @testset "Error messages" begin @test sprint(showerror, MOI.UnsupportedAttribute(MOI.Name())) == "$MOI.UnsupportedAttribute{$MOI.Name}:" * - " Attribute $MOI.Name() is not supported by the the model." + " Attribute $MOI.Name() is not supported by the model." @test sprint(showerror, MOI.UnsupportedAttribute(MOI.Name(), "Message")) == "$MOI.UnsupportedAttribute{$MOI.Name}:" * - " Attribute $MOI.Name() is not supported by the the model: Message" + " Attribute $MOI.Name() is not supported by the model: Message" @test sprint(showerror, MOI.SetAttributeNotAllowed(MOI.Name())) == "$MOI.SetAttributeNotAllowed{$MOI.Name}:" * " Setting attribute $MOI.Name() cannot be performed. You may want to use" * " a `CachingOptimizer` in `AUTOMATIC` mode or you may need to call" * diff --git a/test/functions.jl b/test/functions.jl index a47effb7ff..0c62a0c1d5 100644 --- a/test/functions.jl +++ b/test/functions.jl @@ -1,7 +1,21 @@ +using Compat +using Compat.Test +using MathOptInterface +const MOI = MathOptInterface + @testset "Functions" begin x = MOI.VariableIndex(1) y = MOI.VariableIndex(2) z = MOI.VariableIndex(3) + @testset "Broadcast" begin + xf = MOI.SingleVariable(x) + yf = MOI.SingleVariable(y) + zf = MOI.SingleVariable(z) + function sum_indices(sv1::MOI.SingleVariable, sv2::MOI.SingleVariable) + return sv1.variable.value + sv2.variable.value + end + @test sum_indices.(xf, [yf, zf]) == [3, 4] + end @testset "Copy" begin @testset "VectorOfVariables" begin f = MOI.VectorOfVariables([x, y]) From 9c962ace3adb753af16e1a7ca04d6ec3f43e65ac Mon Sep 17 00:00:00 2001 From: Guilherme Bodin Date: Sun, 24 Feb 2019 18:01:11 -0300 Subject: [PATCH 2/5] fixing conflict --- src/Utilities/functions.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index c65f3355fa..1d13543d03 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -1238,7 +1238,6 @@ end function operate(::typeof(/), ::Type{T}, f::MOI.ScalarAffineFunction{T}, α::T) where T return operate!(/, T, copy(f), α) -<<<<<<< HEAD end function operate!(::typeof(/), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, @@ -1250,8 +1249,6 @@ end function operate(::typeof(/), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, α::T) where T return operate!(/, T, copy(f), α) -======= ->>>>>>> 1bdae588867d2c6a88485c94cba9e29ca6e76fbb end function Base.:/(args::ScalarLike{T}...) where T From 57575bca89db578877f1ce262cfe2164ca6c4e66 Mon Sep 17 00:00:00 2001 From: guilhermebodin Date: Thu, 9 May 2019 18:14:14 -0300 Subject: [PATCH 3/5] update fork --- src/Utilities/functions.jl | 11 -------- test/Utilities/functions.jl | 54 +------------------------------------ 2 files changed, 1 insertion(+), 64 deletions(-) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 801b74f3ec..28b322aeed 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -1262,17 +1262,6 @@ function operate(::typeof(/), ::Type{T}, return operate!(/, T, copy(f), α) end -function operate!(::typeof(/), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, - α::T) where T - map_terms!(term -> operate_term(/, term, α), f) - f.constant /= α - return f -end -function operate(::typeof(/), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}, - α::T) where T - return operate!(/, T, copy(f), α) -end - function Base.:/(args::ScalarLike{T}...) where T return operate(/, T, args...) end diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 9bd554c489..84442a409c 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -379,63 +379,11 @@ end MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], MOI.ScalarQuadraticTerm.([-1, -2, -3], [x, y, x], [x, y, y]), -2) end -<<<<<<< HEAD - @testset "operate" begin - @test f ≈ 7 + (fx + 2fy) * (1fx + fy) + 3fx - @test f ≈ -(-7 - 3fx) + (fx + 2fy) * (1fx + fy) - @test f ≈ -((fx + 2fy) * (MOIU.operate(-, Int, fx) - fy)) + 3fx + 7 - @test f ≈ 7 + MOIU.operate(*, Int, fx, fx) + 3fx * (fy + 1) + 2fy * fy - @test f ≈ (fx + 2) * (fx + 1) + (fy + 1) * (2fy + 3fx) + (5 - 3fx - 2fy) - @test (2.0*fx*fx + 2.0fx*fy + 2.0fx + 2.0)/2.0 ≈ 1.0fx*fx + 1.0fx*fy + 1.0fx + 1.0 - @test (2.0fx + 2.0)/2.0 ≈ 1.0fx + 1.0 - @test f ≈ begin - MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(3, x)], - MOI.ScalarQuadraticTerm.([1], [x], [x]), 4) + - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], - MOI.ScalarQuadraticTerm.([2, 3], [y, x], [y, y]), 3) - end - @test f ≈ begin - MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(3, x)], - MOI.ScalarQuadraticTerm.([1], [x], [x]), 10) - - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], - MOI.ScalarQuadraticTerm.([-2, -3], [y, x], [y, y]), 3) - end - @test f ≈ begin - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(3, x)], 5) + - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], - MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 2) - end - @test f ≈ begin - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(3, x)], 5) - - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], - MOI.ScalarQuadraticTerm.([-1, -2, -3], [x, y, x], [x, y, y]), -2) - end - @test f ≈ begin - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], - MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 2) + - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(3, x)], 5) - end - @test f ≈ begin - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], - MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 12) - - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-3, x)], 5) - end - @test f ≈ begin - MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.([2], [x]), - MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 7) + - MOI.SingleVariable(x) - end - @test f ≈ MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.([3], [x]), - MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 10) - 3 - @test f ≈ - 2.0 * MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm.([3.0], [x]), - MOI.ScalarQuadraticTerm.([1.0, 2.0, 3.0], [x, y, x], [x, y, y]), 7.0) / 2.0 -======= + @test f ≈ begin MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 2) + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(3, x)], 5) ->>>>>>> upstream/master end @test f ≈ begin MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], From 7fd688d3deef64fd7defc0f881567e30ac3b20ba Mon Sep 17 00:00:00 2001 From: guilhermebodin Date: Thu, 9 May 2019 18:15:00 -0300 Subject: [PATCH 4/5] fork update --- test/Utilities/functions.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 84442a409c..3e50da2433 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -379,7 +379,6 @@ end MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], MOI.ScalarQuadraticTerm.([-1, -2, -3], [x, y, x], [x, y, y]), -2) end - @test f ≈ begin MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Int}[], MOI.ScalarQuadraticTerm.([1, 2, 3], [x, y, x], [x, y, y]), 2) + From b5a21087d856f0c432b21ad3217f5e8ea7119533 Mon Sep 17 00:00:00 2001 From: guilhermebodin Date: Thu, 1 Aug 2019 12:05:02 -0300 Subject: [PATCH 5/5] [ci skip] update NEWS.md --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index ac8aa245a8..af07d3f785 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,7 @@ v0.9.0 (May 2?, 2019) - Support for indicator constraints was added (#709, #712). - `DualObjectiveValue` attribute was added (#473). - `RawParameter` attribute was added (#733). +- A `dual_set` function was added (#804). - A `Benchmarks` submodule was added to facilitate solver benchmarking (#769). - A `submit` function was added, this may for intance allow the user to submit solutions or cuts to the solver from a callback (#775). @@ -44,6 +45,7 @@ v0.9.0 (May 2?, 2019) a `VectorOfVariables` (#616). * Fix a type piracy of `operate` (#784). * The `load_constraint` fallback signature was fixed (#760). + * The `set_dot` function was extended to work with sparse arrays (#805). - Bridges improvements: * The bridges no longer store the constraint function and set before it is briged, the bridges now have to implement `ConstraintFunction` and