refactor: unify and modernize periodic table utilities and typing

- Replace atomsymb and atommass tuples with AtomInfo dataclass and PTable enum for atomic data
- Refactor all usages to access atomic symbol and mass via PTable methods
- Remove nptyping dependency, switch to numpy.typing for type annotations
- Update molecule and atom classes to use new typing and atomic data access
- Bump numpy version to 2.x and remove nptyping from dependencies
This commit is contained in:
2026-02-27 17:56:11 -03:00
parent b6e57bc1c5
commit d400970e8f
8 changed files with 242 additions and 356 deletions

View File

@@ -1,52 +1,23 @@
from diceplayer.utils.ptable import atommass
from diceplayer.utils.ptable import PTable
from dataclasses import dataclass
@dataclass
class Atom:
"""
Atom class declaration. This class is used throughout the DicePlayer program to represent atoms.
Atributes:
lbl (int): Dice derived variable used to represent atoms with identical energies and simetric positions.
na (int): Atomic number of the represented atom.
rx (float): x cartesian coordinates of the represented atom.
ry (float): y cartesian coordinates of the represented atom.
rz (float): z cartesian coordinates of the represented atom.
chg (float): charge of the represented atom.
eps (float): quantum number epsilon of the represented atom.
sig (float): quantum number sigma of the represented atom.
"""
def __init__(
self,
lbl: int,
na: int,
rx: float,
ry: float,
rz: float,
chg: float,
eps: float,
sig: float,
) -> None:
"""
The constructor function __init__ is used to create new instances of the Atom class.
lbl: int
na: int
rx: float
ry: float
rz: float
chg: float
eps: float
sig: float
Args:
lbl (int): Dice derived variable used to represent atoms with identical energies and simetric positions.
na (int): Atomic number of the represented atom.
rx (float): x cartesian coordinates of the represented atom.
ry (float): y cartesian coordinates of the represented atom.
rz (float): z cartesian coordinates of the represented atom.
chg (float): charge of the represented atom.
eps (float): quantum number epsilon of the represented atom.
sig (float): quantum number sigma of the represented atom.
"""
self.lbl = lbl
self.na = na
self.rx = rx
self.ry = ry
self.rz = rz
self.chg = chg
self.eps = eps
self.sig = sig
self.mass = atommass[self.na]
@property
def mass(self) -> float:
return PTable.get_atomic_mass(self.na)

View File

@@ -7,11 +7,12 @@ if TYPE_CHECKING:
from nptyping import Float, NDArray, Shape
from diceplayer import logger
from diceplayer.environment.atom import Atom
from diceplayer.environment import Atom
from diceplayer.utils.misc import BOHR2ANG
from diceplayer.utils.ptable import ghost_number
from diceplayer.utils.ptable import GHOST_NUMBER
import numpy as np
import numpy.typing as npt
from numpy.linalg import linalg
from typing_extensions import Any, List, Tuple, Union
@@ -26,12 +27,12 @@ class Molecule:
Atributes:
molname (str): The name of the represented molecule
atom (List[Atom]): List of atoms of the represented molecule
position (NDArray[Any, Any]): The position relative to the internal atoms of the represented molecule
energy (NDArray[Any, Any]): The energy of the represented molecule
gradient (NDArray[Any, Any]): The first derivative of the energy relative to the position
hessian (NDArray[Any, Any]): The second derivative of the energy relative to the position
position (npt.NDArray[np.float64]): The position relative to the internal atoms of the represented molecule
energy (npt.NDArray[np.float64]): The energy of the represented molecule
gradient (npt.NDArray[np.float64]): The first derivative of the energy relative to the position
hessian (npt.NDArray[np.float64]): The second derivative of the energy relative to the position
total_mass (int): The total mass of the molecule
com (NDArray[Any, Any]): The center of mass of the molecule
com (npt.NDArray[np.float64]): The center of mass of the molecule
"""
def __init__(self, molname: str) -> None:
@@ -44,16 +45,16 @@ class Molecule:
self.molname: str = molname
self.atom: List[Atom] = []
self.position: NDArray[Any, Any]
self.energy: NDArray[Any, Any]
self.gradient: NDArray[Any, Any]
self.hessian: NDArray[Any, Any]
self.position: npt.NDArray[np.float64]
self.energy: npt.NDArray[np.float64]
self.gradient: npt.NDArray[np.float64]
self.hessian: npt.NDArray[np.float64]
self.ghost_atoms: List[Atom] = []
self.lp_atoms: List[Atom] = []
self.total_mass: int = 0
self.com: Union[None, NDArray[Any, Any]] = None
self.com: Union[None, npt.NDArray[np.float64]] = None
def add_atom(self, a: Atom) -> None:
"""
@@ -68,7 +69,7 @@ class Molecule:
self.center_of_mass()
def center_of_mass(self) -> NDArray[Any, Any]:
def center_of_mass(self) -> npt.NDArray[np.float64]:
"""
Calculates the center of mass of the molecule
"""
@@ -115,7 +116,7 @@ class Molecule:
return [charge, dipole[0], dipole[1], dipole[2], total_dipole]
def distances_between_atoms(self) -> NDArray[Shape["Any,Any"], Float]:
def distances_between_atoms(self) -> npt.NDArray[np.float64]:
"""
Calculates distances between the atoms of the molecule
@@ -135,16 +136,22 @@ class Molecule:
return np.array(distances).reshape(dim, dim - 1)
def inertia_tensor(self) -> NDArray[Shape["3, 3"], Float]:
def inertia_tensor(self) -> npt.NDArray[np.float64]:
"""
Calculates the inertia tensor of the molecule.
Returns:
NDArray[Shape["3, 3"], Float]: inertia tensor of the molecule.
npt.NDArray[np.float64]: inertia tensor of the molecule.
"""
self.center_of_mass()
Ixx = Ixy = Ixz = Iyy = Iyz = Izz = 0.0
Ixx = 0.0
Ixy = 0.0
Ixz = 0.0
Iyy = 0.0
Iyz = 0.0
Izz = 0.0
for atom in self.atom:
dx = atom.rx - self.com[0]
@@ -374,9 +381,9 @@ class Molecule:
distances = []
for atom1 in self.atom:
if atom1.na != ghost_number:
if atom1.na != GHOST_NUMBER:
for atom2 in molec.atom:
if atom2.na != ghost_number:
if atom2.na != GHOST_NUMBER:
dx = atom1.rx - atom2.rx
dy = atom1.ry - atom2.ry
dz = atom1.rz - atom2.rz

View File

@@ -2,12 +2,12 @@ from __future__ import annotations
from diceplayer import logger
from diceplayer.config.player_config import PlayerConfig
from diceplayer.environment.atom import Atom
from diceplayer.environment import Atom
from diceplayer.environment.molecule import Molecule
from diceplayer.environment.system import System
from diceplayer.interface import Interface
from diceplayer.utils.misc import date_time
from diceplayer.utils.ptable import atomsymb
from diceplayer.utils.ptable import PTable
import numpy as np
from nptyping import NDArray
@@ -161,9 +161,9 @@ class GaussianInterface(Interface):
if (
line[0].title()
!= atomsymb[
!= PTable.get_atomic_symbol(
self.system.molecule[type].atom[site].na
].strip()
).strip()
):
raise SyntaxError(
"Error: Invalid Dice Output. Atom type does not match."
@@ -188,7 +188,7 @@ class GaussianInterface(Interface):
for atom in new_molecule.atom:
asec_charges.append(
{
"lbl": atomsymb[atom.na],
"lbl": PTable.get_atomic_symbol(atom.na),
"rx": atom.rx,
"ry": atom.ry,
"rz": atom.rz,
@@ -258,7 +258,7 @@ class GaussianInterface(Interface):
)
for atom in self.system.molecule[0].atom:
symbol = atomsymb[atom.na]
symbol = PTable.get_atomic_symbol(atom.na)
gaussian_input.append(
"{:<2s} {:>10.5f} {:>10.5f} {:>10.5f}\n".format(
symbol, atom.rx, atom.ry, atom.rz

View File

@@ -2,7 +2,7 @@ from diceplayer import VERSION, logger
from diceplayer.config.player_config import PlayerConfig
from diceplayer.environment import Atom, Molecule, System
from diceplayer.interface import DiceInterface, GaussianInterface
from diceplayer.utils import atomsymb, weekday_date_time
from diceplayer.utils import PTable, weekday_date_time
import yaml
from pydantic import BaseModel
@@ -289,7 +289,7 @@ class Player:
file.write(f"Cycle # {cycle}\n")
for atom in self.system.molecule[0].atom:
symbol = atomsymb[atom.na]
symbol = PTable.get_atomic_symbol(atom.na)
file.write(
f"{symbol:<2s} {atom.rx:>10.6f} {atom.ry:>10.6f} {atom.rz:>10.6f}\n"
)

View File

@@ -6,14 +6,14 @@ from .misc import (
make_step_dir,
weekday_date_time,
)
from .ptable import atommass, atomsymb
from .ptable import AtomInfo, PTable
__all__ = [
"Logger",
"valid_logger",
"atomsymb",
"atommass",
"PTable",
"AtomInfo",
"weekday_date_time",
"date_time",
"compress_files_1mb",

View File

@@ -1,223 +1,143 @@
#### Label used in Dice for a ghost atom
dice_ghost_label = "Xx"
from dataclasses import dataclass
from enum import Enum
#### Tuple of atom symbols
atomsymb = (
"00",
"H ",
"He",
"Li",
"Be",
"B ",
"C ",
"N ",
"O ",
"F ",
"Ne",
"Na",
"Mg",
"Al",
"Si",
"P ",
"S ",
"Cl",
"Ar",
"K ",
"Ca",
"Sc",
"Ti",
"V ",
"Cr",
"Mn",
"Fe",
"Co",
"Ni",
"Cu",
"Zn",
"Ga",
"Ge",
"As",
"Se",
"Br",
"Kr",
"Rb",
"Sr",
"Y ",
"Zr",
"Nb",
"Mo",
"Tc",
"Ru",
"Rh",
"Pd",
"Ag",
"Cd",
"In",
"Sn",
"Sb",
"Te",
"I ",
"Xe",
"Cs",
"Ba",
"La",
"Ce",
"Pr",
"Nd",
"Pm",
"Sm",
"Eu",
"Gd",
"Tb",
"Dy",
"Ho",
"Er",
"Tm",
"Yb",
"Lu",
"Hf",
"Ta",
"W ",
"Re",
"Os",
"Ir",
"Pt",
"Au",
"Hg",
"Ti",
"Pb",
"Bi",
"Po",
"At",
"Rn",
"Fr",
"Ra",
"Ac",
"Th",
"Pa",
"U ",
"Np",
"Pu",
"Am",
"Cm",
"Bk",
"Cf",
"Es",
"Fm",
"Md",
"No",
"Lr",
dice_ghost_label,
)
#### Tuple of atom masses
atommass = (
0.0,
1.0079,
4.0026,
6.9410,
9.0122,
10.811,
12.011,
14.007,
15.999,
18.998,
20.180,
22.990,
24.305,
26.982,
28.086,
30.974,
32.065,
35.453,
39.948,
39.098,
40.078,
44.956,
47.867,
50.942,
51.996,
54.938,
55.845,
58.933,
58.693,
63.546,
65.409,
69.723,
72.640,
74.922,
78.960,
79.904,
83.798,
85.468,
87.620,
88.906,
91.224,
92.906,
95.940,
98.000,
101.07,
102.91,
106.42,
107.87,
112.41,
114.82,
118.71,
121.76,
127.60,
126.90,
131.29,
132.91,
137.33,
138.91,
140.12,
140.91,
144.24,
145.00,
150.36,
151.96,
157.25,
158.93,
162.50,
164.93,
167.26,
168.93,
173.04,
174.97,
178.49,
180.95,
183.84,
186.21,
190.23,
192.22,
195.08,
196.97,
200.59,
204.38,
207.20,
208.98,
209.00,
210.00,
222.00,
223.00,
226.00,
227.00,
232.04,
231.04,
238.03,
237.00,
244.00,
243.00,
247.00,
247.00,
251.00,
252.00,
257.00,
258.00,
259.00,
262.00,
0.000,
)
DICE_GHOST_LABEL = "Xx"
#### Number of the ghost atom
ghost_number = len(atomsymb) - 1
GHOST_NUMBER = 0
@dataclass(frozen=True, slots=True)
class AtomInfo:
atomic_number: int
symbol: str
mass: float
class PTable(Enum):
Xx = AtomInfo(GHOST_NUMBER, DICE_GHOST_LABEL, 0.0)
H = AtomInfo(1, "H", 1.0079)
He = AtomInfo(2, "He", 4.0026)
Li = AtomInfo(3, "Li", 6.9410)
Be = AtomInfo(4, "Be", 9.0122)
B = AtomInfo(5, "B", 10.811)
C = AtomInfo(6, "C", 12.011)
N = AtomInfo(7, "N", 14.007)
O = AtomInfo(8, "O", 15.999)
F = AtomInfo(9, "F", 18.998)
Ne = AtomInfo(10, "Ne", 20.180)
Na = AtomInfo(11, "Na", 22.990)
Mg = AtomInfo(12, "Mg", 24.305)
Al = AtomInfo(13, "Al", 26.982)
Si = AtomInfo(14, "Si", 28.086)
P = AtomInfo(15, "P", 30.974)
S = AtomInfo(16, "S", 32.065)
Cl = AtomInfo(17, "Cl", 35.453)
Ar = AtomInfo(18, "Ar", 39.948)
K = AtomInfo(19, "K", 39.098)
Ca = AtomInfo(20, "Ca", 40.078)
Sc = AtomInfo(21, "Sc", 44.956)
Ti = AtomInfo(22, "Ti", 47.867)
V = AtomInfo(23, "V", 50.942)
Cr = AtomInfo(24, "Cr", 51.996)
Mn = AtomInfo(25, "Mn", 54.938)
Fe = AtomInfo(26, "Fe", 55.845)
Co = AtomInfo(27, "Co", 58.933)
Ni = AtomInfo(28, "Ni", 58.693)
Cu = AtomInfo(29, "Cu", 63.546)
Zn = AtomInfo(30, "Zn", 65.409)
Ga = AtomInfo(31, "Ga", 69.723)
Ge = AtomInfo(32, "Ge", 72.640)
As = AtomInfo(33, "As", 74.922)
Se = AtomInfo(34, "Se", 78.960)
Br = AtomInfo(35, "Br", 79.904)
Kr = AtomInfo(36, "Kr", 83.798)
Rb = AtomInfo(37, "Rb", 85.468)
Sr = AtomInfo(38, "Sr", 87.620)
Y = AtomInfo(39, "Y", 88.906)
Zr = AtomInfo(40, "Zr", 91.224)
Nb = AtomInfo(41, "Nb", 92.906)
Mo = AtomInfo(42, "Mo", 95.940)
Tc = AtomInfo(43, "Tc", 98.000)
Ru = AtomInfo(44, "Ru", 101.07)
Rh = AtomInfo(45, "Rh", 102.91)
Pd = AtomInfo(46, "Pd", 106.42)
Ag = AtomInfo(47, "Ag", 107.87)
Cd = AtomInfo(48, "Cd", 112.41)
In = AtomInfo(49, "In", 114.82)
Sn = AtomInfo(50, "Sn", 118.71)
Sb = AtomInfo(51, "Sb", 121.76)
Te = AtomInfo(52, "Te", 127.60)
I = AtomInfo(53, "I", 126.90)
Xe = AtomInfo(54, "Xe", 131.29)
Cs = AtomInfo(55, "Cs", 132.91)
Ba = AtomInfo(56, "Ba", 137.33)
La = AtomInfo(57, "La", 138.91)
Ce = AtomInfo(58, "Ce", 140.12)
Pr = AtomInfo(59, "Pr", 140.91)
Nd = AtomInfo(60, "Nd", 144.24)
Pm = AtomInfo(61, "Pm", 145.00)
Sm = AtomInfo(62, "Sm", 150.36)
Eu = AtomInfo(63, "Eu", 151.96)
Gd = AtomInfo(64, "Gd", 157.25)
Tb = AtomInfo(65, "Tb", 158.93)
Dy = AtomInfo(66, "Dy", 162.50)
Ho = AtomInfo(67, "Ho", 164.93)
Er = AtomInfo(68, "Er", 167.26)
Tm = AtomInfo(69, "Tm", 168.93)
Yb = AtomInfo(70, "Yb", 173.04)
Lu = AtomInfo(71, "Lu", 174.97)
Hf = AtomInfo(72, "Hf", 178.49)
Ta = AtomInfo(73, "Ta", 180.95)
W = AtomInfo(74, "W", 183.84)
Re = AtomInfo(75, "Re", 186.21)
Os = AtomInfo(76, "Os", 190.23)
Ir = AtomInfo(77, "Ir", 192.22)
Pt = AtomInfo(78, "Pt", 195.08)
Au = AtomInfo(79, "Au", 196.97)
Hg = AtomInfo(80, "Hg", 200.59)
Tl = AtomInfo(81, "Tl", 204.38)
Pb = AtomInfo(82, "Pb", 207.20)
Bi = AtomInfo(83, "Bi", 208.98)
Po = AtomInfo(84, "Po", 209.00)
At = AtomInfo(85, "At", 210.00)
Rn = AtomInfo(86, "Rn", 222.00)
Fr = AtomInfo(87, "Fr", 223.00)
Ra = AtomInfo(88, "Ra", 226.00)
Ac = AtomInfo(89, "Ac", 227.00)
Th = AtomInfo(90, "Th", 232.04)
Pa = AtomInfo(91, "Pa", 231.04)
U = AtomInfo(92, "U", 238.03)
Np = AtomInfo(93, "Np", 237.00)
Pu = AtomInfo(94, "Pu", 244.00)
Am = AtomInfo(95, "Am", 243.00)
Cm = AtomInfo(96, "Cm", 247.00)
Bk = AtomInfo(97, "Bk", 247.00)
Cf = AtomInfo(98, "Cf", 251.00)
Es = AtomInfo(99, "Es", 252.00)
Fm = AtomInfo(100, "Fm", 257.00)
Md = AtomInfo(101, "Md", 258.00)
No = AtomInfo(102, "No", 259.00)
Lr = AtomInfo(103, "Lr", 262.00)
@classmethod
def get_atomic_symbol(cls, atomic_number: int) -> str:
for element in cls:
if element.value.atomic_number == atomic_number:
return element.value.symbol
raise ValueError(f"Atomic number {atomic_number} not found in PTable.")
@classmethod
def get_atomic_mass(cls, atomic_number: int) -> float:
for element in cls:
if element.value.atomic_number == atomic_number:
return element.value.mass
raise ValueError(f"Atomic number {atomic_number} not found in PTable.")
@classmethod
def get_from_atomic_number(cls, atomic_number: int) -> AtomInfo:
for element in cls:
if element.value.atomic_number == atomic_number:
return element.value
raise ValueError(f"Atomic number {atomic_number} not found in PTable.")