mdsim/forces.nim

163 lines
5.1 KiB
Nim

import parsetoml
import particle
import linalg
import math
import strformat
import random
import parsexyz
import threadpool
const nThreads = 8
type
ForceField = ref object
# this is stretching the definition of an ff beyond what's reasonable
# should be split in two - the actual ff and calculation parameters
lennardE*: float
lennardS*: float
neighborCutoff*: float
boundary*: float
dt*: float
maxt*: float
saveInterval*: int
extraVel*: float # TODO remove this once diff velocity is implemented
# TODO: new options - thermostat and periodic boundaries
# cutoff distance for calculations
# load velocity as diff from last two frames options and adapt genrandom for that option
proc newForceField*(path: string, name: string): ForceField =
new(result)
# read config file
let input = parsetoml.parseString(path.readFile())
# initialize variables based on input file
let config = input[name]
result.dt = config["dt"].getFloat()
result.maxt = config["t"].getFloat()
result.lennardE = config["lennardE"].getFloat()
result.lennardS = config["lennardS"].getFloat()
result.boundary = config["boundary"].getFloat()
result.saveInterval = config["saveInterval"].getInt()
result.extraVel = config["extraVel"].getFloat()
result.neighborCutoff = config["neighborCutoff"].getFloat()
proc applyExtraVel*(particles: var seq[Particle], ff: ForceField) =
for i in 0..particles.high():
particles[i].vel = vector(rand(ff.extraVel * 2) - ff.extraVel, rand(ff.extraVel * 2) - ff.extraVel, rand(ff.extraVel * 2) - ff.extraVel)
proc getAcc(particles: seq[Particle], ff: ForceField, index: int): Vector =
result = vector0()
let posThis = particles[index].pos
let mThis = particles[index].mass
let s6 = ff.lennardS ^ 6
let lennard_pre = 24f * ff.lennardE * s6
let boundary = ff.boundary
for i in 0..particles.high:
if i == index:
continue
var diff = particles[i].pos - posThis
# periodic condition
if diff.x > boundary:
diff.x -= 2f * boundary
elif diff.x < -boundary:
diff.x += 2f * boundary
if diff.y > boundary:
diff.y -= 2f * boundary
elif diff.y < -boundary:
diff.y += 2f * boundary
if diff.z > boundary:
diff.z -= 2f * boundary
elif diff.z < -boundary:
diff.z += 2f * boundary
if diff.len() == 0.0:
raise newException(Defect, &"overlap between {i} and {index}")
let distance = diff.len()
if distance > ff.neighborCutoff * ff.lennardS:
continue
let direction = diff / distance
# Lennard-Jones
# F = 4e (6s^6/r^7 - 12s^12/r^13)
let rm1 = 1/distance
let r6 = rm1 ^ 6
result -= direction * (lennard_pre * rm1 * (r6) * (1 - 2f * s6 * r6) / mThis)
proc keepInside(particles: var seq[Particle], ff: ForceField, index: int) =
# periodic boundary condition
let posThis = particles[index].pos
let velThis = particles[index].vel
if posThis.x > ff.boundary and velThis.x > 0:
particles[index].pos.x = -ff.boundary
if posThis.x < -ff.boundary and velThis.x < 0:
particles[index].pos.x = ff.boundary
if posThis.y > ff.boundary and velThis.y > 0:
particles[index].pos.y = -ff.boundary
if posThis.y < -ff.boundary and velThis.y < 0:
particles[index].pos.y = ff.boundary
if posThis.z > ff.boundary and velThis.z > 0:
particles[index].pos.z = -ff.boundary
if posThis.z < -ff.boundary and velThis.z < 0:
particles[index].pos.z = ff.boundary
proc iterate*(particles: var seq[Particle], ff: ForceField, t: float) =
let dt = ff.dt
block step:
# iterate through particles
# update positions
for i in 0..particles.high:
let part = particles[i]
particles[i].pos = part.pos + part.vel * dt + dt * dt * 0.5 * part.acc
# update acceleration and velocity
var newAccs = newSeq[Vector](particles.len)
#for i in 0..nThreads:
# spawn a thread
for i in 0..particles.high:
newAccs[i] = getAcc(particles, ff, i)
for i in 0..particles.high:
let part = particles[i]
let newAcc = newAccs[i]
let newVel = part.vel + (part.acc + newAcc) * (dt * 0.5)
particles[i].vel = newVel
particles[i].acc = newAcc
# enforce boundary
for i in 0..particles.high:
keepInside(particles, ff, i)
proc simulate*(ffName: string, xyzPath: string) =
let ff = newForceField("ffs.toml", ffName)
var particles: seq[Particle] = parseXyz(xyzPath)
applyExtraVel(particles, ff) # TODO replace this
# open the output file
let f = open(xyzPath, fmAppend)
var saveCounter = 0
var t = 0.0
while t < ff.maxt:
iterate(particles, ff, t)
inc saveCounter
if saveCounter >= ff.saveInterval:
saveCounter = 0
appendXyz(f, particles, t)
t += ff.dt
# cleanup
f.close()