Hamiltonian Time Evolution¶
The TimeEvolution program performs Hamiltonian time evolution simulation — simulating real-time quantum dynamics under a given Hamiltonian. Divi supports multiple Trotterization techniques out of the box: ExactTrotterization (full Trotter-Suzuki decomposition) and QDrift (randomized term sampling for shallower circuits on large Hamiltonians).
It supports three output modes:
Probability mode:
te.resultscontains measured basis-state probabilities (dict[str, float]).Observable mode:
te.resultscontains the estimated expectation value (float).Multi-observable mode:
te.resultscontains one expectation value per observable (list[float]), measured from a single shared evolution.
Basic Usage¶
Use probability mode when you want a final-state distribution:
import math
import pennylane as qp
from divi.backends import MaestroSimulator
from divi.qprog import TimeEvolution
backend = MaestroSimulator(shots=5000)
te = TimeEvolution(
hamiltonian=qp.PauliX(0) + qp.PauliX(1),
time=math.pi / 2,
backend=backend,
)
te.run()
probs = te.results
print(probs)
print(f"Circuits executed: {te.total_circuit_count}")
Observable Mode¶
Provide observable=... to estimate expectation values after evolution:
import pennylane as qp
from divi.backends import MaestroSimulator
from divi.qprog import TimeEvolution
backend = MaestroSimulator(shots=5000)
te = TimeEvolution(
hamiltonian=qp.PauliX(0) + qp.PauliZ(0),
time=0.6,
n_steps=8,
observable=qp.PauliZ(0),
backend=backend,
)
te.run()
print(f"<Z0> = {te.results:.6f}")
Tip
On sampling backends, pass shot_distribution="weighted" to focus the
shot budget on the observable’s dominant terms. See
Adaptive Shot Allocation for the full list of strategies.
Multi-Observable Mode¶
Pass a list (or tuple) to observable= to estimate several expectation
values from a single evolution. te.results becomes a list[float] in
the same order as the input observables, and the cost is shared across them:
import math
import pennylane as qp
from divi.backends import MaestroSimulator
from divi.qprog import TimeEvolution
backend = MaestroSimulator(shots=5000)
te = TimeEvolution(
hamiltonian=qp.PauliX(0) + qp.PauliX(1),
time=math.pi / 2,
observable=[qp.PauliZ(0), qp.PauliZ(1), qp.PauliZ(0) @ qp.PauliZ(1)],
backend=backend,
)
te.run()
z0, z1, zz = te.results
print(f"<Z0> = {z0:+.4f}")
print(f"<Z1> = {z1:+.4f}")
print(f"<Z0Z1> = {zz:+.4f}")
Two cost-saving mechanisms apply automatically:
Shared evolution: the
e^{-iHt}body is built and executed once per measurement group, not once per observable.Cross-observable QWC grouping:
MeasurementStageunions the Pauli terms of every observable and groups commuting terms into shared shot batches. In the example above all three observables are diagonal in the Z basis, so they collapse into a single measurement group.
When a quantum error mitigation protocol is set, the savings extend further; see Multi-Observable Programs.
QDrift Trotterization¶
For large Hamiltonians, you can use QDrift to sample terms and average over multiple Hamiltonian samples:
import pennylane as qp
from divi.backends import MaestroSimulator
from divi.hamiltonians import QDrift
from divi.qprog import SuperpositionState, TimeEvolution
backend = MaestroSimulator(shots=5000)
qdrift = QDrift(
keep_fraction=0.5,
sampling_budget=2,
n_hamiltonians_per_iteration=4,
sampling_strategy="weighted",
seed=7,
)
te = TimeEvolution(
hamiltonian=0.7 * qp.PauliZ(0) + 0.4 * qp.PauliX(0) + 0.2 * qp.PauliZ(1),
trotterization_strategy=qdrift,
time=0.8,
initial_state=SuperpositionState(),
backend=backend,
)
te.run()
print(te.results)
Note
Multi-sample QDrift with observable on an expectation-value backend is currently not supported.
Use a shot-based backend or run in probability mode for multi-sample averaging.
Time Evolution Trajectory¶
Use TimeEvolutionTrajectory to simulate dynamics at
multiple time points in parallel. It creates one TimeEvolution program
per time point, runs them via
ProgramEnsemble (with optional circuit batching), and
collects results into a time-ordered mapping.
import math
import numpy as np
import pennylane as qp
from divi.backends import MaestroSimulator
from divi.qprog import TimeEvolutionTrajectory
backend = MaestroSimulator(shots=5000)
trajectory = TimeEvolutionTrajectory(
hamiltonian=qp.PauliX(0),
time_points=np.linspace(0.01, math.pi, 20).tolist(),
observable=qp.PauliZ(0),
backend=backend,
)
trajectory.create_programs()
trajectory.run(blocking=True)
results = trajectory.aggregate_results()
# results: {0.01: 0.9996, 0.166: 0.944, ..., 3.14: 0.998}
trajectory.visualize_results() # plots ⟨Z⟩ vs time
The trajectory supports all the same options as TimeEvolution
(trotterization_strategy, n_steps, order, initial_state).
Batching across time points
Each time point becomes its own program in a
ProgramEnsemble, so the trajectory inherits
the ensemble’s circuit-batching machinery. The default BatchConfig()
already merges every program’s submission into a single backend call:
from divi.qprog import BatchConfig
# Default — single merged call for all time points
trajectory.run(blocking=True, batch_config=BatchConfig())
For dense trajectories (e.g. 512 time points) on
QoroService, set max_concurrent_programs=-1 so
all time points run concurrently and the entire ensemble submits as one
cloud job, reducing overhead significantly:
trajectory.run(
blocking=True,
batch_config=BatchConfig(max_concurrent_programs=-1),
)
See Circuit Batching for the full batching reference.
Next Steps¶
Run the full tutorial scripts: time_evolution.py — covers single-time-point evolution, trajectories, multi-observable groups, and QDrift — and nmr_spectroscopy.py — a 34-spin NMR simulation that demonstrates the cloud-merge recipe end-to-end.
Learn about optimization-based workflows in Ground-State Energy Estimation with VQE and Combinatorial Optimization with QAOA and PCE
Explore backend choices in Backends Guide