diff --git a/include/cse.h b/include/cse.h index ac7c114b1..a6059c945 100644 --- a/include/cse.h +++ b/include/cse.h @@ -434,6 +434,33 @@ int64_t cse_observer_slave_get_real_samples( cse_step_number steps[], cse_time_point times[]); +/** + * Retrieves two time-synchronized series of observed values for two real variables. + * + * \param [in] observer the observer + * \param [in] slave1 index of the first slave + * \param [in] variableIndex1 the first variable index + * \param [in] slave2 index of the second slave + * \param [in] variableIndex2 the second variable index + * \param [in] fromStep the step number to start from + * \param [in] nSamples the number of samples to read + * \param [out] values1 the first series of observed values + * \param [out] values2 the second series of observed values + * + * \returns + * The number of samples actually read, which may be smaller than `nSamples`. + */ +int64_t cse_observer_slave_get_real_synchronized_series( + cse_observer* observer, + cse_slave_index slave1, + cse_variable_index variableIndex1, + cse_slave_index slave2, + cse_variable_index variableIndex2, + cse_step_number fromStep, + size_t nSamples, + double values1[], + double values2[]); + /** * Sets the values of integer variables for one slave. * diff --git a/include/cse/observer/time_series_observer.hpp b/include/cse/observer/time_series_observer.hpp index 950cee703..af1f004f0 100644 --- a/include/cse/observer/time_series_observer.hpp +++ b/include/cse/observer/time_series_observer.hpp @@ -50,10 +50,10 @@ class time_series_observer : public time_series_provider time_point currentTime) override; void simulator_step_complete( - simulator_index index, - step_number lastStep, - duration lastStepSize, - time_point currentTime) override; + simulator_index index, + step_number lastStep, + duration lastStepSize, + time_point currentTime) override; /** * Start observing a variable. @@ -97,6 +97,15 @@ class time_series_observer : public time_series_provider time_point tEnd, gsl::span steps) override; + std::size_t get_synchronized_real_series( + simulator_index sim1, + variable_index variableIndex1, + simulator_index sim2, + variable_index variableIndex2, + step_number fromStep, + gsl::span values1, + gsl::span values2) override; + ~time_series_observer() noexcept override; private: diff --git a/include/cse/observer/time_series_provider.hpp b/include/cse/observer/time_series_provider.hpp index c9d575f88..9baba612b 100644 --- a/include/cse/observer/time_series_provider.hpp +++ b/include/cse/observer/time_series_provider.hpp @@ -98,6 +98,29 @@ class time_series_provider : public observer time_point tBegin, time_point tEnd, gsl::span steps) = 0; + + /** + * Retrieves two time-synchronized series of observed values for two real variables. + * + * \param [in] sim1 index of the first simulator + * \param [in] variableIndex1 the first variable index + * \param [in] sim2 index of the second simulator + * \param [in] variableIndex2 the second variable index + * \param [in] fromStep the step number to start from + * \param [out] values1 the first series of observed values + * \param [out] values2 the second series of observed values + * + * Returns the number of samples actually read, which may be smaller + * than the sizes of `values1` and `values2`. + */ + virtual std::size_t get_synchronized_real_series( + simulator_index sim1, + variable_index variableIndex1, + simulator_index sim2, + variable_index variableIndex2, + step_number fromStep, + gsl::span values1, + gsl::span values2) = 0; }; diff --git a/src/c/cse.cpp b/src/c/cse.cpp index 2939a2f17..7b3a3ebef 100644 --- a/src/c/cse.cpp +++ b/src/c/cse.cpp @@ -468,6 +468,38 @@ int64_t cse_observer_slave_get_real_samples( } } +int64_t cse_observer_slave_get_real_synchronized_series( + cse_observer* observer, + cse_slave_index slave1, + cse_variable_index variableIndex1, + cse_slave_index slave2, + cse_variable_index variableIndex2, + cse_step_number fromStep, + size_t nSamples, + double values1[], + double values2[]) +{ + try { + std::vector timePoints(nSamples); + const auto obs = std::dynamic_pointer_cast(observer->cpp_observer); + if (!obs) { + throw std::invalid_argument("Invalid observer! The provided observer must be a time_series_observer."); + } + size_t samplesRead = obs->get_synchronized_real_series( + slave1, + variableIndex1, + slave2, + variableIndex2, + fromStep, + gsl::make_span(values1, nSamples), + gsl::make_span(values2, nSamples)); + return samplesRead; + } catch (...) { + handle_current_exception(); + return failure; + } +} + int64_t cse_observer_slave_get_integer_samples( cse_observer* observer, cse_slave_index slave, diff --git a/src/cpp/observer/time_series_observer.cpp b/src/cpp/observer/time_series_observer.cpp index 549d02b0d..5f8f26ec8 100644 --- a/src/cpp/observer/time_series_observer.cpp +++ b/src/cpp/observer/time_series_observer.cpp @@ -183,6 +183,12 @@ class time_series_observer::single_slave_observer steps[1] = lastStep; } + const std::map get_real_samples_map(variable_index idx) + { + std::lock_guard lock(lock_); + return realSamples_.at(idx); + } + private: std::map> realSamples_; std::map> intSamples_; @@ -289,6 +295,41 @@ void time_series_observer::get_step_numbers( slaveObservers_.at(sim)->get_step_numbers(tBegin, tEnd, steps); } +std::size_t time_series_observer::get_synchronized_real_series( + simulator_index sim1, + variable_index variableIndex1, + simulator_index sim2, + variable_index variableIndex2, + step_number fromStep, + gsl::span values1, + gsl::span values2) +{ + CSE_INPUT_CHECK(values1.size() == values2.size()); + + const auto realSamples1 = slaveObservers_.at(sim1)->get_real_samples_map(variableIndex1); + const auto realSamples2 = slaveObservers_.at(sim2)->get_real_samples_map(variableIndex2); + + if (realSamples1.empty() || realSamples2.empty()) { + throw std::out_of_range("Samples for both variables not recorded yet!"); + } + auto lastStep1 = realSamples1.rbegin()->first; + auto lastStep2 = realSamples2.rbegin()->first; + + size_t samplesRead = 0; + for (auto step = fromStep; step < fromStep + static_cast(values1.size()); step++) { + auto sample1 = realSamples1.find(step); + auto sample2 = realSamples2.find(step); + if (sample1 != realSamples1.end() && sample2 != realSamples2.end()) { + values1[samplesRead] = sample1->second; + values2[samplesRead] = sample2->second; + samplesRead++; + } else if (lastStep1 < step || lastStep2 < step) { + break; + } + } + return samplesRead; +} + time_series_observer::~time_series_observer() noexcept = default; } // namespace cse diff --git a/test/cpp/CMakeLists.txt b/test/cpp/CMakeLists.txt index 1f6be39bf..0e093abc3 100644 --- a/test/cpp/CMakeLists.txt +++ b/test/cpp/CMakeLists.txt @@ -8,6 +8,7 @@ set(tests "trend_buffer_test" "scenario_manager_test" "scenario_parser_test" + "synchronized_xy_series_test" ) set(unittests "async_slave_unittest" diff --git a/test/cpp/synchronized_xy_series_test.cpp b/test/cpp/synchronized_xy_series_test.cpp new file mode 100644 index 000000000..86bcaec2f --- /dev/null +++ b/test/cpp/synchronized_xy_series_test.cpp @@ -0,0 +1,78 @@ +#include "mock_slave.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define REQUIRE(test) \ + if (!(test)) throw std::runtime_error("Requirement not satisfied: " #test) + +int main() +{ + try { + constexpr cse::time_point startTime = cse::to_time_point(0.0); + constexpr cse::time_point midTime = cse::to_time_point(1.0); + constexpr cse::time_point endTime = cse::to_time_point(2.0); + constexpr cse::duration stepSize = cse::to_duration(0.1); + + cse::log::set_global_output_level(cse::log::level::debug); + + auto algorithm = std::make_shared(stepSize); + auto execution = cse::execution(startTime, algorithm); + + auto observer = std::make_shared(); + execution.add_observer(observer); + + double x1 = 0; + auto simIndex1 = execution.add_slave( + cse::make_pseudo_async(std::make_unique([&x1](double x) { return x + x1++; })), "slave uno"); + + double x2 = 0; + auto simIndex2 = execution.add_slave( + cse::make_pseudo_async(std::make_unique([&x2](double x) { return x + x2++; })), "slave dos"); + + algorithm->set_stepsize_decimation_factor(simIndex2, 2); + + auto variableId1 = cse::variable_id{simIndex1, cse::variable_type::real, 0}; + auto variableId2 = cse::variable_id{simIndex2, cse::variable_type::real, 0}; + + observer->start_observing(variableId1); + + // Run the simulation + auto simResult = execution.simulate_until(midTime); + REQUIRE(simResult.get()); + + observer->start_observing(variableId2); + + simResult = execution.simulate_until(endTime); + REQUIRE(simResult.get()); + + const int numSamples = 20; + const cse::variable_index varIndex = 0; + double realValues1[numSamples]; + double realValues2[numSamples]; + + size_t samplesRead = observer->get_synchronized_real_series(simIndex1, varIndex, simIndex2, varIndex, 5, realValues1, realValues2); + REQUIRE(samplesRead == 5); + + double expectedReals1[5] = {12.0, 14.0, 16.0, 18.0, 20.0}; + double expectedReals2[5] = {6.0, 7.0, 8.0, 9.0, 10.0}; + + for (size_t i = 0; i < samplesRead; i++) { + REQUIRE(std::fabs(realValues1[i] - expectedReals1[i]) < 1.0e9); + REQUIRE(std::fabs(realValues2[i] - expectedReals2[i]) < 1.0e9); + } + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +}