Bài 5: Important Subroutine 3 - HHL Algorithm

Cuối cùng trong bộ 3 bài viết về các subroutines của thuật toán lượng tử, mình muốn đề cập tới thuật toán Harrow-Hassidim-Lloyd (hay HHL) được đặt theo tên của ba tác giả đã giới thiệu thuật toán 1. Thuật toán HHL được phát triển nhằm giải quyết phương trình hoặc hệ phương trình tuyến tính bằng máy tính lượng tử, hay nói cách khác thuật toán sẽ giải phương trình:
Theo phương pháp truyền thống, phương trình trên có thể được giải bằng cách nghịch đảo ma trận
Ở đây, mình sẽ cùng mọi người phân tích rõ hơn về thuật toán này.
Nội dung
Thuật toán HHL
Thuật toán HHL đặc biệt quan trọng cho các bài toán của QML, vì như mọi người đã biết hầu hết các thuật toán machine learning đều đi học tham số
Thực chất vấn đề lớn nhất của HHL là làm sao đưa ma trận
Box 1: Ma trận
Do đó, ta có:
Mọi người có thể đọc chi tiết hơn lý do hình thành của công thức trên ở Section 2.2.2 Nielson & Chuang.
Như vậy, ta hoàn toàn có thể sử dụng một kỹ thuật nhỏ để biến
Dễ dàng có thể thấy
Mặt khác, nếu ta phân rã trị riêng của
Ở đó, các véc-tơ riêng
Thay (1) và (2) vào
Từ kết quả trên, có thể thấy nhiệm vụ của thuật toán HHL sẽ biến đổi đến trạng thái
](/post/important-subroutine-3/circuit_hu61a04533ea5e0fb5c5a8a7eeffc6ee91_36739_95716748da28af513432a8421f6a384d.webp)
Bước 1: Khởi tạo Như minh họa ở Hình 1, thuật toán HHL có đầu vào gốm có 3 thanh ghi:
-
Thanh ghi Ancilla gồm 1 qubit được khởi tạo bằng
. Qubit này sẽ được sử dụng để hỗ trợ cho phép quay (rotation) từ thành . -
Thanh ghi thứ hai sẽ chịu trách nhiệm lưu giữ thông tin về giá trị riêng từ kết quả của thuật toán Phase estimation - QPE. Và tương tự với Bài 4, số qubits được dùng ở thanh ghi này sẽ phụ thuộc vào sai số của thuật toán QPE. Các qubits ở đây cũng được khởi tạo với giá trị
. -
Thanh ghi cuối cùng mã hóa giá trị của
. Mọi biến đổi sau này mình sẽ giả sử đã được chuẩn hóa để đơn giản trong quá trình trình bày các công thức. Trong trường hợp khác hoàn toàn có thể chuẩn hóa theo:
Như vậy, mình có thể suy ra trạng thái đầu vào của bài toán từ 3 thanh ghi trên có dạng:
Bước 2: Phase Estimation
Như mình trình bày ở Bài 4, thuật toán QPE sẽ giúp mình tìm được giá trị riêng,
Xét ma trận
Do đó nếu mình áp dụng thuật toán QPE với ma trận đơn nhất
Bước 3: Nghịch đảo giá trị riêng (Eigenvalues Inversion)
Theo như công thức (3), mình cần tính nghịch đảo của
Phép quay này sẽ biến đổi ancilla qubit (
Mà
Như vậy sau bước 3, kết quả của cả ba thanh ghi là:
Bước 4: Reverse Phase Estimation
Vì mình đã đạt được
Vì các qubits ở thanh ghi thứ hai được đưa vê trạng thái khởi tạo ban đầu
Bước 5: Measurement
Ở bước cuối cùng này, mình thực hiện phép đo trên ancilla qubit và chỉ lấy kết quả khi ancilla qubit có giá trị bằng
Từ đây, ta có thể đấy
Có thể thấy thuật toán HHL có rất nhiều triển vọng cho sự phát triển của QML, tuy nhiên khả năng tính toán vượt trội của HHL cũng là vấn đề của nó. Câu hỏi ở đây là làm thế nào để lấy được thông tin từ véc-tơ đầu ra
Do đó người ta thường dùng thuật HHL theo 2 cách như sau mà vẫn giữ được khả năng tính toán vượt trội của nó:
-
Chiếu trạng thái của
trên một không gian nhỏ hơn và tính toán giá trị kỳ vọng (expectation value) của trên không gian đó: Trong đó được gọi là observable có dạng của một ma trận đơn nhất. Các observables, , thường được dùng có thể kể đến như Pauli-X, Pauli-Y, và Pauli-Z tương ứng với phép chiếu lên x-axis, y-axis, và z-axis của Bloch Sphere. -
Sử dụng HHL nhưng một chương trình con subroutine cho các thuật toán khác nhằm tận dụng khả năng tính toán vượt trội của nó.
Source Code
Đâu tiên ta có class HamiltonianSimulation sẽ tạo một trận đơn nhất
class HamiltonianSimulation(cirq.EigenGate, cirq.testing.SingleQubitGate):
"""
Class này sẽ tạo ma trận đơn nhất từ một ma trận Hermitian đầu vào _H_ và
thời gian t.
"""
def __init__(self, _H_, t, exponent=1.0):
cirq.testing.SingleQubitGate.__init__(self)
cirq.EigenGate.__init__(self, exponent=exponent)
self._H_ = _H_
self.t = t
eigen_vals, eigen_vecs = np.linalg.eigh(self._H_)
self.eigen_components = []
for _lambda_, vec in zip(eigen_vals, eigen_vecs.T):
theta = -_lambda_*t / np.pi
_proj_ = np.outer(vec, np.conj(vec))
self.eigen_components.append((theta, _proj_))
def _with_exponent(self, exponent):
return HamiltonianSimulation(self._H_, self.t, exponent)
def _eigen_components(self):
return self.eigen_components
Ở đây, như mình đã đề cập ở công thức (*), hàm trên sẽ tính ma trận đơn nhất
Tiếp đến, ta có mô-đun thứ hai là QuantumPhaseEstimation. Đoạn code dưới đây hầu hết sẽ giống với code của Bài 4, tuy nhiên nó được cải tiến để có thể hoạt động với ma trận
class ControlledUnitary(cirq.Gate):
def __init__(self, num_qubits, num_input_qubits, U):
self._num_qubits = num_qubits
self.num_input_qubits = num_input_qubits
self.num_control_qubits = num_qubits - self.num_input_qubits
self.U = U
def num_qubits(self) -> int:
return self._num_qubits
def _decompose_(self, qubits):
qubits = list(qubits)
input_state_qubit = qubits[:self.num_input_qubits]
control_qubits = qubits[self.num_input_qubits:]
for i,q in enumerate(control_qubits):
_pow_ = 2 ** (self.num_control_qubits - i - 1)
#yield self.U(q, *input_state_qubit)**_pow_
yield cirq.ControlledGate(self.U**_pow_)(q, *input_state_qubit)
class QuantumPhaseEstimation:
'''
Class QuantumPhaseEstimation được cải tiến từ code bài 4 (https://github.com/qmlvietnam/CodeforBlog/blob/main/QPE%20(1).ipynb)
cho ma trận đơn nhất U bất kỳ.
'''
def __init__(self,
U,
input_qubits,
num_output_qubits=None,
output_qubits=None, initial_circuit=[],measure_or_sim=False):
self.U = U
self.input_qubits = input_qubits
self.num_input_qubits = len(self.input_qubits)
self.initial_circuit = initial_circuit
self.measure_or_sim = measure_or_sim
if output_qubits is not None:
self.output_qubits = output_qubits
self.num_output_qubits = len(self.output_qubits)
elif num_output_qubits is not None:
self.num_output_qubits = num_output_qubits
self.output_qubits = [cirq.LineQubit(i) for i
in range(self.num_input_qubits,self.num_input_qubits+self.num_output_qubits)]
else:
raise ValueError("Alteast one of num_output_qubits or output_qubits to be specified")
self.num_qubits = self.num_input_qubits+self.num_output_qubits
def inv_qft(self):
self._qft_= QFT(qubits=self.output_qubits)
self._qft_.qft_circuit()
print('print',self._qft_)
self.QFT_inv_circuit = self._qft_.inv_circuit
def circuit(self):
self.circuit = cirq.Circuit()
self.circuit.append(cirq.H.on_each(*self.output_qubits))
print(self.circuit)
print(self.output_qubits)
print(self.input_qubits)
print((self.output_qubits + self.input_qubits))
self.qubits = list(self.input_qubits + self.output_qubits)
self.circuit.append(ControlledUnitary(self.num_qubits,
self.num_input_qubits,self.U)(*self.qubits))
self.inv_qft()
self.circuit.append(self.QFT_inv_circuit)
if len(self.initial_circuit) > 0 :
self.circuit = self.initial_circuit + self.circuit
def measure(self):
self.circuit.append(cirq.measure(*self.output_qubits,key='m'))
def simulate_circuit(self,measure=True):
sim = cirq.Simulator()
if measure == False:
result = sim.simulate(self.circuit)
else:
result = sim.run(self.circuit, repetitions=1000).histogram(key='m')
return
class EigenValueInversion triển khai phép nghịch đảo ở Bước 3. Ở đây, như mình đã đề cập giá trị
class EigenValueInversion(cirq.Gate):
"""
Class EigenValueInversion sẽ nghịch đảo giá trị riêng. Ứng với mỗi giá trị riêng,
ta áp dụng phép xoay R_y tương ứng.
"""
def __init__(self, num_qubits, C, t):
super(EigenValueInversion, self)
self._num_qubits = num_qubits
self.C = C
self.t = t
# Tổng số các giá trị riêng self.N
self.N = 2**(num_qubits-1)
def num_qubits(self):
return self._num_qubits
def _decompose_(self, qubits):
base_state = 2**self.N - 1
for eig_val_state in range(self.N):
# Với mỗi véc-tơ cở sở của |\tilde{\lambda_j}> ta tính phép xoay R_y(\theta_j) tương ứng
eig_val_gate = self._ancilla_rotation(eig_val_state)
# Xác định qubits của thanh ghi thứ 2 cho phép biến đổi Controlled-R_Y
if (eig_val_state != 0):
base_state = eig_val_state - 1
qubits_to_flip = eig_val_state ^ base_state
for q in qubits[-2::-1]:
if qubits_to_flip % 2 == 1:
yield cirq.X(q)
qubits_to_flip >>= 1
# Tạo phép biến đổi Controlled-R_Y
eig_val_gate = cirq.ControlledGate(eig_val_gate)
yield eig_val_gate(*qubits)
def _ancilla_rotation(self, eig_val_state):
if eig_val_state == 0:
eig_val_state = self.N
# Tính \theta_j theo công thức mình đề cập ở trên
theta = 2*math.asin(self.C * self.N * self.t / (2*np.pi * eig_val_state))
return cirq.ry(theta)
Từ 3 mô-dun trên, mình có để xây dựng thuật toán HHL một cách hoàn chỉnh
class HHL:
def __init__(self, hamiltonian, initial_state=None, initial_state_transforms=None, qpe_register_size=4, C=None, t=1):
"""
:param hamiltonian: ma trận Hermitan đầu vào
:param C: C
:param t: t
:param initial_state: véc-tơ |b> đầu vào
"""
self.hamiltonian = hamiltonian
self.initial_state = initial_state
self.initial_state_transforms = initial_state_transforms
self.qpe_register_size = qpe_register_size # số qubit của thanh ghi số 2 cho thuật toán QPE
self.C = C
self.t = t
const = self.t/np.pi
self.t = const*np.pi
if self.C is None:
self.C = 2*np.pi / (2**self.qpe_register_size * t)
def build_hhl_circuit(self):
# Khởi tạo cấu trúc mạch cho thuật toán QFT.
# Về sau các phép biến đổi sẽ được thêm vào bằng circuit.append()
self.circuit = cirq.Circuit()
# Khởi tạo Ancilla qubit = |0> cho thanh ghi thứ nhất
self.ancilla_qubit = cirq.LineQubit(0)
# Khởi tạo các qubits của thanh ghi thứ 2 có giá trị |0>
self.qpe_register = [cirq.LineQubit(i) for i in range(1, self.qpe_register_size+1)]
# khởi tạo các qubits mã hóa véc-tơ |b>. Nếu initial_state_transforms = None,
# véc-tơ |b> được cho giá trị là |0>
if self.initial_state is None:
self.initial_state_size = int(np.log2(self.hamiltonian.shape[0]))
if self.initial_state_size == 1:
self.initial_state = [cirq.LineQubit(self.qpe_register_size + 1)]
else:
self.initial_state = [cirq.LineQubit(i) for i in range(self.qpe_register_size + 1,
self.qpe_register_size + 1 + self.initial_state_size)]
for op in list(self.initial_state_transforms):
print(op)
self.circuit.append(op(self.initial_state[0]))
# Tạo ma trận đơn nhất
self.U = HamiltonianSimulation(_H_=self.hamiltonian, t=self.t)
# Quantum Phase Estimation của ma trận U và véc-tơ |b>
_qpe_ = QuantumPhaseEstimation(input_qubits=self.initial_state,
output_qubits=self.qpe_register, U=self.U)
_qpe_.circuit()
print(dir(_qpe_))
print('CIRCUIT',_qpe_.circuit)
self.circuit += _qpe_.circuit
# Nghịch đảo giá trị riêng bằng hàm EigenValueInversion
_eig_val_inv_ = EigenValueInversion(num_qubits=self.qpe_register_size + 1, C=self.C, t=self.t)
self.circuit.append(_eig_val_inv_(*(self.qpe_register + [self.ancilla_qubit])))
print(self.circuit)
# Nghịch đảo các phép toán của QPE
self.circuit.append(_qpe_.circuit**(-1))
# Thực hiện phép đo trên ancilla qubit
self.circuit.append(cirq.measure(self.ancilla_qubit,key='a'))
# Thêm các observables để tính giá trị kỳ vọng
self.circuit.append([
cirq.PhasedXPowGate(
exponent=sympy.Symbol('exponent'),
phase_exponent=sympy.Symbol('phase_exponent'))(*self.initial_state),
cirq.measure(*self.initial_state, key='m')
])
def simulate(self):
# # Mô phỏng chương trình trên máy tính lượng tử và in ra kết quả
simulator = cirq.Simulator()
# Khởi tạo các observables: Pauli-X, Pauli-Y, và Pauli-Z
params = [{
'exponent': 0.5,
'phase_exponent': -0.5
}, {
'exponent': 0.5,
'phase_exponent': 0
}, {
'exponent': 0,
'phase_exponent': 0
}]
results = simulator.run_sweep(self.circuit, params, repetitions=5000)
for label, result in zip(('X', 'Y', 'Z'), list(results)):
expectation = 1 - 2 * np.mean(
result.measurements['m'][result.measurements['a'] == 1])
print('{} = {}'.format(label, expectation))
Để thí nghiệm thuật toán HHL này, mình sẽ lấy ví dụ các tham số như sau:
Với các observables: Pauli-X, Pauli-Y, và Pauli-Z, thì mình mong chờ kết quả tương ứng thu được là:
So với kết quả thu được từ thuật toán HHL trên:
X = 0.19298245614035092
Y = 0.4145995747696669
Z = -0.8652207591014718
Ta thấy thuật toán HHL mang lại kết quả khá sát so với kết quả chúng ta mong đợi. Mọi người có thể thử tăng độ chính xác bằng cách tăng giá trị
Toàn bộ code của thuật toán HHL ở đây.
Cảm ơn mọi người đã đọc bài.