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, Literal
7 :
8 1 : import pytest
9 1 : from pairinteraction.units import ureg
10 :
11 : if TYPE_CHECKING:
12 : from .utils import PairinteractionModule
13 :
14 :
15 1 : def test_ket(pi_module: PairinteractionModule) -> None:
16 1 : ket = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
17 1 : assert ket.species == "Rb"
18 1 : assert ket.n == 60
19 1 : assert ket.l == 0
20 1 : assert pytest.approx(ket.s) == 0.5 # NOSONAR
21 1 : assert pytest.approx(ket.j) == 0.5 # NOSONAR
22 1 : assert pytest.approx(ket.m) == 0.5 # NOSONAR
23 1 : assert ket.parity == "even"
24 1 : assert ket.get_energy().units == ureg.Unit("bohr^2 electron_mass/atomic_unit_of_time^2")
25 1 : assert pytest.approx(ket.get_energy().magnitude) == 0.15335264334573842 # NOSONAR
26 1 : assert pytest.approx(ket.get_energy("GHz")) == 1009011.9215883961 # NOSONAR
27 :
28 1 : assert ket == pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
29 :
30 1 : ket_odd = pi_module.KetAtom("Rb", n=60, l=1, j=1.5, m=-0.5)
31 1 : assert ket_odd.l == 1
32 1 : assert pytest.approx(ket_odd.f) == 1.5 # NOSONAR
33 1 : assert pytest.approx(ket_odd.m) == -0.5 # NOSONAR
34 1 : assert ket_odd.parity == "odd"
35 :
36 1 : formats: list[Literal["raw", "ket", "bra"]] = ["raw", "ket", "bra"]
37 1 : for fmt in formats:
38 1 : label = ket_odd.get_label(fmt)
39 1 : assert all(str(qn) in label for qn in ["Rb", 60, "P", "3/2", "-1/2"])
40 :
41 1 : assert ket_odd != ket
42 1 : assert ket != pi_module.KetAtom("Rb", n=60, l=1, j=1.5, m=0.5)
43 :
44 1 : assert ket.get_matrix_element(ket_odd, "electric_dipole", q=-1) != 0
45 1 : assert ket.get_matrix_element(ket_odd, "electric_dipole", q=0) == 0
46 1 : assert ket.get_matrix_element(ket_odd, "electric_dipole", q=+1) == 0
47 :
48 :
49 1 : def test_get_label_sqdt(pi_module: PairinteractionModule) -> None:
50 1 : ket1 = pi_module.KetAtom("Rb", n=60, l=1, j=1.5, m=-0.5)
51 1 : assert ket1.get_label("raw") == "Rb:60,P_3/2,-1/2"
52 1 : ket2 = pi_module.KetAtom("Sr88_sqdt", n=60, l=0, s=0, j=0, m=0)
53 1 : assert ket2.get_label("raw") == "Sr88:S=0,60,S_0,0"
54 1 : ket3 = pi_module.KetAtom("Sr88_sqdt", n=60, l=1, s=1, j=2, m=1)
55 1 : assert ket3.get_label("raw") == "Sr88:S=1,60,P_2,1"
56 1 : ket4 = pi_module.KetAtom("Sr88_ion", n=60, l=0, j=0.5, m=0.5)
57 1 : assert ket4.get_label("raw") == "Sr88+:60,S_1/2,1/2"
58 1 : assert ket4.get_label("ket") == "|Sr88+:60,S_1/2,1/2⟩"
59 :
60 :
61 1 : def test_get_label_mqdt(pi_module: PairinteractionModule) -> None:
62 1 : ket1 = pi_module.KetAtom("Yb171_mqdt", nu=55.5, l=0, f=1.5, m=1.5)
63 1 : assert ket1.get_label("raw") == "Yb171:S=1.0,nu=55.6,L=0.0,F=3/2,3/2"
64 1 : ket2 = pi_module.KetAtom("Yb171_mqdt", nu=55.1, l=1, f=2.5, m=2.5)
65 1 : assert ket2.get_label("raw") == "Yb171:S=1.0,nu=55.1,L=1.0,F=5/2,5/2"
66 1 : ket3 = pi_module.KetAtom("Yb174_mqdt", nu=60, l=1, f=1, m=1)
67 1 : assert ket3.get_label("raw") == "Yb174:S=0.0,nu=60.0,L=1.0,J=1,1"
68 :
69 :
70 1 : def test_ket_equal(pi_module: PairinteractionModule) -> None:
71 1 : ket1 = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
72 1 : ket2 = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
73 1 : ket3 = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=-0.5)
74 1 : ket4 = pi_module.KetAtom("Rb", n=61, l=0, j=0.5, m=0.5)
75 1 : assert ket1 == ket2
76 1 : assert ket1 != ket3
77 1 : assert ket1 != ket4
78 :
79 :
80 1 : def test_ket_to_state(pi_module: PairinteractionModule) -> None:
81 1 : ket = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
82 1 : state = ket.to_state()
83 :
84 : # the state is trivial, i.e. a single ket with a coefficient of one
85 1 : assert isinstance(state, pi_module.StateAtom)
86 1 : assert state.number_of_kets == 1
87 1 : assert state.is_canonical
88 1 : assert state.is_normalized()
89 1 : assert state.get_corresponding_ket() == ket
90 1 : assert pytest.approx(state.get_coefficients()) == [1.0] # NOSONAR
91 1 : assert pytest.approx(state.get_overlap(ket)) == 1.0 # NOSONAR
92 :
93 : # the state has the same data type (real/complex) as the ket, so both can be combined
94 1 : combined = (state + pi_module.KetAtom("Rb", n=60, l=1, j=1.5, m=0.5).to_state()).normalize()
95 1 : assert combined.number_of_kets == 2
96 1 : assert pytest.approx(combined.get_overlap(ket)) == 0.5 # NOSONAR
97 :
98 :
99 1 : def test_ket_arithmetic(pi_module: PairinteractionModule) -> None:
100 1 : ket_s = pi_module.KetAtom("Rb", n=60, l=0, j=0.5, m=0.5)
101 1 : ket_p = pi_module.KetAtom("Rb", n=60, l=1, j=1.5, m=0.5)
102 :
103 : # adding two kets gives the equally weighted superposition
104 1 : combined = (ket_s + ket_p).normalize()
105 1 : assert isinstance(combined, pi_module.StateAtom)
106 1 : assert combined.number_of_kets == 2
107 1 : assert pytest.approx(combined.get_overlap(ket_s)) == 0.5 # NOSONAR
108 1 : assert pytest.approx(combined.get_overlap(ket_p)) == 0.5 # NOSONAR
109 :
110 : # multiplying with a scalar works from both sides and weights the kets accordingly
111 1 : for weighted in [(ket_s + 2 * ket_p).normalize(), (ket_s + ket_p * 2).normalize()]:
112 1 : assert pytest.approx(weighted.get_overlap(ket_s)) == 1 / 5 # NOSONAR
113 1 : assert pytest.approx(weighted.get_overlap(ket_p)) == 4 / 5 # NOSONAR
114 :
115 : # dividing by a scalar is equivalent to multiplying with the inverse
116 1 : assert pytest.approx((ket_s / 2).get_coefficients()) == (ket_s * 0.5).get_coefficients() # NOSONAR
117 :
118 : # negating a ket is equivalent to multiplying it with minus one
119 1 : assert pytest.approx((-ket_s).get_coefficients()) == (ket_s * -1).get_coefficients() # NOSONAR
120 1 : assert pytest.approx((-ket_s).get_amplitude(ket_s)) == -1.0 # NOSONAR
121 1 : assert ((ket_s + ket_p) + (-ket_p)).number_of_kets == 2
122 :
123 : # subtracting gives the same overlaps, but with an opposite relative sign
124 1 : difference = (ket_s - ket_p).normalize()
125 1 : assert pytest.approx(difference.get_overlap(ket_s)) == 0.5 # NOSONAR
126 1 : assert pytest.approx(difference.get_overlap(ket_p)) == 0.5 # NOSONAR
127 1 : assert difference.get_amplitude(ket_s) * difference.get_amplitude(ket_p) < 0 # type: ignore [operator]
128 :
129 : # a state and a ket can be added directly, in both orders
130 1 : state_s = ket_s.to_state()
131 1 : for mixed in [(state_s + ket_p).normalize(), (ket_p + state_s).normalize()]:
132 1 : assert mixed.number_of_kets == 2
133 1 : assert pytest.approx(mixed.get_overlap(ket_s)) == 0.5 # NOSONAR
134 1 : assert pytest.approx(mixed.get_overlap(ket_p)) == 0.5 # NOSONAR
135 :
136 : # a ket can also be combined with an already existing state, from both sides
137 1 : ket_d = pi_module.KetAtom("Rb", n=60, l=2, j=2.5, m=0.5)
138 1 : assert ((ket_s + ket_p) + ket_d).number_of_kets == 3
139 1 : assert (ket_d + (ket_s + ket_p)).number_of_kets == 3
140 1 : assert ((ket_s + ket_p) - ket_d).number_of_kets == 3
|