Skip to content

Commit 61fbd95

Browse files
RydbergStateMQDT add principal quantum number n
1 parent 50ae5c2 commit 61fbd95

4 files changed

Lines changed: 89 additions & 9 deletions

File tree

src/rydstate/generate_database/generate_states_table.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
import numpy as np
77

8-
from rydstate.rydberg_state.rydberg_sqdt import RydbergStateSQDT
9-
108
if TYPE_CHECKING:
119
from rydstate.basis import BasisMQDT, BasisSQDT
1210
from rydstate.rydberg_state.rydberg_base import RydbergStateBase
@@ -63,13 +61,11 @@ def get_state_data(ids: int, state: RydbergStateBase) -> tuple[float | int | str
6361
angular = state.angular
6462
underspecified_channel_contribution = sum(abs(coeff) ** 2 for coeff, ket in state if ket.angular.contains_unknown)
6563

66-
n = state.n if isinstance(state, RydbergStateSQDT) else 0
67-
6864
data = (
6965
ids, # id
7066
state.get_energy("a.u."), # energy
7167
angular.parity, # parity = (-1)^l_tot
72-
n, # n: quantum number
68+
state.n, # n
7369
state.nu, # nu
7470
angular.f_tot, # f_tot
7571
state.calc_exp_qn("nui"), # exp_nui

src/rydstate/rydberg_state/rydberg_base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ class RydbergStateBase(ABC):
4545
_energy_au: float
4646
"""The energy of the Rydberg state in atomic units (Hartree)."""
4747

48+
n: int
49+
"""The principal quantum number n of the Rydberg state.
50+
51+
For MQDT states, we define the corresponding principal quantum number n via the number of nodes
52+
in the radial wavefunction of the most dominant channel.
53+
"""
54+
4855
def __init__(self) -> None:
4956
if abs(self.norm - 1) > 1e-10:
5057
raise ValueError(
@@ -209,10 +216,7 @@ def calc_exp_qn(self, qn: str) -> float:
209216
if qn == "nu":
210217
return self.nu
211218
if qn == "n":
212-
n = getattr(self, "n", None)
213-
if n is None:
214-
raise ValueError(f"{self} has no quantum number n")
215-
return n # type: ignore [no-any-return]
219+
return self.n
216220
if qn == "nui":
217221
return float(sum([abs(coeff) ** 2 * ket.radial.nu / self.norm**2 for coeff, ket in self]))
218222
raise ValueError(f"Unknown quantum number {qn}")

src/rydstate/rydberg_state/rydberg_mqdt.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from __future__ import annotations
22

33
import logging
4+
from functools import cached_property
45
from typing import TYPE_CHECKING, Any
56

67
import numpy as np
78

89
from rydstate.angular import AngularKetFJ, AngularState
10+
from rydstate.angular.utils import is_unknown
911
from rydstate.rydberg_state.rydberg_base import RydbergStateBase
1012

1113
if TYPE_CHECKING:
@@ -67,3 +69,23 @@ def __str__(self) -> str:
6769
def mqdt(self) -> MQDT:
6870
"""Return the MQDT object used to calculate this state."""
6971
return self.model.mqdt
72+
73+
@cached_property
74+
def n(self) -> int: # type: ignore [override]
75+
"""Return the corresponding principal quantum number n of the state.
76+
77+
We define the corresponding principal quantum number n for MQDT states via the nodes of
78+
the main contributing rydberg ket (nodes = n - l_r - 1).
79+
For FModelSQDT states, the quantum defect is zero, so the channel dependent effective quantum number nui
80+
is already an integer and we simply round it to the nearest integer.
81+
"""
82+
defects = self.model.eigen_quantum_defects
83+
if (
84+
len(defects) == 1 and np.isscalar(defects[0]) and abs(defects[0]) < 1e-10 # type: ignore [arg-type]
85+
):
86+
return round(self.nui[0])
87+
88+
main_ket = max(
89+
[(coeff, ket) for coeff, ket in self if not is_unknown(ket.angular.l_r)], key=lambda x: abs(x[0])
90+
)[1]
91+
return main_ket.radial.nodes + main_ket.angular.l_r + 1

tests/test_rydberg_state_mqdt.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from __future__ import annotations
22

3+
import itertools
34
from typing import TYPE_CHECKING
45

56
import numpy as np
67
import pytest
78
from rydstate import BasisMQDT, RydbergStateSQDT
9+
from rydstate.angular.utils import is_unknown
10+
from rydstate.species import FModelSQDT
811

912
if TYPE_CHECKING:
1013
from rydstate import RydbergStateMQDT
@@ -134,3 +137,58 @@ def test_matrix_element_between_mqdt_and_sqdt_state(basis: BasisMQDT) -> None:
134137
me = s_mqdt.calc_reduced_matrix_element(p_sqdt, "electric_dipole", unit="e a0")
135138
assert np.isfinite(me)
136139
assert abs(me) > 0.0
140+
141+
142+
def test_n_of_sqdt_fallback_model_uses_channel_nui() -> None:
143+
"""For SQDT fallback models n is given by the channel nui, not by nu."""
144+
basis = BasisMQDT("Yb171", nu=(78.0, 81.0), l_r=(5, 5), m=(0.5, 0.5))
145+
assert len(basis.states) > 0
146+
147+
states_with_shifted_nu = 0
148+
for state in basis.states:
149+
assert isinstance(state.model, FModelSQDT)
150+
assert len(state.rydberg_kets) == 1
151+
152+
nui = state.nui[0]
153+
assert abs(nui - round(nui)) < 1e-9
154+
assert state.n == round(nui)
155+
156+
if state.n != round(state.nu):
157+
states_with_shifted_nu += 1
158+
159+
assert states_with_shifted_nu > 0
160+
161+
162+
@pytest.mark.parametrize(
163+
("species", "m", "l_r", "model_name"),
164+
[
165+
("Yb174", (0, 0), (0, 0), "S J=0, nu > 2"),
166+
("Yb174", (0, 0), (2, 2), "D J=2, nu > 5"),
167+
("Yb171", (0.5, 0.5), (1, 1), "P F=1/2, nu > 5.7"),
168+
("Yb171", (0.5, 0.5), (1, 1), "P F=3/2, nu > 10"),
169+
],
170+
)
171+
def test_n_increments_by_one_along_a_rydberg_series(
172+
species: str, m: tuple[float, float], l_r: tuple[int, int], model_name: str
173+
) -> None:
174+
"""Within one Rydberg series of an MQDT model, n increases by exactly one from state to state.
175+
176+
A single MQDT model describes several interleaved Rydberg series (one per channel), so the states
177+
of a model have to be grouped by their dominant channel before comparing consecutive n.
178+
"""
179+
basis = BasisMQDT(species, nu=(40.0, 46.0), l_r=l_r, m=m)
180+
states = [state for state in basis.states if state.model.name == model_name]
181+
assert len(states) > 0
182+
183+
def dominant_channel(state: RydbergStateMQDT) -> int:
184+
"""Return the index of the channel with the largest coefficient (ignoring unknown channels)."""
185+
known = [(abs(coeff), i) for i, (coeff, ket) in enumerate(state) if not is_unknown(ket.angular.l_r)]
186+
return max(known)[1]
187+
188+
states.sort(key=lambda state: (dominant_channel(state), state.nu))
189+
for channel, series in itertools.groupby(states, key=dominant_channel):
190+
n_list = [state.n for state in series]
191+
assert len(n_list) > 1, f"series of channel {channel} is too short to test"
192+
assert all(n_next - n == 1 for n, n_next in itertools.pairwise(n_list)), (
193+
f"n does not increase by one along the series of channel {channel}: {n_list}"
194+
)

0 commit comments

Comments
 (0)