Welcome to pauli
’s documentation!¶
pauli
is a python package for simplifying and manipulating Pauli algebras. The code is closed from the public but available upon request by emailing me at wilso692 at purdue.edu. For an introduction to Pauli algebras, see Nielsen and Chuang
API¶
Usage¶
Installation¶
To use pauli
, first install it using pip
from the command line:
$ python -m venv .venv
$ source .venv/bin/activate
$ (.venv) python -m pip install git+https://github.com/btrainwilson/pauli.git
Or, unzip the package and install it using pip
from the command line:
$ python -m venv .venv
$ source .venv/bin/activate
$ (.venv) unzip pauli.zip
$ (.venv) cd pauli
$ (.venv) python -m pip install -e .
Then, you can use the package either as a command line tool through invoking the pauli
cli command:
$ (.venv) pauli
_____ _ _ _ _
| __ \ | (_) /\ | | | |
| |__) |_ _ _ _| |_ / \ | | __ _ ___| |__ _ __ __ _
| ___/ _` | | | | | | / /\ \ | |/ _` |/ _ \ '_ \| '__/ _` |
| | | (_| | |_| | | | / ____ \| | (_| | __/ |_) | | | (_| |
|_| \__,_|\__,_|_|_| /_/ \_\_|\__, |\___|_.__/|_| \__,_|
__/ |
|___/
----------------------------------------------------------------
Welcome to the Pauli Calculator! Type help or ? to list commands.
----------------------------------------------------------------
>>> 2 * XY + 4 * XY
6XY
with pipe supports
$ (.venv) echo "2 * XY + 4 * XY" | pauli
6XY
$ (.venv) echo "2 * XY + 4 * Y" | pauli > out.log
$ (.venv) cat out.log
2XY + 4Y
or, as the pauli
python package which can be imported into your python scripts.
Examples¶
All of the following examples assume that you have imported pauli
:
import pauli
Single-Qubit Pauli Group Elements¶
Pauli Group Properties¶
The Pauli group \(\sigma_x = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} , \sigma_y = \begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix}, \sigma_z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}, I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}\) exhibits algebraic properties which are useful in the analysis and implementation of quantum algorithms and error correction techniques.
Closure: The product of any two Pauli elements is also a Pauli element \(\sigma_i * \sigma_j \in \{\sigma_x, \sigma_y, \sigma_z, I\}\).
Associativity: For any four Pauli elements, the product is associative \((\sigma_i * \sigma_j) * \sigma_k = \sigma_i * (\sigma_j * \sigma_k)\).
Identity Element: The identity element \(I\) maps each Pauli element to itself. \(\sigma_i * I = \sigma_i\)
Inverse Element: Each element is its own inverse \(\sigma_i * \sigma_i = I\)
The pauli
package implements a PauliElement class which serves as the algebraic backbone.
Element Initialization¶
To initialize a PauliElement
, pass a valid PauliSymbol
enum to the constructor:
X = pauli.PauliElement(pauli.PauliSymbol.X)
Y = pauli.PauliElement(pauli.PauliSymbol.Y)
Z = pauli.PauliElement(pauli.PauliSymbol.Z)
I = pauli.PauliElement(pauli.PauliSymbol.I)
Element String Representation¶
The PauliElement
can be printed as a string using the str
method or the pauli_to_string
function.
assert pauli.pauli_to_string(X) == "X"
assert pauli.pauli_to_string(Y) == "Y"
assert str(Z) == "Z"
assert str(I) == "I"
Element Multiplication¶
Likewise, to multiply two elements, use the *
operator or the multiply_pauli
function. Pauli group multiplication is given by
where \(\epsilon_{ijk}\) is the Levi-Civita symbol which computes the permutation sign of {i,j,k}.
assert pauli.pauli_to_string(X * Y) == "iZ"
assert pauli.pauli_to_string(multiply_pauli(Z, Y)) == "-iX"
Phase Multiplication¶
We can introduce a phase factor \(\phi\) for each PauliElement
by multiplying the element by a complex number using either the *
operator or the multiply_phase
function,
assert pauli.pauli_to_string(multiply_phase(-1, X)) == "-X"
assert pauli.pauli_to_string(-1j * Z) == "-iZ"
Or, we specify phase \(\phi\) as a parameter of the PauliElement
constructor:
X = pauli.PauliElement(PauliSymbol.X, -1)
assert pauli.pauli_to_string(X) == "-X"
Including the phase factor means that the phases of the Pauli group elements are preserved under multiplication:
Which is implemented in the *
and multiply_pauli
function:
assert pauli.pauli_to_string(4 * X * 5 * Y) == "20iZ"
assert pauli.pauli_to_string(multiply_pauli(Z, Y)) == "-iX"
Pauli Algebra¶
While the standard Pauli group only includes a single qubit, a Pauli algebra is a vector space which uses all n
-tensor products of n
-qubit Pauli group elements as its basis. A Pauli algebra uses operators <+,*>
over the complex numbers and Pauli group tensors as a basis.
Let \(\sigma_i\) be a Pauli group element, and let \(\{\sigma_{i_0}, ..., \sigma_{i_n}\}\) be a set of n
Pauli group elements. Then, the Pauli algebra is the vector space spanned by the set of all \(\sigma_{i_j}\).
Variables and String Representation¶
We implement a Pauli algebra using the PauliAlgebra
class. To initialize a PauliAlgebra
vector, we pass a basis string composed of the characters I
, X
, Y
, and Z
to the constructor. Otherwise, a Value Exception is raised.
e0 = pauli.PauliAlgebra("YI")
e1 = pauli.PauliAlgebra("XI")
Each PauliAlgebra
expression can be printed as a string using the str
method or the pauli_algebra_to_string
function.
assert str(e0) == "YI"
assert pauli.pauli_algebra_to_string(e0) == "YI"
assert str(e1) == "XI"
assert pauli.pauli_algebra_to_string(e1) == "XI"
This will become more interesting as we introduce more operators.
Phase Multiplication¶
To introduce a \(\phi\) factor, multiply the basis vector by a complex number or use the multiply_pauli_algebra_by_scalar
function.
e2 = 10.3 * e0
assert str(e2) == "10.3YI"
e2 = 1j * e0
assert str(e2) == "iYI"
e2 = pauli.multiply_pauli_algebra_by_scalar(1j, e0)
assert str(e2) == "iYI"
Algebraic Multiplication¶
To multiply two vectors using the Pauli group multiplication rules, use the *
operator or the multiply_pauli_algebra
function.
e3 = e1 * e0
assert str(e3) == "iZI"
e3 = pauli.multiply_pauli_algebra(e1, e0)
assert str(e3) == "iZI"
Algebraic Addition¶
To add two terms, use the +
operator or the add_pauli_algebra
function.
e2 = e1 + e0
e3 = e1 * e0 + e1 * e0
e4 = pauli.add_pauli_algebra(1j * e1 * e0 , e1 * e0)
assert str(e2) == "XI + YI"
assert str(e3) == "2iZI"
assert str(e4) == "(-1 + i)ZI"
Algebraic Simplification¶
The simplify
method is redundant for our symbolic algebra, because the function is called automatically on __add__
and __mul__
. However, it can be used to simplify expressions in python.
e2 = e1 + e0
e2.simplify()
assert str(e2) == "XI + YI"
e3 = e1 * e0 + e1 * e0
assert str(pauli.simplify(e3)) == "2iZI"
Pauli Interpreter and Command Line Tool¶
Lastly, pauli
comes with a simple interpreter which uses the pauli
library as a backend for pauli algebra evaluation. You can use it to evaluate Pauli expressions through the pauli
package:
# The interpreter supports +, -, and * operators
test = "5 * X * X + 2 * Y * Y + 3 * Z * Z + 1 * I * I"
e = pauli.interpret(test)
assert str(e) == "11I"
# All multiplications must be explicit. I.e., 4(X + Y) is not supported but 4 * (X + Y) is.
test = "5 * (XY * 8 * YI) - 4 * (XY * 8 * YI)"
e = pauli.interpreter.interpret(test)
assert str(e) == "8iZY"
# Complex numbers are supported using the "j" notation
test = "(5 + 1.6j) * X"
e = pauli.interpret(test)
assert str(e) == "(5 + 1.6i)X"
Or, you can pipe it through the command line tool:
$ (.venv) echo "5 * (X + Y)" | pauli
5X + 5Y
$ (.venv) echo "5 * (XY * 8 * YI)" | pauli
40iZY
Invoking the command line tool with no arguments will start a custom interpreter:
$ (.venv) pauli
>>> 5 * (X + Y)
5X + 5Y
>>> 5 * (XY * 8 * YI)
40iZY
>>> exit
Random Hermitian Matrices¶
As a final example, pauli
can generate random Hermitian matrices using the generateHermitian
function. All Pauli matrices are Hermitian.
pauli.generateHermitian
generates a random Hermitian matrix with real and imaginary parts bounded by b
:
for n in range(2, 10):
for b in np.linspace(0.001, 10, 10):
m = pauli.generateHermitian(n, b)
assert np.array_equal(m, m.conj().T)
assert np.all(np.abs(np.real(m)) <= b)
assert np.all(np.abs(np.imag(m)) <= b)
Pauli Decompositions¶
The Pauli matrices form a vector space over all 2 x 2 matrices, so we can decompose an matrix into Pauli operators using pauli.pauliDecomp
and rebuild the original matrix using pauli.generateFromPauliDecomp
:
print("Test 2a: All Reals")
for i in range(100):
r = np.random.rand(4)
m = pauli.generateFromPauliDecomp(r)
r2 = pauli.decompToPauli(m)
assert np.allclose(r, r2)
print("Test 2b: Complex")
for i in range(100):
r = np.random.rand(4) + 1j * np.random.rand(4)
m = pauli.generateFromPauliDecomp(r)
r2 = pauli.decompToPauli(m)
assert np.allclose(r, r2)
print("Test 2c: Random Dropout")
for i in range(100):
r = np.random.rand(4) + 1j * np.random.rand(4)
r[np.random.randint(4)] = 0
m = pauli.generateFromPauliDecomp(r)
r2 = pauli.decompToPauli(m)
assert np.allclose(r, r2)