Skip to content

Instantly share code, notes, and snippets.

@rklancer
Created February 28, 2012 19:22
Show Gist options
  • Save rklancer/1934535 to your computer and use it in GitHub Desktop.
Save rklancer/1934535 to your computer and use it in GitHub Desktop.
###
Stubs. Assume 'box', 'piston' and 'wrap' would be defined for you in the environment:
###
# Helper function so that a callback-accepting computed property can be defined simply
wrap = (f, cb) ->
(cb2) ->
if cb2
f (args...) -> cb2 cb args...
else
cb f()
bind = (target, method) ->
-> method.apply target, arguments
box =
height: -> 15
piston =
cbs: []
x: (val) ->
switch typeof val
when 'function'
@cbs.push val
wrap bind(this, @x), val
when 'undefined'
@_x
else
@_x = val
cb(val) for cb in @cbs
this
###
This is the stuff you would write:
###
# notice this doesn't require us to think about accepting and calling a callback, and still allows composition
area = piston.x (x) ->
x * box.height()
console.log area
areaTimes10 = area (a) ->
10*a
minusAreaTimes10 = areaTimes10 (a) ->
-a
minusAreaTimes10DividedBy10 = minusAreaTimes10 (a) ->
a/10
###
Example of the above properties working properly
###
# whenever piston.x changs, area changes, and areaTimes10's callback is notified
piston.x (val) ->
alert "async notification of piston.x = #{val}"
areaTimes10 (val) ->
alert "async notification of areaTimes10 = #{val}"
minusAreaTimes10 (val) ->
alert "async notification of minusAreaTimes10 = #{val}"
minusAreaTimes10DividedBy10 (val) ->
alert "async notification of minusAreaTimes10DividedBy10 = #{val}"
piston.x 1
alert "immediate calculation of x = #{piston.x()}"
alert "immediate calculation of areaTimes10 = #{areaTimes10()}"
alert "immediate calculation of minusAreaTimes10 = #{minusAreaTimes10()}"
alert "immediate calculation of minusAreaTimes10DividedBy10 = #{minusAreaTimes10DividedBy10()}"
setTimeout (-> piston.x 5), 1000
# Using this pattern you should be able to do things like:
#
# plot
# x: piston.x (newX) -> startX - newX
# y: area
#
# and still have the plot add a new (x, y) value automaticaly whenever piston.x changes
# Programmatically set up atoms in a box, in a deferred/functional fashion so that the "setup formula"
# can be reused for ensemble averaging, etc.
# run a model with default setup, say, '100 atoms in a box'
run()
# 'run' immediately runs the model specified by the hash, and returns a context so you can access the
# running model.
run
width: 10
height: 10
n: 100
# or:
m = model
width: 10
height: 100,
molecules:
mass: 1
charge: -1
n: 50
,
mass: 2
charge: 1
n: 50
run m
# return value of 'run':
{ index: 1, # so runs[1] is this run, in case you didn't save a reference to it when you ran it.
time: [Function],
width: [Function],
#...
}
r = run
width: 10
leftWall: 2
rightWall: 8
n: 100
# Add a piston to the box
modelWithPiston = model
height: 10
leftWall: 0
features: model.piston(x: 10) # or features: model.piston 'vertical', x: 10
r1 = run modelWithPiston
r2 = run modelWithPiston
# Easily specify to set up an ensemble of models of which 0, 1 or more can be watched "live".
ensembleOf10Models = ensemble 10,
width: 10,
height: 10,
n: 50
run ensembleOf10Models
ensembleWithPistons1 = ensemble 10, modelWithPiston
ensembleWithPistons2 = ensemble 10, r1.model()
# (As an optimization, allow models in an ensemble to "forget" their history. For example, this would be
# good if you wanted to take a measurement like "over a large ensemble of models, and for a given t, how
# many atoms with x-velocity v_x(i) <= v_x < v_x(i+1) are within vx_(i)*t of the piston" -- you might not
# want the overhead of having every snapshot remember its past states)
ensembleOf100Models = ensemble 100,
width: 10,
height: 10,
n: 50,
rememberHistory: false
# Allow calculating the force on the walls and piston (in a callback fashion, so that it can be used for
# a live update)
r = runs[0] # r is now a currently-running model
force = r.forceOn 'piston'
# update force display in real time:
force (f) -> $('#force-display').text("#{f} fN")
# just get the force at this tick
force()
2.123811923123
r.stop()
$('#go-button').click -> r.start()
# Allow plotting a value (such as the force) over time
plot x: r.time, y: force
# or just:
plot force # default is y value is simulation time from model associated with force
# Allow refining the plot by increasing the ensemble size after the fact:
# - set up model with some ensemble size (perhaps 1)
# - plot the value & watch it update live
# - stop the plot
# - increase the ensemble size & watch the *existing* plot "smooth out" as more models are calculated
# and added to the ensemble average
# - step ensemble size up or down as needed
# - start simulation from where paused, now with different ensemble size
r = run
width: 10
height: 10
n: 100
force = r.forceOn 'walls'
p = plot force
# after some time
r.stop()
r.ensemble.resize 100, by: 10, -> p.update() # callback when ensemble size changes
# after some time
r.ensemble.stopResize()
r.ensemble.size()
30
# Then
r.start()
# Allow moving the piston by delta x in n timesteps, and record the average force on the piston during
# the move.
r = run
height: 10
leftWall: 0
features: piston x: 10 # or features: [piston 'vertical', x: 10]
n: 100
pst = r.features.piston # or r.features[0]; name 'piston' is inferred from value returned by piston()
dx = 0.01
start = 10
end = 9
for x in [start..end] by -dx
# implicitly async? (push 100 updates to a list)
pst.update x: x
p = plot pst.x, p.forceOn 'piston'
# Allow calculating simulation area when piston 'x' changes
area = (cb) ->
if cb
pst.x (x) -> cb x * r.height()
else
pst.x() * r.height()
area() # area right now
9.90
plot pst.x, area # plot updates with new values of x
plot
x: pst.x (x) -> x + 10 # see cbpattern.coffee
y: area
pst.x (x) ->
# Allow calculating U.
r.internalEnergy()
# Allow calculating P
r.pressure()
# Now it should be easy to plot dU vs PdA + AdP, also dU vs Fdx, also PA^2 (which should be constant for
# adiabatic expansion/compression in 2D)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment