Non-standard Gates and Measurements¶
In this tutorial, we will discuss the non-standard gate and measurement operations Workbench offers and using them in your code.
In addition to the standard quantum computing gates and measurements, such as $X$ and $Ry$ gates and computational basis measurements, Workbench offers several operations that are less ubiquitous in quantum computing literature. They are very useful in fault-tolerant quantum computing for expressing many quantum algorithms and compiling them in surface code-based architectures; however, they require a slightly more detailed introduction. This tutorial offers this introduction.
Gidney elbows¶
Gidney elbows are a pair of operations introduced in the paper Halving the cost of quantum addition ⧉ by Graig Gidney:
- The left Gidney elbow computes the logical AND of two controls, using an auxiliary qubit in the $|0\rangle$ state as the target.
- The right Gidney elbow uncomputes the left elbow, that is, returns the auxiliary qubit to the $|0\rangle$ state so that it can be released or reused in the later computation.
You can write both elbows as regular controlled X gates - the effect on the quantum state will be the same. However, using Gidney elbows expresses the additional context (the fact that the auxiliary qubit starts in the $|0\rangle$ state and should be returned to it after uncomputation). This allows us to compile them more efficiently, that is, using fewer resources: while the left elbow still uses $4$ T gates, the right elbow can be implemented without any T gates using a measure-and-fixup approach detailed in Halving the cost of quantum addition ⧉, figure 3.
The following example illustrates the use of Gidney elbows in Workbench code.
from psiqworkbench import QPU, Qubits
qpu = QPU(num_qubits=3)
reg1, reg2 = Qubits(2, "reg1", qpu=qpu), Qubits(1, "reg2", qpu=qpu)
reg1.had()
qpu.label("Compute AND")
reg2.lelbow(reg1)
qpu.print_state_vector()
qpu.label("Use the value")
reg2.box()
qpu.label("Uncompute")
reg2.relbow(reg1)
qpu.draw()
|reg1|reg2> |0|0> 0.500000+0.000000j |1|0> 0.500000+0.000000j |2|0> 0.500000+0.000000j |3|1> 0.500000+0.000000j
You will find Gidney elbows commonly used in Workbench Qubricks and filters. For examples, see Quantum Arithmetic and Configuring Program Execution.
Pauli product rotations¶
Pauli product rotations and Pauli product measurements (see the next section) comprise the Pauli-based computation framework introduced in A Game of Surface Codes ⧉.
The Pauli product rotation gate (PPR) applies the following operator on the qubits register:
$$\text{PPR}(\theta, \textrm{x}\_\text{mask}, \textrm{z}\_\text{mask}) = \exp^{-i \theta M} = \cos\theta \cdot I - i \sin\theta \cdot M,$$
where
$$ M = \left(\bigotimes_{j \in \text{x}\_\text{mask}} X_j \right) \left(\bigotimes_{k \in \text{z}\_\text{mask}} Z_k \right) $$
Single-qubit PPRs are equivalent to the regular rotation gates:
$$R_x(\theta) = \exp(-i \tfrac{\theta}{2} X) = \text{PPR}(\tfrac{\theta}{2}, 1, 0)$$ $$R_y(\theta) = \exp(-i \tfrac{\theta}{2} Y) = \text{PPR}(\tfrac{\theta}{2}, 1, 1)$$ $$R_z(\theta) = \exp(-i \tfrac{\theta}{2} Z) = \text{PPR}(\tfrac{\theta}{2}, 0, 1)$$
Same as rotation gates, PPR gates support different formats for rotation angle argument.
PPRs use mask-based indexing of qubits in the register to specify the operator $M$ and the qubits that it applies the different Pauli gates to:
- Each mask is an integer, each bit in its binary notation corresponding to one qubit. Like the rest of Workbench, the masks use little-endian notation: the least significant bit corresponds to qubit with index $0$.
- To mark all qubits in the register regardless of its size, you can use mask
~0. To mark no qubits, use mask0.
The following example shows how to apply various PPR gates to a subset of qubits in a register.
from math import pi
from psiqworkbench import QPU, Qubits, Units
qpu = QPU(num_qubits=3)
reg = Qubits(3, "reg", qpu=qpu)
# Single-qubit PPRs
reg[0].ppr(45, 0b1, 0b0)
reg[1].ppr(0.25 * pi * Units.rad, 0b1, 0b1)
reg[2].ppr((1, 4), 0b0, 0b1)
qpu.nop()
# Different ways to specify the same subset of qubits for a PPR gate
reg[:2].ppr(22.5, ~0, 0)
reg.ppr(0.125 * pi * Units.rad, 0b11, 0b00)
reg.ppr((1, 8), 3, 0)
qpu.nop()
# PPR gate with all different gates
reg.ppr(90, 0b011, 0b110)
qpu.nop()
# Excluding some qubits from the PPR gate using masks
reg.ppr(60, 0b100, 0b001)
qpu.draw()
Pauli product measurements¶
The Pauli product measurement (PPM) measures the observable associated with the multi-qubit operator defined as follows:
$$M = \text{sign} \left( \bigotimes_{j \in \text{x}\_\text{mask}} X_j \right) \left( \bigotimes_{k \in \text{z}\_\text{mask}} Z_k \right)$$
Similarly to Pauli product rotation gates, PPMs use mask-based indexing of qubits in the register to specify the mapping of qubits to Paulis. The sign argument can take values $+1$ or $-1$, allowing you to flip the measurement outcome to match your convention.
The following example shows how to apply PPMs to perform parity measurements, first in the Hadamard basis and then in the computational basis. In these cases, all qubits involved in the measurement have the same Paulis associated with them. In general, you can use the x_mask and z_mask arguments to mix and match the Paulis on different qubits.
from psiqworkbench import QPU, Qubits
qpu = QPU(num_qubits=3)
reg = Qubits(3, "reg", qpu=qpu)
# Prepare |+++⟩ state
reg.had()
# Measure parity of the register in |+⟩/|-⟩ basis: the result is always 0 (even parity)
print("Parity measurement in |+⟩/|-⟩ basis: result = "
f"{reg.ppm(1, 0b111, 0b000)}")
reg.print_state_vector()
qpu.nop()
# Measure parity of the first two qubits in |0⟩/|1⟩ basis
print("Parity measurement in |0⟩/|1⟩ basis result = "
f"{reg.ppm(1, 0b000, 0b011)}")
reg.print_state_vector()
qpu.nop()
# Measure parity of the first and the last qubits in |0⟩/|1⟩ basis
print("Parity measurement in |0⟩/|1⟩ basis result = "
f"{reg.ppm(1, 0b000, 0b101)}")
reg.print_state_vector()
qpu.nop()
qpu.draw()
Parity measurement in |+⟩/|-⟩ basis: result = 0 |reg> |0> 0.353553+0.000000j |1> 0.353553+0.000000j |2> 0.353553+0.000000j |3> 0.353553+0.000000j |4> 0.353553+0.000000j |5> 0.353553+0.000000j |6> 0.353553+0.000000j |7> 0.353553+0.000000j Parity measurement in |0⟩/|1⟩ basis result = 0 |reg> |0> 0.500000+0.000000j |3> 0.500000+0.000000j |4> 0.500000+0.000000j |7> 0.500000+0.000000j Parity measurement in |0⟩/|1⟩ basis result = 1 |reg> |3> 0.707107+0.000000j |4> 0.707107+0.000000j
Getting probabilities of certain PPM outcomes¶
Similarly to how you can peek at the outcome probabilities of a computational basis measurement using the peek_read_probability method (see Testing and Debugging), you can peek at the outcome probability of a Pauli product measurement using the peek_ppm_probability method. To do this, you need to provide the x_mask and z_mask arguments that you would pass to the ppm() method (the sign argument is assumed to be +1). The return value with give you the probability of the PPM returning result 0.
The following example illustrates this by initializing a Qubits register to the state $0.6|00\rangle + 0.8|10\rangle$ and printing the probability of measuring 0 with a parity measurement PPM. Since the basis states $|00\rangle$ and $|10\rangle$ have parity 0 and 1, respectively, the probability of the PPM returning 0 is the square of the amplitude of the basis state $|00\rangle$, that is, $0.6^2$.
Note that unlike
peek_read_probability, which takes the outcome as the input argument and returns the probability of that outcome,peek_ppm_probabilityalways returns the probability of outcome 0. Unlike computational measurements on multi-qubit registers, a PPM can only return two values, 0 and 1, so you can deduce the probability of it returning 1 as $(1 - \textrm{probability of returning 0})$.
from psiqworkbench import QPU, Qubits
qpu = QPU(num_qubits=2)
reg = Qubits(2, "reg", qpu=qpu)
reg.push_state([0.6, 0.8, 0., 0.])
print("Probability of measuring 0 in parity measurement in |0⟩/|1⟩ basis = "
f"{reg.peek_ppm_probability(0b00, 0b11)}")
Probability of measuring 0 in parity measurement in |0⟩/|1⟩ basis = 0.36
Next steps¶
You have learned about the advanced Workbench gates and applying them in your programs.
- To learn more about the applications of Gidney elbows, check out Halving the cost of Quantum Addition ⧉ by Graig Gidney.
- To learn more about the applications of Pauli product rotations and Pauli product measurements, check out A Game of Surface Codes ⧉ by Daniel Litinski.