Site navigation:

Contents

- Overview
- Symbolic Manipulation
- Expression simplification and evaluation
- Symbolic versions of built-in math names
- Multi-reference quantities
- More about the QuantSpec class
- Vectors of symbols
- Symbolic differentiation
- Specification Types
- Creation of Python functions from symbolic expressions
- Additional technical information

Symbolic math expressions are supported through the `Quantity` and `QuantSpec` classes, defined in `Symbolic.py`. These classes include the ability to perform numerical or symbolic substitution, expression evaluation, and elementary forms of simplification. The simplifications are partially based on the simplifying nature of the Python math expression parser, and partially on legacy code by Pearu Peterson. These symbolic classes are the ultimate "leaves" in the hierarchy of model specification that is supported by the ModelSpec classes.

In the near future we hope to improve the parsing of PyDSTool symbolic expressions to take full advantage of Python's abstract syntax tree classes, thereby greatly improving the speed of symbolic processing.

The rest of this page describes some of the symbolic manipulation features provided. Further examples can be found by running `tests/Symbolic_test.py`, `tests/Symbolic_Diff_test.py` and examining the output.

The essence of symbolic manipulation is the definition of `Quantity` objects. There are three types of Quantity, a `Var` (Variable), `Par` (Parameter), and `Fun` (Function).

Let us start by defining a variable named 'v' and a parameter 'p'. 'v' might contain the definition of an auxiliary variable, for instance, in terms of state variables, parameters, or functions.

```
>>> k=Var('k') # declaration
>>> v=Var('(((k-(-10)))/(3+4))', 'v')
>>> print v
v
>>> apar=Par('p')
>>> print apar, apar()
p p
>>> apar2=Par('3.5e-3', 'p')
>>> print apar2, apar2()
p 3.5e-3
```

Notice that the string representation of a Quantity is the Quantity's name. To see what a Quantity is defined to be, we must call it before taking its string representation, as we see for the `Var` and the `Par` objects. When a definition is not given to the Quantity, as for 'apar', its definition is merely its name. This usage is more akin to a *declaration* of a name than a definition. At this time there is no class method to add the specification directly to a Quantity that has only been declared. (Such a method is not really necessary when the overhead in checking the specification is almost identical to that necessary to re-create the object entirely.)

```
>>> print v()
(((k-(-10)))/(3+4))
```

In technical terms, symbols appearing in the definition are 'free', and generally refer to other Quantities. They are not treated as 'local parameters', and therefore this is *not* the same as defining a function. Compare:

```
>>> v.freeSymbols
['k']
>>> f=Fun('(((k-(-10)))/(3+4))', [k], 'v')
>>> f.freeSymbols
[]
>>> print f(4)
2.0
>>> print f(k)
(k+10)/7
>>> print f(4,5)
ValueError: Invalid number of arguments for auxiliary function
```

Notice that the function assigned to the Python name 'f' was named 'v', and used the same definition string as the `Var` Quantity 'v'. Therefore, calling `f(4)` simply returns the name of the function applied to its arguments. Calling the function with the requisite arguments works as for other Quantities -- it returns the Quantity's definition. The function knows how many arguments it takes, and returns an error if this number is incorrect. The symbol `k` in square brackets in the second argument of the `Fun` initialization specifies the "signature" of the function.

The simplification of expressions uses evaluation. Algebraic and arithmetical simplification of the definition of a Quantity is done using the object's `simplify` method. `simplify` works *in-place* on the object, and does not return anything. To return a simplified string without altering the original object, call the object's `eval` method with no arguments.

```
>>> print v()
(((k-(-10)))/(3+4))
>>> print v.eval()
(k+10)/7
>>> print v()
(((k-(-10)))/(3+4))
>>> v.simplify()
>>> print v()
(k+10)/7
```

Note that after simplification, the double negative and the extraneous braces have been removed, as well as the arithmetical sub-expression "3+4" simplified to "7".

Quantities can also be evaluated at specific values for the free symbols, but care must be taken with functions in terms of the order in which the operations will be performed. Evaluations at non-numeric values for a symbol performs symbolic substitution:

```
>>> print v.eval(k=4)
2
>>> print f(4)
v(4)
>>> print f.eval(k=4)
2
>>> print f(k).eval(k=4)
2
>>> print f.eval(k='kval')
(kval+10)/7
>>> kquantvar = Var('kquant')
>>> print f.eval(k=kquantvar)
(kquant+10)/7
```

The difference between `f.eval(k=4)` and `f(k).eval(k=4)` is subtle, and is dealt with in the next sub-section.

There is another function available for expression substitution, `subs`, that can act more intelligently regarding defined Parameter quantities. The `subs` function can also take a dictionary of assignments. The result of the substitution will always be simplified as much as possible.

```
>>> print subs(v(), {'k': 4})
2
>>> print subs(v(), {'k': '4'})
2
>>> kpar=Par('4', 'k')
>>> print subs(v(), kpar)
2
```

In the second call, the string "4" has been substituted for the 'k', and the result is the same. In the third, a defined parameter has been provided as a substitution.

Remember to use `subs` on the correct object, otherwise you might get unexpected results!

```
>>> print subs(v, {'k': 4})
v
>>> print subs(v, {'v': 4})
4
```

The first call did nothing because the Quantity assigned to Python name 'v' evaluates to its name, which is also 'v'. This name does not match any of the assignments in the first call to `subs`, but it does in the second.

Evaluation of one symbolic expression can perform automatic substitution of values defined by expressions in the local scope:

```
>>> q=Quantity('xv+1','qv')
>>> x=Quantity('3','xv')
>>> print q.eval()
4
>>> a=x/q
>>> print a
xv/qv
>>> print a.eval()
0.75
```

If Quant`Specs were used in place of Quantity objects in the above code, ``print a` would yield the division in terms of the definitions, namely `3/(xv+1)`.

Evaluation with an explicitly-supplied scope dictionary is much faster, if the relevant definitions are known. Equivalently, these definitions can be supplied in the call as a comma-separated sequence. For instance:

```
>>> print a.eval(xv=x)
3/qv
>>> print a.eval(xv=x,qv=q()) # much faster
0.75
>>> print a.eval({'xv': x, 'qv': q()}) # equivalently fast
```

Note from the above that Quantities only evaluate to their definitions if they are *called* to reduce them to a Quant`Spec object. Otherwise, only the Quantity's "name" will be substituted. `

This is also the best way to implement *partial* evaluation, and when substitution overrides are needed:

```
>>> print a.eval(xv=5,qv=q()) # override xv=3 from local scope
0.833333333333
```

Notice that the override even applies to the substitution made into `q`, which was defined as `xv+1`, so that the result is 5/6 and not 5/4.

Compare the behaviour of `eval` in the following:

```
>>> a=Var('a');b=Var('b');c=Par('c') # declarations
>>> gfun=Fun(a+b/c, [a,b], 'g')
>>> print gfun.eval(a=4)
ValueError: All function signature arguments are necessary for evaluation
>>> print gfun.eval(a=4,b=3)
4+3.0/c
>>> print gfun.eval(a=4,b=3,c=3)
5
>>> print gfun(4,b)
4+b/c
>>> print gfun(a,b).eval(a=4) # equivalent, but too much syntax!
4+b/c
```

Firstly, `gfun` is a function of two arguments, 'a' and 'b', while 'c' is a free variable that must refer to a parameter name if the `Fun` object is to be used in the specification of a right-hand side. When `eval` is called directly on the function, the full function signature must be supplied. But `gfun(a,b)` evaluates to the Quant`Spec object containing the definition of the function. As this is not a function object any of the free names 'a', 'b', or 'c' can be evaluated using ``eval`.

Note that any evaluations given for names not present in the definition of the object will be ignored.

A more efficient way to make multiple symbolic substitutions which are solely textual, i.e. do not involve algebraic simplification, is to use the `mapNames(<name_mapping_dict>)` method of a Quantity. This method takes a dictionary of source -> target names. Matching only occurs on whole symbols (i.e. 'tokens'): a dictionary containing the mapping ('a': 'b') will not map the symbol 'ka_2' to 'kb_2'. The method will also map the Quantity's name if it matches a source name! The method works *in-place* and does not return anything. In other words, the Quantity will be changed directly if any of its constituent symbols were matched.

```
>>> v.mapNames({'k': 'a/b'})
>>> print v()
(a/b+10)/7
>>> v.mapNames({'a': '44', '10': '-10'})
>>> print v()
(44/b-10)/7
>>> v.mapNames({'10': '20'})
>>> print v()
(44/b-20)/7
```

Symbolic versions of basic math functions and constants are exported by `Symbolic.py` and are also title-cased, in a further parallel to Maple. Calling these functions with regular numeric arguments will presently return the value of the original math function applied to that argument. Calling them with string or Quant`Spec arguments will return a Quant``Spec. `

The functions supported included the trigonometric, exponential, and logarithmic functions, the absolute value (`Abs`), power (`Pow`), and square root (`Sqrt`) functions. The constants include `E` and `Pi`.

There is a way to specify a range of related Quantities at once, in a kind of macro. This is useful for repeated definitions, that perhaps are interconnected in a formulaic way. (You may be familiar with this kind of notation in the package XPP.)

```
>>> v = Var('v'); ipar = Par('ipar')
>>> z = Var('3+v/((1+i)*ipar)', 'z[i,0,5]')
```

These define six Quantities `z0` to `z5` that are referenced from the Python object `z` as `z[0]` to `z[5]`, e.g. `z[0] == 3+v/ipar`. We can see this (with a little help from the `eval` method to simplify):

```
>>> print z[0]().eval()
x+v/ipar
```

The following example shows how to define a set of ODE right-hand sides that involve a circular connectivity pattern. Notice that the boundary variables `w0` and `w3` have to be defined separately, so that the multi-quantity definition refers to valid Quantities for all index values.

```
>>> w0 = Var('w0-2*(w3-w1)', 'w0', specType='RHSfuncSpec')
>>> w3 = Var('w3-2*(w2-w0)', 'w3', specType='RHSfuncSpec')
>>> w1and2 = Var('w[i]-2*(w[i-1]-w[i+1])', 'w[i,1,2]', specType='RHSfuncSpec')
```

Note that the Python dereferencing of a multi-def `z` by `z[2]` actually creates an *instance* of `z2`.

These definitions are also discussed on the page ModelSpec.

The Quant`Spec class was mentioned above. It should be considered a pre-Quantity form of raw symbolic expression, in that it has not yet been committed to a life as any particular Quantity sub-type (``Var`, `Par`, or `Fun`). As such, it can be used in different ways in different Quantity definitions, and can be manipulated symbolically in its own right. For the most consistent results, any Quant`Spec results that you want to use again should be defined into a proper ``Var` or `Fun` object.

```
>>> q=QuantSpec('q', '1+k/2')
>>> print 2 * q
2 * (1+k/2)
>>> print 1 * q
1+k/2
>>> print -q
-(1+k/2)
>>> qm = -q
>>> opqm = 1 + qm
>>> print opqm
1-(1+k/2)
>>> print opqm.eval()
```

A major difference to a Quantity object is that the Quant`Spec's name is not an important external part of its nature: only it's definition is important. Therefore, calling ``print` on the object always returns its definition. Also, Quant`Specs are not a good place to manipulate `*vectors* of symbols -- these should be converted to full Quantity types.

A new feature allows Quantities to define *vectors* of symbols (of up to rank 2). In practice this simplifies notation significantly, and permits `Fun` functions of several variables that can be symbolically differentiated. Examples of this are in `tests/Symbolic_Diff_test.py` and `vode_withJac_Symbolic_test.py`.

Symbolic vectors can be defined for Quant`Specs and Quantities, but are intended to be used in Quantity objects. This is because "array indexing" notation (using square brackets) is only properly supported for Quantities. However, the ``fromvector` method turns a vector Quantity *or* Quant`Spec into a list of non-vector Quant``Spec objects, each defining a component of the vector. It takes an optional integer index argument which selects that component of the vector only. Here's a simple example. `

```
>>> x=Var('x');y=Var('y')
>>> f=Var([-3*Pow((2*x+1),3)+2*(x+y),-y/2], 'f')
>>> print f[1] # only works if f is a Quantity type
-y/2
>>> print f.fromvector(1) # equivalent, safe for both types
-y/2
>>> print isinstance(df.fromvector(), list)
True
>>>
```

Note that we could have used the infix `**` notation for powers instead of the function `Pow`.

Other utility methods are provided to reduce Quantities whose definitions are entirely numeric to floating point numbers or arrays of such. Here's an example of Jacobian evaluation.

```
>>> df = Diff(f, [x,y])
>>> print df
[[((-3)*6*Pow((2*x+1),2)+2),2],[0,-0.5]]
>>> dfe = df.eval(x=3,y=10)
>>> print dfe # this is a QuantSpec representation of the array
[[-880,2],[0,-0.5]]
>>> print dfe.isvector()
True
>>> print dfe.dim
2
>>> print dfe.tonumeric() # this is an array
[[ -8.80000000e+02 2.00000000e+00]
[ 0.00000000e+00 -5.00000000e-01]]
```

Here's a demonstration that these methods commute in the expected way.

```
>>> a = df.fromvector(0).eval(x=3,y=10).tonumeric()
>>> b = df.eval(x=3,y=10).tonumeric()[0]
>>> print a[0]==b[0] and a[1]==b[1]
True
```

You may have noticed that Quantities defining multiple variables using the square bracket notation have a clash of notation with vectors. This is easily disambiguated because the Quantity object cannot be both a multiple quantity reference and a vector. It may be vector that *contains* multiple quantity references, but indexing such a Quantity will return its vector components.

Some features of multiple quantity definitions are not yet fully compatible with symbolic differentiation.

Symbolic differentiation of mathematical expressions involving common mathematical functions is supported through the function `Diff`. Most of the contents of this file were originally written by Pearu Peterson as part of a separate project, and later adapted by Ryan Gutenkunst. As Pearu and Ryan appear to have abandoned this version of his code in favour of different (and ultimately more efficient approaches that are less compatible with the PyDSTool classes), I have taken the liberty to adapt the code and include it with PyDSTool. For instance, I fixed a few residual bugs and limitations, and extended the code to work with the `Quantity` and `QuantSpec` classes.

The `Diff` function is meant to be a symbolic counterpart to the numerical derivative function `diff`, implemented in `common.py`, in the spirit of the Maple symbolic engine.

For many examples see the script `PyDSTool/tests/Symbolic_Diff_test.py`.

There are three specification sub-types of both Quantity and Quant`Spec objects, determined by the 'spec``Type' argument at initialization. These types are 'RH``Sfunc``Spec' (the named Quantity is defined as a right-hand side for differential or difference equation), 'Exp``Func``Spec' (the named Quantity is defined explicitly by the expression, such as for an auxiliary variable), and 'Imp``Func``Spec' (defined implicitly by the expression). The default is 'Exp``Func``Spec'. This type does not allow the Quantity's name to appear in the defining expression, whereas the other two types do allow this. `

These type strings are used by the Model`Constructor class to automatically determine how to treat each definition in a 'flattened' ModelSpec specification dictionary, and allows it to verify whether the types are compatible with the target Generator in which the specification will be instantiated. For instance, 'Imp``Func``Spec' definition types cannot be supplied to an ``ExplicitFnGen` generator class.

Symbolic expressions can be turned into Python functions using the `expr2fun` utility. The "free names" of an expression can either be numerically substituted from a supplied name -> value dictionary or left to become formal arguments to the function.

This utility can be handy to graph auxiliary functions from their abstract specifications (e.g., see `/tests/CIN.py`).

Both Quantity and Quant`Spec objects have a ``usedSymbols` attribute that shows which alphanumeric symbols were used in the object's *definition* (not including its name).

When defining a Quantity, additional information can be provided, such as the valid domain of the variable/parameter/function values. The domain defaults to the floating-point continuous domain [-INF,INF].