Skip to content

Commit 8813093

Browse files
committed
Add CountDistinct set
1 parent 73b9338 commit 8813093

File tree

6 files changed

+90
-0
lines changed

6 files changed

+90
-0
lines changed

docs/src/reference/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Complements
100100

101101
```@docs
102102
AllDifferent
103+
CountDistinct
103104
```
104105

105106
## Matrix sets

src/Test/test_basic_constraint.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ _set(::Type{MOI.RootDetConeTriangle}) = MOI.RootDetConeTriangle(3)
110110
_set(::Type{MOI.RootDetConeSquare}) = MOI.RootDetConeSquare(3)
111111
_set(::Type{MOI.Complements}) = MOI.Complements(2)
112112
_set(::Type{MOI.AllDifferent}) = MOI.AllDifferent(3)
113+
_set(::Type{MOI.CountDistinct}) = MOI.CountDistinct(4)
113114

114115
function _set(
115116
::Type{MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.LessThan{T}}},
@@ -274,6 +275,7 @@ for s in [
274275
:RootDetConeSquare,
275276
:Complements,
276277
:AllDifferent,
278+
:CountDistinct,
277279
]
278280
S = getfield(MOI, s)
279281
functions = if S <: MOI.AbstractScalarSet

src/Test/test_cpsat.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,44 @@ function setup_test(
4646
)
4747
return
4848
end
49+
50+
"""
51+
test_cpsat_CountDistinct(model::MOI.ModelLike, config::Config)
52+
53+
Add a VectorOfVariables-in-CountDistinct constraint.
54+
"""
55+
function test_cpsat_CountDistinct(
56+
model::MOI.ModelLike,
57+
config::Config{T},
58+
) where {T}
59+
@requires MOI.supports_constraint(
60+
model,
61+
MOI.VectorOfVariables,
62+
MOI.CountDistinct,
63+
)
64+
@requires MOI.supports_add_constrained_variable(model, MOI.Integer)
65+
@requires _supports(config, MOI.optimize!)
66+
y = [MOI.add_constrained_variable(model, MOI.Integer()) for _ in 1:4]
67+
x = first.(y)
68+
MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.CountDistinct(4))
69+
MOI.optimize!(model)
70+
x_val = round.(Int, MOI.get.(model, MOI.VariablePrimal(), x))
71+
@test length(unique(x_val[2:end])) == x_val[1]
72+
return
73+
end
74+
75+
function setup_test(
76+
::typeof(test_cpsat_CountDistinct),
77+
model::MOIU.MockOptimizer,
78+
::Config{T},
79+
) where {T}
80+
MOIU.set_mock_optimize!(
81+
model,
82+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(
83+
mock,
84+
MOI.OPTIMAL,
85+
(MOI.FEASIBLE_POINT, T[2, 0, 1, 0]),
86+
),
87+
)
88+
return
89+
end

src/Utilities/model.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,7 @@ const LessThanIndicatorZero{T} =
788788
MOI.LogDetConeTriangle,
789789
MOI.LogDetConeSquare,
790790
MOI.AllDifferent,
791+
MOI.CountDistinct,
791792
),
792793
(
793794
MOI.PowerCone,

src/sets.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,49 @@ struct AllDifferent <: AbstractVectorSet
11431143
end
11441144
end
11451145

1146+
"""
1147+
CountDistinct(dimension::Int)
1148+
1149+
The set ``\\{(n, x) \\in \\mathbb{R}^{1+d}\\}`` such that the number of distinct
1150+
values in `x` is `n`.
1151+
1152+
This constraint is sometimes called `nvalues`.
1153+
1154+
## Example
1155+
1156+
```julia
1157+
model = Utilities.Model{Float64}()
1158+
n = add_constrained_variable(model, MOI.Integer())
1159+
x = [add_constrained_variable(model, MOI.Integer()) for _ in 1:3]
1160+
add_constraint(model, vcat(n, x), CountDistinct(4))
1161+
# if n == 1, then x[1] == x[2] == x[3]
1162+
# if n == 2, then x[1] == x[2] != x[3] || x[1] != x[2] == x[3]
1163+
# if n == 3, then x[1] != x[2] != x[3]
1164+
```
1165+
1166+
## Relationship to AllDifferent
1167+
1168+
When the first element is `dimension - 1`, `CountDistinct` is equivalent to an
1169+
[`AllDifferent`](@ref) constraint.
1170+
1171+
```julia
1172+
model = Utilities.Model{Float64}()
1173+
x = [add_constrained_variable(model, MOI.Integer()) for _ in 1:3]
1174+
add_constraint(model, vcat(3, x), CountDistinct(4))
1175+
# equivalent to
1176+
add_constraint(model, x, AllDifferent(3))
1177+
```
1178+
"""
1179+
struct CountDistinct <: AbstractVectorSet
1180+
dimension::Int
1181+
function CountDistinct(dimension::Base.Integer)
1182+
if dimension < 1
1183+
throw(DimensionMismatch("Dimension of CountDistinct must be >= 1."))
1184+
end
1185+
return new(dimension)
1186+
end
1187+
end
1188+
11461189
# isbits types, nothing to copy
11471190
function Base.copy(
11481191
set::Union{
@@ -1178,6 +1221,7 @@ function Base.copy(
11781221
Semicontinuous,
11791222
Semiinteger,
11801223
AllDifferent,
1224+
CountDistinct,
11811225
},
11821226
)
11831227
return set

test/sets.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ function test_sets_DimensionMismatch()
140140
(MOI.RootDetConeTriangle, 0),
141141
(MOI.RootDetConeSquare, 0),
142142
(MOI.AllDifferent, 0),
143+
(MOI.CountDistinct, 1),
143144
)
144145
@test_throws DimensionMismatch S(min_dimension - 1)
145146
@test S(min_dimension) isa S

0 commit comments

Comments
 (0)