Encoding Classical Data into Quantum States¶
Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.
from IPython.core.display import HTML
display(HTML("<style>pre { white-space: pre !important; }</style>"))
Overview¶
Quantum encoding is a process to transform classical information into quantum states. It plays a crucial role in using quantum algorithms to solve classical problems, especially in quantum machine learning tasks. Interested readers can find an example of using quantum encoding in our tutorial on quantum classifier, where we use quantum neural networks to accomplish a classical binary classification task. Quantum encoding can be seen as a quantum circuit that acts on $\left| 0^n \right>$ state (n is the number of qubits), with some parameters determined by the classical information.
In this tutorial, we will discuss five typical encoding schemes, including basis encoding [1], amplitude encoding [1], angle encoding [1], instantaneous quantum polynomial (IQP) style encoding [2], and Hamiltonian evolution ansatz encoding [3]. In Paddle Quantum, we provide built-in methods for the first four encoding strategies.
Basis Encoding¶
Basis encoding is the most intuitive way to encode classical information into a quantum state. It encodes an $n$-bit binary string $x$ to an $n$-qubit quantum state $\left|x\right> = \left|i_x\right>$, where $\left|i_x\right>$ is a computational basis state. For example, if $x=1011$, the corresponding quantum state after basis encoding is $\left|1011\right>$. Let's take a look at how to use Paddle Quantum to implement basis encoding:
# Import necessary library
import paddle
from paddle_quantum.ansatz import Circuit
from paddle_quantum.gate import BasisEncoding, AmplitudeEncoding, AngleEncoding, IQPEncoding
import paddle_quantum as pq
import numpy as np
Start from $\left| 0^n \right>$, and we apply an $X$ gate if the corresponding classical bit is 1. We construct the circuit as follows:
# Number of qubits = length of the classical binary string
n = 4
# Initialize the circuit
basis_enc = Circuit(n)
# X is the classical information
x = '1011'
# Add a Pauli X gate to the ith qubit if the ith classical bit is 1
for i in range(len(x)):
if x[i] == '1':
basis_enc.x(i)
print(basis_enc)
--X-- ----- --X-- --X--
The corresponding quantum state after basis encoding is:
init_state = pq.state.zero_state(n)
basis_quantum_state = basis_enc(init_state)
print(basis_quantum_state)
[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
which is the state $\left|1011\right>$ as we desired.
In Paddle Quantum, we also provide a built-in method for basis encoding:
# Built-in basis encoding
built_in_basis_enc = BasisEncoding(num_qubits=n)
# Classical information x should be of type Tensor
x = paddle.to_tensor([1, 0, 1, 1])
built_in_basis_enc_state = built_in_basis_enc(feature=x)
print(built_in_basis_enc_state)
[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
Amplitude Encoding¶
Amplitude encoding encodes a vector $\mathbf{x}$ of length $N$ into amplitudes of an $n$-qubit quantum state with $n = \lceil\log_2(N)\rceil$:
$$ \begin{align*} \left|\mathbf{x}\right> = \sum\limits_{i}^{N}x_i\left|i\right>\end{align*}, $$where $\left\{\left|i\right>\right\}$ is the computational basis for the Hilbert space. Since the classical information forms the amplitudes of a quantum state, the input needs to satisfy the normalization condition: $\left|\mathbf{x}\right|^{2} = 1$.
For instance, if $\mathbf{x} = \begin{bmatrix} \frac{1}{2}\\ \frac{1}{2}\\ -\frac{1}{2}\\ -\frac{1}{2} \end{bmatrix}$, the corresponding quantum state will be $\left|\mathbf{x}\right> = \frac{1}{2}\left|00\right> + \frac{1}{2} \left|01\right> - \frac{1}{2} \left|10\right> - \frac{1}{2} \left|11\right>$.
Here is another example with $N < 2^n$, if $\mathbf{y} = \begin{bmatrix} \frac{1}{\sqrt{3}}\\\frac{1}{\sqrt{3}}\\\frac{1}{\sqrt{3}} \end{bmatrix}$, the corresponding quantum state will be $\left|\mathbf{y}\right> = \frac{1}{\sqrt{3}}\left|00\right> + \frac{1}{\sqrt{3}}\left|01\right> + \frac{1}{\sqrt{3}}\left|10\right>$.
You may have already noticed, amplitude encoding cannot be represented as a trivial quantum circuit. Instead, it can be implemented using arbitrary state preparation [1]. But don't worry. In Paddle Quantum, we provide a built-in method for amplitude encoding:
# Built-in amplitude encoding
# Number of qubits
n = 2
# Initialize the circuit
built_in_amplitude_enc = AmplitudeEncoding(num_qubits=n)
# Classical information x should be of type Tensor
x = paddle.to_tensor([0.5, 0.5, 0.5])
state = built_in_amplitude_enc(x)
print(state)
[0.57735026+0.j 0.57735026+0.j 0.57735026+0.j 0. +0.j]
In Paddle Quantum, we will normalize the input classical vector by default. As you can see, the result is indeed $\frac{1}{\sqrt{3}}\left|00\right> + \frac{1}{\sqrt{3}}\left|01\right> + \frac{1}{\sqrt{3}}\left|10\right>$.
Angle Encoding¶
Angle encoding makes use of rotation gates to encode classical information $\mathbf{x}$. The classical information determines angles of rotation gates:
$$ \left|\mathbf{x}\right> = \bigotimes_{i}^{n} R(\mathbf{x}_i) \left| 0^n \right>, $$where $R$ can be one of $R_x$, $R_y$, $R_z$. Usually, the number of qubits used for encoding is equal to the dimension of vector $\mathbf{x}$.
For example, when $\mathbf{x} = \begin{bmatrix} \pi \\ \pi\\ \pi \end{bmatrix}$, angle encoding rotates every qubit around Y-axis (if we choose $R_y$) for degree $\pi$, so that the corresponding quantum state will be $\left|111\right>$.
The circuit for angle encoding can be constructed as follows:
# Number of qubits = length of the classical information
n = 3
# Initialize the circuit
angle_enc = Circuit(n)
# X is the classical information
x = paddle.to_tensor([np.pi, np.pi, np.pi], 'float64')
# Add a layer of rotation y gates
for i in range(len(x)):
angle_enc.ry(qubits_idx=i, param=x[i])
print(angle_enc)
--Ry(3.142)-- --Ry(3.142)-- --Ry(3.142)--
The corresponding quantum state after amplitude encoding is:
init_state = pq.state.zero_state(n)
angle_quan_state = angle_enc(init_state)
print([np.round(i, 2) for i in angle_quan_state.data.numpy()])
[(-0+0j), 0j, 0j, (-0+0j), 0j, (-0+0j), (-0+0j), (1+0j)]
The corresponding state is $\left|111\right>$ as we desired.
In Paddle Quantum, we also provide a built-in method for angle encoding:
# Built-in angle encoding
# Number of qubits
n = 3
# Initialize the circuit
built_in_angle_enc = AngleEncoding(num_qubits=n, encoding_gate="ry", feature=x)
# Classical information x should be of type Tensor
x = paddle.to_tensor([np.pi, np.pi, np.pi], 'float64')
init_state = pq.state.zero_state(n)
state = built_in_angle_enc(state=init_state)
print([np.round(i, 2) for i in state.data.numpy()])
[(-0+0j), 0j, 0j, (-0+0j), 0j, (-0+0j), (-0+0j), (1+0j)]
IQP Style Encoding¶
IQP style encoding is a relatively complicated encoding strategy. We encode classical information $\mathbf{x}$ to
$$ \left|\mathbf{x}\right> = \left(\mathrm{U}_\mathrm{Z}(\mathbf{x})\mathrm{H}^{\otimes n}\right)^{r}\left|0^n\right>, $$where $r$ is the depth of the circuit, indicating the repeating times of $\mathrm{U}_\mathrm{Z}(\mathbf{x})\mathrm{H}^{\otimes n}$. $\mathrm{H}^{\otimes n}$ is a layer of Hadamard gates acting on all qubits. $\mathrm{U}_\mathrm{Z}(\mathbf{x})$ is the key step in IQP encoding scheme:
$$ \mathrm{U}_\mathrm{Z}(\mathbf{x})=\prod\limits_{[i,j]\in S}R_{Z_iZ_j}(x_ix_j)\bigotimes_{k=1}^{n} R_z(x_k), $$where $S$ is the set containing all pairs of qubits to be entangled using $R_{ZZ}$ gates.
First, we consider a simple two-qubit gate: $R_{Z_1Z_2}(\theta)$. Its mathematical form $e^{-i\frac{\theta}{2}Z_1\otimes Z_2}$ can be seen as a two-qubit rotation gate around ZZ , which makes these two qubits entangled. One can implement this gate using Paddle Quantum as follows:
# Number of qubits
n = 2
# Initialize the circuit
Rzz = Circuit(n)
# Theta is the angle of Rzz gate
x = paddle.to_tensor([2, 3], 'float64')
# Implement Rzz gate
Rzz.cnot(qubits_idx=[0, 1])
Rzz.rz(qubits_idx=1, param=x[0]*x[1])
Rzz.cnot(qubits_idx=[0, 1])
print(Rzz)
--*-----------------*-- | | --x----Rz(6.000)----x--
In $\mathrm{U}_\mathrm{Z}$, an $R_{ZZ}$ gate needs to be added between every pair of qubits in set $S$. In our built-in IQP encoding method, users are allowed to define their customized set $S$.
Now, let's take a look at how to implement IQP encoding using Paddle Quantum:
# Number of qubits
n = 4
# Initialize the circuit
iqp_enc = Circuit(n)
# X is the classical information
x = paddle.to_tensor([-1.45, 3, 2, -0.05], 'float64')
# S is a list containing all the pairs to be entagled
S = [[0, 1], [1, 2], [2, 3]]
# r is the repeating times of U
r = 1
for i in range(r):
# Add a layer of hadamard gates
iqp_enc.h("full")
# Add a layer of rotation z gates
iqp_enc.rz(qubits_idx="full",param=x)
# Add a layer of ZZ gates
for k in S:
iqp_enc.cnot(k)
iqp_enc.rz(qubits_idx=k[1], param=x[k[0]]*x[k[1]])
iqp_enc.cnot(k)
print(iqp_enc)
--H----Rz(-1.45)----*-----------------*------------------------------------------------ | | --H----Rz(3.000)----x----Rz(-4.35)----x----*-----------------*------------------------- | | --H----Rz(2.000)---------------------------x----Rz(6.000)----x----*-----------------*-- | | --H----Rz(-0.05)--------------------------------------------------x----Rz(-0.10)----x--
The corresponding quantum state after IQP style encoding is:
init_state = pq.state.zero_state(n)
iqp_quantum_state = iqp_enc(init_state)
print([np.round(i, 5) for i in iqp_quantum_state.data.numpy()])
[(-0.20396-0.14456j), (-0.22328-0.11246j), (0.15379-0.1971j), (0.16345-0.18916j), (-0.13157+0.21258j), (-0.09832+0.22985j), (-0.09832-0.22985j), (-0.08671-0.23448j), (-0.11345-0.22278j), (-0.14547-0.20332j), (0.22776-0.10308j), (0.23263-0.09157j), (0.07689-0.23788j), (0.04047-0.2467j), (0.15046+0.19966j), (0.14029+0.20693j)]
In Paddle Quantum, we provide a built-in IQP encoding method that exactly follows the way we explained above. However, this is just a particular case for IQP style encoding. IQP style encoding can refer to a more general class of encoding schemes. For example, you can replace the rotation Z gate with rotation X gate or rotation Y gate and replace $R_{ZZ}$ gate with $R_{XX}$ gate or $R_{YY}$ gate. Besides, you can think of three-qubit rotation gates, and add more layers with these three-qubit rotation gates.
# Built-in IQP style encoding
# Number of qubits
n = 4
# Initialize the circuit
# r is the repeating times of U
r = 1
# S is a list containing all the pairs to be entagled
S = [[0, 1], [1, 2], [2, 3]]
built_in_iqp_enc = IQPEncoding(qubits_idx=S, num_qubits = n, num_repeat=r, feature=x)
# Classical information x should be of type Tensor
x = paddle.to_tensor([-1.45, 3, 2, -0.05], 'float64')
init_state = pq.state.zero_state(n)
built_in_iqp_enc_state = built_in_iqp_enc(state=init_state)
print([np.round(i, 5) for i in built_in_iqp_enc_state.data.numpy()])
[(-0.20396-0.14456j), (-0.22328-0.11246j), (0.15379-0.1971j), (0.16345-0.18916j), (-0.13157+0.21258j), (-0.09832+0.22985j), (-0.09832-0.22985j), (-0.08671-0.23448j), (-0.11345-0.22278j), (-0.14547-0.20332j), (0.22776-0.10308j), (0.23263-0.09157j), (0.07689-0.23788j), (0.04047-0.2467j), (0.15046+0.19966j), (0.14029+0.20693j)]
Hamiltonian Evolution Ansatz Encoding¶
Hamiltonian evolution ansatz encoding, which uses a Trotter formula to approximate an evolution, has been explored in obtaining the ground state of a Hubbard model [4].
$$ \left|\mathbf{x}\right> = \left(\prod\limits_{i=1}^{n}R_{Z_iZ_{i+1}}(\frac{t}{T}x_{i})R_{Y_iY_{i+1}}(\frac{t}{T}x_{i})R_{X_iX_{i+1}}(\frac{t}{T}x_{i})\right)^{T}\bigotimes_{i=1}^{n+1}\left|\psi_{i}\right>, $$where $R_{XX}, R_{YY}, R_{ZZ}$ are the same rotation gates described in IQP style encoding, T is the number of Trotter steps, and $\left|\psi_{i}\right>$ is a Haar-random single-qubit quantum state. You can implement this encoding by applying T layers of $R_{XX}, R_{YY}, R_{ZZ}$ gates to the prepared Haar-random quantum state.
References¶
[1] Schuld, Maria. "Quantum machine learning models are kernel methods." arXiv:2101.11020 (2021).
[2] Havlíček, Vojtěch, et al. "Supervised learning with quantum-enhanced feature spaces." Nature 567.7747 (2019): 209-212.
[3] Huang, Hsin-Yuan, et al. "Power of data in quantum machine learning." Nature Communications 12.1 (2021): 1-9.
[4] Cade, Chris, et al. "Strategies for solving the Fermi-Hubbard model on near-term quantum computers." Physical Review B 102.23 (2020): 235122.