diff --git a/apps/sa-ews2/CMakeLists.txt b/apps/sa-ews2/CMakeLists.txt new file mode 100644 index 0000000..22cff6b --- /dev/null +++ b/apps/sa-ews2/CMakeLists.txt @@ -0,0 +1,4 @@ +ROSA_add_app(sa-ews2 sa-ews2.cpp) +ROSA_add_library_dependencies(sa-ews2 ROSAConfig) +ROSA_add_library_dependencies(sa-ews2 ROSAApp) +ROSA_add_library_dependencies(sa-ews2 ROSAAgent) diff --git a/apps/sa-ews2/sa-ews2.cpp b/apps/sa-ews2/sa-ews2.cpp new file mode 100644 index 0000000..1dac18f --- /dev/null +++ b/apps/sa-ews2/sa-ews2.cpp @@ -0,0 +1,837 @@ +//===-- 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: [" << std::get(I.first) + << ", " << std::get(I.first) << ", " + << std::get(I.first) << ", " + << 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: " << 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}, + 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.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: " << HR.first << "\n" + << (BR.second ? "" : "") + << " BR result: " << BR.first << "\n" + << (SpO2.second ? "" : "") + << " SpO2 result: " << SpO2.first << "\n" + << (BPSys.second ? "" : "") + << " BPSys result: " << BPSys.first << "\n" + << (BodyTemp.second ? "" : "") + << " BodyTemp result: " << 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/docs/CommandGuide/index.rst b/docs/CommandGuide/index.rst index 4057678..3cfe91b 100755 --- a/docs/CommandGuide/index.rst +++ b/docs/CommandGuide/index.rst @@ -1,20 +1,21 @@ =========================== RoSA Applications and Tools =========================== Overview ======== Below is documentation for applications and tools .. toctree:: :caption: Applications :maxdepth: 1 sa-ews0 sa-ews1 + sa-ews2 ccam .. toctree:: :caption: Tools :maxdepth: 1 diff --git a/docs/CommandGuide/sa-ews2.rst b/docs/CommandGuide/sa-ews2.rst new file mode 100644 index 0000000..da0eeb1 --- /dev/null +++ b/docs/CommandGuide/sa-ews2.rst @@ -0,0 +1,13 @@ +SA-EWS2 - Confidence-Enhanced Early Warning Score Based on Fuzzy Logic +====================================================================== + +The application 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 `_. + +The application takes its input from a multi-column CSV file and generates its output to a multi-column CSV file. +Input and output files are to be defined as command line arguments. +A full list of available arguments is printed to the `INFO` stream when `--help` is used. + +Sample data can be used from the SA-EWS1 application.