Documentation

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

The documentation is being updated to acommodate the recent improvements. Please, be patient, we are working on it...


Metric

A metric in an entity, that evaluates differences between a reference and an error signal, i.e.; calculates the metric value. The metric entity is originally designed to work with a constant memory pool, however it is merely a suggestion for implementors, rather than a requirement.

Descriptor

Every metric is described by its descriptor (scgms::TMetric_Descriptor)

struct TMetric_Descriptor {
    const GUID id;
    const wchar_t *description;
};

The descriptor fields are self-explanatory - the id field contains a unique GUID identifier of the metric entity and description contains the metric entity name, represented as a null-terminated string (must not be nullptr).

A shared object implementing a metric must:

  • export a do_get_metric_descriptors function
    • this function returns a continuous array of metric descriptors identified by its first and one-after-last element
  • export a do_create_metric function, and create a metric contained within that array when called

An example of do_get_metric_descriptors may be as follows:

const std::array<const scgms::TMetric_Descriptor, 1> metric_descriptors = { { descriptor_1, descriptor_2 } };

HRESULT IfaceCalling do_get_metric_descriptors(scgms::TMetric_Descriptor **begin, scgms::TMetric_Descriptor **end) {
    *begin = metric_descriptors.data();
    *end = metric_descriptors.data() + metric_descriptors.size();
    return S_OK;
}

An example of do_create_metric may be as follows:

HRESULT IfaceCalling do_create_metric(const scgms::TMetric_Parameters *parameters, scgms::IMetric **metric) {
    if (parameters->metric_id == descriptor_1.id)
        return Manufacture_Object(metric, *parameters);

    return E_NOTIMPL;
}

Interface

Every metric entity must implement the scgms::IMetric interface, which is defined as follows:

class IMetric: public virtual refcnt::IReferenced {
    public:
        virtual HRESULT IfaceCalling Accumulate(const double *times, const double *reference, const double *calculated, const size_t count) = 0;
        virtual HRESULT IfaceCalling Reset() = 0;
        virtual HRESULT IfaceCalling Calculate(double *metric, size_t *levels_accumulated, size_t levels_required) = 0;
        virtual HRESULT IfaceCalling Get_Parameters(TMetric_Parameters *parameters) = 0;
};

  • Accumulate - accumulates the given reference (reference array) and error (calculated array) values at given times; both arrays are of size count
  • Reset - resets the metric object, i.e.; reverses any prior Accumulate calls
  • Calculate - calculates the metric value and stores the result to the metric parameter, setting the levels_accumulated to a count of valid accumulated values
    • metric must not be nullptr
    • levels_accumulated can be nullptr if this value is not desired
    • if the count of accumulated levels is lower than given levels_required, the metric is not calculated and S_FALSE is returned
  • Get_Parameters - stores the metric parameters of this metric object into the parameters argument

A metric is parametrized with a scgms::TMetric_Parameters container, that is defined as follows:

struct TMetric_Parameters {
    const GUID metric_id;
    const unsigned char use_relative_error;
    const unsigned char use_squared_differences;
    const unsigned char prefer_more_levels;
    const double threshold;
};

  • metric_id - a GUID of a metric, that should be created by the factory function
  • use_relative_error - differences are converted to relative errors before accumulating
    • implementing metric may ignore this parameter, as it may not apply
  • use_squared_differences - differences are squared before accumulating
    • implementing metric may ignore this parameter, as it may not apply
  • prefer_more_levels - metric result value is divided by difference count
    • implementing metric may ignore this parameter, as it may not apply
  • threshold - a threshold for a metric; the meaning of this attribute is implementation-specific
    • implementing metric may ignore this parameter, as it may not apply

Example implementation

As an example of a metric, let us implement a simple average difference metric (the absolute/relative variant is configured via the parameters container).

Similarly to any other entities, let us create a descriptor header file, despite containing solely the metric GUID:

#include <rtl/guid.h>

namespace example_metric {
    constexpr GUID id = { 0x47a8815, 0x9747, 0x4621, { 0xb2, 0xf1, 0xf9, 0x8f, 0x61, 0xcb, 0x64, 0xc3 } };	// {047A8815-9747-4621-B2F1-F98F61CB64C3}
}

Then, we define the metric class, complying to the scgms::IMetric interface. We also split the definition and implementation for clarity:

class CExample_Metric : public virtual scgms::IMetric, public virtual refcnt::CReferenced {

    protected:
        const scgms::TMetric_Parameters mParameters;
        std::vector mDifferences;
    public:
        CExample_Metric(const scgms::TMetric_Parameters ¶ms) : mParameters(params) {}
        virtual ~CExample_Metric() = default;

        virtual HRESULT IfaceCalling Accumulate(const double *times, const double *reference, const double *calculated, const size_t count) final;
        virtual HRESULT IfaceCalling Reset() final;
        virtual HRESULT IfaceCalling Calculate(double *metric, size_t *levels_accumulated, size_t levels_required) final;
        virtual HRESULT IfaceCalling Get_Parameters(scgms::TMetric_Parameters *parameters) final;
};

The implementation then may be as follows:

#include <cmath>

HRESULT IfaceCalling CExample_Metric::Accumulate(const double *times, const double *reference, const double *calculated, const size_t count) {

    for (size_t i = 0; i < count; i++) {

        double diff = std::fabs(reference[i] - calculated[i]);
        // the difference should not be NaN, nor infinity
        if (std::isnan(diff) || std::isinf(diff)) {
            continue;
        }

        if (mParameters.use_relative_error) {
            diff /= std::fabs(calculated[i]);
        }
        if (mParameters.use_squared_differences) {
            diff *= diff;
        }

        // push back valid difference to internal vector
        mDifferences.push_back(diff);
    }

    return S_OK;
}

HRESULT IfaceCalling CExample_Metric::Reset() {
    mDifferences.clear();
    return S_OK;
}

HRESULT IfaceCalling CExample_Metric::Calculate(double *metric, size_t *levels_accumulated, size_t levels_required) {

    // did we satisfy the input requirement on level count?
    *levels_accumulated = mDifferences.size();
    if (*levels_accumulated < std::max(static_cast<size_t>(1), levels_required)) {
        return S_FALSE;
    }

    // let us calculate average absolute difference as an example of such metric
    double metricValue = 0;
	
    for (size_t i = 0; i < mDifferences.size(); i++) {
        metricValue += mDifferences[i];
    }
    metricValue /= static_cast<double>(*levels_accumulated);

    // satisfy the prefer_more_levels requirement, if requested
    if (mParameters.prefer_more_levels) {
        metricValue /= static_cast<double>(*levels_accumulated);
    }

    // set the resulting metric
    *metric = metricValue;

    return S_OK;
}

HRESULT IfaceCalling CExample_Metric::Get_Parameters(scgms::TMetric_Parameters *parameters) {
    memcpy(parameters, &mParameters, sizeof(mParameters));
    return S_OK;
}

Then, we define the descriptor block:

namespace example_metric {
    scgms::TMetric_Descriptor descriptor = {
        id,
        L"Example average difference metric"
    };
}

We have to implement and export the do_get_metric_descriptors to expose implemented metric:

const std::array<scgms::TMetric_Descriptor, 1> metric_descriptors = { { example_metric::descriptor } };

HRESULT IfaceCalling do_get_metric_descriptors(scgms::TMetric_Descriptor const **begin, scgms::TMetric_Descriptor const **end) {
    return do_get_descriptors(metric_descriptors, begin, end);
}

And as a last step, implement and export the do_create_metric factory function:

HRESULT IfaceCalling do_create_metric(const scgms::TMetric_Parameters *parameters, scgms::IMetric **metric) {
    if (parameters->metric_id == example_metric::id)
        return Manufacture_Object<CExample_Metric>(metric, *parameters);

    return E_NOTIMPL;
}