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.
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:
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.
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:
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.
There are two main ways in which you can make one instance different from the others.
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.
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.
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)).
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))
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
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.
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.
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
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
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 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.)
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:
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.
Four of the model diagram elements are only used in population submodels. They are:
See the appropriate entries for further information on how to use these elements.
In: Contents >> Working with 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:
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 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:
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