PyCSP3

# Declaring Variables

Below, we show how to declare stand-alone variables, and multi-dimensional arrays of variables.

Remark. In PyCSP$^3$, which is currently targeted to XCSP$^3$-core, we can only define integer and symbolic variables with finite domains, i.e., variables with a finite set of integers or symbols (strings).

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

from pycsp3 import *


## Stand-alone Variables

Stand-alone variables can be declared by means of the PyCSP$^3$ function Var(). To define the domain of a variable, we can simply list values, or use range(). For example, we can define three integer variables w, x and y, as well as a symbolic variable z as follows:

w = Var(range(15))
x = Var(0, 1)
y = Var(0, 2, 4, 6, 8)
z = Var("a", "b", "c")


The domain of a variable is given by a field called dom. So, we can control the domain of the declared variables:

print("Domain of w: ", w.dom)
print("Domain of x: ", x.dom)
print("Domain of y: ", y.dom)
print("Domain of z: ", z.dom)

Domain of w:  0..14
Domain of x:  0 1
Domain of y:  0 2 4 6 8
Domain of z:  a b c


Note that values can be directly listed as above, or given in a set as follows:

clear()  # to discard previously posted variables
w = Var(set(range(15)))
x = Var({0, 1})
y = Var({0, 2, 4, 6, 8})
z = Var({"a", "b", "c"})


We can check that we obtain similar domains:

print("Domain of w: ", w.dom)
print("Domain of x: ", x.dom)
print("Domain of y: ", y.dom)
print("Domain of z: ", z.dom)

Domain of w:  0..14
Domain of x:  0 1
Domain of y:  0 2 4 6 8
Domain of z:  a b c


It is also possible to name the parameter dom when defining the domain:

clear()  # to discard previously posted variables
w = Var(dom=range(15))   # or equivalently, w = Var(dom=set(range(15)))
x = Var(dom={0, 1})
y = Var(dom={0, 2, 4, 6, 8})
z = Var(dom={"a", "b", "c"})


We can check that we obtain similar domains:

print("Domain of w: ", w.dom)
print("Domain of x: ", x.dom)
print("Domain of y: ", y.dom)
print("Domain of z: ", z.dom)

Domain of w:  0..14
Domain of x:  0 1
Domain of y:  0 2 4 6 8
Domain of z:  a b c


Finally, it is of course possible to use generators and comprehension sets. For example, for y, we can write:

clear()
y = Var(i for i in range (10) if i % 2 == 0)
print("Domain of y: ", y.dom)
clear()
y = Var({i for i in range (10) if i % 2 == 0})
print("Domain of y: ", y.dom)
clear()
y = Var(dom={i for i in range(10) if i % 2 == 0})
print("Domain of y: ", y.dom)

Domain of y:  0 2 4 6 8
Domain of y:  0 2 4 6 8
Domain of y:  0 2 4 6 8


## Arrays of Variables

The PyCSP$^3$ function for declaring an array of variables is VarArray() that requires two named parameters size and dom. For declaring a one-dimensional array of variables, the value of size must be an integer (or a list containing only one integer), for declaring a two-dimensional array of variables, the value of size must be a list containing exactly two integers, and so on. The named parameter dom indicates the domain of each variable in the array.

For example, to declare:

• x, a one-dimensional array of 10 variables with domain {0,1}
• y, a two-dimensional array of $5 \times 5$ variables with domain {0,1,…, 9}
• z, a three-dimensional array of $4 \times 3 \times 4$ variables with domain {1, 5, 10, 20}

we write:

clear()  # to discard previously posted variables
x = VarArray(size=10, dom={0, 1})
y = VarArray(size=[5, 5], dom=range(10))
z = VarArray(size=[4, 3, 4], dom={1, 5, 10, 20})


We can display the (structure of the) arrays, as well as the domains of the variables.

print("Array x: ", x)
print("Array y: ", y)
print("Array z: ", z)
print("Domain of variables of x: ", x.dom)
print("Domain of variables of y: ", y.dom)
print("Domain of variables of z: ", z.dom)

Array x:  [x, x, x, x, x, x, x, x, x, x]
Array y:  [
[y, y, y, y, y]
[y, y, y, y, y]
[y, y, y, y, y]
[y, y, y, y, y]
[y, y, y, y, y]
]
Array z:  [
[
[z, z, z, z]
[z, z, z, z]
[z, z, z, z]
],
[
[z, z, z, z]
[z, z, z, z]
[z, z, z, z]
],
[
[z, z, z, z]
[z, z, z, z]
[z, z, z, z]
],
[
[z, z, z, z]
[z, z, z, z]
[z, z, z, z]
],
]
Domain of variables of x:  0 1
Domain of variables of y:  0..9
Domain of variables of z:  1 5 10 20


Important: Indexing starts at 0. For example, x is the third variable of x, and y is the second row of y. Technically, variable arrays are objects that are instances of ListVar, a subclass of list; additional functionalities of such objects are useful, for example, when posting constraints Element.

### Arrays with Variables of Different Domains

In some situations, you may want to declare variables in an array with different domains. For a one-dimensional array, you can give the name of a function that accepts an integer i and returns the domain to be associated with the variable at index i in the array. For a two-dimensional array, you can give the name of a function that accepts a pair of integers (i,j) and returns the domain to be associated with the variable at indexes i, j in the array. And so on.

For example, suppose that the domain of all variables of the first column of y is range(5) instead of range(10). We can write:

clear() # to discard previously posted variables

def domain_y(i,j):
return range(5) if j == 0 else range(10)

y = VarArray(size=[5, 5], dom=domain_y)


We can observe that domains of variables in y are not all the same:

for i in range(5):
print(f"Domains on row {i}: {[y[i][j].dom for j in range(5)]}")

Domains on row 0: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 1: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 2: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 3: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 4: [0..4, 0..9, 0..9, 0..9, 0..9]


We can also use a lambda function:

clear() # to discard previously posted variables

y = VarArray(size=[5, 5], dom=lambda i,j: range(5) if j == 0 else range(10))


We can check:

for i in range(5):
print(f"Domains on row {i}: {[y[i][j].dom for j in range(5)]}")

Domains on row 0: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 1: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 2: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 3: [0..4, 0..9, 0..9, 0..9, 0..9]
Domains on row 4: [0..4, 0..9, 0..9, 0..9, 0..9]


Sometimes, not all variables in an array are relevant. For example, you may only want to use the variables in the lower part of a two-dimensional array (matrix). In that case, the value None must be used.

For example, if one wanted to introduce an auxiliary array y for the problem Golomb Ruler to compute distances between any two pairs of the main array x, we could write:

clear() # to discard previously posted variables

# y[i][j] is the distance between x[i] and x[j] for i strictly less than j
y = VarArray(size=[5, 5], dom=lambda i, j: range(1, 5 * 5) if i < j else None)


One can see that only half of the array is really used (i.e., really contains variables): the lower part (below the main downward diagonal) only contains None. For example, y is equal to None.

print("Array y: ", y)

Array y:  [
[None, y, y, y, y]
[None, None, y, y, y]
[None, None, None, y, y]
[None, None, None, None, y]
[None, None, None, None, None]
]