Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CI pipeline #28

Merged
merged 11 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Test NeuralForceField package

on: [push]

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
# python-version: ["pypy3.10", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Install basics
run: python -m pip install --upgrade pip setuptools wheel
- name: Install package
run: python -m pip install .
# - name: Install linters
# run: python -m pip install flake8 mypy pylint
# - name: Install documentation requirements
# run: python -m pip install -r docs/requirements.txt
# - name: Test with flake8
# run: flake8 polymethod
# - name: Test with mypy
# run: mypy polymethod
# - name: Test with pylint
# run: pylint polymethod
- name: Test with pytest
run: |
pip install pytest pytest-cov
pytest nff/tests --doctest-modules --junitxml=junit/test-results-${{ matrix.python-version }}.xml --cov=nff --cov-report=xml --cov-report=html
- name: Upload pytest test results
uses: actions/upload-artifact@v4
with:
name: pytest-results-${{ matrix.python-version }}
path: junit/test-results-${{ matrix.python-version }}.xml
if: ${{ always() }}
# - name: Test documentation
# run: sphinx-build docs/source docs/build
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,17 @@ dist/
sandbox_excited/
build/

# Editor files
# vim
*.swp
*.swo

# pycharm
.idea/

# coverage and tests
junit
.coverage

# required exceptions
!tutorials/models/ammonia/Ammonia.xyz
5 changes: 5 additions & 0 deletions nff/data/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def __init__(
units: str = "kcal/mol",
check_props: bool = True,
do_copy: bool = True,
device: str = "cuda"
) -> None:
"""Constructor for Dataset class.

Expand All @@ -108,6 +109,7 @@ def __init__(
self.props = props
self.units = units
self.to_units(units)
self.device = device

def __len__(self) -> int:
"""Length of the dataset.
Expand Down Expand Up @@ -289,6 +291,7 @@ def _get_periodic_neighbor_list(
pbc=True,
cutoff=cutoff,
directed=(not undirected),
device=self.device,
)
nbrs, offs = atoms.update_nbr_list()
nbrlist.append(nbrs)
Expand Down Expand Up @@ -444,6 +447,7 @@ def unwrap_xyz(self, mol_dic: dict) -> None:
numbers=self.props["nxyz"][i][:, 0],
cell=self.props["cell"][i],
pbc=True,
device=self.device
)

# recontruct coordinates based on subgraphs index
Expand Down Expand Up @@ -577,6 +581,7 @@ def gen_bond_prior(self, cutoff: float, bond_len_dict: dict | None = None) -> No
"cutoff": cutoff,
"cell": cell,
"nbr_torch": False,
"device": self.device
}

# the coordinates have been unwrapped and try to results offsets
Expand Down
File renamed without changes.
15 changes: 15 additions & 0 deletions nff/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import os
import pytest
import torch

torch.set_num_threads(int(os.getenv("OMP_NUM_THREADS", 1)))


def pytest_addoption(parser):
parser.addoption("--device", action="store", default="cpu", help="Whether to use the CPU or GPU for the tests")


@pytest.fixture
def device(request):
return request.config.getoption("--device")
Binary file added nff/tests/data/azo_diabat.pth.tar
Binary file not shown.
Binary file added nff/tests/data/dataset.pth.tar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from ase.io.trajectory import Trajectory
from ase import Atoms

from nff.md.utils import mol_dot, mol_norm, ZhuNakamuraLogger, atoms_to_nxyz
from nff.md.nvt_test import NoseHoover, NoseHooverChain
from nff.md.utils_ax import mol_dot, mol_norm, ZhuNakamuraLogger, atoms_to_nxyz
from nff.md.nvt_ax import NoseHoover, NoseHooverChain
steinmig marked this conversation as resolved.
Show resolved Hide resolved
from nff.utils.constants import BOHR_RADIUS, FS_TO_AU, AMU_TO_AU, FS_TO_ASE, ASE_TO_FS, EV_TO_AU
from nff.data import Dataset, collate_dicts
from nff.utils.cuda import batch_to
Expand Down
37 changes: 27 additions & 10 deletions nff/io/tests/test_ase.py → nff/tests/test_ase.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import numpy as np
from ase import Atoms

import pytest

ajhoffman1229 marked this conversation as resolved.
Show resolved Hide resolved
from nff.io.ase import AtomsBatch


Expand All @@ -19,6 +21,8 @@ def compare_dicts(d1: dict, d2: dict):
for key, value in d1.items():
if isinstance(value, dict):
compare_dicts(value, d2[key])
elif isinstance(value, str):
assert value == d2[key]
elif isinstance(value, Iterable):
assert np.allclose(value, d2[key])
else:
Expand Down Expand Up @@ -47,10 +51,17 @@ def get_ethanol():
return Atoms(nxyz[:, 0].astype(int), positions=nxyz[:, 1:])


# @ut.skip("skip this for now")
@pytest.mark.usefixtures("device") # Ensure the fixture is accessible
class TestAtomsBatch(ut.TestCase):
def setUp(self):
self.ethanol = get_ethanol()
# Access the device value from the pytest fixture
self.device = self._test_fixture_device

@pytest.fixture(autouse=True)
def inject_device(self, device):
# Automatically set the fixture value to an attribute
self._test_fixture_device = device

@ut.skip("skip this for now")
def test_AtomsBatch(self):
Expand Down Expand Up @@ -111,7 +122,7 @@ def test_AtomsBatch(self):
]
)

atoms_batch = AtomsBatch(self.ethanol, cutoff=2.5)
atoms_batch = AtomsBatch(self.ethanol, cutoff=2.5, device=self.device)
atoms_batch.update_nbr_list()

G1 = nx.from_edgelist(expected_nbrlist_cutoff_2dot5)
Expand All @@ -120,21 +131,21 @@ def test_AtomsBatch(self):
assert nx.is_isomorphic(G1, G2)

def test_get_batch(self):
atoms_batch = AtomsBatch(self.ethanol, cutoff=5)
atoms_batch = AtomsBatch(self.ethanol, cutoff=5, device=self.device)
batch = atoms_batch.get_batch()

assert "nxyz" in batch

def test_from_atoms(self):
atoms_batch = AtomsBatch.from_atoms(self.ethanol, cutoff=2.5)
atoms_batch = AtomsBatch.from_atoms(self.ethanol, cutoff=2.5, device=self.device)

# ensure atomic numbers, positions, and cell are the same
assert np.allclose(atoms_batch.get_atomic_numbers(), self.ethanol.get_atomic_numbers())
assert np.allclose(atoms_batch.get_positions(), self.ethanol.get_positions())
assert np.allclose(atoms_batch.get_cell(), self.ethanol.get_cell())

def test_copy(self):
atoms_batch = AtomsBatch(self.ethanol, cutoff=2.5)
atoms_batch = AtomsBatch(self.ethanol, cutoff=2.5, device=self.device)
atoms_batch.get_batch() # update props
atoms_batch_copy = atoms_batch.copy()

Expand All @@ -154,7 +165,7 @@ def test_copy(self):
assert atoms_batch.requires_large_offsets == atoms_batch_copy.requires_large_offsets

def test_fromdict(self):
atoms_batch = AtomsBatch(self.ethanol, cutoff=2.5)
atoms_batch = AtomsBatch(self.ethanol, cutoff=2.5, device=self.device)
ab_dict = atoms_batch.todict(update_props=True)
ab_from_dict = AtomsBatch.fromdict(ab_dict)

Expand Down Expand Up @@ -183,6 +194,7 @@ def test_fromdict(self):
compare_dicts(ab_dict_props, ab_dict_again_props)


@pytest.mark.usefixtures("device") # Ensure the fixture is loaded
class TestPeriodic(ut.TestCase):
def setUp(self):
nxyz = np.array(
Expand All @@ -205,9 +217,15 @@ def setUp(self):
[0.0, 0.0, 5.51891759],
]
)
self.quartz = AtomsBatch(nxyz[:, 0].astype(int), positions=nxyz[:, 1:], cell=lattice, pbc=True)

def test_ase(self):
self.quartz = AtomsBatch(nxyz[:, 0].astype(int), positions=nxyz[:, 1:], cell=lattice, pbc=True,
device=self._test_fixture_device)

@pytest.fixture(autouse=True)
def inject_device(self, device):
# Automatically set the fixture value to an attribute
self._test_fixture_device = device

def test_print(self):
print(self.quartz)

def test_nbrlist(self):
Expand Down Expand Up @@ -469,7 +487,6 @@ def test_nbrlist(self):
]
)
assert np.allclose(nbrlist, expected_nbrlist)
print(offsets)


if __name__ == "__main__":
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import numpy as np
import torch

import pytest

from nff.data.dataset import (
Dataset,
concatenate_dict,
Expand All @@ -14,8 +16,8 @@
)

current_path = Path(__file__).parent
DATASET_PATH = current_path / "../../../tutorials/data/dataset.pth.tar"
PEROVSKITE_DATA_PATH = current_path / "./data/SrIrO3_bulk_55_nff_all_dataset.pth.tar"
DATASET_PATH = os.path.join(current_path, "..", "..", "..", "tutorials", "data", "dataset.pth.tar")
PEROVSKITE_DATA_PATH = os.path.join(current_path, "data", "SrIrO3_bulk_55_nff_all_dataset.pth.tar")
TARG_NAME = "formula"
VAL_SIZE = 0.1
TEST_SIZE = 0.1
Expand Down Expand Up @@ -223,6 +225,7 @@ def test_inexistent_list_lists(self):
self.assertEqual(ab, expected)


@pytest.mark.usefixtures("device") # Ensure the fixture is accessible
class TestPeriodicDataset(unittest.TestCase):
def setUp(self):
self.quartz = {
Expand All @@ -248,7 +251,12 @@ def setUp(self):
),
}

self.qtz_dataset = Dataset(concatenate_dict(*[self.quartz] * 3))
self.qtz_dataset = Dataset(concatenate_dict(*[self.quartz] * 3), device=self._test_fixture_device)

@pytest.fixture(autouse=True)
def inject_device(self, device):
# Automatically set the fixture value to an attribute
self._test_fixture_device = device

def test_neighbor_list(self):
nbrs, offs = self.qtz_dataset.generate_neighbor_list(cutoff=5)
Expand Down
File renamed without changes.
Loading