QuantumArray#

class QuantumArray(qtype, shape, qs=None)[source]#

This class allows the convenient management of multiple QuantumVariables of one type. Inspired by the well known numpy ndarray, the QuantumArray supports many convenient array manipulation methods. Similar to the numpy equivalent, creating a QuantumArray can be achieved by specifying a shape and a qtype:

>>> import numpy as np
>>> from qrisp import QuantumArray, QuantumFloat
>>> qtype = QuantumFloat(5, -2)
>>> q_array = QuantumArray(qtype = qtype, shape = (2, 2, 2))

Note that qtype is not a type object but a QuantumVariable which serves as an “example”.

To retrieve the entries (i.e. QuantumVariables) from the QuantumArray, we simply index as with regular numpy arrays:

>>> from qrisp import h
>>> qv = q_array[0,0,1]
>>> h(qv[0])
>>> print(q_array)
{OutcomeArray([[[0., 0.],
                [0., 0.]],
               [[0., 0.],
                [0., 0.]]]): 0.5,
 OutcomeArray([[[0.  , 0.25],
                [0.  , 0.  ]],
               [[0.  , 0.  ],
                [0.  , 0.  ]]]): 0.5}

We see the value 0.25 in the second entry because we applied an H-gate onto the 0-th qubit of the QuantumVariable at position (0,0,1). Since the type of this array is a QuantumFloat, with exponent -2, the significance of this qubit is 0.25.

Note that the keys of the dictionary returned by the get_measurement method are no regular numpy arrays, as key objects need to be hashable. Instead, they are objects of an immutable subclass of np.ndarray called OutcomeArray, that supports hashing.

For QuantumArrays, many methods known from numpy arrays work here too:

>>> q_array = q_array.reshape(2,4)

Not only do the ndarray methods work but also many other convenience functions from the numpy module:

>>> q_array_swap = np.swapaxes(q_array, 0, 1)
>>> print(q_array_swap)
{OutcomeArray([[0., 0.],
               [0., 0.],
               [0., 0.],
               [0., 0.]]): 0.5,
 OutcomeArray([[0.  , 0.  ],
               [0.25, 0.  ],
               [0.  , 0.  ],
               [0.  , 0.  ]]): 0.5}

To initiate the array, we use the encode method. Similar to QuantumVariables, we can also use the slicing operator, but this time non-trivial slices are possible as well:

>>> q_array[1:,:] = 2*np.ones((1,4))
>>> print(q_array)
{OutcomeArray([[0., 0., 0., 0.],
               [2., 2., 2., 2.]]): 0.5,
 OutcomeArray([[0.  , 0.25, 0.  , 0.  ],
               [2.  , 2.  , 2.  , 2.  ]]): 0.5}

Quantum indexing

QuantumArrays can be dereferenced by QuantumFloats. This returns a QuantumEnvironment in which the corresponding entry is avaliable as a QuantumVariable.

from qrisp import QuantumBool, QuantumArray, QuantumFloat, h, x, multi_measurement

q_array = QuantumArray(QuantumFloat(1), shape = (4,4))
index_0 = QuantumFloat(2)
index_1 = QuantumFloat(2)

index_0[:] = 2
index_1[:] = 1

h(index_0[0])

with q_array[index_0, index_1] as entry:
    x(entry)
>>> print(multi_measurement([index_0, index_1, q_array]))
{(2, 1, OutcomeArray([[0., 0., 0., 0.],
                      [0., 0., 0., 0.],
                      [0., 1., 0., 0.],
                      [0., 0., 0., 0.]])): 0.5,
 (3, 1, OutcomeArray([[0., 0., 0., 0.],
                      [0., 0., 0., 0.],
                      [0., 0., 0., 0.],
                      [0., 1., 0., 0.]])): 0.5}

Note

This only works for arrays which have a size of an integer power of 2.

Matrix multiplication

For QuantumArrays with qtype QuantumFloat, matrix multiplication is available.

>>> q_array_1 = QuantumArray(qtype, (2,2))
>>> q_array_2 = QuantumArray(qtype, (2,2))
>>> q_array_1[:] = 2*np.eye(2)
>>> q_array_2[:] = [[1,2],[3,4]]
>>> print(q_array_1 @ q_array_2)
{OutcomeArray([[2., 4.],
               [6., 0.]]): 1.0}

Note

By default, the output matrix will have the same qtype as the first input matrix. Here, the qtype is a QuantumFloat with 5 mantissa bits and exponent -2, implying that the result 8 yields overflow. Since qrisps unsigend arithmetic is modular, we get a 0.

It is also possible to multiply classical and quantum matrices

>>> q_array = QuantumArray(qtype, (2,2))
>>> q_array[:] = 3*np.eye(2)
>>> cl_array = np.array([[1,2],[3,4]])
>>> print(q_array @ cl_array)
{OutcomeArray([[3., 6.],
               [1., 4.]]): 1.0}

Methods#

QuantumArray.delete([verify])

Performs the delete method on all QuantumVariables in this array.

QuantumArray.duplicate([init, qs])

This method returns a fresh QuantumArray, with equal qtype and shape.

QuantumArray.encode(value)

The encode method allows to quickly bring a QuantumArray in a desired computational basis state.

QuantumArray.get_measurement([backend, ...])

Method for acquiring measurement results for the given array.

QuantumArray.most_likely(**meas_kwargs)

Performs a measurement and returns the most likely outcome.

Array Restructuring#

Note

These methods never allocate additional qubits and instead return a “view”.

QuantumArray.concatenate(other[, axis])

Concatenates two QuantumArrays along an axis with similar semantics as numpy.concatenate.

QuantumArray.flatten()

Flattens the QuantumArray with similar semantics as numpy.ndarray.flatten.

QuantumArray.ravel()

Ravels the QuantumArray with similar semantics as numpy.ndarray.ravel.

QuantumArray.reshape(*args)

Adjusts the shape of the QuantumArray with similar semantics as numpy.ndarray.reshape.

QuantumArray.swapaxes(axis_1, axis_2)

Swaps the axes of the QuantumArray with similar semantics as numpy.ndarray.swapaxes.

QuantumArray.transpose(*axes)

Reverses the axes of the QuantumArray with similar semantics as numpy.ndarray.transpose.