Line data Source code
1 : # SPDX-FileCopyrightText: 2024 PairInteraction Developers
2 : # SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 1 : from __future__ import annotations
5 :
6 1 : from typing import TYPE_CHECKING, Any, Literal
7 :
8 1 : import numpy as np
9 1 : import pytest
10 1 : from pairinteraction_gui.main_window import MainWindow
11 :
12 1 : from .utils import REFERENCE_PATHS, compare_eigensystem_to_reference, no_log_propagation
13 :
14 : if TYPE_CHECKING:
15 : from pathlib import Path
16 :
17 : from pairinteraction_gui.page import LifetimesPage, OneAtomPage
18 : from pairinteraction_gui.page.two_atoms_page import TwoAtomsPage
19 : from pytestqt.qtbot import QtBot
20 :
21 :
22 1 : @pytest.fixture
23 1 : def base_window(qtbot: QtBot, tmp_path: Path) -> MainWindow:
24 1 : window = MainWindow(cache_dir=tmp_path)
25 1 : window.show()
26 1 : qtbot.addWidget(window)
27 1 : return window
28 :
29 :
30 1 : @pytest.fixture
31 1 : def window_starkmap(base_window: MainWindow) -> MainWindow:
32 1 : one_atom_page: OneAtomPage = base_window.stacked_pages.getNamedWidget("OneAtomPage") # type: ignore [assignment]
33 1 : one_atom_page.ket_config.species_combo_list[0].setCurrentText("Rb")
34 1 : ket_qn = one_atom_page.ket_config.stacked_qn_list[0].currentWidget()
35 1 : ket_qn.items["n"].setValue(60)
36 1 : ket_qn.items["l"].setValue(0)
37 1 : ket_qn.items["m"].setValue(0.5)
38 :
39 1 : basis_qn = one_atom_page.basis_config.stacked_basis_list[0].currentWidget()
40 1 : basis_qn.items["n"].setValue(2)
41 1 : basis_qn.items["l"].setValue(2)
42 1 : basis_qn.items["m"].setChecked(False)
43 :
44 1 : calculation_config = one_atom_page.calculation_config
45 1 : calculation_config.steps.setValue(11)
46 1 : system_config = one_atom_page.system_config
47 1 : system_config.Ez.spinboxes[1].setValue(10)
48 :
49 1 : return base_window
50 :
51 :
52 1 : @pytest.fixture
53 1 : def window_pair_potential(base_window: MainWindow) -> MainWindow:
54 1 : two_atoms_page: TwoAtomsPage = base_window.stacked_pages.getNamedWidget("TwoAtomsPage") # type: ignore [assignment]
55 1 : two_atoms_page.ket_config.species_combo_list[0].setCurrentText("Rb")
56 1 : for ket_qn_stacked in two_atoms_page.ket_config.stacked_qn_list:
57 1 : ket_qn = ket_qn_stacked.currentWidget()
58 1 : ket_qn.items["n"].setValue(60)
59 1 : ket_qn.items["l"].setValue(0)
60 1 : ket_qn.items["m"].setValue(0.5)
61 :
62 1 : for basis_qn_stacked in two_atoms_page.basis_config.stacked_basis_list:
63 1 : basis_qn = basis_qn_stacked.currentWidget()
64 1 : basis_qn.items["n"].setValue(2)
65 1 : basis_qn.items["l"].setValue(2)
66 1 : basis_qn.items["m"].setChecked(False)
67 :
68 1 : two_atoms_page.basis_config.pair_delta_energy.setValue(3)
69 1 : two_atoms_page.basis_config.pair_m_range.setValues(1, 1)
70 :
71 1 : calculation_config = two_atoms_page.calculation_config
72 1 : calculation_config.steps.setValue(5)
73 1 : system_config = two_atoms_page.system_config
74 1 : system_config.distance.setValues(1, 5)
75 1 : return base_window
76 :
77 :
78 1 : @pytest.fixture
79 1 : def window_lifetimes(base_window: MainWindow) -> MainWindow:
80 1 : lifetimes_page: LifetimesPage = base_window.stacked_pages.getNamedWidget("LifetimesPage") # type: ignore [assignment]
81 1 : lifetimes_page.ket_config.species_combo_list[0].setCurrentText("Rb")
82 1 : ket_qn = lifetimes_page.ket_config.stacked_qn_list[0].currentWidget()
83 1 : ket_qn.items["n"].setValue(60)
84 1 : ket_qn.items["l"].setValue(0)
85 1 : ket_qn.items["m"].setValue(0.5)
86 1 : lifetimes_page.ket_config.item_temperature.setValue(300)
87 1 : return base_window
88 :
89 :
90 1 : def test_main_window_basic(qtbot: QtBot, window_starkmap: MainWindow) -> None:
91 : """Test basic main window functionality."""
92 1 : one_atom_page: OneAtomPage = window_starkmap.stacked_pages.getNamedWidget("OneAtomPage") # type: ignore [assignment]
93 1 : qn_item = one_atom_page.ket_config.stacked_qn_list[0].currentWidget().items["n"]
94 1 : qn_item.setValue(60)
95 :
96 1 : ket_label = one_atom_page.ket_config.ket_label_list[0].text()
97 1 : assert all(x in ket_label for x in ["Rb", "60", "S", "1/2"])
98 1 : assert qn_item.label.text() == "n"
99 1 : assert qn_item.value() == 60
100 :
101 1 : qn_item.setValue(61)
102 1 : ket_label = one_atom_page.ket_config.ket_label_list[0].text()
103 1 : assert qn_item.value() == 61
104 1 : assert all(x in ket_label for x in ["Rb", "61", "S", "1/2"])
105 :
106 : # make the basis smaller for faster test
107 1 : basis_qn = one_atom_page.basis_config.stacked_basis_list[0].currentWidget()
108 1 : basis_qn.items["n"].setValue(1)
109 1 : basis_qn.items["l"].setValue(1)
110 1 : basis_qn.items["m"].setValue(0)
111 :
112 1 : one_atom_page.calculate_and_abort.getNamedWidget("Calculate").click()
113 1 : qtbot.waitUntil(lambda: one_atom_page._calculation_finished, timeout=30_000) # ci macOS-15-intel is very slow
114 1 : qtbot.waitUntil(lambda: one_atom_page._plot_finished, timeout=5_000)
115 1 : window_starkmap.close()
116 :
117 :
118 1 : def test_one_atom_page(window_starkmap: MainWindow) -> None:
119 1 : _test_calculate_page(window_starkmap, "OneAtomPage", "stark_map")
120 :
121 :
122 1 : def test_two_atoms_page(window_pair_potential: MainWindow) -> None:
123 1 : _test_calculate_page(window_pair_potential, "TwoAtomsPage", "pair_potential")
124 :
125 :
126 1 : def test_lifetimes_page(window_lifetimes: MainWindow) -> None:
127 1 : page: LifetimesPage = window_lifetimes.stacked_pages.getNamedWidget("LifetimesPage") # type: ignore [assignment]
128 1 : with no_log_propagation("cpp"): # suppress low n state warnings from lifetime calculation
129 1 : parameters, results = page.calculate()
130 1 : page.plotwidget.plot(parameters, results)
131 :
132 1 : assert results.lifetime > 0
133 1 : assert len(results.kets_sp) == len(results.transition_rates_sp)
134 1 : assert len(results.kets_bbr) == len(results.transition_rates_bbr)
135 :
136 1 : python_code = page._create_python_code()
137 1 : python_code = python_code.replace("plt.show()", "plt.close()") # HACK, otherwise it will block the test
138 :
139 1 : locals_globals: dict[str, Any] = {}
140 1 : with no_log_propagation("cpp"): # suppress low n state warnings from lifetime calculation
141 1 : exec(python_code, locals_globals, locals_globals) # noqa: S102
142 :
143 1 : rates_dict = {
144 : key: [sum(r for _, r in page.plotwidget.sorted_rates[label][n]) for n in locals_globals["n_list"]]
145 : for key, label in [("SP", "Spontaneous Decay"), ("BBR", "Black Body Radiation")]
146 : }
147 :
148 1 : assert np.isclose(locals_globals["lifetime"], results.lifetime)
149 1 : for key, rates in rates_dict.items():
150 1 : assert np.allclose(locals_globals["rates_summed"][key], rates)
151 :
152 :
153 1 : def _test_calculate_page(
154 : window: MainWindow,
155 : page_name: Literal["OneAtomPage", "TwoAtomsPage"],
156 : reference_name: str,
157 : ) -> None:
158 1 : page: OneAtomPage | TwoAtomsPage = window.stacked_pages.getNamedWidget(page_name) # type: ignore [assignment]
159 1 : ket_energy_0 = sum(page.ket_config.get_ket_atom(i).get_energy("GHz") for i in range(page.ket_config.n_atoms))
160 :
161 : # Test calculation with fast mode off
162 1 : page.calculation_config.fast_mode.setChecked(False)
163 1 : _parameters, results = page.calculate()
164 1 : energies = np.array(results.energies) + ket_energy_0
165 1 : compare_eigensystem_to_reference(REFERENCE_PATHS[reference_name], energies, np.array(results.ket_overlaps))
166 :
167 : # Test calculation with fast mode on and diagonalze energy_range
168 : # NOTE: with fast mode, the overlaps are different, so we don't compare them
169 1 : page.calculation_config.fast_mode.setChecked(True)
170 1 : page.calculation_config.energy_range.setChecked(True)
171 1 : page.calculation_config.energy_range.setValues(-200, 200)
172 1 : parameters, results = page.calculate()
173 1 : energies = np.array(results.energies) + ket_energy_0
174 1 : compare_eigensystem_to_reference(REFERENCE_PATHS[reference_name], energies)
175 1 : page.plotwidget.plot(parameters, results)
176 :
177 1 : if page_name == "TwoAtomsPage":
178 : # Test fitting of c3/c6/c3+c6 potential curves
179 : # We need to pass the data to the plotwidget.
180 1 : page.plotwidget.fit("c3")
181 : # We run a test fit twice, as it should iterate through the potential curves
182 1 : page.plotwidget.fit("c3")
183 1 : page.plotwidget.fit("c6")
184 1 : page.plotwidget.fit("c3+c6")
185 : # One could consider adding some return values to check if the estimated values are reasonable.
186 : # Currently fit does not return anything, it just plots and shows the fit parameters in UI
187 :
188 : # Test export to Python code
189 1 : python_code = page._create_python_code()
190 1 : python_code = python_code.replace("plt.show()", "plt.close()") # HACK, otherwise it will block the test
191 :
192 : # HACK, see also https://stackoverflow.com/questions/45132645/list-comprehension-in-exec-with-empty-locals-nameerror
193 1 : locals_globals: dict[str, Any] = {}
194 1 : exec(python_code, locals_globals, locals_globals) # noqa: S102
195 1 : energies = np.array(locals_globals["energies_list"]) + ket_energy_0
196 1 : compare_eigensystem_to_reference(REFERENCE_PATHS[reference_name], energies)
197 :
198 :
199 1 : def test_autosave_skips_unchanged_settings(base_window: MainWindow) -> None:
200 : """The periodic autosave must not rewrite the ini file while the user is idle."""
201 1 : one_atom_page: OneAtomPage = base_window.stacked_pages.getNamedWidget("OneAtomPage") # type: ignore [assignment]
202 1 : ket_qn = one_atom_page.ket_config.stacked_qn_list[0].currentWidget()
203 :
204 1 : assert base_window.save_settings() # the first save writes the initial state
205 :
206 1 : for _ in range(3): # further autosave ticks without user interaction must not touch the file
207 1 : assert not base_window.save_settings()
208 :
209 1 : ket_qn.items["n"].setValue(123)
210 1 : assert base_window.save_settings()
211 1 : assert base_window.settings_manager.value("OneAtomPage/ket_config/atom0_n", value_type=int) == 123
212 :
213 1 : base_window.close()
214 :
215 :
216 1 : def test_save_and_restore_settings(qtbot: QtBot, tmp_path: Path) -> None:
217 1 : window = MainWindow(cache_dir=tmp_path)
218 1 : window.show()
219 1 : qtbot.addWidget(window)
220 1 : one_atom_page: OneAtomPage = window.stacked_pages.getNamedWidget("OneAtomPage") # type: ignore [assignment]
221 1 : ket_qn = one_atom_page.ket_config.stacked_qn_list[0].currentWidget()
222 1 : ket_qn.items["n"].setValue(222)
223 1 : ket_qn.items["l"].setValue(999)
224 1 : window.close()
225 :
226 1 : window = MainWindow(cache_dir=tmp_path)
227 1 : window.show()
228 1 : qtbot.addWidget(window)
229 1 : one_atom_page = window.stacked_pages.getNamedWidget("OneAtomPage") # type: ignore [assignment]
230 1 : ket_qn = one_atom_page.ket_config.stacked_qn_list[0].currentWidget()
231 :
232 1 : assert ket_qn.items["n"].value() == 222
233 1 : assert ket_qn.items["l"].value() == 999
234 :
235 1 : window.close()
|