Documentation

This page hosts the documentation of the SmartCGMS software architecture and all of its components.


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

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 the times and levels arrays with internally stored time-ordered (oldest to newest) discrete values; the maximum count is determined by the count parameter, the actual count is stored into the filled 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
  • Get_Discrete_Bounds - fills the time_bounds with bounds of recorded time, fills the level_bounds with bounds of recorded values and level_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
  • Update_Levels - stored a new set of time-value pairs (given in times and levels 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 times
    • params could be nullptr, 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 the times 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)

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.