Be it measurement data, images or coordinates in phase-space, almost all forms of data we encounter in day-to-day life are classical. So does it make sense for quantum computers to deal with classical data? The short answer is yes! In this how-to, we will outline the first few steps of how to encode classical data into a quantum state. You will then be able to use this state to perform quantum computations.
Different embedding types
To encode your classical data into a quantum state, you first have to find out what type of classical data you have. We will distinguish between three different types of data in N -dimensions.
- Discrete data, represented as binary \mathbf{b} \in \{0, 1\}^{N} or integer values \mathbf{k} \in \mathbb{Z}^{N}.
- Real continuous data, represented as floating-point values \mathbf{x} \in \mathbb{R}^{N}.
- Complex continuous data, represented as complex values \boldsymbol{\alpha} \in \mathbb{C}^{2^{N}}.
Keeping the subset relations \{0, 1\}\subset\mathbb{Z}\subset\mathbb{R}\subset\mathbb{C} in mind, one could always choose to interpret the data in the domain \mathcal{D} to be in the superset \mathcal{D}^{\prime}\supset \mathcal{D}.
1. Discrete data, represented as binary or integer values
A suitable encoding for binary data is the so-called BasisEmbedding
. The BasisEmbedding
class interprets a binary string as a qubit basis state with the following mapping:
See below for a simple example of the BasisEmbedding
used to initialize three qubits.
import pennylane as qml N = 3 wires = range(N) dev = qml.device("default.qubit", wires) @qml.qnode(dev) def circuit(b): qml.BasisEmbedding(b, wires) return qml.state() >>> circuit([1, 1, 1]) [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j]
As expected, the result corresponds to the state \lvert 111 \rangle = \lvert 1 \rangle \otimes \lvert 1 \rangle \otimes \lvert 1 \rangle. Representing the \lvert 1 \rangle state as the second standard basis vector and the tensor product as the Kronecker product, we can confirm the result with a quick calculation.
You can also just pass an integer value to the basis embedding function and it will automatically convert it into its binary representation. We can perform a quick sanity check of this functionality by running
>>> print(circuit(7)) [0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j]
which is the same state vector we saw before. Unsurprisingly, the binary label corresponding to this state vector is also consistent with the binary representation of the integer seven.
2. Continuous data, represented as floating-point values
The simplest type of encoding for floating-point data is called AngleEmbedding
. This type of embedding encodes a single floating-point value x\in \mathbb{R} into a quantum state with the mapping
where k\in\{x, y, z\} is the axis of rotation in the Bloch sphere. The default axis of rotation is set to k=x in the AngleEmbedding class. You may also choose to set it to k=y, but make sure to avoid k=z. The latter case is not useful because every x will be mapped to the |0\rangle state; the encoded value will be lost. Note that you can also input a tensor-like object and encode each component as a qubit. Examine the code snippet below to see how to encode a classical floating-point value as a quantum state!
import pennylane as qml from pennylane import numpy as np N = 3 wires = range(3) dev = qml.device("default.qubit", wires) @qml.qnode(dev) def circuit(val_list): qml.AngleEmbedding(val_list, wires) return [qml.expval(qml.PauliZ(w)) for w in wires] >>> circuit([0.0, np.pi / 2, np.pi]) tensor([ 1.0, 0.0, -1.0], requires_grad=True)
Keep in mind that Pauli rotations are 2\pi-periodic up to a global phase, meaning that you should normalize your data to be in \Omega:=[0, \pi)\subset \mathbb{R} if possible. This can be helpful in order to avoid encoding two different values as the same quantum state. While the AngleEmbedding
allows you to encode a lot of information in a single qubit, this comes at the cost of a difficult construction process.
3. Continuous data, represented as complex values
The last type of embedding we want to look at is the AmplitudeEmbedding
. As the name suggests, an array of values can be used as the amplitudes of the state with the mapping
and can be implemented with the following code.
import pennylane as qml N = 3 wires = range(N) dev = qml.device("default.qubit", wires) @qml.qnode(dev) def circuit(features): qml.AmplitudeEmbedding(features, wires) return qml.state() >>> circuit([0.625, 0.0, 0.0, 0.0, 0.625j, 0.375, 0.25, 0.125]) tensor([0.625+0.j , 0. +0.j , 0. +0.j , 0. +0.j , 0. +0.625j, 0.375+0.j , 0.25 +0.j , 0.125+0.j ], requires_grad=True)
Here, the values were chosen to be normalized, i.e. \lVert\boldsymbol{\alpha}\rVert=1. Note that one can use unnormalized data by setting the normalize
parameter of the AmplitudeEmbedding
class to True
.
Conclusion
More advanced embeddings also exist as templated implementations in PennyLane. Besides taking advantage of quantum superposition as we have seen so far, they also take advantage of quantum entanglement. Two such templated embeddings in PennyLane are the QAOAEmbedding
and IQPEmbedding
. For a complete view of possible embeddings, you can visit the templates documentation
. The BasisEmbedding
and AmplitudeEmbedding
are also covered in the Key Concepts section of the PennyLane
website.
About the author
Isidor Schoch
Isidor is a Quantum Engineering MSc student at ETH Zurich. He is passionate about exploring the connections between mathematics, physics and computer science. Besides his studies, he currently also works in the PennyLane performance team as a summer ...