qml.transforms.resolve_dynamic_wires¶
- resolve_dynamic_wires(tape, zeroed=(), any_state=(), min_int=None, allow_resets=True)[source]¶
Map dynamic wires to concrete values determined by the provided
zeroedandany_stateregisters.- Parameters:
tape (QuantumScript) – A circuit that may contain dynamic wire allocations and deallocations
zeroed (Sequence[Hashable]) – a register of wires known to be in the \(|0\rangle\) 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.allow_resets (boo) – Whether or not mid circuit measurements with
reset=Truecan be added to turn any state wires into zeroed wires.
- 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 (
state="zero"), we try three things before raising an error:If wires exist in the
zeroedregister, we take one from that registerIf no
zeroedwires exist and we are allowed to use resets, we pull one fromany_stateand apply a reset operationIf no wires exist in the
zeroedorany_stateregisters andmin_intis notNone, we incrementmin_intand add a new wire.
For a dynamic wire with
state="any", we try:If wires exist in the
any_stateregister, we take one from thereIf no wires exist in
any_state, we pull one fromzeroedIf no wires exist in the
zeroedorany_stateregisters andmin_intis notNone, we incrementmin_intand add a new wire
This transform uses a combination of two different modes: one with fixed registers specified by
zeroedandany_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 inzeroedandany_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(state="zero"): with qml.allocation.allocate(1, state=state) as wires: qml.X(wires) with qml.allocation.allocate(1, state=state) 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
Yoperation.>>> assigned_one_zeroed = resolve_dynamic_wires(circuit, zeroed=("a",)) >>> print(qml.draw(assigned_one_zeroed)()) a: ──X──┤↗│ │0⟩──Y─┤
This reset behavior can be turned off with
allow_resets=False.>>> no_resets = resolve_dynamic_wires(circuit, zeroed=("a",), allow_resets=False) >>> print(qml.draw(no_resets)()) AllocationError: no wires left to allocate.
If we only provide
any_statequbits 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 hadstate="any", no reset operations would occur:>>> print(qml.draw(assigned_any_state)(state="any")) b: ──X──Y─┤
Instead of registers of available wires, a
min_intcan be specified instead. Themin_intindicates 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_intare specified,min_intwill 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─┤