From 27230b18bbfdecbbb85bf1282ad900a7ee28fea1 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel) YANG" Date: Fri, 10 Jan 2025 04:38:23 +0800 Subject: [PATCH] [Breaking/Fix] Skip isotopes when iterating through `core.Element` (#4180) * try to overwrite iter * add test * enhance module documentation, remove ptable class * also check full members * also check full members * inherit from EnumType * avoid hard-coding isotopes * revert to enummeta * add named_isotopes property * fix incorrect merge --------- Co-authored-by: Shyue Ping Ong --- src/pymatgen/core/periodic_table.py | 24 +++++++++++++++++++++--- tests/core/test_periodic_table.py | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/pymatgen/core/periodic_table.py b/src/pymatgen/core/periodic_table.py index c08f9bcc30e..923882ddc94 100644 --- a/src/pymatgen/core/periodic_table.py +++ b/src/pymatgen/core/periodic_table.py @@ -1,4 +1,9 @@ -"""Classes representing Element, Species (Element + oxidation state) and PeriodicTable.""" +"""Classes representing: +- Element: Element in the periodic table. +- Species: Element with optional oxidation state and spin. +- DummySpecies: Non-traditional Elements/Species (vacancies/...). +- ElementType: element types (metal/noble_gas/halogen/s_block/...). +""" from __future__ import annotations @@ -8,7 +13,7 @@ import re import warnings from collections import Counter -from enum import Enum, unique +from enum import Enum, EnumMeta, unique from itertools import combinations, product from pathlib import Path from typing import TYPE_CHECKING, overload @@ -864,7 +869,20 @@ def print_periodic_table(filter_function: Callable | None = None) -> None: print(" ".join(row_str)) -class Element(ElementBase): +class _ElementMeta(EnumMeta): + """Override Element to handle isotopes.""" + + def __iter__(cls): + """Skip named isotopes when iterating.""" + return (elem for elem in super().__iter__() if not elem._is_named_isotope) + + @property + def named_isotopes(cls): + """Get all named isotopes.""" + return tuple(elem for elem in super().__iter__() if elem._is_named_isotope) + + +class Element(ElementBase, metaclass=_ElementMeta): """Enum representing an element in the periodic table.""" # This name = value convention is redundant and dumb, but unfortunately is diff --git a/tests/core/test_periodic_table.py b/tests/core/test_periodic_table.py index a4a696a4f56..84b6fcca159 100644 --- a/tests/core/test_periodic_table.py +++ b/tests/core/test_periodic_table.py @@ -29,6 +29,25 @@ def test_init(self): assert id(Element("Fe")) == id(Element("Fe")) # Test caching + def test_iter(self): + # Make sure isotopes don't show during iteration + assert [elem.name for elem in Element] == ( + "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,Tl,Pb,Bi,Po,At,Rn,Fr,Ra,Ac,Th,Pa,U,Np," + "Pu,Am,Cm,Bk,Cf,Es,Fm,Md,No,Lr,Rf,Db,Sg,Bh,Hs,Mt,Ds,Rg,Cn,Nh,Fl,Mc,Lv,Ts,Og" + ).split(",") + + # Make sure isotopes are still in members + assert list(Element.__members__) == ( + "H,D,T,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,Tl,Pb,Bi,Po,At,Rn,Fr,Ra,Ac,Th,Pa,U,Np," + "Pu,Am,Cm,Bk,Cf,Es,Fm,Md,No,Lr,Rf,Db,Sg,Bh,Hs,Mt,Ds,Rg,Cn,Nh,Fl,Mc,Lv,Ts,Og" + ).split(",") + def test_missing_and_confusing_data(self): with pytest.warns(UserWarning, match="No data available"): _ = Element.H.metallic_radius @@ -391,6 +410,8 @@ def test_isotope(self): 3.0155007134, ] + assert Element.named_isotopes == (Element.D, Element.T) + class TestSpecies(PymatgenTest): def setUp(self):