Skip to content

Commit f146a92

Browse files
split up angular ket
1 parent 75fae8b commit f146a92

8 files changed

Lines changed: 181 additions & 149 deletions

File tree

src/rydstate/angular/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from rydstate.angular import utils
2-
from rydstate.angular.angular_ket import AngularKetDummy, AngularKetFJ, AngularKetJJ, AngularKetLS
2+
from rydstate.angular.angular_ket import AngularKetFJ, AngularKetJJ, AngularKetLS
3+
from rydstate.angular.angular_ket_dummy import AngularKetDummy
34
from rydstate.angular.angular_state import AngularState
45

56
__all__ = [

src/rydstate/angular/angular_ket.py

Lines changed: 2 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import contextlib
43
import logging
54
from abc import ABC
65
from typing import TYPE_CHECKING, Any, ClassVar, Literal, overload
@@ -14,6 +13,7 @@
1413
is_angular_operator_type,
1514
)
1615
from rydstate.angular.utils import (
16+
InvalidQuantumNumbersError,
1717
check_spin_addition_rule,
1818
get_possible_quantum_number_values,
1919
minus_one_pow,
@@ -23,24 +23,14 @@
2323
from rydstate.species import SpeciesObject
2424

2525
if TYPE_CHECKING:
26-
import juliacall
2726
from typing_extensions import Never, Self
2827

2928
from rydstate.angular.angular_matrix_element import AngularMomentumQuantumNumbers, AngularOperatorType
3029
from rydstate.angular.angular_state import AngularState
30+
from rydstate.angular.utils import CouplingScheme
3131

3232
logger = logging.getLogger(__name__)
3333

34-
CouplingScheme = Literal["LS", "JJ", "FJ", "Dummy"]
35-
36-
37-
class InvalidQuantumNumbersError(ValueError):
38-
def __init__(self, ket: AngularKetBase, msg: str = "") -> None:
39-
_msg = f"Invalid quantum numbers for {ket!r}"
40-
if len(msg) > 0:
41-
_msg += f"\n {msg}"
42-
super().__init__(_msg)
43-
4434

4535
class AngularKetBase(ABC):
4636
"""Base class for a angular ket (i.e. a simple canonical spin ketstate)."""
@@ -375,10 +365,6 @@ def calc_reduced_overlap(self, other: AngularKetBase) -> float:
375365

376366
kets = [self, other]
377367

378-
# Dummy overlaps
379-
if any(isinstance(s, AngularKetDummy) for s in kets):
380-
return int(self == other)
381-
382368
# JJ - FJ overlaps
383369
if any(isinstance(s, AngularKetJJ) for s in kets) and any(isinstance(s, AngularKetFJ) for s in kets):
384370
jj = next(s for s in kets if isinstance(s, AngularKetJJ))
@@ -421,10 +407,6 @@ def calc_reduced_matrix_element( # noqa: C901
421407
if not is_angular_operator_type(operator):
422408
raise NotImplementedError(f"calc_reduced_matrix_element is not implemented for operator {operator}.")
423409

424-
# Dummy matrix elements
425-
if any(isinstance(s, AngularKetDummy) for s in [self, other]):
426-
return 0
427-
428410
if type(self) is not type(other):
429411
return self.to_state().calc_reduced_matrix_element(other.to_state(), operator, kappa)
430412
if is_angular_momentum_quantum_number(operator) and operator not in self.quantum_number_names:
@@ -748,122 +730,3 @@ def sanity_check(self, msgs: list[str] | None = None) -> None:
748730
msgs.append(f"{self.f_c=}, {self.j_r=}, {self.f_tot=} don't satisfy spin addition rule.")
749731

750732
super().sanity_check(msgs)
751-
752-
753-
class AngularKetDummy(AngularKetBase):
754-
"""Dummy spin ket for unknown quantum numbers."""
755-
756-
__slots__ = ("name",)
757-
quantum_number_names: ClassVar = ("f_tot",)
758-
coupled_quantum_numbers: ClassVar = {}
759-
coupling_scheme = "Dummy"
760-
761-
name: str
762-
"""Name of the dummy ket."""
763-
764-
def __init__(
765-
self,
766-
name: str,
767-
f_tot: float,
768-
m: float | None = None,
769-
) -> None:
770-
"""Initialize the Spin ket."""
771-
self.name = name
772-
773-
self.f_tot = f_tot
774-
self.m = m
775-
776-
super()._post_init()
777-
778-
def sanity_check(self, msgs: list[str] | None = None) -> None:
779-
"""Check that the quantum numbers are valid."""
780-
msgs = msgs if msgs is not None else []
781-
782-
if self.m is not None and not -self.f_tot <= self.m <= self.f_tot:
783-
msgs.append(f"m must be between -f_tot and f_tot, but {self.f_tot=}, {self.m=}")
784-
785-
if msgs:
786-
msg = "\n ".join(msgs)
787-
raise InvalidQuantumNumbersError(self, msg)
788-
789-
def __repr__(self) -> str:
790-
args = f"{self.name}, f_tot={self.f_tot}"
791-
if self.m is not None:
792-
args += f", m={self.m}"
793-
return f"{self.__class__.__name__}({args})"
794-
795-
def __str__(self) -> str:
796-
return self.__repr__().replace("AngularKet", "")
797-
798-
def __eq__(self, other: object) -> bool:
799-
if not isinstance(other, AngularKetBase):
800-
raise NotImplementedError(f"Cannot compare {self!r} with {other!r}.")
801-
if not isinstance(other, AngularKetDummy):
802-
return False
803-
return self.name == other.name and self.f_tot == other.f_tot and self.m == other.m
804-
805-
def __hash__(self) -> int:
806-
return hash((self.name, self.f_tot, self.m))
807-
808-
809-
def julia_qn_to_dict(qn: juliacall.AnyValue) -> dict[str, float]:
810-
"""Convert MQDT Julia quantum numbers to dict object."""
811-
if "fjQuantumNumbers" in str(qn):
812-
return dict(s_c=qn.sc, l_c=qn.lc, j_c=qn.Jc, f_c=qn.Fc, l_r=qn.lr, j_r=qn.Jr, f_tot=qn.F) # noqa: C408
813-
if "jjQuantumNumbers" in str(qn):
814-
return dict(s_c=qn.sc, l_c=qn.lc, j_c=qn.Jc, l_r=qn.lr, j_r=qn.Jr, j_tot=qn.J, f_tot=qn.F) # noqa: C408
815-
if "lsQuantumNumbers" in str(qn):
816-
return dict(s_c=qn.sc, s_tot=qn.S, l_c=qn.lc, l_r=qn.lr, l_tot=qn.L, j_tot=qn.J, f_tot=qn.F) # noqa: C408
817-
raise ValueError(f"Unknown MQDT Julia quantum numbers {qn!s}.")
818-
819-
820-
def quantum_numbers_to_angular_ket(
821-
species: str | SpeciesObject,
822-
s_c: float | None = None,
823-
l_c: int = 0,
824-
j_c: float | None = None,
825-
f_c: float | None = None,
826-
s_r: float = 0.5,
827-
l_r: int | None = None,
828-
j_r: float | None = None,
829-
s_tot: float | None = None,
830-
l_tot: int | None = None,
831-
j_tot: float | None = None,
832-
f_tot: float | None = None,
833-
m: float | None = None,
834-
) -> AngularKetBase:
835-
r"""Return an AngularKet object in the corresponding coupling scheme from the given quantum numbers.
836-
837-
Args:
838-
species: Atomic species.
839-
s_c: Spin quantum number of the core electron (0 for Alkali, 0.5 for divalent atoms).
840-
l_c: Orbital angular momentum quantum number of the core electron.
841-
j_c: Total angular momentum quantum number of the core electron.
842-
f_c: Total angular momentum quantum number of the core (core electron + nucleus).
843-
s_r: Spin quantum number of the rydberg electron (always 0.5).
844-
l_r: Orbital angular momentum quantum number of the rydberg electron.
845-
j_r: Total angular momentum quantum number of the rydberg electron.
846-
s_tot: Total spin quantum number of all electrons.
847-
l_tot: Total orbital angular momentum quantum number of all electrons.
848-
j_tot: Total angular momentum quantum number of all electrons.
849-
f_tot: Total angular momentum quantum number of the atom (rydberg electron + core).
850-
m: Total magnetic quantum number.
851-
Optional, only needed for concrete angular matrix elements.
852-
853-
"""
854-
with contextlib.suppress(InvalidQuantumNumbersError, ValueError):
855-
return AngularKetLS(
856-
s_c=s_c, l_c=l_c, s_r=s_r, l_r=l_r, s_tot=s_tot, l_tot=l_tot, j_tot=j_tot, f_tot=f_tot, m=m, species=species
857-
)
858-
859-
with contextlib.suppress(InvalidQuantumNumbersError, ValueError):
860-
return AngularKetJJ(
861-
s_c=s_c, l_c=l_c, j_c=j_c, s_r=s_r, l_r=l_r, j_r=j_r, j_tot=j_tot, f_tot=f_tot, m=m, species=species
862-
)
863-
864-
with contextlib.suppress(InvalidQuantumNumbersError, ValueError):
865-
return AngularKetFJ(
866-
s_c=s_c, l_c=l_c, j_c=j_c, f_c=f_c, s_r=s_r, l_r=l_r, j_r=j_r, f_tot=f_tot, m=m, species=species
867-
)
868-
869-
raise ValueError("Invalid combination of angular quantum numbers provided.")
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import TYPE_CHECKING, ClassVar
5+
6+
from rydstate.angular.angular_ket import AngularKetBase
7+
from rydstate.angular.angular_matrix_element import is_angular_operator_type
8+
from rydstate.angular.utils import InvalidQuantumNumbersError
9+
10+
if TYPE_CHECKING:
11+
from typing_extensions import Self
12+
13+
from rydstate.angular.angular_matrix_element import AngularOperatorType
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class AngularKetDummy(AngularKetBase):
19+
"""Dummy spin ket for unknown quantum numbers."""
20+
21+
__slots__ = ("name",)
22+
quantum_number_names: ClassVar = ("f_tot",)
23+
coupled_quantum_numbers: ClassVar = {}
24+
coupling_scheme = "Dummy"
25+
26+
name: str
27+
"""Name of the dummy ket."""
28+
29+
def __init__(
30+
self,
31+
name: str,
32+
f_tot: float,
33+
m: float | None = None,
34+
) -> None:
35+
"""Initialize the Spin ket."""
36+
self.name = name
37+
38+
self.f_tot = f_tot
39+
self.m = m
40+
41+
super()._post_init()
42+
43+
def sanity_check(self, msgs: list[str] | None = None) -> None:
44+
"""Check that the quantum numbers are valid."""
45+
msgs = msgs if msgs is not None else []
46+
47+
if self.m is not None and not -self.f_tot <= self.m <= self.f_tot:
48+
msgs.append(f"m must be between -f_tot and f_tot, but {self.f_tot=}, {self.m=}")
49+
50+
if msgs:
51+
msg = "\n ".join(msgs)
52+
raise InvalidQuantumNumbersError(self, msg)
53+
54+
def __repr__(self) -> str:
55+
args = f"{self.name}, f_tot={self.f_tot}"
56+
if self.m is not None:
57+
args += f", m={self.m}"
58+
return f"{self.__class__.__name__}({args})"
59+
60+
def __str__(self) -> str:
61+
return self.__repr__().replace("AngularKet", "")
62+
63+
def __eq__(self, other: object) -> bool:
64+
if not isinstance(other, AngularKetBase):
65+
raise NotImplementedError(f"Cannot compare {self!r} with {other!r}.")
66+
if not isinstance(other, AngularKetDummy):
67+
return False
68+
return self.name == other.name and self.f_tot == other.f_tot and self.m == other.m
69+
70+
def __hash__(self) -> int:
71+
return hash((self.name, self.f_tot, self.m))
72+
73+
def calc_reduced_overlap(self, other: AngularKetBase) -> float:
74+
return int(self == other)
75+
76+
def calc_reduced_matrix_element(
77+
self: Self,
78+
other: AngularKetBase, # noqa: ARG002
79+
operator: AngularOperatorType,
80+
kappa: int, # noqa: ARG002
81+
) -> float:
82+
if not is_angular_operator_type(operator):
83+
raise NotImplementedError(f"calc_reduced_matrix_element is not implemented for operator {operator}.")
84+
85+
# ignore contributions from dummy kets
86+
return 0

src/rydstate/angular/angular_state.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88

99
from rydstate.angular.angular_ket import (
1010
AngularKetBase,
11-
AngularKetDummy,
1211
AngularKetFJ,
1312
AngularKetJJ,
1413
AngularKetLS,
1514
)
15+
from rydstate.angular.angular_ket_dummy import AngularKetDummy
1616
from rydstate.angular.angular_matrix_element import is_angular_momentum_quantum_number
1717

1818
if TYPE_CHECKING:
1919
from collections.abc import Iterator, Sequence
2020

2121
from typing_extensions import Never, Self
2222

23-
from rydstate.angular.angular_ket import CouplingScheme
2423
from rydstate.angular.angular_matrix_element import AngularMomentumQuantumNumbers, AngularOperatorType
24+
from rydstate.angular.utils import CouplingScheme
2525

2626
logger = logging.getLogger(__name__)
2727

src/rydstate/angular/utils.py

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

3+
import contextlib
4+
from typing import TYPE_CHECKING, Literal
5+
36
import numpy as np
47

8+
if TYPE_CHECKING:
9+
import juliacall
10+
11+
from rydstate.angular.angular_ket import AngularKetBase
12+
from rydstate.species.species_object import SpeciesObject
13+
14+
CouplingScheme = Literal["LS", "JJ", "FJ", "Dummy"]
15+
16+
17+
class InvalidQuantumNumbersError(ValueError):
18+
def __init__(self, ket: AngularKetBase, msg: str = "") -> None:
19+
_msg = f"Invalid quantum numbers for {ket!r}"
20+
if len(msg) > 0:
21+
_msg += f"\n {msg}"
22+
super().__init__(_msg)
23+
524

625
def minus_one_pow(n: float) -> int:
726
"""Calculate (-1)^n for an integer n and raise an error if n is not an integer."""
@@ -42,3 +61,68 @@ def get_possible_quantum_number_values(s_1: float, s_2: float, s_tot: float | No
4261
if s_tot is not None:
4362
return [s_tot]
4463
return [float(s) for s in np.arange(abs(s_1 - s_2), s_1 + s_2 + 1, 1)]
64+
65+
66+
def julia_qn_to_dict(qn: juliacall.AnyValue) -> dict[str, float]:
67+
"""Convert MQDT Julia quantum numbers to dict object."""
68+
if "fjQuantumNumbers" in str(qn):
69+
return dict(s_c=qn.sc, l_c=qn.lc, j_c=qn.Jc, f_c=qn.Fc, l_r=qn.lr, j_r=qn.Jr, f_tot=qn.F) # noqa: C408
70+
if "jjQuantumNumbers" in str(qn):
71+
return dict(s_c=qn.sc, l_c=qn.lc, j_c=qn.Jc, l_r=qn.lr, j_r=qn.Jr, j_tot=qn.J, f_tot=qn.F) # noqa: C408
72+
if "lsQuantumNumbers" in str(qn):
73+
return dict(s_c=qn.sc, s_tot=qn.S, l_c=qn.lc, l_r=qn.lr, l_tot=qn.L, j_tot=qn.J, f_tot=qn.F) # noqa: C408
74+
raise ValueError(f"Unknown MQDT Julia quantum numbers {qn!s}.")
75+
76+
77+
def quantum_numbers_to_angular_ket(
78+
species: str | SpeciesObject,
79+
s_c: float | None = None,
80+
l_c: int = 0,
81+
j_c: float | None = None,
82+
f_c: float | None = None,
83+
s_r: float = 0.5,
84+
l_r: int | None = None,
85+
j_r: float | None = None,
86+
s_tot: float | None = None,
87+
l_tot: int | None = None,
88+
j_tot: float | None = None,
89+
f_tot: float | None = None,
90+
m: float | None = None,
91+
) -> AngularKetBase:
92+
r"""Return an AngularKet object in the corresponding coupling scheme from the given quantum numbers.
93+
94+
Args:
95+
species: Atomic species.
96+
s_c: Spin quantum number of the core electron (0 for Alkali, 0.5 for divalent atoms).
97+
l_c: Orbital angular momentum quantum number of the core electron.
98+
j_c: Total angular momentum quantum number of the core electron.
99+
f_c: Total angular momentum quantum number of the core (core electron + nucleus).
100+
s_r: Spin quantum number of the rydberg electron (always 0.5).
101+
l_r: Orbital angular momentum quantum number of the rydberg electron.
102+
j_r: Total angular momentum quantum number of the rydberg electron.
103+
s_tot: Total spin quantum number of all electrons.
104+
l_tot: Total orbital angular momentum quantum number of all electrons.
105+
j_tot: Total angular momentum quantum number of all electrons.
106+
f_tot: Total angular momentum quantum number of the atom (rydberg electron + core).
107+
m: Total magnetic quantum number.
108+
Optional, only needed for concrete angular matrix elements.
109+
110+
"""
111+
from rydstate.angular.angular_ket import AngularKetFJ, AngularKetJJ, AngularKetLS # noqa: PLC0415
112+
113+
with contextlib.suppress(InvalidQuantumNumbersError, ValueError):
114+
return AngularKetLS(
115+
s_c=s_c, l_c=l_c, s_r=s_r, l_r=l_r, s_tot=s_tot, l_tot=l_tot, j_tot=j_tot, f_tot=f_tot, m=m, species=species
116+
)
117+
118+
with contextlib.suppress(InvalidQuantumNumbersError, ValueError):
119+
return AngularKetJJ(
120+
s_c=s_c, l_c=l_c, j_c=j_c, s_r=s_r, l_r=l_r, j_r=j_r, j_tot=j_tot, f_tot=f_tot, m=m, species=species
121+
)
122+
123+
with contextlib.suppress(InvalidQuantumNumbersError, ValueError):
124+
return AngularKetFJ(
125+
s_c=s_c, l_c=l_c, j_c=j_c, f_c=f_c, s_r=s_r, l_r=l_r, j_r=j_r, f_tot=f_tot, m=m, species=species
126+
)
127+
128+
raise ValueError("Invalid combination of angular quantum numbers provided.")

0 commit comments

Comments
 (0)