QDrift randomized Hamiltonian simulation, approximating e^{-iHt} by stochastically sampling Pauli-term evolutions with probability proportional to coefficient magnitude.
Resources
1Install
npx skillscat add unitarylab/quantum-skills/qdrift Install via the SkillsCat registry.
One-Step Run Example Command
python ./scripts/algorithm.pyQDrift Hamiltonian Simulation Skill Guide
Overview
QDrift is a randomized Hamiltonian simulation method for approximating
$$
U(t)=e^{-iHt}
$$
using randomly sampled short Pauli evolutions.
Key Insight
Instead of applying every Pauli term each slice, QDrift samples a single term at each step with probability proportional to coefficient magnitude, then applies a uniformly scaled angle. This replaces deterministic Trotter ordering with stochastic averaging.
Why QDrift Matters:
- Sampling-based structure can reduce dependence on number of Hamiltonian terms in some regimes.
- It provides a practical randomized baseline against deterministic product formulas.
- It is straightforward to implement and easy to parallelize over repeated trials.
- It naturally supports statistical error analysis through multiple seeds.
Real Applications:
- Sparse or large-term-count Hamiltonian simulation studies.
- Monte Carlo style benchmarking across random trajectories.
- Resource tradeoff experiments under depth constraints.
- Comparative studies with Trotter/Taylor/QSP pipelines.
Learning Objectives
After using this skill, you should be able to:
- Derive and implement QDrift sampling probabilities.
- Understand how
stepscontrols variance and depth. - Use reproducible random seeds for fair comparisons.
- Interpret matrix-level outputs and spectral-norm error.
- Build aggregate statistics over repeated runs.
Prerequisites
Essential knowledge:
- Pauli decomposition of Hermitian matrices.
- Basic probability distributions and random sampling.
- Quantum gate-sequence execution concepts.
Mathematical comfort:
- Expectation and variance basics.
- Norm-based approximation error metrics.
- Scaling intuition with sample count.
Using the Provided Implementation
Quick Start Example
from unitarylab.algorithms import QDriftAlgorithm
# 2-qubit Hamiltonian as (pauli_string, coefficient) list
H_list = [
("ZI", 0.45),
("IZ", 0.45),
("XX", 0.1),
]
algo = QDriftAlgorithm(seed=1234)
result = algo.run(
H_list=H_list,
t=1.0,
epsilon=1e-3,
n_qubits=2,
backend='torch',
)
print("status:", result['status'])
print("error:", result['error'])
print(result.get('plot', ''))Core Parameters Explained
Constructor
class QDriftAlgorithm:
def __init__(self, seed: int = 666) -> None:
...| Parameter | Type | Default | Description |
|---|---|---|---|
seed |
int |
666 |
Random seed for reproducibility. |
run() Parameters
def run(self, H_list, t, epsilon, n_qubits, backend='torch', algo_dir=None):
...| Parameter | Type | Default | Description |
|---|---|---|---|
H_list |
List[Tuple[str, float]] |
required | Hamiltonian as a list of (pauli_string, coefficient) tuples. |
t |
float |
required | Total evolution time. |
epsilon |
float |
required | Target error; used to compute sampling step count $N = \lceil 2\lambda^2 t^2 / \epsilon \rceil$. |
n_qubits |
int |
required | Number of qubits. |
backend |
str |
'torch' |
Simulation backend. |
algo_dir |
str|None |
None |
Output directory for circuit diagram. |
Return Fields
| Key | Type | Description |
|---|---|---|
status |
str |
'success'. |
error |
float |
Estimated approximation error. |
circuit |
Circuit |
The assembled QDrift circuit (one random trajectory). |
circuit_path |
str |
Path to circuit SVG. |
message |
str |
Summary message. |
plot |
str |
ASCII art result panel. |
Understanding the Core Components
1) Probability and angle construction
From _expand in algorithms/qdrift/qdrift.py:
pauli_strings = [p for p, _ in decomposition]
coeffs = np.array([c.real for _, c in decomposition], dtype=float)
lam = np.sum(np.abs(coeffs))
probs = np.abs(coeffs) / lam
indices = np.random.choice(len(decomposition), size=self.steps, p=probs)
sequence = []
for idx in indices:
pauli_str = pauli_strings[idx]
sign = np.sign(coeffs[idx])
angle = sign * lam * t / self.steps
sequence.append((pauli_str, angle))Interpretation:
- Probability mass follows coefficient magnitude.
- Every sampled gate uses same magnitude scale
lam * t / steps, modulated by sign. np.random.choice(..., p=probs)is the stochastic core of QDrift.
2) Circuit assembly from random sequence
From _run:
decomposition = self._pauli_decompose(self.H, self.t)
sequence = self._expand(decomposition, self.t)
for pauli_str, angle in sequence:
gate = pauli_string_evolution(pauli_str, angle)
qc.append(gate, range(self.target_qubits))
self._circuit = qc
self._evolution_result = self._circuit.get_matrix()Interpretation:
- The evolution result corresponds to one random trajectory.
- Different seeds produce different approximation instances.
- For stable evaluation, average over multiple independent runs.
3) External package function notes (brief)
_pauli_decomposecomes from base class and handles decomposition details.pauli_string_evolutioncreates the gate implementation for each sampled term.total_errorcomes from base class and compares to exactexpm(-1jHt).
Hands-On Example: Hamiltonian Simulation
Measure variance across seeds and step counts.
from unitarylab.algorithms import QDriftAlgorithm
H_list = [
("ZI", 0.35),
("IZ", 0.35),
("XX", 0.15),
("YY", 0.15),
]
for epsilon in [0.1, 0.01]:
for seed in [0, 1, 2]:
algo = QDriftAlgorithm(seed=seed)
result = algo.run(
H_list=H_list,
t=1.0,
epsilon=epsilon,
n_qubits=2,
backend='torch',
)
print(f"epsilon={epsilon}, seed={seed}, error={result['error']:.3e}")What to look for:
- Smaller
epsilonleads to more sampling steps and lower error. - Different seeds produce different random trajectories with some variance.
Mathematical Deep Dive
For positive-coefficient decomposition,
$$
H = \sum_{\ell=1}^{L} h_\ell H_\ell,
\quad
h_\ell > 0,
\quad
\lambda = \sum_{\ell=1}^{L} h_\ell.
$$
Sampling probabilities are
$$
p_\ell = \frac{h_\ell}{\lambda}.
$$
The equivalent generalized form used in code is:
$$
H = \sum_{j=1}^{L} c_j P_j
$$
with
$$
\lambda = \sum_{j=1}^{L} |c_j|,
\quad
p_j = \frac{|c_j|}{\lambda}
$$
Set
$$
t_{\mathrm{step}} = \lambda t / N
$$
and build the random product
$$
U_{\text{qdrift}}(t)=\prod_{k=1}^{N}\exp(-iH_{j_k}t_{\mathrm{step}}).
$$
For signed coefficients in this implementation, each sampled gate is
$$
e^{-i,\mathrm{sign}(c_j),\lambda t / N; P_j}
$$
for total N = steps samples.
A commonly cited complexity expression in this randomized setting is
$$
N = O\left(\frac{(\lambda t)^2}{\epsilon}\right),
$$
while first-order deterministic product formulas are often compared using
$$
O\left(\frac{L^2(\lambda t)^2}{\epsilon}\right)
$$
style dependence.
Practical interpretation:
- One trajectory is random and biased toward large-magnitude terms.
- Increasing
Nimproves concentration around target evolution. - Empirical error assessment should include repeated seeds and statistics.
Implementation-consistent notes:
- The positive-coefficient presentation $H=\sum h_j H_j$ is a special case. Current code supports signed real Pauli coefficients by sampling with $|c_j|$ and encoding sign in the rotation angle.
- The common statement "no explicit dependence on $L$" refers to the randomized quantum-step complexity form. In practical implementation, classical decomposition and sampling still iterate across terms.
- In this code path,
target_erroris not used to automatically choosesteps; users should tunestepsexperimentally.
Real-World Applications
- Noisy simulation pipelines where randomized depth patterns are useful.
- Fast baseline approximations for large Pauli expansions.
- Statistical benchmarking for algorithm selection.
- Randomized circuit studies in NISQ-style analyses.