Skip to content

Commit 33c9212

Browse files
committed
Merge branch 'v1' of github.com:openkim/kliff into v1
2 parents e245cbf + b4fa779 commit 33c9212

File tree

13 files changed

+408
-8
lines changed

13 files changed

+408
-8
lines changed

kliff/dataset/dataset.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ def fingerprint(self, fingerprint):
430430
"""
431431
Set the fingerprint of the configuration.
432432
Args:
433-
fingerprint: Numpy array which is the fingerprint of the configuration.
433+
fingerprint: Object which is the fingerprint of the configuration.
434434
"""
435435
self._fingerprint = fingerprint
436436

kliff/transforms/configuration_transforms/configuration_transform.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def forward(self, configuration: Configuration) -> Any:
3232
def __call__(self, configuration: Configuration) -> Any:
3333
fingerprint = self.forward(configuration)
3434
if self.copy_to_config:
35-
configuration.fingerprint(fingerprint)
35+
configuration.fingerprint = fingerprint
3636
return fingerprint
3737

3838
def inverse(self, *args, **kargs) -> Configuration:

kliff/transforms/property_transforms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def _get_property_values(
9696
def _set_property_values(
9797
self,
9898
dataset: Union[List[Configuration], Dataset],
99-
property_values: Union[np.ndarray, List[float, int]],
99+
property_values: Union[np.ndarray, List[Union[float, int]]],
100100
):
101101
"""
102102
Set the property values of all the configurations in a dataset. This method

kliff/utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ def stress_to_voigt(input_stress: np.ndarray) -> list:
194194
stress[0] = input_stress[0, 0]
195195
stress[1] = input_stress[1, 1]
196196
stress[2] = input_stress[2, 2]
197-
stress[3] = input_stress[0, 1]
197+
stress[3] = input_stress[1, 2]
198198
stress[4] = input_stress[0, 2]
199-
stress[5] = input_stress[1, 2]
199+
stress[5] = input_stress[0, 1]
200200
else:
201201
raise ValueError("input_stress must be a 2D array")
202202

@@ -218,7 +218,7 @@ def stress_to_tensor(input_stress: list) -> np.ndarray:
218218
stress[1, 1] = input_stress[1]
219219
stress[2, 2] = input_stress[2]
220220
stress[1, 2] = stress[2, 1] = input_stress[3]
221-
stress[0, 2] = stress[0, 2] = input_stress[4]
221+
stress[0, 2] = stress[2, 0] = input_stress[4]
222222
stress[0, 1] = stress[1, 0] = input_stress[5]
223223

224224
return stress

setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ def get_readme():
124124
"torch",
125125
"numpy",
126126
"ase",
127+
"libdescriptor",
128+
"torch_geometric",
127129
],
128130
"docs": [
129131
"sphinx",

tests/dataset/test_ase_parser.py

+51-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import numpy as np
44
import pytest
5+
from ase import io
56

67
from kliff.dataset import Dataset
78

89

9-
def test_ase_parser():
10-
"""Test ASE parser."""
10+
def test_dataset_from_ase():
11+
"""Test ASE parser reading from file using ASE parser"""
1112

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

3637

38+
def test_dataset_add_from_ase():
39+
"""Test adding configurations to dataset using ASE parser."""
40+
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
41+
data = Dataset()
42+
data.add_from_ase(filename, energy_key="Energy", forces_key="force")
43+
configs = data.get_configs()
44+
45+
assert len(configs) == 4
46+
assert configs[0].species == ["Si" for _ in range(4)]
47+
assert configs[0].coords.shape == (4, 3)
48+
assert configs[0].energy == 123.45
49+
assert np.allclose(configs[0].stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
50+
assert configs[1].forces.shape == (8, 3)
51+
52+
53+
def test_dataset_from_ase_atoms_list():
54+
"""Test ASE parser reading from file using ASE atoms list."""
55+
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
56+
atoms = io.read(filename, index=":")
57+
data = Dataset.from_ase(
58+
ase_atoms_list=atoms, energy_key="Energy", forces_key="force"
59+
)
60+
configs = data.get_configs()
61+
62+
assert len(configs) == 4
63+
assert configs[0].species == ["Si" for _ in range(4)]
64+
assert configs[0].coords.shape == (4, 3)
65+
assert configs[0].energy == 123.45
66+
assert np.allclose(configs[0].stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
67+
assert configs[1].forces.shape == (8, 3)
68+
69+
70+
def test_dataset_add_from_ase_atoms_list():
71+
"""Test adding configurations to dataset using ASE atoms list."""
72+
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
73+
atoms = io.read(filename, index=":")
74+
data = Dataset()
75+
data.add_from_ase(ase_atoms_list=atoms, energy_key="Energy", forces_key="force")
76+
configs = data.get_configs()
77+
78+
assert len(configs) == 4
79+
assert configs[0].species == ["Si" for _ in range(4)]
80+
assert configs[0].coords.shape == (4, 3)
81+
assert configs[0].energy == 123.45
82+
assert np.allclose(configs[0].stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
83+
assert configs[1].forces.shape == (8, 3)
84+
85+
3786
# def test_colabfit_parser():
3887
# TODO: figure minimal colabfit example to run tests on
3988
# pass

tests/dataset/test_configurations.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from pathlib import Path
2+
3+
import numpy as np
4+
import pytest
5+
from ase import io
6+
7+
from kliff.dataset import Configuration
8+
from kliff.dataset.dataset import ConfigurationError
9+
from kliff.utils import stress_to_voigt
10+
11+
12+
def test_configuration_from_ase():
13+
"""Test initializing Configuration from ASE atoms object"""
14+
15+
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
16+
atoms = io.read(filename, index=":")
17+
config = Configuration.from_ase_atoms(
18+
atoms[0], energy_key="Energy", forces_key="force"
19+
)
20+
21+
assert config.species == ["Si" for _ in range(4)]
22+
assert config.coords.shape == (4, 3)
23+
assert config.energy == 123.45
24+
assert np.allclose(config.stress, np.array([1.1, 5.5, 9.9, 8.8, 7.7, 4.4]))
25+
assert config.forces.shape == (4, 3)
26+
assert config.stress.shape == (6,)
27+
28+
29+
def test_configuration_to_ase():
30+
"""Test converting Configuration to ASE atoms object"""
31+
32+
filename = Path(__file__).parents[1].joinpath("test_data/configs/Si_4.xyz")
33+
atoms = io.read(filename, index=":")
34+
config = Configuration.from_ase_atoms(
35+
atoms[0], energy_key="Energy", forces_key="force"
36+
)
37+
38+
atoms = config.to_ase_atoms()
39+
assert np.allclose(atoms.get_positions(), config.coords)
40+
assert atoms.info["energy"] == config.energy
41+
assert np.allclose(atoms.arrays["forces"], config.forces)
42+
assert np.allclose(stress_to_voigt(atoms.info["stress"]), config.stress)
43+
# TODO: As per Marcos' suggestion, we should use the dict
44+
# method to get the ASE atoms properties. It solves the issue
45+
# of associated calculator.
46+
47+
48+
def test_configuration_from_file():
49+
"""Test initializing Configuration from file"""
50+
51+
filename = (
52+
Path(__file__).parents[1].joinpath("test_data/configs/Si_4/Si_T300_step_0.xyz")
53+
)
54+
config = Configuration.from_file(filename)
55+
56+
assert config.species == ["Si" for _ in range(64)]
57+
assert config.coords.shape == (64, 3)
58+
assert config.energy == 0.0
59+
assert config.forces.shape == (64, 3)
60+
# stress should raise exception
61+
with pytest.raises(ConfigurationError):
62+
stress = config.stress

tests/misc/test_utils.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import numpy as np
2+
import pytest
3+
4+
from kliff.utils import stress_to_tensor, stress_to_voigt
5+
6+
7+
def test_stress_conversion():
8+
stress_tensor = np.random.rand(3, 3)
9+
stress_tensor = stress_tensor + stress_tensor.T # make it symmetric
10+
stress_voigt = stress_to_voigt(stress_tensor)
11+
assert np.allclose(
12+
stress_voigt,
13+
np.array(
14+
[
15+
stress_tensor[0, 0],
16+
stress_tensor[1, 1],
17+
stress_tensor[2, 2],
18+
stress_tensor[1, 2],
19+
stress_tensor[0, 2],
20+
stress_tensor[0, 1],
21+
]
22+
),
23+
)
24+
print(stress_tensor, stress_to_tensor(stress_voigt))
25+
assert np.allclose(stress_tensor, stress_to_tensor(stress_voigt))
640 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import numpy as np
2+
from ase.data import atomic_numbers
3+
4+
from kliff.dataset import Configuration
5+
from kliff.transforms.configuration_transforms import ConfigurationTransform
6+
7+
8+
class GlobalCoulombMatrix(ConfigurationTransform):
9+
"""
10+
Coulomb matrix representation of the configuration.
11+
"""
12+
13+
def __init__(self, max_atoms: int = 5, copy_to_config: bool = False):
14+
super().__init__(copy_to_config)
15+
self.max_atoms = max_atoms
16+
17+
def forward(self, configuration: Configuration):
18+
"""
19+
Generate the Coulomb matrix for the configuration.
20+
21+
Args:
22+
configuration: Instance of ~:class:`kliff.dataset.Configuration`. For which the
23+
Coulomb matrix is to be generated.
24+
25+
Returns:
26+
Coulomb matrix of the configuration.
27+
"""
28+
coords = configuration.coords
29+
n_atoms = configuration.get_num_atoms()
30+
coulomb_mat = np.zeros((self.max_atoms, self.max_atoms))
31+
species = [atomic_numbers[elem] for elem in configuration.species]
32+
for i in range(n_atoms):
33+
for j in range(i + 1):
34+
if i == j:
35+
coulomb_mat[i, j] = 0.5 * (species[i] ** 2.4)
36+
else:
37+
r = np.linalg.norm(coords[i] - coords[j])
38+
coulomb_mat[i, j] = species[i] * species[j] / r
39+
coulomb_mat[j, i] = coulomb_mat[i, j]
40+
return coulomb_mat
41+
42+
def backward(self, fingerprint, configuration):
43+
"""
44+
Inverse mapping of the transform. This is not implemented for any of the transforms,
45+
but is there for future use.
46+
"""
47+
NotImplementedError(
48+
"Any of the implemented transforms do not support inverse mapping.\n"
49+
"For computing jacobian-vector product use `backward` function."
50+
)
51+
52+
def collate_fn(self, config_list):
53+
"""
54+
Collate function for the Coulomb matrix transform.
55+
"""
56+
return [self.forward(config) for config in config_list]
57+
58+
59+
def test_configuration_transform(test_data_dir):
60+
config = Configuration.from_file(test_data_dir / "configs/Si.xyz")
61+
transform = GlobalCoulombMatrix(max_atoms=8)
62+
fingerprint = transform(config)
63+
assert fingerprint.shape == (8, 8)
64+
assert np.allclose(fingerprint, fingerprint.T)
65+
assert np.allclose(
66+
fingerprint, np.load(test_data_dir / "precomputed_numpy_arrays/cm_si.npy")
67+
)
68+
assert config.fingerprint is None
69+
dataset = [config, config]
70+
fingerprints = transform.collate_fn(dataset)
71+
assert len(fingerprints) == 2
72+
assert fingerprints[0].shape == (8, 8)
73+
assert fingerprints[1].shape == (8, 8)
74+
assert np.array(fingerprints).shape == (2, 8, 8)

0 commit comments

Comments
 (0)