#head.html
EcolMod Lab
#header.html#boxscript#Think inside the box, one box at a time

xxx

The BoxScript language

A boxscript is a text file written in the BoxScript language . BoxScript was designed as a declarative language. You use it mainly to declare how your model is constructed (which building blocks it is made of and how they are related). You do not use it to define the behaviour of the building blocks themselves. That behaviour is coded in C++ as described in the BoxScript Developer section.

xxx

BoxScript design

A boxscript consists of boxes. A box comes equipped with input and outport ports, which forms the interface by which it can exchange data with other boxes (through their ports). In addition, you can equip a box with auxiliary ports in the boxscript, which work just like the inherent input ports. The only restriction is that you can never change the value of an output port from within a boxscript.

A boxscript has the following, hierarchic structure:

  • A box declaration contains zero or more port declarations followed by zero or more box declarations.
  • A box declaration always begins with a box class name optionally followed by an object name.
  • A port declaration begins with a period (for an input port) or an ampersand (for an auxilliary port) followed by a port name, an equal sign and an expression.
  • A comment runs from // to the end of the line.

The Hello world! boxscript exemplifies most of the BoxScript syntax:

// hello_world1.box⏷
Simulation sim {
  .steps = 3
  OutputText {
    &message = "Hello world!"
    .ports = .[message]
  }
}

You will notice that the contents of a box is embraced by curlies { } and that indeed an object name can be left out (the OutputText box is anonymous). Ports are assigned to an expression by an equal sign, and an expression can take many forms.

White space (space and lines) is used only to assist your eye; it has no effect otherwise (well, except, that a // comment runs to the end of the line). When you refer to an input port, such as .steps that input port must be among the input ports defined for the class (here the Simulation class). You can look up the input (and output) ports belonging to a class either through help, for example,

> help Simulation

or here in the documentation (e.g., Simulation).

Ports have a type, which is either defined in the C++ class (for input and output ports), or is deduced from the expression they are assigned to in the boxscript (for auxilliary ports). In the boxscript above, for example, the steps port is defined to be an integer in the Simulation class, and the message port is deduced to be a string.

xxx

Port types

A port is always typed. It holds a value of a certain type. For input and output ports, that type is determined by the C++ class. For auxiliary ports, their type is deduced from the expression they are assigned to. Most types exist both in a scalar form and a vector form. A scalar holds one value, a vector holds zero or more values of the same type. You cannot build composite types, e.g., vectors of vectors of integers. In fact, you will most of the time deal with scalar types.

The table below shows the names of the types available in BoxScript. Those are the type names used in the online BoxScript documentation (yes, that's right here) and in the response you get from the help command. The corresponding C++ types are only interesting, if you code your own C++ Box classes; you will notice that the Qt libraries are used for vectors, dates and times, while BareDate and Path are classes defined in the BoxScript Framework. The third column shows expressions that exemplify the different types. The c() notation refers to the BoxScript c function used to construct vectors.

BoxScript
type
C++
type
BoxScript
expression
bool bool TRUE or FALSE
vec_bool QVector<bool> c(TRUE, FALSE, FALSE)
int int 117
vec_int QVector<int> c(-3, 10, 7)
double double 3.1412
vec_double QVector<double> c(5, 2.3, 6)
string QString "weather.txt"
vec_string QVector<QString> c("Gila", "monster", 12)
date QDate 24/12/2021 or 2021/12/24 or /12/24/2021
vec_date QVector<QDate> c(13/07/2021, 2021/8/24, /3/20/2023)
time QTime 23:17 or 23:17:05 or 9:5 or 9:50:7
vec_time QVector<QTime> c(23:17, 13:17:05, 9:5)
datetime QDateTime 24/12/2021T13:14 or 03/02/2022
vec_datetime

QVector<QDateTime>

c(24/12/2021T13:14, 03/02/2022, 01/02/2021T14:50)
baredate BareDate 24/12 or /12/24
vec_baredate QVector<BareDate> c(24/12, 3/11, /11/7)
path Path sim[step] | calendar[output::*]
null

not applicable

sim[radius] (unresolved path)

The simple types are bool for boolean (true/false) ports, int for integer ports (whole numbers, negative included), double (real numbers) and string (text).

The various date and time types are true types. You should not put them in apostrophes. A value in apostrophes denote a string type. A date can be written in three different orders: day/month/year, year/month/day or /month/day/year. The latter (American-style) format is indicated by a leading slash. A year is always written in full (with four digits), while day<10 and month<10 can be written with or without a leading zero as you please. The baredate type is a date without a year. You can write it as day/month or /month/day and with leading zeros or not as for a full date.

Ports of the path type must de declared so in the C++ class. You cannot create an auxiliary port of the path type, since the type of the port will always be deduced from the type of the expression. In this example, the n port will be of int type because that's the type of sim[step].

Simulation sim {
  Box {
  &n = sim[step]  
  }
}

There is a whole vocabulary for paths which include the union operator | to combine separate paths into one. Because of that there is no need for a vector of paths. Such a type does not exist.

If a port of date, time, datetime or baredate type was not given an initial value in the class definition (i.e. in the C++ code), it will start out being null. Otherwise, the null type occurs if it has not yet been possible to determine the type of an auxiliary port. You can run this script for an example:

> run demo/port_types/null.box

The script contains a sequence of indirections so long that it takes some steps of simulation, before all references have become disentangled. Here is the boxscript contrived for this demonstration:

// null.box
Simulation sim {
  .steps = 5
  Box A {
    &a = B[a] + 1
  }
  Box B {
    &a = C[a] + 1
  }
  Box C {
    &a = D[a] + 1 
  }
  Box D {
    &a = E[a] + 1
  }
  Box E {
    &a = F[a] + 1
  }
  Box F {
    &a = 10
  }
  OutputText {
    .ports = *[a]
  }
}

Inspect the output by the head command and you will see that the first couple lines contain null values, which are always shown as the text null:

> head
...
 A.a  B.a C.a D.a E.a F.a iteration step
null null  13  12  11  10         1    0
null   14  13  12  11  10         1    1
  15   14  13  12  11  10         1    2
  15   14  13  12  11  10         1    3
  15   14  13  12  11  10         1    4
  15   14  13  12  11  10         1    5
>

If you've got null values in the first few lines of output then include an OutputSelector box with an appropriate value for its beginStep port.

xxx

Expressions

You write an expression on the right-hand side of the equal sign whereever you assign a value to a port. Here are some examples:

.aboveThreshold    = (Weather[temperature] > hibernation[threshold])
&tempInside        = Weather[temperature] + 2.5
.biomass           = 2300*mouse[density] + 6.7*rabbit[density]
&homeArea          = 3.1412*(homeRange[distance]/2)^2
.supplyDemandRatio = min(N[supply]/N[demand], P[supply]/P[demand]) legal??
&demand            = if (.[aboveThreshold] || weather[sunshine] > 500) then 7.6 else 0

To remind you that expressions can be assigned to both input and auxiliary ports, I have written every other line as either an input (with leading .) or an auxiliary (with a leading &) port. The expression in the first line will result in a value of bool type while the others are all of double type.

Expressions are formulated with the common arithmetic operators: +, -, *, and /. In addition, you can use ^ for exponentiation, && for logical and, || for logical or and ! for logical negation. Parentheses () work as expected. Expressions may also inclode functions and conditionals.

  • Scalar and vector operations
  • An expression may mix scalar and vector types. If more then one vector is present, they must be of the same size. If scalars are mixed with vectors, the result will be a vector. The principles reflect those of R. Here are examples showing the addition of scalar and vector integers:

    $$ \begin{split} 3+9&=12 \\[6pt] 3+\begin{bmatrix} 7 \\ 9 \\ 13 \end{bmatrix} &= \begin{bmatrix} 10 \\ 12 \\ 16 \end{bmatrix} \\[6pt] \begin{bmatrix} 7 \\ 9 \\ 13 \end{bmatrix} + \begin{bmatrix} -2 \\ -11 \\ 8 \end{bmatrix} &= \begin{bmatrix} 8 \\ -2 \\ 21 \end{bmatrix}\end{split} $$

    More text here...

  • Mixed-type operations and conversions
  • There are two cases where types can be mixed:

    • inside an expression
    • when the expression type differs from the port being assigned to

    In both cases, types will be converted to the most generic of the involved types or, if such a conversion would make no sense, you will get an error message. For instance, 3 (an int) can be converted to 3.0 (a double) or "3" (a string) but you cannot convert it to a date.

    This table lists the legal conversions:

    BoxScript type Conversions
    bool boolint (0 if FALSE else 1)
    booldouble (0.0 if FALSE else 1.0)
    boolstring ("0" if FALSE else "1")
    int intbool (FALSE if 0 otherwise TRUE)
    intdouble
    double doublebool (FALSE if 0.0 exactly otherwise TRUE)
    doubleint (rounded to nearest integer)
    doublestring
    string stringbool
    FALSE if "0" or "FALSE"
    TRUE if "1" or "TRUE"
    stringint
    stringdouble
    stringdate
    stringtime
    stringdatetime
    stringbaredate
    date dateint [1;366] (day of year)
    datedouble [1;366] (day of year)
    datestring (yyyy/mm/dd format)
    time timeint [0;23] (hour of the day)
    timedouble [0;24) (fractional hour of the day)
    timestring (HH:MM:SS format)
    datetime datetimeint [1:366] (day of year)
    datetimedouble [0;366] (fractional day of year)
    datetimestring (yyyy/mm/ddTHH:MM:SS format)
    baredate baredateint [1;366] (day of year)
    baredatedouble [1;366] (day of year)
    baredatestring (dd/mm format)

    Sections missing from the Expressions sections:

    • Operators
    • Functions
      • c
      • max
      • min
      • sum
    • Conditionals
    • Nodes?

    xxx

    Paths

  • Overview
  • butterfly/larva[inflow]
    

    Here, we've got three nodes: two boxes (butterfly and larva) and a port (inflow). A path like this my match zero or more ports in the boxscript.

  • Joker
  • The joker can be used in place of any node in the path:

    &eggs = *[eggs]
    &birdEggs = bird/*[eggs]

    It can be used to include all ports of a box. This is most useful when defining ports for output as in:

    OutputR {
      Box outputs {
        &lions = lion[density]
        &antelopes = antelope[density] 
      }
      PageR {
        PlotR {
          .ports = outputs[*]
        }
      }
    }

    This is an easy way to define the labels in your output. Here the labels on the plot will be lions and antelopes.

  • Anchors
  • A path will be matched against all nodes in the model unless it is anchored. You anchor a path by beginning it with a slash or one or two periods. The meaning is the same as you may know from file paths:

    • at the root /
    • at the box .
    • at the parent box ..

    A slash is a synonym for the root box, no matter what's its name. Consider this boxscript:

    Simulation sim {
      Calendar calendar { 
      }
      Box lion {
        &date = sim/calendar[date]
        &demand = ./hunger[perCapita]
        Box hunger {
          &perCapita = 0.11
        }
      }
      Box antelope {
        &date = /calendar[date]
        &demand = ./hunger[perCapita]
        Box hunger {
          &perCapita = 0.96
        }
      }
      Box foodWed {
        &demands = ../*[demand]
      }
    }

    Here the paths for date in lion and antelope are equivalent. The paths ./hunger[perCapita] are rooted to their parents, lion and antelope, respectively. Therefore they will each find only one match. The path ../*[demand] is rooted at the parent of foodWeb. Hence it matches both demand ports (and its type will be a vector of real numbers with two elements).

  • Union
  • You can think of a path as a set of matching references. You can merge several such sets by the union operator |. The merged set of references is guaranteed not to contain any duplicates. The union operator is very common when you define ports to show in PlotR. Another common use is to sum ports (forming a vector) to be used as a single input (a scalar):

    .ports = weather[T] | weather[RH] | plant[biomass]
    .inflow = sum(reproduction[outflow] | immigration[value])

  • Qualifications
  • Box nodes can be qualified with a class name. This again is mostly useful in combination with a joker. This will match ports named outflow in boxes of the Stage class:

    .ports = Stage::*[outflow]
    

    Box nodes can be qualified with a namespace too with or without the class:

    .ports = savanna::*[density]
    .ports = cocoa::Plant::*[biomass]
    

    Here, the first path will match ports named density found in boxes of a class belonging to the savanna namespace. The second path will match ports named biomass found in boxes of the Plant class belonging to the cocoa namespace. The namespace is decided by the plugin, in which the class resides. The name of the plugin and the namespace is always the same.

    Port nodes can be qualified with input, output or aux to match only input, output or auxiliary ports. This is most useful in combination with a joker:

    .ports = lion[output::*]
    .ports = antelopes[input::*]
    .ports = lion/kitten[aux::*]
    

  • Family relations
  • You can specify family relations for any node in the path, in terms of descendants and ancestors.

    descendants limit the path to all children, grand-children and so forth, while ancestors limit the path to parent, grandparent and so forth. Hence, descendants fans out to all nodes below (or 'inside', if you which) the current node, while ancestors search upwards along the single line of heritage:

    > load demo/butterfly.box
    ...
    > go butterfly
    Now at Box sim/butterfly
    > find descendants::Stage::*
    Stage sim/butterfly/egg
    Stage sim/butterfly/larva
    Stage sim/butterfly/pupa
    Stage sim/butterfly/adult
    > go larva
    Now at Stage sim/butterfly/larva
    > find ancestors::Box::*
    Box        sim/butterfly
    Simulation sim
    > find ancestors::egg
    No matches
    > find ancestors::*/egg
    Stage sim/butterfly/egg
    

    xxx

    Computations steps

    To be written.

    xxx

    Standard boxes

    The boxes plugin contains model building blocks of general utility. The functionality of these building blocks (classes) is described below

    You can run the boxscripts shown in the sub-sections below yourself. For that purpose you must install Universal Simulator. For example, to run the briere1.box⏷ script, you write at the Universal Simulator prompt:

    > run demo/briere/briere1.box

    Paste the clipboard at the R prompt to see the output as described in Running simulations.

    You can download particular boxscripts and R scripts whereever you see the ⏷ download symbol, if you want to study them further. But you don't need to to run the examples, since all necessary scripts are included with the Universal Simulator installation.

    xxx

    xxx

    #plugins/boxes/accumulator.html

    Accumulator

    Interface

    InputsTypeDefaultPurpose / Expression
    initialdouble0.0 Initial value at reset
    changedouble0.0 Change added to value
    minValuedouble-1.79769e+308 Minimum possible value
    maxValuedouble1.79769e+308 Maximum possible value
    Outputs   
    valuedouble0.0 Accumulated value
    signaldouble0.0 Synonym for value

    Usage

    The Accumulator box keeps track of an accumulated sum. For every simulation step, change (can be negative) is added to its value. This boxscript calculates the day-degrees above a threshold of 10 °C (a DayDegrees box would allow you more control with less BoxScript code):

    // accumulator1.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 01/05/2009
        .end   = 30/09/2009
      }
      Records weather {
        .fileName = "weather/flakkebjerg 2009.txt"
      }
      Accumulator dd18 {
        .change = if (weather[Tavg]>18) then weather[Tavg]-18 else 0
      }
      OutputR {
        PageR {
          .xAxis = calendar[date]
          PlotR {
            .layout = "merged"
            .ports = weather[Tavg] | dd18[change] | dd18[value]
          }
        }
      }
    }

    Note that in the output a port named value is by default labelled after its parent (here dd18):

    xxx

    xxx

    BaseSignal

    Interface

    InputsTypeDefaultPurpose / Expression
    initialSignaldouble0.0 Value of signal at reset
    initialFlagboolFALSE Value of flag at reset
    Outputs   
    signaldouble0.0 Value of the signal
    flagIsUpboolFALSE Is the signal on?
    flagJustRaisedboolFALSE Was the signal just set on?
    flagJustLoweredboolFALSE Was the signal just set off?
    valuedouble0.0 Synonym for signal

    Usage

    BaseSignal is the base class for several derived classes:

    BaseSignal cannot be used directly in a boxscript, only indirectly through one of its derived classes. The input and outputs of BaseSignal have the same purpose in all the derived classes as described here.

    The signal output reflects the status of other inputs (defined in the derived class). As a convenience signal has the synonym value. When the box is reset it will start out with the initialSignal.

    When the signal is used as a binary response (signal is either on or off), the various flag ports may provide more legible code. The description above should suffice to understand their use.

    Please find concrete examples of usage in the derived classes listed above.

    xxx

    Briere

    Briere is derived from PhysiologicalTime.

    #plugins/boxes/briere.html

    Briere

    Interface

    InputsTypeDefaultPurpose / Expression
    Tdouble- oCweather[Tavg]
    timeStepDaysdouble- dcalendar[timeStepDays]
    resetTotalboolFALSE Reset total zero?
    isTickingboolTRUE Is the physiological time running? Else step will be zero
    T0double0.0 oCLower temperature threshold
    Tmaxdouble0.0 oCUpper temperature threshold
    adouble0.0 /oCScaling parameter
    Outputs   
    stepdouble0.0 PTUIncrement in physiological time units (PTU)
    totaldouble0.0 PTUTotal time since reset

  • Background
  • The Briere box implements the non-linear model for physiological development of Briere et al. (1999), defined by three parameters T0, Tmax and a. They designate the lower and upper threshold for development, respectively, and a scaling parameter with no biological meaning. The equation for development rate is

    $$ 1/L = aT(T-T_0)\sqrt{T_{max}-T} $$

    The position of the temperature optimum is given implicitly by T0 and Tmax and can be derived from the above equation as

    $$ T_{opt}= \frac{\sqrt{9T_0^2 - 16T_0T_{max} + 16T_{max}^2} + 3T_0 + 4T_{max}}{10} $$

    Maybe of more relevance is the finding, that if you want to define the curve by its optimum rather than its maximum temperature, you can calculate the maximum temperature from the optimal temperature by

    $$ T_{max} = \frac {T_{opt}(5T_{opt} - 3T_0)}{4T_{opt} - 2T_0} $$

  • Parameter estimation
  • The three parameters of the curve can be estimated by non-linear regression. Here is a typical data set for temperature-dependent insect development rate. It shows the development time (L) and rate (1/L) at different temperatures (T) for brown marmorated stink bug (Halyomorpha halys) (from Nielsen et al. (2008)):

    T [°C]L [d]1/L (d-1)
    150.0000
    17121.500.0082
    2081.070.0123
    2544.650.0224
    2736.670.0273
    3033.730.0296
    3337.670.0265
    350.0000

    Using R nls we arrive at the estimates, T0=14.2°C, Tmax=35.0°C and a=2.836 × 10−5 d−1 that we can plug into this boxscript, which also shows two alternative models (simple linear and the DayDegrees box, which features a breakpoint at the optimum temperature):

    
    // briere1.box⏷
    Simulation sim {
      .steps = temperature[steps]
      SequenceByStep temperature {
        .min = 12
        .max = 37
        .by = 0.2
      }
      DayDegrees dayDegrees {
        .timeStepDays = 1
        .T0 = 14.2
        .Topt = 29
        .Tmax = 35
        .T = temperature[value]
      }
      Briere briere {
        .timeStepDays = 1
        .T0 = 14.2
        .Tmax = 35
        .a = 2.836e-5
        .T = temperature[value]
      } 
      OutputR {
        .scripts = "briere1.R"
        OutputText {
          .ports = temperature[value] | dayDegrees[step] | briere[step]
        }
      }
    }
    

    This is the plot produced:

  • Usage
  • In practical usage, you often want to turn the ticking of the physiological clock on and off. Sometimes, you may want to reset it to zero too. This is what the two remaining input ports (isTicking and resetTotal) are for. They are both of the boolean type. In this script, the clock is only ticking in the period 1 May to 1 October. Moreover, it is reset to zero on 1 May and 1 July:

    // briere2.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 01/01/2009
        .end   = 31/12/2010
      }
      Records weather {
       .fileName = "weather/flakkebjerg 2009.txt"
       .cycle = TRUE
      }
      Briere clock {
        .isTicking = (calendar[date] >= 01/05) && (calendar[date] < 01/10)
        .resetTotal = (calendar[date] == 01/05) || (calendar[date] == 01/07)
        .T0 = 8
        .Tmax = 32
        .a = 2.836e-5  
      }
      OutputR {
        PageR {
          .xAxis = calendar[date]
          PlotR {
            .ports = clock[output::*] | clock[isTicking] | clock[resetTotal]
            .ggplot = "scale_x_datetime(breaks=date_breaks('3 months'), 
                                        labels = date_format ('%b')) +
                       labs(x='2009 - 2010')"
          }
        }
      }
    }
    

    Noticable BoxScript tricks include

    • the use of the cycle port in Records to recycle the weather data;
    • the use of bare dates to ignore the year of the date (e.g., 01/05 to represent 1 May any year);
    • the use of the ggplot port to PlotR to add details to the plot produced.

    The output shows two years worth of data, identical since the weather of 2009 is used for both years:

    xxx

    xxx

    #plugins/boxes/buffer.html

    Buffer

    Interface

    InputsTypeDefaultPurpose / Expression
    initialdouble0.0 Initial amount at first place in buffer
    inputdouble0.0 Input to next place in buffer
    incrementdouble0.0 Increment added to every place in buffer
    sizeint100 Number of places in buffer
    Outputs   
    sumdouble0.0 Total amount in buffer
    averagedouble0.0 Average amount of places used in buffer
    buffervec_doublec() The buffer itself

    xxx

    Calendar

    #plugins/boxes/calendar.html

    Calendar

    Interface

    InputsTypeDefaultPurpose / Expression
    latitudedouble52.0Latitude [-90,90]
    longitudedouble11.0Longitiude [-180,180]
    timeZoneint1 hTime zone
    begindatetime2000/01/01T00:00:00 When simulation begins
    enddatetimenull When simulation ends (optional)
    timeStepint1Time step in units of timeUnit >0
    timeUnitstring"d" Unit of time step (y|d|h|m|s)
    Outputs   
    stepsint0 Number of steps from begin to end
    stepWithinDayint0 Step number within day
    stepsPerDayint0 Number of steps per day
    datedatenull Current date
    timetimenull Current time of the day
    dateTimedatetimenull Current date and time
    atMidnightboolFALSE Just past midnight?
    timeStepSecsdouble0.0 sTime step duration
    timeStepDaysdouble0.0 dTime step duration
    totalTimeStepsint0 Total number of time steps since calendar was reset
    totalTimeint0 Total time since calendar was reset; in units of timeUnit
    totalDaysdouble0.0 dTotal time since calendar was reset in days
    dayOfYearint0Julian day [1;366]
    bareDatebaredatenull date as a bare date

  • Daily time step
  • It is common to include a Date box in your boxscript. Its logical place is at the very top because for every time step, you will usually want update the current time before everything else:

    // calendar1.box⏷
    Simulation sim {
      .steps = 3
      Calendar calendar {
      	.begin = 15/12/2022
      }
      OutputR {
      	 OutputText {
      	   .ports = calendar[date] | calendar[time] | calendar[dateTime]
      	 }
      }
    }
    

    Calendar defaults to daily increments. Hence we get this output from the boxscript:

    > load demo/calendar/calendar1.box
    ...
    > run
    ...
    > head
    ...
          date     time            dateTime iteration step
    2022/12/15 00:00:00 2022/12/15T00:00:00         1    0
    2022/12/16 00:00:00 2022/12/16T00:00:00         1    1
    2022/12/17 00:00:00 2022/12/17T00:00:00         1    2
    2022/12/18 00:00:00 2022/12/18T00:00:00         1    3
    

    You will often use date, time or dateTime to put on the x-axis in the output plots. They can also be used as inputs to boxes that depends on the date or the hour of the day.

  • Changing the time step
  • You can change the time step. Here we also chose to set the specific time of the day for the simulation to start:

    // calendar2.box⏷
    Simulation sim {
      .steps = 3
      Calendar calendar {
        .begin = 15/12/2022T17:30
        .timeStep = 2
        .timeUnit = "m"
      }
      ...
    }
    

    You combine a date and a time with a T inbetween (no spaces). The timeStep must be an integer value, while the timeUnit is a one-character string providing you these options: y)ears, d)ays, h)ours, m)inutes and s)econds. Write the first character in apostrophs as shown above. The output is no surprise:

          date     time            dateTime iteration step
    2022/12/15 17:30:00 2022/12/15T17:30:00         1    0
    2022/12/15 17:32:00 2022/12/15T17:32:00         1    1
    2022/12/15 17:34:00 2022/12/15T17:34:00         1    2
    2022/12/15 17:36:00 2022/12/15T17:36:00         1    3
    

  • Setting the time period of the simulation
  • Maybe you prefer to specify the simulation period by date (maybe by date and time) rather then by the number of steps. That is easily done:

    // calendar3.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 15/12/2022
        .end   = 01/05/2024
      }
      ...
    }
    

    When you have run this, use the command ht 3 to show the three head and 3 tail rows of output:

          date     time            dateTime iteration step
    2022/12/15 00:00:00 2022/12/15T00:00:00         1    0
    2022/12/16 00:00:00 2022/12/16T00:00:00         1    1
    2022/12/17 00:00:00 2022/12/17T00:00:00         1    2
                                        ...
    2024/04/29 00:00:00 2024/04/29T00:00:00         1  501
    2024/04/30 00:00:00 2024/04/30T00:00:00         1  502
    2024/05/01 00:00:00 2024/05/01T00:00:00         1  503
    

    How did that happend you might ask? It seems as if sim[steps] was magically set to 503, so that we would end up on 5 May 2024. Well, if we inspect the final values of the calendar ports, we will find among them an outport called steps:

    > list calendar p
    ...
    >steps = 503
    ...
    

    So, calendar always calculates how many time steps will be need to reach end from begin and puts this value in its steps output. The Simulation box by default looks this up and uses it for its own steps (which is an input, not an output). This is the default expression for steps defined in the C++ code for the Simulation class:

    Input(steps).computes("if exists(Calendar::*[steps]) 
                           then Calendar::*[steps] else 1");
    

    xxx

    xxx

    #plugins/boxes/computation.html

    Computation

    Interface

    InputsTypeDefaultPurpose / Expression
    None
    Outputs   
    stepstring"construct" Name of current computation step

    Usage

    The Computation box simply has a string output telling you the current computation step. The possible values are

    • "ready"
    • "construct"
    • "amend"
    • "initialize"
    • "reset"
    • "update"
    • "cleanup"
    • "debrief"

    It can be useful when learning BoxScript. Here is a simple boxscript for exponential growth:

    // computation1.box⏷
    Simulation sim {
      .iterations = 2
      .steps = 3
      Computation comp {  
      }
      Box pop { 
        &r = 1.05
        &density = if    (comp[step]=="reset" ) then 100.0 
                   elsif (comp[step]=="update") then .[r] * .[density]
                   else .[density]
      }
      OutputR {
        OutputText output {
          .ports = pop[*] | sim[step] | comp[step]
        }
      }
    }
    

    The simulation will run for two iterations, three steps each as governed by the inputs to sim. The Computation box is simply declared without any input values (it has no inputs). The pop box is of the generic (empty) type Box. We equip it with auxiliary ports using the ampersand designator (&). Here we have set the growth rate r to 5% per step. The density is set to 100.0 in the reset step. In the update step the 5% growth is applied, while in all other steps it just keeps its value.

    BoxScript tricks work noting:

    • We include the decimal point 100.0 to set the density port to be of double type. A value of 100 would have set it to an int.
    • References to sibling ports inside a box begins with a period to signify this box, e.g. .[density].

    The output is sent to a text file. Use the command head 10 to see the first 10 (or fewer) lines:

    > head 10
    ...
    comp.step    r density iteration sim.step
        reset 1.05   100.0         1        0
       update 1.05   105.0         1        1
       update 1.05  110.25         1        2
       update 1.05 115.762         1        3
        reset 1.05   100.0         2        0
       update 1.05   105.0         2        1
       update 1.05  110.25         2        2
       update 1.05 115.762         2        3	
    

    You can see that OutputText by default writes one line of output for every reset and update step.

    xxx

    xxx

    #plugins/boxes/date.html

    Date

    Interface

    InputsTypeDefaultPurpose / Expression
    dayint1Day of month [1;31]
    monthint1Month [1;12]
    yearint2000 Year
    Outputs   
    datedatenull The date constructed
    valuedatenull Synonym for date

    Usage

    Use this box to construct a date from day, month and year. Here is an example:

    // date1.box⏷
    Simulation sim {
      .steps = 3
      Date date {
        .day   = sim[step] + 10
        .month = 5
      }
      OutputR {
        OutputText {
          .ports = date[date]
        }
      }
    }
    

    Here is the output:

    > head
    ...
          date iteration step
    2000/05/10         1    0
    2000/05/11         1    1
    2000/05/12         1    2
    2000/05/13         1    3
    

     

    xxx

    xxx

    #plugins/boxes/datesplit.html

    DateSplit

    Interface

    InputsTypeDefaultPurpose / Expression
    datedatenull The date to split
    Outputs   
    dayint0 Day of month
    monthint0 Month
    yearint0 Year

    Usage

    You use the DateSplit box to split a date variable into day, month and year. If you are trying to do something complicated then consider instead moving the complications to an R script that can be executed after the simulation has run (see OutputR). This simple R script demonstrates the functionality:

    // date_split1.box⏷
    Simulation sim {
      .steps = 3
      Calendar calendar {
        .begin = 24/12/2018
      }
      DateSplit dmy {
        .date   = calendar[date]
      }
      OutputR {
        OutputText {
          .ports = dmy[output::*]
        }
      }
    }
    

    The unsurprising output is

    > head
    ...
    day month year iteration step
     24    12 2018         1    0
     25    12 2018         1    1
     26    12 2018         1    2
     27    12 2018         1    3
    

     

    xxx

    DateTimeSignal

    DateTimeSignal is derived from the BaseSignal box.

    #plugins/boxes/datetimesignal.html

    DateTimeSignal

    Interface

    InputsTypeDefaultPurpose / Expression
    initialSignaldouble0.0 Value of signal at reset
    initialFlagboolFALSE Value of flag at reset
    currentDateTimedatetime- calendar[dateTime]
    beginDatebaredate1/1 The beginning of the date interval
    endDatebaredate31/12 The end of the date interval
    beginTimetime00:00:00 The beginning of the time interval
    endTimetime00:00:00 The end of the time interval
    signalInsidedouble1.0 Resulting signal when day and time are inside the day and time intervals
    signalOutsidedouble0.0 Resulting signal when day and time are outside the day and time intervals
    circadianboolTRUE Tells whether signal follows a daily rythm
    Outputs   
    signaldouble0.0 Value of the signal
    flagIsUpboolFALSE Is the signal on?
    flagJustRaisedboolFALSE Was the signal just set on?
    flagJustLoweredboolFALSE Was the signal just set off?
    valuedouble0.0 Synonym for signal

  • Usage
  • The DateTimeSignal box sets the signal to signalInside when the currentDateTime is inside the set limits, otherwise it is set to signalOutside. The limits are set by date (beginDate and endDate) and time of the day (beginTime and endTime) separately. Note that beginDate and endDate are bare dates, which means that they just specify the day and month, not the year. This means that you can have a calendar interval across 1 January, e.g. from 1 December (beginDate) to 1 March (endDate). All intervals of dates and times are closed (i.e. limits are inclusive). If beginTime and endTime keep their default value (00:00), the time of day is ignored.

    If circadian is TRUE then currentDateTime is considered inside the limits, if the date of currentDateTime is inside the [beginDate;endDate] period, and the time of currentDateTime is inside the [beginTime;endTime] period (specifying a recurring signal).

    If circadian is FALSE then currentDateTime is considered inside the limits, if it falls inside the period from beginTime on beginDate to endTime on endDate (specifying a single signal).

  • Circadian signals
  • The difference that circadian makes is easier to understand by example. First we demonstrate a circadian signal:

    // date_time_signal1.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 01/01/2001T12:00
        .end   = 10/01/2001T12:00
        .timeStep = 15
        .timeUnit = "m"
      }
      DateTimeSignal signal {
        .beginDate  = 03/01/2001
        .endDate    = 07/01/2001
        .beginTime  = 14:00
        .endTime    = 20:00
        .initialSignal = 50
        .signalInside  = 80
        .signalOutside = 50
      }
      OutputR {
        PageR {
          .xAxis = calendar[dateTime]
          PlotR {
            .ports = signal[value] | signal[flagIsUp] | 
                     signal[flagJustRaised] | signal[flagJustLowered]
            .ggplot = "scale_x_datetime(breaks=date_breaks('days'), 
                                        labels = date_format ('%d')) +
                       labs(x='January')"
          }
        }
      }
    }
    

    The boxscript raises the signal for five days between 14:00 and 20:00 every day, as shown in the output:

    You can check the precision of the signal going on at the Universal Simulator prompt with the rows 295 300 command:

    > rows 295 300
    ...
               dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    2001/01/04T13:30:00        0              0               0   50.0         1  294
    2001/01/04T13:45:00        0              0               0   50.0         1  295
    2001/01/04T14:00:00        1              1               0   80.0         1  296
    2001/01/04T14:15:00        1              0               0   80.0         1  297
    2001/01/04T14:30:00        1              0               0   80.0         1  298
    2001/01/04T14:45:00        1              0               0   80.0         1  299
    

    Or at the R prompt with sim[295:300,]:

    > sim[295:300,]
                   dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    295 2001-01-04 13:30:00        0              0               0     50         1  294
    296 2001-01-04 13:45:00        0              0               0     50         1  295
    297 2001-01-04 14:00:00        1              1               0     80         1  296
    298 2001-01-04 14:15:00        1              0               0     80         1  297
    299 2001-01-04 14:30:00        1              0               0     80         1  298
    300 2001-01-04 14:45:00        1              0               0     80         1  299
    

    Similarly, you can check the timeliness of the signal going off:

    > rows 320 325
    ...
               dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    2001/01/04T19:45:00        1              0               0   80.0         1  319
    2001/01/04T20:00:00        1              0               0   80.0         1  320
    2001/01/04T20:15:00        0              0               1   50.0         1  321
    2001/01/04T20:30:00        0              0               0   50.0         1  322
    2001/01/04T20:45:00        0              0               0   50.0         1  323
    2001/01/04T21:00:00        0              0               0   50.0         1  324
    

  • Non-circadian signals
  • In the next script (date_time_signal2.box⏷), we only changed one detail, namely the value of circadian in the signal box:

      DateTimeSignal signal {
        .beginDate  = 03/01/2001
        .beginTime  = 14:00
        .endDate    = 07/01/2001
        .endTime    = 20:00
        .initialSignal = 50
        .signalInside  = 80
        .signalOutside = 50
        .circadian = FALSE
      }
    

    We also re-arranged the ordering of the date and time inputs to make their meaning more clear. The output shows that the signal is raised only through one time period stretching 4 days and 6 hours:

    The precise timing is readily revealed:

    > rows 199 202
    ...
               dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    2001/01/03T13:30:00        0              0               0   50.0         1  198
    2001/01/03T13:45:00        0              0               0   50.0         1  199
    2001/01/03T14:00:00        1              1               0   80.0         1  200
    2001/01/03T14:15:00        1              0               0   80.0         1  201
    > rows 608 611
    ...
               dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    2001/01/07T19:45:00        1              0               0   80.0         1  607
    2001/01/07T20:00:00        1              0               0   80.0         1  608
    2001/01/07T20:15:00        0              0               1   50.0         1  609
    2001/01/07T20:30:00        0              0               0   50.0         1  610
    

    Alternatively, we can ignore the time of day all together, as shown in the date_time_signal3.box⏷ script:

      DateTimeSignal signal {
        .beginDate  = 03/01/2001
        .endDate    = 07/01/2001
        .initialSignal = 50
        .signalInside  = 80
        .signalOutside = 50
        .circadian = FALSE
      }
    

    This gives us five full days. Here the signal is going on at midnight:

    > rows 143 146
    ...
               dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    2001/01/02T23:30:00        0              0               0   50.0         1  142
    2001/01/02T23:45:00        0              0               0   50.0         1  143
    2001/01/03T00:00:00        1              1               0   80.0         1  144
    2001/01/03T00:15:00        1              0               0   80.0         1  145
    

    And here the signal is going off at midnight:

    > rows 623 626
    ...
               dateTime flagIsUp flagJustRaised flagJustLowered signal iteration step
    2001/01/07T23:30:00        1              0               0   80.0         1  622
    2001/01/07T23:45:00        1              0               0   80.0         1  623
    2001/01/08T00:00:00        0              0               1   50.0         1  624
    2001/01/08T00:15:00        0              0               0   50.0         1  625
    

    xxx

    DayDegrees

    DayDegrees is derived from the PhysiologicalTime box.

    #plugins/boxes/daydegrees.html

    DayDegrees

    Interface

    InputsTypeDefaultPurpose / Expression
    Tdouble- oCweather[Tavg]
    timeStepDaysdouble- dcalendar[timeStepDays]
    resetTotalboolFALSE Reset total zero?
    isTickingboolTRUE Is the physiological time running? Else step will be zero
    T0double0.0 oCLower temperature threshold
    Toptdouble100.0 oCOptimum temperate; linear decline from here to Tmax
    Tmaxdouble100.0 oCUpper temperature threshold
    Outputs   
    stepdouble0.0 oDIncrement in day-degrees
    totaldouble0.0 oDTotal time since reset

    Usage

    The DayDegrees box implements the classical, linear day-degree scale of physiological development. The briere1.box script (see Briere) compares linear and non-linear models:

    Here, the brown line shows a pure linear model, while the broken green line shows how DayDegrees work. The lower threshold for development (T0) was set to 14.2°C in this example with optimum temperature (Topt) at 29.0°C and maximum temperature (Tmax) at 35.0°C.

    If you choose only to set T0 in your boxscsript, while keeping the default values of Topt and Tmax, you will achiece the pure linear model (brown line).

    This boxscript is identical to the briere2.box⏷ script, except a DayDegree box has replaced the Briere box:

    // day_degrees1.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 01/01/2009
        .end   = 31/12/2010
      }
      Records weather {
       .fileName = "weather/flakkebjerg 2009.txt"
       .cycle = TRUE
      }
      DayDegrees clock {
        .isTicking = (calendar[date] >= 01/05) && (calendar[date] < 01/10)
        .resetTotal = (calendar[date] == 01/05) || (calendar[date] == 01/07)
        .T0 = 8
        .Topt = 29
        .Tmax = 35
      }
      OutputR {
        PageR {
          .xAxis = calendar[date]
          PlotR {
            .ports = clock[output::*] | clock[isTicking] | clock[resetTotal]
            .ggplot = "scale_x_datetime(breaks=date_breaks('3 months'), 
                                        labels = date_format ('%b')) +
                       labs(x='2009 - 2010')"
          }
        }
      }
    }
    

    The output of the boxscript is much the same as that of the briere2.box script, because ambient temperature remained mostly in the linear part of the temperature response:

    For further explanation of the output, please see Briere.

    xxx

    xxx

    #plugins/boxes/dewpoint.html

    DewPoint

    Interface

    InputsTypeDefaultPurpose / Expression
    temperaturedouble0.0 oCAir temperature
    rhdouble0.0 %Air relative humidity
    Outputs   
    valuedouble0.0 oCDew point temperature

    xxx

    FixedSignal

    FixedSignal is derived from BaseSignal.

    #plugins/boxes/fixedsignal.html

    FixedSignal

    Interface

    InputsTypeDefaultPurpose / Expression
    initialSignaldouble0.0 Value of signal at reset
    initialFlagboolFALSE Value of flag at reset
    Outputs   
    signaldouble0.0 Value of the signal
    flagIsUpboolFALSE Is the signal on?
    flagJustRaisedboolFALSE Was the signal just set on?
    flagJustLoweredboolFALSE Was the signal just set off?
    valuedouble0.0 Synonym for signal

    Usage

    The FixedSignal simply fixes the signal at the value given as initialSignal. It is useful when you have a group of boxes (e.g., of sibling boxes) from which you want to select all the boxes of type BaseSignal. Here is the beginning of a boxscript. It does not yet produce any output:

    // fixed_signal1.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 01/01/2001
        .end   = 31/12/2001
      }
      Box vivacity {
        Accumulator accumulator {
        }
        DateTimeSignal summer {
          .beginDate  = 01/06
          .endDate    = 31/08
          .signalInside = 25
        }
        DateTimeSignal autumn {
          .beginDate  = 01/09
          .endDate    = 30/11
          .signalInside = 5
        }
        FixedSignal anyTime {
          .initialSignal = -10
        }
      }
    }
    

    You can select the value port of any child box inside vivacity with the path vivacity/*[value] but, more to the point, you can select only those belonging to boxes of the BaseSignal base class with vivacity/BaseSignal::*[value]:

    > find vivacity/*[value]
    Port double sim/vivacity/accumulator[value]
    Port double sim/vivacity/summer[value]
    Port double sim/vivacity/autumn[value]
    Port double sim/vivacity/anyTime[value]
    > find vivacity/BaseSignal::*[value]
    Port double sim/vivacity/summer[value]
    Port double sim/vivacity/autumn[value]
    Port double sim/vivacity/anyTime[value]
    

    You may find it enlightning to leave out the [value] part of the path:

    > find vivacity/*
    Accumulator    sim/vivacity/accumulator
    DateTimeSignal sim/vivacity/summer
    DateTimeSignal sim/vivacity/autumn
    FixedSignal    sim/vivacity/anyTime
    > find vivacity/BaseSignal::*
    DateTimeSignal sim/vivacity/summer
    DateTimeSignal sim/vivacity/autumn
    FixedSignal    sim/vivacity/anyTime
    

    xxx

    xxx

    #plugins/boxes/functionalresponse.html

    FunctionalResponse

    Interface

    InputsTypeDefaultPurpose / Expression
    attackerdouble0.0 #predatorsAttacker density
    preydouble0.0 #preyResource density
    demandGrossdouble0.0 #preyGross demand of attacker
    attackRatedouble0.0 #attacks per #prey per #predator per time unit
    timeStepdouble1.0 Time unitTime step
    Outputs   
    supplyGrossdouble0.0 #preyGross supply (number of prey attacked)
    propPreyAttackeddouble0.0The proportion of prey attacked [0;1]
    searchEfficacydouble0.0 Efficacy in finding prey [0;1]
    sdRatioGrossdouble0.0Predator's gross supply/demand ratio [0;1]

    xxx

    xxx

    #plugins/boxes/hump.html

    Hump

    Interface

    InputsTypeDefaultPurpose / Expression
    xdouble0.0 x-value at which to calculate curve value
    x0double0.0 Beginning of curve
    x1double1.0 End of curve
    ymindouble0.0 Minimum value of curve
    ymaxdouble1.0 Maximum value of curve
    ditchboolFALSE Is the curve a ditch?
    Outputs   
    valuedouble0.0 Curve value at x-value

    Usage

    You use the Hump box to produce a generic, symmetric hump-or ditch-shaped curve with set limits on the x- and y-axis. The curve is a fourth degree polynomium:

    $$ \begin{split} f_{hump}(x) &= y_{min} + g(x) \\[6pt] f_{ditch}(x) &= y_{max} - g(x) \end{split} $$

    where

    $$ g(x)= \begin{cases} {(y_{max}-y_{min})\left[4{{(x-x_0)(x-x_1)}\over{(x_0-x_1)(x_1-x_0)}}\right]^2} & \text{for} \ x \ \epsilon \ [x_0;x_1] \\ 0 & \text{otherwise} \end{cases} $$

    The following script demonstrates the shapes that can be obtained:

    // hump1.box⏷
    Simulation sim {
      .steps = temperature[steps]
      SequenceByStep temperature {
        .min = -5
        .max = 40
        .by = 0.5
      }
      Box humps {
        Hump hump {
          .x = temperature[value]
          .x0 = 12
          .x1 = 34
          .ymax = 100
        }
        Hump humpOffset {
          .x = temperature[value]
          .x0 = 12
          .x1 = 34
          .ymax = 100
          .ymin = 20
        }
        Hump ditch {
          .x = temperature[value]
          .x0 = 12
          .x1 = 34
          .ymax = 100
          .ditch = TRUE
        }
        Hump ditchOffset {
          .x = temperature[value]
          .x0 = 12
          .x1 = 34
          .ymax = 100
          .ymin = 20
          .ditch = TRUE
        }
      }
      OutputR {
        PageR {
          .xAxis = temperature[value]
          PlotR {
            .ports = humps/*[value]
            .ncol = 2
          }
        }
      }
    }
    

    Here is the output:

    The curve takes off exactly from its cloor or ceiling exactly at the limits x0 and x1 as you can see by the rows command:

    > rows 34 37
    ...
    temperature     hump humpOffset   ditch ditchOffset iteration step
           11.5      0.0       20.0   100.0       100.0         1   33
           12.0      0.0       20.0   100.0       100.0         1   34
           12.5 0.789307    20.6314 99.2107     99.3686         1   35
           13.0  3.01209    22.4097 96.9879     97.5903         1   36
    > rows 77 80
    ...
    temperature     hump humpOffset   ditch ditchOffset iteration step
           33.0  3.01209    22.4097 96.9879     97.5903         1   76
           33.5 0.789307    20.6314 99.2107     99.3686         1   77
           34.0      0.0       20.0   100.0       100.0         1   78
           34.5      0.0       20.0   100.0       100.0         1   79
    

    xxx

    Maker

    #plugins/boxes/maker.html

    Maker

    Interface

    InputsTypeDefaultPurpose / Expression
    replicatesint1 Supply either, (1) the number of clones to replicate from child boxes;
    namesvec_stringc() or (2) the names of clones;
    fileNamestring"" or (3) a file; each line specifies a clone; column headings label inputs to each clone.
    Outputs   
    None

    Usage

    You use a Maker box if you want to make several copies (clones) of a box in the boxscript. The clones will all be exact copies of the original box (albeit with different names), including its port values and any child boxes, recursively (i.e. the clones are deep copies).

    ⏷ Make numbered clones

    In the first example, we create want to create four box objets representing islands with populations of foxes and rabbits. The geolocation of the islands and animal densities should be random. Here is our first sketch of a model without the use of a Maker:
    // islands1.box⏷
    Simulation sim {
      .iterations = 4
      .steps = 5
      Box random {
        RandomiserMonteCarlo randomiser {
        }
        RandomUniform latitude {
          .min = 10
          .max = 20
        }
        RandomUniform longitude {
          .min = -40
          .max = -30
        }
        RandomUniformInt initFox {
          .min = 10
          .max = 20
        }
        RandomUniformInt initRabbit {
          .min = 200
          .max = 1000
        }
      }
      Box island {
        &latitude = random/latitude[value]
        &longitude = random/longitude[value]
        Box fox {
          &value = random/initFox[value]
        }
        Box rabbit {
          &value = random/initRabbit[value]
        }
      } 
      OutputText {
        .ports = sim[iteration] | island[*] | island/*[*]
        OutputSelector {
          .final = TRUE
        }
      }
    }
    
    With the OutputSelector's final port set to TRUE, we get only one line in the output per iteration, as we can quickly see following run by head:
    > run demo/maker/islands1.box
    ...
    > head
    fox rabbit latitude longitude iteration step
     12    725  17.8735  -32.7255         1    5
     16    541   13.247  -39.2857         2    5
     15    814  13.3161  -31.2024         3    5
     18    471  19.2508  -35.2724         4    5
    
    This boxscript will allow us to model on each island (given further elaborations of the model) in sequence. But if we wanted to model them simultaneously, maybe to include migration between the islands? For that we can use a Maker box:
    // islands2.box⏷
    Simulation sim {
      .iterations = 4
      .steps = 5
      Box random {
        RandomiserMonteCarlo randomiser {
        }
        RandomUniform latitude {
          .min = 10
          .max = 20
        }
        RandomUniform longitude {
          .min = -40
          .max = -30
        }
        RandomUniformInt initFox {
          .min = 10
          .max = 20
        }
        RandomUniformInt initRabbit {
          .min = 200
          .max = 1000
        }
      }
      Maker archipelago {
        .replicates = 3
        Box isle {
          &latitude = random/latitude[value]
          &longitude = random/longitude[value]
          Box fox {
            &value = random/initFox[value]
          }
          Box rabbit {
            &value = random/initRabbit[value]
          }
        } 
      }
      OutputText {
        .ports = sim[iteration] | archipelago/*[*] | archipelago/*/*[*]
        OutputSelector {
          .final = TRUE
        }
      }
    }
    
    Here, the isle is used as a template by the Maker (called archipelago) to make three clones (since replicates = 3, which are simply numbered. The list shows the model structure:
    > list
    Simulation sim
      Box random
        RandomiserMonteCarlo randomiser
        RandomUniform latitude
        RandomUniform longitude
        RandomUniformInt initFox
        RandomUniformInt initRabbit
      Maker archipelago
        Box isle0
          Box fox
          Box rabbit
        Box isle1
          Box fox
          Box rabbit
        Box isle2
          Box fox
          Box rabbit
      OutputText 
        OutputSelector 
      OutputWriter outputWriter
    
    If we run the model and look at the output, we are not quite there though. Take a look at the output (lines have been broken for easier reading):
    > run demo/maker/islands2.box
    ...
    > head
    isle0.fox isle0.rabbit isle0.latitude isle0.longitude
           14          786        19.8897        -38.1184
           12          799        11.3178          -31.48
           12          357        14.9339        -38.9938
           11          963        16.4512        -36.3377
    isle1.fox isle1.rabbit isle1.latitude isle1.longitude
           14          786        19.8897        -38.1184
           12          799        11.3178          -31.48
           12          357        14.9339        -38.9938
           11          963        16.4512        -36.3377
    isle2.fox isle2.rabbit isle2.latitude isle2.longitude iteration step
           14          786        19.8897        -38.1184         1    5
           12          799        11.3178          -31.48         2    5
           12          357        14.9339        -38.9938         3    5
           11          963        16.4512        -36.3377         4    5
    
    In each of the four iterations, the three isles share their position (like being stacked), and they have the same densities of foxes and rabbits. We don't know the exact research questions addresses by this model but let's say that in each iteration, isles should be placed randomly and with different, random population densities. Then we need for each isle to draw its own random numbers:
    // islands3.box⏷
    Simulation sim {
      .iterations = 4
      .steps = 5
      RandomiserMonteCarlo randomiser {
      }
      Maker archipelago {
        .replicates = 3
        Box isle {
          &latitude = ./latitude[value]
          &longitude = ./longitude[value]
          RandomUniform latitude {
            .min = 10
            .max = 20
          }
          RandomUniform longitude {
            .min = -40
            .max = -30
          }
          Box fox {
            &value = ./init[value]
            RandomUniformInt init {
              .min = 10
              .max = 20
            }
          }
          Box rabbit {
            &value = ./init[value]
            RandomUniformInt init {
              .min = 200
              .max = 1000
            }
          }
        } 
      }
      OutputText {
        .ports = sim[iteration] | archipelago/*[*] | fox[value] | rabbit[value]
        OutputSelector {
          .final = TRUE
        }
      }
    }
    
    If we inspect isle0, we can see that is equipped with its own random numbers:
    > list isle0
    Box isle0
      RandomUniform latitude
      RandomUniform longitude
      Box fox
        RandomUniformInt init
      Box rabbit
        RandomUniformInt init
    
    This is confirmed further by the output of the model. Now the isles are different in all aspects in every iteration:
    > head
    isle0.fox isle0.rabbit isle0.latitude isle0.longitude
           17          402        19.9245        -34.2778
           16          349        12.3191        -35.1868
           17          342         10.122        -35.4519
           16          633        16.3812        -33.3165
    isle1.fox isle1.rabbit isle1.latitude isle1.longitude
           11          989        18.0103        -31.8565
           13          924        18.7218        -30.2215
           15          880        19.4189        -36.9949
           11          894        15.2746        -36.4758
    isle2.fox isle2.rabbit isle2.latitude isle2.longitude iteration step
           19          547        12.5868        -36.2568         1    5
           15          257        17.2394        -33.4497         2    5
           12          635        15.1014        -37.7954         3    5
           15          949        18.9858        -36.5964         4    5
    
    Note the there can only be one randomiser box on a boxscript. All the random variables in a boxscript draw numbers from this one source.

    Make named clones

    To continue the example above, let's say that we want to model some concrete islands with known positions but still with random population densities. We can achieve this by supplying the Maker box with a vector of names in stead of just the number of replicates. In this example, we include only to islands:
    // islands4.box⏷
    Simulation sim {
      .iterations = 4
      .steps = 5
      RandomiserMonteCarlo randomiser {
      }
      Maker azores {
        .names = c("Faial", "Flores")
        Box isle {
          &latitude  = if (name(.[]) == "Faial") then 38.58 else 39.46
          &longitude = if (name(.[]) == "Faial") then 28.70 else 31.13
          Box fox {
            &value = ./init[value]
            RandomUniformInt init {
              .min = 10
              .max = 20
            }
          }
          Box rabbit {
            &value = ./init[value]
            RandomUniformInt init {
              .min = 200
              .max = 1000
            }
          }
        } 
      }
      OutputText {
        .ports = sim[iteration] | archipelago/*[*] | fox[value] | rabbit[value]
        OutputSelector {
          .final = TRUE
        }
      }
    }
    
    As you can see, we got the wished for structure of the model:
    > list
    Simulation sim
      RandomiserMonteCarlo randomiser
      Maker azores
        Box Faial
          Box fox
            RandomUniformInt init
          Box rabbit
            RandomUniformInt init
        Box Flores
          Box fox
            RandomUniformInt init
          Box rabbit
            RandomUniformInt init
      OutputText 
        OutputSelector 
      OutputWriter outputWriter
    

    xxx

    xxx

    #plugins/boxes/maximum.html

    Maximum

    Interface

    InputsTypeDefaultPurpose / Expression
    valuesvec_doublec() Input values
    Outputs   
    valuedouble0.0 Maximum of inputs; zero if no inputs
    countint0 Number of inputs

    xxx

    xxx

    #plugins/boxes/maximumat.html

    MaximumAt

    Interface

    InputsTypeDefaultPurpose / Expression
    stepint- /.[step]
    trackdouble0.0 Value to track for max value
    Outputs   
    atint0 Step in which maximum value was attained
    valuedouble0.0 The maximum value attained

    Background

    If you need to summarise the trend of a port by the maximum value it attained and in which step it happened, you can use a MaximumAt box. The interface is simple. Just watch the example below. If you run the simulation for several iterations, the outputs at and max may differ between iterations.

    Usage

    We will use a Hump box for demonstration since we now the position of its maximum value:

    // maximum_at1.box⏷
    Simulation sim {
      .steps = 50
      Hump hump {
        .x = sim[step]
        .x0 = 20
        .x1 = 48
        .ymax = 150
      }
      MaximumAt maxValue {
        .track = hump[value]
      }
      OutputR {
        PageR {
          PlotR {
            .ports = hump[value] | maxValue[output::*]
            .ggplot = "geom_vline(xintercept=34, colour='black', linetype='dashed')"
          }
        }
      }
    }
    

    With this parameter setting we expect to find the maximum in the point (34, 150). This is confirmed when we run the script,

    > run demo/maximum_at/maximum_at1.box
    

    and study the output:

    Further confirmation can be gotten by the tail command:

    > tail 3
    ...
    hump at maxValue iteration step
     0.0 34    150.0         1   48
     0.0 34    150.0         1   49
     0.0 34    150.0         1   50
    

    xxx

    xxx

    #plugins/boxes/mean.html

    Mean

    Interface

    InputsTypeDefaultPurpose / Expression
    valuesvec_doublec() Input values
    Outputs   
    valuedouble0.0 Average of inputs; zero if no inputs
    countint0 Number of inputs

    xxx

    xxx

    #plugins/boxes/message.html

    Message

    Interface

    InputsTypeDefaultPurpose / Expression
    textstring"" Text message to print
    whenstring"" In which step to print the message (reset, initialize, etc.)
    Outputs   
    None

    xxx

    xxx

    #plugins/boxes/minimum.html

    Minimum

    Interface

    InputsTypeDefaultPurpose / Expression
    valuesvec_doublec() Input values
    Outputs   
    valuedouble0.0 Minimum of inputs; zero if no inputs
    countint0 Number of inputs

    xxx

    xxx

    #plugins/boxes/multiplum.html

    Multiplum

    Interface

    InputsTypeDefaultPurpose / Expression
    factorsvec_doublec() Input factors
    Outputs   
    valuedouble0.0 The multiplum

    xxx

    xxx

    #plugins/boxes/numberedfile.html

    NumberedFile

    Interface

    InputsTypeDefaultPurpose / Expression
    fileNamestring"" File name with extension, with/without a path
    evaluationOrderint0 Number to append to file name
    numberWidthint0 Number will be left-filled with zero up to this width
    Outputs   
    valuestring"" File name with number

    xxx

    xxx

    #plugins/boxes/offsetdatetime.html

    OffsetDateTime

    Interface

    InputsTypeDefaultPurpose / Expression
    dateTimedatetimenull Date-time that will be offset
    daysint0 dOffset (negative or positive)
    hoursint0 hOffset (negative or positive)
    minutesint0 mOffset (negative or positive)
    Outputs   
    valuedatetimenull Offset date-time

    xxx

    xxx

    #plugins/boxes/onoff.html

    OnOff

    Interface

    InputsTypeDefaultPurpose / Expression
    xdouble0.0 x-value at which to calculate value
    xOndouble0.0 Lower threshold
    xOffdouble0.0 Upper threshold
    valueOndouble1.0 Value inside [xOn,xOff] interval
    valueOffdouble0.0 Value outside [xOn,xOff] interval
    isStickyboolFALSE Should remain on when switched on?
    Outputs   
    valuedouble0.0 Current on or off value
    isOnboolFALSE Is switch on?

    xxx

    xxx

    #plugins/boxes/onoffbydate.html

    OnOffByDate

    Interface

    InputsTypeDefaultPurpose / Expression
    xdatenull datex-value at which to calculate value
    xOnbaredatenull Bare dateLower threshold
    xOffbaredatenull Bare dateUpper threshold
    valueOndouble1.0 Value inside [xOn,xOff] interval
    valueOffdouble0.0 Value outside [xOn,xOff] interval
    isStickyboolFALSE Should remain on when switched on?
    Outputs   
    valuedouble0.0 Current on or off value
    isOnboolFALSE Is switch on?

    xxx

    xxx

    #plugins/boxes/outputr.html

    OutputR

    Interface

    InputsTypeDefaultPurpose / Expression
    keepPlotsboolTRUE Keep previous plots before showing new plots in R?
    clearMemoryboolFALSE Clear R memory?
    showPlotsboolTRUE Show plots in R?
    showLinesint0 Number of lines to show at the prompt
    popUpboolFALSE Show plots in pop-up windows?
    widthdouble7.0 Width of pop-up windows (only used if popUp is set)
    heightdouble7.0 Height of pop-up windows (only used if popUp is set)
    maximizeWindowboolFALSE Maximize plot window size?
    fontSizedouble0.0 Only used if >0
    plotAsListboolFALSE Put plots into an R list?
    saveDataFrameboolFALSE Save output as R data frame?
    skipFormatsbool- OutputWriter::*[skipFormats]
    codestring"" R code to be run before 'scripts'
    scriptsvec_stringc() R scripts to be run at the end
    plotTypesvec_string- PlotR::*[type]
    clearPlotsboolFALSE Deprecated
    Outputs   
    portspath- ./PageR::*[xAxis]|./*/PlotR::*[ports] (Path to all ports used by my pages and plots)
    numPagesint0 Number of pages in this output
    Note the side effects: writes an R script to the output folder copies an R script to the clipboard

    xxx

    OutputSelector

    #plugins/boxes/outputselector.html

    OutputSelector

    Interface

    InputsTypeDefaultPurpose / Expression
    beginStepint0 intOutput is written when this step is reached
    beginDateTimedatetimenull DateTimeOutput is written when this time point is reached (optional)
    stepint- /.[step]
    dateTimedatetime- Calendar::*[dateTime]
    periodint1 If >1: A row of output will be produced with this period
    summarystring"average" Either "average" or "current"; how values are summarized over the given period
    finalboolFALSE Overrules period
    useLocalDecimalCharboolFALSE Use local decimal character in output?
    skipFormatsbool- ! exists(OutputR::*[skipFormats])
    Outputs   
    isActiveboolFALSE Should output be written?
    isSkippingboolFALSE Are lines being skipped?

  • Usage
  • The OutputSelector box determines when output should be produced by the OutputWriter box. Hence, if you do not include an OutputSelector box in your boxscript then the OutputWriter box will create one for you. The default behaviour of OutputSelector is to write one line of output for every reset and update step during the simulation (see Computationel model). The useLocalDecimalChar and skipFormats define different features of the output file as explained for OutputWriter.

    If you want to change the default behaviour of OutputSelector, you must include an OutputSelector box in your boxscript. The right place to put it is as a child box of the OutputText (as exemplified below) or OutputR box, since OutputSelector affects the output produced by OutputText and OutputR.

  • Skip a number of lines
  • You can set the number steps and iterations a simulation is running in the Simulation object. That will by itself result in iterations * (steps + 1) lines in the output file (adding one for each iteration because a line is written also in the reset step). You can skip a number of initial lines in each iteration by setting the beginStep of the OutputSelector box. Maybe the model needs some warming up before it produces reliable or relevant output. Here is one simple example:

    // output_selector1.box⏷
    Simulation sim {
      .iterations = 2
      .steps = 12
      Calendar calendar {
      }
      OutputText {
        .ports = calendar[date]
        OutputSelector {
          .beginStep = 10
        }
      }
    }
    

    When you run it

    > run demo/output_selector/output_selector1.box
    

    and subsequently inspect the few lines produced with the head command, you won't be surprised:

    > head
    Showing C:/Users/au152367/Documents/QDev/UniSim3/output/output_selector1_0001.txt
          date iteration step
    2000/01/11         1   10
    2000/01/12         1   11
    2000/01/13         1   12
    2000/01/11         2   10
    2000/01/12         2   11
    2000/01/13         2   12
    

    Well, unless you had not anticipated that beginStep is acting on every iteration.

  • Skip lines until a certain time
  • As an alternative to beginStep, you can set beginDateTime to commence output at a certain date-time. You can combine them both, which will postpone output until both a certain step and a certain date-time is reached, though I never found the need to do so. Here is a simple example, working in 5-minute time steps

    // output_selector2.box⏷
    Simulation sim {
      .iterations = 2
      Calendar calendar {
        .begin = 01/08/2022T12:00
        .end = 01/08/2022T12:40
        .timeStep = 5
        .timeUnit = "m"
      }
      OutputText {
        .ports = calendar[dateTime]
        OutputSelector {
          .beginDateTime = 01/08/2022T12:30
        }
      }
    }
    

    Run the simulation

    run demo/output_selector/output_selector2.box
    

    and again use head to see the expected output:

    > head
    ...
               dateTime iteration step
    2022/08/01T12:30:00         1    6
    2022/08/01T12:35:00         1    7
    2022/08/01T12:40:00         1    8
    2022/08/01T12:30:00         2    6
    2022/08/01T12:35:00         2    7
    2022/08/01T12:40:00         2    8
    

  • Periodic output
  • You can write only every other line by setting period = 2, every third line by period = 3, and so forth. This can be combined with beginStep and beginDateTime as you wish:

    // output_selector3.box⏷
    Simulation sim {
      .iterations = 2
      .steps = 25
      Calendar calendar {
      }
      OutputText {
        .ports = calendar[date]
        OutputSelector {
          .beginStep = 10
          .period = 6
        }
      }
    }
    

    Notice that the final output written for each iteration is in step 22, even though an iteration lasts 25 steps. Note also that the period counts from the first line written in each iteration (in step 10), not from the beginning of the simulation in each iteration (step 0):

    > head
    ...
          date iteration step
    2000/01/11         1   10
    2000/01/17         1   16
    2000/01/23         1   22
    2000/01/11         2   10
    2000/01/17         2   16
    2000/01/23         2   22
    

  • Final output
  • If you want only the final values to appear in the output, i.e. those in the last step, then you set final to TRUE. This will overrule any of the settings on beginStep, beginDateTime and period. Here is a simple boxscript:

    // output_selector4.box⏷
    Simulation sim {
      .iterations = 3
      .steps = 25
      Calendar calendar {
      }
      OutputText {
        .ports = calendar[date]
        OutputSelector {
          .final = TRUE
        }
      }
    }
    

    When you run the script,

    > run demo/output_selector/output_selector4.box
    

    and look at the output, you will find that only the final step 25 of each of the three iterations appears in the output:

    > head 20
    ...
          date iteration step
    2000/01/26         1   25
    2000/01/26         2   25
    2000/01/26         3   25
    > 
    

    If you run an uncertainty or sensitivity analysis, you will usually set final to TRUE.

    xxx

    xxx

    #plugins/boxes/outputtext.html

    OutputText

    Interface

    InputsTypeDefaultPurpose / Expression
    portspath Path to port(s) to output
    Outputs   
    None

    xxx

    OutputWriter

    #plugins/boxes/outputwriter.html

    OutputWriter

    Interface

    InputsTypeDefaultPurpose / Expression
    showPortsboolFALSE Shows a table of the ports to be written
    portspath- /.[iteration]|/.[step]|OutputText::*[ports]|OutputR::*[ports]
    skipFormatsbool- OutputSelector::*[skipFormats]
    useLocalDecimalCharbool- OutputSelector::*[useLocalDecimalChar]
    isSkippingbool- OutputSelector::*[isSkipping]
    isActivebool- OutputSelector::*[isActive]
    periodint- OutputSelector::*[period]
    summarystring- OutputSelector::*[summary]
    Outputs   
    filePathstring"" Name of output file including absolute path
    decimalCharstring"" Decimal character used in output

  • Usage
  • You'll never need to include an OutputWriter box in a boxscript explicitly. It will be created automaticall by boxes that need it, such as OutputR and OutputText. It is the sole responsibility of OutputWriter to write the simulation output to a file. All of its inputshas been given default expressions, which look up the relevant data in other boxes. You set what to change what is written in the OutputR or OutputText box and you can change how it is written in the OutputSelector box.

    OutputWriter depends much on OutputSelector, in fact, so much that if no OutputSelector box is present in the boxscript then the OutputWriter box will create an OutputSelector as a child box.

    The sun1.box⏷ script, for example, includes an OutputR box explicitly in the script. In a chain reaction, the OutputR box creates an OutputWriter box, which again creates an OutputSelector box:

    > load demo/sun/sun1.box
    Constructing...
    Amending...
    8 boxes created
    > list
    Simulation sim
      Calendar calendar
      Sun sun
      OutputR 
        PageR 
          PlotR 
      OutputWriter outputWriter
        OutputSelector selector
    

    Most often you are not interested in the output file, as Universal Simulator does its best to handle all output automatically behind the scenes but, anyway, here are the details.

  • Output folder
  • The output file will be written to your output folder, which you can locate with the get folders command:

    > get folders
    

    The name of the output file will be the same as the boxscript that produced it, just with a four-digit running number at the end. As a pure text file, its suffix (also known as its file type) is "txt". The file consists of tab-separated columns, separated by tab characters (no, you cannot change the separator character to something else than a tab).

    The output folder will grow slowly for every run you make. Therefore, you should sometimes empty it.

  • Output file format
  • The first line contains unique column names constructed from the names of the ports included. The column names have been shortened as much as possible while keeping them unique. Ports named value will have a column name without the port part if possible; they will be named after their parent box instead ("value" is not an informative label).

    If skipFormats is FALSE, which it defaults to because that's the default value imported from OutputSelector, then the second line in the output file will be information about the format of the values in the column. These format specifications are used when the file is read into R as a data frame. If you want to do all the handling of the output file yourself then set skipFormats to TRUE in the OutputSelector box.

    If you want to use the decimal character specific to the language settings on your computer then set useLocalDecimalChar to TRUE in the OutputSelector box; otherwise a decimal point will be used.

    xxx

    xxx

    #plugins/boxes/pager.html

    PageR

    Interface

    InputsTypeDefaultPurpose / Expression
    xAxispath- /.[step] (Port(s) on x-axis)
    titlestring"" Title shown on page
    ncolint-1 No. of columns to arrange plots in
    nrowint-1 No. of rows to arrange plots in
    commonLegendboolFALSE Collate legends of blots into one common legend
    legendPositionstring"bottom" If 'commonLegend' then place legend here
    widthdouble- ..[width]
    heightdouble- ..[height]
    maximizeWindowbool- ..[maximizeWindow]
    fontSizedouble- ..[fontSize]
    plotAsListbool- ..[plotAsList]
    popUpbool- ..[popUp]
    numPagesint- ..[numPages]
    layoutsvec_string- ./PlotR::*[layout]
    Outputs   
    None

    xxx

    xxx

    #plugins/boxes/plotr.html

    PlotR

    Interface

    InputsTypeDefaultPurpose / Expression
    portspath Port(s) on y-axis
    layoutstring"facetted" Either "merged" or "facetted"
    typestring"default" Type of plot (default|density|histogram(nbins)|SobolConvergence|SobolIndices)
    guideTitlestring"" Title of guide legends
    ggplotstring"" R code that will be added to the ggplot
    endstring"" Deprecated
    endCodestring"" Deprecated
    maxDataint0 Max. number of data rows to plot; ignored if zero
    ncolint-1 Number of columns in arrangement of plots; -1 keeps default
    nrowint-1 Number of rows in arrangement of plots; -1 keeps default
    directionstring"row" Fill in plots by 'row' or 'col'
    iterationint- /.[iteration]
    xAxispath- ..[xAxis]
    widthdouble- ..[width]
    heightdouble- ..[height]
    fontSizeint- ..[fontSize]
    plotAsListbool- ..[plotAsList]
    Outputs   
    None

    xxx

    #plugins/boxes/popup.html

    PopUp

    Interface

    InputsTypeDefaultPurpose / Expression
    titlestring"" Title
    textstring"" Short informative text
    detailsstring"" Further details
    buttonsvec_stringc("OK") Vector of buttons to show: (OK Yes No)
    defaultButtonstring"OK" Button assumed when Return is typed
    escapeButtonstring"OK" Button assumed when Esc is typed
    iconstring"information" Icon to show
    whenstring"initialize" In which step to show message, e.g., "initialize" or "debrief"
    showboolTRUE Determines whether pop-up will be shown
    Outputs   
    answerstring"" Name of button pushed
    acceptedboolFALSE Question accepted?

    xxx

    xxx

    InputsTypeDefaultDescription
    Tdoublecomputed= weather[Tavg]
    timeStepDaysdoublecomputed= calendar[timeStepDays]
    resetTotalboolFALSE []Reset total to zero?
    isTickingboolTRUE []Is the physiological time running? Else step will be zero
    Outputs   
    stepdouble[PTU]Increment in physiological time units
    totaldouble[PTU]Total time since reset

    Usage

    PhysiologicalTime is the base class for two derived Box classes:

    PhysiologicalTime cannot be used directly in a boxscript, only indirectly through one of its derived classes. The input and outputs of PhysiologicalTime have the same purpose in both the derived classes as described here.

    The temperature T defaults to the Tavg port of a weather, which would typically be a Records box reading a weather log with a column labelled Tavg. Feel free to change it in your boxscript.

    It is necessary to know the time step of the simulation to calculate the time step in physiological time units. The timeStepDays input acquires this information from the calendar box. You should change this in your boxscript, only if you have no calendar box.

    In practical usage, you may want to turn the ticking of a physiological clock on and off. Sometimes, you may also want to reset it to zero too. This is what the input ports isTicking and resetTotal, respectively, are for. You can find examples of this under Briere.

    xxx

    xxx

    #plugins/boxes/prioritysignal.html

    PrioritySignal

    Interface

    InputsTypeDefaultPurpose / Expression
    initialSignaldouble0.0 Value of signal at reset
    initialFlagboolFALSE Value of flag at reset
    myFlagsvec_bool- ./BaseSignal::*[flagIsUp]
    mySignalsvec_double- ./BaseSignal::*[signal]
    reverseOrderboolFALSE y|nFind first signal!=0 from top (false) or bottom (true)?
    Outputs   
    signaldouble0.0 Value of the signal
    flagIsUpboolFALSE Is the signal on?
    flagJustRaisedboolFALSE Was the signal just set on?
    flagJustLoweredboolFALSE Was the signal just set off?
    valuedouble0.0 Synonym for signal

    xxx

    xxx

    #plugins/boxes/proportionalsignal.html

    ProportionalSignal

    Interface

    InputsTypeDefaultPurpose / Expression
    initialSignaldouble0.0 Value of signal at reset
    initialFlagboolFALSE Value of flag at reset
    inputdouble25.0 Value determining the signal
    thresholddouble25.0 Input threshold above which the signal is changing
    thresholdBanddouble5.0 Interval of the input over which the signal is changing
    minSignaldouble0.0 Minimum possible signal
    maxSignaldouble100.0 Maximum possible signal
    increasingSignalboolTRUE y|nIs the signal increasing inside the threshold band?
    Outputs   
    signaldouble0.0 Value of the signal
    flagIsUpboolFALSE Is the signal on?
    flagJustRaisedboolFALSE Was the signal just set on?
    flagJustLoweredboolFALSE Was the signal just set off?
    valuedouble0.0 Synonym for signal

    xxx

    xxx

    InputsTypeDefaultDescription
    iterationintcomputed []/.[iteration]
    iterationsintcomputed []/.[iterations]
    doSensitivityAnalysisboolFALSE []Carry out a sensitivity analysis?
    bootstrapSizeint1000 []Size of bootstrap sample (cheap in computation time); only used in sensitivity analysis
    seedint0 []Seed for random numbers; if this is zero a random seed value will be used
    drawAtInitializeboolFALSE []Draw a value when a box is initialized?
    drawAtResetboolTRUE []Draw a value when a box is reset?
    drawAtUpdateboolFALSE []Draw a value when a box is updated?
    Outputs   
    numVariablesint[0;inf)Number of variables which are randomised

  • Background
  • There will be only one source of random numbers in your boxscript. This will be a box of one of the classes:

    These box class have the same interface defined by RandomiserBase. You cannot use RandomiserBase directly in your boxscript (in C++ parlance it is virtual); it only serves as a base class for the three classes above, which you can use—but only one of them and only once in a boxscript.

    You can change the seed to a positive value, if you want the same sequence of pseudo-random numbers every time you run the boxscript. For a RandomiserSobolSequence box, though, the seed has no effect because this box will produce the same sequence of quasi-random numbers always.

    By default random numbers will be drawn in every reset step (see computational model), as indicated by drawAtReset. Other options (they can be combined) are drawAtInitialize and drawAtUpdate. If you choose to draw random numbers at every update, you will end up with a stochastic model. The default setting will change model behaviour for every iteration that you run the model. This is useful for uncertainty analysis and sensitivity analysis. If you want to carry out a sensitivity analysis then set doSensitivityAnalysis to TRUE and consider changing bootstrapSize to 10,000 for serious use.

    The single output numVariables is mysterious. It tells you how many variables are randomised, yet there are no inputs to indicate which variables to randomise? That's because a certain structure of boxes is assumed, as exemplified by this boxscript:

    // randomiser1.box⏷ 
    Box random {
      RandomiserMonteCarlo randomiser {
      }
      RandomUniformInt k {
        .min = 10
        .max = 30
      }
      RandomNormal r {
        .min = 10
        .max = 30 
      }
    }
    

    The box names (random, randomiser, k and r) are all up to you to choose but are used here for reference. There must be one randomiser box with any number of sibling boxes. The sibling boxes of classes derived from RandomBase represent the random variables. Here there are two, k and r. There can be other sibling boxes beside that but you should always have the randomiser as the top-most child. Arrange it all neatly within a common parent box, here random.

  • Stochastic modelling
  • It is easiest to understand how to set up a stochastic model by starting out with one that is not stochastic, yet makes use of random numbers. This is exemplified by this boxscript:

    // randomiser1.box⏷ 
    Simulation sim {
      .iterations = 4
      .steps = 5
      Box random {
        RandomiserMonteCarlo randomiser {
        }
        RandomUniformInt k {
          .min = 10
          .max = 30
        }
        RandomNormal r {
          .min = 10
          .max = 30 
        }
      }
      OutputR {
        PageR {
          PlotR {
            .ports = random/*[value]
            .layout = "merged"
            .ggplot = "geom_point(size=2)"
          }
        }
      }
    }
    

    Run the model,

    > run demo/randomiser/randomiser1.box
    

    and study the output:

    The four panels show the four iterations of the model. Within each iteration the variables remain constant. It is only from one iteration to the next that they attain new values inside their designated intervals (which happens to be [10;30] for both of them). This set-up of the random variables are useful for an uncertainty or a sensitivity analysis.

    Only little changed in the next boxscript. Most importantly, we are now drawing random numbers for every simulation update:

    // randomiser2.box⏷ 
    Simulation sim {
      .iterations = 4
      .steps = 100
      Box random {
        RandomiserMonteCarlo randomiser {
          .drawAtUpdate = TRUE
        }
        RandomUniformInt k {
          .min = 10
          .max = 30
        }
        RandomNormal r {
          .min = 10
          .max = 30 
        }
        OutputR {
          PageR {
            PlotR {
              .ports = random/*[value]
              .layout = "merged"
            }
          }
        }
      }
    }
    

    Run the model,

    > run demo/randomiser/randomiser2.box
    

    and marvel at the changed behaviour of the model. We are now running each of the four iterations for 100 steps to highligh the dynamics:

    This shows the behaviour of a stochastic model. Use those two variables elsewhere in your model as inputs to other boxes, and your model will behave erratically, hopefully within meaningful boundaries. You would refer to the current values by random/k[value] and random/r[value].

  • Uncertainty analysis
  • In uncertainty analysis we study the uncertainty on model outputs induced by uncertainty in model parameters. We have take the introductory butterfly.box⏷ script and made two of the model parameters uncertain:

    // randomiser3.box⏷ 
    Simulation sim {
      .iterations = 4
      Calendar calendar {
        .begin = 01/05/2009
        .end   = 30/09/2009
      }
      Records weather {
        .fileName = "weather/flakkebjerg 2009.txt"
      }
      Box random {
        RandomiserStratified randomiser {
        }
        RandomUniformInt k {
          .min = 10
          .max = 30
        }
        RandomUniform deltaT {
          .min = -3.0
          .max =  3.0
        }
      }
      Box butterfly {
        DayDegrees time {
          .T0 = 5
          .T  = weather[Tavg] + random/deltaT[value]
        }
        Stage egg {
          .initial  = 100 
          .duration = 140
          .timeStep = ../time[step]
          .k = random/k[value]
        }
        Stage larva {
          .inflow   = ../egg[outflow]
          .duration = 200
          .timeStep = ../time[step]
          .k = random/k[value]
        }
        Stage pupa {
          .inflow   = ../larva[outflow]
          .duration = 100
          .timeStep = ../time[step]
          .k = random/k[value]
        }
        Stage adult {
          .inflow   = ../pupa[outflow]
          .duration = 28
          .timeStep = 1
          .k = random/k[value]
        }
      }
      OutputR {
        PageR {
          .xAxis = calendar[date]
          PlotR {
            .ports = Stage::*[value]
            .layout = "merged"
          }
        }
      }
    }
    

    The parameters chosen to be uncertain are k used as input to the Stage boxes and ambient temperature. Temperature enters the model from the Records box, which reads it from a weather file. Assuming the ambient temperature is an uncertain measure for the microclimate experienced by all life stages of the butterfly population, an uncertainty of ±3℃ was added to the temperate. This is represented by the RandomUniform variable deltaT (well, more precisely, deltaT is a box; the variable value is provided by its value output).

    You can run the boxscript,

    > run demo/randomiser/randomiser3.box
    

    and study the uncertainty in the output:

    An alternative presentation of the uncertainty is shown in the next boxscript, running 30 iterations:

    > run demo/randomiser/randomiser4.box
    

    There are 30 curves in each panel, representing the course of life stages phenology for each of the 30 iterations:

    How about summarising the 30 curves in the panels for larva, pupa and adult by the date of their maximum? Then we could show the distribution of these dates as ann alternative illustration of the uncertainty. That's what we'll do in the next script. Here are the final lines of the randomiser5.box⏷ script:

    Box max {
      MaximumAt larva {
        .track = butterfly/larva[value]
      }
      MaximumAt pupa {
        .track = butterfly/pupa[value]
      }
      MaximumAt adult {
        .track = butterfly/adult[value]
      }
    }
    OutputR {
      .scripts = "randomiser5.R"
      OutputSelector {
        .final = TRUE
      }
      OutputText {
        .ports = max/*[at]
      }
    }
    

    We use three MaxAt boxes to catch the simulation step, in which the maximum is reached. By setting final to TRUE in the OutputSelector, we get only one line of out from each simulation iteration. Let's run that,

    > run demo/randomiser/randomiser5.box
    

    The randomiser5.R⏷ script grabbed the output to show these geom_density plots:

    That's a lot of uncertainty! The expected date of maximum occurence stretches over a month for all three life stages. The question is, which of the two uncertain parameters k or deltaT is the main cause of this uncertainty? That's what we'll find out next in the sensitivity analysis.

  • Sensitivity analysis
  • Sensitivity analysis demands many simulation iterations. Therefore, you should use the randomiser that most efficiently explores the n-dimensional space formed by your n uncertain model parameters. That means you should use RandomiserSobolSequence. This has been set up in the next script like so:

    // randomiser6.box⏷ 
    Simulation sim {
      .iterations = 1024
      .silent = TRUE
      Calendar calendar {
        .begin = 01/05/2009
        .end   = 30/09/2009
      }
      Records weather {
        .fileName = "weather/flakkebjerg 2009.txt"
      }
      Box random {
        RandomiserSobolSequence randomiser {
          .doSensitivityAnalysis = TRUE
          .bootstrapSize = 1000
        }
        RandomUniformInt k {
          .min = 10
          .max = 30
        }
        RandomUniform deltaT {
          .min = -3.0
          .max =  3.0
        }
      }
    

    When we are using Sobol' numbers we should be careful to make a balanced sampling of parameter space. To obtain that, the number of simulation iterations must equal \(N(p+2)\) with \(N=2^n\), where \(n\) is a whole, positive number, and \(p\) is the number of parameters sampled (we've got 2). \(N\) is known as the sample size of the sensitivity analysis. These are all valid options for \(p=2\): $$ \begin{align*} 2^1\cdot(2+2) &= 8 \\ 2^2\cdot(2+2) &= 16 \\ 2^3\cdot(2+2) &= 32 \\ \ldots \\ \end{align*} $$

    If you set iterations to an invalid number (which depends only on your value of \(p\)), the simulation will stop immediately if you try to run it. As a service, you will get suggestions to valid values for iterations near the number you picked. So, if you want in the range of, say, 50,000 iterations, write that, and you will be told nearby valid numbers.

    To carry out the sensitivity analysis on the simulation output, we must set doSensitivityAnalysis to TRUE and choose a reasonable bootstrapSize to do statistics on the sensitivity indices. A bootstrapSize of 10,000 seems to be a standard choice in literature but here we'll use 1000 for demonstration.

    The OutputR box in the randomiser6.box⏷ script will show three pages of output produced by PageR boxes:

    OutputR {
      OutputSelector {
        .final = TRUE
      }
      PageR {
        .xAxis = random/*[value]
        PlotR {
          .ports = max/*[at]
          .maxData = 1000
          .ggplot = "geom_smooth(colour='yellow')"
        }
      }  
      PageR {
        .xAxis = random/*[value]
        PlotR {
          .ports = max/*[at]
          .type = "SobolConvergence"
        }
      }
      PageR {
        .xAxis = random/*[value]
        PlotR {
          .ports = max/*[at]
          .type = "SobolIndices"
        }
      }
    }
    

    We invoke the OutputSelector to write only the final values of each iteration. For each of the PageR boxes, you must put all the uncertain parameters on the xAxis. You can use the find command to check whether you got it right:

    >> load demo/randomiser/randomiser6.box
    ...
    > find random/*[value]
    Port int    sim/random/k[value]
    Port double sim/random/deltaT[value]
    

    Likewise, you must put all the model response variables into the ports of the PlotR boxes:

    > find max/*[at]
    Port int sim/max/larva[at]
    Port int sim/max/pupa[at]
    Port int sim/max/adult[at]
    

    Now, it's time to run the model,

    > run demo/randomiser/randomiser6.box
    

    The 1024 iterations took 31 seconds on my machine. Paste into R and wait for the 1000 bootstraps statistics to finish. That took 14 seconds for me. For larger models, you might need >100,000 iterations while 10,000 bootstraps should suffice. For hours' long simulation, you should unattended to TRUE in the Simulation box.

    The first plot represents each iteration result asa dot in each. Remember that the model responses on the y-axis is the simulation step (i.e., days after 30 April), when the maximum value was attained:

    Clearly, k had no influence, while deltaT had a very clear-cut influence. Don't expect such simplicity coming out of more complex models!

    The second plot shows you the increased precision of the estimates of the Sobol' indices:

    We had sample size \(N=256\). The plot shows what would have happened with smaller \(N\). The estimates, shown for deltaT, k and their sum, are clearly converging and not changing much from \(N=128\) to \(N=256\). We conclude that we don't need to increase the sample size.

    The final plot shows the Sobol' indices for each of the model responses:

    The uncertain model parameters are sorted for each model response in decreasing order of importance. We are not surprised that the sensitivity indices for k are both nil, while they are both 1 for deltaT. This stresses that deltaT is not interacting with k to determine model uncertainty. In the cereal aphid-fungus model, you can find a more complicated sensitivity analysis.

    Since the simulation and bootstrapping computations may take a very long time, you should be aware how to save the results. How to do that is described for the cereal aphid-fungus model under Saving and restoring the analysis.

    xxx

    RandomiserMonteCarlo

    See RandomiserSobolSequence.

    xxx

    RandomiserStratified

    See RandomiserSobolSequence.

    xxx

    Randomiser­SobolSequence

    RandomiserSobolSequence is derived from RandomiserBase.

    #plugins/boxes/randomisersobolsequence.html

    RandomiserSobolSequence

    Interface

    InputsTypeDefaultPurpose / Expression
    iterationint- /.[iteration]
    iterationsint- /.[iterations]
    doSensitivityAnalysisboolFALSE Carry out a sensitivity analysis?
    bootstrapSizeint1000 Size of bootstrap sample (cheap in computation time); only used in sensitivity analysis
    seedint0 Seed for random numbers; if this is zero a random seed value will be used
    drawAtInitializeboolFALSE Draw a value when a box is initialized?
    drawAtResetboolTRUE Draw a value when a box is reset?
    drawAtUpdateboolFALSE Draw a value when a box is updated?
    Outputs   
    numVariablesint0Number of variables which are randomised [0;inf)

    Usage

    You can use RandomiserMonteCarlo for stochastic modelling and uncertainty analysis, but RandomiserStratified will explore the parameter space more efficiently. For sensitivity analysis, RandomiserSobolSequence is the preferred choice because it is the most effective of the three in covering all of the parameter space.

    The randomiser7.box⏷ script shows the differences, subtle but important, between the three randomisers. The boxscript breaks the general rule not to put more than one randomiser box into a script. The only good reason would be for a direct comparison like here. Each of the three randomisers is the source of random numbers used to generate random numbers for two variables x and y, both with a uniform distribution of numbers in the interval [20;40) (see RandomUniform).

    // randomiser7.box⏷ 
    Simulation sim {
      .iterations = 32
      .steps = 1
      .silent = TRUE
      Box monteCarlo {
        RandomiserMonteCarlo randomiser {
        }
        RandomUniform x {
          .min = 20
          .max = 40
        }
        RandomUniform y {
          .min = 20
          .max = 40
        }
      }
      Box stratified {
        RandomiserStratified randomiser {
        }
        RandomUniform x {
          .min = 20
          .max = 40
        }
        RandomUniform y {
          .min = 20
          .max = 40
        }
      }
      Box sobolSequence {
        RandomiserSobolSequence randomiser {
        }
        RandomUniform x {
          .min = 20
          .max = 40
        }
        RandomUniform y {
          .min = 20
          .max = 40
        }
      }
      OutputR {
        .scripts = "randomiser7.R"
        OutputSelector {
          .final = TRUE
        }
        OutputText {
          .ports = sim/*/*[value]
        }
      }
    }
    

    Run the boxscript,

    >run demo/randomiser/randomiser7.box
    

    and study the output:

    The three ransomisers worked like this to generate random numbers:

    • RandomiserMonteCarlo picked 32 numbers at random from the interval [20;40) for both x and y.
    • RandomiserStratified first divided the interval [20;40] into 32 slices (strata) of equal area for both x and y. Then it shuffled the order of these slices at random for both x and y. Then it picked a random number from within each slice (stratum).
    • RandomiserSobolSequence picked the first 32 number from the Sobol' sequence for two variables and assigned them to x and y.

    At the bottom you see the distribution of x. The Monte Carlo method yielded the least uniform and the Sobol' sequence the most uniform distribution.

    At the top you see the joint distribution, where the Sobol' sequence stands out with the best coverage of the 2-dimensional variable space; this is what Sobol' designed his sequence for. Moreover, the good coverage of Sobol' numbers is still obtained with many variables in n-dimensional space.

    The randomiser box is ignorant about the statistical distributions that are generated from. Behind the scenes it just produces random numbers in the interval [0;1], which are then grabbed by its sibling random boxes (in this example RandomUniform) and transformed into the appropriate distribution. For another distribution than uniform, you can check out RandomNormal, for example.

    Find more examples in RandomiserBase.

    xxx

    xxx

    RandomBase

    Interface

    InputsTypeDefaultDescription
    Pdouble0.95 [0;1]Range of the distribution covered by the [min;max) range
    drawAtInitializeboolcomputed []ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetboolcomputed []ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdateboolcomputed []ancestors::*/RandomiserBase::*[drawAtUpdate]

    Background

    You cannot have a RandomBase box in your boxscript. It only serves as a virtual base class for boxes to draw random numbers from various distributions:

    They all share the interface of RandomBase. Of the four inputs, you should only ever use P in your boxscripts. If you want to change when random numbers are drawn, you should do so in the RandomiserBase box. For this reason the three inputs (drawAtInitialize, drawAtReset and drawAtUpdate) will not be listed among the inputs for the boxes derived from RandomBase below.

    Usage

    You can have as many of these different kinds of random boxes as you want, but you should always keep as siblings inside one common parent box, together with one randomiser box, as described for RandomiserBase.

    xxx

    xxx

    #plugins/boxes/randombinomial.html

    RandomBinomial

    Interface

    InputsTypeDefaultPurpose / Expression
    Pdouble0.5Probability of event [0;1]
    drawAtInitializebool- ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetbool- ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdatebool- ancestors::*/RandomiserBase::*[drawAtUpdate]
    minboolFALSE Not used
    maxboolTRUE Not used
    Outputs   
    valueboolFALSE The most recently drawn value

    Usage

    RandomBinomial produces numbers from the binomial distribution. value will be TRUE with probability P.

    // random_binomial1.box⏷
    Simulation sim {
      .steps = 100
      Box random {
        RandomiserMonteCarlo randomiser {
          .drawAtUpdate = TRUE
        }
        RandomBinomial x {
          .P = 0.6
        }
        RandomBinomial y {
          .P = 0.9
        }
      }
      Box positive {
        Accumulator x {
          .change = if (random/x[value]) then 1 else 0
        }
        Accumulator y {
          .change = if (random/y[value]) then 1 else 0
        }
      }
      OutputR {
        OutputText {
          .ports = random/*[value] | positive/*[value]
        }
      }
    }
    

    Run the boxscript:

    > run demo/random_binomial/random_binomial1.box
    

    The final lines of the output show how x approaches 60% and y approaches 90%, as expected:

    > tail
    ...
    random.x random.y positive.x positive.y iteration step
           1        1       53.0       88.0         1   95
           1        0       54.0       88.0         1   96
           0        0       54.0       88.0         1   97
           1        1       55.0       89.0         1   98
           0        1       55.0       90.0         1   99
           1        1       56.0       91.0         1  100
    

    xxx

    xxx

    #plugins/boxes/randomlognormal.html

    RandomLogNormal

    Interface

    InputsTypeDefaultPurpose / Expression
    Pdouble0.0Not used [0;1]
    drawAtInitializebool- ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetbool- ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdatebool- ancestors::*/RandomiserBase::*[drawAtUpdate]
    mindouble0.01 Minimum value (included)
    maxdouble1.0 Maximum value (excluded)
    Outputs   
    valuedouble0.0 The most recently drawn value

    xxx

    xxx

    #plugins/boxes/randomloguniform.html

    RandomLogUniform

    Interface

    InputsTypeDefaultPurpose / Expression
    Pdouble0.0Not used [0;1]
    drawAtInitializebool- ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetbool- ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdatebool- ancestors::*/RandomiserBase::*[drawAtUpdate]
    mindouble0.01 Minimum value (included)
    maxdouble1.0 Maximum value (excluded)
    Outputs   
    valuedouble0.0 The most recently drawn value

    xxx

    xxx

    #plugins/boxes/randomnormal.html

    RandomNormal

    Interface

    InputsTypeDefaultPurpose / Expression
    Pdouble0.0Not used [0;1]
    drawAtInitializebool- ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetbool- ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdatebool- ancestors::*/RandomiserBase::*[drawAtUpdate]
    mindouble0.0 Minimum value (included)
    maxdouble1.0 Maximum value (excluded)
    Outputs   
    valuedouble0.0 The most recently drawn value

    xxx

    xxx

    #plugins/boxes/randomuniform.html

    RandomUniform

    Interface

    InputsTypeDefaultPurpose / Expression
    Pdouble0.0Not used [0;1]
    drawAtInitializebool- ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetbool- ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdatebool- ancestors::*/RandomiserBase::*[drawAtUpdate]
    mindouble0.0 Minimum value (included)
    maxdouble1.0 Maximum value (excluded)
    Outputs   
    valuedouble0.0 The most recently drawn value

    xxx

    xxx

    #plugins/boxes/randomuniformint.html

    RandomUniformInt

    Interface

    InputsTypeDefaultPurpose / Expression
    Pdouble0.0Not used for this distribution [0;1]
    drawAtInitializebool- ancestors::*/RandomiserBase::*[drawAtInitialize]
    drawAtResetbool- ancestors::*/RandomiserBase::*[drawAtReset]
    drawAtUpdatebool- ancestors::*/RandomiserBase::*[drawAtUpdate]
    minint0 Minimum value (included)
    maxint1 Maximum value (included)
    Outputs   
    valueint0 The most recently drawn value

    xxx

    xxx

    #plugins/boxes/ratio.html

    Ratio

    Interface

    InputsTypeDefaultPurpose / Expression
    denominatordouble0.0 Number to be divided
    divisordouble0.0 The divisor
    zeroTolerancedouble0.0 Used for infinity check
    allowInfinityResultboolTRUE Will yield an error if divisor < zeroTolerance
    Outputs   
    valuedouble0.0 The ratio

    xxx

    Records

    #plugins/boxes/records.html

    Records

    Interface

    InputsTypeDefaultPurpose / Expression
    fileNamestring"records.txt" Name of file with records; columns separated by white space
    dateColumnNamestring"Date" Name of column with date
    timeColumnNamestring"Time" Name of column with time
    cycleboolFALSE Cycle back to start at end of file? Forces ignoreYear=true
    ignoreYearboolFALSE Ignore year when synchronising with the calendar
    calendarDateTimedatetime- calendar[dateTime]
    Outputs   
    firstDateTimedatetimenull Date-time stamp of the first line in the file
    lastDateTimedatetimenull Date-time stamp of the last line in the file
    currentDateTimedatetimenull Date-time stamp of the current outputs
    nextDateTimedatetimenull Date-time stamp of the next outputs
    currentDatedatenull Date stamp of the current outputs
    nextDatedatenull Date stamp of the next outputs
    currentTimetimenull Time stamp of the current outputs
    nextTimetimenull Time stamp of the next outputs
    Additionally   
    One output port is created for each column in the input file (fileName), except for the date and time columns

  • Daily readings
  • You use a Records box to read logged data, i.e., data with a time stamp, typically a weather file. Here is one example showing the first and last lines of flakkebjerg 2005.txt⏷:

          Date        Tmin Tmax  Tavg  I
    01/01/2005  0    4.7   2.8   1.4
    02/01/2005  0.7  6.2   3.6   1.3
    03/01/2005  1.4  7     4.1   2
    ...
    29/12/2005  -3.6 0.9  -2.2   1.3
    30/12/2005  -2.4 1.3  -0.3   1.7
    31/12/2005  -3  -0.8  -2.5   1.5
    

    The input file must be a column-oriented text file with columns separated by whitespace (any number of blanks and tab stops between each column) and one row for each records. Columns must be complete; you cannot have an empty value. Rows, on the other hand, need not be complete. If some records are missing, say a few days due to a malfunctioning weather station, then the Records box will automatically interpolate (linearly) between neighboring records.

    The Records box must be accompanied by a Calendar box. It is the Calendar box that keeps track of time during a simulation, not the Records box. In this example, the simulation runs through three days in December:

    // records1.box⏷
    Simulation sim {
      .steps = 3
      Calendar calendar {
        .begin = 15/12/2005
      }
      Records weather { 
        .fileName = "../weather/flakkebjerg 2005.txt"
      }
      OutputR {
         OutputText {
           .ports = calendar[date] | weather[Tmin] | weather[Tmax]
         }
      }
    }
    

    We get this output from the simulation, as we would expect:

    > load demo/records/records1.box
    ...
    > run
    ...
    > head
    ...
          date Tmin Tmax iteration step
    2005/12/15  2.7  8.1         1    0
    2005/12/16  1.3  4.5         1    1
    2005/12/17 -4.2  0.7         1    2
    2005/12/18 -2.8  1.6         1    3
    

    Each of the columns in the input file produces an output port with the same name as the column heading.

  • Interpolation
  • The temperatures are daily estimates, which for interpolation purposes are assumed to have occured at midnight. We can explore this by changing the time step to 8 hours:

    // records2.box⏷
    Simulation sim {
      .steps = 6
      Calendar calendar {
        .begin = 15/12/2005
        .timeStep = 8
        .timeUnit = "h"
      }
      Records weather { 
        .fileName = "../weather/flakkebjerg 2005.txt"
      }
      OutputR {
         OutputText {
           .ports = calendar[dateTime] | weather[Tmin] | weather[Tmax]
         }
      }
    }
    

    The output hits the same values as before every midnight (head shows only the first 6 rows of output, write head 10 to see up to ten lines):

    > head 10
               dateTime      Tmin    Tmax iteration step
    2005/12/15T00:00:00       2.7     8.1         1    0
    2005/12/15T08:00:00   2.23333     6.9         1    1
    2005/12/15T16:00:00   1.76667     5.7         1    2
    2005/12/16T00:00:00       1.3     4.5         1    3
    2005/12/16T08:00:00 -0.533333 3.23333         1    4
    2005/12/16T16:00:00  -2.36667 1.96667         1    5
    2005/12/17T00:00:00      -4.2     0.7         1    6
    

    Pitfalls. Plants and animals have a diurnal rythm. Be careful that your model makes sense, if you decrease the time step to less than 1 day. If use a daily accumulated variable like precipitation, you need to transform the data. If your time step is 1 hour, for example, then you must divide the precipitation data in the input file by 24 before using it in a Records box.

  • Hourly readings
  • If you model runs on a finer time scale, you may have records at an hourly interval, or even down to seconds, corresponding to the smallest time step supported by Calendar (1 second). You may also have records at irregular intervals with a time stamp of date and time. In any case, you provide the time of the day in a separate column, as exemplified by the file copenhagen-may-hourly.txt⏷, which contains hourly data for the first half of May:

    Date       Time  Tair Rhair GlobRad DifRad Windspeed
    01-05-2001 00:00 4.7  85     0      0      2.6
    01-05-2001 01:00 4.1  84     0      0      2.6
    01-05-2001 02:00 3.8  84     0      0      2.6
    ...                                        
    15-05-2001 22:00 7.4  83     0      0      5.8
    15-05-2001 23:00 6.7  84     0      0      5.5
    16-05-2001 00:00 6.1  85     0      0      5.1
    

    In our boxscript, we specify the period of the simulation in the Calendar box:

    // records3.box⏷
    Simulation sim {
      Calendar calendar {
        .begin = 01/05/2001T00:00
        .end   = 06/05/2001T24:00
        .timeStep = 15
        .timeUnit = "m"
      }
      Records weather { 
        .fileName = "../weather/copenhagen-may-hourly.txt"
      }
      OutputR {
        PageR {
          .xAxis = calendar[dateTime]
          PlotR {
           .ports = weather[Tair] | weather[GlobRad] |weather[Windspeed]
          }
        }
      }
    }
    

    We let the simulation run with a 15-minute time step, which is a finer time resolution than given in the weather file. The Records box will oblige by interpolating the missing data, as you can see here:

    > ht 3
    ...
               dateTime Tair GlobRad Windspeed iteration step
    2001/05/01T00:00:00  4.7     0.0       2.6         1    0
    2001/05/01T00:15:00 4.55     0.0       2.6         1    1
    2001/05/01T00:30:00  4.4     0.0       2.6         1    2
                    ...
    2001/05/06T23:30:00 11.7     0.0      6.25         1  574
    2001/05/06T23:45:00 11.9     0.0     6.475         1  575
    2001/05/07T00:00:00 12.1     0.0       6.7         1  576
    

    The plot looks like this:

     

    The two flavours of midnight. The time stamps 2001/05/06T24:00:00 and 2001/05/07T00:00:00 are equivalent because 24 hours is translated into 0 hours (midnight) on the following day.

  • Ignoring the year
  • It happens quite often that you want to use a weather file without regards to the year noted in the time stamp. This is the purpose of ignoreYear, which defaults to FALSE but here set it to TRUE:

    // records4.box⏷Simulation sim {
      .steps = 3
      Calendar calendar {
        .begin = 15/12/2022
      }
      Records weather { 
        .fileName = "../weather/flakkebjerg 2005.txt"
        .ignoreYear = TRUE
      }
      ...
    }
    

    The Calendar box sets the simulation to begin on 15 December 2022. In response, the Records box will begin producing output from the first row that has a time stamp of 15 December. In this case, it wll be ignoring the time stamp's year (2005). As a result we get this output:

    We do get the same output as previously, except the year now follows the Calendar box:

          date Tmin Tmax iteration step
    2022/12/15  2.7  8.1         1    0
    2022/12/16  1.3  4.5         1    1
    2022/12/17 -4.2  0.7         1    2
    2022/12/18 -2.8  1.6         1    3
    

  • Extrapolation
  • What happens if we run past the end of the weather file? Consider this:

    // records6.box⏷
    Simulation sim {
      .steps = 5
      Calendar calendar {
        .begin = 28/12/2005
      }
      Records weather { 
        .fileName = "../weather/flakkebjerg 2005.txt"
      }
      ...
    }
    

    The Records box responds by extrapolating the final reading:

          date Tmin Tmax iteration step
    2005/12/28 -3.2 -1.4         1    0
    2005/12/29 -3.6 -0.9         1    1
    2005/12/30 -2.4  1.3         1    2
    2005/12/31 -3.0 -0.8         1    3
    2006/01/01 -3.0 -0.8         1    4
    2006/01/02 -3.0 -0.8         1    5
    

  • Recycling the year
  • If your weather file goes from 1 January to 31 December, or if it otherwise forms a whole year from beginning to end, e.g. from 21 March one year to 20 March the following year, then you can ask the Records box to recycle the readings. In that way, you can use one year's weather data to simulate several years. All right, that's a quick and dirty way of doing it, but sometimes we just want it like that.

    You achieve this behaviour by setting the cycle flag TRUE (it defaults to FALSE). If cycle is TRUE then ignoreYear will always be forced to TRUE as well, since the year registered in the input file has become irrelevant. Let's try it:

    // records7.box⏷
    Simulation sim {
      .steps = 5
      Calendar calendar {
        .begin = 30/12/2022
      }
      Records weather { 
        .fileName = "../weather/flakkebjerg 2005.txt"
        .cycle = TRUE
      }
      ...
    }
    

    We have set the calendar to reach past the end of the year. The output shows that we have successfully achieved to wrap around the weather file on 31 December and cycle back to its beginning on 1 January:

          date Tmin Tmax iteration step
    2022/12/30 -2.4  1.3         1    0
    2022/12/31 -3.0 -0.8         1    1
    2023/01/01  0.0  4.7         1    2
    2023/01/02  0.7  6.2         1    3
    2023/01/03  1.4  7.0         1    4
    2023/01/04  2.6  7.7         1    5
    
    #right.html

    Try it!

    Download the latest version with the newly published Cereal Aphid-Fungus model. Also includes the Virtual Greenhouse model.

    5 Oct 2023

    Model just published

    Read our paper on the Cereal Aphid-Fungus model and study the detailed documentation. Any questions? Write us.

    2 Aug 2023

    Home page overhaul

    We remain candy-coloured until further notice.

    1 Aug 2023

    Contact

    Any questions concerning our models and tools? Interested in visiting the lab? Want to chat online? Write us.

    #footer.html