diff --git a/apps/ccam/ccam.cpp b/apps/ccam/ccam.cpp index 1331eb4..19cf9f9 100644 --- a/apps/ccam/ccam.cpp +++ b/apps/ccam/ccam.cpp @@ -1,36 +1,38 @@ //===-- apps/ccam/ccam.cpp --------------------------------------*- C++ -*-===// // // The RoSA Framework -- Application CCAM // //===----------------------------------------------------------------------===// /// /// \file apps/ccam/ccam.cpp /// /// \author Maximilian Goetzinger (maximilian.goetzinger@tuwien.ac.at) /// /// \date 2019 /// /// \brief The application CCAM implements the case study from the paper: /// M. Goetzinger, N. TaheriNejad, H. A. Kholerdi, A. Jantsch, E. Willegger, /// T. Glatzl, A.M. Rahmani, T.Sauter, P. Liljeberg: Model - Free Condition /// Monitoring with Confidence //===----------------------------------------------------------------------===// #include "rosa/agent/SignalStateDetector.hpp" #include int main(void) { // Just some tests :D std::vector vec = {7, 3, 5, 1, 9}; std::sort(vec.rbegin(), vec.rend()); // std::reverse(vec.begin(), vec.end()); for (auto it = vec.cbegin(); it != vec.cend(); ++it) { std::cout << *it << ' '; } + // TODO: make signal state detector object + return 0; } diff --git a/include/rosa/agent/History.hpp b/include/rosa/agent/History.hpp index 274c99b..2426f9b 100644 --- a/include/rosa/agent/History.hpp +++ b/include/rosa/agent/History.hpp @@ -1,536 +1,536 @@ //===-- 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 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 { // 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(); } /// 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()); } - // TODO: erklären + /// Sort all entries in ascending order. void sortAscending(void) noexcept { std::sort(begin(), end()); } - // TODO: erklären + /// Sort all entries in descending order. void sortDescending(void) noexcept { std::sort(rbegin(), rend()); } 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/State.hpp b/include/rosa/agent/SignalState.hpp similarity index 88% rename from include/rosa/agent/State.hpp rename to include/rosa/agent/SignalState.hpp index 2afd52f..85b8fff 100644 --- a/include/rosa/agent/State.hpp +++ b/include/rosa/agent/SignalState.hpp @@ -1,370 +1,370 @@ //===-- rosa/agent/State.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/State.hpp /// /// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) /// /// \date 2019 /// /// \brief Definition of *state* *functionality*. /// //===----------------------------------------------------------------------===// +// TODO: change to signal state + #ifndef ROSA_AGENT_STATE_HPP #define ROSA_AGENT_STATE_HPP #include "rosa/agent/FunctionAbstractions.hpp" #include "rosa/agent/Functionality.h" #include "rosa/agent/History.hpp" #include namespace rosa { namespace agent { /// State conditions defining how the condition of a \c rosa::agent::State is /// saved in \c rosa::agent::StateInformation. enum class VariableStateCondition { STABLE, ///< The state is stable DRIFTING, ///< The state is drifting UNKNOWN ///< The state is unknown }; template struct StateInformation { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "confidence type is not to arithmetic"); /// The state ID saved as an unsigned integer number unsigned int StateID; /// The StateConfidence shows the overall confidence value of the state. CONFDATATYPE StateConfidence; /// The VariableStateCondition shows the condition of a state (stable or /// drifting) VariableStateCondition VariableStateCondition; /// The StateIsValid shows whether a state is valid or invalid. In this /// context, valid means that enough samples which are in close proximitry /// have been inserted into the state. bool StateIsValid; /// The StateJustGotValid shows whether a state got valid (toggled from /// invalid to valid) during the current inserted sample. bool StateJustGotValid; /// The StateIsValidAfterReentrance shows whether a state is valid after the /// variable changed back to it again. bool StateIsValidAfterReentrance; }; // @Benedikt: now there are 4 datatypes. Do you think we can merge PROCDATATYPE // and PROCDATATYPE somehow? /// \tparam INDATATYPE type of input data, \tparam CONFDATATYPE type of -/// data in that the confidence values are given, \param PROCDATATYPEtype of +/// data in that the confidence values are given, \param PROCDATATYPE type of /// the relative distance and the type of data in which DABs are saved. template class State : 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), - "DAB storage type is not to arithmetic"); + "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>; using StepFuncPointer = std::shared_ptr>; - using StateInfoPtr = std::shared_ptr>; /// StateInfo is a struct StateInformation that contains information about the /// current state. - StateInfoPtr StateInfo; + StateInformation StateInfo; /// 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 FuzzyFunctionSampleMatches is the fuzzy function that gives the /// confidence how bad the new sample matches another sample in the sample /// history. PartFuncPointer FuzzyFunctionSampleMismatches; /// The FuzzyFunctionSampleMatches is the fuzzy function that gives the /// confidence how many samples from the sampe history match the new sample. StepFuncPointer FuzzyFunctionNumOfSamplesMatches; /// The FuzzyFunctionSampleMatches 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; /// The StateIsValid shows whether a state is valid or invalid. In this /// context, valid means that enough samples which are in close proximitry /// have been inserted into the state. bool StateIsValid; /// The StateIsValidAfterReentrance shows whether a state is valid after the /// variable changed back to it again. bool StateIsValidAfterReentrance; public: /// Creates an instance by setting all parameters State(unsigned int StateID, PartFuncPointer FuzzyFunctionSampleMatches, PartFuncPointer FuzzyFunctionSampleMismatches, StepFuncPointer FuzzyFunctionNumOfSamplesMatches, StepFuncPointer FuzzyFunctionNumOfSamplesMismatches, PartFuncPointer FuzzyFunctionSignalIsDrifting, PartFuncPointer FuzzyFunctionSignalIsStable, unsigned int sampleHistorySize, unsigned int DABSize, unsigned int DABHistorySize) noexcept : StateInfo(StateID, 0, VariableStateCondition::UNKNOWN, false, false), SampleHistory(sampleHistorySize), DAB(DABSize), DABHistory(DABHistorySize), FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches), FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches), FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches), FuzzyFunctionNumOfSamplesMismatches( FuzzyFunctionNumOfSamplesMismatches), FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting), FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable) {} /// Destroys \p this object. ~State(void) = default; - void leaveState(void) { + void leaveState(void) noexcept { DAB.clear(); StateIsValidAfterReentrance = false; } - void insertSample(INDATATYPE Sample) { + StateInformation insertSample(INDATATYPE Sample) noexcept { SampleHistory.addEntry(Sample); DAB.addEntry(Sample); if (DAB.full()) { PROCDATATYPE AvgOfDAB = DAB.template average(); DABHistory.addEntry(AvgOfDAB); DAB.clear(); } FuzzyFunctionNumOfSamplesMatches->setRightLimit( SampleHistory->numberOfEntries()); FuzzyFunctionNumOfSamplesMismatches->setRightLimit( SampleHistory->numberOfEntries()); // TODO: calculate whether state is valid and properly set StateIsValid, // StateJustGotValid, StateIsValidAfterReentrance // TODO: check actual state whether it drifts // TODO: write in StateInfo + + return StateInfo; } /// Gives the confidence how likely the new sample matches the state. /// /// \param Sample is the actual sample of the observed signal. /// /// \return the confidence of the new sample is matching the state. CONFDATATYPE - confSampleMatchesState(INDATATYPE Sample) { + confSampleMatchesState(INDATATYPE Sample) noexcept { CONFDATATYPE ConfidenceOfBestCase = 0; - // TODO: history - std::vector RelativeDistanceHistory; + DynamicLengthHistory + RelativeDistanceHistory; // calculate distances to all history samples for (auto &HistorySample : SampleHistory) { PROCDATATYPE RelativeDistance = relativeDistance(Sample, HistorySample); - RelativeDistanceHistory.push_back(RelativeDistance); + RelativeDistanceHistory.addEntry(RelativeDistance); } // sort all calculated distances so that the lowest distance (will get the // highest confidence) is at the beginning. - sort(RelativeDistanceHistory.begin(), RelativeDistanceHistory.end()); + RelativeDistanceHistory.sortAscending(); CONFDATATYPE ConfidenceOfWorstFittingSample = 1; - unsigned int Case = 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. - // TODO: history - // TODO: iterate thorugh case and use [] - // TODO: - for (auto RelativeDistance = RelativeDistanceHistory.cbegin(); - RelativeDistance != RelativeDistanceHistory.cend(); - RelativeDistance++, Case++) { + for (unsigned int Case = 0; + Case < RelativeDistanceHistory.numberOfEntries(); Case++) { CONFDATATYPE ConfidenceFromRelativeDistance; - // CHECK if I should use * for RelativeDistance - if (std::isinf(RelativeDistance)) { + 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 ConfidenceFromRelativeDistance = 0; } else { ConfidenceFromRelativeDistance = - FuzzyFunctionSampleMatches(RelativeDistance); + FuzzyFunctionSampleMatches(RelativeDistanceHistory[Case]); } ConfidenceOfWorstFittingSample = fuzzyAND(ConfidenceOfWorstFittingSample, ConfidenceFromRelativeDistance); // @benedikt: change old-style cast to one of these: reinterpret_cast, // static_cast, dynamic_cast or const_cast. Which should I use? Or should // the HistSampleCounter variable already be CONFDATATYPE type? ConfidenceOfBestCase = fuzzyOR( ConfidenceOfBestCase, fuzzyAND(ConfidenceOfWorstFittingSample, - FuzzyFunctionNumOfSamplesMatches((CONFDATATYPE)Case))); + FuzzyFunctionNumOfSamplesMatches((CONFDATATYPE)Case + 1))); } return ConfidenceOfBestCase; } /// Gives the confidence how likely the new sample mismatches the state. /// /// \param Sample is the actual sample of the observed signal. /// /// \return the confidence of the new sample is mismatching the state. CONFDATATYPE - confSampleMismatchesState(INDATATYPE Sample) { + confSampleMismatchesState(INDATATYPE Sample) noexcept { float ConfidenceOfWorstCase = 1; - // TODO: history! - std::vector RelativeDistanceHistory; + DynamicLengthHistory + RelativeDistanceHistory; // calculate distances to all history samples for (auto &HistorySample : SampleHistory) { - RelativeDistanceHistory.push_back( - relativeDistance(Sample, HistorySample)); + RelativeDistanceHistory.addEntry(relativeDistance(Sample, HistorySample)); } // sort all calculated distances so that the highest distance (will get the // lowest confidence) is at the beginning. - sort(RelativeDistanceHistory.rbegin(), RelativeDistanceHistory.rend()); + RelativeDistanceHistory.sortDescending(); CONFDATATYPE ConfidenceOfBestFittingSample = 0; unsigned int Case = 1; // 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 // confSampleMatchesState(INDATATYPE Sample)". - for (unsigned int RelativeDistance = RelativeDistanceHistory.cbegin(); - RelativeDistance != RelativeDistanceHistory.cend(); - RelativeDistance++, Case++) { + for (unsigned int Case = 0; + Case < RelativeDistanceHistory.numberOfEntries(); Case++) { CONFDATATYPE ConfidenceFromRelativeDistance; - if (std::isinf(RelativeDistance)) { + if (std::isinf(RelativeDistanceHistory[Case])) { ConfidenceFromRelativeDistance = 1; } else { ConfidenceFromRelativeDistance = - FuzzyFunctionSampleMismatches(RelativeDistance); + FuzzyFunctionSampleMismatches(RelativeDistanceHistory[Case]); } ConfidenceOfBestFittingSample = fuzzyOR(ConfidenceOfBestFittingSample, ConfidenceFromRelativeDistance); // @benedikt: change old-style cast to one of these: reinterpret_cast, // static_cast, dynamic_cast or const_cast. Which should I use? Or should // the HistSampleCounter variable already be CONFDATATYPE type? ConfidenceOfWorstCase = fuzzyAND( ConfidenceOfWorstCase, fuzzyOR(ConfidenceOfBestFittingSample, - FuzzyFunctionNumOfSamplesMismatches((CONFDATATYPE)Case))); + FuzzyFunctionNumOfSamplesMismatches((CONFDATATYPE)Case + 1))); } return ConfidenceOfWorstCase; } /// Gives information about the current state. /// /// \return a struct StateInformation that contains information about the /// current state. - StateInfoPtr stateInformation(void) { return StateInfo; } + StateInformation stateInformation(void) noexcept { + return StateInfo; + } private: // @David: Where should these next functions (fuzzyAND, fuzzyOR, // relativeDistance) moved to (I guess we will use them also somewhere else)? // copied from the internet and adapted // (https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c) - CONFDATATYPE fuzzyAND(int n_args, ...) { + CONFDATATYPE fuzzyAND(int n_args, ...) noexcept { 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; } // copied from the internet // (https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c) - CONFDATATYPE fuzzyOR(int n_args, ...) { + CONFDATATYPE fuzzyOR(int n_args, ...) noexcept { 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); std::max(a, max); } va_end(ap); return max; } PROCDATATYPE relativeDistance(INDATATYPE SampleValue, - INDATATYPE HistoryValue) { + INDATATYPE HistoryValue) noexcept { PROCDATATYPE Dist = HistoryValue - SampleValue; if (Dist == 0) { return 0; } else { Dist = Dist / SampleValue; if (Dist < 0) { //@benedikt: 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 agent } // End namespace rosa #endif // ROSA_AGENT_STATE_HPP diff --git a/include/rosa/agent/SignalStateDetector.hpp b/include/rosa/agent/SignalStateDetector.hpp index 3b19cba..460ca67 100644 --- a/include/rosa/agent/SignalStateDetector.hpp +++ b/include/rosa/agent/SignalStateDetector.hpp @@ -1,281 +1,288 @@ //===-- rosa/agent/StateDetector.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/StateDetector.hpp /// /// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at) /// /// \date 2019 /// /// \brief Definition of *state detector* *functionality*. /// //===----------------------------------------------------------------------===// -//TODO: noexcept - #ifndef ROSA_AGENT_STATEDETECTOR_HPP #define ROSA_AGENT_STATEDETECTOR_HPP #include "rosa/agent/FunctionAbstractions.hpp" #include "rosa/agent/Functionality.h" -#include "rosa/agent/State.hpp" +#include "rosa/agent/SignalState.hpp" #include +// TODO: change everything from state to signal state + namespace rosa { namespace agent { /// Implements \c rosa::agent::StateDetector as a functionality that detects /// states given on input samples. /// /// \note This implementation is supposed to be used for samples of an /// arithmetic type. /// -/// \tparam INDATATYPE is the type of input data, \tparam CONFTYPE is type of +/// \tparam INDATATYPE is the type of input data, \tparam CONFDATATYPE is type +/// of /// data in that the confidence values are given -template -class StateDetector : public Functionality { +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), + 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 StatePtr = std::shared_ptr>; - using StateInfoPtr = std::shared_ptr>; + std::shared_ptr>; + using StepFuncPointer = + std::shared_ptr>; + using StatePtr = std::shared_ptr>; + using StateInfoPtr = std::shared_ptr>; /// The NextStateID is a counter variable which stores the ID which the next /// state shall have. unsigned int NextStateID; - /// The StateHasChanged is a flag that show whether a state change has + /// The SignalStateHasChanged is a flag that show whether a state change has /// happened. - bool StateHasChanged; + bool SignalStateHasChanged; /// The CurrentState is a pointer to the (saved) state in which the actual /// variable (signal) of the observed system is. StatePtr CurrentState; /// The DetectedStates is vector in that all detected states are saved. + // TODO: make it to history std::vector DetectedStates; /// 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 FuzzyFunctionSampleMatches is the fuzzy function that gives the /// confidence how bad the new sample matches another sample in the sample /// history. PartFuncPointer FuzzyFunctionSampleMismatches; /// The FuzzyFunctionSampleMatches is the fuzzy function that gives the /// confidence how many samples from the sampe history match the new sample. StepFuncPointer FuzzyFunctionNumOfSamplesMatches; /// The FuzzyFunctionSampleMatches 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 - StateDetector(PartFuncPointer FuzzyFunctionSampleMatches, - PartFuncPointer FuzzyFunctionSampleMismatches, - StepFuncPointer FuzzyFunctionNumOfSamplesMatches, - StepFuncPointer FuzzyFunctionNumOfSamplesMismatches, - PartFuncPointer FuzzyFunctionSignalIsDrifting, - PartFuncPointer FuzzyFunctionSignalIsStable, - unsigned int SampleHistorySize, unsigned int DABSize, - unsigned int DABHistorySize) noexcept - : NextStateID(1), StateHasChanged(false), CurrentState(NULL), + SignalStateDetector(PartFuncPointer FuzzyFunctionSampleMatches, + PartFuncPointer FuzzyFunctionSampleMismatches, + StepFuncPointer FuzzyFunctionNumOfSamplesMatches, + StepFuncPointer FuzzyFunctionNumOfSamplesMismatches, + PartFuncPointer FuzzyFunctionSignalIsDrifting, + PartFuncPointer FuzzyFunctionSignalIsStable, + unsigned int SampleHistorySize, unsigned int DABSize, + unsigned int DABHistorySize) noexcept + : NextStateID(1), SignalStateHasChanged(false), CurrentState(NULL), FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches), FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches), FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches), FuzzyFunctionNumOfSamplesMismatches( FuzzyFunctionNumOfSamplesMismatches), FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting), FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable), SampleHistorySize(SampleHistorySize), DABSize(DABSize), DABHistorySize(DABHistorySize) {} /// Destroys \p this object. - ~StateDetector(void) = default; + ~SignalStateDetector(void) = default; - /// Detects the state to which the new sample belongs or create a new state if + /// Detects a signal state to which the new sample belongs or create a new + /// 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 state ID as unsigend integer type. State IDs start with number + /// \return the signal state ID as unsigend integer type. State IDs start with + /// number /// 1; that means if there is no current state, the return value is 0. - unsigned int detectState(INDATATYPE Sample) { + unsigned int detectSignalState(INDATATYPE Sample) noexcept { StateInfoPtr StateInfo = detectState__debug(Sample); return StateInfo->StateID; } /// Gives information about the current state. /// - /// \return a the State ID (as unsigned integer type) of the current state. + /// \return a the Signal State ID (as unsigned integer type) of the current + /// state. /// State IDs start with number 1; that means if there is no current state, /// the return value is 0. - unsigned int currentStateInformation(void) { + unsigned int currentSignalStateInformation(void) noexcept { StateInfoPtr StateInfo = currentStateInformation__debug(); if (StateInfo) { return StateInfo->StateID; } else { return 0; } } /// Gives information whether a state change has happened or not. /// /// \return true if a state change has happened, and false if not. - bool stateHasChanged(void) { return StateHasChanged; } + bool SignallStateHasChanged(void) noexcept { return SignallStateHasChanged; } private: + // TODO: change exlaination! it is not totally right /// Creates a new state and adds this state to the state vector in which all /// known states are saved. /// /// \param SampleHistorySize the (maximum) size of the sample history. /// \param DABSize the size of a DAB. /// \param DABHistorySize the (maximum) size of the DAB history. /// \param FuzzyFunctionSampleMatches the /// \param FuzzyFunctionSampleMismatches /// \param FuzzyFunctionNumOfSamplesMatches /// \param FuzzyFunctionNumOfSamplesMismatches /// /// \return the new created state or NULL if no state could be created. - StatePtr createNewState(void) { + StatePtr createNewState(void) noexcept { StatePtr S = new (std::nothrow) State( - SampleHistorySize, DABSize, DABHistorySize, FuzzyFunctionSampleMatches, - FuzzyFunctionSampleMismatches, FuzzyFunctionNumOfSamplesMatches, - FuzzyFunctionNumOfSamplesMismatches); + NextStateID, SampleHistorySize, DABSize, DABHistorySize, + FuzzyFunctionSampleMatches, FuzzyFunctionSampleMismatches, + FuzzyFunctionNumOfSamplesMatches, FuzzyFunctionNumOfSamplesMismatches); - // TODO: maybe assert which checks if push_back worked (ask benedikt) + // @benedikt: todo: assert in history, which checks if push_back worked DetectedStates.push_back(S); return S; } #ifdef STATEDETECTORDEBUGMODE public: #else private: #endif // STATEDETECTORDEBUGMODE /// Detects the state to which the new sample belongs or create a new 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 actual state (state ID and other /// parameters). - // TODO: return something const.. cannot remember exactly (ask benedikt) - StateInfoPtr detectState__debug(INDATATYPE Sample) { + StateInfoPtr detectState__debug(INDATATYPE Sample) noexcept { if (!CurrentState) { ASSERT(DetectedStates.empty()); StatePtr S = createNewState(); CurrentState = S; } else { - CONFTYPE ConfidenceSampleMatchesState = + CONFDATATYPE ConfidenceSampleMatchesState = CurrentState->confSampleMatchesState(Sample); - CONFTYPE ConfidenceSampleMismatchesState = + CONFDATATYPE ConfidenceSampleMismatchesState = CurrentState->confSampleMismatchesState(Sample); if (ConfidenceSampleMatchesState > ConfidenceSampleMismatchesState) { - StateHasChanged = false; + SignalStateHasChanged = false; } else { - StateHasChanged = true; + SignalStateHasChanged = true; if (CurrentState->stateInformation()->StateIsValid) { CurrentState->leaveState(); } else { DetectedStates.erase(std::find(DetectedStates.begin(), DetectedStates.end(), CurrentState)); } // TODO (future): additionally save averages to enable fast // iteration through recorded state vector (maybe sort vector based on // these average values) CurrentState = nullptr; for (auto &SavedState : DetectedStates) { if (SavedState != CurrentState) { - CONFTYPE ConfidenceSampleMatchesState = + CONFDATATYPE ConfidenceSampleMatchesState = SavedState->confSampleMatchesState(Sample); - CONFTYPE ConfidenceSampleMismatchesState = + CONFDATATYPE ConfidenceSampleMismatchesState = SavedState->confSampleMismatchesState(Sample); if (ConfidenceSampleMatchesState > ConfidenceSampleMismatchesState) { // TODO (future): maybe it would be better to compare // ConfidenceSampleMatchesState of all states in the vector in // order to find the best matching state. CurrentState = SavedState; break; } } } if (!CurrentState) { StatePtr S = createNewState(); CurrentState = S; } } } - StateInfoPtr StateInfo = CurrentState->insertSample(Sample); + StateInformation StateInfo = + CurrentState->insertSample(Sample); - if (StateInfo->StateJustGotValid) { + if (StateInfo.StateJustGotValid) { NextStateID++; } - return CurrentState->stateInformation(); + return StateInfo; } #ifdef STATEDETECTORDEBUGMODE public: #else private: #endif // STATEDETECTORDEBUGMODE /// Gives information about the current state. /// /// \return a struct StateInformation that contains information about the /// current state or NULL if no current state exists. - StateInfoPtr currentStateInformation__debug(void) { + StateInformation currentStateInformation__debug(void) noexcept { if (CurrentState) { return CurrentState->stateInformation(); } else { return NULL; } } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_STATEDETECTOR_HPP