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.

  1. 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\}\).

  2. Associativity: For any four Pauli elements, the product is associative \((\sigma_i * \sigma_j) * \sigma_k = \sigma_i * (\sigma_j * \sigma_k)\).

  3. Identity Element: The identity element \(I\) maps each Pauli element to itself. \(\sigma_i * I = \sigma_i\)

  4. 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

\[\sigma_i * \sigma_j = \delta_{ij} I + i \epsilon_{ijk} \sigma_k\]

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:

\[\phi_i\sigma_i * \phi_j\sigma_j = \phi_i\phi_j(\delta_{ij} I + i \epsilon_{ijk} \sigma_k).\]

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)