Circuit cutting
qiskit_addon_cutting
Circuit cutting.
cut_wires
cut_wires(circuit, /)
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 TwoQubitQPDGate
s
Return type
circuit
expand_observables
expand_observables(observables, original_circuit, final_circuit, /)
Expand observable(s) according to the qubit mapping between original_circuit
and final_circuit
.
The Qubit
s 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 tooriginal_circuit
- original_circuit (
QuantumCircuit
) – Original circuit - final_circuit (
QuantumCircuit
) – Final circuit, whose qubits the originalobservables
should be expanded to
Return type
Returns
New -qubit observables which are compatible with the -qubit final_circuit
Raises
- ValueError –
observables
andoriginal_circuit
have different number of qubits. - ValueError – Qubit from
original_circuit
cannot be found infinal_circuit
.
partition_circuit_qubits
partition_circuit_qubits(circuit, partition_labels, inplace=False)
Replace all nonlocal gates belonging to more than one partition with instances of TwoQubitQPDGate
.
TwoQubitQPDGate
s 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 withTwoQubitQPDGate
s. - inplace (
bool
) – Flag denoting whether to copy the input circuit before acting on it
Return type
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
partition_problem(circuit, partition_labels=None, observables=None)
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 TwoQubitQPDGate
s 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 SingleQubitQPDGate
s.
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
cut_gates(circuit, gate_ids, inplace=False)
Transform specified gates into TwoQubitQPDGate
s.
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 TwoQubitQPDGate
s 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
generate_cutting_experiments(circuits, observables, num_samples)
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:
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
- ValueError –
num_samples
must be at least one. - ValueError –
circuits
andobservables
are incompatible types - ValueError –
SingleQubitQPDGate
instances must have their cut ID appended to the gate label so they may be associated with other gates belonging to the same cut. - ValueError –
SingleQubitQPDGate
instances are not allowed in unseparated circuits.
reconstruct_expectation_values
reconstruct_expectation_values(results, coefficients, observables)
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 usegenerate_cutting_experiments()
to generate their experiments should take care to order their subexperiments as follows before submitting them to the sampler primitive: -
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 itsWeightType
, 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
ifresults
is aSamplerResult
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 float
s, such that each float is an expectation value corresponding to the input observable in the same position
Raises
- ValueError –
observables
andresults
are of incompatible types. - ValueError – An input observable has a phase not equal to 1.
PartitionedCuttingProblem
class PartitionedCuttingProblem(subcircuits, bases, subobservables=None)
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
Type: list[QPDBasis]
Alias for field number 1
count
count(value, /)
Return number of occurrences of value.
index
index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
subcircuits
Type: dict[Hashable, QuantumCircuit]
Alias for field number 0
subobservables
Type: dict[Hashable, PauliList] | None
Alias for field number 2
Automatic Cut Finding
find_cuts
find_cuts(circuit, optimization, constraints)
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’sdata
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.
OptimizationParameters
class OptimizationParameters(seed=None, max_gamma=1024, max_backjumps=10000, gate_lo=True, wire_lo=True)
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
Type: bool
Default value: True
Bool indicating whether or not to allow LO gate cuts while finding cuts.
max_backjumps
Type: None | int
Default value: 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
Type: float
Default value: 1024
Maximum allowed value of gamma which, if exceeded, forces the search to terminate.
seed
Type: int | None
Default value: None
The seed to use when initializing Numpy random number generators in the best first search priority queue.
wire_lo
Type: bool
Default value: True
Bool indicating whether or not to allow LO wire cuts while finding cuts.
DeviceConstraints
class DeviceConstraints(qubits_per_subcircuit)
Bases: object
Specify the constraints (qubits per subcircuit) that must be respected.
Parameters
qubits_per_subcircuit (int)