Source code for qdk_chemistry.algorithms.hamiltonian_unitary_builder.base

"""QDK/Chemistry Hamiltonian unitary builder abstractions."""

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

from abc import abstractmethod

import numpy as np

from qdk_chemistry.algorithms.base import Algorithm, AlgorithmFactory
from qdk_chemistry.data import (
    FlatPartition,
    LayeredPartition,
    QubitOperator,
    Settings,
    TermPartition,
    UnitaryRepresentation,
)
from qdk_chemistry.data.unitary_representation.containers.pauli_product_formula import ExponentiatedPauliTerm
from qdk_chemistry.utils import Logger

__all__: list[str] = [
    "HamiltonianUnitaryBuilder",
    "HamiltonianUnitaryBuilderFactory",
    "HamiltonianUnitaryBuilderSettings",
    "TimeEvolutionBuilder",
    "TimeEvolutionSettings",
]


[docs] class HamiltonianUnitaryBuilder(Algorithm): """Base class for Hamiltonian unitary builders in QDK/Chemistry algorithms."""
[docs] def __init__(self): """Initialize the HamiltonianUnitaryBuilder.""" super().__init__()
@abstractmethod def _run_impl(self, qubit_hamiltonian: QubitOperator) -> UnitaryRepresentation: """Construct a UnitaryRepresentation for the given Hamiltonian. Args: qubit_hamiltonian: The qubit Hamiltonian. Returns: UnitaryRepresentation: A UnitaryRepresentation for the given Hamiltonian. """ @staticmethod def _pauli_label_to_map(label: str) -> dict[int, str]: """Translate a Pauli label to a mapping ``qubit -> {X, Y, Z}``. Args: label: Pauli string label in little-endian ordering. Returns: Dictionary assigning each non-identity qubit index to its Pauli axis. """ mapping: dict[int, str] = {} for index, char in enumerate(reversed(label)): # reversed: right-most char -> qubit 0 if char != "I": mapping[index] = char return mapping
[docs] class HamiltonianUnitaryBuilderSettings(Settings): """Base settings for Hamiltonian unitary builders."""
[docs] def __init__(self): """Initialize HamiltonianUnitaryBuilderSettings with default values. Attributes: power: The exponent to which the unitary is raised. """ super().__init__() self._set_default("power", "int", 1, "The power to raise the unitary to.")
[docs] class TimeEvolutionSettings(HamiltonianUnitaryBuilderSettings): """Base settings for time evolution builders."""
[docs] def __init__(self): r"""Initialize TimeEvolutionSettings with default values. Attributes: time: The evolution time. power_strategy: The strategy to construct :math:`U^{\\text{power}}`: * ``"rescale"``: produce a single step with effective time :math:`t \\cdot \\text{power}`. * ``"repeat"``: repeat the base :math:`U(t)` ``power`` times. """ super().__init__() self._set_default("time", "float", 0.0, "The evolution time.") self._set_default( "power_strategy", "string", "repeat", "The strategy to construct U^power: 'rescale' multiplies evolution time by power; " "'repeat' repeats the base U power times.", ["rescale", "repeat"], )
[docs] class TimeEvolutionBuilder(HamiltonianUnitaryBuilder): """Base class for time evolution Builders in QDK/Chemistry algorithms."""
[docs] def __init__(self): """Initialize the TimeEvolutionBuilder.""" super().__init__()
def _resolve_power(self) -> tuple[float, int]: """Resolve the power setting into effective time scale and power repetitions. Based on the ``power`` and ``power_strategy`` settings, returns: - For ``"rescale"``: (time * power, 1) — scales the evolution time. - For ``"repeat"``: (time, power) — repeats the base circuit. Returns: A tuple (effective_time, power_repetitions). """ time: float = self._settings.get("time") power: int = self._settings.get("power") strategy: str = self._settings.get("power_strategy") if strategy == "rescale": return time * power, 1 return time, power @abstractmethod def _run_impl(self, qubit_hamiltonian: QubitOperator) -> UnitaryRepresentation: """Construct a UnitaryRepresentation representing the time evolution unitary for the given QubitOperator. Args: qubit_hamiltonian: The qubit Hamiltonian. Returns: UnitaryRepresentation: A UnitaryRepresentation representing the evolution of the given QubitOperator. """ def _group_terms( self, qubit_hamiltonian: QubitOperator, ) -> list[list[QubitOperator]]: """Group Hamiltonian terms for decomposition.""" partition = qubit_hamiltonian.term_partition if partition is not None: Logger.debug( f"{self.name().capitalize()}: consuming QubitOperator.term_partition " f"(strategy={partition.strategy!r}, num_groups={partition.num_groups})." ) return self._groups_from_partition(qubit_hamiltonian, partition) Logger.debug( f"{self.name().capitalize()}: no term_partition present; treating each Pauli term as its own group." ) return [ [ QubitOperator( pauli_strings=[label], coefficients=[coeff], encoding=qubit_hamiltonian.encoding, fermion_mode_order=qubit_hamiltonian.fermion_mode_order, ) ] for label, coeff in zip(qubit_hamiltonian.pauli_strings, qubit_hamiltonian.coefficients, strict=True) ] def _groups_from_partition( self, qubit_hamiltonian: QubitOperator, partition: TermPartition, ) -> list[list[QubitOperator]]: """Materialise a :class:`TermPartition` into sub-groups.""" labels = qubit_hamiltonian.pauli_strings coeffs = qubit_hamiltonian.coefficients encoding = qubit_hamiltonian.encoding fmo = qubit_hamiltonian.fermion_mode_order def _make(indices: tuple[int, ...]) -> QubitOperator: return QubitOperator( pauli_strings=[labels[i] for i in indices], coefficients=np.asarray([coeffs[i] for i in indices]), encoding=encoding, fermion_mode_order=fmo, ) if isinstance(partition, LayeredPartition): layered_groups = partition.groups elif isinstance(partition, FlatPartition): layered_groups = tuple((g,) for g in partition.groups) else: raise TypeError( f"Unsupported TermPartition subtype: {type(partition).__name__}. " "Expected FlatPartition or LayeredPartition." ) groups: list[list[QubitOperator]] = [ [_make(layer) for layer in group_layers if layer] for group_layers in layered_groups ] groups = [g for g in groups if g] groups.sort(key=len) return groups def _exponentiate_commuting( self, group: QubitOperator, time: float, *, atol: float = 1e-12, ) -> list[ExponentiatedPauliTerm]: r"""Exponentiate a group of commuting Pauli terms. Each term :math:`P_j` with coefficient :math:`c_j` is converted to the rotation :math:`e^{-i\,c_j\,t\,P_j}`. Because all terms in the group commute, the product of rotations equals the exponential of the sum regardless of ordering. Args: group: The group of commuting Hamiltonian terms to exponentiate. time: The evolution time used to compute rotation angles (:math:`\theta_j = c_j \cdot t`). atol: Absolute tolerance for filtering small coefficients. Returns: A flat list of ExponentiatedPauliTerm. """ terms: list[ExponentiatedPauliTerm] = [] for label, coeff in group.get_real_coefficients(tolerance=atol): mapping = self._pauli_label_to_map(label) angle = coeff * time terms.append(ExponentiatedPauliTerm(pauli_term=mapping, angle=angle)) return terms
[docs] class HamiltonianUnitaryBuilderFactory(AlgorithmFactory): """Factory class for creating HamiltonianUnitaryBuilder instances."""
[docs] def algorithm_type_name(self) -> str: """Return hamiltonian_unitary_builder as the algorithm type name.""" return "hamiltonian_unitary_builder"
[docs] def default_algorithm_name(self) -> str: """Return Trotter as the default algorithm name.""" return "trotter"