Qiskit SDK 2.1 release notes
2.1.0
Prelude
The Qiskit v2.1 release introduces several important enhancements in key areas including the C API, transpiler capabilities and quantum circuit usability. In addition, it includes many general improvements and bug fixes. The major highlights include:
- C API extensions: Building on the work started in the previous release, Qiskit v2.1 adds support for creating and interacting with quantum circuits via C API functions, supporting the addition of standard gates, standard instructions and unitary gates. Also added in this release is a set of C functions to build and manipulate a
Target
object, as preparation for supporting a complete C API-based transpilation workflow in the next release. In addition,QkComplex64
is now defined as a struct, serving as a compiler-agnostic representation for complex numbers when working with Qiskit’s C API. The C API’s header file has also been updated so that it is generally compatible to be used natively in C++.- Clifford+T basis set support: Transpiling circuits for a target with Clifford+T gates is now supported out of the box. Under the hood, if the basis set consists only of Clifford+T gates, Qiskit ensures that the appropriate passes for handling these gates are added to the preset pass managers. This is an initial step toward supporting transpilation for fault-tolerant backends.
- Enhancements to
BoxOp
: This version adds the ability to attach custom annotations toBoxOp
instructions, further supporting the concept of box statements from OpenQASM 3 in Qiskit. This also includes annotation serialization in both the QASM and QPY formats. Additionally, support for using stretch durations in boxes has been added.- Python 3.9 deprecation: Python 3.9 is deprecated starting with Qiskit v2.1 and will no longer be supported in Qiskit v2.3.
For more details about the above and much more, please see the release notes below and visit the updated documentation.
C API Features
-
The Qiskit C API (qiskit.h) now supports building and interacting with Quantum Circuits. The circuits C API centers around the
QkCircuit
opaque type that represents the circuit. TheQkCircuit
type enables building a circuit with any circuit element defined natively in Qiskit’s internal Rust data model forQuantumCircuit
. This currently includes Standard gates,Measure
,Delay
,Reset
,Barrier
, andUnitaryGate
. The capabilities of the circuits C API will expand in future release as more of the Qiskit data model is added natively to the internal Rust data model enabling it to be used in the C API.For example, you can use the C API to build a 1000 qubit GHZ state:
#include <qiskit.h> int main() { // Create an empty circuit with 1000 qubits and 1000 clbits QkCircuit *qc = qk_circuit_new(1000, 1000); // Add a Hadamard Gate on Qubit 0 uint32_t one_qubit[1] = {0,}; qk_circuit_gate(qc, QkGate_H, one_qubit, NULL); // The NULL pointer is for the parameter array. // Since Hadamard doesn't have parameters it // is never accessed. // Add the CX Gates: uint32_t qubits[2] = {0, 0}; uint32_t num_qubits = qk_circuit_num_qubits(qc); for (int i = 1; i<num_qubits; i++) { qubits[1] = i; qk_circuit_gate(qc, QkGate_CX, qubits, NULL); } // Add the measurements: uint32_t num_clbits = qk_circuit_num_clbits(qc); for (uint32_t i = 0; i<num_clbits; i++) { qk_circuit_measure(qc, i, i); } qk_circuit_free(qc); return 0; }
-
The Qiskit C API (qiskit.h) now supports building a
Target
to represent a transpilation target. For example:#include <qiskit.h> #include <math.h> int main() { // Create a Target with 3 qubits QkTarget *target = qk_target_new(3); // Create a Target Entry for a CX Gate QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX); // Define properties for CX between qubits (0, 1) with a duration of 1.93e-9 sec and error rate 3.17e-10. uint32_t qargs[2] = {0, 1}; qk_target_entry_add_property(cx_entry, qargs, 2, 1.93e-9, 3.17e-10); // Define properties for cx between qubits (1, 0) with a duration of 1.27e-9 sec and no error. uint32_t rev_qargs[2] = {1, 2}; qk_target_entry_add_property(cx_entry, rev_qargs, 2, 1.27e-9, NAN); // Add the cx entry to the target. QkExitCode result_cx = qk_target_add_instruction(target, cx_entry); // Add global ideal Y gate entry to the target QkExitCode result_y = qk_target_add_instruction(target, qk_target_entry_new(QkGate_Y)); // Create a Target entry for a Measurement with increasing duration and error as the qubit indices increase QkTargetEntry *measure = qk_target_entry_new_measure(); for (uint32_t i = 0; i< qk_target_num_qubits(target); i++) { uint32_t q[1] = {i}; qk_target_entry_add_property(measure, q, 1, 1e-6 * (i + 1), 1e-3 * (i + 1)); } QkExitCode result_measure = qk_target_add_instruction(target, measure); return 0; }
-
Added support for querying the Qiskit version information from the C API by using the following macros:
QISKIT_VERSION_MAJOR
: Contains the major version number.QISKIT_VERSION_MINOR
: Contains the minor version number.QISKIT_VERSION_PATCH
: Contains the patch version number.QISKIT_VERSION
: Contains a numeric representation of the version information, which can be used for comparisons.QISKIT_VERSION_NUMERIC(M,m,p)
: A function-like macro that returns the version “M.m.p” as a numeric value, which can be used for comparisons.
For example, to check if the current version is at least 2.1.0, you can use:
if (QISKIT_VERSION >= QISKIT_VERSION_NUMERIC(2, 1, 0)) { // Code for version 2.1.0 or later }
-
Enabled C++ compatibility for the C API. The generated header now allows calling objects and functions from C++ directly. For example, a 100-qubit observable with the term XYZ on the first 3 qubits can be constructed as
#include <iostream> #include <complex> #include <vector> #include <qiskit.h> int main() { uint32_t num_qubits = 100; // Use smart pointer with custom deleter to manage QkObs memory QkObs *obs = qk_obs_zero(num_qubits); // Construct the observable term std::complex<double> coeff_complex = 2.0; QkComplex64 coeff = qk_complex64_from_native(coeff_complex); std::vector<QkBitTerm> bit_terms = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z}; std::vector<uint32_t> indices = {0, 1, 2}; QkObsTerm term { .coeff = coeff, .len = bit_terms.size(), .bit_terms = bit_terms.data(), .indices = indices.data(), .num_qubits = num_qubits }; qk_obs_add_term(obs.get(), &term); // Print observable properties std::cout << "num_qubits: " << qk_obs_num_qubits(obs) << "\n"; std::cout << "num_terms: " << qk_obs_num_terms(obs) << "\n"; qk_obs_free(obs); return 0; }
Circuits Features
-
Added a function
random_circuit_from_graph()
that generates a random circuit that induces the same interaction graph as the one specified by interaction_graph.The probability of randomly drawing an edge from the interaction graph as a two-qubit gate can be set by the user in an edge’s weight attribute in the input interaction graph. If the user does not set the probability, each edge is drawn uniformly. That is, each two-qubit gate represented by an edge in the interaction graph has the same probability of getting added to the random circuit. If only a subset of edge probabilities are set,
ValueError
will be raised.In this example,
cp_map
is a list of edges with arbitrary weights.from qiskit.circuit.random.utils import random_circuit_from_graph import rustworkx as rx pydi_graph = rx.PyDiGraph() n_q = 5 cp_map = [(0, 1, 0.18), (1, 2, 0.15), (2, 3, 0.15), (3, 4, 0.22)] pydi_graph.extend_from_weighted_edge_list(cp_map) # cp_map can be passed in directly as interaction_graph qc = random_circuit_from_graph(interaction_graph = pydi_graph, min_2q_gate_per_edge = 1, max_operands = 2, measure = True, conditional = True, reset = True, seed = 0, insert_1q_oper = True, prob_conditional = 0.21, prob_reset = 0.1) qc.draw(output='mpl')
-
Added a new
QuantumCircuit
method:QuantumCircuit.has_control_flow_op()
to check if aQuantumCircuit
object contains any control flow operations. -
A new module
qiskit.circuit.annotation
and principle objectAnnotation
have been added.Annotations are a way of tagging instructions (currently only
BoxOp
) with local, user-custom data. This data is intended to be consumed by custom transpiler passes. Annotations provide a way to attach data to specific instructions, rather than using the global-context objectPropertySet
during compilation.All
Annotation
objects have anamespace
field. This string key is used for lookups, so consumers can tell if they handle a particular annotation or not. There are currently no methods for querying any abstract semantics of anAnnotation
subclass, but these are expected to expand in the future.See
qiskit.circuit.annotation
for a full discussion of the capabilities and use cases. -
BoxOp
instances (created byQuantumCircuit.box()
) can now be annotated with customAnnotation
instances. The equality of two boxes depends on the annotations being equal.Typically, this is achieved by passing a list of annotations as the sole positional argument when using
QuantumCircuit.box()
in context-manager form:from qiskit.circuit import annotation, QuantumCircuit class MyAnnotation(annotation.Annotation): namespace = "my.annotation" def __eq__(self, other): return isinstance(other, MyAnnotation) qc = QuantumCircuit() with qc.box([MyAnnotation()]): pass
-
The
UnitaryGate.control()
method now internally usesqs_decomposition()
instead ofIsometry
for the decomposition used to define the controlledUnitaryGate
. This change reduces the number ofCXGate
used in thedefinition
for the returnedControlledGate
by approximately 2x. -
Improved the synthesis of a multi-controlled
U1Gate
, so that it will not grow exponentially with the number of controls. -
The
BoxOp.duration
attribute can now be anexpr.Expr
node with typeDuration
, just likeDelay.duration
. This includes also supportingStretch
for the duration of aBoxOp
.
Primitives Features
-
Estimator PUBs used as the input to
BaseEstimatorV2.run()
can now be defined usingSparseObservable
objects for the observable component of the PUB. This is in addition to the existing supported types of:str
,Pauli
,SparsePauliOp
, and a mapping ofstr
or :class`~qiskit.quantum_info.Pauli` tofloat
values. However, if theSparseObservable
contains projectors, support for handling that depends on the primitive implementation. As of this release the implementations in Qiskit (StatevectorEstimator
andBackendEstimatorV2
),qiskit-ibm-runtime
(qiskit_ibm_runtime.EstimatorV2
), and Qiskit Aer’s (qiskit_aer.primitives.EstimatorV2
) primitive implementations do no support projective observables yet. Projective observables are those that contain the terms:0
,1
,+
,-
,r
, orl
. -
Add support to the
DataBin
class to make it serializable withpickle
. This enables making thePrimitiveJob
andPrimitiveResult
class serializable withpickle
as well.
OpenQASM Features
-
qasm3.dump()
andqasm3.dumps()
have a newannotation_handlers
argument, which is used to provide instances ofannotation.OpenQASM3Serializer
to the OpenQASM 3 export process, which can serialize customAnnotation
objects to OpenQASM 3. -
When
qiskit_qasm3_import>=0.6.0
is installed,qasm3.load()
andqasm3.loads()
have a newannotation_handlers
argument, which is used to provide instances ofannotation.OpenQASM3Serializer
to the OpenQASM 3 import process, which can deserialize customAnnotation
objects from OpenQASM 3. This support is currently limited tobox
statements, as this is the only place Qiskit can represent annotations in its data model.
QPY Features
-
Added a new QPY format version 15 which includes support for the new
Annotation
objects, with support from external serializers and deserializers. The format allows such serializers to be stateful, and safe places in the binary format are allocated for the custom state objects and custom annotation representations. -
qpy.dump()
andqpy.load()
now have an optionalannotation_factories
argument, which is used to provide constructor functions ofannotation.QPYSerializer
objects to handleAnnotation
subclasses. These must be supplied by the user, similar tometadata_serializer
, as in general, Qiskit cannot know about all possible externally-definedAnnotation
objects. -
Added a new function
get_qpy_version()
to theqpy
module. This function will inspect a QPY file and retrieve the QPY format version used in the payload. The version is returned as an integer, which can be used for logging or debugging purposes. see #14201.
Quantum Information Features
-
A new class,
PauliLindbladMap
, is added, which is a Pauli-based parametrization of a subset of linear maps of multi-qubit operators, used in noise-learning applications. This class is expected to form the backbone of enhanced noise-learning algorithms, and provide better and more efficient control over noise models in future versions of Qiskit. -
Introduced the
QubitSparsePauli
andQubitSparsePauliList
classes, which represent the same concepts asPauli
andPauliList
respectively, but only store non-identity terms, in a manner analogous toSparseObservable
. These classes are primarily intended to be used with the newPauliLindbladMap
.
Synthesis Features
-
Added a new synthesis algorithm for
HalfAdderGate
that requires no ancillary qubits and has better CX count compared toadder_qft_d00()
: -
Added new decompositions for
MCXGate
utilizing clean ancillae, improving circuit depth and efficiency:synth_mcx_1_clean_kg24()
, using 1 additional clean ancilla qubitsynth_mcx_1_dirty_kg24()
, using 1 additional dirty ancilla qubitsynth_mcx_2_clean_kg24()
, using 2 additional clean ancillary qubitssynth_mcx_2_dirty_kg24()
, using 2 additional dirty ancillary qubits
Example usage:
from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_kg24 n_ctrls = 10 qc = synth_mcx_1_clean_kg24(n_ctrls) qc.draw()
-
The synthesis of multi-controlled
CZGate
gates has been improved to reduce the synthesized gate count, in some cases as much as 99% reduction is possible. This was accomplished by leveraging the improved synthesis around theMCXGate
with controlledCZGate
synthesis too. -
Improved the default plugin for synthesizing
AnnotatedOperation
objects. The improvement is especially useful when creating and transpiling controlled circuits with controlled gates within them. For example:from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import CXGate from qiskit.compiler import transpile inner = QuantumCircuit(5) inner.append(CXGate().control(3, annotated=True), [0, 1, 2, 3, 4]) controlled_inner_gate = inner.to_gate().control(2, annotated=True) qc = QuantumCircuit(15) qc.append(controlled_inner_gate, [0, 1, 2, 3, 4, 5, 6]) qct = transpile(qc, basis_gates=["cx", "u"])
This code creates a quantum circuit
qc
that contains a 2-controlled quantum circuit with a 3-controlled CX-gate within it. With the improvement, the number of CX-gates in the transpiled circuit is reduced from378
to30
. Note that by specifyingannotated=True
when defining control logic, the controlled gates are created as annotated operations. This avoids eager synthesis, allows the transpiler to detect thatcontrolled_inner_gate
is equivalent to a 6-controlled X-gate, and to choose the best synthesis method available for multi-controlled X-gates, in particular utilizing available ancilla qubits. -
The function
adder_qft_d00()
, used for synthesizingModularAdderGate
andHalfAdderGate
gates, now accepts an additional parameterannotated
. IfTrue
, the inverse-QFT-gate within the adders is implemented as an annotated operations, allowing the transpiler to apply additional optimizations. -
The Quantum Shannon Decomposition (
qs_decomposition()
) now includes an optimization that reduces theCXGate
count in the case that the input unitary is a controlled unitary. -
The synthesis function
synth_mcx_1_clean_b95()
now produces a circuit with fewer CX-gates. -
The
SolovayKitaevDecomposition
class now has additional arguments in the initializer, which allow it to be directly constructed from a set ofbasis_gates
and adepth
for the basic approximations. -
Added
SolovayKitaevDecomposition.save_basic_approximations()
to save the set of basic approximations the class uses into a binary format. This change, in combination with the new initializer arguments, allows users to skip the explicit use ofgenerate_basic_approximations()
and only rely onSolovayKitaevDecomposition
.
Transpiler Features
-
The function
generate_preset_pass_manager()
now generates a special pass manager when the basis set consists only of Clifford+T gates. Formally, a Clifford+T basis set must contain only Clifford gates, along with eitherTGate
,TdgGate
, or both. The full list of supported Clifford gates can be obtained by usingget_clifford_gate_names()
.For example:
from qiskit.circuit import QuantumCircuit from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.quantum_info import get_clifford_gate_names basis_gates = get_clifford_gate_names() + ["t", "tdg"] pm = generate_preset_pass_manager(basis_gates=basis_gates) qc = QuantumCircuit(1) qc.rx(0.8, 0) qct = pm.run(qc) print(qct.count_ops())
Would result with:
OrderedDict([('h', 10210), ('t', 4508), ('tdg', 4503), ('sdg', 943), ('s', 941)])
Previously, the generated pass manager could not handle the example above because it couldn’t decompose single-qubit
UGate
rotation gates into Clifford+T gates. However, the new pass manager uses the Solovay-Kitaev decomposition to approximate single-qubit rotation gates usingH
,T
andTdg
gates, and calls theBasisTranslator
transpiler pass to further translate the gates into the target basis set. The new pass manager also has other changes as to enable a more efficient translation into Clifford+T gates.It is important to note that the specified Clifford+T basis gate set should be universal, or else transpilation might not succeed. While the gate set
["h", "t", "tdg"]
or even["h", "t"]
is sufficient for universality, it is recommended to add more Clifford gates to the set if possible, as otherwise the translation might be less efficient. For example, if S-gate is not included, S-gates might be decomposed into pairs of T-gates (that is, Clifford gates might be decomposed into non-Clifford gates, which might not be the desired behavior).Following is a slightly larger example:
from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import QFTGate from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager qc = QuantumCircuit(4) qc.append(QFTGate(4), [0, 1, 2, 3]) basis_gates = ["cx", "s", "sdg", "h", "t", "tdg"] pm = generate_preset_pass_manager(basis_gates=basis_gates, optimization_level=2) qc = QuantumCircuit(4) qc.append(QFTGate(4), [0, 1, 2, 3]) qct = pm.run(qc) print(qct.count_ops())
Would result with:
OrderedDict([('h', 96510), ('tdg', 42396), ('t', 42389), ('s', 8240), ('sdg', 8235), ('cx', 12)])
-
Added a new high-level-synthesis plugin
HalfAdderSynthesisR25
for synthesizing aHalfAdderGate
. The new plugin is based onadder_ripple_r25()
.The
HalfAdderSynthesisDefault
has also been updated to follow the following sequence of half adder synthesizers:"HalfAdder.ripple_r25"
when there are qubits,"HalfAdder.ripple_c04"
when one ancillary qubit is available, and"HalfAdder.ripple_r25"
in all remaining cases. -
Added multiple high-level-synthesis plugins for synthesizing an
MCXGate
:MCXSynthesis1CleanKG24
, based onsynth_mcx_1_clean_kg24()
.MCXSynthesis1DirtyKG24
, based onsynth_mcx_1_dirty_kg24()
.MCXSynthesis2CleanKG24
, based onsynth_mcx_2_clean_kg24()
.MCXSynthesis2DirtyKG24
, based onsynth_mcx_2_dirty_kg24()
.
The
MCXSynthesisDefault
class has also been updated to run the following sequence of MCX synthesize methods until the first one succeeds: :"mcx.2_clean_kg24"
,"mcx.1_clean_kg24"
,"mcx.n_clean_m15"
,"mcx.n_dirty_i15"`, ``"mcx.2_dirty_kg24"
,"mcx.1_dirty_kg24"
,"mcx.1_clean_b95"
,"mcx.noaux_v24"
. The methods are ordered in a way that the better-quality ones are applied first. -
VF2PostLayout
has been added at the end of the default optimization stage when using optimization level 3. -
Added a new
OptimizeCliffordT
transpiler optimization pass that merges pairs of consecutive T-gates into S-gates and pairs of consecutive Tdg-gates into Sdg-gates. This optimization is particularly effective for reducing T-count following Solovay-Kitaev decomposition, which produces multiple consecutive T or Tdg gates. For example:from qiskit.circuit import QuantumCircuit from qiskit.transpiler.passes import SolovayKitaev, OptimizeCliffordT qc = QuantumCircuit(1) qc.rx(0.8, 0) # Run Solovay-Kitaev pass on qc transpiled = SolovayKitaev()(qc) print(transpiled.count_ops().get("t", 0) + transpiled.count_ops().get("tdg", 0)) # Should print 12779 # Run Clifford+T optimization optimized = OptimizeCliffordT()(transpiled) print(optimized.count_ops().get("t", 0) + optimized.count_ops().get("tdg", 0)) # Should print 9011
-
Added the
ContextAwareDynamicalDecoupling
pass, which implements a context-aware dynamical decoupling based on Walsh-Hadamard sequences. The inserted delay sequences will be mutually orthogonal to sequences on neighboring qubits, and take into account control/target spectators of CX and ECR gates. See arXiv:2403.06852 for more information.Example:
from qiskit.circuit.library import QFT from qiskit.transpiler import PassManager, CouplingMap from qiskit.transpiler.passes import ALAPScheduleAnalysis, ContextAwareDynamicalDecoupling from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.providers.fake_provider import GenericBackendV2 num_qubits = 10 circuit = QFT(num_qubits) circuit.measure_all() target = GenericBackendV2( 100, basis_gates=["id", "rz", "sx", "x", "ecr"], coupling_map=CouplingMap.from_grid(10, 10) ).target pm = generate_preset_pass_manager(optimization_level=2, target=target) dd = PassManager([ ALAPScheduleAnalysis(target=target), ContextAwareDynamicalDecoupling(target=target), ]) transpiled = pm.run(circuit) with_dd = dd.run(transpiled) with_dd.draw("mpl", idle_wires=False)
-
Added the following attributes to the
DAGCircuit
class to enable querying the number of stretch variables:num_stretches
,num_captured_stretches
andnum_declared_stretches
. -
Added a new unitary synthesis plugin
CliffordUnitarySynthesis
that attempts to synthesize a given unitary gate by checking if it can be represented by a Clifford, in which case it returns a circuit that implements this unitary and consists only of Clifford gates.The plugin is invoked by the
UnitarySynthesis
transpiler pass when the parametermethod
is set to"clifford"
.In addition, the parameter
plugin_config
ofUnitarySynthesis
can be used to pass the following plugin-specific parameters:- min_qubits: the minimum number of qubits to consider (the default value is 1).
- max_qubits: the maximum number of qubits to consider (the default value is 3).
For example:
import math from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import UnitaryGate from qiskit.quantum_info import Operator from qiskit.transpiler.passes import UnitarySynthesis # clifford unitary over 2 qubits c2 = QuantumCircuit(2) c2.h(0) c2.rz(math.pi / 4, 1) c2.rz(math.pi / 4, 1) c2.sdg(1) uc2 = UnitaryGate(Operator(c2).data) # non-clifford unitary over 2 qubits n2 = QuantumCircuit(2) n2.h(0) n2.rz(math.pi / 4, 1) n2.sdg(1) un2 = UnitaryGate(Operator(n2).data) # quantum circuit with two unitary gates qc = QuantumCircuit(3) qc.append(uc2, [2, 1]) qc.append(un2, [0, 2]) transpiled = UnitarySynthesis(method="clifford")(qc) transpiled.draw("mpl")
Executing the code above re-synthesizes the first unitary gate into Clifford gates, while the second gate remains unchanged.
If we modify the example above as follows:
config = {"min_qubits": 3} transpiled = UnitarySynthesis(method="clifford", plugin_config=config)(qc)
then both unitary gates remain unchanged.
Visualization Features
-
Introduced custom styles for the
dag_drawer()
function. This allows you to pass a dictionary to thestyle
parameter with custom attributes that changes the style of the DAG which the function returns. For example:from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.converters import circuit_to_dag from qiskit.visualization import dag_drawer q = QuantumRegister(3, 'q') c = ClassicalRegister(3, 'c') circ = QuantumCircuit(q, c) circ.h(q[0]) circ.cx(q[0], q[1]) circ.measure(q[0], c[0]) circ.rz(0.5, q[1]).c_if(c, 2) dag = circuit_to_dag(circ) style = { "inputnodecolor": "pink", "outputnodecolor": "lightblue", "opnodecolor": "red", } dag_drawer(dag, style=style)
Upgrade Notes
-
The
python-dateutil
library is no longer a dependency of Qiskit. Since Qiskit v2.0 nothing in the library was using thepython-dateutil
and Qiskit didn’t actually depend on the library anymore. This release removes it from the dependency list so it is not automatically installed as a prerequisite to use Qiskit. If you were relying on Qiskit to install dateutil for you as a dependency you will now need to ensure you’re manually installing it (which is best practice for direct dependencies). -
sympy
is no longer a requirement for installing Qiskit. After the migration to a Rust based symbolic engine for theParameterExpression
class the uses of SymPy are isolated to some visualization utilities, theTemplateOptimization
transpiler pass,ParameterExpression.sympify()
(which is explicitly for SymPy interoperability) andSparsePauliOp.simplify()
if using parameterized coefficients. This functionality is not the most commonly used so SymPy is now treated as an optional dependency and those functions will raise aMissingOptionalLibraryError
exception if they’re used and SymPy is not installed. -
The dependency on
symengine
which was used for buildingParameterExpression
objects has been removed. It has been replaced by a internal symbolic engine and is no longer required for core functionality in Qiskit. The only exception is that symengine was embedded into QPY formats 10, 11, and 12 so it is still required if you are deserializing those formats. The dependency on symengine forqpy.load()
was made explicitly optional in 2.0.0, but if you were previously relying on symengine getting installed by default for this functionality you will now need to manually install it to load the payload. If you were usingParameterExpression.sympify()
to get a symengine expression object from aParameterExpression
that will now return asympy
expression. If you need to use this with symengine you can leveragesymengine.sympify
to convert thesympy
expression to a symengine one.
Circuits Upgrade Notes
-
The
definition
attribute of theHalfAdderGate
has been changed to internally useadder_ripple_r25()
to generate the definition of the gate for a more efficient circuit with no ancillary qubits. If the old definition is desired for some reason you can directly use theadder_qft_d00()
function instead which will generate a circuit equivalent to whatdefinition
would return in previous releases. -
The circuit returned by the
excitation_preserving()
function andExcitationPreserving
class now are built using a singleXXPlusYYGate
. This is a change from previous releases that used anRXXGate
followed by anRYYGate
. This new circuit construction is equivalent but uses fewer gates that are all excitation preserving by definition simpler. -
The
definition
atributes of several standard gates have been updated according to the following principles:- When available, a definition using Clifford gates is preferred over definitions that includes non-Clifford gates.
- When available, a definition using Clifford+T gates is preferred over one that uses a
UGate
. - The use of
PhaseGate
is preferred overU1Gate
. - The use of
UGate
is preferred over`U2Gate
and`U3Gate
.
Crucially, the following invariant still holds: by recursively expanding gate definitions, any gate can be ultimately expressed using only the
["cx", "u"]
basis. The definitions of all the standard gates are all equivalent so no semantics behind the gates change, just the exact circuit construction returned for some gates is no longer exactly the same. This change was necessary to support Clifford+T transpilation. -
Qiskit now uses its own, Rust-based symbolic expression library to implement the internals of
ParameterExpression
andParameter
. As this is a new implementation of the core symbolic math engine used forParameterExpression
there might be minor differences in the exact behavior of some functionality. It should always produce equivalent results for the documented API. Please open an issue if there are any problems with correctness found.
C API Upgrade Notes
-
The way complex numbers are exposed in Qiskit’s C API has changed. Previously,
QkComplex64
was a compiler-dependent typedef that allowed to pass native complex types by pointer to Qiskit’s API (for example asdouble complex*
). While this was convenient, this approach implictly relied on memory-layout assumptions that are not strictly guaranteed.Qiskit v2.1 now exposes
QkComplex64 { double re; double im; }
as a struct, to ensure the memory layout is always compatible and for broader compiler support. For convenience, compiler-dependent convertersqk_complex64_from_native
andqk_complex64_to_native
are provided, which allows translating from the struct to a native complex number. Note that these only work on platforms supportingdouble complex
or for MSVC compilers using_Dcomplex
.For example:
#include <qiskit.h> #include <math.h> #include <stdio.h> #include <complex.h> int main(int argc, char *argv[]) { // platform-independent constructions: QkComplex64 coeff = {5.0, 3.0}; // ... or using converter // double complex native = 5.0 + I * 3; // uses C11 standard, does not work on MSVC // QkComplex64 coeff = qk_complex64_from_native(&native); // convert from native uint32_t num_qubits = 100; QkObs *obs = qk_obs_zero(num_qubits); QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z}; uint32_t indices[3] = {0, 1, 2}; QkObsTerm term = {coeff, 3, bit_terms, indices, num_qubits}; qk_obs_add_term(obs, &term); printf("num_qubits: %u\n", qk_obs_num_qubits(obs)); printf("num_terms: %lu\n", qk_obs_num_terms(obs)); qk_obs_free(obs); return 0; }
QPY Upgrade Notes
- The default QPY version emitted by
qpy.dump()
has been changed to the latest QPY version 15. If you need to generate an older format version for some reason you can use theversion
keyword argument onqpy.dump()
to specify an older version to generate.
Synthesis Upgrade Notes
-
The serialization format for basic approximations in the Solovay-Kitaev algorithms has been changed from
.npy
to another binary format, based on Rust’sserde
andbincode
. All routines loading basic approximations (such asgenerate_basic_approximations()
,SolovayKitaevDecomposition.load_basic_approximations()
or the initializer ofSolovayKitaev
) still support loading the legacy format. Any new file, however, will be stored in the new format. If you relied on the old format, downgrade Qiskit to <2.2 and store the required files. -
The default values for
SolovayKitaev
(and related classes) have increased todepth=12
andreps=5
. This is due to the underlying implementation now being in Rust, which allows us to increase the default precision, while still being significantly faster than the previous Python version.
Transpiler Upgrade Notes
-
The built-in layout plugins for the present pass managers will no longer contain their principal component (e.g. a
SabreLayout
instance for the “sabre” stage) if no coupling constraints are provided. Previously, the plugins would construct invalid instances of their layout passes, under an assumption that separate logic would prevent the passes from executing and raising exceptions.This should have no meaningful effect on the use of the preset pass managers or the plugins, since it was already never valid to call the passes in an invalid state .
Deprecation Notes
-
Support for running Qiskit with Python 3.9 has been deprecated and will be removed in the Qiskit v2.3 release. Version 2.3.0 is the first release after Python 3.9 goes end of life and is no longer supported [1]. This means that starting in the 2.3.0 release you will need to upgrade the Python version you’re using to Python 3.9 or above.
Circuits Deprecations
-
The circuit library underwent a refactoring in the Qiskit v1.3 release, in which alternatives for objects of type
QuantumCircuit
were provided that are either anInstruction
or a Python function for construction. This refactoring allows the compiler to reason about high-level instructions, and reduces the overhead for circuits that do not require high-level optimizations.All
QuantumCircuit
subclasses are now deprecated in favor of their alternatives introduced in Qiskit v1.3. As part of this, theBlueprintCircuit
base class is also deprecated. All have an extended deprecation period and will only be removed in Qiskit v3.0.The
BlueprintCircuit
class does not have a direct replacement, instead use aQuantumCircuit
directly or a function that generates circuits. Seeqiskit.circuit.library
for more details, but some common circuits and their replacements follow:QFT
→QFTGate
TwoLocal
→n_local()
(this is not a typo, this function covers theNLocal
andTwoLocal
functionality)EfficientSU2
→efficient_su2()
RealAmplitudes
→real_amplitudes()
ZZFeatureMap
→zz_feature_map()
QuantumVolume
→quantum_volume()
EvolvedOperatorAnsatz
→evolved_operator_ansatz()
MCXGrayCode
→synth_mcx_gray_code()
MCXRecursive
→synth_mcx_n_dirty_i15()
MCXVChain
→synth_mcx_n_clean_m15()
Bug Fixes
-
Fixed a bug in the
dag_drawer()
function and theDAGCircuit.draw()
method where setting the keyword argumentstyle=plain
did not show circuit labels for the nodes of the DAG in the visualization. -
Fixed a bug in the
QuantumCircuit.assign_parameters()
method where parameters that were not used in the circuit and were passed as strings were not ignored when the argumentstrict=False
was set. Refer to issue #13933 for more details. -
Fixed edge-cases in the
Makefile
configuration for Windows, where the pre-defined environment variableOS
did not match the output of theuname -s
command. -
Fixed the
name
attribute of theOrGate
, that was previously incorrectly set to the string"and"
instead of the expected value"or"
which is now returned. This incorrect value"and"
conflicted with theAndGate.name
and could have possibly led to several problems around using theOrgate
and differentiating it from anAndGate
. -
Fixed a bug in the
qpy.load()
function where it could fail to deserialize circuits whose parameters had been reassigned to parameters with the same names. Fixed #13720, #13720, and #13720. -
Fixed the
GenericBackendV2
to now includeBoxOp
as a supported instruction type in the generatedGenericBackendV2.target
when the keyword argumentcontrol_flow=True
is set in the constructor. -
When synthesizing an
MCXGate
gate with 3 controls, the synthesis functionsynth_mcx_n_dirty_i15()
used to require one auxiliary qubit, producing a circuit with 5 qubits (3 control, 1 target, and 1 auxiliary). However, the actual synthesis algorithm does not make use of this auxiliary qubit. This behavior is now fixed: the synthesized circuit is over 4 qubits (3 control and 1 target), allowing the synthesis function to be applied in a slightly larger number of cases. -
The
QuantumCircuit.draw()
andcircuit_drawer()
function will now renderBoxOp
instructions in aQuantumCircuit
in the same vertical slice if the vertical spans do not overlap are now rendered in the same vertical slice, when possible. -
Fixed the
QuantumCircuit.draw()
method andcircuit_drawer()
function in"mpl"
mode to insert less extraneous space inside the left edge when drawingBoxOp
instances in aQuantumCircuit
.
Other Notes
-
Added a new optional extra dependency target
qpy-compat
. This target should be used if you plan to loadqpy
files using older QPY formats. The target installs extra requirements used for loading QPY files using format versions < 13. If you are only using newer QPY format versions you do no need to install this. Theqpy.dump()
only generates QPY >=13 this is only needed for loading files generated with older (older than 2.0.0) Qiskit releases You can install this new optional variant withpip install qiskit[qpy-compat]
. -
The relative weights of the “basic” and “lookahead” components of the
SabreSwap
andSabreLayout
heuristics have been modified when extended-set tracking is active (as it always is inSabreLayout
, and is by default inSabreSwap
). The heuristic component relating to the distance between qubits in an individual gate in the front layer now no longer weakens proportional to the number of gates in the front layer; this behavior was a historical choice, but at large circuit sizes, has the accidental effect of causing the front layer to be nearly ignored, which is disastrous for efficiency.The resulting routing improvements should be most noticeable for circuits that can frequently be stratified into layers of more than 20 parallel two-qubit gates.