How to create a circuit with multiple registers
Creating and running a circuit with multiple quantum and classical registers with Fire Opal
As algorithms become more complex and circuits grow wider, organizing your circuit in different units allows for more elegant design. Quantum circuits consist of classical and quantum registers, which represent blocks of bits and qubits respectively. Defining multiple registers is one way to write circuits that are easier to understand and visualize by organizing qubits and their measured values into groups based on their purposes.
Storing information in multiple quantum registers is particularly helpful when qubits have a distinct purpose, and some operations are only performed on a subset of qubits. Quantum Error Correction is a canonical example where physical qubits are used for two purposes: to encode logical state and to measure, store, and correct errors. In cases like this, the different types of qubits can be stored in multiple quantum registers.
Classical registers in the context of quantum circuits are used to store the measured values of qubits. Loading measurements into multiple classical registers can help with post-processing.
It's worth noting that since registers are just a means of organizing your circuits, there isn't an inherent benefit of using multiple registers with Fire Opal. However, Fire Opal will serve to optimize the complex circuits that you create with numerous quantum and classical registers when running on real devices.
In this guide, you will learn how to:
- Define a circuit with multiple quantum registers.
- Add multiple classical registers.
- Run the circuit via Fire Opal.
1. Set up your environment
First, import the necessary packages.
In this example, Qiskit is used to create the circuit, but you can use any desired programming framework and then export it to QASM to input to fireopal.execute
or fo.execute
using the import alias below.
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
import fireopal as fo
2. Define the quantum registers
Using the QuantumRegister
class in Qiskit, you can define and label a quantum register, which is a set of qubits. In this example, one quantum register is labeled as "code qubit", "ancilla qubit". These are the fundamental qubit types in Quantum Error Correction repetition codes.
cq = QuantumRegister(2, name="code_qubit")
aq = QuantumRegister(1, name="ancilla_qubit")
3. Define the classical register
The classical register, which will be labeled "syndrome bit", is used to store the measured state of the ancilla qubit.
sb = ClassicalRegister(1, "syndrome_bit")
4. Create the circuit and add gates
Next, a circuit can be initialized from the predefined registers. Two CX gates will be added and then the state of the ancilla qubit will be measured and stored in the classical register, called "syndrome_bit".
circ = QuantumCircuit(cq, aq, sb)
circ.cx(cq[0], aq[0])
circ.cx(cq[1], aq[0])
circ.measure(aq, sb)
print(circ)
code_qubit_0: ──■──────────
│
code_qubit_1: ──┼────■─────
┌─┴─┐┌─┴─┐┌─┐
ancilla_qubit: ┤ X ├┤ X ├┤M├
└───┘└───┘└╥┘
syndrome_bit: 1/═══════════╩═
0
5. Run the circuit using Fire Opal
Finally, the circuit will be executed on a real quantum processor with Fire Opal. The following code block first defines IBM credentials required to run on the chosen backend.
# These are the properties for the publicly available provider for IBM backends.
# If you have access to a private provider and wish to use it, replace these values.
hub = "ibm-q"
group = "open"
project = "main"
token = "YOUR_IBM_TOKEN"
credentials = fo.credentials.make_credentials_for_ibmq(
token=token, hub=hub, group=group, project=project
)
Choose a backend to run the circuit. Run fo.show_supported_devices(credentials)
to get a list of suppported backend names.
# Replace "your_desired_backend" with the name of the device you wish to use.
backend_name = "your_desired_backend"
shot_count = 1024
fire_opal_job = fo.execute(
circuits=[circ.qasm()],
shot_count=shot_count,
credentials=credentials,
backend_name=backend_name,
)
fire_opal_results = fire_opal_job.result()["results"][0]
fire_opal_counts = {
bitstring: int(probability * shot_count)
for bitstring, probability in fire_opal_results.items()
}
print(fire_opal_counts)
{'0': 1018, '1': 6}
Since the code qubits started in the initial state $|00\rangle$, the expected result should be '0' for all shots. When running on real hardware, there is always bound to be a bit of noise, but Fire Opal suppressed most of the hardware errors to largely get the correct result. In the following code block, the expected answer is validated by running the circuit on a simulator.
from qiskit import execute, Aer
simulator_counts = (
execute(circ, Aer.get_backend("qasm_simulator")).result().get_counts()
)
print(simulator_counts)
{'0': 1024}
6. Run the circuit without Fire Opal
Lastly, run the circuit on a real device without Fire Opal for comparison.
from qiskit_ibm_provider import IBMProvider
IBMProvider.save_account(
token, overwrite=True, instance=hub + "/" + group + "/" + project
)
provider = IBMProvider()
backend = provider.get_backend(backend_name)
# Run the circuit on IBM without Fire Opal
ibm_counts = execute(circ, backend=backend, shots=shot_count).result().get_counts()
print(ibm_counts)
{'0': 978, '1': 46}
Given that the ideal expectation was that all 1024 shots should be '0', the distribution generated using Fire Opal was much more accurate (99.4%) than the distribution without Fire Opal (95.5%). This circuit is just one part of a complex QEC algorithm, and so this 4% additional error would compound greatly with multiple iterations. Quickly, running complex algorithms becomes infeasible without the help of Fire Opal's error suppression, and these complex algorithms are best designed with the use of multiple registers.
The package versions below were used to produce this notebook.
from fireopal import print_package_versions
print_package_versions()
| Package | Version |
| --------------------- | ------- |
| Python | 3.11.9 |
| networkx | 2.8.8 |
| numpy | 1.26.4 |
| sympy | 1.12.1 |
| fire-opal | 8.0.0 |
| qctrl-workflow-client | 5.0.1 |