1.1. Operator Class

Quantum operators are represented as matrices in qopt. The class DenseOperator encodes a quantum operator in a dense representation. Simple examples are the pauli matrices

[1]:
from qopt import *

print(DenseOperator.pauli_x())
print(DenseOperator.pauli_y())

DenseOperator with data:
array([[0.+0.j, 1.+0.j],
       [1.+0.j, 0.+0.j]])
DenseOperator with data:
array([[ 0.+0.j, -0.-1.j],
       [ 0.+1.j,  0.+0.j]])

Internally, the DenseOperator is based on a 2-dimensional numpy array, which can be accessed by the data attribute.

[2]:
print(DenseOperator.pauli_x().data)
[[0.+0.j 1.+0.j]
 [1.+0.j 0.+0.j]]

The same class can also be used to store state vectors, density matrices, propagators and so on. The DenseOperator can be initialized from a 2-dimensional numpy array, a scipy sparse matrix or a Qobj from the Quantum Toolbox in Python (QuTiP).

[3]:
import numpy as np
import scipy.sparse as sp
from qutip import sigmaz

pauli_z = DenseOperator(np.diag((1, -1)))
print(pauli_z)
pauli_z_from_sparse = DenseOperator(sp.csr_matrix(np.diag((1, -1))))
print(pauli_z_from_sparse)
print(sigmaz())
print(DenseOperator(sigmaz()))
DenseOperator with data:
array([[ 1.+0.j,  0.+0.j],
       [ 0.+0.j, -1.+0.j]])
DenseOperator with data:
array([[ 1.+0.j,  0.+0.j],
       [ 0.+0.j, -1.+0.j]])
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True
Qobj data =
[[ 1.  0.]
 [ 0. -1.]]
DenseOperator with data:
array([[ 1.+0.j,  0.+0.j],
       [ 0.+0.j, -1.+0.j]])

The matrix arithmetic is overloaded to support intuitive matrix operation like addition, multiplication, scalar multiplication and so on.

[4]:
p_x = DenseOperator.pauli_x()
p_z = DenseOperator.pauli_z()

print("2 * pauli_z =")
print(2 * p_z)
print("pauli_z +  pauli_x =")
print(p_z + p_x)
print("pauli_x * pauli_z =")
print(p_x * p_z)
2 * pauli_z =
DenseOperator with data:
array([[ 2.+0.j,  0.+0.j],
       [ 0.+0.j, -2.+0.j]])
pauli_z +  pauli_x =
DenseOperator with data:
array([[ 1.+0.j,  1.+0.j],
       [ 1.+0.j, -1.+0.j]])
pauli_x * pauli_z =
DenseOperator with data:
array([[ 0.+0.j, -1.+0.j],
       [ 1.+0.j,  0.+0.j]])

The operator class implements plenty of useful functions for quantum mechanics. For example the kronecker matrix product and partial traces are included to work with product spaces.

Assume you describe two qubits and need an operator \(p = \sigma_x \otimes \sigma_0\) that operates as bit flip on the first qubit and as identity on the second qubit. Then this operator is constructed as:

[5]:
p = DenseOperator.pauli_x().kron(DenseOperator.pauli_0())
print(p)
DenseOperator with data:
array([[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
       [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
       [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
       [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]])

And taking the partial trace over the second space provides the bit flip gate:

[6]:
print(.5 * p.ptrace(dims=[2, 2], remove=[1]))
DenseOperator with data:
array([[0.+0.j, 1.+0.j],
       [1.+0.j, 0.+0.j]])

When calculating the partial trace, the argument ‘dims’ specifies the dimensions of the subsystems and the argument ‘remove’ contains a list of the subsystems that are to be traced over.

Another useful function is the spectral decomposition:

[7]:
eigenvalues, eigenvectors = p_x.spectral_decomposition(hermitian=True)
print('Eigenvalues:')
print(eigenvalues)
print('Eigenvectors:')
print(eigenvectors)
Eigenvalues:
[-1.  1.]
Eigenvectors:
[[-0.70710678+0.j  0.70710678+0.j]
 [ 0.70710678+0.j  0.70710678+0.j]]

And complex conjugation, transposition and the calculation of the adjoint matrix are also supported:

[8]:
p_y = DenseOperator.pauli_y()
print('Pauli Y:')
print(p_y)
print('Pauli Y complex conjugated:')
print(p_y.conj())
print('Pauli Y complex transposed:')
print(p_y.tr())
print('Pauli Y complex adjoint:')
print(p_y.dag())
Pauli Y:
DenseOperator with data:
array([[ 0.+0.j, -0.-1.j],
       [ 0.+1.j,  0.+0.j]])
Pauli Y complex conjugated:
DenseOperator with data:
array([[ 0.-0.j, -0.+1.j],
       [ 0.-1.j,  0.-0.j]])
Pauli Y complex transposed:
0j
Pauli Y complex adjoint:
DenseOperator with data:
array([[ 0.-0.j,  0.-1.j],
       [-0.+1.j,  0.-0.j]])

There are even more functions to be discovered in the API documentation.