Skip to main contentIBM Quantum Documentation

Qiskit SDK 1.3 release notes


1.3.1

Prelude

Qiskit 1.3.1 is a minor bugfix release for the 1.3 series.

Circuits Upgrade Notes

  • The generic control method for gates now avoids attempting to translate gates into a supported basis when the gate is already supported. This may slightly change the synthesis of the controlled gate, but it should not increase the two-qubit gate count.

Bug Fixes


1.3.0

Prelude

Qiskit version 1.3.0 brings in major performance and quality improvements for the transpiler. There have been many new features, fixes and improvements introduced in this new version of Qiskit, the highlights are:

  • The core data structures for transpilation in Qiskit have been ported to Rust internally. This includes components such as the DAGCircuit, Target, EquivalenceLibrary, and others. The public APIs for all of these data structures remains unchanged but the performance after the rewrites has improved.

  • The majority of the transpiler passes used by the preset pass manager have been ported into Rust, resulting in an average 6x overall runtime improvement compared to Qiskit 1.2.4 when running the benchpress benchmarks. Some of the particularly impactful passes that greatly benefited from the rewrites were the BasisTranslator, CommutationAnalysis, ConsolidateBlocks, and UnitarySynthesis. You can refer to the feature release notes for a full list of the ported passes.

  • Improvements to the circuit library that increase compilation quality and circuit construction speed. Operations are now distinguished into:

    • Structural operations, that have a unique decomposition, and are represented as functions that return a QuantumCircuit (for example: real_amplitudes()). Most of these functions are built in Rust, resulting in significantly faster circuit construction runtime.
    • Abstract operations, that can be implemented using different decompositions, are represented as Gate or Instruction (for example: PauliEvolutionGate). This allows building an abstract quantum circuit and letting the compiler choose the optimal decomposition.

    Using an abstract circuit description is especially powerful in combination with improvements to the HighLevelSynthesis transpiler pass, which can now take into account idle auxiliary qubits to find the best available decomposition for a given gate.

  • The minimum supported Python version is now 3.9 as Python 3.8 went end of life in 2024-10. Official support for Python 3.13 support was also added in this release.

Circuits Features

  • Improved the functionality of CommutationChecker to include support for the following parameterized gates with free parameters: RXXGate, RYYGate, RZZGate, RZXGate, RXGate, RYGate, RZGate, PhaseGate, U1Gate, CRXGate, CRYGate, 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). 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 and is ~10x faster when generating 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-1.png
  • Added binary arithmetic gates for inplace addition of 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 HalfAdderGate includes a carry-out qubit, and the FullAdderGate includes both a carry-in and a carry-out qubit. See the respective documentation 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 ab0abab|a\rangle |b\rangle |0\rangle \mapsto |a\rangle |b\rangle |a \cdot b\rangle. See the class documentation for details and examples.

  • The boolean logic quantum circuits in qiskit.circuit.library now have equivalent representations as Gate objects enabling their use with HighLevelSynthesis transpiler pass and plugin infrastructure.

  • 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() into 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 Grover’s algorithm and amplitude estimation/amplification, for example. This function is similar to GroverOperator, but does not require you to choose the implementation of the multi-controlled X gate, but instead lets the compiler determine the optimal decomposition. Additionally, it does not wrap the circuit into an opaque gate and is faster because fewer decompositions are required for transpilation.

    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)
    grover_op.draw('mpl')
    _images/release_notes-2.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.

  • 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 random_iqp
     
    random_iqp = random_iqp(num_qubits=4)
    random_iqp.draw('mpl')
    _images/release_notes-3.png
  • 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.

    The desired 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)

    Additionally, the MCMTGate also supports custom (that is, 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:

  • The CommutationChecker class has been rewritten in Rust. This class retains the same functionality and public API as before but is now significantly faster in most cases.

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

    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()

Primitives 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 by using a "run_options" entry inside the options dictionary passed to BackendSamplerV2. The "run_options" entry should be a dictionary that maps argument names to values to be passed to the backend’s run() method. When a "meas_level = 1 " is set in the run options, the results from the backend will be treated as level 1 results instead of bit arrays (the level 2 format).

  • Estimator and StatevectorEstimator return expectation values in a stochastic way if the input circuit includes a reset for some subsystems. The result was previously not reproducible, but now it can be reproduced 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

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 SparsePauliOp.to_sparse_list() to convert an operator into a sparse list format. This works inversely to SparsePauliOp.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 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).

  • Added adder_qft_d00(), adder_ripple_c04(), and adder_ripple_v95() to synthesize the adder gates, ModularAdderGate, HalfAdderGate, 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.

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

  • Added ProductFormula.expand(), which lets you view the expansion of a product formula in a sparse Pauli format. For example, we can query the format of a second-order Trotter expansion of a Hamiltonian as:

    from qiskit.quantum_info import SparsePauliOp
    from qiskit.circuit.library import PauliEvolutionGate
    from qiskit.synthesis.evolution import SuzukiTrotter
     
    hamiltonian = SparsePauliOp(["IX", "XI", "ZZ"], coeffs=[-1, -1, 1])
    evo = PauliEvolutionGate(hamiltonian, time=3.14)
    trotter = SuzukiTrotter(order=2)
    print(trotter.expand(evo))

    which will print

    [('X', [0], -3.14), ('X', [1], -3.14), ('ZZ', [0, 1], 6.28),
    ('X', [1], -3.14), ('X', [0], -3.14)]
  • 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 (for example. by the Hamiltonian size, the number of timesteps, or the Suzuki-Trotter order), the greater 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” and is implemented in Rustiq. 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')
    _images/release_notes-4.png

    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 you to obtain smaller circuits at the expense of possibly not preserving the global phase. For the full list of supported options, see the documentation for PauliEvolutionSynthesisRustiq.

  • Port 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 30x.

  • Port synth_permutation_reverse_lnn_kms() to Rust, which synthesizes a reverse permutation for LNN architecture using the Kutin, Moulton, Smithline method.

  • 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)
tqc.draw('mpl')
_images/release_notes-5.png

Transpiler Features

  • Add argument matrix_based to the CollectCliffords transpiler pass. If the new parameter matrix_based=True, the CollectCliffords transpiler pass can collect unitary gates that are Clifford gates for certain parameters, for example RZGate gates with angles that are multiples of π/2\pi / 2.

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

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

    As well as:

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

    As an example, consider 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).draw('mpl', fold=-1)
    _images/release_notes-6.png

    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 SabreLayout transpiler pass has been updated to run an additional two or three layout trials by default, independently from 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”.

  • The DAGCircuit has been reimplemented in Rust. This rewrite of the Python class should be fully API compatible with the previous Python implementation. While the class was previously implemented using rustworkx, for which the underlying data graph structure exists in Rust, the implementation of the class and all the data was lived in Python. This new version of DAGCircuit stores Rust native representations for all its data and is more memory-efficient due to the compressed qubit and clbit representation designed for instructions at rest. It also enables transpiler passes to fully manipulate a DAGCircuit from Rust, enabling improvements in performance.

  • A new argument qubits_initially_zero has been added to qiskit.compiler.transpile(), generate_preset_pass_manager(), and to PassManagerConfig. If set to the default value of True, the qubits are assumed to be initially in the state 0|0\rangle, potentially allowing additional optimization opportunities for individual transpiler passes. In particular, the HighLevelSynthesis transpiler pass will choose a better decomposition for each MCXGate gate in a circuit when an idle auxiliary qubit in the state 0|0\rangle is available.

    However, there are cases when qubits_initially_zero should be set to False, such as when transpiling for backends that do not properly initialize qubits, or when manually calling transpiler passes on subcircuits of a larger quantum circuit.

  • The constructor for the 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 by using kwargs num_clean_ancillas and num_dirty_ancillas.

  • 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 MCXGates can use the ancilla qubits available on the global circuit but outside the custom gates’ definitions.

  • Added a new method DAGCircuit.control_flow_op_nodes() 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.

  • The majority of the transpiler passes used in the preset pass managers returned by generate_preset_pass_manager() and used internally by transpile() were ported into Rust. This has significantly improved the runtime performance of each pass which has led to a significant improvement in the overall operational efficiency of the transpiler. The list of passes ported to Rust in this release are:

  • 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-7.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-8.png
  • The ConsolidateBlocks 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, Collect2qBlocks or Collect1qRuns had to be run before ConsolidateBlocks for ConsolidateBlocks to do anything. By doing the collection internally, the overhead from 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.

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

  • When using QPY formats 10, 11, or 12 with circuits that contain ParameterExpressions, if the version of the symengine package installed in the environment that generated the payload (qpy.dump()) does not match the version of symengine installed in the enviroment where the payload is loaded (qpy.load()), you will get an error. If you encounter this error, install the symengine version from the error message before calling qpy.load(). QPY format versions 13 or later (or prior to 10) will not have this issue. Therefore, if you’re serializing ParameterExpression objects as part of your circuit or any ScheduleBlock objects, it is recommended that you use version 13 to avoid this issue in the future.

Upgrade Notes

  • The following classes now use the X\sqrt{X} operation to diagonalize the Pauli-Y operator: PauliEvolutionGate, EvolvedOperatorAnsatz, and 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.

    If you were relying on the previous gate usage, we recommend building the circuits with Qiskit 1.2 and exporting them with qpy.dump(), or to write a custom TransformationPass, which performs the translation to your desired basis change gate.

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

Primitives Upgrade Notes

  • When using BackendSamplerV2, circuit metadata is no longer cleared before passing circuits to the run() method of the wrapped BackendV2 instance. If you were previously relying on this behavior you can manually clear the metadata before calling BackendSamplerV2.run() by calling circuit.metadata.clear()

QPY Upgrade Notes

  • The qpy.dump() function now emits format version 13 by default. This means payloads generated with this function by default are only compatible with Qiskit 1.3.0 or later. If the payload needs to be loaded by an earlier version of Qiskit, use the version flag on qpy.dump() to emit the appropriate version. Refer to QPY Compatibility for more details.

Transpiler Upgrade Notes

  • 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. However, with the migration of the DAGCircuit to Rust, 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() or DAGCircuit.substitute_node_with_dag(). 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.

Deprecation Notes

  • The qiskit.pulse module and all it’s associated features is now deprecated and will be removed in Qiskit 2.0.0. This is because primary backend provider with pulse support is IBM and pulse-level access is currently only supported on a subset of IBM’s backends is not supported on their newer architectures. Similarly they have announced that pulse level access will be removed in 2025 Without the largest provider using the feature supporting pulse access anymore the importance of the feature to Qiskit is significantly dimished and weighed against the continued maintanence overhead of the package it is beign removed.

    The deprecation includes all pulse code in qiskit.pulse as well as functionality dependent on or related to pulse, such as pulse visualization, serialization, and custom calibration support. For more details see the deprecation sections below.

    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. Qiskit 1.x will continue to support the qiskit.pulse until it goes end-of-life.

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 qc.if_test((qc.clbits[0], True)):
        qc.x(0)
    with qc.if_test((qc.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:

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


  • 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.convert_to_target() function.

QPY Deprecations

Transpiler Deprecations

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


  • In addition, the following transpiler passes are also being deprecated:

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

Misc. Deprecations

Bug Fixes

  • Fixed a performance regression in QuantumCircuit.assign_parameters() introduced in Qiskit 1.2.0 when calling the method in a tight loop, that caused only a small number of parameters from a heavily parametric circuit to be bound 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() method is now a list instead of a dict_keys instance, matching the expected type and allowing for configuration instance to be deep copied.

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

  • 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 gates such as 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)
  • 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.

  • The transpilation pass InverseCancellation now will recurse into ControlFlowOp operations present in a QuantumCircuit. Previously, the pass would ignore inverse gate pairs inside control flow blocks that could have been cancelled. Refer to #13437 for more details.

  • 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 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 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, for which the visualizer would raise an exception. This behavior has been fixed and 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 Gate.repeat(). Refer to #11990 for more details.

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

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

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

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

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

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

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

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

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