Skip to main contentIBM Quantum Documentation Mirror

Qiskit SDK 2.2 release notes


2.2.1

Prelude

Qiskit 2.2.1 is a small patch release that fixes several bugs identified in the 2.2.0 release.

Transpiler Upgrade Notes

  • The maximum call and trial limits for the exact-matching run of VF2PostLayout at optimization_level=3 have been reduced to avoid excessive runtimes for highly symmetric trial circuits being mapped to large coupling maps.

Bug Fixes

  • DAGCircuit.apply_operation_back(), apply_operation_back() and circuit_to_dag() will now add new edges in a deterministic order. The previous behavior could cause certain transpiler passes (such as SabreSwap) to traverse the DAG in non-deterministic orders.

  • DAGCircuit.apply_operation_front() can no longer insert invalid self loops when handling nodes that include classical conditions.

  • Fixed an issue with pickle support for the SabreSwap where a SabreSwap instance would error when being pickled after the SabreSwap.run() method was run. Fixed #15071.

  • Fixed an issue where is_unitary() was not properly respecting the input tolerance values when checking if an operator is unitary. The method now correctly uses the provided atol and rtol parameters when simplifying the operator and checking if it equals the identity. This fixes #14107.


2.2.0

Prelude

The Qiskit v2.2 adds several enhancements for the C API and the transpiler as well as many other improvements and bug fixes. The major features of this new release are:

  • C API transpile function: The C API now includes a function for transpiling a quantum circuit: qk_transpile(). This function is equivalent to the Python transpile() function for a standalone C context.
  • Litinski transformation pass: A new transformation pass LitinskiTransformation that implements the transform described arXiv:1808.02892. This is pass is typically used in compilation for fault tolerant architectures.
  • Angle bound support for targets: The Target class now supports specifying bounds for the allowed values for parameterized operations added to the target. When an instruction is added to the Target you can add the optional argument angle_bounds to specify the higher and lower bounds for parameterized rotation gates. A new transpiler pass WrapAngles was added to enforce the angle constraints as part of a transpilation pipeline.

The v2.2 release series is the final minor release series with support Python 3.9. The minimum supported Rust version use to build Qiskit from source is now Rust v1.85, raised from 1.79 in 2.1.0. For more information about the above and other changes made, please see the release notes below and review the updated documentation.

C API Features

Circuits Features

OpenQASM Features

  • Added partial support for defcal symbols in the OpenQASM3 exporter. This enables downstream packages to export custom instructions that operate on both quantum and classical bits using qiskit.qasm3.dumps(). Users can now define custom instructions (e.g., a CustomMeasure that acts on a qubit and returns a classical bit) and specify their behavior using DefcalInstruction. These defcals are passed to the exporter via the implicit_defcals argument in qiskit.qasm3.dumps().

    For example:

    from qiskit.circuit import Instruction, QuantumCircuit
    from qiskit.qasm3 import dumps
    from qiskit.qasm3.exporter import DefcalInstruction, types
     
    custom_measure = Instruction("measure_2", 1, 1, [])
    qc = QuantumCircuit(1, 1)
    qc.h(0)
    qc.append(custom_measure, [0], [0])
    qc.measure(0, 0)
     
    defcals = {
        "measure_2": DefcalInstruction("measure_2", 0, 1, types.Bool()),
    }
     
    out_qasm = dumps(qc, implicit_defcals=defcals)
    print(out_qasm)

    Would output the following valid OpenQASM3 string:

    OPENQASM 3.0;
    bit[1] c;
    qubit[1] q;
    h q[0];
    c[0] = measure_2 q[0];
    c[0] = measure q[0];

    This approach assumes that the grammar definition for the defcal is provided externally (e.g., in a header file), although such a file is not strictly required for the exporter to function.

  • The functions qasm3.loads() and qasm3.load() now have an extra argument called num_qubits. If provided, the functions will return circuits that will have qubits equal to num_qubits. If not provided, the returned circuit will have qubits equal to the maximum index seen in the serialized circuit. Refer to #14435 for more details

QPY Features

  • Added a setting named min_qpy_version in the user configuration file. When set, it defines the minimum allowed QPY version for qpy.load(). If the format version of a QPY file is lower than the minimum_qpy_version than the minimum_qpy_version setting it will raise an exception.

  • Introduced QPY format version 16. This new version introduces a new circuit start table to the file header which contains the byte offsets of the start of each circuit in a QPY file. This allows for a potentially more efficient loading of circuits from QPY files, and a potentially multi-threaded Rust implementation in the future. Additionally, the new format version adds support for the new DURATION variant of picoseconds.

Quantum Information Features

Synthesis Features

Transpiler Features

  • Added new high-level-synthesis plugin for synthesizing a ModularAdderGate:

    The ModularAdderSynthesisDefault has also been updated to follow the following sequence of modular adder synthesizers: "ModularAdder.qft_d00" when the number of qubits is 4\leq 4, "ModularAdder.modular_v17" in all other cases.

  • Added a new transpiler pass LitinskiTransformation that implements the transform described in arXiv:1808.02892.

    The input to the pass is a circuit with Clifford and single-qubit RZ-rotation gates, and the output is a circuit with multi-qubit Pauli rotations (implemented as PauliEvolutionGate gates) followed by Clifford gates. The pass raises a TranspilerError exception if the circuit contains non-supported gates.

    The pass supports all of the Clifford gates in the list returned by get_clifford_gate_names(), namely ["id", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg", "cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"]. The list of supported RZ-rotations is ["t", "tdg", "rz"] (we automatically convert T and Tdg gates to RZ-rotations).

    In addition, the LitinskiTransformation constructor accepts an argument fix_clifford. When False (non-default), the returned circuit contains only PauliEvolutionGate gates, with the final Clifford gates omitted. Note that in this case the operators of the original and synthesized circuits will generally not be equivalent.

    For example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler.passes import LitinskiTransformation
    from qiskit.quantum_info import Operator
     
    # The following quantum circuit consists of 5 Clifford gates
    # and two single-qubits RZ-rotation gates (note that Tdg is
    # an RZ-rotation).
    qc = QuantumCircuit(2)
    qc.cx(0, 1)
    qc.rz(0.1, 0)
    qc.cz(0, 1)
    qc.tdg(1)
    qc.h(1)
    qc.s(1)
    qc.cz(1, 0)
     
    # The transformed circuit consists of two PauliEvolution gates
    # and the same Clifford gates as in the original circuit.
    qct = LitinskiTransformation()(qc)
     
    # The circuits before and after the transformation are equivalent
    assert Operator(qc) == Operator(qct)
  • Added a new high-level synthesis plugin MCXSynthesisNoAuxHP24 for MCXGate objects. Furthermore, the default synthesis plugin MCXSynthesisDefault for MCXGate objects was updated to always choose the best synthesis method depending on the available number of auxiliary qubits.

  • Added support for a Target to specify angle bounds on instructions. Prior to this release a Target could specify that an operation that took a parameter either would allow any valid value or a specific value. For example, if RZGate(Parameter("theta")) were added to the target that would indicate an RZGate with any value for theta were allowed. While if RZGate(math.pi) were added to the target that would indicate RZGate that only π\pi is the only allowed value on the target. This new feature enables restricting the allowed angles to be any float value between a an inclusive bound. For example, you can add RZGate(math.pi) to a Target and restrict the angle value between the values 0 and 2π2\pi.

    There are several methods available for working with the angle bounds on the target. The first is Target.add_instruction() which has a new angle_bounds keyword argument that is used to add an angle bound to an instruction in the Target. To work with angle bounds you will also want to register a callback function to the global WRAP_ANGLE_REGISTRY registry that will tell the transpiler and WrapAngles pass how to adjust gates for angle bounds. The callback function will take a list of arbitrary float values representing the gate angles from the circuit, as well as the qubit indices in the circuit the gate was operating on and it will return a DAGCircuit that represents an equivalent circuit for the gate with that angle but respecting the angle bounds and other Target constraints. For example:

    import math
     
    from qiskit.dagcircuit import DAGCircuit
    from qiskit.transpiler import target
    from qiskit.transpiler.passes.utils.wrap_angles import WRAP_ANGLE_REGISTRY
     
    target = Target(num_qubits=1)
    target.add_instruction(RZGate(Parameter("theta")), angle_bounds=[(-math.pi, math.pi)])
     
    def callback(angles: List[float], qubits: List[int]) -> DAGCircuit:
        """Callback function to wrap RZ gate angles
     
        Args:
            angles: The list of floating point parameter values for the instance of RZGate in
                the circuit
            qubits: The physical qubit indices that this gate is operating on
     
        Returns:
            The DAGCircuit of the equivalent circuit"""
     
        angle = angles[0]
        dag = DAGCircuit()
        dag.add_qubits([Qubit()])
        if angle > 0:
            divisor = math.pi
        else:
            divisor = -math.pi
        gate_counts = int(angles[0] // divisor)
        rem = angles[0] % divisor
        for _ in range(gate_counts):
            dag.apply_operation_back(RZGate(math.pi), [dag.qubits[0]], check=True)
        dag.apply_operation_back(rem, [dag.qubits[0]], check=True)
     
    WRAP_ANGLE_REGISTRY.add_wrapper("rz", callback)

    Target.has_angle_bounds() can be used to check whether there are any angle bounds set in the target, and Target.gate_has_angle_bounds().

    If you want to apply the angle bounds from a target to any gates in a circuit the WrapAngles transpiler pass should be used to do this.

  • Added a new kwarg check_angle_bounds to the Target.instruction_supported() method. When set to True (the default) the method will check if the specified parameters also conforms to any angle bounds that may exist for the instruction being queried.

  • Added a new transpiler pass WrapAngles which is used to apply angle constraints on gates in the Target. If a Target has defined angle bounds this pass will analyze all the parameters for the gates in the circuit and check that against the bounds specified in the target. For example, if a target contains a custom gate that has angle bounds the pass will decompose that gate into a gate which conforms to the bounds:

    from qiskit.circuit import Gate, Parameter, QuantumCircuit, Qubit
    from qiskit.dagcircuit import DAGCircuit
    from qiskit.transpiler import Target, WrapAngleRegistry
    from qiskit.transpiler.passes import WrapAngles
     
    class MyCustomGate(Gate):
     
        def __init__(self, angle):
            super().__init__("my_custom", 1, [angle])
     
    param = Parameter("a")
    circuit = QuantumCircuit(1)
    circuit.append(MyCustomGate(6.0), [0])
    target = Target(num_qubits=1)
    target.add_instruction(MyCustomGate(param), angle_bounds=[(0, 0.5)])
     
    def callback(angles, _qubits):
        angle = angles[0]
        if angle > 0:
            number_of_gates = angle / 0.5
        else:
            number_of_gates = (6.28 - angle) / 0.5
        dag = DAGCircuit()
        dag.add_qubits([Qubit()])
        for _ in range(int(number_of_gates)):
            dag.apply_operation_back(MyCustomGate(0.5), [dag.qubits[0]])
        return dag
     
    registry = WrapAngleRegistry()
    registry.add_wrapper("my_custom", callback)
    wrap_pass = WrapAngles(target, registry)
    res = wrap_pass(circuit)
    res.draw('mpl')
    _images/release_notes-1.png
  • DAGCircuit now has a manual implementation of __deepcopy__(). This is orders of magnitude faster than the previous implicit implementation from the pickle protocol, especially for large circuits. This also directly benefits compilation performance when running transpile() or running the preset pass managers returned by generate_preset_pass_manager() at optimization_level=3 as optimization level 3 internally deep copies the DAGCircuit for each iteration of the optimization loop in the optimization stage.

  • A new method, DAGCircuit.make_physical(), is provided, which efficiently replaces the qubits in the DAGCircuit with the canonical physical-qubit register, potentially including expansion. A similar method, QuantumCircuit.ensure_physical() is also available.

  • A new method, DAGCircuit.structurally_equal(), can be used to if two DAGCircuit instances have been created and modified in the exact same order. This is a much stronger test than the standard semantic equivalence check of the == overload, and can be used by transpiler-pass authors to verify that their modification orders are deterministic.

  • Custom subclasses of BasePassManager can now modify their property_set attribute during their _passmanager_frontend() method, to seed initial properties. This provides symmetry, as it was previously only possible to read the final properties during _passmanager_backend().

  • Added a new class OptimizationMetric which specifies the optimization criterion in the HighLevelSynthesis pass. Currently the two supported metrics are:

    • COUNT_2Q: optimizes the number of two-qubit gates.
    • COUNT_T: optimizes the number of T-gates, when transpiling into a Clifford+T basis set.

    The transpiler automatically selects the target metric based on the basis gate set, e.g. it will use COUNT_T if a Clifford+T basis set is detected. However, this class can not be currently set manually when running transpile() or running a preset pass manager generated by generate_preset_pass_manager().

  • Added a new argument optimization_metric to the constructor for HighLevelSynthesis transpiler pass which takes an OptimizationMetric object. When set to COUNT_T, the pass chooses decompositions that are more suitable for the Clifford+T gate set.

  • The default high-level synthesis plugins for ModularAdderGate and MultiplierGate produce better T-counts when transpiling into Clifford+T basis set.

  • The default high-level synthesis plugin for MCXGate produces better T-counts when transpiling into Clifford+T basis set, provided at least 1 ancilla qubit is available.

  • Added a default set of inverse gates for the InverseCancellation transpiler pass. Previously, an explicit list of gates or gate pairs to cancel was a required argument for the constructor of the pass object. Now this list is optional and if no list is provided the self inverse gates are:

    and the inverse pairs:

    will be cancelled by the pass.

  • Added a new argument to the InverseCancellation constructor, run_default, which when set to True will run the new default inverse cancellation gate list in addition to the any custom gates provided. This is intended for use cases where you want to run a custom set of inverse cancellations in addition to the default gates.

  • TranspileLayout has two new methods: from_property_set() and write_into_property_set(), which formalize the current ad-hoc structure of transpilation properties, and how they are converted into a TranspileLayout. This makes it possible for passes during a transpiler pipeline to access what the TranspileLayout will be, modify it in the fully structured form, and then write it back out in canonical form.

    It is expected that in the future version 3.0 of Qiskit, the TranspileLayout (or something akin to it) will be a direct attribute of the DAGCircuit transpiler intermediate representation, and required by passes to be kept in sync with the rest of the DAGCircuit.

  • Re-enabled running VF2PostLayout transpiler pass in the optimization stage for optimization_level=3 with the transpile() function and the generated pass manager objects returned by the generate_preset_pass_manager() function. The pass runs with strict_direction=True after all the physical optimizations performed in the stage to attempt and improve the layout one final time with the exact output circuit. This was previously enabled in Qiskit v2.1.0 but was reverted in 2.1.2 due to issues with the initial implementation. These issues have been fixed and the layout will be properly applied if a better one is found.

Visualization Features

  • A new option, measure_arrows, has been added to the mpl and text backends for the circuit drawer qiskit.visualization.circuit_drawer() and qiskit.circuit.QuantumCircuit.draw(). When this option is set to True, the drawer will draw an arrow from the measure box to the classical bits below. This was the previous behavior in the drawers. If it is set to False, no arrows will be drawn and instead the classical register and bit being used by the measure will be indicated inside the measure box. This allows measure boxes to be stacked within a vertical layer.

    If no option is set, the default is True. The user can change the default in the user config file, by default in the ~/.qiskit directory, in the file settings.conf. Under the [Default] heading, a user can enter circuit_measure_arrows = False to change the default.

Upgrade Notes

  • The minimum support Rust version for building Qiskit from source (including building the C API in standalone mode) has been increased from Rust 1.79 to Rust 1.85. This change was necessary to enable using a newer version of the Rust linear algebra library faer that is used inside Qiskit. There were issues identified when running Qiskit on Windows with the previous version of faer. These were fixed in the newer release, however a newer version of the Rust compiler is required to build the newer version of faer.

Circuits Upgrade Notes

C API Upgrade Notes

QPY Upgrade Notes

  • The default version of QPY emitted by qpy.dump() is now QPY format version 16. If you require a different format version you can use the version argument to specify an earlier format version.

Quantum Information Upgrade Notes

Synthesis Upgrade Notes

  • The default values of the arguments opt_a1 and opt_a2 of qs_decomposition() are now opt_a1 = None and opt_a2 = None. The new defaults will choose the optimal value based on whether the input unitary for the mat argument is controlled or not to result in the minimal CXGate count by default. If you require the previous behavior you can explicitly call opt_a1 = True and opt_a2 = True to maintain the same behavior as previous releases.

Transpiler Upgrade Notes

  • The circuit PassManager now always sets the properties original_circuit_indices and num_input_qubits before execution starts on individual passes. These are properties of the input circuit, which it was previously up to individual passes to set, often as a side effect of their primary purpose.

Circuits Deprecations

Bug Fixes

  • ApplyLayout will now correctly handle the case of applying a zero-qubit Layout. Previously, it would claim that no layout had been set, even if the "layout" field of the PropertySet was equal to Layout().

  • Fixed memory leakage issues during the creation of a QkOpCounts instance and during any calls to qk_opcounts_clear() whenever an empty instance is passed.

  • Previously one could define an invalid PauliEvolutionGate from a list of operators, where the operators were not all defined on the same number of qubits. This is now fixed, and we now raise an error when the gate is defined:

    from qiskit.quantum_info import Pauli, SparsePauliOp
    from qiskit.circuit.library import PauliEvolutionGate
     
    pauli = Pauli("XYZ")  # 3 qubits
    op = SparsePauliOp(["XYIZ"], [1])  # 4 qubits
    evo = PauliEvolutionGate([pauli, op], time=1)
  • Fixed an issue in the VF2Layout transpiler pass where even with a fixed seed set the output from the pass was potentially non-deterministic; specifically if the input circuit had any active qubits that only contained single qubit operations. Fixed #14729

  • Fixed a bug in PassManager.run() where the callback function was not invoked when running with multiple circuits. The callback is now correctly triggered for each pass on each circuit, including in parallel execution.

  • Fixed several problems in the CommutativeInverseCancellation transpiler pass. The pass now works correctly on circuits containing Clifford operations, control-flow operations, and non-invertible operations such as Initialize.

    In addition, the pass now always performs a syntactic (non-matrix-based) check first, when identifying inverse gate pairs. If the gates are not syntactically equal, the argument matrix_based is set to True, and the operation does not act on more than max_qubits qubits, then a matrix-based check is also performed. This slightly improves the reduction potential of the pass.

    Fixed #14407, #14635, and #14645.

  • Fixed several issues in the CommutativeCancellation transpiler pass (and thereby in transpile()), where the global phase of the circuit was not updated correctly. In particular, merging an X-gate and an RX-gate introduced a phase mismatch, while removing a Pauli rotation gate with angle of the form (2+4k)π(2 + 4k)\pi, kZk \in \mathbb Z incorrectly produced a phase shift of 1-1.

  • Fixed a problem in CommutationChecker, where standard controlled gates were not handled correctly if they were controlled on something other than the all-ones state. Fixed #14974

  • Fixed a non-determinism in CommutativeCancellation. This did not affect the order returned by DAGCircuit.topological_nodes() or topological_op_nodes(), which typically should be used when node-order determinism is important, due to their built-in canonical sorting function. However, if inspecting the nodes by arbitrary order (DAGCircuit.op_nodes()) or the edge structure (DAGCircuit.edges()), the iteration order would be non-deterministic after a call to CommutativeCancellation.

  • Fixed a bug in MCXGate.control(), where adding more controls to an open-controlled MCXGate did not take the ctrl_state of the controlled MCX gate into account, thus leading to an incorrect ctrl_state of the extended MCX gate. Note that the explicit MCX classes CXGate, CCXGate, C3XGate, and C4XGate were already handled correctly.

  • Fixed the deprecation warning for Python 3.9 so that it correctly is identified as being caused by user code when importing Qiskit. Previously, it would not be identified as being caused by user code and this meant that Python’s default warning filters would not display the warning to the user.

  • Fixed a bug in PhaseOracle, PhaseOracleGate and BitFlipOracleGate where trying to load from dimacs file raised a TypeError.

  • Fixed a bug in the ElidePermutations transpiler pass, where the qubit mapping was not updated correctly in the presence of PermutationGates, leading to incorrect circuits and updates to the pass manager’s property set.

  • Built-in transpiler passes that set the final_layout property will now correctly handle updating this field if it was already set. This can be observed as the method TranspileLayout.routing_permutation now returning a correct permutation after running more than one pass that sets final_layout.

    This did not affect any normal calls to transpile() or generate_preset_pass_manager() using Qiskit’s built-in plugins; no pipeline constructed in this form would attempt to set final_layout more than once.

  • Fixed a bug in the HighLevelSynthesis pass where, if the circuit contained high level objects with classical registers, these would get mapped to the relative index in the object instead of the corresponding index in the outer circuit. The classical registers are now correctly mapped to the outer circuit index.

  • Fixed a bug in Target.instruction_supported() where the check of instruction’s qubit order was skipped when the method was called with operation_name and parameters arguments that matched an existing instruction.

  • Fixed the behavior of the max_trials argument for VF2Layout when set to None or a negative number. The pass was documented as limiting the search to being based on the size of the circuit or target if the option was set to None and as accepting negative values to specify an unbounded search. However in the 2.1.0 this behavior was incorrectly changed so that None ran an unbounded search and trying to use a negative number would raise an error. These oversights have been corrected so that the pass behaves as documented and is consistent with previous releases.

  • Fixed a problem in high-level synthesis plugins MCXSynthesis1DirtyKG24 and MCXSynthesis2DirtyKG24 for MCXGate, where the plugins did not consider available clean auxiliary qubits as available dirty auxiliary qubits. In particular, the plugin MCXSynthesis2DirtyKG24 did not apply when one clean and one dirty auxiliary qubits were available.

  • The PauliEvolutionGate.to_matrix() method now returns the exact matrix exponential exp(itH)\exp(-it H), where HH is the operator and tt the time passed to the gate. This fixes an unexpected behavior, since the PauliEvolutionGate is documented to represent the exact time evolution, but previously the matrix was dependent on how the compiler approximates the time evolution. The to_matrix method is now consistent with the documentation.

  • Fixed a performance regression when incrementally building ParameterExpression from combining a large number of sub-expressions. Fixed #14653

  • Fixed a correctness bug when exporting circuits with delay instructions using ‘ps’ units to QASM3.

  • Fixed an edge case in the display of the QFT circuit. Previously, when QFT.inverse() was called and then the attributes of the QFT circuit were modified, the QFT was displayed as "IQFT_dg". This was incorrect, and now it correctly shows "IQFT". Fixed #14758.

  • Fixed a bug in the PauliEvolutionSynthesisRustiq plugin that produced incorrect circuits in the case that the operator of a PauliEvolutionGate contains objects of type SparseObservable.

    For example:

    from qiskit.circuit.library import PauliEvolutionGate
    from qiskit.quantum_info import SparseObservable, Operator
    from qiskit.transpiler.passes.synthesis.hls_plugins import PauliEvolutionSynthesisRustiq
     
    obs = SparseObservable.from_sparse_list([("1+XY", (0, 1, 2, 3), 1.5)], num_qubits=4)
    evo = PauliEvolutionGate(obs, 1)
    qct = PauliEvolutionSynthesisRustiq().run(evo)
    assert Operator(qct) == Operator(evo)
  • Fixed an issue with the generate_preset_pass_manager() function where it would incorrectly ignore the timing constraints data contained in a Target object provided when the backend argument was not set. Fixed #14329

  • Fixed a bug in ObservablesArray.coerce_observable() where an exception that an observable was not Hermitian was over-triggered. Observables equal to zero are not allowed and now invoke a graceful failure.

  • qiskit.circuit.library.quantum_volume() was updated to handle a numpy.random.Generator as input for its seed argument. Previously, such a generator argument would result in a TypeError.

  • QuantumCircuit.compose() will now correctly remap anyway variables and stretches used in Delay instructions when the var_remap argument is specified.

  • SabreLayout uses a “dense subset” layout as one of its trials, following the same algorithm as DenseLayout. Previously, however, the version used by Sabre was assigning all virtual qubits, including dummy ancillas, to a physical qubit, compromising the effectiveness of the algorithm, but not its correctness. Sabre will now only use the virtual qubits defined by the user for this initial trial, which may result in small improvements in layout selection when averaged over large classes of circuit.

  • Fixed a bug in SparsePauliOp.simplify() where the method removed Pauli terms based on their coefficients’ magnitudes before combining duplicates. This caused incorrect behavior in two key edge cases:

    1. When multiple identical Pauli terms had small coefficients that were individually below the simplification threshold but whose sum exceeded the threshold, those terms were incorrectly removed.
    2. When multiple identical Pauli terms had coefficients above the threshold but summed to near-zero, they were incorrectly kept.

    See #14194 for more detail.

  • TranspileLayout.initial_index_layout() will now correctly handle the filter_ancillas=True argument if the virtual qubits in the initial_layout were not specified by the constructor in index order.

  • VF2Layout and VF2PostLayout will now correctly include (arbitrary) layout assignments for completely idle qubits. Previously this might have been observed by calls to TranspileLayout.initial_index_layout() failing after a compilation.

  • Fixed an issue in the VF2PostLayout transpiler pass. In certain situations when the pass is being run with the argument strict_direction=True there was a potential runtime scaling issue when the interaction graph of the circuit contained any qubits that only had 1 qubit operations. These mapping problems result in a combinatorial complexity for scoring that would lead to the pass almost always hitting the scoring timeout and typically not improving the layout. In strict_direction=False mode there is an optimized search implementation for these problems, but the additional constraints for strict_direction=True don’t make this approach viable. Instead in these cases VF2PostLayout will now skip the search since the layout problem isn’t viable for the pass.

Other Notes

  • The implementation of Sabre routing used by SabreLayout and SabreSwap now compresses runs of nodes that will automatically become eligible for routing at the same time within its internal virtual-interaction representation. This improves the efficiency of the routing, reduces intermediate memory use, and avoids runs of 2q gates biasing the lookahead heuristic components.

2.1.0rc1

New Features

  • Added C++ support for the C API. The generated header now allows calling objects and functions from C++ directly. For example, a 100-qubit observable with the term XYZ on the first 3 qubits can be constructed as

    #include <iostream>
    #include <complex>
    #include <vector>
    #include <qiskit.h>
     
    int main() {
        uint32_t num_qubits = 100;
     
        // Use smart pointer with custom deleter to manage QkObs memory
        QkObs *obs = qk_obs_zero(num_qubits);
     
        // Construct the observable term
        std::complex<double> coeff = 2.0;
        std::vector<QkBitTerm> bit_terms = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
        std::vector<uint32_t> indices = {0, 1, 2};
     
        QkObsTerm term {
            .coeff      = coeff,
            .len        = bit_terms.size(),
            .bit_terms  = bit_terms.data(),
            .indices    = indices.data(),
            .num_qubits = num_qubits
        };
     
        qk_obs_add_term(obs.get(), &term);
     
        // Print observable properties
        std::cout << "num_qubits: " << qk_obs_num_qubits(obs) << "\n";
        std::cout << "num_terms: "  << qk_obs_num_terms(obs)  << "\n";
     
        qk_obs_free(obs);
        return 0;
    }

    Note that std::complex<double> is compatible with QkComplex64.

  • Improved synthesis of multi-controlled CZGate gates.

  • Improved the default plugin for synthesizing AnnotatedOperation objects. The improvement is especially useful when creating and transpiling controlled circuits with controlled gates within them. For example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.circuit.library import CXGate
    from qiskit.compiler import transpile
     
    inner = QuantumCircuit(5)
    inner.append(CXGate().control(3, annotated=True), [0, 1, 2, 3, 4])
    controlled_inner_gate = inner.to_gate().control(2, annotated=True)
    qc = QuantumCircuit(15)
    qc.append(controlled_inner_gate, [0, 1, 2, 3, 4, 5, 6])
    qct = transpile(qc, basis_gates=["cx", "u"])

    This code creates a quantum circuit qc that contains a 2-controlled quantum circuit with a 3-controlled CX-gate within it. With the improvement, the number of CX-gates in the transpiled circuit is reduced from 378 to 30. Note that by specifying annotated=True when defining control logic, the controlled gates are created as annotated operations. This avoids eager synthesis, allows the transpiler to detect that controlled_inner_gate is equivalent to a 6-controlled X-gate, and to choose the best synthesis method available for multi-controlled X-gates, in particular utilizing available ancilla qubits.

  • The function adder_qft_d00() used for synthesize ModularAdderGate and HalfAdderGate gates now accepts an addional parameter annotated. If True, the inverse-QFT-gate within the adders is implemented as an annotated operations, allowing the transpiler to apply additional optimizations.

  • Incorporated the SparseObservable class with the ObservablesArray class. * There is no change in behavior of existing methods of ObservablesArray, except for the method coerce_observable(), whose return type is changed to SparseObservable. * The new method apply_layout() performs an apply_layout operation on each observable in the array. * The new method equivalent() returns True if two arrays store equivalent observables. * The new methods get_sparse_observable() and sparse_observables_array() return information about the contents of the array. * Estimator pubs can now be defined using SparseObservable objects, in addition to the existing options of str, Pauli, SparsePauliOp, and Mapping[Union[str, Pauli], float]. However projective observables are not yet supported when executing circuits. Projective observables are 0, 1, +, -, r, l. Currently only Pauli’s are supported: I, X, Y, Z.

  • Introduces the QubitSparsePauli, QubitSparsePauliList, and PauliLindbladMap classes. These classes represent, respectively, a single phase-less Pauli operator, a list of phase-less Pauli operators, and a Pauli Lindblad map, all stored in qubit-sparse format.

C API Features

  • Added support for querying the Qiskit version via the C API using the following definitions:

    • QISKIT_VERSION: The version as human readable char* (e.g. "2.2.0").
    • QISKIT_VERSION_HEX: The version as HEX.
    • QISKIT_VERSION_MAJOR: The major version number.
    • QISKIT_VERSION_MINOR: The minor version number.
    • QISKIT_VERSION_PATCH: The patch version number.
    • QISKIT_RELEASE_LEVEL: The release level, e.g. 0xF for final or 0xC for release candidates.
    • QISKIT_RELEASE_SERIAL: The release serial, e.g. 1 for the first release candidate.
    • QISKIT_GET_VERSION_HEX: A macro returning the version a HEX, given major, minor, patch, level, and serial.

    For example, to check if the current version is at least 2.2.0, you can use:

    if (QISKIT_VERSION_HEX >= QISKIT_GET_VERSION_HEX(2, 2, 0, 0xF, 0)) {
        // Code for version 2.2.0 (final) or later
    }

Circuits Features

  • Added a function random_circuit_from_graph() that generates a random circuit that induces the same interaction graph as the interaction graph specified by interaction_graph.

    The probability of randomly drawing an edge from the interaction graph as a two-qubit gate can be set by the user in the weight attribute of an edge in the input interaction graph. If the user does not set the probability, each edge is drawn uniformly, i.e. each two-qubit gate represented by an edge in the interaction graph has the same probability of getting added to the random circuit. If only a subset of edge probabilities are set, ValueError will be raised.

    This is an example where ‘cp_map’ is a list of edges with some arbitrary weights.

    from qiskit.circuit.random.utils import random_circuit_from_graph
    import rustworkx as rx
    pydi_graph = rx.PyDiGraph()
    n_q = 5
    cp_map = [(0, 1, 0.18), (1, 2, 0.15), (2, 3, 0.15), (3, 4, 0.22)]
    pydi_graph.add_nodes_from(range(n_q))
    pydi_graph.add_edges_from(cp_map)
    # cp_map can be passed in directly as interaction_graph
    qc = random_circuit_from_graph(interaction_graph = pydi_graph,
      min_2q_gate_per_edge = 1, 
      max_operands = 2, 
      measure = True, 
      conditional = True, 
      reset = True, 
      seed = 0, 
      insert_1q_oper = True, 
      prob_conditional = 0.21,
      prob_reset = 0.1)
    qc.draw(output='mpl')
    _images/release_notes-2.png
  • Added QuantumCircuit.has_control_flow() to check if a circuit contains any control flow operations.

  • A new module qiskit.circuit.annotation and principle object Annotation have been added.

    Annotations are a way of tagging instructions (currently only BoxOp, in the initial implementation) with local, user-custom data. This data is intended to be consumed by custom transpiler passes. Annotations provide a way to attach data to specific instructions, rather than using the global-context object PropertySet during compilation.

    All Annotation objects have a namespace. This string key is used for lookups, so consumers can tell if they handle a particular annotation or not. There are currently no methods for querying any abstract semantics of an Annotation subclass, but these are expected to expand in the future.

    See qiskit.circuit.annotation for a full discussion of the capabilities and use cases.

  • BoxOp instances (created by QuantumCircuit.box()) can now be annotated with custom Annotation instances. The equality of two boxes depends on the annotations being equal.

    Typically, this is achieved by passing a list of annotations as the sole positional argument when using QuantumCircuit.box() in context-manager form:

    from qiskit.circuit import annotation, QuantumCircuit
     
    class MyAnnotation(annotation.Annotation):
        namespace = "my.annotation"
     
        def __eq__(self, other):
            return isinstance(other, MyAnnotation)
     
    qc = QuantumCircuit()
    with qc.box([MyAnnotation()]):
        pass
  • The C API for Qiskit has been extended with support for building and interacting with quantum circuits.

  • Improve the synthesis of a multi-controlled U1Gate, so that it will not grow exponentially with the number of controls.

  • The BoxOp.duration field can now be an expr.Expr node with type Duration, just like Delay.duration. This includes stretches.

  • Adds an equivalence between an XXPlusYYGate and its decomposition into an RXXGate followed by an RYYGate to the equivalence library.

Primitives Features

OpenQASM Features

  • qasm3.dump() and qasm3.dumps() have a new annotation_handlers argument, which is used to provide instances of annotation.OpenQASM3Serializer to the OpenQASM 3 export process, which can serialize custom Annotation objects to OpenQASM 3.

  • When qiskit_qasm3_import>=0.6.0 is installed, qasm3.load() and qasm3.loads() have a new annotation_handlers argument, which is used to provide instances of annotation.OpenQASM3Serializer to the OpenQASM 3 import process, which can deserialize custom Annotation objects from OpenQASM 3. This support is currently limited to box statements, as this is the only place Qiskit can represent annotations in its data model.

QPY Features

  • QPY version 15 is released, including support for the new Annotation objects, with support from external serializers and deserializers. The format allows such serializers to be stateful, and safe places in the binary format are allocated for the custom state objects and custom annotation representations.

  • qpy.dump() and qpy.load() now have an optional annotation_factories argument, which is used to provide constructor functions of annotation.QPYSerializer objects to handle Annotation subclasses. These must be supplied by the user, similar to metadata_serializer, as in general, Qiskit cannot know about all possible externally-defined Annotation objects.

  • Added a new function get_qpy_version() to the qpy() module. This function will inspect a QPY file and retrieve the QPY format version used in the payload. The version is returned as an integer, which can be used for logging or debugging purposes. see #14201.

Synthesis Features

Transpiler Features

  • The function generate_preset_pass_manager() now generates a special pass manager when the basis set consists of Clifford+T gates only. Formally, a Clifford+T basis set must contain either a TGate or TdgGate (or both), and only Clifford gates in addition. The full list of supported Clifford gates can be obtained using get_clifford_gate_names().

    For example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
    from qiskit.quantum_info import get_clifford_gate_names
     
    basis_gates = get_clifford_gate_names() + ["t", "tdg"]
    pm = generate_preset_pass_manager(basis_gates=basis_gates)
     
    qc = QuantumCircuit(1)
    qc.rx(0.8, 0)
    qct = pm.run(qc)
    print(qct.count_ops())

    Previously, the generated pass manager was not able to handle the example above, as it was not able to decompose single-qubit rotation gates of type UGate into Clifford+T gates. Instead, the new pass manager uses the the Solovay-Kitaev decomposition to approximate single-qubit rotation gates using H, T and Tdg gates, and calls the BasisTranslator transpiler pass to further translate the gates into the target basis set. The new pass manager also has other changes as to enable a more efficient translation into Clifford+T gates.

    It is important to note that the specified Clifford+T basis gate set should be universal, or else transpilation might not succeed. While the gate set ["h", "t", "tdg"]``or even ``["h", "t"] is sufficient for universality, it is recommended to add more Clifford gates to the set if possible, as otherwise the translation might be less efficient. For example, not including S-gate might trigger decomposing S-gates into pairs of T-gates (that is, decomposing Clifford gates into non-Clifford gates, which might not be the desired behavior).

    Here is an additional slightly larger example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.circuit.library import QFTGate
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
     
    qc = QuantumCircuit(4)
    qc.append(QFTGate(4), [0, 1, 2, 3])
     
    basis_gates = ["cx", "s", "sdg", "h", "t", "tdg"]
    pm = generate_preset_pass_manager(basis_gates=basis_gates, optimization_level=2)
     
    qc = QuantumCircuit(4)
    qc.append(QFTGate(4), [0, 1, 2, 3])
     
    qct = pm.run(qc)
    print(qct.count_ops())
  • Added new high-level-synthesis plugin for synthesizing a HalfAdderGate:

    The HalfAdderSynthesisDefault has also been updated to follow the following sequence of half adder synthesizers: "HalfAdder.ripple_r25" when the number of qubits is 3\leq 3, "HalfAdder.ripple_c04" when 1 ancillary qubit is available, "HalfAdder.ripple_r25" in all remaining cases.

  • Added multiple high-level-synthesis plugins for synthesizing an MCXGate:

    The MCXSynthesisDefault has also been updated to follow the following sequence of MCX synthesizers: “mcx.2_clean_kg24”, “mcx.1_clean_kg24”, “mcx.n_clean_m15”, “mcx.n_dirty_i15”, “mcx.2_dirty_kg24”, “mcx.1_dirty_kg24” “mcx.1_clean_b95”, “mcx.noaux_v24”.

  • VF2PostLayout has been added at the end of the default optimization stage when using optimization level 3.

  • Support for creation and interaction with the Target have been added to the Qiskit C API.

    Here’s an example of how it is used:

    #include <qiskit.h>
    #include <math.h>
    
    // Create a Target with space for 2 qubit operations
    QkTarget *target = qk_target_new(2);
    
    // Create a Target Entry for a CRX Gate with fixed parameters
    double crx_params[1] = {3.14};
    QkTargetEntry *cx_entry = qk_target_entry_new_fixed(QkGate_CRX, crx_params);
    
    // Add mapping between (0, 1) and properties duration of 1.93e-9 and error 3.17e-10.
    uint32_t qargs[2] = {0, 1};
    qk_target_entry_add(entry, qargs, 2, 1.93e-9, 3.17e-10);
    // Add mapping between (1, 0) and properties duration of 1.27e-9 and no error.
    uint32_t rev_qargs[2] = {1, 0};
    qk_target_entry_add(entry, rev_qargs, 2, 1.27e-9, NAN);
    
    // Add CRX entry to the target.
    QkExitCode result_crx = qk_target_add_instruction(target, entry);
    
    // Add global Y gate entry to the target
    QkExitCode result_crx = qk_target_add_instruction(target, qk_target_entry_new(QkGate_Y));
  • Added a new OptimizeCliffordT transpiler optimization pass that merges pairs of consecutive T-gates into S-gates and pairs of consecutive Tdg-gates into Sdg-gates. This optimization is particularly effective for reducing T-count following Solovay-Kitaev decomposition, which produces multiple consecutive T or Tdg gates. For example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler.passes import SolovayKitaev, OptimizeCliffordT
     
    qc = QuantumCircuit(1)
    qc.rx(0.8, 0)
     
    # Run Solovay-Kitaev pass on qc
    transpiled = SolovayKitaev()(qc)
    print(transpiled.count_ops().get("t", 0) + transpiled.count_ops().get("tdg", 0))
     
    # Run Clifford+T optimization
    optimized = OptimizeCliffordT()(transpiled)
    print(optimized.count_ops().get("t", 0) + optimized.count_ops().get("tdg", 0))
  • Added the ContextAwareDynamicalDecoupling pass, which implements a context-aware dynamical decoupling based on Walsh-Hadamard sequences. The inserted delay sequences will be mutually orthogonal to sequences on neighboring qubits, and take into account control/target spectators of CX and ECR gates. See arXiv:2403.06852 for more information.

    Example:

    from qiskit.circuit.library import QFT
    from qiskit.transpiler import PassManager
    from qiskit.transpiler.passes import ALAPScheduleAnalysis, ContextAwareDynamicalDecoupling
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
    from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
     
    num_qubits = 10
    circuit = QFT(num_qubits)
    circuit.measure_all()
     
    target = FakeSherbrooke().target
     
    pm = generate_preset_pass_manager(optimization_level=2, target=target)
    dd = PassManager([
        ALAPScheduleAnalysis(target=target),
        ContextAwareDynamicalDecoupling(target=target),
    ])
     
    transpiled = pm.run(circuit)
    with_dd = dd.run(transpiled)
     
    print(with_dd.draw(idle_wires=False))
  • Adds DAGCircuit attributes num_stretches, num_captured_stretches, and num_declared_stretches.

  • Added a new unitary synthesis plugin CliffordUnitarySynthesis that attempts to syntesize a given unitary gate by checking if it can be represented by a Clifford, in which case it returns a circuit implementing this unitary and consisting only of Clifford gates.

    The plugin is invoked by the UnitarySynthesis transpiler pass when the parameter method is set to "clifford".

    In addition, the parameter plugin_config of UnitarySynthesis can be used to pass the following plugin-specific parameters:

    • min_qubits: the minumum number of qubits to consider (the default value is 1).
    • max_qubits: the maximum number of qubits to consider (the default value is 3).

    For example:

    import math
     
    from qiskit.circuit import QuantumCircuit
    from qiskit.circuit.library import UnitaryGate
    from qiskit.quantum_info import Operator
    from qiskit.transpiler.passes import UnitarySynthesis
     
    # clifford unitary over 2 qubits
    c2 = QuantumCircuit(2)
    c2.h(0)
    c2.rz(math.pi / 4, 1)
    c2.rz(math.pi / 4, 1)
    c2.sdg(1)
    uc2 = UnitaryGate(Operator(c2).data)
     
    # non-clifford unitary over 2 qubits
    n2 = QuantumCircuit(2)
    n2.h(0)
    n2.rz(math.pi / 4, 1)
    n2.sdg(1)
    un2 = UnitaryGate(Operator(n2).data)
     
    # quantum circuit with two unitary gates
    qc = QuantumCircuit(3)
    qc.append(uc2, [2, 1])
    qc.append(un2, [0, 2])
     
    transpiled = UnitarySynthesis(method="clifford")(qc)

    Executing the code above resynthesized the first unitary gate into Clifford gates, while the second gate remains unchanged.

    If we modify the example above as follows:

    config = {"min_qubits": 3}
    transpiled = UnitarySynthesis(method="clifford", plugin_config=config)(qc)

    then both unitary gates remain unchanged.

Visualization Features

  • Introduced custom styles for the dag_drawer() function. This allows you to pass a dictionary to the style parameter with custom attributes that changes the style of the DAG the function returns. For example:

    from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
    from qiskit.converters import circuit_to_dag
    from qiskit.visualization import dag_drawer
     
    q = QuantumRegister(3, 'q')
    c = ClassicalRegister(3, 'c')
    circ = QuantumCircuit(q, c)
    circ.h(q[0])
    circ.cx(q[0], q[1])
    circ.measure(q[0], c[0])
    circ.rz(0.5, q[1]).c_if(c, 2)
     
    dag = circuit_to_dag(circ)
     
    style = {
        "inputnodecolor": "pink",
        "outputnodecolor": "lightblue",
        "opnodecolor": "red",
    }
     
    dag_drawer(dag, style=style)
  • Box instructions whose vertical spans do not overlap are now rendered in the same vertical slice, when possible.

Upgrade Notes

  • The python-dateutil library is no longer a dependency of Qiskit. Since Qiskit 2.0.0 nothing in the library was actively using the library anymore and Qiskit didn’t actually depend on the library anymore. This release removes it from the dependency list so it is not automatically installed as a prerequisite anymore. If you were relying on Qiskit to install dateutil for you as a dependency you will now need to ensure you’re manually installing it (which is best practice for direct dependencies).

  • sympy is no longer a requirement for installing Qiskit. After the migration to a Rust based symbolic engine for the ParameterExpression class the uses of SymPy are isolated to some visualization utilities, the TemplateOptimization transpiler pass, ParameterExpression.sympify() (which is explicitly for SymPy interoperability) and SparsePauliOp.simplify() if using parameterized coefficients. This functionality is not the most commonly used so SymPy is now treated as an optional dependency and those functions will raise a MissingOptionalLibrary exception if they’re used and SymPy is not installed.

  • The dependency on symengine which was used for building ParameterExpression objects has been removed. It has been replaced by a internal symbolic engine and is no longer required for core functionality in Qiskit. The only exception is that symengine was embedded into QPY formats 10, 11, and 12 so it is still required if you are deserializing those formats. The dependency on symengine for qpy.load() was made explicitly optional in 2.0.0, but if you were previously relying on symengine getting installed by default for this functionality you will now need to manually install it to load the payload.

Circuits Upgrade Notes

  • The circuit definition of HalfAdderGate has been upgraded to use adder_ripple_r25(). To obtain the old behaviour, use the definition of adder_qft_d00() instead.

  • excitation_preserving() and ExcitationPreserving now use a single XXPlusYYGate where before it used an RXXGate followed by an RYYGate.

  • The control method of UnitaryGate now uses qs_decomposition() instead of Isometry for decomposition. This change reduces the number of CXGate by approximately 2x.

  • The definitions of certain standard gates have been updated according to the following principles:

    • When available, a definition using Clifford gates is preferred over one that includes non-Clifford gates.
    • When available, a definition using Clifford+T gates is preferred over one that includes UGate.
    • The use of PhaseGate is preferred over U1Gate.
    • The use of UGate is preferred over U2Gate and U3Gate.

    Crucially, the following invariant still holds: by recursively expanding gate definitions, any gate can be ultimately expressed using only the ["cx", "u"] basis.

  • Qiskit now uses its own, Rust-based symbolic expression library to implement the internals of ParameterExpression and Parameter. As this is a new implementation of the core symbolic math engine used for ParameterExpression there might be minor differences in the exact behavior of some functionality. It should always produce equivalent results for the documented API. Please open an issue if there are any problems with correctness found.

Synthesis Upgrade Notes

  • The synthesis function synth_mcx_1_clean_b95() now produces a circuit with fewer layers of wrappings.

  • The serialization format in for basic approximations in the Solovay Kitaev algorithms has been changed from .npy to another binary format, based on Rust’s serde and bincode. All routines loading basic approximations (such as generate_basic_approximations(), SolovayKitaevDecomposition.load_basic_approximations() or the initializer of SolovayKitaev) still support loading the legacy format. Any new file, however, will be stored in the new format. If you relied on the old format, downgrade Qiskit to <2.2 and store the required files.

  • The default values for SolovayKitaev (and related classes) have increased to depth=12 and reps=5. This is due to the underlying implementation now being in Rust, which allows us to increase the default precision, while still being significantly faster than the previous Python version.

Transpiler Upgrade Notes

  • The built-in layout plugins for the present pass managers will no longer contain their principal component (e.g. a SabreLayout instance for the “sabre” stage) if no coupling constraints are provided. Previously, the plugins would construct invalid instances of their layout passes, under an assumption that separate logic would prevent the passes from executing and raising exceptions.

    This should have no meaningful effect on the use of the preset pass managers or the plugins, since it was already never valid to call the passes in an invalid state .

Deprecation Notes

  • Support for running Qiskit with Python 3.9 has been deprecated and will be removed in the Qiskit 2.3.0 release. The 2.3.0 is the first release after Python 3.9 goes end of life and is no longer supported. [1] This means that starting in the 2.3.0 release you will need to upgrade the Python version you’re using to Python 3.9 or above.

    [1] https://devguide.python.org/versions/

C API Deprecations

  • Deprecated the macro QISKIT_VERSION_NUMERIC in favor of the direct replacement QISKIT_GET_VERSION_HEX. This macro can be used to generate a hexanumeric version format from decimal version numbers.

Circuits Deprecations

Security Issues

  • Fixed a security vulnerability in qpy.load() when loading payloads that use sympy to serialize ParameterExpression objects and other symbolic expressions. This potentially includes any QPY payload using QPY version < 10, and optionally 10, 11, and 12 depending on the symbolic encoding used in the serialization step (qpy.dump()).

Bug Fixes

  • Fixes a bug where style=plain did not show circuit labels for the nodes of the DAG.

  • Fixed a visualization bug in the text circuit drawer where post-transpilation control-flow operations could have their closing “bracket” rendered vertically out of order. See #14271 for more detail.

  • Fixed a bug in the UnitarySynthesis transpiler pass, where the pass ignored the synth_gates parameter and only synthesized the unitary gates. The pass now correctly synthesizes all gates specified in the synth_gates parameter. Fixed #14343.

  • Fixed edge-cases in the Makefile configuration for Windows, where the pre-defined environment variable OS did not match the output of the uname -s command.

  • Fixed the name of the OrGate, that was set to “and” instead of “or”, and could have possibly led to several problems.

  • Fixed an issue in the ConsolidateBlocks transpiler pass where it would fail to consolidate some blocks if the KAK gate selected (either directly or via the target) is supercontrolled and not CXGate. Fixed #14413

  • Fixed a bug in DAGCircuit that would cause output Var nodes to become input nodes during deepcopy and pickling.

  • Fixed an oversight in the Target class where setting a new value for the dt attribute and subsequently calling target.durations() would not show the updated dt value in the returned InstructionDurations object. This is now fixed through an invalidation of the internal target instruction durations cache in the dt setter.

  • Added missing repr support for Duration.

  • Added missing support for Python pickling of Duration. This was preventing parallel transpilation of circuits with delay() instructions that use duration expressions.

  • Fixed a problem in BasisTranslator transpiler pass, where the global phase of the DAG was not updated correctly. Fixed #14074.

  • Fixed a problem in HighLevelSynthesis transpiler pass that caused it to erroneously terminate early on certain circuits with control flow operations. Fixed #14338

  • Fixed the construction of Instantaneous Quantum Polynomial time (IQP) circuits in IQP and by iqp(). The previous implementation incorrectly used powers of the T\sqrt{T} gate instead of powers of the TT gate.

  • Fixed a bug in the PauliEvolutionSynthesisDefault and PauliEvolutionSynthesisRustiq plugins that modified the .synthesis attribute of the original circuit when setting preserve_order=False. The behavior of the plugins has been restored and the original circuit is now preserved throughout the transpilation pipeline.

  • Fixed a bug in meth qpy.load where it could fail to deserialize circuits whose parameters had been re-assigned to parameters with the same names. Fixed #13720, #13720, and #13720.

  • Fixed a bug in QPY (qiskit.qpy) where circuits containing gates of class MCMTGate would fail to serialize. See #13965.

  • SabreLayout and SabreSwap will no longer panic when applying the routing result to a circuit that uses expr.Var or Stretch objects in a nested control-flow scope.

  • SabreLayout will now correctly propagate a circuit’s name and metadata fields when performing as a joint layout and routing pass.

  • Fixed a problem in the SolovayKitaevSynthesis unitary synthesis plugin, where repeatedly running the plugin with different basis gates incorrectly reused the basis gates from the first run only. The problem was due to ignoring the basis gates when caching basic approximations, and is now fixed.

  • Fixed a problem in the SolovayKitaev transpiler pass where the pass could crash due to encountering a 180 degree rotation in the internal recursion, which was not handled correctly.

  • Fixed a problem in the SolovayKitaev transpiler pass where the generated approximation could have a phase that differs by π\pi from the correct value. This resulted due to the internal SO(3)SO(3) representation, which requires additional handling to obtain the correct sign of the qubit gate matrix. Fixed #9552

  • GenericBackendV2 now correctly allows BoxOp when control_flow=True.

  • When synthesizing an MCXGate gate with 3 controls, the synthesis functon synth_mcx_n_dirty_i15() used to require one auxiliary qubit, producing a circuit with 5 qubits (3 control, 1 target, and 1 auxiliary). However, the actual synthesis algorithm does not make use of this auxiliary qubit. This behavior is now fixed: the synthesized circuit is over 4 qubits (3 control and 1 target), allowing to apply the synthesis function in a slighty larger number of cases.

  • Circuits containing delays with stretches (see QuantumCircuit.add_stretch()) can now successfully compile using the preset pass-managers (transpile() and generate_preset_pass_manager()) when targetting a backend that has alignment constraints, for example IBM Quantum Eagle devices like ibm_sherbrooke.

  • The fallback error heuristic in VF2Layout and VF2PostLayout, used when there were no reported error rates, could previously assign errors greater than one, and have unpredictable effects on the resulting layout scores.

Other Notes

  • A new optional extra dependency variant qpy-compat was added. This target should be installed if you plan to load qpy files as it installs extra requirements used for loading QPY files using format versions < 13. If you are only using newer qpy format you do no need to install this. By default qpy.dump() only generates QPY >=13. You can install this new optional variant with pip install qiskit[qpy-compat]`

  • The relative weights of the “basic” and “lookahead” components of the SabreSwap and SabreLayout heuristics have been modified when extended-set tracking is active (as it always is in SabreLayout, and is by default in SabreSwap). The heuristic component relating to the distance between qubits in an individual gate in the front layer now no longer weakens proportional to the number of gates in the front layer; this behavior was a historical choice, but at large circuit sizes, has the accidental effect of causing the front layer to be nearly ignored, which is disastrous for efficiency.

    The resulting routing improvements should be most noticeable for circuits that can frequently be stratified into layers of more than 20 parallel two-qubit gates.

  • The Matplotlib circuit drawer will now insert less extraneous space inside the left edge when drawing BoxOp instances in circuits.


2.0.0rc1

Prelude

This release adds support for stretch variables to QuantumCircuit which are used to express relationships between instruction durations. For example, in order to ensure a sequence of gates between two barriers will be left-aligned, whatever their actual durations may be, we can do the following:

from qiskit import QuantumCircuit
from numpy import pi
 
qc = QuantumCircuit(5)
qc.barrier()
qc.cx(0, 1)
qc.u(pi/4, 0, pi/2, 2)
qc.cx(3, 4)
 
a = qc.add_stretch("a")
b = qc.add_stretch("b")
c = qc.add_stretch("c")
 
# Use the stretches as Delay duration.
qc.delay(a, [0, 1])
qc.delay(b, 2)
qc.delay(c, [3, 4])
qc.barrier()

For additional context and examples, refer to the OpenQASM 3 language specification.

New Features

  • Support for the Linux aarch64 platform has been promoted to tier 1 support as documented in:

    /guides/install-qiskit#operating-system-support

    from its previous support level of tier 2 in the 1.x release series.

  • Introduced a C API to build and interact with sparse observables. While the API surface in this release is fairly small, just covering the SparseObservable class this new API lays the foundation for Qiskit’s C interface.

    The detailed syntax and more information is available under the C API docs, but a minimal example to construct the 100-qubit observable X0 Y1 Z1 is:

    #include <complex.h>
    #include <qiskit.h>
    #include <stdint.h>
    #include <stdio.h>
     
    int main(int argc, char *argv[]) {
        // build a 100-qubit empty observable
        uint32_t num_qubits = 100;
        QkObs *obs = qk_obs_zero(num_qubits);
     
        // add the term 2 * (X0 Y1 Z2) to the observable
        complex double coeff = 2;
        QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
        uint32_t indices[3] = {0, 1, 2};
        QkObsTerm term = {coeff, 3, bit_terms, indices, num_qubits};
        qk_obs_add_term(obs, &term);
     
        // print some properties
        printf("num_qubits: %u\n", qk_obs_num_qubits(obs));
        printf("num_terms: %lu\n", qk_obs_num_terms(obs));
     
        // free the memory allocated for the observable
        qk_obs_free(obs);
     
        return 0;
    }

Circuits Features

  • Added a new get_control_flow_name_mapping() convenience function that returns a mapping of Qiskit’s control-flow operation names to their corresponding class. Example usage:

    from qiskit.circuit import get_control_flow_name_mapping
     
    ctrl_flow_name_map = get_control_flow_name_mapping()
    if_else_object = ctrl_flow_name_map["if_else"]
     
    print(if_else_object)
    <class 'qiskit.circuit.controlflow.if_else.IfElseOp'>
  • Added a new method, QuantumCircuit.estimate_duration(), to compute the estimated duration of a scheduled circuit output from the transpiler. This should be used if you need an estimate of the full circuit duration instead of the deprecated QuantumCircuit.duration attribute.

  • The new BitFlipOracleGate and PhaseOracleGate have the same interface as PhaseOracle (except the evaluate_bitstring method). Bit-flip oracle gate synthesizes a bit flip oracle instead of a phase flip oracle, meaning it acts on one additional qubit and can be seen as applying a controlled X operation, where the control is the value of the expression encoded by the oracle.

    from qiskit import QuantumCircuit
    from qiskit.circuit.library.bit_flip_oracle import BitFlipOracleGate
    from qiskit.circuit.library.phase_oracle import PhaseOracleGate
    qc = QuantumCircuit(5)
    bool_expr = "(x0 & x1 | ~x2) & x4"
    oracle = BitFlipOracleGate(bool_expr)
    qc.compose(oracle, inplace=True)
    print(qc)
         ┌─────────────────────┐
    q_0: ┤0                    ├
         │                     │
    q_1: ┤1                    ├
         │                     │
    q_2: ┤2 (x0 & x1 | ~x2)... ├
         │                     │
    q_3: ┤3                    ├
         │                     │
    q_4: ┤4                    ├
         └─────────────────────┘
    print(qc.decompose())
    q_0: ──o────■────■──
           │    │    │  
    q_1: ──┼────o────■──
           │    │    │  
    q_2: ──o────o────┼──
           │    │    │  
    q_3: ──■────■────■──
         ┌─┴─┐┌─┴─┐┌─┴─┐
    q_4: ┤ X ├┤ X ├┤ X ├
         └───┘└───┘└───┘
    qc = QuantumCircuit(5)
    bool_expr = "(x0 & x1 | ~x2) & x4"
    oracle = PhaseOracleGate(bool_expr)
    qc.compose(oracle, inplace=True)
    print(qc)
         ┌───────────────┐
    q_0: ┤0              ├
         │               │
    q_1: ┤1              ├
         │  Phase oracle │
    q_2: ┤2              ├
         │               │
    q_3: ┤3              ├
         └───────────────┘
    q_4: ─────────────────
    print(qc.decompose())
    q_0: ─o──■──■─
          │  │  │ 
    q_1: ─┼──o──■─
          │  │  │ 
    q_2: ─o──o──┼─
          │  │  │ 
    q_3: ─■──■──■─
    
    q_4: ─────────
  • Added a new argument approximation_degree to CommutationChecker.commute() and CommutationChecker.commute_nodes(), which allows to set the approximation threshold for when gates are said to commute. See the docstring of CommutationChecker for more detail.

  • A new method, QuantumCircuit.noop(), allows qubits to be marked as explicitly used within a control-flow builder scope, without adding a corresponding operation to them.

  • The classical realtime-expressions module qiskit.circuit.classical can now represent constant expressions. The Expr class now has a bool const attribute which indicates the expression’s const-ness. This allows us to enforce that expressions in certain contexts must be possible to evaluate at compile time.

    All Var expressions are considered to be non-const, while all Value expressions are const.

    An expression comprised only of other const expressions is also const:

    from qiskit.circuit.classical import expr
     
    assert expr.bit_and(5, 6).const

    An expression that contains any non-const expression is non-const:

    from qiskit.circuit.classical import expr, types
     
    assert not expr.bit_and(5, expr.Var.new("a", types.Uint(5)).const
  • The classical realtime-expressions module qiskit.circuit.classical can now represent duration using the new types Duration.

    The module qiskit.circuit also has a new Duration class which can be used as a literal value within classical expressions.

    The lift() function can be used to create a value expression from a Duration instance:

    from qiskit.circuit import Duration
    from qiskit.circuit.classical import expr
     
    expr.lift(Duration.dt(1000))
    # Value(Duration.dt(1000), Duration())
  • The classical realtime-expressions module qiskit.circuit.classical can now represent IEEE-754 double-precision floating point values using the new type Float.

    The lift() function can be used to create a value expression from a Python float:

    from qiskit.circuit.classical import expr
     
    expr.lift(5.0)
    # >>> Value(5.0, Float())

    This type is intended primarily for use in timing-related (duration and stretch) expressions. It is not compatible with bitwise or logical operations, though it can be used (dangerously) with these if first explicitly cast to something else.

  • Reduce the number of two-qubit gates when decomposing some multi-controlled single-qubit unitary gates. For example,

    • For multi-controlled YGate on 10 qubits, we reduce the CXGate count by 56%,
    • For multi-controlled HGate on 10 qubits, we reduce the CXGate count by 56%,
    • For multi-controlled SXGate and SXdgGate on 10 qubits, we reduce the CXGate count by 80%,
    • For multi-controlled UGate on 10 qubits, we reduce the CXGate count by 31%.
  • The classical realtime-expressions module qiskit.circuit.classical can now represent arithmetic operations add(), sub(), mul(), and div() on numeric and timing operands.

    For example:

    from qiskit.circuit import QuantumCircuit, ClassicalRegister, Duration
    from qiskit.circuit.classical import expr
     
    # Subtract two integers
    cr = ClassicalRegister(4, "cr")
    qc = QuantumCircuit(cr)
    with qc.if_test(expr.equal(expr.sub(cr, 2), 3)):
        pass
     
    # Multiply a Duration by a Float
    with qc.if_test(expr.less(expr.mul(Duration.dt(200), 2.0), Duration.ns(500))):
        pass
     
    # Divide a Duration by a Duration to get a Float
    with qc.if_test(expr.greater(expr.div(Duration.dt(200), Duration.dt(400)), 0.5)):
        pass

    For additional examples, see the module-level documentation linked above.

  • UCGate now includes a mux_simp boolean attribute that enables the search for simplifications of Carvalho et al., implemented in _simplify(). This optimization, enabled by default, identifies and removes unnecessary controls from the multiplexer, reducing the number of CX gates and circuit depth, especially in separable state preparation with Initialize.

  • The PauliEvolutionGate now natively supports SparseObservables as input. This efficiently allows to handle evolution under projectors, which are implemented as controls of a phase rotation and require less gates than explicitly expanding the projector in terms of Paulis. For example:

    from qiskit.circuit.library import PauliEvolutionGate
    from qiskit.quantum_info import SparseObservable
     
    obs = SparseObservable("001")
    evo_proj = PauliEvolutionGate(obs, time=1)
    print(evo_proj.definition.draw())
  • A new expression node Stretch has been added to the classical expression system to represent stretch variables. To create a new stretch` variable, you can use :meth:`.QuantumCircuit.add_stretch`. The resulting expression is a constant expression of type :class:`~.types.Duration`, which can currently be used as the ``duration argument of a delay().

    The Stretch expression is most similar to the existing Var expression used to represent classical variables in a circuit, except it is constant and is always of type Duration. It can be used in other expressions (e.g. you can multiply it by a numeric constant) and QuantumCircuit provides full scoping support for it (e.g. it can be captured by or declared within a control flow scope).

  • Added Gate versions of the single-register arithmetic gates, which allow the compiler to perform high-level optimizations compared to their QuantumCircuit variants. These are:

Primitives Features

  • Added to_bool_array() method to BitArray class that returns the bit array as a boolean NumPy array. The order argument can be used to specify the endianness of the output array.

  • Expanded the docstring of ObservablesArray.tolist() to make it clear it might return a scalar in the case the observables array is of dimension 0.

Providers Features

  • Added the ability to set the dt property of GenericBackendV2 in the class initializer with a new dt argument. Example usage:

    from qiskit.providers.fake_provider import GenericBackendV2
    backend = GenericBackendV2(
      num_qubits=5, 
      basis_gates=["cx", "id", "rz", "sx", "x"],
      dt= 2.22*e-10, 
      seed=42)

Quantum Information Features

  • Added SparseObservable.to_sparse_list() to obtain a sparse list representation of a SparseObservable. For example:

    from qiskit.quantum_info import SparseObservable
     
    obs = SparseObservable.from_list([("+II", 1), ("-II", 1)])
    print(obs.to_sparse_list())  # [("+", [2], 1), ("-", [2], 1)]
  • Added SparseObservable.as_paulis() to express a sparse observable in terms of Paulis only by expanding all projectors. For example:

    from qiskit.quantum_info import SparseObservable
     
    obs = SparseObservable("+-")
    obs_paulis = obs.as_paulis()  # 1/4 ( II + XI - IX - XX )
  • Support construction of a SparsePauliOp from a SparseObservable via the new method SparsePauliOp.from_sparse_observable. It is important to remember that SparseObservables can efficiently represent projectors, which require an exponential number of terms in the SparsePauliOp.

  • SparseObservable now supports operator composition using the compose() method, similar to other quantum_info classes. This is analagous to matrix multiplication, though the method is entirely matrix free.

  • SparseObservable.BitTerm as a new attribute, label, which contains the single-character Python string used to represent the term in string labels.

  • The method StabilizerState.expectation_value() can now accept an operator of type SparsePauliOp.

Synthesis Features

Transpiler Features

  • Added support for two-qubit fractional basis gates, such as RZZGate, to the ConsolidateBlocks transpiler pass. The decomposition itself is done using the TwoQubitControlledUDecomposer.

    For example:

    from qiskit import QuantumCircuit
    from qiskit.transpiler import generate_preset_pass_manager
    from qiskit.transpiler.passes import ConsolidateBlocks
     
    qc = QuantumCircuit(2)
    qc.rzz(0.1, 0, 1)
    qc.rzz(0.2, 0, 1)
    consolidate_pass = ConsolidateBlocks(basis_gates=["rz", "rzz", "sx", "x", "rx"])
    block = consolidate_pass(qc)  # consolidate the circuit into a single unitary block
    block.draw(output='mpl')
     
    pm = generate_preset_pass_manager(
        optimization_level=2, basis_gates=["rz", "rzz", "sx", "x", "rx"]
    )
    tqc = pm.run(qc)  # synthesizing the circuit into basis gates
    tqc.draw(output='mpl')
  • Added support for two-qubit fractional basis gates, such as RZZGate, to the UnitarySynthesis transpiler pass. The decomposition is done using the TwoQubitControlledUDecomposer, and supports both standard and custom basis gates.

    For example:

    from qiskit import QuantumCircuit
    from qiskit.quantum_info import random_unitary
    from qiskit.transpiler.passes import UnitarySynthesis
    from qiskit.converters import circuit_to_dag, dag_to_circuit
     
    unitary = random_unitary(4, seed=1)
    qc = QuantumCircuit(2)
    qc.append(unitary, [0, 1])
    dag = circuit_to_dag(qc)
    circ = UnitarySynthesis(basis_gates=['rzz', 'rx', 'rz']).run(dag)
    dag_to_circuit(circ).draw(output='mpl')
  • Added a new transpiler pass, LightCone that is used to get the lightcone of a circuit when measuring a subset of qubits or a specific Pauli string. For example if you had a circuit like:

    _images/release_notes-3.png

    running the pass would eliminate the gates that do not affect the outcome.

    from qiskit.transpiler.passes.optimization.light_cone import LightCone
    from qiskit.transpiler.passmanager import PassManager
    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(3,1)
    qc.h(range(3))
    qc.cx(0,1)
    qc.cx(2,1)
    qc.h(range(3))
    qc.measure(0,0)
     
    pm = PassManager([LightCone()])
    new_circuit = pm.run(qc)
    new_circuit.draw("mpl")
    _images/release_notes-4.png
  • Added a new argument max_block_width to the class BlockCollector and to the transpiler passes CollectLinearFunctions and CollectCliffords. This argument allows to restrict the maximum number of qubits over which a block of nodes is defined.

    For example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler.passes import CollectLinearFunctions
     
    qc = QuantumCircuit(5)
    qc.h(0)
    qc.cx(0, 1)
    qc.cx(1, 2)
    qc.cx(2, 3)
    qc.cx(3, 4)
     
    # Collects all CX-gates into a single block 
    qc1 = CollectLinearFunctions()(qc)
    qc1.draw(output='mpl')
     
    # Collects CX-gates into two blocks of width 3 
    qc2 = CollectLinearFunctions(max_block_width=3)(qc)
    qc2.draw(output='mpl')
  • Added a new option, collect_from_back, to CollectMultiQBlocks. When set to True, the blocks are collected in the reverse direction, from the outputs towards the inputs of the circuit. The blocks are still reported following the normal topological order. This leads to an additional flexibility provided by the pass, and additional optimization opportunities when combined with a circuit resynthesis method.

  • Added a new argument approximation_degree to CommutationAnalysis, which allows setting the approximation threshold for when gates are said to commute. See the class docstring for more information.

  • A new transpiler pass, ContractIdleWiresInControlFlow, is available from qiskit.transpiler.passes. This pass removes qubits from control-flow blocks if the semantics allow this, and the qubit is idle throughout the control-flow operation. Previously, the routing stage of the preset pass managers might have done this as an accidental side-effect of how they worked, but the behavior is now more properly placed in an optimization pass.

  • A new routing plugin stage is added, called "default". In Qiskit 2.0, this is simply an alias for the previous default "sabre". The underlying default algorithm may change over the course of the Qiskit 2.x series for some or all targets, but you can always set routing_method="sabre" explicitly to maintain the current behavior.

  • A new translation plugin stage is added, called "default". In Qiskit 2.0, this is simply an alias for the previous default "translator". The underlying default algorithm may change over the course of the Qiskit 2.x series for some or all targets, but you can always set translation_method="translator" explicitly to maintain the current behavior.

  • The HighLevelSynthesis transpiler pass now synthesizes objects of type AnnotatedOperation via the plugin interface.

  • PassManager.run() now accepts a property_set argument, which can be set to a Mapping-like object to provide the initial values of the pipeline’s PropertySet. This can be used to recommence a partially applied compilation, or to reuse certain analysis from a prior compilation in a new place.

  • The scheduling passes PadDelay and PadDynamicalDecoupling has new arguments on their constructors target and durations these are used to specify the Target or InstructionDurations respectively. This is required to provide the durations for the instructions in the circuit when this pass is run.

  • Added a new method to the Target class has a new method, seconds_to_dt(). This is used to translate a duration in seconds to a number of discretized time steps of the system time resolution specified in the Target.dt attribute. This is typically useful for converting the InstructionProperties.duration value to units of dt.

  • The synth_cnot_depth_line_kms() pass was ported into rust, with preliminary benchmarks pointing at a factor of 20x speedup.

  • The synth_cx_cz_depth_line_my() pass was ported into rust, with preliminary benchmarks pointing at a factor of 70x speedup.

  • The Split2QUnitaries transpiler pass has been upgraded to handle the case where the unitary in consideration can be written as a SWAP gate and two 1-qubit gates. In this case, it splits the unitary and also applies virtual swapping similar to what is done in ElidePermutations. This functionality can be controlled with a new argument, split_swap, on the constructor of :class`.Split2QUnitaries` which can be used to disable splitting swap equivalent gates.

Misc. Features

  • qiskit.utils now contains utilities to provide better control and inspection of Qiskit’s multiprocessing parallelization settings. In particular, one can now use should_run_in_parallel() to query whether parallel_map() (and pass managers) will launch subprocesses for suitable inputs, and use the context manager should_run_in_parallel.override() to temporarily override most system and user configuration around this decision.

    An additional function, default_num_processes() reads the default maximum number of subprocesses that Qiskit will use for process-based parallelism.

  • A new environment variable, QISKIT_IGNORE_USER_SETTINGS, now controls whether to read the user settings file on import qiskit. If set to the string true, the settings file will not be read. This is useful for isolating certain instances of Qiskit from the system environment, such as for testing.

Upgrade Notes

  • Qiskit no longer supports Linux i686 and 32 bit Windows. Starting in Qiskit 2.0 a 64 bit platform is needed to run Qiskit. The user base for 32bit architectures is relatively small and as Qiskit continues to focus on improving performance to handle the increased scale in the complexity of quantum computing hardware maintaining support for 32 bit platforms is proving increasingly difficult. This coupled with the larger scientific/numeric Python community’s trend away from 32 bit platform support maintaining support for 32bit platforms is no longer viable. Qiskit 1.x will still continue to support 32 bit platforms, but starting in this 2.0.0 release Qiskit no longer supports these platforms, will not publish pre-compiled binaries for these platforms any longer, and there is no longer any guarantee of being able to build from source on 32 bit platforms.

  • The minimum supported Rust version for building Qiskit from source is now 1.79. This has been raised from the previous minimum supported Rust version of 1.70 in the Qiskit 1.x release series.

  • Increased the minimum threshold for when gates are assumed to be the identity in RemoveIdentityEquivalent from machine epsilon to 1e-12 to account for round-off errors in the fidelity calculation and for consistency with the other classes, such as CommutationAnalysis and TwoQubitWeylDecomposition.

  • Updated the metric used to check commutations in CommutationChecker. Two gates are assumed to commute if the average gate fidelity of the commutation is above 1 - 1e-12. This value is chosen to account for round-off errors in the fidelity calculation and for consistency with RemoveIdentityEquivalent and TwoQubitWeylDecomposition. See the class docstring for more information.

  • BlueprintCircuit.copy_empty_like() now returns an empty QuantumCircuit with the same number of qubits/clbits and the same metadata as the original circuit, instead of a BlueprintCircuit. This change is to mitigate unexpected behavior where dealing with an “empty” copy of a blueprint circuit would re-build the circuit data. Note that BlueprintCircuit.copy() still returns a BlueprintCircuit. Related: #13535

  • Qiskit Pulse has been completely removed in this release, following its deprecation in Qiskit 1.3. This include all pulse module files, pulse visualization functionality, support for ScheduleBlock and pulse-gate serialization/deserialization capability in QPY, calibrations management in QuantumCircuit, Target and DAGCircuit and pulse-based fake backends. For more details about the removed components related to pulse, see the corresponding sections below.

    Note that Pulse migration to Qiskit Dynamics, as was the initial plan following the deprecation of Pulse, has been put on hold due to Qiskit Dynamics development priorities. Users wanting to use Qiskit Pulse as a frontend to supporting backends or in other uses-cases can still use it via Qiskit versions prior to 2.0, which include Pulse functionality.

  • The functions sequence and schedule from the compiler module have been removed following their deprecation in Qiskit 1.3. They relied on being able to translate circuits to pulse using backend definitions, a capability that is no longer present. For this reason they have been removed with no proposed alternative. Note that this removals relate to the Pulse package which is also being removed in Qiskit 2.0.

Circuits Upgrade Notes

  • Bit and Register as well as their subclassess are no longer guaranteed to be comparable using is checks, due to conversions to and from Python which may re-allocate each instance exposed to Python.

  • Bit and Register (and their subclasses) can no longer be subclassed. This was never intended to be supported behavior, and doing so would cause unspecified behavior in Qiskit. It is no longer possible to do this as an implementation detail of the classes.

  • It is no longer possible to create instances of the base Bit and Register classes is no longer possible. Directly instantiating these classes was clearly documented as something that was not supported and being able to do it was was just an implementation artifact of how the class heirarchy in previous releases. Starting in this release it is no longer possible to do this.

  • The PhaseOracle no longer relies on the tweedledum library but might not be synthesized as effectively as before. BitFlipOracleGate was added as an alternative to directly synthesizing BooleanExpression, as this class is removed in Qiskit 2.0. A PhaseOracleGate was added and will replace PhaseOracle in Qiskit 3.0.

    The interface of PhaseOracle was simplified; it no longer accepts a synthesizer parameter, and the expression parameter can only be a string; ClassicalElement has been deprecated in Qiskit 1.4.

    PhaseOracle is used exactly as before:

    from qiskit.circuit.library.phase_oracle import PhaseOracle
    bool_expr = "(x0 & x1 | ~x2) & x4"
    oracle = PhaseOracle(bool_expr)
    print(oracle)
    q_0: ─o──■──■─
          │  │  │ 
    q_1: ─┼──o──■─
          │  │  │ 
    q_2: ─o──o──┼─
          │  │  │ 
    q_3: ─■──■──■─
  • The method QuantumCircuit.measure_active() has changed the name of the classical register it creates, as the previous name conflicted with an OpenQASM reserved word. Instead of measure, it is now called meas, aligning with the register name used by measure_all().

  • DAGCircuit.control_flow_op_nodes() now always returns a list, even if empty. Previously, it returned None if empty, and never returned the empty list, which required special handling. If you need to explicitly test for emptiness in both Qiskit 1.x and 2.x, you can do:

    control_flow_nodes = dag.control_flow_op_nodes()
    if not control_flow_nodes:
        # There are no control-flow nodes.
        pass
  • The generic control method for gates now avoids attempting to translate gates into a supported basis, if the gate is already supported. This can slightly change the synthesis of the controlled gate, although it should not increase the two-qubit gate count.

  • Converting a quantum circuit to a gate with converters.circuit_to_instruction() fails properly when given circuit contains control flow instructions.

  • The internal function qiskit.circuit.add_control.add_control was removed, as it is not part of the public API. It had fragile preconditions to uphold and was a common source of bugs. Uses of add_control(SomeGate(...), ...) should change to SomeGate(...).control(...) using Gate.control() instead, which is far safer.

  • The ParameterExpression.sympify() method can now raise a MissingOptionalLibrary exception if sympy is not installed. In the Qiskit 1.x releases sympy was always guaranteed to be installed, but starting in 2.x this is no longer a hard requirement and may only be needed if you are using this method. As this functionality explicitly requires sympy you will need to ensure you have sympy installed to use the method.

  • The deprecated argument .dag for DAGNode and its subclasses DAGOpNode, DAGOutNode, and DAGInNode. dag was optional parameter when constructing these objects that was unused and ignored since the 1.3.0 release. The parameter was deprecated in 1.4 and has now been is removed.

  • The QuantumCircuit methods:

    • cast
    • cbit_argument_conversion
    • cls_instances
    • cls_prefix
    • qbit_argument_conversion

    have been removed, following their deprecation in Qiskit 1.2. These methods were internal helper functions, and never intended to be public API. No replacement is provided.

  • The deprecated attributes for Instruction and Gate: duration and unit have been removed. This includes setting the unit or duration arguments for any qiskit.circuit.Instruction or subclass. These attributes were deprecated in Qiskit 1.3.0 and were used to attach a custom execution duration and unit for that duration to an individual instruction. However, the source of truth of the duration of a gate is the BackendV2 Target which contains the duration for each instruction supported on the backend. The duration of an instruction is not something that’s typically user adjustable and is an immutable property of the backend. If you were previously using this capability to experiment with different durations for gates you can mutate the InstructionProperties.duration field in a given Target to set a custom duration for an instruction on a backend (the unit is always in seconds in the Target).

  • The deprecated attribute for qiskit.circuit.Instruction and Gate: condition has been removed. These attributes were deprecated in the 1.3.0 release. This functionality has been superseded by the IfElseOp class which can be used to describe a classical condition in a circuit.

  • The deprecated methods for Instruction and Gate: c_if and condition_bits have been removed. These methods were deprecated in the 1.3.0 release. This functionality has been superseded by the IfElseOp class which can be used to describe a classical condition in a circuit. For example, a circuit previously using Instruction.c_if() like:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.x(0).c_if(0, 1)
    qc.z(1.c_if(1, 0)
    qc.measure(0, 0)
    qc.measure(1, 1)

    can be rewritten as:

    qc = QuantumCircuit(2, 2)
    qc.h(0)
    with expected.if_test((expected.clbits[0], True)):
        qc.x(0)
    with expected.if_test((expected.clbits[1], False)):
        qc.z(1)
    qc.measure(0, 0)
    qc.measure(1, 1)
  • The deprecated method InstructionSet.c_if has been removed. This method was deprecated in the 1.3.0 release. This functionality has been superseded by the IfElseOp class which can be used to describe a classical condition in a circuit.

  • As part of Pulse removal in Qiskit 2.0, the calibrations property has been removed from the QuantumCircuit, DAGCircuit and DAGDependency classes. In addition, the method has_calibration_for has been removed from the QuantumCircuit and DAGCircuit classes and add_calibration has been removed from QuantumCircuit.

  • The classical_function was removed. This module was dependent on the tweedledum library which is no longer compatible with newer versions of Python. As an alternative, the PhaseOracleGate and BitFlipOracleGate classes can be used to generate circuits from boolean expressions.

  • The internal representation of UnitaryGate has changed when they’re added to a QuantumCircuit. The object stored in the circuit will not necessarily share a common reference to the object added to the circuit anymore. This was never guaranteed to be the case and mutating the UnitaryGate object directly or by reference was unsound and always likely to corrupt the circuit, especially if you changed the matrix. If you need to mutate an element in the circuit (which is not recommended as it’s inefficient and error prone) you should ensure that you do something like:

    from qiskit.circuit import QuantumCircuit
    from qiskit.quantum_info import random_unitary
    from qiskit.circuit.library import UnitaryGate
    import numpy as np
     
    qc = QuantumCircuit(2)
    qc.unitary(np.eye(2, dtype=complex))
     
    new_op = UnitaryGate(random_unitary(2))
    qc.data[0] = qc.data[0].replace(operation=new_op)

    This also applies to DAGCircuit too, but you can use DAGCircuit.substitute_node() instead.

  • The CircuitInstruction.params attribute for a CircuitInstruction that contains an UnitaryGate for its operation will no longer contain the underlying unitary matrix for the gate. This is because the internal representation of the gate no longer treats the matrix object as a parameter. If you need to access the matrix of the gate you can do this either via the CircuitInstruction.matrix or the UnitaryGate.params field of the CircuitInstruction.operation.

Primitives Upgrade Notes

  • As a consequence of the removal of the BackendV1 model, the BackendSamplerV2 and BackendEstimatorV2 classes no longer accept inputs of type BackendV1 in their backend input argument.

  • Primitive V1 implementations and V1-exclusive non-versioned type aliases, deprecated in Qiskit 1.2, have been removed. These interfaces have been superseded by their V2 counterparts. The removal includes the following classes implementing V1 interfaces:

    As well as the following non-versioned type aliases:

    This removal does NOT affect the explicitly-versioned BaseEstimatorV1 and BaseSamplerV1 abstract interface definitions or related result and job classes, which have been kept to maintain backward compatibitily.

    In addition, the following utility functions have been removed. These functions were only used in Primitive V1 implementations:

Providers Upgrade Notes

  • The configuration method of BasicSimulator has been removed following its deprecation in Qiskit 1.3. This method returned a BackendConfiguration instance, and this class was part of the deprecated BackendV1 workflow. The individual configuration elements can be retrieved directly from the backend or from the contained Target instance (backend.target).

  • The run_experiment method of BasicSimulator has also been removed. This method took an instance of the QasmQobjExperiment class as an input argument, and the class has been deprecated since Qiskit 1.2.

  • The BackendV1 model has been removed following its deprecation in Qiskit 1.2.0. This includes the BackendV1 class as well as related modules and utils, as they have been superseded by the BackendV2 model. The list of removed items includes:

    • BackendV1 class: the core of the removed model

    • All elements in qiskit/providers/models, as they were used to represent components of the BackendV1 model:

      • BackendConfiguration
      • BackendProperties
      • BackendStatus
      • QasmBackendConfiguration
      • PulseBackendConfiguration
      • UchannelLO
      • GateConfig
      • PulseDefaults
      • PulseQobjDef
      • Command
      • GateProperties
      • Nduv
      • JobStatus: This class has been superseded by the more widely used JobStatus
      • PulseDefaults
    • BackendV2Converter class: used to convert from BackendV1 to BackendV2

    • convert_to_target function: used to build a Target instance from legacy BackendV1 components (such as BackendConfiguration or BackendProperties)

    • BackendPropertyError and BackendConfigurationError: exceptions linked to removed classes

  • The BasicSimulator backend no longer can simulate classical control flow. It only supported using .c_if()/.condition for modeling control flow, as this has now been removed from Qiskit’s data model it no longer can support control flow.

  • All fake backend classes based on the deprecated BackendV1 have been removed from the providers.fake_provider module. These classes have been deprecated since Qiskit 1.2 and were part of the deprecated BackendV1 workflow. Their use in tests has been replaced with the GenericBackendV2 class, which allows to create custom instances of BackendV2 that implement a simulated BackendV2.run(). The removal affects:

    • Base classes:

      • FakeBackend
      • FakePulseBackend
      • FakeQasmBackend
    • Fake backends for special testing purposes: * Fake1Q * FakeOpenPulse2Q * FakeOpenPulse3Q

    • Legacy fake backends:

      • Fake5QV1
      • Fake20QV1
      • Fake7QPulseV1
      • Fake27QPulseV1
      • Fake127QPulseV1
  • As part of Pulse removal in Qiskit 2.0, the following methods have been removed:

    • qiskit.providers.BackendV2.instruction_schedule_map
    • qiskit.providers.BackendV2.drive_channel
    • qiskit.providers.BackendV2.measure_channel
    • qiskit.providers.BackendV2.acquire_channel
    • qiskit.providers.BackendV2.control_channel
  • As part of Pulse removal in Qiskit 2.0, pulse support has been removed from GenericBackendV2. This includes the ability to initialize the backend with custom calibrations (calibrate_instructions argument) and pulse channels information.

  • Removed the abstract base classes Provider and ProviderV1 which have been deprecated since Qiskit 1.1.0. The abstraction provided by these interface definitions were not providing a huge value, solely just the attributes name, backends, and a get_backend() method. A _provider_, as a concept, will continue existing as a collection of backends. If you’re implementing a provider currently you can adjust your code by simply removing ProviderV1 as the parent class of your implementation. As part of this you probably would want to add an implementation of get_backend for backwards compatibility. For example:

    def get_backend(self, name=None, **kwargs):
      backend = self.backends(name, **kwargs)
      if len(backends) > 1:
        raise QiskitBackendNotFoundError("More than one backend matches the criteria")
      if not backends:
        raise QiskitBackendNotFoundError("No backend matches the criteria")
      return backends[0]

QPY Upgrade Notes

  • The qpy.load() function can now raise a MissingOptionalLibrary exception if a QPY v10, v11, or v12 payload is passed in that is using symengine symbolic expressions and symengine is not installed. Or if sympy is not installed for any other QPY payload < v13. In the Qiskit 1.x releases symengine and sympy were always guaranteed to be installed, but starting in 2.x this is no longer a hard requirement and may only be needed if you’re deserializing a QPY file that was generated using symengine. Parsing these QPY payloads requires symengine (0.11.0 or 0.13.0) as it’s usage is baked into the format specification for QPY v10, v11, and v12 so if the payload requires it there is no option but to install a compatible version of symengine. Similarly, sympy was was used for ParameterExpression encoding for all QPY versions from 1 through 12.

  • The minimum QPY compatibility version, QPY_COMPATIBILITY_VERSION, has been raised to 13 from 10 in the 1.x release. This version controls the minimum version of QPY that can be emitted by the qpy.dump() function. This means qpy.dump() can only emit QPY v13 and v14 in this release. QPY v13 is still compatible with Qiskit 1.3.x and 1.4.x which means payloads can be generated in Qiskit 2.x that can be loaded with the Qiskit 1.x release series still.

    This change was necessary as QPY versions 10 through 12 requires either the sympy and symengine libraries to generate a serialization for ParameterExpression objects, but in Qiskit 2.x neither library is required for the ParameterExpression object.

  • With the removal of Pulse in Qiskit 2.0, support for serializing ScheduleBlock programs via the qiskit.qpy.dump() function has been removed. Users can still load payloads containing pulse gates using the qiskit.qpy.load() function, however those will be treated as opaque custom instructions. Loading ScheduleBlock payloads is not supported anymore and will result with a QpyError exception.

Synthesis Upgrade Notes

  • The plugins for LinearFunction no longer raise an error if another object than LinearFunction is passed into the run method. Instead, None is returned, which is consistent with the other plugins. If you relied on this error being raised, you can manually perform an instance-check.

  • The atomic_evolution argument to ProductFormula (and its subclasses QDrift, LieTrotter and SuzukiTrotter ) has a new function signature. Rather than taking some Pauli operator and time coefficient and returning the evolution circuit, the new function takes in an existing circuit and should append the evolution of the provided Pauli and given time to this circuit. This new implementation benefits from significantly better performance.

Transpiler Upgrade Notes

  • The routing plugin stage name default is now reserved for the Qiskit built-in plugin of the same name.

  • The default routing plugin stage is now "default". In Qiskit 2.0, this is simply an alias for the previous default "sabre". The underlying default algorithm may change over the course of the Qiskit 2.x series for some or all targets, but you can always set routing_method="sabre" explicitly to maintain the current behavior.

  • The translation plugin stage name default is now reserved for the Qiskit built-in plugin of the same name.

  • The default translation plugin stage is now "default". In Qiskit 2.0, this is simply an alias for the previous default "translator". The underlying default algorithm may change over the course of the Qiskit 2.x series for some or all targets, but you can always set translation_method="translator" explicitly to maintain the current behavior.

  • The deprecated transpiler passes ASAPSchedule, ALAPSchedule, DynamicalDecoupling, and AlignMeasures have been removed. These passes were marked as deprecated. They have been replaced by the ALAPScheduleAnalysis, ASAPScheduleAnalysis, PadDynamicalDecoupling, and ConstrainedReschedule passes respectively which can be used instead.

  • In the case that neither a target nor a set of basis gates are specified, the HighLevelSynthesis transpiler pass synthesizes circuits with annotated operations with fewer layers of wrappings than before (this happens, for instance, for the circuit produced by multiplier_cumulative_h18()).

  • The keyword argument property_set is now reserved in BasePassManager.run(), and cannot be used as a kwarg that will be forwarded to the subclass’s conversion from the front-end representation to the internal representation.

  • The following deprecated uses of the BackendProperties object in the transpilation pipeline have been removed in Qiskit 2.0:

    The following passes have also been updated to only accept a target instead of:

    The BackendProperties class has been deprecated since Qiskit 1.2, as it was part of the BackendV1 workflow. Specific instruction properties such as gate errors or durations can be added to a Target upon construction through the Target.add_instruction() method, and communicated to the relevant transpiler passes through the target input argument.

  • As a consequence of the removal of the BackendV1 model, the accepted input types of the following transpiler objects have been updated:

  • The ResetAfterMeasureSimplification transpiler pass now will use an IfElseOp to condition the execution of the XGate instead of setting a condition attribute on the gate. This is because the condition attribute has been removed from the Qiskit data model.

  • The deprecated ConvertConditionsToIfOps transpiler pass has been removed. The underlying condition attribute of Instruction class has been removed so this transpiler pass no longer had anything to convert from. Instead you should directly use IfElseOp to classically condition the execution of an operation.

  • The PadDelay and PadDynamicalDecoupling transpiler passes now require a new argument when constructed. Either target or durations need to be specified with a Target or InstructionDurations respectively. Without these the passes will not be able to determine the duration of instructions in the circuit and will error. Previously these passes would determine these values from the now removed duration attribute of Instruction objects.

  • The previously deprecated argument propagate_condition on the DAGCircuit methods substitute_node_with_dag() and substitute_node() has been removed. These arguments were deprecated in Qiskit 1.4.0 and were removed because the larger removal of Instruction.condition it no longer served a purpose.

  • The previously deprecated AlignMeasures transpiler pass has been removed. This pass was deprecated in Qiskit 1.1.0. Instead the ConstrainedReschedule pass should be used. ConstrainedReschedule performs the same function and also supports aligning to additional timing constraints.

  • When calling generate_preset_pass_manager() or transpile() and specifying the instruction_durations argument with a list instead of an InstructionDurations object and the list specifies durations in units of dt a dt parameter is required if scheduling the circuit.

  • Removed the deprecated DAGNode.sort_key attribute. This attribute was deprecated in the Qiskit 1.4.0 release. As the lexicographical topological sorting is done internally and rust and the sort key attribute was unused this was removed to remove the overhead from DAG node creation. If you were relying on the sort key you can reproduce it from a given node using something like:

    def get_sort_key(node: DAGNode):
        if isinstance(node, (DAGInNode, DAGOutNode)):
            return str(node.wire)
        return ",".join(
            f"{dag.find_bit(q).index:04d}" for q in itertools.chain(node.qargs, node.cargs)
        )
  • The following transpile() and generate_preset_pass_manager() input arguments, deprecated since Qiskit 1.3 , have been removed from the API:

    • instruction_durations
    • timing_constraints

    In addition to this, the specification of custom basis gates through the basis gate argument of transpile() and generate_preset_pass_manager(), also deprecated in Qiskit 1.3, is no longer allowed, and a ValueError will be raised in these cases.

    The information formerly provided through these can still be specified via the backend or target arguments. You can build a Target instance with defined instruction durations doing:

    Target.from_configuration(..., instruction_durations=...)

    For specific timing constraints:

    Target.from_configuration(..., timing_constraints=...)

    And for custom basis gates, you can manually add them to the target or use .from_configuration with a custom name mapping, for example:

    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
    )
  • The transpile() and generate_preset_pass_manager() interfaces now raise a UserWarning when providing a coupling_map and/or basis_gates along with a backend. In these cases there are multiple sources of truth, the user intentions are not always clear, and there can be conflicts that generate_preset_pass_manager() may not know how to resolve. In these cases, we highly encourage the creation of a custom target that combines the chosen constraints.

    One of these situations is the specification of a gate with 3 or more qubits in backend or basis_gates together with a custom coupling map. The coupling map does not provide the necessary connectivity details to be able to determine the action of the gate. In these cases, transpile() and generate_preset_pass_manager() now raise a ValueError.

  • As part of Pulse removal in Qiskit 2.0, all pulse and calibration related functionality in the transpiler has been removed. This includes the following:

    The following passes and function have been removed:

    • qiskit.transpiler.passes.PulseGates pass
    • qiskit.transpiler.passes.ValidatePulseGates pass
    • qiskit.transpiler.passes.RXCalibrationBuilder pass
    • qiskit.transpiler.passes.RZXCalibrationBuilder pass
    • qiskit.transpiler.passes.RZXCalibrationBuilderNoEcho pass
    • qiskit.transpiler.passes.EchoRZXWeylDecomposition pass
    • qiskit.transpiler.passes.NoramlizeRXAngle pass
    • qiskit.transpiler.passes.rzx_templates() function

    The inst_map argument has been removed from the following elements:

    Calibration support has been removed:

    • calibration has been removed from the InstructionProperties ‘s constructor and is no longer a property of that class.
    • The has_calibration, get_calibration, instruction_schedule_map and update_from_instruction_schedule_map methods have been removed from the Target class.
  • The deprecated StochasticSwap transpiler pass, and its associated built-in routing stage plugin “stochastic”, have been removed. These were marked as deprecated in the Qiskit 1.3.0 release. The pass has been superseded by the SabreSwap which should be used instead as it offers better performance and output quality. For example if the pass was previously invoked via the transpile function such as with:

    from qiskit import transpile
    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler import CouplingMap
    from qiskit.providers.fake_provider import GenericBackendV2
     
     
    qc = QuantumCircuit(4)
    qc.h(0)
    qc.cx(0, range(1, 4))
    qc.measure_all()
     
    cmap = CouplingMap.from_heavy_hex(3)
    backend = GenericBackendV2(num_qubits=cmap.size(), coupling_map=cmap)
     
    tqc = transpile(
        qc,
        routing_method="stochastic",
        layout_method="dense",
        seed_transpiler=12342,
        target=backend.target
    )

    this should be replaced with:

    tqc = transpile(
        qc,
        routing_method="sabre",
        layout_method="dense",
        seed_transpiler=12342,
        target=backend.target
    )
  • The pass qiskit.transpiler.passes.CXCancellation was removed. It was deprecated in favor of class:.InverseCancellation, which is more generic. CXCancellation is fully semantically equivalent to InverseCancellation([CXGate()]).

  • The SolovayKitaev transpiler pass no longer raises an exception on circuits that contain single-qubit operations without a to_matrix method (such as measures, barriers, control-flow operations) or parameterized single-qubit operations, but will leave them unchanged.

  • Plugins for the translation stage of the preset compiler are now required to respect gate directionality in the Target in their output. Previously, transpile() and generate_preset_pass_manager() would generate a PassManager that contained fix-up passes if needed. You must now include these in your own custom stage, if your stage does not guarantee that it respects directionality.

    You can use the GateDirection pass to perform the same fix-ups that Qiskit used to do. For example:

    from qiskit.transpiler import PassManager
    from qiskit.transpiler.passes import GateDirection
    from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin
     
    class YourTranslationPlugin(PassManagerStagePlugin):
        def pass_manager(self, pass_manager_config, optimization_level):
            pm = PassManager([
                # ... whatever your current setup is ...
            ])
            # Add the two-qubit directionality-fixing pass.
            pm.append(GateDirection(
                pass_manager_config.coupling_map,
                pass_manager_config.target,
            ))
            return pm
  • The preset pass managers no longer populate the implicit pre_optimization stage of their output StagedPassManager. You can now safely assign your own PassManager to this field. You could previously only append to the existing PassManager.

  • The default value for the generate_routing_passmanager() argument seed_transpiler has changed from None to -1. This was done because this flag was only used to configure the VF2PostLayout transpiler pass in the output, and for that pass in particular the randomization typically only hurts performance and is not desirable. If you were relying on the previous default value you can restore this behavior by explicitly setting the argument seed_transpiler=None. If you were explicitly setting a seed value for this parameter there is no change in behavior.

Visualization Upgrade Notes

  • The idle_wires parameter in all circuit drawers has been extended with a new option, "auto", which is now the default behavior. If you still want to display wires without instructions, explicitly set idle_wires=True.

    When set to "auto", the behavior is as follows: - If the circuit has a defined .layout attribute, idle_wires is automatically set to False (hiding idle wires). - Otherwise, idle_wires remains True (showing all wires, as previous default).

    Here an example. A circuit without a layout, using idle_wires="auto":

    qr_0: ────────
          ┌───┐┌─┐
    qr_1: ┤ H ├┤M├
          └───┘└╥┘
    cr_0: ══════╬═
    
    cr_1: ══════╩═

    Once a layout is applied, idle_wires="auto" sets idle_wires to False, hiding idle wires:

              ┌───┐┌─┐
    qr_1 -> 1 ┤ H ├┤M├
              └───┘└╥┘
        cr_1: ══════╩═

    If you want to display all wires in a laid-out circuit, set idle_wires=True explicitly:

         qr_0 -> 0 ────────
                   ┌───┐┌─┐
         qr_1 -> 1 ┤ H ├┤M├
                   └───┘└╥┘
    ancilla_0 -> 2 ──────╫─
    
             cr_0: ══════╬═
    
             cr_1: ══════╩═

    As quantum computers scale to more qubits, even small circuits can produce large circuit representations after transpilation. The "auto" setting helps improve readability by hiding unnecessary wires when possible.

  • The array_to_latex() and Operator.draw() methods can now raise a MissingOptionalLibrary exception if the sympy library is not installed. In the Qiskit 1.x releases symengine and sympy were always guaranteed to be installed, but starting in 2.x this is no longer a hard requirement. The latex visualization for a matrix relies on the sympy library, so if you’re using this functionality you should ensure that you have sympy installed.

  • As a consequence of the removal of the BackendV1 model, the plot_gate_map(), plot_error_map() and plot_circuit_layout() functions no longer accept inputs of type BackendV1 in their backend input argument.

  • The timeline drawer now requires the target argument is specified when called. As instructions no longer contain duration attributes this extra argument is required to specify the durations for all the supported instructions. Without the argument the timeline drawer does not have access to this information.

  • As part of the Pulse removal in Qiskit 2.0, support for pulse drawing via qiskit.visualization.pulse_drawer has been removed.

Misc. Upgrade Notes

  • The deprecate_function and deprecate_arguments decorators had been deprecated since 0.24, released on May 2023, and have been removed in 2.0. Current deprecate_func`() replaces @deprecate_function and current deprecate_arg() replaces @deprecate_arguments.

  • The assemble function and related capabilities (contained in the assembler module) have been removed from the codebase following their deprecation in Qiskit 1.2. assemble was used to generate Qobj in the context of the deprecated BackendV1 workflow. The conversion is no longer necessary as the transpilation and primitives pipeline handles quantum circuits directly, rendering the Qobj obsolete.

    The removal includes the following public API components:

    • qiskit.compiler.assemble function
    • qiskit.assembler.assemble_circuits function
    • qiskit.assembler.assemble_schedules function
    • qiskit.assembler.disassemble function
    • qiskit.assembler.RunConfig class
    • qiskit.circuit.Instruction.assemble method
  • The Qobj structure and related classes, deprecated in Qiskit 1.2.0, have been removed. They were introduced as part of the BackendV1 workflow and are no longer necessary for interacting with BackendV2 backends. This removal includes:

    • QobjExperimentHeader
    • QobjHeader
    • QasmQobj
    • QasmQobjInstruction
    • QasmQobjExperimentConfig
    • QasmQobjExperiment
    • QasmQobjConfig
    • QasmExperimentCalibrations
    • GateCalibration
    • PulseQobj
    • PulseQobjInstruction
    • PulseQobjExperimentConfig
    • PulseQobjExperiment
    • PulseQobjConfig
    • QobjMeasurementOption
    • PulseLibraryItem
  • The MeasLevel and :class`.MeasReturnType` classes, previously defined in qobj/utils.py, have been migrated to result/models.py following the removal of the qobj module. These classes were not part of the public API. The import path has been updated from: from qiskit.qobj.utils import MeasLevel, MeasReturnType to: from qiskit.result import MeasLevel, MeasReturnType.

  • The use of positional arguments in the constructor of Result has been disabled. Please set all arguments using kwarg syntax, i.e: Result(backend_name="name", ....). In addition to this, the qobj_id argument will no longer be used in the construction of the Result internals. It is still possible to set qobj_id as a generic kwarg, which will land in the metadata field with the other generic kwargs.

  • As part of Pulse removal in Qiskit 2.0, the sequence and schedule_circuit functions from qiskit.scheduler together with the ScheduleConfig class have been removed.

  • The qiskit.result.mitigation module has been removed following its deprecation in Qiskit 1.3. The removal includes the LocalReadoutMitigator and CorrelatedReadoutMitigator classes as well as the associated utils.

    There is no alternative path in Qiskit, as their functionality had been superseded by the mthree package, found in https://github.com/Qiskit/qiskit-addon-mthree.

Circuits Deprecations

  • The deprecated QuantumCircuit.duration attribute was not removed in this release as originally planned, it will be removed as part of the Qiskit 3.0.0 release instead. This functionality has been superseded by the QuantumCircuit.estimate_duration() method which should be used instead.

  • The Multiple-Control-Multiple-Target in MCMT is now deprecated and replaced by MCMTGate, which is a proper Gate subclass. Using a gate instead of a circuit allows the compiler to reason about the object at a higher level of abstraction and allows for multiple synthesis plugins.

Transpiler Deprecations

  • The deprecated DAGCircuit.duration attribute was not removed in this release as originally planned, it will be removed as part of the Qiskit 3.0.0 release instead. This functionality has been superseded by the QuantumCircuit.estimate_duration() method which should be used instead.

  • The propagate_condition argument of DAGCircuit.substitute_node() and DAGCircuit.substitute_node_with_dag() has been deprecated. With the removal of Instruction.condition from the Qiskit data model this option no longer serves a purpose. If it is set it no longer has any effect. It is not removed from the signature to maintain compatibility during the migration from Qiskit 1.x -> 2.0. This option will be removed in Qiskit 3.0.

  • The function generate_pre_op_passmanager() is deprecated. It is no longer used in the Qiskit preset pass managers, and its purpose is defunct; it originally generated a fix-up stage for translation plugins that did not respect ISA directionality. Translation stages are now required to respect directionality, so the functionality is not needed, and most likely, no replacement is required.

Bug Fixes

  • Fixed a bug where the barrier labels were incorrectly positioned when using the reverse_bits = True parameter in the QuantumCircuit.draw() method. The bug caused the labels on barrier operations to be misaligned, leading to potential confusion in circuit visualizations. Fixed #13609.

  • Fixed an inconsistency in dealing with close-to-identity gates in the transpilation process, where gates that are close to the identity were assumed to commute with everything, but not removed. The underlying issue were different metrics used in RemoveIdentityEquivalent and CommutationAnalysis (and, by extension, CommutativeInverseCancellation). Both now use the average gate fidelity and the same threshold to assess whether a gate should be treated as identity (such as a rotation gate with very small angle). See either of the aforementioned classes’ docstrings for more information. Fixed #13547.

  • Applied a small regularisation factor against ill-conditioned Hermitian matrices in super-operator representations.

  • Commutation relations of Instructions with float-only params were eagerly cached by the CommutationChecker, using the params as key to query the relation. This could lead to faulty results, if the instruction’s definition depended on additional information that just the params attribute, such as e.g. the case for PauliEvolutionGate. This behavior is now fixed, and the commutation checker only conservatively caches commutations for Qiskit-native standard gates. This can incur a performance cost if you were relying on your custom gates being cached, however, we cannot guarantee safe caching for custom gates, as they might rely on information beyond params.

  • Fixed a bug in the CommmutationChecker, where checking commutation of instruction with non-numeric values in the params attribute (such as the PauliGate) could raise an error. Fixed #13570.

  • Fixed a bug where calling QuantumCircuit.decompose() on an instruction that had no definition inside a c_if block would raise an error. Fixed #13493.

  • Operations inside a control flow (e.g. QuantumCircuit.for_loop()) were not correctly decomposed when calling QuantumCircuit.decompose(). This behavior is now fixed and instructions are unrolled. Fixed #13544.

  • Comparisons of Delay instructions, including within circuits, now require the units to be equal as well as the duration value.

  • The CommutationChecker did not handle commutations of the CRXGate, CRYGate and CRZGate correctly for angles π(4k+2)\pi(4k + 2) for kZk \in \mathbb Z. In these cases, the controlled rotations were falsely assumed to commute with any gate. Now these gates correctly commute with any gate if the rotation angle is a multiple of 4π4\pi.

  • Added default definitions for FullAdderGate, HalfAdderGate, ModularAdderGate and MultiplierGate gates, allowing to contruct Operators from quantum circuits containing these gates.

  • Fixed the number of clean ancilla qubits required by FullAdderSynthesisV95, HalfAdderSynthesisV95, and ModularAdderSynthesisV95 plugins.

  • Added missing FullAdderSynthesisDefault plugin that chooses the best decomposition for FullAdderGate based on the number of clean ancilla qubits available.

  • Fixed HalfAdderSynthesisDefault and ModularAdderSynthesisDefault plugins, for HalfAdderGate and ModularAdderGate respectively, to choose the best decomposition based on the number of clean ancilla qubits available.

  • Fix incorrect behavior in CircuitData in which, upon parameter assignment, we attempted to modify the cached operation inside of a PackedInstruction. Now we instead discard said cache prompting the PackedInstruction to build a new Python operation should it be needed.

  • Fixed a bug in QuantumCircuit.assign_parameters(), occurring when assigning parameters to standard gates whose definition has already been triggered. In this case, the new values were not properly propagated to the gate instances. While the circuit itself was still compiled as expected, inspecting the individual operations would still show the old parameter.

    For example:

    from qiskit.circuit.library import EfficientSU2
     
    circuit = EfficientSU2(2, flatten=True)
    circuit.assign_parameters([1.25] * circuit.num_parameters, inplace=True)
    print(circuit.data[0].operation.params)  # would print θ[0] instead of 1.25

    Fixed #13478.

  • Fixed a bug in the CommutationChecker which could fail upon checking the commutation relation of a two-qubit Pauli rotation with a gate that is not in the commutation cache. For example:

    import numpy as np
    from qiskit.circuit.library import RXXGate, RGate
    from qiskit.circuit.commutation_library import SessionCommutationChecker as scc
     
    res = scc.commute(RGate(2, 2), [1], [], RXXGate(np.pi / 2), [0, 1], [])

    This behavior is now resolved and the commutation relation correctly computed. Fixed #13742.

  • Fixed a bug that caused the circuit library functions efficient_su2(), real_amplitudes(), excitation_preserving() and pauli_two_design() to error out when constructed for num_qubits==1. For a single qubit these circuits will not contain any 2-qubit gates.

  • Fixed a series of bugs when processing circuit with parameterized global phases, where upon assignment the global phase was not correctly assigned. Known cases this affected include:

    • assigning parameters after calling QuantumCircuit.decompose() on a circuit, where the decomposition introduces a global phase
    • assigning parameters on a circuit constructed from a DAG via dag_to_circuit()
    • assigning parameters on circuits created with pauli_twirl_2q_gates(), where the circuit to be twirled had a parameterized global phase

    Fixed #13534.

  • Previously the HighLevelSynthesis transpiler pass synthesized an instruction for which a synthesis plugin is available, regardless of whether the instruction is already supported by the target or a part of the explicitly passed basis_gates. This behavior is now fixed, so that such already supported instructions are no longer synthesized.

  • An issue where QuantumCircuit.qubit_stop_time() and QuantumCircuit.qubit_duration() returned incorrect time (duration) was fixed. It was triggered when some qubits have instructions but other qubits are idle. Fixes #8729.

  • The transpilation pass InverseCancellation now runs inside of flow controlled blocks. Previously, it ignored the pairs of gates in classical blocks that could be cancelled. Refer to #13437 for more details.

  • Fixed a bug where any instruction called "mcmt" would be passed into the high-level synthesis routine for a MCMTGate, which causes a failure or invalid result. In particular, this could happen accidentally when handling the MCMT _circuit_, named "mcmt", and implicitly converting it into an instruction e.g. when appending it to a circuit. Fixed #13563.

  • Fixed a bug in RZGate.control() for more than 1 control qubit, which used an unnecessarily expensive decomposition. Fixed #13473.

  • Fixed a bug in the basis approximation generation for SolovayKitaev. Previously, generating discrete basis approximations using generate_basis_approximations for a basis containing "sx" or "sxdg" gates would fail. This has now been fixed.

  • Fix a bug in the multi-controlled rotation circuit methods QuantumCircuit.mcrx(), QuantumCircuit.mcry(), and QuantumCircuit.mcrz(), when the user provides an unbounded parameter, as well as when calling RXGate.control(), RYGate.control() or RZGate.control() where the rotation angle is a ParameterExpression. Previously, the user got an error that this gate cannot be synthesized with unbound parameter, and now these multi-controlled rotation circuits can be synthesized without raising an error.

  • The PauliEvolutionGate, if used with a product formula synthesis (this is the default), did not correctly handle all-identity terms in the operator. The all-identity term should introduce a global phase equal to -evolution_time, but was off by a factor of 2 and could break for parameterized times. This behavior is now fixed. Fixed #13625.

  • Fixed an inconsistency in the circuit generated by a Pauli evolution synthesis with SuzukiTrotter or LieTrotter (the default) method. For parameterized evolution times, the resulting circuits contained parameters with a spurious, zero complex part, which affected the output of ParameterExpression.sympify(). The output now correctly is only real. Fixed #13642.

  • Fixed a bug that caused PauliList.insert() with qubit=True to produce a phase attribute with the wrong shape when the original object was length 1. Fixed #13623.

  • Fixed a bug in the RemoveIdentityEquivalent transpiler pass, where gates close to identity up to a global phase were removed from the circuit, but the global phase of the circuit was not updated. In particular, RemoveIdentityEquivalent now removes non-parameterized GlobalPhaseGate gates.

  • Circuits compiled using a preset passmanager constructed by generate_preset_pass_manager() will now correctly retain their name attribute, as they do with transpile().

  • Fix a bug in qasm3.Exporter that caused the exporter to crash when handling a unitary gate due to incorrect processing of its params field.

  • Fixed a bug in random_clifford() that stopped it from sampling the full Clifford group.

  • Fixed a bug in the Target.instruction_supported() method where targets with self.num_qubits==None would always return false independently of the supported basis set.

  • Fixed a bug in the UnitarySynthesis transpiler pass, where blocks of UnitaryGates on 3 qubits or more were not correctly synthesized. This led, e.g., to the circuit being overwritten with the last processed block or to internal panics when encountering measurements after such a block. Fixed #13586.

  • Fixed a bug in the UnitarySynthesis transpiler pass where non-2-qubit gates would be included in the available 2 qubit basis, causing the TwoQubitWeylDecomposition to panic because of the dimension mismatch.

  • Fixed a bug that caused Statevector.expectation_value() to yield incorrect results for the identity operator when the statevector was not normalized.

  • Calling an AnalysisPass or a TransformationPass like a function (as in pass_ = MyPass(); pass_(qc)) will now respect any requirements that the pass might have. For example, scheduling passes such as ALAPScheduleAnalysis require that TimeUnitConversion runs before them. Running the pass via a PassManager always respected this requirement, but until this note, it was not respected by calling the pass directly.

  • When a TranspilerError subclass is raised by a pass inside a call to PassManger.run(), the exception will now be propagated through losslessly, rather than becoming a chained exception with an erased type.

  • SabreSwap will no longer contract idle qubit wires out of control-flow blocks during routing. This was generally a valid optimization, but not an expected side effect of a routing pass. You can now use the ContractIdleWiresInControlFlow pass to perform this contraction.

  • When SabreLayout is used to do both layout and routing simultaneously (as is the case for the default options to transpile() and generate_preset_pass_manager()) on a Target or CouplingMap with disjoint connectivity, and the input circuit fits into a single component of the coupling map, the routing permutation will now be tracked correctly.

    Previously, any qubits in the coupling map that were not connected, even indirectly, to a qubit used by the routed circuit would not be included in the final routing permutation. This could cause surprising behaviour a long way from the point of failure, even if compilation appeared to succeed, such as calls to TranspileLayout.final_index_layout() raising KeyError.

    This bug did not affect backends that were fully connected, as most are.

  • Fixed a bug where a initializing SparsePauliOp with a large number of Pauli-Y terms (typically 100\geq 100) and no explicit coeffs would result in a coefficient close to 1 but with a floating point error. The coefficient is now correctly 1 per default. Fixed #13522.

  • Fixed a per-process based non-determinism in SparsePauliOp.to_matrix. The exact order of the floating-point operations in the summation would previously vary per process, but will now be identical between different invocations of the same script. See #13413.


1.3.0rc1

New Features

  • Support for level 1 data was added to BackendSamplerV2 as was support for passing options through to the run() method of the wrapped BackendV2. The run options can be specified using a "run_options" entry inside of the options dicitonary passed to BackendSamplerV2. The "run_options" entry should be a dictionary mapping argument names to values for passing to the backend’s run() method. When a "meas_level" option with a value of 1 is set in the run options, the results from the backend will be treated as level 1 results rather as bit arrays (the level 2 format).

Circuits Features

  • Improved the functionality of CommutationChecker to include support for the following parameterized gates with free parameters: RXXGate,:class:.RYYGate,:class:.RZZGate,:class:.RZXGate, RXGate,:class:.RYGate,:class:.RZGate,:class:.PhaseGate, U1Gate,:class:.CRXGate,:class:.CRYGate,:class:.CRZGate, CPhaseGate.

    Before these were only supported with bound parameters.

  • Added a new function quantum_volume() for generating a quantum volume QuantumCircuit object as defined in A. Cross et al. Validating quantum computers using randomized model circuits, Phys. Rev. A 100, 032328 (2019) https://link.aps.org/doi/10.1103/PhysRevA.100.032328. This new function differs from the existing QuantumVolume class in that it returns a QuantumCircuit object instead of building a subclass object. The second is that this new function is multithreaded and implemented in rust so it generates the output circuit ~10x faster than the QuantumVolume class.

  • Improved the runtime performance of constructing the QuantumVolume class with the classical_permutation argument set to True. Internally it now calls the quantum_volume() function which is written in Rust which is ~10x faster to generate a quantum volume circuit.

  • Added a new circuit manipulation function pauli_twirl_2q_gates() that can be used to apply Pauli twirling to a given circuit. This only works for twirling a fixed set of two-qubit gates, currently CXGate, ECRGate, CZGate, iSwapGate. For example:

    from qiskit.circuit import QuantumCircuit, pauli_twirl_2q_gates
     
    qc = QuantumCircuit(2)
    qc.cx(0, 1)
    twirled_circuit = pauli_twirl_2q_gates(qc, seed=123456)
    twirled_circuit.draw("mpl")
    _images/release_notes-5.png
  • Added binary arithmetic gates for inplace addition two nn-qubit registers, that is abaa+b|a\rangle |b\rangle \mapsto |a\rangle |a+b\rangle. The ModularAdderGate implements addition modulo 2n2^n, the AdderGate implements standard addition including a carry-out, and the FullAdderGate includes a carry-in qubit. See the respective documentations for details and examples.

    In contrast to the existing library circuits, such as CDKMRippleCarryAdder, handling the abstract gate allows the compiler (or user) to select the optimal gate synthesis, depending on the circuit’s context.

  • Added the MultiplierGate for multiplication of two nn-qubit registers, that is ababab|a\rangle |b\rangle \mapsto |a\rangle |b\rangle |a \cdot b\rangle. See the class documentations for details and examples.

  • Quantum circuits in qiskit.circuit.library.boolean_logic now have equivalent representations as Gate objects:

  • Specialized implementations of __eq__() have been added for all standard-library circuit gates. Most of the standard gates already specialized this method, but a few did not, and could cause significant slowdowns in unexpected places.

  • Added evolved_operator_ansatz(), hamiltonian_variational_ansatz(), and qaoa_ansatz() to the circuit library to implement variational circuits based on operator evolutions. evolved_operator_ansatz() and qaoa_ansatz() are functionally equivalent to EvolvedOperatorAnsatz and QAOAAnsatz, but generally more performant.

    The hamiltonian_variational_ansatz() is designed to take a single Hamiltonian and automatically split it into commuting terms to implement a Hamiltonian variational ansatz. This could already be achieved manually by using the EvolvedOperatorAnsatz, but is now more convenient to use.

  • Added grover_operator() to construct a Grover operator circuit, used in e.g. Grover’s algorithm and amplitude estimation/amplification. This function is similar to GroverOperator, but does not require choosing the implementation of the multi-controlled X gate a-priori and let’s the compiler choose the optimal decomposition instead. In addition to this, it does not wrap the circuit into an opaque gate and is faster as less decompositions are required to transpile.

    Example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.circuit.library import grover_operator
     
    oracle = QuantumCircuit(2)
    oracle.z(0)  # good state = first qubit is |1>
    grover_op = grover_operator(oracle, insert_barriers=True)
    print(grover_op.draw())
  • Added a new argument "apply_synthesis" to Decompose, which allows the transpiler pass to apply high-level synthesis to decompose objects that are only defined by a synthesis routine. For example:

    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Clifford
    from qiskit.transpiler.passes import Decompose
     
    cliff = Clifford(HGate())
    circuit = QuantumCircuit(1)
    circuit.append(cliff, [0])
     
    # Clifford has no .definition, it is only defined by synthesis
    nothing_happened = Decompose()(circuit)
     
    # this internally runs the HighLevelSynthesis pass to decompose the Clifford
    decomposed = Decompose(apply_synthesis=True)(circuit)
  • Added the iqp() function to construct Instantaneous Quantum Polynomial time (IQP) circuits. In addition to the existing IQP class, the function also allows construction of random IQP circuits:

    from qiskit.circuit.library import iqp
     
    random_iqp = iqp(num_qubits=4)
    print(random_iqp.draw())
  • Added the MCMTGate to represent a multi-control multi-target operation as a gate. This gate representation of the existing MCMT circuit allows the compiler to select the best available implementation according to the number and the state of auxiliary qubits present in the circuit.

    Specific implementation can be chosen by specifying the high-level synthesis plugin:

    from qiskit import QuantumCircuit, transpile
    from qiskit.circuit.library import MCMTGate, HGate
    from qiskit.transpiler.passes import HLSConfig  # used for the synthesis config
     
    mcmt = MCMTGate(HGate(), num_ctrl_qubits=5, num_target_qubits=3)
     
    circuit = QuantumCircuit(20)
    circuit.append(mcmt, range(mcmt.num_qubits))
     
    config = HLSConfig(mcmt=["vchain"])  # alternatively use the "noaux" method
    synthesized = transpile(circuit, hls_config=config)

    The MCMTGate in addition also supports custom (i.e., open) control states of the control qubits.

  • As a part of circuit library modernization, each of the following quantum circuits is either also represented as a Gate object or can be constructed using a synthesis method:

  • Added circuit library functions pauli_feature_map(), z_feature_map(), zz_feature_map() to construct Pauli feature map circuits. These functions are approximately 8x faster than the current circuit library objects, PauliFeatureMap, ZFeatureMap, and ZZFeatureMap, and will replace them in the future. Note, that the new functions return a plain QuantumCircuit instead of a BlueprintCircuit.

    The functions can be used as drop-in replacement:

    from qiskit.circuit.library import pauli_feature_map, PauliFeatureMap
     
    fm = pauli_feature_map(20, paulis=["z", "xx", "yyy"])
    also_fm = PauliFeatureMap(20, paulis=["z", "xx", "yyy"]).decompose()

OpenQASM Features

QPY Features

  • Added a new QPY format version 13 that adds a Qiskit native representation of ParameterExpression objects.

Quantum Information Features

  • The performance of SparsePauliOp.from_operator() has been optimized on top of the algorithm improvements methods introduced in Qiskit 1.0. It is now approximately five times faster than before for fully dense matrices, taking approximately 40ms to decompose a 10q operator involving all Pauli terms.

  • Added a new argument assume_unitary to qiskit.quantum_info.Operator.power(). When True, we use a faster method based on Schur’s decomposition to raise an Operator to a fractional power.

  • Added SparsePauliOperator.to_sparse_list() to convert an operator into a sparse list format. This works inversely to SparsePauliOperator.from_sparse_list(). For example:

    from qiskit.quantum_info import SparsePauliOp
     
    op = SparsePauliOp(["XIII", "IZZI"], coeffs=[1, 2])
    sparse = op.to_sparse_list()  # [("X", [3], 1), ("ZZ", [1, 2], 2)]
     
    other = SparsePauliOp.from_sparse_list(sparse, op.num_qubits)
    print(other == op)  # True
  • The performance of Pauli.to_label() has significantly improved for large Paulis.

  • The method Operator.power() has a new parameter branch_cut_rotation. This can be used to shift the branch-cut point of the root around, which can affect which matrix is chosen as the principal root. By default, it is set to a small positive rotation to make roots of operators with a real-negative eigenvalue (like Pauli operators) more stable against numerical precision differences.

  • A new observable class has been added. SparseObservable represents observables as a sum of terms, similar to SparsePauliOp, but with two core differences:

    1. Each complete term is stored as (effectively) a series of (qubit, bit_term) pairs, without storing qubits that undergo the identity for that term. This significantly improves the memory usage of observables such as the weighted sum of Paulis iciZi\sum_i c_i Z_i.
    2. The single-qubit term alphabet is overcomplete for the operator space; it can represent Pauli operators (like SparsePauliOp), but also projectors onto the eigenstates of the Pauli operators, like 00\lvert 0\rangle\langle 0\rangle. Such projectors can be measured on hardware equally as efficiently as their corresponding Pauli operator, but SparsePauliOp would require an exponential number of terms to represent 00n{\lvert0\rangle\langle0\rvert}^{\otimes n} over nn qubits, while SparseObservable needs only a single term.

    You can construct and manipulate SparseObservable using an interface familiar to users of SparsePauliOp:

    from qiskit.quantum_info import SparseObservable
     
    obs = SparseObservable.from_sparse_list([
      ("XZY", (2, 1, 0), 1.5j),
      ("+-", (100, 99), 0.5j),
      ("01", (50, 49), 0.5),
    ])

    SparseObservable is not currently supported as an input format to the primitives (qiskit.primitives), but we expect to expand these interfaces to include them in the future.

Synthesis Features

  • Added adder_qft_d00(), adder_ripple_c04(), and adder_ripple_v95() to synthesize the adder gates, ModularAdderGate, AdderGate, and FullAdderGate.

  • Added multiplier_cumulative_h18() and multiplier_qft_r17() to synthesize the MultiplierGate.

  • Added synth_mcmt_vchain() to synthesize the multi-control multi-target gate with a linear number of Toffoli gates and k-1 auxiliary qubits for k control qubits, along with the high-level synthesis plugin MCMTSynthesisVChain.

  • Added a high-level synthesis plugin structure for the MCMTGate, including the MCMTSynthesisNoAux (for no auxiliary qubits), the aforementioned MCMTSynthesisVChain (using num_control - 1 auxiliary qubits), and the MCMTSynthesisDefault to let the compiler choose the optimal decomposition.

  • Added ProductFormula.expand() which allows to view the expansion of a product formula in a sparse Pauli format.

  • Added the plugin structure for the PauliEvolutionGate. The default plugin, PauliEvolutionSynthesisDefault, constructs circuit as before, but faster as it internally uses Rust. The larger the circuit (e.g. by the Hamiltonian size, the number of timesteps, or the Suzuki-Trotter order), the higher the speedup. For example, a 100-qubit Heisenberg Hamiltonian with 10 timesteps and a 4th-order Trotter formula is now constructed ~9.4x faster. The new plugin, PauliEvolutionSynthesisRustiq, uses the synthesis algorithm that is described in the paper “Faster and shorter synthesis of Hamiltonian simulation circuits” by de Brugière and Martiel (https://arxiv.org/abs/2404.03280) and is implemented in https://github.com/smartiel/rustiq-core. For example:

    from qiskit.circuit import QuantumCircuit
    from qiskit.quantum_info import SparsePauliOp
    from qiskit.circuit.library import PauliEvolutionGate
    from qiskit.compiler import transpile
    from qiskit.transpiler.passes import HLSConfig
     
    op = SparsePauliOp(["XXX", "YYY", "IZZ"])
    qc = QuantumCircuit(4)
    qc.append(PauliEvolutionGate(op), [0, 1, 3])
    config = HLSConfig(PauliEvolution=[("rustiq", {"upto_phase": False})])
    tqc = transpile(qc, basis_gates=["cx", "u"], hls_config=config)
    tqc.draw(output='mpl')

    This code snippet uses the "rustiq" plugin to synthesize PauliEvolutionGate objects in the quantum circuit qc. The plugin is called with the additional option "upto_phase" = False allowing to obtain smaller circuits at the expense of possibly not preserving the global phase. For the full list of supported options, see documentation for PauliEvolutionSynthesisRustiq.

  • Added a new argument preserve_order to ProductFormula, which allows re-ordering the Pauli terms in the Hamiltonian before the product formula expansion, to compress the final circuit depth. By setting this to False, a term of form

Z0Z1+X1X2+Y2Y3Z_0 Z_1 + X_1 X_2 + Y_2 Y_3

will be re-ordered to

Z0Z1+Y2Y3+X1X2Z_0 Z_1 + Y_2 Y_3 + X_1 X_2

which will lead to the RZZ and RYY rotations being applied in parallel, instead of three sequential rotations in the first part.

This option can be set via the plugin interface:

from qiskit import QuantumCircuit, transpile
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.quantum_info import SparsePauliOp
from qiskit.synthesis.evolution import SuzukiTrotter
from qiskit.transpiler.passes import HLSConfig
 
op = SparsePauliOp(["XXII", "IYYI", "IIZZ"])
time, reps = 0.1, 1
 
synthesis = SuzukiTrotter(order=2, reps=reps)
hls_config = HLSConfig(PauliEvolution=[("default", {"preserve_order": False})])
 
circuit = QuantumCircuit(op.num_qubits)
circuit.append(PauliEvolutionGate(op, time), circuit.qubits)
 
tqc = transpile(circuit, basis_gates=["u", "cx"], hls_config=hls_config)
print(tqc.draw())

Transpiler Features

  • Add an argument matrix_based to the CollectCliffords() transpiler pass. If the new parameter matrix_based=True, the CollectCliffords() transpiler pass can collect RZGate(np.pi/2) gates and other unitary gates that are Clifford() gates for certain parameters.

  • The RemoveIdentityEquivalent transpiler pass is now run as part of the preset pass managers at optimization levels 2 and 3. The pass is run in the init stage and the optimization stage, because the optimizations it applies are valid in both stages and the pass is fast to execute.

  • The SabreLayout transpiler pass has been updated to run an additional 2 or 3 layout trials by default independently of the layout_trials keyword argument’s value. A trivial layout and its reverse are included for all backends, just like the DenseLayout trial that was added in 1.2.0. In addition to this, the largest rings on an IBM backend heavy hex connectivity graph are added if the backends are 127, 133, or 156 qubits. This can provide a good starting point for some circuits on these commonly run backends, while for all others it’s just an additional “random trial”.

  • Improved handling of ancilla qubits in the HighLevelSynthesis transpiler pass. For example, a circuit may have custom gates whose definitions include MCXGates. Now the synthesis algorithms for the inner MCX-gates can use the ancilla qubits available on the global circuit but outside the custom gates’ definitions.

  • Port most of the logic of the transpiler pass ElidePermutations to Rust.

  • Added a new transpiler pass, RemoveIdentityEquivalent that is used to remove gates that are equivalent to an identity up to some tolerance. For example if you had a circuit like:

    _images/release_notes-6.png

    running the pass would eliminate the CPhaseGate:

    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler.passes import RemoveIdentityEquivalent
     
    qc = QuantumCircuit(2)
    qc.cp(1e-20, 0, 1)
     
    removal_pass = RemoveIdentityEquivalent()
    result = removal_pass(qc)
    result.draw("mpl")
    _images/release_notes-7.png
  • The ConsolidateGates pass will now run the equivalent of the Collect2qBlocks pass internally if it was not run in a pass manager prior to the pass. Previously it was required that Collect2qBlocks or Collect1qRuns were run prior to ConsolidateBlocks for ConsolidateBlocks to do anything. By doing the collection internally the overhead of the pass is reduced. If Collect2qBlocks or Collect1qRuns are run prior to ConsolidateBlocks the collected runs by those passes from the property set are used and there is no change in behavior for the pass.

Visualization Features

  • The timeline_drawer() visualization function has a new argument target, used to specify a Target object for the visualization. By default the function used the Instruction.duration to get the duration of a given instruction, but specifying the target will leverage the timing details inside the target instead.

Known Issues

  • Versions of Qiskit before 1.2.4 will not be able to load QPY files dumped using qpy.dump(), even with version set appropriately, if:

    • there are unbound ParameterExpressions in the QPY file,
    • the use_symengine=True flag was set (which is the default in Qiskit >= 1.0.0) in qpy.dump(),
    • the version of symengine installed in the generating and loading environments are not within the same minor version.

    This applies regardless of the version of Qiskit used in the generation (at least up to Qiskit 1.2.4 inclusive).

    If you want to maximize compatibility with older versions of Qiskit, you should set use_symengine=False. Newer versions of Qiskit should not require this.

  • QPY files from the Qiskit 0.45 series can, under a very specific and unlikely set of circumstances, fail to load with any newer version of Qiskit, including Qiskit 1.2.4. The criteria are:

    • the QuantumCircuit or ScheduleBlock to be dumped contained unbound ParameterExpression objects,
    • the installed version of symengine was in the 0.9 series (which was the most recent release during the support window of Qiskit 0.45),
    • the use_symengine=True flag was set (which was not the default).

    Later versions of Qiskit used during generation are not affected, because they required newer versions than symengine 0.9.

    In this case, you can recover the QPY file by reloading it with an environment with Qiskit 0.45.3 and symengine 0.9.2 installed. Then, use qpy.dump() with use_symengine=False to re-export the file. This will then be readable by any newer version of Qiskit.

  • When using QPY formats 10, 11, or 12 there is a dependency on the version of symengine installed in the payload for serialized ParamerExpression if there is mismatched version of the installed symengine package between the environment that generated the payload with qpy.dump() and the installed version that is trying to load the payload with qpy.load(). If this is encountered you will need to install the symengine version from the error message emitted to load the payload. QPY format version >= 13 (or < 10) will not have this issue and it is recommended if you’re serializing ParameterExpression objects as part of your circuit or any ScheduleBlock objects you use version 13 to avoid this issue in the future.

Upgrade Notes

  • The supported versions of symengine have been pre-emptively capped at < 0.14.0 (which is expected to be the next minor version, as of this release of Qiskit). This has been done to protect against a potential incompatibility in qpy when serializing ParameterExpression objects. The serialization used in QPY Format versions 10, 11, and 12 for ParameterExpression objects is tied to the symengine version used to generate it, and there is the potential for a future symengine release to not be compatible. This upper version cap is to prevent a future release of symengine causing incompatibilities when trying to load QPY files using qpy.load.

  • When using BackendSamplerV2, circuit metadata is no longer cleared before passing circuits to the run() method of the wrapped BackendV2 instance.

  • The following classes now use the X\sqrt{X} operation to diagonalize the Pauli-Y operator: PauliEvolutionGate, EvolvedOperatorAnsatz, PauliFeatureMap. Previously, these classes used either HSH S or RX(π/2)R_X(-\pi/2) as basis transformation. Using the X\sqrt{X} operation, represented by the SXGate is more efficient as it uses only a single gate implemented as singleton.

Circuits Upgrade Notes

  • The QuantumVolume class will generate circuits with different unitary matrices and permutations for a given seed value from the previous Qiskit release. This is due to using a new internal random number generator for the circuit generation that will generate the circuit more quickly. If you need an exact circuit with the same seed you can use the previous release of Qiskit and generate the circuit with the flatten=True argument and export the circuit with qpy.dump() and then load it with this release.

QPY Upgrade Notes

  • The qpy.dump() function will now emit format version 13 by default. This means payloads generated with this function by default will only be compatible with Qiskit >= 1.3.0. If you need for the payload to be loaded by a older version of Qiskit you can use the version flag on qpy.dump() to emit a version compatible with earlier releases of Qiskit. You can refer to QPY Compatibility for more details on this.

Deprecation Notes

  • The Qiskit Pulse package is being deprecated and will be removed in Qiskit 2.0.0. Pulse-level access is currently only supported on a subset of Eagle devices and not supported on the Heron architecture. Furthermore, newer IBM Quantum architectures will not support pulse-level access. As a consequence, supporting Pulse as a first-class citizen frontend in the Qiskit SDK itself makes little sense going forward. The deprecation includes all pulse code in qiskit.pulse as well as functionality dependant or related to pulse such as pulse visualization, serialization and custom calibration support. For more details see the deprecation sections.

    The Pulse package as a whole, along with directly related components in Qiskit, will be moved to the Qiskit Dynamics repository to further enable pulse and low-level control simulation.

Circuits Deprecations

  • Deprecated the Instruction.condition attribute and the Instruction.c_if() method. They will be removed in Qiskit 2.0, along with any uses in the Qiskit data model. This functionality has been superseded by the IfElseOp class which can be used to describe a classical condition in a circuit. For example, a circuit using Instruction.c_if() like:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.x(0).c_if(0, 1)
    qc.z(1.c_if(1, 0)
    qc.measure(0, 0)
    qc.measure(1, 1)

    can be rewritten as:

    qc = QuantumCircuit(2, 2)
    qc.h(0)
    with expected.if_test((expected.clbits[0], True)):
        qc.x(0)
    with expected.if_test((expected.clbits[1], False)):
        qc.z(1)
    qc.measure(0, 0)
    qc.measure(1, 1)

    The now deprecated ConvertConditionsToIfOps transpiler pass can be used to automate this conversion for existing circuits.


  • As part of the Qiskit Pulse package deprecation, the following dependencies are deprecated as well:

    • qiskit.circuit.QuantumCircuit.calibrations
    • qiskit.circuit.QuantumCircuit.has_calibration_for()
    • qiskit.circuit.QuantumCircuit.add_calibration()
    • qiskit.dagcircuit.DAGCircuit.calibrations
    • qiskit.dagcircuit.DAGCircuit.has_calibration_for()
    • qiskit.dagcircuit.DAGCircuit.add_calibration()
    • qiskit.dagcircuit.DAGDependency.calibrations
  • The QuantumCircuit.unit and QuantumCircuit.duration attributes have been deprecated and will be removed in Qiskit 2.0.0. These attributes were used to track the estimated duration and unit of that duration to execute on the circuit. However, the values of these attributes were always limited, as they would only be properly populated if the transpiler were run with the correct settings. The duration was also only a guess based on the longest path on the sum of the duration of DAGCircuit and wouldn’t ever correctly account for control flow or conditionals in the circuit.

  • The DAGCircuit.unit and DAGCircuit.duration attributes have been deprecated and will be removed in Qiskit 2.0.0. These attributes were used to track the estimated duration and unit of that duration to execute on the circuit. However, the values of these attributes were always limited, as they would only be properly populated if the transpiler were run with the correct settings. The duration was also only a guess based on the longest path on the sum of the duration of DAGCircuit and wouldn’t ever correctly account for control flow or conditionals in the circuit.

  • The Instruction.duration and Instruction.unit attributes have been deprecated and will be removed in Qiskit 2.0.0. This includes setting the unit or duration arguments for any qiskit.circuit.Instruction or subclass. These attributes were used to attach a custom execution duration and unit for that duration to an individual instruction. However, the source of truth of the duration of a gate is the BackendV2 Target which contains the duration for each instruction supported on the backend. The duration of an instruction is not something that’s typically user adjustable and is an immutable property of the backend. If you were previously using this capability to experiment with different durations for gates you can mutate the InstructionProperties.duration field in a given Target to set a custom duration for an instruction on a backend (the unit is always in seconds in the Target).

Providers Deprecations

  • The BasicSimulator.configuration() method is deprecated and will be removed in 2.0.0. This method returned a legacy providers.models.BackendConfiguration instance which is part of the deprecated BackendV1 model. This model has been replaced with BackendV2, where the constraints are stored directly in the backend instance or the underlying Target (backend.target).

    Here is a quick guide for accessing the most common BackendConfiguration attributes in the BackendV2 model:””

    BackendV1 model (deprecated) ————> BackendV2 model —————————- ————— backend.configuration().backend_name backend.name backend.configuration().backend_version backend.backend_version backend.configuration().n_qubits backend.num_qubits backend.configuration().num_qubits backend.num_qubits backend.configuration().basis_gates backend.target.operation_names (*) backend.configuration().coupling_map backend.target.build_coupling_map() backend.configuration().local No representation backend.configuration().simulator No representation backend.configuration().conditional No representation backend.configuration().open_pulse No representation backend.configuration().memory No representation backend.configuration().max_shots No representation

    (*) Note that backend.target.operation_names includes basis_gates and additional non-gate instructions, in some implementations it might be necessary to filter the output.

    See this guide for more information on migrating to the BackendV2 model.


    • instruction_schedule_map()
    • drive_channel()
    • measure_channel()
    • acquire_channel()
    • control_channel()

    Consequently, the corresponding channel methods in the qiskit.providers.BackendV2Converter and qiskit.providers.fake_provider.GenericBackendV2 classes are being deprecated as well.

    In addition, the pulse_channels and calibrate_instructions arguments in the BackendV2 initializer method are being deprecated.

  • The defaults argument is being deprecated from the qiskit.providers.backend_compat.convert_to_target() function.

QPY Deprecations

  • As part of the Qiskit Pulse package deprecation, serializing a qiskit.pulse.ScheduleBlock-based payloads is being deprecated. In particular, passing qiskit.pulse.ScheduleBlock objects to the programs argument in the qiskit.qpy.dump() function is being deprecated.

Transpiler Deprecations

  • The transpiler pass ConvertConditionsToIfOps has been deprecated and will be removed in Qiskit 2.0.0. This class is now deprecated because the underlying data model for Instruction.condition which this pass is converting from has been deprecated and will be removed in 2.0.0.

  • Providing custom gates through the basis_gates argument is deprecated for both transpile() and generate_preset_pass_manager(), this functionality will be removed in Qiskit 2.0. Custom gates are still supported in the Target model, and can be provided through the target argument. One can build a Target instance from scratch or use the Target.from_configuration() method with the custom_name_mapping argument. For example:

    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
    )

    • calibration
    • update_from_instruction_schedule_map()
    • has_calibration()
    • get_calibration()
    • instruction_schedule_map()

    In addition the following transpiler passer are also being deprecated:

    • PulseGates
    • ValidatePulseGates
    • RXCalibrationBuilder
    • RZXCalibrationBuilder
    • EchoRZXWeylDecomposition
  • The inst_map argument in generate_preset_pass_manager(), from_configuration(), PassManagerConfig initializer and generate_scheduling() is being deprecated.

  • The calibration argument in InstructionProperties() initializer methods is being deprecated.

  • The following transpile() and generate_preset_pass_manager() arguments are deprecated in favor of defining a custom Target: instruction_durations, timing_constraints, and backend_properties. These arguments can be used to build a target with Target.from_configuration():

    Target.from_configuration(
         ...
         backend_properties = backend_properties,
         instruction_durations = instruction_durations,
         timing_constraints = timing_constraints
    )
  • The method PassManagerConfig.from_backend() will stop supporting inputs of type BackendV1 in the backend parameter in a future release no earlier than 2.0. BackendV1 is deprecated and implementations should move to BackendV2.

Visualization Deprecations

  • As part of the Qiskit Pulse package deprecation, pulse drawing via qiskit.visualization.pulse_drawer() is being deprecated.

Misc. Deprecations

  • The qiskit.result.mitigation module has been deprecated and will be removed in the 2.0 release. The deprecation includes the LocalReadoutMitigator and CorrelatedReadoutMitigator classes as well as the associated utils. Their functionality has been superseded by the mthree package, found in https://github.com/Qiskit/qiskit-addon-mthree.


  • As part of the Qiskit Pulse package deprecation, the following functions and class are being deprecated as well:

    • qiskit.compiler.schedule()
    • qiskit.compiler.sequence()
    • qiskit.assembler.assemble_schedules()
    • qiskit.scheduler.methods.as_soon_as_possible()
    • qiskit.scheduler.methods.as_late_as_possible()
    • qiskit.scheduler.schedule_circuit.schedule_circuit()
    • qiskit.scheduler.ScheduleConfig

Bug Fixes

  • Fixed a bug in the transpiler pass ElidePermutations where the qubit mapping was not updated correctly in the presence of PermutationGates.

  • Fixed a potential source of non-determinism in DenseLayout (and by extension SabreLayout) when targeting a CouplingMap or Target that has more than one subgraph with the same degree of connectivity. In these case the exact output layout from the pass could previously fluctuate based on the number of local CPUs and thread execution speed.

  • The HighLevelSynthesis transpiler pass no longer raises an exception when encountering a custom gate that is called “qft” but is not an instance of QFTGate. Instead, the synthesis plugins for QFT gates ignore such a gate, and the gate’s definition is used (if provided).

  • Fixed an issue introduced in the now yanked 1.2.3 bugfix release that would cause an exception with the error message “Qiskit doesn’t support loading a symengine payload generated with symengine >= 1.0” to be raised whenever loading a QPY file that was generated with a different symengine version from the version installed by the loading. This issue could only occur in 1.2.3.

  • Fixed an issue with qpy.load() when loading a QPY file containing a ParameterExpression, if the versions of symengine installed in the generating and loading environments were not the same. For example, if a QPY file containing ParameterExpressions was generated using Qiskit 1.2.2 with symengine==0.11.0 installed, Qiskit 1.2.2 with syengine==0.13.0 installed would be unable to load it.

    Previously, an error would have been raised by symengine around this version mismatch. This has been worked around for symengine 0.11 and 0.13 (there was no 0.12), but if you’re trying to use different versions of symengine and there is a mismatch, this version of Qiskit still might not work.

  • Fixed an issue when running transpile() or run() on a pass manager generated by generate_preset_pass_manager() using optimization_level 2 or 3 when the routing_method argument is set to "none" to explicitly disable routing. Previously under these conditions the transpiler would run the ElidePermutation pass as part of the init stage as under normal conditions this is a useful optimization to remove SwapGate and PermutationGate instances from the circuit. But when routing_method="none" this optimization wasn’t expected as it permutes the circuit in a similar manner to routing which shouldn’t be performed when routing_method="none". This has been fixed by no longer running ElidePermutation if routing_method="none" is set. Fixed #13144

  • The OpenQASM 2 importer previously would output incorrect Gate instances for gate calls referring to a gate definition that followed a prior gate definition that was being treated as a built-in operation by a CustomInstruction. See #13339 for more detail.

  • The OpenQASM 3 exporter has restored its behavior of accepting non-standard-library include files in the includes argument to qasm3.dump(), dumps(), and Exporter. These will insert a suitable include statement into the output as before, and the exporter remains unaware of the intended gates in that include file; you should pass the gates you expect it to define in the basis_gates argument to the same functions.

    We expect to improve the export mechanism against non-standard include files in a future release of Qiskit.

  • Fixed a performance regression in QuantumCircuit.assign_parameters() introduced in Qiskit 1.2.0 when calling the method in a tight loop, binding only a small number of parameters out of a heavily parametric circuit on each iteration. If possible, it is still more performant to call assign_parameters() only once, with all assignments at the same time, as this reduces the proportion of time spent on input normalization and error-checking overhead.

  • For BasicSimulator, the basis_gates entry in the configuration instance returned by the configuration() is now a list rather than a dict_keys instance, matching the expected type and allowing for configuration instance to be deep copied.

  • Fixed a bug in QuantumCircuit.decompose() where objects that could be synthesized with HighLevelSynthesis were first synthesized and then decomposed immediately (i.e., they were decomposed twice instead of once). This affected, e.g., MCXGate or Clifford, among others.

  • Fixed a bug in QuantumCircuit.decompose(), where high-level objects without a definition were not decomposed if they were explicitly set via the "gates_to_decompose" argument. For example, previously the following did not perform a decomposition but now works as expected:

    from qiskit import QuantumCircuit
    from qiskit.quantum_info import Clifford
    from qiskit.transpiler.passes import Decompose
     
    cliff = Clifford(HGate())
    circuit = QuantumCircuit(1)
    circuit.append(cliff, [0])
     
    decomposed = Decompose(gates_to_decompose=["clifford"])(circuit)
  • Fixed Operator.power() when called with non-integer powers on a matrix whose Schur form is not diagonal (for example, most non-unitary matrices).

  • Operator.power() will now more reliably return the expected principal value from a fractional matrix power of a unitary matrix with a 1-1 eigenvalue. This is tricky in general, because floating-point rounding effects can cause a matrix to _truly_ have an eigenvalue on the negative side of the branch cut (even if its exact mathematical relation would not), and imprecision in various BLAS calls can falsely find the wrong side of the branch cut.

    Operator.power() now shifts the branch-cut location for matrix powers to be a small complex rotation away from 1-1. This does not solve the problem, it just shifts it to a place where it is far less likely to be noticeable for the types of operators that usually appear. Use the new branch_cut_rotation parameter to have more control over this.

    See #13305.


1.3.0b1

New Features

  • Added a new class QFTGate for natively representing Quantum Fourier Transforms (QFTs). The older way of representing QFTs via quantum circuits, see QFT, remains for backward compatibility. The new way of representing a QFT via a gate avoids synthesizing its definition circuit when the gate is declared, delaying the actual synthesis to the transpiler. It also allows to easily choose between several different algorithms for synthesizing QFTs, which are available as high-level-synthesis plugins.

  • Added a synthesis method synth_qft_full() for constructing a QFT circuit assuming a fully-connected architecture.

  • Added two high-level-synthesis plugins for synthesizing a QFTGate. The class QFTSynthesisFull is based on synth_qft_full() and synthesizes a QFT gate assuming all-to-all connectivity. The class QFTSynthesisLine is based on synth_qft_line() and synthesizes a QFT gate assuming linear nearest neighbor connectivity.

  • MCXVChain has two new Boolean parameters relative_phase and action_only. If action_only the circuit does not clean the dirty qubits. If relative_phase the gate is implemented up to a global phase. Both parameters are used to optimize the decomposition of MCXVChain.

  • Added two parameters to GenericBackendV2 to exclude error (noise_info) and pulse channel information (pulse_channels) from the construction of the backend. These parameters are true by default, replicating the initial default behavior of the constructor. A memory-sensitive user may set these options to False to reduce the memory overhead by 40x when transpiling on large- scale GenericBackendV2.

  • The StabilizerState class now has a new method probabilities_dict_from_bitstring() allowing the user to pass single bitstring to measure an outcome for. Previouslly the probabilities_dict() would be utilized and would at worst case calculate (2^n) number of probability calculations (depending on the state), even if a user wanted a single result. With this new method the user can calculate just the single outcome bitstring value a user passes to measure the probability for. As the number of qubits increases, the more prevelant the performance enhancement may be (depending on the state) as only 1 bitstring result is measured.

  • Implemented UniformSuperpositionGate class, which allows the creation of a uniform superposition state using the Shukla-Vedula algorithm. This feature facilitates the creation of quantum circuits that produce a uniform superposition state 1Mj=0M1j\frac{1}{\sqrt{M}} \sum_{j=0}^{M-1} |j\rangle, where MM is a positive integer representing the number of computational basis states with an amplitude of 1M\frac{1}{\sqrt{M}}. This implementation supports the efficient creation of uniform superposition states, requiring only O(log2(M))O(\log_2 (M)) qubits and O(log2(M))O(\log_2 (M)) gates. Usage example:

    from qiskit import QuantumCircuit 
    from qiskit.circuit.library.data_preparation import UniformSuperpositionGate
     
    M = 5
    num_qubits = 3
    usp_gate = UniformSuperpositionGate(M, num_qubits)
    qc = QuantumCircuit(num_qubits)
    qc.append(usp_gate, list(range(num_qubits)))
     
    qc.draw()

Circuits Features

  • Added support for AnnotatedOperation.params() and AnnotatedOperation.validate_parameter(), which enable circuit-level parameter handling (such as binding parameters) for annotated operations.

  • CircuitInstruction and DAGOpNode each have new methods to query various properties of their internal Operation, without necessarily needing to access it. These methods are:

    If applicable, using any of these methods is significantly faster than querying CircuitInstruction.operation or DAGOpNode.op directly, especially if the instruction or node represents a Qiskit standard gate. This is because the standard gates are stored natively in Rust, and their Python representation is only created when requested.

  • A native rust representation of Qiskit’s standard gate library has been added. When a standard gate is added to a QuantumCircuit or DAGCircuit it is now represented in a more efficient manner directly in Rust seamlessly. Accessing that gate object from a circuit or dag will return a new Python object representing the standard gate. This leads to faster and more efficient transpilation and manipulation of circuits for functionality written in Rust.

  • Improved performance of the method DAGCircuit.quantum_causal_cone() by not examining the same non-directive node multiple times when reached from different paths.

  • Replacing the internal synthesis algorithm of StatePreparation and Initialize of Shende et al. by the algorithm given in Isometry of Iten et al. The new algorithm reduces the number of CX gates and the circuit depth by a factor of 2.

  • ParameterExpression now supports the unary + operator.

  • Added a new function to qiskit.circuit.random that allows to generate a pseudo-random Clifford circuit with gates from the standard library: random_clifford_circuit(). Example usage:

    from qiskit.circuit.random import random_clifford_circuit
     
    circ = random_clifford_circuit(num_qubits=2, num_gates=6)
    circ.draw(output='mpl')
    _images/release_notes-8.png
  • A new data attribute, qiskit.circuit.CONTROL_FLOW_OP_NAMES, is available to easily find and check whether a given Instruction is a control-flow operation by name.

  • The standard equivalence library (SessionEquivalenceLibrary) now has rules that can directly convert between Qiskit’s standard-library 2q continuous Ising-type interactions (e.g. CPhaseGate, RZZGate, RZXGate, and so on) using local equivalence relations. Previously, several of these conversions would go via a 2-CX form, which resulted in less efficient circuit generation.

    Note

    In general, the BasisTranslator is not guaranteed to find the “best” equivalence relation for a given Target, but will always find an equivalence if one exists. We rely on more expensive resynthesis and gate-optimization passes in the transpiler to improve the output. These passes are currently not as effective for basis sets with a continuously parametrized two-qubit interaction as they are for discrete super-controlled two-qubit interactions.

  • The random_circuit function from qiskit.circuit.random.utils has a new feature where users can specify a distribution num_operand_distribution (a dict) that specifies the ratio of 1-qubit, 2-qubit, 3-qubit, and 4-qubit gates in the random circuit. For example, if num_operand_distribution = {1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25} is passed to the function then the generated circuit will have approximately 25% of 1-qubit, 2-qubit, 3-qubit, and 4-qubit gates (The order in which the dictionary is passed does not matter i.e. you can specify num_operand_distribution = {3: 0.5, 1: 0.0, 4: 0.3, 2: 0.2} and the function will still work as expected). Also it should be noted that the if num_operand_distribution is not specified then max_operands will default to 4 and a random circuit with a random gate distribution will be generated. If both num_operand_distribution and max_operands are specified at the same time then num_operand_distribution will be used to generate the random circuit. Example usage:

    from qiskit.circuit.random import random_circuit
     
    circ = random_circuit(num_qubits=6, depth=5, num_operand_distribution = {1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25})
    circ.draw(output='mpl')
  • PauliFeatureMap and ZZFeatureMap now support specifying the entanglement as a dictionary where the keys represent the number of qubits, and the values are lists of integer tuples that define which qubits are entangled with one another. This allows for more flexibility in constructing feature maps tailored to specific quantum algorithms. Example usage:

    from qiskit.circuit.library import PauliFeatureMap
    entanglement = {
      1: [(0,), (2,)],
      2: [(0, 1), (1, 2)],
      3: [(0, 1, 2)],
    }
    qc = PauliFeatureMap(3, reps=2, paulis=['Z', 'ZZ', 'ZZZ'], entanglement=entanglement, insert_barriers=True)
    qc.decompose().draw('mpl')
  • The count_ops() method in QuantumCircuit has been re-written in Rust. It now runs between 3 and 9 times faster.

  • Added the insert_barriers keyword argument to the repeat() method. Setting it to True will insert barriers between circuit repetitions.

  • QuantumCircuit has several new methods to work with and inspect manual Var variables.

    See Working with real-time typed classical data for more in-depth discussion on all of these.

    The new methods are:

    In addition, there are several new dynamic attributes on QuantumCircuit surrounding these variables:

  • ControlFlowOp and its subclasses now have a iter_captured_vars() method, which will return an iterator over the unique variables captured in any of its immediate blocks.

  • DAGCircuit has several new methods to work with and inspect manual Var variables. These are largely equivalent to their QuantumCircuit counterparts, except that the DAGCircuit ones are optimized for programmatic access with already defined objects, while the QuantumCircuit methods are more focussed on interactive human use.

    The new methods are:

    There are also new public attributes:

  • DAGCircuit.wires will now also contain any Var manual variables in the circuit as well, as these are also classical data flow.

  • A new method, Var.new(), is added to manually construct a real-time classical variable that owns its memory.

  • QuantumCircuit.compose() has two need keyword arguments, var_remap and inline_captures to better support real-time classical variables.

    var_remap can be used to rewrite Var nodes in the circuit argument as its instructions are inlined onto the base circuit. This can be used to avoid naming conflicts.

    inline_captures can be set to True (defaults to False) to link all Var nodes tracked as “captures” in the argument circuit with the same Var nodes in the base circuit, without attempting to redeclare the variables. This can be used, in combination with QuantumCircuit.copy_empty_like()’s vars_mode="captures" handling, to build up a circuit layer by layer, containing variables.

  • DAGCircuit.compose() has a new keyword argument, inline_captures, which can be set to True to inline “captured” Var nodes on the argument circuit onto the base circuit without redeclaring them. In conjunction with the vars_mode="captures" option to several DAGCircuit methods, this can be used to combine DAGs that operate on the same variables.

  • QuantumCircuit.copy_empty_like() and DAGCircuit.copy_empty_like() have a new keyword argument, vars_mode which controls how any memory-owning Var nodes are tracked in the output. By default ("alike"), the variables are declared in the same input/captured/local mode as the source. This can be set to "captures" to convert all variables to captures (useful with compose()) or "drop" to remove them.

  • A new vars_mode keyword argument has been added to the DAGCircuit methods:

    which has the same meaning as it does for copy_empty_like().

Primitives Features

  • Added a new method BitArray.postselect() that returns all shots containing specified bit values. Example usage:

    from qiskit.primitives.containers import BitArray
     
    ba = BitArray.from_counts({'110': 2, '100': 4, '000': 3})
    print(ba.postselect([0,2], [0,1]).get_counts())
    # {'110': 2, '100': 4}
  • The metadata of Primitives V2 implementations, i.e., StatevectorSampler, StatevectorEstimator, BackendSamplerV2 and BackendEstimatorV2, has been updated to match that of IBM quantum devices.

    Note that metadata of StatevectorEstimator does not have shots because the class computes expectation values with Statevector and shots are not used.

  • Estimator and StatevectorEstimator return expectation values in a stochastic way if the input circuit includes a reset for a some subsystems. The result was not reproducible, but it is now reproducible if a random seed is set. For example:

    from qiskit.primitives import StatevectorEstimator
     
    estimator = StatevectorEstimator(seed=123)

    or:

    from qiskit.primitives import Estimator
     
    estimator = Estimator(options={"seed":123})

OpenQASM Features

  • The internal symbol table of the OpenQASM 3 exporter (qiskit.qasm3) has been rewritten, which should result in cleaner outputs when using Qiskit standard-library gates that are not in the OpenQASM 3 standard-library headers, and more deterministic outputs. For example, using several RZXGates will now result in only a single parametric definition, and when naming collisions occur, the symbol table will assign a deterministic counter to make names unique, rather than a non-deterministic integer (previously, the object identity was used).

  • The vendored version of the OpenQASM 3.0 standard library has been updated to match the state as of commit 4ca1d79383. This should generally have no effect on your use of Qiskit, unless you were retrieving our vendored file for your own use.

  • The OpenQASM 3 exporter supports manual-storage Var nodes on circuits.

QPY Features

  • QPY (qiskit.qpy) format version 12 has been added, which includes support for memory-owning Var variables. See Version 12 for more detail on the format changes.

Synthesis Features

  • Port internal binary matrix utils from Python to Rust, including binary matrix multiplication, gaussian elimination, rank calculation, binary matrix inversion, and random invertible binary matrix generation. These functions are not part of the Qiskit API, and porting them to rust improves the performance of certain synthesis methods.

  • MCXRecursive with kk control qubits and a single clean auxiliary qubit now requires at most 16k816k-8 CX gates.

  • Port synth_permutation_acg(), used to synthesize qubit permutations, to Rust. This produces an approximate 3x performance improvement on 1000 qubit circuits.

  • Port synth_permutation_basic(), used to synthesize qubit permutations, to Rust.

  • Port synth_cnot_full_pmh(), used to synthesize a linear function into a CX network, to Rust. This produces approximately 44x speedup, as measured on 100 qubit circuits.

  • The function synth_cnot_full_pmh() now allows choosing the (heuristically) optimal section_size by setting it to None. Then, a value is chosen which attempts to minimize the upper bound on the number of CX gates, that is αlog2(n)\alpha \log_2(n) where nn is the number of qubits and α0.56\alpha \approx 0.56.

  • The function synth_clifford_bm() was ported to Rust. Recall that this function optimally synthesizes Clifford operators on 1, 2 or 3 qubits with respect to the number of CX-gates. This leads to a significant increase in performance. For Cliffords over 3 qubits, the speedup in on the order of 80 times.

  • The function synth_clifford_greedy() that synthesizes Clifford operators was ported to Rust, leading to a significant increase in performance for all numbers of qubits. For Cliffords over 50 qubits, the speedup is on the order of 1000 times.

  • Port synth_permutation_depth_lnn_kms(), used to synthesize permutations for linear connectivity, to Rust.

  • Added synthesis functions synth_mcx_gray_code() and synth_mcx_noaux_v24() that synthesize multi-controlled X gates. These functions do not require additional ancilla qubits.

  • Added synthesis functions synth_c3x() and synth_c4x() that synthesize 3-controlled and 4-controlled X-gates respectively.

  • Add a synthesis function synth_mcx_n_dirty_i15() that synthesizes a multi-controlled X gate with kk controls using k2k - 2 dirty ancillary qubits producing a circuit with at most 8k68 * k - 6 CX gates, by Iten et. al. (arXiv:1501.06911).

  • Add a synthesis function synth_mcx_n_clean_m15() that synthesizes a multi-controlled X gate with kk controls using k2k - 2 clean ancillary qubits producing a circuit with at most 6k66 * k - 6 CX gates, by Maslov (arXiv:1508.03273).

  • Add a synthesis function synth_mcx_1_clean_b95() that synthesizes a multi-controlled X gate with kk controls using a single clean ancillary qubit producing a circuit with at most 16k816 * k - 8 CX gates, by Barenco et al. (arXiv:quant-ph/9503016).

  • The function random_clifford() was ported to Rust, improving the runtime by a factor of 3.

  • Port :func: .synth_cz_depth_line_mr to Rust. This function synthesizes a CZ circuit for linear nearest neighbor (LNN) connectivity, based on the Maslov and Roetteler method. On a 350x350 binary matrix, the Rust implementation yields a speedup of about 30 times.

  • Port :func: .synth_permutation_reverse_lnn_kms to Rust, which synthesizes a reverse permutation for linear nearest-neighbor architecture using the Kutin, Moulton, Smithline method.

  • Added the wrap keyword argument to the ProductFormula classes which (when enabled) wraps individual Pauli evolution terms. This can be useful when visualizing circuits.

Transpiler Features

  • Added a new import path option for generate_preset_pass_manager(), so that it can now be imported as:

    from qiskit import generate_preset_pass_manager

    instead of having to type the full path:

    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

    The function is also importable from the qiskit.transpiler module as:

    from qiskit.transpiler import generate_preset_pass_manager
  • Added a new user config file option sabre_all_threads and a corresponding environment variable QISKIT_SABRE_ALL_THREADS. When this flag is set the preset pass managers will run the SabreLayout and SabreSwap transpiler passes using all the available CPUs on the local system. Using this option is a tradeoff between determinism of output between different computers and potentially better output with fewer SwapGates.

    These transpiler passes run multiple random trials in parallel and pick the output which results in the fewest SwapGates. As a rule of thumb, if you run more trials, this provides the algorithm more opportunities to find a better result. By default, the preset pass managers use a fixed number of trials, in this release 5 trials for levels 0 and 1, and 20 trials for levels 2 and 3, but these numbers may change in future releases (and were different in historical releases). Using a fixed number of trials results in deterministic results regardless of the local system, because even with a fixed seed if you were to default to the number of local CPUs available the results would different when running between different computers.

    If the default number of trials for a given optimization level is higher than the number of local CPUs it will use the optimization level default which is higher.

  • The optimization_level argument for the generate_preset_pass_manager() function is now optional. If it’s not specified it will default to using optimization level 2. As the argument is now optional, the first positional argument has been expanded to enable passing a Target or a BackendV2 as the first argument for more convenient construction. For example:

    from qiskit.transpiler.preset_passmanager import generate_preset_pass_manager
    from qiskit.providers.fake_provider import GenericBackendV2
     
    backend = GenericBackendV2(100)
     
    generate_preset_pass_manager(backend.Target)

    will construct a default pass manager for the 100 qubit :class`.GenericBackendV2` instance.

  • Added a new pass Split2QUnitaries that iterates over all two-qubit gates or unitaries in a circuit and replaces them with two single-qubit unitaries, if possible without introducing errors, i.e. the two-qubit gate/unitary is actually a (kronecker) product of single-qubit unitaries.

  • The passes Collect2qBlocks, ConsolidateBlocks and Split2QUnitaries have been added to the init stage of the preset pass managers with optimization level 2 and optimization level 3. The modification of the init stage should allow for a more efficient routing for quantum circuits that either:

    • contain two-qubit unitaries/gates that are actually a product of single-qubit gates
    • contain multiple two-qubit gates in a continuous block of two-qubit gates.

    In the former case, the routing of the two-qubit gate can simply be skipped as no real interaction between a pair of qubits occurs. In the latter case, the lookahead space of routing algorithms is not ‘polluted’ by superfluous two-qubit gates, i.e. for routing it is sufficient to only consider one single two-qubit gate per continuous block of two-qubit gates. These passes are not run if the pass managers target a Target that has a discrete basis gate set, i.e. all basis gates have are not parameterized.

  • Port part of the logic from the StarPrerouting, used to find a star graph connectivity subcircuit and replaces it with a linear routing equivalent.

  • The function star_preroute() now performs the heavily lifting to transform the dag by in the rust space by taking advantage of the functions _build_sabre_dag() and _apply_sabre_result().

  • A new dt argument has been added to generate_preset_pass_manager() to match the set of arguments of transpile(). This will allow for the internal conversion of transpilation constraints to a Target representation.

  • Added multiple high-level-synthesis plugins for synthesizing an MCXGate:

    As well:

    • MCXSynthesisDefault, choosing the most efficient synthesis method based on the number of clean and dirty ancilla qubits available.

    As an example, consider how the transpilation of the following circuit:

    from qiskit.circuit import QuantumCircuit
    from qiskit.compiler import transpile
     
    qc = QuantumCircuit(7)
    qc.x(0)
    qc.mcx([0, 1, 2, 3], [4])
    qc.mcx([0, 1, 2, 3, 4], [5])
    qc.mcx([0, 1, 2, 3, 4, 5], [6])
     
    transpile(qc)

    For the first MCX gate, qubits 5 and 6 can be used as clean ancillas, and the best available synthesis method synth_mcx_n_clean_m15 will get chosen. For the second MCX gate, qubit 6 can be used as a clean ancilla, the method synth_mcx_n_clean_m15 no longer applies, so the method synth_mcx_1_clean_b95 will get chosen. For the third MCX gate, there are no ancilla qubits, and the method synth_mcx_noaux_v24 will get chosen.

  • The implementation of the DAGCircuit has been rewritten in Rust. This rewrite of the Python class should be fully API compatible with the previous Python implementation of the class. While the class was previously implemented using rustworkx and its underlying data graph structure existed in Rust, the implementation of the class and all the data was stored in Python. This new version of DAGCircuit stores a Rust native representation for all its data and is fully implemented in Rust. This new implementation should be more efficient in memory usage as it compresses the qubit and clbit representation for instructions at rest. It also enables speed up for transpiler passes as they can fully manipulate a DAGCircuit from Rust.

  • A new argument qubits_initially_zero has been added to qiskit.compiler.transpile(), generate_preset_pass_manager(), and to PassManagerConfig. If set to True, the qubits are assumed to be initially in the state 0|0\rangle, potentially allowing additional optimization opportunities for individual transpiler passes.

  • The constructor for HighLevelSynthesis transpiler pass now accepts an additional argument qubits_initially_zero. If set to True, the pass assumes that the qubits are initially in the state 0|0\rangle. In addition, the pass keeps track of clean and dirty auxiliary qubits throughout the run, and passes this information to plugins via kwargs num_clean_ancillas and num_dirty_ancillas.

  • Added a new method DAGCircuit.control_flow_ops() which provides a fast path to get all the DAGOpNode in a DAGCircuit that contain a ControlFlowOp. This was possible before using the DAGCircuit.op_nodes() method and passing the ControlFlowOp class as a filter, but this new function will perform the operation faster.

  • Ported the entirety of the Optimize1qGatesDecomposition transpiler pass to Rust. This improves the runtime performance of the pass between 5x to 10x.

  • Added a Rust implementation of CommutationAnalysis in analyze_commutations().

  • The the CommutationChecker class has been reimplemented in Rust. This retains the same functionality as before but is now significantly in most cases.

  • The RemoveDiagonalGatesBeforeMeasure transpiler pass has been upgraded to include more diagonal gates: PhaseGate, CPhaseGate, CSGate, CSdgGate and CCZGate. In addition, the code of the RemoveDiagonalGatesBeforeMeasure was ported to Rust, and is now x20 faster for a 20 qubit circuit.

Visualization Features

  • The user configuration file has a new option circuit_idle_wires, which takes a Boolean value. This allows users to set their preferred default behavior of the idle_wires option of the circuit drawers QuantumCircuit.draw() and circuit_drawer(). For example, adding a section to ~/.qiskit/settings.conf with:

    [default]
    circuit_idle_wires = false

    will change the default to display the bits in reverse order.

  • The text and Matplotlib circuit drawers (QuantumCircuit.draw()) have minimal support for displaying expressions involving manual real-time variables. The Store operation and the variable initializations are not yet supported; for large-scale dynamic circuits, we recommend using the OpenQASM 3 export capabilities (qasm3.dumps()) to get a textual representation of a circuit.

Misc. Features

  • Added a new build-time environment variable QISKIT_NO_CACHE_GATES which when set to a value of 1 (i.e. QISKIT_NO_CACHE_GATES=1) which decreases the memory overhead of a CircuitInstruction and DAGOpNode object at the cost of decreased runtime on multiple accesses to CircuitInstruction.operation and DAGOpNode.op. If this environment variable is set when building the Qiskit python package from source the caching of the return of these attributes will be disabled.

Upgrade Notes

  • The minimum supported version of Python is now 3.9, this has been raised from the previous minimum support version of 3.8. This change was necessary because the upstream cPython project no longer supports Python 3.8.

Circuits Upgrade Notes

  • The annotated argument of the Gate.control() method is now None, which allows Qiskit to choose whether to annotate a controlled operation. If the concrete implementation (annotated=False) is available, it will be returned by default. Otherwise, the annotated implementation will be returned (annotated=True). This allows, for example, to defer the synthesis of controlled, parameterized gates.

  • The Operation instances of DAGOpNode.op being returned will not necessarily share a common reference to the underlying object anymore. This was never guaranteed to be the case and mutating the op directly by reference was unsound and always likely to corrupt the dag’s internal state tracking Due to the internal refactor of the QuantumCircuit and DAGCircuit to store standard gates in rust the output object from DAGOpNode.op will now likely be a copy instead of a shared instance. If you need to mutate an element should ensure that you either do:

    op = dag_node.op
    op.params[0] = 3.14159
    dag_node.op = op

    or:

    op = dag_node.op
    op.params[0] = 3.14159
    dag.substitute_node(dag_node, op)

    instead of doing something like:

    dag_node.op.params[0] = 3.14159

    which will not work for any standard gates in this release. It would have likely worked by chance in a previous release but was never an API guarantee.

  • The Operation instances of CircuitInstruction.operation being returned will not necessarily share a common reference to the underlying object anymore. This was never guaranteed to be the case and mutating the operation directly by reference was unsound and always likely to corrupt the circuit, especially when parameters were in use. Due to the internal refactor of the QuantumCircuit to store standard gates in rust the output object from CircuitInstruction.operation will now likely be a copy instead of a shared instance. If you need to mutate an element in the circuit (which is strongly not recommended as it’s inefficient and error prone) you should ensure that you do:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(1)
    qc.p(0)
     
    op = qc.data[0].operation
    op.params[0] = 3.14
     
    qc.data[0] = qc.data[0].replace(operation=op)

    instead of doing something like:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(1)
    qc.p(0)
     
    qc.data[0].operation.params[0] = 3.14

    which will not work for any standard gates in this release. It would have likely worked by chance in a previous release but was never an API guarantee.

Primitives Upgrade Notes

  • BitArray.slice_bits() and BitArray.slice_shots() will now raise IndexError when indices are out of bounds. They used to raise ValueError in the case.

  • BitArray.__getitem__() will now raise IndexError when indices are out of bounds or the number of dimensions of indices does not match that of BitArray. They used to raise ValueError in the case.

Providers Upgrade Notes

  • Implementations of BackendV2 (and BackendV1) may desire to update their run() methods to eagerly reject inputs containing typed classical variables (see qiskit.circuit.classical) and the Store instruction, if they do not have support for them. The new Store instruction is treated by the transpiler as an always-available “directive” (like Barrier); if your backends do not support this won’t be caught by the transpiler.

    See Real-time variables for more information.

QPY Upgrade Notes

Synthesis Upgrade Notes

  • The atomic_evolution argument to ProductFormula (and its subclasses) has a new function signature. Rather than taking some Pauli operator and time coefficient and returning the evolution circuit, the new function takes in an existing circuit and should append the evolution of the provided Pauli and given time to this circuit. This new implementation benefits from significantly better performance.

  • LieTrotter and SuzukiTrotter no longer wrap the individually evolved Pauli terms into gate definitions. If you rely on a certain decomposition level of your circuit, you have to remove one level of decompose() or add the wrap=True keyword argument to your synthesis object.

Transpiler Upgrade Notes

  • The default optimization_level used by the transpile() function when one is not specified has been changed to level 2. This makes it consistent with the default used by generate_preset_pass_manager() which is used internally by transpile(). Optimization level 2 provides a much better balance between the run time of the function and the optimizations it performs, it’s a better tradeoff to use by default.

    The API of transpile() remains unchanged because, fundamentally, level 2 and level 1 have the same semantics. If you were previously relying on the implicit default of level 1, you can simply set the argument optimization_level=1 when you call transpile(). Similarly you can change the default back in your local environment by using a user config file and setting the transpile_optimization_level field to 1.

    The only potential issue is that your transpilation workflow may be relying on an implicit trivial layout (where qubit 0 in the circuit passed to transpile() is mapped to qubit 0 on the target backend/coupling, 1->1, 2->2, etc.) without specifying optimization_level=1, layout_method="trivial", or explicitly setting initial_layout when calling transpile(). This behavior was a side effect of the preset pass manager construction in optimization level 1 and is not mirrored in level 2. If you need this behavior you can use any of the three options listed previously to make this behavior explicit.

    Similarly, if you were targeting a discrete basis gate set you may encounter an issue using the new default with optimization level 2 (or running explicitly optimization level 3), as the additional optimization passes that run in level 2 and 3 don’t work in all cases with a discrete basis. You can explicitly set optimization_level=1 manually in this case. In general the transpiler does not currently fully support discrete basis sets and if you’re relying on this you should likely construct a pass manager manually to build a compilation pipeline that will work with your target.

  • The default routing pass used by optimization level 0 for generate_preset_pass_manager() and transpile() has been changed from StochasticSwap to SabreSwap. The SabreSwap pass performs exactly the same function but performs better in both runtime and output quality (in number of swap gates and depth) compared to StochasticSwap. For optimization_level=0 this shouldn’t matter because it’s not expected to run routing for the typical use case of level 0.

    If you were relying on the previous default routing algorithm for any reason you can use the routing_method argument for transpile() and generate_preset_pass_manager() to "stochastic" to use the StochasticSwap pass.

  • The generate_preset_pass_manager() function has been upgraded to, when possible, internally convert transpiler constraints into a Target instance. If a backend input of type BackendV1 is provided, it will be converted to BackendV2 to expose its Target. This change does not require any user action.

  • DAGNode objects (and its subclasses DAGInNode, DAGOutNode, and DAGOpNode) no longer return references to the same underlying object from DAGCircuit methods. This was never a guarantee before that all returned nodes would be shared reference to the same object, but with the migration of the DAGCircuit to Rust when a DAGNode a new DAGNode instance is generated on the fly when a node is returned to Python. These objects will evaluate as equal using == or similar checks that rely on __eq__ but will no longer identify as the same object.

  • The DAGOpNode instances returned from the DAGCircuit are no longer shared references to the underlying data stored on the DAG. In previous release it was possible to do something like:

    for node in dag.op_nodes():
        node.op = new_op

    however this type of mutation was always unsound as it could break the DAG’s internal caching and cause corruption of the data structure. Instead you should use the API provided by DAGCircuit for mutation such as DAGCircuit.substitute_node(), DAGCircuit.substitute_node_with_dag(), or DAGCircuit.contract_node(). For example the above code block would become:

    for node in dag.op_nodes():
      dag.substitute_node(node, new_op)

    This is similar to an upgrade note from 1.2.0 where this was noted on for mutation of the DAGOpNode.op attribute, not the DAGOpNode itself. However in 1.3 this extends to the entire object, not just it’s inner op attribute. In general this type of mutation was always unsound and not supported, but could previously have potentially worked in some cases.

  • The transpile() now assumes that the qubits are initially in the state 0|0\rangle. To avoid this assumption, one can set the argument qubits_initially_zero to False.

Misc. Upgrade Notes

  • The minimum version of rustworkx required to run this release has been increased from 0.14.0 to 0.15.0. This is required because Qiskit is now using new functionality added in the rustworkx 0.15.0 release which improves performance.

Circuits Deprecations

  • The following circuit methods were not intended for public use, but were accidentally left documented in the public API during the 1.0 release. They are now deprecated from Qiskit 1.2 and will be removed in Qiskit 2.0:

    • QuantumCircuit.cast
    • QuantumCircuit.cls_instances
    • QuantumCircuit.cls_prefix
    • QuantumCircuit.cbit_argument_conversion
    • QuantumCircuit.qbit_argument_conversion
  • Treating CircuitInstruction as a tuple-like iterable is deprecated, and this legacy path way will be removed in Qiskit 2.0. You should use the attribute-access fields operation, qubits, and clbits instead. For example:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure([0, 1], [0, 1])
     
    # Deprecated.
    for op, qubits, clbits in qc.data:
        pass
    # New style.
    for instruction in qc.data:
        op = instruction.operation
        qubits = instruction.qubits
        clbits = instruction.clbits

Primitives Deprecations

  • Primitive V1 implementations and V1-exclusive non-versioned type aliases are now deprecated in favor of their V2 counterparts. The deprecation is extended to the following classes implementing V1 interfaces:

    As well as the following non-versioned type aliases:

    This deprecation does NOT affect the explicitly-versioned BaseEstimatorV1 and BaseSamplerV1 abstract interface definitions or related result and job classes.

    In addition, the following utility functions are deprecated:

Providers Deprecations

  • The BackendV1 class is deprecated and it will be removed not earlier than the next major release. There are several migration paths available depending on the main purpose of the backend object:

    - To expose backend information with no access to execution (just a hardware description), consider constructing a :class:`.Target` directly.
    - To provides access to execution capabilities, consider relying on the primitives interfaces instead.
    - Alternatively, to continue providing simultaneous :class:`.Target` (hardware information) and ``run`` (execution) capabilities, consider moving to :class:`.BackendV2` (see <https://qisk.it/backendV1-to-V2>).
  • The models in qiskit.providers.models are part of the deprecated BackendV1 workflow and no longer necessary for BackendV2. If a user workflow requires these representations it likely relies on deprecated functionality and should be updated to use BackendV2.

  • The Qobj structure and related classes are now deprecated, they were introduced as part of the BackendV1 workflow and are no longer necessary for interacting with BackendV2 backends. Remote backend interaction should be done via QPY or OpenQASM instead.

Transpiler Deprecations

  • The assemble function is now deprecated and will be removed in the 2.0 release. The function was primarily used to create a Qobj, which is no longer necessary in BackendV2-based workflows. It was also used for binding parameters, a functionality fully covered by assign_parameters().

  • Deprecated StochasticSwap which has been superseded by SabreSwap. If the class is called from the transpile function, the change would be, for example:

    from qiskit import transpile
    from qiskit.circuit import QuantumCircuit
    from qiskit.transpiler import CouplingMap
    from qiskit.providers.fake_provider import GenericBackendV2
     
     
    qc = QuantumCircuit(4)
    qc.h(0)
    qc.cx(0, range(1, 4))
    qc.measure_all()
     
    cmap = CouplingMap.from_heavy_hex(3)
    backend = GenericBackendV2(num_qubits=cmap.size(), coupling_map=cmap)
     
    tqc = transpile(
        qc,
        routing_method="stochastic",
        layout_method="dense",
        seed_transpiler=12342,
        target=backend.target
    )

    to:

    tqc = transpile(
        qc,
        routing_method="sabre",
        layout_method="sabre",
        seed_transpiler=12342,
        target=backend.target
    )

    While for a pass manager, the change would be:

    passmanager = PassManager(StochasticSwap(coupling, 20, 13))
    new_qc = passmanager.run(qc)

    to:

    passmanager = PassManager(SabreSwap(backend.target, "basic"))
    new_qc = passmanager.run(qc)

Visualization Deprecations

  • The transition_visualization() function has been deprecated and will be removed in the 2.0.0 release. This function had a number of limitations which limited it’s utility to only very specific use cases and didn’t fit in with the rest of the Qiskit visualization module.

  • The justify argument of circuit_drawer() or QuantumCircuit.draw(), will no longer support invalid values (previously changing them to the default), and in a future release they will error. Valid justify values are "left", "right" or "none".

Bug Fixes

  • Fixes an issue with the visualizations of some backends/coupling maps that showed as folded on their own. The default ``neato` setting <https://graphviz.org/docs/layouts/neato/>`_ works well in most cases. However, prism overlap returns a more regular layout for other scenarios.

  • Fixed a series of issues when controlling parameterized standard gates. The controlled version of some gates (e.g. RXXGate or RYGate for more than 1 control) cannot be synthesized if they contain unbound parameters. Previously, calling .control() but now we create an AnnotatedOperation as placeholder. This allows to insert the controlled gate into a circuit, bind the parameters at a later stage, and then synthesize the operation. Fixes #10311, #10697, and #12135.

  • The SGate and SdgGate now correctly return a CSGate, resp. CSdgGate, if they are controlled on a single control qubit.

  • Parametric controlled standard-library gates (such as CRXGate) will now get correctly extracted to a Rust-space standard gate when using QuantumCircuit.append() and the gate object. Previously there was a discrepancy where using the QuantumCircuit.crx() method would cause a correct extraction in Rust space, but the append() form would not. The bug should generally not have caused any unsoundness from Python.

  • Fixed a bug in TwoQubitBasisDecomposer where the Rust-based code would panic if the given KAK gate wasn’t a Rust-space StandardGate.

  • Fixed a bug where InstructionDurations.from_backend() did not work for BackendV2 backends. Fixed #12760 <https://github.com/Qiskit/qiskit/issues/12760>.

  • Fixed a bug in BitArray.from_counts() and BitArray.from_samples(). Previously these would raise an error if given data containing only zeros, and no value for the optional argument num_bits. Now they produce a BitArray with BitArray.num_bits set to 1.

  • Fixed a bug in the ConsolidateBlocks transpiler pass, when the input circuit contains a custom opaque gate and neither the basis_gates or target options are set the pass would raise a QiskitError and fail. This has been corrected so that the in these situations the transpiler pass will not consolidate the block identified containing a custom gate instead of failing.

  • Fixed the definition of the CUGate matrix in Rust-space. While this was not noticable while handling the CUGate purely on Python side, this had knock-on effects when transpiler passes were using the Rust representation, such as could happen in Consolidate2qBlocks. Fixed #13118.

  • Fixed a bug in PadDynamicalDecoupling, which previously did not correctly display the error message that a delay is not pulse-aligned, if the previous or following node was an input/output node. Now, the error message is correctly displayed.

  • Fixed a bug in HoareOptimizer where a controlled gate was simplified by removing its controls but the new gate was not handled correctly.

  • The keyword argument order of the function BitArray.from_bool_array() should be ‘little’ or ‘big’. Added checks to raise error if an invalid value is entered.

  • Improve the decomposition of the gate generated by QuantumCircuit.mcx() without using ancilla qubits, so that the number of CXGate will grow quadratically in the number of qubits and not exponentially.

  • Fixed the behavior of generate_preset_pass_manager() to raise a ValueError exception if not provided with a non-negative integer seed_transpiler argument.

  • Fixed an edge case in SabreLayout, where in rare cases on large devices and challenging circuits, the routing would fail. This was due to the release valve making more than one two-qubit gate routable, where only one was expected. Fixed #13081.

  • Fixed a bug in Split2QUnitaries where it would fail to run on circuits with custom gates that didn’t implement __array__(). See #12984.

  • Fixed a bug in StatePreparation where the normalize argument was ignored for input arrays. Fixed #12984.

  • Fixed a bug of StatevectorSampler that ignored gates with c_if. It will raise an error because Statevector cannot handle c_if.

  • Fixed a bug where various synthesis methods created circuits without quantum or classical registers. This also affected functions that internally used the synthesis methods, such as Clifford.to_circuit(). Fixed #13041.

  • Fix a bug that caused the method Initialize.gates_to_uncompute() fail.

  • MCXVChain with k controls and k-2 dirty auxiliary qubits now requires 8k-6 cx gates.

  • Fixed a bug in synth_cnot_full_pmh() where providing a section_size that did not divide the number of qubits without remainder could lead to wrong results. Now any section_size (at most equal to the number of qubits) synthesizes the correct circuit. For a (heuristically) optimal value, set section_size=None.

  • The OpenQASM 3 exporter will now correctly error when asked to use a keyword or other invalid identifier as a “basis gate”, as it has no way of putting out correct output in these cases.

  • The OpenQASM 3 exporter (qiskit.qasm3) will now correctly export multiple instances of PauliEvolutionGate from a circuit. Previously, only a single instance would be exported, and all other instances would silently use the same (incorrect) version.

  • The OpenQASM 3 exporter (qiskit.qasm3) will now correctly escape gate names. Previously, a gate whose name was an invalid OpenQASM 3 identifier would cause invalid OpenQASM 3 to be generated.

  • Fixed an edge case when transpiling a circuit with optimization_level 2 or 3 with an incomplete 1-qubit basis gate set on a circuit containing 2-qubit gates, that can be implemented as a product of single qubit gates. This bug is resolved by restricting Split2QUnitaries to consider only UnitaryGate objects. Fixed #12970.

  • A series of input-handling inconsistencies between transpile() and generate_preset_pass_manager() have been fixed. These inconsistencies would lead to different transpilation outputs for the same inputs, or generate_preset_pass_manager() failing for certain input combinations accepted by transpile().

  • Changes the way in which the BackendEstimatorV2 class calculates the std to ensure that it matches the correct formula.

  • Fixed an issue where circuit_drawer() or the QuantumCircuit.draw() method would not raise a warning when an invalid value was passed to the justify argument, before changing it to the default. Now, it will raise a warning if an invalid value is passed. Valid justify values are "left", "right" or "none". Refer to #12089 <https://github.com/Qiskit/qiskit/issues/12089> for more details.

  • Fixed an issue with DAGCircuit.apply_operation_back() and DAGCircuit.apply_operation_front() where previously if you set a Clbit object to the input for the qargs argument it would silently be accepted. This has been fixed so the type mismatch is correctly identified and an exception is raised.

  • Fixed SparsePauliOp.apply_layout() and Pauli.apply_layout() to raise QiskitError if duplicate indices or negative indices are provided as part of a layout.

  • Fixed a missing decorator in C3SXGate that made it fail if Gate.to_matrix() was called. The gate matrix is now return as expected.

  • Fixed a bug with the "circular" and "sca" entanglement for NLocal circuits and its derivatives. For entanglement blocks of more than 2 qubits, the circular entanglement was previously missing some connections. For example, for 4 qubits and a block size of 3 the code previously used:

    [(2, 3, 0), (0, 1, 2), (1, 2, 3)]

    but now is correctly adding the (3, 0, 1) connections, that is:

    [(2, 3, 0), (3, 0, 1), (0, 1, 2), (1, 2, 3)]

    As such, the "circular" and "sca" entanglements use num_qubits entangling blocks per layer.

  • Add more Clifford gates to the CollectCliffords() transpiler pass. In particular, we have added the gates ECRGate(), DCXGate(), iSWAPGate(), SXGate() and SXdgGate() to this transpiler pass.

  • Fix a bug in Isometry due to an unnecessary assertion, that led to an error in UnitaryGate.control() when UnitaryGate had more that two qubits.

  • The QuantumCircuit.parameters attribute will now correctly be empty when using QuantumCircuit.copy_empty_like() on a parametric circuit. Previously, an internal cache would be copied over without invalidation. Fix #12617.

  • QuantumCircuit.depth() will now correctly handle operations that do not have operands, such as GlobalPhaseGate.

  • QuantumCircuit.depth() will now count the variables and clbits used in real-time expressions as part of the depth calculation.

  • Fix the SolovayKitaev transpiler pass when loading basic approximations from an exising .npy file. Previously, loading a stored approximation which allowed for further reductions (e.g. due to gate cancellations) could cause a runtime failure. Additionally, the global phase difference of the U(2) gate product and SO(3) representation was lost during a save-reload procedure. Fixes Qiskit/qiskit#12576.

  • Fixed SparsePauliOp.apply_layout() to work correctly with zero-qubit operators. For example, if you previously created a 0 qubit and applied a layout like:

    op = SparsePauliOp("")
    op.apply_layout(None, 3)

    this would have previously raised an error. Now this will correctly return an operator of the form: SparsePauliOp(['III'], coeffs=[1.+0.j])

  • Fixed a bug when SparsePauliOp.paulis is set to be a PauliList with nonzero phase, where subsequent calls to several SparsePauliOp methods would produce incorrect results. Now when SparsePauliOp.paulis is set to a PauliList with nonzero phase, the phase is absorbed into SparsePauliOp.coeffs, and the phase of the input PauliList is set to zero.

  • Fixed an oversight in the Commuting2qGateRouter transpiler pass where the qreg permutations were not added to the pass property set, so they would have to be tracked manually by the user. Now it’s possible to access the permutation through the output circuit’s layout property and plug the pass into any transpilation pipeline without loss of information.

  • Fixed a floating-point imprecision when scaling certain pulse units between seconds and nanoseconds. If the pulse was symbolically defined, an unnecessary floating-point error could be introduced by the scaling for certain builds of symengine, which could manifest in unexpected results once the symbols were fully bound. See #12392.

  • Fixed a bug in qiskit.visualization.pulse_v2.interface.draw() that didn’t draw pulse schedules when the draw function was called with a BackendV2 argument. Because the V2 backend doesn’t report hardware channel frequencies, the generated drawing will show ‘no freq.’ below each channel label.

  • Fixed an issue with dag_drawer() and DAGCircuit.draw() when attempting to visualize a DAGCircuit instance that contained Var wires. The visualizer would raise an exception trying to do this which has been fixed so the expected visualization will be generated.

  • The VF2Layout pass would raise an exception when provided with a Target instance without connectivity constraints. This would be the case with targets from Aer 0.13. The issue is now fixed.

  • Fixes an error when calling the method UnitaryGate.repeat(). Refer to #11990 for more details.

  • The constructor GenericBackendV2 was allowing to create malformed backends because it accepted basis gates that couldn’t be allocated in the backend size . That is, a backend with a single qubit should not accept a basis with two-qubit gates.

  • PassManager.run() will no longer waste time serializing itself when given multiple inputs if it is only going to work in serial.

  • ParameterExpression was updated so that fully bound instances that compare equal to instances of Python’s built-in numeric types (like float and int) also have hash values that match those of the other instances. This change ensures that these types can be used interchangeably as dictionary keys. See #12488.

  • Fixed that the entanglement in PauliFeatureMap and ZZFeatureMap could be given as List[int] or List[List[int]], which was incompatible with the fact that entanglement blocks of different sizes are used. Instead, the entanglement can be given as dictionary with {block_size: entanglement} pairs.

  • Fixed a bug in plot_coupling_map() that caused the edges of the coupling map to be colored incorrectly. See https://github.com/Qiskit/qiskit/pull/12369 for details.

  • The OpenQASM 2 parser (qiskit.qasm2) can now handle conditionals with integers that do not fit within a 64-bit integer. Fixed #12773.

  • The OpenQASM 2.0 parser (qasm2.load() and qasm2.loads()) can now evaluate gate-angle expressions including integer operands that would overflow the system-size integer. These will be evaluated in a double-precision floating-point context, just like the rest of the expression always has been. Beware: an arbitrarily large integer will not necessarily be exactly representable in double-precision floating-point, so there is a chance that however the circuit was generated, it had already lost all numerical precision modulo 2π2\pi.

  • Custom gates (those stemming from a gate statement) in imported OpenQASM 2 programs will now have an Gate.to_matrix() implementation. Previously they would have no matrix definition, meaning that roundtrips through OpenQASM 2 could needlessly lose the ability to derive the gate matrix. Note, though, that the matrix is calculated by recursively finding the matrices of the inner gate definitions, as Operator does, which might be less performant than before the round-trip.

  • Previously, DAGCircuit.replace_block_with_op() allowed to place an n-qubit operation onto a block of m qubits, leaving the DAG in an invalid state. This behavior has been fixed, and the attempt will raise a DAGCircuitError.

  • Target.has_calibration() has been updated so that it does not raise an exception for an instruction that has been added to the target with None for its instruction properties. Fixes #12525.


1.1.0rc1

New Features

  • Extended the commutation analysis performed by CommutationChecker to only operate on hardware circuits to also work with abstract circuits, i.e. each operation in the input quantum circuit is now checked for its matrix representation before proceeding to the analysis. In addition, the operation is now checked for its ability to be cached in the session commutation library. For example, this now enables computing whether AnnotatedOperation commute. This enables transpiler passes that rely on CommutationChecker internally, such as CommutativeCancellation, during earlier stages of a default transpilation pipeline (prior to basis translation).

  • The methods power(), power(), as well as the similar methods of subclasses of Gate (such as of SGate) all have an additional argument annotated. The default value of False corresponds to the existing behavior. Furthermore, for standard gates with an explicitly defined power method, the argument annotated has no effect, for example both SGate().power(1.5, annotated=False) and SGate().power(1.5, annotated=True) return a PhaseGate. The difference manifests for gates without an explicitly defined power method. The value of False returns a UnitaryGate, just as before, while the value of True returns an AnnotatedOperation that represents the instruction modified with the “power modifier”.

  • The implementation BackendEstimatorV2 of BaseEstimatorV2 was added. This estimator supports BackendV1 and BackendV2.

    import numpy as np
    from qiskit import transpile
    from qiskit.circuit.library import IQP
    from qiskit.primitives import BackendEstimatorV2
    from qiskit.providers.fake_provider import Fake7QPulseV1
    from qiskit.quantum_info import SparsePauliOp, random_hermitian
     
    backend = Fake7QPulseV1()
    estimator = BackendEstimatorV2(backend=backend)
    n_qubits = 5
    mat = np.real(random_hermitian(n_qubits, seed=1234))
    circuit = IQP(mat)
    observable = SparsePauliOp("Z" * n_qubits)
    isa_circuit = transpile(circuit, backend=backend, optimization_level=1)
    isa_observable = observable.apply_layout(isa_circuit.layout)
    job = estimator.run([(isa_circuit, isa_observable)], precision=0.01)
    result = job.result()
    print(f"> Expectation value: {result[0].data.evs}")
    print(f"> Standard error: {result[0].data.stds}")
    print(f"> Metadata: {result[0].metadata}")
  • The implementation BackendSamplerV2 of BaseSamplerV2 was added. This sampler supports BackendV1 and BackendV2 that allow memory option to compute bitstrings.

    import numpy as np
    from qiskit import transpile
    from qiskit.circuit.library import IQP
    from qiskit.primitives import BackendSamplerV2
    from qiskit.providers.fake_provider import Fake7QPulseV1
    from qiskit.quantum_info import random_hermitian
     
    backend = Fake7QPulseV1()
    sampler = BackendSamplerV2(backend=backend)
    n_qubits = 5
    mat = np.real(random_hermitian(n_qubits, seed=1234))
    circuit = IQP(mat)
    circuit.measure_all()
    isa_circuit = transpile(circuit, backend=backend, optimization_level=1)
    job = sampler.run([isa_circuit], shots=100)
    result = job.result()
    print(f"> bitstrings: {result[0].data.meas.get_bitstrings()}")
    print(f"> counts: {result[0].data.meas.get_counts()}")
    print(f"> Metadata: {result[0].metadata}")
  • Added ctrl_state parameter to QuantumCircuit.mcp() and MCPhaseGate().

    The QuantumCircuit.mcp() function and MCPhaseGate() have been updated to include a ctrl_state parameter. This enhancement allows users to specify the control state of the multi-controlled phase gate. The parameter can accept either a decimal value or a bitstring and defaults to controlling the ‘1’ state if not provided.

    from qiskit import QuantumCircuit
     
    qc = QuantumCircuit(4)
    qc.mcp(0.2,[0,1,2],3,ctrl_state=2)
  • The transpiler pass ElidePermutations runs by default with optimization level 2 and 3. Intuitively, removing SwapGates and PermutationGates in a virtual circuit is almost always beneficial, as it makes the circuit shorter and easier to route. As OptimizeSwapBeforeMeasure is a special case of ElidePermutations, it has been removed from optimization level 3.

  • Added a new optimization transpiler pass, ElidePermutations, which is designed to run prior to the Layout stage and will optimize away any SwapGates and PermutationGates in a circuit by permuting virtual qubits. For example, taking a circuit with SwapGates:

    _images/release_notes-9.png

    will remove the swaps when the pass is run:

    from qiskit.transpiler.passes import ElidePermutations
    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(3)
    qc.h(0)
    qc.swap(0, 1)
    qc.swap(2, 0)
    qc.cx(1, 0)
    qc.measure_all()
     
    ElidePermutations()(qc).draw("mpl")
    _images/release_notes-10.png

    The pass also sets the virtual_permutation_layout property set, storing the permutation of the virtual qubits that was optimized away.

  • The KMSSynthesisLinearFunction plugin for synthesizing LinearFunction objects now accepts two additional options use_inverted and use_transposed. These option modify the matrix on which the underlying synthesis algorithm runs by possibly inverting and/or transposing it, and then suitably adjust the synthesized circuit. By varying these options, we generally get different synthesized circuits, and in cases may obtain better results than for their default values.

  • The PMHSynthesisLinearFunction plugin for synthesizing LinearFunction objects now accepts several additional options. The option section_size is passed to the underlying synthesis method. The options use_inverted and use_transposed modify the matrix on which the underlying synthesis algorithm runs by possibly inverting and/or transposing it, and then suitably adjust the synthesized circuit. By varying these options, we generally get different synthesized circuits, and in cases may obtain better results than for their default values.

  • The HLSConfig now has two additional optional arguments. The argument plugin_selection can be set either to "sequential" or to "all". If set to “sequential” (default), for every higher-level-object the HighLevelSynthesis pass will consider the specified methods sequentially, in the order they appear in the list, stopping at the first method that is able to synthesize the object. If set to “all”, all the specified methods will be considered, and the best synthesized circuit, according to plugin_evaluation_fn will be chosen. The argument plugin_evaluation_fn is an optional callable that evaluates the quality of the synthesized quantum circuit; a smaller value means a better circuit. When set to None, the quality of the circuit is its size (i.e. the number of gates that it contains).

    The following example illustrates the new functionality:

    from qiskit import QuantumCircuit
    from qiskit.circuit.library import LinearFunction
    from qiskit.synthesis.linear import random_invertible_binary_matrix
    from qiskit.transpiler.passes import HighLevelSynthesis, HLSConfig
     
    # Create a circuit with a linear function
    mat = random_invertible_binary_matrix(7, seed=37)
    qc = QuantumCircuit(7)
    qc.append(LinearFunction(mat), [0, 1, 2, 3, 4, 5, 6])
     
    # Run different methods with different parameters,
    # choosing the best result in terms of depth.
    hls_config = HLSConfig(
        linear_function=[
            ("pmh", {}),
            ("pmh", {"use_inverted": True}),
            ("pmh", {"use_transposed": True}),
            ("pmh", {"use_inverted": True, "use_transposed": True}),
            ("pmh", {"section_size": 1}),
            ("pmh", {"section_size": 3}),
            ("kms", {}),
            ("kms", {"use_inverted": True}),
        ],
        plugin_selection="all",
        plugin_evaluation_fn=lambda circuit: circuit.depth(),
    )
     
    # synthesize
    qct = HighLevelSynthesis(hls_config=hls_config)(qc)

    In the example, we run multiple synthesis methods with different parameters, choosing the best circuit in terms of depth. Note that optimizing circuit.size() instead would pick a different circuit.

  • Added ctrl_state parameter to QuantumCircuit.mcx().

    The QuantumCircuit.mcx() function in the quantum circuit library has been enhanced to include a ctrl_state parameter, allowing users to specify the control state of the multi-controlled X gate. This parameter can accept either a decimal value or a bitstring and defaults to controlling the ‘1’ state if not provided.

    from qiskit import QuantumCircuit
     
    qc = QuantumCircuit(3, 3)
    qc.mcx([0, 1], 2, ctrl_state="00")
  • Added the CommutativeCancellation pass to the init stage of the preset pass managers for optimization levels 2 and 3. This enables the preset pass managers to cancel additional logical operations at the beginning of the compilation pipeline.

  • Added a new method Layout.inverse() which is used for taking the inverse of a Layout object. Added a new method Layout.compose() which is used for composing two Layout objects together. Added a new method Layout.to_permutation() which is used for creating a permutation corresponding to a Layout object.

  • This release of Qiskit finalizes support for NumPy 2.0. Qiskit will continue to support both Numpy 1.x and 2.x for the foreseeable future.

  • Added a new reduction to the OptimizeAnnotated transpiler pass. This reduction looks for annotated operations (objects of type AnnotatedOperation that consist of a base operation B and a list M of control, inverse and power modifiers) with the following properties:

    • the base operation B needs to be synthesized (i.e. it’s not already supported by the target or belongs to the equivalence library)
    • the definition circuit for B can be expressed as P -- Q -- R with R=P1R = P^{-1}

    In this case the modifiers can be moved to the Q-part only. As a specific example, controlled QFT-based adders have the form control - [QFT -- U -- IQFT], which can be simplified to QFT -- control-[U] -- IQFT. By removing the controls over QFT and IQFT parts of the circuit, one obtains significantly fewer gates in the transpiled circuit.

  • Added two new methods to the DAGCircuit class: qiskit.dagcircuit.DAGCircuit.op_successors() returns an iterator to DAGOpNode successors of a node, and qiskit.dagcircuit.DAGCircuit.op_successors() returns an iterator to DAGOpNode predecessors of a node.

  • Added a new transpiler pass, RemoveFinalReset, which will remove any Reset operation which is the final instruction on a qubit wire. For example, taking a circuit with final Resets:

    _images/release_notes-11.png

    will remove the final resets when the pass is run:

    from qiskit.transpiler.passes import RemoveFinalReset
    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(3, 1)
    qc.reset(0)
    qc.h(range(3))
    qc.cx(1, 0)
    qc.measure(0, 0)
    qc.reset(range(3))
    RemoveFinalReset()(qc).draw("mpl")
    _images/release_notes-12.png
  • The performance of SparsePauliOp.to_matrix() has been greatly improved for both dense and sparse forms. By default, both will now take advantage of threaded parallelism available on your system, subject to the RAYON_NUM_THREADS environment variable. You can temporarily force serial execution using the new force_serial Boolean argument to to_matrix().

  • Added a new transpiler pass StarPreRouting which is designed to identify star connectivity subcircuits and then replace them with an optimal linear routing. This is useful for certain circuits that are composed of this circuit connectivity such as Bernstein Vazirani and QFT. For example:

Circuits Features

  • A QuantumCircuit can now contain typed classical variables:

    from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister
    from qiskit.circuit.classical import expr, types
     
    qr = QuantumRegister(2, "q")
    cr = ClassicalRegister(2, "c")
    qc = QuantumCircuit(qr, cr)
    # Add two input variables to the circuit with different types.
    a = qc.add_input("a", types.Bool())
    mask = qc.add_input("mask", types.Uint(2))
     
    # Test whether the input variable was true at runtime.
    with qc.if_test(a) as else_:
        qc.x(0)
    with else_:
        qc.h(0)
     
    qc.cx(0, 1)
    qc.measure(qr, cr)
     
    # Add a typed variable manually, initialized to the same value as the classical register.
    b = qc.add_var("b", expr.lift(cr))
     
    qc.reset([0, 1])
    qc.h(0)
    qc.cx(0, 1)
    qc.measure(qr, cr)
     
    # Store some calculated value into the `b` variable.
    qc.store(b, expr.bit_and(b, cr))
    # Test whether we had equality, up to a mask.
    with qc.if_test(expr.equal(expr.bit_and(b, mask), mask)):
        qc.x(0)

    These variables can be specified either as inputs to the circuit, or as scoped variables. The circuit object does not yet have support for representing typed classical-variable outputs, but this will be added later when hardware and the result interfaces are in more of a position to support it. Circuits that represent a block of an inner scope may also capture variables from outer scopes.

    A variable is a Var node, which can now contain an arbitrary type, and represents a unique memory location within its live range when added to a circuit. These can be constructed in a circuit using QuantumCircuit.add_var() and add_input(), or at a lower level using Var.new().

    Variables can be manually stored to, using the Store instruction and its corresponding circuit method QuantumCircuit.store(). This includes writing to Clbit and ClassicalRegister instances wrapped in Var nodes.

    Variables can be used wherever classical expressions (see qiskit.circuit.classical.expr) are valid. Currently this is the target expressions of control-flow operations, though we plan to expand this to gate parameters in the future, as the type and expression system are expanded.

    See Real-time classical computation for more discussion of these variables, and the associated data model.

    These are supported throughout the transpiler, through QPY serialization (qiskit.qpy), OpenQASM 3 export (qiskit.qasm3), and have initial support through the circuit visualizers (see QuantumCircuit.draw()).

    Note

    The new classical variables and storage will take some time to become supported on hardware and simulator backends. They are not supported in the primitives interfaces (qiskit.primitives), but will likely inform those interfaces as they evolve.

  • The classical realtime-expressions module qiskit.circuit.classical can now represent indexing and bitshifting of unsigned integers and bitlikes (e.g. ClassicalRegister). For example, it is now possible to compare one register with the bitshift of another:

    from qiskit.circuit import QuantumCircuit, ClassicalRegister
    from qiskit.circuit.classical import expr
     
    cr1 = ClassicalRegister(4, "cr1")
    cr2 = ClassicalRegister(4, "cr2")
    qc = QuantumCircuit(cr1, cr2)
    with qc.if_test(expr.equal(cr1, expr.shift_left(cr2, 2))):
        pass

    Qiskit can also represent a condition that dynamically indexes into a register:

    with qc.if_test(expr.index(cr1, cr2)):
        pass
  • The construction performance of NLocal and its derived circuit-library subclasses (e.g. EfficientSU2 and RealAmplitudes) has significantly improved, when the rotation and/or entanglement subblocks are simple applications of a single Qiskit standard-library gate. Since these circuits are constructed lazily, you might not see the improvement immediately on instantiation of the class, but instead on first access to its internal structure. Performance improvements are on the order of ten times faster.

  • QuantumCircuit.append() now has a copy keyword argument, which defaults to True. When an instruction with runtime parameters (ParameterExpressions) is appended to a circuit, by default, the circuit has always created a copy of the instruction so that if QuantumCircuit.assign_parameters() attempts to mutate the instruction in place, it does not affect other references to the same instruction. Now, setting copy=False allows you to override this, so you can avoid the copy penalty if you know your instructions will not be used in other locations.

  • QuantumCircuit.compose() now has a copy keyword argument, which defaults to True. By default, compose() copies all instructions, so that mutations from one circuit do not affect any other. If copy=False, then instructions from the other circuit will become directly owned by the new circuit, which may involve mutating them in place. The other circuit must not be used afterwards, in this case.

  • Construction time for QuantumVolume circuits has been significantly improved, on the order of 10x or a bit more. The internal SU4 gates will now also use more bits of randomness during their generation, leading to more representative volume circuits, especially at large widths and depths.

  • QuantumVolume now has a flatten keyword argument. This defaults to False, where the constructed circuit contains a single instruction that in turn contains the actual volume structure. If set True, the circuit will directly have the volumetric SU4 matrices.

  • UnitaryGate now accepts an optional num_qubits argument. The only effect of this is to skip the inference of the qubit count, which can be helpful for performance when many gates are being constructed.

  • All of the “standard gates” in the circuit library (qiskit.circuit.library) can now be specified by string name for the entangling operations in TwoLocal circuits, such as RealAmplitudes and EfficientSU2.

Primitives Features

  • Version 2 of the primitives is introduced via a new base class for both the sampler and the estimator, along with new types for their inputs and outputs. The emphasis of this new version is on performing vectorized calls to the primitive run() methods, so that sweeps over parameter value sets and observables can be efficiently specified. See StatevectorSampler and StatevectorEstimator for reference implementations of the V2 primitives.

    Moreover, the estimator has gained a precision argument in the run() method that specifies the targeted precision of the expectation value estimates. Analogously, the sampler has moved shots out of the options and into the arguments of the run() method. The sampler has also been changed to return the outputs (e.g. bitstrings) from every shot, rather than providing a Counts-like return, and also to store data from separate ClassicalRegisters . This enables derived classes to implement sampler support for circuits with classical control flow.

    The primitive V2 base classes are:

    The new types which are used for inputs and outputs are:

    • SamplerPubLike: primitive unified bloc (PUB) of sampler inputs; a union type of allowed inputs to a sampler
    • EstimatorPubLike: Primitive unified bloc (PUB) of estimator inputs; a union type of allowed inputs to an estimator
    • PubResult: the data and metadata resulting from a single PUB’s execution
    • DataBin: A namespace to hold data from a single PUB’s execution
    • BitArray: an array-valued collection of bit values in a dense format
    • PrimitiveResult: an iterable of PubResults along with metadata
  • The reference implementation StatevectorEstimator of BaseEstimatorV2 was added. As seen in the example below, this estimator (and all V2 estimators) supports providing arrays of observables and/or arrays of parameter value sets that are attached to particular circuits.

    Each tuple of (circuit, observables, <optional> parameter values, <optional> precision), called an estimator primitive unified bloc (PUB), produces its own array-based result. The run() method can be given many pubs at once.

    from qiskit.circuit import Parameter, QuantumCircuit
    from qiskit.primitives import StatevectorEstimator
    from qiskit.quantum_info import Pauli, SparsePauliOp
     
    import matplotlib.pyplot as plt
    import numpy as np
     
    # Define a circuit with two parameters.
    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.cx(0, 1)
    circuit.ry(Parameter("a"), 0)
    circuit.rz(Parameter("b"), 0)
    circuit.cx(0, 1)
    circuit.h(0)
     
    # Define a sweep over parameter values, where the second axis is over
    # the two parameters in the circuit.
    params = np.vstack([
        np.linspace(-np.pi, np.pi, 100),
        np.linspace(-4 * np.pi, 4 * np.pi, 100)
    ]).T
     
    # Define three observables. Many formats are supported here including
    # classes such as qiskit.quantum_info.SparsePauliOp. The inner length-1
    # lists cause this array of observables to have shape (3, 1), rather
    # than shape (3,) if they were omitted.
    observables = [
        [SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
        [Pauli("XX")],
        [Pauli("IY")]
    ]
     
    # Instantiate a new statevector simulation based estimator object.
    estimator = StatevectorEstimator()
     
    # Estimate the expectation value for all 300 combinations of
    # observables and parameter values, where the pub result will have
    # shape (3, 100). This shape is due to our array of parameter
    # bindings having shape (100,), combined with our array of observables
    # having shape (3, 1)
    pub = (circuit, observables, params)
    job = estimator.run([pub])
     
    # Extract the result for the 0th pub (this example only has one pub).
    result = job.result()[0]
     
    # Error-bar information is also available, but the error is 0
    # for this StatevectorEstimator.
    result.data.stds
     
    # Pull out the array-based expectation value estimate data from the
    # result and plot a trace for each observable.
    for idx, pauli in enumerate(observables):
        plt.plot(result.data.evs[idx], label=pauli)
    plt.legend()
  • The reference implementation StatevectorSampler of BaseSamplerV2 was added. As seen in the example below, this sampler (and all V2 samplers) supports providing arrays of parameter value sets to bind against a single circuit.

    Each tuple of (circuit, <optional> parameter values, <optional> shots), called a sampler primitive unified bloc (PUB), produces its own array-based result. The run() method can be given many pubs at once.

    from qiskit.circuit import (
        Parameter, QuantumCircuit, ClassicalRegister, QuantumRegister
    )
    from qiskit.primitives import StatevectorSampler
     
    import matplotlib.pyplot as plt
    import numpy as np
     
    # Define our circuit registers, including classical registers
    # called 'alpha' and 'beta'.
    qreg = QuantumRegister(3)
    alpha = ClassicalRegister(2, "alpha")
    beta = ClassicalRegister(1, "beta")
     
    # Define a quantum circuit with two parameters.
    circuit = QuantumCircuit(qreg, alpha, beta)
    circuit.h(0)
    circuit.cx(0, 1)
    circuit.cx(1, 2)
    circuit.ry(Parameter("a"), 0)
    circuit.rz(Parameter("b"), 0)
    circuit.cx(1, 2)
    circuit.cx(0, 1)
    circuit.h(0)
    circuit.measure([0, 1], alpha)
    circuit.measure([2], beta)
     
    # Define a sweep over parameter values, where the second axis is over.
    # the two parameters in the circuit.
    params = np.vstack([
        np.linspace(-np.pi, np.pi, 100),
        np.linspace(-4 * np.pi, 4 * np.pi, 100)
    ]).T
     
    # Instantiate a new statevector simulation based sampler object.
    sampler = StatevectorSampler()
     
    # Start a job that will return shots for all 100 parameter value sets.
    pub = (circuit, params)
    job = sampler.run([pub], shots=256)
     
    # Extract the result for the 0th pub (this example only has one pub).
    result = job.result()[0]
     
    # There is one BitArray object for each ClassicalRegister in the
    # circuit. Here, we can see that the BitArray for alpha contains data
    # for all 100 sweep points, and that it is indeed storing data for 2
    # bits over 256 shots.
    assert result.data.alpha.shape == (100,)
    assert result.data.alpha.num_bits == 2
    assert result.data.alpha.num_shots == 256
     
    # We can work directly with a binary array in performant applications.
    raw = result.data.alpha.array
     
    # For small registers where it is anticipated to have many counts
    # associated with the same bitstrings, we can turn the data from,
    # for example, the 22nd sweep index into a dictionary of counts.
    counts = result.data.alpha.get_counts(22)
     
    # Or, convert into a list of bitstrings that preserve shot order.
    bitstrings = result.data.alpha.get_bitstrings(22)
    print(bitstrings)
  • Added methods to join multiple BitArray objects along various axes.

    ba = BitArray.from_samples(['00', '11'])
    print(ba)
    # BitArray(<shape=(), num_shots=2, num_bits=2>)
     
    # reshape the bit array because `concatenate` requires an axis.
    ba_ = ba.reshape(1, 2)
    print(ba_)
    # BitArray(<shape=(1,), num_shots=2, num_bits=2>)
     
    ba2 = BitArray.concatenate([ba_, ba_])
    print(ba2.get_bitstrings())
    # ['00', '11', '00', '11']
     
    # `concatenate_bits` and `concatenates_shots` do not require any axis.
     
    ba3 = BitArray.concatenate_bits([ba, ba])
    print(ba3.get_bitstrings())
    # ['0000', '1111']
     
    ba4 = BitArray.concatenate_shots([ba, ba])
    print(ba4.get_bitstrings())
    # ['00', '11', '00', '11']
  • Added methods to generate a subset of BitArray object by slicing along various axes.

    • __getitem__(): slice the array along an existing axis of the array.
    • slice_bits(): slice the array along the bit axis.
    • slice_shots(): slice the array along the shot axis.
    ba = BitArray.from_samples(['0000', '0001', '0010', '0011'], 4)
    print(ba)
    # BitArray(<shape=(), num_shots=4, num_bits=4>)
    print(ba.get_bitstrings())
    # ['0000', '0001', '0010', '0011']
     
    ba2 = ba.reshape(2, 2)
    print(ba2)
    # BitArray(<shape=(2,), num_shots=2, num_bits=2>)
    print(ba2[0].get_bitstrings())
    # ['0000', '0001']
    print(ba2[1].get_bitstrings())
    # ['0010', '0011']
     
    ba3 = ba.slice_bits([0, 2])
    print(ba3.get_bitstrings())
    # ['00', '01', '00', '01']
     
    ba4 = ba.slice_shots([0, 2])
    print(ba3.get_bitstrings())
    # ['0000', '0010']
  • Added a method transpose() to transpose a BitArray.

    ba = BitArray.from_samples(['00', '11']).reshape(2, 1, 1)
    print(ba)
    # BitArray(<shape=(2, 1), num_shots=1, num_bits=2>)
    print(ba.transpose())
    # BitArray(<shape=(1, 2), num_shots=1, num_bits=2>)
  • Added a method expectation_values() to compute expectation values of diagonal operators.

    ba = BitArray.from_samples(['01', '11'])
    print(ba.expectation_values(["IZ", "ZI", "01"]))
    # [-1.   0.   0.5]
  • qiskit.primitives.containers.DataBin now satisfies the qiskit.primitives.containers.Shaped protocol. This means that every DataBin instance now has the additional attributes * shape: tuple[int, …] the leading shape of every entry in the instance * ndim: int the length of shape * size: int the product of the entries of shape The shape can be passed to the constructor.

  • Added mapping-like features to DataBin, i.e., __getitem__, __contains__, __iter__, keys(), values(), and items().

    from qiskit import QuantumCircuit
    from qiskit.primitives import StatevectorSampler
     
    circuit = QuantumCircuit(1)
    circuit.h(0)
    circuit.measure_all()
     
    sampler = StatevectorSampler()
    result = sampler.run([circuit]).result()
    databin = result[0].data
    for creg, arr in databin.items():
        print(creg, arr)
    for creg in databin:
        print(creg, databin[creg])
  • The subclass SamplerPubResult of PubResult was added, which BaseSamplerV2 implementations can return. The main feature added in this new subclass is join_data(), which joins together (a subset of) the contents of data into a single object. This enables the following patterns:

    job_result =  sampler.run([pub1, pub2, pub3], shots=123).result()
     
    # assuming all returned data entries are BitArrays
    counts1 = job_result[0].join_data().get_counts()
    bistrings2 = job_result[1].join_data().get_bitstrings()
    array3 = job_result[2].join_data().array

Providers Features

Pulse Features

  • It is now possible to assign parameters to pulse Schedule and ScheduleBlock objects by specifying the parameter name as a string. The parameter name can be used to assign values to all parameters within the Schedule or ScheduleBlock that have the same name. Moreover, the parameter name of a ParameterVector can be used to assign all values of the vector simultaneously (the list of values should therefore match the length of the vector).

  • The assign_parameters methods of Schedule and ScheduleBlock now support assigning a ParameterVector to a list of parameter values simultaneously in addition to assigning individual Parameter instances to individual values.

Quantum Information Features

  • Added a new apply_layout() method that is equivalent to apply_layout(). This method is used to apply a TranspileLayout layout from the transpiler to a Pauli observable that was built for an input circuit. This enables working with BaseEstimator / BaseEstimatorV2 implementations and local transpilation when the input is of type Pauli. For example:

    from qiskit.circuit.library import RealAmplitudes
    from qiskit.primitives import BackendEstimatorV2
    from qiskit.providers.fake_provider import GenericBackendV2
    from qiskit.quantum_info import Pauli
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
     
    psi = RealAmplitudes(num_qubits=2, reps=2)
    H1 = Pauli("XI")
    backend = GenericBackendV2(num_qubits=7)
    estimator = BackendEstimatorV2(backend=backend)
    thetas = [0, 1, 1, 2, 3, 5]
    pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
    transpiled_psi = pm.run(psi)
    permuted_op = H1.apply_layout(transpiled_psi.layout)
    res = estimator.run([(transpiled_psi, permuted_op, thetas)]).result()

    where an input circuit is transpiled locally before it’s passed to run. Transpilation expands the original circuit from 2 to 7 qubits (the size of backend) and permutes its layout, which is then applied to H1 using apply_layout() to reflect the transformations performed by pm.run().

  • Adds the PauliList.noncommutation_graph() and SparsePauliOp.noncommutation_graph() methods, exposing the construction of non-commutation graphs, recasting the measurement operator grouping problem into a graph coloring problem. This permits users to work with these graphs directly, for example to explore coloring algorithms other than the one used by SparsePauliOp.group_commuting().

Synthesis Features

Transpiler Features

  • The following analysis passes now accept constraints encoded in a Target thanks to a new target input argument:

    The target constraints will have priority over user-provided constraints, for coherence with the rest of the transpiler pipeline.

Upgrade Notes

  • Removes the hard-coding of style options for plot_histogram(). This allows Matplotlib style files to be faithfully applied to the figures. Users looking to go beyond the defaults set by Matplotlib can make their own style files, or pass a Matplotlib Axes object to plot_histogram and post-apply any customizations.

  • The transpile() function has been upgraded to internally convert backend inputs of type BackendV1 to BackendV2, which allows the transpilation pipeline to now access the backend constraints through a Target. This change does not require any user action.

Circuits Upgrade Notes

  • The random-number usage of QuantumVolume has changed, so you will get a different circuit for a fixed seed between older versions of Qiskit and this version. The random-unitary generation now uses more bits of entropy, so large circuits will be less biased.

  • The internal UnitaryGate instances in the definition of a QuantumVolume circuit will no longer have a label field set. Previously this was set to the string su4_<seed> where <seed> was a three-digit number denoting the seed of an internal Numpy pRNG instance for that gate. Doing this was a serious performance problem, and the seed ought not to have been useful; if you need to retrieve the matrix from the gate, simply use the Gate.to_matrix() method.

Primitives Upgrade Notes

  • The function qiskit.primitives.containers.make_data_bin() no longer creates and returns a qiskit.primitives.containers.DataBin subclass. It instead always returns the DataBin class. However, it continues to exist for backwards compatibility, though will eventually be deprecated. All users should migrate to construct DataBin instances directly, instead of instantiating subclasses as output by make_data_bin().

Synthesis Upgrade Notes

  • The TwoQubitWeylDecomposition no longer will self-specialize into a subclass on creation. This was an internal detail of the TwoQubitWeylDecomposition previously, and was not a documented public behavior as all the subclasses behaved the same and were only used for internal dispatch. However, as it was discoverable behavior this release note is to document that this will no longer occur and all instances of TwoQubitWeylDecomposition will be of the same type. There is no change in behavior for public methods of the class.

Transpiler Upgrade Notes

  • The preset StagedPassManager returned for optimization level 2 by generate_preset_pass_manager() and level_2_pass_manager() have been reworked to provide a better balance between runtime and optimization. This means the output circuits will change compared to earlier releases. If you need an exact pass manager from level 2 in earlier releases you can either build it manually or use it from an earlier release and save the circuits with qpy to load with a newer release.

Deprecation Notes

  • Support for running Qiskit with Python 3.8 has been deprecated and will be removed in the Qiskit 1.3.0 release. The 1.3.0 is the first release after Python 3.8 goes end of life and is no longer supported. [1] This means that starting in the 1.3.0 release you will need to upgrade the Python version you’re using to Python 3.9 or above.

    [1] https://devguide.python.org/versions/

  • The parameters show_idle and show_barrier in the timeline drawers had been replaced by idle_wires and plot_barriers respectively to match the circuit drawer parameters. Their previous names are now deprecated and will be removed in the next major release. The new parameters are fully equivalent.

Providers Deprecations

  • The abstract base classes Provider and ProviderV1 are now deprecated and will be removed in Qiskit 2.0.0. The abstraction provided by these interface definitions were not providing a huge value. solely just the attributes name, backends, and a get_backend(). A _provider_, as a concept, will continue existing as a collection of backends. If you’re implementing a provider currently you can adjust your code by simply removing ProviderV1 as the parent class of your implementation. As part of this you probably would want to add an implementation of get_backend for backwards compatibility. For example:

    def get_backend(self, name=None, **kwargs):
      backend = self.backends(name, **kwargs)
      if len(backends) > 1:
          raise QiskitBackendNotFoundError("More than one backend matches the criteria")
      if not backends:
          raise QiskitBackendNotFoundError("No backend matches the criteria")
      return backends[0]

Synthesis Deprecations

  • The TwoQubitWeylDecomposition.specialize() method is now deprecated and will be removed in the Qiskit 2.0.0 release. This method never had a public purpose and was unsafe for an end user to call as it would mutate the calculated decomposition in the object and produce invalid fields in the object. It was only used internally to construct a new TwoQubitWeylDecomposition object. Despite this it was still a documented part of the public API for the class and is now being deprecated without any potential replacement. This release it always will raise a NotImplementedError when called because the specialization subclassing has been removed as part of the Rust rewrite of the class.

Transpiler Deprecations

  • The pass qiskit.transpiler.passes.CXCancellation was deprecated in favor of class:.InverseCancellation, which is more generic. CXCancellation is fully semantically equivalent to InverseCancellation([CXGate()]).

  • The transpilation pass qiskit.transpiler.passes.ALAPSchedule is now deprecated. It was pending for deprecation since Qiskit 0.37 (with Terra 0.21), released on June 2022. The pass is replaced by ALAPScheduleAnalysis, which is an analysis pass.

  • The transpilation pass qiskit.transpiler.passes.ASAPSchedule is now deprecated. It was pending for deprecation since Qiskit 0.37 (with Terra 0.21), released on June 2022. It has been superseded by ASAPScheduleAnalysis and the new scheduling workflow.

  • The transpilation pass qiskit.transpiler.passes.DynamicalDecoupling is now deprecated. It was pending for deprecation since Qiskit 0.37 (with Terra 0.21), released on June 2022. Instead, use PadDynamicalDecoupling, which performs the same function but requires scheduling and alignment analysis passes to run prior to it.

  • The transpilation pass qiskit.transpiler.passes.AlignMeasures is now deprecated. It was pending for deprecation since Qiskit 0.37 (with Terra 0.21), released on June 2022. Instead, use ConstrainedReschedule, which performs the same function and also supports aligning to additional timing constraints.

Bug Fixes

  • Fixed an issue with the qpy.dump() function where, when the use_symengine flag was set to a truthy object that evaluated to True but was not actually the boolean True, the generated QPY payload would be corrupt. For example, if you set use_symengine to HAS_SYMENGINE, this object evaluates to True when cast as a bool, but isn’t actually True.

  • Fixed an issue with the circuit_drawer() function and QuantumCircuit.draw() method when loading a matplotlib style via the user configuration file.

  • Fixed an issue where the ConstrainedReschedule transpiler pass would previously error if the circuit contained a Reset instruction. This has been corrected so that the pass no longer errors, however an actual hardware may behave differently from what Qiskit scheduler assumes especially for mid-circuit measurements and resets. Qiskit scheduler raises RuntimeWarning if it encounters circuit containing either. Fixed #10354

  • Fixed an issue with the CommutationChecker class where it would error if a gate’s name attribute was UTF8 encoded. Previously only gate names with ascii encoding would work. Fixed #12501

  • Fixed a performance issue in the BackendSamplerV2 and BackendEstimatorV2. Fixed #12290

  • Fixed an issue with the convert_to_target() where the converter would incorrectly ignore control flow instructions if they were specified in the BackendConfiguration.supported_instructions attribute which is the typical location that control flow instructions are specified in a BackendConfiguration object. Fixed #11872.

  • Fixed an issue with the circuit_drawer() or QuantumCircuit.draw() when using the mpl output option where the program would hang if the circuit being drawn had a ControlFlow operation in it and the fold option was set to -1 (meaning no fold). Fixed #12012.

  • Fixed a bug in the conversion of custom pulse instructions to the legacy qiskit.qobj format. The bug was introduced in Qiskit 1.0.0 and caused conversion of instructions with custom pulse shapes to raise an error. After the fix, the conversion is carried out correctly, and the custom pulse is converted to pulse.Waveform as it should. Fixed #11828.

  • A bug in transpile() has been fixed where custom instruction_durations, dt and backend_properties constraints would be ignored when provided at the same time as a backend of type BackendV2. The behavior after the fix is now independent of whether the provided backend is of type BackendV1 or type BackendV2. Similarly, custom timing_constraints are now overridden by target inputs but take precedence over BackendV1 and BackendV2 inputs.

  • Calling EquivalenceLibrary.set_entry() will now correctly update the internal graph object of the library. Previously, the metadata would be updated, but the graph structure would be unaltered, meaning that users like BasisTranslator would still use the old rules. Fixed #11958.

  • The EvolvedOperatorAnsatz now correctly handles the case where the operators argument is an empty list. Previously, this would result in an error.

  • From now on, EvolvedOperatorAnsatz will not have any qregs when thera are zero qubits, instead of having a QuantumRegister instance with zero qubits. This behavior aligns more consistently with its superclass QuantumCircuit.

  • The method Instruction.repeat() now moves a set condition to the outer returned Instruction and leave the inner gates of its definition unconditional. Previously, the method would leave ClassicalRegister instances within the inner definition, which was an invalid state, and would manifest itself as seemingly unrelated bugs later, such as during transpilation or export. Fixed #11935.

  • Fixed an issue in the InverseCancellation transpiler pass where in some cases it would incorrectly cancel a self-inverse parameterized gate even if the parameter value didn’t match. Fixed #11815

  • Improve the decomposition of the gates MCXGate and MCPhaseGate without using ancilla qubits, so that the number of CXGate will grow quadratically in the number of qubits and not exponentially.

  • A bug that crashes the convert_to_target() function when qubit properties (either T1, T2 or frequency) are missing was fixed. The missing property values in QubitProperties are filled with None.

  • BasePassManager.run() will no longer leak the previous PropertySet into new workflows when called more than once. Previously, the same PropertySet as before would be used to initialize follow-on runs, which could mean that invalid property information was being given to tasks. The behavior now matches that of Qiskit 0.44. Fixed #11784.

  • Pauli.evolve() now correctly handles quantum circuits containing ECR gates. Formerly they were not recognized as Clifford gates, and an error was raised.

  • Fixed a bug in Pauli.evolve() where evolving by a circuit with a name matching certain Clifford gates (‘cx’, ‘cz’, etc) would evolve the Pauli according to the name of the circuit, not by the contents of the circuit. This bug occurred only with the non-default option frame='s'.

  • Fixed a performance issue in the qpy.load() function when deserializing QPY payloads with large numbers of qubits or clbits in a circuit.

  • Fixed a bug where qiskit.primitives.containers.estimator_pub.EstimatorPub.coerce() and qiskit.primitives.containers.sampler_pub.SamplerPub.coerce() improperly handle the case where the parameter values are a BindingsArray instance, giving rise to a ValueError whenever it is attempted.

  • Fixed a bug in the handling of default_alignment argument of build(). Inputs of type AlignmentKind are now correctly processed as default alignments.

  • Fixed a bug in qiskit.pulse.utils.format_parameter_value() function that unintentionally converts large enough integer numbers into float values or causes unexpected rounding. See qiskit/#11971 for details.

  • Fix incorrect implemention of qDRIFT, negative coefficients of the Hamiltonian are now added back whereas they were always forced to be positive.

  • A bug has been fixed in convert_durations_to_dt() where the function would blindly apply a conversion from seconds to dt on circuit durations, independently of the original units of the attribute. This could lead to wrong orders of magnitude in the reported circuit durations.

  • The preset pass managers of transpile() will no longer fail on circuits with control flow, if no hardware target or basis-gate set is specified. They will now treat such abstract targets as permitting all control-flow operations. Fixed #11906.

  • The method qiskit.instruction.Instruction.soft_compare() is meant to compare whether two gates match in their name, number of qubits, number of clbits, and the number of parameters. However, there was a typo where it would not check the number of qubits and number of clbits for a match. This resolves the apparent typo.

  • The default init plugin was not properly raising a TranspilerError when called with an invalid optimization level.

  • Fixed qiskit.primitives.containers.observables_array.ObservablesArray.coerce() so that it returns a 0-d array when the input is a single, unnested observable. Previously, it erroneously upgraded to a single dimension, with shape (1,).

  • Fixed an issue with the Operator.from_circuit() constructor method where it would incorrectly interpret the final layout permutation resulting in an invalid Operator being constructed. Previously, the final layout was processed without regards for the initial layout, i.e. the initialization was incorrect for all quantum circuits that have a non-trivial initial layout.

  • Parameter was updated so that instances that compare equal always have the same hash. Previously, only the Parameter.uuid was compared, so Paramemter instances with different names could compare equal if they had been constructed using a common value for the uuid parameter (which is usually not passed explicitly).

  • Parameter instances used as stand-ins for input variables in OpenQASM 3 programs will now have their names escaped to avoid collisions with built-in gates during the export to OpenQASM 3. Previously there could be a naming clash, and the exporter would generate invalid OpenQASM 3.

  • Fixed bug in QuantumCircuit.draw() that was causing custom style dictionaries for the Matplotlib drawer to be modified upon execution.

  • QuantumCircuit.append() with copy=True (its default) will now correctly copy instructions parametrized by ParameterExpression instances, and not just by Parameter instances.

  • The internal handling of custom circuit calibrations and InstructionDurations has been offloaded from the transpile() function to the individual transpiler passes: qiskit.transpiler.passes.scheduling.DynamicalDecoupling, qiskit.transpiler.passes.scheduling.padding.DynamicalDecoupling. Before, instruction durations from circuit calibrations would not be taken into account unless they were manually incorporated into instruction_durations input argument, but the passes that need it now analyze the circuit and pick the most relevant duration value according to the following priority order: target > custom input > circuit calibrations.

  • Fixed a bug in transpile() where the num_processes argument would only be used if dt or instruction_durations were provided.

Other Notes