commit
a08f15ef48
6 changed files with 513 additions and 0 deletions
Binary file not shown.
@ -0,0 +1,35 @@ |
|||
[config] |
|||
# time step |
|||
dt = 0.001 |
|||
# maximum time |
|||
t = 1000.0 |
|||
# gravitational constant |
|||
G = 0.0 |
|||
# gravitational acceleration |
|||
g = 0.000001 |
|||
# stabilize centre of mass? |
|||
stab_mass = false |
|||
# how often reset centre of mass |
|||
stab_interval = 100 |
|||
# lennard jones parameters |
|||
lennardE = 15 |
|||
lennardS = 0.5 |
|||
# boundary details |
|||
boundary = 30.0 |
|||
# save to disk every n time steps |
|||
saveInterval = 200 |
|||
|
|||
# format: after an initial config block, |
|||
# every block is one object, with the name being in square brackets |
|||
# mandatory fields: |
|||
# pos (vector) - initial position |
|||
# vel (vector) - initial velocity |
|||
# mass - its mass |
|||
|
|||
[random] |
|||
count = 80 |
|||
xmin = -29.0 |
|||
xmax = 29.0 |
|||
ymin = -29.0 |
|||
ymax = 29.0 |
|||
vmax = 0.0 |
@ -0,0 +1,46 @@ |
|||
import math |
|||
import strformat |
|||
|
|||
type |
|||
Vector* = object |
|||
x*: float |
|||
y*: float |
|||
|
|||
func vector*(x, y: float): Vector = |
|||
Vector(x: x, y: y) |
|||
|
|||
func `+`*(a, b: Vector): Vector = |
|||
Vector(x: a.x + b.x, y: a.y + b.y) |
|||
|
|||
func `-`*(a: Vector): Vector = |
|||
Vector(x: -a.x, y: -a.y) |
|||
|
|||
func `-`*(a, b: Vector): Vector = |
|||
a + (-b) |
|||
|
|||
func `*`*(a: Vector, f: float): Vector = |
|||
Vector(x: a.x * f, y: a.y * f) |
|||
|
|||
func `*`*(f: float, a: Vector): Vector = |
|||
a * f |
|||
|
|||
func dot*(a, b: Vector): float = |
|||
a.x * b.x + a.y * b.y |
|||
|
|||
func len*(a: Vector): float = |
|||
sqrt(dot(a, a)) |
|||
|
|||
func `/`*(a: Vector, f: float): Vector = |
|||
a * (1/f) |
|||
|
|||
func normalize*(a: Vector): Vector = |
|||
a / a.len |
|||
|
|||
func `+=`*(a: var Vector, b: Vector) = |
|||
a = a + b |
|||
|
|||
func `-=`*(a: var Vector, b: Vector) = |
|||
a = a - b |
|||
|
|||
func `$`*(a: Vector): string = |
|||
&"[{a.x}, {a.y}]" |
@ -0,0 +1,178 @@ |
|||
import linalg |
|||
import parsetoml |
|||
|
|||
import math |
|||
import strformat |
|||
import random |
|||
|
|||
# define types |
|||
type |
|||
Particle = object |
|||
name: string |
|||
pos: Vector |
|||
vel: Vector |
|||
acc: Vector |
|||
mass: float |
|||
|
|||
# read input file |
|||
let input = parsetoml.parseString("input.toml".readFile()) |
|||
|
|||
# initialize variables based on input file |
|||
let config = input["config"] |
|||
var t = 0.0 # current time |
|||
let dt = config["dt"].getFloat() |
|||
let maxt = config["t"].getFloat() |
|||
let gravConst = config["G"].getFloat() |
|||
let gravAcc = config["g"].getFloat() |
|||
let stabilizeMassCentre = config["stab_mass"].getBool() |
|||
let stabInterval = config["stab_interval"].getInt() |
|||
var stabCounter = 0 |
|||
let lennardE = config["lennardE"].getFloat() |
|||
let lennardS = config["lennardS"].getFloat() |
|||
let boundary = config["boundary"].getFloat() |
|||
let saveInterval = config["saveInterval"].getInt() |
|||
# a thing that runs every 100 frames to move the centre of mass back to the original one |
|||
var particles: seq[Particle] = @[] |
|||
|
|||
# process the rest of the input file |
|||
for key, val in input.tableVal.pairs: |
|||
if key == "config": |
|||
continue |
|||
elif key == "random": |
|||
let count = val["count"].getInt() |
|||
let xmin = val["xmin"].getFloat() |
|||
let xmax = val["xmax"].getFloat() |
|||
let ymin = val["ymin"].getFloat() |
|||
let ymax = val["ymax"].getFloat() |
|||
let vmax = val["vmax"].getFloat() |
|||
for i in 0..count: |
|||
block thisPart: |
|||
var particle = Particle(name: "random") |
|||
var pass = false |
|||
var attempt = 0 |
|||
while not pass: |
|||
inc attempt |
|||
if attempt > 10: |
|||
break thisPart |
|||
particle.pos = vector(rand(xmax-xmin)+xmin, rand(ymax-ymin)+ymin) |
|||
pass = true |
|||
for p in particles: |
|||
if (p.pos - particle.pos).len() < lennardS * 2.5: |
|||
pass = false |
|||
break |
|||
particle.vel = vector(rand(vmax), rand(vmax)) |
|||
particle.acc = vector(0.0, 0.0) |
|||
particle.mass = 1.0 |
|||
particles.add(particle) |
|||
else: |
|||
let x: float = val["pos"][0].getFloat() |
|||
let y: float = val["pos"][1].getFloat() |
|||
let vx: float = val["vel"][0].getFloat() |
|||
let vy: float = val["vel"][1].getFloat() |
|||
let pos = vector(x, y) |
|||
let vel = vector(vx, vy) |
|||
let mass = val["mass"].getFloat() |
|||
let particle = Particle(name: key, pos: pos, vel: vel, acc: vector(0,0), mass: mass) |
|||
particles.add(particle) |
|||
|
|||
# open the output file |
|||
let f = open("output.txt", fmWrite) |
|||
# do the simulation, writing results to a file (streaming) |
|||
f.writeLine("mdsim input file: input.toml") |
|||
proc oupStatus = |
|||
# output current status |
|||
var status: string = "" |
|||
for i in 0..particles.high: |
|||
status &= &"{particles[i].name} {$particles[i].pos} {$particles[i].vel} " |
|||
f.writeLine(&"{t} {status}") |
|||
|
|||
oupStatus() |
|||
|
|||
proc getForces(index: int): Vector = |
|||
result = vector(0, 0) |
|||
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 |
|||
let direction = diff.normalize() |
|||
let distance = diff.len() |
|||
let mThat = particles[i].mass |
|||
|
|||
# gravity between particles |
|||
# m2 * C / r^2 (so it's acceleartion, mass of this doesn't apply) |
|||
result += direction * (mThat * gravConst / distance / distance) |
|||
|
|||
# Lennard-Jones |
|||
# F = 4e (6s^6/r^7 - 12s^12/r^13) |
|||
result -= direction * (4f * lennardE * (6.0 * (lennardS^6)/(distance^7) - 12.0 * (lennardS^12)/(distance^13))) / mThis |
|||
|
|||
result += vector(0, gravAcc) |
|||
|
|||
proc keepInside(index: int) = |
|||
let posThis = particles[index].pos |
|||
let velThis = particles[index].vel |
|||
|
|||
if posThis.x > boundary and velThis.x > 0: |
|||
particles[index].vel.x = -velThis.x |
|||
|
|||
if posThis.x < -boundary and velThis.x < 0: |
|||
particles[index].vel.x = -velThis.x |
|||
|
|||
if posThis.y > boundary and velThis.y > 0: |
|||
particles[index].vel.y = -velThis.y |
|||
|
|||
if posThis.y < -boundary and velThis.y < 0: |
|||
particles[index].vel.y = -velThis.y |
|||
|
|||
|
|||
proc getCentreOfMass(): Vector = |
|||
var totMass = 0.0 |
|||
result = vector(0.0, 0.0) |
|||
for i in 0..particles.high: |
|||
let part = particles[i] |
|||
totMass += part.mass |
|||
result += part.pos * part.mass |
|||
result = result / totMass |
|||
|
|||
let initCentreOfMass = getCentreOfMass() |
|||
|
|||
proc resetCentreOfMass() = |
|||
let cCentre = getCentreOfMass() |
|||
let delta = cCentre - initCentreOfMass |
|||
for i in 0..particles.high: |
|||
particles[i].pos -= delta |
|||
|
|||
var saveCounter = 0 |
|||
|
|||
while t < maxt: |
|||
block step: |
|||
# iterate through particles |
|||
for i in 0..particles.high: |
|||
let part = particles[i] |
|||
particles[i].pos = part.pos + part.vel * dt + dt * dt * 0.5 * part.acc |
|||
|
|||
for i in 0..particles.high: |
|||
let part = particles[i] |
|||
let newAcc = getForces(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(i) |
|||
inc saveCounter |
|||
if saveCounter >= saveInterval: |
|||
saveCounter = 0 |
|||
oupStatus() |
|||
t += dt |
|||
inc stabCounter |
|||
if stabCounter > stabInterval: |
|||
stabCounter = 0 |
|||
if stabilizeMassCentre: |
|||
resetCentreOfMass() |
|||
|
|||
# cleanup |
|||
f.close() |
@ -0,0 +1,254 @@ |
|||
import sdl2 |
|||
import sdl2/ttf |
|||
|
|||
import strutils |
|||
import linalg |
|||
import sequtils |
|||
import strformat |
|||
import os |
|||
|
|||
var filename = "output.txt" |
|||
if paramCount() > 0: |
|||
filename = paramStr(1) |
|||
let lines = filename.readFile().split('\n').filterIt(it.len() > 0) |
|||
|
|||
const |
|||
windowWidth = 1000 |
|||
windowHeight = 1000 |
|||
|
|||
ballRadius = 8 |
|||
textHeight = 35 |
|||
|
|||
# assumed to be in ratio with the win width/height |
|||
xMin = -50.0 |
|||
xMax = 50.0 |
|||
yMin = -50.0 |
|||
yMax = 50.0 |
|||
|
|||
type |
|||
Particle = object |
|||
pos: Vector |
|||
vel: Vector |
|||
name: string |
|||
|
|||
var lineN = 1 |
|||
proc parseLine(line: string, t: var float): seq[Particle] = |
|||
type |
|||
TokenType = enum |
|||
ttFloat, ttString |
|||
Token = object |
|||
lineN: int |
|||
case kind: TokenType: |
|||
of ttFloat: |
|||
floatVal: float |
|||
of ttString: |
|||
stringVal: string |
|||
|
|||
var |
|||
c = 1 |
|||
tokens: seq[Token] |
|||
|
|||
proc advance: char = |
|||
c.inc |
|||
line[c-1] |
|||
|
|||
proc peek: char = |
|||
if c > line.high(): |
|||
'\0' |
|||
else: |
|||
line[c] |
|||
|
|||
proc scanNum: float = |
|||
let start = c - 1 |
|||
while peek() in {'0'..'9'}: |
|||
discard advance() |
|||
if peek() == '.': |
|||
discard advance() |
|||
while peek() in {'0'..'9'}: |
|||
discard advance() |
|||
if peek() == 'e': |
|||
discard advance() |
|||
if peek() == '-' or peek() == '+': |
|||
discard advance() |
|||
while peek() in {'0'..'9'}: |
|||
discard advance() |
|||
return parseFloat(line[start..c-1]) |
|||
|
|||
proc scanIdent: string = |
|||
let start = c - 1 |
|||
while peek() in {'a' .. 'z'}: |
|||
discard advance() |
|||
return line[start..c-1] |
|||
|
|||
t = scanNum() |
|||
while c < line.len(): |
|||
case advance(): |
|||
of '-': |
|||
tokens.add(Token(lineN: lineN, kind: ttFloat, floatVal: scanNum())) |
|||
of '0'..'9': |
|||
tokens.add(Token(lineN: lineN, kind: ttFloat, floatVal: scanNum())) |
|||
of 'a' .. 'z': |
|||
tokens.add(Token(lineN: lineN, kind: ttString, stringVal: scanIdent())) |
|||
else: |
|||
discard #whitespace or [ or ] |
|||
|
|||
var i = 0 |
|||
while i < tokens.len(): |
|||
try: |
|||
let name = tokens[i].stringVal |
|||
let x = tokens[i+1].floatVal |
|||
let y = tokens[i+2].floatVal |
|||
let vx = tokens[i+3].floatVal |
|||
let vy = tokens[i+4].floatVal |
|||
i += 5 |
|||
result.add(Particle(name: name, pos: vector(x, y), vel: vector(vx, vy))) |
|||
except: |
|||
echo "parsing crashed on line" & $tokens[i].lineN |
|||
raise newException(Defect, "Parser crash") |
|||
|
|||
inc lineN |
|||
|
|||
|
|||
proc vec2scr(v: Vector, x, y: var cint) = |
|||
let xper = (v.x - xMin) / (xMax - xMin) |
|||
let yper = (v.y - yMin) / (yMax - yMin) |
|||
x = cint(xper * windowWidth) |
|||
y = cint(yper * windowHeight) |
|||
|
|||
# traces |
|||
var trace: array[2000, Vector] |
|||
var traceIndex = 0 |
|||
|
|||
proc drawRectAt(v: Vector, renderer: RendererPtr) = |
|||
trace[traceIndex] = v |
|||
traceIndex.inc() |
|||
if traceIndex > trace.high(): |
|||
traceIndex = 0 |
|||
# how many % of the screen it is at |
|||
var x, y: cint |
|||
vec2scr(v, x, y) |
|||
if x < ballRadius or y < ballRadius: |
|||
return |
|||
if x + ballRadius > windowWidth or y + ballRadius > windowHeight: |
|||
return |
|||
x -= ballRadius |
|||
y -= ballRadius |
|||
let size = cint(2 * ballRadius) |
|||
var r = rect(x, y, size, size) |
|||
renderer.fillRect(r) |
|||
|
|||
proc drawArrow(start: Vector, delta: Vector, renderer: RendererPtr) = |
|||
var x, y: cint |
|||
var ex, ey: cint |
|||
vec2scr(start, x, y) |
|||
vec2scr(start+delta, ex, ey) |
|||
renderer.drawLine(x, y, ex, ey) |
|||
|
|||
proc drawTrace(renderer: RendererPtr) = |
|||
var x, y: cint |
|||
for v in trace: |
|||
vec2scr(v, x, y) |
|||
renderer.drawPoint(x, y) |
|||
|
|||
proc draw(renderer: RendererPtr, font: FontPtr, frame: int, speed: int) = |
|||
let line = lines[frame] |
|||
var t = 0.0 |
|||
let parts = parseLine(line, t) |
|||
|
|||
renderer.setDrawColor(0, 0, 0, 255) |
|||
renderer.clear() |
|||
|
|||
renderer.setDrawColor(255, 255, 255, 255) |
|||
for i in 0..parts.high: |
|||
let part = parts[i] |
|||
part.pos.drawRectAt(renderer) |
|||
#part.pos.drawArrow(part.vel, renderer) |
|||
|
|||
renderer.setDrawColor(0, 0, 255, 255) |
|||
#renderer.drawTrace() |
|||
|
|||
let color = color(255, 255, 255, 0) |
|||
let text = &"t = {t:>09.2f} frame = {frame} speed = {speed}" |
|||
let surface = ttf.renderTextSolid(font, text, color) |
|||
let texture = renderer.createTextureFromSurface(surface) |
|||
surface.freeSurface() |
|||
defer: texture.destroy() |
|||
let textWidth: int = text.len() * int(float(textHeight) * 0.6) |
|||
var r = rect( |
|||
cint((windowWidth - textWidth) div 2), |
|||
0, |
|||
cint(textWidth), |
|||
textHeight |
|||
) |
|||
renderer.copy(texture, nil, addr r) |
|||
|
|||
renderer.present() |
|||
|
|||
type SDLException = object of Defect |
|||
template sdlFailIf(cond: bool, reason: string) = |
|||
if cond: |
|||
raise newException(SDLException, reason & ", SDL error " & $getError()) |
|||
|
|||
proc main = |
|||
|
|||
sdlFailIf(not sdl2.init(INIT_VIDEO or INIT_TIMER or INIT_EVENTS)): |
|||
"SDL2 init required" |
|||
defer: sdl2.quit() |
|||
|
|||
let win = createWindow( |
|||
title = "vis", |
|||
x = SDL_WINDOWPOS_CENTERED, |
|||
y = SDL_WINDOWPOS_CENTERED, |
|||
w = windowWidth, |
|||
h = windowHeight, |
|||
flags = SDL_WINDOW_SHOWN |
|||
) |
|||
|
|||
sdlFailIf (win.isNil): "window couldn't be created" |
|||
defer: win.destroy() |
|||
|
|||
let renderer = createRenderer( |
|||
window = win, |
|||
index = -1, |
|||
flags = Renderer_Accelerated or Renderer_PresentVsync or Renderer_TargetTexture |
|||
) |
|||
|
|||
sdlFailIf renderer.isNil: "renderer couldn't be created" |
|||
defer: renderer.destroy() |
|||
|
|||
sdlFailIf(not ttfInit()): "TTF init failed" |
|||
defer: ttfQuit() |
|||
|
|||
let font = ttf.openFont("FreeMono.ttf", textHeight) |
|||
sdlFailIf(font.isNil): "font couldn't be created" |
|||
|
|||
var |
|||
running = true |
|||
frame = 0 |
|||
speed = 1 |
|||
|
|||
while running: |
|||
var event = defaultEvent |
|||
while pollEvent(event): |
|||
case event.kind: |
|||
of QuitEvent: |
|||
running = false |
|||
break |
|||
of KeyDown: |
|||
if event.key.keysym.scancode == SDL_SCANCODE_UP: |
|||
speed *= 2 |
|||
elif event.key.keysym.scancode == SDL_SCANCODE_DOWN: |
|||
if speed > 1 or speed < -1: |
|||
speed = speed div 2 |
|||
elif event.key.keysym.scancode == SDL_SCANCODE_LEFT: |
|||
speed = -speed |
|||
else: |
|||
discard |
|||
frame += speed |
|||
if frame < 1: |
|||
frame = 1 |
|||
if frame > lines.high(): |
|||
frame = lines.high() |
|||
draw(renderer, font, frame, speed) |
|||
|
|||
main() |
Loading…
Reference in new issue