Simulation

There are four different functions in Cropbox for model simulation. For information regarding syntax, please check the reference.

Tip

When running any of these functions, do not forget to include Controller as a mixin for the system.

instance()

The instance() function is the core of all simulative functions. To run any kind of simulation of a system, the system must first be instantiated. The instance() function simply makes an instance of a system with an initial condition specified by a configuration and additional options.

Example

@system S(Controller) begin
    a ~ advance
    b => 1 ~ preserve(parameter)
    c(a, b) => a*b ~ track
end

s = instance(S)
S
context=<Context>
config=<Config>
a=0.0
b=1.0
c=0.0

After creating an instance of a system, we can simulate the system manually, using the update!() function.

update!(s)
S
context=<Context>
config=<Config>
a=1.0
b=1.0
c=1.0
update!(s)
S
context=<Context>
config=<Config>
a=2.0
b=1.0
c=2.0

We can also specify a configuration object in the function to change or fill in parameter values of the system.

c = @config(:S => :b => 2)

instance(S; config=c)
S
context=<Context>
config=<Config>
a=0.0
b=2.0
c=0.0

simulate()

simulate() runs a simulation by creating an instance of a specified system and updating it a specified number of times in order to generate an output in the form of a DataFrame. You can think of it as a combination of the instance() and the update!() function where each row of the DataFrame represents an update.

Example

@system S(Controller) begin
    a ~ advance
    b => 1 ~ preserve(parameter)
    c(a, b) => a*b ~ track
end

 simulate(S; stop=2)
3×4 DataFrame
Rowtimeabc
Quantity…Float64Float64Float64
10.0 hr0.01.00.0
21.0 hr1.01.01.0
32.0 hr2.01.02.0

Just like the instance() function, we can add a configuration object to change or fill in the parameter values.

c = @config(:S => :b => 2)

simulate(S; config=c, stop=2)
3×4 DataFrame
Rowtimeabc
Quantity…Float64Float64Float64
10.0 hr0.02.00.0
21.0 hr1.02.02.0
32.0 hr2.02.04.0


Tip

When using the simulate() function, it is recommended to always include an argument for the stop keyword unless you only want to see the initial calculations.

evaluate()

The evaluate() function compares two datasets with a choice of evaluation metric. You can compare two DataFrames (commonly the observation and the estimation data), or a System and a DataFrame, which will automatically simulate the system to generate a DataFrame that can be compared. Naturally, if you already have a DataFrame output from a previous simulation, you can use the first method.

Two DataFrames

obs = DataFrame(time = [1, 2, 3]u"hr", a = [10, 20, 30]u"g")

est = DataFrame(time = [1, 2, 3]u"hr", a = [11, 19, 31]u"g", b = [12, 22, 28]u"g")

evaluate(obs, est; index = :time, target = :a, metric = :rmse)
1.0 g

If the column names are different, you can pair the columns in the target argument to compare the two.

evaluate(obs, est; index = :time, target = :a => :b)
2.0 g

System and a DataFrame

@system S(Controller) begin
    p => 10 ~ preserve(parameter, u"g/hr")
    t(context.clock.time) ~ track(u"hr")
    a(p, t) => p*t ~ track(u"g")
end

evaluate(S, est; target = :a, stop = 3)
1.0 g

calibrate()

calibrate() is a function used to estimate a set of parameters for a given system, that will yield a simulation as closely as possible to a provided observation data. A multitude of simulations are conducted using different combinations of parameter values specified by a range of possible values. The optimal set of parameters is selected based on the chosen evaluation metric (RMSE by default). The algorithm used is the differential evolution algorithm from BlackBoxOptim.jl. The function returns a Config object that we can directly use in model simulations.

Example

@system S(Controller) begin
           a => 0 ~ preserve(parameter)
           b(a) ~ accumulate
end

obs = DataFrame(time=10u"hr", b=200)

p = calibrate(S, obs; target=:b, parameters=:S => :a => (0, 100), stop=10)

Config for 1 system:

S
a=20.0