This page was generated from docs/basics/sharp_points.ipynb. Interactive online version:

# π‘ The sharp points of Rockpool πο

Rockpool aims to be an intuitive bridge between supervised machine learning and dynamic signal processing, hiding the complexities of the underlying dynamical systems as much as possible.

However, there are a few places where you need to consider that a Rockpool-based solution is actually a dynamical system, and not a simple stack of stateless linear algebra.

This notebook illustrates some of the sharp points that can jab you when you dip your hands into Rockpool. Our goal is to make this list disappear over time.

```
[1]:
```

```
# - Switch off warnings
import warnings
warnings.filterwarnings("ignore")
# - Rockpool imports
from rockpool import TSContinuous
# - General imports and configuration
import numpy as np
import sys
!{sys.executable} -m pip install --quiet matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams["figure.figsize"] = [12, 4]
plt.rcParams["figure.dpi"] = 300
```

## π¦ How to use sampled time series data (in)correctly in Rockpoolο

Time series data loaded from elsewhere probably comes in a clocked raster format. You can easily use this data in Rockpool, but there are a couple of tricky points to watch out for.

### Wrong: how to generate a time base for clocked dataο

```
[2]:
```

```
T = 1000
dt = 1e-3
data = np.random.rand(T)
t_start = 23.6
time_base = np.arange(t_start, t_start + len(data) * dt, dt)
```

This approach can sometimes lead to rounding errors such that `time_base`

is one sample too short, especially for floating point numbers with < 64 bit precision.

The better way is to generate integer time steps, then scale and shift them:

```
[3]:
```

```
time_base = np.arange(T) * dt + t_start
```

### Wrong: how to define a time series from clocked dataο

Say we have a 20-second sample, sampled on a 100ms clock. Letβs convert this into a continuous time series:

```
[4]:
```

```
dt = 100e-3
T = round(20 / dt)
data = np.random.rand(T)
time_base = np.arange(len(data)) * dt
ts = TSContinuous(time_base, data)
ts
```

```
[4]:
```

```
non-periodic TSContinuous object `unnamed` from t=0.0 to 19.900000000000002. Samples: 200. Channels: 1
```

Huh? The time series is too short, itβs one `dt`

off! But there are 200 samplesβ¦?

Time series objects donβt have an intrisic clock; you can define samples at any point in time. So `TSContinuous`

has no way to know that you expected a 20 second duration. By default, the time series ends at the point in time where the last sample occurs. But for clocked data, you probably expected there to be an extra `dt`

.

The correct low-level way to define the time series is to specify `t_stop`

explicitly:

```
[5]:
```

```
ts = TSContinuous(time_base, data, t_stop=20.0)
ts
```

```
[5]:
```

```
non-periodic TSContinuous object `unnamed` from t=0.0 to 20.0. Samples: 200. Channels: 1
```

However, we provide a convenience method `TSContinuous.from_clocked()`

to make this easier:

```
[6]:
```

```
ts = TSContinuous.from_clocked(data, dt=dt)
ts
```

```
[6]:
```

```
non-periodic TSContinuous object `unnamed` from t=0.0 to 20.000000000000004. Samples: 200. Channels: 1
```

`TSContinuous.from_clocked()`

is the canonical way to import clocked time-series data into Rockpool. If you use `from_clocked()`

then everything should behave as you expect it to.

## π Defining extents for `TSEvent`

time series dataο

Event time series are represented in Rockpool using the `TSEvent`

class, which represents events as occuring at discrete moments in time, on one of a number of channels. This is different from an βevent rasterβ representation, where events are placed into discrete time bins, and the temporal resolution is limited to bin durations `dt`

.

```
[7]:
```

```
from rockpool import TSEvent
from matplotlib import pyplot as plt
times = [0.2, 0.8, 1.2, 1.4, 1.8, 2.2, 3.3, 3.5, 3.6, 4.2, 4.8, 5.2, 5.8, 6.2, 6.5, 6.8]
channels = [0, 3, 3, 1, 6, 6, 3, 3, 5, 5, 4, 0, 5, 1, 1, 2]
ts = TSEvent(times, channels, t_start=0.0, t_stop=7.0)
ts.plot();
```

```
[8]:
```

```
from IPython.display import Image
Image("TSEvent_to_raster.png")
```

```
[8]:
```

The image above shows the relationship between an event time series and one possible raster representation of that time series. Note that a raster is inherently lossy β it can only represent events down to a minimum temporal resolution `dt`

.

We can convert a `TSEvent`

object into a raster by using the appropriately-named `raster()`

method:

```
def raster(
dt: float,
t_start: float=None,
t_stop: float=None,
num_timesteps: int=None,
channels: numpy.ndarray=None,
add_events: bool=False,
include_t_stop: bool=False,
) -> numpy.ndarray:
```

With an event raster, the time base is explicitly defined. Time proceeds from `t = t_start`

at the beginning of the raster, and continues until `t = t_start + dt * T`

at the end of the raster (after `T`

time bins). If you convert a raster directly to a `TSEvent`

object using the `from_raster()`

method, then `t_start`

and `t_stop`

are inferred.

But a freshly-created `TSEvent`

object canβt infer `t_start`

and `t_stop`

β we need to supply these explicitly when creating the `TSEvent`

. If we donβt, things can get confusing:

```
[9]:
```

```
tsBad = TSEvent(times, channels, t_stop=7.0, name="No extents π€’π€¬")
print(tsBad)
tsGood = TSEvent(times, channels, t_start=0.0, t_stop=7.0, name="With extents ππ₯°")
print(tsGood)
```

```
non-periodic `TSEvent` object `No extents π€’π€¬` from t=0.2 to 7.0. Channels: 7. Events: 16
non-periodic `TSEvent` object `With extents ππ₯°` from t=0.0 to 7.0. Channels: 7. Events: 16
```

Imagine you tried to `evolve()`

a layer with the first `ts`

β the `evolve()`

method would probably get the evolution duration wrong.

Things would also get messy if you tried to convert the first `ts`

to a `raster()`

. The time bins would begin at `t = 0.2`

rather than `t = 0`

.

The moral of the story is, you should *always* set the extents for a `TSEvent`

, if you are creating it from a list of event times.

## π¦ `Module`

s in Rockpool are dynamical systems, and donβt get reset implicitlyο

Neurons and modules in Rockpool behave quite differently from standard ANNs. They are intrinsically stateful, even in the absence of explicit recurrent connectivity. Single neurons have an internal state, which evolves dynamically over time following a set of differential equations.

Standard ANNs are stateless, meaning that each frame or sample is processed completely independently. Recurrent ANNs or LSTMs are often implicitly reset at the start of each trial. Neurons in Rockpool are only ever reset explicitly using `nn.modules.Module.reset_state()`

.

An explicit reset could become important during training, to ensure that a networkβs response to a trial is not contaminated by the response to the previous trial.