Propagator
The Propagator algorithm in QDK/Chemistry converts a time-dependent Hamiltonian over a finite time interval into a single effective (time-independent) Hamiltonian.
Following QDK/Chemistry’s algorithm design principles, it takes a TimeDependentQubitHamiltonian and a time interval as input and produces a QubitOperator as output.
Overview
Many quantum-chemistry workflows need to simulate how a system evolves under a Hamiltonian that changes with time — for example, a molecule driven by a laser pulse. The time-dependent nature of these systems makes them challenging to simulate directly. The standard approach is to:
Divide the total evolution into short time steps.
For each step, compute an effective Hamiltonian that approximates the time-dependent Hamiltonian over that interval.
Feed that effective Hamiltonian into a time-evolution routine (a HamiltonianUnitaryBuilder) to produce the quantum circuit for that step.
Step 2 is what a propagator does. Given an interval \([t_1, t_2]\) and a time-dependent Hamiltonian \(H(t)\), the propagator returns a time-independent \(H_\text{eff}\) that best represents the evolution during that interval.
The propagator’s output is divided by the step length \(\delta t = t_2 - t_1\), so that the downstream unitary builder applies \(U(t_2, t_1) \approx \exp(-\mathrm{i}\,\delta t\,H_\text{eff})\). This convention keeps propagator and builder responsibilities strictly separated.
Typical workflow
A propagator is not usually called directly. Instead, an
EvolutionCircuitBuilder
(e.g., EulerEvolutionCircuitBuilder) creates one internally from its
propagator setting and calls it once per time step. The typical
sequence within each step is:
The circuit builder passes the
TimeDependentQubitHamiltonianand the current interval \([t_1, t_2]\) to the propagatorThe propagator returns an effective
QubitOperatorThe HamiltonianUnitaryBuilder implements the effective evolution as a
UnitaryRepresentationA CircuitMapper converts the unitary into executable gates
The EulerEvolutionCircuitBuilder
orchestrates this loop for every time step and combines the per-step
circuits into a single evolution circuit.
Using the Propagator
Note
This algorithm is currently available only in the Python API.
This section demonstrates how to create, configure, and use a propagator.
Propagators are typically used as a nested algorithm within a
HamiltonianSimulation,
but can also be created and called independently.
Input requirements
The Propagator requires the following inputs:
- TimeDependentQubitHamiltonian
A
TimeDependentQubitHamiltoniandescribing how the Hamiltonian varies with time. Currently the only supported container type isDrivenContainer, which represents Hamiltonians of the form \(H(t) = H_0 + f(t)\,H_1\) where \(f(t)\) is a user-supplied drive function.- Time interval
Two floats
t_startandt_enddefining the interval over which the effective Hamiltonian is computed.
Creating a propagator
from qdk_chemistry.algorithms import create
propagator = create("propagator", "magnus")
Configuring settings
Settings vary by implementation. See Available implementations below for implementation-specific options.
propagator.settings().set("order", 1)
Running the propagator
import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import DrivenQubitHamiltonian, LatticeGraph
from qdk_chemistry.utils.model_hamiltonians import create_ising_hamiltonian
# 1. Build a driven Hamiltonian: H(t) = H0 + sin(2πt)·H1
lattice = LatticeGraph.chain(4)
h0 = create_ising_hamiltonian(lattice, j=1.0, h=0.0) # ZZ coupling
h1 = create_ising_hamiltonian(lattice, j=0.0, h=0.5) # Transverse X field
td_hamiltonian = DrivenQubitHamiltonian(h0, h1, drive=lambda t: np.sin(2 * np.pi * t))
# 2. Create the propagator and compute the effective Hamiltonian
propagator = create("propagator", "magnus")
h_eff = propagator.run(td_hamiltonian, t_start=0.0, t_end=0.1)
print(f"Effective Hamiltonian has {len(h_eff.pauli_strings)} Pauli terms")
When used as a nested algorithm inside an EvolutionCircuitBuilder, the
propagator is configured via the propagator setting:
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import AlgorithmRef
# Configure propagator as a nested algorithm inside an evolution circuit builder
propagator_ref = AlgorithmRef("propagator", "magnus", order=1)
euler_builder = create(
"evolution_circuit_builder",
"euler",
propagator=propagator_ref,
total_time=1.0,
dt=0.1,
)
Available implementations
QDK/Chemistry’s Propagator provides a unified interface for computing effective Hamiltonians.
You can discover available implementations programmatically:
from qdk_chemistry.algorithms import available
print(available("propagator"))
Time-averaged propagator
Factory name: "magnus"
This is the default (and currently only) propagator. It computes the time-averaged Hamiltonian over each interval. For a driven Hamiltonian \(H(t) = H_0 + f(t)\,H_1\) the result is:
where the drive integral is evaluated by numerical quadrature (scipy.integrate.quad).
This is the leading-order term of the Magnus expansion. For sufficiently smooth \(H(t)\), it has \(O(\delta t^3)\) local error and second-order global accuracy over a fixed evolution interval.
Settings
Setting |
Type |
Description |
|---|---|---|
|
int |
Expansion order. Default: |
Supported Hamiltonian types
Only DrivenContainer Hamiltonians (\(H_0 + f(t)\,H_1\)) are supported.
Passing any other container type raises NotImplementedError.
Further reading
The above examples can be downloaded as a complete Python script.
EvolutionCircuitBuilder: Time-evolution circuit composition
HamiltonianSimulation: Full simulation with circuit execution and measurement
HamiltonianUnitaryBuilder: Constructs the time-evolution unitary from the effective Hamiltonian
Settings: Configuration settings for algorithms
Factory Pattern: Understanding algorithm creation