Site navigation:

Model functional specifications


  1. Introduction
  2. Target languages
  3. Elements of a system specification
  4. Macros
  5. Auxiliary functions
  6. Sub-expression substitutions in target language [technical]

1. Introduction

The module deals with the specification of functionality of a Generator object. A FuncSpec object is passed to a Generator object when it is initialized, which the Generator uses to build internal representations of its trajectory generation functions -- for instance, a function that computes the value of the right-hand side of an ODE, which can be passed to an ODE solver.

2. Target languages

Valid target languages for model specifications in PyDSTool are: Python, C, and ODETools/ADMC++ (automatic differentiation via ODETools). However, to access ODETools/ADMC++ you should specify C as the target language and then use the ADMC++ Generator class. These languages are specified by a case-insensitive string name, 'python' or 'c'.

You can use the VFGEN software for further help in importing and exporting vector field definitions.

3. Elements of a system specification

A system specification is made up of some or all of the following types of definition:

Auxiliary variables are meant for the output of useful quantities derived from state variables, parameters etc. As such, a specification of a state variable may not refer to an auxiliary variable. Specification of valid domains for these variables, parameters, and the independent variable are optional, depending on the target Generator. These domains are supplied as two-element lists containing the interval range endpoints that can include Inf. Also see BoundsSafety for uses of the domain information.

If you want to avoid costly re-calculation of terms, either define auxiliary ("helper") functions or utilize the reuseterms option in your system definition. Examples of use are provided in the examples/ directory. Auxiliary functions are also a way to create derived parameters. E.g., suppose you have a parameter P in your model that really is a derivation from a function of two other parameters, A and B, according to P = B*exp(A). In PyDSTool, create an auxiliary function with no arguments that computes the necessary derivation:

auxfns = {'P': ([], 'B*exp(A)')}

You will have to call this function to use it, i.e. use P() in your vector field definitions.

User-defined events (see Events) and autonomous external inputs can also be provided to the system specification.

3.1. Mathematical expression syntax

PyDSTool's API provides a simple syntax for the specification of expressions and functions that define Generator objects -- i.e. differential or difference equation right-hand sides, and auxiliary ('helper') or explicit functions. We have not undertaken the time-consuming effort of building a true parser to deal with this, and there are some restrictions on what can be specified through the API, and what syntax or naming errors can be detected.

The expression syntax is independent of the target language, and is "compiled" into target language specifications by the FuncSpec class (see FunctionalSpec). Symbolic manipulation of the abstract syntax prior to "compilation" is described on the page Symbolic. The specification of structured models, that exhibit hierarchical modularity and other such symmetries, is described on the page ModelSpec, along with examples.

3.1.1. Syntax checking

There is a small amount of syntax checking provided at 'compile' time, mainly to check that all string tokens reference known variables, parameters, inputs, auxiliary functions or math objects. No semantic checking is done, and errors not caught at the time of initializing a Generator may lead to cryptic Python errors during trajectory generation.

3.1.2. Standard math names

Standard math names such as sin, pow, and pi do not need prefixing with a library reference such as in math.sin (for a Python target). Time must be referenced as t, and references to variables, parameters, and external inputs is by the corresponding declared name. This is known as "index-free" notation. Internally, The module will translate these names to array index references once and for all. The user does not have to know or use these indices. Some support for special math names (such as the error function provided by SciPy) is provided, although many of these will only function with Python-based ODE integrators (Euler and VODE).

The sign and mod functions are available in both python and C.

3.1.3. Special functions

Presently, "special" functions (as defined by scipy) are only available in the python-based integrators Vode and Euler. When using it, prefix the function name with special_ in your definition string. The list of available functions is:

airy, airye, ai_zeros, bi_zeros, ellipj, ellipk, ellipkinc, ellipe, ellipeinc, jn, jv, jve, yn, yv, yve, kn, kv, kve, iv, ive, hankel1, hankel1e, hankel2, hankel2e, lmbda, jnjnp_zeros, jnyn_zeros, jn_zeros, jnp_zeros, yn_zeros, ynp_zeros, y0_zeros, y1_zeros, y1p_zeros, j0, j1, y0, y1, i0, i0e, i1, i1e, k0, k0e, k1, k1e, itj0y0, it2j0y0, iti0k0, it2i0k0, besselpoly, jvp, yvp, kvp, ivp, h1vp, h2vp, sph_jn, sph_yn, sph_jnyn, sph_in, sph_kn, sph_inkn, riccati_jn, riccati_yn, struve, modstruve, itstruve0, it2struve0, itmodstruve0, bdtr, bdtrc, bdtri, btdtr, btdtri, fdtr, fdtrc, fdtri, gdtr, gdtrc, gdtria, nbdtr, nbdtrc, nbdtri, pdtr, pdtrc, pdtri, stdtr, stdtridf, stdtrit, chdtr, chdtrc, chdtri, ndtr, ndtri, smirnov, smirnovi, kolmogorov, kolmogi, tklmbda, gamma, gammaln, gammainc, gammaincinv, gammaincc, gammainccinv, beta, betaln, betainc, betaincinv, psi, digamma, rgamma, polygamma, erf, erfc, erfinv, erfcinv, erf_zeros, fresnel, fresnel_zeros, fresnelc_zeros, fresnels_zeros, modfresnelp, modfresnelm, lpn, lqn, lpmn, lqmn, lpmv, sph_harm, legendre, chebyt, chebyu, chebyc, chebys, jacobi, laguerre, genlaguerre, hermite, hermitenorm, gegenbauer, sh_legendre, sh_chebyt, sh_chebyu, sh_jacobi, hyp2f1, hyp1f1, hyperu, hyp0f1, hyp2f0, hyp1f2, hyp3f0, pbdv, pbvv, pbwa, pbdv_seq, pbvv_seq, pbdn_seq, mathieu_a, mathieu_b, mathieu_even_coef, mathieu_odd_coef, mathieu_cem, mathieu_sem, mathieu_modcem1, mathieu_modcem2, mathieu_modsem1, mathieu_modsem2, pro_ang1, pro_rad1, pro_rad2, obl_ang1, obl_rad1, obl_rad2, pro_cv, obl_cv, pro_cv_seq, obl_cv_seq, pro_ang1_cv, pro_rad1_cv, pro_rad2_cv, obl_ang1_cv, obl_rad1_cv, obl_rad2_cv, kelvin, kelvin_zeros, ber, bei, berp, beip, ker, kei, kerp, keip, ber_zeros, bei_zeros, berp_zeros, beip_zeros, ker_zeros, kei_zeros, kerp_zeros, keip_zeros, expn, exp1, expi, wofz, dawsn, shichi, sici, spence, zeta, zetac, cbrt, exp10, exp2, radian, cosdg, sindg, tandg, cotdg, log1p, expm1, cosm1, round

To use similar functions in C definitions you will have to include a C library defining them and call them with the imported names (see this section). You will have to let PyDSTool know which functions you are using so that it doesn't throw an error for finding an "undefined" name, using the ignorespecial keyword in the defining arguments.

3.1.4. Noteworthy syntax quirk: powers

For Python language targets you should always use the syntax pow(x,p) rather than x^p or x**p in order to express powers involving floating point values. Python does not behave correctly with the caret operator ^ for floating point numbers. In order to make it easier to convert legacy code to work with PyDSTool, the utility convertPowers(<string>, <target>) is available, where <target> is "^", "**", or "pow".

If you use the symbolic expression classes (see Symbolic), you can build expressions using the ** operator. Internally, the represenation for powers are converted to using the pow function.


4. Macros

For an example of macros in action, see the script.

Here, there are two summations, over indices i and j. The p[j] will map on to one of the three parameters defined, and the user-defined indexfunc will be called with the value of j added to the selected parameter value, depending on i and the evaluation of the if statement.

4.1 Multiple state variable definitions using the 'for' macro

A 'for' loop macro is provided to ease repetitive definitions in all specification strings. This is pre-processed to 'unroll' the loop into a flat list of definitions before the parser processes the specification string. The syntax is:

for(i, <ilo>, <ihi>, <expr_in_i>)

where <ilo> and <ihi> are integers, and the expression in i (or any other alphabet character) has all occurrences of i in square brackets replaced with the appropriate integer. Parameter declarations for such expressions are strings of the form x[i] for a variable x.

     pars={'p0': 0, 'p1': 1, 'p2': 2},
     varspecs={'z[j]': 'for(j, 0, 1, 2*z[j+1] + p[j])',
               'z2': '-z0 + p2 + special_erfc(2.0)'})   # z2 is defined as the end case

4.2 Summation using the 'sum' macro

sum(i, 0, 4, <code_using_i>)

     pars={'p0': 0, 'p1': 1, 'p2': 2},
     varspecs={'x': 'sum(i, 0, 4, sum(j, 0, 1, if([i]==2, 0, indexfunc([j] + p[j]))))'},
     fnspecs={'indexfunc': (['x'], 'pi*x')}  # user-defined function returning Pi * x

5. Auxiliary functions

Auxiliary functions are accessible to variable specifications and event definitions by name. To do this, create an entry in the arguments passed to the Generator class named fnspecs. This should contain a dictionary mapping function names to definitions (e.g. see the example for 'indexfunc' in the above sub-section. These functions can be used inside event definitions provided a copy of the relevant fnspecs are passed to the event creation function. That copy of the function specifications are not a substitute for the declarations to the Generator class at initialization.

As well as the option for user-defined functions, several in-built auxiliary functions are provided. Currently, these are:

The first two are present for dealing with discrete-time systems (i.e., MapSystem), or legacy ODE code (e.g., from XPP) when the discrete events they involve are not computed beyond the accuracy of the integrator's step size. For accurate discrete event handling in continuous-time systems, the hybrid system Model class should be used.

The user may specify other, user-defined, auxiliary functions. These can help to simplify and organize the evaluation of right-hand sides, and to improve computational efficiency. User-defined functions are exposed in the generator as python functions. For instance, a function 'h' of one argument for a generator g is exposed as g.auxfns.h and has the same calling sequence as the internal function. Any parameter values used in the definition of the function are also mapped dynamically to the current values of

6. Sub-expression substitutions in target language [technical]

There is a helpful option to use a two-pass sub-expression parser and term reuser in expressions appearing in the target language vector field function definition, and in auxiliary functions / variable defintions. The user can list pairs of (subexpression, symbolname) strings in a dictionary at the initialization of the Generator, through the keyword reuseterms. If the subexpressions are actually found in the function code, this causes

double symbolname = subexpression;

in C-code Generators, and

symbolname = subexpression

in Python-code Generators. to appear in the preamble of the appropriate function, and all occurrences of subexpression will be replaced in the RHS. The two-pass aspect allows 'second-order subexpressions'. For instance, consider the specification


A reuseterms dictionary  {'afunc(p)': 'afp', 'sin(afp)': 'sa', 'other(x)': 'ox'}  will result in the final specification:


preceded by the declarations:

double afp = afunc(p);
double sa = sin(afp);

(in C)

afp = afunc(p)
sa = sin(afp)

(in Python)

Note that, because other(x) did not appear in the code specification, no declarations for ox are made. This avoids some compiler warnings about unused symbols.

The two-pass system ensures that second-order references are not processed until the second pass, and that the declarations appear in the correct order.