diff --git a/examples/agent-functionalities/agent-functionalities.cpp b/examples/agent-functionalities/agent-functionalities.cpp index c1d77a1..47dd576 100644 --- a/examples/agent-functionalities/agent-functionalities.cpp +++ b/examples/agent-functionalities/agent-functionalities.cpp @@ -1,183 +1,211 @@ //===-- examples/agent-functionalities/agent-functionalities.cpp *-- C++-*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/agent-functionalities/agent-functionalities.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief A simple example on defining \c rosa::Agent instances using /// \c rosa::agent::Functionality object as components. /// //===----------------------------------------------------------------------===// #include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/Confidence.hpp" #include "rosa/agent/FunctionAbstractions.hpp" #include "rosa/agent/RangeConfidence.hpp" -#include "rosa/agent/Confidence.hpp" #include "rosa/config/version.h" #include "rosa/core/Agent.hpp" #include "rosa/core/MessagingSystem.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include using namespace rosa; using namespace rosa::agent; using namespace rosa::terminal; /// A dummy wrapper for testing \c rosa::MessagingSystem. /// /// \note Since we test \c rosa::MessagingSystem directly here, we need to get /// access to its protected members. That we do by imitating to be a decent /// subclass of \c rosa::MessagingSystem, while calling protected member /// functions on an object of a type from which we actually don't inherit. struct SystemTester : protected MessagingSystem { template static AgentHandle createMyAgent(MessagingSystem *S, const std::string &Name, Funs &&... Fs) { return ((SystemTester *)S)->createAgent(Name, std::move(Fs)...); } static void destroyMyAgent(MessagingSystem *S, const AgentHandle &H) { ((SystemTester *)S)->destroyUnit(unwrapAgent(H)); } }; /// A special \c rosa::Agent with its own state. class MyAgent : public Agent { public: using Tick = AtomConstant; private: enum class Categories { Bad, Normal, Good }; static const std::map CategoryNames; - History H; + StaticLengthHistory H; Confidence C; RangeAbstraction A; PartialFunction L; RangeConfidence RCL; RangeConfidence RCS; public: void handler(Tick, uint8_t V) noexcept { // Record \p V to the \c rosa::agent::History, then print state info. H << V; ASSERT(H.entry() == V); // Sanity check. LOG_INFO_STREAM << "\nNext value: " << PRINTABLE(V) << ", confidence: " << C(H) << ", category: " << CategoryNames.at(A(H.entry())) << ", partial: " << int(L(H.entry())) << ", range-confidence-linear: "; std::map res_lin = RCL(H.entry()); - for (auto i : res_lin){ - LOG_INFO_STREAM << " " << CategoryNames.at(i.first) - << " " << i.second << "," ; + for (auto i : res_lin) { + LOG_INFO_STREAM << " " << CategoryNames.at(i.first) << " " << i.second + << ","; } LOG_INFO_STREAM << " range-confidence-sine: "; std::map res_sine = RCS(H.entry()); - for (auto i : res_sine){ - LOG_INFO_STREAM << " " << CategoryNames.at(i.first) - << " " << i.second << "," ; + for (auto i : res_sine) { + LOG_INFO_STREAM << " " << CategoryNames.at(i.first) << " " << i.second + << ","; } LOG_INFO_STREAM << '\n'; - } MyAgent(const AtomValue Kind, const rosa::id_t Id, const std::string &Name, MessagingSystem &S) : Agent(Kind, Id, Name, S, THISMEMBER(handler)), H(), C(5, 20, 1), A({{{(uint8_t)10, (uint8_t)14}, Categories::Normal}, {{(uint8_t)15, (uint8_t)17}, Categories::Good}, {{(uint8_t)18, (uint8_t)19}, Categories::Normal}}, Categories::Bad), L({{{0, 2}, std::make_shared>(0, 1)}, {{2, 4}, std::make_shared>(2, 0)}, {{4, 6}, std::make_shared>(6, -1)}}, 0), - RCL({ - {Categories::Bad, PartialFunction({ - {{0, 3}, std::make_shared> - (0, 1.0/3)}, - {{3, 6}, std::make_shared> - (1, 0)}, - {{6, 9}, std::make_shared> - (3.0, -1.0/3)}, - },0)}, - {Categories::Normal, PartialFunction({ - {{6, 9}, std::make_shared> - (-2, 1.0/3)}, - {{9, 12}, std::make_shared> - (1, 0)}, - {{12, 15}, std::make_shared> - (5, -1.0/3)}, - },0)}, - {Categories::Good, PartialFunction({ - {{12, 15}, std::make_shared> - (-4, 1.0/3)}, - {{15, 18}, std::make_shared> - (1, 0)}, - {{18, 21}, std::make_shared> - (7, -1.0/3)}, - },0)} - }), - RCS({ - {Categories::Bad, PartialFunction({ - {{0, 3}, std::make_shared> - (M_PI/3, 0.5, -M_PI/2, 0.5)}, - {{3, 6}, std::make_shared>(1, 0)}, - {{6, 9}, std::make_shared> - (M_PI/3, 0.5, -M_PI/2 + 3, 0.5)}, - },0)}, - {Categories::Normal, PartialFunction({ - {{6, 9}, std::make_shared> - (M_PI/3, 0.5, -M_PI/2, 0.5)}, - {{9, 12}, std::make_shared>(1, 0)}, - {{12, 15}, std::make_shared> - (M_PI/3, 0.5, -M_PI/2 + 3, 0.5)}, - },0)}, - {Categories::Good, PartialFunction({ - {{12, 15}, std::make_shared> - (M_PI/3, 0.5, -M_PI/2, 0.5)}, - {{15, 18}, std::make_shared>(1, 0)}, - {{18, 21}, std::make_shared> - (M_PI/3, 0.5, -M_PI/2 + 3, 0.5)}, - },0)} - }, true){} + RCL({{Categories::Bad, + PartialFunction( + { + {{0, 3}, + std::make_shared>(0, + 1.0 / 3)}, + {{3, 6}, + std::make_shared>(1, 0)}, + {{6, 9}, + std::make_shared>( + 3.0, -1.0 / 3)}, + }, + 0)}, + {Categories::Normal, + PartialFunction( + { + {{6, 9}, + std::make_shared>(-2, + 1.0 / 3)}, + {{9, 12}, + std::make_shared>(1, 0)}, + {{12, 15}, + std::make_shared>( + 5, -1.0 / 3)}, + }, + 0)}, + {Categories::Good, + PartialFunction( + { + {{12, 15}, + std::make_shared>(-4, + 1.0 / 3)}, + {{15, 18}, + std::make_shared>(1, 0)}, + {{18, 21}, + std::make_shared>( + 7, -1.0 / 3)}, + }, + 0)}}), + RCS({{Categories::Bad, + PartialFunction( + { + {{0, 3}, + std::make_shared>( + M_PI / 3, 0.5, -M_PI / 2, 0.5)}, + {{3, 6}, + std::make_shared>(1, 0)}, + {{6, 9}, + std::make_shared>( + M_PI / 3, 0.5, -M_PI / 2 + 3, 0.5)}, + }, + 0)}, + {Categories::Normal, + PartialFunction( + { + {{6, 9}, + std::make_shared>( + M_PI / 3, 0.5, -M_PI / 2, 0.5)}, + {{9, 12}, + std::make_shared>(1, 0)}, + {{12, 15}, + std::make_shared>( + M_PI / 3, 0.5, -M_PI / 2 + 3, 0.5)}, + }, + 0)}, + {Categories::Good, + PartialFunction( + { + {{12, 15}, + std::make_shared>( + M_PI / 3, 0.5, -M_PI / 2, 0.5)}, + {{15, 18}, + std::make_shared>(1, 0)}, + {{18, 21}, + std::make_shared>( + M_PI / 3, 0.5, -M_PI / 2 + 3, 0.5)}, + }, + 0)}}, + true) {} }; const std::map MyAgent::CategoryNames{ {Categories::Bad, "Bad"}, {Categories::Normal, "Normal"}, {Categories::Good, "Good"}}; int main(void) { - LOG_INFO_STREAM << library_string() << " -- " << Color::Red - << "agent-functionalities example" << Color::Default - << '\n'; + LOG_INFO_STREAM << library_string() << " -- " << Color::Red + << "agent-functionalities example" << Color::Default << '\n'; std::unique_ptr S = MessagingSystem::createSystem("Sys"); MessagingSystem *SP = S.get(); AgentHandle A = SystemTester::createMyAgent(SP, "MyAgent"); - std::vector Vs{0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13, - 15, 14, 15, 16, 19, 20, 21}; + std::vector Vs{0, 1, 2, 3, 4, 5, 6, 7, 9, 10, + 11, 13, 15, 14, 15, 16, 19, 20, 21}; for (auto I = Vs.begin(); I != Vs.end(); ++I) { A.send(MyAgent::Tick::Value, *I); } SystemTester::destroyMyAgent(SP, A); return 0; } diff --git a/include/rosa/agent/Confidence.hpp b/include/rosa/agent/Confidence.hpp index 3d07606..35b7496 100644 --- a/include/rosa/agent/Confidence.hpp +++ b/include/rosa/agent/Confidence.hpp @@ -1,204 +1,202 @@ //===-- rosa/agent/Confidence.hpp -------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/Confidence.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *confidence* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_CONFIDENCE_HPP #define ROSA_AGENT_CONFIDENCE_HPP #include "rosa/agent/History.hpp" #include "rosa/support/debug.hpp" #include namespace rosa { namespace agent { /// Confidence validator. /// /// Checks the plausibility of given values by validating if a valid region /// contains them. It also capable of checking consistency by validating the /// rate of change recorded by a \c rosa::agent::History object against a /// maximal absolute valid rate of change. /// /// \tparam T type of values to validate /// /// \note The template is defined only for arithmetic types. /// /// \note The lower bound is inclusive and the upper bound is exclusive. /// /// \invariant The bounds are defined in a meaningful way:\code /// LowerBound <= UpperBound /// \endcode template class Confidence : public Functionality { // Make sure the actual type argument is an arithmetic type. STATIC_ASSERT(std::is_arithmetic::value, "not arithmetic Confidence"); public: /// Unsigned type corresponding to \p T. using UT = unsigned_t; /// The minimal value of type \p T. /// \note Not exist \c V of type \p T such that `V < Min`. static constexpr T Min = std::is_integral::value ? std::numeric_limits::min() : std::numeric_limits::lowest(); /// The maximal value of type \p T. /// \note Not exist \c V of type \p T such that `V > Max`. static constexpr T Max = (std::is_integral::value || !std::numeric_limits::has_infinity) ? std::numeric_limits::max() : std::numeric_limits::infinity(); /// The maximal value of type \c UT. /// \note Not exist \c V of type \c UT such that `V > UnsignedMax`. static constexpr UT UnsignedMax = (std::is_integral::value || !std::numeric_limits::has_infinity) ? std::numeric_limits::max() : std::numeric_limits::infinity(); private: /// The inclusive lower bound for plausibility check. T LowerBound; /// The exclusive upper bound for plausibility check. T UpperBound; /// The maximal absolute rate of change for consistency check. UT ChangeRate; public: /// Creates an instance by setting the validator variables. /// /// \param LowerBound the lower bound for plausability check /// \param UpperBound the upper bound for plausability check /// \param ChangeRate maximal absolute rate of change for consistency check /// /// \pre The bounds are defined in a meaningful way:\code /// LowerBound <= UpperBound /// \endcode Confidence(const T LowerBound = Min, const T UpperBound = Max, const UT ChangeRate = UnsignedMax) noexcept - : LowerBound(LowerBound), - UpperBound(UpperBound), - ChangeRate(ChangeRate) { + : LowerBound(LowerBound), UpperBound(UpperBound), ChangeRate(ChangeRate) { // Make sure Confidence is created in a valid state. if (LowerBound > UpperBound) { ROSA_CRITICAL("Confidence with LowerBound higher than UpperBound"); } } /// Destroys \p this object. ~Confidence(void) = default; /// Gives a snapshot of the current state of the validator variables. /// /// \param [out] LowerBound to copy \c rosa::agent::Confidence::LowerBound /// into /// \param [out] UpperBound to copy \c rosa::agent::Confidence::UpperBound /// into /// \param [out] ChangeRate to copy \c rosa::agent::Confidence::ChangeRate /// into void getParameters(T &LowerBound, T &UpperBound, UT &ChangeRate) const noexcept { // Copy members to the given references. LowerBound = this->LowerBound; UpperBound = this->UpperBound; ChangeRate = this->ChangeRate; } /// Sets the lower bound for plausability check. /// /// Beyond setting the lower bound, the function also adjusts the upper bound /// to the given lower bound if the new lower bound would be higher than the /// upper bound. /// /// \param LowerBound the new lower bound to set void setLowerBound(const T LowerBound) noexcept { // Adjust UpperBound if necessary, then set LowerBound. if (UpperBound < LowerBound) { UpperBound = LowerBound; } this->LowerBound = LowerBound; } /// Sets the upper bound for plausability check. /// /// Beyond setting the upper bound, the function also adjusts the lower bound /// to the given upper bound if the new upper bound would be lower than the /// lower bound. /// /// \param UpperBound the new upper bound to set void setUpperBound(const T UpperBound) noexcept { // Adjust LowerBound if necessary, then set UpperBound. if (UpperBound < LowerBound) { LowerBound = UpperBound; } this->UpperBound = UpperBound; } /// Sets the maximal rate of change for consistency check. /// /// \param ChangeRate the new rate of change to set void setChangeRate(const UT ChangeRate) noexcept { // Set ChangeRate. this->ChangeRate = ChangeRate; } /// Tells the binary confidence on the plausibility of a value. /// /// \param V value to check /// /// \return whether \c V is within the range defined by /// \c rosa::agent::Confidence::LowerBound and /// \c rosa::agent::Confidence::UpperBound. bool operator()(const T V) const noexcept { // Return if \c V is plausible. return LowerBound <= V && V < UpperBound; } /// Tells the binary confidence on the plausibility and consistency of the /// last value recorded by a \c rosa::agent::History instance. /// /// Consistency of the last value is checked by validating the difference with /// its preceding entry. /// /// \note The \c rosa::agent::History instance needs to store values of type /// \p T. /// /// \note An empty \c rosa::agent::History instance results in full /// confidence. /// /// \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 *history* whose last entry to check template - bool operator()(const History &H) const noexcept { + bool operator()(const StaticLengthHistory &H) const noexcept { if (H.empty()) { // No entry to validate. return true; } else { // Validate the last entry and the one step average absolute difference. return (*this)(H.entry()) && H.averageAbsDiff(1) <= ChangeRate; } } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_CONFIDENCE_HPP diff --git a/include/rosa/agent/FunctionAbstractions.hpp b/include/rosa/agent/FunctionAbstractions.hpp index 9a7127a..59200de 100644 --- a/include/rosa/agent/FunctionAbstractions.hpp +++ b/include/rosa/agent/FunctionAbstractions.hpp @@ -1,224 +1,222 @@ //===-- 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/Functionality.h" #include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/Functionality.h" #include "rosa/support/debug.hpp" #include -#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{ +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"); + "LinearFunction not arithmetic T"); STATIC_ASSERT((std::is_arithmetic::value), - "LinearFunction not to arithmetic"); + "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), + : Abstraction(Intercept), Intercept(Intercept), Coefficient(Coefficient) {} /// 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; } /// 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; + 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{ +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"); + "SineFunction not arithmetic T"); STATIC_ASSERT((std::is_arithmetic::value), - "SineFunction not to arithmetic"); + "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) {} + : 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; + return Amplitude * sin(Frequency * X + Phase) + Average; } }; /// 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"); + "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) + PartialFunction( + const std::map, std::shared_ptr>> &Map, + const R Default) : Abstraction(Default), - RA(Map, std::shared_ptr> - (new 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 1097ad0..9676f47 100644 --- a/include/rosa/agent/History.hpp +++ b/include/rosa/agent/History.hpp @@ -1,292 +1,548 @@ //===-- 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 + 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 }; /// Implements *history* 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 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 History : public Functionality, private std::array { +class StaticLengthHistory : public Functionality, 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: /// Creates an instances by initializing the indices for the circular buffer. - History(void) noexcept : Data(0), Space(0) {} + StaticLengthHistory(void) noexcept : Data(0), Space(0) {} /// Destroys \p this object. - ~History(void) = default; + ~StaticLengthHistory(void) = default; /// Tells the retention policy applied to \p this object. /// /// \return \c rosa::agent::History::P static constexpr HistoryPolicy policyOfHistory(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 \c rosa::agent::History::N static constexpr size_t lengthOfHistory(void) noexcept { 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 { return Data <= Space ? Space - Data : max_size() - Data + Space; } /// 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; } /// 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 { 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]; } private: /// Tells if the circular buffer is full. /// /// \return if the circular buffer is full. bool full(void) const noexcept { return numberOfEntries() == N; } /// 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 { // 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(); } } 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()) { + (*this)[(Space - 1) % max_size()] = 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 < N /// \endcode template typename std::enable_if< std::is_arithmetic::value && std::is_signed::value, X>::type trend(const size_t D = N - 1) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); ASSERT(0 <= D && D < N); // 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 < N /// \endcode template typename std::enable_if::value, size_t>::type averageAbsDiff(const size_t D = N - 1) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); ASSERT(0 <= D && D < N); // 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; } } }; /// 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 -History &operator<<(History &H, const T &V) noexcept { +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 Functionality, 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::size; + using std::vector::max_size; + using std::vector::resize; + using std::vector::push_back; + using std::vector::operator[]; + + /// The current length of the DynamicLengthHistory. + size_t Length; + +public: + /// Creates an instances by setting an initial length + DynamicLengthHistory(size_t Length) noexcept : Length(Length) { + this->resize(Length); + } + + /// Destroys \p this object. + ~DynamicLengthHistory(void) = default; + + /// Tells the retention policy applied to \p this object. + /// + /// \return \c rosa::agent::DynamicLengthHistory::P + static constexpr HistoryPolicy policyOfDynamicLengthHistory(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 \c rosa::agent::DynamicLengthHistory::N + static constexpr size_t lengthOfHistory(void) noexcept { return max_size(); } + + /// 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(); } + + /// 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; } + + /// 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 { + ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. + return this->operator[](size() - I - 1); + } + +private: + /// Tells if the buffer is full. + /// + /// \return if the buffer is full. + bool full(void) const noexcept { return numberOfEntries() == Length; } + + /// 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 { + if (full()) { + erase(begin()); + } + push_back(V); + } + +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()) { + erase(end()); + } + 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 *DynamicLengthHistory* + /// + /// \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 < Length + /// \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 < Length); // 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 *DynamicLengthHistory* + /// + /// \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 < Length + /// \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 < Length); // 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; + } + } +}; + +/// 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