Environment — Generic Parameter Background Model

The Environment model is designed to handle changing external parameters and the initialization and update of non-uniform parameter backgrounds to any spatial model. To that end, the model associates its own CellManager with the CellManager of the parent model, i.e. it uses the configuration from the parent’s CellManager to build its own. A cell in the parent model is then linked to the corresponding cell in the Environment model, which provides access to its state. If only the uniform parameter is used, this is optional.

This model provides one library of so-called “environment parameter functions” that can be used to modify the uniform parameter values in time, and a second library of so-called “environment state functions” that can be used to set and modify cell-wise (heterogeneous) parameters. It is also possible to add custom environment functions of both types. For the environment state functions, it differentiates between functions which are invoked once at initialization and others which are invoked during iteration. Furthermore, it is possible to control the times at which these functions are invoked. Additionally, a select config can be added within the state function configuration, which is passed to the select_cells feature of the CellManager (see Entity Selection).

The Environment model is an example of how Utopia models can be nested in each other.

Adding an Environment to your own model

The following is a guide to give a finished CA-model access to a global parameter and a non-uniform parameter background, or, in other words: an environment. The model is considered to have a global parameter named some_global_parameter and a heterogeneous parameter named some_heterogeneous_parameter which were up to now constant in time and spatially uniform, i.e. of the same value for all cells. Of course it is possible to use only either of the two types of parameters and to have several global or heterogeneous parameters.

First things first: include the header of the Environment model:

#include <utopia/models/Environment/Environment.hh>

Create your Environment’s global parameter, adding any parameters you consider part of the environment.

// Use the dummy type EnvParam if you want only to use heterogeneous
// parameter:
// using EnvParam = Environment::DummyEnvParam;

/// Parameter of the Environment model
/** NOTE this needs to inherit from the Environment::BaseEnvParam
 */
struct EnvParam : Utopia::Models::Environment::BaseEnvParam
{
    double some_global_parameter;
    // .. Add more parameters of type double here ..

    EnvParam(const Utopia::DataIO::Config& cfg)
    :
        some_global_parameter(
            Utopia::get_as<double>("some_global_parameter",cfg, 0.))
        // .. initialize additional parameters here ..
    { }

    ~EnvParam() = default;

    /// Getter
    double get_env(const std::string& key) const override {
        if (key == "some_global_parameter") {
            return some_global_parameter;
        }
        // .. Add the new keys here using else if ..
        throw std::invalid_argument("No access method for key '" + key
                                    + "' in EnvParam!");
    }

    /// Setter
    void set_env(const std::string& key,
                const double& value) override
    {
        if (key == "some_global_parameter") {
            some_global_parameter = value;
        }
        // .. Add the new keys here using else if ..
        else {
            throw std::invalid_argument("No setter method for key '" + key
                                        + "' in EnvParam!");
        }
    }
};

Create your Environment’s cell state, adding any heterogeneous parameters you consider part of the environment.

// Use the dummy type EnvCellState if you want only to use heterogeneous
// parameter:
// using EnvCellState = Environment::DummyEnvCellState;

/// State of the Environment model
/** NOTE this needs to inherit from the Environment::BaseEnvCellState,
 *       which gives the State access to its position in space.
 */
struct EnvCellState : Utopia::Models::Environment::BaseEnvCellState {
    /// The some_heterogeneous_parameter
    double some_heterogeneous_parameter;

    // Can add more parameters here ...

    /// Construct the cell state
    /** \details Uses the cell_manager.cell_params entry of your model
     */
    EnvCellState(const Utopia::DataIO::Config& cfg)
    :
        some_heterogeneous_parameter(
            get_as<double>("some_heterogeneous_parameter", cfg, 0.))
        // .. Add more initializations here ..
    { }

    ~EnvCellState() { }

    /// Getter
    double get_env(const std::string& key) const override {
        if (key == "some_heterogeneous_parameter") {
            return some_heterogeneous_parameter;
        }
        // can put more keys here when using more parameters
        // use else if
        else {
            throw std::invalid_argument("No parameter '"+ key +
                                        "' available in EnvCellState!");
        }
    }

    /// Setter
    void set_env(const std::string& key, const double& value) override {
        if (key == "some_heterogeneous_parameter") {
            some_heterogeneous_parameter = value;
        }
        // can put more keys here when using more parameters
        // use else if
        else {
            throw std::invalid_argument("No parameter '"+ key +
                                        "' available in EnvCellState!");
        }
    }
};

Next, create the link that is used to access the associated cell in the environment:

using EnvModel = Environment::Environment<EnvParam, EnvCellState>;

// NOTE you do not need the following if you use the DummyEnvCellState

using EnvCell = EnvModel::CellManager::Cell;

/// The type of the link container of cells in the Environment model
template<typename>
struct EnvLinks {
    /// Link to the associated cell in the Environment model
    std::shared_ptr<EnvCell> env;
};

If you use the EnvCellState: pass the created link to the CellTraits in your model, adapting this to the current definitions in your model. The last entry, EnvLinks, is important. If your current model does not have one or more of the entries shown below, use the values given in this example, i.e. the trait’s default values.

using CellTraits = Utopia::CellTraits<MyModelCell,   // models cell state
                        Update::sync,  // update mode
                        // whether to use the default constructor
                        false,
                        EmptyTag,      // cell tags
                        EnvLinks>;     // -- the links --

The following changes will need to happen in your model’s class, as it will be the parent to the Environment model.

First, you will need to add a (private) member to your model, preferably somewhere close to where you currently define the CellManager of your model:

// -- Members of this model -- //
/// The cell manager
CellManager _cm;

/// The Environment model
EnvModel _envm;

/// You might want to keep the global parameter
double _some_global_parameter;
// NOTE you need to manually synchronize it with the environment!

// ...

Then, within the constructor of your model, the constructor of the EnvModel must be called. Again, this should happen shortly after the construction of the CellManager. The EnvModel receives your CellManager as an argument, because it uses its configuration in order to create a corresponding grid representation. It then associates the cells with each other, such that the env member is set correctly.

/// Construct your model
template<class ParentModel>
YourModel (const std::string name, ParentModel& parent)
:
    Base(name, parent),

    // Initialize the CellManager
    _cm(*this),

    // Initialize the Environment, providing it with that cell manager
    _envm("Environment", *this, _cm),
    // NOTE you can forgo without associated cm if you use the
    //      DummyEnvCellState:
    // _envm("Environment", *this),

    // ...

    {
        // ...

        // Track parameters; these will be saved to the HDF5 file
        _envm.track_parameter("some_global_parameter");
        _envm.track_state("some_heterogeneous_parameter");
        // .. add your additional ones here ..

        // or alternatively: multiple ones
        // _envm.track_parameters({"some_global_parameter",
                                   "some_other_glob_parameter"});
        // _envm.track_states({"some_heterogeneous_parameter",
                                   "some_other_het_parameter"});
    }

Warning

Make sure to iterate the Environment model when performing a step. This should happen first, to ensure that the timings of both models correspond.

void perform_step () {
    _envm.iterate();

    // Synchronize your global parameter
    _some_global_parameter = _envm.get_parameter(
                                    "some_global_parameter");

    // ... all the rest of your perform_step method
}

You can access the parameters in the following way:

RuleFunc update = [this](const auto& cell)
{
    // The cell's state
    auto state = cell->state;

    // The environment cell's state
    auto env_state = cell->custom_links().env->state;

    // use the synchronized some_global_parameter

With this, your model is ready to use and just needs to be configured!

At some point, coupled models will require manually writing the prolog and epilog:

/// Call the prolog of the sub-models and the model defaults
void prolog () {
    _envm.prolog(); // prolog of the submodel
    // NOTE the prolog has to be called before starting the iteration of a
    //      submodel. This can be at any time, but usually it will be here.

    this->__prolog();; // default prolog
}

/// Call the epilog of the sub-models
/** NOTE this overwrites the default of your model's epilog. So make
 *  sure that everything that needs to be done has been done.
 */
void epilog () {
    _envm.epilog(); // epilog of the submodel
    // NOTE the epilog should be called at the end of the submodel's
    //      iteration. This can be at any time, but typically it will be here.

    this->__epilog(); // default epilog
}

Configuring the Environment model

To restore the uniform configuration of your model, simply move the some_heterogeneous_parameter key to the parameters of your cell initialization. This works even if you don’t initialize your model from the configuration, because the Environment model inherits the full CellManager configuration.

YourModel:
  some_global_parameter: 3.14

  cell_manager:
    cell_params:
      some_heterogeneous_parameter: 0.2

    ..

Now you can use the Environment model to introduce non-uniformities into your model, e.g. initialize a spatial gradient within some_heterogeneous_parameter.

YourModel:
  # ...

  # Environment configuration
  Environment:
    # provide some parameter functions
    env_param_funcs:
      - set:
          some_global_parameter:
            value: 20
            times: [0]
            invoke_at_initialization: True
      - sigmoidal:
          some_global_parameter:
            amplitude: 10
            period: 20
            phase: 0.5
            offset: 10
            # NOTE this is not invoked at initialization
    # provide some state functions called at initialization
    init_env_state_funcs:
      # provide a sequence of functions
      - uniform:
          some_heterogeneous_parameter:
            # overwrite the default initialization
            mode: set
            value: 0.0
      - slope:
          some_heterogeneous_parameter:
            mode: add
            values_north_south: [1., 0.]
            # interpolate linearly between these two values

    # provide some state functions for updates
    env_state_funcs:
      - slope:
          some_heterogeneous_parameter:
            mode: set
            values_north_south: [0., 1.]  # flip the slope
            times: [2]                    # ... in the second iteration
      - uniform:
          some_heterogeneous_parameter:
            mode: add
            value: 0.1
            select: # this function is applied only to a selection of cells
              mode: sample
              generate: once    # generate a selection that is fixed
              # can also be 'always' to generate every invocation
              num_cells: 4
              fix_selection: false  # generated new for every call

Warning

When using recursive update of the configuration files, make sure to overwrite entries you no longer wish to use. For instance, if you set a default env_state_funcs in your default model configuration, but do not wish to update the intial environmental state from a run config, write:

Environment:
  init_env_state_funcs:
    # your init sequence here ...

  env_state_funcs: ~  # overwrites the sequence, and nothing is invoked

Warning

The cell manager is currently set up according to the configuration of the parent’s cell manager. Unfortunately, this does not include information about the space, hence make sure to also copy the space config to the Environment’s configuration, e.g. using yaml anchors, or the association of the two cell managers will fail!

Hint

If you are unsure which environment functions are set up and are invoked at which point in time, call utopia run with the --debug flag, which will increase log verbosity and tell you when environment functions are called. Alternatively, you can add the log_level: debug or log_level: trace entry to the Environment model entry in the configuration.

Note

You can still use the features of the model even if your current model does not use the cell_manager. If you only use the EnvParam, just use the model without an associated cell manager (see the above description of how to change to constructor of your model). If you want to use the EnvCellState, pass the template argument associate=false; the model can then be initialized without an associated cell manager. However, in all of these cases you must configure the cell_manager within the Environment node of your configuration:

Environment:
  cell_manager:
    grid:
      structure: square
      resolution: 1
    neighborhood:
      mode: empty
    cell_params: ~


  # Continue as described above

Collection of parameter and state functions

Currently implemented environment functions are available in the two collections ParameterFunctionCollection and StateFunctionCollection. Please check their documentations for the configuration of the respective function.

Default Model Configuration

Below are the default configuration parameters for the Environment model. These include an example of how environment functions can be set up.

# --- Space parameters --------------------------------------------------------
# The physical space this model is embedded in
space:
  periodic: true

# --- CellManager and cell initialization -------------------------------------
# NOTE Only used in standalone mode
cell_manager:
  grid:
    structure: square
    resolution: 16      # in cells per unit length of physical space

  # Cell initialization parameters
  cell_params:
    # The heterogeneous model default parameter.
    # Here, it will be overwritten by the Environment's init_env_state_funcs
    some_heterogeneous_parameter: 0.2

some_global_parameter: 0.


# --- Default configuration for Environment model -----------------------------
# ... Environment functions invoked once at initialisation ....................
init_env_state_funcs:
  # provide a sequence here
  - uniform:
      # The parameter to which to apply the env
      some_heterogeneous_parameter:
        mode: set     # can be: set, add
        value: 0.0
      # Can add more parameters here ..

  - steps:
      some_heterogeneous_parameter:
        mode: add
        latitudes: [0.5]
        values_north_south: [.4, .0]

  - noise:
      some_heterogeneous_parameter:
        mode: add
        distribution: normal
        mean: .0
        stddev: .1

  - uniform:
      # The parameter to which to apply the env
      some_heterogeneous_parameter:
        mode: set     # can be: set, add
        value: -0.2
        select:   # apply this rule only to cm.select_cells(..)
          mode: clustered_simple
          generate: once  # can be once or always

          # Clustering parameters
          p_seed: .02    # Probability with which a cell is a cluster seed
          p_attach: .1   # Attachment probability (per neighbor)
          num_passes: 5  # How many attachment procedures to perform

# ... Environment functions invoked each step .................................
# To control the times at which these are invoked, you can add the `times` key,
# which is a sequence of invocation times. If the `times` key is not given,
# the environment function is invoked _every_ step. Note that zero or negative
# times are filtered out. For longer sequences, the !listgen tag can be used.
env_state_funcs:
  - noise:
      some_heterogeneous_parameter:
        mode: add
        distribution: normal
        mean: .0
        stddev: .02

  - slope:
      some_heterogeneous_parameter:
        mode: add
        values_north_south: [-0.1, 0.]
        times: !listgen [5]

  - slope:
      some_heterogeneous_parameter:
        mode: add
        values_north_south: [0.2, 0.]
        times: !listgen [5, 11, 2]


env_param_funcs:
  - set:
      some_global_parameter:
        value: 10
        invoke_at_initialization: True
  - sinusoidal:
      some_global_parameter:
        amplitude: 10
        period: 20
        times: !listgen [100]
        phase: 1
        offset: 10

        invoke_at_initialization: False # must not be invoked at initialization