Skip to main content
IBM Quantum Platform

Qiskit SDK 2.4 release notes


2.4.1

Prelude

Qiskit v2.4.1 fixes a small number of bugs identified since the release of Qiskit v2.4.0.

Bug Fixes

  • Fixed a panic in RemoveIdentityEquivalent when the global phase of the circuit contained ParameterVectorElement objects. The message was:

    Cannot clone pointer into Python heap without the thread being attached.

    See #16053.

  • Fixed qpy.load() for QPY versions >=13, where Delay instructions with integer durations could deserialize to incorrect types. This could cause later code to raise errors, such as qasm3.dumps_experimental() returning an error saying “Failed to parse parameter value”. See #16076 for more detail.


2.4.0

Prelude

Qiskit 2.4.0 is a new feature release of the Qiskit SDK. The highlights of this release are:

  • Support for building Python extension modules that use the Qiskit C API and can be safely distributed in Python wheels: This release makes it practical to move performance‑critical or low‑level components using Qiskit into compiled extensions that leverage Qiskit’s C API. These extensions can now be packaged and distributed as ordinary Python wheels using standard Python tooling.
  • Initial porting of QPY serialization and deserialization internals to Rust: This greatly speeds up both generating and loading QPY payloads and is also a starting point towards adding QPY support to the C API.
  • Expansion of the C API’s functionality: The C API now exposes DAG-based representations through an opaque QkDag type, along with DAG-level transpiler pass functions that operate directly on this representation. Additional features include support for parameterized circuits, instructions for Pauli-based computations, and a circuit text drawer.
  • Improvements to the Clifford+T transpilation pipeline: We have significantly improved the Clifford+T transpilation pipeline for targeting discrete Clifford+T basis gate sets, delivering higher compilation quality and substantially faster runtimes. In particular, Qiskit now uses the gridsynth algorithm as the default method for synthesizing RZ rotations in a discrete basis, replacing the Solovay-Kitaev decomposition used in prior releases.

C API Features

Circuits Features

  • Added a PauliProductMeasurement.pauli() method to return the underlying Pauli measured by the instruction, including its global phase of +1 or -1.

  • The formally private attribute QuantumCircuit._data is now public API, but only as a handle for passing to C-API functions expecting QkCircuit. The type of the object is not specified in the public API, nor are any methods on it.

  • QuantumCircuit.compose() can be up to approximately two times faster when composing very long circuits onto another.

  • Added support for SparseObservable to evolved_operator_ansatz().

  • A new PauliProductRotationGate has been added, which represents generic Pauli product rotations of the form exp(iθ/2P)\exp(-i \theta / 2 P) for a Pauli product PP and a rotation angle θ\theta. Together with the PauliProductMeasurement, these gates form a basis for Pauli-based computation.

  • The RCCXGate is self-inverse. The RCCXGate.inverse() now returns a new RCCXGate instance instead of an instance of Gate with a custom definition.

  • Introduced a new unary negation operation negate(), allowing users to negate float and duration expressions. In case a scalar is passed, it is automatically lifted to the correct type.

    Example usage:

    from qiskit.circuit import ClassicalRegister
    from qiskit.circuit.classical import expr, types
    
    qc = QuantumCircuit(2, 2)
    in_var1 = qc.add_input("in1", types.Float())
    
    with qc.if_test(expr.greater(in_var1, 0.2)) as else_:
        qc.rz(0.3, 0)
    with else_:
        with qc.if_test(expr.greater(expr.negate(in_var1), 0.2)): 
            qc.rz(-0.3, 0)

Providers Features

  • The BasicSimulator now automatically detects and optimizes simulation of Clifford circuits by using the stabilizer formalism via StabilizerState. This provides significant performance improvements for circuits containing only Clifford gates (H, S, CX, CZ, X, Y, Z, etc.), with polynomial scaling instead of exponential scaling in the number of qubits. Non-Clifford circuits continue to use the existing statevector simulation method. This optimization is transparent to users and requires no code changes.

    For example:

    from qiskit import QuantumCircuit
    from qiskit.providers.basic_provider import BasicSimulator
    
    # Clifford circuit (Bell state)
    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure([0, 1], [0, 1])
    
    # Automatically uses optimized StabilizerState simulation
    backend = BasicSimulator()
    result = backend.run(qc, shots=1000).result()
    counts = result.get_counts()

Quantum Information Features

Transpiler Features

  • Added a new filter_fn argument to the analysis passes Collect1qRuns and Collect2qBlocks. This optional callable allows to filter collected circuits based on custom criteria, for example to return only single-qubits runs with at least one Hadamard gate, or only 2-qubit blocks with at least 3 CX-gates.

  • Added a new transpiler pass, SynthesizeRZRotations, which replaces all single-qubit RZGate rotation gates with floating-point angles by equivalent Clifford+T sequences.

    The synthesis accuracy can be controlled by either specifying an approximation_degree, or alternatively by explicitly setting both synthesis_error and cache_error.

    Example usage:

    from qiskit import QuantumCircuit
    from qiskit.transpiler.passes import SynthesizeRZRotations
    
    qc = QuantumCircuit(1)
    qc.rz(1.23, 0)
    
    rz_synthesis_pass = SynthesizeRZRotations(approximation_degree=0.9999)
    synthesized_qc = rz_synthesis_pass(qc)
    synthesized_qc.draw("mpl")
    _images/release_notes-1.png
  • The StandardEquivalenceLibrary now includes an RXXGate/RYYGate-based equivalence for XXMinusYYGate, matching the one that already existed for XXPlusYYGate.

  • Extended the transpiler pass SubstitutePi4Rotations, to handle more standard gates:

  • Added the function clifford_t_pass_manager() that creates a pass manager for transpiling into Clifford+T basis sets. This function is automatically invoked by generate_preset_pass_manager() when the target basis consists of Clifford+T gates.

    The generated transpilation pipeline consisting of the following stages:

    • Initialization: Decomposes larger gates into 1-qubit and 2-qubits gates and performs logical optimizations.
    • Layout: Applies the default layout strategy used for continuous basis sets.
    • Routing: Applies the default routing strategy used for continuous basis sets.
    • RZ translation: Translates the circuit into Clifford+RZ basis.
    • RZ optimization: Optimizes the circuit within Clifford+RZ basis.
    • T translation: Translates the circuit into Clifford+T basis.
    • T optimization: Optimizes the circuit within Clifford+T basis.
    • Scheduling: Applies the default scheduling strategy used for continuous basis sets.

    These stages are still experimental and subject to change. In particular, they are not yet exposed as transpiler stage plugins (unlike the stages for continuous basis sets).

    For best results, consider including both TT and TT^\dagger into the specified Clifford+T basis and as many Clifford gates as possible. For example:

    basis_gates = get_clifford_gate_names() + ["t", "tdg"]
  • The standard equivalence library has been expanded and refined:

    • Added decompositions for RXGate (in terms of HGate and RZGate) and ZGate (in terms of SdgGate)
    • Simplified decompositions for TGate and TdgGate to use a single corresponding dagger gate plus Clifford gates
  • The OptimizeCliffordT transpiler optimization pass now accepts a basis_gates argument, preferring decompositions that use Clifford gates from the target basis.

  • Added a new argument called insert_barrier to the LitinskiTransformation pass. If fix_clifford=True and insert_barrier=True, a barrier is added in between the Pauli-based computation part of the circuit and the final cliffords.

  • Added an argument use_ppr to the LitinskiTransformation pass, which, if True, uses the Rust-native PauliProductRotationGate to represent Pauli product rotations. This improves performance and ergonomics, since the PauliProductRotationGate directly represents single Pauli products instead of generic sums of Paulis. If use_ppr=False (the default), the pass keeps using PauliEvolutionGate objects to represent the rotations.

  • Added a new transpiler pass ConvertToPauliRotations, that converts a quantum circuit containing single-qubit, two-qubit and three-qubit standard gates, barriers and measurements, into an equivalent circuit containing PauliProductRotationGate gates and PauliProductMeasurement instructions.

  • The RemoveIdentityEquivalent transpiler pass is now multithreaded by default. This improves the runtime performance of the pass when running the pass on large circuits. You can control the threading behavior using the RAYON_NUM_THREADS environment variable. You can refer to the function’s documentation on how to use this environment variable.

  • Added a new argument min_distance to the SabrePreLayout pass, that specifies the distance for the first VF2 run with the augmented coupling map. Setting min_distance > 1 skips all smaller-distance checks, and in particular skips the distance-1 check which corresponds to running the VF2Layout pass.

  • The call_limit_vf2 argument of the SabrePreLayout pass can now be a 2-tuple, similarly to the call_limit argument of VF2Layout. When a 2-tuple, the first item is used before the first match is found, then the limit swaps to the second after.

  • The OptimizeCliffordT transpiler optimization pass now also replaces chains of consecutive Clifford+T gates when the T-count stays the same but the Clifford count is reduced.

  • The fast-path main loop of UnitarySynthesis now aggressively caches its helper two-qubit decomposer objects internally, which makes the whole pass approximately 2x faster in its standard calling position in the preset pass managers.

Visualization Features

  • The CouplingMap.draw() function now supports passing layout methods to be used in rustworkx.visualization.graphviz_draw() which is called internally. This enables you to adjust the graphviz graph layout algorithm used for visualizing the CouplingMap object.

  • Added a new barrier_label_len parameter (default 16) to QuantumCircuit.draw() and circuit_drawer(). When a Barrier label exceeds this length, it is truncated and "..." is appended. This prevents very long barrier labels from making circuit diagrams unreadable. The parameter is supported by all three drawer backends (text, mpl, latex).

C API Upgrade Notes

  • qk_circuit_to_python() no longer returns a complete QuantumCircuit, but only the type of QuantumCircuit._data. The new method qk_circuit_to_python_full() provides the old functionality.

  • qk_obs_to_python() now both requires and takes ownership of the given observable. You now must not free the observable after calling this function. This is consistent with all other *_to_python functions in the C API, and avoids the overhead of cloning the data.

  • QkInstruction.params was changed from a pointer to double to a pointer to const QkParam * objects. This change happened because gate parameters in the C API are no longer restricted to fixed numeric values, but may also be parameterized expressions with unbound symbols, which are represented by a QkParam *.

  • The QISKIT_VERSION_NUMERIC symbol was removed from the header file, after being marked deprecated in Qiskit 2.2. Use QISKIT_VERSION_HEX instead, which correctly includes information about the pre-release status.

  • Modified QkTargetOp to use QkParam as the type for its QkTargetOp.params attribute. Allowing users to access any free or fixed parameter in an operation.

  • The qk_transpile_stage_optimization() function has a new required argument, layout which takes a mutable pointer to a QkTranspileLayout object which represents the layout of the transpile pipeline up until that point. Typically this will be the object as returned from qk_transpile_stage_layout() and qk_transpile_stage_routing(). This is necessary because at optimization level 3 the optimization stage will run the VF2PostLayout pass which can change the layout and that needs to be tracked when running the stage.

OpenQASM Upgrade Notes

  • Previous OpenQASM 3 code generated by qiskit.qasm3 did not include the type in the iterator variable. The exporter now includes an explicit type, following the OpenQASM 3.0 specification.

Synthesis Upgrade Notes

Transpiler Upgrade Notes

  • The internal random number generator usage for the CSPLayout pass has been changed. This means that for a fixed seed the pass will potentially return different results from previous releases. Seeded results are not guaranteed to be stable between releases for the transpiler but if you were relying on the exact results from this pass you can run the pass in an early release and use qpy to save the output and load it with the latest release.

Miscellaneous Upgrade Notes

  • Qiskit now builds on the manylinux_2_28 image, which effectively increases the required version of glibc on Linux hosts to 2.28, up from 2.17. This should be supported on Debian 10+ (buster), Ubuntu 18.10+ (cosmic), Fedora 29+ and RHEL 8+.

    NumPy on Python 3.14 already requires glibc 2.28, so in practice, Qiskit was already not usable on platforms limited to the manylinux2014 dependencies with later Python versions. The glibc version was updated for all Python versions for consistency in the build.

    It may continue to be possible to build Qiskit from source using older versions of glibc, though this will no longer be tested in CI.

C API Deprecations

Build System Changes

  • The C API headers are now built into the distribution location by an in-repo binary qiskit-bindgen-c, rather than in the build script of qiskit-cext. The make recipe make cheader (and the complete make c) still produce the complete distribution artifact in dist/c.

  • The build system now reads the environment variable QISKIT_BUILD_PROFILE to choose whether to build in release or debug modes. If this is not set, pip install . will (continue to) default to release mode, and pip install -e . to debug mode.

  • The minimum version of CMake required to build and run the C-API test suite has been lowered to 3.20 from 3.27. Qiskit does not commit to any particular policy around the version of CMake required, but will make a best-effort attempt to match the repositories of common and still-supported Linux distributions.

  • Qiskit now manages its additional development dependencies using separate PEP 735 dependency-groups entries for different tasks, replacing the old monolithic requirements-dev.txt and requirements-optional.txt files.

  • Packages depending on the Qiskit C API to build can now specify Qiskit as a Python-space build dependency, then use qiskit.capi.get_include() to find the header files.

Known Issues

  • Qiskit v2.2 and v2.3 can generate unreadable QPY files for circuits containing a ParameterExpression where all the contained Parameter instances were cancelled out of the final expression. For example, if the expression 0*x + 1.5 appears in a circuit, Qiskit v2.2 and v2.3 will both produce a QPY file that cannot be read by any version of Qiskit, including themselves and v2.4, due to errors in the QPY generation.

    This error will typically appear (in Qiskit v2.4, at least) as a QpyError with a message such as “malformed expression: stack was empty before expression completed”.

Bug Fixes

  • QuantumCircuit.append() now correctly rejects duplicate classical bits in the cargs argument, matching the behavior of qargs. While classical bits can in theory be copied, unlike quantum bits, the internal data model of Qiskit cannot represent multiple use of the same bit, and circuits using this would typically experience internal library panics or nonsensical downstream simulator behavior due to the data-model assumption violation.

  • Fixed a mismatch in QkTargetOp in which the length of the array stored in QkTargetOp.params did not match the number exposed by QkTargetOp.num_params. Wildcard parameters are now represented by NAN, and the length of the array will always be QkTargetOp.num_params. This bug was only present in 2.3.0rc1.

  • Fixed a runtime cost linear in the number of bits of the base circuit in QuantumCircuit.compose(). This could cause performance problems when repeatedly composing small circuits with no clbits onto a base with a very large number of clbits.

  • Counts objects will now correctly error on construction if the keys are not all in the same format.

  • Fixed a bug in CommutationChecker where certain Rust-backed gates, such as UnitaryGate, could bypass matrix size limits, leading to the construction of excessively large matrices.

  • Fixed a bug in evolved_operator_ansatz(), where setting the parameter_prefix argument to a single string could cause an error if identity terms were removed, depending on the order of operators.

  • Fixed a bug where the loop variable of a ForLoopOp was incorrectly tracked in the outer circuit’s parameter table, causing it to appear in QuantumCircuit.parameters and making assign_parameters() raise an internal RuntimeError. See #15657 for details.

  • Fixed an unclear error message in Initialize.inverse(), which now immediately raises a clear error indicating that the instruction is not invertible. See #15595.

  • Fixed MCX synthesis methods to handle correctly the edge case where num_ctrl_qubits is 00. Fixed #15665.

  • Fixed an issue in the qk_transpile() and qk_transpile_stage_optimization() C API functions when using optimization_level=3 in QkTranspileOptions. Previously, the internal minimum-point tracking logic failed to update the DAG after finding a better candidate circuit, which could result in circuits with suboptimal depth and size.

  • ParameterExpression instances with cancelled-out variables (like x - x) will now retain the reference to x after a round-trip through QPY and pickle.

  • ParameterExpression instances that evaluated to a bare value (like 0*x + 2) will now successfully round-trip through QPY and pickle.

  • Fixed a bug around PauliEvolutionGate, where the average gate fidelity was slightly larger than the actual fidelity. This had knock-on effects in passes such as RemoveIdentityEquivalent, which would remove gates at slightly larger angles than it should have. This has now been fixed and the PauliEvolutionGate is guaranteed to handle cutoffs the same way as other standard rotation gates.

  • Fixed commutativity check between two PauliProductMeasurement instructions in the case that both instructions measure to the same classical bit. In this case, the later measurement overwrites the result of the earlier measurement, and consequently, interchanging the two measurement instructions inside the quantum circuit is generally invalid.

  • Fixed synth_qft_line() to correctly synthesize circuits with 32 or more qubits.

  • Fixed a bug in the ElidePermutations transpiler pass that caused it to crash when handling single-qubit permutations.

  • Fixed an issue with QuantumCircuit.compose() when called with front=True and an explicit qubits mapping, which could incorrectly raise a ValueError instead of returning the correct circuit. See #15834.

  • Fixed the Gate.control() method to correctly handle the edge case where num_ctrl_qubits is 00. Previously, this could raise an error or cause a core dump. Now it returns a copy of the original gate. Fixed #15666.

  • Fixed the QuantumCircuit.mcrx(), QuantumCircuit.mcry(), QuantumCircuit.mcrz(), QuantumCircuit.mcp(), and QuantumCircuit.mcx() methods when appending multi-controlled gates with no control qubits. Previously, this could lead to memory overflows. This case is now handled correctly. Fixed #15664.

  • Fixed the definition of MCPhaseGate for the case of 00 controls qubits.

  • Fixed an issue in BasisTranslator where circuits containing nested control flow operations did not have the transformation applied to their control flow blocks. See #13162, #14025, and #15734.

  • The OpenQASM 3 exporter (qiskit.qasm3) now includes the type for the iterator in for loops. Fixes #13725.

  • QuantumCircuit.remove_final_measurements() will no longer emit a spurious warning about trying to add registers when called on a circuit with a set layout.

Other Notes

  • The SabrePreLayout pass is simplified to leverage improvements in the VF2Layout pass. The expensive post-processing step to minimize “extra” edges in the layout has been removed, as this optimization is now handled more efficiently by internal scoring mechanism of VF2Layout. Consequently, the improve_layout argument is now obsolete and has no effect.
Was this page helpful?
Report a bug, typo, or request content on GitHub.