diff --git a/docs/src/submodules/Bridges/list_of_bridges.md b/docs/src/submodules/Bridges/list_of_bridges.md index 8dcfd5ce00..56fcc8fd52 100644 --- a/docs/src/submodules/Bridges/list_of_bridges.md +++ b/docs/src/submodules/Bridges/list_of_bridges.md @@ -51,6 +51,7 @@ Bridges.Constraint.IndicatorActiveOnFalseBridge Bridges.Constraint.IndicatorSOS1Bridge Bridges.Constraint.SemiToBinaryBridge Bridges.Constraint.ZeroOneBridge +Bridges.Constraint.AllDifferentToCountDistinctBridge Bridges.Constraint.BinPackingToMILPBridge Bridges.Constraint.CountDistinctToMILPBridge Bridges.Constraint.TableToMILPBridge diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index acccf47809..7364c6934c 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -18,6 +18,7 @@ include("map.jl") include("set_map.jl") include("single_bridge_optimizer.jl") +include("bridges/all_different.jl") include("bridges/bin_packing.jl") include("bridges/count_distinct.jl") include("bridges/det.jl") @@ -93,6 +94,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T} MOI.Bridges.add_bridge(bridged_model, SemiToBinaryBridge{T}) MOI.Bridges.add_bridge(bridged_model, ZeroOneBridge{T}) # Constraint programming bridges + MOI.Bridges.add_bridge(bridged_model, AllDifferentToCountDistinctBridge{T}) # TODO(odow): this reformulation assumes the bins are numbered 1..N. We # should fix this to use the variable bounds before adding automatically. # MOI.Bridges.add_bridge(bridged_model, BinPackingToMILPBridge{T}) diff --git a/src/Bridges/Constraint/bridges/all_different.jl b/src/Bridges/Constraint/bridges/all_different.jl new file mode 100644 index 0000000000..33a0204337 --- /dev/null +++ b/src/Bridges/Constraint/bridges/all_different.jl @@ -0,0 +1,204 @@ +# 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. + +""" + AllDifferentToCountDistinctBridge{T,F} <: Bridges.Constraint.AbstractBridge + +`AllDifferentToCountDistinctBridge` implements the following reformulations: + + * ``x \\in \\textsf{AllDifferent}(d)`` to ``(n, x) \\in \\textsf{CountDistinct}(1+d)`` + and ``n = d`` + * ``f(x) \\in \\textsf{AllDifferent}(d)`` to ``(d, f(x)) \\in \\textsf{CountDistinct}(1+d)`` + +## Source node + +`AllDifferentToCountDistinctBridge` supports: + + * `F` in [`MOI.AllDifferent`](@ref) + +where `F` is [`MOI.VectorOfVariables`](@ref) or +[`MOI.VectorAffineFunction{T}`](@ref). + +## Target nodes + +`AllDifferentToCountDistinctBridge` creates: + + * `F` in [`MOI.CountDistinct`](@ref) + * [`MOI.VariableIndex`](@ref) in [`MOI.EqualTo{T}`](@ref) +""" +mutable struct AllDifferentToCountDistinctBridge{ + T, + F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}, +} <: AbstractBridge + f::F + y::Union{Nothing,MOI.VariableIndex} + ci::MOI.ConstraintIndex{F,MOI.CountDistinct} + + function AllDifferentToCountDistinctBridge{T}( + f::MOI.VectorOfVariables, + y::MOI.VariableIndex, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.CountDistinct}, + ) where {T} + return new{T,MOI.VectorOfVariables}(f, y, ci) + end + + function AllDifferentToCountDistinctBridge{T}( + f::MOI.VectorAffineFunction{T}, + ci::MOI.ConstraintIndex{MOI.VectorAffineFunction{T},MOI.CountDistinct}, + ) where {T} + return new{T,MOI.VectorAffineFunction{T}}(f, nothing, ci) + end +end + +const AllDifferentToCountDistinct{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{AllDifferentToCountDistinctBridge{T},OT} + +function bridge_constraint( + ::Type{AllDifferentToCountDistinctBridge{T,F}}, + model::MOI.ModelLike, + f::F, + s::MOI.AllDifferent, +) where {T,F<:MOI.VectorOfVariables} + d = MOI.output_dimension(f) + y, _ = MOI.add_constrained_variable(model, MOI.EqualTo(T(d))) + ci = MOI.add_constraint( + model, + MOI.Utilities.operate(vcat, T, y, f), + MOI.CountDistinct(d + 1), + ) + return AllDifferentToCountDistinctBridge{T}(f, y, ci) +end + +function bridge_constraint( + ::Type{AllDifferentToCountDistinctBridge{T,F}}, + model::MOI.ModelLike, + f::F, + s::MOI.AllDifferent, +) where {T,F<:MOI.VectorAffineFunction{T}} + d = MOI.output_dimension(f) + ci = MOI.add_constraint( + model, + MOI.Utilities.operate(vcat, T, T(d), f), + MOI.CountDistinct(d + 1), + ) + return AllDifferentToCountDistinctBridge{T}(f, ci) +end + +function MOI.supports_constraint( + ::Type{<:AllDifferentToCountDistinctBridge{T}}, + ::Type{<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}}, + ::Type{MOI.AllDifferent}, +) where {T} + return true +end + +function MOI.Bridges.added_constrained_variable_types( + ::Type{AllDifferentToCountDistinctBridge{T,MOI.VectorOfVariables}}, +) where {T} + return Tuple{Type}[(MOI.EqualTo{T},)] +end + +function MOI.Bridges.added_constrained_variable_types( + ::Type{AllDifferentToCountDistinctBridge{T,MOI.VectorAffineFunction{T}}}, +) where {T} + return Tuple{Type}[] +end + +function MOI.Bridges.added_constraint_types( + ::Type{AllDifferentToCountDistinctBridge{T,F}}, +) where {T,F} + return Tuple{Type,Type}[(F, MOI.CountDistinct),] +end + +function concrete_bridge_type( + ::Type{<:AllDifferentToCountDistinctBridge{T}}, + ::Type{F}, + ::Type{MOI.AllDifferent}, +) where {T,F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}} + return AllDifferentToCountDistinctBridge{T,F} +end + +function MOI.get( + ::MOI.ModelLike, + ::MOI.ConstraintFunction, + bridge::AllDifferentToCountDistinctBridge, +) + return bridge.f +end + +function MOI.get( + ::MOI.ModelLike, + ::MOI.ConstraintSet, + bridge::AllDifferentToCountDistinctBridge, +) + return MOI.AllDifferent(MOI.output_dimension(bridge.f)) +end + +function MOI.delete( + model::MOI.ModelLike, + bridge::AllDifferentToCountDistinctBridge, +) + MOI.delete(model, bridge.ci) + if bridge.y !== nothing + MOI.delete(model, bridge.y) + end + return +end + +function MOI.get( + bridge::AllDifferentToCountDistinctBridge, + ::MOI.NumberOfVariables, +)::Int64 + if bridge.y === nothing + return 0 + end + return 1 +end + +function MOI.get( + bridge::AllDifferentToCountDistinctBridge, + ::MOI.ListOfVariableIndices, +)::Vector{MOI.VariableIndex} + if bridge.y === nothing + return MOI.VariableIndex[] + end + return [bridge.y] +end + +function MOI.get( + bridge::AllDifferentToCountDistinctBridge{T}, + ::MOI.NumberOfConstraints{MOI.VariableIndex,MOI.EqualTo{T}}, +)::Int64 where {T} + if bridge.y === nothing + return 0 + end + return 1 +end + +function MOI.get( + bridge::AllDifferentToCountDistinctBridge{T}, + ::MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.EqualTo{T}}, +) where {T} + if bridge.y === nothing + return MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}[] + end + ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}(bridge.y.value) + return [ci] +end + +function MOI.get( + ::AllDifferentToCountDistinctBridge{T,F}, + ::MOI.NumberOfConstraints{F,MOI.CountDistinct}, +)::Int64 where {T,F} + return 1 +end + +function MOI.get( + bridge::AllDifferentToCountDistinctBridge{T,F}, + ::MOI.ListOfConstraintIndices{F,MOI.CountDistinct}, +) where {T,F} + return [bridge.ci] +end diff --git a/src/Test/test_cpsat.jl b/src/Test/test_cpsat.jl index f98be44697..93d52b1ac1 100644 --- a/src/Test/test_cpsat.jl +++ b/src/Test/test_cpsat.jl @@ -22,6 +22,7 @@ function test_cpsat_AllDifferent( @requires _supports(config, MOI.optimize!) y = [MOI.add_constrained_variable(model, MOI.Integer()) for _ in 1:3] x = first.(y) + MOI.add_constraint.(model, x, MOI.Interval(zero(T), T(2))) MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.AllDifferent(3)) MOI.optimize!(model) x_val = MOI.get.(model, MOI.VariablePrimal(), x) diff --git a/test/Bridges/Constraint/all_different.jl b/test/Bridges/Constraint/all_different.jl new file mode 100644 index 0000000000..34fe292829 --- /dev/null +++ b/test/Bridges/Constraint/all_different.jl @@ -0,0 +1,74 @@ +# 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 TestConstraintAllDifferent + +using Test + +using MathOptInterface +const MOI = MathOptInterface + +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_VectorOfVariables() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.AllDifferentToCountDistinctBridge, + """ + variables: x, y, z + [x, y, z] in AllDifferent(3) + x in Interval(1.0, 2.0) + y >= 2.0 + y <= 3.0 + z == 2.0 + """, + """ + variables: x, y, z, n + [n, x, y, z] in CountDistinct(4) + x in Interval(1.0, 2.0) + y >= 2.0 + y <= 3.0 + z == 2.0 + n == 3.0 + """, + ) + return +end + +function test_runtests_VectorAffineFunction() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.AllDifferentToCountDistinctBridge, + """ + variables: x, y, z + [2.0 * x + -1.0, y, z] in AllDifferent(3) + x in Interval(1.0, 2.0) + y >= 2.0 + y <= 3.0 + z == 2.0 + """, + """ + variables: x, y, z + [3.0, 2.0 * x + -1.0, y, z] in CountDistinct(4) + x in Interval(1.0, 2.0) + y >= 2.0 + y <= 3.0 + z == 2.0 + """, + ) + return +end + +end # module + +TestConstraintAllDifferent.runtests()