API documentation¶
Saltype datatypes¶
Salt¶
- class saltype.datatype.Salt(node)¶
Most of the user objects to deal with in SALT are instance of this class, representing a Salt entity. The operators and most unary functions from the math module are emulated. With that, as intended, most scalar mathamatics implementations are doable.
Technically, the :py:obj:
Saltclass is only a smart pointer to the underlying node object. Consequently, coding:a = Leaf(2.1) b = a + 0
will simplify the sum and let b and a point to the same leaf node with value 2.1.
Objects of type
Saltare normally only created via mathematical operations on other instances of the same type, whereas the start is made by the sub-classLeaf- ALLOW_MIX_FLOAT = True¶
Normally, it is convenient to be allowed writing:
E = 0.5 * m * sq(v)
Still, it is easy to imagine to code bugs by forgetting to instantiate important input variables as
Leafobjects. By setting this attribute toFalse, operators with mixedfloat-Saltdatatypes are disallowed unless the constant is already cached.This implies that 0, 1 and 2 are always allowed as constant contributions.
While
ALLOW_MIX_FLOATisTrue, newly encountered constants will be cached unlessFLOAT_CACHE_MAXis exceeded.- Type:
bool
- FLOAT_CACHE_MAX = 100¶
Newly encountered constants are cached if
ALLOW_MIX_FLOATis set toTrue, but to prevent uncontrolled growths in memory, caching is stopped after the given number of entries.- Type:
int
- invalidate()¶
Between two queries for values, when also independent variables have changed their value, invalidate has to be called to trigger a new evaluation. The method can be called before, during or after the independent variables are actually changed - with the same effect.
- Returns:
None
- plain()¶
Creates a new symbol of the same value, but not propagating dependencies and derivatives:
a = Leaf(1.0) b = exp(a) b_plain = b.plain() c = Derivative([b, b_plain], [a])
The symbols in
cwill now keep the values 2.718… and zero.- Returns:
The plain symbol without derivative tracing
- Return type:
- recalc()¶
Force the evaluation of this symbol.
The normal purpose of saltype is to treat large quantities of dependent and independent symbols by following the cycle of invalidating, setting values, and getting values. However, if you suddenly really need to know the value of a variable out of this scheme, use recalc.
If you don’t know what you are doing, the only way to obtain the correct value from a node is to query the value (dump it), invalidate, and reevaluate, this time keeping the result. This is what
recalcdoes.- Returns:
The symbols value
- Return type:
float
- select(switch)¶
This method implements a primitive conditional. The call:
y = x.select(a)
is equivalent to the float operation:
y = x if a > 0 else 0
- Parameters:
switch (
Salt) – The decission variable- Returns:
The Salt equivalent to y in above if construct
- Return type:
Salt
- property value¶
Value is a property that is read-only except for instances of the
Leafsubclass. Requesting this property returns its numerical (float) value, if necessary after re-evaluating the underlying Salt graph - or parts of it.- Type:
float
Leaf¶
- class saltype.datatype.Leaf(value=0.0)¶
Bases:
SaltThis class is the starting point to build up a Salt algebra graph.
“In the beginning, there was a leaf!” – Caterpillar’s bible
Leafs are the only instances of
Saltthat support a read-writevalueattribute. It is per definition independent of any other symbols.- __init__(value=0.0)¶
Constructor to instantiate a
Leafobject from a float value.- Parameters:
value (float) – The initial numerical value of the node
- property value¶
Same property as defined in base class
Salt, but writable. Setting this property has no immediate side effects. In particular, dependent nodes do not get notified to re-evaluate automatically. For performance reasons,Salt.invaludateneeds to be called on the dependent variables in order to trigger reevaluation.- Type:
float
Tools¶
SaltArray¶
- class saltype.tools.SaltArray(source=None)¶
This class represents an array specialised for symbols in it.
You may instantiate this list with anything in it, but for the specific methods to work, the containing datatypes better are other containers or objects of type
Salt. Derivatives are represented asSaltArrayobjects. Some slicing functionality is included, and indexing supports multi-dimensional lists. Furthermore the objects are iteratable.- __init__(source=None)¶
Constructor of an empty object or one based on the given source.
- append(data)¶
Same method as the corresponding one for
listobjects.
- extend(data)¶
Same method as the corresponding one for
listobjects.
- invalidate()¶
Same as
Salt.invalidate, just applied to the entire container, therefore slightly more efficient :return: None
- static invalidate_container(container)¶
The static version of
invalidate, can be applied to any (nested) container
- recalc()¶
Same as
Salt.recalc, just applied to the entire container, therefore slightly more efficient- Returns:
The values of the symbols within the container
- Return type:
<list <list ...<float>...>
- static recalc_container(container)¶
The static version of
recalc, can be applied to any (nested) container
- property value¶
Same as
Salt.value, just applied to the entire container and returning a nested container of same shape as original, containing the float values- Returns:
The values of the symbols within the container
- Return type:
<list <list ...<float>...>
Derivative¶
- class saltype.tools.Derivative(dependent, independent)¶
Bases:
SaltArrayThis class could have been implemented as a function, as it consciously behaves like one. However, the cleanest way to encapsulate it’s (private) content is probably to define it as a class, representing its own result.
When deriving (right under construction), the dependent variables and their underlying graph are first analysed in order to avoid chewing on derivatives that are anyway zero. Then, the graph is again traversed recursively in order to obtain and pre-simplify the derivatives with respect to the independent variables.
In fear of performance issues, the final more rigorous simplification is not included here, but might be in the future.
- __init__(dependent, independent)¶
Construct the result object as the symbolic derivative \(\mathrm{d}y/\mathrm{d}x\).
- Parameters:
dependent (Iterable container of
Salt) – The variables y to deriveindependent (Iterable container of
Salt) – The variables x to derive with to
sparse_derivative¶
- saltype.tools.sparse_derivative(dependent, independent)¶
Derive the symbols in the ordered container
dependentwith respect to the ordered container of symbolsindependent. The result is a nested dictionary, of which the main key is the index of the dependent variable independent, the secondary key the index of the independent variable inindependent, and its value theSaltobject.- Parameters:
dependent (list<Salt>) – The container holding the dependent variables
independent (list<Salt>) – The container holding the independent variables
- Result:
The nested dictionary, mapping indices to the derived symbols.
- Return type:
dict<int, dict<int, Salt>>
dump¶
- saltype.tools.dump(symbols, scope=None)¶
This class dumps valid python code that defines the given symbols. The code is always generated down to the leaf nodes.
A list of string representation of the symbolic graph. Here, the entire graph below symbols is processed down to the
Leafnodes. A scope can be provided as an argument, providing the algorithm with names of user-known variables. The following example:a, b, c, d, e = map(Leaf, range(5)) f = (a + b) * c g = b * b h = d * e i = h + f scope = {"a": a, "b": b, "c": c, "d": d, "e": e, "f": f, "g": g, "h": h, "result": i} print "\n".join(dump([f,g,h,i], scope))
will produce the following output:
var_1 = a + b f = var_1 * c g = b ** 2 h = d * e result = h + f
Note that
var_1is no variable known at user scope, but an internal node. It will therefore be given a generic name, as all variables that are not member ofscope. Let us emphasise that SALT itself does not hold any symbol names in the nodes. When dumping the graph, the user is free to call them then and there by his/her favourite pet.Actually, if the dumped code is to be used to be executed (somewhere else) later, you might want to utilise some variable groups as lists. If above code is to be a function with
[a, b, c, d, e]as argumentx, define scope as such:scope = dict("(x[%d]" % i, var) for i, var in enumerate((a, b, c, d, e))} scope.update(f=f, g=g, h=h, result=i)
- Parameters:
symbols (Iterable 1D container of
Salt) – The symbols for which the graph shall be dumpedscope (dict(string,
Salt)) – A dictionary to map variable names to known symbols
- Returns:
A list of strings, each of them representing an assignment with one operator or function (representing one symbolic node)
- Return type:
list<string>
Note that multiple variables can point to the same node, hence SALT cannot even distinguish them. If scope provides multiple variables representing the same symbol, an arbitrary name will be selected for generating the string representation.
simplify¶
- saltype.tools.simplify(symbols)¶
This function simplifies the given symnbol or container of symbols in-place.
In the current implementation, it applies the same simplifications as when creating the graph, but simultaneously removes duplicates. That is:
a, b = Leaf(3.14159), Leaf(2.71828) c = (a + b) + sin(a + b) simplify(c)
will simplify to:
x = a + b c = x + sin(x)
This kind of simplification has a great impact on automatically generated derivatives, as the chain rule leaves a lot of common terms for the derivatives of different independent variables. Not all of them can be avoided while generating the derivatives.
Naturally, duplicate nodes (that is: same type and same child nodes) can only be detected if they are under the symbolic graph reachable by the given symbols:
c = sin(a+b) d = cos(a+b) simplify(c)
This code would not be able to detect existance of a + b as duplicate somewhere else in the graph - another consequence of avoiding bidirectional linking, sorry - not.
- Parameters:
symbols (Iterable container of
Salt) – A single symbol or a container of symbols to be simplified. Containers can be nested and inhomogenious, as long as they are either iterable or of typeSalt- Returns:
Number of duplicates found
- Return type:
int
Empanada and Empanadiña¶
- saltype.tools.empanada(func, inp, dim_out=1)¶
This function is described here.
- Parameters:
func (f: list<float> -> (list<float>, list<list<float>>)) – The function to be embedded, taking a list of input variables as argument - to be consistent with
inp, and returning a list of values with its dimensionality given bydim_out, as well as the Jacobian \(J\) as the derivative matrix of output variables with respect to input variables.inp (list<
Salt>) – The list of input symbols that will be linked to the function input arguments
- Returns:
A list of symbols linked to the return values of
func, with the first derivative being represented by \(J\)- Return type:
list<
Salt>
- saltype.tools.empanadina(func, inp)¶
This function is described here and is very similar to
empanada, just reduced for scalar usage.- Parameters:
func (f: float -> (float, float)) – The function to be embedded, taking the input variable as argument, and returning the function value and the derivative of it with respect to the input variable.
inp (
Salt) – The input symbol that will be linked to the function input argument
- Returns:
The symbol linked to the return value of
func, with the first derivative being represented by \(J\)- Return type: