Note
Click here to download the full example code
Advanced Usage¶
In the previous three introductory tutorials (qubit rotation, Gaussian transformation, and plugins & hybrid computation) we explored the basic concepts of PennyLane, including qubit- and CV-model quantum computations, gradient-based optimization, and the construction of hybrid classical-quantum computations.
In this tutorial, we will highlight some of the more advanced features of Pennylane.
Multiple measurements¶
In all the previous examples, we considered quantum functions with only single expectation values. In fact, PennyLane supports the return of multiple measurements, up to one per wire.
As usual, we begin by importing PennyLane and the PennyLane-provided version of NumPy, and set up a 2-wire qubit device for computations:
import pennylane as qml
from pennylane import numpy as np
dev = qml.device("default.qubit", wires=2)
We will start with a simple example circuit, which generates a two-qubit entangled state, then evaluates the expectation value of the Pauli Z operator on each wire.
@qml.qnode(dev)
def circuit(param):
qml.RX(param, wires=0)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
The degree of entanglement of the qubits is determined by the value of param
. For a value of
\(\frac{\pi}{2}\), they are maximally entangled. In this case, the reduced states on each
subsystem are completely mixed, and local expectation values — like those we are measuring —
will average to zero.
print(circuit(np.pi / 2))
Out:
[2.22044605e-16 2.22044605e-16]
Notice that the output of the circuit is a NumPy array with shape=(2,)
, i.e., a two-dimensional
vector. These two dimensions match the number of expectation values returned in our quantum
function circuit
.
Note
It is important to emphasize that the expectation values in circuit
are both local,
i.e., this circuit is evaluating \(\left\langle \sigma_z\right\rangle_0\) and \(\left\langle \sigma_z\right\rangle_1\),
not \(\left\langle \sigma_z\otimes \sigma_z\right\rangle_{01}\) (where the subscript
denotes which wires the observable is located on).
In order to measure a tensor-product observable like \(\langle\sigma_z \otimes \sigma_z \rangle _{01}\),
the matrix multiplication operator @
can be used:
@qml.qnode(dev)
def circuit(param):
qml.RX(param, wires=0)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
print(circuit(np.pi / 2))
Out:
1.0
Notice how this expectation value differs from the local versions above.
We may even mix different return types, for example expectation values and variances:
@qml.qnode(dev)
def circuit(param):
qml.RX(param, wires=0)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1))
Keyword arguments¶
While automatic differentiation is a handy feature, sometimes we want certain parts of our computational pipeline (e.g., the inputs \(x\) to a parameterized quantum function \(f(x;\bf{\theta})\) or the training data for a machine learning model) to not be differentiated.
PennyLane uses the pattern that all positional arguments to quantum functions are available to be differentiated, while keyword arguments are never differentiated. Thus, when using the gradient-descent-based introduction/optimizers included in PennyLane, all numerical parameters appearing in non-keyword arguments will be updated, while all numerical values included as keyword arguments will not be updated.
Note
When constructing the circuit, keyword arguments are defined by providing a
default value in the function signature. If you would prefer that the keyword argument
value be passed every time the quantum circuit function is called, the default value
can be set to None
.
For example, let’s create a quantum node that accepts two arguments; a differentiable
circuit parameter param
, and a fixed circuit parameter fixed
:
@qml.qnode(dev)
def circuit(param, fixed=None):
qml.RX(fixed, wires=0)
qml.RX(param, wires=1)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
Calling the circuit, we can feed values to the keyword argument fixed
:
Out:
[0.98006658 0.97517033]
[0.36235775 0.36054748]
Since keyword arguments do not get considered when computing gradients, the Jacobian will still be a 2-dimensional vector.
j = qml.jacobian(circuit, argnum=0)
print(j(2.5, fixed=3.2))
Out:
[5.55111512e-17 5.97451615e-01]
Once defined, keyword arguments must always be passed as keyword arguments. PennyLane does not support passing keyword argument values as positional arguments.
For example, the following circuit evaluation will correctly update the value of the fixed parameter:
print(circuit(0.1, fixed=0.4))
Out:
[0.92106099 0.91645953]
However, attempting to pass the fixed parameter as a positional argument will
not work, and PennyLane will attempt to use the default value (None
) instead:
>>> circuit(0.1, 0.4)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-949e31911afa> in <module>()
----> 1 circuit(0.1, 0.4)
~/pennylane/variable.py in val(self)
134
135 # The variable is a placeholder for a keyword argument
--> 136 value = self.kwarg_values[self.name][self.idx] * self.mult
137 return value
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
Total running time of the script: ( 0 minutes 0.092 seconds)
Contents
Downloads
Related tutorials