diff --git a/apps/sa-ews2/sa-ews2.cpp b/apps/sa-ews2/sa-ews2.cpp index 6cd706f..7681f4e 100644 --- a/apps/sa-ews2/sa-ews2.cpp +++ b/apps/sa-ews2/sa-ews2.cpp @@ -1,841 +1,846 @@ //===-- apps/sa-ews2/sa-ews2.cpp --------------------------------*- C++ -*-===// // // The RoSA Framework -- Application SA-EWS2 // // Distributed under the terms and conditions of the Boost Software License 1.0. // See accompanying file LICENSE. // // If you did not receive a copy of the license file, see // http://www.boost.org/LICENSE_1_0.txt. // //===----------------------------------------------------------------------===// /// /// \file apps/sa-ews2/sa-ews2.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// Maximilian Goetzinger (maxgot@utu.fi) /// /// \date 2020 /// /// \brief The application SA-EWS2 implements the case study from the paper: /// M. Götzinger, A. Anzanpour, I. Azimi, N. TaheriNejad, A. Jantsch, /// A. M. Rahmani, and P. Liljeberg: Confidence-Enhanced Early Warning Score /// Based on Fuzzy Logic. DOI: 10.1007/s11036-019-01324-5 //===----------------------------------------------------------------------===// #include "rosa/agent/RangeConfidence.hpp" #include "rosa/agent/CrossCombinator.h" #include "rosa/agent/ReliabilityConfidenceCombination.h" #include "rosa/config/version.h" #include "rosa/app/Application.hpp" #include "rosa/support/csv/CSVReader.hpp" #include "rosa/support/csv/CSVWriter.hpp" #include "rosa/support/iterator/split_tuple_iterator.hpp" #include "cxxopts/cxxopts.hpp" #include using namespace rosa; using namespace rosa::agent; using namespace rosa::app; using namespace rosa::terminal; using namespace rosa::csv; using namespace rosa::iterator; const std::string AppName = "SA-EWS2"; /// Representation type of warning levels. /// \note Make sure it suits all defined enumeration values. using WarningScoreType = uint8_t; /// Warning levels for abstraction. /// /// \note Conversion functions \c feedbackFromSymbol(), \c feedbackToSymbol(), /// and the master input handler generated by \c createLowLevelAgent() expect /// enumeration values to constitute a consecutive range of non-negative /// integers starting from \c 0. enum WarningScore : WarningScoreType { No = 0, Low = 1, High = 2, Emergency = 3 }; /// Vector with all possible warning levels. std::vector warningScores = {No, Low, High, Emergency}; /// Type used to represent reliability values. using ReliabilityType = double; /// The result type of low-level functions. using WarningValue = AppTuple; /// The helper struct used by the Likeliness Combinator to provide feedback. using EWSSymbol = Symbol; /// All feedback calculated by Likelienss Combinator for one slave. using EWSFeedback = std::vector; /// The feedback provided by master to a slave agent. /// The tuple contains the reliability feedback for each warning level in order. using FeedbackTuple = AppTuple; /// Converts feedback calculated by Likeliness Combinator into a tuple that can /// be sent between agents. /// /// \param F calculated standard feedback /// /// \return feedback tuple FeedbackTuple feedbackFromSymbol(const EWSFeedback &F) { // Fill a map with feedback for warning levels. std::map M; std::for_each(F.cbegin(), F.cend(), [&M](const EWSSymbol &S) { M.insert({S.Identifier, S.Likeliness}); }); // Expect feedback for all warning levels. ASSERT(M.size() == warningScores.size()); return {M[No], M[Low], M[High], M[Emergency]}; } /// Converts feedback tuple to standard format used by the Likeliness Combinator. /// /// \param F feedback tuple /// /// \return feedback in standard format EWSFeedback feedbackToSymbol(const FeedbackTuple &F) { return {{No, std::get(F)}, {Low, std::get(F)}, {High, std::get(F)}, {Emergency, std::get(F)}}; } /// Helper function creating an application sensor and setting its execution /// policy for decimation. /// /// \note The sensors are created without defining a normal generator function, /// which is suitable for simulation only. /// /// \tparam T type of values for the sensor to generate /// /// \param App the application to create the sensor in /// \param Name name of the new sensor /// \param Decimation the decimation parameter /// /// \return handle for the new sensor template AgentHandle createSensor(std::unique_ptr &App, const std::string &Name, const size_t Decimation) { AgentHandle Sensor = App->createSensor(Name); App->setExecutionPolicy(Sensor, AppExecutionPolicy::decimation(Decimation)); return Sensor; } /// Helper function creating an application agent for pre-processing sensory /// values and setting its execution policy for decimation. /// /// Received values are assessed for reliability and abstracted into a \c /// WarningScore. The result of the processing function is a pair of assessed /// reliability and abstracted values. /// /// The agent also accepts feedback from its master, which is taken into account /// for the pre-processing of later sensory values. /// /// \note The result, \c WarningScore, is returned as \c WarningScoreType /// because enumeration types are not integrated into built-in types. Hence, a /// master to these agents receives its input as \c WarningScoreType values, and /// may cast them to \c WarningScore explicitly. /// /// \tparam T type of values to receive from the sensor /// /// \param App the application to create the agent in /// \param Name name of the new agent /// \param Decimation the decimation parameter /// \param R reliability/confidence combination to use /// /// \return handle for the new agent template AgentHandle createLowLevelAgent(std::unique_ptr &App, const std::string &Name, const size_t Decimation, ReliabilityAndConfidenceCombination &R) { using masterhandler = std::function)>; using result = Optional; using input = AppTuple; using handler = std::function)>; AgentHandle Agent = App->createAgent( Name, masterhandler([&, Name](std::pair I) { LOG_INFO_STREAM << "\n******\n" << Name << " " << (I.second ? "" : "") << " master feedback: [" << PRINTABLE(std::get(I.first)) << ", " << PRINTABLE(std::get(I.first)) << ", " << PRINTABLE(std::get(I.first)) << ", " << PRINTABLE(std::get(I.first)) << "]\n******\n"; const auto Feedback = feedbackToSymbol(I.first); R.feedback(Feedback); }), handler([&, Name](std::pair I) -> result { LOG_INFO_STREAM << "\n******\n" << Name << " " << (I.second ? "" : "") << " value: " << PRINTABLE(std::get<0>(I.first)) << "\n******\n"; const auto SensorValue = std::get<0>(I.first); const auto AbstractedValue = R.bestSymbol(SensorValue); return {WarningValue(AbstractedValue.Identifier, AbstractedValue.Likeliness)}; })); App->setExecutionPolicy(Agent, AppExecutionPolicy::decimation(Decimation)); return Agent; } /// Helper function to print an error message in red color to the terminal and /// exit from the application. /// /// \note The function never returns as it calles `exit()`. /// /// \param Error error message /// \param ExitCode exit code to return from the application void logErrorAndExit(const std::string &Error, const int ExitCode) { LOG_ERROR_STREAM << Color::Red << Error << Color::Default << std::endl; exit(ExitCode); } int main(int argc, char *argv[]) { LOG_INFO_STREAM << '\n' << library_string() << " -- " << Color::Red << AppName << " app" << Color::Default << '\n'; /// Paths for the CSV files for simulation. /// ///@{ std::string DataCSVPath; std::string ScoreCSVPath; ///@} /// Whether CSV files have header row at the beginning. bool CSVHeader = false; /// Delimiter character in CSV files. char CSVDelimiter = ','; /// Decimation of sensors and agents. size_t Decimation = 1; /// How many cycles of simulation to perform. size_t NumberOfSimulationCycles = 128; // Handle command-line arguments. try { cxxopts::Options Options(argv[0], library_string() + " -- " + AppName); Options.add_options()("i,input", "Path for the CSV file providing input data", cxxopts::value(DataCSVPath), "file") ("o,output", "Path for the CSV file to write output scores", cxxopts::value(ScoreCSVPath), "file") ("header", "CSV files have header row", cxxopts::value(CSVHeader)->default_value("false")) ("delimiter", "CSV delimiter character", cxxopts::value(CSVDelimiter)->default_value(","), "char") ("d,decimation", "Decimation of sensors and agents", cxxopts::value(Decimation)->default_value("1"), "int") ("c,cycles", "Number of simulation cycles to perform", cxxopts::value(NumberOfSimulationCycles)->default_value("16"), "int") ("h,help", "Print usage"); auto Args = Options.parse(argc, argv); if (Args.count("help")) { LOG_INFO_STREAM << '\n' << Options.help() << std::endl; exit(0); } if (Args.count("input") == 0) { throw std::invalid_argument("Argument --input must be defined."); } if (Args.count("output") == 0) { throw std::invalid_argument("Argument --output must be defined."); } } catch (const cxxopts::OptionException &e) { logErrorAndExit(e.what(), 1); } catch (const std::invalid_argument &e) { logErrorAndExit(e.what(), 1); } std::unique_ptr App = Application::create(AppName); // // Relevant types and definitions. // using HRType = float; using HRConfidence = RangeConfidence; using HRParFun = PartialFunction; using HRLinFun = LinearFunction; using BRType = float; using BRConfidence = RangeConfidence; using BRParFun = PartialFunction; using BRLinFun = LinearFunction; using SpO2Type = int32_t; using SpO2Confidence = RangeConfidence; using SpO2ParFun = PartialFunction; using SpO2LinFun = LinearFunction; using BPSysType = float; using BPSysConfidence = RangeConfidence; using BPSysParFun = PartialFunction; using BPSysLinFun = LinearFunction; using BodyTempType = float; using BodyTempConfidence = RangeConfidence; using BodyTempParFun = PartialFunction; using BodyTempLinFun = LinearFunction; // // Create application sensors. // LOG_INFO("Creating sensors."); // All sensors are created without defining a normal generator function, but // with the default value of the second argument. That, however, requires the // data type to be explicitly defined. This is good for simulation only. AgentHandle HRSensor = createSensor(App, "HR Sensor", Decimation); AgentHandle BRSensor = createSensor(App, "BR Sensor", Decimation); AgentHandle SpO2Sensor = createSensor(App, "SpO2 Sensor", Decimation); AgentHandle BPSysSensor = createSensor(App, "BPSys Sensor", Decimation); AgentHandle BodyTempSensor = createSensor(App, "BodyTemp Sensor", Decimation); // // Create functionalities. // LOG_INFO("Creating Functionalities for Agents."); // // Define reliabilities and confindences. // const size_t TimeStep = 1; const size_t HistoryLength = 32; using TimeParFun = PartialFunction; using TimeLinFun = LinearFunction; std::shared_ptr> TimeConfidence( - new TimeParFun({{{std::numeric_limits::lowest(), 0}, - std::make_shared(1, 0)}, - {{0, HistoryLength}, + new TimeParFun({{{0, HistoryLength}, std::make_shared(0, 1, HistoryLength, 0)}}, 0)); ReliabilityAndConfidenceCombination HRReliability; HRReliability.setIdentifiers(warningScores); HRReliability.setHistoryLength(HistoryLength); HRReliability.setTimeStep(TimeStep); HRReliability.setTimeFunctionForLikelinessFunction(TimeConfidence); std::shared_ptr HRConf(new HRConfidence( {{No, HRParFun({{{56.5f, 62.5f}, std::make_shared(56.5f, 0, 62.5f, 1)}, {{62.5f, 97.5f}, std::make_shared(1, 0)}, {{97.5f, 103.5f}, std::make_shared(97.5f, 1, 103.5f, 0)}}, 0)}, {Low, HRParFun( {{{47.5f, 53.5f}, std::make_shared(47.5f, 0, 53.5f, 1)}, {{53.5f, 56.5f}, std::make_shared(1, 0)}, {{56.5f, 62.5f}, std::make_shared(56.5f, 1, 62.5f, 0)}, {{97.5f, 103.5f}, std::make_shared(97.5f, 0, 103.5f, 1)}, {{103.5f, 107.5f}, std::make_shared(1, 0)}, {{107.5f, 113.5f}, std::make_shared(107.5f, 1, 113.5f, 0)}}, 0)}, {High, HRParFun( {{{36.5f, 42.5f}, std::make_shared(36.5f, 0, 42.5f, 1)}, {{42.5f, 47.5f}, std::make_shared(1, 0)}, {{47.5f, 53.5f}, std::make_shared(47.5f, 1, 53.5f, 0)}, {{107.5f, 113.5f}, std::make_shared(107.5f, 0, 113.5f, 1)}, {{113.5f, 126.5f}, std::make_shared(1, 0)}, {{126.5f, 132.5f}, std::make_shared(126.5f, 1, 132.5f, 0)}}, 0)}, {Emergency, HRParFun( {{{36.5f, 42.5f}, std::make_shared(36.5f, 1, 42.5f, 0)}, {{42.5f, 126.5f}, std::make_shared(0, 0)}, {{126.5f, 132.5f}, std::make_shared(126.5f, 0, 132.5f, 1)}}, 1)}})); HRReliability.setConfidenceFunction(HRConf); std::shared_ptr> HRAbsRel( new HRParFun({{{0.f, 200.f}, std::make_shared(1, 0)}, {{200.f, 300.f}, std::make_shared(200.f, 1, 300.f, 0)}}, 0)); HRReliability.setAbsoluteReliabilityFunction(HRAbsRel); std::shared_ptr> HRRelSlope(new HRParFun( {{{-200.f, -100.f}, std::make_shared(-200.f, 0, -100.f, 1)}, {{-100.f, 100.f}, std::make_shared(1, 0)}, {{100.f, 200.f}, std::make_shared(100.f, 1, 200.f, 0)}}, 0)); HRReliability.setReliabilitySlopeFunction(HRRelSlope); ReliabilityAndConfidenceCombination BRReliability; BRReliability.setIdentifiers(warningScores); BRReliability.setHistoryLength(HistoryLength); BRReliability.setTimeStep(TimeStep); BRReliability.setTimeFunctionForLikelinessFunction(TimeConfidence); std::shared_ptr BRConf(new BRConfidence( {{No, BRParFun( {{{7.5f, 9.5f}, std::make_shared(7.5f, 0, 9.5f, 1)}, {{9.5f, 13.5f}, std::make_shared(1, 0)}, {{13.5f, 15.5f}, std::make_shared(12.5f, 1, 15.5f, 0)}}, 0)}, {Low, BRParFun( {{{13.5f, 15.5f}, std::make_shared(13.5f, 0, 15.5f, 1)}, {{15.5f, 19.5f}, std::make_shared(1, 0)}, {{19.5f, 21.5f}, std::make_shared(19.5f, 1, 21.5f, 0)}}, 0)}, {High, BRParFun( {{{std::numeric_limits::lowest(), 7.5f}, std::make_shared(1, 0)}, {{7.5f, 9.5f}, std::make_shared(7.5f, 1, 9.5f, 0)}, {{19.5f, 21.5f}, std::make_shared(19.5f, 0, 21.5f, 1)}, {{21.5f, 28.5f}, std::make_shared(1, 0)}, {{28.5f, 30.5f}, std::make_shared(28.5f, 1, 30.5f, 0)}}, 0)}, {Emergency, BRParFun({{{std::numeric_limits::lowest(), 28.5f}, std::make_shared(0, 0)}, {{28.5f, 30.5f}, std::make_shared(28.5f, 0, 30.5f, 1)}}, 1)}})); BRReliability.setConfidenceFunction(BRConf); std::shared_ptr> BRAbsRel( new BRParFun({{{0.f, 40.f}, std::make_shared(1, 0)}, {{40.f, 60.f}, std::make_shared(40.f, 1, 60.f, 0)}}, 0)); BRReliability.setAbsoluteReliabilityFunction(BRAbsRel); std::shared_ptr> BRRelSlope( new BRParFun({{{-30.f, -20.f}, std::make_shared(-30.f, 0, -20.f, 1)}, {{-20.f, 20.f}, std::make_shared(1, 0)}, {{20.f, 30.f}, std::make_shared(20.f, 1, 30.f, 0)}}, 0)); BRReliability.setReliabilitySlopeFunction(BRRelSlope); ReliabilityAndConfidenceCombination SpO2Reliability; SpO2Reliability.setIdentifiers(warningScores); SpO2Reliability.setHistoryLength(HistoryLength); SpO2Reliability.setTimeStep(TimeStep); SpO2Reliability.setTimeFunctionForLikelinessFunction(TimeConfidence); std::shared_ptr SpO2Conf(new SpO2Confidence( {{No, SpO2ParFun({{{93, 96}, std::make_shared(93, 0, 96, 1)}, {{96, std::numeric_limits::max()}, std::make_shared(1, 0)}}, 0)}, {Low, SpO2ParFun({{{88, 91}, std::make_shared(88, 0, 91, 1)}, {{91, 93}, std::make_shared(1, 0)}, {{93, 96}, std::make_shared(93, 1, 96, 0)}}, 0)}, {High, SpO2ParFun({{{83, 86}, std::make_shared(83, 0, 86, 1)}, {{86, 88}, std::make_shared(1, 0)}, {{88, 91}, std::make_shared(88, 1, 91, 0)}}, 0)}, {Emergency, SpO2ParFun({{{83, 86}, std::make_shared(83, 1, 86, 0)}, {{86, std::numeric_limits::max()}, std::make_shared(0, 0)}}, 1)}})); SpO2Reliability.setConfidenceFunction(SpO2Conf); std::shared_ptr> SpO2AbsRel( new SpO2ParFun( { {{0, 100}, std::make_shared(1, 0)}, }, 0)); SpO2Reliability.setAbsoluteReliabilityFunction(SpO2AbsRel); std::shared_ptr> SpO2RelSlope( new SpO2ParFun({{{-8, -5}, std::make_shared(-8, 0, -5, 1)}, {{-5, 5}, std::make_shared(1, 0)}, {{5, 8}, std::make_shared(5, 1, 8, 0)}}, 0)); SpO2Reliability.setReliabilitySlopeFunction(SpO2RelSlope); ReliabilityAndConfidenceCombination BPSysReliability; BPSysReliability.setIdentifiers(warningScores); BPSysReliability.setHistoryLength(HistoryLength); BPSysReliability.setTimeStep(TimeStep); BPSysReliability.setTimeFunctionForLikelinessFunction(TimeConfidence); std::shared_ptr BPSysConf(new BPSysConfidence( {{No, BPSysParFun({{{97.5f, 103.5f}, std::make_shared(97.5f, 0, 103.5f, 1)}, {{103.5f, 146.5f}, std::make_shared(1, 0)}, {{146.5f, 152.5f}, std::make_shared(146.5f, 1, 152.5f, 0)}}, 0)}, {Low, BPSysParFun({{{77.5f, 83.5f}, std::make_shared(77.5f, 0, 83.5f, 1)}, {{83.5f, 97.5f}, std::make_shared(1, 0)}, {{97.5f, 103.5f}, std::make_shared(97.5f, 1, 103.5f, 0)}, {{146.5f, 152.5f}, std::make_shared(146.5f, 0, 152.5f, 1)}, {{152.5f, 166.5f}, std::make_shared(1, 0)}, {{166.5f, 172.5f}, std::make_shared(166.5f, 1, 172.5f, 0)}}, 0)}, {High, BPSysParFun({{{66.5f, 72.5f}, std::make_shared(66.5f, 0, 72.5f, 1)}, {{72.5f, 77.5f}, std::make_shared(1, 0)}, {{77.5f, 83.5f}, std::make_shared(77.5f, 1, 83.5f, 0)}, {{166.5f, 172.5f}, std::make_shared(166.5f, 0, 172.5f, 1)}, {{172.5f, 176.5f}, std::make_shared(1, 0)}, {{176.5f, 182.5f}, std::make_shared(176.5f, 1, 182.5f, 0)}}, 0)}, {Emergency, BPSysParFun({{{66.5f, 72.5f}, std::make_shared(66.5f, 1, 72.5f, 0)}, {{72.5f, 176.5f}, std::make_shared(0, 0)}, {{176.5f, 182.5f}, std::make_shared(176.5f, 0, 182.5f, 1)}}, 1)}})); BPSysReliability.setConfidenceFunction(BPSysConf); std::shared_ptr> BPSysAbsRel( new BPSysParFun( {{{0.f, 260.f}, std::make_shared(1, 0)}, {{260.f, 400.f}, std::make_shared(260.f, 1, 400.f, 0)}}, 0)); BPSysReliability.setAbsoluteReliabilityFunction(BPSysAbsRel); std::shared_ptr> BPSysRelSlope( new BPSysParFun( {{{-100.f, -50.f}, std::make_shared(-100.f, 0, -50.f, 1)}, {{-50.f, 50.f}, std::make_shared(1, 0)}, {{50.f, 100.f}, std::make_shared(50.f, 1, 100.f, 0)}}, 0)); BPSysReliability.setReliabilitySlopeFunction(BPSysRelSlope); ReliabilityAndConfidenceCombination BodyTempReliability; BodyTempReliability.setIdentifiers(warningScores); BodyTempReliability.setHistoryLength(HistoryLength); BodyTempReliability.setTimeStep(TimeStep); BodyTempReliability.setTimeFunctionForLikelinessFunction(TimeConfidence); std::shared_ptr BodyTempConf(new BodyTempConfidence( {{No, BodyTempParFun( {{{34.55f, 35.55f}, std::make_shared(34.55f, 0, 35.55f, 1)}, {{35.55f, 37.55f}, std::make_shared(1, 0)}, {{37.55f, 38.55f}, std::make_shared(37.55f, 1, 38.55f, 0)}}, 0)}, {Low, BodyTempParFun({}, 0)}, {High, BodyTempParFun( {{{std::numeric_limits::lowest(), 34.55f}, std::make_shared(1, 0)}, {{34.55f, 35.55f}, std::make_shared(34.55f, 1, 35.55f, 0)}, {{37.55f, 38.55f}, std::make_shared(37.55f, 0, 38.55f, 1)}, {{38.55f, 39.05f}, std::make_shared(1, 0)}, {{39.05f, 40.05f}, std::make_shared(39.05f, 1, 40.05f, 0)}}, 0)}, {Emergency, BodyTempParFun( {{{std::numeric_limits::lowest(), 39.05f}, std::make_shared(0, 0)}, {{39.05f, 40.05f}, std::make_shared(39.05f, 0, 40.05f, 1)}}, 1)}})); BodyTempReliability.setConfidenceFunction(BodyTempConf); std::shared_ptr> BodyTempAbsRel( new BodyTempParFun( {{{-70.f, -50.f}, std::make_shared(-70.f, 0, -50.f, 1)}, {{-50.f, 40.f}, std::make_shared(1, 0)}, {{40.f, 60.f}, std::make_shared(40.f, 1, 60.f, 0)}}, 0)); BodyTempReliability.setAbsoluteReliabilityFunction(BodyTempAbsRel); std::shared_ptr> BodyTempRelSlope( new BodyTempParFun( {{{-0.1f, -0.05f}, std::make_shared(-0.1f, 0, -0.05f, 1)}, {{-0.05f, 0.05f}, std::make_shared(1, 0)}, {{0.05f, 0.1f}, std::make_shared(0.05f, 1, 0.1f, 0)}}, 0)); BodyTempReliability.setReliabilitySlopeFunction(BodyTempRelSlope); // // Create low-level application agents with \c createLowLevelAgent. // LOG_INFO("Creating low-level agents."); AgentHandle HRAgent = createLowLevelAgent(App, "HR Agent", Decimation, HRReliability); AgentHandle BRAgent = createLowLevelAgent(App, "BR Agent", Decimation, BRReliability); AgentHandle SpO2Agent = createLowLevelAgent(App, "SpO2 Agent", Decimation, SpO2Reliability); AgentHandle BPSysAgent = createLowLevelAgent(App, "BPSys Agent", Decimation, BPSysReliability); AgentHandle BodyTempAgent = createLowLevelAgent( App, "BodyTemp Agent", Decimation, BodyTempReliability); // // Connect sensors to low-level agents. // LOG_INFO("Connect sensors to their corresponding low-level agents."); App->connectSensor(HRAgent, 0, HRSensor, "HR Sensor Channel"); App->connectSensor(BRAgent, 0, BRSensor, "BR Sensor Channel"); App->connectSensor(SpO2Agent, 0, SpO2Sensor, "SpO2 Sensor Channel"); App->connectSensor(BPSysAgent, 0, BPSysSensor, "BPSys Sensor Channel"); App->connectSensor(BodyTempAgent, 0, BodyTempSensor, "BodyTemp Sensor Channel"); // // Create a high-level application agent. // LOG_INFO("Create high-level agent."); // Slave positions on BodyAgent. enum SlaveIndex : rosa::id_t { HRIdx = 0, BRIdx = 1, SpO2Idx = 2, BPSysIdx = 3, BodyTempIdx = 4 }; const ReliabilityType CrossLikelinessParameter = 1.5; CrossCombinator BodyCrossCombinator; + + BodyCrossCombinator.addIdentifiers(HRIdx, warningScores); + BodyCrossCombinator.addIdentifiers(BRIdx, warningScores); + BodyCrossCombinator.addIdentifiers(SpO2Idx, warningScores); + BodyCrossCombinator.addIdentifiers(BPSysIdx, warningScores); + BodyCrossCombinator.addIdentifiers(BodyTempIdx, warningScores); + BodyCrossCombinator.setCrossLikelinessParameter(CrossLikelinessParameter); using WarningLikelinessFun = LikelinessFunction; std::shared_ptr BRCrossLikelinessFun( new WarningLikelinessFun(0.6)); BodyCrossCombinator.addCrossLikelinessProfile(HRIdx, BRIdx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BRIdx, HRIdx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BRIdx, SpO2Idx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BRIdx, BPSysIdx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BRIdx, BodyTempIdx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(SpO2Idx, BRIdx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BPSysIdx, BRIdx, BRCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BodyTempIdx, BRIdx, BRCrossLikelinessFun); std::shared_ptr HRBPCrossLikelinessFun( new WarningLikelinessFun(2.5)); BodyCrossCombinator.addCrossLikelinessProfile(HRIdx, BPSysIdx, HRBPCrossLikelinessFun); BodyCrossCombinator.addCrossLikelinessProfile(BPSysIdx, HRIdx, HRBPCrossLikelinessFun); // The new agent logs its input values, results in a pair of the sum of // received warning scores and their cross-reliability as well as calculated // cross-likeliness feedback for each of its slaves. using BodyAgentInput = std::pair; using BodyAgentOutput = Optional; using BodyAgentFeedback = Optional; using BodyAgentResult = std::tuple; using BodyAgentHandler = std::function; AgentHandle BodyAgent = App->createAgent( "Body Agent", BodyAgentHandler([&](BodyAgentInput HR, BodyAgentInput BR, BodyAgentInput SpO2, BodyAgentInput BPSys, BodyAgentInput BodyTemp) -> BodyAgentResult { LOG_INFO_STREAM << "\n*******\nBody Agent trigged with values:\n" << (HR.second ? "" : "") << " HR result: " << PRINTABLE(HR.first) << "\n" << (BR.second ? "" : "") << " BR result: " << PRINTABLE(BR.first) << "\n" << (SpO2.second ? "" : "") << " SpO2 result: " << PRINTABLE(SpO2.first) << "\n" << (BPSys.second ? "" : "") << " BPSys result: " << PRINTABLE(BPSys.first) << "\n" << (BodyTemp.second ? "" : "") << " BodyTemp result: " << PRINTABLE(BodyTemp.first) << "\n******\n"; using ValueType = std::tuple; const std::vector SlaveValues{ {HRIdx, std::get<0>(HR.first), std::get<1>(HR.first)}, {BRIdx, std::get<0>(BR.first), std::get<1>(BR.first)}, {SpO2Idx, std::get<0>(SpO2.first), std::get<1>(SpO2.first)}, {BPSysIdx, std::get<0>(BPSys.first), std::get<1>(BPSys.first)}, {BodyTempIdx, std::get<0>(BodyTemp.first), std::get<1>(BodyTemp.first)}}; const auto [crossReliability, feedbackValues] = BodyCrossCombinator(SlaveValues); struct ScoreSum { void operator()(const ValueType &V) { ews += std::get<1>(V); } WarningScoreType ews{0}; }; const ScoreSum scoreSum = std::for_each( SlaveValues.cbegin(), SlaveValues.cend(), ScoreSum()); return {{WarningValue(scoreSum.ews, crossReliability)}, {feedbackFromSymbol(feedbackValues.at(HRIdx))}, {feedbackFromSymbol(feedbackValues.at(BRIdx))}, {feedbackFromSymbol(feedbackValues.at(SpO2Idx))}, {feedbackFromSymbol(feedbackValues.at(BPSysIdx))}, {feedbackFromSymbol(feedbackValues.at(BodyTempIdx))}}; })); App->setExecutionPolicy(BodyAgent, AppExecutionPolicy::decimation(Decimation)); // // Connect low-level agents to the high-level agent. // LOG_INFO("Connect low-level agents to the high-level agent."); App->connectAgents(BodyAgent, HRIdx, HRAgent, "HR Agent Channel"); App->connectAgents(BodyAgent, BRIdx, BRAgent, "BR Agent Channel"); App->connectAgents(BodyAgent, SpO2Idx, SpO2Agent, "SpO2 Agent Channel"); App->connectAgents(BodyAgent, BPSysIdx, BPSysAgent, "BPSys Agent Channel"); App->connectAgents(BodyAgent, BodyTempIdx, BodyTempAgent, "BodyTemp Agent Channel"); // // For simulation output, create a logger agent writing the output of the // high-level agent into a CSV file. // LOG_INFO("Create a logger agent."); // Create CSV writer. std::ofstream ScoreCSV(ScoreCSVPath); csv::CSVTupleWriter ScoreWriter(ScoreCSV, CSVDelimiter); if (CSVHeader) { ScoreWriter.writeHeader({"EWS", "Reliability"}); } // The agent writes each new input value into a CSV file and produces nothing. // \note The execution of the logger is not subject to decimation. using logger_result = AppTuple; AgentHandle LoggerAgent = App->createAgent( "Logger Agent", std::function(std::pair)>( [&ScoreWriter]( std::pair Score) -> Optional { // The state of \p ScoreWriter is not checked, expecting good. ScoreWriter << Score.first; return {}; })); // // Connect the high-level agent to the logger agent. // LOG_INFO("Connect the high-level agent to the logger agent."); App->connectAgents(LoggerAgent, 0, BodyAgent, "Body Agent Channel"); // // Do simulation. // LOG_INFO("Setting up and performing simulation."); // // Initialize application for simulation. // App->initializeSimulation(); // // Open CSV files and register them for their corresponding sensors. // // Type aliases for iterators. using CSVDataIterator = CSVIterator; const auto CSVHeaderInfo = CSVHeader ? HeaderInformation::HasHeader : HeaderInformation::HasNoHeader; std::ifstream DataCSV(DataCSVPath); auto [HRRange, BRRange, SpO2Range, BPSysRange, BodyTempRange] = splitTupleIterator( CSVDataIterator(DataCSV, 0, CSVHeaderInfo, CSVDelimiter), CSVDataIterator()); App->registerSensorValues(HRSensor, std::move(begin(HRRange)), end(HRRange)); App->registerSensorValues(BRSensor, std::move(begin(BRRange)), end(BRRange)); App->registerSensorValues(SpO2Sensor, std::move(begin(SpO2Range)), end(SpO2Range)); App->registerSensorValues(BPSysSensor, std::move(begin(BPSysRange)), end(BPSysRange)); App->registerSensorValues(BodyTempSensor, std::move(begin(BodyTempRange)), end(BodyTempRange)); // // Simulate. // App->simulate(NumberOfSimulationCycles); return 0; } diff --git a/include/rosa/agent/CrossCombinator.h b/include/rosa/agent/CrossCombinator.h index 9b8a8d4..b5a4f54 100644 --- a/include/rosa/agent/CrossCombinator.h +++ b/include/rosa/agent/CrossCombinator.h @@ -1,558 +1,558 @@ //===-- rosa/delux/CrossCombinator.h ----------------------------*- C++ -*-===// // // The RoSA Framework // // Distributed under the terms and conditions of the Boost Software License 1.0. // See accompanying file LICENSE. // // If you did not receive a copy of the license file, see // http://www.boost.org/LICENSE_1_0.txt. // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/CrossCombinator.h /// /// \author Daniel Schnoell /// /// \date 2019-2020 /// /// \note based on Maximilian Goetzinger(maxgot @utu.fi) code in /// CAM_Dirty_include SA-EWS2_Version... inside Agent.cpp /// /// \brief /// /// \todo there is 1 exception that needs to be handled correctly. /// \note the default search function is extremely slow maybe this could be done /// via template for storage class and the functions/methods to efficiently find /// the correct LinearFunction //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_CROSSCOMBINATOR_H #define ROSA_AGENT_CROSSCOMBINATOR_H #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/Functionality.h" #include "rosa/agent/ReliabilityConfidenceCombination.h" #include "rosa/core/forward_declarations.h" // needed for id_t #include "rosa/support/log.h" // needed for error "handling" // nedded headers #include #include //assert #include // for static methods #include #include namespace rosa { namespace agent { template std::vector> &operator<<( std::vector> &me, std::vector> Values) { for (auto tmp : Values) { std::pair tmp2; tmp2.first = std::get<0>(tmp); tmp2.second = std::get<1>(tmp); me.push_back(tmp2); } return me; } /// This is the Combinator class for cross Reliabilities. It has many functions /// with different purposes /// \brief It takes the Identifiers and Reliabilities of all given ids and /// calculates the Likeliness of them together. Also it can creates the /// feedback that is needed by the \c ReliabilityConfidenceCombination, which /// is a kind of confidence. /// /// \tparam IdentifierType Data type of the Identifier ( Typically double /// or float) \tparam LikelinessType Data type of the Likeliness ( Typically /// long or int) // this might be swapped /// /// \note This class is commonly in a master slave relationship as master with /// \c ReliabilityConfidenceCombination. The \c operator()() combines the /// Likeliness of all connected Slaves and uses that as its own Likeliness /// also creates the feedback for the Slaves. /// /// \note more information about how the Likeliness and feedback is -/// created at \c operator()() , \c getCombinedCrossLikeliness() , \c -/// getCombinedInputLikeliness() , \c getOutputLikeliness() [ this is the -/// used Likeliness ], \c getCrossLikeliness() [ this is the feedback -/// for all Compares ] +/// created at \c addIdentifiers() , \c operator()() , +/// \c getCombinedCrossLikeliness() , \c getCombinedInputLikeliness() , +/// \c getOutputLikeliness() [ this is the used Likeliness ], +/// \c getCrossLikeliness() [ this is the feedback for all Compares ] /// -/// a bit more special Methods \c CrossConfidence() ,\c CrossLikeliness() +/// a bit more special Methods \c CrossConfidence() , \c CrossLikeliness() template class CrossCombinator { public: static_assert(std::is_arithmetic::value, "HighLevel: IdentifierType has to be an arithmetic type\n"); static_assert(std::is_arithmetic::value, "HighLevel: LikelinessType has to be an arithmetic type\n"); // --------------------------------------------------------------------------- // useful definitions // --------------------------------------------------------------------------- /// typedef To shorten the writing. /// \c Symbol using Symbol = Symbol; /// To shorten the writing. using Abstraction = typename rosa::agent::Abstraction; /// The return type for the \c operator()() Method struct returnType { LikelinessType CrossLikeliness; std::map> Likeliness; }; // ------------------------------------------------------------------------- // Relevant Methods // ------------------------------------------------------------------------- /// Calculates the CrossLikeliness and the Likeliness for each id for all /// of there Identifiers. /// /// \param Values It gets the Identifiers and Reliabilities of /// all connected Compare Agentss inside a vector. /// /// \return it returns a struct \c returnType containing the \c /// getCombinedCrossLikeliness() and \c getCrossLikeliness() returnType operator()( std::vector> Values) { return {getOutputLikeliness(Values), getCrossLikeliness(Values)}; } /// returns the combined Cross Likeliness via \c /// LikelinessCombinationMethod \c /// setLikelinessCombinationMethod() for all ids \c /// CrossLikeliness() \param Values the used Values LikelinessType getCombinedCrossLikeliness( const std::vector> &Values) noexcept { LikelinessType Likeliness = -1; std::vector> Agents; Agents << Values; for (auto Value : Values) { id_t id = std::get<0>(Value); IdentifierType sc = std::get<1>(Value); // calculate the cross Likeliness for this Compare agent LikelinessType realCrossLikelinessOfCompareInput = CrossLikeliness({id, sc}, Agents); if (Likeliness != -1) Likeliness = LikelinessCombinationMethod( Likeliness, realCrossLikelinessOfCompareInput); else Likeliness = realCrossLikelinessOfCompareInput; } return Likeliness; } /// returns the combined via \c CombinedInputLikelinessCombinationMethod \c /// setCombinedInputLikelinessCombinationMethod() input Likeliness \param Values /// the used Values LikelinessType getCombinedInputLikeliness( const std::vector> &Values) noexcept { LikelinessType combinedInputRel = -1; std::vector> Agents; Agents << Values; for (auto Value : Values) { LikelinessType rel = std::get<2>(Value); if (combinedInputRel != -1) combinedInputRel = CombinedInputLikelinessCombinationMethod(combinedInputRel, rel); else combinedInputRel = rel; } return combinedInputRel; } /// returns the combination via \c OutputLikelinessCombinationMethod \c /// setOutputLikelinessCombinationMethod() of the Cross Likeliness and /// input Likeliness \param Values the used Values LikelinessType getOutputLikeliness( const std::vector> &Values) noexcept { return OutputLikelinessCombinationMethod( getCombinedInputLikeliness(Values), getCombinedCrossLikeliness(Values)); } /// returns the crossConfidence for all ids \c CrossConfidence() /// \param Values the used Values std::map> getCrossLikeliness( const std::vector> &Values) noexcept { std::vector> Agents; std::map> output; std::vector output_temporary; Agents << Values; for (auto Value : Values) { id_t id = std::get<0>(Value); output_temporary.clear(); for (IdentifierType thoIdentifier : Identifiers[id]) { Symbol data; data.Identifier = thoIdentifier; data.Likeliness = CrossConfidence(id, thoIdentifier, Agents); output_temporary.push_back(data); } output.insert({id, output_temporary}); } return output; } /// Calculates the Cross Confidence /// \brief it uses the Identifier value and calculates /// the Confidence of a given agent( represented by their id ) for a given /// Identifiers in connection to all other given agents /// /// \note all combination of agents and there corresponding Cross Likeliness /// function have to be specified LikelinessType CrossConfidence(const id_t &MainAgent, const IdentifierType &TheoreticalValue, const std::vector> &CompareInputs) noexcept { LikelinessType crossReliabiability; std::vector values; for (std::pair CompareInput : CompareInputs) { if (CompareInput.first == MainAgent) continue; if (TheoreticalValue == CompareInput.second) crossReliabiability = 1; else crossReliabiability = 1 / (crossLikelinessParameter * std::abs(TheoreticalValue - CompareInput.second)); // profile Likeliness LikelinessType crossLikelinessFromProfile = getCrossLikelinessFromProfile( MainAgent, CompareInput.first, (IdentifierType)std::abs(TheoreticalValue - CompareInput.second)); values.push_back( std::max(crossReliabiability, crossLikelinessFromProfile)); } return Method(values); } /// Calculates the Cross Likeliness /// \brief it uses the Identifier value and calculates /// the Likeliness of a given agent( represented by their id ) in connection /// to all other given agents /// /// \note all combination of agents and there corresponding Cross Likeliness /// function have to be specified LikelinessType CrossLikeliness(const std::pair &MainAgent, const std::vector> &CompareInputs) noexcept { LikelinessType crossReliabiability; std::vector values; for (std::pair CompareInput : CompareInputs) { if (CompareInput.first == MainAgent.first) continue; if (MainAgent.second == CompareInput.second) crossReliabiability = 1; else crossReliabiability = 1 / (crossLikelinessParameter * std::abs(MainAgent.second - CompareInput.second)); // profile Likeliness LikelinessType crossLikelinessFromProfile = getCrossLikelinessFromProfile( MainAgent.first, CompareInput.first, (IdentifierType)std::abs(MainAgent.second - CompareInput.second)); values.push_back( std::max(crossReliabiability, crossLikelinessFromProfile)); } return Method(values); } // -------------------------------------------------------------------------- // Defining the class // -------------------------------------------------------------------------- /// adds a Cross Likeliness Profile used to get the Likeliness of the /// Identifier difference /// /// \param idA The id of the one \c Agent ( ideally the id of \c Unit to make /// it absolutely unique ) /// /// \param idB The id of the other \c Agent /// /// \param Function A shared pointer to an \c Abstraction it would use the /// difference in Identifier for its input void addCrossLikelinessProfile( //conf const id_t &idA, const id_t &idB, const std::shared_ptr &Function) noexcept { Functions.push_back({true, idA, idB, Function}); //confidence Profiles } /// sets the cross Likeliness parameter void setCrossLikelinessParameter(const LikelinessType &val) noexcept { crossLikelinessParameter = val; } /// This is the adder for the Identifiers /// \param id The id of the Agent of the Identifiers /// \param _Identifiers id specific Identifiers. This will be copied So that if /// Compares have different Identifiers they can be used correctly. \brief The /// Identifiers of all connected Compare Agents has to be known to be able to /// iterate over them void addIdentifiers(const id_t &id, //add IdentifierIdentifiers const std::vector &_Identifiers) noexcept { Identifiers.insert({id, _Identifiers}); } // ------------------------------------------------------------------------- // Combinator Settings // ------------------------------------------------------------------------- /// sets the used method to combine the values /// \param Meth the method which should be used. predefined functions in the /// struct \c predefinedMethods \c /// CONJUNCTION() \c AVERAGE() \c DISJUNCTION() void setCrossLikelinessCombinatorMethod( const std::function values)> &Meth) noexcept { Method = Meth; } /// sets the combination method for the combined cross Likeliness /// \param Meth the method which should be used. predefined functions in the /// struct \c predefinedMethods LikelinessCombinationMethod() void setLikelinessCombinationMethod( const std::function &Meth) noexcept { LikelinessCombinationMethod = Meth; } /// sets the combined input rel method /// \param Meth the method which should be used. predefined functions in the /// struct \c predefinedMethods CombinedInputLikelinessCombinationMethod() void setCombinedInputLikelinessCombinationMethod( const std::function &Meth) noexcept { CombinedInputLikelinessCombinationMethod = Meth; } /// sets the used OutputLikelinessCombinationMethod /// \param Meth the method which should be used. predefined functions in the /// struct \c predefinedMethods OutputLikelinessCombinationMethod() void setOutputLikelinessCombinationMethod( const std::function &Meth) noexcept { OutputLikelinessCombinationMethod = Meth; } // ------------------------------------------------------------------------- // Predefined Functions // ------------------------------------------------------------------------- /// This struct is a pseudo name space to have easier access to all predefined /// methods while still not overcrowding the class it self struct predefinedMethods { /// predefined combination method static LikelinessType CONJUNCTION(std::vector values) { return *std::min_element(values.begin(), values.end()); } /// predefined combination method static LikelinessType AVERAGE(std::vector values) { return std::accumulate(values.begin(), values.end(), (LikelinessType)0) / values.size(); } /// predefined combination method static LikelinessType DISJUNCTION(std::vector values) { return *std::max_element(values.begin(), values.end()); } /// predefined combination Method static LikelinessType LikelinessCombinationMethodMin(LikelinessType A, LikelinessType B) { return std::min(A, B); } /// predefined combination Method static LikelinessType LikelinessCombinationMethodMax(LikelinessType A, LikelinessType B) { return std::max(A, B); } /// predefined combination Method static LikelinessType LikelinessCombinationMethodMult(LikelinessType A, LikelinessType B) { return A * B; } /// predefined combination Method static LikelinessType LikelinessCombinationMethodAverage(LikelinessType A, LikelinessType B) { return (A + B) / 2; } /// predefined combination Method static LikelinessType CombinedInputLikelinessCombinationMethodMin(LikelinessType A, LikelinessType B) { return std::min(A, B); } /// predefined combination Method static LikelinessType CombinedInputLikelinessCombinationMethodMax(LikelinessType A, LikelinessType B) { return std::max(A, B); } /// predefined combination Method static LikelinessType CombinedInputLikelinessCombinationMethodMult(LikelinessType A, LikelinessType B) { return A * B; } /// predefined combination Method static LikelinessType CombinedInputLikelinessCombinationMethodAverage(LikelinessType A, LikelinessType B) { return (A + B) / 2; } /// predefined combination method static LikelinessType OutputLikelinessCombinationMethodMin(LikelinessType A, LikelinessType B) { return std::min(A, B); } /// predefined combination method static LikelinessType OutputLikelinessCombinationMethodMax(LikelinessType A, LikelinessType B) { return std::max(A, B); } /// predefined combination method static LikelinessType OutputLikelinessCombinationMethodMult(LikelinessType A, LikelinessType B) { return A * B; } /// predefined combination method static LikelinessType OutputLikelinessCombinationMethodAverage(LikelinessType A, LikelinessType B) { return (A + B) / 2; } }; // ------------------------------------------------------------------------- // Cleanup // ------------------------------------------------------------------------- ~CrossCombinator() { Functions.clear(); } // -------------------------------------------------------------------------- // Parameters // -------------------------------------------------------------------------- private: struct Functionblock { bool exists = false; id_t A; id_t B; std::shared_ptr Funct; }; std::map> Identifiers; /// From Maxi in his code defined as 1 can be changed by set LikelinessType crossLikelinessParameter = 1; /// Stored Cross Likeliness Functions std::vector Functions; /// Method which is used to combine the generated values std::function)> Method = predefinedMethods::AVERAGE; std::function LikelinessCombinationMethod = predefinedMethods::LikelinessCombinationMethodMin; std::function CombinedInputLikelinessCombinationMethod = predefinedMethods::CombinedInputLikelinessCombinationMethodMin; std::function OutputLikelinessCombinationMethod = predefinedMethods::OutputLikelinessCombinationMethodMin; //-------------------------------------------------------------------------------- // helper function /// very inefficient searchFunction Functionblock (*searchFunction)(std::vector vect, const id_t nameA, const id_t nameB) = [](std::vector vect, const id_t nameA, const id_t nameB) -> Functionblock { for (Functionblock tmp : vect) { if (tmp.A == nameA && tmp.B == nameB) return tmp; if (tmp.A == nameB && tmp.B == nameA) return tmp; } return Functionblock(); }; /// evaluates the corresponding LinearFunction with the Identifier difference /// \param nameA these two parameters are the unique Identifiers /// \param nameB these two parameters are the unique Identifiers /// for the LinerFunction /// /// \note it doesn't matter if they are swapped LikelinessType getCrossLikelinessFromProfile( const id_t &nameA, const id_t &nameB, const IdentifierType &IdentifierDifference) noexcept { Functionblock block = searchFunction(Functions, nameA, nameB); if (!block.exists) { LOG_WARNING(("CrossLikeliness: Block:" + std::to_string(nameA) + "," + std::to_string(nameB) + " doesn't exist, returning 0")); return 0; } return block.Funct->operator()(IdentifierDifference); } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_CROSSCOMBINATOR_H