Working with submodels : Multiple-instance submodels

Multiple-instance submodels

Multiple-instance submodels are one of Simile's most valuable features, for constructing object-based models. For real-world objects, such as a tree, it is useful to be able to model several particular instances of the object, each acting in the same general way, though differing in their particular attributes.

  • Fixed membership submodels have a specified number of instances
  • Per-record submodels have one instance for each set of data loaded for its parameters.
  • Population (variable membership) submodels offer control over the number of instances using special model elements
  • Special-purpose submodels have many instances, and extra features which assist in modelling particular common phenomena, e.g., spatial grids

Working with submodels : Multiple-instance submodels : Fixed

Multiple-instance submodels: fixed membership

A fixed-membership submodel is one type of multiple-instance submodel. In this case, the number of instances is fixed throughout the simulation run.

By making a fixed-membership multiple-instance submodel, we are saying that:

  • The model contains a fixed number of objects, each of which behaves according to the same rules (represented by the model elements and equations inside it).
  • Each object may (and usually does) differ in one or more numeric values: the initial value for compartments, or the values of parameters.

When should I use a fixed-membership multiple-instance submodel?

Fixed-membership submodels are useful for handling many forms of disaggregation in model design. You may, for example, be interested in modelling the changing population of a country like the UK. You first model has a single compartment, representing the total population size. You then decide to represent this information on a regional basis, in order to capture differences in population parameters in different regions. You have now disaggregated your model into multiple regions. Your model diagram looks very similar: the only difference is that the part representing population dynamics is now wrapped up in a multiple-instance submodel, one instance for each region. This is a fixed membership submodel, because the number of regions is fixed.

Conversely, fixed-membership models can be used for scaling up. To take the previous context, you might have begun making a model of the population growth of just one region. You then want to scale the model up to the whole of the UK. Again, you wrap up the original model and make it into a fixed-membership, multiple-instance submodel.

Although disaggregation and scaling up are conceptually very different - in fact, opposite - activities, the appearance of both the before and after model diagrams is the same in both cases. The only difference comes in terms of the initialisation and parameterisation for the original model.

How to make a fixed-membership submodel

  1. Use the submodel tool to drag a submodel envelope on your model diagram. The submodel may be drawn in an open area of the model diagram, and not enclose anything. Or you can drag the submodel around existing elements on your model diagram, if you want those enclosed in the submodel.
  1. Open up the submodel properties dialogue by selecting the Pointer tool, then double-clicking anywhere in the blank area of the submodel (not on its border, and not on any existing elements). Note that the "Generated set" radio button has been selected by default. This term denotes that the submodel is to be fixed-membership. Enter a value into the "Dimensions" box corresponding to the number of instances you want. Click on the "OK" button.
  1. Back on the model diagram, the submodel's appearance has now changed. Its simple border has now been replaced by multiple lines on the bottom and right, indicating that there is a fixed number of multiple instances (it's meant to look like a stack of cards).

Dimensions of a fixed-membership submodel

The dimensions of a submodel are a list of one or more dimensions, separated by commas if there is more than one. Each dimension can be:

  • An integer greater than 1, or
  • The name of an enumerated type, or
  • size(x), where x is the name of another component in the model. The dimension then has the same value as the dimensions of the named component.

If there is just one dimension, the submodel's instances form a one-dimensional array, or vector, in which each instance has a unique index which is a number starting from one (if the dimension is numerical) or a member of the enumerated type whose name is the dimension.

If there is more than one dimension, the submodel instances are arranged in an n-dimensional array, with each instance having indices corresponding to its place long each dimension.

How to make the different instances different

There are two main ways in which you can make one instance different from the others.

  • You can give each instance a different initial value for one or more compartments.
  • You can give each instance a different value for one or more parameters.

Conceptually, these are very different. In the first case, you are saying that the differences between the individuals are simply in terms of the state they happen to be in when you start the simulation. In the second case, you are saying that the individuals differ in some intrinsic property. You can of course mix these: individuals may differ in both their initial state and their parameterisation.

There are several methods you can use to assign different values to different instances. These techniques apply equally to compartments and parameters.

Load the values from a file

If you put a fixed parameter inside a fixed membership submodel, then when you run the model, there will be an entry in the file parameters dialogue requiring values for this parameter. The data you enter will have to include a value for each instance of the submodel.

Generate the values randomly

Enter the expression

rand_const(0,10) (replacing 0 and 10 by whatever range you want)

in the Equation box for the compartment or parameter to cause each instance to be assigned a value randomly picked from the specified range. rand_const(A,B) samples from a rectangular probability distribution over the range A...B: Simile also alows you to use values from other distributions, e.g. Normal (and it is possible to engineer these yourselves).

Note that if there is no explicit indication that they return a constant value, Simile's statistical functions return a new sample from the given distribution on each time step. If you are using a sampling function to set up a multiple instance submodel with data that conforms to a statistical pattern, it is probable that you want each instance to keep the value it was given when the model was initialized or reset. This can be arranged by wrapping the statistical function in the at_init() function, e.g., at_init(gaussian_var(50,10)).

Select elements from an array

If your submodel has a small number of instances, you can explicitly list the value for each instance in an array, and select from this array the value for each instance.

Let's say you want to model the loss of water from 5 tanks. The tanks differ in terms of the initial amount of water they hold: say 10,5,20,50 and 9 litres. You have made the water flow model into a submodel with 5 instances. You could use the following expression in the Equation box to initialise the water compartment:

element([10,5,20,50,9],index(1)) (replacing [10,…,9] by whatever you need)

This selects the value 10 for the first instance, the value 5 for the second, and so on. See the help information on the functions element and index for more information.

Alternatively, you could have a separate array variable, say "initial_water", whose only job is to hold the array of values, take an influence arrow from it to the compartment, then select a value from that array for each instance:

initial_water = [10,5,20,50,9]

water = element([initial_water],index(1))

Use conditional expressions

You can develop expressions conditional on the value of index(1) (i.e. the numeric index of each instance) in a wide variety of ways. For example, you want the first three instances to have a value of 20, and the rest 10, then you could use the expression:

if index(1)<=3 then 20 else 10

Use a sketched graph or built-in table

You could use the sketch graph or built-in table functions in the equation dialogue to specify the values of the compartment or parameter as a function of index(1). This is particularly appropriate if the instances have a natural ordering, e.g. age-classes in a population, or soil layers, when the quantity under consideration might be considered to have some ordered relationship with the instance number.

The in_preceding() function

This calculates the value of its argument in the immediately preceding instance of the submodel. For instance, a variable with the equation in_preceding(prev(0))+1 will have a value of 1 in the first instance, 2 in the second, and so on up to the total number of instances in the last instance, irrespective of the submodel's dimensions.

Variables exported from a fixed-membership multiple-instance submodel

If you take an influence from a variable (a) in a fixed-membership multiple-instance submodel to another variable (b) in the same submodel, then b sees a as a scalar variable: i.e. as having a single value. You could use an expression for b like

3*a

How can this be, when we know that a has one value for every instance of the submodel? It's because the equation is expressed as a general rule: whatever value of a a particular instance has, then its value of x is 3 times greater.

However, things change when we take an influence from a variable (a) in the submodel to one outside (c). Because c is outside the submodel, it can see all the values of a. a must now be treated as an array of values, not as a single value.

An array is denoted by enclosing the name of the variable with [...], thus a inside the submodel has become [a] outside it. Thus, when we open up the Equation dialogue window for c, the influencing variable appears as an array, and its name is enclosed in square brackets. And the expression for calculating c must do the same. Thus, legitimate expressions for c are:

3*[a] c becomes an array with as many elements as a, each multiplied by 3

sum([a]) c has a single value, equal to the sum of the elements in the array [a]

In: Contents >> Working with submodels

Working with submodels : Multiple-instance submodels : Per-record submodels

Submodels with one instance per data record

It may be that you want to run your model with many different datasets, and these datasets each have different numbers of records which are supposed to correspond to instances of a submodel. Or perhaps you have nested submodels, where the membership of the inner submodel is different in each instance of the outer submodel, with the actual memberships being determined by the numbering of data records in a file.

For these cases, Simile provides the option of setting the number of instances according to the number of data points given for the fixed parameters within the model. To use this option, open the submodel properties dialogue and select the second radio button on the "Control of number of instances" panel, captioned "Using number of data records in file". The submodel will have the same border style as a population submodel, but should not contain any of the population control symbols. It must contain at least one fixed parameter.

When running the model, you must provide data for the fixed parameter(s). For each instance containing the per-record submodel, there must be at least one data item (the submodel must have at least one member) and the indices should run from 1 consecutively to the number of instances. You can have nested per-record submodels, possibly with the memberships of both the outer and inner submodels being set by the same parameter.

In: Contents >> Working with submodels

Working with submodels : Multiple-instance submodels : population

Multiple-instance submodels: population

A population submodel is one type of multiple-instance submodel. In this case, the number of instances may vary throughout the simulation run.

By making a population submodel, we are saying that the model contains a collection of objects, and individual objects can be created and destroyed during the course of a simulation run. The objects may (and usually do) differ in their attributes, but this is not such a central idea as it is for fixed-membership submodels: we can get interesting behaviour from a population model even if all the individuals have the same initial state and the same parameterisation.

See also the 6th video in the Simile Tutorial Series.

When should I use a population submodel?

When I introduced the idea of fixed-membership multiple-instance submodels, I said that you can decide to have one in your model either by breaking something into smaller pieces (disaggregation), or by representing a larger unit as a multiple of a set of smaller units (scaling up).

Similar ideas apply to using a population submodel, but we use slightly different language. If you are interested in the behaviour of some large unit, such as the population dynamics of the deer on an estate, then you may consider it appropriate to model this in terms of all the individual deer on the estate. This is more a process of decomposition of the population into individuals rather than disaggregation. With the latter term, the small unit is like a miniature version of the larger one, with all the same attributes, whereas an individual deer is not a smaller version of a population. Conversely, you may have first modelled one deer, following its life history, then decided to make a model with lots of those rather than just one. This is more a process of multiplication rather than scaling up. (There is no standard terminology here. Don't worry too much about the terms used: the important point is to see that something different is going on with populations.)

How to make a population submodel

  1. Use the submodel tool to drag a submodel envelope on your model diagram. The submodel may be drawn in an open area of the model diagram, and not enclose anything. Or you can drag the submodel around existing elements on your model diagram, if you want those enclosed in the submodel.
  1. Open up the submodel properties dialogue by selecting the Pointer tool, then double-clicking anywhere in the blank area of the submodel (not on its border, and not on any existing elements). Click on the "Population" radio button. Do not enter a value into the "Dimensions" box. You can use the Creation symbol to specify the initial number of individuals in the population. Click on the "OK" button.
  1. Back on the model diagram, the submodel's appearance has now changed. Its simple border has now been replaced by a double line on the bottom and right, and a double line on the top and left.

How to make the different instances different

In general, you can use methods that are the same as or similar to those used for fixed-membership multiple-instance submodels, with some differences:

  • Use the channel_is() function. If your population has multiple channels (creation, immigration and reproduction symbols) then an equation can apply this function to a parameter corresponding to an influence from one of the channels to get a boolean result which is true in each individual that appeared due to that channel.
  • Use the parent() function. If your population contains a reproduction symbol, an equation can apply this function to a parameter corresponding to an influence from this symbol. The result is the index number of the individual that became the parent of the current individual, or 0 if the current individual appeared due to some other channel.
  • The in_progenitor() function evaluates its argument as if in the population individual whose reproduction channel caused the current individual to come into existence. For instance, in_progenitor(index(1)) is equivalent to parent().
  • You cannot put a file parameter (fixed or variable) in a variable-membership submodel. This is because the table created by loading the data for a file parameter has a fixed number of values, and cannot be matched to a variable number of submodel instances.

Variables exported from a population submodel

With a fixed-membership multiple-instance submodel, variables are exported as arrays, since Simile knows precisely how many elements there are, and this number remains fixed for the duration of the simulation run. However, the number of instances for a population submodel can change dynamically during the course of the simulation run. Therefore, the set of values for a variable are exported as a list rather than an array. The number of elements in a list can vary, and (unlike an array) we can attach no particular significance to "the third element" of the list (since this might refer to quite different instances at different times during the course of the simulation)

Note that although an array can be passed around from one variable to another, a list must be processed into a scalar (single-valued) quantity as soon as it is received. Since any quantity exported from a population submodel must be a list, this means that the receiving variable must derive a single value from this list: its sum, perhaps, or its largest value, using a function capable of having a list as one of its arguments.

Special model diagram elements for population submodels

Four of the model diagram elements are only used in population submodels. They are:

  • initialisation, for specifying the initial number of instances;
  • migration, for specifying an absolute rate of creating new instances;
  • reproduction, for specifying the rate per individual for creating new instances; and
  • extermination, for specifying the conditions under which an instance is removed.

See the appropriate entries for further information on how to use these elements.

In: Contents >> Working with submodels

 

Working with submodels : Multiple-instance submodels : Special-purpose submodels

Special-purpose submodels

Generally, a Simile submodel has no built-in semantics besides what is described above for specifying its dimensions and how the number of instances is determined. However, certain uses of submodels are so common that we have decided to create special types of submodel to better support these uses, making models using them simpler and computationally faster. So, instead of the three radio buttons, Simile v6.1 includes a pulldown menu of six submodel types. When a selection is made, a message explaining the type is shown, together with entry fields for additional information  required for that type. Simple (dimensionless) submodels are now separate from those with array dimensions, and there are two predefined types:

Rectangular grid

The submodel has two dimensions and represents rectangular patches that cover a larger rectangular area. In addition to the usual features of array submodels, rectangular grid submodels can contain the following:

  • The functions row_id() and column_id() can be used to get the indices of each instance's row and column in the grid (these are equivalent to index(2) and index(1) respectively)
  • An influence's properties can be edited to enable the role "Include list of values from up to 8 grid squares...". When this role is enabled, the equation of the destination component can refer to the source component values in two ways: by the normal name, which just gives the value in the same submodel instance as normal, or by the name prefixed with "from_8_nbrs_", which gives a list of values from the instances representing the 8 grid squares adjoining the current one at sides or corners. There may be fewer than 8 values, e.g., if the current square is on the side or corner of the whole grid.

Hexagonal grid

The submodel has two dimensions and represents regular-hexagonal patches that cover a larger, roughly rectangular area in a honeycomb pattern. The patches have vertical sides, and odd-numbered rows are shifted half a width to the right so they tesselate with the even-numbered rows above and below. Hexagonal grid submodels can contain the following:

  • The functions row_id() and column_id(), as for rectangular grids
  • The functions hex_centre_x() and hex_centre_y() which give the x and y coordinates respectively of the centre of the hexagon relative to the bottom-left corner of the grid, in multiples of the length of one side of a hexagon
  • The functions hex_vertices_x() and hex_vertices_y() which give arrays of six values that are the x and y coordinates of the vertices of each hexagon, in the same terms as the centres above
  • An influence's properties can be edited to enable the role "Include list of values from up to 6 grid hexagons...". When this role is enabled, the equation of the destination component can refer to the source component values in two ways: by the normal name, which just gives the value in the same submodel instance as normal, or by the name prefixed with "from_6_nbrs_", which gives a list of values from the instances representing the 6 grid hexagons sharing an edge with the current one. There may be fewer than 6 values, e.g., if the current hexagon is on the side or corner of the whole grid.

More about values from neighbours

These special influence roles are intended to replace the requirement of having a separate self-association submodel representing the neighbour relationship between members of spatial grid arrays, and moving values between neighbours by taking them out to this association submodel by one role and back by the other. The lists of values they generate are variable-membership (due to different neighbour counts at sides/corners) so must be summed or applied to some other cumulative function before being assigned to the value of a local variable. However, all the usual operations can be applied to them before this. In fact the grid cell submodel itself can be made variable-membership, by adding a condition component to it just as is done for ordinary array submodels. This could be useful if we have an irregularly shaped area that we want to break down into grid cells -- in this case, set the dimensions of the grid so it covers the whole area, then add a condition that is true only for those cells that fall into the irregular area. Cells will then have fewer neighbours if they are at the edge of the selected area, and a single 'island' cell would have no neighbours, with the neighbour influence roles supplying empty lists.

Additionally, the lists have indices from special enumerated types representing the different directions in which the neighbour can lie. For the rectangular grid these are the points of the compass: nw, n, ne, etc. For the hexagonal grid these correspond to odd-numbered hours on a clock face, and so are written 1h, 3h ... 11h. These can be used like members of any other enumerated type, and to make a multi-instance submodel in which they are the indices, the dimension should be rect_nbr or hex_nbr. Now, because Simile v6.1 also introduces the ability to use the element() function to select sublists from lists, we can get other sets of neighbour data that might be useful, e.g.,

element({from_8_nbrs_size}, ["n","w","e","s"])

will produce a list of the values of 'size' in only the neighbouring rectangles that share a whole side with the current one.

element({from_6_nbrs_fire}, ["1h", "3h", "5h"])

will produce a list of the values of 'fire' in only the neighbouring hexagons further right than the current one. Note though that this could also be done by comparing the results of the hex_centre_x() function in the local and neighbouring hexagons, e.g., is 'fire' true in any neighbour hexagon to the right? use:

any({from_6_nbrs_fire} and {from_6_nbrs_xctr}>xctr)

...where xctr is another variable with equation hex_centre_x() and an influence to the current one with values from neighbours enabled.

In: Contents >> Working with submodels