Chapter 14: Modularity

Many FRED models need similar components, so FRED supports several ways to re-use FRED code. This chapter address the use of include statements that includes other files of FRED code, and use statements that invoke FRED modules.

Include Files and FRED_PATH

One way to re-use FRED code is through the use of include statements. If a FRED program has a statement of the form:

include <filename.fred>

then the indicated file is inserted into the FRED program at that point.

FRED searches for the indicated file through the list of directories defined in the environmental variable FRED_PATH. Environmental variables are set in your .bashrc file or other shell startup script (.zshrc for new Mac users), for example:

export FRED_PATH=".:${HOME}/FRED_PROJECTS/project1:${HOME}/FRED_PROJECTS/project2"

With the settings above, the search for an included file would first look in the current directory, followed by ${HOME}/FRED_PROJECTS/project1 and then ${HOME}/FRED_PROJECTS/project2, where $HOME is the user’s home directory. Continuing with the example settings above, if the FRED program contains the statement:

include sub_project_z/submodel.fred

then the file would be found if it is located at ${HOME}/FRED_PROJECTS/project2/sub_project_z/submodel.fred and does not appear in any earlier directory in FRED_PATH.

The included filename may also include the relative path element ../.

If the FRED_PATH environmental variable is not set, FRED searches for the file in the current working directory.

Modules

The FRED module construct takes file inclusion further by supporting parameters that replace parts of the included code. In programming terms, a module as a kind of macro whose expansion is inserted into the calling program.

A FRED module is included in a FRED program by a code block of the form:

use <module_id> {
    <parameter> = <value>
    <parameter> = <value>
    ...
    <parameter> = <value>
}

where each parameter is defined within the module and each value may be any non-empty string. The FRED code in the module is inserted into the FRED program at this point, but each instance of a parameter is replaced by its associated value. Parameters may appear anywhere with the module’s code, even within other strings, so modules can be used in a wide range of situations, including partially rewriting the module’s code.

The <module_id> may include a path and may refer to the FRED environmental variables, just like included files. As special case is that if the <module_id> has the form FRED::<module_name> then the module is called from the FRED Library, as explained in the Section below.

For example, if a module with id Influenza is located in the file $FRED_PROJECT/Influenza.fredmod then it may be called with use $FRED_PROJECT/Influenza.

Example Module: Seasonality of Transmission

Before discussing how to create modules, let’s consider a specific example. Many diseases have seasonality, meaning that the transmissibility varies according to the time of the year. Seasonality may be due to changes in temperature, humidity, human contacts patterns, or other causes. One way to model seasonality is to change the transmissibility of a condition by multiplying the default transmissibility by the current value of a function that cycles over the course of a year and whose apex corresponds to the time of year with peak transmissibility and whose nadir corresponds to the time of minimal transmissibility.

The following figure shows a seasonally adjusted transmissibility curve with a maximum transmissibility of 1.5 on January 1 of each year with a reduction of 100% occurring six months later.

_images/chapter14.image1.png

For other conditions, different seasonal adjustments may be appropriate. The following figure shows a seasonally adjusted transmissibility curve with a maximum transmissibility of 1.5 in the Spring and Fall of each year with a reduction of 50% occurring in the Summer and Winter.

_images/chapter14.image2.png

Rather than having to copy and edit the code for seasonality for each condition, we can create a single module called Seasonal_Transmission and then invoke that module for as many different conditions as we need, passing appropriate parameters to give different characteristics of seasonality for each condition.

Let’s see how this would work in a FRED program that contains two conditions, say for influenza A (InfA) and influenza B (InfB). Suppose we know that InfA has its peak about April 1 and has a 90% seasonal decline 6 months later, and that InfB has its peak about November 7 and has n 80% seasonal decline 6 months later.

The main program might contain the following fragments of code:

simulation {
    locations = Jefferson_County_PA
    start_date = 2020-Jan-01
    end_date = 2020-Dec-31
}

include InfA.fred
include InfB.fred

...

use FRED::Seasonal_Transmission {
    condition = InfA
    peak_day_of_year = 120
    seasonal_reduction = 0.9
    seasonal_period = 365
}


use FRED::Seasonal_Transmission {
    condition = InfB
    peak_day_of_year = 300
    seasonal_reduction = 0.8
    seasonal_period = 365
}

This program uses the Seasonal_Transmission module from the FRED library for each disease, passing in the appropriate control parameters.

Assuming that 5 cases of each disease are imported on each day of the simulation (using another module of the FRED Library discussed below), the resulting epidemic curves look like this:

_images/chapter14.seasonal-trans.png

The simulation starts on January 1 when the season for InfB is already past its peak, so only a small outbreak occurs before it fades out. The epidemic for InfB takes off again in the Fall, but its peak is lower the the curve for InfA, since there is already some immunity within the population from the Spring InfB outbreak.

Defining a FRED Module

A module is similar to an ordinary FRED component model containing one or more condition, with some specific differences:

  • A module must be a separate file with the extension .fredmod

  • If the module contains parameters, it must contain a prototype of the module call of the form:

    prototype <module_id> {
        <parameter> = <value>
        <parameter> = <value>
        ...
        <parameter> = <value>
    }
    
  • Within the code of the module, each instance of a parameter in the prototype that is to be substituted for must be delimited (before and after) by the dollar sign symbol $.

When the module is called, the values supplied in the use block in the calling program are substituted for the delimited parameters within the module code.

If the calling program omits any parameter, then the value on the right-hand-side is used as a default value.

The calling program may include the parameters in any order.

After substitutions for the parameters, the result is included at that point in the FRED program.

Module Tags

Modules often construct one or more new conditions each time they are called. Modules usually use one or more of the parameters as a module tags to distinguish different invocations of the modules from one another.

For example, in the Seasonal_Transmission example discussed here, the prototype contains:

prototype Seasonal_Transmission {
    condition = CONDITION_TAG
    ...
}

where the condition parameter is a module tag that names the condition whose transmission should be affected by the invocation of the module. In the main program above, there are two calls to the modules, one with

condition = InfA

and one with

condition = InfB

Since the modules tags are different, a different set of FRED statements will be included in the main program for each call to the module.

Example of a Module

Here is an example of the Seasonal_Transmission module from the FRED Library. The module is contained in a file named Seasonal_Transmission.fredmod:

##########################################
# Seasonal_Transmission Module
# Author: John Grefenstette, [email protected]
# Date: 10 Jun 2020
##########################################

prototype Seasonal_Transmission {
    condition = CONDITION_TAG
    peak_day_of_year = 1
    seasonal_reduction = 0
    seasonal_period = 365
}

variables {

    # variable used to hold original transmissibility
    global Seasonal_Transmission_$condition$_max_trans

    global Seasonal_Transmission_$condition$_peak_day_of_year
    Seasonal_Transmission_$condition$_peak_day_of_year = $peak_day_of_year$

    global Seasonal_Transmission_$condition$_seasonal_reduction
    Seasonal_Transmission_$condition$_seasonal_reduction = $seasonal_reduction$

    global Seasonal_Transmission_$condition$_seasonal_period
    Seasonal_Transmission_$condition$_seasonal_period = $seasonal_period$
}

condition SEASONAL_TRANSMISSION_OF_$condition$ {
    start_state = Start
    meta_start_state = Adjust

    state Start {
          wait(0)
        next(Excluded)
    }

    state Adjust {

        if (sim_day == 0) then \
           Seasonal_Transmission_$condition$_max_trans = transmissibility_of_$condition$

        $condition$.trans = Seasonal_Transmission_$condition$_max_trans * \
           (1.0 - Seasonal_Transmission_$condition$_seasonal_reduction * \
            0.5 * (1 + sin(2*3.14159  * \
           (abs(day_of_year - Seasonal_Transmission_$condition$_peak_day_of_year) /
            Seasonal_Transmission_$condition$_seasonal_period + 0.75))))

        wait(24)
        next(Adjust)
    }
}

The module contains three components.

  • The prototype block defines the parameters and their default values.

  • The variables block defines a set of variables used in the module.

  • The condition block defines a condition that will be included in the calling program.

Notice that the variable names and the condition name contain the module tag supplied in the prototype, so each time the module is called with a distinct tag, distinct variables and conditions will be created.

The details of the formula used to adjust the transmissibility need not concern us here, other than to note that the formula alters $condition$.trans, which will refer to the transmissibility of the condition named in the tag parameter.

When the module is called with condition = InfA in the example above, the parameters are replaced by the values specified, producing the code segment:

condition SEASONAL_TRANSMISSION_OF_InfA {
    start_state = Start
    meta_start_state = Adjust

    state Start {
          wait(0)
        next(Excluded)
    }

    state Adjust {

        if (sim_day==0) then \
       Seasonal_Transmission_InfA_max_trans = transmissibility_of_InfA

        InfA.trans = Seasonal_Transmission_InfA_max_trans * \
           (1.0 - Seasonal_Transmission_InfA_seasonal_reduction * \
            0.5 * (1 + sin(2*3.14159  * \
           (abs(day_of_year - Seasonal_Transmission_InfA_peak_day_of_year) /
            Seasonal_Transmission_InfA_seasonal_period + 0.75))))

        wait(24)
        next(Adjust)
    }
}

Notice that the name of the condition added by the module is SEASONAL_TRANSMISSION_OF_InfA as a result of the substitution of InfA for the string $condition$ in the module file. The state SEASONAL_TRANSMISSION_OF_InfA.Adjust changes the transmissibility of the InfA condition. Likewise, the second invocation of the module will produce code for a condition called SEASONAL_TRANSMISSION_OF_InfB and adjust the transmissibility of the InfB condition.