diff --git a/apps/sa-ews1/sa-ews1.cpp b/apps/sa-ews1/sa-ews1.cpp index 7fdf65b..ebcc645 100644 --- a/apps/sa-ews1/sa-ews1.cpp +++ b/apps/sa-ews1/sa-ews1.cpp @@ -1,404 +1,599 @@ //===-- 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/Confidence.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 { No = 0, Low = 1, High = 2, Emergency = 3 }; +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. /// -/// \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. +/// 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 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. +/// \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 CC confidence validator to use /// \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 Confidence &CC, - const Abstraction &A) { - using handler = std::function(std::pair)>; - using result = Optional; +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 { + 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(); + << " 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 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); + // + // 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 HRSensor = createSensor(App, "HR Sensor", Decimation); + AgentHandle BRSensor = createSensor(App, "BR Sensor", Decimation); AgentHandle SpO2Sensor = - createSensor(App, "SpO2 Sensor", Decimation); + createSensor(App, "SpO2 Sensor", Decimation); AgentHandle BPSysSensor = - createSensor(App, "BPSys Sensor", Decimation); + createSensor(App, "BPSys Sensor", Decimation); AgentHandle BodyTempSensor = - createSensor(App, "BodyTemp Sensor", Decimation); + 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 BodyTempConfidence(-60, - nextRepresentableFloatingPoint(50.0f)); - // // Define abstractions. // - RangeAbstraction HRAbstraction( + 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 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( + 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( + 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, - HRConfidence, HRAbstraction); + HRAbstraction, HRReliability); AgentHandle BRAgent = createLowLevelAgent(App, "BR Agent", Decimation, - BRConfidence, BRAbstraction); + BRAbstraction, BRReliability); AgentHandle SpO2Agent = createLowLevelAgent(App, "SpO2 Agent", Decimation, - SpO2Confidence, SpO2Abstraction); + SpO2Abstraction, SpO2Reliability); AgentHandle BPSysAgent = createLowLevelAgent( - App, "BPSys Agent", Decimation, BPSysConfidence, BPSysAbstraction); + App, "BPSys Agent", Decimation, BPSysAbstraction, BPSysReliability); AgentHandle BodyTempAgent = - createLowLevelAgent(App, "BodyTemp Agent", Decimation, BodyTempConfidence, - BodyTempAbstraction); + 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."); - // 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. + // 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 { + 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" + << " HR result: " << HR.first << "\n" << (BR.second ? "" : "") - << " BR warning score: " << BR.first << "\n" + << " BR result: " << BR.first << "\n" << (SpO2.second ? "" : "") - << " SpO2 warning score: " << SpO2.first << "\n" + << " SpO2 result: " << SpO2.first << "\n" << (BPSys.second ? "" : "") - << " BPSys warning score: " << BPSys.first << "\n" + << " BPSys result: " << BPSys.first << "\n" << (BodyTemp.second ? "" : "") - << " BodyTemp warning score: " << BodyTemp.first + << " BodyTemp result: " << BodyTemp.first << "\n******\n"; - return {HR.first + BR.first + SpO2.first + BPSys.first + - BodyTemp.first}; + + 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, 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"); + 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::CSVWriter ScoreWriter(ScoreCSV); + csv::CSVTupleWriter 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. + using logger_result = AppTuple; 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; - } + 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; + 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; }