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.
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:
Auxiliary variables are meant for the output of useful quantities derived from state variables, parameters etc. Thus, 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 may be optional, depending on the target Generator. These 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 /tests/ 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. 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.