Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions docs/source/user/comprehensive/algorithms/hamiltonian_simulation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
Hamiltonian simulation
======================

The :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.base.HamiltonianSimulation` algorithm in QDK/Chemistry simulates the time evolution of a quantum system under a time-dependent Hamiltonian and measures observable expectation values.
Following QDK/Chemistry's :doc:`algorithm design principles <../design/index>`, it takes a :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian`, a list of observable :class:`~qdk_chemistry.data.QubitHamiltonian` operators, and a state-preparation :class:`~qdk_chemistry.data.Circuit` as input and returns a list of :class:`~qdk_chemistry.data.EnergyExpectationResult` and :class:`~qdk_chemistry.data.MeasurementData` pairs.

Overview
--------

Hamiltonian simulation solves the time-dependent Schrödinger equation :math:`i\,\partial_t U = H(t)\,U` on a quantum computer.
For a Hamiltonian that changes with time — for example, a molecule driven by a laser pulse — the algorithm divides the total evolution into discrete time steps and builds a quantum circuit for each step.

The simulation pipeline for each step is:

1. A :doc:`Propagator <propagator>` evaluates the effective Hamiltonian for the current time interval
2. A :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>` constructs the time-evolution unitary from the effective Hamiltonian
3. A :doc:`CircuitMapper <circuit_mapper>` converts the unitary into executable gates

After the full evolution circuit is assembled, observables are measured using an :doc:`EnergyEstimator <energy_estimator>` executed via a :doc:`CircuitExecutor <circuit_executor>`.


Using the HamiltonianSimulation
-------------------------------

.. note::
This algorithm is currently available only in the Python API.

This section demonstrates how to create, configure, and run a Hamiltonian simulation.
The ``run`` method returns a list of tuples containing :class:`~qdk_chemistry.data.EnergyExpectationResult` and :class:`~qdk_chemistry.data.MeasurementData` objects — one per observable.

Input requirements
~~~~~~~~~~~~~~~~~~

The :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.base.HamiltonianSimulation` requires the following inputs:

TimeDependentQubitHamiltonian
A :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian` describing how the Hamiltonian varies with time.

Observables
A list of :class:`~qdk_chemistry.data.QubitHamiltonian` operators to measure after evolution.
Each observable must have the same number of qubits as the Hamiltonian.

State preparation circuit
A :class:`~qdk_chemistry.data.Circuit` that prepares the initial state before time evolution.
This is typically generated by the :doc:`StatePreparation <state_preparation>` algorithm from a :class:`~qdk_chemistry.data.Wavefunction`.

.. rubric:: Creating a simulation algorithm

.. tab:: Python API

.. code-block:: python

from qdk_chemistry.algorithms import registry

sim = registry.create("hamiltonian_simulation", "euler_integrator")

.. rubric:: Configuring settings

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

.. tab:: Python API

.. code-block:: python

sim.settings().set("total_time", 1.0)
sim.settings().set("dt", 0.1)

.. rubric:: Running the simulation

.. tab:: Python API

.. code-block:: python

results = sim.run(td_hamiltonian, observables, state_prep_circuit, shots=1000)

for energy_result, measurement_data in results:
print(energy_result.energy)


Available implementations
-------------------------

QDK/Chemistry's :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.base.HamiltonianSimulation` provides a unified interface for time-dependent simulation methods.
You can discover available implementations programmatically:

.. tab:: Python API

.. code-block:: python

from qdk_chemistry.algorithms import registry

registry.available("hamiltonian_simulation")

Euler integrator
~~~~~~~~~~~~~~~~

.. rubric:: Factory name: ``"euler_integrator"``

The Euler integrator divides the total evolution time :math:`[0, T]` into steps of size ``dt``.
The number of full steps is ``floor(T / dt)``.
If ``T`` is not an exact multiple of ``dt``, a final cleanup step of size ``T mod dt`` is appended.

At each step, a :doc:`Propagator <propagator>` computes the effective Hamiltonian for the interval, which is then converted into a quantum circuit.
Per-step unitaries are combined via ``UnitaryContainer.combine``, which merges adjacent identical Pauli terms at step boundaries.

After evolving the state, each observable is measured independently using the configured energy estimator.
The evolution circuit can be retrieved after execution via the ``get_circuit()`` method.

.. rubric:: Settings

The Euler integrator inherits settings from :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.base.HamiltonianSimulationSettings`:

.. list-table::
:header-rows: 1
:widths: 25 15 60

* - Setting
- Type
- Description
* - ``total_time``
- float
- Total evolution time :math:`T`. Default: ``1.0``.
* - ``dt``
- float
- Time step size. Must be positive and not exceed ``total_time``. Default: ``0.0`` (must be set by user).
* - ``propagator``
- :class:`~qdk_chemistry.data.AlgorithmRef`
- Propagator used to evaluate the effective Hamiltonian over each step. Default: :class:`~qdk_chemistry.data.AlgorithmRef` to ``"propagator"`` with method ``"magnus"``.
* - ``evolution_builder``
- :class:`~qdk_chemistry.data.AlgorithmRef`
- Time-evolution builder used to construct the unitary from the effective Hamiltonian. Default: :class:`~qdk_chemistry.data.AlgorithmRef` to ``"hamiltonian_unitary_builder"`` with method ``"trotter"``.
* - ``circuit_mapper``
- :class:`~qdk_chemistry.data.AlgorithmRef`
- Circuit mapper used to convert the unitary to executable gates. Default: :class:`~qdk_chemistry.data.AlgorithmRef` to ``"circuit_mapper"`` with method ``"pauli_sequence"``.
* - ``circuit_executor``
- :class:`~qdk_chemistry.data.AlgorithmRef`
- Circuit executor used to run quantum circuits. Default: :class:`~qdk_chemistry.data.AlgorithmRef` to ``"circuit_executor"`` with method ``"qdk_sparse_state_simulator"``.
* - ``observable_estimator``
- :class:`~qdk_chemistry.data.AlgorithmRef`
- Estimator used to compute observable expectation values. Default: :class:`~qdk_chemistry.data.AlgorithmRef` to ``"energy_estimator"`` with method ``"qdk"``.


Related classes
---------------

- :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian`: Input time-dependent Hamiltonian
- :class:`~qdk_chemistry.data.QubitHamiltonian`: Observable operators
- :class:`~qdk_chemistry.data.Circuit`: State-preparation circuit and output evolution circuit
- :class:`~qdk_chemistry.data.EnergyExpectationResult`: Output energy expectation values
- :class:`~qdk_chemistry.data.MeasurementData`: Output measurement data
- :doc:`Propagator <propagator>`: Computes effective Hamiltonians for each time step

Further reading
---------------

- :doc:`Propagator <propagator>`: Effective Hamiltonians for time-dependent evolution
- :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`: Constructs the time-evolution unitary from the effective Hamiltonian
- :doc:`EnergyEstimator <energy_estimator>`: Observable expectation value estimation
- :doc:`CircuitExecutor <circuit_executor>`: Quantum circuit execution backends
- :doc:`Settings <settings>`: Configuration settings for algorithms
- :doc:`Factory Pattern <factory_pattern>`: Understanding algorithm creation
8 changes: 8 additions & 0 deletions docs/source/user/comprehensive/algorithms/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ All algorithms follow a :doc:`factory pattern <factory_pattern>` design, allowin
phase_estimation
qpe_circuit_builder
hamiltonian_unitary_builder
propagator
hamiltonian_simulation
circuit_mapper
circuit_executor

Expand Down Expand Up @@ -86,6 +88,12 @@ The following table summarizes the available algorithm classes in QDK/Chemistry
* - :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`
- Hamiltonian simulation unitaries
- QubitHamiltonian → UnitaryRepresentation
* - :doc:`Propagator <propagator>`
- Effective Hamiltonians for time-dependent evolution
- TimeDependentQubitHamiltonian → QubitHamiltonian
* - :doc:`HamiltonianSimulation <hamiltonian_simulation>`
- Time-dependent Hamiltonian simulation
- TimeDependentQubitHamiltonian + Observables → Energy
* - :doc:`ControlledCircuitMapper <circuit_mapper>`
- Controlled-unitary circuit synthesis
- UnitaryRepresentation → Circuit
Expand Down
172 changes: 172 additions & 0 deletions docs/source/user/comprehensive/algorithms/propagator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
Propagator
==========

The :class:`~qdk_chemistry.algorithms.propagator.base.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 :doc:`algorithm design principles <../design/index>`, it takes a :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian` and a time interval as input and produces a :class:`~qdk_chemistry.data.QubitHamiltonian` 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:

1. Divide the total evolution into short time steps.
2. For each step, compute an **effective Hamiltonian** that approximates the time-dependent Hamiltonian over that interval.
3. Feed that effective Hamiltonian into a time-evolution routine (a :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`) to produce the quantum circuit for that step.

Step 2 is what a propagator does. Given an interval :math:`[t_1, t_2]` and a time-dependent Hamiltonian :math:`H(t)`, the propagator returns a time-independent :math:`H_\text{eff}` that best represents the evolution during that interval.

The propagator's output is divided by the step length :math:`\delta t = t_2 - t_1`, so that the downstream unitary builder — which multiplies by :math:`\delta t` — recovers the correct exponent.
This convention keeps propagator and builder responsibilities strictly separated.

Typical workflow
~~~~~~~~~~~~~~~~

A propagator is not usually called directly. Instead, a
:class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.base.HamiltonianSimulation`
algorithm (e.g., ``EulerIntegrator``) creates one internally from its
``propagator`` setting and calls it once per time step. The typical
sequence within each step is:

1. The integrator passes the :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian` and the current interval :math:`[t_1, t_2]` to the propagator
2. The propagator returns an effective :class:`~qdk_chemistry.data.QubitHamiltonian`
3. The :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>` implements the effective evolution as a :class:`~qdk_chemistry.data.UnitaryRepresentation`
4. A :doc:`CircuitMapper <circuit_mapper>` converts the unitary into executable gates

The :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.euler_integrator.EulerIntegrator`
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
:class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.base.HamiltonianSimulation`,
but can also be created and called independently.

Input requirements
~~~~~~~~~~~~~~~~~~

The :class:`~qdk_chemistry.algorithms.propagator.base.Propagator` requires the following inputs:

TimeDependentQubitHamiltonian
A :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian` describing how the Hamiltonian varies with time.
Currently the only supported container type is
:class:`~qdk_chemistry.data.time_dependent_qubit_hamiltonian.containers.driven.DrivenContainer`,
which represents Hamiltonians of the form :math:`H(t) = H_0 + f(t)\,H_1` where :math:`f(t)` is a
user-supplied drive function.

Time interval
Two floats ``t_start`` and ``t_end`` defining the interval over which the effective Hamiltonian is computed.

.. rubric:: Creating a propagator

.. tab:: Python API

.. code-block:: python

from qdk_chemistry.algorithms import registry

propagator = registry.create("propagator", "magnus")

.. rubric:: Configuring settings

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

.. tab:: Python API

.. code-block:: python

propagator.settings().set("order", 1)

.. rubric:: Running the propagator

.. tab:: Python API

.. code-block:: python

h_eff = propagator.run(td_hamiltonian, t_start, t_end)

When used as a nested algorithm inside an ``EulerIntegrator``, the
propagator is configured via the ``propagator`` setting:

.. tab:: Python API

.. code-block:: python

from qdk_chemistry.data import AlgorithmRef

euler.settings().set("propagator", AlgorithmRef("propagator", "magnus"))
Comment on lines +103 to +105


Available implementations
-------------------------

QDK/Chemistry's :class:`~qdk_chemistry.algorithms.propagator.base.Propagator` provides a unified interface for computing effective Hamiltonians.
You can discover available implementations programmatically:

.. tab:: Python API

.. code-block:: python

from qdk_chemistry.algorithms import registry

registry.available("propagator")

Time-averaged propagator
~~~~~~~~~~~~~~~~~~~~~~~~~

.. rubric:: Factory name: ``"magnus"``

This is the default (and currently only) propagator. It computes the time-averaged Hamiltonian over each interval. For a driven Hamiltonian :math:`H(t) = H_0 + f(t)\,H_1` the result is:

.. math::

H_\text{eff} = H_0 + \bar{f}\,H_1,
\qquad
\bar{f} = \frac{1}{\delta t}\int_{t_1}^{t_2} f(t')\,\mathrm{d}t'

where the drive integral is evaluated by numerical quadrature (``scipy.integrate.quad``).

This is the leading-order term of the Magnus expansion. This approximation gives :math:`O(\delta t^2)` accuracy.

.. rubric:: Settings

.. list-table::
:header-rows: 1
:widths: 25 15 60

* - Setting
- Type
- Description
* - ``order``
- int
- Expansion order. Default: ``1``. Only order 1 (time averaging) is currently implemented; higher values raise :class:`NotImplementedError`.

.. rubric:: Supported Hamiltonian types

Only :class:`~qdk_chemistry.data.time_dependent_qubit_hamiltonian.containers.driven.DrivenContainer` Hamiltonians (:math:`H_0 + f(t)\,H_1`) are supported.
Passing any other container type raises :class:`NotImplementedError`.


Related classes
---------------

- :class:`~qdk_chemistry.data.TimeDependentQubitHamiltonian`: Input time-dependent Hamiltonian
- :class:`~qdk_chemistry.data.QubitHamiltonian`: Output effective Hamiltonian
- :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.euler_integrator.EulerIntegrator`: The time-stepping integrator that calls the propagator each step
- :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`: Constructs the time-evolution unitary from the effective Hamiltonian produced by the propagator

Further reading
---------------

- :class:`~qdk_chemistry.algorithms.time_evolution.hamiltonian_simulation.euler_integrator.EulerIntegrator`: Time-stepping integrator that uses the propagator
- :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`: Constructs the time-evolution unitary from the effective Hamiltonian
- :doc:`Settings <settings>`: Configuration settings for algorithms
- :doc:`Factory Pattern <factory_pattern>`: Understanding algorithm creation
Loading
Loading