qml.transforms.resolve_dynamic_wires¶
- resolve_dynamic_wires(tape, zeroed=(), any_state=(), min_int=None)[source]¶
Map dynamic wires to concrete values determined by the provided
zeroed
andany_state
registers.- Parameters:
tape (QuantumScript) – A circuit that may contain dynamic wire allocations and deallocations
zeroed (Sequence[Hashable]) – a register of wires known to be the zero state
any_state (Sequence[Hashable]) – a register of wires with any state
min_int (Optional[int]) – If not
None
, new wire labels can be created starting at this integer and incrementing whenever a new wire is needed.
- Returns:
A batch of tapes and a postprocessing function
- Return type:
tuple[QuantumScript], Callable[[ResultBatch], Result]
Note
This transform currently uses a “Last In, First Out” (LIFO) stack based approach to distributing wires. This minimizes the total number of wires used, at the cost of higher depth and more resets. Other approaches could be taken as well, such as a “First In, First out” algorithm that minimizes depth.
This approach also means we pop wires from the end of the stack first.
For a dynamic wire requested to be in the zero state (
require_zeros=True
), we try three things before erroring:If wires exist in the
zeroed
register, we take one from that registerIf no
zeroed
wires exist, we pull one fromany_state
and apply a reset operationIf no wires exist in the
zeroed
orany_state
registers andmin_int
is notNone
, we incrementmin_int
and add a new wire.
For a dynamic wire with
require_zeros=False
, we try:If wires exist in the
any_state
, we take one from that registerIf no wires exist in
any_state
, we pull one fromzeroed
If no wires exist in the
zeroed
orany_state
registers andmin_int
is notNone
, we incrementmin_int
and add a new wire
This transform uses a combination of two different modes: one with fixed registers specified by
zeroed
andany_state
, and one with a dynamically sized register characterized by the integermin_int
. We assume that the upfront cost associated with using more wires has already been paid for anything inzeroed
andany_state
. Whether or not we use them, they will still be there. In this case, using a fresh wire is cheaper than reset. For the dynamically sized register, we assume that we have to pay an additional cost each time we allocate a new wire. For the dynamically sized register, applying a reset operation is therefor cheaper than allocating a new wire.This approach minimizes the width of the circuit at the cost of more reset operations.
def circuit(require_zeros=True): with qml.allocation.allocate(1, require_zeros=require_zeros) as wires: qml.X(wires) with qml.allocation.allocate(1, require_zeros=require_zeros) as wires: qml.Y(wires)
>>> print(qml.draw(circuit)()) <DynamicWire>: ──Allocate──X──Deallocate─┤ <DynamicWire>: ──Allocate──Y──Deallocate─┤
If we provide two zeroed qubits to the transform, we can see that the two operations have been assigned to both wires known to be in the zero state.
>>> from pennylane.transforms import resolve_dynamic_wires >>> assigned_two_zeroed = resolve_dynamic_wires(circuit, zeroed=("a", "b")) >>> print(qml.draw(assigned_two_zeroed)()) a: ──Y─┤ b: ──X─┤
If we only provide one zeroed wire, we perform a reset on that wire before reusing for the
Y
operation.>>> assigned_one_zeroed = resolve_dynamic_wires(circuit, zeroed=("a",)) >>> print(qml.draw(assigned_one_zeroed)()) a: ──X──┤↗│ │0⟩──Y─┤
If we only provide
any_state
qubits with unknown states, then they will be reset to zero before being used in an operation that requires a zero state.>>> assigned_any_state = resolve_dynamic_wires(circuit, any_state=("a", "b")) >>> print(qml.draw(assigned_any_state)()) b: ──┤↗│ │0⟩──X──┤↗│ │0⟩──Y─|
Note that the last provided wire with label
"b"
is used first. If the wire allocations hadrequire_zeros=False
, no reset operations would occur:>>> print(qml.draw(assigned_any_state)(require_zeros=False)) b: ──X──Y─┤
Instead of registers of available wires, a
min_int
can be specified instead. Themin_int
indicates the first integer to start allocating wires to. Whenever we have no qubits available to allocate, we increment the integer and add a new wire to the pool:>>> circuit_integers = resolve_dynamic_wires(circuit, min_int=0) >>> print(qml.draw(circuit_integers)()) 0: ──X──┤↗│ │0⟩──Y─┤
Note that we still prefer using already created wires over creating new wires.
def multiple_allocations(): with qml.allocation.allocate(1) as wires: qml.X(wires) with qml.allocation.allocate(3) as wires: qml.Toffoli(wires)
>>> circuit_integers2 = resolve_dynamic_wires(multiple_allocations, min_int=0) >>> print(qml.draw(circuit_integers2)()) 0: ──X──┤↗│ │0⟩─╭●─┤ 1: ──────────────├●─┤ 2: ──────────────╰X─┤
If both an explicit register and
min_int
are specified,min_int
will be used once all available explicit wires are loaned out. Below,"a"
is extracted and used first, but then wires are extracted starting from0
.>>> zeroed_and_min_int = resolve_dynamic_wires(multiple_allocations, zeroed=("a",), min_int=0) >>> print(qml.draw(zeroed_and_min_int)()) a: ──X──┤↗│ │0⟩─╭●─┤ 0: ──────────────├●─┤ 1: ──────────────╰X─┤