"""
A module containing the Element class.
"""
import logging
from typing import Annotated
from numbers import Real
from beartype.typing import Any, NewType
from beartype.vale import Is
from PQAnalysis.type_checking import (
runtime_type_checking,
runtime_type_checking_setter,
)
from PQAnalysis.utils.custom_logging import setup_logger
from PQAnalysis import __package_name__
from ..exceptions import ElementNotFoundError
[docs]
class Element:
"""
A class representing an element.
An Element can be initialized in three ways:
1) By giving the atomic number of the element (e.g. 6).
2) By giving the symbol of the element (e.g. 'C').
3) By giving None.
Examples
--------
>>> element = Element(6)
>>> (element.symbol, element.atomic_number, element.mass)
('c', 6, 12.0107)
>>> element = Element('C')
>>> (element.symbol, element.atomic_number, element.mass)
('c', 6, 12.0107)
>>> element = Element()
>>> (element.symbol, element.atomic_number, element.mass)
(None, None, None)
"""
logger = logging.getLogger(__package_name__).getChild(__qualname__)
logger = setup_logger(logger)
@runtime_type_checking
def __init__(self, element_id: int | str | None = None) -> None:
"""
Initializes the Element with the given parameters.
Parameters
----------
element_id : int | str | None, optional
The identifier of the element. If an integer is given, it is
interpreted as the atomic number. If a string is given, it is
interpreted as the symbol of the element. If None is given, the
symbol, atomic number and mass are set to None, by default None
Raises
------
ElementNotFoundError
If the given id is not a valid element identifier.
"""
try:
# If id is an integer, it is interpreted as the atomic number.
if isinstance(element_id, int):
self._atomic_number = element_id
self._symbol = atomicNumbersReverse[element_id]
# If id is a string, it is interpreted as the symbol of the element.
elif isinstance(element_id, str):
self._symbol = element_id.lower()
self._atomic_number = atomicNumbers[self._symbol]
# If id is None, the symbol, atomic number
# and mass are set to None, meaning an empty element.
if element_id is None:
self._symbol = None
self._atomic_number = None
self._mass = None
else:
self._mass = atomicMasses[self._symbol]
except KeyError:
self.logger.error(
ElementNotFoundError(element_id).message,
exception=ElementNotFoundError
)
[docs]
def __str__(self) -> str:
"""
Returns a string representation of the Element.
Returns
-------
str
A string representation of the Element.
"""
return f"Element({self.symbol}, {self.atomic_number}, {self.mass})"
[docs]
def __repr__(self) -> str:
"""
Returns a representation of the Element.
Returns
-------
str
A representation of the Element.
"""
return self.__str__()
[docs]
def __eq__(self, other: Any) -> bool:
"""
Checks whether the Element is equal to another Element.
Parameters
----------
other : Any
The other Element to check for equality.
Returns
-------
bool
True if the Element is equal to the other Element, False otherwise.
"""
if not isinstance(other, Element):
return False
return self.symbol == other.symbol and self.atomic_number == other.atomic_number
@property
def symbol(self) -> str | None:
"""str | None: The symbol of the Element."""
return self._symbol
@property
def atomic_number(self) -> int | None:
"""int | None: The atomic number of the Element."""
return self._atomic_number
@property
def mass(self) -> Real | None:
"""Real | None: The mass of the Element."""
return self._mass
[docs]
class CustomElement(Element):
"""
A class representing a custom element it
inherits from the Element class.
"""
@runtime_type_checking
def __init__(self, symbol: str, atomic_number: int, mass: Real): # pylint: disable=super-init-not-called
"""
Parameters
----------
symbol : str
the custom atomic symbol
atomic_number : int
the custom atomic number
mass : Real
the custom atomic mass
"""
self._symbol = symbol
self._atomic_number = atomic_number
self._mass = mass
@Element.symbol.setter
@runtime_type_checking_setter
def symbol(self, value: str) -> None:
self._symbol = value
#: A type hint for a list of elements
Elements = NewType(
"Elements",
Annotated[
list,
Is[lambda list: all(isinstance(element, Element) for element in list)]]
)
atomicMasses = {
"h": 1.00794,
"d": 2.014101778,
"t": 3.0160492675,
"he": 4.002602,
"li": 6.941,
"be": 9.012182,
"b": 10.811,
"c": 12.0107,
"n": 14.0067,
"o": 15.9994,
"f": 18.9984032,
"ne": 20.1797,
"na": 22.989770,
"mg": 24.3050,
"al": 26.981538,
"si": 28.0855,
"p": 30.973761,
"s": 32.065,
"cl": 35.453,
"ar": 39.948,
"k": 39.0983,
"ca": 40.078,
"sc": 44.955910,
"ti": 47.880,
"v": 50.9415,
"cr": 51.9961,
"mn": 54.938049,
"fe": 55.845,
"co": 58.933200,
"ni": 58.6934,
"cu": 63.546,
"zn": 65.399,
"ga": 69.723,
"ge": 72.64,
"as": 74.92160,
"se": 78.96,
"br": 79.904,
"kr": 83.798,
"rb": 85.4678,
"sr": 87.62,
"y": 88.90585,
"zr": 91.224,
"nb": 92.90638,
"mo": 95.94,
"tc": 98.9063,
"ru": 101.07,
"rh": 102.9055,
"pd": 106.42,
"ag": 107.8682,
"cd": 112.411,
"in": 114.818,
"sn": 118.71,
"sb": 121.76,
"te": 127.6,
"i": 126.90447,
"xe": 131.293,
"cs": 132.90546,
"ba": 137.327,
"la": 138.9055,
"ce": 140.116,
"pr": 140.90765,
"nd": 144.24,
"pm": 146.9151,
"sm": 150.36,
"eu": 151.964,
"gd": 157.25,
"tb": 158.92534,
"dy": 162.5,
"ho": 164.93032,
"er": 167.259,
"tm": 168.93421,
"yb": 173.04,
"lu": 174.967,
"hf": 178.49,
"ta": 180.9479,
"w": 183.84,
"re": 186.207,
"os": 190.23,
"ir": 192.217,
"pt": 195.078,
"au": 196.96655,
"hg": 200.59,
"tl": 204.3833,
"pb": 207.2,
"bi": 208.98038,
"po": 208.9824,
"at": 209.9871,
"rn": 222.0176,
"fr": 223.0197,
"ra": 226.0254,
"ac": 227.0278,
"th": 232.0381,
"pa": 231.03588,
"u": 238.0289,
"np": 237.0482,
"pu": 244.0642,
"am": 243.0614,
"cm": 247.0703,
"bk": 247.0703,
"cf": 251.0796,
"es": 252.0829,
"fm": 257.0951,
"md": 258.0986,
"no": 259.1009,
"lr": 260.1053,
"q": 999.00000,
"x": 999.00000,
"cav": 1000.00000,
"sup": 1000000.0,
"dum": 1.0
}
atomicNumbers = {
"h": 1,
"d": 1,
"t": 1,
"he": 2,
"li": 3,
"be": 4,
"b": 5,
"c": 6,
"n": 7,
"o": 8,
"f": 9,
"ne": 10,
"na": 11,
"mg": 12,
"al": 13,
"si": 14,
"p": 15,
"s": 16,
"cl": 17,
"ar": 18,
"k": 19,
"ca": 20,
"sc": 21,
"ti": 22,
"v": 23,
"cr": 24,
"mn": 25,
"fe": 26,
"co": 27,
"ni": 28,
"cu": 29,
"zn": 30,
"ga": 31,
"ge": 32,
"as": 33,
"se": 34,
"br": 35,
"kr": 36,
"rb": 37,
"sr": 38,
"y": 39,
"zr": 40,
"nb": 41,
"mo": 42,
"tc": 43,
"ru": 44,
"rh": 45,
"pd": 46,
"ag": 47,
"cd": 48,
"in": 49,
"sn": 50,
"sb": 51,
"te": 52,
"i": 53,
"xe": 54,
"cs": 55,
"ba": 56,
"la": 57,
"ce": 58,
"pr": 59,
"nd": 60,
"pm": 61,
"sm": 62,
"eu": 63,
"gd": 64,
"tb": 65,
"dy": 66,
"ho": 67,
"er": 68,
"tm": 69,
"yb": 70,
"lu": 71,
"hf": 72,
"ta": 73,
"w": 74,
"re": 75,
"os": 76,
"ir": 77,
"pt": 78,
"au": 79,
"hg": 80,
"tl": 81,
"pb": 82,
"bi": 83,
"po": 84,
"at": 85,
"rn": 86,
"fr": 87,
"ra": 88,
"ac": 89,
"th": 90,
"pa": 91,
"u": 92,
"np": 93,
"pu": 94,
"am": 95,
"cm": 96,
"bk": 97,
"cf": 98,
"es": 99,
"fm": 100,
"md": 101,
"no": 102,
"lr": 103,
"q": 999,
"x": 999,
"cav": 1000,
"sup": 1000000,
"dum": 1
}
atomicNumbersReverse = {v: k for k, v in atomicNumbers.items()}