Signal
The signal entity is a general purpose container of time-ordered values. We recognize two types of signals:
- measured signals - signals of this type typically store discrete values as they come through the filter chain
- returns discrete values and bounds if requested through an appropriate method call
- uses the approximator entity to approximate/interpolate the discrete values and thus calculate the continuous levels
- calculated signals - signals produced by a signal model
- do not work with discrete values and/or bounds, corresponding methods return
E_NOTIMPL
- use a custom implementation to calculate continuous levels, do not use any approximator
- do not work with discrete values and/or bounds, corresponding methods return
An interface definition for a signal entity is called scgms::ISignal
and is defined as follows:
class ISignal : public virtual refcnt::IReferenced {
public:
virtual HRESULT IfaceCalling Get_Discrete_Levels(double* const times, double* const levels, const size_t count, size_t *filled) const = 0;
virtual HRESULT IfaceCalling Get_Discrete_Bounds(TBounds* const time_bounds, TBounds* const level_bounds, size_t *level_count) const = 0;
virtual HRESULT IfaceCalling Update_Levels(const double *times, const double *levels, const size_t count) = 0;
virtual HRESULT IfaceCalling Get_Continuous_Levels(IModel_Parameter_Vector *params, const double* times, double* const levels,
const size_t count, const size_t derivation_order) const = 0;
virtual HRESULT IfaceCalling Get_Default_Parameters(IModel_Parameter_Vector *parameters) const = 0;
};
Here, TBounds
is a simple bounds container:
struct TBounds {
double Min, Max;
};
Above defined methods often works with times
and levels
pointers. These are pointers to arrays containing the time and level values. Both of these arrays are the same size, which is determined by the count
parameter. The value at a given index in the levels
array was recorded with a time at the same index in the times
array.
Get_Discrete_Levels
- fills thetimes
andlevels
arrays with internally stored time-ordered (oldest to newest) discrete values; the maximum count is determined by thecount
parameter, the actual count is stored into thefilled
parameter- a call to
Get_Discrete_Bounds
usually comes prior to this method call to obtain the actual count of values - the returned times and values are ordered from the oldest to the newest
- measured signals implements this method to return measured values
- calculated signals return
E_NOTIMPL
- a call to
Get_Discrete_Bounds
- fills thetime_bounds
with bounds of recorded time, fills thelevel_bounds
with bounds of recorded values andlevel_count
with the count of levels stored- any of the parameters may be set to
nullptr
to signalize we are not interested in such bounds or counts; this may improve performance - measured signals return valid bounds and counts, if requested
- calculated signals return
E_NOTIMPL
- any of the parameters may be set to
Update_Levels
- stored a new set of time-value pairs (given intimes
andlevels
arrays) into the internal storage- measured signals should store new levels
- calculated signals return
E_NOTIMPL
Get_Continuous_Levels
- retrieves levels (or level derivation) at given arbitrary times, that do not necessarily match the recorded discrete levels timesparams
could benullptr
, typically when calling this method on a measured signal, or when use of default parameters is intended- the
levels
array must be equal in size to thetimes
array (pre-allocated prior the method call) derivation_order
contains the desired order of derivation to be retrieved; one may use the pre-defined constants for levels-only (scgms::apxNo_Derivation) or first-order derivation (scgms::apxFirst_Order_Derivation)- in a measured signal, this retrieves the derivation approximation given by the approximator entity
- calculated signal calculates the derivation by its own internal rules
- however, it is not required to support any derivation order greater than 0, i.e.; the only required implementation is for retrieving the levels-only
Get_Default_Parameters
- retrieves a default parameter set for a given signal- measured signals return
E_NOTIMPL
- calculated signals return the default parameter set copied to the pre-allocated container (via the container
set
method)
- measured signals return
Example implementation
Please, refer to the signal model subpage to see an example of a model signal implementation. To implement a measured signal, do not inherit from the calculated signal parent, but rather implement every method in your class.
Use of a signal entity
Many scientific papers and engineering work relies on data series to process signals. Each signal is a dedicated data series. ISignal
interface provides
a convenient way to update the data series with new levels measured at specific times, to query their bounds and the levels - see the methods containing _Discrete_
. Moreover, it provide approximation/interpolation
of the data series, which is done using the IAproximator interface - see the Get_Continuous_Levels
method.
When it comes to performance, the main advantage is that entire signal is processed at once, possibly using vectorized instructions. For example, you can have the glucose sensor measured levels, which are transparently transformed to a more precise blood glucose level estimate using the diffusion model. We achieve such functionality with the Signal Model.
Warning
Be aware that we provide this interface as a convenience only. For a practical use, we strongly discourage its use in favor to the CCircular_Buffer template available with the Native Scripts.
With the circular buffer, you have fixed memory use, which is important with low-power, embedded devices. ISignal
can accomodate as many glucose levels as desired, thus
possibly leading to memory exhaustion on devices with limited amounts of RAM.
In a realistic experimental setup, it is also much more reasonable to calculate new signal values from the recent value(s) of possibly one or several signal(s) only. Therefore, the circular buffer results to faster, memory constant, inexpensive code, which is suitable for practictal, glucose-management, low-power devices. On high-performance servers, it can yield considerably greater performance. You just need to start using the per-partes computation instead of the classic signal processing.
When peer reviewing various conference and journal papers, we found that many of them goes the wrong direction by not considering the limited amount of RAM on low-power devices.
Actually, they did not address limited-computing issues to prove that their work is feasible for a practical use. Therefore, you may find the ISignal
convenient when
porting an older project. For a new project, we strongly suggest to use the circular buffer instead.