Qiskit SDK 2.1 release notes
2.1.2
Prelude
Qiskit 2.1.2 is a small patch release, fixing several bugs found in the 2.1 series.
Bug Fixes
-
In Qiskit 2.1.0 the
VF2PostLayouttranspiler pass was added to theoptimizationstage of the preset pass manager whenoptimization_level=3. However, the output of the pass was not ever applied to the circuit. This release removes the pass from the preset pass manager’soptimizationstage to save the runtime cost. In Qiskit 2.2.0 the pass will be part of theoptimizationstage and the output will be applied if the pass finds a better layout. -
Fixed an issue in the
VF2Layouttranspiler pass where even with a fixed seed set the output from the pass was potentially non-deterministic; specifically if the input circuit had any active qubits that only contained single qubit operations. Fixed #14729 -
Fixed a bug in
PassManager.run()where thecallbackfunction was not invoked when running with multiple circuits. The callback is now correctly triggered for each pass on each circuit, including in parallel execution. -
Built-in transpiler passes that set the
final_layoutproperty will now correctly handle updating this field if it was already set. This can be observed as the methodTranspileLayout.routing_permutationnow returning a correct permutation after running more than one pass that setsfinal_layout.This did not affect any normal calls to
transpile()orgenerate_preset_pass_manager()using Qiskit’s built-in plugins; no pipeline constructed in this form would attempt to setfinal_layoutmore than once. -
Fixed a bug in
Target.instruction_supported()where the check of instruction’s qubit order was skipped when the method was called withoperation_nameandparametersarguments that matched an existing instruction. -
Fixed a correctness bug when exporting circuits with delay instructions using
psunits to QASM3. -
Fixed an edge case in the display of the
QFTcircuit. Previously, whenQFT.inverse()was called and then the attributes of the QFT circuit were modified, the QFT’s name was displayed as"IQFT_dg". This was incorrect, and now it correctly shows"IQFT". Fixed #14758. -
Fixed a memory leak in the coordination of using
qk_circuit_get_instructionandqk_circuit_instruction_clear. -
Clarified the error message for invalid specified versions in
qpy.dump()to make it easier to act on. -
Fixed an issue with the
generate_preset_pass_manager()function where it would incorrectly ignore the timing constraints data contained in aTargetobject provided when thebackendargument was not set. Fixed #14329 -
Fixed a bug in
SparsePauliOp.simplify()where the method removed Pauli terms based on their coefficients’ magnitudes before combining duplicates. This caused incorrect behavior in two key edge cases:- When multiple identical Pauli terms had small coefficients that were individually below the simplification threshold but whose sum exceeded the threshold, those terms were incorrectly removed.
- When multiple identical Pauli terms had coefficients above the threshold but summed to near-zero, they were incorrectly kept.
See #14194 for more detail.
2.1.1
Prelude
Qiskit 2.1.1 is a small patch release, fixing several bugs found in the 2.1 series.
Bug Fixes
-
Fixed a bug in the
DAGOpNodeequality check, where comparing twoDAGOpNodeobjects that contain aBoxOpinstruction. Previously, theBoxOp.unitattribute was not considered as part of the equality check which could lead to a two unequal nodes be evaluated as equal. -
Fixed the deprecation warning for Python 3.9 so that it is correctly identified as being caused by user code when importing Qiskit. Previously, it would not be identified as being caused by user code, and this meant that Python’s default warning filters would not display the warning to the user.
-
Fixed the behavior of the
max_trialsargument forVF2Layoutwhen set toNoneor a negative number. The pass was documented as limiting the search to be based on the size of the circuit or target if the option was set toNone, and as accepting negative values to specify an unbounded search. However, in 2.1.0 this behavior was incorrectly changed so thatNoneran an unbounded search and using a negative number would raise an error. These oversights have been corrected so that the pass behaves as documented and is consistent with previous releases. -
Fixed a problem in high-level synthesis plugins
MCXSynthesis1DirtyKG24andMCXSynthesis2DirtyKG24forMCXGate, where the plugins did not consider available clean auxiliary qubits as available dirty auxiliary qubits. In particular, the pluginMCXSynthesis2DirtyKG24did not apply when one clean and one dirty auxiliary qubits were available. -
Fixed a performance regression when incrementally building
ParameterExpressionby combining a large number of sub-expressions. Fixed #14653
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
Targetobject, as preparation for supporting a complete C API-based transpilation workflow in the next release. In addition,QkComplex64is 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 toBoxOpinstructions, 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
QkCircuitopaque type that represents the circuit. TheQkCircuittype 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
Targetto 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,
ValueErrorwill be raised.In this example,
cp_mapis 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
QuantumCircuitmethod:QuantumCircuit.has_control_flow_op()to check if aQuantumCircuitobject contains any control flow operations. -
A new module
qiskit.circuit.annotationand principle objectAnnotationhave 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 objectPropertySetduring compilation.All
Annotationobjects have anamespacefield. 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 anAnnotationsubclass, but these are expected to expand in the future.See
qiskit.circuit.annotationfor a full discussion of the capabilities and use cases. -
BoxOpinstances (created byQuantumCircuit.box()) can now be annotated with customAnnotationinstances. 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 ofIsometryfor the decomposition used to define the controlledUnitaryGate. This change reduces the number ofCXGateused in thedefinitionfor the returnedControlledGateby approximately 2x. -
Improved the synthesis of a multi-controlled
U1Gate, so that it will not grow exponentially with the number of controls. -
The
BoxOp.durationattribute can now be anexpr.Exprnode with typeDuration, just likeDelay.duration. This includes also supportingStretchfor the duration of aBoxOp.
Primitives Features
-
Estimator PUBs used as the input to
BaseEstimatorV2.run()can now be defined usingSparseObservableobjects for the observable component of the PUB. This is in addition to the existing supported types of:str,Pauli,SparsePauliOp, and a mapping ofstrorPaulitofloatvalues. However, if theSparseObservablecontains projectors, support for handling that depends on the primitive implementation. As of this release the implementations in Qiskit (StatevectorEstimatorandBackendEstimatorV2),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
DataBinclass to make it serializable withpickle. This enables making thePrimitiveJobandPrimitiveResultclass serializable withpickleas well.
OpenQASM Features
-
qasm3.dump()andqasm3.dumps()have a newannotation_handlersargument, which is used to provide instances ofannotation.OpenQASM3Serializerto the OpenQASM 3 export process, which can serialize customAnnotationobjects to OpenQASM 3. -
When
qiskit_qasm3_import>=0.6.0is installed,qasm3.load()andqasm3.loads()have a newannotation_handlersargument, which is used to provide instances ofannotation.OpenQASM3Serializerto the OpenQASM 3 import process, which can deserialize customAnnotationobjects from OpenQASM 3. This support is currently limited toboxstatements, 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
Annotationobjects, 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_factoriesargument, which is used to provide constructor functions ofannotation.QPYSerializerobjects to handleAnnotationsubclasses. These must be supplied by the user, similar tometadata_serializer, as in general, Qiskit cannot know about all possible externally-definedAnnotationobjects. -
Added a new function
get_qpy_version()to theqpymodule. 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
QubitSparsePauliandQubitSparsePauliListclasses, which represent the same concepts asPauliandPauliListrespectively, 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
HalfAdderGatethat requires no ancillary qubits and has better CX count compared toadder_qft_d00(): -
Added new decompositions for
MCXGateutilizing 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
CZGategates 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 theMCXGatewith controlledCZGatesynthesis too. -
Improved the default plugin for synthesizing
AnnotatedOperationobjects. 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
qcthat 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 from378to30. Note that by specifyingannotated=Truewhen defining control logic, the controlled gates are created as annotated operations. This avoids eager synthesis, allows the transpiler to detect thatcontrolled_inner_gateis 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 synthesizingModularAdderGateandHalfAdderGategates, 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 theCXGatecount 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
SolovayKitaevDecompositionclass now has additional arguments in the initializer, which allow it to be directly constructed from a set ofbasis_gatesand adepthfor 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
UGaterotation gates into Clifford+T gates. However, the new pass manager uses the Solovay-Kitaev decomposition to approximate single-qubit rotation gates usingH,TandTdggates, and calls theBasisTranslatortranspiler 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
HalfAdderSynthesisR25for synthesizing aHalfAdderGate. The new plugin is based onadder_ripple_r25().The
HalfAdderSynthesisDefaulthas 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
MCXSynthesisDefaultclass 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. -
VF2PostLayouthas been added at the end of the default optimization stage when using optimization level 3. -
Added a new
OptimizeCliffordTtranspiler 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
ContextAwareDynamicalDecouplingpass, 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
DAGCircuitclass to enable querying the number of stretch variables:num_stretches,num_captured_stretchesandnum_declared_stretches. -
Added a new unitary synthesis plugin
CliffordUnitarySynthesisthat 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
UnitarySynthesistranspiler pass when the parametermethodis set to"clifford".In addition, the parameter
plugin_configofUnitarySynthesiscan 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 thestyleparameter 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-dateutillibrary is no longer a dependency of Qiskit. Since Qiskit v2.0 nothing in the library was using thepython-dateutiland 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). -
sympyis no longer a requirement for installing Qiskit. After the migration to a Rust based symbolic engine for theParameterExpressionclass the uses of SymPy are isolated to some visualization utilities, theTemplateOptimizationtranspiler 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 aMissingOptionalLibraryErrorexception if they’re used and SymPy is not installed. -
The dependency on
symenginewhich was used for buildingParameterExpressionobjects 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 aParameterExpressionthat will now return asympyexpression. If you need to use this with symengine you can leveragesymengine.sympifyto convert thesympyexpression to a symengine one.
Circuits Upgrade Notes
-
The
definitionattribute of theHalfAdderGatehas 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 whatdefinitionwould return in previous releases. -
The circuit returned by the
excitation_preserving()function andExcitationPreservingclass now are built using a singleXXPlusYYGate. This is a change from previous releases that used anRXXGatefollowed by anRYYGate. This new circuit construction is equivalent but uses fewer gates that are all excitation preserving by definition simpler. -
The
definitionatributes 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
PhaseGateis preferred overU1Gate. - The use of
UGateis preferred over`U2Gateand`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
ParameterExpressionandParameter. As this is a new implementation of the core symbolic math engine used forParameterExpressionthere 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,
QkComplex64was 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_nativeandqk_complex64_to_nativeare provided, which allows translating from the struct to a native complex number. Note that these only work on platforms supportingdouble complexor 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 theversionkeyword 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
.npyto another binary format, based on Rust’sserdeandbincode. 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=12andreps=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
SabreLayoutinstance 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
QuantumCircuitwere provided that are either anInstructionor 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
QuantumCircuitsubclasses are now deprecated in favor of their alternatives introduced in Qiskit v1.3. As part of this, theBlueprintCircuitbase class is also deprecated. All have an extended deprecation period and will only be removed in Qiskit v3.0.The
BlueprintCircuitclass does not have a direct replacement, instead use aQuantumCircuitdirectly or a function that generates circuits. Seeqiskit.circuit.libraryfor more details, but some common circuits and their replacements follow:QFT→QFTGateTwoLocal→n_local()(this is not a typo, this function covers theNLocalandTwoLocalfunctionality)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=plaindid 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=Falsewas set. Refer to issue #13933 for more details. -
Fixed edge-cases in the
Makefileconfiguration for Windows, where the pre-defined environment variableOSdid not match the output of theuname -scommand. -
Fixed the
nameattribute 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.nameand could have possibly led to several problems around using theOrgateand 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
GenericBackendV2to now includeBoxOpas a supported instruction type in the generatedGenericBackendV2.targetwhen the keyword argumentcontrol_flow=Trueis set in the constructor. -
When synthesizing an
MCXGategate 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 renderBoxOpinstructions in aQuantumCircuitin 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 drawingBoxOpinstances in aQuantumCircuit.
Other Notes
-
Added a new optional extra dependency target
qpy-compat. This target should be used if you plan to loadqpyfiles 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
SabreSwapandSabreLayoutheuristics 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.