Accessing simulation data
A single run generates a SamplePath object which is a recursive array provided by RecursiveArrayTools.jl.
sample_path = simulate(state, model, SortingDirect(), tfinal = 100.0, save_points = 0:25:100.0)
sample_patht: 5-element Array{Float64,1}:
0.0
25.0
50.0
75.0
100.0
x: 5-element Array{Array{Int64,1},1}:
[10, 0, 0, 0, 0]
[3, 7, 1, 9, 61]
[1, 9, 0, 13, 102]
[1, 9, 0, 11, 101]
[1, 9, 1, 12, 118]Access the state at the first time point:
sample_path[1]5-element Array{Int64,1}:
10
0
0
0
0Access the third component at the second time point:
sample_path[3,2]1Access entire history for the third and fourth components:
sample_path[[3,4], :]2×5 Array{Int64,2}:
0 1 0 0 1
0 9 13 11 12Running multiple simulations generates an Ensemble, a collection of SamplePath objects; that is, Vector{SamplePath}. These objects also support indexing:
# ensemble of 10 trajectories
ensemble = [simulate(state, model, SortingDirect(), tfinal = 100.0, save_points = 0:25:100.0) for _ in 1:10];10-element Array{SamplePath{Int64,2,Array{Array{Int64,1},1},Array{Float64,1}},1}:
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [4, 6, 2, 12, 96], [0, 10, 1, 36, 265], [1, 9, 0, 30, 277], [0, 10, 0, 25, 287]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [0, 10, 1, 17, 159], [0, 10, 0, 15, 159], [2, 8, 0, 13, 164], [0, 10, 0, 15, 158]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [9, 1, 0, 1, 6], [1, 9, 1, 13, 114], [1, 9, 0, 11, 114], [0, 10, 1, 20, 187]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [7, 3, 0, 1, 31], [2, 8, 0, 3, 24], [1, 9, 0, 12, 49], [3, 7, 0, 11, 51]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [3, 7, 0, 2, 9], [7, 3, 0, 5, 11], [6, 4, 0, 4, 10], [7, 3, 2, 12, 22]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [2, 8, 1, 10, 47], [0, 10, 0, 15, 116], [1, 9, 0, 24, 111], [0, 10, 0, 11, 114]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [6, 4, 0, 3, 1], [3, 7, 0, 9, 33], [2, 8, 0, 10, 31], [5, 5, 0, 11, 33]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [0, 10, 1, 13, 55], [1, 9, 1, 21, 181], [2, 8, 0, 20, 248], [0, 10, 0, 23, 241]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [0, 10, 1, 13, 158], [1, 9, 0, 16, 160], [0, 10, 0, 11, 160], [1, 9, 0, 9, 160]]
t: [0.0, 25.0, 50.0, 75.0, 100.0]
x: Array{Int64,1}[[10, 0, 0, 0, 0], [0, 10, 1, 14, 102], [0, 10, 0, 18, 133], [1, 9, 0, 13, 135], [0, 10, 0, 10, 132]]Retrieve an individual sample path:
ensemble[2]t: 5-element Array{Float64,1}:
0.0
25.0
50.0
75.0
100.0
x: 5-element Array{Array{Int64,1},1}:
[10, 0, 0, 0, 0]
[0, 10, 1, 17, 159]
[0, 10, 0, 15, 159]
[2, 8, 0, 13, 164]
[0, 10, 0, 15, 158]Index into a SamplePath:
ensemble[2][[3,4], :]2×5 Array{Int64,2}:
0 1 0 0 0
0 17 15 13 15Working with DataFrames
Both SamplePath and Ensemble implement the IterableTables.jl interface. This means that they act as data sources that can be converted into any supported data sink. For example, you can convert an Ensemble into a DataFrame:
using DataFrames
DataFrame(ensemble)| trial | t | X1 | X2 | X3 | X4 | X5 | |
|---|---|---|---|---|---|---|---|
| Int64 | Float64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | 1 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 2 | 1 | 25.0 | 4 | 6 | 2 | 12 | 96 |
| 3 | 1 | 50.0 | 0 | 10 | 1 | 36 | 265 |
| 4 | 1 | 75.0 | 1 | 9 | 0 | 30 | 277 |
| 5 | 1 | 100.0 | 0 | 10 | 0 | 25 | 287 |
| 6 | 2 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 7 | 2 | 25.0 | 0 | 10 | 1 | 17 | 159 |
| 8 | 2 | 50.0 | 0 | 10 | 0 | 15 | 159 |
| 9 | 2 | 75.0 | 2 | 8 | 0 | 13 | 164 |
| 10 | 2 | 100.0 | 0 | 10 | 0 | 15 | 158 |
| 11 | 3 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 12 | 3 | 25.0 | 9 | 1 | 0 | 1 | 6 |
| 13 | 3 | 50.0 | 1 | 9 | 1 | 13 | 114 |
| 14 | 3 | 75.0 | 1 | 9 | 0 | 11 | 114 |
| 15 | 3 | 100.0 | 0 | 10 | 1 | 20 | 187 |
| 16 | 4 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 17 | 4 | 25.0 | 7 | 3 | 0 | 1 | 31 |
| 18 | 4 | 50.0 | 2 | 8 | 0 | 3 | 24 |
| 19 | 4 | 75.0 | 1 | 9 | 0 | 12 | 49 |
| 20 | 4 | 100.0 | 3 | 7 | 0 | 11 | 51 |
| 21 | 5 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 22 | 5 | 25.0 | 3 | 7 | 0 | 2 | 9 |
| 23 | 5 | 50.0 | 7 | 3 | 0 | 5 | 11 |
| 24 | 5 | 75.0 | 6 | 4 | 0 | 4 | 10 |
| 25 | 5 | 100.0 | 7 | 3 | 2 | 12 | 22 |
| 26 | 6 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 27 | 6 | 25.0 | 2 | 8 | 1 | 10 | 47 |
| 28 | 6 | 50.0 | 0 | 10 | 0 | 15 | 116 |
| 29 | 6 | 75.0 | 1 | 9 | 0 | 24 | 111 |
| 30 | 6 | 100.0 | 0 | 10 | 0 | 11 | 114 |
| 31 | 7 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 32 | 7 | 25.0 | 6 | 4 | 0 | 3 | 1 |
| 33 | 7 | 50.0 | 3 | 7 | 0 | 9 | 33 |
| 34 | 7 | 75.0 | 2 | 8 | 0 | 10 | 31 |
| 35 | 7 | 100.0 | 5 | 5 | 0 | 11 | 33 |
| 36 | 8 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 37 | 8 | 25.0 | 0 | 10 | 1 | 13 | 55 |
| 38 | 8 | 50.0 | 1 | 9 | 1 | 21 | 181 |
| 39 | 8 | 75.0 | 2 | 8 | 0 | 20 | 248 |
| 40 | 8 | 100.0 | 0 | 10 | 0 | 23 | 241 |
| 41 | 9 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 42 | 9 | 25.0 | 0 | 10 | 1 | 13 | 158 |
| 43 | 9 | 50.0 | 1 | 9 | 0 | 16 | 160 |
| 44 | 9 | 75.0 | 0 | 10 | 0 | 11 | 160 |
| 45 | 9 | 100.0 | 1 | 9 | 0 | 9 | 160 |
| 46 | 10 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 47 | 10 | 25.0 | 0 | 10 | 1 | 14 | 102 |
| 48 | 10 | 50.0 | 0 | 10 | 0 | 18 | 133 |
| 49 | 10 | 75.0 | 1 | 9 | 0 | 13 | 135 |
| 50 | 10 | 100.0 | 0 | 10 | 0 | 10 | 132 |
Each component of the state vector appears as a column, along with trajectory trial and timestamp t columns. If the conversion does not produce the expected result, one may be able to force the correct behavior using the tablefy function:
import BioSimulator: tablefy
DataFrame(tablefy(sample_path)) # DataFrame(sample_path) is currently incorrect| t | X1 | X2 | X3 | X4 | X5 | |
|---|---|---|---|---|---|---|
| Float64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | 0.0 | 10 | 0 | 0 | 0 | 0 |
| 2 | 25.0 | 3 | 7 | 1 | 9 | 61 |
| 3 | 50.0 | 1 | 9 | 0 | 13 | 102 |
| 4 | 75.0 | 1 | 9 | 0 | 11 | 101 |
| 5 | 100.0 | 1 | 9 | 1 | 12 | 118 |
Saving simulation results
Because SamplePath and Ensemble support iteration, you can save simulation data directly using Julia's I/O interface.
Example here
The easiest approach takes advantage of IterableTables.jl:
using CSV
CSV.write(file, result, delim = '\t')Obtaining summary statistics
Summary statistics for ensembles are supported directly:
using Statistics
mean(ensemble)
std(result)
var(result)