flashmd 0.2.0__tar.gz → 0.2.2__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.0
3
+ Version: 0.2.2
4
4
  Summary: Accelerated molecular dynamics with large-time-step predictions
5
5
  Author: flashmd developers
6
6
  License: Apache-2.0
@@ -65,6 +65,7 @@ time_step = 16 # 16 fs; also available: 1, 2, 4, 8, 32, 64, 128 fs
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
+ atoms.set_velocities(atoms.get_velocities() - atoms.get_velocities().mean(axis=0))
68
69
 
69
70
  # Load models
70
71
  device="cuda" if torch.cuda.is_available() else "cpu"
@@ -36,6 +36,7 @@ time_step = 16 # 16 fs; also available: 1, 2, 4, 8, 32, 64, 128 fs
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
+ atoms.set_velocities(atoms.get_velocities() - atoms.get_velocities().mean(axis=0))
39
40
 
40
41
  # Load models
41
42
  device="cuda" if torch.cuda.is_available() else "cpu"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flashmd"
3
- version = "0.2.0"
3
+ version = "0.2.2"
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_velocities().mean(axis=0))
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_velocities().mean(axis=0))
@@ -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.
@@ -37,7 +37,7 @@ def get_standard_vv_step(
37
37
 
38
38
  if rescale_energy:
39
39
  info("@flashmd: Old energy", verbosity.debug)
40
- old_energy = sim.properties("potential") + sim.properties("kinetic_md")
40
+ old_energy = sim.properties("conserved")
41
41
 
42
42
  motion.integrator.pstep(level=0)
43
43
  motion.integrator.pconstraints()
@@ -48,7 +48,7 @@ def get_standard_vv_step(
48
48
 
49
49
  if rescale_energy:
50
50
  info("@flashmd: Energy rescale", verbosity.debug)
51
- new_energy = sim.properties("potential") + sim.properties("kinetic_md")
51
+ new_energy = sim.properties("conserved")
52
52
  kinetic_energy = sim.properties("kinetic_md")
53
53
  alpha = np.sqrt(1.0 - (new_energy - old_energy) / kinetic_energy)
54
54
  motion.beads.p[:] = alpha * dstrip(motion.beads.p)
@@ -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)
@@ -76,7 +76,7 @@ def get_flashmd_vv_step(sim, model, device, rescale_energy=True, random_rotation
76
76
  info("@flashmd: Starting VV", verbosity.debug)
77
77
  if rescale_energy:
78
78
  info("@flashmd: Old energy", verbosity.debug)
79
- old_energy = sim.properties("potential") + sim.properties("kinetic_md")
79
+ old_energy = sim.properties("conserved")
80
80
 
81
81
  info("@flashmd: Stepper", verbosity.debug)
82
82
  system = ipi_to_system(motion, device, dtype)
@@ -109,11 +109,12 @@ def get_flashmd_vv_step(sim, model, device, rescale_energy=True, random_rotation
109
109
 
110
110
  if rescale_energy:
111
111
  info("@flashmd: Energy rescale", verbosity.debug)
112
- new_energy = sim.properties("potential") + sim.properties("kinetic_md")
112
+ new_energy = sim.properties("conserved")
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()
116
+ motion.integrator.pconstraints() # just to be sure
117
+
117
118
  info("@flashmd: End of VV step", verbosity.debug)
118
119
 
119
120
  return flashmd_vv
@@ -155,7 +156,7 @@ def get_nvt_stepper(
155
156
  sim,
156
157
  model,
157
158
  device,
158
- rescale_energy=True,
159
+ rescale_energy=False,
159
160
  random_rotation=False,
160
161
  use_standard_vv=False,
161
162
  ):
@@ -223,7 +224,7 @@ def get_npt_stepper(
223
224
  sim,
224
225
  model,
225
226
  device,
226
- rescale_energy=True,
227
+ rescale_energy=False,
227
228
  random_rotation=False,
228
229
  use_standard_vv=False,
229
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.0
3
+ Version: 0.2.2
4
4
  Summary: Accelerated molecular dynamics with large-time-step predictions
5
5
  Author: flashmd developers
6
6
  License: Apache-2.0
@@ -65,6 +65,7 @@ time_step = 16 # 16 fs; also available: 1, 2, 4, 8, 32, 64, 128 fs
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
+ atoms.set_velocities(atoms.get_velocities() - atoms.get_velocities().mean(axis=0))
68
69
 
69
70
  # Load models
70
71
  device="cuda" if torch.cuda.is_available() else "cpu"
@@ -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