Circuit cutting (qiskit_addon_cutting)

Circuit cutting.

cut_wires(circuit, /)[source]

Transform all CutWire instructions in a circuit to Move instructions marked for cutting.

The returned circuit will have one newly allocated qubit for every CutWire instruction.

See Sec. 3 and Appendix A of 2302.03366v1 for more information about the two different representations of wire cuts: single-qubit (CutWire) vs. two-qubit (Move).

Parameters:

circuit (QuantumCircuit) – Original circuit with CutWire instructions

Returns:

New circuit with CutWire instructions replaced by Move instructions wrapped in TwoQubitQPDGates

Return type:

circuit

expand_observables(observables, original_circuit, final_circuit, /)[source]

Expand observable(s) according to the qubit mapping between original_circuit and final_circuit.

The Qubits on final_circuit must be a superset of those on original_circuit.

Given a PauliList of observables, this function returns new observables with identity operators placed on the qubits that did not exist in original_circuit. This way, observables on original_circuit can be mapped to appropriate observables on final_circuit.

This function is designed to be used after calling final_circuit = transform_cuts_to_moves(original_circuit) (see transform_cuts_to_moves()).

This function requires observables.num_qubits == original_circuit.num_qubits and returns new observables such that retval.num_qubits == final_circuit.num_qubits.

Parameters:
  • observables (PauliList) – Observables corresponding to original_circuit

  • original_circuit (QuantumCircuit) – Original circuit

  • final_circuit (QuantumCircuit) – Final circuit, whose qubits the original observables should be expanded to

Return type:

PauliList

Returns:

New \(N\)-qubit observables which are compatible with the \(N\)-qubit final_circuit

Raises:
  • ValueErrorobservables and original_circuit have different number of qubits.

  • ValueError – Qubit from original_circuit cannot be found in final_circuit.

partition_circuit_qubits(circuit, partition_labels, inplace=False)[source]

Replace all nonlocal gates belonging to more than one partition with instances of TwoQubitQPDGate.

TwoQubitQPDGates belonging to a single partition will not be affected.

Parameters:
  • circuit (QuantumCircuit) – The circuit to partition

  • partition_labels (Sequence[Hashable]) – A sequence containing a partition label for each qubit in the input circuit. Nonlocal gates belonging to more than one partition will be replaced with TwoQubitQPDGates.

  • inplace (bool) – Flag denoting whether to copy the input circuit before acting on it

Return type:

QuantumCircuit

Returns:

The output circuit with each nonlocal gate spanning two partitions replaced by a TwoQubitQPDGate

Raises:
  • ValueError – The length of partition_labels does not equal the number of qubits in the circuit.

  • ValueError – Input circuit contains unsupported gate.

partition_problem(circuit, partition_labels=None, observables=None)[source]

Separate an input circuit and observable(s).

If partition_labels is provided, then qubits with matching partition labels will be grouped together, and non-local gates spanning more than one partition will be cut apart. The label None is treated specially: any qubit with that partition label must be unused in the circuit.

If partition_labels is not provided, then it will be determined automatically from the connectivity of the circuit. This automatic determination ignores any TwoQubitQPDGates in the circuit, as these denote instructions that are explicitly destined for cutting. The resulting partition labels, in the automatic case, will be consecutive integers starting with 0. Qubits which are idle throughout the circuit will be assigned a partition label of None.

All cut instructions will be replaced with SingleQubitQPDGates.

If provided, observables will be separated along the boundaries specified by the partition labels.

Parameters:
  • circuit (QuantumCircuit) – The circuit to partition and separate

  • partition_labels (Sequence[Hashable] | None) – A sequence of labels, such that each label corresponds to the circuit qubit with the same index

  • observables (PauliList | None) – The observables to separate

Return type:

PartitionedCuttingProblem

Returns:

A tuple containing a dictionary mapping a partition label to a subcircuit, a list of QPD bases (one for each circuit gate or wire which was decomposed), and, optionally, a dictionary mapping a partition label to a list of Pauli observables.

Raises:
  • ValueError – The number of partition labels does not equal the number of qubits in the circuit.

  • ValueError – An input observable acts on a different number of qubits than the input circuit.

  • ValueError – An input observable has a phase not equal to 1.

  • ValueError – A qubit with a label of None is not idle

  • ValueError – The input circuit should contain no classical bits or registers.

cut_gates(circuit, gate_ids, inplace=False)[source]

Transform specified gates into TwoQubitQPDGates.

Parameters:
  • circuit (QuantumCircuit) – The circuit containing gates to be decomposed

  • gate_ids (Sequence[int]) – The indices of the gates to decompose

  • inplace (bool) – Flag denoting whether to copy the input circuit before acting on it

Return type:

tuple[QuantumCircuit, list[QPDBasis]]

Returns:

A copy of the input circuit with the specified gates replaced with TwoQubitQPDGates and a list of QPDBasis instances – one for each decomposed gate.

Raises:

ValueError – The input circuit should contain no classical bits or registers.

generate_cutting_experiments(circuits, observables, num_samples)[source]

Generate cutting subexperiments and their associated coefficients.

If the input, circuits, is a QuantumCircuit instance, the output subexperiments will be contained within a 1D array, and observables is expected to be a PauliList instance.

If the input circuit and observables are specified by dictionaries with partition labels as keys, the output subexperiments will be returned as a dictionary which maps each partition label to a 1D array containing the subexperiments associated with that partition.

In both cases, the subexperiment lists are ordered as follows:

\([sample_{0}observable_{0}, \ldots, sample_{0}observable_{N-1}, sample_{1}observable_{0}, \ldots, sample_{M-1}observable_{N-1}]\)

The coefficients will always be returned as a 1D array – one coefficient for each unique sample.

Parameters:
  • circuits (QuantumCircuit | dict[Hashable, QuantumCircuit]) – The circuit(s) to partition and separate

  • observables (PauliList | dict[Hashable, PauliList]) – The observable(s) to evaluate for each unique sample

  • num_samples (int | float) – The number of samples to draw from the quasi-probability distribution. If set to infinity, the weights will be generated rigorously rather than by sampling from the distribution.

Return type:

tuple[list[QuantumCircuit] | dict[Hashable, list[QuantumCircuit]], list[tuple[float, WeightType]]]

Returns:

A tuple containing the cutting experiments and their associated coefficients. If the input circuits is a QuantumCircuit instance, the output subexperiments will be a sequence of circuits – one for every unique sample and observable. If the input circuits are represented as a dictionary keyed by partition labels, the output subexperiments will also be a dictionary keyed by partition labels and containing the subexperiments for each partition. The coefficients are always a sequence of length-2 tuples, where each tuple contains the coefficient and the WeightType. Each coefficient corresponds to one unique sample.

Raises:
  • ValueErrornum_samples must be at least one.

  • ValueErrorcircuits and observables are incompatible types

  • ValueErrorSingleQubitQPDGate instances must have their cut ID appended to the gate label so they may be associated with other gates belonging to the same cut.

  • ValueErrorSingleQubitQPDGate instances are not allowed in unseparated circuits.

reconstruct_expectation_values(results, coefficients, observables)[source]

Reconstruct an expectation value from the results of the sub-experiments.

Parameters:
  • results (SamplerResult | PrimitiveResult | dict[Hashable, SamplerResult | PrimitiveResult]) –

    The results from running the cutting subexperiments. If the cut circuit was not partitioned between qubits and run separately, this argument should be a SamplerResult instance or a dictionary mapping a single partition to the results. If the circuit was partitioned and its pieces were run separately, this argument should be a dictionary mapping partition labels to the results from each partition’s subexperiments.

    The subexperiment results are expected to be ordered in the same way the subexperiments are ordered in the output of generate_cutting_experiments() – one result for every sample and observable, as shown below. The Qiskit Sampler primitive will return the results in the same order the experiments are submitted, so users who do not use generate_cutting_experiments() to generate their experiments should take care to order their subexperiments as follows before submitting them to the sampler primitive:

    \([sample_{0}observable_{0}, \ldots, sample_{0}observable_{N-1}, sample_{1}observable_{0}, \ldots, sample_{M-1}observable_{N-1}]\)

  • coefficients (Sequence[tuple[float, WeightType]]) – A sequence containing the coefficient associated with each unique subexperiment. Each element is a tuple containing the coefficient (a float) together with its WeightType, which denotes how the value was generated. The contribution from each subexperiment will be multiplied by its corresponding coefficient, and the resulting terms will be summed to obtain the reconstructed expectation value.

  • observables (PauliList | dict[Hashable, PauliList]) – The observable(s) for which the expectation values will be calculated. This should be a PauliList if results is a SamplerResult instance. Otherwise, it should be a dictionary mapping partition labels to the observables associated with that partition.

Return type:

list[float]

Returns:

A list of floats, such that each float is an expectation value corresponding to the input observable in the same position

Raises:
  • ValueErrorobservables and results are of incompatible types.

  • ValueError – An input observable has a phase not equal to 1.

class PartitionedCuttingProblem(subcircuits, bases, subobservables=None)[source]

Bases: NamedTuple

The result of decomposing and separating a circuit and observable(s).

Create new instance of PartitionedCuttingProblem(subcircuits, bases, subobservables)

Parameters:
  • subcircuits (dict[Hashable, QuantumCircuit])

  • bases (list[QPDBasis])

  • subobservables (dict[Hashable, PauliList] | None)

bases: list[QPDBasis]

Alias for field number 1

count(value, /)

Return number of occurrences of value.

index(value, start=0, stop=9223372036854775807, /)

Return first index of value.

Raises ValueError if the value is not present.

subcircuits: dict[Hashable, QuantumCircuit]

Alias for field number 0

subobservables: dict[Hashable, PauliList] | None

Alias for field number 2

Automatic Cut Finding

find_cuts(circuit, optimization, constraints)[source]

Find cut locations in a circuit, given optimization parameters and cutting constraints.

Parameters:
  • circuit (QuantumCircuit) – The circuit to cut. The input circuit may not contain gates acting on more than two qubits.

  • optimization (OptimizationParameters) – Options for controlling optimizer behavior. Currently, the optimal cuts are chosen using Dijkstra’s best-first search algorithm.

  • constraints (DeviceConstraints) – Constraints on how the circuit may be partitioned

Return type:

tuple[QuantumCircuit, dict[str, float]]

Returns:

A circuit containing BaseQPDGate instances. The subcircuits resulting from cutting these gates will be runnable on the devices meeting the constraints.

A metadata dictionary:
  • cuts: A list of length-2 tuples describing each cut in the output circuit. The tuples are formatted as (cut_type: str, cut_id: int). The cut ID is the index of the cut gate or wire in the output circuit’s data field.

  • sampling_overhead: The sampling overhead incurred from cutting the specified gates and wires.

  • minimum_reached: A bool indicating whether or not the search conclusively found the minimum of cost function. minimum_reached = False could also mean that the cost returned was actually the lowest possible cost but that the search was not allowed to run long enough to prove that this was the case.

Raises:

ValueError – The input circuit contains a gate acting on more than 2 qubits.

class OptimizationParameters(seed=None, max_gamma=1024, max_backjumps=10000, gate_lo=True, wire_lo=True)[source]

Bases: object

Specify parameters that control the optimization.

If either of the constraints specified by max_backjumps or max_gamma are exceeded, the search terminates but nevertheless returns the result of a greedy best first search, which gives an upper-bound on gamma.

Parameters:
gate_lo: bool = True

Bool indicating whether or not to allow LO gate cuts while finding cuts.

max_backjumps: None | int = 10000

Maximum number of backjumps that can be performed before the search is forced to terminate; setting it to None implies that no such restriction is placed.

max_gamma: float = 1024

Maximum allowed value of gamma which, if exceeded, forces the search to terminate.

seed: int | None = None

The seed to use when initializing Numpy random number generators in the best first search priority queue.

wire_lo: bool = True

Bool indicating whether or not to allow LO wire cuts while finding cuts.

class DeviceConstraints(qubits_per_subcircuit)[source]

Bases: object

Specify the constraints (qubits per subcircuit) that must be respected.

Parameters:

qubits_per_subcircuit (int)

get_qpu_width()[source]

Return the number of qubits per subcircuit.

Return type:

int