You are here

Working with submodels : Using externally-supplied procedures

Using externally-supplied procedures

As of version 4.8, submodels in Simile can be used to embody operations specified by procedures supplied separately, either as c or c++ code, or by shared libraries. You can also include your own code in a simile model using the external procedural functions mechanism, but using a submodel offers a number of advantages, namely:

  1. The external procedure can accept arrays as inputs, and write arrays as outputs

  2. Many outputs can be written by the procedure

  3. A procedure embodied by a submodel can have its own internal state.

However, note that there are very few cases in which you actually need an external procedure to implement a particular algorithm as a submodel, since more or less anything is possible using an iterative submodel. The facility described here is included primarily for the case where you already have a procedure or library to implement a complicated operation.

A submodel that embodies an external procedure can also contain components whose values will be calculated by the normal Simile mechanism. It can be any of the types of submodel used by Simile.

Incorporating external code requires that we have variables in the submodel which hold the values going in and out of it. It is important that the datatypes of these variables, and their dimensions if they are arrays, match up to those that the external code is expecting. But most importantly of all, they must be passed in the right order.

Datatypes and dimensions

Simile's variables all have datatypes of int or double-precision. The datatype of each variable is derived from its equation and the datatypes of its parameters. If the equation is for an enumerated type member or a boolean value, the variable's datatype will be int. If it is for a numerical value, the datatype will be int if the value has no physical dimensions and its equation will only ever produce an integer (e.g., index(1)), or double otherwise. If you need to pass an integer value to or from your external procedure, make sure it is actually an integer. The unit appears in the model diagram popup for the variable, and in its equation dialogue. If the unit is real (i.e., datatype is double) when you want an int, go into the equation dialogue and delete the old 'units' entry, and do the same with any input parameters with units of real.

All Simile's real values are double-precision. If your procedure uses single precision floats, you must adapt it to read and write double-precision values.

The dimensions of array variables also depend on the equation. For inputs to the external procedure, your model should be generating arrays with the right dimensionality and datatype anyway. Variables for outputs from the external procedure must be given equations for default values, which basically serve the purpose of creating an array of the right dimensionality containing values of the right datatype. For instance, if your external procedure creates a 5x3 array of integers, the value into which it is put can have the equation makearray(makearray(0,3),5). If you put 0.0 instead of 0, you will get a 5x3 array of double-precisions. Default value equations are also used to set the datatype and dimensions of file parameters.

Simile's arrays of size n use elements 0 through n-1 of a c++ array. In earlier versions of Simile, the arrays were actually made one element too big, and elements 1 through n were used, with element 0 being unused. This has changed, in order to make it easier to integrate other c++ procedures which typically use element 0 as the first element of an array. However, the equation language functions that deal with array elements, i.e., index(n), element(m,n) and place_in(n), still use 1 as the index for the first value, so old Simile models will run unchanged (and use less memory!)

Parameter order

The order is specified by the names of the variables. Values to be passed to the procedure must be given captions beginning with "inputn", where n is the position in which that input is passed. When choosing this position, characters in the caption from the first non-alphanumeric character (e.g., space, underscore, parentheses) are ignored, and can be used to make the model diagram more readable, so for instance a variable called "input3 (volume of porridge)" will still be passed as the third input.

A similar scheme applies for outputs. Their captions begin with "outputn", where n is the position in the outputs in which that output appears. The external procedure gets as arguments all the inputs in order, followed by all the outputs in order. An output variable's default value may or may not be overwritten by the procedure, but there are two differences between the handling of inputs and outputs:

  • The procedure will not run in each time step until the input values and default output values have all been set in the model, but the input values can be used to calculate values of other Simile variables before or after the procedure has run, while output values will only be used elsewhere after the procedure has run,

  • Scalar (non-array) inputs are passed to the procedure as their actual values, while scalar outputs and all arrays are passed by reference, i.e., the procedure gets a pointer to a memory location where it can read the default value and write its result.

The return value of the procedure is not used by the model, but it just indicates to Simile whether an error has occurred in the procedure.

How often to run

Simile assumes that if the inputs and default outputs to a procedure do not change over time, then neither will its actual outputs, so the procedure will only be called at initialization or reset of the model. If you know the procedure's outputs change over time by themselves, make sure it runs every time step by setting an unused default output value to something you know changes over time, e.g., time().

Specifying the procedure

Now the submodel's contents specify how the procedure is to be called. But we still need to tell Simile the external procedure's name, and where to find it. Open the submodel properties dialogue, and look in the calculation panel. There is a checkbutton labelled 'Use own code'. After checking this you can hit the 'setup' button to get a subdialogue in which you specify the procedure. This has three sections:

  1. The procedure name section. Enter just the name of the procedure, not its parameters, as these are all specified by the model contents as described above.

  2. The name of a file to be included when building the model. This can either be a c or c++ (.c or .cpp) source code file containing the whole procedure definition, or a header file (.h) containing the declaration of the procedure in a precompiled library file.

  3. A list of library files. You do not need any library files, but they must be readable by the compiler Simile is using. For Unix systems the shared objects (.so or .dylib) work fine, but for Windows using the included compiler you need an archive (.a) file. Gnu MINGW includes utilities for creating this if all you have is a dynamic-linked library (.dll).

If your model is to run on multiple platforms, include libraries for all of them; Simile will only use the appropriate ones on each platform.

State for external code

One reason for using an externally written procedure is if you have a legacy model, written in FORTRAN or similar, which you would rather include in a Simile model as is than re-implement in Simile. These models are likely to include state variables of their own, allowing them to have dynamic behaviour. There are two issues here; firstly, we need to know when to reset these values and when to update them. An external procedure can tell what Simile is doing, by accessing the time and dt global arrays of doubles. These have a value at index 0 plus a value for each time step size in the Simile model. The values of t and dt for the time steps give you the time of last evaluation and the step size respectively for each different step size (like the time(n) and dt(n) functions), but the index 0 values are especially useful. time[0] gives the current integration method and which pass is being done if it is Runge-Kutta, while dt[0] indicates whether the model is being initialized (negative), reset (0) or updated (appropriate time step number). This can be used to select the action for an external model.

If an external procedure is only ever going to be called once per time step, then it can simply keep its state in locally declared variables. However, if you call the same external procedure in two different submodels, or in a multiple instance submodel, then each one must have its own instance of the state. Simplest way to do this is to make an array of the appropriate size in Simile and pass it to the procedure as an output. If the default values are constants, Simile will not write them once it has initialized, and the external procedure can use the space as a scratch pad unique to each instance.

In: Contents >> Working with submodels