Skip to main contentIBM Quantum Documentation

Qiskit SDK 1.2 release notes


1.2.0

Prelude

The Qiskit 1.2.0 release focuses mainly on improving performance and quality of the compiler. Also, it is the last release supporting Python 3.8. In Qiskit 1.3.0 the minimal required Python version will be 3.9. Among a wide range of new features, improvements, and fixes, the release highlights are:

  • The circuit infrastructure, including gates and operations, is moved to Rust. This enables a series of speedups in circuit construction and circuit manipulation.
  • Leveraging the above point, parts of the synthesis library are now constructing circuits in Rust. This produces a significant speedup when synthesizing operations such as Cliffords, permutations, or linear functions. As example, decomposing 50+ qubit Cliffords benefit from an approximate 1000-fold speedup in runtime.
  • The quality of compiled circuits is improved by optimizing with a unitary peephole optimization at the initial stage of the transpilation workflow (at optimization level >1), and by using a dense layout as a Sabre layouting trial (at optimization level >0).

New Features

  • Added a new class QFTGate for natively representing Quantum Fourier Transforms (QFTs). The older way of representing QFTs via quantum circuits, see QFT, remains for backward compatibility. The new way of representing a QFT via a gate avoids synthesizing its definition circuit when the gate is declared, delaying the actual synthesis to the transpiler. It also allows to easily choose between several different algorithms for synthesizing QFTs, which are available as high-level-synthesis plugins.

  • Added a synthesis method synth_qft_full() for constructing a QFT circuit assuming a fully-connected architecture.

  • Added two high-level-synthesis plugins for synthesizing a QFTGate. The class QFTSynthesisFull is based on synth_qft_full() and synthesizes a QFT gate assuming an all-to-all connectivity. The class QFTSynthesisLine is based on synth_qft_line() and synthesizes a QFT gate assuming a linear-nearest-neighbor connectivity.

  • Added two parameters to GenericBackendV2 to exclude error (noise_info) and pulse channel information (pulse_channels) from the construction of the backend. These parameters are True by default, replicating the initial default behavior of the constructor. A memory-sensitive user may set these options to False to reduce the memory overhead by 40x when transpiling on large- scale GenericBackendV2.

  • The StabilizerState class now has a new method StabilizerState.probabilities_dict_from_bitstring() allowing the user to pass single bitstring to measure an outcome for. Previouslly the StabilizerState.probabilities_dict() would be utilized and would at worst case calculate (2n2^n) number of probability calculations (depending on the state), even if a user wanted a single result. With this new method the user can calculate just the single outcome bitstring value a user passes to measure the probability for. As the number of qubits increases, the more prevelant the performance enhancement may be (depending on the state) as only 1 bitstring result is measured.

  • Implemented UniformSuperpositionGate class, which allows the creation of a uniform superposition state using the Shukla-Vedula algorithm. This feature facilitates the creation of quantum circuits that produce a uniform superposition state 1Mj=0M1j\frac{1}{\sqrt{M}} \sum_{j=0}^{M-1} |j\rangle, where MM is a positive integer representing the number of computational basis states with an amplitude of 1M\frac{1}{\sqrt{M}}. This implementation supports the efficient creation of uniform superposition states, requiring only O(log2(M))O(\log_2 (M)) qubits and O(log2(M))O(\log_2 (M)) gates. Usage example:

    from qiskit import QuantumCircuit 
    from qiskit.circuit.library.data_preparation import UniformSuperpositionGate
     
    M = 5
    num_qubits = 3
    usp_gate = UniformSuperpositionGate(M, num_qubits)
    qc = QuantumCircuit(num_qubits)
    qc.append(usp_gate, list(range(num_qubits)))
     
    qc.draw()

Circuits Features

  • Added a new function random_clifford_circuit() to qiskit.circuit, that allows to generate a pseudo-random Clifford circuit with gates from the standard library. Example usage:

    from qiskit.circuit.random import random_clifford_circuit
     
    circ = random_clifford_circuit(num_qubits=2, num_gates=6)
    circ.draw(output='mpl')
    _images/release_notes-1.png
  • Added support for AnnotatedOperation.params and AnnotatedOperation.validate_parameter(), which enable circuit-level parameter handling (such as binding parameters) for annotated operations.

  • CircuitInstruction and DAGOpNode each have new methods to query various properties of their internal Operation, without necessarily needing to access it. These methods are:

    If applicable, using any of these methods is significantly faster than querying CircuitInstruction.operation or DAGOpNode.op directly, especially if the instruction or node represents a Qiskit standard gate. This is because the standard gates are stored natively in Rust, and their Python representation is only created when requested.

  • A native rust representation of Qiskit’s standard gate library has been added. When a standard gate is added to a QuantumCircuit or DAGCircuit it is now represented in a more efficient manner directly in Rust. Accessing that gate object from a circuit or DAG will return a new Python object representing the standard gate. This leads to faster and more efficient transpilation and manipulation of circuits for functionality written in Rust.

  • The random_circuit() function has a new feature where users can specify a distribution num_operand_distribution (a dict) that specifies the ratio of 1-qubit, 2-qubit, 3-qubit, and 4-qubit gates in the random circuit. For example, if num_operand_distribution = {1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25} is passed to the function then the generated circuit will have approximately 25% of 1-qubit, 2-qubit, 3-qubit, and 4-qubit gates. Also it should be noted that the if num_operand_distribution is not specified then max_operands will default to 4 and a random circuit with a random gate distribution will be generated. If both num_operand_distribution and max_operands are specified at the same time then num_operand_distribution will be used to generate the random circuit.

    Example usage:

    from qiskit.circuit.random import random_circuit
     
    circ = random_circuit(
      num_qubits=6, depth=5, num_operand_distribution={1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25}
    )
    circ.draw(output="mpl")
    _images/release_notes-2.png
  • Improved performance of the method DAGCircuit.quantum_causal_cone() by not examining the same non-directive node multiple times when reached from different paths.

  • Added the insert_barriers keyword argument to the QuantumCircuit.repeat() method. Setting it to True will insert barriers between circuit repetitions.

  • Replacing the internal synthesis algorithm of StatePreparation and Initialize of Shende et al. by the algorithm given in Isometry of Iten et al. The new algorithm reduces the number of CX gates and the circuit depth by a factor of 2.

  • ParameterExpression now supports the unary + operator.

Primitives Features

OpenQASM Features

  • The internal symbol table of the OpenQASM 3 exporter (qiskit.qasm3) has been rewritten, which should result in cleaner outputs when using Qiskit standard-library gates that are not in the OpenQASM 3 standard-library headers, and more deterministic outputs. For example, using several RZXGates will now result in only a single parametric definition, and when naming collisions occur, the symbol table will assign a deterministic counter to make names unique, rather than a non-deterministic integer (previously, the object identity was used).

  • The vendored version of the OpenQASM 3.0 standard library has been updated to match the state as of commit 4ca1d79383. This should generally have no effect on your use of Qiskit, unless you were retrieving our vendored file for your own use.

Synthesis Features

  • MCXRecursive with kk control qubits and a single clean auxiliary qubit now requires at most 16k816k-8 CX gates.

  • MCXVChain has two new Boolean parameters relative_phase and action_only. If action_only is True the circuit does not clean the dirty qubits. If relative_phase is True the gate is implemented up to a global phase. Both parameters are used to optimize the decomposition of MCXVChain.

  • MCXVChain with kk controls and k2k-2 dirty auxiliary qubits now requires 8k68k-6 CX gates.

  • Port synth_permutation_acg(), used to synthesize qubit permutations, to Rust. This produces an approximate 3x performance improvement on 1000 qubit circuits.

  • Port synth_permutation_basic(), used to synthesize qubit permutations, to Rust.

  • Port synth_cnot_count_full_pmh(), used to synthesize a linear function into a CX network, to Rust. This produces approximately 44x speedup, as measured on 100 qubit circuits.

  • The function synth_cnot_count_full_pmh() now allows choosing the (heuristically) optimal section_size by setting it to None. Then, a value is chosen which attempts to minimize the upper bound on the number of CX gates, that is αlog2(n)\alpha \log_2(n) where nn is the number of qubits and α0.56\alpha \approx 0.56.

  • The function synth_clifford_bm() was ported to Rust. Recall that this function optimally synthesizes Clifford operators on 1, 2 or 3 qubits with respect to the number of CX-gates. This leads to a significant increase in performance. For Cliffords over 3 qubits, the speedup in on the order of 80 times.

  • The function synth_clifford_greedy() that synthesizes Clifford operators was ported to Rust, leading to a significant increase in performance for all numbers of qubits. For Cliffords over 50 qubits, the speedup is on the order of 1000 times.

  • Added the wrap keyword argument to the ProductFormula classes which (when enabled) wraps individual Pauli evolution terms. This can be useful when visualizing circuits.

  • The atomic_evolution argument to ProductFormula (and its subclasses) has a new function signature. Rather than taking some Pauli operator and time coefficient and returning the evolution circuit, the new function takes in an existing circuit and should append the evolution of the provided Pauli and given time to this circuit. This new implementation benefits from significantly better performance.

  • Improved the performance of synth_permutation_depth_lnn_kms(), used to synthesize permutations for linear connectivity, by porting it to Rust.

Transpiler Features

  • Added a new import path option for generate_preset_pass_manager(), so that it can now be imported as:

    from qiskit import generate_preset_pass_manager

    instead of having to type the full path:

    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

    The function is also importable from the qiskit.transpiler module as:

    from qiskit.transpiler import generate_preset_pass_manager
  • Added a new user config file option sabre_all_threads and a corresponding environment variable QISKIT_SABRE_ALL_THREADS. When this flag is set the preset pass managers will run the SabreLayout and SabreSwap transpiler passes using all the available CPUs on the local system. Using this option is a tradeoff between determinism of output between different computers and potentially better output with fewer SwapGates.

    These transpiler passes run multiple random trials in parallel and pick the output which results in the fewest SwapGates. As a rule of thumb, if you run more trials, this provides the algorithm more opportunities to find a better result. By default, the preset pass managers use a fixed number of trials, in this release 5 trials for levels 0 and 1, and 20 trials for levels 2 and 3, but these numbers may change in future releases (and were different in historical releases). Using a fixed number of trials results in deterministic results regardless of the local system, because even with a fixed seed if you were to default to the number of local CPUs available the results would different when running between different computers.

    If the default number of trials for a given optimization level is higher than the number of local CPUs it will use the optimization level default which is higher.

  • Added a new pass Split2QUnitaries that iterates over all two-qubit gates or unitaries in a circuit and replaces them with two single-qubit unitaries, if possible without introducing errors, i.e., the two-qubit gate/unitary is actually a tensor product of single-qubit unitaries.

  • The passes Collect2qBlocks, ConsolidateBlocks and Split2QUnitaries have been added to the init stage of the preset pass managers with optimization level 2 and optimization level 3. The modification of the init stage should allow for a more efficient routing for quantum circuits that either:

    • contain two-qubit unitaries/gates that are actually a product of single-qubit gates, or
    • contain multiple two-qubit gates in a continuous block of two-qubit gates.

    In the former case, the routing of the two-qubit gate can simply be skipped as no real interaction between a pair of qubits occurs. In the latter case, the lookahead space of routing algorithms is not ‘polluted’ by superfluous two-qubit gates, i.e., for routing it is sufficient to only consider one single two-qubit gate per continuous block of two-qubit gates. These passes are not run if the pass managers target a Target that has a discrete basis gate set, i.e., all basis gates have are not parameterized.

  • The performance of StarPreRouting, used to find a star graph connectivity subcircuit, is improved by performing the heavy lifting in Rust space.

  • The SabreLayout transpiler pass has been updated to always run an additional trial using the same algorithm as DenseLayout to choose a starting point for Sabre’s layout algorithm. The starting point used by the layout algorithm can have a large influence on the quality of the results. By default SabreLayout still start with layout_trials random trials but in addition there will be a single trial that uses the densest subgraph of the connectivity graph as the starting point. This may yield better results in some cases especially on cases with smaller circuits for larger coupling maps.

  • A new dt argument has been added to generate_preset_pass_manager() to match the set of arguments of transpile(). This will allow for the internal conversion of transpilation constraints to a Target representation.

Visualization Features

  • The user configuration file has a new option circuit_idle_wires, which takes a Boolean value. This allows users to set their preferred default behavior of the idle_wires option of the circuit drawers QuantumCircuit.draw() and circuit_drawer(). For example, adding a section to ~/.qiskit/settings.conf with:

    [default]
    circuit_idle_wires = false

    will change the default to not display idle wires.

Misc. Features

  • Added a new build-time environment variable QISKIT_NO_CACHE_GATES which when set to a value of 1 (i.e. QISKIT_NO_CACHE_GATES=1) decreases the memory overhead of a CircuitInstruction and DAGOpNode object at the cost of increased runtime on multiple accesses to CircuitInstruction.operation and DAGOpNode.op. If this environment variable is set when building the Qiskit Python package from source the caching of the return of these attributes will be disabled.

Circuits Upgrade Notes

  • The annotated argument of the Gate.control() method is now None by default, which allows Qiskit to choose whether to annotate a controlled operation. If the concrete implementation (annotated=False) is available, it will be returned by default. Otherwise, the annotated implementation will be returned (annotated=True). This allows, for example, to defer the synthesis of controlled, parameterized gates.

  • The Operation instances of DAGOpNode.op being returned will not necessarily share a common reference to the underlying object anymore. This was never guaranteed to be the case and mutating the DAGOpNode.op directly by reference was unsound and always likely to corrupt the DAG’s internal state tracking Due to the internal refactor of the QuantumCircuit and DAGCircuit to store standard gates in Rust, the output object from DAGOpNode.op will now likely be a copy instead of a shared instance. If you need to mutate an element should ensure that you do:

    op = dag_node.op
    op.params[0] = 3.14159
    dag.substitute_node(dag_node, op)

    instead of doing something like:

    dag_node.op.params[0] = 3.14159

    which will not work for any standard gate in this release. It would have likely worked by chance in a previous release but was never an API guarantee.

  • The Operation instances of CircuitInstruction.operation being returned will not necessarily share a common reference to the underlying object anymore. This was never guaranteed to be the case and mutating the CircuitInstruction.operation directly by reference was unsound and always likely to corrupt the circuit, especially when parameters were in use. Due to the internal refactor of the QuantumCircuit to store standard gates in Rust, the output object from CircuitInstruction.operation will now likely be a copy instead of a shared instance. If you need to mutate an element in the circuit (which is strongly not recommended as it’s inefficient and error prone) you should ensure that you do:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(1)
    qc.p(0)
     
    op = qc.data[0].operation
    op.params[0] = 3.14
     
    qc.data[0] = qc.data[0].replace(operation=op)

    instead of doing something like:

    from qiskit.circuit import QuantumCircuit
     
    qc = QuantumCircuit(1)
    qc.p(0)
     
    qc.data[0].operation.params[0] = 3.14

    which will not work for any standard gates in this release. It would have likely worked by chance in a previous release but was never an API guarantee.

Primitives Upgrade Notes

  • BitArray.slice_bits() and BitArray.slice_shots() will now raise IndexError when indices are out of bounds. They used to raise ValueError in the case.

  • BitArray.__getitem__() will now raise IndexError when indices are out of bounds or the number of dimensions of indices does not match that of BitArray. They used to raise ValueError in the case.

Synthesis Upgrade Notes

  • LieTrotter and SuzukiTrotter no longer wrap the individually evolved Pauli terms into gate definitions. If you rely on a certain decomposition level of your circuit, you have to remove one level of QuantumCircuit.decompose() or add the wrap=True keyword argument to your synthesis object.

Transpiler Upgrade Notes

  • Optimization levels 2 and 3 now additionally employ peephole optimization is now employed before the translation/synthesis stage (previously this was only done in the optimization stage). The effects of peephole optimization include the removal of gates that are very close to the identity, for example, controlled-phase gates with a rotational angle smaller than 2π2252\pi \otimes 2^{-25}.

  • The default routing pass used by optimization level 0 for generate_preset_pass_manager() and transpile() has been changed from StochasticSwap to SabreSwap. The SabreSwap pass performs exactly the same function but performs better in both runtime and output quality (in number of swap gates and depth) compared to StochasticSwap. For optimization_level=0 this shouldn’t matter because it’s not expected to run routing for the typical use case of level 0.

    If you were relying on the previous default routing algorithm for any reason you can use the routing_method argument for transpile() and generate_preset_pass_manager() to "stochastic" to use the StochasticSwap pass.

  • The generate_preset_pass_manager() function has been upgraded to, when possible, internally convert transpiler constraints into a Target instance. If a backend input of type BackendV1 is provided, it will be converted to BackendV2 to expose its Target. This change does not require any user action.

Misc. Upgrade Notes

  • The minimum version of rustworkx required to run this release has been increased from 0.14.0 to 0.15.0. This is required because Qiskit is now using new functionality added in the rustworkx 0.15.0 release which improves performance.

Circuits Deprecations

Primitives Deprecations

Providers Deprecations

Transpiler Deprecations

  • The assemble() function is now deprecated and will be removed in the 2.0 release. The function was primarily used to create a Qobj, which is no longer necessary in BackendV2-based workflows. It was also used for binding parameters, a functionality fully covered by QuantumCircuit.assign_parameters().

Visualization Deprecations

  • The justify argument of circuit_drawer() or QuantumCircuit.draw(), will no longer support invalid values (previously changing them to the default), and in a future release they will error. Valid justify values are "left", "right" or "none".

  • The visualize_transition() function has been deprecated and will be removed in the 2.0.0 release. This function had a number of limitations which limited its utility to only very specific use cases and did not fit in with the rest of the Qiskit visualization module.

Bug Fixes

  • Fixed a series of issues when controlling parameterized standard gates. The controlled version of some gates (e.g. RXXGate or RYGate for more than 1 control) cannot be synthesized if they contain unbound parameters. Previously, calling .control() in such a case failed, but now we create an AnnotatedOperation as placeholder. This allows to insert the controlled gate into a circuit, bind the parameters at a later stage, and then synthesize the operation. Fixes #10311, #10697, and #12135.

  • The SGate and SdgGate now correctly return a CSGate, resp. CSdgGate, if they are controlled on a single control qubit.

  • Fix the calculation of the standard deviation in BackendEstimatorV2, by taking into account co-variance of non-commuting Paulis. Fixed Qiskit/qiskit-ibm-runtime#1751.

  • Fixed an issue where circuit_drawer() or the QuantumCircuit.draw() method would not raise a warning when an invalid value was passed to the justify argument, before changing it to the default. Now, it will raise a warning if an invalid value is passed. Valid justify values are "left", "right" or "none". Refer to #12089 for more details.

  • Fixed SparsePauliOp.apply_layout() and Pauli.apply_layout() to raise QiskitError if duplicate indices or negative indices are provided as part of a layout.

  • Fixed a bug in the ConsolidateBlocks transpiler pass, when the input circuit contains a custom opaque gate and neither the basis_gates or target options are set the pass would raise a QiskitError and fail. This has been corrected so that the in these situations the transpiler pass will not consolidate the block identified containing a custom gate instead of failing.

  • Fixed a bug in PadDynamicalDecoupling, which previously did not correctly display the error message that a delay is not pulse-aligned, if the previous or following node was an input/output node. Now, the error message is correctly displayed.

  • The keyword argument order of the function BitArray.from_bool_array() should be "little" or "big". Added checks to raise error if an invalid value is entered.

  • Improve the decomposition of the gate generated by QuantumCircuit.mcx() without using ancilla qubits, so that the number of CXGates will grow quadratically in the number of qubits, as expected, and not exponentially.

  • Fixed SparsePauliOp.apply_layout() to work correctly with zero-qubit operators. For example, if you previously created a 0 qubit and applied a layout like:

    op = SparsePauliOp("")
    op.apply_layout(None, 3)

    this would have previously raised an error. Now this will correctly return an operator of the form: SparsePauliOp(['III'], coeffs=[1.+0.j])

  • Fixed a bug of StatevectorSampler that ignored gates with c_if. It will raise an error because Statevector cannot handle c_if.

  • Fixed an oversight in the Commuting2qGateRouter transpiler pass where the quantum register permutations were not added to the pass property set, so they would have to be tracked manually by the user. Now it is possible to access the permutation through the output circuit’s layout property and plug the pass into any transpilation pipeline without loss of information.

  • Fixed a floating-point imprecision when scaling certain pulse units between seconds and nanoseconds. If the pulse was symbolically defined, an unnecessary floating-point error could be introduced by the scaling for certain builds of symengine, which could manifest in unexpected results once the symbols were fully bound. Fixed #12392.

  • Fixed a bug in synth_cnot_count_full_pmh() where providing a section_size that did not divide the number of qubits without remainder could lead to wrong results. Now any section_size (at most equal to the number of qubits) synthesizes the correct circuit. For a (heuristically) optimal value, set section_size=None.

  • PassManager.run() will no longer waste time serializing itself when given multiple inputs if it is only going to work in serial.

  • Fixed a bug in plot_coupling_map() that caused the edges of the coupling map to be colored incorrectly. Fixed #12354.

  • The OpenQASM 2.0 parser (qasm2.load() and qasm2.loads()) can now evaluate gate-angle expressions including integer operands that would overflow the system-size integer. These will be evaluated in a double-precision floating-point context, just like the rest of the expression always has been. Beware: an arbitrarily large integer will not necessarily be exactly representable in double-precision floating-point, so there is a chance that however the circuit was generated, it had already lost all numerical precision modulo 2π2\pi.

  • The OpenQASM 3 exporter (see qiskit.qasm3) will now correctly error when asked to use a keyword or other invalid identifier as a “basis gate”, as it has no way of generating correct output in these cases.

  • The OpenQASM 3 exporter (qiskit.qasm3) will now correctly export multiple instances of PauliEvolutionGate from a circuit. Previously, only a single instance would be exported, and all other instances would silently use the same (incorrect) version.

  • The OpenQASM 3 exporter (qiskit.qasm3) will now correctly escape gate names. Previously, a gate whose name was an invalid OpenQASM 3 identifier would cause invalid OpenQASM 3 to be generated.

  • A series of input-handling inconsistencies between transpile() and generate_preset_pass_manager() have been fixed. These inconsistencies would lead to different transpilation outputs for the same inputs, or generate_preset_pass_manager() failing for certain input combinations accepted by transpile().

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