Skip to content

✨ Implement HHL algorithm #582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 28, 2025
1 change: 1 addition & 0 deletions src/mqt/bench/benchmark_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def get_supported_benchmarks() -> list[str]:
"ghz",
"graphstate",
"grover",
"hhl",
"qaoa",
"qft",
"qftentangled",
Expand Down
78 changes: 78 additions & 0 deletions src/mqt/bench/benchmarks/hhl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
# Copyright (c) 2025 Munich Quantum Software Company GmbH
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""HHL Benchmark Circuit Generation."""

from __future__ import annotations

import numpy as np
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit.circuit.library import QFTGate


def create_circuit(num_qubits: int) -> QuantumCircuit:
"""HHL algorithm for a fixed 2x2 Hermitian matrix A using scalable QPE precision.

This implementation simulates a more accurate model of the HHL algorithm
for the matrix A = [[1, 1], [1, 3]] with known eigenvalues.

Args:
num_qubits: Number of qubits in the phase estimation register (precision control)

Returns:
QuantumCircuit: Qiskit circuit implementing HHL
"""
assert num_qubits >= 3, "Number of qubits must be at least 3 for HHL."
num_qpe_qubits = num_qubits - 2
qr_sys = QuantumRegister(1, name="sys") # System qubit (|b⟩)
qr_eig = QuantumRegister(num_qpe_qubits, name="phase") # Eigenvalue estimation
qr_anc = QuantumRegister(1, name="ancilla") # Ancilla for rotation
cr = ClassicalRegister(1, name="c") # Classical for system measurement
qc = QuantumCircuit(qr_sys, qr_eig, qr_anc, cr)

# Step 1: Prepare |b⟩ = |1⟩
qc.x(qr_sys[0])

# Step 2: Apply Hadamards to phase register (QPE start)
qc.h(qr_eig)

# Step 3: Controlled-unitary approximation simulating controlled-e^{iAt}
# A = [[1, 1], [1, 3]], eigenvalues: lam1 ≈ 0.382, lam2 ≈ 3.618
# We simulate one eigenvalue's evolution as approximation
t = 2 * np.pi
lam = 3.618 # simulate dominant eigenvalue for better realism

for j in range(num_qpe_qubits):
angle = t * lam / (2 ** (j + 1))
qc.cp(angle, qr_eig[j], qr_sys[0])

# Step 4: Apply inverse QFT
qc.append(QFTGate(num_qpe_qubits).inverse(), qr_eig)

# Step 5: Controlled Ry rotations on ancilla (based on actual eigenvalue)
for j in range(num_qpe_qubits):
# Approximate eigenvalue encoded in basis state |j⟩
# Use inverse λ (scaled appropriately)
estimated_lambda = lam / (2 ** (num_qpe_qubits - j))
if estimated_lambda > 0:
inv_lambda = 1.0 / estimated_lambda
inv_lambda = np.clip(inv_lambda, -1, 1) # valid for arcsin
theta = 2 * np.arcsin(inv_lambda)
qc.cry(theta, qr_eig[j], qr_anc[0])

# Step 6: QPE uncomputation (apply QFT + reverse controlled unitary)
qc.append(QFTGate(num_qpe_qubits), qr_eig)
for j in reversed(range(num_qpe_qubits)):
angle = -t * lam / (2 ** (j + 1))
qc.cp(angle, qr_eig[j], qr_sys[0])

# Step 7: Final Hadamards and measurement
qc.h(qr_eig)
qc.measure(qr_sys[0], cr[0])
qc.name = "hhl"
return qc
3 changes: 3 additions & 0 deletions tests/test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
ghz,
graphstate,
grover,
hhl,
qaoa,
qft,
qftentangled,
Expand Down Expand Up @@ -97,6 +98,7 @@ def output_path() -> str:
(dj, 3),
(graphstate, 3),
(grover, 3),
(hhl, 3),
(qaoa, 3),
(qft, 3),
(qftentangled, 3),
Expand Down Expand Up @@ -186,6 +188,7 @@ def test_dj_constant_oracle() -> None:
# Algorithm-level tests
("dj", BenchmarkLevel.ALG, 3, None, None),
("wstate", BenchmarkLevel.ALG, 3, None, None),
("hhl", BenchmarkLevel.ALG, 3, None, None),
("shor", BenchmarkLevel.ALG, 18, None, None),
("grover", BenchmarkLevel.ALG, 3, None, None),
("qwalk", BenchmarkLevel.ALG, 3, None, None),
Expand Down
Loading