# 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[0].dom)
print("Domain of variables of y: ", y[0][0].dom)
print("Domain of variables of z: ", z[0][0][0].dom)

Array x:  [x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9]]
Array y:  [
[y[0][0], y[0][1], y[0][2], y[0][3], y[0][4]]
[y[1][0], y[1][1], y[1][2], y[1][3], y[1][4]]
[y[2][0], y[2][1], y[2][2], y[2][3], y[2][4]]
[y[3][0], y[3][1], y[3][2], y[3][3], y[3][4]]
[y[4][0], y[4][1], y[4][2], y[4][3], y[4][4]]
]
Array z:  [
[
[z[0][0][0], z[0][0][1], z[0][0][2], z[0][0][3]]
[z[0][1][0], z[0][1][1], z[0][1][2], z[0][1][3]]
[z[0][2][0], z[0][2][1], z[0][2][2], z[0][2][3]]
],
[
[z[1][0][0], z[1][0][1], z[1][0][2], z[1][0][3]]
[z[1][1][0], z[1][1][1], z[1][1][2], z[1][1][3]]
[z[1][2][0], z[1][2][1], z[1][2][2], z[1][2][3]]
],
[
[z[2][0][0], z[2][0][1], z[2][0][2], z[2][0][3]]
[z[2][1][0], z[2][1][1], z[2][1][2], z[2][1][3]]
[z[2][2][0], z[2][2][1], z[2][2][2], z[2][2][3]]
],
[
[z[3][0][0], z[3][0][1], z[3][0][2], z[3][0][3]]
[z[3][1][0], z[3][1][1], z[3][1][2], z[3][1][3]]
[z[3][2][0], z[3][2][1], z[3][2][2], z[3][2][3]]
],
]
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[2] is the third variable of x, and y[1] 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[1][0] is equal to None.

print("Array y: ", y)

Array y:  [
[None, y[0][1], y[0][2], y[0][3], y[0][4]]
[None, None, y[1][2], y[1][3], y[1][4]]
[None, None, None, y[2][3], y[2][4]]
[None, None, None, None, y[3][4]]
[None, None, None, None, None]
]