|
1 | 1 | import copy |
| 2 | +import os |
2 | 3 | import pickle |
3 | 4 | import random |
| 5 | +import unittest as ut |
4 | 6 | from datetime import datetime |
| 7 | +from pathlib import Path |
5 | 8 |
|
6 | 9 | import numpy as np |
| 10 | +import pytest |
7 | 11 | import torch |
8 | 12 | from ase.io.trajectory import Trajectory |
9 | 13 | from torch.utils.data import DataLoader |
10 | 14 |
|
11 | 15 | from nff.data import Dataset, collate_dicts |
| 16 | +from nff.io.ase import AtomsBatch |
| 17 | +from nff.io.ase_calcs import NeuralFF |
| 18 | +from nff.md.nvt import Langevin |
12 | 19 | from nff.md.nvt_ax import NoseHoover, NoseHooverChain |
13 | 20 | from nff.md.utils_ax import ZhuNakamuraLogger, atoms_to_nxyz, mol_dot, mol_norm |
14 | 21 | from nff.train import load_model |
|
18 | 25 | HBAR = 1 |
19 | 26 | OUT_FILE = "trj.csv" |
20 | 27 | LOG_FILE = "trj.log" |
| 28 | +this_file = Path(__file__).resolve() |
| 29 | +ETHANOL_MODEL_PATH = ( |
| 30 | + this_file.parent.parent.parent / "tutorials" / "models" / "cco_1" / "best_model" |
| 31 | +) # Simon's SchNet model |
21 | 32 |
|
22 | 33 |
|
23 | 34 | METHOD_DIC = {"nosehoover": NoseHoover, "nosehooverchain": NoseHooverChain} |
24 | 35 |
|
25 | 36 |
|
| 37 | +def get_directed_ethanol(): |
| 38 | + """Returns an ethanol molecule. |
| 39 | +
|
| 40 | + Returns: |
| 41 | + ethanol (Atoms) |
| 42 | + """ |
| 43 | + props = { |
| 44 | + "nxyz": torch.Tensor( |
| 45 | + [ |
| 46 | + [6.0000e00, 5.5206e-03, 5.9149e-01, -8.1382e-04], |
| 47 | + [6.0000e00, -1.2536e00, -2.5536e-01, -2.9801e-02], |
| 48 | + [8.0000e00, 1.0878e00, -3.0755e-01, 4.8230e-02], |
| 49 | + [1.0000e00, 6.2821e-02, 1.2838e00, -8.4279e-01], |
| 50 | + [1.0000e00, 6.0567e-03, 1.2303e00, 8.8535e-01], |
| 51 | + [1.0000e00, -2.2182e00, 1.8981e-01, -5.8160e-02], |
| 52 | + [1.0000e00, -9.1097e-01, -1.0539e00, -7.8160e-01], |
| 53 | + [1.0000e00, -1.1920e00, -7.4248e-01, 9.2197e-01], |
| 54 | + [1.0000e00, 1.8488e00, -2.8632e-02, -5.2569e-01], |
| 55 | + ] |
| 56 | + ), |
| 57 | + "energy": torch.tensor(-4.3701), |
| 58 | + "energy_grad": torch.Tensor( |
| 59 | + [ |
| 60 | + [10.2030, -33.6563, 1.9132], |
| 61 | + [-59.5878, 42.4086, 10.0746], |
| 62 | + [-36.9785, 2.0060, 18.7998], |
| 63 | + [-1.8185, 5.6604, 4.6715], |
| 64 | + [-1.8685, 0.9660, -1.9927], |
| 65 | + [11.0286, -11.6878, 18.4956], |
| 66 | + [38.0142, -24.5804, -16.6240], |
| 67 | + [5.8505, 15.7041, -12.9981], |
| 68 | + [35.1569, 3.1794, -22.3399], |
| 69 | + ] |
| 70 | + ), |
| 71 | + "smiles": "CCO", |
| 72 | + "num_atoms": torch.tensor(9), |
| 73 | + "nbr_list": torch.Tensor( |
| 74 | + [ |
| 75 | + [0, 1], |
| 76 | + [0, 2], |
| 77 | + [0, 3], |
| 78 | + [0, 4], |
| 79 | + [0, 5], |
| 80 | + [0, 6], |
| 81 | + [0, 7], |
| 82 | + [0, 8], |
| 83 | + [1, 2], |
| 84 | + [1, 3], |
| 85 | + [1, 4], |
| 86 | + [1, 5], |
| 87 | + [1, 6], |
| 88 | + [1, 7], |
| 89 | + [1, 8], |
| 90 | + [2, 3], |
| 91 | + [2, 4], |
| 92 | + [2, 5], |
| 93 | + [2, 6], |
| 94 | + [2, 7], |
| 95 | + [2, 8], |
| 96 | + [3, 4], |
| 97 | + [3, 5], |
| 98 | + [3, 6], |
| 99 | + [3, 7], |
| 100 | + [3, 8], |
| 101 | + [4, 5], |
| 102 | + [4, 6], |
| 103 | + [4, 7], |
| 104 | + [4, 8], |
| 105 | + [5, 6], |
| 106 | + [5, 7], |
| 107 | + [5, 8], |
| 108 | + [6, 7], |
| 109 | + [6, 8], |
| 110 | + [7, 8], |
| 111 | + ] |
| 112 | + ), |
| 113 | + "charge": torch.tensor(0.0), |
| 114 | + "spin": torch.tensor(0.0), |
| 115 | + } |
| 116 | + return AtomsBatch(positions=props["nxyz"][:, 1:], directed=True, numbers=props["nxyz"][:, 0], props=props) |
| 117 | + |
| 118 | + |
26 | 119 | class ZhuNakamuraDynamics(ZhuNakamuraLogger): |
27 | 120 | """ |
28 | 121 | Class for running Zhu-Nakamura surface-hopping dynamics. This method follows the description in |
@@ -974,3 +1067,44 @@ def run(self): |
974 | 1067 | ) |
975 | 1068 |
|
976 | 1069 | batched_zn.run() |
| 1070 | + |
| 1071 | + |
| 1072 | +# @pytest.mark.usefixtures("device") |
| 1073 | +@pytest.mark.skip("Works locally but need to update to work on remote CI") |
| 1074 | +class TestLangevin(ut.TestCase): |
| 1075 | + def setUp(self): |
| 1076 | + self.ethanol = get_directed_ethanol() |
| 1077 | + self.device = self._test_fixture_device |
| 1078 | + self.model = NeuralFF.from_file(ETHANOL_MODEL_PATH, device=self.device) |
| 1079 | + self.ethanol.set_calculator(self.model) |
| 1080 | + if os.path.exists("langevin.traj"): |
| 1081 | + os.remove("langevin.traj") |
| 1082 | + if os.path.exists("langevin.log"): |
| 1083 | + os.remove("langevin.log") |
| 1084 | + |
| 1085 | + @pytest.mark.timeout(30) |
| 1086 | + def test_langevin(self): |
| 1087 | + # Set up Langevin dynamics |
| 1088 | + my_dt = 1.0 # fs |
| 1089 | + my_temp = 100 # K |
| 1090 | + my_friction = 1.0 |
| 1091 | + logfile = "langevin.log" |
| 1092 | + |
| 1093 | + dyn = Langevin( |
| 1094 | + self.ethanol, |
| 1095 | + timestep=my_dt, |
| 1096 | + temperature=my_temp, |
| 1097 | + friction=my_friction, |
| 1098 | + maxwell_temp=my_temp, |
| 1099 | + logfile=logfile, |
| 1100 | + trajectory="langevin.traj", |
| 1101 | + ) |
| 1102 | + dyn.run(steps=40) |
| 1103 | + |
| 1104 | + # Check that the trajectory file was created |
| 1105 | + assert os.path.exists("langevin.traj") |
| 1106 | + assert os.path.exists("langevin.log") |
| 1107 | + |
| 1108 | + |
| 1109 | +if __name__ == "__main__": |
| 1110 | + ut.main() |
0 commit comments