diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index a52a8b57f1..0f836763f1 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -21,19 +21,6 @@ include("bridgeoptimizer.jl") include("singlebridgeoptimizer.jl") include("lazybridgeoptimizer.jl") -# This is used by JuMP and removes the need to update JuMP everytime a bridge is added -MOIU.@model(AllBridgedConstraints, - (), - (MOI.EqualTo, MOI.LessThan, MOI.GreaterThan, MOI.Interval,), - (MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, - MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, - MOI.PositiveSemidefiniteConeSquare, - MOI.LogDetConeTriangle, MOI.RootDetConeTriangle), - (), - (MOI.SingleVariable,), - (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction), - (MOI.VectorOfVariables,), - (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)) """ full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T @@ -41,8 +28,7 @@ Returns a `LazyBridgeOptimizer` bridging `model` for every bridge defined in this package and for the coefficient type `T`. """ function full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T - cache = MOIU.UniversalFallback(AllBridgedConstraints{T}()) - bridged_model = LazyBridgeOptimizer(model, cache) + bridged_model = LazyBridgeOptimizer(model) add_bridge(bridged_model, GreaterToLessBridge{T}) add_bridge(bridged_model, LessToGreaterBridge{T}) add_bridge(bridged_model, NonnegToNonposBridge{T}) @@ -66,49 +52,35 @@ function full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T 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) +const GreaterToLess{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GreaterToLessBridge{T}, OT} +const LessToGreater{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LessToGreaterBridge{T}, OT} +const NonnegToNonpos{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonnegToNonposBridge{T}, OT} +const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT} include("vectorizebridge.jl") -@bridge Vectorize VectorizeBridge () (MOI.EqualTo, MOI.LessThan, MOI.GreaterThan,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () +const Vectorize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorizeBridge{T}, OT} include("scalarizebridge.jl") -@bridge Scalarize ScalarizeBridge () () (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) +const Scalarize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarizeBridge{T}, OT} 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.PositiveSemidefiniteConeTriangle, MOI.LogDetConeTriangle, - MOI.RootDetConeTriangle), - (MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2), (), (), (), - (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) - ) +const ScalarSlack{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarSlackBridge{T}, OT} +const VectorSlack{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorSlackBridge{T}, OT} include("functionize_bridge.jl") -@bridge ScalarFunctionize ScalarFunctionizeBridge () (MOI.Interval, MOI.LessThan, MOI.GreaterThan) () () (MOI.SingleVariable,) () () () -@bridge(VectorFunctionize, VectorFunctionizeBridge, (), (), - (MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, - MOI.RotatedSecondOrderCone, MOI.GeometricMeanCone, - MOI.PositiveSemidefiniteConeSquare, MOI.PositiveSemidefiniteConeTriangle, MOI.LogDetConeTriangle, - MOI.RootDetConeTriangle), - (MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2), (), (), - (MOI.VectorOfVariables,), () - ) +const ScalarFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ScalarFunctionizeBridge{T}, OT} +const VectorFunctionize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorFunctionizeBridge{T}, OT} include("intervalbridge.jl") -@bridge SplitInterval SplitIntervalBridge () (MOI.Interval,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () () +const SplitInterval{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SplitIntervalBridge{T}, OT} include("rsocbridge.jl") -@bridge RSOC RSOCBridge () () (MOI.RotatedSecondOrderCone,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,) +const RSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCBridge{T}, OT} include("quadtosocbridge.jl") -@bridge QuadtoSOC QuadtoSOCBridge () (MOI.LessThan, MOI.GreaterThan) () () () (MOI.ScalarQuadraticFunction,) () () +const QuadtoSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadtoSOCBridge{T}, OT} include("geomeanbridge.jl") -@bridge GeoMean GeoMeanBridge () () (MOI.GeometricMeanCone,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,) +const GeoMean{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GeoMeanBridge{T}, OT} include("squarepsdbridge.jl") -@bridge SquarePSD SquarePSDBridge () () (MOI.PositiveSemidefiniteConeSquare,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction) +const SquarePSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SquarePSDBridge{T}, OT} include("detbridge.jl") -@bridge LogDet LogDetBridge () () (MOI.LogDetConeTriangle,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,) -@bridge RootDet RootDetBridge () () (MOI.RootDetConeTriangle,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,) +const LogDet{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LogDetBridge{T}, OT} +const RootDet{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RootDetBridge{T}, OT} include("soctopsdbridge.jl") -@bridge SOCtoPSD SOCtoPSDBridge () () (MOI.SecondOrderCone,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,) -@bridge RSOCtoPSD RSOCtoPSDBridge () () (MOI.RotatedSecondOrderCone,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,) +const SOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SOCtoPSDBridge{T}, OT} +const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}, OT} end # module diff --git a/src/Bridges/bridgeoptimizer.jl b/src/Bridges/bridgeoptimizer.jl index 1444dc71c7..53fbaf1f5b 100644 --- a/src/Bridges/bridgeoptimizer.jl +++ b/src/Bridges/bridgeoptimizer.jl @@ -64,7 +64,7 @@ end Return the `AbstractBridge` used to bridge the constraint with index `ci`. """ -bridge(b::AbstractBridgeOptimizer, ci::CI) = b.bridges[ci] +bridge(b::AbstractBridgeOptimizer, ci::CI) = b.bridges[ci.value] # By convention, they should be stored in a `bridges` field using a # dictionary-like object. @@ -74,12 +74,12 @@ MOI.optimize!(b::AbstractBridgeOptimizer) = MOI.optimize!(b.model) # By convention, the model should be stored in a `model` field function MOI.is_empty(b::AbstractBridgeOptimizer) - return isempty(b.bridges) && MOI.is_empty(b.model) + return isempty(b.bridges) && isempty(b.constraint_types) && MOI.is_empty(b.model) end function MOI.empty!(b::AbstractBridgeOptimizer) MOI.empty!(b.model) - MOI.empty!(b.bridged) empty!(b.bridges) + empty!(b.constraint_types) end function MOI.supports(b::AbstractBridgeOptimizer, attr::Union{MOI.AbstractModelAttribute, @@ -96,26 +96,29 @@ function MOIU.supports_default_copy_to(b::AbstractBridgeOptimizer, end # References +function _constraint_index(b::AbstractBridgeOptimizer, i::Integer) + F, S = b.constraint_types[i] + return MOI.ConstraintIndex{F, S}(i) +end MOI.is_valid(b::AbstractBridgeOptimizer, vi::VI) = MOI.is_valid(b.model, vi) -function MOI.is_valid(b::AbstractBridgeOptimizer, ci::CI) +function MOI.is_valid(b::AbstractBridgeOptimizer, ci::CI{F, S}) where {F, S} if is_bridged(b, typeof(ci)) - return MOI.is_valid(b.bridged, ci) + return 1 ≤ ci.value ≤ length(b.bridges) && b.bridges[ci.value] !== nothing && + (F, S) == b.constraint_types[ci.value] else return MOI.is_valid(b.model, ci) end end function MOI.delete(b::AbstractBridgeOptimizer, vi::VI) - for (F, S) in MOI.get(b.bridged, MOI.ListOfConstraints()) - if F == MOI.SingleVariable - for ci in MOI.get(b.bridged, MOI.ListOfConstraintIndices{F, S}()) - f = MOI.get(b.bridged, MOI.ConstraintFunction(), ci)::MOI.SingleVariable - if f.variable == vi - MOI.delete(b, ci) - end + for i in eachindex(b.bridges) + if b.bridges[i] !== nothing && b.constraint_types[i][1] == MOI.SingleVariable + ci = _constraint_index(b, i) + f = MOI.get(b, MOI.ConstraintFunction(), ci)::MOI.SingleVariable + if f.variable == vi + MOI.delete(b, ci) end end end - MOIU.delete_variable_in_constraints(b.bridged, vi) MOI.delete(b.model, vi) end function MOI.delete(b::AbstractBridgeOptimizer, ci::CI) @@ -124,8 +127,7 @@ function MOI.delete(b::AbstractBridgeOptimizer, ci::CI) throw(MOI.InvalidIndex(ci)) end MOI.delete(b, bridge(b, ci)) - delete!(b.bridges, ci) - MOI.delete(b.bridged, ci) + b.bridges[ci.value] = nothing else MOI.delete(b.model, ci) end @@ -135,15 +137,17 @@ end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ListOfConstraintIndices{F, S}) where {F, S} if is_bridged(b, F, S) - list = MOI.get(b.bridged, attr) + list = MOI.ConstraintIndex{F, S}[MOI.ConstraintIndex{F, S}(i) for i in eachindex(b.bridges) if MOI.is_valid(b, MOI.ConstraintIndex{F, S}(i))] else list = MOI.get(b.model, attr) end for bridge in values(b.bridges) - for c in MOI.get(bridge, attr) - i = something(findfirst(isequal(c), list), 0) - if !iszero(i) - MOI.deleteat!(list, i) + if bridge !== nothing + for c in MOI.get(bridge, attr) + i = something(findfirst(isequal(c), list), 0) + if !iszero(i) + MOI.deleteat!(list, i) + end end end end @@ -152,8 +156,10 @@ end function _numberof(b::AbstractBridgeOptimizer, model::MOI.ModelLike, attr::Union{MOI.NumberOfConstraints, MOI.NumberOfVariables}) s = MOI.get(model, attr) - for v in values(b.bridges) - s -= MOI.get(v, attr) + for bridge in values(b.bridges) + if bridge !== nothing + s -= MOI.get(bridge, attr) + end end return s end @@ -163,15 +169,27 @@ end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.NumberOfConstraints{F, S}) where {F, S} if is_bridged(b, F, S) - # The constraints contained in `b.bridged` may have been added by - # bridges - return _numberof(b, b.bridged, attr) + s = count(i -> MOI.is_valid(b, MOI.ConstraintIndex{F, S}(i)), eachindex(b.bridges)) else - return _numberof(b, b.model, attr) + s = MOI.get(b.model, attr) end + # The constraints counted in `s` may have been added by bridges + for v in values(b.bridges) + if v !== nothing + s -= MOI.get(v, attr) + end + end + return s end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ListOfConstraints) - list_of_types = [MOI.get(b.model, attr); MOI.get(b.bridged, attr)] + list_of_types = MOI.get(b.model, attr) + list_of_bridged_types = Set{Tuple{DataType, DataType}}() + for i in eachindex(b.bridges) + if b.bridges[i] !== nothing + push!(list_of_bridged_types, b.constraint_types[i]) + end + end + list_of_types = [list_of_types; collect(list_of_bridged_types)] # Some constraint types show up in `list_of_types` even when all the # constraints of that type have been created by bridges and not by the user. # The code in `NumberOfConstraints` takes care of removing these constraints @@ -224,8 +242,7 @@ end # Constraint attributes function MOI.get(b::AbstractBridgeOptimizer, - attr::MOI.AbstractConstraintAttribute, - ci::CI) + attr::MOI.AbstractConstraintAttribute, ci::CI) if is_bridged(b, typeof(ci)) return MOI.get(b, attr, bridge(b, ci)) else @@ -234,7 +251,7 @@ function MOI.get(b::AbstractBridgeOptimizer, end function MOI.supports(b::AbstractBridgeOptimizer, attr::MOI.AbstractConstraintAttribute, - IndexType::Type{CI{F, S}}) where {F,S} + IndexType::Type{CI{F, S}}) where {F, S} if is_bridged(b, IndexType) return MOI.supports(b, attr, concrete_bridge_type(b, F, S)) else @@ -255,7 +272,7 @@ end function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, constraint_index::CI) if is_bridged(b, typeof(constraint_index)) - return MOI.get(b.bridged, attr, constraint_index) + return get(b.con_to_name, constraint_index, MOIU.EMPTYSTRING) else return MOI.get(b.model, attr, constraint_index) end @@ -264,7 +281,7 @@ end function MOI.supports(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, Index::Type{CI{F,S}}) where {F,S} if is_bridged(b, Index) - return MOI.supports(b.bridged, attr, Index) + return true else return MOI.supports(b.model, attr, Index) end @@ -272,54 +289,40 @@ end function MOI.set(b::AbstractBridgeOptimizer, attr::MOI.ConstraintName, constraint_index::CI, name::String) if is_bridged(b, typeof(constraint_index)) - MOI.set(b.bridged, attr, constraint_index, name) + b.con_to_name[constraint_index] = name + b.name_to_con = nothing # Invalidate the name map. else MOI.set(b.model, attr, constraint_index, name) end end -## Getting functions and sets -function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintSet, ci::CI) - if is_bridged(b, typeof(ci)) - return MOI.get(b.bridged, attr, ci) - else - return MOI.get(b.model, attr, ci) - end -end -function MOI.get(b::AbstractBridgeOptimizer, attr::MOI.ConstraintFunction, - ci::CI) - if is_bridged(b, typeof(ci)) - return MOI.get(b.bridged, attr, ci) - else - return MOI.get(b.model, attr, ci) - end -end -## Setting functions and sets -function MOI.set(b::AbstractBridgeOptimizer, ::MOI.ConstraintSet, - constraint_index::CI{F, S}, set::S) where {F, S} - if is_bridged(b, typeof(constraint_index)) - MOI.set(b, MOI.ConstraintSet(), bridge(b, constraint_index), set) - MOI.set(b.bridged, MOI.ConstraintSet(), constraint_index, set) - else - MOI.set(b.model, MOI.ConstraintSet(), constraint_index, set) - end -end -function MOI.set(b::AbstractBridgeOptimizer, ::MOI.ConstraintFunction, - constraint_index::CI{F, S}, func::F) where {F, S} - if is_bridged(b, typeof(constraint_index)) - MOI.set(b, MOI.ConstraintFunction(), bridge(b, constraint_index), func) - MOI.set(b.bridged, MOI.ConstraintFunction(), constraint_index, func) - else - MOI.set(b.model, MOI.ConstraintFunction(), constraint_index, func) - end + +# Name (similar to `UniversalFallback`) +function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{MOI.VariableIndex}, + name::String) + return MOI.get(b.model, IdxT, name) end -# Name -function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{<:MOI.Index}, +function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{<:MOI.ConstraintIndex}, name::String) + if b.name_to_con === nothing + b.name_to_con = MOIU.build_name_to_con_map(b.con_to_name) + end + if is_bridged(b, IdxT) - return MOI.get(b.bridged, IdxT, name) + ci = get(b.name_to_con, name, nothing) + if ci == CI{Nothing, Nothing}(-1) + error("Multiple constraints have the name $name.") + elseif ci isa IdxT + return ci + else + return nothing + end else - return MOI.get(b.model, IdxT, name) + ci = MOI.get(b.model, IdxT, name) + if ci !== nothing && haskey(b.name_to_con, name) + error("Multiple constraints have the name $name.") + end + return ci end end @@ -327,10 +330,23 @@ end # model. Therefore, we try the model first, and then the bridge if that fails. function MOI.get(b::AbstractBridgeOptimizer, IdxT::Type{CI}, name::String) + if b.name_to_con === nothing + b.name_to_con = MOIU.build_name_to_con_map(b.con_to_name) + end + ci = MOI.get(b.model, IdxT, name) if ci === nothing - return MOI.get(b.bridged, IdxT, name) + b_ci = get(b.name_to_con, name, nothing) + if b_ci == CI{Nothing, Nothing}(-1) + error("Multiple constraints have the name $name.") + else + return b_ci + end else + if haskey(b.name_to_con, name) + error("Multiple constraints have the name $name.") + end + return ci end end @@ -340,8 +356,7 @@ function MOI.supports_constraint(b::AbstractBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) if is_bridged(b, F, S) - return supports_bridging_constraint(b, F, S) && - MOI.supports_constraint(b.bridged, F, S) + return supports_bridging_constraint(b, F, S) else return MOI.supports_constraint(b.model, F, S) end @@ -355,9 +370,9 @@ function MOI.add_constraint(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction, BridgeType = concrete_bridge_type(b, typeof(f), typeof(s)) # `add_constraint` might throw an `UnsupportedConstraint` but no # modification has been done in the previous line - ci = MOI.add_constraint(b.bridged, f, s) - @assert !haskey(b.bridges, ci) - b.bridges[ci] = BridgeType(b, f, s) + push!(b.bridges, BridgeType(b, f, s)) + push!(b.constraint_types, (typeof(f), typeof(s))) + ci = MOI.ConstraintIndex{typeof(f), typeof(s)}(length(b.bridges)) return ci else return MOI.add_constraint(b.model, f, s) @@ -367,17 +382,7 @@ function MOI.add_constraints(b::AbstractBridgeOptimizer, f::Vector{F}, s::Vector{S}) where { F <: MOI.AbstractFunction, S <: MOI.AbstractSet} if is_bridged(b, F, S) - # See add_constraint for why we we call `concrete_bridge_type` separately - # before `add_constraints` - BridgeType = concrete_bridge_type(b, F, S) - # `add_constraints` might throw an `UnsupportedConstraint` but no - # modification has been done in the previous line - cis = MOI.add_constraints(b.bridged, f, s) - for (n, ci) in enumerate(cis) - @assert !haskey(b.bridges, ci) - b.bridges[ci] = BridgeType(b, f[n], s[n]) - end - return cis + return MOI.add_constraint.(b, f, s) else return MOI.add_constraints(b.model, f, s) end @@ -386,7 +391,6 @@ function MOI.modify(b::AbstractBridgeOptimizer, ci::CI, change::MOI.AbstractFunctionModification) if is_bridged(b, typeof(ci)) MOI.modify(b, bridge(b, ci), change) - MOI.modify(b.bridged, ci, change) else MOI.modify(b.model, ci, change) end diff --git a/src/Bridges/flip_sign_bridge.jl b/src/Bridges/flip_sign_bridge.jl index c689460bf9..9b43a831ab 100644 --- a/src/Bridges/flip_sign_bridge.jl +++ b/src/Bridges/flip_sign_bridge.jl @@ -1,5 +1,5 @@ """ - FlipSignBridge{S1, S2, F} + FlipSignBridge{T, 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 @@ -7,26 +7,26 @@ 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, + T, 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} +function MOI.supports_constraint(::Type{<:FlipSignBridge{T, S1}}, + ::Type{<:MOI.AbstractFunction}, + ::Type{S1}) where {T, S1<:MOI.AbstractSet} return true end function added_constraint_types( - ::Type{<:FlipSignBridge{S1, S2, F}}) where {S1, S2, F} + ::Type{<:FlipSignBridge{T, S1, S2, F}}) where {T, 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} +function MOI.get(::FlipSignBridge{T, S1, S2, F}, + ::MOI.NumberOfConstraints{F, S2}) where {T, S1, S2, F} return 1 end -function MOI.get(bridge::FlipSignBridge{S1, S2, F}, - ::MOI.ListOfConstraintIndices{F, S2}) where {S1, S2, F} +function MOI.get(bridge::FlipSignBridge{T, S1, S2, F}, + ::MOI.ListOfConstraintIndices{F, S2}) where {T, S1, S2, F} return [bridge.flipped_constraint] end @@ -42,6 +42,11 @@ function MOI.get(model::MOI.ModelLike, return -MOI.get(model, attr, bridge.flipped_constraint) end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + bridge::FlipSignBridge{T}) where T + return MOIU.operate(-, T, MOI.get(model, attr, bridge.flipped_constraint)) +end + function MOI.modify(model::MOI.ModelLike, bridge::FlipSignBridge, change::MOI.ScalarCoefficientChange) MOI.modify( @@ -65,13 +70,13 @@ end """ GreaterToLessBridge{T, F<:MOI.AbstractScalarFunction} <: - FlipSignBridge{MOI.GreaterThan{T}, MOI.LessThan{T}, F} + FlipSignBridge{T, 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} + FlipSignBridge{T, MOI.GreaterThan{T}, MOI.LessThan{T}, F} flipped_constraint::CI{F, MOI.LessThan{T}} end function GreaterToLessBridge{T, F}(model::MOI.ModelLike, @@ -87,21 +92,27 @@ function concrete_bridge_type(::Type{<:GreaterToLessBridge{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 +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + bridge::GreaterToLessBridge) + set = MOI.get(model, attr, bridge.flipped_constraint) + return MOI.GreaterThan(-set.upper) +end """ LessToGreaterBridge{T, F<:MOI.AbstractScalarFunction} <: - FlipSignBridge{MOI.LessThan{T}, MOI.GreaterThan{T}, F} + FlipSignBridge{T, 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} + FlipSignBridge{T, MOI.LessThan{T}, MOI.GreaterThan{T}, F} flipped_constraint::CI{F, MOI.GreaterThan{T}} end function LessToGreaterBridge{T, F}(model::MOI.ModelLike, @@ -117,20 +128,27 @@ function concrete_bridge_type(::Type{<:LessToGreaterBridge{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 +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + bridge::LessToGreaterBridge) + set = MOI.get(model, attr, bridge.flipped_constraint) + return MOI.LessThan(-set.lower) +end """ - NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction} + NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction} <: + FlipSignBridge{T, MOI.Nonnegatives, MOI.Nonpositives, F} Transforms a `G`-in-`Nonnegatives` constraint into a `F`-in-`Nonpositives` constraint. """ struct NonnegToNonposBridge{T, F<:MOI.AbstractVectorFunction} <: - FlipSignBridge{MOI.Nonnegatives, MOI.Nonpositives, F} + FlipSignBridge{T, MOI.Nonnegatives, MOI.Nonpositives, F} flipped_constraint::CI{F, MOI.Nonpositives} end function NonnegToNonposBridge{T, F}(model::MOI.ModelLike, @@ -149,13 +167,14 @@ function concrete_bridge_type(::Type{<:NonnegToNonposBridge{T}}, end """ - NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction} + NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction} <: + FlipSignBridge{T, MOI.Nonpositives, MOI.Nonnegatives, F} Transforms a `G`-in-`Nonpositives` constraint into a `F`-in-`Nonnegatives` constraint. """ struct NonposToNonnegBridge{T, F<:MOI.AbstractVectorFunction} <: - FlipSignBridge{MOI.Nonpositives, MOI.Nonnegatives, F} + FlipSignBridge{T, MOI.Nonpositives, MOI.Nonnegatives, F} flipped_constraint::CI{F, MOI.Nonnegatives} end function NonposToNonnegBridge{T, F}(model::MOI.ModelLike, diff --git a/src/Bridges/functionize_bridge.jl b/src/Bridges/functionize_bridge.jl index b032d8d7aa..900719394c 100644 --- a/src/Bridges/functionize_bridge.jl +++ b/src/Bridges/functionize_bridge.jl @@ -60,6 +60,16 @@ function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, MOI.set(model, MOI.ConstraintSet(), c.constraint, change) end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + b::ScalarFunctionizeBridge) + f = MOIU.canonical(MOI.get(model, attr, b.constraint)) + return convert(MOI.SingleVariable, f) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + b::ScalarFunctionizeBridge) + return MOI.get(model, attr, b.constraint) +end + # vector version """ @@ -121,3 +131,18 @@ function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, bridge::VectorFunctionizeBridge{T, S}, change::S) where {T, S} MOI.set(model, MOI.ConstraintSet(), bridge.constraint, change) end + +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + b::VectorFunctionizeBridge) + f = MOIU.canonical(MOI.get(model, attr, b.constraint)) + @assert all(iszero, f.constants) + @assert length(f.terms) == MOI.output_dimension(f) + @assert all(t -> isone(t.scalar_term.coefficient), f.terms) + terms = sort(f.terms, by = t -> t.output_index) + @assert all(i -> terms[i].output_index == i, 1:MOI.output_dimension(f)) + return MOI.VectorOfVariables([t.scalar_term.variable_index for t in terms]) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + b::VectorFunctionizeBridge) + return MOI.get(model, attr, b.constraint) +end diff --git a/src/Bridges/intervalbridge.jl b/src/Bridges/intervalbridge.jl index 6e96c37330..3dff98fca5 100644 --- a/src/Bridges/intervalbridge.jl +++ b/src/Bridges/intervalbridge.jl @@ -88,3 +88,13 @@ function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::SplitIntervalBrid MOI.set(model, MOI.ConstraintSet(), c.lower, MOI.GreaterThan(change.lower)) MOI.set(model, MOI.ConstraintSet(), c.upper, MOI.LessThan(change.upper)) end + +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + b::SplitIntervalBridge) + return MOI.get(model, attr, b.lower) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + b::SplitIntervalBridge) + return MOI.Interval(MOI.get(model, attr, b.lower).lower, + MOI.get(model, attr, b.upper).upper) +end diff --git a/src/Bridges/lazybridgeoptimizer.jl b/src/Bridges/lazybridgeoptimizer.jl index bd4c6b2834..467050fabe 100644 --- a/src/Bridges/lazybridgeoptimizer.jl +++ b/src/Bridges/lazybridgeoptimizer.jl @@ -1,6 +1,6 @@ # /!\ LazyBridgeOptimizer is still experimental and is meant to be used by JuMP. Use the SingleBridgeOptimizer in the solver tests as explained in the MOI documentation. """ - LazyBridgeOptimizer{OT<:MOI.ModelLike, MT<:MOI.ModelLike} <: AbstractBridgeOptimizer + LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer The `LazyBridgeOptimizer` combines several bridges, which are added using the [`add_bridge`](@ref) function. Whenever a constraint is added, it only attempts to bridge it if it is not supported by the internal model (hence its name `Lazy`). @@ -10,21 +10,25 @@ bridged into a constraint `F2`-in-`S2` (unsupported by the internal model) using bridged into a constraint `F3`-in-`S3` (supported by the internal model) using bridge 3, it will choose bridge 1 as it allows to bridge `F`-in-`S` using only one bridge instead of two if it uses bridge 2 and 3. """ -struct LazyBridgeOptimizer{OT<:MOI.ModelLike, MT<:MOI.ModelLike} <: AbstractBridgeOptimizer +mutable struct LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer model::OT # Internal model - bridged::MT # Model containing bridged constraints - bridges::Dict{CI, AbstractBridge} # Constraint Index of bridged constraint in bridged -> Bridge + con_to_name::Dict{CI, String} + name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} + # Constraint Index of bridged constraint -> Bridge. + # It is set to `nothing` when the constraint is deleted. + bridges::Vector{Union{Nothing, AbstractBridge}} + # Constraint Index of bridged constraint -> Constraint type. + constraint_types::Vector{Tuple{DataType, DataType}} bridgetypes::Vector{Any} # List of types of available bridges dist::Dict{Tuple{DataType, DataType}, Int} # (F, S) -> Number of bridges that need to be used for an `F`-in-`S` constraint best::Dict{Tuple{DataType, DataType}, DataType} # (F, S) -> Bridge to be used for an `F`-in-`S` constraint end -function LazyBridgeOptimizer(model::MOI.ModelLike, bridged::MOI.ModelLike) - LazyBridgeOptimizer{typeof(model), - typeof(bridged)}(model, bridged, - Dict{CI, AbstractBridge}(), - Any[], - Dict{Tuple{DataType, DataType}, Int}(), - Dict{Tuple{DataType, DataType}, DataType}()) +function LazyBridgeOptimizer(model::MOI.ModelLike) + return LazyBridgeOptimizer{typeof(model)}( + model, Dict{CI, String}(), nothing, + Union{Nothing, AbstractBridge}[], Tuple{DataType, DataType}[], + Any[], Dict{Tuple{DataType, DataType}, Int}(), + Dict{Tuple{DataType, DataType}, DataType}()) end function _dist(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) diff --git a/src/Bridges/quadtosocbridge.jl b/src/Bridges/quadtosocbridge.jl index 12a6de56a0..07402c6619 100644 --- a/src/Bridges/quadtosocbridge.jl +++ b/src/Bridges/quadtosocbridge.jl @@ -17,9 +17,9 @@ is implemented, see the Note section below for more details. If `Q` is positive semidefinite, there exists `U` such that ``Q = U^T U``, the inequality can then be rewritten as ```math -\\|U x\\|_2 \\le 2 (a^T x + b) +\\|U x\\|_2^2 \\le 2 (-a^T x - b) ``` -which is equivalent to the membership of `(1, a^T x + b, Ux)` to the rotated +which is equivalent to the membership of `(1, -a^T x - b, Ux)` to the rotated second-order cone. ## Homogeneous case @@ -65,9 +65,6 @@ function QuadtoSOCBridge{T}(model, func::MOI.ScalarQuadraticFunction{T}, MOI.GreaterThan{T}}) where T set_constant = MOI.constant(set) less_than = set isa MOI.LessThan - if !less_than - set_constant = -set_constant - end Q, index_to_variable_map = matrix_from_quadratic_terms(func.quadratic_terms) if !less_than rmul!(Q, -1) @@ -195,3 +192,34 @@ function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, output += bridge.set_constant return output end + +# TODO +#function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, +# b::QuadtoSOCBridge) +#end + +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + b::QuadtoSOCBridge{T}) where T + if b.less_than + return MOI.LessThan(b.set_constant) + else + return MOI.GreaterThan(b.set_constant) + end +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + b::QuadtoSOCBridge{T}) where T + f = MOI.get(model, attr, b.soc) + fs = MOIU.eachscalar(f) + q = zero(MOI.ScalarQuadraticFunction{T}) + for i in 3:MOI.output_dimension(f) + term = MOIU.operate(*, T, fs[i], fs[i]) + term = MOIU.operate!(/, T, term, 2 * one(T)) + MOIU.operate!(+, T, q, term) + end + MOIU.operate!(-, T, q, fs[2]) + if !b.less_than + MOIU.operate!(-, T, q) + end + q.constant += b.set_constant + return q +end diff --git a/src/Bridges/scalarizebridge.jl b/src/Bridges/scalarizebridge.jl index 61be7ccc2e..06eeb93bb8 100644 --- a/src/Bridges/scalarizebridge.jl +++ b/src/Bridges/scalarizebridge.jl @@ -1,14 +1,14 @@ -const VectorLPSet = Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives} +const VectorLinearSet = Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives} scalar_set_type(::Type{<:MOI.Zeros}, T::Type) = MOI.EqualTo{T} scalar_set_type(::Type{<:MOI.Nonpositives}, T::Type) = MOI.LessThan{T} scalar_set_type(::Type{<:MOI.Nonnegatives}, T::Type) = MOI.GreaterThan{T} """ - ScalarizeBridge{T} + ScalarizeBridge{T, F, S} -Transforms a constraint `AbstractVectorFunction`-in-`S` where `S <: LPCone` to -`AbstactVectorFunction`-in-`scalar_set_type(S)`. +Transforms a constraint `AbstractVectorFunction`-in-`vector_set(S)` where +`S <: LPCone{T}` to `F`-in-`S`. """ mutable struct ScalarizeBridge{T, F, S} <: AbstractBridge scalar_constraints::Vector{CI{F, S}} @@ -16,7 +16,7 @@ mutable struct ScalarizeBridge{T, F, S} <: AbstractBridge end function ScalarizeBridge{T, F, S}(model::MOI.ModelLike, f::MOI.AbstractVectorFunction, - set::VectorLPSet) where {T, F, S} + set::VectorLinearSet) where {T, F, S} dimension = MOI.output_dimension(f) constants = MOI.constant(f, T) new_f = MOIU.scalarize(f, true) @@ -29,7 +29,7 @@ end function MOI.supports_constraint(::Type{ScalarizeBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, - ::Type{<:VectorLPSet}) where T + ::Type{<:VectorLinearSet}) where T return true end function added_constraint_types(::Type{ScalarizeBridge{T, F, S}}) where {T, F, S} @@ -37,7 +37,7 @@ function added_constraint_types(::Type{ScalarizeBridge{T, F, S}}) where {T, F, S end function concrete_bridge_type(::Type{<:ScalarizeBridge{T}}, F::Type{<:MOI.AbstractVectorFunction}, - S::Type{<:VectorLPSet}) where T + S::Type{<:VectorLinearSet}) where T return ScalarizeBridge{T, MOIU.scalar_type(F), scalar_set_type(S, T)} end diff --git a/src/Bridges/singlebridgeoptimizer.jl b/src/Bridges/singlebridgeoptimizer.jl index 61ac98be63..bc1fcb92f4 100644 --- a/src/Bridges/singlebridgeoptimizer.jl +++ b/src/Bridges/singlebridgeoptimizer.jl @@ -1,50 +1,32 @@ """ - SingleBridgeOptimizer{BT<:AbstractBridge, MT<:MOI.ModelLike, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer + SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer The `SingleBridgeOptimizer` bridges any constraint supported by the bridge `BT`. This is in contrast with the [`LazyBridgeOptimizer`](@ref) which only bridges the constraints that are unsupported by the internal model, even if they are supported by one of its bridges. """ -struct SingleBridgeOptimizer{BT<:AbstractBridge, MT<:MOI.ModelLike, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer +mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer model::OT - bridged::MT - bridges::Dict{CI, BT} + con_to_name::Dict{CI, String} + name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing} + # Constraint Index of bridged constraint -> Bridge. + # It is set to `nothing` when the constraint is deleted. + bridges::Vector{Union{Nothing, AbstractBridge}} + # Constraint Index of bridged constraint -> Constraint type. + constraint_types::Vector{Tuple{DataType, DataType}} end -function SingleBridgeOptimizer{BT, MT}(model::OT) where {BT, MT, OT <: MOI.ModelLike} - SingleBridgeOptimizer{BT, MT, OT}(model, MT(), Dict{CI, BT}()) +function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike} + SingleBridgeOptimizer{BT, OT}( + model, Dict{CI, String}(), nothing, + Union{Nothing, AbstractBridge}[], Tuple{DataType, DataType}[]) end -is_bridged(b::SingleBridgeOptimizer, ::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) = false -bridge_type(b::SingleBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) where BT = BT - -# :((Zeros, SecondOrderCone)) -> (:(MOI.Zeros), :(MOI.SecondOrderCone)) - -""" -macro bridge(modelname, bridge, scalarsets, typedscalarsets, vectorsets, typedvectorsets, scalarfunctions, typedscalarfunctions, vectorfunctions, typedvectorfunctions) - -Creates a type `modelname` implementing the MOI model interface and bridging the `scalarsets` scalar sets `typedscalarsets` typed scalar sets, `vectorsets` vector sets, `typedvectorsets` typed vector sets, `scalarfunctions` scalar functions, `typedscalarfunctions` typed scalar functions, `vectorfunctions` vector functions and `typedvectorfunctions` typed vector functions. -To give no set/function, write `()`, to give one set `S`, write `(S,)`. - -### Examples - -The optimizer layer bridging the constraints `ScalarAffineFunction`-in-`Interval` is created as follows: -```julia -@bridge SplitInterval MOIB.SplitIntervalBridge () (Interval,) () () () (ScalarAffineFunction,) () () -``` -Given an optimizer `optimizer` implementing `ScalarAffineFunction`-in-`GreaterThan` and `ScalarAffineFunction`-in-`LessThan`, the optimizer -``` -bridgedmodel = SplitInterval(model) -``` -will additionally support `ScalarAffineFunction`-in-`Interval`. -""" -macro bridge(modelname, bridge, ss, sst, vs, vst, sf, sft, vf, vft) - bridged_model_name = Symbol(string(modelname) * "Instance") - bridged_funs = :(Union{$((sf.args)...), $((sft.args)...), $((vf.args)...), $((vft.args)...)}) - bridged_sets = :(Union{$((ss.args)...), $((sst.args)...), $((vs.args)...), $((vst.args)...)}) - - esc(quote - $MOIU.@model $bridged_model_name $ss $sst $vs $vst $sf $sft $vf $vft - const $modelname{T, OT<:$MOI.ModelLike} = $MOIB.SingleBridgeOptimizer{$bridge{T}, $bridged_model_name{T}, OT} - $MOIB.is_bridged(::$modelname, ::Type{<:$bridged_funs}, ::Type{<:$bridged_sets}) = true - $MOIB.supports_bridging_constraint(::$modelname, ::Type{<:$bridged_funs}, ::Type{<:$bridged_sets}) = true - end) +function supports_bridging_constraint(b::SingleBridgeOptimizer{BT}, + F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) where BT + return MOI.supports_constraint(BT, F, S) +end +function is_bridged(b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, + S::Type{<:MOI.AbstractSet}) + return supports_bridging_constraint(b, F, S) end +bridge_type(b::SingleBridgeOptimizer{BT}, ::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) where BT = BT diff --git a/src/Bridges/slackbridge.jl b/src/Bridges/slackbridge.jl index dbe6393f36..54b9b3cfe2 100644 --- a/src/Bridges/slackbridge.jl +++ b/src/Bridges/slackbridge.jl @@ -66,7 +66,7 @@ function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, c::ScalarSlac 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 + # 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) @@ -90,6 +90,16 @@ function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::ScalarSlackBridge MOI.set(model, MOI.ConstraintSet(), c.slack_in_set, change) end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + b::ScalarSlackBridge{T}) where T + return MOIU.operate(+, T, MOI.get(model, attr, b.equality), + MOI.SingleVariable(b.slack)) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + b::ScalarSlackBridge) + return MOI.get(model, attr, b.slack_in_set) +end + # vector version """ @@ -108,7 +118,7 @@ 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))) + new_f = MOIU.operate(-, T, f, 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) @@ -177,3 +187,13 @@ 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 + +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + b::VectorSlackBridge{T}) where T + return MOIU.operate(+, T, MOI.get(model, attr, b.equality), + MOI.VectorOfVariables(b.slacks)) +end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, + b::VectorSlackBridge) + return MOI.get(model, attr, b.slacks_in_set) +end diff --git a/src/Bridges/vectorizebridge.jl b/src/Bridges/vectorizebridge.jl index 8ad6abef65..f4896e3dd8 100644 --- a/src/Bridges/vectorizebridge.jl +++ b/src/Bridges/vectorizebridge.jl @@ -1,4 +1,4 @@ -const LPCone{T} = Union{MOI.EqualTo{T}, MOI.LessThan{T}, MOI.GreaterThan{T}} +const ScalarLinearSet{T} = Union{MOI.EqualTo{T}, MOI.LessThan{T}, MOI.GreaterThan{T}} vector_set(::MOI.EqualTo) = MOI.Zeros(1) vector_set(::MOI.LessThan) = MOI.Nonpositives(1) @@ -9,45 +9,44 @@ vector_set_type(::Type{<:MOI.LessThan}) = MOI.Nonpositives vector_set_type(::Type{<:MOI.GreaterThan}) = MOI.Nonnegatives """ - VectorizeBridge{T} + VectorizeBridge{T, F, S, G} -Transforms a constraint `AbstractScalarFunction`-in-`S` where `S <: LPCone` to -`AbstactVectorFunction`-in-`vector_set_type(S)`. +Transforms a constraint `G`-in-`scalar_set_type(S, T)` where +`S <: VectorLinearSet` to `F`-in-`S`. """ -mutable struct VectorizeBridge{T, F, S} <: AbstractBridge +mutable struct VectorizeBridge{T, F, S, G} <: AbstractBridge vector_constraint::CI{F, S} set_constant::T # constant in scalar set end -function VectorizeBridge{T, F, S}(model::MOI.ModelLike, - f::MOI.AbstractScalarFunction, - set::MOI.AbstractScalarSet) where {T, F, S} +function VectorizeBridge{T, F, S, G}(model::MOI.ModelLike, g::G, + set::MOI.AbstractScalarSet) where {T, F, S, G} set_constant = MOI.constant(set) - g = MOIU.operate(-, T, f, set_constant) - if -set_constant != MOI.constant(g)[1] + h = MOIU.operate(-, T, g, set_constant) + if -set_constant != MOI.constant(h)[1] # This means the constant in `f` was not zero - constant = MOI.constant(g)[1] + set_constant - throw(MOI.ScalarFunctionConstantNotZero{typeof(constant), typeof(f), + constant = MOI.constant(h)[1] + set_constant + throw(MOI.ScalarFunctionConstantNotZero{typeof(constant), G, typeof(set)}(constant)) end - h = MOIU.operate(vcat, T, g) - vector_constraint = MOI.add_constraint(model, h, vector_set(set)) - return VectorizeBridge{T, F, S}(vector_constraint, set_constant) + f = MOIU.operate(vcat, T, h) + vector_constraint = MOI.add_constraint(model, f, vector_set(set)) + return VectorizeBridge{T, F, S, G}(vector_constraint, set_constant) end function MOI.supports_constraint(::Type{VectorizeBridge{T}}, ::Type{<:MOI.AbstractScalarFunction}, - ::Type{<:LPCone{T}}) where T + ::Type{<:ScalarLinearSet{T}}) where T return true end -function added_constraint_types(::Type{VectorizeBridge{T, F, S}}) where {T, F, S} +function added_constraint_types(::Type{<:VectorizeBridge{T, F, S}}) where {T, F, S} return [(F, S)] end function concrete_bridge_type(::Type{<:VectorizeBridge{T}}, - F::Type{<:MOI.AbstractScalarFunction}, - S::Type{<:LPCone{T}}) where T - G = MOIU.promote_operation(-, T, F, T) - H = MOIU.promote_operation(vcat, T, G) - return VectorizeBridge{T, H, vector_set_type(S)} + G::Type{<:MOI.AbstractScalarFunction}, + S::Type{<:ScalarLinearSet{T}}) where T + H = MOIU.promote_operation(-, T, G, T) + F = MOIU.promote_operation(vcat, T, H) + return VectorizeBridge{T, F, vector_set_type(S), G} end # Attributes, Bridge acting as an model @@ -97,8 +96,19 @@ function MOI.modify(model::MOI.ModelLike, bridge::VectorizeBridge, [(1, change.new_coefficient)])) end function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, - bridge::VectorizeBridge, new_set::LPCone) + bridge::VectorizeBridge, new_set::ScalarLinearSet) bridge.set_constant = MOI.constant(new_set) MOI.modify(model, bridge.vector_constraint, MOI.VectorConstantChange([-bridge.set_constant])) end +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + bridge::VectorizeBridge{T, F, S, G}) where {T, F, S, G} + f = MOIU.scalarize(MOI.get(model, attr, bridge.vector_constraint), true) + @assert isone(length(f)) + # If `G` is `MOI.SingleVariable`, `f` will be `MOI.ScalarAffineFunction`. + return convert(G, f[1]) +end +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, + bridge::VectorizeBridge{T, F, S}) where {T, F, S} + return scalar_set_type(S, T)(bridge.set_constant) +end diff --git a/src/Test/contlinear.jl b/src/Test/contlinear.jl index 04f9b2a97a..67c52ec0f2 100644 --- a/src/Test/contlinear.jl +++ b/src/Test/contlinear.jl @@ -746,11 +746,24 @@ function linear6test(model::MOI.ModelLike, config::TestConfig) # s.t. 0.0 <= x (c1) # y <= 0.0 (c2) - MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -1.0], [x,y]), 0.0)) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -1.0], [x, y]), + 0.0)) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - c1 = MOI.add_constraint(model, MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), MOI.GreaterThan(0.0)) - c2 = MOI.add_constraint(model, MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, y)], 0.0), MOI.LessThan(0.0)) + fx = convert(MOI.ScalarAffineFunction{Float64}, + MOI.SingleVariable(x)) + c1 = MOI.add_constraint(model, fx, MOI.GreaterThan(0.0)) + fy = convert(MOI.ScalarAffineFunction{Float64}, + MOI.SingleVariable(y)) + c2 = MOI.add_constraint(model, fy, MOI.LessThan(0.0)) + + if config.query + @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ fx + @test MOI.get(model, MOI.ConstraintSet(), c1) == MOI.GreaterThan(0.0) + @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ fy + @test MOI.get(model, MOI.ConstraintSet(), c2) == MOI.LessThan(0.0) + end if config.solve @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED @@ -769,6 +782,14 @@ function linear6test(model::MOI.ModelLike, config::TestConfig) # s.t. 100.0 <= x # y <= 0.0 MOI.set(model, MOI.ConstraintSet(), c1, MOI.GreaterThan(100.0)) + + if config.query + @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ fx + @test MOI.get(model, MOI.ConstraintSet(), c1) == MOI.GreaterThan(100.0) + @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ fy + @test MOI.get(model, MOI.ConstraintSet(), c2) == MOI.LessThan(0.0) + end + if config.solve MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status @@ -783,6 +804,14 @@ function linear6test(model::MOI.ModelLike, config::TestConfig) # s.t. 100.0 <= x # y <= -100.0 MOI.set(model, MOI.ConstraintSet(), c2, MOI.LessThan(-100.0)) + + if config.query + @test MOI.get(model, MOI.ConstraintFunction(), c1) ≈ fx + @test MOI.get(model, MOI.ConstraintSet(), c1) == MOI.GreaterThan(100.0) + @test MOI.get(model, MOI.ConstraintFunction(), c2) ≈ fy + @test MOI.get(model, MOI.ConstraintSet(), c2) == MOI.LessThan(-100.0) + end + if config.solve MOI.optimize!(model) @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status diff --git a/src/Test/contquadratic.jl b/src/Test/contquadratic.jl index 4eebd5c2f1..0f963d7a97 100644 --- a/src/Test/contquadratic.jl +++ b/src/Test/contquadratic.jl @@ -385,7 +385,7 @@ function qcp3test(model::MOI.ModelLike, config::TestConfig) end end -function qcp4test(model::MOI.ModelLike, config::TestConfig) +function _qcp4test(model::MOI.ModelLike, config::TestConfig, less_than::Bool) atol = config.atol rtol = config.rtol # Max x @@ -394,7 +394,8 @@ function qcp4test(model::MOI.ModelLike, config::TestConfig) @test MOIU.supports_default_copy_to(model, #=copy_names=# false) @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) - @test MOI.supports_constraint(model, MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}) + quad_set = less_than ? MOI.LessThan(3.0) : MOI.GreaterThan(-3.0) + @test MOI.supports_constraint(model, MOI.ScalarQuadraticFunction{Float64}, typeof(quad_set)) MOI.empty!(model) @test MOI.is_empty(model) @@ -407,8 +408,11 @@ function qcp4test(model::MOI.ModelLike, config::TestConfig) cf = MOI.ScalarQuadraticFunction([MOI.ScalarAffineTerm(0.0, x)], MOI.ScalarQuadraticTerm.([2.0, 1.0, 2.0], [x, x, y], [x, y, y]), 0.0) - c = MOI.add_constraint(model, cf, MOI.LessThan(3.0)) - @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}}()) == 1 + if !less_than + MOIU.operate!(-, Float64, cf) + end + c = MOI.add_constraint(model, cf, quad_set) + @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarQuadraticFunction{Float64}, typeof(quad_set)}()) == 1 MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)) @@ -417,6 +421,7 @@ function qcp4test(model::MOI.ModelLike, config::TestConfig) if config.query @test cf ≈ MOI.get(model, MOI.ConstraintFunction(), c) + @test quad_set == MOI.get(model, MOI.ConstraintSet(), c) end if config.solve @@ -433,14 +438,18 @@ function qcp4test(model::MOI.ModelLike, config::TestConfig) @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1.0 atol=atol rtol=rtol @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1.0 atol=atol rtol=rtol - @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ 3.0 atol=atol rtol=rtol + @test MOI.get(model, MOI.ConstraintPrimal(), c) ≈ MOI.constant(quad_set) atol=atol rtol=rtol end end +qcp4test(model::MOI.ModelLike, config::TestConfig) = _qcp4test(model, config, true) +qcp5test(model::MOI.ModelLike, config::TestConfig) = _qcp4test(model, config, false) + const qcptests = Dict("qcp1" => qcp1test, "qcp2" => qcp2test, "qcp3" => qcp3test, - "qcp4" => qcp4test) + "qcp4" => qcp4test, + "qcp5" => qcp5test) @moitestset qcp diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index c4d3da0e4a..28b322aeed 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -618,8 +618,11 @@ function operate_term(::typeof(*), α::T, t::MOI.ScalarQuadraticTerm{T}) where T end function operate_term(::typeof(*), t1::MOI.ScalarAffineTerm, t2::MOI.ScalarAffineTerm) - MOI.ScalarQuadraticTerm(t1.coefficient * t2.coefficient, t1.variable_index, - t2.variable_index) + coef = t1.coefficient * t2.coefficient + if t1.variable_index == t2.variable_index + coef *= 2 + end + MOI.ScalarQuadraticTerm(coef, t1.variable_index, t2.variable_index) end function operate_term(::typeof(*), α::T, t::MOI.VectorAffineTerm{T}) where T @@ -659,6 +662,11 @@ function operate_terms(::typeof(+), MOI.VectorQuadraticTerm}}) return terms end +function operate_terms!(::typeof(-), + terms::Vector{<:Union{MOI.ScalarAffineTerm, + MOI.ScalarQuadraticTerm}}) + return map!(term -> operate_term(-, term), terms, terms) +end function operate_terms(::typeof(-), terms::Vector{<:Union{MOI.ScalarAffineTerm, MOI.ScalarQuadraticTerm}}) @@ -666,7 +674,7 @@ function operate_terms(::typeof(-), end function operate_terms(::typeof(-), terms::Vector{<:Union{MOI.VectorAffineTerm, - MOI.VectorQuadraticTerm}}) + MOI.VectorQuadraticTerm}}) return map(term -> operate_term(-, term), terms) end @@ -727,6 +735,15 @@ function operate!(op::typeof(+), ::Type{T}, f, g, h, args...) where T return operate!(+, T, f, h, args...) end +# Unary - +function operate!(op::typeof(-), ::Type{T}, f::MOI.ScalarQuadraticFunction{T}) where T + operate_terms!(-, f.quadratic_terms) + operate_terms!(-, f.affine_terms) + f.constant = -f.constant + return f +end + + # Scalar Variable +/- ... function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, f::MOI.SingleVariable, diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 6b926edb16..d3faa03357 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -137,23 +137,17 @@ function _removevar!(constrs::Vector{<:ConstraintEntry{MOI.SingleVariable}}, end return rm end -# TODO Remove this function when #523 is closed -# Delete the variable of index `vi` in the constraints and delete its -# `SingleVariable` constraints -function delete_variable_in_constraints(model::AbstractModel, vi::VI) +function MOI.delete(model::AbstractModel, vi::VI) + if !MOI.is_valid(model, vi) + throw(MOI.InvalidIndex(vi)) + end + model.objective = removevariable(model.objective, vi) # `ci_to_remove` is the list of indices of the `SingleVariable` constraints # of `vi` ci_to_remove = broadcastvcat(constrs -> _removevar!(constrs, vi), model) for ci in ci_to_remove MOI.delete(model, ci) end -end -function MOI.delete(model::AbstractModel, vi::VI) - if !MOI.is_valid(model, vi) - throw(MOI.InvalidIndex(vi)) - end - model.objective = removevariable(model.objective, vi) - delete_variable_in_constraints(model, vi) if model.variable_indices === nothing model.variable_indices = Set(MOI.get(model, MOI.ListOfVariableIndices())) diff --git a/src/Utilities/universalfallback.jl b/src/Utilities/universalfallback.jl index ce50056ccd..f45b873bd3 100644 --- a/src/Utilities/universalfallback.jl +++ b/src/Utilities/universalfallback.jl @@ -79,8 +79,11 @@ function MOI.delete(uf::UniversalFallback, ci::CI{F, S}) where {F, S} delete!(d, ci) end end -# TODO Remove this function when #523 is closed -function delete_variable_in_universal_constraints(uf::UniversalFallback, vi::VI) +function MOI.delete(uf::UniversalFallback, vi::VI) + MOI.delete(uf.model, vi) + for d in values(uf.varattr) + delete!(d, vi) + end for (FS, constraints) in uf.constraints for (ci, constraint) in constraints f, s = constraint @@ -94,20 +97,6 @@ function delete_variable_in_universal_constraints(uf::UniversalFallback, vi::VI) end end end -# TODO Remove this function when #523 is closed -# Delete the variable of index `vi` in the constraints and delete its -# `SingleVariable` constraints -function delete_variable_in_constraints(uf::UniversalFallback, vi::VI) - delete_variable_in_constraints(uf.model, vi) - delete_variable_in_universal_constraints(uf, vi) -end -function MOI.delete(uf::UniversalFallback, vi::VI) - MOI.delete(uf.model, vi) - for d in values(uf.varattr) - delete!(d, vi) - end - delete_variable_in_universal_constraints(uf, vi) -end # Attributes _get(uf, attr::MOI.AbstractOptimizerAttribute) = uf.optattr[attr] diff --git a/test/Bridges/Bridges.jl b/test/Bridges/Bridges.jl index f40ddb99e7..55b71591ee 100644 --- a/test/Bridges/Bridges.jl +++ b/test/Bridges/Bridges.jl @@ -20,4 +20,3 @@ end include("detbridge.jl") include("soctopsdbridge.jl") end -include("external.jl") diff --git a/test/Bridges/external.jl b/test/Bridges/external.jl deleted file mode 100644 index 15121e2d25..0000000000 --- a/test/Bridges/external.jl +++ /dev/null @@ -1,61 +0,0 @@ -using Test - -using MathOptInterface -const MOI = MathOptInterface -const MOIU = MathOptInterface.Utilities - -include("simple_model.jl") - -# We need to test this in a module at the top level because it can't be defined -# in a testset. If it runs without error, then we're okay. -module TestExternalBridge - using MathOptInterface - - struct StrictlyGreaterThan <: MathOptInterface.AbstractScalarSet end - struct StrictlyGreaterBridge{T} <: MathOptInterface.Bridges.AbstractBridge end - - function StrictlyGreaterBridge( - model, - func::MathOptInterface.SingleVariable, - set::StrictlyGreaterThan) - return StrictlyGreaterBridge{Float64}() - end - - function MathOptInterface.supports_constraint( - ::Type{StrictlyGreaterBridge{T}}, - ::Type{MathOptInterface.SingleVariable}, - ::Type{StrictlyGreaterThan}) where {T} - return true - end - - function MathOptInterface.Bridges.added_constraint_types( - ::Type{StrictlyGreaterBridge{T}}, - ::Type{MathOptInterface.SingleVariable}, - ::Type{StrictlyGreaterThan}) where {T} - return [( - MathOptInterface.SingleVariable, - MathOptInterface.GreaterThan{T} - )] - end - - MathOptInterface.Bridges.@bridge(StrictlyGreater, StrictlyGreaterBridge, - (StrictlyGreaterThan, ), - (), - (), - (), - (MathOptInterface.SingleVariable, ), - (), - (), - () - ) -end - -@testset "@bridge with external components" begin - model = SimpleModel{Float64}(); - @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{Float64}) - @test !MOI.supports_constraint(model, MOI.SingleVariable, TestExternalBridge.StrictlyGreaterThan) - - bridge = TestExternalBridge.StrictlyGreater{Float64}(model); - @test MOI.supports_constraint(bridge, MOI.SingleVariable, MOI.GreaterThan{Float64}) - @test MOI.supports_constraint(bridge, MOI.SingleVariable, TestExternalBridge.StrictlyGreaterThan) -end diff --git a/test/Bridges/flip_sign_bridge.jl b/test/Bridges/flip_sign_bridge.jl index f1fae08549..310f4462ed 100644 --- a/test/Bridges/flip_sign_bridge.jl +++ b/test/Bridges/flip_sign_bridge.jl @@ -1,8 +1,17 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + include("simple_model.jl") mock = MOIU.MockOptimizer(SimpleModel{Float64}()) config = MOIT.TestConfig() -config_with_basis = MOIT.TestConfig(basis = true) @testset "GreaterToLess" begin bridged_mock = MOIB.GreaterToLess{Float64}(mock) diff --git a/test/Bridges/functionize_bridge.jl b/test/Bridges/functionize_bridge.jl index 7cb00c1174..b451e6b8d9 100644 --- a/test/Bridges/functionize_bridge.jl +++ b/test/Bridges/functionize_bridge.jl @@ -1,3 +1,19 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() +config_with_basis = MOIT.TestConfig(basis = true) + @testset "Scalar functionize" begin MOI.empty!(mock) bridged_mock = MOIB.ScalarFunctionize{Float64}(mock) diff --git a/test/Bridges/intervalbridge.jl b/test/Bridges/intervalbridge.jl index cb923af73d..f4e9827e9d 100644 --- a/test/Bridges/intervalbridge.jl +++ b/test/Bridges/intervalbridge.jl @@ -1,3 +1,19 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() +config_with_basis = MOIT.TestConfig(basis = true) + @testset "Interval" begin bridged_mock = MOIB.SplitInterval{Float64}(mock) MOIT.basic_constraint_tests(bridged_mock, config, diff --git a/test/Bridges/lazybridgeoptimizer.jl b/test/Bridges/lazybridgeoptimizer.jl index 2ce014b13d..7802c2785b 100644 --- a/test/Bridges/lazybridgeoptimizer.jl +++ b/test/Bridges/lazybridgeoptimizer.jl @@ -76,7 +76,7 @@ end @testset "Bridge adding no constraint" begin mock = MOIU.MockOptimizer(NothingModel{Int}()) - bridged = MOIB.LazyBridgeOptimizer(mock, NothingModel{Int}()) + bridged = MOIB.LazyBridgeOptimizer(mock) MOI.Bridges.add_bridge(bridged, BridgeAddingNoConstraint{Float64}) @test MOI.Bridges.supports_bridging_constraint(bridged, MOI.SingleVariable, @@ -95,8 +95,7 @@ end end mock = MOIU.MockOptimizer(NoRSOCModel{Float64}()) -bridged_mock = MOIB.LazyBridgeOptimizer( - mock, MOIB.AllBridgedConstraints{Float64}()) +bridged_mock = MOIB.LazyBridgeOptimizer(mock) @testset "UnsupportedConstraint when it cannot be bridged" begin x = MOI.add_variables(bridged_mock, 4) diff --git a/test/Bridges/quadtosocbridge.jl b/test/Bridges/quadtosocbridge.jl index a430951a3f..47e4c4b21f 100644 --- a/test/Bridges/quadtosocbridge.jl +++ b/test/Bridges/quadtosocbridge.jl @@ -1,3 +1,18 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() + @testset "QuadtoSOC" begin bridged_mock = MOIB.QuadtoSOC{Float64}(mock) @testset "Error for non-convex quadratic constraints" begin @@ -61,5 +76,9 @@ MOI.LessThan{Float64}}())) test_delete_bridge(bridged_mock, ci, 1, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),)) end + MOIU.set_mock_optimize!(mock, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0], MOI.FEASIBLE_POINT)) + MOIT.qcp4test(bridged_mock, config) + MOIT.qcp5test(bridged_mock, config) end end diff --git a/test/Bridges/scalarizebridge.jl b/test/Bridges/scalarizebridge.jl index 9350b562bf..8e1b06a30d 100644 --- a/test/Bridges/scalarizebridge.jl +++ b/test/Bridges/scalarizebridge.jl @@ -1,3 +1,18 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() + @testset "Scalarize" begin bridged_mock = MOIB.Scalarize{Float64}(mock) # VectorOfVariables-in-Nonnegatives diff --git a/test/Bridges/slackbridge.jl b/test/Bridges/slackbridge.jl index 952ffda016..2f4ea0c136 100644 --- a/test/Bridges/slackbridge.jl +++ b/test/Bridges/slackbridge.jl @@ -1,3 +1,18 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() + @testset "Scalar slack" begin MOI.empty!(mock) bridged_mock = MOIB.ScalarSlack{Float64}(mock) diff --git a/test/Bridges/utilities.jl b/test/Bridges/utilities.jl index c6f8422cf2..f238280ef3 100644 --- a/test/Bridges/utilities.jl +++ b/test/Bridges/utilities.jl @@ -8,7 +8,7 @@ end function test_delete_bridge( m::MOIB.AbstractBridgeOptimizer, ci::MOI.ConstraintIndex{F, S}, nvars::Int, nocs::Tuple; used_bridges = 1, num_bridged = 1) where {F, S} - num_bridges = length(m.bridges) + num_bridges = count(bridge -> bridge !== nothing, m.bridges) @test MOI.get(m, MOI.NumberOfVariables()) == nvars test_noc(m, F, S, num_bridged) for noc in nocs @@ -23,7 +23,7 @@ function test_delete_bridge( @test err.index == ci end @test !MOI.is_valid(m, ci) - @test length(m.bridges) == num_bridges - used_bridges + @test count(bridge -> bridge !== nothing, m.bridges) == num_bridges - used_bridges test_noc(m, F, S, num_bridged - 1) # As the bridge has been removed, if the constraints it has created where not removed, it wouldn't be there to decrease this counter anymore @test MOI.get(m, MOI.NumberOfVariables()) == nvars diff --git a/test/Bridges/vectorizebridge.jl b/test/Bridges/vectorizebridge.jl index 59110eb87e..88e1d8a476 100644 --- a/test/Bridges/vectorizebridge.jl +++ b/test/Bridges/vectorizebridge.jl @@ -1,3 +1,18 @@ +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() + @testset "Vectorize" begin bridged_mock = MOIB.Vectorize{Float64}(mock) diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 80c7eee410..3e50da2433 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -579,7 +579,7 @@ end @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)) + MOI.VectorQuadraticTerm(3, MOI.ScalarQuadraticTerm(200.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) ==