System
In Cropbox, a system is a unit of model component that contains a collection of variables. The framework guarantees that the most current state of a variable is accessible by another variable that depends on said variable, provided that they are within the same system. To ensure a correct propagation of variable states, the system must have a linear order of computation that satisfies all the requirements for dependency imposed by variable declarations. Any inconsistency caused by a cyclic dependency between variables stops code generation and results in an error. This is intentional, as we want to avoid such logical errors from going through unnoticed.
Once a system is defined, its structure is fixed and variables cannot be added or removed. The variables themselves, however, can still be updated throughout time steps. Variables declared in another system can be also accessed if the entire system holding dependent variables has already been updated. This is done by declaring an external system as a member of another system.
Creating a System
A system in Cropbox is created through the Cropbox-specific macro, @system
.
@system name[{patches..}][(mixins..)] [<: type] [decl] -> Type{<:System}
@system
declares a new system called name
, with new variables declared in decl
block using a custom syntax. mixins
allow specifications of existing systems to be used for the new system. patches
may provide type substitution and/or constant definition needed for advanced use.
Example
Here is an example of what a simple system may look like.
@system begin
i => 1 ~ preserve
a => 0.1 ~ preserve(parameter)
r(a, x) => a*x ~ track
x(r) ~ accumulate(init = i)
end
In this system, we declared four variables.
i
: variable containing initial value ofx
which never changes (preserved)a
: variable containing constant parameter of exponential growthr
: rate variable which needs to be calculated or tracked every time stepx
: state variable which accumulates by rater
over time with initial valuei
We can use the Julia macro @macroexpand
to see the expression generated by the @system
macro.
Mixin
A mixin
is a system that is included as a part another system. While each system implements its own set of variables, these variables can be linked with variables from other systems through the use of mixins. Controller is a mixin required to instantiate a system.
Example
Here is an example where the system S3
is declared with systems S1
and S2
as mixins.
@system S1 begin
a => 1 ~ preserve(parameter)
b(a) => 2a ~ track
end
@system S2 begin
a => 2 ~ preserve(parameter)
b(a, c) => a*c ~ track
c => 1 ~ preserve
end
@system S3(S1, S2, Controller) begin
d(a) => 3a ~ preserve
end
instance(S3)
S3 | |||
context | = | <Context> | |
config | = | <Config> | |
a | = | 2.0 | |
b | = | 2.0 | |
c | = | 1.0 | |
d | = | 6.0 |
The order of mixins when creating a system is significant. When two variables from two different mixins share a name, the variable from the latter mixin in the system declaration will take priority over the first.
Context
Whenever a system is constructed in Cropbox, an internal variable named context
referencing to an instance of a Context
system is included by default. The purpose of the Context
system is to manage the time and configuration of a system.
This is what the Context
system looks like:
@system Context begin
context ~ ::Nothing
config ~ ::Config(override)
clock(config) ~ ::Clock
end
The variables config
and clock
, referencing to the systems Config
and Clock
respectively, are necessary for system instantiation and thus included in every new system by default.
Clock
Within the Context
system, there is a clock
variable referring to the Clock
system. The Clock
system is responsible for keeping track of time-related variables, namely init
, step
, time
, and tick
.
This is what the Clock
system looks like:
abstract type Clock <: System end
timeunit(::Type{<:Clock}) = u"hr"
@system Clock{timeunit = timeunit(Clock)} begin
context ~ ::Nothing
config ~ ::Config(override)
init => 0 ~ preserve(unit=timeunit, parameter)
step => 1 ~ preserve(unit=timeunit, parameter)
time => nothing ~ advance(init=init, step=step, unit=timeunit)
tick => nothing ~ advance::int
end
time
is an advance
variable which is essentially an accumulate
variable tailored for keeping time of simulation. By default, time
starts at hour 0 and increases by 1-hour intervals. tick
is another time variable that is responsible for keeping track of the number of updates performed. As a result, context.clock.time
and context.clock.tick
are often used as the index for the x-axis of plots and visualizations. The step
variable is determines the time-step intervals of simulation.
Config
Unlike the Clock
system, the Config
referred to by the config
variable in Context
is not a system. Config
is a configuration object structured as a nested dictionary or hash table to store user-defined parameter values as a triplet of system - variable - value. When a configuration object containing specified parameter values are provided to a instantiation of a system, the corresponding variables in the system with the tag parameter
will have the configuration values plugged in. Read more about configurations here.
Controller
An instance of context and configuration provided to an instance of a new system is usually sourced by a parent system that holds a variable referring to that system. However, because there is no parent system for the instantiation of the first system, context and configuration need to be supplied elsewhere. Controller
is a pre-built system of Cropbox made to handle such issues by creating an instance of Context by itself.
This is what the Controller
system looks like:
@system Controller begin
config ~ ::Config(override)
context(config) ~ ::Context(context)
end
The Config
object referred to by the config
variable is overridden by a keyword argument (config)
of the system constructor instance()
and functions such as simulate()
and visualize()
. Therefore at least one (and usually only one) system is designated to possess Controller
as one of its mixins. In order to run an instance or a simulation of a system, Controller
must be included as a mixin. Unlike the system Context
, Controller must be explicitly declared as a mixin when declaring a system.
When you create a system that you want to instantiate, make sure to have Controller
as a mixin. You can also make a system instantiable by making a new system with the original system and Controller
as mixins.
Calendar
Calendar
is a system similar to the Clock
system. Calendar
provides time
and step
variables, but in the type of ZonedDateTime from TimeZones.jl. Much like Clock
, Calendar
is a pre-built Cropbox system, but Calendar
is not included by default as a variable reference like context
for the system Context
. Also, unlike Clock
, Calendar
is not embedded in Context
.
This is what the Calendar
system looks like:
@system Calendar begin
init ~ preserve::datetime(extern, parameter)
last => nothing ~ preserve::datetime(extern, parameter, optional)
time(t0=init, t=context.clock.time) => t0 + convert(Cropbox.Dates.Second, t) ~ track::datetime
date(time) => Cropbox.Dates.Date(time) ~ track::date
step(context.clock.step) ~ preserve(u"hr")
stop(time, last) => begin
isnothing(last) ? false : (time >= last)
end ~ flag
count(init, last, step) => begin
if isnothing(last)
nothing
else
# number of update!() required to reach `last` time
(last - init) / step
end
end ~ preserve::int(round, optional)
end