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
-
Fixed a bug where calling
QuantumCircuit.decompose()
on an instruction that had no definition inside ac_if
block would raise an error. Fixed #13493. -
Operations inside a control flow operation (e.g.
QuantumCircuit.for_loop()
) were not correctly decomposed when callingQuantumCircuit.decompose()
. Fixed #13544. -
Added default definitions for
FullAdderGate
,HalfAdderGate
,ModularAdderGate
andMultiplierGate
gates, allowingOperator
s to be constructed from quantum circuits containing these gates. -
Corrected the number of clean ancilla qubits required by
FullAdderSynthesisV95
,HalfAdderSynthesisV95
, andModularAdderSynthesisV95
plugins. -
Added missing
FullAdderSynthesisDefault
plugin that chooses the best decomposition forFullAdderGate
based on the number of clean ancilla qubits available. -
Fixed
HalfAdderSynthesisDefault
andModularAdderSynthesisDefault
plugins, forHalfAdderGate
andModularAdderGate
respectively, to choose the best decomposition based on the number of clean ancilla qubits available. -
Fixed an incorrect caching behavior during parameter assignment that could result in definitions within the
EquivalenceLibrary
becoming corrupted. This resolves unexpected issues when running basis translation in parallel. Fixed #13504. -
Fixed a series of bugs when processing circuits with parameterized global phases in which the global phase was not properly assigned during parameter assignment. Known cases this affected include:
- assigning parameters after calling
QuantumCircuit.decompose()
on a circuit, where the decomposition introduces a global phase - assigning parameters on a circuit constructed from a DAG via
dag_to_circuit()
- assigning parameters on circuits created with
pauli_twirl_2q_gates()
, where the circuit to be twirled had a parameterized global phase
Fixed #13534.
- assigning parameters after calling
-
Fixed a bug in
RZGate.control()
for more than 1 control qubit, which used an unnecessarily expensive decomposition. Fixed #13473.
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
, andUnitarySynthesis
. 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
orInstruction
(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 volumeQuantumCircuit
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 existingQuantumVolume
class in that it returns aQuantumCircuit
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 theQuantumVolume
class. -
Improved the runtime performance of constructing the
QuantumVolume
class with theclassical_permutation
argument set toTrue
. Internally it now calls thequantum_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, currentlyCXGate
,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")
-
Added binary arithmetic gates for inplace addition of two -qubit registers, that is . The
ModularAdderGate
implements addition modulo , theHalfAdderGate
includes a carry-out qubit, and theFullAdderGate
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 -qubit registers, that is . See the class documentation for details and examples. -
The boolean logic quantum circuits in
qiskit.circuit.library
now have equivalent representations asGate
objects enabling their use withHighLevelSynthesis
transpiler pass and plugin infrastructure.AndGate
, representingAND
,OrGate
, representingOR
,BitwiseXorGate
, representingXOR
,InnerProductGate
, representingInnerProduct
.
-
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()
, andqaoa_ansatz()
into the circuit library to implement variational circuits based on operator evolutions.evolved_operator_ansatz()
andqaoa_ansatz()
are functionally equivalent toEvolvedOperatorAnsatz
andQAOAAnsatz
, 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 theEvolvedOperatorAnsatz
, 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 toGroverOperator
, 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')
-
A new data attribute,
qiskit.circuit.CONTROL_FLOW_OP_NAMES
, is available to easily find and check whether a givenInstruction
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.NoteIn general, the
BasisTranslator
is not guaranteed to find the “best” equivalence relation for a givenTarget
, 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"
toDecompose
, 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 existingIQP
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')
-
Added the
MCMTGate
to represent a multi-control multi-target operation as a gate. This gate representation of the existingMCMT
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:GraphState
is represented byGraphStateGate
,FourierChecking
can be constructed usingfourier_checking()
,UnitaryOverlap
can be constructed usingunitary_overlap()
,HiddenLinearFunction
can be constructed usinghidden_linear_function()
,PhaseEstimation
can be constructed usingphase_estimation()
.
-
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
andZZFeatureMap
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 inQuantumCircuit
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
, andZZFeatureMap
, 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 therun()
method of the wrappedBackendV2
. The run options can be specified by using a"run_options"
entry inside theoptions
dictionary passed toBackendSamplerV2
. The"run_options"
entry should be a dictionary that maps argument names to values to be passed to the backend’srun()
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
andStatevectorEstimator
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
- The class
qasm3.CustomGate
is now inspectable programmatically. Itsconstructor
,name
,num_params
andnum_qubits
can now be viewed from Python after the object has been constructed. This allows you to inspect the contents of provided data attributes likeSTDGATES_INC_GATES
.
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
toqiskit.quantum_info.Operator.power()
. WhenTrue
, we use a faster method based on Schur’s decomposition to raise anOperator
to a fractional power. -
Added
SparsePauliOp.to_sparse_list()
to convert an operator into a sparse list format. This works inversely toSparsePauliOp.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 parameterbranch_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 toSparsePauliOp
, but with two core differences:- 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 . - 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 . Such projectors can be measured on hardware equally as efficiently as their corresponding Pauli operator, butSparsePauliOp
would require an exponential number of terms to represent over qubits, whileSparseObservable
needs only a single term.
You can construct and manipulate
SparseObservable
using an interface familiar to users ofSparsePauliOp
: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. - Each complete term is stored as (effectively) a series of
Synthesis Features
-
Added synthesis functions
synth_mcx_gray_code()
andsynth_mcx_noaux_v24()
that synthesize multi-controlled X gates. These functions do not require additional ancilla qubits. -
Added synthesis functions
synth_c3x()
andsynth_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 controls using dirty ancillary qubits producing a circuit with at most 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 controls using clean ancillary qubits producing a circuit with at most CX gates, by Maslov (arXiv:1508.03273). -
Add a synthesis function
synth_mcx_1_clean_b95()
that synthesizes a multi-controlled X gate with controls using a single clean ancillary qubit producing a circuit with at most CX gates, by Barenco et al. (arXiv:quant-ph/9503016). -
Added
adder_qft_d00()
,adder_ripple_c04()
, andadder_ripple_v95()
to synthesize the adder gates,ModularAdderGate
,HalfAdderGate
, andFullAdderGate
. -
Added
multiplier_cumulative_h18()
andmultiplier_qft_r17()
to synthesize theMultiplierGate
. -
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 pluginMCMTSynthesisVChain
. -
Added a high-level synthesis plugin structure for the
MCMTGate
, including theMCMTSynthesisNoAux
(for no auxiliary qubits), the aforementionedMCMTSynthesisVChain
(usingnum_control - 1
auxiliary qubits), and theMCMTSynthesisDefault
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')
This code snippet uses the Rustiq plugin to synthesize
PauliEvolutionGate
objects in the quantum circuit qc. The plugin is called with the additional optionupto_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 forPauliEvolutionSynthesisRustiq
. -
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
toProductFormula
, which allows re-ordering the Pauli terms in the Hamiltonian before the product formula expansion, to compress the final circuit depth. By setting this toFalse
, a term of form
will be re-ordered to
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')
Transpiler Features
-
Add argument
matrix_based
to theCollectCliffords
transpiler pass. If the new parametermatrix_based=True
, theCollectCliffords
transpiler pass can collect unitary gates that are Clifford gates for certain parameters, for exampleRZGate
gates with angles that are multiples of . -
The
RemoveIdentityEquivalent
transpiler pass is now run as part of the preset pass managers at optimization levels 2 and 3. The pass runs during theinit
andoptimization
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
:MCXSynthesisNCleanM15
, based onsynth_mcx_n_clean_m15()
.MCXSynthesisNDirtyI15
, based onsynth_mcx_n_dirty_i15()
.MCXSynthesis1CleanB95
, based onsynth_mcx_1_clean_b95()
.MCXSynthesisNoAuxV24
, based onsynth_mcx_noaux_v24()
.MCXSynthesisGrayCode
, based onsynth_mcx_gray_code()
.
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)
For the first MCX gate, qubits
5
and6
can be used as clean ancillas, and the best available synthesis methodsynth_mcx_n_clean_m15
will get chosen. For the second MCX gate, qubit6
can be used as a clean ancilla, the methodsynth_mcx_n_clean_m15
no longer applies, so the methodsynth_mcx_1_clean_b95
will get chosen. For the third MCX gate, there are no ancilla qubits, and the methodsynth_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 thelayout_trials
keyword argument’s value. A trivial layout and its reverse are included for all backends, just like theDenseLayout
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 ofDAGCircuit
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 aDAGCircuit
from Rust, enabling improvements in performance. -
A new argument
qubits_initially_zero
has been added toqiskit.compiler.transpile()
,generate_preset_pass_manager()
, and toPassManagerConfig
. If set to the default value ofTrue
, the qubits are assumed to be initially in the state , potentially allowing additional optimization opportunities for individual transpiler passes. In particular, theHighLevelSynthesis
transpiler pass will choose a better decomposition for eachMCXGate
gate in a circuit when an idle auxiliary qubit in the state is available.However, there are cases when
qubits_initially_zero
should be set toFalse
, 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 toTrue
, the pass assumes that the qubits are initially in the state . In addition, the pass keeps track of clean and dirty auxiliary qubits throughout the run, and passes this information to plugins by using kwargsnum_clean_ancillas
andnum_dirty_ancillas
. -
Improved handling of ancilla qubits in the
HighLevelSynthesis
transpiler pass. For example, a circuit may have custom gates whose definitions includeMCXGate
s. Now the synthesis algorithms for the innerMCXGate
s 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 theDAGOpNode
in aDAGCircuit
that contain aControlFlowOp
. This was possible before using theDAGCircuit.op_nodes()
method and passing theControlFlowOp
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 bytranspile()
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:ConsolidateBlocks
BasisTranslator
CommutationAnalysis
CommutativeCancellation
ElidePermutations
Optimize1qGatesDecomposition
UnitarySynthesis
GateDirection
CheckGateDirection
RemoveDiagonalGatesBeforeMeasure
CheckMap
Split2QUnitaries
FilterOpNodes
Depth
Size
GatesInBasis
InverseCancellation
BarrierBeforeFinalMeasurements
-
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: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")
-
The
ConsolidateBlocks
pass will now run the equivalent of theCollect2qBlocks
pass internally if it was not run in a pass manager prior to the pass. Previously,Collect2qBlocks
orCollect1qRuns
had to be run beforeConsolidateBlocks
forConsolidateBlocks
to do anything. By doing the collection internally, the overhead from the pass is reduced. IfCollect2qBlocks
orCollect1qRuns
are run prior toConsolidateBlocks
, 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
andCCZGate
. In addition, the code of theRemoveDiagonalGatesBeforeMeasure
was ported to Rust, and is now x20 faster for a 20 qubit circuit.
Visualization Features
- The
timeline_drawer()
visualization function has a new argumenttarget
, used to specify aTarget
object for the visualization. By default the function used theInstruction.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
ParameterExpression
s, if the version of thesymengine
package installed in the environment that generated the payload (qpy.dump()
) does not match the version ofsymengine
installed in the enviroment where the payload is loaded (qpy.load()
), you will get an error. If you encounter this error, install thesymengine
version from the error message before callingqpy.load()
. QPY format versions 13 or later (or prior to 10) will not have this issue. Therefore, if you’re serializingParameterExpression
objects as part of your circuit or anyScheduleBlock
objects, it is recommended that you use version 13 to avoid this issue in the future.
Upgrade Notes
-
The following classes now use the operation to diagonalize the Pauli-Y operator:
PauliEvolutionGate
,EvolvedOperatorAnsatz
, andPauliFeatureMap
. Previously, these classes used either or as basis transformation. Using the operation, represented by theSXGate
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 customTransformationPass
, 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 theflatten=True
argument and export the circuit withqpy.dump()
and then load it with this release.
Primitives Upgrade Notes
- When using
BackendSamplerV2
, circuit metadata is no longer cleared before passing circuits to therun()
method of the wrappedBackendV2
instance. If you were previously relying on this behavior you can manually clear the metadata before callingBackendSamplerV2.run()
by callingcircuit.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 theversion
flag onqpy.dump()
to emit the appropriate version. Refer to QPY Compatibility for more details.
Transpiler Upgrade Notes
-
DAGNode
objects (and its subclassesDAGInNode
,DAGOutNode
, andDAGOpNode
) no longer return references to the same underlying object fromDAGCircuit
methods. This was never a guarantee before that all returned nodes would be shared reference to the same object. However, with the migration of theDAGCircuit
to Rust, a newDAGNode
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 theDAGCircuit
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 asDAGCircuit.substitute_node()
orDAGCircuit.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 theDAGOpNode
itself. However in 1.3 this extends to the entire object, not just it’s innerop
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 . To avoid this assumption, one can set the argumentqubits_initially_zero
toFalse
.
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 theInstruction.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 theIfElseOp
class which can be used to describe a classical condition in a circuit. For example, a circuit usingInstruction.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:
qiskit.circuit.QuantumCircuit.calibrations
qiskit.circuit.QuantumCircuit.has_calibration_for()
qiskit.circuit.QuantumCircuit.add_calibration()
qiskit.dagcircuit.DAGCircuit.calibrations
qiskit.dagcircuit.DAGCircuit.has_calibration_for()
qiskit.dagcircuit.DAGCircuit.add_calibration()
qiskit.dagcircuit.DAGDependency.calibrations
-
The
QuantumCircuit.unit
andQuantumCircuit.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 ofDAGCircuit
and wouldn’t ever correctly account for control flow or conditionals in the circuit. -
The
DAGCircuit.unit
andDAGCircuit.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 ofDAGCircuit
and wouldn’t ever correctly account for control flow or conditionals in the circuit. -
The
Instruction.duration
andInstruction.unit
attributes have been deprecated and will be removed in Qiskit 2.0.0. This includes setting theunit
orduration
arguments for anyqiskit.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 theBackendV2
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 theInstructionProperties.duration
field in a givenTarget
to set a custom duration for an instruction on a backend (the unit is always in seconds in theTarget
).
Providers Deprecations
-
The
BasicSimulator.configuration()
method is deprecated and will be removed in 2.0.0. This method returned a legacyproviders.models.BackendConfiguration
instance which is part of the deprecatedBackendV1
model. This model has been replaced withBackendV2
, where the constraints are stored directly in the backend instance or the underlyingTarget
(backend.target
).Here is a quick guide for accessing the most common
BackendConfiguration
attributes in theBackendV2
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
includesbasis_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. -
As part of the Qiskit Pulse package deprecation, all pulse-related functionality in
qiskit.providers.BackendV2
class is being deprecated. This includes the following methods:Consequently, the corresponding channel methods in the
qiskit.providers.BackendV2Converter
andqiskit.providers.fake_provider.GenericBackendV2
classes are being deprecated as well.In addition, the
pulse_channels
andcalibrate_instructions
arguments in theBackendV2
initializer method are being deprecated. -
The
defaults
argument is being deprecated from theqiskit.providers.convert_to_target()
function.
QPY Deprecations
- As part of the Qiskit Pulse package deprecation, serializing
qiskit.pulse.ScheduleBlock
-based payloads is also being deprecated. Particularly the passing ofqiskit.pulse.ScheduleBlock
objects to the programs argument in theqiskit.qpy.dump()
.
Transpiler Deprecations
-
Deprecated
StochasticSwap
which has been superseded bySabreSwap
. 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 forInstruction.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 bothtranspile()
andgenerate_preset_pass_manager()
. This functionality will be removed in Qiskit 2.0. Custom gates are still supported in theTarget
model, and can be provided through thetarget
argument. One can build aTarget
instance from scratch or use theTarget.from_configuration()
method with thecustom_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 )
-
As part of the Qiskit Pulse package deprecation, pulse-related aspects in the
qiskit.transpiler.Target
class are being deprecated. These include:update_from_instruction_schedule_map()
has_calibration()
get_calibration()
instruction_schedule_map()
In addition, the following transpiler passes are also being deprecated:
-
The
inst_map
argument ingenerate_preset_pass_manager()
,from_configuration()
,PassManagerConfig
initializer andgenerate_scheduling()
is being deprecated. -
The
calibration
argument inInstructionProperties()
initializer methods is being deprecated. -
The following
transpile()
andgenerate_preset_pass_manager()
arguments are deprecated in favor of defining a customTarget
:instruction_durations
,timing_constraints
, andbackend_properties
. These arguments can be used to build a target withTarget.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 typeBackendV1
in the backend parameter in a future release no earlier than 2.0.BackendV1
is deprecated and implementations should move toBackendV2
.
Misc. Deprecations
-
The
qiskit.result.mitigation
module has been deprecated and will be removed in Qiskit 2.0. The deprecation includes theLocalReadoutMitigator
andCorrelatedReadoutMitigator
classes as well as the associated utility functions. Their functionality has been superseded by the mthree package, found in https://github.com/Qiskit/qiskit-addon-mthree. -
As part of the Qiskit Pulse package deprecation, the following functions and class are being deprecated as well:
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 callassign_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
, thebasis_gates
entry in the configuration instance returned by theconfiguration()
method is now a list instead of adict_keys
instance, matching the expected type and allowing for configuration instance to be deep copied. -
Fixed an issue with
DAGCircuit.apply_operation_back()
andDAGCircuit.apply_operation_front()
where previously if you set aClbit
object to the input for theqargs
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 ifGate.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 forNLocal
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 usenum_qubits
entangling blocks per layer. -
Add more Clifford gates to the
CollectCliffords
transpiler pass. In particular, we have added the gatesECRGate
,DCXGate
,iSwapGate
,SXGate
andSXdgGate
to this transpiler pass. -
Fixed a bug in
QuantumCircuit.decompose()
where objects that could be synthesized withHighLevelSynthesis
were first synthesized and then decomposed immediately (i.e., they were decomposed twice instead of once). This affected gates such asMCXGate
orClifford
, 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 passedbasis_gates
. This behavior is now fixed, so that such already supported instructions are no longer synthesized. -
The transpilation pass
InverseCancellation
now will recurse intoControlFlowOp
operations present in aQuantumCircuit
. 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 inUnitaryGate.control()
whenUnitaryGate
had more that two qubits. -
The
QuantumCircuit.parameters
attribute will now correctly be empty when usingQuantumCircuit.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 asGlobalPhaseGate
. -
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 aPauliList
with nonzero phase, where subsequent calls to severalSparsePauliOp
methods would produce incorrect results. Now whenSparsePauliOp.paulis
is set to aPauliList
with nonzero phase, the phase is absorbed intoSparsePauliOp.coeffs
, and the phase of the inputPauliList
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 aBackendV2
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()
andDAGCircuit.draw()
when attempting to visualize aDAGCircuit
instance that containedVar
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 aTarget
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 (likefloat
andint
) 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 anGate.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, asOperator
does, which might be less performant than before the round-trip. -
Previously,
DAGCircuit.replace_block_with_op()
allowed to place ann
-qubit operation onto a block ofm
qubits, leaving the DAG in an invalid state. This behavior has been fixed, and the attempt will raise aDAGCircuitError
. -
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 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 . 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 newbranch_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 withNone
for its instruction properties. Fixes #12525.