#!/usr/bin/env python import numpy as np from jax import numpy as jnp, jit, grad # toy moleculat dynamic system parameters (constants) De = 1.6 # [eV (electronvolt)] alpha = 3.028 # [A^-1] re = 1.411 # [A] mass = 18.998403 # atomic mass of a single particle def dump(file, position, velocity, box_size, mode = "w", comment = ""): """ Dumps `position` and `velocity` to `file` with meta data. First writes a header to `file` in `mode` file write mode defines as three lines. `` `` `` then each line (nr of particles many) have the form `x y z vx vy vx`. """ header = [str(position.shape[0]), comment, str(box_size)] data = np.hstack((position, velocity)) np.savetxt(file, data, comments = "", header = "\n".join(header)) def load(file): """ Loads a state from `file` and returns the `position` and `velocity` system states as two `numpy` arrays. As a side effect the config object as agumented with `file` header info. """ if isinstance(file, str): with open(file) as handle: return load(handle) # read header nr_particles = int(file.readline().strip()) file.readline() # ignore comment line box_size = float(file.readline().strip()) # read the state data, each line constains `x y z vx vy vz` data = np.loadtxt(file, max_rows = nr_particles) return np.hsplit(data, 2), box_size def iter_load(file): """ Iterator yielding similar to `load` entries from `file` in the same format. """ if isinstance(file, str): with open(file) as handle: yield from iter_load(handle) else: while True: # read header line = file.readline().strip() if not line: return nr_particles = int(line) file.readline() # ignore comment line line = file.readline().strip() if not line: return box_size = float(line) # read the state data, each line constains `x y z vx vy vz` data = np.loadtxt(file, max_rows = nr_particles) if not data.shape[0] == nr_particles: return yield np.hsplit(data, 2), box_size def print_prog_bar(index, max_index): """ Simple progress bar. Just for convenience. """ if (index % 100) and (max_index != index + 1): return progress = (30 * (index + 1)) // max_index print(f"\r{(index + 1)}/{max_index} - " \ f"[{'':=>{(progress)}}{'': >{(30 - progress)}}]", end = "\n" if max_index == index + 1 else "", flush = True) @jit def energy(position, box_size): """ Computes the potential energy of a system of particles with `position` using Morses potential. """ # enforce expected shape # (`scipy.optimize.minimize` drops shape information) if len(position.shape) == 1: position = position.reshape((-1, 3)) # compute all pairwise position differences (all!) diff = position[:, jnp.newaxis, :] - position # extract only one of two distance combinations of non-equal particles lower_tri = jnp.tril_indices(position.shape[0], k = -1) diff = diff[lower_tri[0], lower_tri[1], :] # fold space in all directions. The `max_dist` is the maximum distance # in the folded space untill the distance through the folded space # border is smaller than inside the box. 3-axis parallel fold max_dist = box_size / 2.0 diff = jnp.mod(diff + max_dist, box_size) - max_dist # Compute distances dist = jnp.linalg.norm(diff, axis = 1) # calc inbetween exp(.) expression ex = jnp.exp(-alpha * (dist - re)) # evaluate Morse potential return De * jnp.sum(ex * (ex - 2.0)) @jit def force(position, box_size): """ Computes the forces acting on each particle in `position` as the negative gradient of the potential energy. """ return -grad(energy)(position, box_size) @jit def kinetic(velocity): """ Computes the kenetic energy given a systems `velocity` state. """ return (mass / 2.0) * (velocity**2).sum() @jit def step(position, velocity, acceleration, box_size, delta_t): """ Performs a single Newton time step with `delta_t` given system state through the current particle `position`, `velocity` and `acceleration`. """ # update position with a second order Taylor expantion position += delta_t * velocity + (0.5 * delta_t**2) * acceleration # compute new particle acceleration through Newton’s second law of motion acceleration_next = force(position, box_size) / mass # update velocity with a finite mean approximation velocity += (0.5 * delta_t) * (acceleration + acceleration_next) # updated state return position, velocity, acceleration_next