Multi-dimensional Variables and Expressions
Defining multi-dimensional variables
PyQBPP supports multi-dimensional variables and multi-dimensional integer variables of arbitrary depth using the var() function. Their basic usage is as follows:
var("name", shape=(s1, s2, ..., sd)): Creates an array of variables with the givennameand shape $s_1\times s_2\times \cdots\times s_d$.var("name", shape=(s1, s2, ..., sd), between=(l, u)): Creates an array of integer variables with the specified range and shape.
The following program creates a binary variable with dimension $2\times 3\times 4$:
import pyqbpp as qbpp
x = qbpp.var("x", shape=(2, 3, 4))
print("x =", x)
Each variable in x can be accessed as x[i][j][k].
Array properties
PyQBPP arrays provide properties similar to NumPy.
| Property | Returns | Description |
|---|---|---|
a.shape | tuple | Size of each dimension (e.g. (2, 3, 4)) |
a.ndim | int | Number of dimensions |
a.size | int | Total number of elements (product of all dimensions) |
len(a) | int | Size of the outermost dimension (same as a.shape[0]) |
import pyqbpp as qbpp
x = qbpp.var("x", shape=(2, 3, 4))
print("shape:", x.shape) # (2, 3, 4)
print("ndim:", x.ndim) # 3
print("size:", x.size) # 24
print("len:", len(x)) # 2
Arrays of constants, variables, and expressions
Passing a Python list to qbpp.array(list) creates an array whose element type is automatically deduced from the first element:
| Call form | Result | Description |
|---|---|---|
qbpp.array([1, 2, 3]) | 1D integer constant array | Integer constant array |
qbpp.array([[1,2],[3,4]]) | 2D integer constant array | 2-D integer constant array |
qbpp.array([v1, v2]) | 1D binary variable array | Binary variable array |
qbpp.array([e1, e2]) | 1D expression array | Expression array |
Integer constant arrays can be used in element-wise operations with variable arrays. The following program computes the sum of the element-wise product of a $2\times 2$ integer constant matrix c and a binary variable matrix x:
import pyqbpp as qbpp
c = qbpp.array([[1, 2], [3, 4]])
x = qbpp.var("x", shape=(2, 2))
f = qbpp.sum(c * x)
print("f =", f)
c * x returns an element-wise product, and qbpp.sum sums all elements into a single expression. The output of this program is:
f = x[0][0] +2*x[0][1] +3*x[1][0] +4*x[1][1]
Creating integer variable arrays with individual ranges
When defining a multi-dimensional array of integer variables, all elements created by qbpp.var("name", shape=(s1, s2, ...), between=(l, u)) share the same range $[l, u]$. In many practical problems, however, each element may need a different range. There are three approaches to achieve this.
Approach 1: Placeholder array
First create a placeholder array using qbpp.var("name", shape=..., equal=val), then assign individual ranges to each element using qbpp.constrain():
import pyqbpp as qbpp
max_vals = qbpp.array([3, 7, 15, 5])
x = qbpp.var("x", shape=len(max_vals), equal=0)
for i in range(len(max_vals)):
x[i] = qbpp.constrain(x[i], between=(0, max_vals[i]))
for i in range(len(max_vals)):
print(f"x[{i}] = {x[i]}")
Here, qbpp.var("x", shape=4, equal=0) creates a mutable array of 4 integer variable placeholders, each initialized with the constant value 0. Each element is then reassigned with its own range using qbpp.constrain(x[i], between=(0, max_vals[i])). The qbpp.constrain() function automatically inherits the name from the placeholder, so no explicit name is needed.
NOTE The
equal=value can be any integer (not just 0). It allocates a mutable array in memory where each element can be individually reassigned. It does not create an equality constraint.
Approach 2: Passing lists to between=
You can pass Python lists as the between bounds. Each element of the array will be assigned the corresponding range from the lists:
import pyqbpp as qbpp
max_vals = [3, 7, 15, 5]
x = qbpp.var("x", shape=len(max_vals), between=(0, max_vals))
for i in range(len(max_vals)):
print(f"x[{i}] = {x[i]}")
This is the most concise approach. The shape= specifies the array dimensions, and between= assigns individual ranges from the lists element by element.
Approach 3: List comprehension with array
You can also use a Python list comprehension wrapped with qbpp.array():
import pyqbpp as qbpp
max_vals = qbpp.array([3, 7, 15, 5])
x = qbpp.array([qbpp.var(f"x[{i}]", between=(0, max_vals[i]))
for i in range(len(max_vals))])
This approach creates the variables directly without placeholders. Note that an explicit name (e.g., f"x[{i}]") must be provided for each variable, and the result must be wrapped with qbpp.array() to enable element-wise operations.
Defining multi-dimensional expressions
PyQBPP allows you to define multi-dimensional expressions with arbitrary depth using the function expr():
expr(shape=(s1, s2, ..., sd)): Creates a multi-dimensional array of expressions with shape $s_1\times s_2\times \cdots\times s_d$.
The following program defines a 3-dimensional array x of variables with shape $2\times 3\times 4$ and a 2-dimensional array f of size $2\times 3$. Then, using nested loops, each f[i][j] accumulates the sum of x[i][j][0] through x[i][j][3]:
import pyqbpp as qbpp
x = qbpp.var("x", shape=(2, 3, 4))
f = qbpp.expr(shape=(2, 3))
for i in range(2):
for j in range(3):
for k in range(4):
f[i][j] += x[i][j][k]
f.simplify_as_binary()
for i in range(2):
for j in range(3):
print(f"f[{i}][{j}] =", f[i][j])
This program produces the following output:
f[0][0] = x[0][0][0] +x[0][0][1] +x[0][0][2] +x[0][0][3]
f[0][1] = x[0][1][0] +x[0][1][1] +x[0][1][2] +x[0][1][3]
f[0][2] = x[0][2][0] +x[0][2][1] +x[0][2][2] +x[0][2][3]
f[1][0] = x[1][0][0] +x[1][0][1] +x[1][0][2] +x[1][0][3]
f[1][1] = x[1][1][0] +x[1][1][1] +x[1][1][2] +x[1][1][3]
f[1][2] = x[1][2][0] +x[1][2][1] +x[1][2][2] +x[1][2][3]
Creating an array of expressions by operations
An array of expressions can be created without explicitly calling expr(). When an arithmetic operation yields an array-shaped result, an array of expressions with the same shape is created automatically.
import pyqbpp as qbpp
x = qbpp.var("x", shape=(2, 3))
f = x + 1
f += x - 2
f.simplify_as_binary()
for i in range(2):
for j in range(3):
print(f"f[{i}][{j}] =", f[i][j])
This program outputs:
f[0][0] = -1 +2*x[0][0]
f[0][1] = -1 +2*x[0][1]
f[0][2] = -1 +2*x[0][2]
f[1][0] = -1 +2*x[1][0]
f[1][1] = -1 +2*x[1][1]
f[1][2] = -1 +2*x[1][2]
Iterating over multi-dimensional arrays
Since PyQBPP arrays support Python iteration, nested for loops can be used:
import pyqbpp as qbpp
x = qbpp.var("x", shape=(2, 3))
f = x + 1
f += x - 2
f.simplify_as_binary()
for row in f:
for element in row:
print(f"({element})", end="")
print()
This program outputs:
(-1 +2*x[0][0])(-1 +2*x[0][1])(-1 +2*x[0][2])
(-1 +2*x[1][0])(-1 +2*x[1][1])(-1 +2*x[1][2])
array and Python list
PyQBPP’s array is an opaque object backed by the QUBO++ shared library (.so). It is not a Python list — it is a specialized data structure optimized for QUBO++ operations.
Creating an array from a Python list
You can convert a Python list into an array using qbpp.array():
w = qbpp.array([64, 27, 47, 74, 12, 83, 63, 40])
Once converted, the array supports element-wise arithmetic (+, -, *, /, ~), sum(), sqr(), simplify(), and other QUBO++ functions efficiently.
When you don’t need qbpp.array()
When a Python list is used in an arithmetic operation with an array, it is automatically converted. For example:
w = [64, 27, 47, 74, 12, 83, 63, 40]
x = qbpp.var("x", shape=len(w))
f = w * x # list * Array → element-wise multiplication
In this case, wrapping w with qbpp.array() is not necessary. However, if w is used repeatedly in multiple operations, wrapping it once with qbpp.array() can improve performance by avoiding repeated conversions from list to array.
Example: list vs array behavior
The following example illustrates the difference between a Python list and an array:
import pyqbpp as qbpp
x = qbpp.var("x")
u = [x+2, x+3, x+5, x+7]
w = qbpp.array([x+2, x+3, x+5, x+7])
print(f"2 * u = {2 * u}")
print(f"2 * w = {2 * w}")
Output:
2 * u = [2 +x, 3 +x, 5 +x, 7 +x, 2 +x, 3 +x, 5 +x, 7 +x]
2 * w = [4 +2*x, 6 +2*x, 10 +2*x, 14 +2*x]
With the Python list u, 2 * u produces a repeated list (8 elements). With the array w, 2 * w produces an element-wise multiplication (each element multiplied by 2).
Key differences from Python list
| array | Python list | |
|---|---|---|
Element-wise + | Element-wise addition | List concatenation |
Element-wise * | Element-wise multiplication | List repetition |
~x | Element-wise negation | TypeError |
sum() | Sum of all elements as an expression | Python built-in sum |
sqr() | Element-wise squaring | Not available |
append(), pop() | Not available | Available |
| Slicing | x[1:3], x[:n], x[-n:] | x[1:3] |
NOTE An array is a fixed-size, opaque container. Python list operations such as
append(),pop(),insert(), and slice assignment are not supported. Use Python slice syntax (x[1:3],x[:n], etc.) for extracting sub-arrays.