Created
February 28, 2012 19:22
-
-
Save rklancer/1934535 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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