|
24 | 24 |
|
25 | 25 | if TYPE_CHECKING: |
26 | 26 | import juliacall |
27 | | - from typing_extensions import Self |
| 27 | + from typing_extensions import Never, Self |
28 | 28 |
|
29 | 29 | from rydstate.angular.angular_matrix_element import AngularMomentumQuantumNumbers, AngularOperatorType |
30 | 30 | from rydstate.angular.angular_state import AngularState |
31 | 31 |
|
32 | 32 | logger = logging.getLogger(__name__) |
33 | 33 |
|
34 | | -CouplingScheme = Literal["LS", "JJ", "FJ"] |
| 34 | +CouplingScheme = Literal["LS", "JJ", "FJ", "Dummy"] |
35 | 35 |
|
36 | 36 |
|
37 | 37 | class InvalidQuantumNumbersError(ValueError): |
@@ -224,6 +224,9 @@ def to_state(self, coupling_scheme: Literal["JJ"]) -> AngularState[AngularKetJJ] |
224 | 224 | @overload |
225 | 225 | def to_state(self, coupling_scheme: Literal["FJ"]) -> AngularState[AngularKetFJ]: ... |
226 | 226 |
|
| 227 | + @overload |
| 228 | + def to_state(self, coupling_scheme: Literal["Dummy"]) -> Never: ... |
| 229 | + |
227 | 230 | @overload |
228 | 231 | def to_state(self: Self) -> AngularState[Self]: ... |
229 | 232 |
|
@@ -372,6 +375,10 @@ def calc_reduced_overlap(self, other: AngularKetBase) -> float: |
372 | 375 |
|
373 | 376 | kets = [self, other] |
374 | 377 |
|
| 378 | + # Dummy overlaps |
| 379 | + if any(isinstance(s, AngularKetDummy) for s in kets): |
| 380 | + return int(self == other) |
| 381 | + |
375 | 382 | # JJ - FJ overlaps |
376 | 383 | if any(isinstance(s, AngularKetJJ) for s in kets) and any(isinstance(s, AngularKetFJ) for s in kets): |
377 | 384 | jj = next(s for s in kets if isinstance(s, AngularKetJJ)) |
@@ -414,6 +421,10 @@ def calc_reduced_matrix_element( # noqa: C901 |
414 | 421 | if not is_angular_operator_type(operator): |
415 | 422 | raise NotImplementedError(f"calc_reduced_matrix_element is not implemented for operator {operator}.") |
416 | 423 |
|
| 424 | + # Dummy matrix elements |
| 425 | + if any(isinstance(s, AngularKetDummy) for s in [self, other]): |
| 426 | + return 0 |
| 427 | + |
417 | 428 | if type(self) is not type(other): |
418 | 429 | return self.to_state().calc_reduced_matrix_element(other.to_state(), operator, kappa) |
419 | 430 | 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: |
739 | 750 | super().sanity_check(msgs) |
740 | 751 |
|
741 | 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 | + |
742 | 809 | def julia_qn_to_dict(qn: juliacall.AnyValue) -> dict[str, float]: |
743 | 810 | """Convert MQDT Julia quantum numbers to dict object.""" |
744 | 811 | if "fjQuantumNumbers" in str(qn): |
|
0 commit comments