Static field-agent models with Campo

We will use the PCRaster modelling framework for static and dynamic modelling, and use the campo Python module to create field and agent based objects. We will start with building a static model.

Open the template file static_model.py in your text editor and inspect it. The script imports the necessary Python modules and creates the static model class FoodEnvironment . You will add your model processes, i.e. operations on fields or agents, to the initial section of the model script. Processes stated in this section will be executed once when running the model.

We will model food propensity of households and use household locations and shop locations in the municipality of Utrecht, the Netherlands, for our model. The locations of stores and household are stored in two separate text files that we will use later on.

Initializing your model

To add phenomena to a model you first need to initialize a model data object. Remove pass from the initial method and add the following to your model:

foodenv = campo.LueMemory()

The foodenv object will provide access to the methods for constructing agents, and eventually arrange the storage to the LUE dataset on disk.

Adding phenomena, property sets and properties

You can create a phenomenon with add_phenomenon("phenomenon_name") . Add the phenomenon foodstore for the food stores to your food environment model object. This will add an empty phenomenon with zero agents to your model:

foodstore = foodenv.add_phenomenon("foodstore")

A phenomenon can contain point agents or field agents, the type of an agent is defined by its spatial domain. Point agents are specified by two-dimensional coordinates, field agents by the spatial extent given by two corner coordinates of their bounding box.

First of all you will add point agents to your model.

Adding point agents

You can add property sets to the food store phenomenon with foodstore.add_property_set(property_set_name, input_file), where both arguments are strings, the first assigning the name of the property set and the second the filename to the input file defining the spatial domain.

Add a propert set frontdoor to your phenomenon and use the file shops_locations.csv setting the spatial domain. The domain input file is also used to determine the overall number of the agents in a model, here point agents given by the x and y coordinates of their front door locations.

With a property set initialised you can add various properties to a property set. The properties share at the same time the spatial domain of their property set. You can add properties by just using the dot notation on property sets and initialize them directly, e.g. setting postal_code to 1234 with:

foodstore.frontdoor.postal_code = 1234

This will assign the value 1234 to the postal code property value of each agent.

You can also initialise each agent with a different value, for example with random values. We will now initialise the property x_initial with values drawn from a uniform distribution. First, you need to add properties that hold the lower and upper limits for the random distribution. Use -0.5 as the lower limit and 0.5 for the upper limit.

Afterwards initialise the x_initial property values with

foodstore.frontdoor.x_initial = campo.uniform(foodstore.frontdoor.lower, foodstore.frontdoor.upper)

Each of the agents will now hold a random value within the given interval.

Hint: example script
import pcraster as pcr
import pcraster.framework as pcrfw

import campo


class FoodEnvironment(pcrfw.StaticModel):
    def __init__(self):
        pcrfw.StaticModel.__init__(self)

    def initial(self):
        foodenv = campo.LueMemory()

        foodstore = foodenv.add_phenomenon('foodstore')

        foodstore.add_property_set('frontdoor', 'shops_locations.csv')

        foodstore.frontdoor.delta = 0.2
        foodstore.frontdoor.postal_code = 1234

        foodstore.frontdoor.lower = -0.5
        foodstore.frontdoor.upper = 0.5
        foodstore.frontdoor.x_initial = campo.uniform(foodstore.frontdoor.lower, foodstore.frontdoor.upper)

myModel = FoodEnvironment()
staticFrw = pcrfw.StaticFramework(myModel)
staticFrw.run()

Writing property values to a LUE dataset

All agents and properties you created so far exist only in memory. To store those to disk you need to perform a few additional steps at the end of your initial section.

First create a LUE data set with a given name by

foodenv.create_dataset("food_environment.lue")

All phenomena, property sets and properties can then be stored in the LUE dataset with

foodenv.write()

Task: Run the script to obtain your LUE dataset.

Inspecting point agent property values

We provide a Python script that reads property values from an existing LUE dataset that you just created when running your model. For point agents, pandas dataframes are returned showing the objects per row and property values per column.

Open the script read_agents.py and inspect it. Check the filename of your LUE dataset and modify the property names you want to include if necessary.

campo.dataframe.select returns a dataframe with dictionary based structure and xarray datasets. First argument is the dataset and phenomenon, second argument a list of properties that you want to access. The to_csv method is using this dataframe to generate a plain text output file of the agent’s property values.

Task: Run the script to obtain the result values.

Visualising point agent property values

The food store locations are spread over the municipality of Utrecht. To create a simple visualisation of the point agent locations we provide the script plot_point_agents.py. Open and inspect it. You open an existing LUE dataset and create a dataframe with desired point agent properties. We will only use one point agent property for plotting, the x_initial property. The create_point_pdf() function is a helper function that you can use to generate a geospatial PDF with the road network of the municipality of Utrecht and the front door locations of each food store as a second layer.

Task: Run the script and find the output file utrecht.pdf in your working directory. Open the file in your PDF viewer. In the Layer tab you can select or deselect the road or foodstores layer.

Operations on point agents

Now that the properties are created and initialised, you can perform several operations to modify their values. Arithmetic operations (+, -, /, *) and comparisons (e.g. <, >=) can be directly used with properties, they are overloaded operators and are executed for each object in a property. Other operations on properties need to be taken from the campo module, such as the uniform() operation.

Task: Add a few operations to modify property values. Check their updated values afterwards with the read_agents.py script.

Hint: example script
# Your initial section could state operations similar to this:

  def initial(self):
     ...

     foodstore.frontdoor.a = 0.2 + 0.7 + foodstore.frontdoor.x_initial
     foodstore.frontdoor.b = 1234 - 4
     foodstore.frontdoor.c = 4.5 * campo.exp(foodstore.frontdoor.a)

Adding field agents

We now will add agents that have a two-dimensional spatial extent. In general, the spatial extents can be for all agents of the same size (e.g. fixed sized neighbourhoods), or the extents can differ between agents (e.g. different catchments). In our example we will consider the surrounding of the food stores with different neighbourhood sizes per agent.

In general, field agents are added comparably to the point agents with add_property_set. But now the input file shops_extent.csv is used describing the spatial domain holds the spatial extent of each agent given by the lower left and upper right coordinates, and the number of rows and columns of the raster.

Task: Add a field agent property set surrounding to your model. Also add and initialise a few field agent properties.

Hint: example script
# Your initial section could state operations similar to this:

  def initial(self):
     ...

     foodstore.add_property_set('surrounding', 'shops_extent.csv')

     foodstore.surrounding.lower = 3
     foodstore.surrounding.upper = 12
     foodstore.surrounding.c = campo.uniform(foodstore.surrounding.lower, foodstore.surrounding.upper)

Operations on field agents

Operations on field agents can be performed in a similar manner to the operations on point agents, mathematical operations can be used as before. Add a few field properties and execute operations (that are now spatial operations) on the properties. You can also use the campo.uniform() operation to create random values, in this case two-dimensional random fields for each agent in the property set.

Task: Add a few field agent operations to your model.

Spatial operations

With a two-dimensional raster-based spatial domain we particularly can execute spatial operations on the agents of a property set. You can, for example, calculate for each food store the distances to other foodstores within their spatial surrounding. This can be done with the spread(start_locations, initial_friction, friction) operation calculating for each cell the shortest distance to non zero cells.

First, you need to create a raster with locations of food stores in a surrounding. Use the feature_to_raster operation:

foodstore.surrounding.start_locations = campo.feature_to_raster(foodstore.surrounding, foodstore.frontdoor)

First input argument to the operation is the field property set holding the spatial surrounding of each food store, second argument are the front door locations of the all the food stores in the modelling area. Return value of the operation is a raster with values of 1 for cells with corresponding food store locations and 0 otherwise.

You also need to create properties for the other two arguments of the spread operation, the initial friction distance (use the value 0) and the friction (use the value 1). Using these two values you calculate the absolute distances (in metres).

Task: Add the calculation of spread to your initial section and run the model script.

Hint: example script
# Your initial section could state operations similar to this:

foodstore.surrounding.start_locations = campo.feature_to_raster(foodstore.surrounding, foodstore.frontdoor)

foodstore.surrounding.initial_friction = 0
foodstore.surrounding.friction = 1

foodstore.surrounding.distance = campo.spread(foodstore.surrounding.start_locations, foodstore.surrounding.initial_friction, foodstore.surrounding.friction)

Visualising field agent property values

To briefly visualise the values of the field agent properties we provide the script plot_field_agents.py. Open and inspect it. Similar to the point agent script you open an existing LUE dataset and create a dataframe with the desired field agent properties. For a simplified plotting we will only use one field agent property, the distance property.

The create_field_pdf() function is a helper function that you can use to generate a geospatial PDF with the road network of the municipality of Utrecht and the spatial surroundings of each food store as a separate layer. Run the script and find the output file utrecht.pdf in your working directory. Open the file in your PDF viewer. In the Layer tab you can select or deselect individual stores.

Spatial domains of field agents

In our example we used different spatial extents for the agents of our surrounding property. Another option implemented in Campo is that all field agents have the same spatial extent and discretisation.

Task: Use the file shops_extent2.csv to initialise your field agents, run your model and create the PDF.