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:Salt class 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 Salt are normally only created via mathematical operations on other instances of the same type, whereas the start is made by the sub-class Leaf

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 Leaf objects. By setting this attribute to False, operators with mixed float- Salt datatypes are disallowed unless the constant is already cached.

This implies that 0, 1 and 2 are always allowed as constant contributions.

While ALLOW_MIX_FLOAT is True, newly encountered constants will be cached unless FLOAT_CACHE_MAX is exceeded.

Type:

bool

FLOAT_CACHE_MAX = 100

Newly encountered constants are cached if ALLOW_MIX_FLOAT is set to True, 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 c will now keep the values 2.718… and zero.

Returns:

The plain symbol without derivative tracing

Return type:

Salt

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 recalc does.

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 Leaf subclass. 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: Salt

This 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 Salt that support a read-write value attribute. It is per definition independent of any other symbols.

__init__(value=0.0)

Constructor to instantiate a Leaf object 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.invaludate needs 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 as SaltArray objects. 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.

Parameters:

source (Iterable (nested) container of Salt) – The symbols to be treated as a collection. Containers can be nested and inhomogeneous, as long as they are either iterable or of type Salt. If source is not provided (or None), the object is initialised as an empty container.

append(data)

Same method as the corresponding one for list objects.

extend(data)

Same method as the corresponding one for list objects.

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

static value_container(container)

The static version of value, can be applied to any (nested) container

Derivative

class saltype.tools.Derivative(dependent, independent)

Bases: SaltArray

This 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 derive

  • independent (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 dependent with respect to the ordered container of symbols independent. The result is a nested dictionary, of which the main key is the index of the dependent variable in dependent, the secondary key the index of the independent variable in independent, and its value the Salt object.

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 Leaf nodes. 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_1 is 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 of scope. 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 argument x, 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 dumped

  • scope (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 type Salt

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 by dim_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:

Salt