Skip to main contentIBM Quantum Documentation

Qiskit v2.0 migration guide

Qiskit v2.0 is the next step within the Qiskit SDK's newly stabilized life cycle. This major version does break compatibility with previous versions of Qiskit, but, in contrast to the v1.0 release, doesn't involve any changes in the packaging structure.

This guide describes migration paths for the most important feature changes in Qiskit v2.0, organized by module. Use the table of contents on the right side to navigate to the module you are interested in.

Read more about the benefits of Qiskit v2.0 on the IBM® blog.


qiskit.circuit

Removal of legacy c_if conditions

Classical conditions in Qiskit have been preferentially represented by the instruction IfElseOp since before Qiskit v1.0. These operations are typically added to a QuantumCircuit by using the if_test method as a context manager.

This also means that the Instruction no longer has a condition attribute, and that you cannot chain c_if calls on the output of a QuantumCircuit gate-creating method call.

The only instructions that might have a condition now are IfElseOp and WhileLoopOp. The most correct way to test for a condition is to use an isinstance check to validate that you have the correct class. If you want to support the legacy Instruction.condition attribute during your library's transition to Qiskit v2.0 and later, you can use getattr(operation, "condition", None), which will typically cause Instruction instances to behave as they did in Qiskit v1.x and earlier.

The original, legacy method of supporting simple register-equality conditions in Qiskit was to use the c_if method on individual instructions or instruction sets. This was strictly less featureful than the full control-flow form, and was never well-supported on hardware. This legacy method has been removed in Qiskit v2.0; you should use the if_test method with a proper condition. You can continue to use the 2-tuple form of (register, integer) to represent a register equality, or you can use the more expressive classical computation subsystem to represent more complex conditions.

For example, you might have written:

from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
 
qr = QuantumRegister(2, "q")
cr = ClassicalRegister(2, "c")
qc = QuantumCircuit(qr, cr)
qc.h(0)
qc.h(1)
qc.measure(qr, cr)
 
# Apply an X to qubit 0 if both measurements were 1.
qc.x(0).c_if(cr, 3)

The most direct translation of this is to use the 2-tuple form with the if_test context manager:

from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
 
qr = QuantumRegister(2, "q")
cr = ClassicalRegister(2, "c")
qc = QuantumCircuit(qr, cr)
qc.h(0)
qc.h(1)
qc.measure(qr, cr)
 
# Apply an X to qubit 0 if both measurements were 1.
with qc.if_test((cr, 3)):
    qc.x(0)

Note that in this form, you can also have the conditional gate access to multiple operations (simply add more into the indented block) without re-evaluating the conditions. You can also provide an else statement, and use more complex conditional logic:

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.classical import expr
 
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.h(1)
qc.measure(qc.qubits, qc.clbits)
 
# Apply an X to qubit 0 if both measurements were the same:
with qc.if_test(expr.equal(qc.clbits[0], qc.clbits[1])) as else_:
    qc.x(0)
# ... or apply a Y to both qubits otherwise:
with else_:
    qc.y(0)
    qc.y(1)

Removal of Instruction.duration and Instruction.unit

Circuit Instruction objects previously had attributes called duration and unit. These no longer exist in Qiskit v2.0. This is because the field generally only duplicated information that was a fixed property of hardware, and was stored explicitly in the Target. Storing the information separately for each individual instruction significantly increased the memory demands of Qiskit and slowed down even circuit operations that did not directly need to touch the information.

The Delay instruction still contains these fields, as does the new-in-v2.0 BoxOp, because these instructions have a variable delay.

You can retrieve the hardware-specified duration of a particular hardware-supported Instruction by querying the Target. To do this, index the Target first with the operation's name, and then the indices of the hardware qubits as a tuple. This will return either None (if the instruction is supported, but no specific properties were assigned to it), or an InstructionProperties instance, from which the duration field may provide the duration in seconds (or None, if not supplied).

target = "<some Target from a hardware vendor>"
 
# Query the duration of a CX instruction on hardware qubits (2, 4),
# returning `None` if the duration is not specified.
properties = target["cx"][2, 4]
duration = None if properties is None else properties.duration
 
# You can also do this in one step, using `getattr`:
duration = getattr(target["cx"][2, 4], "duration", None)

In general, as classical control-flow becomes more common within circuit executions, the "duration" of a circuit will become less and less well defined. While Qiskit v2.x still includes the QuantumCircuit.duration attribute, it is recommended to move away from it, as it will likely be removed in the future; the assumptions under which it is valid will be violated more and more frequently. Instead, you can use the new QuantumCircuit.estimate_duration method, which is safer and clearer about the necessary assumptions.


qiskit.transpiler

Changes to generate_preset_pass_manager interface

Qiskit v2.0.0 includes a series of updates to the preset pass manager interface (that is, the generate_preset_pass_manager and transpile functions) that affect the handling of loose constraint inputs, such as coupling_map or basis_gates. The number of transpilation use cases where loose constraints are accepted has been reduced in favor of the target and backend inputs (where backend is a BackendV2 that contains a Target instance), which allow for a more expressive communication of these constraints.

The following examples show the main changes and suggested alternatives for migrating. Note that all of the migration tips below apply to both transpile and generate_preset_pass_manager:

  1. The backend_properties input argument is no longer supported, and the BackendProperties class has been removed. You should use the target or backend arguments instead to communicate gate durations and errors. There are multiple paths for constructing a custom Target:

a. Build a target from scratch and add the relevant errors and durations to the corresponding gates:

from qiskit.transpiler import Target, InstructionProperties, generate_preset_pass_manager
from qiskit.circuit.library import XGate, CXGate
 
target = Target(num_qubits=2)
target.add_instruction(CXGate(), {(0, 1): InstructionProperties(error=.0001, duration=5e-7)})
target.add_instruction(
    XGate(),
    {
        (0,): InstructionProperties(error=.00001, duration=5e-8),
        (1,): InstructionProperties(error=.00002, duration=6e-8)
    }
)
 
pm = generate_preset_pass_manager(target=target)

b. Use GenericBackendV2 to quickly build a generic target with pre-filled errors and durations. These can also be updated post-construction:

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import generate_preset_pass_manager
 
target = GenericBackendV2(num_qubits=2, basis_gates=["x", "cx"]).target
target.update_instruction_properties("x", (0,), InstructionProperties(0.00001))
target.update_instruction_properties("x", (1,), InstructionProperties(0.00002))
target.update_instruction_properties("cx", (0, 1), InstructionProperties(0.0001))
 
pm = generate_preset_pass_manager(target=target)
  1. The instruction_durations and timing_constraints input arguments are no longer supported. You should use the target or backend arguments instead. The alternatives are constructing a custom Target from scratch, constructing a generic backend, and, in addition to these, it is possible to build a custom target with instruction_durations and/or timing_constraints using the Target.from_configuration method:

    from qiskit.transpiler import Target, generate_preset_pass_manager
    from qiskit.transpiler.timing_constraints import TimingConstraints
    from qiskit.transpiler.instruction_durations import InstructionDurations
     
    instruction_durations = InstructionDurations([("x", None, 200), ("cx", None, 1000)], dt=1e-7)
     
    timing_constraints = TimingConstraints(
            granularity=1, min_length=1, pulse_alignment=1, acquire_alignment=1
        )
     
    target = Target.from_configuration(num_qubits=2, 
                                        basis_gates=["x", "cx"], 
                                        instruction_durations=instruction_durations, 
                                        timing_constraints=timing_constraints)
     
    pm = generate_preset_pass_manager(target=target)
  2. Providing coupling_map and/or basis_gates along with a backend is discouraged, and from v2.0 a UserWarning will be raised. In these cases there are multiple sources of truth, and information such as gate errors and durations from the backend cannot be resolved based on the information given. The suggested alternative is to define a custom target that combines the chosen constraints either from scratch, using Target.from_configuration or GenericBackendV2.

  3. The basis_gates argument no longer accepts custom basis gates. The suggested alternative is the target argument, which can be built from scratch or through Target.from_configuration such as:

    from qiskit.circuit.library import XGate
    from qiskit.transpiler.target import Target
     
    basis_gates = ["my_x", "cx"]
    custom_name_mapping = {"my_x": XGate()}
    target = Target.from_configuration(
        basis_gates=basis_gates, num_qubits=2, custom_name_mapping=custom_name_mapping
    )
    pm = generate_preset_pass_manager(target=target)
  4. The input combination where a custom coupling_map is provided with either a backend or basis_gates that contain gates with three or more qubits is no longer allowed. The coupling map does not provide the necessary connectivity details to be able to determine the action of the gate. In these cases, the recommended migration path is the creation of a custom target from scratch.

  5. The inst_map argument has been removed without alternative. It was used to provide pulse information to the transpiler, and the full qiskit.pulse module has been removed.


qiskit.pulse

The module qiskit.pulse has been completely removed from Qiskit, without replacement. This includes the related items:

This happened because IBM Quantum® QPUs ceased to offer pulse-level access, and the Qiskit team no longer had resources to maintain the little-used package. You can continue to use the qiskit.pulse module while using the v1.x series of Qiskit, which has bug-fix support until September 2024 and security support until March 2025, but the module is removed from the v2.x series.

Most uses of qiskit.pulse can be replaced with direct access to fractional gates on IBM Quantum Heron QPUs (and later generations, if applicable). You can read more in the migration guide for converted pulse usage to fractional gates.

You can no longer load QPY files that contain ScheduleBlock objects in Qiskit v2.0 because of the removal of Qiskit Pulse. You can continue to load these files in the Qiskit v1.x series.

Was this page helpful?
Report a bug or request content on GitHub.