Home > Software engineering >  How to combine simulated animation with graphs in CodeWorld?
How to combine simulated animation with graphs in CodeWorld?

Time:10-23

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?

CodePudding user response:

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

  • it’d itself use activityOf of course
  • it’d keep track of the world state
  • it’d keep track of time
  • it’d keep track of the the recent graph values
  • maybe it splits incoming TimePassing in two, if needed, to sample the graph at the right intervals.
  • and it’d compose the Picture provided by the wrapped activity with the graph

Sounds like a fun exercise. And now I’d have a generic function that I can use with many graph-drawing simulations.

CodePudding user response:

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!

program = activityOf(initial, change, tank_water)
composedOf = pictures

-- Initial values of the total state, i.e. state_and_table
initial(rs) = state_and_table
state_and_table = (time_0, h_0, table_1_0, table_2_0, table_3_0, table_4_0, table_5_0)
time_0 = 0
h_0 = 7
table_1_0 = 0
table_2_0 = 0
table_3_0 = 0
table_4_0 = 0
table_5_0 = 0

-- Functions to read elements of state_and_table and from a tuple
time_of(time_value,_,_,_,_,_,_) = time_value
h_of(_,h_value,_,_,_,_,_) = h_value
table_1_of(_,_,table_value,_,_,_,_) = table_value
table_2_of(_,_,_,table_value,_,_,_) = table_value
table_3_of(_,_,_,_,table_value,_,_) = table_value
table_4_of(_,_,_,_,_,table_value,_) = table_value
table_5_of(_,_,_,_,_,_,table_value) = table_value

second_of(_,x_value) = x_value
      
-- Parameters related to the water flow
g = 9.81
area = 0.02

-- Parameters of the tank
width = 3
height = 8
position = -5

-- Inlet and outlet flow
qin = 0.0
qout(h) = area*sqrt(2*g*max(h,0))

-- Change of state_and_table 
change(state_and_table, TimePassing(dt)) = 

   -- Update time
   (time_of(state_and_table)   dt,

   -- Physical state equation using Euler approximation 
   max(h_of(state_and_table),0) - qout(h_of(state_and_table))*dt   qin*dt, 

   -- Event of recording state to table at predfined times
   table_1_update(state_and_table),
   table_2_update(state_and_table),
   table_3_update(state_and_table),
   table_4_update(state_and_table),
   table_5_update(state_and_table))

-- Default equation
change(state_and_table, other) = state_and_table

-- Events of recording state to table at given times
table_1_update(state_and_table) 
 | time_of(state_and_table) > 0  && table_1_of(state_and_table)==0 = h_of(state_and_table)
 | otherwise = table_1_of(state_and_table)

table_2_update(state_and_table) 
 | time_of(state_and_table) > 15  && table_2_of(state_and_table)==0 = h_of(state_and_table)
 | otherwise = table_2_of(state_and_table)

table_3_update(state_and_table) 
   | time_of(state_and_table) > 30  && table_3_of(state_and_table)==0 = h_of(state_and_table)
 | otherwise = table_3_of(state_and_table)

table_4_update(state_and_table) 
 | time_of(state_and_table) > 45 && table_4_of(state_and_table)==0 = h_of(state_and_table)
 | otherwise = table_4_of(state_and_table)

table_5_update(state_and_table) 
 | time_of(state_and_table) > 60  && table_5_of(state_and_table)==0 = h_of(state_and_table)
 | otherwise = table_5_of(state_and_table)

-- Animation of system
tank_water(state_and_table) = composedOf [tank, water(h_of(state_and_table)), 
                                          graph(state_and_table), coordinatePlane]  

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 = 15s etc
graph(state_and_table) = polyline [ p | p <-
          [(0,  table_1_of(state_and_table)),
          (5/2,  table_2_of(state_and_table)),
          (10/2, table_3_of(state_and_table)),
          (15/2, table_4_of(state_and_table)),
          (20/2, table_5_of(state_and_table))],
          second_of(p) /=0]
  • Related