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 and any_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:

  1. If wires exist in the zeroed register, we take one from that register

  2. If no zeroed wires exist, we pull one from any_state and apply a reset operation

  3. If no wires exist in the zeroed or any_state registers and min_int is not None, we increment min_int and add a new wire.

For a dynamic wire with require_zeros=False, we try:

  1. If wires exist in the any_state, we take one from that register

  2. If no wires exist in any_state, we pull one from zeroed

  3. If no wires exist in the zeroed or any_state registers and min_int is not None, we increment min_int and add a new wire

This transform uses a combination of two different modes: one with fixed registers specified by zeroed and any_state, and one with a dynamically sized register characterized by the integer min_int. We assume that the upfront cost associated with using more wires has already been paid for anything in zeroed and any_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 had require_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. The min_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 from 0.

>>> 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─┤