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.
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.
The discrete model extends the filter state model :
Configure
call.Initialize
method call.Execute
method and peform steps in Step
method. The Step
method is called exclusively in this state.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.
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 signal subpage.
Then we have to define and export do_get_model_descriptors
and do_get_signal_descriptors
functions:
const std::array model_descriptors = { { example_discrete_model::desc } };
const std::array 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(model, parameters, output);
}
return E_NOTIMPL;
}