Reputation: 1084
I play with CodeWorld and use the "entrypoint" activityOf() to simulate a physical system consisting of tanks. Here we have one tank with an outlet flow qout(h) that depends on the height of volume level. Of course, for serious simulation work one should use other software, but still fun to do here!
program = activityOf(initial, change, tank_water)
composedOf = pictures
-- Initial value and parameter
initial(rs) = 5
g = 9.81
area = 0.015
-- Inlet flow
qin = 0.0
-- Dynamics using Euler approximation
qout(h) = area*sqrt(2*g*max(h,0))
change(h, TimePassing(dt)) = max(h,0) - qout(h)*dt + qin*dt
change(h, other) = h
-- Animation of system
tank_water(h) = composedOf [tank, water(h), graph, coordinatePlane]
tank = translated(thickRectangle(width,height,0.2),position,4)
width = 3
height = 8
position = -5
water(h) = translated(colored(solidPolygon [(-width/2, 0), (-width/2, h), (width/2, h), (width/2,0)],
light(blue)),position,0)
-- Graph of evolution of h(t) - here used monitored values and about 5 seconds between each data point
graph = polyline [(0,5), (1,3.7), (2,2.3), (3,1.3), (4,0.7), (5,0.2), (6, 0)]
For educational purpose I think it is good to combine an animation with a graph in a diagram that shows how the height develops over time. Here I entered in the code a "fake" graph since it was easy to measure up (and I could also enter the analytical solution to make the graph adapt to parameters).
I wonder how one can include a subsystem that collect data from the animation and present it in the graph as the simulation proceed. Any ideas?
One cumbersome idea I think of is to extend the model state that include the measured points to be collected during the simulation. We can beforehand say that we collect 10 samples of h and t with a time distance of say 5 seconds and we expand the graph as the data comes in.
Another idea is to somehow "add-on" some general data logger to the activityOf()-program that store data on a file that you afterwards can study with any software. Perhaps such a logger is already available in the Haskell-environment?
But I am very uncertain of how to do this and here are perhaps some better and more general way to do it?
Upvotes: 2
Views: 104
Reputation: 1084
I made a solution "the cumbersome" way. Wish I could structure it better from the input I have got. Appreciate suggestions for improvements so that the code could be shorter. I feel clumsy with tuple-handling. Further creation of the table and later graph could be more separated from the tank-process. That would facilitate re-use to other simulation tasks in CodeWorld. Improvements welcome!
--
I have now updated the code and made it shorter but also added more data points in the table. The state_and_table must be a tuple in CodeWorld and handling of tuples not possible with an index. Further, you can now change the pump rate qin with arrows up and down.
Expand to two tanks (or more) make it more of a challenge to control the water height and leave that as an exercise to you! Enjoy!
Link to run https://code.world/#P2uQaw2KBSbyspnQSn5E4Gw
program = activityOf(initial, change, tank_water)
-- Initial values of the total state, i.e. state_and_table
initial(rs) = state_and_table
state_and_table = (time_0, h_0, u_0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
time_0 = 0
h_0 = 7
u_0 = 0
-- Functions to read elements of state_and_table
time_of(time_value,_,_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_) = time_value
h_of(_,h_value,_,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_) = h_value
u_of(_,_,u_value,_,_,_,_,_,_,_,_,_,_,
_,_,_,_,_,_,_,_,_,_) = u_value
table_entry((_,_,_,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,
x11,x12,x13,x14,x15,x16,x17,x18,x19,x20), k)
| k==1 = x1
| k==2 = x2
| k==3 = x3
| k==4 = x4
| k==5 = x5
| k==6 = x6
| k==7 = x7
| k==8 = x8
| k==9 = x9
| k==10 = x10
| k==11 = x11
| k==12 = x12
| k==13 = x13
| k==14 = x14
| k==15 = x15
| k==16 = x16
| k==17 = x17
| k==18 = x18
| k==19 = x19
| k==20 = x20
-- Events of recording state to table at the following times
table_time = [ 5*t-5 | t <- [1..20]]
-- Parameters related to the water flow
g = 9.81
area = 0.025
-- Parameters of the tank
width = 5
height = 8
position = -5
-- Inlet and outlet flow
qin(u) = max(u, 0)
qout(h) = area*sqrt(2*g*max(h,0)) -- Bernoulli's physical model
-- Change of state_and_table
change(state_and_table, TimePassing(dt)) =
-- Update time
(time_of(state_and_table) + dt,
-- Physical state equation -- Euler approximation of differantial equation
max(h_of(state_and_table),0) - qout(h_of(state_and_table))*dt + qin(u_of(state_and_table))*dt,
-- Control variable u
u_of(state_and_table),
-- Event of recording state to table at predfined times
table_update(state_and_table,1),
table_update(state_and_table,2),
table_update(state_and_table,3),
table_update(state_and_table,4),
table_update(state_and_table,5),
table_update(state_and_table,6),
table_update(state_and_table,7),
table_update(state_and_table,8),
table_update(state_and_table,9),
table_update(state_and_table,10),
table_update(state_and_table,11),
table_update(state_and_table,12),
table_update(state_and_table,13),
table_update(state_and_table,14),
table_update(state_and_table,15),
table_update(state_and_table,16),
table_update(state_and_table,17),
table_update(state_and_table,18),
table_update(state_and_table,19),
table_update(state_and_table,20))
-- Key input
change(state_and_table, KeyPress("Up")) =
(time_of(state_and_table),
h_of(state_and_table),
u_of(state_and_table)+0.1,
table_update(state_and_table,1),
table_update(state_and_table,2),
table_update(state_and_table,3),
table_update(state_and_table,4),
table_update(state_and_table,5),
table_update(state_and_table,6),
table_update(state_and_table,7),
table_update(state_and_table,8),
table_update(state_and_table,9),
table_update(state_and_table,10),
table_update(state_and_table,11),
table_update(state_and_table,12),
table_update(state_and_table,13),
table_update(state_and_table,14),
table_update(state_and_table,15),
table_update(state_and_table,16),
table_update(state_and_table,17),
table_update(state_and_table,18),
table_update(state_and_table,19),
table_update(state_and_table,20))
change(state_and_table, KeyPress("Down")) =
(time_of(state_and_table),
h_of(state_and_table),
u_of(state_and_table)-0.1,
table_update(state_and_table,1),
table_update(state_and_table,2),
table_update(state_and_table,3),
table_update(state_and_table,4),
table_update(state_and_table,5),
table_update(state_and_table,6),
table_update(state_and_table,7),
table_update(state_and_table,8),
table_update(state_and_table,9),
table_update(state_and_table,10),
table_update(state_and_table,11),
table_update(state_and_table,12),
table_update(state_and_table,13),
table_update(state_and_table,14),
table_update(state_and_table,15),
table_update(state_and_table,16),
table_update(state_and_table,17),
table_update(state_and_table,18),
table_update(state_and_table,19),
table_update(state_and_table,20))
-- Default equation
change(state_and_table, other) = state_and_table
table_update(state_and_table, k)
| time_of(state_and_table) > table_time # k && table_entry(state_and_table,k)==0 = h_of(state_and_table)
| otherwise = table_entry(state_and_table, k)
-- Animation of system
composedOf = pictures
tank_water(state_and_table) = composedOf [headline, pump(qin(u_of(state_and_table))),
tank, water(h_of(state_and_table)),
graph(state_and_table),
coordinatePlane]
headline = translated(lettering("Tank dynamics"),5,7.5)
pump(qin) = translated(composedOf [lettering("- pump rate ="), translated(lettering(printed(qin)),3.7,0)], 4.5,6.5)
tank = translated(thickRectangle(width,height,0.2),position,4)
water(h) = translated(colored(solidPolygon [(-width/2, 0), (-width/2, h), (width/2, h), (width/2,0)],
light(blue)), position,0)
-- Graph of evolution of h(t) - note time scale is 1 = 10 s etc
scale = 1/10
graph(state_and_table) = colored(polyline [(scale*table_time#k, table_entry(state_and_table, k))
| k <- [1..length(table_time)], table_entry(state_and_table, k) /=0],
light(blue))
Upvotes: 0
Reputation: 25763
I’ll respond using syntax and types form the “regular” Haskell environment, https://code.world/haskell.
The type of a activityOf
is:
activityOf ::
world ->
(Event -> world -> world) ->
(world -> Picture) ->
IO ()
and as you observe, there is no built in way to log values and graph them. Adding that to activityOf
seems wrong (there are so many thing we might want to add).
But what type would you want for such a function? Maybe the following:
activityWithGraphOf ::
world ->
(Event -> world -> world) ->
(world -> Picture) ->
(world -> Double) ->
IO ()
With this abstraction in mind, I would go and implement that function
activityOf
of courseworld
stateTimePassing
in two, if needed, to sample the graph at the right intervals.Picture
provided by the wrapped activity with the graphSounds like a fun exercise. And now I’d have a generic function that I can use with many graph-drawing simulations.
Upvotes: 1