最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Minimum image convention for a MD simulation in Python - Stack Overflow

programmeradmin5浏览0评论

I'm trying to implement a simple molecular dynamic simulation in Python for N atoms with periodic boundary conditions using minimum image convention. Here is my code:

import numpy as np
import matplotlib.pyplot as plt
from scipy.constants import Boltzmann

# Constants
mass_of_argon = 39.948  # amu
dt = 0.1
num_steps = 5000
n = 2
d = 4.0
epsilon = 0.0103  # eV
sigma = 3.405  # A
kb = 8.617333262e-5  # eV/K
T_in = 95
cutoff = 2.5 * sigma
cutoff2 = cutoff ** 2  

N = n**3
L = n * d


def initialize_positions(n, d):
    pos = np.zeros((N, 3))
    count = 0
    for i in range(n):
        for j in range(n):
            for k in range(n):
                pos[count] = np.array([i * d, j * d, k * d])
                count += 1
    return pos


def initialize_velocities(T, N):
    vel = np.random.randn(N, 3)  
    vel -= np.mean(vel, axis=0)  
    T_current = mass_of_argon * np.sum(vel**2) / (3 * kb * N)
    vel *= np.sqrt(T / T_current)  
    return vel


def get_acc_pot(X, L):
    forces = np.zeros((N, 3))
    potential_energy = np.zeros(N)
    D = X[None, :, :] - X[:, None, :]
    D -= np.rint(D / L) * L  
    D2 = np.sum(D**2, axis=-1)
    D2_safe = np.maximum(D2, 1e-10)  
    DS2 = np.divide(sigma**2, D2_safe)
    valid = D2 < cutoff2  
    DS6 = np.where(valid, DS2**3, 0) 
    
    for i in range(N - 1):
        for j in range(i + 1, N):
            r = D[i, j, :]
            r2 = D2[i, j]
            rs6 = DS6[i, j]
            F = np.where(valid[i, j], 48 * epsilon * rs6 * r * (rs6 - 0.5) / r2, 0)
            forces[i, :] -= F
            forces[j, :] += F
            potential_energy[i] += np.where(valid[i, j], 4 * epsilon * (rs6 * (rs6 - 1)), 0)
            potential_energy[j] += np.where(valid[i, j], 4 * epsilon * (rs6 * (rs6 - 1)), 0)
    
    return forces / mass_of_argon, potential_energy


def update_pos_vel(X, V, A, dt, L):
    X_new = X + V * dt + 0.5 * A * dt**2
    X_new %= L  
    A_new, potential_energy = get_acc_pot(X_new, L)
    V_new = V + 0.5 * (A + A_new) * dt
    return X_new, V_new, A_new, potential_energy


def K_energy(V):
    kinetic_energy = 0.5 * mass_of_argon * np.sum(V**2, axis=1)
    return kinetic_energy


def run_md(dt, num_steps, T):
    pos = initialize_positions(n, d)
    vel = initialize_velocities(T, N)
    acc, pot_energy = get_acc_pot(pos, L)
    pos_history = np.zeros((num_steps, N, 3))
    vel_history = np.zeros((num_steps, N, 3))
    ke_history = np.zeros((num_steps, N))
    pe_history = np.zeros((num_steps, N))
    
    for step in range(num_steps):
        pos, vel, acc, pot_energy = update_pos_vel(pos, vel, acc, dt, L)
        pos_history[step] = pos
        vel_history[step] = vel
        pe_history[step] = pot_energy
        ke_history[step] = K_energy(vel)
    
    return pos_history, vel_history, ke_history, pe_history


pos_hist, vel_hist, ke_hist, pe_hist = run_md(dt, num_steps, T_in)

def plot_energy(i):
    t_vals = np.linspace(0, dt * num_steps, num_steps)
    plt.figure(figsize=(10, 5))
    plt.plot(t_vals, ke_hist[:, i], label=f'Kinetic energy for atom {i+1}')
    plt.plot(t_vals, pe_hist[:, i], label=f'Potential energy for atom {i+1}')
    plt.plot(t_vals, ke_hist[:, i] + pe_hist[:, i], label=f'Potential energy for atom {i+1}')
    plt.xlabel('Time (ps)')
    plt.ylabel('Energy (eV)')
    plt.legend()
    plt.title(f'Energy evolution for Atom {i+1}')
    plt.show()

def plot_x(i):
    t_vals = np.linspace(0, dt * num_steps, num_steps)
    plt.figure(figsize=(10, 5))
    plt.plot(t_vals, pos_hist[:, i, 0], label='x-Position for atom {i+1}')
    plt.xlabel('Time (ps)')
    plt.ylabel('Position (Å)')
    plt.legend()
    plt.title(f'x-position evolution for Atom {i+1}')
    plt.show()

def plot_v(i):
    t_vals = np.linspace(0, dt * num_steps, num_steps)
    plt.figure(figsize=(10, 5))
    plt.plot(t_vals, vel_hist[:, i, 0], label='x-velocity')
    plt.xlabel('Time (ps)')
    plt.ylabel('Velocity (Å/ps)')
    plt.legend()
    plt.title(f'x-Velocity evolution for Atom {i+1}')
    plt.show()


plot_energy(1)
plot_x(1)
plot_v(1)

The problem is my implementation of min image convention does not seem to work properly, although I made sure to include it both before and after updating the positions (as you can see in get_acc_pot function). It is instead making a weird jump when reaching the boundary! Here is a plot of the position of a random atom:

It is so hard to spot where I went wrong when I have been working on the same code for multiple days and thought that a second eye might be a good idea! Any tips/advice is much appreciated!:)

发布评论

评论列表(0)

  1. 暂无评论