Compiling for Quantinuum Hardware without Querying Quantinuum API¶
This notebook contains an example of how to investigate circuits compiled for Quantinuum hardware without logging in or submitting to Quantinuum hardware. This may be useful if it is desired to explore circuit compilation in depth before submitting.
Circuit Preparation ¶
Create your circuit.
from pytket.circuit import Circuit, OpType
from pytket.circuit.display import render_circuit_jupyter
circuit = Circuit(2, name="Bell Test")
circuit.H(0).CX(0, 1).measure_all()
[H q[0]; CX q[0], q[1]; Measure q[0] --> c[0]; Measure q[1] --> c[1]; ]
render_circuit_jupyter(circuit)
Set up Backend¶
Set up a QuantinuumBackend
object. The difference is the machine_debug
option uses the default pytket-quantinuum
options such as pytket's version of the Quantinuum native gate set rather than querying the Quantinuum API for this information.
from pytket.extensions.quantinuum import QuantinuumBackend
machine = "H1-1E"
backend = QuantinuumBackend(device_name=machine, machine_debug=True)
Investigate Native Gate Set¶
Users can view the hard-coded native gate set for the Quantinuum backend using the following command.
import pytket.extensions.quantinuum.backends.quantinuum as qtm
print(qtm._GATE_SET)
{<OpType.PhasedX: 66>, <OpType.Rz: 36>, <OpType.ZZMax: 68>, <OpType.ClassicalExpBox: 100>, <OpType.Barrier: 8>, <OpType.WASM: 14>, <OpType.SetBits: 15>, <OpType.CopyBits: 16>, <OpType.RangePredicate: 17>, <OpType.ExplicitPredicate: 18>, <OpType.ExplicitModifier: 19>, <OpType.MultiBit: 20>, <OpType.Measure: 61>, <OpType.Reset: 63>}
It's possible that the hardcoded verion is not up to date with the latest native gate set as described in the System Model H1 Product Data Sheet. In this case, the Rzz gate, which is the ZZPhase
gate in pytket, is missing. This can be added by running the following command.
qtm._GATE_SET.add(OpType.ZZPhase)
Circuit Compilation ¶
To obtain more reliable results we aim to reduce the noise and probability of error.
- Find an alternative Circuit that is observationally equivalent in a perfect noiseless setting but uses fewer resources (i.e. gates, time).
- The simplest optimizations will take an inefficient pattern, find all matches in the given
Circuit
and replace them by the efficient alternative. - i.e.
RemoveRedundancies
pass, which looks for a number of easy-to-spot redundant gates, such as zero-parameter rotation gates, gate-inverse pairs, adjacent rotation gates in the same basis, and diagonal rotation gates followed by measurements.
TKET's default pass manager for Backends¶
TKET has a default pass manager for each backend that is called by:
get_compiled_circuit(circuit,optimisation_level)
There are three compilation levels:
- $ \textsf{Level 0} $ just solves the device constraints without optimizing.
- $ \textsf{Level 1} $ additionally performs some light optimizations.
- $ \textsf{Level 2} $ adds more intensive optimizations that can increase compilation time for large circuits. (This level is the default level)
The specific list of TKET passes for these optimization levels for each backend can be found online
- Quantinuum H-series devices: https://cqcl.github.io/pytket-quantinuum/api/#default-compilation
- IBMQ devices: https://cqcl.github.io/pytket-quantinuum/api/#default-compilation
Circuits can now be compiled with the get_compiled_circuit
function without querying the Quantinuum API.
compiled_circuit = backend.get_compiled_circuit(circuit, optimisation_level=2)
render_circuit_jupyter(compiled_circuit)
print("Total number of gates =", compiled_circuit.n_gates)
print("Number of two qubit gates =", compiled_circuit.n_2qb_gates())
Total number of gates = 6 Number of two qubit gates = 1
compiled_circuit.n_gates
6
Generate your own compiler pass¶
Rebase¶
Substitute each gate in a Circuit with an equivalent sequence of gates in the target gateset according to some known gate decompositions.
For example, in pytket
, we define a rebase to the IBM gateset with auto_rebase_pass
.
from pytket.passes import auto_rebase_pass
ibm_rebase = auto_rebase_pass({OpType.X,OpType.SX,OpType.Rz,OpType.CX})
ibm_rebase.apply(circuit);
render_circuit_jupyter(circuit)
Let's generate a random circuit to see how the general purpose FullPeepholeOptimise
pass works. A random quantum circuit is a sequence of random quantum gates. We typically want our random circuit to consist of single-qubit and two-qubit gates that approximate a unitary Haar-random operator, meaning it's distributed uniformly over the group of all unitary operators.
import random
from pytket import Circuit
from pytket.circuit import OpType
# Define gate set
gate_set = [OpType.X, OpType.Y, OpType.Z, OpType.H, OpType.S, OpType.T, OpType.V, OpType.CX]
# Number of qubits in the circuit
num_qubits = 5
# Length of the circuit
circuit_length = 20
def generate_random_circuit(num_qubits, circuit_length, gate_set):
circuit = Circuit(num_qubits)
for _ in range(circuit_length):
# Select a random gate from the gate set
random_gate = random.choice(gate_set)
# Select a random qubit (or pair of qubits for CX gate)
if random_gate == OpType.CX:
qubits = random.sample(range(num_qubits), 2)
else:
qubits = [random.choice(range(num_qubits))]
# Add the gate to the circuit
circuit.add_gate(random_gate, qubits)
return circuit
# Generate a random circuit
random_circuit = generate_random_circuit(num_qubits, circuit_length, gate_set)
print(random_circuit.get_commands())
[V q[0];, T q[1];, X q[2];, X q[3];, V q[4];, Y q[0];, X q[1];, CX q[4], q[2];, T q[0];, H q[1];, X q[2];, Y q[4];, CX q[0], q[4];, CX q[1], q[3];, V q[2];, T q[0];, S q[2];, Y q[3];, CX q[3], q[4];, V q[4];]
print("Total number of gates =", random_circuit.n_gates)
print("Number of two qubit gates =", random_circuit.n_2qb_gates())
Total number of gates = 20 Number of two qubit gates = 3
from pytket.passes import auto_rebase_pass
ibm_rebase = auto_rebase_pass({OpType.X,OpType.SX,OpType.Rz,OpType.CX})
ibm_rebase.apply(random_circuit);
render_circuit_jupyter(random_circuit)
print("Total number of gates =", random_circuit.n_gates)
print("Number of two qubit gates =", random_circuit.n_2qb_gates())
Total number of gates = 26 Number of two qubit gates = 3
from pytket.passes import FullPeepholeOptimise
FullPeepholeOptimise().apply(random_circuit)
print("Total number of gates =", random_circuit.n_gates)
print("Number of two qubit gates =", random_circuit.n_2qb_gates())
Total number of gates = 9 Number of two qubit gates = 3
You can also combine TKET passes with the SequencePass
.
from pytket.passes import FullPeepholeOptimise, auto_rebase_pass
from pytket.passes import SequencePass
ibm_rebase = auto_rebase_pass({OpType.X,OpType.SX,OpType.Rz,OpType.CX})
# Generate a SequencePass
seq_pass = SequencePass([FullPeepholeOptimise(), ibm_rebase])
# Generate aother random circuit
random_circuit2 = generate_random_circuit(num_qubits, circuit_length, gate_set)
# Apply the sequence pass
seq_pass.apply(random_circuit2 )
print("Total number of gates =", random_circuit2.n_gates)
print("Number of two qubit gates =", random_circuit2.n_2qb_gates())
Total number of gates = 17 Number of two qubit gates = 4