Skip to main contentIBM Quantum Documentation Mirror

Execute dynamic circuits

  • The code on this page was developed using the following requirements. We recommend using these versions or newer.

    qiskit[all]~=2.3.0
    qiskit-ibm-runtime~=0.43.1
    
Dynamic circuits now available on all backends

The new version of dynamic circuits is now available to all users on all backends. You can now run dynamic circuits at utility scale. See the announcement for more details.

Dynamic circuits are powerful tools with which you can measure qubits in the middle of a quantum circuit execution and then perform classical logic operations within the circuit, based on the outcome of those mid-circuit measurements. This process is also known as classical feedforward. While these are early days of understanding how best to take advantage of dynamic circuits, the quantum research community has already identified a number of use cases, such as the following:

These improvements brought by dynamic circuits, however, come with trade-offs. Mid-circuit measurements and classical operations typically have longer execution time than two-qubit gates, and this increase in time might negate the benefits of reduced circuit depth. Therefore, reducing the length of mid-circuit measurement is a focus area of improvement as IBM Quantum® releases the new version of dynamic circuits.

The OpenQASM 3 specification defines a number of control-flow structures, but Qiskit Runtime currently only supports the conditional if statement. In Qiskit SDK, this corresponds to the if_test method on QuantumCircuit. This method returns a context manager and is typically used in a with statement. This guide describes how to use this conditional statement.

Note

The code examples in this guide use the standard measure instruction for mid-circuit measurements. However, it is recommended that you use the MidCircuitMeasure instruction instead, if the backend supports it. See the Mid-circuit measurements section for details.


Find backends that support dynamic circuits

To find all backends that your account can access and support dynamic circuits, run code like the following. This example assumes that you have saved your login credentials. You could also explicitly specify credentials when initializing your Qiskit Runtime service account. This would let you view backends available on a specific instance or plan type, for example.

Notes
  • The backends that are available to the account depend on the instance specified in the credentials.
  • The new version of dynamic circuits is now available to all users on all backends. See the announcement for more details.
from qiskit_ibm_runtime import QiskitRuntimeService
 
service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)

Output:

[<IBMBackend('ibm_fez')>, <IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_boston')>, <IBMBackend('ibm_miami')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_torino')>, <IBMBackend('ibm_kingston')>]

Mid-circuit measurements

Prior to qiskit-ibm-runtime v0.43.0, measure was the only measurement instruction in Qiskit. Mid-circuit measurements, however, have different tuning requirements than terminal measurements (measurements that happen at the end of a circuit). For example, you need to consider the instruction duration when tuning a mid-circuit measurement because longer instructions cause noisier circuits. You don't need to consider instruction duration for terminal measurements because there are no instructions after terminal measurements.

Note

The MidCircuitMeasure instruction maps to the measure_2 instruction reported in the backend's supported_instructions. However, measure_2 is not supported on all backends. Use service.backends(filters=lambda b: "measure_2" in b.supported_instructions) to find backends that support it. New measurements might be added in the future, but this is not guarenteed.

MidCircuitMeasure method

In qiskit-ibm-runtime v0.43.0, the MidCircuitMeasure instruction was introduced. As the name suggests, it is a new measurement instruction that is optimized for mid-circuit on IBM® QPUs. While you can use QuantumCircuit.measure for a mid-circuit measurement, because of its design, MidCircuitMeasure is typically a better choice. For example, it adds less overhead to your circuit than when using QuantumCircuit.measure.

from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.circuit import MidCircuitMeasure
 
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
 
circ = QuantumCircuit(2, 2)
circ.x(0)
circ.append(MidCircuitMeasure(), [0], [0])
# circ.measure([0], [0])
# circ.measure_all()
print(circ.draw(cregbundle=False))
Important notes
  • There must be at least one classical register in order to use measurements.
  • The Sampler primitive requires circuit measurements. You can add circuit measurements with the Estimator primitive, but they are ignored.

Qiskit Runtime limitations

Be aware of the following constraints when running dynamic circuits in Qiskit Runtime.

  • Due to the limited physical memory on control electronics, there is also a limit on the number of if statements and size of their operands. This limit is a function of the number of broadcasts and the number of broadcasted bits in a job (not a circuit).

    When processing an if condition, measurement data needs to be transferred to the control logic to make that evaluation. A broadcast is a transfer of unique classical data, and broadcasted bits is the number of classical bits being transferred. Consider the following:

    c0 = ClassicalRegister(3)
    c1 = ClassicalRegister(5)
    ...
    with circuit.if_test((c0, 1)) ...
    with circuit.if_test((c0, 3)) ...
    with circuit.if_test((c1[2], 1)) ...

    In the previous code example, the first two if_test objects on c0 are considered one broadcast because the content of c0 did not change, and thus does not need to be re-broadcasted. The if_test on c1 is a second broadcast. The first one broadcasts all three bits in c0 and the second broadcasts just one bit, making a total of four broadcasted bits.

    Currently, if you broadcast 60 bits each time, then the job can have approximately 300 broadcasts. If you broadcast just one bit each time, however, then the job can have 2400 broadcasts.

  • The operand used in an if_test statement must be 32 or fewer bits. Thus, if you are comparing an entire ClassicalRegister, the size of that ClassicalRegister must be 32 or fewer bits. If you are comparing just a single bit from a ClassicalRegister, however, that ClassicalRegister can be of any size (since the operand is only one bit).

    For example, the "Not valid" code block does not work because cr is more than 32 bits. You can, however, use a classical register wider than 32 bits if you are testing only one bit, as shown in the "Valid" code block.

       cr = ClassicalRegister(50)
       qr = QuantumRegister(50)
       circuit = QuantumCircuit(qr, cr)
       ...
       circ.measure(qr, cr)
       with circ.if_test((cr, 15)):
          ...
  • Nested conditionals are not allowed. For example, the following code block will not work because it has an if_test inside another if_test:

       c1 = ClassicalRegister(1, "c1")
       c2 = ClassicalRegister(2, "c2")
       ...
       with circ.if_test((c1, 1)):
        with circ.if_test(c2, 1)):
         ...
  • Having reset or measurements inside conditionals is not supported.

  • Arithmetic operations are not supported.

  • See the OpenQASM 3 feature table to determine which OpenQASM 3 features are supported on Qiskit and Qiskit Runtime.

  • When OpenQASM 3 (instead of QuantumCircuit) is used as the input format to pass circuits to Qiskit Runtime primitives, only instructions that can be loaded into Qiskit are supported. Classical operations, for example, are not supported because they cannot be loaded into Qiskit. See Import an OpenQASM 3 program into Qiskit for more information.

  • The for, while, and switch instructions are not supported.


Use dynamic circuits with Estimator

Since Estimator does not support dynamic circuits, you can use Sampler and build your own measurement circuits instead. Alternatively, you can use the Executor primitive, which supports dynamic circuits.

To replicate Estimator's behavior, follow this process:

  1. Group the terms of all observables into a partition. This can be done by using the PauliList API, for example.
    Note

    You can use the BitArray primitive attribute to compute the expectation values of the provided observables.

  2. Execute one basis change circuit per partition (whichever basis change needs to be done for each partition). See the Measurement bases addon utility measurement_bases module for more information. For more information, see the documentation for the Qiskit addon utilities package.
  3. Add back together the results for each partition.

Next steps

Recommendations