Site navigation:

Contents

The `FuncSpec.py` 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.

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.

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

- Name of model system [Needed]
- State variables [Needed]
- Auxiliary variables
- Auxiliary (or "helper") functions
- System parameters
- Domains for state variables and parameters
- Initial conditions for state variables
- Autonomous external inputs
- User-defined zero-crossing events
- Algorithmic parameters (e.g., integration tolerances)
- List of special names to ignore when parsing
- Other target Generator-specific options

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.

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.

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.

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 `FuncSpec.py` 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.

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.

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.

For an example of macros in action, see the basic_gen_tests.py 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.

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`.

```
args(name='for_demo',
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
```

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

```
args(name='sum_demo',
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
)
```

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:

`if(<cond_expr>, <expr_1>, <expr_2>)`evaluates to`<expr_1>`if`<cond_expr>`evaluates to True, otherwise`<expr_2`is evaluated.`heav(<expr>)`= 1 if the expression evaluates to a positive value, otherwise = 0.`globalindepvar(<local_indepvar>)`returns the system's "global" independent variable (usually time) corresponding to the "local" value of the independent variable. See HybridSystems.`initcond(<var_name>)`returns the initial condition of the named state variable.

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 `g.pars`.

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

`2*sin(afunc(p))+cos(afunc(p))*sin(afunc(p))-afunc(p)/5.`

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

`2*sa+cos(afp)*sa-afp/5.`

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.