QSP-based Hamiltonian simulation approximates e^{-iHt} by block-encoding the Hamiltonian and applying polynomial spectral transformations via interleaved signal-processing rotations, achieving high precision with efficiently bounded circuit depth.
Resources
1Install
npx skillscat add unitarylab/quantum-skills/qsp Install via the SkillsCat registry.
QSP Hamiltonian Simulation Skill Guide
Overview
QSP-based Hamiltonian simulation approximates the time-evolution operator
$$
U(t) = e^{-iHt}
$$
by converting it into a polynomial approximation problem on the eigenvalues of the block-encoded Hamiltonian.
Key Insight
Rather than decomposing $H$ term-by-term as in Trotterization, QSP encodes the full spectral action of $e^{-iHt}$ as a polynomial transformation applied to a block-encoding oracle. The Hamiltonian is block-encoded once, and separate QSP circuits approximate $\beta\cos(tH)$ and $\beta\sin(tH)$ via Chebyshev series. An LCU construction merges them into a single-slice evolution circuit. When the polynomial degree is insufficient, the algorithm automatically increases the number of time slices.
Why QSP Matters:
- Achieves near-optimal query complexity for Hamiltonian simulation.
- Approximates arbitrary analytic functions of a block-encoded operator.
- Polynomial degree and time slices give fine-grained control over accuracy vs. depth.
- Strong theoretical foundation for comparing against Trotter and QDrift methods.
Real Applications:
- Precision time evolution of molecular and lattice Hamiltonians.
- Subroutine in quantum linear algebra and quantum walks.
- Benchmarking advanced simulation methods against product formulas.
- Digital simulation pipelines where gate budget is the primary constraint.
Learning Objectives
After using this skill, you should be able to:
- Explain how block encoding and polynomial approximation replace term-by-term Trotterization.
- Understand how
degreeandtime_slicesjointly control accuracy and circuit depth. - Use the
QSPHSAlgorithmclass correctly for Hamiltonian simulation experiments. - Interpret the approximate and exact evolution matrices and the Frobenius-norm error.
- Build reproducible comparisons of accuracy across parameter sweeps.
Prerequisites
Essential knowledge:
- Hermitian Hamiltonians and unitary time evolution.
- Block encoding of quantum operators.
- Quantum circuit composition and controlled-unitary gate sequences.
Mathematical comfort:
- Chebyshev polynomial series and Bessel function expansions.
- Matrix norms, especially the Frobenius norm.
- Linear combination of unitaries (LCU) framework.
Using the Provided Implementation
Quick Start Example
import numpy as np
from unitarylab.algorithms import QSPHSAlgorithm
# 2x2 Hermitian Hamiltonian
H = np.array([[2, 1],
[1, 3]], dtype=complex)
algo = QSPHSAlgorithm(text_mode="plain")
result = algo.run(
H=H,
t=1.0,
error=1e-8,
degree=15,
beta=0.7,
)
print("status :", result["status"])
print("circuit_path:", result["circuit_path"])
print("file_path :", result["file_path"])Core Parameters Explained
Constructor
class QSPHSAlgorithm:
def __init__(self, text_mode: str = "plain", algo_dir: str = None) -> None:
...| Parameter | Type | Default | Description |
|---|---|---|---|
text_mode |
str |
"plain" |
Output formatting mode for saved text reports. |
algo_dir |
str|None |
None |
Directory for saving results. Auto-derived from CWD if None. |
run() Parameters
def run(self, H, t, error, degree=15, beta=0.7):
...| Parameter | Type | Default | Constraints | Description |
|---|---|---|---|---|
H |
np.ndarray |
required | Square, Hermitian; padded to next power-of-2 dim if needed | Hamiltonian matrix. |
t |
float |
required | Finite real number | Total evolution time. |
error |
float |
required | > 0 |
Target approximation error; drives degree and slice estimation. |
degree |
int |
15 |
≥ 1 |
Upper bound on QSP polynomial degree per time slice. |
beta |
float |
0.7 |
(0, 1) strictly |
Preconditioning factor for numerical stability. |
Return Fields
| Key | Type | Description |
|---|---|---|
status |
str |
'ok' on success. |
circuit_path |
str |
Path to saved SVG circuit diagram. |
file_path |
str |
Path to saved text result file. |
Stored in algo.output after run():
| Key | Type | Description |
|---|---|---|
Approximate evolution matrix |
np.ndarray |
$U_{\text{approx}}$ composed from time slices. |
Exact evolution matrix |
np.ndarray |
$e^{-iHt}$ computed via scipy.linalg.expm. |
Frobenius norm of error |
float |
$|U_{\text{approx}} - U_{\text{exact}}|_F$. |
Understanding the Core Components
1) Block encoding and degree estimation
From run() in algorithm.py:
encoded_H = block_encode(H, method="nagy")
UH = encoded_H.circuit # circuit that block-encodes H/α
alpha = encoded_H.alpha # scaling factor α
m = encoded_H.total_qubits - n # ancilla qubit count@staticmethod
def _estimate_required_degree(alpha: float, t: float, target_error: float) -> int:
t_scaled = abs(alpha * t)
return max(1, int(np.ceil(1.4 * t_scaled + np.log(1.0 / target_error))))Interpretation:
block_encode(H, method="nagy")returns a normalized oracle with scaling factoralpha.- The required degree grows linearly with
|alpha * t_slice|and logarithmically with1/error. - When the requested
degreeis too low,time_slicesis doubled until the per-slice degree fits.
2) Chebyshev coefficient construction via Bessel functions
From run() in algorithm.py:
t = alpha * slice_time # dimensionless parameter s = α · t_slice
coef_cos = np.zeros(d + 1)
coef_cos[0] = jn(0, t) * beta
for i in range(1, d + 1):
if i % 2 == 0:
coef_cos[i] = jn(i, t) * 2 * (-1) ** (i / 2) * beta
coef_sin = np.zeros(d + 1)
for i in range(d + 1):
if i % 2 != 0:
coef_sin[i] = jn(i, t) * 2 * (-1) ** ((i - 1) / 2) * beta
cos_Ht = QSP(UH, n, m, coef_cos.copy(), 0)
sin_Ht = QSP(UH, n, m, coef_sin.copy(), 1)Interpretation:
- Even-index Bessel coefficients fill the cosine expansion; odd-index fill the sine expansion.
betascales both polynomials to keep values inside $[-1, 1]$ for numerical stability.QSP(UH, n, m, coef, parity)constructs the QSP circuit for the given Chebyshev series.
3) LCU combination and matrix composition
From run() in algorithm.py:
qc = Circuit(n + m + 2)
qc.h(n + m + 1) # |+⟩ on selection qubit
qc.s(n + m + 1) # S gate → |+i⟩
qc.z(n + m + 1) # phase adjustment
qc.append(cos_Ht, list(range(n + m + 1)), [n + m + 1], [0]) # cos part, control=0
qc.append(sin_Ht, list(range(n + m + 1)), [n + m + 1], [1]) # sin part, control=1
qc.h(n + m + 1) # final Hadamard
u_slice = qc.get_matrix(n) * factor # factor = 2 / beta
if time_slices > 1:
U_approx = np.linalg.matrix_power(u_slice, time_slices)
else:
U_approx = u_sliceInterpretation:
- The LCU uses one extra qubit as a selection register to coherently combine cos and sin blocks.
factor = 2/betarescales the output to recover the true $e^{-iHt}$ normalization.- Multiple slices are composed by matrix power rather than circuit repetition, enabling exact linear-algebra comparison.
4) Notes on external package functions (brief)
block_encode(H, method="nagy")is fromunitarylab.library; it returnsencoded_Hwith.circuit,.alpha, and.total_qubits.QSP(UH, n, m, coef, parity)is fromunitarylab.library._qsp; it builds the interleaved signal-processing circuit for the given coefficient array.Circuit.get_matrix(n)extracts the $2^n \times 2^n$ unitary submatrix acting on the $n$ system qubits.- Error is computed in
run()by comparing toscipy.linalg.expm(-1j * H * t)via Frobenius norm.
Hands-On Example: Hamiltonian Simulation
Sweep degree and t to observe accuracy vs. circuit depth trade-offs.
import numpy as np
from unitarylab.algorithms import QSPHSAlgorithm
H = np.array([[2, 1],
[1, 3]], dtype=complex)
for t in [1.0, 3.0, 5.0]:
for degree in [10, 20, 40]:
algo = QSPHSAlgorithm(text_mode="plain")
result = algo.run(H=H, t=t, error=1e-8, degree=degree, beta=0.7)
frob_err = algo.output["Frobenius norm of error"]
print(f"t={t:.1f}, degree={degree:>2d}, error={frob_err:.2e}, status={result['status']}")What to look for:
- Larger
trequires a higherdegreeor more time slices to maintain accuracy. - Increasing
degreereduces error until it saturates at the Bessel-approximation limit. - When
degreeis insufficient, the algorithm compensates by splitting into more time slices.
Mathematical Deep Dive
The block encoding scales the Hamiltonian as
$$
\langle 0^m | U_H | 0^m \rangle = \frac{H}{\alpha}
$$
where $\alpha$ is the 1-norm scaling factor from block_encode. For each time slice of duration $t_{\text{slice}} = t / \text{time_slices}$, the dimensionless parameter is
$$
s = \alpha \cdot t_{\text{slice}}
$$
The target functions are expanded as Chebyshev series using Bessel coefficients:
$$
\beta \cos(sx) \approx \sum_{k \text{ even}} c_k T_k(x), \qquad c_0 = \beta J_0(s),\quad c_k = 2(-1)^{k/2}\beta J_k(s)
$$
$$
\beta \sin(sx) \approx \sum_{k \text{ odd}} c_k T_k(x), \qquad c_k = 2(-1)^{(k-1)/2}\beta J_k(s)
$$
The LCU combines both blocks to recover the full complex exponential:
$$
U_{\text{approx}} = \frac{2}{\beta}\bigl(\beta\cos(sH) - i,\beta\sin(sH)\bigr) \approx e^{-iHt_{\text{slice}}}
$$
Accuracy is measured by the Frobenius norm of the residual:
$$
\varepsilon = |U_{\text{approx}}^{\text{time_slices}} - e^{-iHt}|_F
$$
The required polynomial degree per slice is estimated as:
$$
d_{\text{req}} = \left\lceil 1.4 \cdot |\alpha \cdot t_{\text{slice}}| + \ln(1/\varepsilon) \right\rceil
$$
Overall circuit complexity is dominated by:
$$
O!\left(d_{\text{req}} \times \text{time_slices} \times \text{cost}(U_H)\right)
$$
Implementation-consistent notes:
betakeeps polynomial values inside $[-1, 1]$; the factor2/betaundoes this rescaling at the matrix level.- The
QSP_MAX_TIME_SLICESenvironment variable (default4096) caps the automatic slice expansion loop. Hmust be Hermitian to withinatol=1e-12; non-power-of-2 dimensions are zero-padded before encoding.
Real-World Applications
- High-precision digital simulation of molecular electronic structure.
- Ground-state preparation via imaginary-time evolution via polynomial approximation.
- Quantum linear systems algorithms that use $e^{-iHt}$ as a subroutine.
- Circuit resource estimation studies comparing QSP, Trotter, and QDrift on the same Hamiltonian.