# Time-series access

Since one dimension of time-series data is associated with time, there is a natural way to index into time-series data using time objects as indices. For the data classes (Time-series) an indexing operation performed with a single time-point (a single-element `TimeArray` or a `TimeArray` with more than one element) should result in returning the data at that time-point - that is, removal of the time-dimension from the data.

The base-classes representing time (Time) serve as natural intermediaries in this process, by providing the integer index of a particular time-point (or returning an array of integers, as the case may be, see below). Therefore, these classes should include a method which performs this conversion, `index_at()`. This function should accept as parameter a `TimeArray` and return integers corresponding to the location of that time-point in the different types of time classes.

## Access into Time classes

### `EventArray`

`ev.index_at()` returns the indices of the values in the array that are closest to t. That is, it returns i, such that \(|(t-t_i)|\) is the minimal.

Potentially, an optional ‘tolerance’ argument can be implemented, specifying a maximal time difference between the index time and the returned time.

### `NonUniformTime`

As above, `nut.index_at()` also returns the indices in the array that are closest to t. Since `NonUniformTime` is ordered, this should give you either the index below or the index above the time-point you provide as input, depending on what interval (\(|t-t_i|\) or \(|t-t_{i+1}|\)) is smaller.

### `UniformTime`

`ut.index_at()` returns the indices of the values in the array that are the largest time values, smaller than the input values t. That is, it returns i for which \(t_i\) is the maximal one, which still fulfills: \(t_i<t\).

### Questions

The following questions apply to all three cases:

- what happens when the t is smaller than the smallest entry in the array return None?

- what happens when t is larger than the last entry in the time array? return None?

### `at()`

This function extracts the value of the time array, which corresponds to the output of `index_at()` with an input t.

That is, for an instance `T` of one of the time classes, this function will return:

    T.time[T.index_at(t)]

## Indexing into data time-series objects

### Indexing with time

The above function `index_at()` serves as the basis for the implementation of the function `at()` for the time-series data objects. This function returns the part of the data in `UniformTimeSeries.data` (or the equivalent data structure in `EventSeries` and `NonUniformTimeSeries`) that corresponds to the times provided.

Importantly, the result of indexing into a time-series data object using a time object is always again either an instance of the same time-series data class or an instance of a vanilla nd-array. The latter case only occurs, when a single time point is used to index into the time-series data and is analogous to indexing with a single integer into an nd-array. Conversion between different time-series classes can occur if the indexing time-points are non-uniform (for conversion between `UniformTimeSeries` and `NonUniformTimeSeries`) or if the time-points are not ordered (for conversion from `UniformTimeSeries` or from `NonUniformTimeSeries` to `EventSeries`).

Currently, the plan is to implement the indexing operation using the method `at()` and only later to map the method `ts.__getitem__()` to the function `ts.at()`. For now, we not that using the function `ts.at()` directly is more flexible since it allows to use additional keyword arguments, so, for now, it is unclear what to set as the default behavior for `at()`, which will be executed by `__getitem__()`.

The function `during()` will receive as input a `TimeInterval` objects and will return the data corresponding to the interval, while dealing appropriately with the `TI.t_step` (see Interval object for details). How is this done? For an object of class `UniformTimeSeries`, access using intervals, will give you back a uniform time-series objects with the time being of length of `TI.t_start` - `TI.t_stop` and with the `TS.t0` offset by the `TimeInterval`’s `TI.t_step`.

### Indexing with integers

In parallel to the access with time-points, described above, we would like to implement indexing the time-series classes directly using integer indices and ordinary slices (with integer start, stop, and step). This should have the same effect as indexing the underlying nd-array using the same indices and slices, such that:

    T.at(T.time.index_at(i)) = T[i] = T.data[...,i]
    T.time.at(i) = T.time[i] = T.time.asarray()[i]

In order to make the above code more compact, would be another reason to implement the the time dimension as the first dimension (not last, see Time-series): this would allow to rewrite the above:

    T.at(i) = T[i] = T.data[i]

Every time-series data (and time) object would also implements a method `T.slice_at()` that given a `TimeInterval` object TI (see Interval object) returns an integer slice slice(i,j) suitable for indexing both into the nd-array `T.data` and into `T.time`:

     T.interval2slice(TI) = slice(T.time2index(TI.t_start),
     T.time2index(TI.t_stop))

    data_slice = T.data[...,T.slice_at(TI)]
    time_slice = T.time[T.slice_at(TI)]