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 also becomes relevant in the context of mid-circuit measurement, since the intermediate values can be stored in a separate register.

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:

  1. Define a circuit with multiple quantum registers.
  2. Add multiple classical registers.
  3. 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.

from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
import fireopal

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 = fireopal.credentials.make_credentials_for_ibmq(
    token=token, hub=hub, group=group, project=project
)

Choose a backend to run the circuit. Run fireopal.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 = fireopal.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.

Now that you've learned how to create a circuit with multiple registers, try our tutorial on Performing parity checks with Fire Opal. The tutorial leverages both mid-circuit measurements and the use of multiple quantum and classical 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.3  |
| networkx     | 2.8.8   |
| numpy        | 1.23.5  |
| qiskit-terra | 0.24.1  |
| sympy        | 1.12    |
| fire-opal    | 5.3.1   |

Was this useful?

cta background

New to Fire Opal?

Get access to everything you need to automate and optimize quantum hardware performance at scale.