import vecs2pauli as vtp
myvector = [1, 0, 0, 0, 0, 0, 0, -1j] # the |000> - i|111> state
print(vtp.get_stabilizers(myvector))
# prints ["+ZZI", "-XYX", "+IZZ"]
Indeed, one can compute by hand (or using NumPy) that each of the three matrices , and , or any product of the three, maps the vector to itself under matrix-vector multiplication.
In fact, these 3 matrices and their products (23=8 matrices in total) are all tensor product of Pauli matrices (Pauli strings) doing so.
That is, these 3 matrices generate the stabilizer group of the vector.
An alternative is to verify that this stabilizer group indeed has a single vector that is stabilized (ignoring any global scalar):
print(vtp.find_basis_for_stabilised_subspace(["+ZZI", "-XYX", "+IZZ"]))
# prints [array([0.70710678+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.-0.70710678j])]
# that is, [1, 0, 0, 0, 0, 0, 0, 0, -i] multiplied by 0.70710678 for normalization
So the code finds precisely the vector we inputted.
Finding if one vector can be constructed from another using single-qubit Pauli operations
Vecs2Pauli can also be used for finding all single-qubit Pauli operations between two vectors:
import vecs2pauli as vtp
source = [1, 0, 0, 1] # the |00> + |11> state
target = [0, -1j, -1j, 0] # the |01> + |10> state, multiplied by a global factor -i
print(vtp.get_local_pauli_transformations(source, target))
# prints ((1-0j), '-iIX', ['ZZ', 'XX'])
The printed result implies that all single-qubit Pauli transformations from the source vector to the target vector can be written as where g is a product of a subset of ; that is, g is one of or .
Example from quantum error correction: from the codespace's basis vectors to its stabilisers and back
The main functionality of Vecs2Pauli is to convert between vectors and local-Pauli transformations.
As example, we consider the 3-qubit repetition code: a scheme which encodes the state of a single qubit into three qubits.
The first task: given the logical basis vectors of the 3-qubit repetition code, find the code's stabiliser description.
import vecs2pauli as vtp
# the logical basis states |0>_L and |1>_L of the 3-qubit repetition code
logical_zero = [1, 0, 0, 0, 0, 0, 0, 0] # |000>
logical_one = [0, 0, 0, 0, 0, 0, 0, 1] # |111>
# finding the stabilizers of the code
stabilizer_group_zero = vtp.get_stabilizers(logical_zero) # ["+ZZZ", "+IZI", "+IIZ"]
stabilizer_group_one = vtp.get_stabilizers(logical_one) # ["-ZZZ", "+IZI", "-IIZ"]
print(vtp.intersect_stabilizer_groups(stabilizer_group_zero, stabilizer_group_one))
# prints ["+ZZI", "+IZZ"]
Conversely, given the stabiliser description of the code, find a set of (orthogonal) vectors which spans the codespace:
import vecs2pauli as vtp
stabilizer_group = ["+ZZI", "+IZZ"]
print(vtp.find_basis_for_stabilised_subspace(stabilizer_group))
# prints a list containing [1, 0, 0, 0, 0, 0, 0, 0] and [0, 0, 0, 0, 0, 0, 0, 1],
# i.e. |000> and |111>
Explanation of the quantum error-correction example above
In jargon, we say that the 3-qubit repetition code encodes a single logical qubit by using three physical qubits.
By doing so, it becomes possible to detect and correct for a specific type of error on the single qubit which would not have been possible when one uses a single qubit directly.
Hence, the 3-qubit repetition code enables quantum error correction.
In the 3-qubit repetition code, the single-qubit quantum state would be represented as *.
The states and are said to span the code space of the 3-qubit repetition code, because all states that are not of the form , are not encoded quantum states.
Hence, if such a state occurs during the run of a quantum algorithm on our encoded single qubit, we know an error must have occurred.
Describing an error correction code is the same as describing its code space.
However, describing the vectors which span the code space (either in vector notation or Diract notation) is no longer possible for codes which use many qubits, because writing down these vectors requires exponentially much space in general.
Instead, for a very large class of quantum-error correction schemes known as stabiliser codes one uses the stabiliser group: the group of local-Pauli transformations** which keep precisely all vectors in the code space, but no other vectors, unchanged.
The 3-qubit repetition code is also a stabiliser group, and and are stabilised by the group .
The stabiliser group is exponentially large in , where is the number of physical qubits and the number of logical qubits, but fortunately we can describe it using at most group generators.
For the 3-qubit code, any stabiliser can be written as any product of and (or doing nothing: ).
In the example above, Vecs2Pauli converts between the two representations of the 3-qubit repetition code: a vector basis of the code space (for example and ) and generators of the stabiliser group ( and ).
*Here we use Dirac notation (see the brief explanation here). In vector notation, we would write for the single-qubit state and for the three-qubit state.