163 lines
5.1 KiB
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() |