## Discrete model

A discrete model interface is an extension of a filter interface. The SmartCGMS provides signal generator filter, which instantiates selected discrete model and manages its lifetime.

Discrete model is an entity, which has a state and is able to change this state in incremental, discrete steps. Unlike a signal model, a discrete model preserves its state, which renders it unable to retrieve past signal values. A typical use of discrete model is to implement a compartment model - it has a state (current compartment quantities) and performs discrete steps (usually as a single step of an ordinary differential equation solver).

Descriptor of a discrete model contains `scgms::NModel_Flags::Discrete_Model`

in the `flags`

field. The model is instantiated using the `do_create_discrete_model`

call.

### Interface

Every discrete model entity must implement both `scgms::IFilter`

and `scgms::IDiscrete_Model`

interface (as the former is a parent of the latter). The former is described in a
filter subpage. The latter is defined as an abstract C++ class as follows:

class IDiscrete_Model : public virtual scgms::IFilter {
public:
virtual HRESULT IfaceCalling Initialize(const double current_time, const uint64_t segment_id) = 0;
virtual HRESULT IfaceCalling Step(const double time_advance_delta) = 0;
};

The `scgms::IFilter`

method contracts remains the same - both are called upon the calls to the parent filter (i.e.; when the signal generator filter interface method is called,
the filter calls the same method of the discrete model).

The `Initialize`

is called by outer code typically at the start of a time segment. Its sole purpose is to initialize the state of the discrete model at a given time. The model should store
the timestamp, as any further stepping is performed in relative increments. This method must be called exactly once, before any `Step`

method call. Any further calls results in
`E_ILLEGAL_STATE_CHANGE`

return code. Otherwise, this method returns `S_OK`

.

The `Step`

method is called for two reasons. The first is to advance the model internal state by a fixed, discrete amount of time, when the `time_advance_delta`

parameter is a positive number.
The second is to emit current state, when the `time_advance_delta`

equals zero. This parameter must never be negative, othwerise the call returns with `E_INVALIDARG`

error code. This method
should also never be called prior the `Initialize`

call. Doing so will result in an `E_ILLEGAL_METHOD_CALL`

error code.

### Operation

The discrete model extends the filter state model :

*Created*- the discrete model just got instantiated and waits for the`Configure`

call.*Configured*- the discrete model is initialized and waits for the`Initialize`

method call.*Operational*- the discrete model is configured and properly initialized. It is able to process device events in`Execute`

method and peform steps in`Step`

method. The`Step`

method is called exclusively in this state.*Terminated*- the discrete model received the`Shut_Down`

device event code, deallocated all its resources and waits for its deallocation by outer code. Any further`Configure`

,`Execute`

,`Initialize`

and`Step`

call on this discrete model instance is invalid.

The behaviour is similar to a filter entity, with the exception of initialization step.

One important thing to note is, that when the discrete model is encapsulated by a filter (preferably a signal generator filter), the `Execute`

method
call is chained through the discrete model, as depicted in figure below.

### Example implementation

At first, we usually create a header defines for our model to describe the purpose and basic idea. As an example, let us define a model, that generates a signal in a "saw" form (increasing until a certain point, in which it resets to a base value). Please note, that this is a very simple example of such a model and merely describes the interface and operation of discrete models.

The structures for a model usually falls to a descriptor file header:

namespace example_discrete_model {
constexpr GUID model_id = { 0x1ac12918, 0x9a9c, 0x144f, { 0xaf, 0x12, 0x45, 0x8c, 0xf1, 0x53, 0x57, 0xe5 } };
constexpr GUID saw_signal_id = { 0x9a7dd21a, 0x1744, 0xfe6a, { 0x89, 0x90, 0xa4, 0x91, 0x3b, 0xa, 0x11, 0x21 } };
const size_t param_count = 3;
// we represent model parameters by named fields and also as a vector
struct TParameters {
union {
struct {
double base, step_per_minute, threshold;
};
double vector[param_count];
};
};
const TParameters lower_bounds = { { 0.0, 0.05, 5.0 } };
const TParameters default_parameters = { { 5.0, 0.1, 10.0 } };
const TParameters upper_bounds = { { 10.0, 0.5, 20.0 } };
}

Now, we can define the discrete model class (using the SmartCGMS SDK):

class CExample_Discrete_Model : public scgms::CBase_Filter, public scgms::IDiscrete_Model {
private:
example_discrete_model::TParameters mParameters;
protected:
uint64_t mSegment_Id = scgms::Invalid_Segment_Id;
double mCurrent_Time = 0;
double mCurrent_Value = 0;
protected:
// scgms::CBase_Filter iface implementation
virtual HRESULT Do_Execute(scgms::UDevice_Event event) override final {
// just pass the event further
return mOutput.Send(event);
}
virtual HRESULT Do_Configure(scgms::SFilter_Configuration configuration, refcnt::Swstr_list& error_description) override final {
mCurrent_Value = mParameters.base;
return S_OK;
}
public:
CExample_Discrete_Model(scgms::IModel_Parameter_Vector *parameters, scgms::IFilter *output);
virtual ~CExample_Discrete_Model() = default;
// scgms::IDiscrete_Model iface
virtual HRESULT IfaceCalling Initialize(const double current_time, const uint64_t segment_id) override final {
if (mSegment_Id != scgms::Invalid_Segment_Id) {
return E_ILLEGAL_STATE_CHANGE;
}
mSegment_Id = segment_id;
mCurrent_Time = current_time;
return S_OK;
}
virtual HRESULT IfaceCalling Step(const double time_advance_delta) override final {
if (mSegment_Id == scgms::Invalid_Segment_Id) {
return E_ILLEGAL_METHOD_CALL;
}
if (time_advance_delta < 0.0) {
return E_INVALIDARG;
}
if (time_advance_delta > 0.0) {
mCurrent_Time += time_advance_delta;
mCurrent_Value += (time_advance_delta / scgms::One_Minute) * mParameters.step_per_minute;
if (mCurrent_Value > mParameters.threshold) {
mCurrent_Value = mParameters.base;
}
}
scgms::UDevice_Event evt{ scgms::NDevice_Event_Code::Level };
evt.device_id() = example_discrete_model::model_id;
evt.device_time() = mCurrent_Time;
evt.level() = mCurrent_Value;
evt.signal_id() = example_discrete_model::saw_signal_id;
evt.segment_id() = mSegment_Id;
return mOutput.Send(evt);
}
};

Then, we define a descriptor block:

// we will sure use the following headers
#include <utils/descriptor_utils.h>
#include <iface/DeviceIface.h>
#include <lang/dstrings.h>
#include <rtl/manufactory.h>
namespace example_discrete_model {
const wchar_t *model_param_ui_names[model_param_count] = {
L"Saw base value",
L"Increment per minute",
L"Upper threshold"
};
const scgms::NModel_Parameter_Value model_param_types[model_param_count] = {
scgms::NModel_Parameter_Value::mptDouble,
scgms::NModel_Parameter_Value::mptDouble,
scgms::NModel_Parameter_Value::mptDouble
};
constexpr size_t number_of_calculated_signals = 1;
const GUID calculated_signal_ids[number_of_calculated_signals] = {
saw_signal_id
};
const wchar_t* calculated_signal_names[number_of_calculated_signals] = {
L"Saw signal"
};
const GUID reference_signal_ids[number_of_calculated_signals] = {
Invalid_GUID
};
scgms::TModel_Descriptor desc = {
model_id,
scgms::NModel_Flags::Discrete_Model,
L"Example discrete model (saw)",
nullptr,
model_param_count,
0,
model_param_types,
model_param_ui_names,
nullptr,
lower_bounds.vector,
default_parameters.vector,
upper_bounds.vector,
number_of_calculated_signals,
calculated_signal_ids,
reference_signal_ids,
};
// for more info, see signal subpage
const scgms::TSignal_Descriptor saw_signal_desc = {
saw_signal_id,
L"Saw signal",
L"",
scgms::NSignal_Unit::Unitless,
0xFFFF0000,
0xFFFF0000,
scgms::NSignal_Visualization::smooth,
scgms::NSignal_Mark::none,
nullptr
};
}

The block above contains a definition of signal (its descriptor). For more details about the descriptor contents, see the signal page.

Then we have to define and export `do_get_model_descriptors`

and `do_get_signal_descriptors`

functions:

const std::array<scgms::TModel_Descriptor, 1> model_descriptors = { { example_discrete_model::desc } };
const std::array<scgms::TSignal_Descriptor, 1> signal_descriptors = { { example_discrete_model::saw_signal_desc } };
HRESULT IfaceCalling do_get_model_descriptors(scgms::TModel_Descriptor **begin, scgms::TModel_Descriptor **end) {
return do_get_descriptors(model_descriptors, begin, end);
}
HRESULT IfaceCalling do_get_signal_descriptors(scgms::TSignal_Descriptor **begin, scgms::TSignal_Descriptor **end) {
return do_get_descriptors(signal_descriptors, begin, end);
}

The last step is to implement the factory method `do_create_discrete_model`

:

HRESULT IfaceCalling do_create_discrete_model(const GUID *model_id, scgms::IModel_Parameter_Vector *parameters, scgms::IFilter *output, scgms::IDiscrete_Model **model) {
if (*model_id == example_discrete_model::model_id) {
return Manufacture_Object<CExample_Discrete_Model>(model, parameters, output);
}
return E_NOTIMPL;
}