diff --git a/setup.py b/setup.py
index 70f7be7..23f8b11 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
setup(
name="pyvalem",
- version="2.5.16",
+ version="2.6",
description="A package for managing simple chemical species and states",
long_description=long_description,
long_description_content_type="text/x-rst",
@@ -28,6 +28,7 @@
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only",
"Operating System :: OS Independent",
],
diff --git a/src/pyvalem/states/_state_parser.py b/src/pyvalem/states/_state_parser.py
index be231a9..e381402 100644
--- a/src/pyvalem/states/_state_parser.py
+++ b/src/pyvalem/states/_state_parser.py
@@ -14,6 +14,7 @@
from .racah_symbol import RacahSymbol
from .rotational_state import RotationalState
from .vibrational_state import VibrationalState
+from .compound_LS_coupling import CompoundLSCoupling
# the following has two purposes: keys determine the order in which the
# states are parsed, and the values determine the sorting order of states
@@ -22,6 +23,7 @@
[
(GenericExcitedState, 0),
(AtomicConfiguration, 1),
+ (CompoundLSCoupling, 1),
(AtomicTermSymbol, 2),
(DiatomicMolecularConfiguration, 1),
(MolecularTermSymbol, 2),
diff --git a/src/pyvalem/states/atomic_term_symbol.py b/src/pyvalem/states/atomic_term_symbol.py
index 761dfe9..f934993 100644
--- a/src/pyvalem/states/atomic_term_symbol.py
+++ b/src/pyvalem/states/atomic_term_symbol.py
@@ -24,11 +24,13 @@
integer + pp.Optional(pp.Suppress("/") + "2") + pp.StringEnd()
).setResultsName("Jstr")
atom_parity = pp.Literal("o").setResultsName("parity")
+seniority = pp.Suppress("{") + integer.setResultsName("seniority") + pp.Suppress("}")
atom_term = (
pp.Optional(moore_label)
+ atom_Smult
+ atom_Lletter
+ pp.Optional(atom_parity)
+ + pp.Optional(seniority)
+ pp.Optional(pp.Suppress("_") + atom_Jstr)
+ pp.StringEnd()
)
@@ -48,6 +50,7 @@ def __init__(self, state_str):
self.parity = None
self.J = None
self.moore_label = ""
+ self.seniority = None
self._parse_state(state_str)
def _parse_state(self, state_str):
@@ -62,6 +65,8 @@ def _parse_state(self, state_str):
self.Lletter = components.Lletter
self.L = atom_L_symbols.index(components.Lletter)
self.parity = components.get("parity")
+ if components.get("seniority"):
+ self.seniority = int(components.get("seniority"))
self.moore_label = components.get("moore_label", "")
try:
self.J = parse_fraction(components.Jstr)
@@ -92,6 +97,8 @@ def html(self):
]
if self.parity:
html_chunks.append("o")
+ if self.seniority:
+ html_chunks.append(str(self.seniority))
if self.J is not None:
j_str = float_to_fraction(self.J)
html_chunks.append("{0:s}".format(j_str))
@@ -106,6 +113,8 @@ def latex(self):
]
if self.parity:
latex_chunks.append("^o")
+ if self.seniority:
+ latex_chunks.append(str(self.seniority))
if self.J is not None:
j_str = float_to_fraction(self.J)
latex_chunks.append("_{{{}}}".format(j_str))
diff --git a/src/pyvalem/states/compound_LS_coupling.py b/src/pyvalem/states/compound_LS_coupling.py
new file mode 100644
index 0000000..fec885e
--- /dev/null
+++ b/src/pyvalem/states/compound_LS_coupling.py
@@ -0,0 +1,82 @@
+"""
+The CompoundLSCoupling class, representing a complex atomic state with
+multiple configurations coupling within the LS formulism to several
+intermediate terms, e.g. "4f(2Fo)5d2(1G)6s(2G)". The final term should
+be given as its own AtomicTermSymbol. The full CompoundLSCoupling state
+should contain no spaces; NB the different interaction strength cases
+detailed in Martin et al. (sec. 11.8.1) are not yet implemented.
+
+W. C. Martin, W. Wiese, A. Kramida, "Atomic Spectroscopy" in "Springer
+Handbook of Atomic, Molecular and Optical Physics", G. W. F. Drake (ed.),
+https://doi.org/10.1007/978-3-030-73893-8_11
+
+Includes methods for parsing a string into quantum numbers and labels,
+creating an HTML representation of the term symbol, etc.
+"""
+
+import re
+
+from pyvalem.states._base_state import State, StateParseError
+from .atomic_term_symbol import AtomicTermSymbol, AtomicTermSymbolError
+from .atomic_configuration import AtomicConfiguration, AtomicConfigurationError
+
+term_patt = r"\((.*?)\)"
+
+
+class CompoundLSCouplingError(StateParseError):
+ pass
+
+
+class CompoundLSCoupling(State):
+ def __init__(self, state_str):
+ self.state_str = state_str
+ self.atomic_configurations = []
+ self.term_symbols = []
+ self._parse_state(self.state_str)
+
+ def _parse_state(self, state_str):
+ terms = re.findall(term_patt, state_str)
+ configs = state_str.split("(")
+
+ if len(configs) == 0 or len(terms) == 0:
+ raise CompoundLSCouplingError
+
+ try:
+ for i, config in enumerate(configs[1:], start=1):
+ configs[i] = configs[i][len(terms[i - 1]) + 1 :]
+ except IndexError:
+ raise CompoundLSCouplingError
+
+ if not configs[-1]:
+ configs = configs[:-1]
+
+ try:
+ self.terms = [AtomicTermSymbol(term) for term in terms]
+ self.atomic_configurations = [
+ AtomicConfiguration(config) for config in configs
+ ]
+ except (AtomicTermSymbolError, AtomicConfigurationError):
+ raise CompoundLSCouplingError
+
+ def __repr__(self):
+ return self.state_str
+
+ @property
+ def html(self):
+ html_chunks = []
+ for config, term in zip(self.atomic_configurations, self.terms):
+ html_chunks.append(config.html)
+ html_chunks.append(f"({term.html})")
+ if len(self.atomic_configurations) > len(self.terms):
+ html_chunks.append(self.atomic_configurations[-1].html)
+ return "".join(html_chunks)
+
+ @property
+ def latex(self):
+ latex_chunks = []
+ for config, term in zip(self.atomic_configurations, self.terms):
+ latex_chunks.append(config.latex)
+ latex_chunks.append(f"({term.latex})")
+ if len(self.atomic_configurations) > len(self.terms):
+ latex_chunks.append(self.atomic_configurations[-1].latex)
+ return "".join(latex_chunks)
diff --git a/tests/test_atomic_term_symbols.py b/tests/test_atomic_term_symbols.py
index 7c7e0b7..4adc7ad 100644
--- a/tests/test_atomic_term_symbols.py
+++ b/tests/test_atomic_term_symbols.py
@@ -65,6 +65,21 @@ def test_moore_label(self):
self.assertEqual(a1.latex, r"z{}^{3}\mathrm{P}^o")
self.assertRaises(AtomicTermSymbolError, AtomicTermSymbol, "A5D")
+ def test_seniority_label(self):
+ a0 = AtomicTermSymbol("2D{1}_3/2")
+ a1 = AtomicTermSymbol("2D{2}_3/2")
+ a2 = AtomicTermSymbol("4I{3}")
+ self.assertEqual(a0.seniority, 1)
+ self.assertEqual(a1.seniority, 2)
+ self.assertEqual(a2.seniority, 3)
+
+ self.assertEqual(a0.html, "2D13/2")
+ self.assertEqual(a0.latex, r"{}^{2}\mathrm{D}1_{3/2}")
+ self.assertEqual(a1.html, "2D23/2")
+ self.assertEqual(a1.latex, r"{}^{2}\mathrm{D}2_{3/2}")
+ self.assertEqual(a2.html, "4I3")
+ self.assertEqual(a2.latex, r"{}^{4}\mathrm{I}3")
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_compound_LS_coupling.py b/tests/test_compound_LS_coupling.py
new file mode 100644
index 0000000..0f4629e
--- /dev/null
+++ b/tests/test_compound_LS_coupling.py
@@ -0,0 +1,56 @@
+"""
+Unit tests for the Compound LS Coupling module of PyValem
+"""
+
+import unittest
+
+from pyvalem.states.atomic_configuration import AtomicConfiguration
+from pyvalem.states.atomic_term_symbol import AtomicTermSymbol
+from pyvalem.states.compound_LS_coupling import CompoundLSCoupling
+
+
+class CompoundLSCouplingTest(unittest.TestCase):
+ def test_compound_LS_coupling(self):
+ s0 = CompoundLSCoupling("3d10.4f7(8So)4p6.5d(9Do)6s(8Do)7s")
+ a0 = AtomicConfiguration("3d10.4f7")
+ a1 = AtomicConfiguration("4p6.5d")
+ a2 = AtomicConfiguration("6s")
+ a3 = AtomicConfiguration("7s")
+ for i, ac in enumerate((a0, a1, a2, a3)):
+ self.assertEqual(ac, s0.atomic_configurations[i])
+ self.assertEqual(
+ s0.html,
+ """3d104f7(8So)4p65d(9Do)6s(8Do)7s""",
+ )
+ self.assertEqual(
+ s0.latex,
+ r"3d^{10}4f^{7}({}^{8}\mathrm{S}^o)4p^{6}5d({}^{9}\mathrm{D}^o)6s({}^{8}\mathrm{D}^o)7s",
+ )
+
+ s1 = CompoundLSCoupling("4f10(3K{2})6s.6p(1Po)")
+ t0 = AtomicTermSymbol("3K{2}")
+ t1 = AtomicTermSymbol("1Po")
+ for i, ts in enumerate((t0, t1)):
+ self.assertEqual(ts, s1.terms[i])
+ self.assertEqual(s1.terms[0].seniority, 2)
+ self.assertEqual(
+ s1.html,
+ """4f10(3K2)6s6p(1Po)""",
+ )
+ self.assertEqual(
+ s1.latex, r"4f^{10}({}^{3}\mathrm{K}2)6s6p({}^{1}\mathrm{P}^o)"
+ )
+
+ s2 = CompoundLSCoupling("4d9(2P)5d2(3Fo)")
+ self.assertEqual(
+ s2.html,
+ """4d9(2P)5d2(3Fo)""",
+ )
+ self.assertEqual(
+ s2.latex, r"4d^{9}({}^{2}\mathrm{P})5d^{2}({}^{3}\mathrm{F}^o)"
+ )
+
+ s3 = CompoundLSCoupling("5p5(2Po_3/2)6d")
+ self.assertEqual(
+ s3.html, """5p5(2Po3/2)6d"""
+ )
diff --git a/tests/test_stateful_species.py b/tests/test_stateful_species.py
index c5f43a2..2396828 100644
--- a/tests/test_stateful_species.py
+++ b/tests/test_stateful_species.py
@@ -112,6 +112,14 @@ def test_atomic_term_symbols(self):
self.assertRaises(StateParseError, StatefulSpecies, "Ti +2 A5D")
+ def test_compoundLScoupling_species(self):
+ ss1 = StatefulSpecies("N 2s2.2p2(3P)6d 4F")
+ self.assertEqual(ss1.formula.formula, "N")
+ scl = ss1.states[0]
+ sat = ss1.states[1]
+ self.assertEqual(scl.html, "2s22p2(3P)6d")
+ self.assertEqual(sat.html, "4F")
+
if __name__ == "__main__":
unittest.main()