diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index 5cb28941bc..2ec0bc2802 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -60,6 +60,7 @@ The one-dimensional set types implemented in MathOptInterface.jl are: | [`ZeroOne()`](@ref) | ``\{ 0, 1 \}`` | | [`Semicontinuous(l, u)`](@ref) | ``\{ 0\} \cup [l, u]`` | | [`Semiinteger(l, u)`](@ref) | ``\{ 0\} \cup \{l,l+1,\ldots,u-1,u\}`` | +| [`LazyScalarSet(set)`](@ref) | ``set`` | ## Vector cones diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index d78681869e..567bb58d49 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -72,6 +72,7 @@ ZeroOne Semicontinuous Semiinteger Parameter +LazyScalarSet ``` ## Vector sets diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index f24f3b14b0..bef068a65e 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -72,6 +72,7 @@ function add_all_bridges(model, ::Type{T}) where {T} # It is also really useful only to PATHSolver.jl, which could add this # to MOI.ListOfRequiredBridges. MOI.Bridges.add_bridge(model, IntegerToZeroOneBridge{T}) + MOI.Bridges.add_bridge(model, LazyScalarSetBridge{T}) MOI.Bridges.add_bridge(model, LessToGreaterBridge{T}) if T <: AbstractFloat # See note in docstring of AbstractToIntervalBridge MOI.Bridges.add_bridge(model, LessToIntervalBridge{T}) diff --git a/src/Bridges/Constraint/bridges/LazyScalarSetBridge.jl b/src/Bridges/Constraint/bridges/LazyScalarSetBridge.jl new file mode 100644 index 0000000000..f493e282e9 --- /dev/null +++ b/src/Bridges/Constraint/bridges/LazyScalarSetBridge.jl @@ -0,0 +1,80 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +""" + LazyScalarSetBridge{ + T, + F<:MOI.AbstractScalarFunction, + S<:MOI.AbstractScalarSet, + } + +`LazyScalarSetBridge` implements the following reformulation: + + * ``f \\in LazyScalarSet(s)`` into ``f \\in s``. + +## Source node + +`LazyScalarSetBridge` supports: + + * `F` in `LazyScalarSet{S}` + +## Target nodes + +`LazyScalarSetBridge` creates: + + * `F` in `S` +""" +struct LazyScalarSetBridge{ + T, + F<:MOI.AbstractScalarFunction, + S<:MOI.AbstractScalarSet, +} <: MOI.Bridges.Constraint.SetMapBridge{T,S,MOI.LazyScalarSet{S},F,F} + constraint::MOI.ConstraintIndex{F,S} + + function LazyScalarSetBridge{T,F,S}( + constraint::MOI.ConstraintIndex{F,S}, + ) where {T,F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} + return new{T,F,S}(constraint) + end +end + +MOI.Bridges.map_function(::Type{<:LazyScalarSetBridge}, f) = f + +MOI.Bridges.inverse_map_function(::Type{<:LazyScalarSetBridge}, f) = f + +MOI.Bridges.adjoint_map_function(::Type{<:LazyScalarSetBridge}, f) = f + +MOI.Bridges.inverse_adjoint_map_function(::Type{<:LazyScalarSetBridge}, f) = f + +function MOI.Bridges.map_set( + ::Type{LazyScalarSetBridge{T,F,S}}, + set::MOI.LazyScalarSet{S}, +) where {T,F,S} + return set.set +end + +function MOI.Bridges.inverse_map_set( + ::Type{LazyScalarSetBridge{T,F,S}}, + set::S, +) where {T,F,S} + return MOI.LazyScalarSet(set) +end + +function MOI.supports_constraint( + ::Type{<:LazyScalarSetBridge}, + ::Type{<:MOI.AbstractScalarFunction}, + ::Type{<:MOI.LazyScalarSet}, +) + return true +end + +function MOI.Bridges.Constraint.concrete_bridge_type( + ::Type{<:LazyScalarSetBridge{T}}, + ::Type{F}, + ::Type{MOI.LazyScalarSet{S}}, +) where {T,F<:MOI.AbstractScalarFunction,S<:MOI.AbstractScalarSet} + return LazyScalarSetBridge{T,F,S} +end diff --git a/src/Utilities/sets.jl b/src/Utilities/sets.jl index 341b9bb1cf..cca6145ce8 100644 --- a/src/Utilities/sets.jl +++ b/src/Utilities/sets.jl @@ -90,6 +90,16 @@ end supports_shift_constant(::Type{<:MOI.Parameter}) = true +function supports_shift_constant( + ::Type{MOI.LazyScalarSet{S}}, +) where {S<:MOI.AbstractScalarSet} + return supports_shift_constant(S) +end + +function shift_constant(set::MOI.LazyScalarSet, constant) + return MOI.LazyScalarSet(shift_constant(set.set, constant)) +end + """ ScalarLinearSet{T} diff --git a/src/sets.jl b/src/sets.jl index aaf69260bd..affe319129 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -2866,6 +2866,33 @@ function Base.show(io::IO, s::VectorNonlinearOracle{T}) where {T} return end +""" + LazyScalarSet(set::AbstractScalarSet) <: AbstractScalarSet + +A set that wraps an inner `set` with a hint that the constraint can be treated +lazily by the solver. + +## Example + +```jldoctest +julia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()); + +julia> x = MOI.add_variable(model); + +julia> MOI.add_constraint( + model, + 1.0 * x, + MOI.LazyScalarSet(MOI.GreaterThan(1.0)), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LazyScalarSet{MathOptInterface.GreaterThan{Float64}}}(1) +``` +""" +struct LazyScalarSet{S<:AbstractScalarSet} <: AbstractScalarSet + set::S +end + +Base.copy(set::LazyScalarSet) = LazyScalarSet(copy(set.set)) + # TODO(odow): these are not necessarily isbits. They may not be safe to return # without copying if the number is BigFloat, for example. function Base.copy( diff --git a/test/Bridges/Constraint/test_LazyScalarSetBridge.jl b/test/Bridges/Constraint/test_LazyScalarSetBridge.jl new file mode 100644 index 0000000000..82c1349947 --- /dev/null +++ b/test/Bridges/Constraint/test_LazyScalarSetBridge.jl @@ -0,0 +1,41 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestConstraintLazyScalarSetBridge + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +function test_runtests_scalar_affine_function() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.LazyScalarSetBridge, + """ + variables: x + 1.0 * x in LazyScalarSet(GreaterThan(0.0)) + """, + """ + variables: x + 1.0 * x in GreaterThan(0.0) + """, + ) + return +end + +end # module + +TestConstraintLazyScalarSetBridge.runtests() diff --git a/test/Utilities/test_sets.jl b/test/Utilities/test_sets.jl index 9c06ce97fe..5a8aa934cc 100644 --- a/test/Utilities/test_sets.jl +++ b/test/Utilities/test_sets.jl @@ -257,6 +257,19 @@ function test_set_dot_scaling(n = 10) return end +function test_lazy_scalar_set() + set = MOI.LazyScalarSet(MOI.GreaterThan(1.0)) + set_2 = copy(set) + @test set_2 == set + @test MOI.Utilities.supports_shift_constant(typeof(set)) + set = MOI.Utilities.shift_constant(set, 2.0) + @test set_2 != set + @test set == MOI.LazyScalarSet(MOI.GreaterThan(3.0)) + set = MOI.LazyScalarSet(MOI.Integer()) + @test !MOI.Utilities.supports_shift_constant(typeof(set)) + return +end + end # module TestUtilitiesSets.runtests()