Unverified Commit ec35c7da authored by Gao, Xiang's avatar Gao, Xiang Committed by GitHub
Browse files

Use PyTorch's TestCase to compare tensor (#533)

* Use PyTorch's TestCase to compare tensor

* save

* save

* save

* save

* save

* save

* save

* save

* save

* Update test_energies.py

* Update test_ase.py

* save

* flake8
parent 13f53da8
import unittest
import torch import torch
import torchani import torchani
import os import os
tolerance = 1e-5
class _TestAEVBase(torchani.testing.TestCase):
class _TestAEVBase(unittest.TestCase):
def setUp(self): def setUp(self):
path = os.path.dirname(os.path.realpath(__file__)) path = os.path.dirname(os.path.realpath(__file__))
...@@ -16,15 +13,11 @@ class _TestAEVBase(unittest.TestCase): ...@@ -16,15 +13,11 @@ class _TestAEVBase(unittest.TestCase):
self.radial_length = self.aev_computer.radial_length self.radial_length = self.aev_computer.radial_length
self.debug = False self.debug = False
def assertAEVEqual(self, expected_radial, expected_angular, aev, tolerance=tolerance): def assertAEVEqual(self, expected_radial, expected_angular, aev):
radial = aev[..., :self.radial_length] radial = aev[..., :self.radial_length]
angular = aev[..., self.radial_length:] angular = aev[..., self.radial_length:]
radial_diff = expected_radial - radial
if self.debug: if self.debug:
aid = 1 aid = 1
print(torch.stack([expected_radial[0, aid, :], radial[0, aid, :], radial_diff.abs()[0, aid, :]], dim=1)) print(torch.stack([expected_radial[0, aid, :], radial[0, aid, :]]))
radial_max_error = torch.max(torch.abs(radial_diff)).item() self.assertEqual(expected_radial, radial)
angular_diff = expected_angular - angular self.assertEqual(expected_angular, angular)
angular_max_error = torch.max(torch.abs(angular_diff)).item()
self.assertLess(radial_max_error, tolerance)
self.assertLess(angular_max_error, tolerance)
...@@ -16,7 +16,7 @@ const_file = os.path.join(path, '../torchani/resources/ani-1x_8x/rHCNO-5.2R_16-3 ...@@ -16,7 +16,7 @@ const_file = os.path.join(path, '../torchani/resources/ani-1x_8x/rHCNO-5.2R_16-3
N = 97 N = 97
class TestAEVConstructor(unittest.TestCase): class TestAEVConstructor(torchani.testing.TestCase):
# Test that checks that the friendly constructor # Test that checks that the friendly constructor
# reproduces the values from ANI1x with the correct parameters # reproduces the values from ANI1x with the correct parameters
def testCoverLinearly(self): def testCoverLinearly(self):
...@@ -35,13 +35,10 @@ class TestAEVConstructor(unittest.TestCase): ...@@ -35,13 +35,10 @@ class TestAEVConstructor(unittest.TestCase):
constants = aev_computer.constants() constants = aev_computer.constants()
constants_alt = aev_computer_alt.constants() constants_alt = aev_computer_alt.constants()
for c, ca in zip(constants, constants_alt): for c, ca in zip(constants, constants_alt):
if isinstance(c, torch.Tensor):
self.assertTrue(torch.isclose(c, ca).all())
else:
self.assertEqual(c, ca) self.assertEqual(c, ca)
class TestIsolated(unittest.TestCase): class TestIsolated(torchani.testing.TestCase):
# Tests that there is no error when atoms are separated # Tests that there is no error when atoms are separated
# a distance greater than the cutoff radius from all other atoms # a distance greater than the cutoff radius from all other atoms
# this can throw an IndexError for large distances or lone atoms # this can throw an IndexError for large distances or lone atoms
...@@ -153,7 +150,7 @@ class TestAEVJIT(TestAEV): ...@@ -153,7 +150,7 @@ class TestAEVJIT(TestAEV):
self.aev_computer = torch.jit.script(self.aev_computer) self.aev_computer = torch.jit.script(self.aev_computer)
class TestPBCSeeEachOther(unittest.TestCase): class TestPBCSeeEachOther(torchani.testing.TestCase):
def setUp(self): def setUp(self):
consts = torchani.neurochem.Constants(const_file) consts = torchani.neurochem.Constants(const_file)
self.aev_computer = torchani.AEVComputer(**consts).to(torch.double) self.aev_computer = torchani.AEVComputer(**consts).to(torch.double)
...@@ -175,7 +172,7 @@ class TestPBCSeeEachOther(unittest.TestCase): ...@@ -175,7 +172,7 @@ class TestPBCSeeEachOther(unittest.TestCase):
for _ in range(100): for _ in range(100):
translation = torch.randn(3, dtype=torch.double) translation = torch.randn(3, dtype=torch.double)
_, aev2 = self.aev_computer((species, coordinates + translation), cell=cell, pbc=pbc) _, aev2 = self.aev_computer((species, coordinates + translation), cell=cell, pbc=pbc)
self.assertTrue(torch.allclose(aev, aev2)) self.assertEqual(aev, aev2)
def testPBCConnersSeeEachOther(self): def testPBCConnersSeeEachOther(self):
species = torch.tensor([[0, 0]]) species = torch.tensor([[0, 0]])
...@@ -257,7 +254,7 @@ class TestPBCSeeEachOther(unittest.TestCase): ...@@ -257,7 +254,7 @@ class TestPBCSeeEachOther(unittest.TestCase):
self.assertEqual(atom_index2.tolist(), [1]) self.assertEqual(atom_index2.tolist(), [1])
class TestAEVOnBoundary(unittest.TestCase): class TestAEVOnBoundary(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.eps = 1e-9 self.eps = 1e-9
...@@ -280,13 +277,13 @@ class TestAEVOnBoundary(unittest.TestCase): ...@@ -280,13 +277,13 @@ class TestAEVOnBoundary(unittest.TestCase):
def assertInCell(self, coordinates): def assertInCell(self, coordinates):
coordinates_cell = coordinates @ self.inv_cell coordinates_cell = coordinates @ self.inv_cell
self.assertTrue(torch.allclose(coordinates, coordinates_cell @ self.cell)) self.assertEqual(coordinates, coordinates_cell @ self.cell)
in_cell = (coordinates_cell >= -self.eps) & (coordinates_cell <= 1 + self.eps) in_cell = (coordinates_cell >= -self.eps) & (coordinates_cell <= 1 + self.eps)
self.assertTrue(in_cell.all()) self.assertTrue(in_cell.all())
def assertNotInCell(self, coordinates): def assertNotInCell(self, coordinates):
coordinates_cell = coordinates @ self.inv_cell coordinates_cell = coordinates @ self.inv_cell
self.assertTrue(torch.allclose(coordinates, coordinates_cell @ self.cell)) self.assertEqual(coordinates, coordinates_cell @ self.cell)
in_cell = (coordinates_cell >= -self.eps) & (coordinates_cell <= 1 + self.eps) in_cell = (coordinates_cell >= -self.eps) & (coordinates_cell <= 1 + self.eps)
self.assertFalse(in_cell.all()) self.assertFalse(in_cell.all())
...@@ -300,10 +297,10 @@ class TestAEVOnBoundary(unittest.TestCase): ...@@ -300,10 +297,10 @@ class TestAEVOnBoundary(unittest.TestCase):
self.assertInCell(coordinates) self.assertInCell(coordinates)
_, aev = self.aev_computer((self.species, coordinates), cell=self.cell, pbc=self.pbc) _, aev = self.aev_computer((self.species, coordinates), cell=self.cell, pbc=self.pbc)
self.assertGreater(aev.abs().max().item(), 0) self.assertGreater(aev.abs().max().item(), 0)
self.assertTrue(torch.allclose(aev, self.aev)) self.assertEqual(aev, self.aev)
class TestAEVOnBenzenePBC(unittest.TestCase): class TestAEVOnBenzenePBC(torchani.testing.TestCase):
def setUp(self): def setUp(self):
consts = torchani.neurochem.Constants(const_file) consts = torchani.neurochem.Constants(const_file)
...@@ -319,7 +316,6 @@ class TestAEVOnBenzenePBC(unittest.TestCase): ...@@ -319,7 +316,6 @@ class TestAEVOnBenzenePBC(unittest.TestCase):
self.natoms = self.aev.shape[1] self.natoms = self.aev.shape[1]
def testRepeat(self): def testRepeat(self):
tolerance = 5e-6
c1, c2, c3 = self.cell c1, c2, c3 = self.cell
species2 = self.species.repeat(1, 4) species2 = self.species.repeat(1, 4)
coordinates2 = torch.cat([ coordinates2 = torch.cat([
...@@ -332,7 +328,7 @@ class TestAEVOnBenzenePBC(unittest.TestCase): ...@@ -332,7 +328,7 @@ class TestAEVOnBenzenePBC(unittest.TestCase):
_, aev2 = self.aev_computer((species2, coordinates2), cell=cell2, pbc=self.pbc) _, aev2 = self.aev_computer((species2, coordinates2), cell=cell2, pbc=self.pbc)
for i in range(3): for i in range(3):
aev3 = aev2[:, i * self.natoms: (i + 1) * self.natoms, :] aev3 = aev2[:, i * self.natoms: (i + 1) * self.natoms, :]
self.assertTrue(torch.allclose(self.aev, aev3, atol=tolerance)) self.assertEqual(self.aev, aev3)
def testManualMirror(self): def testManualMirror(self):
c1, c2, c3 = self.cell c1, c2, c3 = self.cell
...@@ -343,7 +339,7 @@ class TestAEVOnBenzenePBC(unittest.TestCase): ...@@ -343,7 +339,7 @@ class TestAEVOnBenzenePBC(unittest.TestCase):
], dim=1) ], dim=1)
_, aev2 = self.aev_computer((species2, coordinates2)) _, aev2 = self.aev_computer((species2, coordinates2))
aev2 = aev2[:, :self.natoms, :] aev2 = aev2[:, :self.natoms, :]
self.assertTrue(torch.allclose(self.aev, aev2)) self.assertEqual(self.aev, aev2)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -24,7 +24,7 @@ class TestAEVBenzeneMD(_TestAEVBase): ...@@ -24,7 +24,7 @@ class TestAEVBenzeneMD(_TestAEVBase):
pbc = torch.from_numpy(pbc) pbc = torch.from_numpy(pbc)
coordinates = torchani.utils.map2central(cell, coordinates, pbc) coordinates = torchani.utils.map2central(cell, coordinates, pbc)
_, aev = self.aev_computer((species, coordinates), cell=cell, pbc=pbc) _, aev = self.aev_computer((species, coordinates), cell=cell, pbc=pbc)
self.assertAEVEqual(expected_radial, expected_angular, aev, 5e-5) self.assertAEVEqual(expected_radial, expected_angular, aev)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -10,7 +10,6 @@ path = os.path.dirname(os.path.realpath(__file__)) ...@@ -10,7 +10,6 @@ path = os.path.dirname(os.path.realpath(__file__))
class TestAEVTripeptideMD(_TestAEVBase): class TestAEVTripeptideMD(_TestAEVBase):
def testTripeptideMD(self): def testTripeptideMD(self):
tol = 5e-6
for i in range(100): for i in range(100):
datafile = os.path.join(path, 'test_data/tripeptide-md/{}.dat'.format(i)) datafile = os.path.join(path, 'test_data/tripeptide-md/{}.dat'.format(i))
with open(datafile, 'rb') as f: with open(datafile, 'rb') as f:
...@@ -21,7 +20,7 @@ class TestAEVTripeptideMD(_TestAEVBase): ...@@ -21,7 +20,7 @@ class TestAEVTripeptideMD(_TestAEVBase):
expected_radial = torch.from_numpy(expected_radial).float().unsqueeze(0) expected_radial = torch.from_numpy(expected_radial).float().unsqueeze(0)
expected_angular = torch.from_numpy(expected_angular).float().unsqueeze(0) expected_angular = torch.from_numpy(expected_angular).float().unsqueeze(0)
_, aev = self.aev_computer((species, coordinates)) _, aev = self.aev_computer((species, coordinates))
self.assertAEVEqual(expected_radial, expected_angular, aev, tol) self.assertAEVEqual(expected_radial, expected_angular, aev)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -4,7 +4,7 @@ import math ...@@ -4,7 +4,7 @@ import math
import unittest import unittest
class TestALAtomic(unittest.TestCase): class TestALAtomic(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.device = torch.device( self.device = torch.device(
'cuda' if torch.cuda.is_available() else 'cpu') 'cuda' if torch.cuda.is_available() else 'cpu')
...@@ -26,7 +26,7 @@ class TestALAtomic(unittest.TestCase): ...@@ -26,7 +26,7 @@ class TestALAtomic(unittest.TestCase):
def testAverageAtomicEnergies(self): def testAverageAtomicEnergies(self):
_, energies = self.model.atomic_energies( _, energies = self.model.atomic_energies(
(self.species, self.coordinates)) (self.species, self.coordinates))
self.assertTrue(energies.shape == self.coordinates.shape[:-1]) self.assertEqual(energies.shape, self.coordinates.shape[:-1])
# energies of all hydrogens should be equal # energies of all hydrogens should be equal
self.assertTrue((torch.isclose( self.assertTrue((torch.isclose(
energies[:, :-1], energies[:, :-1],
...@@ -57,10 +57,10 @@ class TestALQBC(TestALAtomic): ...@@ -57,10 +57,10 @@ class TestALQBC(TestALAtomic):
# correctness of shape # correctness of shape
torch.set_printoptions(precision=15) torch.set_printoptions(precision=15)
self.assertTrue(energies.shape[-1] == self.coordinates.shape[0]) self.assertEqual(energies.shape[-1], self.coordinates.shape[0])
self.assertTrue(energies.shape[0] == len(self.model.neural_networks)) self.assertEqual(energies.shape[0], len(self.model.neural_networks))
self.assertTrue( self.assertEqual(
energies[0] == self.first_model((self.species, energies[0], self.first_model((self.species,
self.coordinates)).energies) self.coordinates)).energies)
self.assertTrue( self.assertTrue(
torch.isclose( torch.isclose(
...@@ -96,7 +96,7 @@ class TestALQBC(TestALAtomic): ...@@ -96,7 +96,7 @@ class TestALQBC(TestALAtomic):
_, _, qbc = self.model.energies_qbcs((species, coordinates)) _, _, qbc = self.model.energies_qbcs((species, coordinates))
std[0] = std[0] / math.sqrt(5) std[0] = std[0] / math.sqrt(5)
std[1] = std[1] / math.sqrt(4) std[1] = std[1] / math.sqrt(4)
self.assertTrue(torch.isclose(std, qbc).all()) self.assertEqual(std, qbc)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -21,7 +21,7 @@ def get_numeric_force(atoms, eps): ...@@ -21,7 +21,7 @@ def get_numeric_force(atoms, eps):
return fn return fn
class TestASE(unittest.TestCase): class TestASE(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.model = torchani.models.ANI1x(model_index=0).double() self.model = torchani.models.ANI1x(model_index=0).double()
...@@ -30,24 +30,19 @@ class TestASE(unittest.TestCase): ...@@ -30,24 +30,19 @@ class TestASE(unittest.TestCase):
# Run a Langevin thermostat dynamic for 100 steps and after the dynamic # Run a Langevin thermostat dynamic for 100 steps and after the dynamic
# check once that the numerical and analytical force agree to a given # check once that the numerical and analytical force agree to a given
# relative tolerance # relative tolerance
relative_tolerance = 0.1
atoms = Diamond(symbol="C", pbc=True) atoms = Diamond(symbol="C", pbc=True)
calculator = self.model.ase() calculator = self.model.ase()
atoms.set_calculator(calculator) atoms.set_calculator(calculator)
dyn = Langevin(atoms, 5 * units.fs, 30000000 * units.kB, 0.002) dyn = Langevin(atoms, 5 * units.fs, 30000000 * units.kB, 0.002)
dyn.run(100) dyn.run(100)
f = torch.from_numpy(atoms.get_forces()) f = atoms.get_forces()
fn = get_numeric_force(atoms, 0.001) fn = get_numeric_force(atoms, 0.001)
df = (f - fn).abs().max() self.assertEqual(f, fn, rtol=0.1, atol=0)
avgf = f.abs().mean()
if avgf > 0:
self.assertLess(df / avgf, relative_tolerance)
def testWithNumericalStressWithPBCEnabled(self): def testWithNumericalStressWithPBCEnabled(self):
# Run NPT dynamics for some steps and periodically check that the # Run NPT dynamics for some steps and periodically check that the
# numerical and analytical stresses agree up to a given # numerical and analytical stresses agree up to a given
# absolute difference # absolute difference
tolerance = 1e-5
filename = os.path.join(path, '../tools/generate-unit-test-expect/others/Benzene.json') filename = os.path.join(path, '../tools/generate-unit-test-expect/others/Benzene.json')
benzene = read(filename) benzene = read(filename)
# set velocities to a very small value to avoid division by zero # set velocities to a very small value to avoid division by zero
...@@ -66,10 +61,9 @@ class TestASE(unittest.TestCase): ...@@ -66,10 +61,9 @@ class TestASE(unittest.TestCase):
def test_stress(): def test_stress():
stress = benzene.get_stress() stress = benzene.get_stress()
numerical_stress = calculator.calculate_numerical_stress(benzene) numerical_stress = calculator.calculate_numerical_stress(benzene)
diff = torch.from_numpy(stress - numerical_stress).abs().max().item() self.assertEqual(stress, numerical_stress)
self.assertLess(diff, tolerance) dyn.attach(test_stress, interval=30)
dyn.attach(test_stress, interval=5) dyn.run(120)
dyn.run(20)
class TestASEWithPTI(unittest.TestCase): class TestASEWithPTI(unittest.TestCase):
......
...@@ -7,7 +7,7 @@ skipIfNoGPU = unittest.skipIf(not torch.cuda.is_available(), ...@@ -7,7 +7,7 @@ skipIfNoGPU = unittest.skipIf(not torch.cuda.is_available(),
@unittest.skipIf(not torchani.has_cuaev, "only valid when cuaev is installed") @unittest.skipIf(not torchani.has_cuaev, "only valid when cuaev is installed")
class TestCUAEV(unittest.TestCase): class TestCUAEV(torchani.testing.TestCase):
def testJIT(self): def testJIT(self):
def f(coordinates, species, Rcr: float, Rca: float, EtaR, ShfR, EtaA, Zeta, ShfA, ShfZ, num_species: int): def f(coordinates, species, Rcr: float, Rca: float, EtaR, ShfR, EtaA, Zeta, ShfA, ShfZ, num_species: int):
......
...@@ -9,7 +9,7 @@ batch_size = 256 ...@@ -9,7 +9,7 @@ batch_size = 256
ani1x_sae_dict = {'H': -0.60095298, 'C': -38.08316124, 'N': -54.7077577, 'O': -75.19446356} ani1x_sae_dict = {'H': -0.60095298, 'C': -38.08316124, 'N': -54.7077577, 'O': -75.19446356}
class TestData(unittest.TestCase): class TestData(torchani.testing.TestCase):
def testTensorShape(self): def testTensorShape(self):
ds = torchani.data.load(dataset_path).subtract_self_energies(ani1x_sae_dict).species_to_indices().shuffle().collate(batch_size).cache() ds = torchani.data.load(dataset_path).subtract_self_energies(ani1x_sae_dict).species_to_indices().shuffle().collate(batch_size).cache()
...@@ -35,7 +35,7 @@ class TestData(unittest.TestCase): ...@@ -35,7 +35,7 @@ class TestData(unittest.TestCase):
def testReEnter(self): def testReEnter(self):
# make sure that a dataset can be iterated multiple times # make sure that a dataset can be iterated multiple times
ds = torchani.data.load(dataset_path) ds = torchani.data.load(dataset_path)
for d in ds: for _ in ds:
pass pass
entered = False entered = False
for d in ds: for d in ds:
...@@ -107,22 +107,19 @@ class TestData(unittest.TestCase): ...@@ -107,22 +107,19 @@ class TestData(unittest.TestCase):
len(ds) len(ds)
def testSAE(self): def testSAE(self):
tolerance = 1e-5
shifter = torchani.EnergyShifter(None) shifter = torchani.EnergyShifter(None)
torchani.data.load(dataset_path).subtract_self_energies(shifter) torchani.data.load(dataset_path).subtract_self_energies(shifter)
true_self_energies = torch.tensor([-19.354171758844188, true_self_energies = torch.tensor([-19.354171758844188,
-19.354171758844046, -19.354171758844046,
-54.712238523648587, -54.712238523648587,
-75.162829556770987], dtype=torch.float64) -75.162829556770987], dtype=torch.float64)
diff = torch.abs(true_self_energies - shifter.self_energies) self.assertEqual(true_self_energies, shifter.self_energies)
for e in diff:
self.assertLess(e, tolerance)
def testDataloader(self): def testDataloader(self):
shifter = torchani.EnergyShifter(None) shifter = torchani.EnergyShifter(None)
dataset = list(torchani.data.load(dataset_path).subtract_self_energies(shifter).species_to_indices().shuffle()) dataset = list(torchani.data.load(dataset_path).subtract_self_energies(shifter).species_to_indices().shuffle())
loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, collate_fn=torchani.data.collate_fn, num_workers=64) loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, collate_fn=torchani.data.collate_fn, num_workers=64)
for i in loader: for _ in loader:
pass pass
......
...@@ -9,12 +9,11 @@ path = os.path.dirname(os.path.realpath(__file__)) ...@@ -9,12 +9,11 @@ path = os.path.dirname(os.path.realpath(__file__))
N = 97 N = 97
class TestEnergies(unittest.TestCase): class TestEnergies(torchani.testing.TestCase):
# tests the predicions for a torchani.nn.Sequential(AEVComputer(), # tests the predicions for a torchani.nn.Sequential(AEVComputer(),
# ANIModel(), EnergyShifter()) against precomputed values # ANIModel(), EnergyShifter()) against precomputed values
def setUp(self): def setUp(self):
self.tolerance = 5e-5
model = torchani.models.ANI1x(model_index=0) model = torchani.models.ANI1x(model_index=0)
self.aev_computer = model.aev_computer self.aev_computer = model.aev_computer
self.nnp = model.neural_networks self.nnp = model.neural_networks
...@@ -30,8 +29,7 @@ class TestEnergies(unittest.TestCase): ...@@ -30,8 +29,7 @@ class TestEnergies(unittest.TestCase):
species = torch.from_numpy(species) species = torch.from_numpy(species)
energies = torch.from_numpy(energies).to(torch.float) energies = torch.from_numpy(energies).to(torch.float)
energies_ = self.model((species, coordinates)).energies energies_ = self.model((species, coordinates)).energies
max_diff = (energies - energies_).abs().max().item() self.assertEqual(energies, energies_, exact_dtype=False)
self.assertLess(max_diff, self.tolerance)
def testPadding(self): def testPadding(self):
species_coordinates = [] species_coordinates = []
...@@ -50,8 +48,7 @@ class TestEnergies(unittest.TestCase): ...@@ -50,8 +48,7 @@ class TestEnergies(unittest.TestCase):
species_coordinates) species_coordinates)
energies = torch.cat(energies) energies = torch.cat(energies)
energies_ = self.model((species_coordinates['species'], species_coordinates['coordinates'])).energies energies_ = self.model((species_coordinates['species'], species_coordinates['coordinates'])).energies
max_diff = (energies - energies_).abs().max().item() self.assertEqual(energies, energies_, exact_dtype=False)
self.assertLess(max_diff, self.tolerance)
class TestEnergiesEnergyShifterJIT(TestEnergies): class TestEnergiesEnergyShifterJIT(TestEnergies):
......
...@@ -8,10 +8,10 @@ path = os.path.dirname(os.path.realpath(__file__)) ...@@ -8,10 +8,10 @@ path = os.path.dirname(os.path.realpath(__file__))
N = 10 N = 10
class TestEnsemble(unittest.TestCase): class TestEnsemble(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.tol = 1e-5 self.conformations = 20
ani1x = torchani.models.ANI1x() ani1x = torchani.models.ANI1x()
self.aev_computer = ani1x.aev_computer self.aev_computer = ani1x.aev_computer
self.model_iterator = ani1x.neural_networks self.model_iterator = ani1x.neural_networks
...@@ -25,10 +25,8 @@ class TestEnsemble(unittest.TestCase): ...@@ -25,10 +25,8 @@ class TestEnsemble(unittest.TestCase):
energy2 = [m((species, coordinates))[1] for m in model_list] energy2 = [m((species, coordinates))[1] for m in model_list]
energy2 = sum(energy2) / len(model_list) energy2 = sum(energy2) / len(model_list)
force2 = torch.autograd.grad(energy2.sum(), coordinates)[0] force2 = torch.autograd.grad(energy2.sum(), coordinates)[0]
energy_diff = (energy1 - energy2).abs().max().item() self.assertEqual(energy1, energy2)
force_diff = (force1 - force2).abs().max().item() self.assertEqual(force1, force2)
self.assertLess(energy_diff, self.tol)
self.assertLess(force_diff, self.tol)
def testGDB(self): def testGDB(self):
for i in range(N): for i in range(N):
......
...@@ -8,10 +8,9 @@ path = os.path.dirname(os.path.realpath(__file__)) ...@@ -8,10 +8,9 @@ path = os.path.dirname(os.path.realpath(__file__))
N = 97 N = 97
class TestForce(unittest.TestCase): class TestForce(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.tolerance = 1e-5
model = torchani.models.ANI1x(model_index=0) model = torchani.models.ANI1x(model_index=0)
self.aev_computer = model.aev_computer self.aev_computer = model.aev_computer
self.nnp = model.neural_networks self.nnp = model.neural_networks
...@@ -29,8 +28,7 @@ class TestForce(unittest.TestCase): ...@@ -29,8 +28,7 @@ class TestForce(unittest.TestCase):
_, energies = self.model((species, coordinates)) _, energies = self.model((species, coordinates))
derivative = torch.autograd.grad(energies.sum(), derivative = torch.autograd.grad(energies.sum(),
coordinates)[0] coordinates)[0]
max_diff = (forces + derivative).abs().max().item() self.assertEqual(forces, -derivative)
self.assertLess(max_diff, self.tolerance)
def testPadding(self): def testPadding(self):
species_coordinates = [] species_coordinates = []
...@@ -52,8 +50,7 @@ class TestForce(unittest.TestCase): ...@@ -52,8 +50,7 @@ class TestForce(unittest.TestCase):
for coordinates, forces in coordinates_forces: for coordinates, forces in coordinates_forces:
derivative = torch.autograd.grad(energies, coordinates, derivative = torch.autograd.grad(energies, coordinates,
retain_graph=True)[0] retain_graph=True)[0]
max_diff = (forces + derivative).abs().max().item() self.assertEqual(forces, -derivative)
self.assertLess(max_diff, self.tolerance)
class TestForceJIT(TestForce): class TestForceJIT(TestForce):
......
...@@ -7,7 +7,7 @@ import pickle ...@@ -7,7 +7,7 @@ import pickle
path = os.path.dirname(os.path.realpath(__file__)) path = os.path.dirname(os.path.realpath(__file__))
class TestGrad(unittest.TestCase): class TestGrad(torchani.testing.TestCase):
# torch.autograd.gradcheck and torch.autograd.gradgradcheck verify that # torch.autograd.gradcheck and torch.autograd.gradgradcheck verify that
# the numerical and analytical gradient and hessian of a function # the numerical and analytical gradient and hessian of a function
# matches to within a given tolerance. # matches to within a given tolerance.
......
...@@ -8,7 +8,7 @@ path = os.path.dirname(os.path.realpath(__file__)) ...@@ -8,7 +8,7 @@ path = os.path.dirname(os.path.realpath(__file__))
dspath = os.path.join(path, '../dataset/ani-1x/sample.h5') dspath = os.path.join(path, '../dataset/ani-1x/sample.h5')
class TestBuiltinModelsJIT(unittest.TestCase): class TestBuiltinModelsJIT(torchani.testing.TestCase):
# Tests if JIT compiled models have the same output energies # Tests if JIT compiled models have the same output energies
# as eager (non JIT) models # as eager (non JIT) models
...@@ -22,7 +22,7 @@ class TestBuiltinModelsJIT(unittest.TestCase): ...@@ -22,7 +22,7 @@ class TestBuiltinModelsJIT(unittest.TestCase):
input_ = (properties['species'], properties['coordinates'].float()) input_ = (properties['species'], properties['coordinates'].float())
_, e = model(input_) _, e = model(input_)
_, e2 = torch.jit.script(model)(input_) _, e2 = torch.jit.script(model)(input_)
self.assertTrue(torch.allclose(e, e2)) self.assertEqual(e, e2)
def _test_ensemble(self, ensemble): def _test_ensemble(self, ensemble):
self._test_model(ensemble) self._test_model(ensemble)
......
...@@ -9,7 +9,7 @@ iptpath = os.path.join(path, 'test_data/inputtrain.ipt') ...@@ -9,7 +9,7 @@ iptpath = os.path.join(path, 'test_data/inputtrain.ipt')
dspath = os.path.join(path, '../dataset/ani1-up_to_gdb4/ani_gdb_s01.h5') dspath = os.path.join(path, '../dataset/ani1-up_to_gdb4/ani_gdb_s01.h5')
class TestNeuroChem(unittest.TestCase): class TestNeuroChem(torchani.testing.TestCase):
def testNeuroChemTrainer(self): def testNeuroChemTrainer(self):
d = torch.device('cuda' if torch.cuda.is_available() else 'cpu') d = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
......
...@@ -6,7 +6,7 @@ import torchani ...@@ -6,7 +6,7 @@ import torchani
b = torchani.utils.broadcast_first_dim b = torchani.utils.broadcast_first_dim
class TestPaddings(unittest.TestCase): class TestPaddings(torchani.testing.TestCase):
def testVectorSpecies(self): def testVectorSpecies(self):
species1 = torch.tensor([[0, 2, 3, 1]]) species1 = torch.tensor([[0, 2, 3, 1]])
...@@ -28,7 +28,7 @@ class TestPaddings(unittest.TestCase): ...@@ -28,7 +28,7 @@ class TestPaddings(unittest.TestCase):
[3, 2, 0, 1, 0], [3, 2, 0, 1, 0],
[3, 2, 0, 1, 0], [3, 2, 0, 1, 0],
]) ])
self.assertEqual((atomic_properties['species'] - expected_species).abs().max().item(), 0) self.assertEqual(atomic_properties['species'], expected_species)
self.assertEqual(atomic_properties['coordinates'].abs().max().item(), 0) self.assertEqual(atomic_properties['coordinates'].abs().max().item(), 0)
def testTensorShape1NSpecies(self): def testTensorShape1NSpecies(self):
...@@ -51,7 +51,7 @@ class TestPaddings(unittest.TestCase): ...@@ -51,7 +51,7 @@ class TestPaddings(unittest.TestCase):
[3, 2, 0, 1, 0], [3, 2, 0, 1, 0],
[3, 2, 0, 1, 0], [3, 2, 0, 1, 0],
]) ])
self.assertEqual((atomic_properties['species'] - expected_species).abs().max().item(), 0) self.assertEqual(atomic_properties['species'], expected_species)
self.assertEqual(atomic_properties['coordinates'].abs().max().item(), 0) self.assertEqual(atomic_properties['coordinates'].abs().max().item(), 0)
def testTensorSpecies(self): def testTensorSpecies(self):
...@@ -80,20 +80,17 @@ class TestPaddings(unittest.TestCase): ...@@ -80,20 +80,17 @@ class TestPaddings(unittest.TestCase):
[3, 2, 0, 1, 0], [3, 2, 0, 1, 0],
[3, 2, 0, 1, 0], [3, 2, 0, 1, 0],
]) ])
self.assertEqual((atomic_properties['species'] - expected_species).abs().max().item(), 0) self.assertEqual(atomic_properties['species'], expected_species)
self.assertEqual(atomic_properties['coordinates'].abs().max().item(), 0) self.assertEqual(atomic_properties['coordinates'].abs().max().item(), 0)
def testPresentSpecies(self): def testPresentSpecies(self):
species = torch.tensor([0, 1, 1, 0, 3, 7, -1, -1]) species = torch.tensor([0, 1, 1, 0, 3, 7, -1, -1])
present_species = torchani.utils.present_species(species) present_species = torchani.utils.present_species(species)
expected = torch.tensor([0, 1, 3, 7]) expected = torch.tensor([0, 1, 3, 7])
self.assertEqual((expected - present_species).abs().max().item(), 0) self.assertEqual(expected, present_species)
class TestStripRedundantPadding(unittest.TestCase): class TestStripRedundantPadding(torchani.testing.TestCase):
def _assertTensorEqual(self, t1, t2):
self.assertEqual((t1 - t2).abs().max().item(), 0)
def testStripRestore(self): def testStripRestore(self):
species1 = torch.randint(4, (5, 4), dtype=torch.long) species1 = torch.randint(4, (5, 4), dtype=torch.long)
...@@ -119,14 +116,14 @@ class TestStripRedundantPadding(unittest.TestCase): ...@@ -119,14 +116,14 @@ class TestStripRedundantPadding(unittest.TestCase):
b({'species': species123[:5, ...], 'coordinates': coordinates123[:5, ...]})) b({'species': species123[:5, ...], 'coordinates': coordinates123[:5, ...]}))
species1_ = species_coordinates1_['species'] species1_ = species_coordinates1_['species']
coordinates1_ = species_coordinates1_['coordinates'] coordinates1_ = species_coordinates1_['coordinates']
self._assertTensorEqual(species1_, species1) self.assertEqual(species1_, species1)
self._assertTensorEqual(coordinates1_, coordinates1) self.assertEqual(coordinates1_, coordinates1)
species_coordinates12_ = torchani.utils.strip_redundant_padding( species_coordinates12_ = torchani.utils.strip_redundant_padding(
b({'species': species123[:7, ...], 'coordinates': coordinates123[:7, ...]})) b({'species': species123[:7, ...], 'coordinates': coordinates123[:7, ...]}))
species12_ = species_coordinates12_['species'] species12_ = species_coordinates12_['species']
coordinates12_ = species_coordinates12_['coordinates'] coordinates12_ = species_coordinates12_['coordinates']
self._assertTensorEqual(species12_, species12) self.assertEqual(species12_, species12)
self._assertTensorEqual(coordinates12_, coordinates12) self.assertEqual(coordinates12_, coordinates12)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -3,7 +3,7 @@ import torch ...@@ -3,7 +3,7 @@ import torch
import torchani import torchani
class TestSpeciesConverter(unittest.TestCase): class TestSpeciesConverter(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.c = torchani.SpeciesConverter(['H', 'C', 'N', 'O']) self.c = torchani.SpeciesConverter(['H', 'C', 'N', 'O'])
...@@ -19,7 +19,7 @@ class TestSpeciesConverter(unittest.TestCase): ...@@ -19,7 +19,7 @@ class TestSpeciesConverter(unittest.TestCase):
], dtype=torch.long) ], dtype=torch.long)
dummy_coordinates = torch.empty(2, 5, 3) dummy_coordinates = torch.empty(2, 5, 3)
output = self.c((input_, dummy_coordinates)).species output = self.c((input_, dummy_coordinates)).species
self.assertTrue(torch.allclose(output, expect)) self.assertEqual(output, expect)
class TestSpeciesConverterJIT(TestSpeciesConverter): class TestSpeciesConverterJIT(TestSpeciesConverter):
...@@ -29,7 +29,7 @@ class TestSpeciesConverterJIT(TestSpeciesConverter): ...@@ -29,7 +29,7 @@ class TestSpeciesConverterJIT(TestSpeciesConverter):
self.c = torch.jit.script(self.c) self.c = torch.jit.script(self.c)
class TestBuiltinEnsemblePeriodicTableIndex(unittest.TestCase): class TestBuiltinEnsemblePeriodicTableIndex(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.model1 = torchani.models.ANI1x() self.model1 = torchani.models.ANI1x()
...@@ -48,16 +48,16 @@ class TestBuiltinEnsemblePeriodicTableIndex(unittest.TestCase): ...@@ -48,16 +48,16 @@ class TestBuiltinEnsemblePeriodicTableIndex(unittest.TestCase):
energy2 = self.model2((self.species2, self.coordinates)).energies energy2 = self.model2((self.species2, self.coordinates)).energies
derivative1 = torch.autograd.grad(energy1.sum(), self.coordinates)[0] derivative1 = torch.autograd.grad(energy1.sum(), self.coordinates)[0]
derivative2 = torch.autograd.grad(energy2.sum(), self.coordinates)[0] derivative2 = torch.autograd.grad(energy2.sum(), self.coordinates)[0]
self.assertTrue(torch.allclose(energy1, energy2)) self.assertEqual(energy1, energy2)
self.assertTrue(torch.allclose(derivative1, derivative2)) self.assertEqual(derivative1, derivative2)
def testCH4Single(self): def testCH4Single(self):
energy1 = self.model1[0]((self.species1, self.coordinates)).energies energy1 = self.model1[0]((self.species1, self.coordinates)).energies
energy2 = self.model2[0]((self.species2, self.coordinates)).energies energy2 = self.model2[0]((self.species2, self.coordinates)).energies
derivative1 = torch.autograd.grad(energy1.sum(), self.coordinates)[0] derivative1 = torch.autograd.grad(energy1.sum(), self.coordinates)[0]
derivative2 = torch.autograd.grad(energy2.sum(), self.coordinates)[0] derivative2 = torch.autograd.grad(energy2.sum(), self.coordinates)[0]
self.assertTrue(torch.allclose(energy1, energy2)) self.assertEqual(energy1, energy2)
self.assertTrue(torch.allclose(derivative1, derivative2)) self.assertEqual(derivative1, derivative2)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -11,7 +11,7 @@ from ase import Atoms ...@@ -11,7 +11,7 @@ from ase import Atoms
path = os.path.dirname(os.path.realpath(__file__)) path = os.path.dirname(os.path.realpath(__file__))
class TestStructureOptimization(unittest.TestCase): class TestStructureOptimization(torchani.testing.TestCase):
def setUp(self): def setUp(self):
self.tolerance = 1e-6 self.tolerance = 1e-6
...@@ -31,10 +31,7 @@ class TestStructureOptimization(unittest.TestCase): ...@@ -31,10 +31,7 @@ class TestStructureOptimization(unittest.TestCase):
opt = BFGS(atoms) opt = BFGS(atoms)
opt.run() opt.run()
coordinates = atoms.get_positions() coordinates = atoms.get_positions()
coordinates = torch.from_numpy(coordinates) self.assertEqual(old_coordinates, coordinates)
distances = (old_coordinates - coordinates).norm(dim=1)
rmse = distances.mean()
self.assertLess(rmse, self.tolerance)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -3,7 +3,7 @@ import torch ...@@ -3,7 +3,7 @@ import torch
import torchani import torchani
class TestUtils(unittest.TestCase): class TestUtils(torchani.testing.TestCase):
def testChemicalSymbolsToInts(self): def testChemicalSymbolsToInts(self):
str2i = torchani.utils.ChemicalSymbolsToInts(['A', 'B', 'C', 'D', 'E', 'F']) str2i = torchani.utils.ChemicalSymbolsToInts(['A', 'B', 'C', 'D', 'E', 'F'])
......
...@@ -13,7 +13,7 @@ path = os.path.dirname(os.path.realpath(__file__)) ...@@ -13,7 +13,7 @@ path = os.path.dirname(os.path.realpath(__file__))
path = os.path.join(path, '../dataset/xyz_files/H2O.xyz') path = os.path.join(path, '../dataset/xyz_files/H2O.xyz')
class TestVibrational(unittest.TestCase): class TestVibrational(torchani.testing.TestCase):
def testVibrationalWavenumbers(self): def testVibrationalWavenumbers(self):
model = torchani.models.ANI1x().double() model = torchani.models.ANI1x().double()
...@@ -44,8 +44,7 @@ class TestVibrational(unittest.TestCase): ...@@ -44,8 +44,7 @@ class TestVibrational(unittest.TestCase):
freq2, modes2, _, _ = torchani.utils.vibrational_analysis(masses[species], hessian) freq2, modes2, _, _ = torchani.utils.vibrational_analysis(masses[species], hessian)
freq2 = freq2[6:].float() freq2 = freq2[6:].float()
modes2 = modes2[6:] modes2 = modes2[6:]
ratio = freq2 / freq self.assertEqual(freq, freq2, atol=0, rtol=0.02, exact_dtype=False)
self.assertLess((ratio - 1).abs().max(), 0.02)
diff1 = (modes - modes2).abs().max(dim=-1).values.max(dim=-1).values diff1 = (modes - modes2).abs().max(dim=-1).values.max(dim=-1).values
diff2 = (modes + modes2).abs().max(dim=-1).values.max(dim=-1).values diff2 = (modes + modes2).abs().max(dim=-1).values.max(dim=-1).values
......
...@@ -39,6 +39,7 @@ from . import units ...@@ -39,6 +39,7 @@ from . import units
from pkg_resources import get_distribution, DistributionNotFound from pkg_resources import get_distribution, DistributionNotFound
import warnings import warnings
import importlib_metadata import importlib_metadata
from . import testing
has_cuaev = 'torchani.cuaev' in importlib_metadata.metadata(__package__).get_all('Provides') has_cuaev = 'torchani.cuaev' in importlib_metadata.metadata(__package__).get_all('Provides')
...@@ -55,7 +56,7 @@ except DistributionNotFound: ...@@ -55,7 +56,7 @@ except DistributionNotFound:
pass pass
__all__ = ['AEVComputer', 'EnergyShifter', 'ANIModel', 'Ensemble', 'SpeciesConverter', __all__ = ['AEVComputer', 'EnergyShifter', 'ANIModel', 'Ensemble', 'SpeciesConverter',
'utils', 'neurochem', 'models', 'units', 'has_cuaev'] 'utils', 'neurochem', 'models', 'units', 'has_cuaev', 'testing']
try: try:
from . import ase # noqa: F401 from . import ase # noqa: F401
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment