Consider the space of density matrices corresponding to a Hilbert space $\mathcal{H}$ of dimension $d$. The basis used for this Hilbert-Schmidt space, $B(\mathcal{H})$, can be any set of $d\times d$ matrices which span the density matrix space. pyGSTi supports arbitrary bases by deriving from the pygsti.tools.Basis
class, and constains built-in support for the following basis sets:
"std"
to appropriate function arguments."pp"
."gm"
."qt"
.Various functions and objects within pyGSTi require knowledge of what Hilbert-Schmidt basis is being used. The pygsti.objects.Basis
object encapsulates a basis, and is the most flexible way of specifying a basis in pyGSTi. Alternatively, many functions also accept the short strings "std"
, "gm"
, "pp"
, and "qt"
to select one of the standard bases. In this tutorial, we'll demonstrate how to create a Basis
object and use it and related functions to obtain and change the basis of the operation matrices and SPAM vectors stored in a Model
.
The most straightforward way to create a Basis
object is to provide its short name and dimension. PyGSTi contains built-in support for bases consisting of the tensor product of Pauli matrices (or just the Pauli matrices in the case of 1 qubit), named "pp"
, as well as the Gell-Mann matrices, named "gm"
. It also contains a special "qutrit" basis, named "qt"
, for the case of 3-level quantum systems. In cases when there are an integral number of qubits, and the dimension equals $2^N$, the "pp"
basis is usually preferred since it is more intuitive. In other cases, where the Hilbert space includes non-qubit (e.g. environmental) degrees of freedom, the Gell-Mann basis may be useful since it can be used in any dimension. Note that both the Gell-Mann and Pauli-Product bases reduce to the usual Pauli matrices plus identity in when the dimension equals 2 (1 qubit).
Here are some examples:
import pygsti
from pygsti import Basis
pp = Basis('pp', 2) # Pauli-product (in this dim=2 case, just the Paulis)
std = Basis('std', 2) # "standard" basis of matrix units
gm = Basis('gm', 2) # Gell-Mann
qt = Basis('qt', 3) # qutrit - must be dim 3
bases = [pp, std, gm, qt]
Each of the pp
, std
, and gm
bases created will have $4$ $2x2$ matrices each. The qt
basis has $9$ $3x3$ matrices instead:
for basis in bases:
print('\n{} basis (dim {}):'.format(basis.name, basis.dim.dmDim))
print('{} elements:'.format(len(basis)))
for element in basis:
pygsti.tools.print_mx(element)
pp basis (dim 2): 4 elements: 0.7071 0 0 0.7071 0 0.7071 0.7071 0 0 +0j 0 -0.7071j 0 +0.7071j 0 +0j 0.7071 0 0 -0.7071 std basis (dim 2): 4 elements: 1.0000 0 0 0 0 1.0000 0 0 0 0 1.0000 0 0 0 0 1.0000 gm basis (dim 2): 4 elements: 0.7071 0 0 0.7071 0 0.7071 0.7071 0 0 +0j 0 -0.7071j 0 +0.7071j 0 +0j 0.7071 0 0 -0.7071 qt basis (dim 3): 9 elements: 0.5774 0 0 0 0.5774 0 0 0 0.5774 -0.4082 0 0 0 0.8165 0 0 0 -0.4082 0 0 0.7071 0 0 0 0.7071 0 0 0 +0j 0 -0.5000j 0 +0j 0 +0.5000j 0 +0j 0 +0.5000j 0 +0j 0 -0.5000j 0 +0j 0 0.5000 0 0.5000 0 0.5000 0 0.5000 0 0 +0j 0 -0.5000j 0 +0j 0 +0.5000j 0 +0j 0 -0.5000j 0 +0j 0 +0.5000j 0 +0j 0.7071 0 0 0 0 0 0 0 -0.7071 0 +0j 0 +0j 0 -0.7071j 0 +0j 0 +0j 0 +0j 0 +0.7071j 0 +0j 0 +0j 0 0.5000 0 0.5000 0 -0.5000 0 -0.5000 0
However, custom bases can be easily created by supplying matrices:
import numpy as np
std2x2Matrices = [
np.array([[1, 0],
[0, 0]]),
np.array([[0, 1],
[0, 0]]),
np.array([[0, 0],
[1, 0]]),
np.array([[0, 0],
[0, 1]])]
alt_standard = Basis(matrices=std2x2Matrices,
name='std',
longname='Standard')
print(alt_standard)
Standard Basis : (no labels computed yet)
More complex bases can be created by chaining other bases together along the diagonal. For example, a composition of the $2x2$ std
basis with the $1x1$ std
basis leads to a basis with state vectors of length $5$, or $5x5$ matrices:
comp = Basis('std', [2, 1])
comp = Basis([('std', 2), ('std', 1)])
comp = Basis([std, ('std', 1)])
comp = Basis([Basis('std', 2), Basis('std', 1)])
print(comp)
for element in comp:
print(element)
Matrix-unit Basis : (no labels computed yet) [[1.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j]] [[0.+0.j 1.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j]] [[0.+0.j 0.+0.j 0.+0.j] [1.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j]] [[0.+0.j 0.+0.j 0.+0.j] [0.+0.j 1.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j]] [[0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 1.+0.j]]
Once created, bases are used to manipulate matrices and vectors within pygsti:
from pygsti.tools import change_basis, flexible_change_basis
mx = np.array([[1, 0, 0, 1],
[0, 1, 2, 0],
[0, 2, 1, 0],
[1, 0, 0, 1]])
change_basis(mx, 'std', 'gm') # shortname lookup
change_basis(mx, std, gm) # object only
change_basis(mx, std, 'gm') # combination
array([[ 2.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 0.00000000e+00, 3.00000000e+00, 0.00000000e+00, 0.00000000e+00], [ 0.00000000e+00, 0.00000000e+00, -1.00000000e+00, 0.00000000e+00], [ 2.02930727e-17, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00]])
Composite bases can be converted between expanded and contracted forms:
mxInStdBasis = np.array([[1,0,0,2],
[0,0,0,0],
[0,0,0,0],
[3,0,0,4]],'d')
begin = Basis('std', 2)
end = Basis('std', [1,1])
mxInReducedBasis = flexible_change_basis(mxInStdBasis, begin, end)
print(mxInReducedBasis)
original = flexible_change_basis(mxInReducedBasis, end, begin)
[[1.+0.j 2.+0.j] [3.+0.j 4.+0.j]]