Skip to content

Commit 0cd19ca

Browse files
✨ Implement HHL algorithm (#582)
Quite some time ago, the HHL implementation based on `qiskit.algorithms` has been removed. Now, the time has come to re-add this benchmark. In this current implementation, a simplified version is implemented that solves a specific linear equation system and the `num_qubits` scale the number of QPE qubits. This gives the advantages of getting a circuit that actually has the specified number of qubits in comparison to before. However, while I think this implementation could and should be improved in the future, this might be a fair starting point. This PR resolves #216. --------- Signed-off-by: Nils Quetschlich <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent c33962e commit 0cd19ca

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

src/mqt/bench/benchmark_generation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def get_supported_benchmarks() -> list[str]:
4646
"ghz",
4747
"graphstate",
4848
"grover",
49+
"hhl",
4950
"qaoa",
5051
"qft",
5152
"qftentangled",

src/mqt/bench/benchmarks/hhl.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
2+
# Copyright (c) 2025 Munich Quantum Software Company GmbH
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: MIT
6+
#
7+
# Licensed under the MIT License
8+
9+
"""HHL Benchmark Circuit Generation."""
10+
11+
from __future__ import annotations
12+
13+
import numpy as np
14+
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
15+
from qiskit.circuit.library import QFTGate
16+
17+
18+
def create_circuit(num_qubits: int) -> QuantumCircuit:
19+
"""HHL algorithm for a fixed 2x2 Hermitian matrix A using scalable QPE precision.
20+
21+
This implementation simulates a more accurate model of the HHL algorithm
22+
for the matrix A = [[1, 1], [1, 3]] with known eigenvalues.
23+
24+
Args:
25+
num_qubits: Number of qubits in the phase estimation register (precision control)
26+
27+
Returns:
28+
QuantumCircuit: Qiskit circuit implementing HHL
29+
"""
30+
assert num_qubits >= 3, "Number of qubits must be at least 3 for HHL."
31+
num_qpe_qubits = num_qubits - 2
32+
qr_sys = QuantumRegister(1, name="sys") # System qubit (|b⟩)
33+
qr_eig = QuantumRegister(num_qpe_qubits, name="phase") # Eigenvalue estimation
34+
qr_anc = QuantumRegister(1, name="ancilla") # Ancilla for rotation
35+
cr = ClassicalRegister(1, name="c") # Classical for system measurement
36+
qc = QuantumCircuit(qr_sys, qr_eig, qr_anc, cr)
37+
38+
# Step 1: Prepare |b⟩ = |1⟩
39+
qc.x(qr_sys[0])
40+
41+
# Step 2: Apply Hadamards to phase register (QPE start)
42+
qc.h(qr_eig)
43+
44+
# Step 3: Controlled-unitary approximation simulating controlled-e^{iAt}
45+
# A = [[1, 1], [1, 3]], eigenvalues: lam1 ≈ 0.382, lam2 ≈ 3.618
46+
# We simulate one eigenvalue's evolution as approximation
47+
t = 2 * np.pi
48+
lam = 3.618 # simulate dominant eigenvalue for better realism
49+
50+
for j in range(num_qpe_qubits):
51+
angle = t * lam / (2 ** (j + 1))
52+
qc.cp(angle, qr_eig[j], qr_sys[0])
53+
54+
# Step 4: Apply inverse QFT
55+
qc.append(QFTGate(num_qpe_qubits).inverse(), qr_eig)
56+
57+
# Step 5: Controlled Ry rotations on ancilla (based on actual eigenvalue)
58+
for j in range(num_qpe_qubits):
59+
# Approximate eigenvalue encoded in basis state |j⟩
60+
# Use inverse λ (scaled appropriately)
61+
estimated_lambda = lam / (2 ** (num_qpe_qubits - j))
62+
if estimated_lambda > 0:
63+
inv_lambda = 1.0 / estimated_lambda
64+
inv_lambda = np.clip(inv_lambda, -1, 1) # valid for arcsin
65+
theta = 2 * np.arcsin(inv_lambda)
66+
qc.cry(theta, qr_eig[j], qr_anc[0])
67+
68+
# Step 6: QPE uncomputation (apply QFT + reverse controlled unitary)
69+
qc.append(QFTGate(num_qpe_qubits), qr_eig)
70+
for j in reversed(range(num_qpe_qubits)):
71+
angle = -t * lam / (2 ** (j + 1))
72+
qc.cp(angle, qr_eig[j], qr_sys[0])
73+
74+
# Step 7: Final Hadamards and measurement
75+
qc.h(qr_eig)
76+
qc.measure(qr_sys[0], cr[0])
77+
qc.name = "hhl"
78+
return qc

tests/test_bench.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
ghz,
5353
graphstate,
5454
grover,
55+
hhl,
5556
qaoa,
5657
qft,
5758
qftentangled,
@@ -97,6 +98,7 @@ def output_path() -> str:
9798
(dj, 3),
9899
(graphstate, 3),
99100
(grover, 3),
101+
(hhl, 3),
100102
(qaoa, 3),
101103
(qft, 3),
102104
(qftentangled, 3),
@@ -186,6 +188,7 @@ def test_dj_constant_oracle() -> None:
186188
# Algorithm-level tests
187189
("dj", BenchmarkLevel.ALG, 3, None, None),
188190
("wstate", BenchmarkLevel.ALG, 3, None, None),
191+
("hhl", BenchmarkLevel.ALG, 3, None, None),
189192
("shor", BenchmarkLevel.ALG, 18, None, None),
190193
("grover", BenchmarkLevel.ALG, 3, None, None),
191194
("qwalk", BenchmarkLevel.ALG, 3, None, None),

0 commit comments

Comments
 (0)