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)
Example block output

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)
Example block output

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)
Example block output
  • 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