Note

Click here to download the full example code

# Advanced Qudit Framework

This tutorial outlines the mathematical framework needed to do computations with qudits and outlines how users can use this framework within True-Q™.

## The Weyl-Heisenberg Group

The Weyl-Heisenberg group is a generalization of the Pauli group to \(d\)-dimensional systems and has operators analogous to the Pauli \(X\) and \(Z\) operators, which are referred to as “shift” (\(X\)) and “clock” (\(Z\)) operators. In cases where the dimension is unclear, we will denote the \(d\)-dimensional shift (clock) matrix as \(X_d\) (\(Z_d\)).

These operators are defined to be,

where \(\oplus\) denotes addition modulo \(d\). When \(d=2\), these operators are the familiar Pauli \(X\) and \(Z\) operators. Their actions,

where \(a\in\mathbb{Z}_d\) and operations act according to modular
arithmetic, for example for 3-dimensional qudits (aka *qutrits*),
\(X\ket{2}=\ket{0}\).

Often, we specify elements of the Weyl-Heisenberg group (Weyl operators) by the powers of the clock and shift operators acting on a qudit along with an integer \(k\) which specifies the phase. For a single-qudit system, a Weyl operator can be written as \(\omega(k)X^xZ^z\), where

Then, the powers and phase which specify that Weyl operator are \((x, z)\in \mathbb{Z}_d^2\) and \(k\) respectively.

##
**Advanced note**: Expectation values of Weyl operators

For \(d>2\), the Weyl operators are unitary but not Hermitian. This means that they can still be used to describe the evolution of quantum systems. However, the expectation of a Weyl operator \(W\) for a system in the state \(\rho\), that is, \(\mathrm{Tr}(W^\dagger \rho)\), may be complex-valued, i.e. takes the form \(r*\textrm{exp}(i\phi)\). When \(\rho\) is expected to be an eigenstate of \(W\) (e.g. \(|0\rangle\) for \(W = Z\)), then any other eigenstates are error states. The phase, \(\phi'\), of a measured expectation value, \(r'*\textrm{exp}[i(\phi+\phi')]\), relative to the ideal phase, \(r*\textrm{exp}(i\phi)\), indicates that the error states were not observed with equal frequencies.

As the Weyl operators form a trace-orthogonal operator basis with respect to the Hilbert-Schmidt inner product, any state \(\rho\) can be expanded as \(\sum_W \frac{p_W(\rho)}{d^n} W\) where \(p_W(\rho) = \mathrm{Tr}(W^\dagger \rho)\) and \(n\) is the number of qudits. The expectation value \(\mathrm{Tr}(W^\dagger \rho)\) is thus the \(W\) component of \(\rho\).

## Defining Weyl Operators in True-Q™

A \(n\)-qudit Weyl operator can be expressed as an element
\(w\in\mathbb{Z}_d^{2n}\) and an integer specifying the phase, where the first
(last) \(n\) entries of \(w\) are the powers of the shift (clock) operators
acting on each of the first (last) \(n\) qudits. For example, the 3-qudit Weyl
operator \(X\otimes X^2\otimes Z\) can be specified by \(1,2,0,0,0,1\) with
\(k=0\). To initialize a Weyl operator from a string in True-Q™, we specify the
action of the operator on each individual qudit sequentially, separated by a `W`

.
For example, the operator \(X\otimes X^2\otimes Z\) given above would be specified
by `W10W20W01`

as

```
import numpy as np
import trueq.math as tqm
# create a three-qutrit Weyls object which stores the example given above
weyl = tqm.Weyls("W10W20W01", 3)
# we can access the powers as follows:
print(weyl.powers)
```

```
[[1 2 0 0 0 1]]
```

The second argument in the constructor specifies the dimension of the qudits. If the
global dimension has been specified by `tq.settings.set_dim(3)`

, these methods will
use the specified dimension automatically. To instantiate a
`Weyls`

object with multiple Weyl operators, we separate
the operators by a `_`

character. In this notation, all operators must act on the
same number of qudits. For example, for the pair \(\{Z\otimes XZ^2,
X^2\otimes XZ\}\), we can run

```
tqm.Weyls("W01W12_W20W11", 3)
```

```
Weyls('W01W12_W20W11', dim=3)
```

Note

In the example above, we stored Weyl operators as
`Weyls`

objects. True-Q™ has several classes for
storing Weyl operators which are used in different contexts. The
`Weyls`

class does not store phases, and is used, for
example, to specify targeted errors for error diagnostic protocols such as
Cycle Benchmarking (CB). Some of True-Q™'s classes for Weyl
operators account for phases and some do not. If you are unsure which is relevant
for your use-case, consult the API references.

## The Clifford Group on Qudits

The \(n\)-qudit Clifford group is defined to be the group of unitary operators that map each \(n\)-qudit Weyl operator to a phase multiple of an \(n\)-qudit Weyl operator under conjugation. This coincides with the definition of the \(n\)-qubit Clifford group with respect to the Pauli group. We can specify elements of the \(d\)-dimensional Clifford group by their unique action on a basis of the Weyl-Heisenberg group. True-Q™ follows the convention of specifying the mapping of the Weyl basis described by the rows of an identity matrix, that is the set \(\{X_1, X_2,...,X_n, Z_1,Z_2,...,Z_n\}\).

A `Clifford`

object then stores the outcome of applying the
corresponding Clifford operator to each of those basis elements. For example, a
generalized Hadamard (or Fourier) gate on 2 qudits maps the basis elements
\(XI\rightarrow ZI\), \(IX\rightarrow IZ\), \(ZI\rightarrow X^{d-1}I\),
and \(IZ\rightarrow IX^{d-1}\), and would therefore be stored as the ordered list
\(\{ZI,\: IZ,\: X^{d-1}I,\: IX^{d-1}\}\). We can instantiate a
`Clifford`

object which stores a Hadamard as

```
# instantiate a 2-qudit Hadamard gate with dimension 3
hadamard = tqm.Clifford("W01W00_W00W01_W20W00_W00W20", [0, 0, 0, 0], 3)
```

The second argument provides the phases on the basis elements after the Clifford is
applied. True-Q™ has `Clifford`

constructors for
generalized versions of the Hadamard (`fourier()`

),
controlled-X (`cx()`

) and controlled-Z
(`cz()`

) gates.

```
built_in_h = tqm.Clifford.fourier(3)
```

We can retrieve the unitary matrix representation of a
`Clifford`

by calling
`mat()`

. Let’s use the matrices to check if these
constructions are equivalent:

```
np.isclose(hadamard.mat, np.kron(built_in_h.mat, built_in_h.mat))
```

```
array([[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True, True, True]])
```

Some users may desire to incorporate random Cliffords in their circuits. For
convenience, we provide the `random()`

function that
can construct a random Clifford as follows:

```
# construct a random Clifford on 2 qutrits
tqm.Clifford.random(2, 3)
```

```
Clifford('W21W12_W11W20_W11W00_W00W12', [1, 2, 2, 2], dim=3)
```

**Total running time of the script:** ( 0 minutes 0.003 seconds)