Skip to content

Commit

Permalink
Merge pull request #164 from ipcamit/kliff-master-v1
Browse files Browse the repository at this point in the history
Added tests for newer modules
  • Loading branch information
mjwen authored Feb 23, 2024
2 parents f9f547b + be6b207 commit b4fa779
Show file tree
Hide file tree
Showing 13 changed files with 408 additions and 8 deletions.
2 changes: 1 addition & 1 deletion kliff/dataset/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def fingerprint(self, fingerprint):
"""
Set the fingerprint of the configuration.
Args:
fingerprint: Numpy array which is the fingerprint of the configuration.
fingerprint: Object which is the fingerprint of the configuration.
"""
self._fingerprint = fingerprint

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def forward(self, configuration: Configuration) -> Any:
def __call__(self, configuration: Configuration) -> Any:
fingerprint = self.forward(configuration)
if self.copy_to_config:
configuration.fingerprint(fingerprint)
configuration.fingerprint = fingerprint
return fingerprint

def inverse(self, *args, **kargs) -> Configuration:
Expand Down
2 changes: 1 addition & 1 deletion kliff/transforms/property_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def _get_property_values(
def _set_property_values(
self,
dataset: Union[List[Configuration], Dataset],
property_values: Union[np.ndarray, List[float, int]],
property_values: Union[np.ndarray, List[Union[float, int]]],
):
"""
Set the property values of all the configurations in a dataset. This method
Expand Down
6 changes: 3 additions & 3 deletions kliff/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ def stress_to_voigt(input_stress: np.ndarray) -> list:
stress[0] = input_stress[0, 0]
stress[1] = input_stress[1, 1]
stress[2] = input_stress[2, 2]
stress[3] = input_stress[0, 1]
stress[3] = input_stress[1, 2]
stress[4] = input_stress[0, 2]
stress[5] = input_stress[1, 2]
stress[5] = input_stress[0, 1]
else:
raise ValueError("input_stress must be a 2D array")

Expand All @@ -218,7 +218,7 @@ def stress_to_tensor(input_stress: list) -> np.ndarray:
stress[1, 1] = input_stress[1]
stress[2, 2] = input_stress[2]
stress[1, 2] = stress[2, 1] = input_stress[3]
stress[0, 2] = stress[0, 2] = input_stress[4]
stress[0, 2] = stress[2, 0] = input_stress[4]
stress[0, 1] = stress[1, 0] = input_stress[5]

return stress
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ def get_readme():
"torch",
"numpy",
"ase",
"libdescriptor",
"torch_geometric",
],
"docs": [
"sphinx",
Expand Down
53 changes: 51 additions & 2 deletions tests/dataset/test_ase_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import numpy as np
import pytest
from ase import io

from kliff.dataset import Dataset


def test_ase_parser():
"""Test ASE parser."""
def test_dataset_from_ase():
"""Test ASE parser reading from file using ASE parser"""

# training set
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
Expand All @@ -34,6 +35,54 @@ def test_ase_parser():
assert np.allclose(configs[1].stress, np.array([9.9, 5.5, 1.1, 8.8, 7.7, 4.4]))


def test_dataset_add_from_ase():
"""Test adding configurations to dataset using ASE parser."""
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
data = Dataset()
data.add_from_ase(filename, energy_key="Energy", forces_key="force")
configs = data.get_configs()

assert len(configs) == 4
assert configs[0].species == ["Si" for _ in range(4)]
assert configs[0].coords.shape == (4, 3)
assert configs[0].energy == 123.45
assert np.allclose(configs[0].stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
assert configs[1].forces.shape == (8, 3)


def test_dataset_from_ase_atoms_list():
"""Test ASE parser reading from file using ASE atoms list."""
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
atoms = io.read(filename, index=":")
data = Dataset.from_ase(
ase_atoms_list=atoms, energy_key="Energy", forces_key="force"
)
configs = data.get_configs()

assert len(configs) == 4
assert configs[0].species == ["Si" for _ in range(4)]
assert configs[0].coords.shape == (4, 3)
assert configs[0].energy == 123.45
assert np.allclose(configs[0].stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
assert configs[1].forces.shape == (8, 3)


def test_dataset_add_from_ase_atoms_list():
"""Test adding configurations to dataset using ASE atoms list."""
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
atoms = io.read(filename, index=":")
data = Dataset()
data.add_from_ase(ase_atoms_list=atoms, energy_key="Energy", forces_key="force")
configs = data.get_configs()

assert len(configs) == 4
assert configs[0].species == ["Si" for _ in range(4)]
assert configs[0].coords.shape == (4, 3)
assert configs[0].energy == 123.45
assert np.allclose(configs[0].stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
assert configs[1].forces.shape == (8, 3)


# def test_colabfit_parser():
# TODO: figure minimal colabfit example to run tests on
# pass
62 changes: 62 additions & 0 deletions tests/dataset/test_configurations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from pathlib import Path

import numpy as np
import pytest
from ase import io

from kliff.dataset import Configuration
from kliff.dataset.dataset import ConfigurationError
from kliff.utils import stress_to_voigt


def test_configuration_from_ase():
"""Test initializing Configuration from ASE atoms object"""

filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
atoms = io.read(filename, index=":")
config = Configuration.from_ase_atoms(
atoms[0], energy_key="Energy", forces_key="force"
)

assert config.species == ["Si" for _ in range(4)]
assert config.coords.shape == (4, 3)
assert config.energy == 123.45
assert np.allclose(config.stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
assert config.forces.shape == (4, 3)
assert config.stress.shape == (6,)


def test_configuration_to_ase():
"""Test converting Configuration to ASE atoms object"""

filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
atoms = io.read(filename, index=":")
config = Configuration.from_ase_atoms(
atoms[0], energy_key="Energy", forces_key="force"
)

atoms = config.to_ase_atoms()
assert np.allclose(atoms.get_positions(), config.coords)
assert atoms.info["energy"] == config.energy
assert np.allclose(atoms.arrays["forces"], config.forces)
assert np.allclose(stress_to_voigt(atoms.info["stress"]), config.stress)
# TODO: As per Marcos' suggestion, we should use the dict
# method to get the ASE atoms properties. It solves the issue
# of associated calculator.


def test_configuration_from_file():
"""Test initializing Configuration from file"""

filename = (
Path(__file__).parents[1].joinpath("test_data/configs/Si_4/Si_T300_step_0.xyz")
)
config = Configuration.from_file(filename)

assert config.species == ["Si" for _ in range(64)]
assert config.coords.shape == (64, 3)
assert config.energy == 0.0
assert config.forces.shape == (64, 3)
# stress should raise exception
with pytest.raises(ConfigurationError):
stress = config.stress
25 changes: 25 additions & 0 deletions tests/misc/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import numpy as np
import pytest

from kliff.utils import stress_to_tensor, stress_to_voigt


def test_stress_conversion():
stress_tensor = np.random.rand(3, 3)
stress_tensor = stress_tensor + stress_tensor.T # make it symmetric
stress_voigt = stress_to_voigt(stress_tensor)
assert np.allclose(
stress_voigt,
np.array(
[
stress_tensor[0, 0],
stress_tensor[1, 1],
stress_tensor[2, 2],
stress_tensor[1, 2],
stress_tensor[0, 2],
stress_tensor[0, 1],
]
),
)
print(stress_tensor, stress_to_tensor(stress_voigt))
assert np.allclose(stress_tensor, stress_to_tensor(stress_voigt))
Binary file added tests/test_data/precomputed_numpy_arrays/cm_si.npy
Binary file not shown.
74 changes: 74 additions & 0 deletions tests/transformations/test_configuration_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import numpy as np
from ase.data import atomic_numbers

from kliff.dataset import Configuration
from kliff.transforms.configuration_transforms import ConfigurationTransform


class GlobalCoulombMatrix(ConfigurationTransform):
"""
Coulomb matrix representation of the configuration.
"""

def __init__(self, max_atoms: int = 5, copy_to_config: bool = False):
super().__init__(copy_to_config)
self.max_atoms = max_atoms

def forward(self, configuration: Configuration):
"""
Generate the Coulomb matrix for the configuration.
Args:
configuration: Instance of ~:class:`kliff.dataset.Configuration`. For which the
Coulomb matrix is to be generated.
Returns:
Coulomb matrix of the configuration.
"""
coords = configuration.coords
n_atoms = configuration.get_num_atoms()
coulomb_mat = np.zeros((self.max_atoms, self.max_atoms))
species = [atomic_numbers[elem] for elem in configuration.species]
for i in range(n_atoms):
for j in range(i + 1):
if i == j:
coulomb_mat[i, j] = 0.5 * (species[i] ** 2.4)
else:
r = np.linalg.norm(coords[i] - coords[j])
coulomb_mat[i, j] = species[i] * species[j] / r
coulomb_mat[j, i] = coulomb_mat[i, j]
return coulomb_mat

def backward(self, fingerprint, configuration):
"""
Inverse mapping of the transform. This is not implemented for any of the transforms,
but is there for future use.
"""
NotImplementedError(
"Any of the implemented transforms do not support inverse mapping.\n"
"For computing jacobian-vector product use `backward` function."
)

def collate_fn(self, config_list):
"""
Collate function for the Coulomb matrix transform.
"""
return [self.forward(config) for config in config_list]


def test_configuration_transform(test_data_dir):
config = Configuration.from_file(test_data_dir / "configs/Si.xyz")
transform = GlobalCoulombMatrix(max_atoms=8)
fingerprint = transform(config)
assert fingerprint.shape == (8, 8)
assert np.allclose(fingerprint, fingerprint.T)
assert np.allclose(
fingerprint, np.load(test_data_dir / "precomputed_numpy_arrays/cm_si.npy")
)
assert config.fingerprint is None
dataset = [config, config]
fingerprints = transform.collate_fn(dataset)
assert len(fingerprints) == 2
assert fingerprints[0].shape == (8, 8)
assert fingerprints[1].shape == (8, 8)
assert np.array(fingerprints).shape == (2, 8, 8)
Loading

0 comments on commit b4fa779

Please sign in to comment.