flashmd 0.2.1__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashmd
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Accelerated molecular dynamics with large-time-step predictions
5
5
  Author: flashmd developers
6
6
  License: Apache-2.0
@@ -60,12 +60,15 @@ from flashmd.ase.langevin import Langevin
60
60
 
61
61
 
62
62
  # Choose your time step (go for 10-30x what you would use in normal MD for your system)
63
- time_step = 16 # 16 fs; also available: 1, 2, 4, 8, 32, 64, 128 fs
63
+ time_step = 64 # 64 fs; also available: 1, 2, 4, 8, 16, 32, 128 fs
64
64
 
65
65
  # Create a structure and initialize velocities
66
66
  atoms = ase.build.bulk("Al", "fcc", cubic=True)
67
67
  MaxwellBoltzmannDistribution(atoms, temperature_K=300)
68
68
 
69
+ # It is generally a good idea to remove any net velocity from the system
70
+ atoms.set_velocities(atoms.get_velocities() - atoms.get_momenta().sum(axis=0) / atoms.get_masses().sum())
71
+
69
72
  # Load models
70
73
  device="cuda" if torch.cuda.is_available() else "cpu"
71
74
  energy_model, flashmd_model = get_pretrained("pet-omatpes", time_step)
@@ -131,3 +134,6 @@ pip install flashmd==0.1.2 ase==3.24.0 pet-mad==1.4.3
131
134
 
132
135
  and using the "PET-MAD" models (PBEsol) from https://huggingface.co/lab-cosmo/flashmd.
133
136
  Note that the results were obtained through the i-PI interface.
137
+
138
+ Instructions and material to reproduce the results in the paper are available on
139
+ Materials Cloud at https://doi.org/10.24435/materialscloud:b7-xq.
@@ -31,12 +31,15 @@ from flashmd.ase.langevin import Langevin
31
31
 
32
32
 
33
33
  # Choose your time step (go for 10-30x what you would use in normal MD for your system)
34
- time_step = 16 # 16 fs; also available: 1, 2, 4, 8, 32, 64, 128 fs
34
+ time_step = 64 # 64 fs; also available: 1, 2, 4, 8, 16, 32, 128 fs
35
35
 
36
36
  # Create a structure and initialize velocities
37
37
  atoms = ase.build.bulk("Al", "fcc", cubic=True)
38
38
  MaxwellBoltzmannDistribution(atoms, temperature_K=300)
39
39
 
40
+ # It is generally a good idea to remove any net velocity from the system
41
+ atoms.set_velocities(atoms.get_velocities() - atoms.get_momenta().sum(axis=0) / atoms.get_masses().sum())
42
+
40
43
  # Load models
41
44
  device="cuda" if torch.cuda.is_available() else "cpu"
42
45
  energy_model, flashmd_model = get_pretrained("pet-omatpes", time_step)
@@ -102,3 +105,6 @@ pip install flashmd==0.1.2 ase==3.24.0 pet-mad==1.4.3
102
105
 
103
106
  and using the "PET-MAD" models (PBEsol) from https://huggingface.co/lab-cosmo/flashmd.
104
107
  Note that the results were obtained through the i-PI interface.
108
+
109
+ Instructions and material to reproduce the results in the paper are available on
110
+ Materials Cloud at https://doi.org/10.24435/materialscloud:b7-xq.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flashmd"
3
- version = "0.2.1"
3
+ version = "0.2.3"
4
4
  requires-python = ">=3.9"
5
5
 
6
6
  readme = "README.md"
@@ -16,10 +16,11 @@ class Bussi(VelocityVerlet):
16
16
  model: AtomisticModel | List[AtomisticModel],
17
17
  time_constant: float = 10.0 * ase.units.fs,
18
18
  device: str | torch.device = "auto",
19
- rescale_energy: bool = True,
19
+ rescale_energy: bool = False,
20
+ random_rotation: bool = False,
20
21
  **kwargs,
21
22
  ):
22
- super().__init__(atoms, timestep, model, device, rescale_energy, **kwargs)
23
+ super().__init__(atoms, timestep, model, device, rescale_energy, random_rotation, **kwargs)
23
24
 
24
25
  self.temperature_K = temperature_K
25
26
  self.time_constant = time_constant
@@ -1,8 +1,6 @@
1
1
  from .velocity_verlet import VelocityVerlet
2
2
  import ase.units
3
3
  from typing import List
4
-
5
- # from ..utils.pretrained import load_pretrained_models
6
4
  from metatomic.torch import AtomisticModel
7
5
  import torch
8
6
  import ase
@@ -17,14 +15,19 @@ class Langevin(VelocityVerlet):
17
15
  temperature_K: float,
18
16
  model: AtomisticModel | List[AtomisticModel],
19
17
  time_constant: float = 100.0 * ase.units.fs,
18
+ fixcm: bool = True,
20
19
  device: str | torch.device = "auto",
21
- rescale_energy: bool = True,
20
+ rescale_energy: bool = False,
21
+ random_rotation: bool = False,
22
22
  **kwargs,
23
23
  ):
24
- super().__init__(atoms, timestep, model, device, rescale_energy, **kwargs)
24
+ super().__init__(atoms, timestep, model, device, rescale_energy, random_rotation, **kwargs)
25
25
 
26
26
  self.temperature_K = temperature_K
27
27
  self.friction = 1.0 / time_constant
28
+ self.fixcm = fixcm
29
+ if self.fixcm:
30
+ self.atoms.set_velocities(self.atoms.get_velocities() - self.atoms.get_momenta().sum(axis=0) / self.atoms.get_masses().sum())
28
31
 
29
32
  def step(self):
30
33
  self.apply_langevin_half_step()
@@ -39,3 +42,5 @@ class Langevin(VelocityVerlet):
39
42
  ase.units.kB * self.temperature_K * self.atoms.get_masses()[:, None]
40
43
  ) * np.random.randn(*old_momenta.shape)
41
44
  self.atoms.set_momenta(new_momenta)
45
+ if self.fixcm:
46
+ self.atoms.set_velocities(self.atoms.get_velocities() - self.atoms.get_momenta().sum(axis=0) / self.atoms.get_masses().sum())
@@ -9,6 +9,7 @@ from metatomic.torch import System
9
9
  import ase
10
10
  from ..stepper import FlashMDStepper
11
11
  import numpy as np
12
+ from scipy.spatial.transform import Rotation
12
13
 
13
14
 
14
15
  class VelocityVerlet(MolecularDynamics):
@@ -19,6 +20,7 @@ class VelocityVerlet(MolecularDynamics):
19
20
  model: AtomisticModel | List[AtomisticModel],
20
21
  device: str | torch.device = "auto",
21
22
  rescale_energy: bool = True,
23
+ random_rotation: bool = False,
22
24
  **kwargs,
23
25
  ):
24
26
  super().__init__(atoms, timestep, **kwargs)
@@ -41,6 +43,7 @@ class VelocityVerlet(MolecularDynamics):
41
43
 
42
44
  self.stepper = FlashMDStepper(model, self.device)
43
45
  self.rescale_energy = rescale_energy
46
+ self.random_rotation = random_rotation
44
47
 
45
48
  def step(self):
46
49
  if self.rescale_energy:
@@ -49,7 +52,33 @@ class VelocityVerlet(MolecularDynamics):
49
52
  system = _convert_atoms_to_system(
50
53
  self.atoms, device=self.device, dtype=self.dtype
51
54
  )
55
+
56
+ if self.random_rotation:
57
+ # generate a random rotation matrix with SciPy
58
+ R = torch.tensor(
59
+ _get_random_rotation(),
60
+ device=system.positions.device,
61
+ dtype=system.positions.dtype,
62
+ )
63
+ # apply the random rotation
64
+ old_cell = system.cell
65
+ system.cell = system.cell @ R.T
66
+ system.positions = system.positions @ R.T
67
+ # change momentum TensorMap in place
68
+ system.get_data("momenta").block().values[:] = (
69
+ (system.get_data("momenta").block().values.squeeze(-1) @ R.T).unsqueeze(-1)
70
+ )
71
+
52
72
  new_system = self.stepper.step(system)
73
+
74
+ if self.random_rotation:
75
+ # revert q, p to the original reference frame, load old cell
76
+ new_system.cell = old_cell
77
+ new_system.positions = new_system.positions @ R
78
+ new_system.get_data("momenta").block().values[:] = (
79
+ (new_system.get_data("momenta").block().values.squeeze(-1) @ R).unsqueeze(-1)
80
+ )
81
+
53
82
  self.atoms.set_positions(new_system.positions.detach().cpu().numpy())
54
83
  self.atoms.set_momenta(
55
84
  new_system.get_data("momenta")
@@ -120,3 +149,10 @@ def _convert_atoms_to_system(
120
149
  ),
121
150
  )
122
151
  return system
152
+
153
+
154
+ def _get_random_rotation():
155
+ R = Rotation.random().as_matrix()
156
+ if np.random.rand() < 0.5:
157
+ R *= -1 # allow improper rotations
158
+ return R
@@ -0,0 +1,97 @@
1
+ from typing import Dict, List
2
+
3
+ import torch
4
+ from metatensor.torch import TensorBlock, TensorMap
5
+ from metatomic.torch import System
6
+
7
+
8
+ @torch.jit.script
9
+ def enforce_physical_constraints(
10
+ systems: List[System],
11
+ predictions: Dict[str, TensorMap],
12
+ timestep: float,
13
+ ) -> Dict[str, TensorMap]:
14
+ """
15
+ Enforces physical constraints in the predictions of a FlashMD model, namely
16
+ conservation of momentum of the center of mass and uniform linear motion of the
17
+ center of mass.
18
+ """
19
+
20
+ new_predictions: Dict[str, TensorMap] = {}
21
+
22
+ for key, prediction_tmap in predictions.items():
23
+ if key == "momenta":
24
+ # conservation of momentum of the center of mass
25
+ system_sizes = [len(s) for s in systems]
26
+ masses = [s.get_data("masses").block().values for s in systems]
27
+ total_masses = [m.sum() for m in masses]
28
+ momenta_before = [s.get_data("momenta").block().values for s in systems]
29
+ momenta_now = torch.split(prediction_tmap.block().values, system_sizes)
30
+ velocities_now = [p / m[:, None] for p, m in zip(momenta_now, masses)]
31
+ velocities_com_before = [
32
+ torch.sum(p, dim=0) / M for p, M in zip(momenta_before, total_masses)
33
+ ]
34
+ velocities_com_now = [
35
+ torch.sum(p, dim=0) / M for p, M in zip(momenta_now, total_masses)
36
+ ]
37
+ velocities_now = [
38
+ v - v_com_now_i + v_com_before_i
39
+ for v, v_com_before_i, v_com_now_i in zip(
40
+ velocities_now, velocities_com_before, velocities_com_now
41
+ )
42
+ ]
43
+ momenta_now = [v * m[:, None] for v, m in zip(velocities_now, masses)]
44
+ new_predictions[key] = TensorMap(
45
+ prediction_tmap.keys,
46
+ [
47
+ TensorBlock(
48
+ values=torch.concatenate(momenta_now),
49
+ samples=prediction_tmap.block().samples,
50
+ components=prediction_tmap.block().components,
51
+ properties=prediction_tmap.block().properties,
52
+ )
53
+ ],
54
+ )
55
+ elif key == "positions":
56
+ # uniform linear motion of the center of mass
57
+ system_sizes = [len(s) for s in systems]
58
+ masses = [s.get_data("masses").block().values for s in systems]
59
+ total_masses = [m.sum() for m in masses]
60
+ positions_before = [s.positions.unsqueeze(-1) for s in systems]
61
+ momenta = [s.get_data("momenta").block().values for s in systems]
62
+ positions_now = torch.split(prediction_tmap.block().values, system_sizes)
63
+ velocities_com = [
64
+ torch.sum(p, dim=0) / M for p, M in zip(momenta, total_masses)
65
+ ]
66
+ positions_com_before = [
67
+ torch.sum(q * m[:, None], dim=0) / M
68
+ for q, m, M in zip(positions_before, masses, total_masses)
69
+ ]
70
+ positions_com_now = [
71
+ torch.sum(q * m[:, None], dim=0) / M
72
+ for q, m, M in zip(positions_now, masses, total_masses)
73
+ ]
74
+ positions_now = [
75
+ q - q_com_now_i + q_com_before_i + v_com_i * timestep
76
+ for q, q_com_now_i, q_com_before_i, v_com_i in zip(
77
+ positions_now,
78
+ positions_com_now,
79
+ positions_com_before,
80
+ velocities_com,
81
+ )
82
+ ]
83
+ new_predictions[key] = TensorMap(
84
+ prediction_tmap.keys,
85
+ [
86
+ TensorBlock(
87
+ values=torch.concatenate(positions_now),
88
+ samples=prediction_tmap.block().samples,
89
+ components=prediction_tmap.block().components,
90
+ properties=prediction_tmap.block().properties,
91
+ )
92
+ ],
93
+ )
94
+ else:
95
+ new_predictions[key] = prediction_tmap
96
+
97
+ return new_predictions
@@ -15,7 +15,7 @@ from metatensor.torch import Labels, TensorBlock, TensorMap
15
15
 
16
16
 
17
17
  def get_standard_vv_step(
18
- sim, model=None, device=None, rescale_energy=True, random_rotation=False
18
+ sim, model=None, device=None, rescale_energy=False, random_rotation=False
19
19
  ):
20
20
  """
21
21
  Returns a velocity Verlet stepper function for i-PI simulations.
@@ -56,7 +56,7 @@ def get_standard_vv_step(
56
56
  return vv_step
57
57
 
58
58
 
59
- def get_flashmd_vv_step(sim, model, device, rescale_energy=True, random_rotation=False):
59
+ def get_flashmd_vv_step(sim, model, device, rescale_energy=False, random_rotation=False):
60
60
  capabilities = model.capabilities()
61
61
 
62
62
  model_timestep = float(model.module.timestep)
@@ -113,7 +113,7 @@ def get_flashmd_vv_step(sim, model, device, rescale_energy=True, random_rotation
113
113
  kinetic_energy = sim.properties("kinetic_md")
114
114
  alpha = np.sqrt(1.0 - (new_energy - old_energy) / kinetic_energy)
115
115
  motion.beads.p[:] = alpha * dstrip(motion.beads.p)
116
- motion.integrator.pconstraints() # just to make sure; should be cheap
116
+ motion.integrator.pconstraints() # just to be sure
117
117
 
118
118
  info("@flashmd: End of VV step", verbosity.debug)
119
119
 
@@ -156,7 +156,7 @@ def get_nvt_stepper(
156
156
  sim,
157
157
  model,
158
158
  device,
159
- rescale_energy=True,
159
+ rescale_energy=False,
160
160
  random_rotation=False,
161
161
  use_standard_vv=False,
162
162
  ):
@@ -224,7 +224,7 @@ def get_npt_stepper(
224
224
  sim,
225
225
  model,
226
226
  device,
227
- rescale_energy=True,
227
+ rescale_energy=False,
228
228
  random_rotation=False,
229
229
  use_standard_vv=False,
230
230
  ):
@@ -5,6 +5,8 @@ import torch
5
5
  from metatomic.torch import System
6
6
  from metatrain.utils.neighbor_lists import get_system_with_neighbor_lists
7
7
  from metatomic.torch import AtomisticModel
8
+ from .constraints import enforce_physical_constraints
9
+ import ase.units
8
10
 
9
11
 
10
12
  class FlashMDStepper:
@@ -14,6 +16,7 @@ class FlashMDStepper:
14
16
  device: torch.device,
15
17
  ):
16
18
  self.model = model.to(device)
19
+ self.time_step = float(model.module.timestep) * ase.units.fs
17
20
 
18
21
  # one of these for each model:
19
22
  self.evaluation_options = ModelEvaluationOptions(
@@ -41,6 +44,10 @@ class FlashMDStepper:
41
44
  model_outputs = self.model(
42
45
  [system], self.evaluation_options, check_consistency=False
43
46
  )
47
+ model_outputs = enforce_physical_constraints(
48
+ [system], model_outputs, timestep=self.time_step
49
+ )
50
+
44
51
  new_q = model_outputs["positions"].block().values.squeeze(-1)
45
52
  new_p = model_outputs["momenta"].block().values.squeeze(-1)
46
53
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashmd
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Accelerated molecular dynamics with large-time-step predictions
5
5
  Author: flashmd developers
6
6
  License: Apache-2.0
@@ -60,12 +60,15 @@ from flashmd.ase.langevin import Langevin
60
60
 
61
61
 
62
62
  # Choose your time step (go for 10-30x what you would use in normal MD for your system)
63
- time_step = 16 # 16 fs; also available: 1, 2, 4, 8, 32, 64, 128 fs
63
+ time_step = 64 # 64 fs; also available: 1, 2, 4, 8, 16, 32, 128 fs
64
64
 
65
65
  # Create a structure and initialize velocities
66
66
  atoms = ase.build.bulk("Al", "fcc", cubic=True)
67
67
  MaxwellBoltzmannDistribution(atoms, temperature_K=300)
68
68
 
69
+ # It is generally a good idea to remove any net velocity from the system
70
+ atoms.set_velocities(atoms.get_velocities() - atoms.get_momenta().sum(axis=0) / atoms.get_masses().sum())
71
+
69
72
  # Load models
70
73
  device="cuda" if torch.cuda.is_available() else "cpu"
71
74
  energy_model, flashmd_model = get_pretrained("pet-omatpes", time_step)
@@ -131,3 +134,6 @@ pip install flashmd==0.1.2 ase==3.24.0 pet-mad==1.4.3
131
134
 
132
135
  and using the "PET-MAD" models (PBEsol) from https://huggingface.co/lab-cosmo/flashmd.
133
136
  Note that the results were obtained through the i-PI interface.
137
+
138
+ Instructions and material to reproduce the results in the paper are available on
139
+ Materials Cloud at https://doi.org/10.24435/materialscloud:b7-xq.
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/flashmd/__init__.py
5
+ src/flashmd/constraints.py
5
6
  src/flashmd/ipi.py
6
7
  src/flashmd/models.py
7
8
  src/flashmd/stepper.py
File without changes
File without changes
File without changes
File without changes
File without changes