LCOV - code coverage report
Current view: top level - src/pairinteraction/ket - ket_pair.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 51 61 83.6 %
Date: 2026-08-14 15:26:44 Functions: 7 9 77.8 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2024 PairInteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3           1 : from __future__ import annotations
       4             : 
       5           1 : from collections.abc import Sequence
       6           1 : from typing import TYPE_CHECKING, Any, Literal, TypeGuard, overload
       7             : 
       8           1 : import numpy as np
       9           1 : from typing_extensions import TypeAliasType
      10             : 
      11           1 : from pairinteraction.ket.ket_atom import KetAtom
      12           1 : from pairinteraction.ket.ket_base import KetBase
      13             : 
      14             : if TYPE_CHECKING:
      15             :     from pairinteraction import _backend
      16             :     from pairinteraction.state import StateAtom
      17             :     from pairinteraction.system.system_atom import SystemAtom
      18             :     from pairinteraction.units import PintFloat
      19             : 
      20             : 
      21           1 : KetAtomTuple = TypeAliasType("KetAtomTuple", tuple[KetAtom, KetAtom] | Sequence[KetAtom])
      22             : 
      23             : 
      24           1 : def is_ket_pair_like(obj: Any) -> TypeGuard[KetPairLike]:
      25           1 :     return isinstance(obj, KetPair) or is_ket_atom_tuple(obj)
      26             : 
      27             : 
      28           1 : def is_ket_atom_tuple(obj: Any) -> TypeGuard[KetAtomTuple]:
      29           1 :     return hasattr(obj, "__len__") and len(obj) == 2 and all(isinstance(x, KetAtom) for x in obj)
      30             : 
      31             : 
      32           1 : class KetPair(KetBase):
      33             :     """Ket for a pair state of two atoms.
      34             : 
      35             :     For pair systems, we choose KetPair object as the product states of the single-atom eigenstates.
      36             :     Thus, the Ket pair objects depend on the system and the applied fields.
      37             :     Therefore for different pair systems the KetPair objects are not necessarily orthogonal anymore.
      38             : 
      39             :     Currently one cannot create a KetPair object directly, but they are used in the background when creating a
      40             :     :class:`pairinteraction.BasisPair` object.
      41             : 
      42             :     """
      43             : 
      44           1 :     _cpp: _backend.KetPairComplex
      45             : 
      46           1 :     def __init__(self) -> None:
      47             :         """Creating a KetPair object directly is not possible."""  # noqa: D401
      48           0 :         raise NotImplementedError("KetPair objects cannot be created directly.")
      49             : 
      50           1 :     @property
      51           1 :     def m(self) -> float:
      52             :         """The magnetic quantum number m (int or half-int)."""
      53           0 :         return self._cpp.get_quantum_number_m()
      54             : 
      55           1 :     def get_label(
      56             :         self,
      57             :         fmt: Literal["raw", "ket", "bra", "detailed"] = "raw",
      58             :         *,
      59             :         stop_after_num_kets: int = 3,
      60             :         stop_after_accumulated_overlap: float = 0.95,
      61             :     ) -> str:
      62             :         """Label representing the ket pair.
      63             : 
      64             :         Args:
      65             :             fmt: The format of the label, i.e. whether to return the raw label, or the label in ket or bra notation.
      66             :             stop_after_num_kets: Maximum number of single atom kets to include in the label for each StateAtom.
      67             :             stop_after_accumulated_overlap: Stop including kets in the single atom label,
      68             :                 if the accumulated overlap of the included kets exceeds this value.
      69             : 
      70             :         Returns:
      71             :             A string representation of the ket pair.
      72             : 
      73             :         """
      74           1 :         if fmt == "detailed":
      75           0 :             atom_labels = [
      76             :                 atom.get_label(stop_after_num_kets, stop_after_accumulated_overlap) for atom in self.state_atoms
      77             :             ]
      78           0 :             return f"({atom_labels[0]}) ⊗ ({atom_labels[1]})"
      79           1 :         return super().get_label(fmt)
      80             : 
      81           1 :     def _get_raw_label(self) -> str:
      82           1 :         precision = 100 * np.finfo(float).eps
      83           1 :         labels = []
      84           1 :         for state_atom in self.state_atoms:
      85           1 :             ket_idx = state_atom.get_corresponding_ket_index()
      86           1 :             coefficient = state_atom.get_coefficients()[ket_idx]
      87           1 :             optional_tilde = "~" if abs(coefficient - 1.0) > precision else ""
      88           1 :             labels.append(optional_tilde + state_atom.get_ket(ket_idx).get_label("raw"))
      89           1 :         return "; ".join(labels)
      90             : 
      91           1 :     @property
      92           1 :     def state_atoms(self) -> tuple[StateAtom, StateAtom]:
      93             :         """Return the state atoms of the ket pair."""
      94           1 :         from pairinteraction.state import StateAtom, StateAtomReal
      95             : 
      96           1 :         _state_atom_class = StateAtomReal if isinstance(self, KetPairReal) else StateAtom
      97             : 
      98           1 :         state_atoms = []
      99           1 :         for atomic_state in self._cpp.get_atomic_states():
     100           1 :             state = _state_atom_class._from_cpp_object(atomic_state)
     101           1 :             state_atoms.append(state)
     102           1 :         return tuple(state_atoms)  # type: ignore [return-value]
     103             : 
     104             : 
     105           1 : class KetPairReal(KetPair):
     106           1 :     _cpp: _backend.KetPairReal  # type: ignore [assignment]
     107             : 
     108             : 
     109           1 : KetPairLike = TypeAliasType("KetPairLike", KetPair | KetAtomTuple)
     110             : 
     111             : 
     112           1 : def get_ketpairlike_m(ket: KetPair | KetAtomTuple) -> float:
     113           1 :     if is_ket_atom_tuple(ket):
     114           1 :         m1 = ket[0].m
     115           1 :         m2 = ket[1].m
     116           1 :         return m1 + m2
     117           0 :     if isinstance(ket, KetPair):
     118           0 :         return ket.m
     119           0 :     raise TypeError(f"Unknown type: {type(ket)=}")
     120             : 
     121             : 
     122             : @overload
     123             : def get_ketpairlike_energy(
     124             :     ket: KetPair | KetAtomTuple, system_atoms: Sequence[SystemAtom], unit: None
     125             : ) -> PintFloat: ...
     126             : 
     127             : 
     128             : @overload
     129             : def get_ketpairlike_energy(ket: KetPair | KetAtomTuple, system_atoms: Sequence[SystemAtom], unit: str) -> float: ...
     130             : 
     131             : 
     132           1 : def get_ketpairlike_energy(
     133             :     ket: KetPair | KetAtomTuple, system_atoms: Sequence[SystemAtom], unit: str | None
     134             : ) -> float | PintFloat:
     135           1 :     if is_ket_atom_tuple(ket):
     136           1 :         energy1 = system_atoms[0].get_corresponding_energy(ket[0], unit)
     137           1 :         energy2 = system_atoms[1].get_corresponding_energy(ket[1], unit)
     138           1 :         return energy1 + energy2
     139           0 :     if isinstance(ket, KetPair):
     140           0 :         return ket.get_energy(unit)
     141           0 :     raise TypeError(f"Unknown type: {type(ket)=}")

Generated by: LCOV version 1.16