Pauli group¶
The famous 1-qubit Pauli matrices are defined as
$$ \sigma_0=I=\begin{bmatrix}1&0\\0&1\end{bmatrix},\quad\sigma_1=\sigma_x=X=\begin{bmatrix}0&1\\1&0\end{bmatrix},\quad \sigma_2=\sigma_y=Y=\begin{bmatrix}0&-i\\i&0\end{bmatrix},\quad \sigma_3=\sigma_z=Z=\begin{bmatrix}1&0\\0&-1\end{bmatrix} $$
import numpy as np
try:
import numqi
except ImportError:
%pip install numqi
import numqi
np_rng = np.random.default_rng()
Similarly, the $n$-qubit Pauli matrices are defined by tensor producting the 1-qubit Pauli matrices. Under the matrix multiplication, these matrices form a group, which is called Pauli group $P_n$.
$$ P_{n}=\lbrace e^{ij_{0}\pi/2}\sigma_{j_{1}}\otimes\cdots\sigma_{j_{n}}:j_{k}\in\mathbb{F}_{4}\rbrace =\lbrace \pm1,\pm i\rbrace \otimes\lbrace I,X,Y,Z\rbrace ^{\otimes n} $$
Its canonical name is extra special group with $p=2$ wiki-link or the Heisenberg group over a finite field stackexchange-link. This group has $4^{n+1}$ elements.
Group center $Z(P_n)=\left\{I,-I,iI,-iI\right\}$. $P/Z(P_n)$ is an Abelian group, no phase factor
num_qubit = 2
pauli = numqi.random.rand_pauli(num_qubit)
print(f'pauli_str: {pauli.str_}')
print(f'pauli_sign: {pauli.sign}')
print(f'pauli_np:\n{pauli.full_matrix}')
pauli_str: IZ pauli_sign: (-1+0j) pauli_np: [[-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]]
group isomorphism¶
There exists a one-to-one mapping $P_{n}\cong \mathbb{F}_{2}^{2n+2}$ (group isomorphism)
$$ x \in P_{n},x=\left(-1\right)^{x_0}\left(i\right)^{x_{0}^{\prime}}\prod_{i=1}^{n}X_{i}^{x_{i}}Z_{i}^{x_{i+n}}\sim x_{\mathbb{F}}=\left(x_{0},x_{0}^{\prime},x_{1},x_{2},x_{3},\cdots,x_{2n}\right) $$
identity $I_n\sim (0,0,\cdots,0)\in\mathbb{F}^{2n+2}_2$
commutation relation $x,y\in P_{n}$
$$ xy=\left(-1\right)^{f\left(x_{\mathbb{F}},y_{\mathbb{F}}\right)}yx $$
$$ f\left(x_{\mathbb{F}},y_{\mathbb{F}}\right)=\sum_{i=1}^{n}x_{i}y_{i+n}+x_{i+n}y_{i}\simeq\langle x_{\mathbb{F}},y_{\mathbb{F}}\rangle $$
inverse $x\in P_{n},y=x^{-1}$
$$ y_{0}=x_{0}+x_{0}^{\prime}+\sum_{i=1}^{n}x_{i}x_{i+n},y_{0}^{\prime}=x_{0}^{\prime},y_{i}=x_{i} $$
multiplication $x,y\in P_n,z=xy$
$$ z_{0}=x_{0}+y_{0}+\left\lfloor \frac{x_{0}^{\prime}+y_{0}^{\prime}}{2}\right\rfloor +\sum_{i=1}^{n}x_{i}y_{i+n},z_{0}^{\prime}=x_{0}^{\prime}+y_{0}^{\prime},z_{i}=x_{i}+y_{i} $$
Example: $I=0000,X=0010,Y=0111,Z=0001$
for x in ['I', 'X', 'Y', 'Z']:
print(f'Pauli({x}):', numqi.gate.PauliOperator.from_str(x))
print('random 3-qubit Pauli:', numqi.random.rand_pauli(3))
Pauli(I): I [0,0,0,0] Pauli(X): X [0,0,1,0] Pauli(Y): Y [0,1,1,1] Pauli(Z): Z [0,0,0,1] random 3-qubit Pauli: ZYY [1,0,0,1,1,1,1,1]
Let's verify the matrix multiplication in $P_2$.
pauli0 = numqi.random.rand_pauli(2)
pauli1 = numqi.random.rand_pauli(2)
pauli2 = pauli0 @ pauli1
print(f'pauli0: {pauli0}\n{pauli0.full_matrix}\n')
print(f'pauli1: {pauli1}\n{pauli1.full_matrix}\n')
print(f'pauli2: {pauli2}\n{pauli2.full_matrix}\n')
tmp0 = pauli0.full_matrix @ pauli1.full_matrix
print(f'pauli0 @ pauli1:\n{tmp0}\n')
pauli0: YY [1,0,1,1,1,1] [[ 0.+0.j 0.+0.j 0.+0.j -1.+0.j] [ 0.+0.j 0.+0.j 1.+0.j 0.+0.j] [ 0.+0.j 1.+0.j 0.+0.j 0.+0.j] [-1.+0.j 0.+0.j 0.+0.j 0.+0.j]] pauli1: iXI [0,1,1,0,0,0] [[0.+0.j 0.+0.j 0.+1.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+1.j] [0.+1.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+1.j 0.+0.j 0.+0.j]] pauli2: ZY [0,1,0,1,1,1] [[ 0.+0.j 0.-1.j 0.+0.j 0.+0.j] [ 0.+1.j 0.+0.j 0.+0.j 0.+0.j] [ 0.+0.j 0.+0.j -0.+0.j 0.+1.j] [ 0.+0.j 0.+0.j 0.-1.j -0.+0.j]] pauli0 @ pauli1: [[0.+0.j 0.-1.j 0.+0.j 0.+0.j] [0.+1.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+1.j] [0.+0.j 0.+0.j 0.-1.j 0.+0.j]]
Such a group isomorphism is useful when number of qubits is large. For example, the matrix representation of a $n$-qubit Pauli group element is $2^{n}\times 2^{n}$, while the $\mathbb{F}_2$ representation is only $2n+2$. So we can handle large number of qubits with the $\mathbb{F}_2$ representation. Such advantage will be more obvious when we consider the Clifford circuit (see later tutorials).
num_qubit = 1000
pauli0 = numqi.random.rand_pauli(num_qubit)
pauli1 = numqi.random.rand_pauli(num_qubit)
pauli2 = pauli0 @ pauli1
print(pauli2.str_)
YZYIYYIYZIYYIIYYYYIXXXZIIXYXXXZZXIYYZYIZXYYIYYIXYXYXIZXYYYYIYZYXXZIIYZIXZIZZIZIIIIIXYIXYYYXXYYYXXXZXZXYYYXIYZXIXZYYIXZIZXXYZIYIXYYZIZYIXYYXIXIIXZYYIXIXYIXYXZXXYXXYXXZXYZZXXZYXXZXIZXYYZIIXYYXXZIIXXYXZIYXYZIYIXZYYYIZXXXZZYIYXZZIYYXZIYXYIZIXYIZXZIZYYIYZXXIXZZIYYIYZXZXYXXXIXIXXZYXZYIYIXIXXZIZIXZXZZIIZIXYZYYZYIZIIYYIYIIYXXXXIIIZIYYZXZIZIXXZYIIYZIXZZXXIZYYYXIZYIXIXYZXZZZIXZXZXXIIIXXIZYYZXXIZXXYIZIIZZIZIZYIYXYIIXYIIZIZXXYZZIZIZIYXZYIIXZXIIYXYZIXIYXIYZYYYIIYXIZXXYIZYZIIIXIXYZZIZYYYXYYXYZYIYIZYZXZYXXIZIYYIIXZXZXIIIXZIYYIIXXIZZXYXXIZYXXXXYYYXXZIIXZIIZZXZIXXYYXXIXYYZZXZIIYXZIZIXIZYZZXXZYXZYZZIYXZIZZZIXZIZYZZIIXYYZXZZYIZYXXXYXZIZZIZIZZYIXYIYZZXIXIIXYIIXYIZIYZIYYZZZYYIXIXZZYXYIYYZXZYZXXYIZZIXYZIYXXZXIXIIYZYYXXXIIYIXYIXXIYZYYIYXXYIXIXZYZXXYYIIYZZXZYZZXYYYXIYXYZZIYIYYYXIYIIXIZZIXXZXZYZYZXYZIIXYXXIIZXZXXYZIYYIIYZIXYZYYXZXXIXIXXYIZIIXZYYYXXXXXYZYIIXZYZXXZXZYXIXYIYZZZYYIIZYIIXYYYXXZYYZZZXIYYZYYXZZXZIIZIIXIYYZXIXIXIIXYIYIXXYIZYZYYXYYYYXIXXIYXXXXZIIIYXIYIXXXZYZXXYZZYIIZIZXIZZZZIIZXZYZXIIIXIZXZIYXYXXZIZZXY