LCOV - code coverage report
Current view: top level - src/pairinteraction_gui/page - lifetimes_page.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 58 0.0 %
Date: 2025-05-02 21:49:59 Functions: 0 10 0.0 %

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

Generated by: LCOV version 1.16