Time Evolution

Time Evolution

Hamiltonian Time Evolution

The TimeEvolution class simulates the evolution of a quantum state under a given Hamiltonian using Trotter-Suzuki decomposition. It supports multiple trotterization strategies, observable measurement, and multi-time-point trajectories.

Basic Usage

import pennylane as qml
from divi.qprog.algorithms import TimeEvolution
from divi.qprog._hamiltonians import ExactTrotterization
from divi import ParallelSimulator

# Define a Hamiltonian
coeffs = [1.0, 0.5, 0.3]
ops = [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0), qml.PauliX(1)]
hamiltonian = qml.Hamiltonian(coeffs, ops)

# Evolve for time t=1.0 with 10 Trotter steps
sim = TimeEvolution(
    hamiltonian=hamiltonian,
    trotterization_strategy=ExactTrotterization(),
    n_steps=10,
    time=1.0,
    shots=10000,
    backend=ParallelSimulator(),
)

sim.run()

Trotterization Strategies

Divi provides two built-in trotterization strategies:

ExactTrotterization (default)

Standard Trotter-Suzuki decomposition that applies all Hamiltonian terms in each time step:

from divi.qprog._hamiltonians import ExactTrotterization

strategy = ExactTrotterization()

QDrift

Randomly samples Hamiltonian terms proportional to their coefficients, producing shallower circuits at the cost of introducing sampling noise:

from divi.qprog._hamiltonians import QDrift

strategy = QDrift(n_samples=50)

QDrift is particularly useful for:

  • Hamiltonians with many terms where exact Trotterization produces very deep circuits
  • Near-term hardware where circuit depth is the limiting factor
  • Cases where ensemble averaging can reduce sampling variance

Time Trajectories

Simulate evolution at multiple time points to observe dynamics:

import numpy as np

sim = TimeEvolution(
    hamiltonian=hamiltonian,
    time_points=np.linspace(0.1, 2.0, 20),
    n_steps=10,
    observable=qml.PauliZ(0),
    backend=ParallelSimulator(),
)

sim.run()

# Access the trajectory of expectation values
for entry in sim.trajectory:
    print(f"t={entry['time']:.2f}, <Z>={entry['expval']:.4f}")

Observables

Measure expectation values of observables at each time point:

sim = TimeEvolution(
    hamiltonian=hamiltonian,
    time=1.0,
    n_steps=10,
    observable=qml.PauliZ(0) @ qml.PauliZ(1),
    backend=ParallelSimulator(),
)

When no observable is specified, the raw probability distribution is returned.

QDrift Ensemble Averaging

When using QDrift, you can run multiple independent circuit samples and average the results for improved accuracy:

sim = TimeEvolution(
    hamiltonian=hamiltonian,
    trotterization_strategy=QDrift(n_samples=30),
    ensemble_size=10,
    time=1.0,
    n_steps=5,
    observable=qml.PauliZ(0),
    backend=ParallelSimulator(),
)

sim.run()

Batched Execution

For large numbers of time points, use batch_size to control memory usage:

sim = TimeEvolution(
    hamiltonian=hamiltonian,
    time_points=np.linspace(0.01, 5.0, 500),
    n_steps=20,
    batch_size=50,  # Process 50 time points at a time
    backend=ParallelSimulator(),
)

Initial States

Control the initial state before evolution:

Value Description
"Zeros" All qubits in |0⟩ state (default)
"Superposition" Equal superposition via Hadamard gates
"Ones" All qubits in |1⟩ state
"01+-..." Custom per-qubit state string

Custom Initial State

You can specify the initial state of each qubit individually using a string of characters:

Character State
0 |0⟩
1 |1⟩
+ |+⟩ = H|0⟩
- |−⟩ = HX|0⟩

The string length must match the number of qubits in the Hamiltonian:

# 4-qubit Hamiltonian: qubit 0 in |1⟩, qubit 1 in |+⟩, qubit 2 in |0⟩, qubit 3 in |−⟩
sim = TimeEvolution(
    hamiltonian=hamiltonian,
    initial_state="1+0-",
    time=1.0,
    n_steps=10,
    backend=ParallelSimulator(),
)