Skip to content

Commit e4317cb

Browse files
start adding AngularKetDummy
1 parent aeb2125 commit e4317cb

3 files changed

Lines changed: 99 additions & 11 deletions

File tree

src/rydstate/angular/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from rydstate.angular import utils
2-
from rydstate.angular.angular_ket import AngularKetFJ, AngularKetJJ, AngularKetLS
2+
from rydstate.angular.angular_ket import AngularKetDummy, AngularKetFJ, AngularKetJJ, AngularKetLS
33
from rydstate.angular.angular_state import AngularState
44

55
__all__ = [
6+
"AngularKetDummy",
67
"AngularKetFJ",
78
"AngularKetJJ",
89
"AngularKetLS",

src/rydstate/angular/angular_ket.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424

2525
if TYPE_CHECKING:
2626
import juliacall
27-
from typing_extensions import Self
27+
from typing_extensions import Never, Self
2828

2929
from rydstate.angular.angular_matrix_element import AngularMomentumQuantumNumbers, AngularOperatorType
3030
from rydstate.angular.angular_state import AngularState
3131

3232
logger = logging.getLogger(__name__)
3333

34-
CouplingScheme = Literal["LS", "JJ", "FJ"]
34+
CouplingScheme = Literal["LS", "JJ", "FJ", "Dummy"]
3535

3636

3737
class InvalidQuantumNumbersError(ValueError):
@@ -224,6 +224,9 @@ def to_state(self, coupling_scheme: Literal["JJ"]) -> AngularState[AngularKetJJ]
224224
@overload
225225
def to_state(self, coupling_scheme: Literal["FJ"]) -> AngularState[AngularKetFJ]: ...
226226

227+
@overload
228+
def to_state(self, coupling_scheme: Literal["Dummy"]) -> Never: ...
229+
227230
@overload
228231
def to_state(self: Self) -> AngularState[Self]: ...
229232

@@ -372,6 +375,10 @@ def calc_reduced_overlap(self, other: AngularKetBase) -> float:
372375

373376
kets = [self, other]
374377

378+
# Dummy overlaps
379+
if any(isinstance(s, AngularKetDummy) for s in kets):
380+
return int(self == other)
381+
375382
# JJ - FJ overlaps
376383
if any(isinstance(s, AngularKetJJ) for s in kets) and any(isinstance(s, AngularKetFJ) for s in kets):
377384
jj = next(s for s in kets if isinstance(s, AngularKetJJ))
@@ -414,6 +421,10 @@ def calc_reduced_matrix_element( # noqa: C901
414421
if not is_angular_operator_type(operator):
415422
raise NotImplementedError(f"calc_reduced_matrix_element is not implemented for operator {operator}.")
416423

424+
# Dummy matrix elements
425+
if any(isinstance(s, AngularKetDummy) for s in [self, other]):
426+
return 0
427+
417428
if type(self) is not type(other):
418429
return self.to_state().calc_reduced_matrix_element(other.to_state(), operator, kappa)
419430
if is_angular_momentum_quantum_number(operator) and operator not in self.quantum_number_names:
@@ -739,6 +750,62 @@ def sanity_check(self, msgs: list[str] | None = None) -> None:
739750
super().sanity_check(msgs)
740751

741752

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+
742809
def julia_qn_to_dict(qn: juliacall.AnyValue) -> dict[str, float]:
743810
"""Convert MQDT Julia quantum numbers to dict object."""
744811
if "fjQuantumNumbers" in str(qn):

src/rydstate/angular/angular_state.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from rydstate.angular.angular_ket import (
1010
AngularKetBase,
11+
AngularKetDummy,
1112
AngularKetFJ,
1213
AngularKetJJ,
1314
AngularKetLS,
@@ -17,7 +18,7 @@
1718
if TYPE_CHECKING:
1819
from collections.abc import Iterator, Sequence
1920

20-
from typing_extensions import Self
21+
from typing_extensions import Never, Self
2122

2223
from rydstate.angular.angular_ket import CouplingScheme
2324
from rydstate.angular.angular_matrix_element import AngularMomentumQuantumNumbers, AngularOperatorType
@@ -32,22 +33,28 @@ class AngularState(Generic[_AngularKet]):
3233
def __init__(
3334
self, coefficients: Sequence[float], kets: Sequence[_AngularKet], *, warn_if_not_normalized: bool = True
3435
) -> None:
35-
self.coefficients = np.array(coefficients)
36-
self.kets = kets
36+
"""Initialize an angular state as a linear combination of angular kets.
37+
38+
All kets must be of the same type (coupling scheme), and no duplicate kets are allowed.
39+
Dummy kets (AngularKetDummy) are ignored in the state representation,
40+
however adding them is recommended for normalization purposes.
41+
"""
42+
self._coefficients = np.array(coefficients)
43+
self._kets = kets
3744
self._warn_if_not_normalized = warn_if_not_normalized
3845

3946
if len(coefficients) != len(kets):
4047
raise ValueError("Length of coefficients and kets must be the same.")
4148
if len(kets) == 0:
4249
raise ValueError("At least one ket must be provided.")
43-
if not all(type(ket) is type(kets[0]) for ket in kets):
50+
if not all(type(ket) is type(self.kets[0]) for ket in self.kets):
4451
raise ValueError("All kets must be of the same type.")
45-
if len(set(kets)) != len(kets):
46-
raise ValueError("AngularState initialized with duplicate kets.")
52+
if len(set(self.kets)) != len(self.kets):
53+
raise ValueError("AngularState initialized with duplicate kets: %s", self.kets)
4754
if abs(self.norm - 1) > 1e-10 and warn_if_not_normalized:
4855
logger.warning("AngularState initialized with non-normalized coefficients: %s, %s", coefficients, kets)
4956
if self.norm > 1:
50-
self.coefficients /= self.norm
57+
self._coefficients /= self.norm
5158

5259
def __iter__(self) -> Iterator[tuple[float, _AngularKet]]:
5360
return zip(self.coefficients, self.kets).__iter__()
@@ -60,6 +67,16 @@ def __str__(self) -> str:
6067
terms = [f"{coeff}*{ket!s}" for coeff, ket in self]
6168
return f"{', '.join(terms)}"
6269

70+
@property
71+
def kets(self) -> list[_AngularKet]:
72+
return [ket for ket in self._kets if not isinstance(ket, AngularKetDummy)]
73+
74+
@property
75+
def coefficients(self) -> np.ndarray:
76+
return np.array(
77+
[coeff for coeff, ket in zip(self._coefficients, self._kets) if not isinstance(ket, AngularKetDummy)]
78+
)
79+
6380
@property
6481
def coupling_scheme(self) -> CouplingScheme:
6582
"""Return the coupling scheme of the state."""
@@ -68,7 +85,7 @@ def coupling_scheme(self) -> CouplingScheme:
6885
@property
6986
def norm(self) -> float:
7087
"""Return the norm of the state (should be 1)."""
71-
return np.linalg.norm(self.coefficients) # type: ignore [return-value]
88+
return np.linalg.norm(self._coefficients) # type: ignore [return-value]
7289

7390
@overload
7491
def to(self, coupling_scheme: Literal["LS"]) -> AngularState[AngularKetLS]: ...
@@ -79,6 +96,9 @@ def to(self, coupling_scheme: Literal["JJ"]) -> AngularState[AngularKetJJ]: ...
7996
@overload
8097
def to(self, coupling_scheme: Literal["FJ"]) -> AngularState[AngularKetFJ]: ...
8198

99+
@overload
100+
def to(self, coupling_scheme: Literal["Dummy"]) -> Never: ...
101+
82102
def to(self, coupling_scheme: CouplingScheme) -> AngularState[Any]:
83103
"""Convert to specified coupling scheme.
84104

0 commit comments

Comments
 (0)