Source code for qdk_chemistry.algorithms.time_evolution.evolution_circuit_builder.euler_builder

r"""Euler evolution circuit builder.

Builds a state-preparation + time-evolution circuit by dividing
:math:`[0, T]` into Euler steps of size ``dt``, applying a propagator
at each step to compute the effective Hamiltonian, Trotterizing, and
mapping to a quantum circuit.  No circuit execution is performed.

The default propagator (``magnus``) computes the Magnus-expanded
Hamiltonian over each interval, giving second-order global accuracy for
smooth drives.  Other propagators can be substituted via the
``propagator`` setting.
"""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from __future__ import annotations

from typing import TYPE_CHECKING

from qdk_chemistry.algorithms.hamiltonian_unitary_builder.base import TimeEvolutionBuilder
from qdk_chemistry.algorithms.propagator.base import Propagator
from qdk_chemistry.data import (
    Circuit,
    QubitOperator,
    TimeDependentQubitHamiltonian,
    UnitaryRepresentation,
)
from qdk_chemistry.data.circuit import QsharpFactoryData
from qdk_chemistry.utils import Logger
from qdk_chemistry.utils.qsharp import QSHARP_UTILS

from .base import EvolutionCircuitBuilder, EvolutionCircuitBuilderSettings

if TYPE_CHECKING:
    from qdk_chemistry.data.unitary_representation.containers.base import UnitaryContainer

__all__: list[str] = ["EulerEvolutionCircuitBuilder", "EulerEvolutionCircuitBuilderSettings"]


[docs] class EulerEvolutionCircuitBuilderSettings(EvolutionCircuitBuilderSettings): """Settings for the Euler evolution circuit builder."""
[docs] def __init__(self): """Initialize the settings for EulerEvolutionCircuitBuilder.""" super().__init__()
[docs] class EulerEvolutionCircuitBuilder(EvolutionCircuitBuilder): r"""Euler-step evolution circuit builder. Divides :math:`[0, T]` into steps of size ``dt``. At each step, the configured propagator evaluates the effective Hamiltonian, the evolution builder Trotterizes it, and the circuit mapper compiles it to QIR. The resulting per-step circuits are combined via :meth:`~qdk_chemistry.data.unitary_representation.containers.base.UnitaryContainer.combine`. The output is a single :class:`~qdk_chemistry.data.circuit.Circuit` (state-prep + evolution) that can be passed to ``circuit.get_qre_application()`` for resource estimation. """
[docs] def __init__(self): """Initialize EulerEvolutionCircuitBuilder.""" Logger.trace_entering() super().__init__() self._settings = EulerEvolutionCircuitBuilderSettings()
def _run_impl( self, hamiltonian: TimeDependentQubitHamiltonian, state_prep: Circuit, ) -> Circuit: """Build the evolution circuit. Args: hamiltonian: Time-dependent Hamiltonian. state_prep: Circuit that prepares the initial state. Returns: The combined state-prep + evolution circuit. """ total_time: float = self._settings.get("total_time") evolution = self._build_time_dependent_evolution(hamiltonian, total_time) circuit = self._map_to_circuit(evolution) return self._prepend_state_prep(state_prep, circuit, hamiltonian.num_qubits) def _build_time_dependent_evolution( self, hamiltonian: TimeDependentQubitHamiltonian, total_time: float, ) -> UnitaryRepresentation: r"""Build the combined unitary via Euler steps. Divides :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. Args: hamiltonian: The time-dependent qubit Hamiltonian. total_time: Total evolution time. Returns: Combined ``UnitaryRepresentation`` for the full evolution. Raises: ValueError: If ``dt`` or ``total_time`` are invalid. TypeError: If the propagator is not a ``Propagator``. """ dt: float = self._settings.get("dt") if total_time == 0.0: raise ValueError("total_time must be nonzero.") if dt == 0.0: raise ValueError(f"dt ({dt}) must be nonzero.") if dt / total_time > 1: raise ValueError(f"dt ({dt}) must not exceed total_time ({total_time}).") if dt / total_time < 0: raise ValueError(f"dt ({dt}) must match the sign of total_time ({total_time}).") num_full_steps = int(total_time / dt) residual = total_time - num_full_steps * dt combined_container: UnitaryContainer | None = None propagator = self._create_nested("propagator") if not isinstance(propagator, Propagator): raise TypeError(f"propagator must be a Propagator, got {type(propagator).__name__}.") for i in range(num_full_steps): t_start = i * dt t_end = (i + 1) * dt h_snapshot = propagator.run(hamiltonian, t_start, t_end) step_evolution = self._create_time_step_evolution(h_snapshot, dt) time_step_container = step_evolution.get_container() if combined_container is None: combined_container = time_step_container else: combined_container = combined_container.combine(time_step_container) if residual != 0.0: t_start = num_full_steps * dt t_end = total_time h_snapshot = propagator.run(hamiltonian, t_start, t_end) step_evolution = self._create_time_step_evolution(h_snapshot, residual) time_step_container = step_evolution.get_container() if combined_container is None: combined_container = time_step_container else: combined_container = combined_container.combine(time_step_container) return UnitaryRepresentation(container=combined_container) def _create_time_step_evolution( self, qubit_hamiltonian: QubitOperator, time: float, ) -> UnitaryRepresentation: """Create the time-evolution unitary for one step. Args: qubit_hamiltonian: The qubit Hamiltonian for this time step. time: Duration of the time step. Returns: The unitary representation for this step. Raises: TypeError: If the evolution builder is not a ``TimeEvolutionBuilder``. """ evolution_builder = self._create_nested("evolution_builder") if not isinstance(evolution_builder, TimeEvolutionBuilder): raise TypeError( f"evolution_builder must be a TimeEvolutionBuilder, got {type(evolution_builder).__name__}." ) evolution_builder.settings().set("time", time) return evolution_builder.run(qubit_hamiltonian) def _map_to_circuit(self, evolution: UnitaryRepresentation) -> Circuit: """Map a time-evolution unitary into an executable circuit. Args: evolution: The unitary representation to compile. Returns: The compiled quantum circuit. """ circuit_mapper = self._create_nested("circuit_mapper") return circuit_mapper.run(evolution) def _prepend_state_prep(self, state_prep: Circuit, circuit: Circuit, num_qubits: int) -> Circuit: """Compose state-preparation and evolution circuits. Args: state_prep: Circuit that prepares the initial state. circuit: The evolution circuit to prepend state-prep to. num_qubits: Number of qubits in the system. Returns: The combined state-prep + evolution circuit. Raises: RuntimeError: If either circuit lacks a Q# operation handle. ValueError: If the circuits have incompatible encodings. """ state_prep_op = state_prep._qsharp_op # noqa: SLF001 circuit_op = circuit._qsharp_op # noqa: SLF001 if state_prep_op is None or circuit_op is None: raise RuntimeError("State-preparation circuit composition requires Q# operations on both circuits.") if state_prep.encoding is not None and circuit.encoding is not None and state_prep.encoding != circuit.encoding: raise ValueError( "State-preparation circuit and evolution circuit use different encodings " f"('{state_prep.encoding}' and '{circuit.encoding}')." ) target_indices = list(range(num_qubits)) combined_encoding = circuit.encoding if circuit.encoding is not None else state_prep.encoding sequential_parameters = { "first": state_prep_op, "second": circuit_op, "targets": target_indices, } return Circuit( qsharp_factory=QsharpFactoryData( program=QSHARP_UTILS.CircuitComposition.MakeSequentialCircuit, parameter=sequential_parameters, ), qsharp_op=QSHARP_UTILS.CircuitComposition.MakeSequentialOp(state_prep_op, circuit_op), encoding=combined_encoding, )
[docs] def name(self) -> str: """Return ``euler`` as the algorithm name.""" return "euler"