diff --git a/apps/sa-ews1/sa-ews1.cpp b/apps/sa-ews1/sa-ews1.cpp index 0433e43..7fdf65b 100644 --- a/apps/sa-ews1/sa-ews1.cpp +++ b/apps/sa-ews1/sa-ews1.cpp @@ -1,326 +1,404 @@ //===-- apps/sa-ews1/sa-ews1.cpp --------------------------------*- C++ -*-===// // // The RoSA Framework -- Application SA-EWS1 // // 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-ews1/sa-ews1.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2020 /// /// \brief The application SA-EWS1 implements the case study from the paper: -/// M. Götzinger, N. Taherinejad, A. M. Rahmani, P. Liljeberg, A. Jantsch, and -/// H. Tenhunen: Enhancing the Early Warning Score System Using Data Confidence -/// DOI: 10.1007/978-3-319-58877-3_12 +/// M. Götzinger, A. Anzanpour, I. Azimi, N. TaheriNejad, and A. M. Rahmani: +/// Enhancing the Self-Aware Early Warning Score System through Fuzzified Data +/// Reliability Assessment. DOI: 10.1007/978-3-319-98551-0_1 //===----------------------------------------------------------------------===// #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/Confidence.hpp" #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-EWS1"; -/// Paths for the CSV files for simulation. -/// -///@{ -const std::string HRCSVPath = "HR.csv"; -const std::string BRCSVPath = "BR.csv"; -const std::string SpO2CSVPath = "SpO2.csv"; -const std::string BPSysCSVPath = "BPSys.csv"; -const std::string BodyTempCSVPath = "BodyTemp.csv"; -const std::string ScoreCSVPath = "Score.csv"; -///@} - -/// How many cycles of simulation to perform. -const size_t NumberOfSimulationCycles = 16; - /// Warning levels for abstraction. enum WarningScore { No = 0, Low = 1, High = 2, Emergency = 3 }; +/// 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. +/// values and setting its execution policy for decimation. +/// +/// \todo Replace Confidence with Reliability and send abstracted value together +/// with reliability value. /// /// Received values are first validated for confidence. Values which the /// validator does not mark confident are ignored. Confident values are /// abstracted into a \c WarningScore value, which is the result of the /// processing function. /// /// \note The result, \c WarningScore, is returned as \c uint32_t because /// enumeration types are not integrated into built-in types. Hence, a master /// to these agents receives its input as \c uint32_t 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 CC confidence validator to use /// \param A abstraction to use /// /// \return handle for the new agent template AgentHandle createLowLevelAgent(std::unique_ptr &App, const std::string &Name, + const size_t Decimation, const Confidence &CC, const Abstraction &A) { using handler = std::function(std::pair)>; using result = Optional; - return App->createAgent( + AgentHandle Agent = App->createAgent( Name, handler([&, Name](std::pair I) -> result { LOG_INFO_STREAM << "\n******\n" << Name << " " << (I.second ? "" : "") << " value: " << I.first << "\n******\n"; return (I.second && CC(I.first)) ? result(A(I.first)) : result(); })); + App->setExecutionPolicy(Agent, AppExecutionPolicy::decimation(Decimation)); + return Agent; } -int main(void) { +/// Helper function to print and 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[]) { + /// Paths for the CSV files for simulation. + /// + ///@{ + std::string DataCSVPath; + std::string ScoreCSVPath; + ///@} + + /// Decimation of sensors and agents. + size_t Decimation = 1; + + /// How many cycles of simulation to perform. + size_t NumberOfSimulationCycles = 16; + + // 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 fr the CSV file to write output scores", + cxxopts::value(ScoreCSVPath), "file") + ("d,decimation", "Decimation of sensors and agents", + cxxopts::value(Decimation)->default_value("1")) + ("c,cycles", "Number of simulation cycles to perform", + cxxopts::value(NumberOfSimulationCycles)->default_value("16")); + + auto Args = Options.parse(argc, argv); + if (Args.count("data") == 0) { + throw std::invalid_argument("Argument --data must be defined."); + } + if (Args.count("score") == 0) { + throw std::invalid_argument("Argument --score must be defined."); + } + } catch (const cxxopts::OptionException &e) { + logErrorAndExit(e.what(), 1); + } catch (const std::invalid_argument &e) { + logErrorAndExit(e.what(), 1); + } + LOG_INFO_STREAM << '\n' << library_string() << " -- " << Color::Red << AppName << "app" << Color::Default << '\n' << Color::Yellow << "CSV files are read from and written to the current working directory." << Color::Default << '\n'; std::unique_ptr App = Application::create(AppName); // // 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 = App->createSensor("HR Sensor"); - AgentHandle BRSensor = App->createSensor("BR Sensor"); - AgentHandle SpO2Sensor = App->createSensor("SpO2 Sensor"); - AgentHandle BPSysSensor = App->createSensor("BPSys Sensor"); - AgentHandle BodyTempSensor = App->createSensor("BodyTemp Sensor"); + 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 confidence validators. // // Lower bounds are inclusive and upper bounds are exclusive. Confidence HRConfidence(0, 501); Confidence BRConfidence(0, 301); Confidence SpO2Confidence(0, 101); - Confidence BPSysConfidence(0,501); + Confidence BPSysConfidence(0, 501); Confidence BodyTempConfidence(-60, nextRepresentableFloatingPoint(50.0f)); // // Define abstractions. // RangeAbstraction HRAbstraction( {{{0, 40}, Emergency}, {{40, 51}, High}, {{51, 60}, Low}, {{60, 100}, No}, {{100, 110}, Low}, {{110, 129}, High}, {{129, 200}, Emergency}}, Emergency); RangeAbstraction BRAbstraction({{{0, 9}, High}, {{9, 14}, No}, {{14, 20}, Low}, {{20, 29}, High}, {{29, 50}, Emergency}}, Emergency); RangeAbstraction SpO2Abstraction({{{1, 85}, Emergency}, {{85, 90}, High}, {{90, 95}, Low}, {{95, 100}, No}}, Emergency); RangeAbstraction BPSysAbstraction( {{{0, 70}, Emergency}, {{70, 81}, High}, {{81, 101}, Low}, {{101, 149}, No}, {{149, 169}, Low}, {{169, 179}, High}, {{179, 200}, Emergency}}, Emergency); RangeAbstraction BodyTempAbstraction( {{{0.f, 28.f}, Emergency}, {{28.f, 32.f}, High}, {{32.f, 35.f}, Low}, {{35.f, 38.f}, No}, {{38.f, 39.5f}, High}, {{39.5f, 100.f}, Emergency}}, Emergency); // // Create low-level application agents with \c createLowLevelAgent. // LOG_INFO("Creating low-level agents."); - AgentHandle HRAgent = - createLowLevelAgent(App, "HR Agent", HRConfidence, HRAbstraction); - AgentHandle BRAgent = - createLowLevelAgent(App, "BR Agent", BRConfidence, BRAbstraction); - AgentHandle SpO2Agent = - createLowLevelAgent(App, "SpO2 Agent", SpO2Confidence, SpO2Abstraction); + AgentHandle HRAgent = createLowLevelAgent(App, "HR Agent", Decimation, + HRConfidence, HRAbstraction); + AgentHandle BRAgent = createLowLevelAgent(App, "BR Agent", Decimation, + BRConfidence, BRAbstraction); + AgentHandle SpO2Agent = createLowLevelAgent(App, "SpO2 Agent", Decimation, + SpO2Confidence, SpO2Abstraction); AgentHandle BPSysAgent = createLowLevelAgent( - App, "BPSys Agent", BPSysConfidence, BPSysAbstraction); - AgentHandle BodyTempAgent = createLowLevelAgent( - App, "BodyTemp Agent", BodyTempConfidence, BodyTempAbstraction); + App, "BPSys Agent", Decimation, BPSysConfidence, BPSysAbstraction); + AgentHandle BodyTempAgent = + createLowLevelAgent(App, "BodyTemp Agent", Decimation, BodyTempConfidence, + BodyTempAbstraction); // // 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."); // The new agent logs its input values and results in the the sum of them. + // \todo Additionally calculate cross-reliability for the warning score and + // result a pair of those values. AgentHandle BodyAgent = App->createAgent( "Body Agent", std::function( std::pair, std::pair, std::pair, std::pair, std::pair)>( [](std::pair HR, std::pair BR, std::pair SpO2, std::pair BPSys, std::pair BodyTemp) -> Optional { LOG_INFO_STREAM << "\n*******\nBody Agent trigged with values:\n" << (HR.second ? "" : "") << " HR warning score: " << HR.first << "\n" << (BR.second ? "" : "") << " BR warning score: " << BR.first << "\n" << (SpO2.second ? "" : "") << " SpO2 warning score: " << SpO2.first << "\n" << (BPSys.second ? "" : "") << " BPSys warning score: " << BPSys.first << "\n" << (BodyTemp.second ? "" : "") << " BodyTemp warning score: " << BodyTemp.first << "\n******\n"; return {HR.first + BR.first + SpO2.first + BPSys.first + BodyTemp.first}; })); + 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, 0, HRAgent, "HR Agent Channel"); App->connectAgents(BodyAgent, 1, BRAgent, "BR Agent Channel"); App->connectAgents(BodyAgent, 2, SpO2Agent, "SpO2 Agent Channel"); App->connectAgents(BodyAgent, 3, BPSysAgent, "BPSys Agent Channel"); App->connectAgents(BodyAgent, 4, 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::CSVWriter ScoreWriter(ScoreCSV); // 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. + // \todo Print the pairs that are sent by BodyAgent into a multi-column CSV + // file. AgentHandle LoggerAgent = App->createAgent( "Logger Agent", std::function(std::pair)>( [&ScoreWriter](std::pair Score) -> Optional { if (Score.second) { // 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 CSVInt = csv::CSVIterator; - using CSVFloat = csv::CSVIterator; - - std::ifstream HRCSV(HRCSVPath); - App->registerSensorValues(HRSensor, CSVInt(HRCSV), CSVInt()); - - std::ifstream BRCSV(BRCSVPath); - App->registerSensorValues(BRSensor, CSVInt(BRCSV), CSVInt()); - - std::ifstream SpO2CSV(SpO2CSVPath); - App->registerSensorValues(SpO2Sensor, CSVInt(SpO2CSV), CSVInt()); - - std::ifstream BPSysCSV(BPSysCSVPath); - App->registerSensorValues(BPSysSensor, CSVInt(BPSysCSV), CSVInt()); - - std::ifstream BodyTempCSV(BodyTempCSVPath); - App->registerSensorValues(BodyTempSensor, CSVFloat(BodyTempCSV), CSVFloat()); + using CSVDataIterator = + CSVIterator; + std::ifstream DataCSV(DataCSVPath); + auto [HRRange, BRRange, SpO2Range, BPSysRange, BodyTempRange] = + splitTupleIterator(CSVDataIterator(DataCSV), 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; }