diff --git a/docs/source/user/comprehensive/algorithms/hamiltonian_simulation.rst b/docs/source/user/comprehensive/algorithms/hamiltonian_simulation.rst new file mode 100644 index 000000000..422a47f04 --- /dev/null +++ b/docs/source/user/comprehensive/algorithms/hamiltonian_simulation.rst @@ -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 ` evaluates the effective Hamiltonian for the current time interval +2. A :doc:`HamiltonianUnitaryBuilder ` constructs the time-evolution unitary from the effective Hamiltonian +3. A :doc:`CircuitMapper ` converts the unitary into executable gates + +After the full evolution circuit is assembled, observables are measured using an :doc:`EnergyEstimator ` executed via a :doc:`CircuitExecutor `. + + +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 ` 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 ` 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 `: Computes effective Hamiltonians for each time step + +Further reading +--------------- + +- :doc:`Propagator `: Effective Hamiltonians for time-dependent evolution +- :doc:`HamiltonianUnitaryBuilder `: Constructs the time-evolution unitary from the effective Hamiltonian +- :doc:`EnergyEstimator `: Observable expectation value estimation +- :doc:`CircuitExecutor `: Quantum circuit execution backends +- :doc:`Settings `: Configuration settings for algorithms +- :doc:`Factory Pattern `: Understanding algorithm creation diff --git a/docs/source/user/comprehensive/algorithms/index.rst b/docs/source/user/comprehensive/algorithms/index.rst index 703079a2a..832a7abd3 100644 --- a/docs/source/user/comprehensive/algorithms/index.rst +++ b/docs/source/user/comprehensive/algorithms/index.rst @@ -25,6 +25,8 @@ All algorithms follow a :doc:`factory pattern ` design, allowin phase_estimation qpe_circuit_builder hamiltonian_unitary_builder + propagator + hamiltonian_simulation circuit_mapper circuit_executor @@ -86,6 +88,12 @@ The following table summarizes the available algorithm classes in QDK/Chemistry * - :doc:`HamiltonianUnitaryBuilder ` - Hamiltonian simulation unitaries - QubitHamiltonian → UnitaryRepresentation + * - :doc:`Propagator ` + - Effective Hamiltonians for time-dependent evolution + - TimeDependentQubitHamiltonian → QubitHamiltonian + * - :doc:`HamiltonianSimulation ` + - Time-dependent Hamiltonian simulation + - TimeDependentQubitHamiltonian + Observables → Energy * - :doc:`ControlledCircuitMapper ` - Controlled-unitary circuit synthesis - UnitaryRepresentation → Circuit diff --git a/docs/source/user/comprehensive/algorithms/propagator.rst b/docs/source/user/comprehensive/algorithms/propagator.rst new file mode 100644 index 000000000..2392e504b --- /dev/null +++ b/docs/source/user/comprehensive/algorithms/propagator.rst @@ -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 `) 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 ` implements the effective evolution as a :class:`~qdk_chemistry.data.UnitaryRepresentation` +4. A :doc:`CircuitMapper ` 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")) + + +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 `: 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 `: Constructs the time-evolution unitary from the effective Hamiltonian +- :doc:`Settings `: Configuration settings for algorithms +- :doc:`Factory Pattern `: Understanding algorithm creation diff --git a/python/src/qdk_chemistry/algorithms/propagator/magnus_propagator.py b/python/src/qdk_chemistry/algorithms/propagator/magnus_propagator.py index 1a80b73bc..d3e1e494a 100644 --- a/python/src/qdk_chemistry/algorithms/propagator/magnus_propagator.py +++ b/python/src/qdk_chemistry/algorithms/propagator/magnus_propagator.py @@ -1,15 +1,29 @@ r"""Time-averaged propagator with Magnus expansion. -Computes the effective Hamiltonian for a time interval via the Magnus -expansion truncated at a configurable order. +Computes the effective (time-independent) Hamiltonian for a time interval +:math:`[t_1, t_2]` via the Magnus expansion of the time-ordered +propagator :math:`U(t_2, t_1) = \exp(\Omega(t_2, t_1))`, where -**Order 1** (default) is the time-averaged Hamiltonian: +.. math:: + + \Omega = \Omega_1 + \Omega_2 + \Omega_3 + \cdots + +is a series of nested time integrals of :math:`H(t)`. The leading +(order-1) term is the time-averaged Hamiltonian .. math:: - \Omega_1 = \int_{t_1}^{t_2} H(t')\,\mathrm{d}t' + \Omega_1 = \int_{t_1}^{t_2} H(t')\,\mathrm{d}t', -Higher orders add commutator corrections computed recursively via +while higher orders add nested-commutator corrections, e.g. + +.. math:: + + \Omega_2 = -\frac{1}{2} + \int_{t_1}^{t_2}\!\mathrm{d}t' + \int_{t_1}^{t'}\!\mathrm{d}t''\,[H(t'), H(t'')], + +and in general the :math:`\Omega_n` follow the recursion .. math:: @@ -17,9 +31,22 @@ = \sum_{k=1}^{n-1} \frac{B_k}{k!} \sum_{j_1+\cdots+j_k=n-1} \mathrm{ad}_{\Omega_{j_1}} \cdots - \mathrm{ad}_{\Omega_{j_k}}(H(t)) + \mathrm{ad}_{\Omega_{j_k}}(H(t)), where :math:`B_k` are Bernoulli numbers. + +Implementation status +--------------------- +Only the leading-order (order-1) term is currently implemented, and only +for +:class:`~qdk_chemistry.data.time_dependent_qubit_hamiltonian.containers.driven.DrivenContainer` +Hamiltonians of the form :math:`H(t) = H_0 + f(t)\,H_1`. Requesting an +``order`` greater than 1, or passing any other container type, raises +:class:`NotImplementedError`. + +Accuracy +-------- +The order-1 propagator gives :math:`O(\Delta t^2)` accuracy. """ # -------------------------------------------------------------------------------------------- @@ -45,7 +72,15 @@ class MagnusPropagatorSettings(Settings): - """Settings for the Magnus propagator.""" + """Settings for the Magnus propagator. + + Attributes: + order (int): Magnus expansion order (default = 1). Only the + leading-order term (order 1, time averaging) is currently + implemented; any larger value raises + :class:`NotImplementedError` when the propagator runs. + + """ def __init__(self): """Initialize settings with default Magnus expansion order.""" @@ -57,12 +92,26 @@ class MagnusPropagator(Propagator): r"""Magnus propagator for time-dependent Hamiltonian simulation. Evaluates the effective Hamiltonian for an interval :math:`[t_1, t_2]` - via the Magnus expansion. Currently only **order 1** (time averaging) - is implemented: :math:`H_\text{eff} = H_0 + \bar f\,H_1`. Orders - higher than 1 raise :class:`NotImplementedError`. + via the Magnus expansion. Currently only the leading-order + (**order 1**, time averaging) term is implemented, and only for + :class:`~qdk_chemistry.data.time_dependent_qubit_hamiltonian.containers.driven.DrivenContainer` + Hamiltonians :math:`H(t) = H_0 + f(t)\,H_1`, for which the drive + integral reduces to a scalar quadrature and + + .. 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'. + + The propagator returns this + :math:`H_\text{eff} = \Omega_1 / \delta t` (the time-averaged exponent + divided by :math:`\delta t = t_2 - t_1`) so that a time-stepping + integrator that multiplies by :math:`\delta t` recovers the full + exponent :math:`\Omega_1`. This gives :math:`O(\Delta t^2)` accuracy. - For :class:`~qdk_chemistry.data.time_dependent_qubit_hamiltonian.containers.driven.DrivenContainer` - Hamiltonians the drive integral reduces to a scalar quadrature. + Requesting an ``order`` greater than 1, or passing any other container + type, raises :class:`NotImplementedError`. """ @@ -89,6 +138,10 @@ def _run_impl( Raises: ValueError: If *t_end* is not greater than *t_start*. + NotImplementedError: If the Hamiltonian container is not a + :class:`~qdk_chemistry.data.time_dependent_qubit_hamiltonian.containers.driven.DrivenContainer`, + or if the requested Magnus expansion ``order`` is greater + than 1. """ if t_end <= t_start: @@ -118,21 +171,34 @@ def _magnus_driven( ) -> QubitHamiltonian: r"""Order-1 Magnus expansion (time averaging) for driven Hamiltonians. - For the driven case :math:`H(t) = H_0 + f(t)\,H_1` every Magnus term - reduces to a linear combination of nested commutators of :math:`H_0` - and :math:`H_1` with scalar coefficients that are iterated integrals - over the drive function. The propagator returns - :math:`\Omega / \delta t` so that the builder (which multiplies by - ``dt``) recovers the full exponent. + For the driven case :math:`H(t) = H_0 + f(t)\,H_1` the order-1 + (leading) Magnus term is the time-averaged Hamiltonian + + .. math:: + + \Omega_1 = \int_{t_1}^{t_2} H(t')\,\mathrm{d}t' + = \delta t\,H_0 + + \left(\int_{t_1}^{t_2} f(t')\,\mathrm{d}t'\right) H_1, + + because :math:`H_0` is constant and only the scalar drive + :math:`f(t)` carries the time dependence. The drive integral is + evaluated by numerical quadrature. + + The propagator returns + :math:`H_\text{eff} = \Omega_1 / \delta t = H_0 + \bar f\,H_1`, + the exponent divided by :math:`\delta t = t_2 - t_1`, so that a + time-stepping integrator (which multiplies by ``dt``) recovers the + full exponent :math:`\Omega_1`. - Computes :math:`H_\text{eff} = \frac{1}{\delta t} \int_{t_1}^{t_2} H(t')\,\mathrm{d}t'` - where :math:`H(t) = H_0 + f(t)\,H_1`. + Only the leading-order term is computed; this gives + :math:`O(\delta t^2)` accuracy. Partitions from :math:`H_0` and :math:`H_1` are preserved via :meth:`~qdk_chemistry.data.QubitHamiltonian.__mul__` and :meth:`~qdk_chemistry.data.QubitHamiltonian.__add__`. - Returns :math:`H_\text{eff}`. + Returns the effective time-independent Hamiltonian + :math:`H_\text{eff}`. """ dt = t_end - t_start