diff --git a/src/rydstate/angular/angular_ket.py b/src/rydstate/angular/angular_ket.py index 446d3f96..78b23888 100644 --- a/src/rydstate/angular/angular_ket.py +++ b/src/rydstate/angular/angular_ket.py @@ -292,21 +292,10 @@ def get_qn(self, qn: AngularMomentumQuantumNumbers, *, allow_unknown: bool | Non if qn in self.quantum_number_names: qn_value = getattr(self, qn) else: - for coupled_quantum_numbers in ( - self.coupled_quantum_numbers, - AngularKetLS.coupled_quantum_numbers, - AngularKetJJ.coupled_quantum_numbers, - AngularKetFJ.coupled_quantum_numbers, - ): - if qn not in coupled_quantum_numbers: - continue - - qn_1, qn_2 = coupled_quantum_numbers[qn] - qn_1_value = self.get_qn(qn_1, allow_unknown=True) - qn_2_value = self.get_qn(qn_2, allow_unknown=True) - qn_value = try_trivial_spin_addition(qn_1_value, qn_2_value, None) - if not is_unknown(qn_value): - break + coupling_scheme = get_coupling_scheme_for_quantum_number(qn) + state = self.to_state(coupling_scheme) + if state.calc_std_qn(qn) == 0: + qn_value = state.calc_exp_qn(qn) allow_unknown = self._allow_unknown if allow_unknown is None else allow_unknown if not allow_unknown and is_unknown(qn_value): diff --git a/src/rydstate/rydberg_state/rydberg_ket.py b/src/rydstate/rydberg_state/rydberg_ket.py index 0db0dee7..73821718 100644 --- a/src/rydstate/rydberg_state/rydberg_ket.py +++ b/src/rydstate/rydberg_state/rydberg_ket.py @@ -183,7 +183,7 @@ def core_state(self) -> RydbergStateSQDT[Any] | None: ion_species = f"{self.species}_ion" try: - ion_sqdt = get_sqdt(ion_species) + core_sqdt = get_sqdt(ion_species) except ValueError: logger.warning( "No SQDT data available for the ion species of %s, " @@ -198,14 +198,15 @@ def core_state(self) -> RydbergStateSQDT[Any] | None: if is_unknown(l_c) or is_unknown(j_c) or is_unknown(f_c): return None - angular_ket = AngularKetLS(l_r=l_c, j_tot=j_c, f_tot=f_c, species=ion_species) + # The core electron of the neutral atom is the valence electron of the ion, with total angular momentum j_c. + core_angular_ket = AngularKetLS(l_r=l_c, j_tot=j_c, f_tot=f_c, species=ion_species) # TODO: we should probably also store n_c for the core angular ket in the future # for now, it is correct to assume that the core electron is # in the lowest allowed shell of the ion for the given l_c for n_c in range(l_c + 1, l_c + 15): - if ion_sqdt.is_allowed_shell(n_c, l_c, 0.5): - return RydbergStateSQDT(ion_species, n_c, angular_ket=angular_ket, sqdt=ion_sqdt) + if core_sqdt.is_allowed_shell(n_c, l_c, 0.5): + return RydbergStateSQDT(ion_species, n_c, angular_ket=core_angular_ket, sqdt=core_sqdt) raise ValueError(f"No allowed shell found for ion species {ion_species} with l_c={l_c}.") diff --git a/src/rydstate/species/strontium_ion/potential_data.py b/src/rydstate/species/strontium_ion/potential_data.py index ad1cc06e..3ddb9e3e 100644 --- a/src/rydstate/species/strontium_ion/potential_data.py +++ b/src/rydstate/species/strontium_ion/potential_data.py @@ -18,15 +18,15 @@ class _PotentialMarinescu1994StrontiumIonAbstract(PotentialMarinescu1994): # Note that the potential there is defined with Marinescu a_j = Greene \alpha_i as follows: # a_1 = \alpha_1 # a_2 = \alpha_3 - # a_3 = \alpha_2 + # a_3 = -\alpha_2 (Note the minus sign!) # and a_4 = 0 alpha_c_marinescu_1994 = 7.5 r_c_dict_marinescu_1994: ClassVar = {0: 1.7965, 1: 1.3960, 2: 1.6820, 3: 1.0057} model_potential_parameter_marinescu_1994: ClassVar = { - 0: (3.4187, 1.5915, 4.7332, 0), - 1: (3.3235, 1.5712, 2.2539, 0), - 2: (3.2533, 1.5996, 3.2330, 0), - 3: (5.3540, 5.6624, 7.9517, 0), + 0: (3.4187, 1.5915, -4.7332, 0), + 1: (3.3235, 1.5712, -2.2539, 0), + 2: (3.2533, 1.5996, -3.2330, 0), + 3: (5.3540, 5.6624, -7.9517, 0), } diff --git a/tests/test_ion.py b/tests/test_ion.py index ce5f2d9a..5ddd5b5f 100644 --- a/tests/test_ion.py +++ b/tests/test_ion.py @@ -1,4 +1,7 @@ +import logging + import pytest +from rydstate import RydbergStateSQDT from rydstate.species import get_element_properties, get_potential_class, get_sqdt from rydstate.species.element_properties import ElementProperties from rydstate.species.utils import calc_energy_from_nu, calc_nu_from_energy, get_all_subclasses @@ -42,3 +45,38 @@ def test_ion_nist_data_is_loaded(species: str) -> None: assert (5, 2, 1.5, 0.5) in levels # 4f14.5d 2D3/2 # only doublets (s_tot = 1/2) since Yb+ is alkali-like (closed-shell 4f14 core) assert all(s_tot == 0.5 for (_n, _l, _j, s_tot) in levels) + + +# Low-lying Sr+ states (n, l_r) whose energies come from the NIST data (no quantum defects are defined +# for the ion, so nu is taken from the measured levels and the radial shape from the model potential). +# The expected number of radial nodes of a state is n - l - 1. +STRONTIUM_ION_LOW_LYING_STATES = [ + (5, 0), # 5s, ground state of Sr+ + (6, 0), # 6s + (7, 0), # 7s + (5, 1), # 5p + (6, 1), # 6p + (4, 2), # 4d + (5, 2), # 5d + (4, 3), # 4f +] + + +@pytest.mark.parametrize(("n", "l_r"), STRONTIUM_ION_LOW_LYING_STATES) +def test_strontium_ion_radial_wavefunction(n: int, l_r: int, caplog: pytest.LogCaptureFixture) -> None: + """The integrated Sr+ wavefunction is normalized, has n - l - 1 nodes and raises no warnings.""" + j_tot = l_r + 0.5 + state = RydbergStateSQDT("Sr88_ion", n=n, l_r=l_r, j_tot=j_tot, f_tot=j_tot) + radial = state.radial + + with caplog.at_level(logging.WARNING): + radial.integrate_wavefunction() + + # No sanity-check (or any other) warning should be emitted during the integration. + warnings = [record.getMessage() for record in caplog.records if record.levelno >= logging.WARNING] + assert warnings == [], f"Unexpected warnings for Sr88_ion n={n} l={l_r}:\n" + "\n".join(warnings) + + # The wavefunction is correctly integrated and normalized ... + assert radial.norm == pytest.approx(1.0) + # ... and has exactly n - l - 1 nodes with the (sign-corrected) model potential. + assert radial.nodes == n - l_r - 1