qml.queuing

Overview

Warning

Unless you are a PennyLane developer, you likely do not need to use these classes directly.

This module contains the classes for placing objects into queues.

Description

Users provide quantum functions which PennyLane needs to convert into a circuit representation capable of being executed by a device. A quantum function is any callable that:

For example:

def qfunc(x, scale_value=1):
    qml.RX(x * scale_value, wires=0)
    if (1 != 2):
        qml.S(0)
    return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliX(1))

To convert from a quantum function to a representation of a circuit, we use queuing.

A queuable object is anything that can be placed into a queue. These will be Operator, MeasurementProcess, and QuantumTape objects. Operator and MeasurementProcess objects achieve queuing via a queue() method called upon construction. Note that even though QuantumTape is a queuable object, it does not have a queue method.

When an object is queued, it sends itself to the QueuingManager. The QueuingManager is a global singleton class that facilitates placing objects in the queue. All of QueuingManager’s methods and properties are class methods and properties, so all instances will access the same information.

The active_context() is the queue where any new objects are placed. The QueuingManager is said to be recording if an active context exists.

Active contexts are AnnotatedQueue instances. They are context managers where recording occurs within a with block.

Let’s take a look at an example. If we query the QueuingManager outside of an AnnotatedQueue’s context, we can see that nothing is recording and no active context exists.

>>> print("Are we recording? ", qml.QueuingManager.recording())
Are we recording?  False
>>> print("What's the active context? ", qml.QueuingManager.active_context())
What's the active context?  None

Inside of a context, we can see the active recording context:

>>> with qml.queuing.AnnotatedQueue() as q:
...     print("Are we recording? ", qml.QueuingManager.recording())
...     print("Is q the active queue? ", q is qml.QueuingManager.active_context())
Are we recording?  True
Is q the active queue?  True

If we have nested AnnotatedQueue contexts, only the innermost one will be recording. Once the currently active queue exits, any outer queue will resume recording.

>>> with qml.queuing.AnnotatedQueue() as q1:
...     print("Is q1 recording? ", q1 is qml.QueuingManager.active_context())
...     with qml.queuing.AnnotatedQueue() as q2:
...         print("Is q1 recording? ", q1 is qml.QueuingManager.active_context())
...     print("Is q1 recording? ", q1 is qml.QueuingManager.active_context())
Is q1 recording?  True
Is q1 recording?  False
Is q1 recording?  True

If we construct an operator inside the recording context, we can see it is added to the queue:

>>> with qml.queuing.AnnotatedQueue() as q:
...     op = qml.PauliX(0)
>>> q.queue
[PauliX(wires=[0])]

If an operator is constructed outside of the context, we can manually add it to the queue by calling the queue() method. The queue() method is automatically called upon initialization, but it can also be manually called at a later time.

>>> op = qml.PauliX(0)
>>> with qml.queuing.AnnotatedQueue() as q:
...     op.queue()
>>> q.queue
[PauliX(wires=[0])]

An object can only exist up to once in the queue, so calling queue multiple times will not do anything.

>>> op = qml.PauliX(0)
>>> with qml.queuing.AnnotatedQueue() as q:
...     op.queue()
...     op.queue()
>>> q.queue
[PauliX(wires=[0])]

The apply() method allows a single object to be queued multiple times in a circuit. The function queues a copy of the original object if it already in the queue.

>>> op = qml.PauliX(0)
>>> with qml.queuing.AnnotatedQueue() as q:
...     qml.apply(op)
...     qml.apply(op)
>>> q.queue
[PauliX(wires=[0]), PauliX(wires=[0])]
>>> q.queue[0] is q.queue[1]
False

In the case of operators composed of other operators, each operator in the composite will be queued. For example, both PauliX and PauliX raised to a power.

>>> with qml.queuing.AnnotatedQueue() as q:
...     base = qml.PauliX(0)
...     pow_op = base ** 1.5
>>> q.queue
[PauliX(wires=[0]), PauliX(wires=[0])**1.5]

In this case, each object will have metadata associated with it. At later processing steps, the original PauliX will be ignored in favor of the operator that owns it.

>>> q.get_info(base)
{'owner': PauliX(wires=[0])**1.5}
>>> q.get_info(base)["owner"] is pow_op
True

Once the queue is constructed, the process_queue() function converts it into the operations, measurements, and state prep present in the final circuit. This step eliminates any object that has an owner.

>>> with qml.queuing.AnnotatedQueue() as q:
...     qml.QubitStateVector(np.array([1.0, 0]), wires=0)
...     base = qml.PauliX(0)
...     pow_op = base ** 1.5
...     qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
>>> ops, measurements, prep = qml.queuing.process_queue(q)
>>> ops
[PauliX(wires=[0])**1.5]
>>> measurements
[expval(PauliZ(wires=[0]) @ PauliX(wires=[1]))]
>>> prep
[QubitStateVector(tensor([1., 0.], requires_grad=True), wires=[0])]

These three lists can be used to construct a QuantumScript:

>>> qml.tape.QuantumScript(ops, measurements, prep)
<QuantumScript: wires=[0, 1], params=1>

In order to construct new operators within a recording, but without queuing them, either use the stop_recording() context or specify do_queue=False upon construction:

>>> with qml.queuing.AnnotatedQueue() as q:
...     qml.PauliX(0, do_queue=False)
...     with qml.QueuingManager.stop_recording():
...         qml.PauliY(1)
>>> q.queue
[]

Functions

apply(op[, context])

Apply an instantiated operator or measurement to a queuing context.

process_queue(queue)

Process the annotated queue, creating a list of quantum operations and measurement processes.

Classes

AnnotatedQueue

Lightweight class that maintains a basic queue of operations, in addition to metadata annotations.

QueuingError

Exception that is raised when there is a queuing error

QueuingManager()

Singleton global entry point for managing active recording contexts.