HHL Algorithm

Download NotebookDownload CodeView source on Gitee

Overview

Objective of the HHL algorithm: Given a Hermitian matrix A and a unit vector b, the goal is to solve the equation Ax=b.

Let |b=j=1Nbj|j, where |j represents the basis states. The key of the algorithm lies in simulating eiAΔt on |b.

Assume the spectral decomposition of A is as follows:

A=j=1Nλj|ujuj|

Then,

eiAΔt=j=1NeiλjΔt|ujuj|

Express |b in terms of the basis {|uj}: |b=j=1Nβj|uj.

Using Quantum Phase Estimation (QPE), where U=eiAΔt, the resulting transformation is as follows:

|bQPEj=1Nβj|uj|λj~

Here, |λj~ is the estimation of the eigenvalue λj (the tilde represents an estimation, the same applies in the following).

It is easy to see that the solution to the equation is:

|x=A1|b=jβj(λj)1|uj

Therefore, we only need to extract the information from the quantum state |λj~.

Steps of the HHL Algorithm

Below, we provide a detailed explanation of the steps involved in the HHL algorithm, including the mathematical derivations.

Data Preprocessing

As mentioned earlier, A needs to be Hermitian, i.e., A=A. However, this condition can be relaxed. If A is not Hermitian, we can construct A~ as follows:

A~=(0AA0)

Then, we solve the equation:

A~y=(b0)

It can be easily verified that the solution to this equation, y, has the following form:

y=(0x)

where Ax=b.

For b, since HHL is a quantum algorithm, the input should be a quantum state |b=j=1Nbj|j. The details of how to prepare such a quantum state and encode classical information into quantum information are beyond the scope of this algorithm. Here, we assume that the quantum state has been prepared accordingly.

Preparation of Operator U=eiAΔt

The efficient preparation of the operator U=eiAΔt is a core problem. As we will see later, the complexity of the entire HHL algorithm mainly depends on the operator U, so it is necessary to efficiently simulate U.

The preparation of this time evolution operator U=eiAΔt belongs to the problem of quantum system simulation (Hamiltonian simulation), which is an important quantum problem that will not be discussed in detail here.

In the original paper, a sparsity constraint was imposed on the matrix A in order to efficiently simulate eiAΔt. For general dense matrices A, simulating their evolution can be computationally expensive.

Here, we won’t go into the proof of the correctness of QPE. Readers only need to know that the input of QPE is a unitary operator U and its eigenvector |u, and the output is an estimate φ~ of the phase φ such that U|u=e2πiφ|u.

Specifically, using t auxiliary qubits, the action of QPE is as follows:

|b|0t|0=j=1Nβj|uj|0t|0QPEj=1Nβj|uj|φj~|0

Here, |φj~ is the estimate of the phase φj. An additional auxiliary qubit is added after the t auxiliary qubits for the subsequent rotation step.

To better understand, let’s take an example: Take

A=Z=|00||11|

Then

U=eiAΔt=eiΔt|00|+eiΔt|11|

The two phases are φ1=Δt/2π and φ2=Δt/2π.

If we use t=4 auxiliary qubits and take Δt=π/4, then φ1=1/8 and φ2=1/8.

The estimated values of the two phases are φ1~=0.0010×24=2 and φ2~=0.1110×24=14. Since the phase is a decimal between [0,1), multiplying it by 2t gives its corresponding quantum state |qt1qtt. It should be noted that because the phase is a decimal modulo 1, 0.0010 is mapped to 0.1110. To restore the phase (to restore λ), we first select a small enough Δt such that |φ|<1/2. This way, if |qt1qtt<2t1 corresponds to a positive number, |qt1qtt>2t1 corresponds to a negative number.

Through simple quantitative calculations, we obtain the following mapping relationship:

λΔt2π=φ={φ~/2t,φ>0,φ~<2t1φ~/2t1,φ<0,φ~>2t1

Here, λ is the eigenvalue of A, φ is the phase of U=eiAΔt, and φ~ is the estimate of φ.

Conditional Rotation

After QPE, the overall quantum state is as follows:

j=1Nβj|uj|φj~|0

To extract the information of λj from the quantum state |φj~, we need to use conditional rotation gates CR(k), which have the following effect:

CR(k)|φ~|b={|φ~|b,kφ~|φ~Ry(2arcsinCλ)|b,k=φ~

In simple terms, the rotation operation is only applied to the subsequent qubits when k selects the correct φ~.

Since we don’t know the correct φ~, we brute force all possible CR(k), where k=1,,2t1.

The effect of the rotation is straightforward:

k=12t1ICR(k)j=1Nβj|uj|φj~|0=j=1Nβj|uj|φj~(1(Cλj)2|0+Cλj|1)

After applying inverse QPE once again, the overall quantum state is as follows:

|ψ=j=1Nβj|uj|0t(1(Cλj)2|0+Cλj|1)

Measurement

Measure the last qubit. When the measurement result is |1, the probability is

p1=C2j=1N(βj/λj)2

After the measurement, the quantum state becomes

|ψ1=1(j=1N(βj/λj)2)1j=1Nβjλj|uj|0t|1

Clearly, there is no entanglement between the three quantum registers at this point. If we only look at the first quantum register, it is already in the state |x (ignoring a normalization factor).

It should be noted that there is a parameter C in the rotation operation. We see that C does not affect the correctness of the final result, but it does affect the probability p1 of obtaining the result. We certainly hope that C can be as large as possible, so that p1 can be maximized. However, C|λj|, it is smaller than the absolute value of all eigenvalues. If there is no prior information about the smallest absolute value of λ, we can only conservatively choose a very small C and then use amplitude amplification techniques to increase the probability of obtaining the result.

Implementation with MindQuantum

Here, we use MindQuantum to implement a simple example to illustrate the process and correctness of the HHL algorithm.

For convenience, we choose a simple matrix A=Z=[1001], because its time evolution operator eiZΔt=Rz(2Δt) is relatively easy to implement.

[1]:
import numpy as np

A = np.array([[1, 0], [0, -1]])
es, vs = np.linalg.eig(A)

print(f"eigenvalues of A:\n {es}")
print(f"eigenvectors of A:\n {vs}")

b = np.array([0.6, 0.8])
print(f"b: {b}")
print(f"Solution of Ax=b is: {np.linalg.solve(A, b)}")
eigenvalues of A:
 [ 1. -1.]
eigenvectors of A:
 [[1. 0.]
 [0. 1.]]
b: [0.6 0.8]
Solution of Ax=b is: [ 0.6 -0.8]

Here, we import various required functions:

[2]:
from mindquantum.core.gates import H, X, RY, RZ, Measure, Power, BARRIER
from mindquantum.core.circuit import Circuit
from mindquantum.simulator import Simulator

For the preparation of the quantum state |b=cosθ|0+sinθ|1, it can be achieved using an Ry(2θ) gate.

The following code prepares the state |b=0.6|0+0.8|1 and performs a measurement.

[3]:
circ = Circuit()
circ += RY(2 * np.arcsin(0.8)).on(0)
circ += Measure().on(0)

sim = Simulator(backend="mqvector", n_qubits=1)
res = sim.sampling(circ, shots=10000)
res.svg()
[3]:
../_images/case_library_hhl_algorithm_9_0.svg

The following is the step-by-step construction of the complete quantum circuit, with t=4, Δt=π/4, and C=0.5.

  • First is QPE.

  • Then comes CR(k), k=1,,15.

  • Next is inverse QPE.

  • Finally, measurement is performed.

[4]:
from mindquantum.algorithm.library import qft

# q1 ~ q4 : for QPE
t = 4
qc = [4, 3, 2, 1]

# store |b> , and store the result |x>
qb = 5

# use for conditional rotation
qs = 0

# choose a time evolution
dt = np.pi / 4

# choose a small C
C = 0.5

circ = Circuit()

# prepare b
circ += RY(2 * np.arcsin(0.8)).on(qb)

# QPE
for i in qc:
    circ += H.on(i)

for (i, q) in enumerate(qc):
    circ += Power(RZ(-2 * dt), 2**i).on(qb, q)

# apply inverse QFT
circ += qft(list(reversed(qc))).hermitian()

# conditional rotate
circ += BARRIER
for k in range(1, 2**t):
    for i in range(t):
        if (k & (1 << i)) == 0:
            circ += X.on(qc[i])
    phi = k / (2**t)
    if k > 2**(t-1):
        phi -= 1.0
    l = 2 * np.pi / dt * phi
    circ += RY(2 * np.arcsin(C / l)).on(qs, qc)

    for i in range(t):
        if (k & (1 << i)) == 0:
            circ += X.on(qc[i])
    circ += BARRIER

# apply inverse QPE
circ += qft(list(reversed(qc)))

for (i, q) in enumerate(qc):
    circ += Power(RZ(2 * dt), 2**i).on(qb, q)

for i in qc:
    circ += H.on(i)

circ.svg()
[4]:
../_images/case_library_hhl_algorithm_11_0.svg

How do we verify the correctness of our results? Through measurement. Since the result is a quantum state |x, currently MindQuantum does not have a method to directly extract the internal values of a specific qubit. Therefore, we can only obtain information about the state through measurement.

[5]:
sim = Simulator(backend="mqvector", n_qubits=2 + t)
sim.apply_circuit(circ)

circ_m = Circuit()
circ_m += Measure().on(qs)
circ_m += Measure().on(qb)

res = sim.sampling(circ_m, shots=100000)
res.svg()
[5]:
../_images/case_library_hhl_algorithm_13_0.svg
[6]:
res.data.get("01", 0) / (res.data.get("01", 0) + res.data.get("11", 0))
[6]:
0.3612780010344965

By statistically analyzing the measurement results of q5 when q0 is 1, we observe that the proportion of q5 = 0 is 0.36, which is consistent with the expected answer.

However, this method only provides information about the magnitude of the amplitudes. To obtain more information, such as phase, adjustments to the measurement would be required, but we won’t discuss that here.

[7]:
from mindquantum.utils.show_info import InfoTable

InfoTable('mindquantum', 'scipy', 'numpy')
[7]:
Software Version
mindquantum0.9.11
scipy1.10.1
numpy1.23.5
System Info
Python3.9.16
OSLinux x86_64
Memory8.3 GB
CPU Max Thread8
DateSat Dec 30 23:19:52 2023