Time evolution builder

The TimeEvolutionBuilder algorithm in QDK/Chemistry constructs quantum circuits that implement the Hamiltonian simulation unitary \(U(t) = e^{-iHt}\). Following QDK/Chemistry’s algorithm design principles, it takes a QubitHamiltonian and a time parameter as input and produces a TimeEvolutionUnitary as output.

Overview

Hamiltonian simulation — constructing the unitary \(U(t) = e^{-iHt}\) — is a central subroutine in many quantum algorithms. The TimeEvolutionBuilder provides a unified interface for methods that construct this operator from a QubitHamiltonian.

QDK/Chemistry currently provides Trotter-Suzuki product formulas for this task. These decompose \(e^{-iHt}\) into a sequence of elementary Pauli rotations \(e^{-i\theta P}\) that can be directly implemented as quantum gates, with controllable approximation error via the Trotter order and number of time divisions [Suz92]. The resulting TimeEvolutionUnitary objects wrap a PauliProductFormulaContainer — a list of exponentiated Pauli terms with a repetition count.

Using the TimeEvolutionBuilder

Note

This algorithm is currently available only in the Python API.

This section demonstrates how to create, configure, and run a time evolution builder. The run method returns a TimeEvolutionUnitary object that can be used by any algorithm that requires a Hamiltonian simulation unitary (e.g., PhaseEstimation).

Input requirements

The TimeEvolutionBuilder requires the following inputs:

QubitHamiltonian

A QubitHamiltonian containing the Pauli-string representation of the Hamiltonian. This can be obtained from the QubitMapper algorithm, constructed from a model Hamiltonian, or built directly.

Time

A float specifying the evolution time \(t\) in \(U(t) = e^{-iHt}\).

Creating a builder

from qdk_chemistry.algorithms import create

# Create a time evolution builder
trotter = create("time_evolution_builder", "trotter")

Configuring settings

Settings vary by implementation. See Available implementations below for implementation-specific options.

# Configure a second-order Trotter builder with automatic step count
trotter = create("time_evolution_builder", "trotter")
trotter.settings().set("order", 2)
trotter.settings().set("target_accuracy", 1e-3)
trotter.settings().set("error_bound", "commutator")

Running the builder

import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import Structure

# 1. Setup molecule
coords = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]])
symbols = ["H", "H"]
structure = Structure(coords, symbols=symbols)

# 2. SCF
scf_solver = create("scf_solver")
E_scf, wfn_scf = scf_solver.run(
    structure, charge=0, spin_multiplicity=1, basis_or_guess="sto-3g"
)

# 3. Hamiltonian construction
hamiltonian_constructor = create("hamiltonian_constructor")
hamiltonian = hamiltonian_constructor.run(wfn_scf.get_orbitals())

# 4. Qubit mapping
qubit_mapper = create("qubit_mapper", encoding="jordan-wigner")
qubit_ham = qubit_mapper.run(hamiltonian)

# 5. Build time evolution unitary
trotter = create("time_evolution_builder", "trotter", order=2)
evolution = trotter.run(qubit_ham, time=0.1)

print(f"Container type: {evolution.get_container_type()}")
print(f"Number of qubits: {evolution.get_num_qubits()}")
print(evolution.get_summary())

Available implementations

QDK/Chemistry’s TimeEvolutionBuilder provides a unified interface for Hamiltonian simulation methods. You can discover available implementations programmatically:

from qdk_chemistry.algorithms import registry

# List all registered time evolution builder implementations
implementations = registry.available("time_evolution_builder")
print(implementations)  # e.g. ['trotter', 'qdrift', 'partially_randomized']

Trotter-Suzuki product formulas

Factory name: "trotter"

The Trotter-Suzuki decomposition approximates the time-evolution operator as a product of individual Pauli exponentials. For a Hamiltonian \(H = \sum_j \alpha_j P_j\):

First-order Trotter (\(p = 1\)):

\[e^{-iHt} \approx \left[\prod_j e^{-i\alpha_j P_j t / N}\right]^N\]

Second-order Trotter (Strang splitting, \(p = 2\)):

\[e^{-iHt} \approx \left[\prod_{j=1}^{L-1} e^{-i\alpha_j P_j t / 2N} \cdot e^{-i\alpha_L P_L t / N} \cdot \prod_{j=L-1}^{1} e^{-i\alpha_j P_j t / 2N}\right]^N\]

Higher even orders are constructed via the recursive Suzuki composition [Suz92]:

\[S_{2k}(t) = S_{2k-2}(u_k t)^2 \, S_{2k-2}\!\bigl((1-4u_k) t\bigr) \, S_{2k-2}(u_k t)^2\]

where \(u_k = 1/(4 - 4^{1/(2k-1)})\).

The number of Trotter steps \(N\) can be specified directly (num_divisions) or computed automatically from a target_accuracy using one of two error bounds:

Commutator bound (default)

A tighter bound [CST+21] based on nested commutators: \(N = \lceil \frac{t^{2}}{2\epsilon} \sum_{j<k}\lVert[\alpha_jP_j,\alpha_kP_k]\rVert \rceil\)

Naive bound

A looser triangle-inequality bound: \(N = \lceil (\sum_j|\alpha_j|)^{2}t^{2}/\epsilon \rceil\)

When both num_divisions and target_accuracy are specified, the builder uses whichever requires more Trotter steps.

Settings

Setting

Type

Description

order

int

Trotter-Suzuki order (1 for first-order, 2+ for higher even orders). Default is 1.

target_accuracy

float

Target approximation error \(\epsilon\). When set to 0.0 (default), automatic step-count estimation is disabled.

num_divisions

int

Explicit number of Trotter steps \(N\). When set to 0 (default), determined from target_accuracy.

error_bound

str

Error bound strategy: "commutator" (default, tighter) or "naive" (simpler).

weight_threshold

float

Coefficient threshold below which Pauli terms are discarded. Default is 1e-12.

Further reading

  • The above examples can be downloaded as a complete Python script.

  • PhaseEstimation: Quantum phase estimation algorithms

  • QubitMapper: Map fermionic Hamiltonians to qubit operators

  • Settings: Configuration settings for algorithms

  • Factory Pattern: Understanding algorithm creation