Skip to main contentIBM Quantum Documentation
Important

IBM Quantum Platform is moving and this version will be sunset on July 1. To get started on the new platform, read the migration guide.

Qiskit SDK 2.1 release notes


2.1.0

Prelude

The Qiskit v2.1 release introduces several important enhancements in key areas including the C API, transpiler capabilities and quantum circuit usability. In addition, it includes many general improvements and bug fixes. The major highlights include:

  • C API extensions: Building on the work started in the previous release, Qiskit v2.1 adds support for creating and interacting with quantum circuits via C API functions, supporting the addition of standard gates, standard instructions and unitary gates. Also added in this release is a set of C functions to build and manipulate a Target object, as preparation for supporting a complete C API-based transpilation workflow in the next release. In addition, QkComplex64 is now defined as a struct, serving as a compiler-agnostic representation for complex numbers when working with Qiskit’s C API. The C API’s header file has also been updated so that it is generally compatible to be used natively in C++.
  • Clifford+T basis set support: Transpiling circuits for a target with Clifford+T gates is now supported out of the box. Under the hood, if the basis set consists only of Clifford+T gates, Qiskit ensures that the appropriate passes for handling these gates are added to the preset pass managers. This is an initial step toward supporting transpilation for fault-tolerant backends.
  • Enhancements to BoxOp: This version adds the ability to attach custom annotations to BoxOp instructions, further supporting the concept of box statements from OpenQASM 3 in Qiskit. This also includes annotation serialization in both the QASM and QPY formats. Additionally, support for using stretch durations in boxes has been added.
  • Python 3.9 deprecation: Python 3.9 is deprecated starting with Qiskit v2.1 and will no longer be supported in Qiskit v2.3.

For more details about the above and much more, please see the release notes below and visit the updated documentation.

C API Features

  • The Qiskit C API (qiskit.h) now supports building and interacting with Quantum Circuits. The circuits C API centers around the QkCircuit opaque type that represents the circuit. The QkCircuit type enables building a circuit with any circuit element defined natively in Qiskit’s internal Rust data model for QuantumCircuit. This currently includes Standard gates, Measure, Delay, Reset, Barrier, and UnitaryGate. The capabilities of the circuits C API will expand in future release as more of the Qiskit data model is added natively to the internal Rust data model enabling it to be used in the C API.

    For example, you can use the C API to build a 1000 qubit GHZ state:

    #include <qiskit.h>
     
    int main() {
        // Create an empty circuit with 1000 qubits and 1000 clbits
        QkCircuit *qc = qk_circuit_new(1000, 1000);
     
        // Add a Hadamard Gate on Qubit 0
        uint32_t one_qubit[1] = {0,};
        qk_circuit_gate(qc, QkGate_H, one_qubit, NULL); // The NULL pointer is for the parameter array.
                                                        // Since Hadamard doesn't have parameters it
                                                        // is never accessed.
     
        // Add the CX Gates:
        uint32_t qubits[2] = {0, 0};
        uint32_t num_qubits = qk_circuit_num_qubits(qc);
        for (int i = 1; i<num_qubits; i++) {
            qubits[1] = i;
            qk_circuit_gate(qc, QkGate_CX, qubits, NULL);
        }
     
        // Add the measurements:
        uint32_t num_clbits = qk_circuit_num_clbits(qc);
     
        for (uint32_t i = 0; i<num_clbits; i++) {
            qk_circuit_measure(qc, i, i);
        }
        qk_circuit_free(qc);
        return 0;
    }
  • The Qiskit C API (qiskit.h) now supports building a Target to represent a transpilation target. For example:

    #include <qiskit.h>
    #include <math.h>
    
    int main() {
        // Create a Target with 3 qubits
        QkTarget *target = qk_target_new(3);
    
        // Create a Target Entry for a CX Gate
        QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX);
        // Define properties for CX between qubits (0, 1) with a duration of 1.93e-9 sec and error rate 3.17e-10.
        uint32_t qargs[2] = {0, 1};
        qk_target_entry_add_property(cx_entry, qargs, 2, 1.93e-9, 3.17e-10);
        // Define properties for cx between qubits (1, 0) with a duration of 1.27e-9 sec and no error.
        uint32_t rev_qargs[2] = {1, 2};
        qk_target_entry_add_property(cx_entry, rev_qargs, 2, 1.27e-9, NAN);
        // Add the cx entry to the target.
        QkExitCode result_cx = qk_target_add_instruction(target, cx_entry);
        // Add global ideal Y gate entry to the target
        QkExitCode result_y = qk_target_add_instruction(target, qk_target_entry_new(QkGate_Y));
        // Create a Target entry for a Measurement with increasing duration and error as the qubit indices increase
        QkTargetEntry *measure = qk_target_entry_new_measure();
        for (uint32_t i = 0; i< qk_target_num_qubits(target); i++) {
            uint32_t q[1] = {i};
            qk_target_entry_add_property(measure, q, 1, 1e-6 * (i + 1), 1e-3 * (i + 1));
        }
        QkExitCode result_measure = qk_target_add_instruction(target, measure);
        return 0;
    }
  • Added support for querying the Qiskit version information from the C API by using the following macros:

    • QISKIT_VERSION_MAJOR : Contains the major version number.
    • QISKIT_VERSION_MINOR : Contains the minor version number.
    • QISKIT_VERSION_PATCH : Contains the patch version number.
    • QISKIT_VERSION : Contains a numeric representation of the version information, which can be used for comparisons.
    • QISKIT_VERSION_NUMERIC(M,m,p) : A function-like macro that returns the version “M.m.p” as a numeric value, which can be used for comparisons.

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

    if (QISKIT_VERSION >= QISKIT_VERSION_NUMERIC(2, 1, 0)) {
        // Code for version 2.1.0 or later
    }
  • Enabled C++ compatibility 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_complex = 2.0;
        QkComplex64 coeff = qk_complex64_from_native(coeff_complex);
        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;
    }

Circuits Features

  • Added a function random_circuit_from_graph() that generates a random circuit that induces the same interaction graph as the one 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 an edge’s weight attribute in the input interaction graph. If the user does not set the probability, each edge is drawn uniformly. That is, 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.

    In this example, cp_map is a list of edges with 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.extend_from_weighted_edge_list(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-1.png
  • Added a new QuantumCircuit method: QuantumCircuit.has_control_flow_op() to check if a QuantumCircuit object 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) 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 field. 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 UnitaryGate.control() method now internally uses qs_decomposition() instead of Isometry for the decomposition used to define the controlled UnitaryGate. This change reduces the number of CXGate used in the definition for the returned ControlledGate by approximately 2x.

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

  • The BoxOp.duration attribute can now be an expr.Expr node with type Duration, just like Delay.duration. This includes also supporting Stretch for the duration of a BoxOp.

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

  • Added a new QPY format version 15 which includes 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.

Quantum Information Features

  • A new class, PauliLindbladMap, is added, which is a Pauli-based parametrization of a subset of linear maps of multi-qubit operators, used in noise-learning applications. This class is expected to form the backbone of enhanced noise-learning algorithms, and provide better and more efficient control over noise models in future versions of Qiskit.

  • Introduced the QubitSparsePauli and QubitSparsePauliList classes, which represent the same concepts as Pauli and PauliList respectively, but only store non-identity terms, in a manner analogous to SparseObservable. These classes are primarily intended to be used with the new PauliLindbladMap.

Synthesis Features

  • Added a new synthesis algorithm for HalfAdderGate that requires no ancillary qubits and has better CX count compared to adder_qft_d00():

  • Added new decompositions for MCXGate utilizing clean ancillae, improving circuit depth and efficiency:

    Example usage:

    from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_kg24
     
    n_ctrls = 10
    qc = synth_mcx_1_clean_kg24(n_ctrls)
    qc.draw()
  • The synthesis of multi-controlled CZGate gates has been improved to reduce the synthesized gate count, in some cases as much as 99% reduction is possible. This was accomplished by leveraging the improved synthesis around the MCXGate with controlled CZGate synthesis too.

  • 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 synthesizing ModularAdderGate and HalfAdderGate gates, now accepts an additional parameter annotated. If True, the inverse-QFT-gate within the adders is implemented as an annotated operations, allowing the transpiler to apply additional optimizations.

  • The Quantum Shannon Decomposition (qs_decomposition()) now includes an optimization that reduces the CXGate count in the case that the input unitary is a controlled unitary.

  • The synthesis function synth_mcx_1_clean_b95() now produces a circuit with fewer CX-gates.

  • The SolovayKitaevDecomposition class now has additional arguments in the initializer, which allow it to be directly constructed from a set of basis_gates and a depth for the basic approximations.

  • Added SolovayKitaevDecomposition.save_basic_approximations() to save the set of basic approximations the class uses into a binary format. This change, in combination with the new initializer arguments, allows users to skip the explicit use of generate_basic_approximations() and only rely on SolovayKitaevDecomposition.

Transpiler Features

  • The function generate_preset_pass_manager() now generates a special pass manager when the basis set consists only of Clifford+T gates. Formally, a Clifford+T basis set must contain only Clifford gates, along with either TGate, TdgGate, or both. The full list of supported Clifford gates can be obtained by 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())

    Would result with:

    OrderedDict([('h', 10210), ('t', 4508), ('tdg', 4503), ('sdg', 943), ('s', 941)])

    Previously, the generated pass manager could not handle the example above because it couldn’t decompose single-qubit UGate rotation gates into Clifford+T gates. However, the new pass manager uses 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, if S-gate is not included, S-gates might be decomposed into pairs of T-gates (that is, Clifford gates might be decomposed into non-Clifford gates, which might not be the desired behavior).

    Following is a 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())

    Would result with:

    OrderedDict([('h', 96510), ('tdg', 42396), ('t', 42389), ('s', 8240), ('sdg', 8235), ('cx', 12)])
  • Added a new high-level-synthesis plugin HalfAdderSynthesisR25 for synthesizing a HalfAdderGate. The new plugin is based on adder_ripple_r25().

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

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

    The MCXSynthesisDefault class has also been updated to run the following sequence of MCX synthesize methods until the first one succeeds: : "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". The methods are ordered in a way that the better-quality ones are applied first.

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

  • 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))
    # Should print 12779
     
    # Run Clifford+T optimization
    optimized = OptimizeCliffordT()(transpiled)
    print(optimized.count_ops().get("t", 0) + optimized.count_ops().get("tdg", 0))
    # Should print 9011
  • 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, CouplingMap
    from qiskit.transpiler.passes import ALAPScheduleAnalysis, ContextAwareDynamicalDecoupling
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
    from qiskit.providers.fake_provider import GenericBackendV2
     
    num_qubits = 10
    circuit = QFT(num_qubits)
    circuit.measure_all()
     
    target = GenericBackendV2(
        100,
        basis_gates=["id", "rz", "sx", "x", "ecr"],
        coupling_map=CouplingMap.from_grid(10, 10)
    ).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)
     
    with_dd.draw("mpl", idle_wires=False)
    _images/release_notes-2.png
  • Added the following attributes to the DAGCircuit class to enable querying the number of stretch variables: num_stretches, num_captured_stretches and num_declared_stretches.

  • Added a new unitary synthesis plugin CliffordUnitarySynthesis that attempts to synthesize a given unitary gate by checking if it can be represented by a Clifford, in which case it returns a circuit that implements this unitary and consists 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 minimum 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)
    transpiled.draw("mpl")
    _images/release_notes-3.png

    Executing the code above re-synthesizes 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 which 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)

Upgrade Notes

  • The python-dateutil library is no longer a dependency of Qiskit. Since Qiskit v2.0 nothing in the library was using the python-dateutil 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 to use Qiskit. 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 MissingOptionalLibraryError 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. If you were using ParameterExpression.sympify() to get a symengine expression object from a ParameterExpression that will now return a sympy expression. If you need to use this with symengine you can leverage symengine.sympify to convert the sympy expression to a symengine one.

Circuits Upgrade Notes

  • The definition attribute of the HalfAdderGate has been changed to internally use adder_ripple_r25() to generate the definition of the gate for a more efficient circuit with no ancillary qubits. If the old definition is desired for some reason you can directly use the adder_qft_d00() function instead which will generate a circuit equivalent to what definition would return in previous releases.

  • The circuit returned by the excitation_preserving() function and ExcitationPreserving class now are built using a single XXPlusYYGate. This is a change from previous releases that used an RXXGate followed by an RYYGate. This new circuit construction is equivalent but uses fewer gates that are all excitation preserving by definition simpler.

  • The definition atributes of several standard gates have been updated according to the following principles:

    • When available, a definition using Clifford gates is preferred over definitions that includes non-Clifford gates.
    • When available, a definition using Clifford+T gates is preferred over one that uses a 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. The definitions of all the standard gates are all equivalent so no semantics behind the gates change, just the exact circuit construction returned for some gates is no longer exactly the same. This change was necessary to support Clifford+T transpilation.

  • 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.

C API Upgrade Notes

  • The way complex numbers are exposed in Qiskit’s C API has changed. Previously, QkComplex64 was a compiler-dependent typedef that allowed to pass native complex types by pointer to Qiskit’s API (for example as double complex*). While this was convenient, this approach implictly relied on memory-layout assumptions that are not strictly guaranteed.

    Qiskit v2.1 now exposes QkComplex64 { double re; double im; } as a struct, to ensure the memory layout is always compatible and for broader compiler support. For convenience, compiler-dependent converters qk_complex64_from_native and qk_complex64_to_native are provided, which allows translating from the struct to a native complex number. Note that these only work on platforms supporting double complex or for MSVC compilers using _Dcomplex.

    For example:

    #include <qiskit.h>
    #include <math.h>
    #include <stdio.h>
    #include <complex.h>
     
    int main(int argc, char *argv[]) {
        // platform-independent constructions:
        QkComplex64 coeff = {5.0, 3.0};
     
        // ... or using converter
        // double complex native = 5.0 + I * 3; // uses C11 standard, does not work on MSVC
        // QkComplex64 coeff = qk_complex64_from_native(&native);  // convert from native
     
        uint32_t num_qubits = 100;
        QkObs *obs = qk_obs_zero(num_qubits);
        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);
        printf("num_qubits: %u\n", qk_obs_num_qubits(obs));
        printf("num_terms: %lu\n", qk_obs_num_terms(obs));
     
        qk_obs_free(obs);
        return 0;
    }

QPY Upgrade Notes

  • The default QPY version emitted by qpy.dump() has been changed to the latest QPY version 15. If you need to generate an older format version for some reason you can use the version keyword argument on qpy.dump() to specify an older version to generate.

Synthesis Upgrade Notes

  • The serialization format 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 v2.3 release. Version 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/

Circuits Deprecations

Bug Fixes

  • Fixed a bug in the dag_drawer() function and the DAGCircuit.draw() method where setting the keyword argument style=plain did not show circuit labels for the nodes of the DAG in the visualization.

  • Fixed a bug in the QuantumCircuit.assign_parameters() method where parameters that were not used in the circuit and were passed as strings were not ignored when the argument strict=False was set. Refer to issue #13933 for more details.

  • 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 attribute of the OrGate, that was previously incorrectly set to the string "and" instead of the expected value "or" which is now returned. This incorrect value "and" conflicted with the AndGate.name and could have possibly led to several problems around using the Orgate and differentiating it from an AndGate.

  • Fixed a bug in the qpy.load() function where it could fail to deserialize circuits whose parameters had been reassigned to parameters with the same names. Fixed #13720, #13720, and #13720.

  • Fixed the GenericBackendV2 to now include BoxOp as a supported instruction type in the generated GenericBackendV2.target when the keyword argument control_flow=True is set in the constructor.

  • When synthesizing an MCXGate gate with 3 controls, the synthesis function 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 the synthesis function to be applied in a slightly larger number of cases.

  • The QuantumCircuit.draw() and circuit_drawer() function will now render BoxOp instructions in a QuantumCircuit in the same vertical slice if the vertical spans do not overlap are now rendered in the same vertical slice, when possible.

  • Fixed the QuantumCircuit.draw() method and circuit_drawer() function in "mpl" mode to insert less extraneous space inside the left edge when drawing BoxOp instances in a QuantumCircuit.

Other Notes

  • Added a new optional extra dependency target qpy-compat. This target should be used if you plan to load qpy files using older QPY formats. The target installs extra requirements used for loading QPY files using format versions < 13. If you are only using newer QPY format versions you do no need to install this. The qpy.dump() only generates QPY >=13 this is only needed for loading files generated with older (older than 2.0.0) Qiskit releases 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.

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