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"
.Numerous functions 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 gate matrices and SPAM vectors stored in a GateSet
.
The most straightforward way to create a Basis
object is to provide its short name and dimension:
from pygsti import Basis
pp = Basis('pp', 2)
std = Basis('std', 2)
gm = Basis('gm', 2)
qt = Basis('qt', 3) # qt 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('{} basis (dim {}):'.format(basis.name, basis.dim.dmDim))
print('{} elements:'.format(len(basis)))
for element in basis:
print(element)
pp basis (dim 2): 4 elements: [[0.70710678+0.j 0. +0.j] [0. +0.j 0.70710678+0.j]] [[0. +0.j 0.70710678+0.j] [0.70710678+0.j 0. +0.j]] [[0.+0. j 0.-0.70710678j] [0.+0.70710678j 0.+0. j]] [[ 0.70710678+0.j 0. +0.j] [ 0. +0.j -0.70710678+0.j]] std basis (dim 2): 4 elements: [[1.+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] [1.+0.j 0.+0.j]] [[0.+0.j 0.+0.j] [0.+0.j 1.+0.j]] gm basis (dim 2): 4 elements: [[0.70710678+0.j 0. +0.j] [0. +0.j 0.70710678+0.j]] [[0. +0.j 0.70710678+0.j] [0.70710678+0.j 0. +0.j]] [[0.+0. j 0.-0.70710678j] [0.+0.70710678j 0.+0. j]] [[ 0.70710678+0.j 0. +0.j] [ 0. +0.j -0.70710678+0.j]] qt basis (dim 3): 9 elements: [[0.57735027+0.j 0. +0.j 0. +0.j] [0. +0.j 0.57735027+0.j 0. +0.j] [0. +0.j 0. +0.j 0.57735027+0.j]] [[-0.40824829+0.j 0. +0.j 0. +0.j] [ 0. +0.j 0.81649658+0.j 0. +0.j] [ 0. +0.j 0. +0.j -0.40824829+0.j]] [[0. +0.j 0. +0.j 0.70710678+0.j] [0. +0.j 0. +0.j 0. +0.j] [0.70710678+0.j 0. +0.j 0. +0.j]] [[0.+0. j 0.-0.5j 0.+0. j] [0.+0.5j 0.+0. j 0.+0.5j] [0.+0. j 0.-0.5j 0.+0. j]] [[0. +0.j 0.5+0.j 0. +0.j] [0.5+0.j 0. +0.j 0.5+0.j] [0. +0.j 0.5+0.j 0. +0.j]] [[0.+0. j 0.-0.5j 0.+0. j] [0.+0.5j 0.+0. j 0.-0.5j] [0.+0. j 0.+0.5j 0.+0. j]] [[ 0.70710678+0.j 0. +0.j 0. +0.j] [ 0. +0.j 0. +0.j 0. +0.j] [ 0. +0.j 0. +0.j -0.70710678+0.j]] [[0.+0. j 0.+0. j 0.-0.70710678j] [0.+0. j 0.+0. j 0.+0. j] [0.+0.70710678j 0.+0. j 0.+0. j]] [[ 0. +0.j 0.5+0.j 0. +0.j] [ 0.5+0.j 0. +0.j -0.5+0.j] [ 0. +0.j -0.5+0.j 0. +0.j]]
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 : (0,0), (0,1), (1,0), (1,1)
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)
std,std Basis : M(std,std)[0,0], M(std,std)[0,1], M(std,std)[0,2], M(std,std)[0,3], M(std,std)[1,0] [[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., 0., 0., 0.], [ 0., 3., 0., 0.], [ 0., 0., -1., 0.], [ 0., 0., 0., 0.]])
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]]