Source code for pantea.simulation.molecular_dynamics
from __future__ import annotations
from typing import Optional
import jax
import jax.numpy as jnp
from pantea.atoms.box import _wrap_into_box
from pantea.logger import logger
from pantea.simulation.system import System
from pantea.simulation.thermostat import BrendsenThermostat
from pantea.types import Array
from pantea.units import units
@jax.jit
def _get_verlet_new_positions(
positions: Array, velocities: Array, forces: Array, time_step: Array
) -> Array:
return positions + velocities * time_step + 0.5 * forces * time_step * time_step
@jax.jit
def _get_verlet_new_velocities(
velocities: Array,
forces: Array,
new_forces: Array,
time_step: Array,
) -> Array:
return velocities + 0.5 * (forces + new_forces) * time_step
[docs]class MDSimulator:
def __init__(
self,
time_step: float,
thermostat: Optional[BrendsenThermostat] = None,
) -> None:
"""
A molecular dynamics simulator to predict trajectory of particles over time.
:param time_step: time step in Hartree time unit
:type time_step: float
:param thermostat: input thermostat that controls the temperature of the system
to the target value, defaults to None
:type thermostat: Optional[BrendsenThermostat], optional
This simulator is intended to be used for small systems of atoms during
the potential training in order to generate new training dataset.
"""
logger.debug(f"Initializing {self.__class__.__name__}")
self.time_step: Array = jnp.array(time_step)
self.thermostat = thermostat
self.step: int = 0
self.elapsed_time: float = 0.0
[docs] def simulate_one_step(self, system: System) -> None:
"""Update parameters for next time step."""
self.verlet_integration(system)
self.step += 1
self.elapsed_time += float(self.time_step)
if self.thermostat is not None:
system.velocities = self.thermostat.get_rescaled_velocities(self, system)
[docs] def verlet_integration(self, system: System) -> None:
"""Update atom positions, velocities, and forces based on Verlet algorithm."""
new_positions = _get_verlet_new_positions(
system.positions, system.velocities, system.forces, self.time_step
)
if system.box is not None:
new_positions = _wrap_into_box(new_positions, system.box.lattice)
system.structure.positions = new_positions
new_forces = system.potential.compute_forces(system.structure)
system.velocities = _get_verlet_new_velocities(
system.velocities, system.forces, new_forces, self.time_step
)
system.structure.forces = new_forces
[docs] def repr_physical_params(self, system: System) -> str:
"""Represent current physical parameters."""
return (
f"{self.step:<10} "
f"time[ps]:{units.TO_PICO_SECOND * self.elapsed_time:<10.5f} "
f"Temp[K]:{system.get_temperature():<10.5f} "
f"Etot[Ha]:{system.get_total_energy():<15.10f} "
f"Epot[Ha]:{system.get_potential_energy():<15.10f} "
f"Pres[kb]:{system.get_pressure() * units.TO_KILO_BAR:<10.5f}"
if system.structure.box
else ""
)