diff --git a/include/rosa/agent/FunctionAbstractions.hpp b/include/rosa/agent/FunctionAbstractions.hpp index b5a0d82..199dd8f 100644 --- a/include/rosa/agent/FunctionAbstractions.hpp +++ b/include/rosa/agent/FunctionAbstractions.hpp @@ -1,357 +1,360 @@ //===-- rosa/agent/FunctionAbstractions.hpp ---------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/FunctionAbstractions.hpp /// /// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at) /// /// \date 2019 /// /// \brief Definition of *FunctionAbstractions* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP #define ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/Functionality.h" #include "rosa/support/debug.hpp" #include #include #include #include namespace rosa { namespace agent { /// Implements \c rosa::agent::Abstraction as a linear function, /// y = Coefficient * X + Intercept. /// /// \note This implementation is supposed to be used to represent a linear /// function from an arithmetic domain to an arithmetic range. This is enforced /// statically. /// /// \tparam D type of the functions domain /// \tparam R type of the functions range template class LinearFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "LinearFunction not arithmetic T"); STATIC_ASSERT((std::is_arithmetic::value), "LinearFunction not to arithmetic"); protected: /// The Intercept of the linear function const D Intercept; /// The Coefficient of the linear function const D Coefficient; public: /// Creates an instance. /// /// \param Intercept the intercept of the linear function /// \param Coefficient the coefficient of the linear function LinearFunction(D Intercept, D Coefficient) noexcept : Abstraction(Intercept), Intercept(Intercept), Coefficient(Coefficient) {} /// Creates an instance given the two points on a linear function. /// /// \param x1 The x-value of the first point /// \param y1 The x-value of the first point /// \param x2 The y-value of the second point /// \param y2 The y-value of the second point LinearFunction(D x1, R y1, D x2, R y2) noexcept : LinearFunction(y1 - x1 * (y1 - y2) / (x1 - x2), (y1 - y2) / (x1 - x2)) {} /// Creates an instance given the two points on a linear function. /// /// \param p1 The coordinates of the first point /// \param p2 The coordinates of the second point LinearFunction(std::pair p1, std::pair p2) noexcept : LinearFunction(p1.first, p1.second, p2.first, p2.second) {} /// Destroys \p this object. ~LinearFunction(void) = default; /// Checks wether the Abstraction evaluates to default at the given position /// As LinearFunctions can be evaluated everythwere, this is always false /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false bool isDefaultAt(const D &V) const noexcept override { (void)V; return false; } /// Getter for member variable Intercept /// /// \return Intercept D getIntercept() const { return Intercept; } /// Setter for member variable Intercept /// /// \param Intercept the new Intercept void setIntercept(const D &Intercept) { this->Intercept = Intercept; } /// Getter for member variable Coefficient /// /// \return Coefficient D getCoefficient() const { return Coefficient; } /// Setter for member variable Coefficient /// /// \param Coefficient the new Intercept void setCoefficient(const D &Coefficient) { this->Coefficient = Coefficient; } /// Set Intercept and Coefficient from two points on the linear function /// /// \param x1 The x-value of the first point /// \param y1 The x-value of the first point /// \param x2 The y-value of the second point /// \param y2 The y-value of the second point void setFromPoints(D x1, R y1, D x2, R y2) { Coefficient = (y1 - y2) / (x1 - x2); Intercept = y1 - Coefficient * x1; } /// Set Intercept and Coefficient from two points on the linear function /// /// \param p1 The coordinates of the first point /// \param p2 The coordinates of the second point inline void setFromPoints(std::pair p1, std::pair p2) { setFromPoints(p1.first, p1.second, p2.first, p2.second); } /// Evaluates the linear function /// /// \param X the value at which to evaluate the function /// /// \return Coefficient*X + Intercept virtual R operator()(const D &X) const noexcept override { return Intercept + X * Coefficient; } }; /// Implements \c rosa::agent::Abstraction as a sine function, /// y = Amplitude * sin(Frequency * X + Phase) + Average. /// /// \note This implementation is supposed to be used to represent a sine /// function from an arithmetic domain to an arithmetic range. This is enforced /// statically. /// /// \tparam D type of the functions domain /// \tparam R type of the functions range template class SineFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "SineFunction not arithmetic T"); STATIC_ASSERT((std::is_arithmetic::value), "SineFunction not to arithmetic"); protected: /// The frequency of the sine wave const D Frequency; /// The Ampiltude of the sine wave const D Amplitude; /// The Phase-shift of the sine wave const D Phase; /// The y-shift of the sine wave const D Average; public: /// Creates an instance. /// /// \param Frequency the frequency of the sine wave /// \param Amplitude the amplitude of the sine wave /// \param Phase the phase of the sine wave /// \param Average the average of the sine wave SineFunction(D Frequency, D Amplitude, D Phase, D Average) noexcept : Abstraction(Average), Frequency(Frequency), Amplitude(Amplitude), Phase(Phase), Average(Average) {} /// Destroys \p this object. ~SineFunction(void) = default; /// Checks wether the Abstraction evaluates to default at the given position /// As SineFunctions can be evaluated everythwere, this is always false /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false bool isDefaultAt(const D &V) const noexcept override { (void)V; return false; } /// Evaluates the sine function /// /// \param X the value at which to evaluate the function /// \return the value of the sine-function at X virtual R operator()(const D &X) const noexcept override { return Amplitude * sin(Frequency * X + Phase) + Average; } }; enum StepDirection { StepUp, StepDown }; /// Implements \c rosa::agent::PartialFunction as a step function from 0 to 1 /// with a ramp in between /// /// \tparam D type of the functions domain /// \tparam R type of the functions range template class StepFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); STATIC_ASSERT((std::is_arithmetic::value), "abstracting not to arithmetic"); private: D Coefficient; D RightLimit; StepDirection Direction; public: /// Creates an instance by Initializing the underlying \c Abstraction. /// /// \param Coefficient Coefficient of the ramp /// \param Direction wether to step up or down /// /// \pre Coefficient > 0 StepFunction(D Coefficient, StepDirection Direction = StepUp) : Abstraction(0), Coefficient(Coefficient), RightLimit(1.0f / Coefficient), Direction(Direction) { ASSERT(Coefficient > 0); } /// Destroys \p this object. ~StepFunction(void) = default; /// Setter for Coefficient /// /// \param Coefficient the new Coefficient void setCoefficient(const D &Coefficient) { ASSERT(Coefficient > 0); this->Coefficient = Coefficient; this->RightLimit = 1 / Coefficient; } /// Setter for RightLimit /// - /// \param RightLimit the new RightLimit - void setRightLimit(const D &RightLimit) { - ASSERT(RightLimit > 0); - this->RightLimit = RightLimit; - this->Coefficient = 1 / RightLimit; + /// \param RightLimit_ the new RightLimit + //@Benedikt: I had to change the name of the parameter from RightLimit to + // RightLimit_, because otherwise there was a "warning treaded as error: + // warning: C4458: declaration of 'RightLimit' hides class member" + void setRightLimit(const D &RightLimit_) { + ASSERT(RightLimit_ > 0); + this->RightLimit = RightLimit_; + this->Coefficient = 1 / RightLimit_; } /// Checks wether the Abstraction evaluates to default at the given position /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false if the is negative, true otherwise bool isDefaultAt(const D &V) const noexcept override { return V > 0; } /// Executes the Abstraction /// /// \param V value to abstract /// /// \return the abstracted value R operator()(const D &V) const noexcept override { R ret = 0; if (V <= 0) ret = 0; else if (V >= RightLimit) ret = 1; else ret = V * Coefficient; return Direction == StepDirection::StepUp ? ret : 1 - ret; } }; /// Implements \c rosa::agent::Abstraction as a partial function from a domain /// to a range. /// /// \note This implementation is supposed to be used to represent a partial /// function from an arithmetic domain to an arithmetic range. This is enforced /// statically. /// /// A partial function is defined as a list of abstractions, where each /// abstraction is associated a range in which it is defined. These ranges must /// be mutually exclusive. /// /// \tparam D type of the functions domain /// \tparam R type of the functions range template class PartialFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); STATIC_ASSERT((std::is_arithmetic::value), "abstracting not to arithmetic"); private: /// A \c rosa::agent::RangeAbstraction RA is used to represent the association /// from ranges to Abstractions. /// This returns the Abstraction that is defined for any given value, or /// a default Abstraction if no Abstraction is defined for that value. RangeAbstraction>> RA; public: /// Creates an instance by Initializing the underlying \c Abstraction. /// /// \param Map the mapping to do abstraction according to /// \param Default abstraction to abstract to by default /// /// \pre Each key defines a valid range such that `first <= second` and /// there are no overlapping ranges defined by the keys. PartialFunction( const std::map, std::shared_ptr>> &Map, const R Default) : Abstraction(Default), RA(Map, std::shared_ptr>(new Abstraction(Default))) { } /// Destroys \p this object. ~PartialFunction(void) = default; /// Checks wether the Abstraction evaluates to default at the given position /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false if the value falls into a defined range and the Abstraction /// defined for that range does not fall back to it's default value. bool isDefaultAt(const D &V) const noexcept override { return RA.isDefaultAt(V) ? true : RA(V)->isDefaultAt(V); } /// Searches for an Abstraction for the given value and executes it for that /// value, if such an Abstraction is found. The default Abstraction is /// evaluated otherwise. /// /// \param V value to abstract /// /// \return the abstracted value based on the set mapping R operator()(const D &V) const noexcept override { return RA(V)->operator()(V); } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP diff --git a/include/rosa/agent/History.hpp b/include/rosa/agent/History.hpp index 5593cb7..d1cc791 100644 --- a/include/rosa/agent/History.hpp +++ b/include/rosa/agent/History.hpp @@ -1,528 +1,580 @@ //===-- rosa/agent/History.hpp ----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/History.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *history* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_HISTORY_HPP #define ROSA_AGENT_HISTORY_HPP #include "rosa/agent/Functionality.h" #include "rosa/config/config.h" #include "rosa/support/debug.hpp" #include "rosa/support/type_helper.hpp" #include +#include #include +#include namespace rosa { namespace agent { /// Retention policies defining what a \c rosa::agent::History instance should /// do when the number of recorded entries reached its capacity. enum class HistoryPolicy { SRWF, ///< Stop Recording When Full -- no new entry is recorded when full FIFO, ///< First In First Out -- overwrite the earliest entry with a new one LIFO ///< Last In First Out -- overwrite the latest entry with a new one }; template class History : public Functionality { public: History(void) noexcept {} /// Destroys \p this object. virtual ~History(void) = default; /// Tells the retention policy applied to \p this object. /// /// \return \c rosa::agent::History::P static constexpr HistoryPolicy policy(void) noexcept { return P; } /// Tells how many entries may be recorded by \c this object. /// /// \note The number of entries that are actually recorded may be smaller. /// /// \return The max number of entries that may be recorded virtual size_t maxLength(void) const noexcept = 0; /// Tells how many entries are currently recorded by \p this object. /// /// \return number of entries currently recorded by \p this object. /// /// \post The returned value cannot be larger than the capacity of \p this /// object:\code /// 0 <= numberOfEntries() && numberOfEntries <= lengthOfHistory() /// \endcode virtual size_t numberOfEntries(void) const noexcept = 0; /// Tells if \p this object has not recorded anything yet. /// /// \return if \p this object has no entries recorded bool empty(void) const noexcept { return numberOfEntries() == 0; } /// Tells if the history reached it's maximum length /// /// \return if the history reached it's maximum length. bool full(void) const noexcept { return numberOfEntries() == maxLength(); } /// Gives a constant lvalue reference to an entry stored in \p this object. /// /// \note The recorded entries are indexed starting from the latest one. /// /// \param I the index at which the stored entry to take from /// /// \pre \p I is a valid index:\code /// 0 <= I && I < numberOfEntries() /// \endcode virtual const T &entry(const size_t I = 0) const noexcept = 0; /// Removes all entries recorded in \p this object. virtual void clear() noexcept = 0; private: /// Pushes a new entry into the history. /// /// \note The earliest entry gets overwritten if the history is full. /// /// \param V value to push into the history virtual void pushBack(const T &V) noexcept = 0; /// Replaces the most recent entry in the history. /// /// \param V value to replace the most current value with virtual void replaceFront(const T &V) noexcept = 0; public: /// Adds a new entry to \p this object and tells if the operation was /// successful. /// /// \note Success of the operation depends on the actual policy. /// /// \param V value to store /// /// \return if \p V was successfully stored bool addEntry(const T &V) noexcept { switch (P) { default: ROSA_CRITICAL("unkown HistoryPolicy"); case HistoryPolicy::LIFO: if (full()) { replaceFront(V); return true; } case HistoryPolicy::SRWF: if (full()) { return false; } // \note Fall through to FIFO which unconditionally pushes the new entry. case HistoryPolicy::FIFO: // FIFO and SRWF not full. pushBack(V); return true; } } /// Tells the trend set by the entries recorded by \p this object. /// /// The number of steps to go back when calculating the trend is defined as /// argument to the function. /// /// \note The number of steps that can be made is limited by the number of /// entries recorded by \p this object. /// /// \note The function is made a template only to be able to use /// \c std::enable_if. /// /// \tparam X always use the default! /// /// \param D number of steps to go back in *history* /// /// \return trend set by analyzed entries /// /// \pre Statically, \p this object stores signed arithmetic values:\code /// std::is_arithmetic::value && std::is_signed::value /// \endcode Dynamically, \p D is a valid number of steps to take:\code /// 0 <= D && D < lengthOfHistory() /// \endcode template typename std::enable_if< std::is_arithmetic::value && std::is_signed::value, X>::type trend(const size_t D) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); ASSERT(0 <= D && D < maxLength()); // Boundary check. if (numberOfEntries() < 2 || D < 1) { // No entries for computing trend. return {}; // Zero element of \p T } else { // Here at least two entries. // \c S is the number of steps that can be done. const size_t S = std::min(numberOfEntries() - 1, D); size_t I = S; // Compute trend with linear regression. size_t SumIndices = 0; T SumEntries = {}; T SumSquareEntries = {}; T SumProduct = {}; while (I > 0) { // \note Indexing for the regression starts in the past. const size_t Index = S - I; const T Entry = entry(--I); SumIndices += Index; SumEntries += Entry; SumSquareEntries += Entry * Entry; SumProduct += Entry * Index; } return (SumProduct * S - SumEntries * SumIndices) / (SumSquareEntries * S - SumEntries * SumEntries); } } /// Tells the average absolute difference between consecutive entries recorded /// by \p this object /// The number of steps to go back when calculating the average is defined as /// argument to the function. /// /// \note The number of steps that can be made is limited by the number of /// entries recorded by \p this object. /// /// \note The function is made a template only to be able to use /// \c std::enable_if. /// /// \tparam X always use the default! /// /// \param D number of steps to go back in *history* /// /// \pre Statically, \p this object stores arithmetic values:\code /// std::is_arithmetic::value /// \endcode Dynamically, \p D is a valid number of steps to take:\code /// 0 <= D && D < lengthOfHistory() /// \endcode template typename std::enable_if::value, size_t>::type averageAbsDiff(const size_t D) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); ASSERT(0 <= D && D < maxLength()); // Boundary check. if (numberOfEntries() < 2 || D < 1) { // No difference to average. return {}; // Zero element of \p T } else { // Here at least two entries. // \c S is the number of steps that can be done. const size_t S = std::min(numberOfEntries() - 1, D); // Sum up differences as non-negative values only, hence using an // unsigned variable for that. size_t Diffs = {}; // Init to zero. // Count down entry indices and sum up all the absolute differences. size_t I = S; T Last = entry(I); while (I > 0) { T Next = entry(--I); Diffs += Last < Next ? Next - Last : Last - Next; Last = Next; } // Return the average of the summed differences. return Diffs / S; } } /// Tells the average of all entries recorded by \p this object /// /// \tparam R type of the result template R average() const noexcept { R Average = 0; for (size_t I = 0; I < numberOfEntries(); I++) { Average += entry(I); } Average /= numberOfEntries(); return Average; } }; /// Implements *history* by recording and storing values. /// The length of the underlying std::array is static and must be set at /// compile-time /// /// \note Not thread-safe implementation, which should not be a problem as any /// instance of \c rosa::agent::Functionality is an internal component of a /// \c rosa::Agent, which is the basic unit of concurrency. /// /// \tparam T type of values to store /// \tparam N number of values to store at most /// \tparam P retention policy to follow when capacity is reached /// /// \invariant The size of the underlying \c std::array is `N + 1`:\code /// max_size() == N + 1 && N == max_size() - 1 /// \endcode template class StaticLengthHistory : public History, private std::array { // Bring into scope inherited functions that are used. using std::array::max_size; using std::array::operator[]; /// The index of the first data element in the circular buffer. size_t Data; /// The index of the first empty slot in the circular buffer. size_t Space; public: using History::policy; using History::empty; using History::full; using History::addEntry; using History::trend; using History::averageAbsDiff; /// Creates an instances by initializing the indices for the circular buffer. StaticLengthHistory(void) noexcept : Data(0), Space(0) {} /// Destroys \p this object. ~StaticLengthHistory(void) override = default; /// Tells how many entries may be recorded by \c this object. /// /// \note The number of entries that are actually recorded may be smaller. /// /// \return \c rosa::agent::History::N size_t maxLength(void) const noexcept override { return N; } /// Tells how many entries are currently recorded by \p this object. /// /// \return number of entries currently recorded by \p this object. /// /// \post The returned value cannot be larger than the capacity of \p this /// object:\code /// 0 <= numberOfEntries() && numberOfEntries <= lengthOfHistory() /// \endcode size_t numberOfEntries(void) const noexcept override { return Data <= Space ? Space - Data : max_size() - Data + Space; } /// Gives a constant lvalue reference to an entry stored in \p this object. /// /// \note The recorded entries are indexed starting from the latest one. /// /// \param I the index at which the stored entry to take from /// /// \pre \p I is a valid index:\code /// 0 <= I && I < numberOfEntries() /// \endcode const T &entry(const size_t I = 0) const noexcept override { ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. // Position counted back from the last recorded entry. typename std::make_signed::type Pos = Space - (1 + I); // Actual index wrapped around to the end of the buffer if negative. return (*this)[Pos >= 0 ? Pos : max_size() + Pos]; } /// Removes all entries recorded in \p this object. void clear() noexcept override { Data = 0; Space = 0; } private: /// Pushes a new entry into the circular buffer. /// /// \note The earliest entry gets overwritten if the buffer is full. /// /// \param V value to push into the buffer void pushBack(const T &V) noexcept override { // Store value to the first empty slot and step Space index. (*this)[Space] = V; Space = (Space + 1) % max_size(); if (Data == Space) { // Buffer was full, step Data index. Data = (Data + 1) % max_size(); } } /// Replaces the most recent entry in the history. /// /// \param V value to replace the most current value with void replaceFront(const T &V) noexcept override { (*this)[(Space - 1) % max_size()] = V; } }; /// Adds a new entry to a \c rosa::agent::History instance. /// /// \note The result of \c rosa::agent::History::addEntry is ignored. /// /// \tparam T type of values stored in \p H /// \tparam N number of values \p H is able to store /// \tparam P retention policy followed by \p H when capacity is reached /// /// \param H to add a new entry to /// \param V value to add to \p H /// /// \return \p H after adding \p V to it template StaticLengthHistory &operator<<(StaticLengthHistory &H, const T &V) noexcept { H.addEntry(V); return H; } /// Implements *DynamicLengthHistory* by recording and storing values. /// /// \note Not thread-safe implementation, which should not be a problem as any /// instance of \c rosa::agent::Functionality is an internal component of a /// \c rosa::Agent, which is the basic unit of concurrency. /// /// \tparam T type of values to store /// \tparam P retention policy to follow when capacity is reached template class DynamicLengthHistory : public History, private std::vector { + //@benedikt: if i dont make these public, I cannot iterate from outside + // through the history. E.g., "for (auto &SavedSignalState : + // DetectedSignalStates)" at line ~297 in "SignalStateDetector.hpp". Do you + // have an idea to make this in a better/more beautiful way? +public: // Bring into scope inherited functions that are used. using std::vector::erase; using std::vector::begin; using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; using std::vector::size; using std::vector::max_size; using std::vector::resize; using std::vector::push_back; using std::vector::pop_back; using std::vector::operator[]; /// The current length of the DynamicLengthHistory. size_t Length; public: using History::policy; using History::empty; using History::full; using History::addEntry; using History::trend; using History::averageAbsDiff; /// Creates an instances by setting an initial length DynamicLengthHistory(size_t Length) noexcept : Length(Length) { this->resize(Length); } /// Destroys \p this object. ~DynamicLengthHistory(void) override = default; /// Tells how many entries may be recorded by \c this object. /// /// \note The number of entries that are actually recorded may be smaller. /// /// \return \c rosa::agent::DynamicLengthHistory::N size_t maxLength(void) const noexcept override { return Length; } /// Tells how many entries are currently recorded by \p this object. /// /// \return number of entries currently recorded by \p this object. /// /// \post The returned value cannot be larger than the capacity of \p this /// object:\code /// 0 <= numberOfEntries() && numberOfEntries <= /// lengthOfHistory() \endcode - size_t numberOfEntries(void) const noexcept { return size(); } + size_t numberOfEntries(void) const noexcept override { return size(); } /// Gives a constant lvalue reference to an entry stored in \p this object. /// /// \note The recorded entries are indexed starting from the latest one. /// /// \param I the index at which the stored entry to take from /// /// \pre \p I is a valid index:\code /// 0 <= I && I < numberOfEntries() /// \endcode const T &entry(const size_t I = 0) const noexcept override { ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. return this->operator[](size() - I - 1); } /// Removes all entries recorded in \p this object. void clear() noexcept override { erase(begin(), end()); } + /// Sort all entries in ascending order. + void sortAscending(void) noexcept { std::sort(begin(), end()); } + + /// Sort all entries in descending order. + void sortDescending(void) noexcept { std::sort(rbegin(), rend()); } + + /// Delets one element of the history. + /// + /// \param the element which shall be deleted. + // @benedikt: is this ok like that? should there be some "error handling"? + // checking if V is not null, or if V is member of the vector? + void deleteEntry(T &V) { erase(std::find(begin(), end(), V)); } + + /// Gives back the lowest entry of the history. + /// + /// \return the lowest entry. In case of an empty history, the maximum value + /// of the chosen data type is returned. + //@benedikt: please check if it is right + T lowestEntry() { + auto it = std::min_element(begin(), end()); + + if (it == end()) { + return std::numeric_limits::max(); + } else { + return *it; + } + } + + /// Gives back the highest entry of the history. + /// + /// \return the highest entry. In case of an empty history, the minimum value + /// of the chosen data type is returned. + //@benedikt: please check if it is right + T highestEntry() { + auto it = std::max_element(begin(), end()); + + if (it == end()) { + return std::numeric_limits::min(); + } else { + return *it; + } + } + private: /// Pushes a new entry into the circular buffer. /// /// \note The earliest entry gets overwritten if the buffer is full. /// /// \param V value to push into the buffer void pushBack(const T &V) noexcept override { if (full()) { erase(begin()); } push_back(V); } /// Replaces the most recent entry in the history. /// /// \param V value to replace the most current value with void replaceFront(const T &V) noexcept override { (void)pop_back(); push_back(V); } public: /// Resizes the History length. If the new length is smaller than the number /// of currently stored values, values are deleted according to the /// HistoryPolicy. /// /// @param NewLength The new Length of the History. void setLength(size_t NewLength) noexcept { Length = NewLength; if (NewLength < numberOfEntries()) { switch (P) { default: ROSA_CRITICAL("unkown HistoryPolicy"); case HistoryPolicy::LIFO: case HistoryPolicy::SRWF: // Delete last numberOfEntries() - NewLength items from the back erase(begin() + NewLength, end()); break; case HistoryPolicy::FIFO: // Delete last numberOfEntries() - NewLength items from the front erase(begin(), begin() + (numberOfEntries() - NewLength)); break; } } this->resize(Length); } }; /// Adds a new entry to a \c rosa::agent::DynamicLengthHistory instance. /// /// \note The result of \c rosa::agent::DynamicLengthHistory::addEntry is /// ignored. /// /// \tparam T type of values stored in \p H /// \tparam P retention policy followed by \p H when capacity is reached /// /// \param H to add a new entry to /// \param V value to add to \p H /// /// \return \p H after adding \p V to it template DynamicLengthHistory &operator<<(DynamicLengthHistory &H, const T &V) noexcept { H.addEntry(V); return H; } } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_HISTORY_HPP diff --git a/include/rosa/agent/SignalState.hpp b/include/rosa/agent/SignalState.hpp new file mode 100644 index 0000000..99b135b --- /dev/null +++ b/include/rosa/agent/SignalState.hpp @@ -0,0 +1,472 @@ +//===-- rosa/agent/SignalState.hpp ------------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SignalState.hpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Definition of *signal state* *functionality*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_AGENT_SIGNALSTATE_HPP +#define ROSA_AGENT_SIGNALSTATE_HPP + +#include "rosa/agent/FunctionAbstractions.hpp" +#include "rosa/agent/Functionality.h" +#include "rosa/agent/History.hpp" +#include "rosa/support/math.hpp" + +#include + +namespace rosa { +namespace agent { + +/// Signal state conditions defining how the condition of a \c +/// rosa::agent::SignalState is saved in \c rosa::agent::SignalStateInformation. +enum class SignalStateCondition { + STABLE, ///< The signal state is stable + DRIFTING, ///< The signal state is drifting + UNKNOWN ///< The signal state is unknown +}; + +template struct SignalStateInformation { + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), + "confidence type is not to arithmetic"); + + /// The signal state ID saved as an unsigned integer number + unsigned int SignalStateID; + /// The SignalStateConfidence shows the overall confidence value of the signal + /// state. + CONFDATATYPE SignalStateConfidence; + /// The SignalStateCondition shows the condition of a signal state (stable or + /// drifting) + SignalStateCondition SignalStateCondition; + /// The SignalStateIsValid saves the number of samples which have been + /// inserted into the state after entering it. + unsigned int NumberOfInsertedSamplesAfterEntrance; + /// The SignalStateIsValid shows whether a signal state is valid or invalid. + /// In this context, valid means that enough samples which are in close + /// proximitry have been inserted into the signal state. + bool SignalStateIsValid; + /// The SignalStateJustGotValid shows whether a signal state got valid + /// (toggled from invalid to valid) during the current inserted sample. + bool SignalStateJustGotValid; + /// The SignalStateIsValidAfterReentrance shows whether a signal state is + /// valid after the variable changed back to it again. + bool SignalStateIsValidAfterReentrance; + /// The SignalIsStableNotDrifting shows whether a signa is stable and not + /// drifting. + bool SignalIsStable; +}; + +/// \tparam INDATATYPE type of input data, \tparam CONFDATATYPE type of +/// data in that the confidence values are given, \tparam PROCDATATYPE type of +/// the relative distance and the type of data in which DABs are saved. +template +class SignalState : public Functionality { + + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), + "input data type not arithmetic"); + STATIC_ASSERT((std::is_arithmetic::value), + "confidence data type is not to arithmetic"); + STATIC_ASSERT( + (std::is_arithmetic::value), + "process data type (DAB and Relative Distance) is not to arithmetic"); + +private: + // For the convinience to write a shorter data type name + using PartFuncPointer = + std::shared_ptr>; + // @Benedikt: are INDATATYPE, CONFDATATYPE right here? + using StepFuncPointer = + std::shared_ptr>; + + /// SignalStateInfo is a struct SignalStateInformation that contains + /// information about the current state. + SignalStateInformation SignalStateInfo; + + /// The FuzzyFunctionSampleMatches is the fuzzy function that gives the + /// confidence how good the new sample matches another sample in the sample + /// history. + PartFuncPointer FuzzyFunctionSampleMatches; + + /// The FuzzyFunctionSampleMismatches is the fuzzy function that gives the + /// confidence how bad the new sample matches another sample in the sample + /// history. + PartFuncPointer FuzzyFunctionSampleMismatches; + + /// The FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the + /// confidence how many samples from the sampe history match the new sample. + StepFuncPointer FuzzyFunctionNumOfSamplesMatches; + + /// The FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives + /// the confidence how many samples from the sampe history mismatch the new + /// sample. + StepFuncPointer FuzzyFunctionNumOfSamplesMismatches; + + /// The FuzzyFunctionSignalIsDrifting is the fuzzy function that gives the + /// confidence how likely it is that the signal (resp. the state of a signal) + /// is drifting. + PartFuncPointer FuzzyFunctionSignalIsDrifting; + + /// The FuzzyFunctionSignalIsStable is the fuzzy function that gives the + /// confidence how likely it is that the signal (resp. the state of a signal) + /// is stable (not drifting). + PartFuncPointer FuzzyFunctionSignalIsStable; + + /// SampleHistory is a history in that the last sample values are stored. + DynamicLengthHistory SampleHistory; + /// DAB is a (usually) small history of the last sample values of which a + /// average is calculated if the DAB is full. + DynamicLengthHistory DAB; + /// DABHistory is a history in that the last DABs (to be exact, the averages + /// of the last DABs) are stored. + DynamicLengthHistory DABHistory; + + /// LowestConfidenceMatchingHistory is a history in that the lowest confidence + /// for the current sample matches all history samples are saved. + DynamicLengthHistory + LowestConfidenceMatchingHistory; + /// HighestConfidenceMatchingHistory is a history in that the highest + /// confidence for the current sample matches all history samples are saved. + DynamicLengthHistory + HighestConfidenceMismatchingHistory; + +public: + // @Maxi doxygen per default doesn't display private attributes of a class. So + // I copied them to the constructor. So the user has more information. + /// Creates an instance by setting all parameters + /// \param SignalStateID The Id of the SignalStateinfo \c + /// SignalStateInformation. + /// + /// \param FuzzyFunctionSampleMatches The FuzzyFunctionSampleMatches is the + /// fuzzy function that gives the confidence how good the new sample matches + /// another sample in the sample history. + /// + /// \param FuzzyFunctionSampleMismatches The FuzzyFunctionSampleMismatches is + /// the fuzzy function that gives the confidence how bad the new sample + /// matches another sample in the sample history. + /// + /// \param FuzzyFunctionNumOfSamplesMatches The + /// FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the + /// confidence how many samples from the sampe history match the new sample. + /// + /// \param FuzzyFunctionNumOfSamplesMismatches The + /// FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives the + /// confidence how many samples from the sampe history mismatch the new + /// sample. + /// + /// \param FuzzyFunctionSignalIsDrifting The FuzzyFunctionSignalIsDrifting is + /// the fuzzy function that gives the confidence how likely it is that the + /// signal (resp. the state of a signal) is drifting. + /// + /// \param FuzzyFunctionSignalIsStable The FuzzyFunctionSignalIsStable is the + /// fuzzy function that gives the confidence how likely it is that the signal + /// (resp. the state of a signal) is stable (not drifting). + /// + /// \param SampleHistorySize Size of the Sample History \c + /// DynamicLengthHistory . SampleHistory is a history in that the last sample + /// values are stored. + /// + /// \param DABSize Size of DAB \c DynamicLengthHistory . DAB is a (usually) + /// small history of the last sample values of which a average is calculated + /// if the DAB is full. + /// + /// \param DABHistorySize Size of the DABHistory \c DynamicLengthHistory . + /// DABHistory is a history in that the last DABs (to be exact, the averages + /// of the last DABs) are stored. + /// + SignalState(unsigned int SignalStateID, unsigned int SampleHistorySize, + unsigned int DABSize, unsigned int DABHistorySize, + PartFuncPointer FuzzyFunctionSampleMatches, + PartFuncPointer FuzzyFunctionSampleMismatches, + StepFuncPointer FuzzyFunctionNumOfSamplesMatches, + StepFuncPointer FuzzyFunctionNumOfSamplesMismatches, + PartFuncPointer FuzzyFunctionSignalIsDrifting, + PartFuncPointer FuzzyFunctionSignalIsStable) noexcept + : SignalStateInfo{SignalStateID, 0, SignalStateCondition::UNKNOWN, 0, + false, false, + false, //@maxi added the Signal is stable bool + true}, + + FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches), + FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches), + FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches), + FuzzyFunctionNumOfSamplesMismatches( + FuzzyFunctionNumOfSamplesMismatches), + FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting), + FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable), + SampleHistory(SampleHistorySize), DAB(DABSize), + DABHistory(DABHistorySize), + LowestConfidenceMatchingHistory(SampleHistorySize), + HighestConfidenceMismatchingHistory(SampleHistorySize) {} + + /// Destroys \p this object. + ~SignalState(void) = default; + + void leaveSignalState(void) noexcept { + DAB.clear(); + SignalStateInfo.NumberOfInsertedSamplesAfterEntrance = 0; + SignalStateInfo.SignalStateIsValidAfterReentrance = false; + } + + SignalStateInformation + insertSample(INDATATYPE Sample) noexcept { + + validateSignalState(Sample); + + SampleHistory.addEntry(Sample); + + DAB.addEntry(Sample); + if (DAB.full()) { + PROCDATATYPE AvgOfDAB = DAB.template average(); + DABHistory.addEntry(AvgOfDAB); + DAB.clear(); + } + + //@Benedikt: Do I really have to cast here? + FuzzyFunctionNumOfSamplesMatches->setRightLimit( + static_cast(SampleHistory.numberOfEntries())); + FuzzyFunctionNumOfSamplesMismatches->setRightLimit( + static_cast(SampleHistory.numberOfEntries())); + + checkSignalStability(); + + return SignalStateInfo; + } + + /// Gives the confidence how likely the new sample matches the signal state. + /// + /// \param Sample is the actual sample of the observed signal. + /// + /// \return the confidence of the new sample is matching the signal state. + CONFDATATYPE + confidenceSampleMatchesSignalState(INDATATYPE Sample) noexcept { + + CONFDATATYPE ConfidenceOfBestCase = 0; + + DynamicLengthHistory + RelativeDistanceHistory(SampleHistory.maxLength()); + + // calculate distances to all history samples + for (auto &HistorySample : SampleHistory) { + PROCDATATYPE RelativeDistance = + relativeDistance(Sample, HistorySample); + RelativeDistanceHistory.addEntry(RelativeDistance); + } + + // sort all calculated distances so that the lowest distance (will get the + // highest confidence) is at the beginning. + RelativeDistanceHistory.sortAscending(); + + CONFDATATYPE ConfidenceOfWorstFittingSample = 1; + + // Case 1 means that one (the best fitting) sample of the history is + // compared with the new sample. Case 2 means the two best history samples + // are compared with the new sample. And so on. + // TODO (future): to accelerate -> don't start with 1 start with some higher + // number because a low number (i guess lower than 5) will definetely lead + // to a low confidence. except the history is not full. + + for (unsigned int Case = 0; + Case < RelativeDistanceHistory.numberOfEntries(); Case++) { + + CONFDATATYPE ConfidenceFromRelativeDistance; + + if (std::isinf(RelativeDistanceHistory[Case])) { + // TODO (future) if fuzzy is defined in a way that infinity is not 0 it + // would be a problem + //@benedikt: check if your partialfunctions can take infinity as + // argument + //@benedikt: same as before "->operator()" + ConfidenceFromRelativeDistance = 0; + } else { + ConfidenceFromRelativeDistance = FuzzyFunctionSampleMatches->operator()( + RelativeDistanceHistory[Case]); + } + + ConfidenceOfWorstFittingSample = fuzzyAND( + 2, ConfidenceOfWorstFittingSample, ConfidenceFromRelativeDistance); + //@benedikt: do i have to pass the number 2 to tell the function how many + // arguments are following? + //@benedikt: same as before with "->operator()" + ConfidenceOfBestCase = fuzzyOR( + 2, ConfidenceOfBestCase, + fuzzyAND(2, ConfidenceOfWorstFittingSample, + FuzzyFunctionNumOfSamplesMatches->operator()( + static_cast(Case) + 1))); + } + + return ConfidenceOfBestCase; + } + + /// Gives the confidence how likely the new sample mismatches the signal + /// state. + /// + /// \param Sample is the actual sample of the observed signal. + /// + /// \return the confidence of the new sample is mismatching the signal state. + CONFDATATYPE + confidenceSampleMismatchesSignalState(INDATATYPE Sample) noexcept { + + float ConfidenceOfWorstCase = 1; + + DynamicLengthHistory + RelativeDistanceHistory(SampleHistory.maxLength()); + + // calculate distances to all history samples + for (auto &HistorySample : SampleHistory) { + RelativeDistanceHistory.addEntry( + relativeDistance(Sample, HistorySample)); + } + + // sort all calculated distances so that the highest distance (will get the + // lowest confidence) is at the beginning. + RelativeDistanceHistory.sortDescending(); + + CONFDATATYPE ConfidenceOfBestFittingSample = 0; + + // Case 1 means that one (the worst fitting) sample of the history is + // compared with the new sample. Case 2 means the two worst history samples + // are compared with the new sample. And so on. + // TODO (future): to accelerate -> don't go until end. Confidences will only + // get higher. See comment in "CONFDATATYPE + // confidenceSampleMatchesSignalState(INDATATYPE Sample)". + for (unsigned int Case = 0; + Case < RelativeDistanceHistory.numberOfEntries(); Case++) { + + CONFDATATYPE ConfidenceFromRelativeDistance; + + if (std::isinf(RelativeDistanceHistory[Case])) { + ConfidenceFromRelativeDistance = 1; + } else { + //@benedikt: I had to change the following line. The outcommented line + // was the original one. I think it is ugly like that (new line). Do you + // have an idea how to make it better/more beautiful? + ConfidenceFromRelativeDistance = + FuzzyFunctionSampleMismatches->operator()( + RelativeDistanceHistory[Case]); + // FuzzyFunctionSampleMismatches(RelativeDistanceHistory[Case]); + } + + //@benedikt: do i have to pass the number 2 to tell the function how many + // arguments are following? + ConfidenceOfBestFittingSample = fuzzyOR( + 2, ConfidenceOfBestFittingSample, ConfidenceFromRelativeDistance); + + //@benedikt: do i have to pass the number 2 to tell the function how many + // arguments are following? + //@benedikt: same as before with "->operator()" + ConfidenceOfWorstCase = fuzzyAND( + 2, ConfidenceOfWorstCase, + fuzzyOR(2, ConfidenceOfBestFittingSample, + FuzzyFunctionNumOfSamplesMismatches->operator()( + static_cast(Case) + 1))); + } + + return ConfidenceOfWorstCase; + } + + /// Gives information about the current signal state. + /// + /// \return a struct SignalStateInformation that contains information about + /// the current signal state. + SignalStateInformation signalStateInformation(void) noexcept { + return SignalStateInfo; + } + +private: + void validateSignalState(INDATATYPE Sample) { + // TODO (future): WorstConfidenceDistance and BestConfidenceDistance could + // be set already in "CONFDATATYPE + // confidenceSampleMatchesSignalState(INDATATYPE Sample)" and "CONFDATATYPE + // confidenceSampleMismatchesSignalState(INDATATYPE Sample)" when the new + // sample is compared to all history samples. This would save a lot time + // because the comparisons are done only once. However, it has to be asured + // that the these two functions are called before the insertation, and the + // FuzzyFunctions for validation and matching have to be the same! + CONFDATATYPE LowestConfidenceMatching = 1; + CONFDATATYPE HighestConfidenceMismatching = 0; + for (auto &HistorySample : SampleHistory) { + // TODO (future): think about using different fuzzy functions for + // validation and matching. + //@benedikt: same with "->operator()" + LowestConfidenceMatching = + fuzzyAND(2, LowestConfidenceMatching, + FuzzyFunctionSampleMatches->operator()( + relativeDistance( + Sample, HistorySample))); + //@benedikt: same with "->operator()" + HighestConfidenceMismatching = + fuzzyOR(2, HighestConfidenceMismatching, + FuzzyFunctionSampleMismatches->operator()( + relativeDistance( + Sample, HistorySample))); + } + LowestConfidenceMatchingHistory.addEntry(LowestConfidenceMatching); + HighestConfidenceMismatchingHistory.addEntry(HighestConfidenceMismatching); + + LowestConfidenceMatching = LowestConfidenceMatchingHistory.lowestEntry(); + HighestConfidenceMismatching = + HighestConfidenceMismatchingHistory.highestEntry(); + + //@benedikt: same with "->operator()" + CONFDATATYPE ConfidenceSignalStateIsValid = fuzzyAND( + 2, LowestConfidenceMatching, + FuzzyFunctionNumOfSamplesMatches->operator()(static_cast( + SignalStateInfo.NumberOfInsertedSamplesAfterEntrance))); + //@benedikt: same with "->operator()" + CONFDATATYPE ConfidenceSignalStateIsInvalid = fuzzyOR( + 2, HighestConfidenceMismatching, + FuzzyFunctionNumOfSamplesMismatches->operator()(static_cast( + SignalStateInfo.NumberOfInsertedSamplesAfterEntrance))); + + if (ConfidenceSignalStateIsValid > ConfidenceSignalStateIsInvalid) { + if (SignalStateInfo.SignalStateIsValid) { + SignalStateInfo.SignalStateJustGotValid = false; + } else { + SignalStateInfo.SignalStateJustGotValid = true; + } + SignalStateInfo.SignalStateIsValid = true; + SignalStateInfo.SignalStateIsValidAfterReentrance = true; + } + } + + void checkSignalStability(void) { + CONFDATATYPE ConfidenceSignalIsStable; + CONFDATATYPE ConfidenceSignalIsDrifting; + + if (DABHistory.numberOfEntries() >= 2) { + //@benedikt: same "->operator()" + ConfidenceSignalIsStable = FuzzyFunctionSignalIsStable->operator()( + relativeDistance( + DABHistory[DABHistory.numberOfEntries() - 1], DABHistory[0])); + //@benedikt: same "->operator()" + ConfidenceSignalIsDrifting = FuzzyFunctionSignalIsDrifting->operator()( + relativeDistance( + DABHistory[DABHistory.numberOfEntries() - 1], DABHistory[0])); + } else { + // QUESTION: is it ok to say stable = 1 and drift = 0, when I simply don't + // know because the state is so new. Is there an option for saying don't + // know? + ConfidenceSignalIsStable = 1; + ConfidenceSignalIsDrifting = 0; + } + + SignalStateInfo.SignalIsStable = + ConfidenceSignalIsStable >= ConfidenceSignalIsDrifting; + } +}; + +} // End namespace agent +} // End namespace rosa + +#endif // ROSA_AGENT_SIGNALSTATE_HPP diff --git a/include/rosa/agent/SignalStateDetector.hpp b/include/rosa/agent/SignalStateDetector.hpp new file mode 100644 index 0000000..b608dc0 --- /dev/null +++ b/include/rosa/agent/SignalStateDetector.hpp @@ -0,0 +1,285 @@ +//===-- rosa/agent/SignalStateDetector.hpp ----------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SignalStateDetector.hpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Definition of *signal state detector* *functionality*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_AGENT_SIGNALSTATEDETECTOR_HPP +#define ROSA_AGENT_SIGNALSTATEDETECTOR_HPP + +#include "rosa/agent/FunctionAbstractions.hpp" +#include "rosa/agent/Functionality.h" +#include "rosa/agent/History.hpp" +#include "rosa/agent/SignalState.hpp" + +#include + +namespace rosa { +namespace agent { + +/// Implements \c rosa::agent::SignalStateDetector as a functionality that +/// detects signal states given on input samples. +/// +/// \note This implementation is supposed to be used for samples of an +/// arithmetic type. +/// +/// \tparam INDATATYPE type of input data, \tparam CONFDATATYPE type of +/// data in that the confidence values are given, \tparam PROCDATATYPE type of +/// the relative distance and the type of data in which DABs are saved. +template +class SignalStateDetector : public Functionality { + + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), + "input data type not arithmetic"); + STATIC_ASSERT((std::is_arithmetic::value), + "confidence abstraction type is not to arithmetic"); + +private: + // For the convinience to write a shorter data type name + using PartFuncPointer = + std::shared_ptr>; + using StepFuncPointer = + std::shared_ptr>; + using SignalStatePtr = + std::shared_ptr>; + + /// The NextSignalStateID is a counter variable which stores the ID which the + /// next signal state shall have. + unsigned int NextSignalStateID; + + /// The SignalStateHasChanged is a flag that show whether a signal has changed + /// its state. + bool SignalStateHasChanged; + + /// The CurrentSignalState is a pointer to the (saved) signal state in which + /// the actual variable (signal) of the observed system is. + SignalStatePtr CurrentSignalState; + + /// The DetectedSignalStates is vector in that all detected signal states are + /// saved. + DynamicLengthHistory DetectedSignalStates; + + /// The FuzzyFunctionSampleMatches is the fuzzy function that gives the + /// confidence how good the new sample matches another sample in the sample + /// history. + PartFuncPointer FuzzyFunctionSampleMatches; + + /// The FuzzyFunctionSampleMismatches is the fuzzy function that gives the + /// confidence how bad the new sample matches another sample in the sample + /// history. + PartFuncPointer FuzzyFunctionSampleMismatches; + + /// The FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the + /// confidence how many samples from the sampe history match the new sample. + StepFuncPointer FuzzyFunctionNumOfSamplesMatches; + + /// The FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives + /// the confidence how many samples from the sampe history mismatch the new + /// sample. + StepFuncPointer FuzzyFunctionNumOfSamplesMismatches; + + /// The FuzzyFunctionSignalIsDrifting is the fuzzy function that gives the + /// confidence how likely it is that the signal is drifting. + PartFuncPointer FuzzyFunctionSignalIsDrifting; + + /// The FuzzyFunctionSignalIsStable is the fuzzy function that gives the + /// confidence how likely it is that the signal is stable (not drifting). + PartFuncPointer FuzzyFunctionSignalIsStable; + + /// SampleHistorySize is the (maximum) size of the sample history. + unsigned int SampleHistorySize; + /// DABSize the size of a DAB (Discrete Average Block). + unsigned int DABSize; + /// DABHistorySize is the (maximum) size of the DAB history. + unsigned int DABHistorySize; + +public: + /// Creates an instance by setting all parameters + /// \param FuzzyFunctionSampleMatches The FuzzyFunctionSampleMatches is the + /// fuzzy function that gives the confidence how good the new sample matches + /// another sample in the sample history. + /// + /// \param FuzzyFunctionSampleMismatches The FuzzyFunctionSampleMismatches is + /// the fuzzy function that gives the confidence how bad the new sample + /// matches another sample in the sample history. + /// + /// \param FuzzyFunctionNumOfSamplesMatches The + /// FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the + /// confidence how many samples from the sampe history match the new sample. + /// + /// \param FuzzyFunctionNumOfSamplesMismatches The + /// FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives the + /// confidence how many samples from the sampe history mismatch the new + /// sample. + /// + /// \param FuzzyFunctionSignalIsDrifting The FuzzyFunctionSignalIsDrifting is + /// the fuzzy function that gives the confidence how likely it is that the + /// signal (resp. the state of a signal) is drifting. + /// + /// \param FuzzyFunctionSignalIsStable The FuzzyFunctionSignalIsStable is the + /// fuzzy function that gives the confidence how likely it is that the signal + /// (resp. the state of a signal) is stable (not drifting). + /// + /// \param SampleHistorySize Sets the History size which will be used by \c + /// SignalState. + /// + /// \param DABSize Sets the DAB size which will be used by \c SignalState. + /// + /// \param DABHistorySize Sets the size which will be used by \c SignalState. + /// + SignalStateDetector(unsigned int MaximumNumberOfSignalStates, + PartFuncPointer FuzzyFunctionSampleMatches, + PartFuncPointer FuzzyFunctionSampleMismatches, + StepFuncPointer FuzzyFunctionNumOfSamplesMatches, + StepFuncPointer FuzzyFunctionNumOfSamplesMismatches, + PartFuncPointer FuzzyFunctionSignalIsDrifting, + PartFuncPointer FuzzyFunctionSignalIsStable, + unsigned int SampleHistorySize, unsigned int DABSize, + unsigned int DABHistorySize) noexcept + : NextSignalStateID(1), SignalStateHasChanged(false), + CurrentSignalState(NULL), + DetectedSignalStates(MaximumNumberOfSignalStates), + FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches), + FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches), + FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches), + FuzzyFunctionNumOfSamplesMismatches( + FuzzyFunctionNumOfSamplesMismatches), + FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting), + FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable), + SampleHistorySize(SampleHistorySize), DABSize(DABSize), + DABHistorySize(DABHistorySize) {} + + /// Destroys \p this object. + ~SignalStateDetector(void) = default; + + /// Detects the signal state to which the new sample belongs or create a new + /// signal state if the new sample does not match to any of the saved states. + /// + /// \param Sample is the actual sample of the observed signal. + /// + /// \return the information of the current signal state (signal state ID and + /// other parameters). + SignalStateInformation + detectSignalState(INDATATYPE Sample) noexcept { + + if (!CurrentSignalState) { + ASSERT(DetectedSignalStates.empty()); + + SignalStatePtr S = createNewSignalState(); + CurrentSignalState = S; + } else { + CONFDATATYPE ConfidenceSampleMatchesSignalState = + CurrentSignalState->confidenceSampleMatchesSignalState(Sample); + CONFDATATYPE ConfidenceSampleMismatchesSignalState = + CurrentSignalState->confidenceSampleMismatchesSignalState(Sample); + + if (ConfidenceSampleMatchesSignalState > + ConfidenceSampleMismatchesSignalState) { + SignalStateHasChanged = false; + } else { + SignalStateHasChanged = true; + + if (CurrentSignalState->signalStateInformation().SignalStateIsValid) { + CurrentSignalState->leaveSignalState(); + } else { + //@benedikt: changed from vector to history. can i still do the next + // line? + DetectedSignalStates.deleteEntry(CurrentSignalState); + } + + // TODO (future): additionally save averages to enable fast iteration + // through recorded signl state history (maybe sort vector based on + // these + // average values) + CurrentSignalState = nullptr; + + //@benedikt: same question + for (auto &SavedSignalState : DetectedSignalStates) { + if (SavedSignalState != CurrentSignalState) { + ConfidenceSampleMatchesSignalState = + SavedSignalState->confidenceSampleMatchesSignalState(Sample); + ConfidenceSampleMismatchesSignalState = + SavedSignalState->confidenceSampleMismatchesSignalState(Sample); + + if (ConfidenceSampleMatchesSignalState > + ConfidenceSampleMismatchesSignalState) { + // TODO (future): maybe it would be better to compare + // ConfidenceSampleMatchesSignalState of all signal states in the + // vector in order to find the best matching signal state. + CurrentSignalState = SavedSignalState; + break; + } + } + } + + if (!CurrentSignalState) { + SignalStatePtr S = createNewSignalState(); + CurrentSignalState = S; + } + } + } + + SignalStateInformation SignalStateInfo = + CurrentSignalState->insertSample(Sample); + + if (SignalStateInfo.SignalStateJustGotValid) { + NextSignalStateID++; + } + + return SignalStateInfo; + } + + /// Gives information about the current signal state. + /// + /// \return a struct SignalStateInformation that contains information about + /// the current signal state or NULL if no current signal state exists. + SignalStateInformation + currentSignalStateInformation(void) noexcept { + if (CurrentSignalState) { + return CurrentSignalState->signalStateInformation(); + } else { + return NULL; + } + } + + /// Gives information whether a signal state change has happened or not. + /// + /// \return true if a signal state change has happened, and false if not. + bool signalStateHasChanged(void) noexcept { return SignalStateHasChanged; } + +private: + /// Creates a new signal state and adds it to the signal state vector in which + /// all known states are saved. + /// + /// \return a pointer to the newly created signal state or NULL if no state + /// could be created. + SignalStatePtr createNewSignalState(void) noexcept { + SignalStatePtr S(new SignalState( + NextSignalStateID, SampleHistorySize, DABSize, DABHistorySize, + FuzzyFunctionSampleMatches, FuzzyFunctionSampleMismatches, + FuzzyFunctionNumOfSamplesMatches, FuzzyFunctionNumOfSamplesMismatches, + FuzzyFunctionSignalIsDrifting, FuzzyFunctionSignalIsStable)); + + // @benedikt: todo: assert in history, which checks if push_back worked + DetectedSignalStates.addEntry(S); + + return S; + } +}; + +} // End namespace agent +} // End namespace rosa + +#endif // ROSA_AGENT_SIGNALSTATEDETECTOR_HPP diff --git a/include/rosa/agent/SystemState.hpp b/include/rosa/agent/SystemState.hpp new file mode 100644 index 0000000..207e284 --- /dev/null +++ b/include/rosa/agent/SystemState.hpp @@ -0,0 +1,44 @@ +//===-- rosa/agent/SystemState.hpp ------------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SystemState.hpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Definition of *system state* *functionality*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_AGENT_SYSTEMSTATE_HPP +#define ROSA_AGENT_SYSTEMSTATE_HPP + +#include "rosa/agent/Functionality.h" + +namespace rosa { +namespace agent { + +/// TODO TEXT +template class SystemState : public Functionality { + + /* //TODO: STATIC_ASSERT +// Make sure the actual type arguments are matching our expectations. +STATIC_ASSERT(std::is_arithmetic::value, + "confidence abstraction type is not to arithmetic"); +*/ + +private: + unsigned int x; + +public: + SystemState() {} +}; + +} // End namespace agent +} // End namespace rosa + +#endif // ROSA_AGENT_SYSTEMSTATE_HPP diff --git a/include/rosa/agent/SystemStateDetector.hpp b/include/rosa/agent/SystemStateDetector.hpp new file mode 100644 index 0000000..c159991 --- /dev/null +++ b/include/rosa/agent/SystemStateDetector.hpp @@ -0,0 +1,51 @@ +//===-- rosa/agent/SystemStateDetector.hpp ----------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SystemStateDetector.hpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Definition of *system state detector* *functionality*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_AGENT_SYSTEMSTATEDETECTOR_HPP +#define ROSA_AGENT_SYSTEMSTATEDETECTOR_HPP + +#include "rosa/agent/Functionality.h" +#include "rosa/agent/History.hpp" +#include + +namespace rosa { +namespace agent { + +/// TODO TEXT +template +class SystemStateDetector : public Functionality { + + /* //TODO: STATIC_ASSERT +// Make sure the actual type arguments are matching our expectations. +STATIC_ASSERT(std::is_arithmetic::value, + "confidence abstraction type is not to arithmetic"); +*/ + +private: + unsigned int NextSignalStateID; + + bool SystemStateHasChanged; + + // DynamicLengthHistory DetectedSignalStates; + +public: + SystemStateDetector() : NextSignalStateID(1) {} +}; + +} // End namespace agent +} // End namespace rosa + +#endif // ROSA_AGENT_SYSTEMSTATEDETECTOR_HPP diff --git a/include/rosa/support/math.hpp b/include/rosa/support/math.hpp index f88a13d..a356c93 100644 --- a/include/rosa/support/math.hpp +++ b/include/rosa/support/math.hpp @@ -1,57 +1,201 @@ //===-- rosa/support/math.hpp -----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/math.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Math helpers. /// //===----------------------------------------------------------------------===// +// !!!!!! Please check lines 60 - 180 forward !!!!!!!!!!!!!! + #ifndef ROSA_SUPPORT_MATH_HPP #define ROSA_SUPPORT_MATH_HPP +#include "debug.hpp" +#include +#include #include +#include #include #include #include namespace rosa { /// Computes log base 2 of a number. /// /// \param N the number to compute log base 2 for /// /// \return log base 2 of \p N constexpr size_t log2(const size_t N) { return ((N < 2) ? 1 : 1 + log2(N / 2)); } /// Tells the next representable floating point value. /// /// \tparam T type to operate on /// /// \note The second type argument enforces \p T being a floating point type, /// always use the default value! /// /// \param V value to which find the next representable one /// /// \return the next representable value of type \p T after value \p V /// /// \pre Type \p T must be a floating point type, which is enforced by /// `std::enable_if` in the second type argument. template ::value>> T nextRepresentableFloatingPoint(const T V) { return std::nextafter(V, std::numeric_limits::infinity()); } +#if false //can't compile original +// copied from the internet and adapted +// +(https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c) +/// Conjuncts two or more values with each other. +/// +/// \param two or more values of the same datatype +/// +/// \return the conjunction of the values given as parameter. +template +CONFDATATYPE fuzzyAND(int n_args, ...) noexcept { + // TODO: check datatype, if there are at least two arguments, and if they are + // between 0 and 1 + // David suggests: nstead of a variadic argument, you could pass the values as + // an std::array (with a template argument for the length). When you pass the + // values as a container, you can simply use std::max_element and + // std::min_element to have a one-liner implementation of the these fuzzy + // functions. + va_list ap; + va_start(ap, n_args); + CONFDATATYPE min = va_arg(ap, CONFDATATYPE); + for (int i = 2; i <= n_args; i++) { + CONFDATATYPE a = va_arg(ap, CONFDATATYPE); + min = std::min(a, min); + } + va_end(ap); + return min; +} +#else + +template +CONFDATATYPE fuzzyAND(std::array Data) noexcept { + STATIC_ASSERT(std::is_arithmetic::value, + "Type of FuzzyAnd is not arithmetic"); + STATIC_ASSERT(size > 1, "Number of Arguments is to little"); + for (auto tmp : Data) + ASSERT(tmp <= 1 && tmp >= 0); + return *std::min_element(Data.begin(), Data.end()); +} + +#if false // safer +template +std::enable_if_t< + std::conjunction_v...>, + CONFDATATYPE> +fuzzyAND(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept { + return fuzzyAND( + std::array{Data, Datan...}); +} +#else +template +CONFDATATYPE fuzzyAND(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept { + return fuzzyAND( + std::array{Data, Datan...}); +} +#endif + +#endif + + +#if false //can't compile original +/// Disjuncts two or more values with each other. +/// +/// +/// +/// \return the disjunction of the values given as parameter. +// copied from the internet +// (https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c) +template +CONFDATATYPE fuzzyOR(int n_args, ...) noexcept { + // TODO: check datatype and if they are between 0 and 1 + // David suggests: nstead of a variadic argument, you could pass the values as + // an std::array (with a template argument for the length). When you pass the + // values as a container, you can simply use std::max_element and + // std::min_element to have a one-liner implementation of the these fuzzy + // functions. + va_list ap; + va_start(ap, n_args); + CONFDATATYPE max = va_arg(ap, CONFDATATYPE); + for (int i = 2; i <= n_args; i++) { + CONFDATATYPE a = va_arg(ap, CONFDATATYPE); + max = std::max(a, max); + } + va_end(ap); + return max; +} +#else + + +template +CONFDATATYPE fuzzyOR(std::array Data) noexcept { + STATIC_ASSERT(std::is_arithmetic::value, + "Type of FuzzyAnd is not arithmetic"); + STATIC_ASSERT(size > 1, "Number of Arguments is to little"); + for (auto tmp : Data) + ASSERT(tmp <= 1 && tmp >= 0); + return *std::max_element(Data.begin(), Data.end()); +} + +#if false // safer +template +std::enable_if_t< + std::conjunction_v...>, + CONFDATATYPE> +fuzzyOR(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept { + return fuzzyOR( + std::array{Data, Datan...}); +} +#else +template +CONFDATATYPE fuzzyOR(CONFDATATYPE Data, _CONFDATATYPE... Datan) noexcept { + return fuzzyOR( + std::array{Data, Datan...}); +} +#endif + + +#endif + +template +PROCDATATYPE relativeDistance(INDATATYPE NewValue, + INDATATYPE HistoryValue) noexcept { + PROCDATATYPE Dist = HistoryValue - NewValue; + + if (Dist == 0) { + return 0; + } else { + Dist = Dist / NewValue; + if (Dist < 0) { + // TODO: I guess this multiplication here should not be done because + // it could be that the distance fuzzy functions are not symetrical + //(negative and positive side) + Dist = Dist * (-1); + } + return (Dist); + } +} + } // End namespace rosa #endif // ROSA_SUPPORT_MATH_HPP diff --git a/lib/agent/CMakeLists.txt b/lib/agent/CMakeLists.txt index 7edb556..4e70aa5 100644 --- a/lib/agent/CMakeLists.txt +++ b/lib/agent/CMakeLists.txt @@ -1,18 +1,26 @@ set(LIB_INCLUDE_DIR ${ROSA_MAIN_INCLUDE_DIR}/rosa/agent) add_library(ROSAAgent ${LIB_INCLUDE_DIR}/namespace.h namespace.cpp ${LIB_INCLUDE_DIR}/Functionality.h Functionality.cpp ${LIB_INCLUDE_DIR}/Abstraction.hpp Abstraction.cpp ${LIB_INCLUDE_DIR}/FunctionAbstractions.hpp FunctionAbstractions.cpp ${LIB_INCLUDE_DIR}/RangeConfidence.hpp RangeConfidence.cpp ${LIB_INCLUDE_DIR}/History.hpp History.cpp ${LIB_INCLUDE_DIR}/Confidence.hpp Confidence.cpp + ${LIB_INCLUDE_DIR}/SignalState.hpp + SignalState.cpp + ${LIB_INCLUDE_DIR}/SignalStateDetector.hpp + SignalStateDetector.cpp + ${LIB_INCLUDE_DIR}/SystemState.hpp + SystemState.cpp + ${LIB_INCLUDE_DIR}/SystemStateDetector.hpp + SystemStateDetector.cpp ) diff --git a/lib/agent/SignalState.cpp b/lib/agent/SignalState.cpp new file mode 100644 index 0000000..ec6b7bd --- /dev/null +++ b/lib/agent/SignalState.cpp @@ -0,0 +1,20 @@ +//===-- rosa/agent/SignalState.cpp ----------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SignalState.cpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Implementation for rosa/agent/SignalState.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/agent/SignalState.hpp. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/agent/SignalState.hpp" diff --git a/lib/agent/SignalStateDetector.cpp b/lib/agent/SignalStateDetector.cpp new file mode 100644 index 0000000..3f45910 --- /dev/null +++ b/lib/agent/SignalStateDetector.cpp @@ -0,0 +1,20 @@ +//===-- rosa/agent/SignalStateDetector.cpp ----------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SignalStateDetector.cpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Implementation for rosa/agent/SignalStateDetector.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/agent/SignalStateDetector.hpp. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/agent/SignalStateDetector.hpp" diff --git a/lib/agent/SystemState.cpp b/lib/agent/SystemState.cpp new file mode 100644 index 0000000..86662c2 --- /dev/null +++ b/lib/agent/SystemState.cpp @@ -0,0 +1,20 @@ +//===-- rosa/agent/SystemState.cpp ----------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SystemState.cpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Implementation for rosa/agent/SystemState.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/agent/SystemState.hpp. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/agent/SystemState.hpp" diff --git a/lib/agent/SystemStateDetector.cpp b/lib/agent/SystemStateDetector.cpp new file mode 100644 index 0000000..e5ec9d5 --- /dev/null +++ b/lib/agent/SystemStateDetector.cpp @@ -0,0 +1,20 @@ +//===-- rosa/agent/SystemStateDetector.cpp ----------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/SystemStateDetector.cpp +/// +/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Implementation for rosa/agent/SystemStateDetector.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/agent/SystemStateDetector.hpp. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/agent/SystemStateDetector.hpp"