4141
4242import copy
4343from abc import ABC , abstractmethod
44- from typing import Any , Dict , Iterable , List , Optional , Tuple , Union
44+ from collections .abc import Iterable
45+ from typing import Any
4546
4647from arc .exceptions import InputError
4748from arc .level .cbs import (
@@ -74,11 +75,11 @@ class Term(ABC):
7475 label : str
7576
7677 @abstractmethod
77- def required_levels (self ) -> List [ Tuple [str , Level ]]:
78+ def required_levels (self ) -> list [ tuple [str , Level ]]:
7879 """Return ``[(sub_label, Level), ...]`` pairs for every SP this term needs."""
7980
8081 @abstractmethod
81- def evaluate (self , energies : Dict [str , float ]) -> float :
82+ def evaluate (self , energies : dict [str , float ]) -> float :
8283 """Combine sub-job energies into this term's contribution.
8384
8485 The keys of ``energies`` are the ``sub_label`` strings yielded by
@@ -87,11 +88,11 @@ def evaluate(self, energies: Dict[str, float]) -> float:
8788 """
8889
8990 @abstractmethod
90- def as_dict (self ) -> Dict [str , Any ]:
91+ def as_dict (self ) -> dict [str , Any ]:
9192 """Serialise to a JSON/YAML-friendly dict including a discriminator ``type``."""
9293
9394 @classmethod
94- def from_dict (cls , data : Dict [str , Any ]) -> "Term" :
95+ def from_dict (cls , data : dict [str , Any ]) -> "Term" :
9596 """Reconstruct a ``Term`` subclass from its serialised dict.
9697
9798 Dispatches on the ``type`` discriminator written by :meth:`as_dict`.
@@ -114,7 +115,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "Term":
114115 )
115116
116117
117- def _coerce_level (value : Union [ str , Dict [str , Any ], Level ] ) -> Level :
118+ def _coerce_level (value : str | dict [str , Any ] | Level ) -> Level :
118119 """Accept either a string, dict, or Level; return a Level instance."""
119120 if isinstance (value , Level ):
120121 return value
@@ -128,27 +129,27 @@ def _coerce_level(value: Union[str, Dict[str, Any], Level]) -> Level:
128129class SinglePointTerm (Term ):
129130 """One absolute single-point energy at one level of theory."""
130131
131- def __init__ (self , label : str , level : Union [ str , Dict [str , Any ], Level ] ):
132+ def __init__ (self , label : str , level : str | dict [str , Any ] | Level ):
132133 if not label :
133134 raise InputError ("SinglePointTerm requires a non-empty label." )
134135 self .label = label
135136 self .level = _coerce_level (level )
136137
137- def required_levels (self ) -> List [ Tuple [str , Level ]]:
138+ def required_levels (self ) -> list [ tuple [str , Level ]]:
138139 return [(self .label , self .level )]
139140
140- def evaluate (self , energies : Dict [str , float ]) -> float :
141+ def evaluate (self , energies : dict [str , float ]) -> float :
141142 return energies [self .label ]
142143
143- def as_dict (self ) -> Dict [str , Any ]:
144+ def as_dict (self ) -> dict [str , Any ]:
144145 return {
145146 "type" : "single_point" ,
146147 "label" : self .label ,
147148 "level" : self .level .as_dict (),
148149 }
149150
150151 @classmethod
151- def _from_dict (cls , data : Dict [str , Any ]) -> "SinglePointTerm" :
152+ def _from_dict (cls , data : dict [str , Any ]) -> "SinglePointTerm" :
152153 return cls (label = data ["label" ], level = data ["level" ])
153154
154155
@@ -162,8 +163,8 @@ class DeltaTerm(Term):
162163 def __init__ (
163164 self ,
164165 label : str ,
165- high : Optional [ Union [ str , Dict [str , Any ], Level ]] ,
166- low : Optional [ Union [ str , Dict [str , Any ], Level ]] ,
166+ high : str | dict [str , Any ] | Level | None ,
167+ low : str | dict [str , Any ] | Level | None ,
167168 ):
168169 if not label :
169170 raise InputError ("DeltaTerm requires a non-empty label." )
@@ -179,13 +180,13 @@ def __init__(
179180 def _sub (self , suffix : str ) -> str :
180181 return f"{ self .label } __{ suffix } "
181182
182- def required_levels (self ) -> List [ Tuple [str , Level ]]:
183+ def required_levels (self ) -> list [ tuple [str , Level ]]:
183184 return [(self ._sub ("high" ), self .high ), (self ._sub ("low" ), self .low )]
184185
185- def evaluate (self , energies : Dict [str , float ]) -> float :
186+ def evaluate (self , energies : dict [str , float ]) -> float :
186187 return energies [self ._sub ("high" )] - energies [self ._sub ("low" )]
187188
188- def as_dict (self ) -> Dict [str , Any ]:
189+ def as_dict (self ) -> dict [str , Any ]:
189190 return {
190191 "type" : "delta" ,
191192 "label" : self .label ,
@@ -194,7 +195,7 @@ def as_dict(self) -> Dict[str, Any]:
194195 }
195196
196197 @classmethod
197- def _from_dict (cls , data : Dict [str , Any ]) -> "DeltaTerm" :
198+ def _from_dict (cls , data : dict [str , Any ]) -> "DeltaTerm" :
198199 return cls (label = data ["label" ], high = data ["high" ], low = data ["low" ])
199200
200201
@@ -241,7 +242,7 @@ def __init__(
241242 self ,
242243 label : str ,
243244 formula : str ,
244- levels : List [ Union [ str , Dict [str , Any ], Level ] ],
245+ levels : list [ str | dict [str , Any ] | Level ],
245246 components : str = "total" ,
246247 ):
247248 if not label :
@@ -282,7 +283,7 @@ def __init__(
282283 # construction time catches "martin_3pt with 2 levels" before a sub-job
283284 # ever runs. When new built-ins are added, update this table alongside
284285 # the entry in arc.level.cbs.BUILTIN_FORMULAS.
285- _BUILTIN_FORMULA_ARITY : Dict [str , int ] = {
286+ _BUILTIN_FORMULA_ARITY : dict [str , int ] = {
286287 "helgaker_corr_2pt" : 2 ,
287288 "helgaker_hf_2pt" : 2 ,
288289 "martin_3pt" : 3 ,
@@ -336,14 +337,14 @@ def _user_fn(energies):
336337 def _sub (self , cardinal : int ) -> str :
337338 return f"{ self .label } __card_{ cardinal } "
338339
339- def required_levels (self ) -> List [ Tuple [str , Level ]]:
340+ def required_levels (self ) -> list [ tuple [str , Level ]]:
340341 return [(self ._sub (c ), lvl ) for c , lvl in zip (self ._cardinals , self .levels )]
341342
342- def evaluate (self , energies : Dict [str , float ]) -> float :
343+ def evaluate (self , energies : dict [str , float ]) -> float :
343344 cardinal_to_energy = {c : energies [self ._sub (c )] for c in self ._cardinals }
344345 return self ._formula_callable (cardinal_to_energy )
345346
346- def as_dict (self ) -> Dict [str , Any ]:
347+ def as_dict (self ) -> dict [str , Any ]:
347348 return {
348349 "type" : "cbs_extrapolation" ,
349350 "label" : self .label ,
@@ -353,7 +354,7 @@ def as_dict(self) -> Dict[str, Any]:
353354 }
354355
355356 @classmethod
356- def _from_dict (cls , data : Dict [str , Any ]) -> "CBSExtrapolationTerm" :
357+ def _from_dict (cls , data : dict [str , Any ]) -> "CBSExtrapolationTerm" :
357358 return cls (
358359 label = data ["label" ],
359360 formula = data ["formula" ],
@@ -388,9 +389,9 @@ class CompositeProtocol:
388389 def __init__ (
389390 self ,
390391 base : SinglePointTerm ,
391- corrections : Optional [ List [ Term ]] = None ,
392- preset_name : Optional [ str ] = None ,
393- reference : Optional [ str ] = None ,
392+ corrections : list [ Term ] | None = None ,
393+ preset_name : str | None = None ,
394+ reference : str | None = None ,
394395 ):
395396 if not isinstance (base , SinglePointTerm ):
396397 raise InputError (
@@ -409,7 +410,7 @@ def __init__(
409410 # A collision (e.g. SinglePointTerm(label='delta_T__high') plus a
410411 # DeltaTerm(label='delta_T', ...) whose 'high' sub-leg also ends up as
411412 # 'delta_T__high') would overwrite state silently. Reject at construction.
412- sub_labels : List [str ] = []
413+ sub_labels : list [str ] = []
413414 for term in [base , * corrections ]:
414415 for sub_label , _level in term .required_levels ():
415416 sub_labels .append (sub_label )
@@ -427,22 +428,22 @@ def __init__(
427428 self .reference = reference
428429
429430 @property
430- def terms (self ) -> List [Term ]:
431+ def terms (self ) -> list [Term ]:
431432 """Convenience: ``[base, *corrections]`` in protocol order."""
432433 return [self .base , * self .corrections ]
433434
434- def evaluate (self , energies : Dict [str , float ]) -> float :
435+ def evaluate (self , energies : dict [str , float ]) -> float :
435436 """Combine all sub-job energies into the protocol's electronic energy."""
436437 return sum (term .evaluate (energies ) for term in self .terms )
437438
438- def iter_required_jobs (self ) -> Iterable [Tuple [str , str , Level ]]:
439+ def iter_required_jobs (self ) -> Iterable [tuple [str , str , Level ]]:
439440 """Yield ``(term_label, sub_label, Level)`` triples for every required SP."""
440441 for term in self .terms :
441442 for sub_label , level in term .required_levels ():
442443 yield (term .label , sub_label , level )
443444
444- def as_dict (self ) -> Dict [str , Any ]:
445- out : Dict [str , Any ] = {
445+ def as_dict (self ) -> dict [str , Any ]:
446+ out : dict [str , Any ] = {
446447 "base" : self .base .as_dict (),
447448 "corrections" : [t .as_dict () for t in self .corrections ],
448449 }
@@ -453,7 +454,7 @@ def as_dict(self) -> Dict[str, Any]:
453454 return out
454455
455456 @classmethod
456- def from_dict (cls , data : Dict [str , Any ]) -> "CompositeProtocol" :
457+ def from_dict (cls , data : dict [str , Any ]) -> "CompositeProtocol" :
457458 """Inverse of :meth:`as_dict`. Each entry must already include its discriminator."""
458459 if not isinstance (data , dict ) or "base" not in data :
459460 raise InputError (
@@ -479,7 +480,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "CompositeProtocol":
479480 )
480481
481482 @classmethod
482- def from_user_input (cls , raw : Union [ str , Dict [str , Any ] ]) -> "CompositeProtocol" :
483+ def from_user_input (cls , raw : str | dict [str , Any ]) -> "CompositeProtocol" :
483484 """Accept the YAML-shaped user input and produce a validated protocol.
484485
485486 Three forms are accepted:
@@ -498,7 +499,7 @@ def from_user_input(cls, raw: Union[str, Dict[str, Any]]) -> "CompositeProtocol"
498499 arc.exceptions.InputError
499500 On any malformed input.
500501 """
501- preset_name : Optional [ str ] = None
502+ preset_name : str | None = None
502503 if isinstance (raw , str ):
503504 preset_name = raw
504505 raw = expand_preset (raw )
@@ -549,7 +550,7 @@ def from_user_input(cls, raw: Union[str, Dict[str, Any]]) -> "CompositeProtocol"
549550 f"SinglePointTerm dict; got { type (base_raw ).__name__ } ."
550551 )
551552
552- corrections : List [Term ] = []
553+ corrections : list [Term ] = []
553554 for entry in raw .get ("corrections" , []):
554555 if not isinstance (entry , dict ):
555556 raise InputError (
0 commit comments