diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index e27caecb..1dbf780e 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -22,32 +22,18 @@ jobs: steps: - uses: "actions/checkout@v4" + # Whether to download Git-LFS files + with: + lfs: true - name: Setup python for test ${{ matrix.py }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} - - name: Install system dependencies for PyTables (Linux/macOS) - run: | - python -m pip install --upgrade pip wheel - pip install numpy==1.26.4 - if [[ "$RUNNER_OS" == "Linux" ]]; then - sudo apt-get update - sudo apt-get install -y libhdf5-dev libblosc-dev - elif [[ "$RUNNER_OS" == "macOS" ]]; then - export HOMEBREW_NO_INSTALL_CLEANUP=1 - brew update - brew install hdf5 c-blosc - export CPATH="$(brew --prefix hdf5)/include:$(brew --prefix c-blosc)/include:$CPATH" - export LIBRARY_PATH="$(brew --prefix hdf5)/lib:$(brew --prefix c-blosc)/lib:$LIBRARY_PATH" - export HDF5_DIR="$(brew --prefix hdf5)" - fi - - - name: Install development version run: | - pip install -v . + pip install -e . - name: Install extra test dependencies run: | @@ -77,5 +63,3 @@ jobs: click-to-expand: true report-title: 'Dev Test Report' pytest-args: '-m dev' - - diff --git a/atomdb/data/elements_data.h5 b/atomdb/data/elements_data.h5 index f8cb3e66..bafd3e4e 100644 --- a/atomdb/data/elements_data.h5 +++ b/atomdb/data/elements_data.h5 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fde2a5f5db8c0adb8418016ea8b85d5f12af4e2e40a7f07bef7bcfe474ae3e81 +oid sha256:af6bc5014271228a3f11c7b64e4dfc5b41a28193c77bd319d59ca8043f2aa4b9 size 105117616 diff --git a/atomdb/migration/periodic/elements_data.py b/atomdb/migration/periodic/elements_data.py index 22256219..11719db8 100644 --- a/atomdb/migration/periodic/elements_data.py +++ b/atomdb/migration/periodic/elements_data.py @@ -5,6 +5,7 @@ import warnings from atomdb.utils import CONVERTOR_TYPES + # Suppresses NaturalNameWarning warnings from PyTables. warnings.filterwarnings('ignore', category=pt.NaturalNameWarning) @@ -25,7 +26,7 @@ { 'basic_property': 'symbol', - 'table_name': 'symbol', + 'table_name': 'elem', 'description': 'Atom Symbol', 'type': 'string', }, diff --git a/atomdb/species.py b/atomdb/species.py index 4758be51..e04aa6cd 100644 --- a/atomdb/species.py +++ b/atomdb/species.py @@ -38,18 +38,19 @@ from numbers import Integral elements_hdf5_file = files("atomdb.data").joinpath("elements_data.h5") - -PROPERTY_NAME_MAP = { - "atmass": "atmass", - "cov_radius": "cov_radius", - "vdw_radius": "vdw_radius", - "at_radius": "at_radius", - "polarizability": "polarizability", - "dispersion_c6": "dispersion_c6", - "elem": "symbol", - "atnum": "atnum", - "name": "name", -} +ELEMENTS_H5FILE = pt.open_file(elements_hdf5_file, mode="r") + +PROPERTY_NAME_MAP = [ + "atmass", + "cov_radius", + "vdw_radius", + "at_radius", + "polarizability", + "dispersion_c6", + "elem", + "atnum", + "name" +] __all__ = [ "Species", @@ -116,6 +117,42 @@ def default_matrix(): # return wrapper +# def scalar(method): +# r"""Expose a SpeciesData field.""" +# name = method.__name__ +# +# @property +# def wrapper(self): +# print("hi") +# +# # Map the name of the method in the SpeciesData class to the name in the Elements class +# # This dict can be removed if the Elements csv file uses the same names as the SpeciesData class. +# namemap = { +# "cov_radius": "cov_radius", +# "vdw_radius": "vdw_radius", +# "at_radius": "at_radius", +# "polarizability": "pold", +# "dispersion_c6": "c6", +# "atmass": "mass", +# } +# +# if name == "atmass": +# print(f"inside atmass {getattr(Element(self._data.elem), namemap[name])}") +# return getattr(Element(self._data.elem), namemap[name]) +# if name in namemap: +# # Only return Element property if neutral, otherwise None +# charge = self._data.atnum - self._data.nelec +# print(f"charge {charge}") +# print(f"inside the other {getattr(Element(self._data.elem), namemap[name])}") +# return getattr(Element(self._data.elem), namemap[name]) if charge == 0 else None +# +# return getattr(self._data, name) +# +# # conserve the docstring of the method +# wrapper.__doc__ = method.__doc__ +# return wrapper + + def scalar(method): r"""Expose a SpeciesData field.""" name = method.__name__ @@ -128,39 +165,37 @@ def wrapper(self): # calculate charge then if charge is not zero (ions) --> return none charge = self._data.atnum - self._data.nelec - if charge != 0: + if charge != 0 and name not in ["atmass", "elem", "atnum", "name"]: return None - # open the HDF5 file in read mode - with pt.open_file(elements_hdf5_file, mode="r") as h5file: - # get the element group - element_group = f"/Elements/{self._data.atnum:03d}" - - table_name = PROPERTY_NAME_MAP[name] - table_path = f"{element_group}/{table_name}" - - # get the table node from the HDF5 file - table = h5file.get_node(table_path) - - # Handle basic properties (single row) - if table.nrows == 1: - value = table[0]["value"] - # if the value is an int, return it as an int - if isinstance(value, Integral): - return int(value) - # if the value is a string, decode from bytes - elif isinstance(value, bytes): - return value.decode("utf-8") - else: - # handle properties with multiple sources - result = {} - for row in table: - source = row["source"].decode("utf-8") - value = row["value"] - # exclude none values - if not np.isnan(value): - result[source] = float(value) - return result if result else None + # get the element group + element_group = f"/Elements/{self._data.atnum:03d}" + + table_name = name #PROPERTY_NAME_MAP[name] + table_path = f"{element_group}/{table_name}" + + # get the table node from the HDF5 file + table = ELEMENTS_H5FILE.get_node(table_path) + + # Handle basic properties (single column --> no sources) + if len(table.colnames) == 1 and table.colnames[0] == "value": + value = table[0]["value"] + # if the value is an int, return it as an int + if isinstance(value, Integral): + return int(value) + # if the value is a string, decode from bytes + elif isinstance(value, bytes): + return value.decode("utf-8") + else: + # handle properties with multiple sources + result = {} + for row in table: + source = row["source"].decode("utf-8") + value = row["value"] + # exclude none values + if not np.isnan(value): + result[source] = float(value) + return result if result else None wrapper.__doc__ = method.__doc__ return wrapper diff --git a/atomdb/test/test_nist.py b/atomdb/test/test_nist.py index 350820b2..02cb0fc2 100644 --- a/atomdb/test/test_nist.py +++ b/atomdb/test/test_nist.py @@ -11,6 +11,7 @@ TEST_DATAPATH = files("atomdb.test.data") TEST_DATAPATH = os.fspath(TEST_DATAPATH._paths[0]) +ELEMENTS_HDF5_FILE = files("atomdb.data").joinpath("elements_data.h5") TEST_CASES_MAKE_PROMOLECULE = [ pytest.param( @@ -151,6 +152,9 @@ def test_nist_data(case): """ Test getting the attributes of the atom for Hydrogen and Carbon. """ + if not ELEMENTS_HDF5_FILE.exists(): + pytest.skip(f"Required data file not found: {ELEMENTS_HDF5_FILE}") + elem = case.get("elem") charge = case.get("atnum") - case.get("nelec") mult = case.get("nspin") + 1