mdsim/forces.nim

109 lines
3.8 KiB
Nim

import parsetoml
import particle
import linalg
import math
import strformat
import random
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
gravAcc*: 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.gravAcc = config["g"].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()
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
for i in 0..particles.high:
if i == index:
continue
let diff = particles[i].pos - posThis
if diff.len() == 0.0:
raise newException(Defect, &"overlap between {i} and {index}")
let direction = diff.normalize()
let distance = diff.len()
# Lennard-Jones
# F = 4e (6s^6/r^7 - 12s^12/r^13)
result -= direction * (4f * ff.lennardE * (6.0 * (ff.lennardS^6)/(distance^7) - 12.0 * (ff.lennardS^12)/(distance^13))) / mThis
# Gravity downwards
result += vector(0, 0, ff.gravAcc)
proc keepInside(particles: var seq[Particle], ff: ForceField, index: int) =
# TODO: thermostatic walls and periodic boundary condition
let posThis = particles[index].pos
let velThis = particles[index].vel
if posThis.x > ff.boundary and velThis.x > 0:
particles[index].vel.x = -velThis.x
if posThis.x < -ff.boundary and velThis.x < 0:
particles[index].vel.x = -velThis.x
if posThis.y > ff.boundary and velThis.y > 0:
particles[index].vel.y = -velThis.y
if posThis.y < -ff.boundary and velThis.y < 0:
particles[index].vel.y = -velThis.y
if posThis.z > ff.boundary and velThis.z > 0:
particles[index].vel.z = -velThis.z
if posThis.z < -ff.boundary and velThis.z < 0:
particles[index].vel.z = -velThis.z
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
for i in 0..particles.high:
let part = particles[i]
let newAcc = getAcc(particles, ff, 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)