Pauli Basis and Clifford group
In this section, we introduce the definition of Pauli strings and basic operations on them. We also introduce the Clifford group and how to simulate a Clifford circuit applied on Pauli strings.
Pauli Strings
A pauli string is a tensor product of Pauli operators acting on different qubits. PauliString
is a subtype of CompositeBlock
with a field ids
storing the Pauli operators. We can define pauli string with PauliString
or @P_str
string literal.
using TensorQEC, TensorQEC.Yao
PauliString(Pauli(1), Pauli(0), Pauli(3), Pauli(2)) # X_1Z_3Y_4
PauliString(4, (1, 2, 4)=>Pauli(1)) # X_1X_2X_4
P"XZZ"
XZZ
Note that the printed Pauli string is in big-endian order, i.e. the first Pauli operator is the leftmost one. We can check if two Pauli strings commute or anticommute with iscommute
and isanticommute
.
p1 = PauliString(5, (1,2)=>Pauli(1))
p2 = PauliString(5, (1,2)=>Pauli(3))
iscommute(p1, p2)
true
We can use Yao.mat
to get the matrix representation of a Pauli string.
mat(ComplexF64, P"XZ") # X_1Z_2
LuxurySparse.SDPermMatrixCSC{ComplexF64, Int64, Vector{ComplexF64}, Vector{Int64}}
(2, 1) = 1.0 + 0.0im
(1, 2) = 1.0 + 0.0im
(4, 3) = -1.0 + 0.0im
(3, 4) = -1.0 + 0.0im
Pauli Group Element
PauliGroupElement
is a Pauli string with a phase factor. It is a subtype of AbstractPauli
.
PauliGroupElement(1, P"XZ") # +i * XZ
+i * XZ
The product of two Pauli strings is a Pauli group element.
P"XZ" * P"YI" # +i * ZZ
+i * ZZ
iscommute
and isanticommute
also work for Pauli group element.
iscommute(P"XZ" * P"YI", PauliGroupElement(1, P"XZ"))
isanticommute(P"XZ" * P"YI",PauliGroupElement(1, P"XZ"))
true
We can also use Yao.mat
to get the matrix representation of a Pauli group element.
mat(ComplexF64, PauliGroupElement(1, P"XZ"))
LuxurySparse.SDPermMatrixCSC{ComplexF64, Int64, Vector{ComplexF64}, Vector{Int64}}
(2, 1) = 0.0 + 1.0im
(1, 2) = 0.0 + 1.0im
(4, 3) = -0.0 - 1.0im
(3, 4) = -0.0 - 1.0im
Linear Combination of Pauli strings
SumOfPaulis
is a linear combination of Pauli strings, i.e. $c_1 P_1 + c_2 P_2 + \cdots + c_n P_n$. It is a subtype of AbstractPauli
.
sp = SumOfPaulis([0.6=>P"IXY", 0.8=>P"ZZY"])
0.6 * IXY + 0.8 * ZZY
The sum of two Pauli strings or two Pauli group elements is a SumOfPaulis
.
P"IXY" + P"ZZY"
PauliGroupElement(1, P"XZ") + PauliGroupElement(2, P"YI")
-1 + 0im * YI + 0 + 1im * XZ
We can also use Yao.mat
to get the matrix representation of a SumOfPaulis
.
mat(ComplexF64, sp)
8×8 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 16 stored entries:
⋅ ⋅ ⋅ … ⋅ 0.0-0.6im ⋅
⋅ ⋅ ⋅ 0.0+0.8im ⋅ 0.0-0.6im
⋅ ⋅ ⋅ ⋅ 0.0+0.8im ⋅
⋅ ⋅ ⋅ 0.0-0.6im ⋅ 0.0-0.8im
0.0+0.8im ⋅ 0.0+0.6im ⋅ ⋅ ⋅
⋅ 0.0-0.8im ⋅ … ⋅ ⋅ ⋅
0.0+0.6im ⋅ 0.0-0.8im ⋅ ⋅ ⋅
⋅ 0.0+0.6im ⋅ ⋅ ⋅ ⋅
Pauli Basis
pauli_basis
generates all the Pauli strings of a given length. Those Pauli strings are stored in a high-dimensional array.
pauli_basis(2)
16-element Vector{PauliString{2}}:
II
XI
YI
ZI
IX
XX
YX
ZX
IY
XY
YY
ZY
IZ
XZ
YZ
ZZ
pauli_decomposition
returns the coefficients of a matrix in the Pauli basis. The returned coefficients are stored in a SumOfPaulis
.
pauli_decomposition(ConstGate.CNOT)
0.5 + 0.0im * II + 0.5 + 0.0im * ZI + 0.5 + 0.0im * IX + -0.5 + 0.0im * ZX
That implies that $\mathrm{CNOT} = \frac{1}{2} (I \otimes I + I \otimes X + Z \otimes I - Z \otimes X)$. We can check this by
# Note: `Yao.kron` has an inversed order of the arguments compared to `LinearAlgebra.kron`.
0.5*(mat(kron(I2,I2) + kron(I2,X) + kron(Z,I2) - kron(Z,X))) ≈ mat(ConstGate.CNOT)
true
pauli_repr
returns the matrix representation of a quantum gate in the Pauli basis. For Hadamard gate H, we know that $HIH = I, HXH = Z, HYH = -Y, HZH = X$. We can convert $H$ into the Pauli basis.
pauli_repr(H)
4×4 Matrix{Float64}:
1.0 0.0 0.0 0.0
0.0 0.0 0.0 1.0
0.0 0.0 -1.0 0.0
0.0 1.0 0.0 0.0
Clifford Group
Clifford group is the set of all permutations of Pauli group, i.e. the pauli_repr
of its elements are "permutation matrices" with phases. It can be generated by Hadamard gate, S gate and CNOT gate[Bravyi2022]. We can use TensorQEC.clifford_group
to generate the Clifford group.
TensorQEC.clifford_group(1)
24-element Vector{CliffordGate{LuxurySparse.PermMatrixCSC{Complex{Int64}, Int64, StaticArraysCore.SVector{4, Complex{Int64}}, StaticArraysCore.SVector{4, Int64}}}}:
CliffordGate(nqubits = 1)
I → I
X → Z
Y → -Y
Z → X
CliffordGate(nqubits = 1)
I → I
X → Y
Y → -X
Z → Z
CliffordGate(nqubits = 1)
I → I
X → X
Y → Y
Z → Z
CliffordGate(nqubits = 1)
I → I
X → -Y
Y → -Z
Z → X
CliffordGate(nqubits = 1)
I → I
X → Z
Y → X
Z → Y
CliffordGate(nqubits = 1)
I → I
X → -X
Y → -Y
Z → Z
CliffordGate(nqubits = 1)
I → I
X → X
Y → -Z
Z → Y
CliffordGate(nqubits = 1)
I → I
X → X
Y → Z
Z → -Y
CliffordGate(nqubits = 1)
I → I
X → -Z
Y → Y
Z → X
CliffordGate(nqubits = 1)
I → I
X → Z
Y → -X
Z → -Y
⋮
CliffordGate(nqubits = 1)
I → I
X → X
Y → -Y
Z → -Z
CliffordGate(nqubits = 1)
I → I
X → -Y
Y → -X
Z → -Z
CliffordGate(nqubits = 1)
I → I
X → -X
Y → -Z
Z → -Y
CliffordGate(nqubits = 1)
I → I
X → -X
Y → Z
Z → Y
CliffordGate(nqubits = 1)
I → I
X → -Z
Y → -Y
Z → -X
CliffordGate(nqubits = 1)
I → I
X → Y
Y → X
Z → -Z
CliffordGate(nqubits = 1)
I → I
X → -Z
Y → X
Z → -Y
CliffordGate(nqubits = 1)
I → I
X → -X
Y → Y
Z → -Z
CliffordGate(nqubits = 1)
I → I
X → -Y
Y → Z
Z → -X
Each element in the Clifford group acts on pauli basis as a permutation matrix. For $n= 1, 2$, and $3$, this group contains $24$, $11520$, and $92897280$ elements, respectively. We can use CliffordGate
to convert a Yao gate into a Clifford gate, which is characterized by a permutation (with phases) of Pauli basis.
pm = CliffordGate(H)
CliffordGate(nqubits = 1)
I → I
X → Z
Y → -Y
Z → X
With the permutation matrix representation, we can efficienlly simulate a Clifford circuit. We first show how to apply a Clifford gate to a Pauli string. Here we apply the Hadamard gate to the second qubit of Pauli string $I_1X_2$ and get $I_1Z_2$ with a phase $1$.
ps1 = P"IX" # same as: PauliString(Pauli(0), Pauli(1))
elem = pm(ps1, (2,))
+1 * IZ
Put those all together, we can apply a Clifford circuit to a Pauli string by clifford_simulate
.
qc = chain(put(5, 1 => H), control(5, 1, 2 => Z), control(5, 3, 4 => X), control(5, 5, 3 => X), put(5, 1 => X))
vizcircuit(qc)
Apply the circuit to Pauli string $Z_1Y_2I_3Y_4X_5$, we get $Y_1X_2Y_3Y_4Y_5$ with a phase $1$.
ps = P"ZYIYX"
res = clifford_simulate(ps, qc)
ps2 = res.output
-1 * YXYYY
where res.output
is the Pauli string after the Clifford circuit and res.phase
is the phase factor. It corresponds to the following quantum circuit.
clifford_simulation_circuit = chain(qc', yaoblock(ps), qc)
CircuitStyles.barrier_for_chain[] = true # setup barrier for better visualization
vizcircuit(clifford_simulation_circuit)
Here, we use yaoblock
to convert the Pauli string to a Yao block.
We can check the result by
CircuitStyles.barrier_for_chain[] = false # disable barrier
mat(clifford_simulation_circuit) ≈ mat(yaoblock(ps2))
true
We can also visualize the history of Pauli strings by TensorQEC.annotate_history
.
TensorQEC.annotate_history(res)
- Bravyi2022Bravyi, S., Latone, J.A., Maslov, D., 2022. 6-qubit optimal Clifford circuits. npj Quantum Inf 8, 1–12. https://doi.org/10.1038/s41534-022-00583-7