Changelog#
Version 2.2.0#
Breaking changes#
A
SparsePauliOpis now one observable, the weighted sum of its terms, as it is forEstimatorV2. 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
nas(n,).CutExperiment.observablesis now theObservablesArraythat was asked for, and has nolen(), use.sizeor.shape. The Pauli terms the circuits actually measure areCutExperiment.observable_terms.quasi_probabilities(),nearest_probabilities()andcounts()return, by default, the ten most likely bitstrings rather than all of them, most likely first. All three taketopfor a different number andtop=Nonefor the previous behaviour. Note thatcounts()sums toshotsonly withtop=Noneand Onlyquasi_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
kqubits is now flat inkrather 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 whateverkis; andmarginal(qubits)gives a coarser distribution over a subset.probabilities()returns every value as a numpy array.The observables of an experiment given
qubitsare built on demand, so nothing pays for the2**kPauli labels unless it asks for them, and the weights between them and the terms are never written out at all. Estimating them withestimate_expectation_values()still works.Reconstruction is now sparse, costing shots rather than
2**num_qubitsper subcircuit.Fixed
top()skipping unmeasured outcomes
Observables as qiskit’s estimator takes them#
observablesnow takes anything qiskit’s estimator takes: a Pauli label, aPauli, aSparsePauliOp, aSparseObservable(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 aSparseObservable(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 raisesCircuitError: register size erroron 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()andrun_cut_circuit()takerun_optionsas 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 aCutCircuit, aCutExperimentor plain circuits.transpile_experiments()defaults tooptimization_level=3rather than0, and no longer raises on a backend without a target, such as a simulator.
Version 2.1.2#
transpile_subcircuits()followed byget_experiment_circuits()orrun_cut_circuit()no longer raisesAttributeErroron a non IQMBackendV2, such asGenericBackendV2or 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_sizereturns the number of circuits in a group, as documented, rather than the instruction count of the first subcircuit.
Version 2.1.1#
run()andrun_cut_circuit()takequbitsas well, and return the reconstructed distribution instead of expectation values when given it.observablesis optional on both, and passing neither or both raises.
Version 2.1.0#
Reconstructing a probability distribution#
get_experiment_circuits()takesqubitsin place ofobservables, andestimate_probabilities()then reconstructs the distribution over those qubits by an inverse Walsh-Hadamard transform. Costs2**kobservables but no extra circuits, since they share one measurement setting. See the derivation.The result is a
dictof quasi-probabilities, which can be negative.nearest_probabilities()gives the closest true distribution andcounts()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.shotsandCutExperiment.can_reconstruct_probabilitiesare public, and the types the public functions return are importable fromQCut.
Version 2.0.0#
Breaking changes#
RawResultno longer takes asamplesargument. It is nowRawResult(results, shots, experiment=None)and carries the experiment itself.RawResult.result()keeps its shape but each subcircuit now holds aCircuitResultcontaining 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.errorsandQCut.utils. The names exported fromQCutare 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 aCutOptionsobject rather than from keyword arguments. See Options and Automatic cuts for details.Cut edge weights are
log gammarather thangamma, which also changes which cuts are chosen. See Automatic cuts.Subcircuits are no longer given empty classical registers, and an unused
qpd_measregister is dropped. Code reading registers by position rather than by name has to be updated.CutExperiment.expv_data()is gone andestimate_expectation_values()takes only the results, which now carry the experiment they came from. Replaceestimate_expectation_values(results, experiment.expv_data())withestimate_expectation_values(results). See Usage.get_experiment_circuits()no longer modifies theCutCircuitit is given, so one can be reused for several observable sets.transpile_subcircuits()raises rather than quietly overriding whenremove_final_rzsoroptimize_single_qubitsis passed for an IQM backend. Those rewrite a circuit that still carries cut placeholders; usetranspile_experiments()instead.perform_move_routingnow 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#
Joint cutting of parallel rotation gates#
Single-axis rotation gates running in parallel between the same two partitions share one decomposition: two parallel
rzzcost 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**nto2**(n+1) - 1and 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.gammais the sampling overhead of a split andCutCircuit.optimal_gammathe 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.CutExperimentcarries 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. Passuse_iqm_transpiler=Falseto 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=Falseraises 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 itsbackend, on both the plain and the communicating execution path. Note thatqiskit.primitives.StatevectorSamplercannot 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()takesrun_options, passed on to everyruncall. A target that batches on its own account, such as fiqci-ems does, is also given QCut’smax_batch_size, so it does not split a batch QCut has already sized.run()andrun_cut_circuit()now takeshots.
Other#
from QCut import *no longer raises.Test suite split into tiers. See
CONTRIBUTING.md.Dropped pickle, experiment generation is much faster.
finder_max_qubitsno 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 usecut_circuit.append(cut(), [1])Instead of
cut_circuit.append(cutCZ, [0,1])now usecut_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
SparsePauliOpobjects 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
cutGatefunction 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
QCutFindmodule 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
mitigateflagBug fixes
- Remove old two qubit gate
CutWireoperation Users using the old method can consult documentation for migration help
- Remove old two qubit gate
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_wiremethod. 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.
- Major optimisation on
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 ofdeepcopy()in_get_experiment_circuits(). Slight performance improvement.
- Use
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