@system name[{patches..}][(mixins..)] [<: type] [decl] -> Type{<:System}

Declare a new system called name with new variables declared in decl block using a custom syntax. The resultant system is subtype of System or a custom type. mixins allows reusing specification of existing systems to be pasted into the declaration of new system. patches may provide type substitution and/or constant definition needed for advanced use.


name[(args..; kwargs..)][: alias] [=> expr] [~ [state][::type|<:type][(tags..)]]
  • name: variable name; usually short abbreviation.
  • args: automatically bound depending variables
  • kwargs: custom bound depending variables; used by call and integrate.
  • alias: alternative name; long description.
  • expr: state-specific code snippet; use begin-end block for multiple statements.
  • type: internal data type; default is Float64 for many, but not all, variables.
  • tags: state-specific options; unit, min/max, etc.


  • hold: marks a placeholder for variable shared between mixins.
  • wrap: passes a state variable to other fucnction as is with no unwrapping its value.
  • advance: manages a time-keeping variable; time and tick from Clock.
  • preserve: keeps initially assigned value with no further updates; constants, parameters.
  • tabulate: makes a two dimensional table with named keys; i.e. partitioning table.
  • interpolate: makes a curve function interpolated with discrete values; i.e. soil characteristic curve.
  • track: evaluates variable expression as is for each update.
  • remember: keeps tracking variable until a certain condition is met; essentially track turning into preserve.
  • provide: manages a table of time-series in DataFrame.
  • drive: fetches the current value from a time-series; maybe supplied by provide.
  • call: defines a partial function bound with some variables.
  • integrate: calculates integral using Gaussian method; not for time domain.
  • accumulate: emulates integration of rate variable over time; essentially Euler method.
  • capture: calculates difference between integration for each time step.
  • flag: sets a boolean flag; essentially track::Bool.
  • produce: attaches a new instance of system dynamically constructed; i.e. root structure growth.
  • bisect: solves nonlinear equation using bisection method; i.e. gas-exchange model coupling.
  • solve: solves polynomial equation symbolically; i.e. quadratic equations in photosynthesis model.


julia> @system S(Controller) begin
           a => 1 ~ preserve(parameter)
           b(a) ~ accumulate
@config c.. -> Config | Vector{Config}

Construct a set or multiple sets of configuration.

A basic unit of configuration for a system S is represented by a pair in the form of S => pv. System name S is expressed in a symbol. If actual type of system is used, its name will be automatically converted to a symbol.

A parameter name and corresponding value is then represented by another pair in the form of p => v. When specifiying multiple parameters, a tuple of pairs like (p1 => v1, p2 => v2) or a named tuple like (p1 = v1, p2 = v2) can be used. Parameter name must be a symbol and should indicate a variable declared with parameter tag as often used by preserve state variable. For example, :S => (:a => 1, :b => 2) has the same meaning as S => (a = 1, b = 2) in the same scope.

Configurations for multiple systems can be concatenated by a tuple. Multiple elements in c separated by commas implicitly forms a tuple. For example, :S => (:a => 1, :b => 2), :T => :x => 1 represents a set of configuration for two systems S and T with some parameters. When the same names of system or variable appears again during concatenation, it will be overriden by later ones in an order appeared in a tuple. For example, :S => :a => 1, :S => :a => 2 results into :S => :a => 2. Instead of commas, + operator can be used in a similar way as (:S => :a => 1) + (:S => :a => 2). Note parentheses placed due to operator precedence.

When multiple sets of configurations are needed, as in configs for simulate, a vector of Config is used. This macro supports some convenient ways to construct a vector by composing simpler configurations. Prefix operator ! allows expansion of any iterable placed in the configuration value. Infix operator * allows multiplication of a vector of configurations with another vector or a single configuration to construct multiple sets of configurations. For example, !(:S => :a => 1:2) is expanded into two sets of separate configurations [:S => :a => 1, :S => :a => 2]. (:S => :a => 1:2) * (:S => :b => 0) is multiplied into [:S => (a = 1, b = 0), :S => (a = 2, b = 0)].


julia> @config :S => (:a => 1, :b => 2)
Config for 1 system:
    a = 1
    b = 2
julia> @config :S => :a => 1, :S => :a => 2
Config for 1 system:
    a = 2
julia> @config !(:S => :a => 1:2)
2-element Vector{Config}:
julia> @config (:S => :a => 1:2) * (:S => :b => 0)
2-element Vector{Config}: