LCOV - code coverage report
Current view: top level - src/pairinteraction_gui/page - lifetimes_page.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 22 59 37.3 %
Date: 2025-06-06 09:09:03 Functions: 1 10 10.0 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2025 Pairinteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4           1 : import logging
       5           1 : from typing import TYPE_CHECKING
       6             : 
       7           1 : import mplcursors
       8           1 : import numpy as np
       9             : 
      10           1 : from pairinteraction_gui.config import KetConfigLifetimes
      11           1 : from pairinteraction_gui.page.base_page import SimulationPage
      12           1 : from pairinteraction_gui.plotwidget.plotwidget import PlotWidget
      13           1 : from pairinteraction_gui.qobjects import show_status_tip
      14             : 
      15             : if TYPE_CHECKING:
      16             :     import pairinteraction.real as pi
      17             : 
      18           1 : logger = logging.getLogger(__name__)
      19             : 
      20             : 
      21           1 : class LifetimesPage(SimulationPage):
      22             :     """Page for calculating lifetimes."""
      23             : 
      24           1 :     title = "Lifetimes"
      25           1 :     tooltip = "Calculate the lifetimes and transition rates for a specified ket."
      26             : 
      27           1 :     ket_config: KetConfigLifetimes
      28             : 
      29           1 :     def setupWidget(self) -> None:
      30           1 :         self.plotwidget = PlotWidget(self)
      31           1 :         self.layout().addWidget(self.plotwidget)
      32           1 :         super().setupWidget()
      33             : 
      34           1 :         show_status_tip(self, "Ready", timeout=1)
      35             : 
      36             :         # all attributes of instance BaseConfig will be added to the toolbox in postSetupWidget
      37           1 :         self.ket_config = KetConfigLifetimes(self)
      38             : 
      39           1 :     def calculate(self) -> None:
      40             :         # since this calculation is rather fast, we can just call it directly and dont have to use a different process
      41           0 :         ket = self.ket_config.get_ket_atom(0)
      42           0 :         temperature = self.ket_config.get_temperature()
      43           0 :         self.kets_sp, self.transition_rates_sp = ket.get_spontaneous_transition_rates(unit="1/ms")
      44           0 :         self.kets_bbr, self.transition_rates_bbr = ket.get_black_body_transition_rates(temperature, "K", unit="1/ms")
      45             : 
      46           1 :     def update_plot(self) -> None:
      47           0 :         ax = self.plotwidget.canvas.ax
      48           0 :         ax.clear()
      49             : 
      50           0 :         n_list = np.arange(0, np.max([s.n for s in self.kets_bbr]) + 1)
      51           0 :         sorted_rates: dict[str, dict[int, list[tuple[pi.KetAtom, float]]]] = {}
      52           0 :         for key, kets, rates in [
      53             :             ("BBR", self.kets_bbr, self.transition_rates_bbr),
      54             :             ("SP", self.kets_sp, self.transition_rates_sp),
      55             :         ]:
      56           0 :             sorted_rates[key] = {n: [] for n in n_list}
      57           0 :             for i, s in enumerate(kets):
      58           0 :                 sorted_rates[key][s.n].append((s, rates[i]))
      59           0 :         self.sorted_rates = sorted_rates
      60             : 
      61           0 :         rates_summed = {key: [sum(rates for _, rates in sorted_rates[key][n]) for n in n_list] for key in sorted_rates}
      62           0 :         bar_sp = ax.bar(n_list, rates_summed["SP"], label="Spontaneous Decay", color="blue", alpha=0.8)
      63           0 :         bar_bbr = ax.bar(n_list, rates_summed["BBR"], label="Black Body Radiation", color="red", alpha=0.8)
      64           0 :         self.artists = (bar_sp, bar_bbr)
      65           0 :         ax.legend()
      66             : 
      67           0 :         ax.set_xlabel("Principal Quantum Number $n$")
      68           0 :         ax.set_ylabel(r"Transition Rates (1 / ms)")
      69             : 
      70           0 :         self.add_cursor()
      71             : 
      72           0 :         self.plotwidget.canvas.draw()
      73             : 
      74           1 :     def add_cursor(self) -> None:
      75             :         """Add interactive cursor to the plot."""
      76             :         # Remove any existing cursors to avoid duplicates
      77           0 :         if hasattr(self, "mpl_cursor"):
      78           0 :             if hasattr(self.mpl_cursor, "remove"):  # type: ignore
      79           0 :                 self.mpl_cursor.remove()  # type: ignore
      80           0 :             del self.mpl_cursor  # type: ignore
      81             : 
      82           0 :         self.mpl_cursor = mplcursors.cursor(
      83             :             self.artists,
      84             :             hover=mplcursors.HoverMode.Transient,
      85             :             annotation_kwargs={
      86             :                 "bbox": {"boxstyle": "round,pad=0.5", "fc": "white", "alpha": 0.9, "ec": "gray"},
      87             :                 "arrowprops": {"arrowstyle": "->", "connectionstyle": "arc3", "color": "gray"},
      88             :             },
      89             :         )
      90             : 
      91           0 :         @self.mpl_cursor.connect("add")
      92           0 :         def on_add(sel: mplcursors.Selection) -> None:
      93           0 :             label = sel.artist.get_label()
      94           0 :             x, y, width, height = sel.artist[sel.index].get_bbox().bounds
      95             : 
      96           0 :             n = round(x + width / 2)
      97           0 :             key = "BBR" if "Black Body" in label else "SP"
      98           0 :             state_text = "\n".join(f"{s}: {r:.5f}/ms" for (s, r) in self.sorted_rates[key][n])
      99           0 :             text = f"{label} to n={n}:\n{state_text}"
     100             : 
     101           0 :             sel.annotation.set(text=text, position=(0, 20), anncoords="offset points")
     102           0 :             sel.annotation.xy = (x + width / 2, y + height / 2)

Generated by: LCOV version 1.16