Skip to main contentIBM 

Release News: Qiskit SDK v2.2 is here!

Technical release summary for Qiskit SDK v2.2, including updates on top new features, performance improvements, and our ongoing efforts to make Qiskit the world's most performant quantum SDK.

Today, we're excited to announce the release of Qiskit SDK v2.2! The latest minor release of the Qiskit v2.x series brings sizable performance improvements and some long-awaited capabilities that will enable seamless quantum-centric supercomputing workflows in high-performance computing (HPC) environments.

A major milestone for Qiskit’s C API

Without a doubt, the biggest update in Qiskit SDK v2.2 concerns Qiskit’s C API, which takes an important step toward building out our support for HPC environments with the introduction of a standalone transpiler function that is directly callable from C. With this capability in place, it is now possible to construct end-to-end quantum workflows that you can execute natively in C and other compiled languages integrated via the C API.

That's what quantum developers call “a really big deal.” We predict that many important algorithms will use quantum to accelerate classical HPC, and therefore require end-to-end quantum + HPC workflows constructed in the compiled languages that are most widely used in the HPC community. To highlight how important this is, we’ve built a new quantum + HPC workflow demo that walks you through the process of building out a complete implementation of SQD in C++, arguably the most popular programming language for HPC.

Read more about the new quantum + HPC workflow demo on the IBM Quantum® blog, or explore the demo on GitHub.

The new HPC workflow demo is a tremendous achievement, but it’s far from the only thing you’ll be getting from Qiskit v2.2. Read on to learn about the top features and performance improvements you’ll be getting when you update to the new release, and check our release notes for details on anything not covered here.

The TL;DR

Other major updates we’re going to cover in this article include:

  • New C API transpiler function: Below, we share additional details and code examples related to the new C API transpiler function, and highlight some of the new tools we’ve added in Qiskit v2.2 to support qubit layouts produced by the transpiler.
  • Optimizing the transpiler for fault tolerance: We’ve made some progress towards fault-tolerant compilation workflows with the new LitinskiTransformation pass for Pauli-based computation. This will not be relevant for most users in the near term, but we include a few details on this new capability below for those who are interested to learn more about our journey to fault-tolerant quantum computing.
  • Expanded Target model: Added support for a Target to specify angle bounds for instructions. This is primarily useful for transpiling against backends that support fractional gates. There is also a new WrapAngles pass that will apply angle constraints on gates in a Target.
  • Performance improvements: Circuit transpilation is now 10-20% faster on average!
  • Upgrade considerations: The "upgrade considerations” section at the end of this blog includes important information on Qiskit’s Rust version (upgrading from version 1.79 to version 1.85 in Qiskit v2.2), Python 3.9 end of life (will no longer be supported in Qiskit v2.3), support for Intel Macs (to be downgraded in Qiskit v2.3), and upcoming circuit library deprecations (classes to be removed in Qiskit v3.0).

Top new features & improvements

In this section we’ll cover some of the most important new features and enhancements included in this release. If you want to know more about these or other features that didn’t make it into this article, be sure to read the full release notes here.

Transpile circuits using Qiskit’s C API

Qiskit SDK v2.2 exposes a new qk_transpile() function for transpiling circuits via the C API. This is exciting news because circuit transpilation has been one of the final missing ingredients needed to enable the construction of complete, end-to-end quantum workloads built entirely in C and other compiled languages bound to the C API.

The new function mirrors the primary functionality of the preset pass managers in the Qiskit’s Python API for transpiling circuits constructed with the C API. This means it is possible to transpile a circuit in pure-C environments —no need for costly Python calls or interpreters. Read the quantum + HPC workflow demo blog to learn more.

The code example below shows how you can build and transpile a circuit with the new transpiler function, and then print the gate counts:

#include <math.h> #include <qiskit.h> #include <stdio.h> // Built a target for a linear connectivity with RZ, SX, X, CZ basis gate set. QkTarget *build_target(void) { const uint32_t n = 100; QkTarget *target = qk_target_new(n); qk_target_add_instruction(target, qk_target_entry_new(QkGate_SX)); qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); qk_target_add_instruction(target, qk_target_entry_new(QkGate_RZ)); QkTargetEntry *cx = qk_target_entry_new(QkGate_CZ); for (uint32_t i = 0; i < n - 1; i++) { uint32_t q[2] = {i, i + 1}; qk_target_entry_add_property(cx, q, 2, 4e-7, 1e-5); } qk_target_add_instruction(target, cx); return target; } /// Build a GHZ state. QkCircuit *build_circuit(void) { const uint32_t n = 10; QkCircuit *circuit = qk_circuit_new(n, 0); qk_circuit_gate(circuit, QkGate_H, (uint32_t[1]){0}, NULL); for (uint32_t i = 0; i < n - 1; i++) { uint32_t q[2] = {i + 1, 0}; qk_circuit_gate(circuit, QkGate_CX, q, NULL); } return circuit; } void print_op_counts(QkCircuit *circuit) { QkOpCounts counts = qk_circuit_count_ops(circuit); printf("Num qubits: %u\n", qk_circuit_num_qubits(circuit)); printf("Counts: %zu\n", counts.len); for (size_t i = 0; i < counts.len; i++) { printf("%s: %zu\n", counts.data[i].name, counts.data[i].count); } qk_opcounts_clear(&counts); } int main(void) { /// -- Get a target and a circuit QkTarget *target = build_target(); QkCircuit *circuit = build_circuit(); printf("Count before transpilation:\n"); print_op_counts(circuit); // - For result handling char *error = NULL; QkTranspileResult result = {NULL, NULL}; /// -- Transpile circuit with qk_transpile QkTranspileOptions options = qk_transpiler_default_options(); QkExitCode exit = qk_transpile(circuit, target, &options, &result, &error); // - Result handling if (exit != QkExitCode_Success) { printf("Transpilation failed\n"); } qk_circuit_free(circuit); qk_target_free(target); qk_transpile_layout_free(result.layout); if (error != NULL) { printf("%s", error); qk_str_free(error); qk_circuit_free(result.circuit); return 1; } printf("Count after transpilation:\n"); print_op_counts(result.circuit); qk_circuit_free(result.circuit); return 0; }
Count before transpilation: Num qubits: 10 Counts: 2 cx: 9 h: 1 Count after transpilation: Num qubits: 100 Counts: 4 sx: 31 rz: 29 cz: 16 x: 6

Accessing layout information using the C API

To support qubit layouts produced by the transpiler, we’ve also added a QkTranspileLayout object to store qubit mappings and permutations. Additionally, there is now a new qk_obs_apply_layout() function to apply qubit layouts to a QkObs observable. This function ingests the observable, the new qubit indices, and an output number of qubits. Importantly, this function allows applying transpile layouts, usually given as QkTranspileLayout by a transpiler pass, to an observable.

Below is a code snippet that shows all of these features in action:

#include <qiskit.h> #include <stdio.h> #include <stdlib.h> // These are reused from a previous example. QkTarget *build_target(void); QkCircuit *build_circuit(void); int main(void) { int ret = 0; // Get a target and build a circuit, using the same functions as in the // previous code example. QkTarget *target = build_target(); QkCircuit *circuit = build_circuit(); uint32_t num_target_qubits = qk_target_num_qubits(target); uint32_t num_circuit_qubits = qk_circuit_num_qubits(circuit); // Set up local storage for each term, to pass to the observable builder. QkObsTerm term; QkBitTerm x_term = QkBitTerm_X; term.coeff = (QkComplex64){1.0, 0.0}; term.len = 1; term.bit_terms = &x_term; term.num_qubits = num_circuit_qubits; // Create an observable that's the sum of all single-qubit X measurements. QkObs *obs = qk_obs_zero(num_circuit_qubits); for (uint32_t qubit = 0; qubit < num_circuit_qubits; qubit++) { term.indices = &qubit; qk_obs_add_term(obs, &term); } // Transpile the circuit, as before, but ignoring the error message. QkTranspileResult result = {NULL, NULL}; QkTranspileOptions options = qk_transpiler_default_options(); options.seed = 20251008; QkExitCode exit = qk_transpile(circuit, target, &options, &result, NULL); if (exit != QkExitCode_Success) { fprintf(stderr, "Transpilation failed unexpectedly.\n"); ret = 1; goto cleanup; } // Show the observable in terms of virtual qubits: char *obs_repr = qk_obs_str(obs); printf("Before layout: %s\n", obs_repr); qk_str_free(obs_repr); // Extract the final layout from the transpilation result. uint32_t *layout = malloc(num_circuit_qubits * sizeof(*layout)); qk_transpile_layout_final_layout(result.layout, true, layout); // ... and apply the compiled layout to the observable. qk_obs_apply_layout(obs, layout, num_target_qubits); // Show the observable after applying the transpilation layout: obs_repr = qk_obs_str(obs); printf("After layout: %s\n", obs_repr); qk_str_free(obs_repr); free(layout); cleanup: qk_transpile_layout_free(result.layout); qk_circuit_free(result.circuit); qk_circuit_free(circuit); qk_target_free(target); return ret; }
Before layout: SparseObservable { num_qubits: 10, coeffs: [<cut for clarity>], bit_terms: [X, X, X, X, X, X, X, X, X, X], indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], boundaries: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } After layout: SparseObservable { num_qubits: 100, coeffs: [<cut for clarity>], bit_terms: [X, X, X, X, X, X, X, X, X, X], indices: [7, 0, 1, 2, 3, 4, 5, 6, 9, 8], boundaries: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }

Note that qk_obs_str currently produces a debug-format output, which we’ve truncated for clarity in the above. This will be improved in a future version of Qiskit.

Litinski transformation for fault tolerance

The latest Qiskit SDK release adds a new transpilation pass, LitinskiTransformation, which implements the transform popularized by this 2019 Quantum paper. This pass is used in compilation for some fault-tolerant architectures and brings us closer to being able to support workloads for fault-tolerant quantum computing.

The input for the pass is a circuit with Clifford and single-qubit RZ-rotation gates, and the output is a circuit with multi-qubit Pauli rotations—implemented as PauliEvolutionGate gates—followed by Clifford gates. To bring your circuit into this format, you can transpile it using the Clifford gate names returned by get_clifford_gate_names and the supported RZ rotations.

Qiskit v2.2 also introduces new ancilla-free synthesis methods for Adders and MCX gates. These methods are more efficient for fault-tolerant architectures than previous methods, which were optimized for a continuous, near-term gate set. For example, the MCX is based on this 2024 paper.

The new methods are leveraged in the HighLevelSynthesis pass, which exposes a new optimization_metric keyword that allows us to target either the minimization of 2-qubit gate counts for near-term targets, or T count for fault-tolerant targets.

from qiskit import QuantumCircuit, transpile from qiskit.circuit.library import PhaseOracleGate, grover_operator from qiskit.quantum_info import get_clifford_gate_names from qiskit.transpiler.passes import LitinskiTransformation # Build an oracle for Grover operation clause = "(x1 ^ x2) | (x2 & x3)" oracle = PhaseOracleGate(clause) circuit = QuantumCircuit(oracle.num_qubits) circuit.append(oracle, circuit.qubits) # Construct the Grover operator grover = grover_operator(circuit) print("High-level:", grover.count_ops()) # Transpile to Clifford+T basis set basis = ["t", "tdg"] + get_clifford_gate_names() cliff = transpile(grover, basis_gates=basis) print("Cliff+T:", cliff.count_ops()) # Apply Litinski transformation, without # adding the final Cliffords trafo = LitinskiTransformation(fix_clifford=False) pbc = trafo(cliff) print("PBC:", pbc.count_ops())

Here's a sample of what the output might look like:

High-level: OrderedDict({'h': 8, 'x': 6, 'Phase Oracle': 1, 'ccx': 1}) Cliff+T: OrderedDict({'cx': 12, 'tdg': 7, 't': 7, 'x': 6, 'h': 6, 'cz': 2, 'z': 2, 's': 2, 'sdg': 1}) PBC: OrderedDict({'PauliEvolution': 14})

Expanded Target Model

A newly expanded Target model enables backends to describe parameter constraints with much finer granularity than was previously possible. Most notably, a Target now supports bounds on gate parameters—meaning, for example, that it can restrict RZ(θ) to an interval like [0, π]. This enables Qiskit to represent hardware constraints that exist on real systems, such as fractional gates available on IBM Quantum Heron systems, which only support RZZ gates with angles between 0 and π/2, or AQT’s IBEX Q1 which supports RXX between 0 and π/4 and R with theta constraints between between 0 and π/2 and phi between 0 and 2π.

The new WrapAngles transpiler pass enforces these bounds by automatically rewriting out‑of‑range angles into equivalent, hardware‑valid sequences. You can also ask the Target to validate a proposed operation with instruction_supported(..., check_angle_bounds=True).

The updated Target model also introduces valuable quality-of-life improvements with a seconds_to_dt() function, which converts physical seconds into device time steps for clearer device time handling. This makes it easier to combine calibration‑informed durations with scheduling passes. Together, these changes will help providers describe their hardware more faithfully, and enable users to compile circuits that are correct‑by‑construction for a given device’s instruction set and timing grid.

Several methods are available for working with the angle bounds on the Target. The first is Target.add_instruction() which has a new angle_bounds keyword argument that you’ll use to add an angle bound to an instruction in the Target. To work with angle bounds, you will also want to register a callback function to the global WRAP_ANGLE_REGISTRY registry, which will tell the transpiler and WrapAngles pass how to adjust gates for angle bounds. This preferred path will change in the next minor release, but we plan to maintain the current path for backwards compatibility.

The callback function will take a list of arbitrary float values representing the gate angles from the circuit, as well as the qubit indices the gate was operating on in the circuit. It will return a DAGCircuit representing an equivalent circuit for the gate with that angle while still respecting the angle bounds and other Target constraints.

Here’s an example of how you might define this callback function and register it to the WRAP_ANGLE_REGISTRY:

import math from qiskit.circuit import Parameter, Qubit from qiskit.circuit.library import RZGate from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler import Target from qiskit.transpiler.passes.utils.wrap_angles import WRAP_ANGLE_REGISTRY target = Target(num_qubits=1) target.add_instruction(RZGate(Parameter("theta")), angle_bounds=[(-math.pi, math.pi)]) def callback(angles: list[float], qubits: list[int]) -> DAGCircuit: """Callback function to wrap RZ gate angles Args: angles: The list of floating point parameter values for the instance of RZGate in the circuit qubits: The physical qubit indices that this gate is operating on Returns: The DAGCircuit of the equivalent circuit""" angle = angles[0] dag = DAGCircuit() dag.add_qubits([Qubit()]) if angle > 0: divisor = math.pi else: divisor = -math.pi gate_counts = int(angles[0] // divisor) rem = angles[0] % divisor for _ in range(gate_counts): dag.apply_operation_back(RZGate(math.pi), [dag.qubits[0]], check=True) dag.apply_operation_back(RZGate(rem), [dag.qubits[0]], check=True) return dag WRAP_ANGLE_REGISTRY.add_wrapper("rz", callback)

Performance improvements in circuit transpilation

Qiskit v2.2 brings measurable performance improvement to the transpiler. Using the Benchpress transpiler benchmarks, which run Qiskit’s default transpiler across a variety of circuits for different targets, we observe a consistent 10-20% speedup for transpilation tasks on average across the entire benchmark suite.

This performance gain is similar across multiple runs of Benchpress and on multiple systems, confirming a sustained improvement in transpiler efficiency. While the precise contributing factors are not obvious, early indications suggest that, as more code in Qiskit is converted to Rust, the Rust compiler finds more optimization opportunities, contributing to the observed speedup.

2.2-transpile-benchmarks.png

Upgrade considerations

In this section, we’ll cover some important considerations to have in mind when upgrading to Qiskit v2.2. Many of these are related to changes in the various platforms that Qiskit supports. Others center on upcoming deprecations in Qiskit itself. Note that all deprecations raise warnings and may result in code breakage when updating to the next major release pursuant to Qiskit’s deprecation policy, which you can find documented in full here.

For guidance on how to deal with deprecation warnings, check out the deprecations section in our previous article here. You can see more detail about the deprecations specific to this release here.

Rust version increase

If you are a Qiskit user who builds from source, please note that the current v2.2 release updates Qiskit’s minimum supported Rust version from Rust 1.79 to Rust 1.85. This change was necessary to enable the transition to a newer version of one of our Rust linear algebra libraries. Previous versions and other libraries exhibited problems with build or numerical precision on some relevant platforms.

Python 3.9 end of life

As a reminder, Python 3.9 has reached end of life and Qiskit SDK v2.2 will be the final Qiskit release to include support for it. Starting with the v2.3 release in December, you will need to upgrade your Python version to Python 3.10 or above to continue using the latest version of Qiskit.

Downgrading support for Intel Macs

If you use Qiskit on a machine that runs macOS on Intel processors (x86-64), please note that we will be forced to downgrade the support tier for your device starting with Qiskit v2.3. This is due to the manufacturer’s withdrawal of support for Intel Macs, which in turn has driven the deprecation of Intel Mac CI runners on GitHub and Azure.

Circuit library deprecations

The following classes in the circuit library are deprecated as of Qiskit v2.2 and will be removed in Qiskit 3.0. They have been replaced with modern gate equivalents:

  • ExactReciprocalExactReciprocalGate
  • LinearAmplitudeFunctionLinearAmplitudeFunctionGate
  • PhaseOraclePhaseOracleGate
  • PiecewisePolynomialPauliRotationsPiecewisePolynomialPauliRotationsGate
  • QuantumVolumequantum_volume()

To understand why we’re making these changes, it’s important to know that there are two categories of object in the circuit library. One category comprises objects that represent a single logical operation, such as an arithmetic gate that performs some mathematical operation on quantum data. The other category is made up of circuits with particular structure, such as a variational ansatz or a quantum volume circuit.

Before Qiskit v1.3, many objects of both types were represented by subclasses of QuantumCircuit. Qiskit v1.3 introduced new Gate subclasses for several logical operations, enabling the compiler to make better decisions about synthesis. It also introduced new Rust-accelerated circuit-constructor functions for many structured circuits, which were faster and demonstrated less surprising behavior when used directly in Primitives applications.

Qiskit v2.1 deprecated most of the inefficient old objects, marking them for removal in Qiskit v3.0. The above-mentioned deprecations were deferred, and are now added in Qiskit v2.2.

Qiskit v1.4 support

Qiskit SDK v1.4 bug fixing support is ending soon, with the release of v1.4.5. For six months after the release of Qiskit v2.0 on March 30th, the Qiskit team has been providing bug fixing support through successive 1.4.x patch releases. This support will conclude with the release of v1.4.5. If you are still using the 1.x series, we strongly recommend considering an upgrade to v2.x. Although the v1.4 series will continue to receive minimum security support until March 2026 (one year after the release of v2.0), regular bug fixes will no longer be provided.

Final notes

And there you have it! The most important details of the latest Qiskit SDK release. Remember, if you want to put ideas forward for future versions of Qiskit you can always open a GitHub issue to request a feature or report a bug! And if you want to follow what's coming up in the next release you can take a look at the Qiskit Roadmap.

Many people contributed to this release, special thanks to (in alphabetical order):

Aidan Wagner, Alexander Ivrii, Aqil Ahmad, Archit Chadalawada, Arnau Casau, Asadullah Bin Rahman, Daniel Puzzuoli, Edwin Navarro, Elena Peña Tapia, Eli Arbel, Eric Arellano, Erik Chagas Rozal, Gadi Aleksandrowicz, Ian Hincks, Inho Choi, Jake Lishman, James R. Garrison, Jens Palsberg, Joshua, Julien Gacon, Jun Doi, Kaelyn Ferris, kaldag, Kalyan Dasgupta, Keli Huang, Kevin Hartman, Krishan Sharma, littlebullGit, Luciano Bello, Matthew Treinish, Mostafizur Rahaman Laskar, Phalak, Raynel Sanchez, Rebecca Dimock, Ricard Santiago Raigada García, Samuele Ferracin, Shelly Garion, Simon Martiel, Soumyadip Sarkar, Vyom Patel, WhimsyHippo, Will Shanks, and Yael Ben-Haim

This blog post was written by Kaelyn Ferris with help from Antonio Córcoles, Blake Johnson, Jake Lishman, Julien Gacon, Luciano Bello, Matthew Treinish, Robert Davis, and Salvador de la Puente.