Sinn – User Manual

Note

The best way to get started with Sinn is to use the Sinn-full template, which provides a fully functioning inference workflow. The documentation below is intended for those who need to extend that template, or otherwise want to dig deeper into the library underlying it.

Purpose

At its core, the purpose of Sinn is to symbolically integrate update equations. The target application is for problems which can be cast into this form: Given some code implementing a closed set of update equations for variables \(x_1, \dotsc, x_n\) and parameters \(\Theta\),

\[\begin{split}x_{1,k+1} &= f_1(x_{1,k}, \dotsc, x_{n,k}; \Theta) \,, \\ \vdots \\ x_{n,k+1} &= f_n(x_{1,k}, \dotsc, x_{n,k}; \Theta) \,,\end{split}\]

and a function \(\hat{g}\) on the set of variables at all time points \(k\),

\[\hat{g}\bigl(\{x_{1,k}, \dotsc, x_{n,k}\}_{k=1}^K; \Theta \bigr) \,,\]

construct a symbolic function \(g\) equal to \(\hat{g}\), but requiring (at most) only the initial condition for each variable \(x_i\). The function \(g\) must thus integrate the update equations \(f_1, \dotsc f_n\) to generate the variables \(\{x_{1,k}, \dotsc, x_{n,k}\}_{k=1}^K\), which it can then pass to \(\hat{g}\).

Crucially, because \(g\) is symbolic, it can both be compiled and differentiated. This makes it possible both to efficiently simulate (i.e. integrated) models, and to construct inference algorithm to fit them using exact analytical gradients. Hence why we named this a package for simulation and inference.

Note that Sinn is agnostic to the manner in which the update equations were obtained: they can originate from both a truly discrete process or the update step of an ODE integration scheme.

Key concepts

We define the following:

History (\(\mathcal{H}\))

A combination of:

  • The value of a variable at all time points: \(\mathcal{H}_i := \{x_{i,k}\}_{k=1}^K\).
  • A time value associated to each index: \(\{t_k\}_{k=1}^K\).
Parameters (\(\Theta\))
The parameters on which the update functions depend.
Model (\(\mathcal{M}\))
A set histories, parameters, update equations, along with anything else required to integrate the equations. This may include a random number generator, if some update equations are stochastic.

Sinn proposes a stateful API, in which the compilation of \(g\) depends on a few state variables attached to the histories:

The current time index
of a history is the latest time point for which its value has been computed.
The lock state
of a history determines whether it can be updated. This indicates whether values for that history need to be computed (via its update function) or whether they are provided as initial conditions – analogous to setting a variable as “observed” in other packages, with the difference that the lock state is mutable. Locked and unlocked histories are treated differently during compilation. (TODO: Link to fig/explanation in models.rst explaining difference in compilation.)

This deviation from Theano’s purely functional ideals allows to specify initial conditions in a natural way, and to compile multiple functions which lock different subsets of histories. For example, when constructing an expectation-maximization scheme, one would typically unlock the latent histories for the E-step, but lock them for the M-step.

The graph below provides an overview of the compilation steps.

Important

A fundamental assumption is Sinn is that histories are causal. In other words:

  • If \(x_{i,k}\) is computed, this always implies that \(x_{i,k-l}\) (\(l>0\)) has also been computed.
  • To compute \(x_{i,k}\), it is always sufficient (but perhaps not necessary) to have computed \(x_i\) up to \(k-1\), and all other histories up to \(k\).
flowchart LR subgraph create_model["Create model"] direction TB params[List parameters] hists[List histories] upds[List update functions] state[List state histories] hists --> ConnHU["Attach update function to histories"] upds --> ConnHU end subgraph init_model["Set model state"] direction TB init_hists["Set initial value(s) of state histories"] lock["Lock observed histories"] end subgraph compile_model["Compile g"] direction TB onestep["Construct computational graph for one time<br>step update of unlocked histories"] scan["Iterate ('scan') graph K steps forward"] compile["Compile iterated graph"] onestep --> scan --> compile end create_model --> init_model init_model --> onestep compile --> g

Typical sequence of steps to compiling a function depending on model variables. Connection and compilation steps are performed automatically.

Core components

The concepts described above are implemented via four core classes, which are described in the sections listed below. For a first reading, we recommend skimming through the axis section, and then focusing on the histories and models sections, since the latter two objects are the ones with which one interacts the most. Kernels are only needed for models involving convolutions, and thus can be read later.

Caution

Negative indices are not special.

Sinn does not recognize negative indices (or at least not the way you may expect it to). This is primarily a protection for the user against hard-to-track bugs, but also helps make the interface for setting initial conditions more intuitive. (See Indexing.)

Indices and tables