diff --git a/apps/sa-ews1/sa-ews1.cpp b/apps/sa-ews1/sa-ews1.cpp index 87cf778..3f6b376 100644 --- a/apps/sa-ews1/sa-ews1.cpp +++ b/apps/sa-ews1/sa-ews1.cpp @@ -1,602 +1,620 @@ //===-- 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) /// Maximilian Goetzinger (maxgot@utu.fi) /// /// \date 2017-2020 /// /// \brief The application SA-EWS1 implements the case study from the paper: /// 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/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-EWS1"; /// Representation type of warning levels. /// \note Make sure it suits all defined enumeration values. using WarningScoreType = uint8_t; /// Warning levels for abstraction. 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; /// 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. /// /// \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 A abstraction to use /// \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, const Abstraction &A, ReliabilityAndConfidenceCombination &R) { using result = Optional; using input = AppTuple; using handler = std::function)>; AgentHandle Agent = App->createAgent( Name, 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 WarningScoreType Score = A(SensorValue); const ReliabilityType InputReliability = R.getInputReliability(SensorValue); return {WarningValue(Score, InputReliability)}; })); 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 = 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") + ("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 = int32_t; using HRParFun = PartialFunction; using HRLinFun = LinearFunction; using BRType = int32_t; using BRParFun = PartialFunction; using BRLinFun = LinearFunction; using SpO2Type = int32_t; using SpO2ParFun = PartialFunction; using SpO2LinFun = LinearFunction; using BPSysType = int32_t; using BPSysParFun = PartialFunction; using BPSysLinFun = LinearFunction; using BodyTempType = float; 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 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); // // Define reliabilities. // ReliabilityAndConfidenceCombination HRReliability; HRReliability.setTimeStep(1); std::shared_ptr> HRAbsRel( new HRParFun({{{0, 200}, std::make_shared(1, 0)}, {{200, 300}, std::make_shared(200, 1, 300, 0)}}, 0)); HRReliability.setAbsoluteReliabilityFunction(HRAbsRel); std::shared_ptr> HRRelSlope(new HRParFun( {{{-200, -100}, std::make_shared(-200, 0, -100, 1)}, {{-100, 100}, std::make_shared(1, 0)}, {{100, 200}, std::make_shared(100, 1, 200, 0)}}, 0)); HRReliability.setReliabilitySlopeFunction(HRRelSlope); ReliabilityAndConfidenceCombination BRReliability; BRReliability.setTimeStep(1); std::shared_ptr> BRAbsRel( new BRParFun({{{0, 40}, std::make_shared(1, 0)}, {{40, 60}, std::make_shared(40, 1, 60, 0)}}, 0)); BRReliability.setAbsoluteReliabilityFunction(BRAbsRel); std::shared_ptr> BRRelSlope( new BRParFun({{{-30, -20}, std::make_shared(-30, 0, -20, 1)}, {{-20, 20}, std::make_shared(1, 0)}, {{20, 30}, std::make_shared(20, 1, 30, 0)}}, 0)); BRReliability.setReliabilitySlopeFunction(BRRelSlope); ReliabilityAndConfidenceCombination SpO2Reliability; SpO2Reliability.setTimeStep(1); 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.setTimeStep(1); std::shared_ptr> BPSysAbsRel( new BPSysParFun( {{{0, 260}, std::make_shared(1, 0)}, {{260, 400}, std::make_shared(260, 1, 400, 0)}}, 0)); BPSysReliability.setAbsoluteReliabilityFunction(BPSysAbsRel); std::shared_ptr> BPSysRelSlope( new BPSysParFun( {{{-100, -50}, std::make_shared(-100, 0, -50, 1)}, {{-50, 50}, std::make_shared(1, 0)}, {{50, 100}, std::make_shared(50, 1, 100, 0)}}, 0)); BPSysReliability.setReliabilitySlopeFunction(BPSysRelSlope); ReliabilityAndConfidenceCombination BodyTempReliability; BodyTempReliability.setTimeStep(1); 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, HRAbstraction, HRReliability); AgentHandle BRAgent = createLowLevelAgent(App, "BR Agent", Decimation, BRAbstraction, BRReliability); AgentHandle SpO2Agent = createLowLevelAgent(App, "SpO2 Agent", Decimation, SpO2Abstraction, SpO2Reliability); AgentHandle BPSysAgent = createLowLevelAgent( App, "BPSys Agent", Decimation, BPSysAbstraction, BPSysReliability); AgentHandle BodyTempAgent = createLowLevelAgent(App, "BodyTemp Agent", Decimation, BodyTempAbstraction, 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 }; CrossCombinator BodyCrossCombinator; BodyCrossCombinator.setCrossLikelinessParameter(1.5); 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 and results in a pair of the sum of // received warning scores and their cross-reliability. 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 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 ReliabilityType crossReliability = BodyCrossCombinator.getOutputLikeliness(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)}; })); 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); + 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), CSVDataIterator()); + 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; }