PyCSP3

# Constraint Count

The constraint Count imposes that the number of variables from a specified list of variables that take their values from a specified set (often, a single value) respects a numerical condition.

Below, we give several illustrations corresponding to representative use cases of the constraint Count.

## Case 1: AtLeast1

The first case is when at least 1 variable from a specified list must take a specified value.

To see how this constraint works, we need first to import the library PyCSP$^3$:

from pycsp3 import *


For our illustration, we introduce an array $x$ of 4 variables, each one with {0,1,2} as domain.

x = VarArray(size=4, dom=range(3))


We can display (the structure of) the array as well as the domain of the first variable.

print("Array of variable x: ", x)
print("Domain of any variable: ", x[0].dom)

Array of variable x:  [x[0], x[1], x[2], x[3]]
Domain of any variable:  0..2


We simply post a single constraint Count: at least 1 variable of $x$ must take the value 1.

satisfy(
Count(x, value=1) >= 1
);


By calling the function solve(), we can check that the problem (actually, the single constraint) is satisfiable (SAT). We can also print the values assigned to the variables in the found solution; we can call the function values() that collects the values assigned (in the last found solution) to a specified list of variables.

if solve() is SAT:
print("Solution: ", values(x))

Solution:  [0, 0, 0, 1]


One can infer that the number of solutions (supports) for this constraint is $3^4 - 2^4$.

if solve(sols=ALL) is SAT:
print("Last found solution: ", values(x))
print("Number of solutions: ", n_solutions())

Last found solution:  [2, 1, 1, 1]
Number of solutions:  65


## Case 2: AtMost1

The second case is when at most 1 variable from a specified list must take a specified value.

To start, we remove the last posted constraint by calling the function unpost().

unpost()


We control that there are no more constraints.

print(posted())

[]


We post a single constraint Count: at most 1 variable of $x$ must take the value 0.

satisfy(
Count(x, value=0) <= 1
);


We can call the solver.

if solve() is SAT:
print("Solution: ", values(x))

Solution:  [0, 1, 1, 1]


One can infer that the number of solutions (supports) for this constraint is $2^4 + 4 \times 2^3=48$.

if solve(sols=ALL) is SAT:
print("Last found solution: ", values(x))
print("Number of solutions: ", n_solutions())

Last found solution:  [2, 2, 2, 1]
Number of solutions:  48


## Case 3: ExactlyK

The third case is when exactly k variables from a specified list must take a specified value.

To start, we remove the last posted constraint by calling the function unpost().

unpost()


We post a single constraint Count: exactly 2 variables of $x$ must take the value 2.

satisfy(
Count(x, value=2) == 2
);


Interestingly, we can display the internal representation of the posted constraint; although a little bit technical, we can see that the information is correctly recorded (note that ‘eq’ stands for ‘equal to’).

print(posted())

count(list:[x[0], x[1], x[2], x[3]], values:[2], condition:(eq,2))


We can call the solver.

if solve() is SAT:
print("Solution: ", values(x))

Solution:  [0, 0, 2, 2]


One can infer that the number of solutions (supports) for this constraint is ${4 \choose 2} \times 2^2=24$.

if solve(sols=ALL) is SAT:
print("Last found solution: ", values(x))
print("Number of solutions: ", n_solutions())

Last found solution:  [1, 2, 2, 0]
Number of solutions:  24


## Case 4: Count Defined by a Variable

The fouth case is when a variable is used to represent (count) the number of variables from a specified list that take a specified value.

To start, we remove the last posted constraint by calling the function unpost().

unpost()


We introduce a variable, used for recording the “counting”.

y = Var(range(5))


We post a single constraint Count: the number of variables of $x$ that take the value 1 is $y$.

satisfy(
Count(x, value=1) == y
);


We can call the solver.

if solve() is SAT:
print("Solution: ", values(x), value(y))

Solution:  [0, 0, 0, 0] 0


For any tuple that can be built from $x$, which is $3^4$, the value of $y$ can be determined.

if solve(sols=ALL) is SAT:
print("Last found solution: ", values(x), value(y))
print("Number of solutions: ", n_solutions())

Last found solution:  [1, 2, 0, 0] 1
Number of solutions:  81


## Case 5: Among

The fifth case is when exactly k variables from a specified list must take a specified value.

First of all, we need to remove everything that has been introduced so far.

clear()  # to clear previously posted variables and constraints


We re-introduce an array $x$ of 4 variables, each one with {0,1,2} as domain.

x = VarArray(size=4, dom=range(3))


We post a single constraint Count: the number of variables of $x$ that take their values among ${0,2}$ must be equal to 3.

satisfy(
Count(x, values=[1,2]) == 3
);


We can call the solver.

if solve() is SAT:
print("Solution: ", values(x))

Solution:  [0, 1, 1, 1]


## Case 6: Count on Expressions

The sixth case is when we count the number of occurrences of a specified value over a list of expressions.

To start, we remove the last posted constraint by calling the function unpost().

unpost()


We post a single constraint Count: the number of expressions that take the value 3 is 2.

satisfy(
Count([x[0]+x[1], x[1]+x[2], x[2]+x[3]], value=3) == 2
);


By enumerating the solutions, one can observe that two expressions are always evaluated as the value 3.

if solve(sols=ALL) is SAT:
for i in range(n_solutions()):
print(f"Solution {i+1}: {values(x, sol=i)}")

Solution 1: [0, 1, 2, 1]
Solution 2: [0, 2, 1, 2]
Solution 3: [1, 2, 1, 0]
Solution 4: [1, 2, 1, 1]
Solution 5: [1, 1, 2, 1]
Solution 6: [2, 2, 1, 2]
Solution 7: [2, 1, 2, 0]
Solution 8: [2, 1, 2, 2]
Solution 9: [1, 2, 2, 1]
Solution 10: [2, 1, 1, 2]