Changelog#

Version 2.2.0#

Breaking changes#

  • A SparsePauliOp is now one observable, the weighted sum of its terms, as it is for EstimatorV2. It used to be read as a list of observables, one expectation value per Pauli, with its coefficients ignored. To keep that reading, pass the labels themselves: SparsePauliOp(["IZ", "ZI"]) becomes ["IZ", "ZI"].

  • The expectation values are shaped like the observables given, as an estimator’s are, so one observable comes back as a zero-dimensional array and a list of n as (n,).

  • CutExperiment.observables is now the ObservablesArray that was asked for, and has no len(), use .size or .shape. The Pauli terms the circuits actually measure are CutExperiment.observable_terms.

  • quasi_probabilities(), nearest_probabilities() and counts() return, by default, the ten most likely bitstrings rather than all of them, most likely first. All three take top for a different number and top=None for the previous behaviour. Note that counts() sums to shots only with top=None and Only quasi_probabilities() gets cheaper this way since the other two project onto the nearest physical distribution first.

Reconstructing a distribution no longer costs 2**k#

  • Reconstructing a distribution over k qubits is now flat in k rather than exponential in it with a proper reconstruction method. See the derivation.

  • The distribution is held in that product form, so three new queries never expand it: top(count) gives the most likely bitstrings, exactly, by branch and bound (measured at 17 ms and 0.2 MB for the top 100 of 224, against 270 ms and 384 MB just to hold the values); probability_of(bitstring) gives one value in about 25 microseconds whatever k is; and marginal(qubits) gives a coarser distribution over a subset. probabilities() returns every value as a numpy array.

  • The observables of an experiment given qubits are built on demand, so nothing pays for the 2**k Pauli labels unless it asks for them, and the weights between them and the terms are never written out at all. Estimating them with estimate_expectation_values() still works.

  • Reconstruction is now sparse, costing shots rather than 2**num_qubits per subcircuit.

  • Fixed top() skipping unmeasured outcomes

Observables as qiskit’s estimator takes them#

  • observables now takes anything qiskit’s estimator takes: a Pauli label, a Pauli, a SparsePauliOp, a SparseObservable (qiskit 2.1+), a {label: coefficient} mapping, or any nested sequence of those. Coefficients are applied rather than ignored.

  • Projector terms are supported: 0 1 + - r l, whether written as labels or carried by a SparseObservable (qiskit 2.1+).

Version 2.1.4#

  • Small fixes to transpilation on IQM Star backends

Version 2.1.3#

  • Reading results is orders of magnitude quicker. Each subcircuit is read once per group rather than walking every combination of their outcomes, and observables sharing a measurement setting are read together.

  • get_experiment_circuits() no longer raises CircuitError: register size error on a transpiled subcircuit whose routing moved a qubit onto a wire a wire cut had ended. The qubits left to measure are counted by qubit rather than by the wire holding one.

  • run() and run_cut_circuit() take run_options as well.

  • Added ParallelBackend, which runs an experiment on several backends at once, each batch transpiled for the one about to run it. See Running on several backends.

  • Added transpile_circuits(), which takes a CutCircuit, a CutExperiment or plain circuits.

  • transpile_experiments() defaults to optimization_level=3 rather than 0, and no longer raises on a backend without a target, such as a simulator.

Version 2.1.2#

  • transpile_subcircuits() followed by get_experiment_circuits() or run_cut_circuit() no longer raises AttributeError on a non IQM BackendV2, such as GenericBackendV2 or one of IBM’s.

  • Generating experiment circuits is about a third quicker and takes about half the memory.

  • A generated QPD, joint rotation cuts included, merges the gate’s local unitaries into its operations instead of carrying them as separate gates, so the circuits hold about a third fewer instructions.

  • CutExperiment.group_size returns the number of circuits in a group, as documented, rather than the instruction count of the first subcircuit.

Version 2.1.1#

  • run() and run_cut_circuit() take qubits as well, and return the reconstructed distribution instead of expectation values when given it. observables is optional on both, and passing neither or both raises.

Version 2.1.0#

Reconstructing a probability distribution#

  • get_experiment_circuits() takes qubits in place of observables, and estimate_probabilities() then reconstructs the distribution over those qubits by an inverse Walsh-Hadamard transform. Costs 2**k observables but no extra circuits, since they share one measurement setting. See the derivation.

  • The result is a dict of quasi-probabilities, which can be negative. nearest_probabilities() gives the closest true distribution and counts() scales it by the shots the experiment ran at, or by one of your own.

  • A circuit carrying final measurements can be cut. They are removed on a copy, so the circuit passed in is left as it was.

  • RawResult.shots and CutExperiment.can_reconstruct_probabilities are public, and the types the public functions return are importable from QCut.

Version 2.0.0#

Breaking changes#

  • RawResult no longer takes a samples argument. It is now RawResult(results, shots, experiment=None) and carries the experiment itself.

  • RawResult.result() keeps its shape but each subcircuit now holds a CircuitResult containing what the backend or sampler returned, plus the shot scale and the label selection that group takes from it rather than a counts dict. Call .counts() on one for the counts.

  • Modules are grouped into subpackages: QCut.qpd, QCut.cutting, QCut.execution, QCut.errors and QCut.utils. The names exported from QCut are unchanged; code importing a module directly has to be updated.

  • find_cuts() is now deterministic and costs several candidate partitions before choosing, so it returns different, cheaper plans than 1.3.2 did for the same circuit.

  • find_cuts() now reads its configuration from a CutOptions object rather than from keyword arguments. See Options and Automatic cuts for details.

  • Cut edge weights are log gamma rather than gamma, which also changes which cuts are chosen. See Automatic cuts.

  • Subcircuits are no longer given empty classical registers, and an unused qpd_meas register is dropped. Code reading registers by position rather than by name has to be updated.

  • CutExperiment.expv_data() is gone and estimate_expectation_values() takes only the results, which now carry the experiment they came from. Replace estimate_expectation_values(results, experiment.expv_data()) with estimate_expectation_values(results). See Usage.

  • get_experiment_circuits() no longer modifies the CutCircuit it is given, so one can be reused for several observable sets.

  • transpile_subcircuits() raises rather than quietly overriding when remove_final_rzs or optimize_single_qubits is passed for an IQM backend. Those rewrite a circuit that still carries cut placeholders; use transpile_experiments() instead.

  • perform_move_routing now defaults to whether the backend needs it, on for a resonator device and off otherwise, and an explicit value is honoured either way.

Cutting arbitrary two-qubit gates#

  • Any two-qubit gate can be cut, with the decomposition derived from its KAK coordinates. See Gate cuts and Theory.

Joint cutting of parallel rotation gates#

  • Single-axis rotation gates running in parallel between the same two partitions share one decomposition: two parallel rzz cost 30 subexperiments instead of 36, three cost 132 instead of 216. On by default. See the derivation.

Wire cuts with classical communication#

  • A block of parallel wire cuts can exchange the measured outcome, taking the overhead from 4**n to 2**(n+1) - 1 and two wires from 64 subexperiments to 28. These run in waves. Used by default for blocks of two or more. See the derivation.

Gate consolidation#

  • Runs of gates on the same qubit pair are merged before cutting, so the pair costs one cut. Runs need not be contiguous, gates that commute with the run are moved out of the way. On by default, and compared against not merging. See Options.

Sampling instead of enumerating#

  • The experiment can be sampled from the quasiprobability distribution rather than enumerated, which bounds the number of circuits when the exact count is out of reach. Automatic above 1000 groups. See Options.

Configuration#

  • Added CutOptions, collected once and carried through the run. Covers consolidation, joint cuts, wire cut communication, expansion strategy, sampling and the cut finder. See Options.

Knowing what a cut costs#

  • CutCircuit.gamma is the sampling overhead of a split and CutCircuit.optimal_gamma the least those same cuts could cost with every decomposition available. Both are closed form, so the cost of a plan can be read before any experiment circuits are built. CutExperiment carries both forward.

Running on real hardware#

  • Improved and fixed bugs in transpilation for real backends.

  • Better IQM support: pip install "QCut[iqm]", and both transpile helpers use IQM’s own transpiler for IQM backends, resonator machines included. Pass use_iqm_transpiler=False to opt out. See Usage.

  • Resonator devices are supported by both transpile helpers. Their MOVE gates are routed while the subcircuits still carry cut placeholders, which the routing pass used to drop along with the classical registers and the layout. use_iqm_transpiler=False raises for them, since standard qiskit has no MOVE gate.

  • transpile_experiments() works on IQM backends.

  • A block of parallel wire cuts is never bundled for a resonator device, which reports its qubits as fully coupled but has no two-qubit gate that avoids its resonator. Those cuts fall back to one block per wire rather than to the local decomposition.

  • run_experiments() also takes a V2 sampler as its backend, on both the plain and the communicating execution path. Note that qiskit.primitives.StatevectorSampler cannot be used, since it refuses mid-circuit measurements.

  • Every batch of a wave is submitted before any of it is collected, so a run queues all of its jobs at once rather than waiting out each batch in turn.

  • run_experiments() takes run_options, passed on to every run call. A target that batches on its own account, such as fiqci-ems does, is also given QCut’s max_batch_size, so it does not split a batch QCut has already sized.

  • run() and run_cut_circuit() now take shots.

Other#

  • from QCut import * no longer raises.

  • Test suite split into tiers. See CONTRIBUTING.md.

  • Dropped pickle, experiment generation is much faster.

  • finder_max_qubits no longer raises when given as a list.

  • Benchmarks against IBM’s cutting addon, in benchmarks/.

Version 1.3.2#

  • Fix bug in how weights for different gates were being handled by QCutFind.

Version 1.3.1#

  • Fix bug in find_cuts() where on circuits that already had the desired partition early return condition would return incorrect type.

Version 1.3.0#

Support for cutting SWAP and iSWAP gates#

  • Added support for cutting SWAP and iSWAP gates in addition to CZ gates and wire cuts.
    • Uses optimal QPDs instead of naively decomposing into CZ gates, resulting in significantly fewer subcircuits needed for the same number of cuts.

    • QCutFind also supports finding optimal cut locations for cutting SWAP and iSWAP gates.

    • Refactored codebase to support cutting arbitrary gates in a more modular way, making it easier to add support for cutting more gates in the future.

    • Check documentation for details on how to use.

Version 1.2.0#

Refactor run_experiments()#

  • Refactor run_experiments() to improve performance, flexibility, and code quality.

  • run_experiments() now batches circuits into groups for more efficient execution on real hardware.
    • By default, circuits are grouped into batches of 100, but this can be adjusted with the max_batch_size parameter in run_experiments().

  • run_experiments() now returns a RawResults object instead of a list of preprocessed results. This is to allow for more flexible postprocessing of results, including support for custom postprocessing functions.
    • post processing now automatically happens when calling get_expectation_values() that now takes a RawResults object as an argument.

Add RawResults class#

  • The RawResults object contains the raw results from the backend, as well as metadata such as the number of shots and samples used.
    • This allows for more flexible postprocessing of results, including support for custom postprocessing functions.

    • Like a qiskit result object the RawResults has a result() method that returns the raw results from the backend. The format is currently quite messy and could be improved in future releases.

Add logging#

  • Added logging to the codebase to improve debuggability and provide more information about the execution of the code.

  • Examples on how to use the logging can be found in the documentation.

Version 1.1.1#

  • Fix find_cut() not passing max_qubits parameter to get_locations_and_subcircuits(), which caused incorrect cut_circuits to be returned.

Version 1.1.0#

  • Small syntax change for placing wire cuts or cz cuts directly
    • Instead of cut_circuit.append(cut, [1]) now use cut_circuit.append(cut(), [1])

    • Instead of cut_circuit.append(cutCZ, [0,1]) now use cut_circuit.append(**cutGate(CZGate(), 0, 1))

    • Check documentation for details on how to use.

  • Comprehensive refactor of codebase and documentation to improve readability and maintainability.

Version 1.0.2#

  • Fix bug in transpile_experiments()

  • Adjust supported qiskit and python versions to better match iqm-client
    • Drop Python 3.10 support

    • Drop qiskit 1.0 support

    • Supported versions now Python >= 3.11, < 3.13 and qiskit >= 1.1, < 3.0

Version 1.0.1#

  • Fix typo in pyproject.toml

Version 1.0.0#

  • Yanked due to pyproject.toml typo. Please use 1.0.1 instead.

  • Support for Qiskit 2.x

  • QCut now supports Qiskit 1.0+

Version 0.9.2#

  • Minor fix

Version 0.9.1#

  • Fixes for issues for subcircuit construction with Qiskit > 1.2
    • Qiskit version requirement updated to >= 1.0, < 2.0

Version 0.9.0#

  • Migrate from index based Z-observables to Qiskit’s SparsePauliOps
    • The observables parameter for all functions now takes a list of Qiskit’s SparsePauliOp objects instead of lists of qubit indices.

    • This allows for more general observables to be calculated, including multi-qubit observables and observables with different Pauli operators.

    • Check documentation for details on how to use.

Version 0.8.0#

  • Support for cutting 2 qubit gates
    • Added cutGate function for cutting 2 qubit gates directly.

    • Cutting done by transpiling the 2 qubit gate into a cut CZ gate with appropriate basis changes.

    • For non CZ family gates this results in suboptimal decompositions. More optimised decompositions will be added in future releases.

    • Check documentation for details on how to use.

Version 0.7.0#

  • Rework transpilation workflow
    • Transpilation now done per subcircuit instead of per experiment circuit.
      • Per experiment transpilation still provided for more control.

      • Users can of course still manually transpile circuits before passing to QCut.

    • This greatly reduces the number of transpilation calls needed, improving performance.

    • Check documentation for details on how to use.

  • Bug fixes

Version 0.6.0#

  • Support cutting CZ gates
    • Added support for cutting CZ gates in addition wire cuts.

    • Check documentation for details on how to use.

  • Added automatic cut finding feature
    • Added QCutFind module for automatically finding good cut locations in a circuit.

    • Check documentation for details on how to use.

  • Bug fixes

  • Drop windows support for the time being due to METIS issues
    • Windows users should use WSL

Version 0.3.0#

  • Support for IQM Qiskit 17.8
    • Added support for IQM Qiskit 17.8.

  • Removed built in mitigate flag

  • Bug fixes

  • Remove old two qubit gate CutWire operation
    • Users using the old method can consult documentation for migration help

Version 0.2.4#

  • Bugfix for incorrect partitioning for cases where there are multiple cuts on a single wire

Version 0.2.3#

  • Major optimisation on _move_to_new_wire method.
    • Old version took around 11s for a random circuit with depth of 50 and 50 qubits.

    • New version takes around 0.2s for the same circuit.

Version 0.2.2#

  • Bugfix for array overflow in get_experiment_circuits

Version 0.2.1#

  • Hotfix for incorrect version of qiskit-aer in pyproject.toml.

  • Fix pypi workflow

Version 0.2.0#

  • Single qubit cut gate now the default cut method
    • Greatly simplifies placing cuts.

    • Old two qubit gate deprecated and will be removed soon.

    • Check out documentation for migration help.

Version 0.1.3#

  • Hotfix for source files not included in pypi build.
    • 0.1.0 - 0.1.2 not installable.

Version 0.1.2#

  • Add Qiskit 1.0 support.
    • Supported versions now >= 0.45.3, < 1.2.

    • No workflow changes. No migration required.

    • Compatible with qiskit-iqm 13.15

  • Add Python 3.11 support.
    • Supported versions now >= 3.9, < 3.12.

  • Fix bug in _get_bounds() method.

  • Use pickle.loads(pickle.dumps()) instead of deepcopy() in _get_experiment_circuits().
    • Slight performance improvement.

  • Revert back to vx.x.x versioning scheme.

Version 0.1.1#

  • Code quality improvements:
    • Move to pyproject.toml. Contents of ruff.toml and setup.py now live in pyproject.toml.

    • Relative imports are now absolute imports.

    • All images are now located under the _static folder.

  • Added Github workflows:
    • Added github actions workflows for building the documentation and testing that the documentation can be built.

    • Added github actions workflows for testing the code.

    • Added github actions workflows for publishing to pypi.

    • Added github actions workflows for linting.

  • Revamped documentation:
    • Documentation now uses the Book theme.

    • Fixed all warnings from sphinx when building the documentation.

    • Fixed spelling mistakes.

    • Added a new page for the changelog.

  • README now contains the information to build the docs.

  • Change versioning scheme to “x.x.x” instead of “vx.x.x”.

Version 0.1.0#

  • First release