QCut API reference

Contents

QCut API reference#

Everything QCut exports is available from the top-level QCut namespace. The list below is generated from QCut.__all__, so an object added there appears here with no change to the docs.

Init circuit knitting.

class QCut.ParallelBackend(backends, optimization_level: int = 3, transpile_options: dict | None = None, use_iqm_transpiler: bool = True)[source]#

Bases: object

Spread an experiment’s jobs over several backends, one batch each in turn.

QCut submits every batch of a wave before collecting any of it, so handing successive batches to different backends leaves them all queued at once. Each batch is transpiled for the backend about to run it, so the backends do not have to be alike, and only one batch is ever held in its transpiled form. The experiment itself stays as it was built.

Pass it where a backend goes:

backend = ParallelBackend([first, second])
results = ck.run_experiments(experiment, shots=4096, backend=backend)

A batch goes only to a backend wide enough for it, so a fleet of different sizes works. The pieces that fit anywhere are shared, and the widest go to the backends that can hold them. The batches are dealt out in turn among those that fit.

The experiment does not have to be transpiled beforehand. Each batch is transpiled for the backend.

backends#

the backends, in the order the batches are dealt out.

Type:

list

submitted#

how many circuits each has been given, which is what to read to see how the work was shared.

Type:

list[int]

__init__(backends, optimization_level: int = 3, transpile_options: dict | None = None, use_iqm_transpiler: bool = True)[source]#

Init.

Parameters:
  • backends – the backends to run on, two or more.

  • optimization_level (int) – optimization level for transpilation (0-3).

  • transpile_options (dict) – arguments passed to the transpiler.

  • use_iqm_transpiler (bool) – whether an IQM backend may use IQM’s transpiler.

Raises:

QCutError – fewer than two backends were given.

property max_shots: int | None#

The most shots every one of them takes, so a batch fits wherever it lands.

run(circuits, shots: int = 1024, **options)[source]#

Transpile this batch for the backend whose turn it is, and submit it there.

Parameters:
  • circuits – the batch, as QCut sized it.

  • shots (int) – what to run them at. Passed on unchanged: it is what the estimator divides by, and the waves allocate it themselves.

  • **options – passed on to the backend.

Returns:

The backend’s own job, so the results are read exactly as they would be had the backend been given the batch directly.

Raises:

QCutError – no backend is wide enough for the batch.

QCut.transpile_circuits(circuits: CutCircuit | CutExperiment | list[QuantumCircuit], backend, optimization_level: int = 3, transpile_options: dict | None = None, use_iqm_transpiler: bool = True)[source]#

Transpile for a backend, whatever stage the circuits have reached.

A CutCircuit goes to transpile_subcircuits(), which has placeholders to protect, and a CutExperiment to transpile_experiments(), which does not. Plain circuits are transpiled as they are, which is what a backend given circuits rather than an experiment needs – see ParallelBackend.

Parameters:
  • circuits – a cut circuit, an experiment, or circuits with nothing left to hide.

  • backend – backend to transpile to.

  • optimization_level (int) – optimization level for transpilation (0-3).

  • transpile_options (dict) – arguments passed to the transpiler.

  • use_iqm_transpiler (bool) – whether an IQM backend may use IQM’s transpiler.

Returns:

The same kind of thing it was given, transpiled.

QCut.transpile_experiments(cut_experiment: CutExperiment, backend, optimization_level: int = 3, transpile_options: dict | None = None, use_iqm_transpiler: bool = True) CutExperiment[source]#

Transpile experiment circuits. Transpiles all generated experiment circuits for a given backend. Most often one should use transpile_subcircuits instead, as that only transpiles subcircuits before experiment generation which is a lot more efficient. This function is mainly provided for special cases where one needs/wants extra control over the transpilation of experiment circuits.

As with transpile_subcircuits(), an IQM backend is handed to IQM’s own transpiler when the adapter is installed, with the same defaults inverted and for the same reasons. No placeholders are left at this point, so nothing has to be hidden from it as barriers – and nothing has to give up IQM’s single-qubit optimisation either, which is why this route produces the shallower circuits of the two, at the cost of transpiling every experiment circuit rather than each subcircuit once. The layout still has to be recorded afterwards.

Parameters:
  • cut_experiment – (CutExperiment): Experiment circuits to be transpiled.

  • backend (str) – Backend to transpile to.

  • optimization_level (int) – Optimization level for transpilation (0-3).

  • transpile_options (dict) – Arguments passed to the transpiler.

  • use_iqm_transpiler (bool) – Whether an IQM backend may use IQM’s transpiler. Pass False for the ordinary qiskit path.

Returns:

Transpiled experiment circuits wrapped in CutExperiment class.

Return type:

CutExperiment

QCut.transpile_subcircuits(cut_circuit: CutCircuit, backend, optimization_level: int = 3, transpile_options: dict | None = None, use_iqm_transpiler: bool = True) CutCircuit[source]#

Transpile subcircuits for a given backend. More efficient than transpiling experiment circuits as it only transpiles each subcircuit once instead of each experiment circuit. However, may lead to suboptimal transpilation results as the transpiler cannot use the backend object directly due to need to retain some placeholder gates for cuts and observables. transpile_options can be used to pass additional options to the transpiler. For more control over transpilation of experiment circuits, use transpile_experiments or manually transpile them.

On an IQM backend this gives up IQM’s single-qubit optimisation, which would otherwise commute Z rotations across the cut placeholders and put them on the wrong side of a cut. Nothing can be told to leave the placeholders alone – a barrier is a scheduling directive there, not an algebraic wall – so circuits come out perhaps a fifth deeper than they need to be. transpile_experiments() has no placeholders left to protect and keeps the optimisation, so it is the one to use when the depth matters more than the transpilation time.

Parameters:
  • cut_circuit (CutCircuit) – The split circuit whose subcircuits are transpiled.

  • backend – Backend to transpile to.

  • optimization_level (int) – Optimization level for transpilation (0-3).

  • transpile_options (dict) – Arguments passed to the transpiler.

  • use_iqm_transpiler (bool) – Whether an IQM backend may use IQM’s transpiler. Pass False for the ordinary qiskit path.

Returns:

Transpiled subcircuits wrapped in CutCircuit class.

Return type:

CutCircuit

QCut.estimate_expectation_values(results: RawResult) ndarray[source]#

Calculate the estimated expectation values.

The estimate is the quasiprobability sum itself,

\[\langle O \rangle = (-1)^{w+1} \sum_g c_g E_g\]

over the subcircuit groups, where \(c_g\) is the group’s coefficient, \(w\) counts the wire cuts and \(E_g\) is the group’s own estimate: over the outcomes of its subcircuits, the product of their probabilities times the observable’s eigenvalue times the sign the mid-circuit measurements carry. The parity is the qpd register’s sign convention: every one of its bits maps 0 to -1, so an unwritten bit contributes -1 and only the number allocated per subcircuit survives.

Multi-qubit observables pick up a further \((-1)^{m+1}\) for their \(m\) qubits.

\(E_g\) factorises over the subcircuits, so each is read once per group rather than walking the product of their outcomes, and the observables sharing a measurement setting are read together rather than one pass each. See _outcome_spectrum().

Parameters:

results (RawResult) – raw results from experiment circuits, carrying the experiment they came from.

Returns:

one expectation value per observable, shaped like the observables the experiment was built with, so a single observable comes back as a zero-dimensional array

Return type:

np.ndarray

QCut.estimate_run(cut_experiment: CutExperiment, shots: int = 4096, backend=None, max_batch_size: int = 100) RunEstimate[source]#

Work out what run_experiments() will cost, without submitting anything.

Exact for an experiment that runs in one go. One whose wire cuts communicate runs in waves, and every wave after the first spends its shots in proportion to what the one before it measured, so only that first wave is known in advance. The rest is worked out as if every group got an equal share. That gives the most circuits a wave can submit, since a group whose label never came up submits none, and the fewest jobs it can take, since an uneven share puts circuits wanting very different shot counts in jobs of their own.

Parameters:
  • cut_experiment (CutExperiment) – the experiment circuits that would be run.

  • shots (int) – shots per circuit, as passed to run_experiments().

  • backend – the backend or sampler that would run it, read only for the most shots it takes in one job.

  • max_batch_size (int) – maximum number of circuits submitted per run call.

Returns:

the circuits, jobs and shots the run takes, and a breakdown by job.

Return type:

RunEstimate

QCut.get_experiment_circuits(cut_circuit: CutCircuit, observables: str | Pauli | SparsePauliOp | SparseObservable | Mapping[str | Pauli, float] | _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | _NestedSequence[complex | bytes | str] | None = None, qubits: list[int] | None = None) CutExperiment[source]#

Generate experiment circuits by inserting QPD operations on measure/initialize/cutCZ nodes.

Parameters:
  • cut_circuit (CutCircuit) – The cut circuit to generate experiment circuits for.

  • observables – the observables to estimate, taken the way qiskit’s estimator takes them – a label, a Pauli, a SparsePauliOp, a SparseObservable, a {label: coefficient} mapping, or any nested sequence of those. The expectation values come back shaped like what is given here.

  • qubits (list[int]) – The qubits to measure.

  • provided. (One of observables or qubits must be)

Returns:

An object containing the generated experiment circuits and related information.

Return type:

CutExperiment

QCut.get_locations_and_subcircuits(circuit: QuantumCircuit, max_qubits: list[int] | None = None, options: CutOptions | None = None) CutCircuit[source]#

Get cut locations and subcircuits with placeholder operations.

Parameters:
  • circuit (QuantumCircuit) – circuit with cuts inserted

  • max_qubits (list[int], optional) – list of maximum qubits per subcircuit when using automatic cut finding. If None, no constraint is used. Defaults to None. In general it is not necessary to manually specify this parameter.

  • options (CutOptions, optional) – configuration for the run. Defaults to QCut.options.DEFAULT_OPTIONS.

Returns:

the subcircuits, the cut locations, and the map of subcircuit qubit indices to original circuit qubit indices.

Return type:

CutCircuit

QCut.run(circuit: QuantumCircuit, observables: str | Pauli | SparsePauliOp | SparseObservable | Mapping[str | Pauli, float] | _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | _NestedSequence[complex | bytes | str] | None = None, backend=AerSimulator('aer_simulator'), max_batch_size: int = 100, options: CutOptions | None = None, shots: int = 4096, qubits: list[int] | None = None, run_options: dict | None = None) ndarray | QuasiProbabilities[source]#

Run the whole circuit knitting sequence with one function call.

Parameters:
  • circuit (QuantumCircuit) – circuit with cut experiments

  • observables – the observables to estimate, taken as qiskit’s estimator takes them. See get_experiment_circuits().

  • backend – backend to use for running experiment circuits (optional)

  • max_batch_size (int) – maximum number of circuits submitted per backend.run call (optional)

  • options (CutOptions) – configuration for the run (optional)

  • shots (int) – number of shots per circuit run, as in run_experiments() (optional)

  • qubits (list[int]) – the qubits to reconstruct a distribution over, instead of estimating observables (optional)

  • run_options (dict) – passed on to the backend or sampler, as in run_experiments() (optional)

  • provided. (One of observables or qubits must be)

Returns:

one expectation value per observable, shaped like the observables given.A single observable comes back as a zero-dimensional array or, when given qubits, the reconstructed distribution over them.

Return type:

np.ndarray | QuasiProbabilities

QCut.run_cut_circuit(cut_circuit: CutCircuit, observables: str | Pauli | SparsePauliOp | SparseObservable | Mapping[str | Pauli, float] | _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | _NestedSequence[complex | bytes | str] | None = None, backend=AerSimulator('aer_simulator'), max_batch_size: int = 100, options: CutOptions | None = None, shots: int = 4096, qubits: list[int] | None = None, run_options: dict | None = None) ndarray | QuasiProbabilities[source]#

After splitting the circuit run the rest of the circuit knitting sequence.

Parameters:
  • cut_circuit (CutCircuit) – the split circuit, carrying its placeholder operations and cut locations

  • observables – the observables to estimate, taken as qiskit’s estimator takes them. See get_experiment_circuits().

  • backend – backend to use for running experiment circuits (optional)

  • max_batch_size (int) – maximum number of circuits submitted per backend.run call (optional)

  • options (CutOptions) – configuration, overriding what the CutCircuit carries (optional)

  • shots (int) – number of shots per circuit run, as in run_experiments() (optional)

  • qubits (list[int]) – the qubits to reconstruct a distribution over, instead of estimating observables (optional)

  • run_options (dict) – passed on to the backend or sampler, as in run_experiments() (optional)

  • provided. (One of observables or qubits must be)

Returns:

one expectation value per observable, shaped like the observables given. A single observable comes back as a zero-dimensional array or, when given qubits, the reconstructed distribution over them.

Return type:

np.ndarray | QuasiProbabilities

QCut.run_experiments(cut_experiment: CutExperiment, shots: int = 4096, backend=None, max_batch_size: int = 100, run_options: dict | None = None) RawResult[source]#

Run experiment circuits.

Loop through experiment circuits and then loop through circuit group and run each circuit. Store results as [group0, group1, …] where group is [res0, res1, …]. where res is “xxx yy”: count xxx are the measurements from the end of circuit measurements on the meas classical register and yy are the qpd basis measurement results from the qpd_meas class register.

Parameters:
  • cut_experiment (CutExperiment) – the experiment circuits to run

  • shots (int) – number of shots per circuit run (optional). Communicating wire cuts spend it differently: the waves split a total of shots times the number of groups per subcircuit between them, in proportion to how often each label came up, so individual circuits run at very different counts and only that total is fixed. See _run_communicating().

  • backend – backend or V2 sampler used for running the circuits. A sampler is run through its own interface and its results are read per register, so the circuits must already be in its target’s basis.

  • max_batch_size (int) – maximum number of circuits submitted per backend.run call. Larger batches reduce per-job overhead on real hardware. A target that batches on its own account, as e.g. fiqci-ems does, is given this size too, so it does not split a batch QCut has already sized.

  • run_options (dict) – passed on to every run call. Use it for whatever the target takes per run, since QCut sets only shots itself.

Returns:

the raw counts, carrying the experiment they came from so that QCut.estimate_expectation_values() can be called on them alone

Return type:

RawResult

QCut.cut() QuantumCircuit | Instruction[source]#

Return a single qubit wire cut instruction.

QCut.cutCZ() QuantumCircuit | Instruction[source]#

Return a two qubit cutCZ gate instruction.

QCut.cutSWAP() QuantumCircuit | Instruction[source]#

Return a two qubit cutSWAP gate instruction.

QCut.cutISWAP() QuantumCircuit | Instruction[source]#

Return a two qubit cutISWAP gate instruction.

QCut.cutGate(gate: Gate, control: int | list[int], target: int | list[int], single_qubit_basis: list[str] | None = None) dict[source]#

Return a CutGate circuit decomposing the input gate with QPD cut markers.

control and target together define the ordered physical qubit mapping for gate inputs 0..n-1: gate input i maps to physical qubit (controls + targets)[i].

A two-qubit gate becomes a single CutTwoQubitGate marker, so its QPD is generated from its matrix at expansion time. Larger gates are still transpiled into the {u, cz, swap, iswap} basis first, which turns them into several cuts.

Parameters:
  • gate (Gate) – the gate to cut.

  • control (int | list[int]) – qubit, or qubits, the gate’s first inputs map to.

  • target (int | list[int]) – qubit, or qubits, its remaining inputs map to.

  • single_qubit_basis (list[str]) – basis the single-qubit parts are translated into, if the default is not wanted (optional).

Returns:

keyword arguments for QuantumCircuit.append, so the call site reads circuit.append(**cutGate(...)).

Return type:

dict

QCut.find_cuts(circuit, options: CutOptions | None = None)[source]#

Partition a quantum circuit into subcircuits by inserting cut operations.

Converts the input circuit into a graph representation and partitions it into the specified number of subcircuits using METIS partitioning. Optionally refines the partitioning to respect maximum qubit constraints per subcircuit. Cut operations are inserted at the partition boundaries, and the resulting subcircuits and their mappings are returned.

Parameters:
  • circuit (QuantumCircuit) – The input quantum circuit to partition.

  • options (CutOptions, optional) – configuration for the run. Defaults to QCut.options.DEFAULT_OPTIONS.

Returns:

A circuit with cut operations inserted, ready for decomposition into subcircuits.

Return type:

CutCircuit

class QCut.CutOptions(consolidate: Literal['auto', 'always', 'never'] | bool = 'auto', joint_rotation_cuts: bool = True, wire_cut_communication: Literal['auto', 'always', 'never'] | bool = 'auto', expansion: Literal['auto', 'exact', 'sample'] = 'auto', max_exact_groups: int = 1000, num_samples: int | None = None, seed: int | None = None, finder_candidates: int = 5, finder_cut_mode: Literal['both', 'wire', 'gate'] = 'both', finder_max_qubits: int | list[int] | None = None, finder_num_partitions: int | None = None)[source]#

Bases: object

Configuration for a cutting run.

consolidate merges runs of gates acting on the same qubit pair into one gate before cutting, so the pair costs one cut instead of several. See ConsolidateStrategy. Merging can cost more than it saves once joint cutting is in play, because a merged run of gates about different axes is no longer a single-axis rotation and cannot be bundled, so "auto" compares the two plans outright rather than guessing.

joint_rotation_cuts cuts parallel two-qubit rotation gates with one joint decomposition instead of one each, which costs strictly less in both sampling overhead and circuit count.

wire_cut_communication lets the two sides of a wire cut exchange the measured outcome, which lowers the overhead of a block of n parallel wires from 4**n to 2**(n+1) - 1. It makes those experiments run in two phases, since the state one side prepares depends on what the other measured. See CommunicationStrategy.

expansion decides how the decomposition becomes experiment circuits, see ExpansionStrategy. Under "auto", max_exact_groups is the largest exact group count still enumerated rather than sampled. When sampling, num_samples draws are taken, defaulting to max_exact_groups, and seed fixes the sampler for a reproducible experiment set.

finder_candidates is how many candidate partitions the cut finder generates and costs before keeping the cheapest. METIS returns only the partitioning that minimises its own objective, the weighted edge cut, which stops being the true cost once cuts share a decomposition, so several are generated and compared on what they actually cost. Under a qubit budget they vary by how much imbalance METIS is allowed before they vary by seed, since that changes the partition more. Both are fixed, which makes the finder reproducible; seed shifts the seeds as a set.

finder_cut_mode limits the finder to wire cuts, to gate cuts, or lets it use both. finder_num_partitions asks for a number of pieces and finder_max_qubits for a size limit per piece, either one number for all of them or a list with one entry per partition. With neither given the finder splits in two.

consolidate: Literal['auto', 'always', 'never'] | bool = 'auto'#
joint_rotation_cuts: bool = True#
wire_cut_communication: Literal['auto', 'always', 'never'] | bool = 'auto'#
expansion: Literal['auto', 'exact', 'sample'] = 'auto'#
max_exact_groups: int = 1000#
num_samples: int | None = None#
seed: int | None = None#
finder_candidates: int = 5#
finder_cut_mode: Literal['both', 'wire', 'gate'] = 'both'#
finder_max_qubits: int | list[int] | None = None#
finder_num_partitions: int | None = None#
property consolidate_mode: str#

The consolidation strategy, with True and False normalised.

property min_communicating_block: int#

Smallest wire cut block that will use classical communication.

property sample_count: int#

Number of draws to use when sampling.

property num_partitions: int#

Number of partitions to cut the circuit into.

property max_qubits: list[int] | None#
should_sample(exact_groups: int) bool[source]#

Whether an experiment of exact_groups combinations should be sampled.

replace(**changes) CutOptions[source]#

Return a copy with changes applied.

__init__(consolidate: Literal['auto', 'always', 'never'] | bool = 'auto', joint_rotation_cuts: bool = True, wire_cut_communication: Literal['auto', 'always', 'never'] | bool = 'auto', expansion: Literal['auto', 'exact', 'sample'] = 'auto', max_exact_groups: int = 1000, num_samples: int | None = None, seed: int | None = None, finder_candidates: int = 5, finder_cut_mode: Literal['both', 'wire', 'gate'] = 'both', finder_max_qubits: int | list[int] | None = None, finder_num_partitions: int | None = None) None#
QCut.consolidate_two_qubit_blocks(circuit: QuantumCircuit, restrict_to: set[frozenset] | None = None) QuantumCircuit[source]#

Merge runs of gates acting on the same qubit pair into single unitaries.

Parameters:
  • circuit – circuit to rewrite. Two-qubit cut markers are unwrapped, merged and marked again. Wire cut markers end a run and are left in place.

  • restrict_to – if given, only these qubit pairs are considered. Pass the marked pairs to leave gates alone that were never going to be cut.

Returns:

An equivalent circuit. Returns the input unchanged if nothing was worth merging.

QCut.estimate_probabilities(result: RawResult) QuasiProbabilities[source]#

Reconstruct the distribution over the qubits the experiment measured.

The circuits do not grow with the number of qubits asked for. Z observables all commute, so they share one measurement setting and the experiment is the size it would have been for a single observable. Neither does the reading. What comes back is held as a product over the subcircuits rather than as a table, so building it costs nothing in k – see _separable_form(). Only asking for every bitstring at once does, and QuasiProbabilities.top(), QuasiProbabilities.probability_of() and QuasiProbabilities.marginal() answer without doing that.

The bitstrings are written with the first of the chosen qubits last, so a subset given as [2, 0] reads qubit 2 as the rightmost character.

Parameters:

result (RawResult) – results of an experiment built with qubits.

Returns:

the distribution, which also offers the projected distribution and a scaling to counts.

Return type:

QuasiProbabilities

Raises:

ValueError – the experiment was built with observables rather than qubits, so it does not carry the full set this needs.

class QCut.QuasiProbabilities(data, shots: int | None = None)[source]#

Bases: Mapping

A reconstructed distribution over bitstrings.

A mapping from bitstring to quasi-probability, so it indexes, iterates and plots like a dict. The values are held as a product over the subcircuits, which is what lets top(), probability_of() and marginal() answer without ever building the 2**k entries the mapping would have. Iterating, values() and items() do build them, once, and keep them.

The three dict views, quasi_probabilities(), nearest_probabilities() and counts(), report the DEFAULT_TOP most likely bitstrings unless given a top of their own, since a distribution over more than a few qubits has more entries than is usually needed. top=None asks for all of them. Only quasi_probabilities() gets out of building them all the other two project onto the nearest physical distribution first, and what that projection is depends on every value.

shots#

what the experiment ran at, carried so counts() has a scale to work with by default.

Type:

int | None

qubits#

the qubits the distribution spans, in the order their bits are read back. The first is the last character of a key.

Type:

list[int]

__init__(data, shots: int | None = None)[source]#

Init from a mapping of bitstring to value, which must cover every bitstring.

Parameters:
  • data (Mapping[str, float]) – the quasi-probabilities, keyed by bitstring.

  • shots (int) – what the experiment ran at, if it is known.

classmethod from_distribution(distribution: SeparableDistribution, shots: int | None = None, qubits: list[int] | None = None) QuasiProbabilities[source]#

Wrap a separable distribution without expanding it.

Parameters:
  • distribution (SeparableDistribution) – the reconstruction, as _separable_form() builds it.

  • shots (int) – what the experiment ran at, if it is known.

  • qubits (list[int]) – the qubits it spans, in the order their bits are read back. Defaults to naming the bits themselves.

Returns:

the distribution, nothing materialised.

Return type:

QuasiProbabilities

classmethod from_array(values: ndarray, shots: int | None = None, qubits: list[int] | None = None) QuasiProbabilities[source]#

Wrap values already worked out, indexed by outcome.

Parameters:
  • values (np.ndarray) – 2**k quasi-probabilities, entry x being the bitstring format(x, f"0{k}b").

  • shots (int) – what the experiment ran at, if it is known.

  • qubits (list[int]) – the qubits it spans, in the order their bits are read back. Defaults to naming the bits themselves.

Returns:

the distribution.

Return type:

QuasiProbabilities

property width: int#

How many qubits the distribution spans.

probabilities() ndarray[source]#

Every value at once.

This is the one accessor that holds 2**width values. It is built on the first call and kept, so asking twice costs once.

Returns:

2**width quasi-probabilities, entry x being the bitstring format(x, f"0{width}b").

Return type:

np.ndarray

probability_of(bitstring: str) float[source]#

One bitstring’s quasi-probability, without building the rest.

Costs one pass over the experiment’s groups per subcircuit, so it does not grow with how many qubits the distribution spans.

Parameters:

bitstring (str) – the bitstring, first chosen qubit last.

Returns:

its quasi-probability, which may be negative.

Return type:

float

Raises:

KeyError – the bitstring is not one of this distribution’s.

top(count: int) dict[str, float][source]#

The most likely bitstrings, without building the rest.

Parameters:

count (int) – how many bitstrings to return. Capped at 2**width.

Returns:

the bitstrings and their quasi-probabilities, most likely first. Values may be negative and are not projected – projection needs the whole distribution, so it is only offered by nearest_probabilities().

Return type:

dict[str, float]

Raises:

ValueErrorcount is not positive.

marginal(qubits: list[int]) QuasiProbabilities[source]#

The distribution over a subset of the qubits, summed over the rest.

Parameters:

qubits (list[int]) – the qubits to keep, from qubits, in the order their bits should be read back.

Returns:

the marginal distribution.

Return type:

QuasiProbabilities

Raises:

ValueError – a qubit is not one this distribution spans.

quasi_probabilities(top: int | None = 10) dict[str, float][source]#

The values as reconstructed, negative ones included.

Parameters:

top (int) – how many of the most likely bitstrings to return, most likely first. None for the whole distribution, in numerical order. Capped at 2**width. Defaults to DEFAULT_TOP.

Returns:

the quasi-probabilities, keyed by bitstring.

Return type:

dict[str, float]

Raises:

ValueErrortop is neither None nor positive.

nearest_probabilities(top: int | None = 10) dict[str, float][source]#

The closest true distribution.

The projection is over the whole distribution however few bitstrings are asked for, since the mass clipped off the negative entries has to go somewhere. So top sets how much is reported, not how much is computed.

With top=None the bitstrings clipped to zero are kept, so the result covers the same keys as the quasi-probabilities it came from.

Parameters:

top (int) – how many of the most likely bitstrings to return, most likely first. None for all of them, in numerical order. Defaults to DEFAULT_TOP.

Returns:

the projected distribution, keyed by bitstring.

Return type:

dict[str, float]

Raises:

ValueErrortop is neither None nor positive.

counts(shots: int | None = None, top: int | None = 10) dict[str, float][source]#

The distribution scaled to a shot count.

Parameters:
  • shots (int) – what to scale by. Defaults to the shots the experiment ran at.

  • top (int) – how many of the most likely bitstrings to return, most likely first. None for all of them. Defaults to DEFAULT_TOP.

Returns:

the projected distribution scaled by shots.

Return type:

dict[str, float]

Raises:

ValueError – no shot count was carried and none was given, or top is neither None nor positive.

class QCut.RunEstimate(breakdown: dict[str, ~QCut.execution.circuit_knitting.JobEstimate]=<factory>, exact: bool = True)[source]#

Bases: object

What running an experiment will cost, before anything is submitted.

breakdown: dict[str, JobEstimate]#
exact: bool = True#
property jobs: int#

How many run calls the experiment takes.

property circuits: int#

How many circuits are submitted, over every job.

property shots: int#

How many shots the experiment spends in total.

__init__(breakdown: dict[str, ~QCut.execution.circuit_knitting.JobEstimate]=<factory>, exact: bool = True) None#
class QCut.JobEstimate(circuits: int, shots: int)[source]#

Bases: object

One run call: how many circuits it carries, at how many shots each.

circuits: int#
shots: int#
__init__(circuits: int, shots: int) None#
class QCut.RawResult(results: list[list[dict[int, CircuitResult]]], shots: int, experiment=None)[source]#

Bases: object

Simple wrapper class for raw experiment results. Stores the raw results, the shot count they were taken at, and the experiment they came from, so that QCut.estimate_expectation_values() can be called on the result alone rather than having the caller carry a second object around.

__init__(results: list[list[dict[int, CircuitResult]]], shots: int, experiment=None)[source]#
property experiment#

The experiment these results came from, if it was recorded.

property shots: int#

The shot count the experiment was run at.

result() list[list[dict[int, CircuitResult]]][source]#

Get raw results for all experiments, as [group][observable][subcircuit].

Each subcircuit holds a CircuitResult, which is what the backend or sampler returned rather than counts taken out of it. Call CircuitResult.counts() on one for its counts.

class QCut.CircuitResult(result: object, index: int = 0, scale: float = 1.0, label_filter: tuple[tuple, ...]=<factory>)[source]#

Bases: object

One subcircuit’s result within one group, before it is turned into counts.

Holds what the backend or the sampler handed back rather than counts taken out of it, so running an experiment does no arithmetic of its own and the estimate can be recomputed from the same results.

scale brings the circuit back to the nominal shot count, since a batch runs at whatever its own circuits asked for. label_filter is the communicating wire cuts’ selection: several groups share one measuring circuit and each keeps only the shots carrying the label it answers, so the selection belongs to the group’s own result rather than to the circuit they all read.

result: object#
index: int = 0#
scale: float = 1.0#
label_filter: tuple[tuple, ...]#
raw_counts() dict[str, float][source]#

Counts as measured, brought to the nominal shot count.

counts() dict[str, float][source]#

What this group takes from the circuit: rescaled, then label-selected.

__init__(result: object, index: int = 0, scale: float = 1.0, label_filter: tuple[tuple, ...]=<factory>) None#