diff --git a/include/rosa/agent/SignalState.hpp b/include/rosa/agent/SignalState.hpp index cb545fe..5a3d014 100644 --- a/include/rosa/agent/SignalState.hpp +++ b/include/rosa/agent/SignalState.hpp @@ -1,471 +1,471 @@ //===-- 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" 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 +enum SignalStateCondition : uint8_t { + STABLE = 0, ///< The signal state is stable + DRIFTING = 1, ///< The signal state is drifting + UNKNOWN = 2 ///< The signal state is unknown }; /// TODO: write description 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, /// drifting, or unknown) 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