Source code for molfoundry.atomid
from typing import Dict, List, Optional
from .core import TSObject, coerce_type
from .exceptions import LogicError
_class_ref = TSObject.get_class_ref("AtomId")
# noinspection PyPep8Naming
[docs]
class AtomId(TSObject):
"""Identifies a chemical element and exposes its data from the shared ``Atom``
table.
Construct one by symbol or by atomic number::
carbon = AtomId.bySymbol("C")
carbon = AtomId.byId(6)
print(carbon.protons, carbon.valenceElectrons, carbon.orbitalNames)
``AtomId`` is also what ``Vertex.atomId`` returns. An AtomId that comes from a
non-atom vertex (e.g. an electron), an unknown symbol, or an unknown atomic
number is *invalid*: ``valid`` is ``False``, ``id`` is available, and every
element property raises ``LogicError``.
"""
def __init__(self) -> None:
# A bare AtomId is invalid; use bySymbol / byId (or Vertex.atomId).
super().__init__(_class_ref)
[docs]
@staticmethod
def bySymbol(symbol: str) -> "AtomId":
"""Look up an element by symbol (e.g. ``"C"``). Invalid if unknown."""
return AtomId.wrap(TSObject.call_static(_class_ref, "fromSymbol", symbol))
# noinspection PyShadowingBuiltins
[docs]
@staticmethod
def byId(id: int) -> "AtomId":
"""Look up an element by atomic number / id (e.g. ``6`` -> carbon).
Invalid if no element has that atomic number."""
return AtomId.wrap(TSObject.call_static(_class_ref, "fromId", id))
def __int__(self) -> int:
return int(self._ref.getId())
def __str__(self) -> str:
# Matches MØD's ``operator<<``: the atomic number, not the symbol.
return str(int(self))
def __eq__(self, other: object) -> bool:
# MØD compares AtomId to AtomId only (by id); not to a bare int.
if isinstance(other, AtomId):
return int(self) == int(other)
return NotImplemented
def __hash__(self) -> int:
# Defining ``__eq__`` otherwise makes the type unhashable; hash on the id
# so equal AtomIds hash alike.
return hash(int(self))
def __repr__(self) -> str:
return f"AtomId({self._ref.getSymbol()!r})" if self.valid else "AtomId(invalid)"
def _require_valid(self) -> None:
if not self.valid:
raise LogicError("Invalid AtomId has no element data")
@property
def valid(self) -> bool:
return bool(self._ref.isValid())
@property
def id(self) -> int:
"""The atomic number (proton count); 0 for an invalid AtomId."""
return int(self._ref.getId())
@property
def symbol(self) -> str:
self._require_valid()
return self._ref.getSymbol()
@property
def protons(self) -> int:
self._require_valid()
return int(self._ref.getProtons())
@property
def electrons(self) -> int:
self._require_valid()
return int(self._ref.getElectrons())
@property
def standardAtomicWeight(self) -> Optional[float]:
self._require_valid()
value = coerce_type(self._ref.getStandardAtomicWeight())
return float(value) if value is not None else None
@property
def mostAbundantIsotope(self) -> Optional[int]:
self._require_valid()
value = coerce_type(self._ref.getMostAbundantIsotope())
return int(value) if value is not None else None
@property
def principalQuantumNumber(self) -> int:
self._require_valid()
return int(self._ref.getPrincipalQuantumNumber())
@property
def valenceElectrons(self) -> int:
self._require_valid()
return int(self._ref.getValenceElectrons())
@property
def orbitals(self) -> List[List[int]]:
"""Electron occupancy per orbital, in fill order: one inner list per
occupied orbital, each entry a 1 (single electron) or 2 (pair)."""
self._require_valid()
return [[int(x) for x in orbital] for orbital in self._ref.getOrbitals()]
@property
def orbitalNames(self) -> List[str]:
"""Orbital names with occupancy, e.g. ``["1s2", "2s2", "2p2"]``."""
self._require_valid()
return [str(name) for name in self._ref.getOrbitalNames()]
@property
def isotopicMasses(self) -> Dict[int, float]:
"""Mapping of mass number to isotopic mass. Empty when untabulated."""
self._require_valid()
return {int(pair[0]): float(pair[1]) for pair in self._ref.getIsotopicMasses()}