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
:
- The
backend_properties
input argument is no longer supported, and theBackendProperties
class has been removed. You should use thetarget
orbackend
arguments instead to communicate gate durations and errors. There are multiple paths for constructing a customTarget
:
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)
-
The
instruction_durations
andtiming_constraints
input arguments are no longer supported. You should use thetarget
orbackend
arguments instead. The alternatives are constructing a customTarget
from scratch, constructing a generic backend, and, in addition to these, it is possible to build a custom target withinstruction_durations
and/ortiming_constraints
using theTarget.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)
-
Providing
coupling_map
and/orbasis_gates
along with abackend
is discouraged, and from v2.0 aUserWarning
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, usingTarget.from_configuration
orGenericBackendV2
. -
The
basis_gates
argument no longer accepts custom basis gates. The suggested alternative is thetarget
argument, which can be built from scratch or throughTarget.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)
-
The input combination where a custom
coupling_map
is provided with either abackend
orbasis_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. -
The
inst_map
argument has been removed without alternative. It was used to provide pulse information to the transpiler, and the fullqiskit.pulse
module has been removed.
qiskit.pulse
The module qiskit.pulse
has been completely removed from Qiskit, without replacement.
This includes the related items:
- the
QuantumCircuit
,DAGCircuit
andDAGDependency
propertycalibrations
. - the
QuantumCircuit
methodshas_calibration_for
andadd_calibration
. - the
DAGCircuit
methodhas_calibration_for
. - the transpiler passes
PulseGates
,RXCalibrationBuilder
,RZXCalibrationBuilder
,EchoRZXWeylDecomposition
,ValidatePulseGates
. - the module
qiskit.scheduler
and all its contents. - the
BackendV2
methodsinstruction_schedule_map
,drive_channel
,measure_channel
,acquire_channel
andcontrol_channel
. - the
Target
methodshas_calibration
,get_calibration
,instruction_schedule_map
andupdate_from_instruction_schedule_map
. - the
inst_map
argument ofgenerate_preset_pass_manager
,transpile
, andTarget.from_configuration
. - the
calibration
field ofInstructionProperties
.
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.