diff --git a/include/cosim/algorithm/simulator.hpp b/include/cosim/algorithm/simulator.hpp index b3c9aa28..52925796 100644 --- a/include/cosim/algorithm/simulator.hpp +++ b/include/cosim/algorithm/simulator.hpp @@ -133,6 +133,55 @@ class simulator : public manipulable virtual step_result do_step( time_point currentT, duration deltaT) = 0; + + /// A type used for references to saved states (see `save_state()`). + using state_index = int; + + /** + * Saves the current state. + * + * This will create and store a copy of the simulator's current internal + * state, so that it can be restored at a later time. The copy is stored + * internally in the simulator, and must be referred to by the returned + * `state_index`. The index is only valid for this particular simulator. + * + * The function may be called at any point after `setup()` has been called. + */ + virtual state_index save_state() = 0; + + /** + * Saves the current state, overwriting a previously-saved state. + * + * This function does the same as `save_state()`, except that it + * overwrites a state which has previously been stored by that function. + * The old index thereafter refers to the newly-saved state. + */ + virtual void save_state(state_index stateIndex) = 0; + + /** + * Restores a previously-saved state. + * + * This restores the simulator to a state which has previously been saved + * using `save_state()`. + * + * Note that the saved state is supposed to be the *complete and exact* + * state of the simulator at the moment `save_state()` was called. For example, + * if the state was saved while the simulator was in initialisation mode + * (between `setup()` and `start_simulation()`), then it will be restored + * in that mode, and `start_simulation()` must be called before the + * simulation can start. Similarly, if it is saved at logical time `t`, + * then the first `do_step()` call after restoration must start at `t`. + */ + virtual void restore_state(state_index stateIndex) = 0; + + /** + * Frees all resources (e.g. memory) associated with a saved state. + * + * After this, the state may no longer be restored with `restore_state()`, + * nor may it be overwritten with `save_state(state_index)`. The + * implementation is free to reuse the same `state_index` at a later point. + */ + virtual void release_state(state_index stateIndex) = 0; }; } // namespace cosim diff --git a/include/cosim/fmi/v1/fmu.hpp b/include/cosim/fmi/v1/fmu.hpp index ce3971ad..0ab9d68c 100644 --- a/include/cosim/fmi/v1/fmu.hpp +++ b/include/cosim/fmi/v1/fmu.hpp @@ -165,6 +165,11 @@ class slave_instance : public fmi::slave_instance gsl::span variables, gsl::span values) override; + state_index save_state() override; + void save_state(state_index overwriteState) override; + void restore_state(state_index state) override; + void release_state(state_index state) override; + // fmi::slave_instance methods std::shared_ptr fmu() const override { diff --git a/include/cosim/fmi/v2/fmu.hpp b/include/cosim/fmi/v2/fmu.hpp index c383c001..e6958608 100644 --- a/include/cosim/fmi/v2/fmu.hpp +++ b/include/cosim/fmi/v2/fmu.hpp @@ -21,9 +21,11 @@ #include #include #include +#include struct fmi2_import_t; +using fmi2_FMU_state_t = void*; namespace cosim @@ -165,6 +167,11 @@ class slave_instance : public fmi::slave_instance gsl::span variables, gsl::span values) override; + state_index save_state() override; + void save_state(state_index stateIndex) override; + void restore_state(state_index stateIndex) override; + void release_state(state_index stateIndex) override; + // fmi::slave_instance methods std::shared_ptr fmu() const override { @@ -178,6 +185,14 @@ class slave_instance : public fmi::slave_instance fmi2_import_t* fmilib_handle() const; private: + struct saved_state + { + fmi2_FMU_state_t fmuState = nullptr; + bool setupComplete = false; + bool simStarted = false; + }; + void copy_current_state(saved_state& state); + std::shared_ptr fmu_; fmi2_import_t* handle_; @@ -185,6 +200,9 @@ class slave_instance : public fmi::slave_instance bool simStarted_ = false; std::string instanceName_; + + std::vector savedStates_; + std::queue savedStatesFreelist_; }; diff --git a/include/cosim/slave.hpp b/include/cosim/slave.hpp index 48201bad..8f0576be 100644 --- a/include/cosim/slave.hpp +++ b/include/cosim/slave.hpp @@ -23,6 +23,7 @@ namespace cosim { + /** * An interface for classes that represent co-simulation slaves. * @@ -276,6 +277,59 @@ class slave set_boolean_variables(boolean_variables, boolean_values); set_string_variables(string_variables, string_values); } + + /// A type used for references to saved states (see `save_state()`). + using state_index = int; + + /** + * Saves the current state. + * + * This will create and store a copy of the slave's current internal + * state, so that it can be restored at a later time. The copy is stored + * internally in the slave, and must be referred to by the returned + * `state_index`. The index is only valid for this particular slave. + * + * The function may be called at any point after `setup()` has been called. + * + * \pre `this->model_description().can_save_state` + */ + virtual state_index save_state() = 0; + + /** + * Saves the current state, overwriting a previously-saved state. + * + * This function does the same as `save_state()`, except that it + * overwrites a state which has previously been stored by that function. + * The old index thereafter refers to the newly-saved state. + * + * \pre `this->model_description().can_save_state` + */ + virtual void save_state(state_index stateIndex) = 0; + + /** + * Restores a previously-saved state. + * + * This restores the slave to a state which has previously been saved + * using `save_state()`. + * + * Note that the saved state is supposed to be the *complete and exact* + * state of the slave at the moment `save_state()` was called. For example, + * if the state was saved while the slave was in initialisation mode + * (between `setup()` and `start_simulation()`), then it will be restored + * in that mode, and `start_simulation()` must be called before the + * simulation can start. Similarly, if it is saved at logical time `t`, + * then the first `do_step()` call after restoration must start at `t`. + */ + virtual void restore_state(state_index stateIndex) = 0; + + /** + * Frees all resources (e.g. memory) associated with a saved state. + * + * After this, the state may no longer be restored with `restore_state()`, + * nor may it be overwritten with `save_state(state_index)`. The + * implementation is free to reuse the same `state_index` at a later point. + */ + virtual void release_state(state_index stateIndex) = 0; }; diff --git a/src/cosim/fmi/v1/fmu.cpp b/src/cosim/fmi/v1/fmu.cpp index f7fc72cc..76152847 100644 --- a/src/cosim/fmi/v1/fmu.cpp +++ b/src/cosim/fmi/v1/fmu.cpp @@ -549,6 +549,38 @@ void slave_instance::set_string_variables( } +slave::state_index slave_instance::save_state() +{ + throw error( + make_error_code(errc::unsupported_feature), + "Getting and setting state not supported in FMI 1.0"); +} + + +void slave_instance::save_state(state_index) +{ + throw error( + make_error_code(errc::unsupported_feature), + "Getting and setting state not supported in FMI 1.0"); +} + + +void slave_instance::restore_state(state_index) +{ + throw error( + make_error_code(errc::unsupported_feature), + "Getting and setting state not supported in FMI 1.0"); +} + + +void slave_instance::release_state(state_index) +{ + throw error( + make_error_code(errc::unsupported_feature), + "Getting and setting state not supported in FMI 1.0"); +} + + std::shared_ptr slave_instance::v1_fmu() const { return fmu_; diff --git a/src/cosim/fmi/v2/fmu.cpp b/src/cosim/fmi/v2/fmu.cpp index 7267c380..3b5aaec9 100644 --- a/src/cosim/fmi/v2/fmu.cpp +++ b/src/cosim/fmi/v2/fmu.cpp @@ -569,6 +569,55 @@ void slave_instance::set_string_variables( } +slave::state_index slave_instance::save_state() +{ + saved_state currentState; + copy_current_state(currentState); + if (savedStatesFreelist_.empty()) { + savedStates_.push_back(currentState); + return static_cast(savedStates_.size()-1); + } else { + const auto stateIndex = savedStatesFreelist_.front(); + savedStatesFreelist_.pop(); + savedStates_.at(stateIndex) = currentState; + return stateIndex; + } +} + + +void slave_instance::save_state(state_index stateIndex) +{ + copy_current_state(savedStates_.at(stateIndex)); +} + + +void slave_instance::restore_state(state_index stateIndex) +{ + const auto& state = savedStates_.at(stateIndex); + const auto status = fmi2_import_set_fmu_state(handle_, state.fmuState); + if (status != fmi2_status_ok && status != fmi2_status_warning) { + throw error( + make_error_code(errc::model_error), + last_log_record(instanceName_).message); + } + setupComplete_ = state.setupComplete; + simStarted_ = state.simStarted; +} + + +void slave_instance::release_state(state_index state) +{ + auto fmuState = savedStates_.at(state).fmuState; + savedStatesFreelist_.push(state); + const auto status = fmi2_import_free_fmu_state(handle_, &fmuState); + if (status != fmi2_status_ok && status != fmi2_status_warning) { + throw error( + make_error_code(errc::model_error), + last_log_record(instanceName_).message); + } +} + + std::shared_ptr slave_instance::v2_fmu() const { return fmu_; @@ -581,6 +630,24 @@ fmi2_import_t* slave_instance::fmilib_handle() const } +void slave_instance::copy_current_state(saved_state& state) +{ + if (!fmi2_import_get_capability(handle_, fmi2_cs_canGetAndSetFMUstate)) { + throw error( + make_error_code(errc::unsupported_feature), + instanceName_ + ": FMU does not support state saving"); + } + const auto status = fmi2_import_get_fmu_state(handle_, &state.fmuState); + if (status != fmi2_status_ok && status != fmi2_status_warning) { + throw error( + make_error_code(errc::model_error), + last_log_record(instanceName_).message); + } + state.setupComplete = setupComplete_; + state.simStarted = simStarted_; +} + + } // namespace v2 } // namespace fmi } // namespace cosim diff --git a/src/cosim/proxy/remote_slave.cpp b/src/cosim/proxy/remote_slave.cpp index 11444721..2ba9fd87 100644 --- a/src/cosim/proxy/remote_slave.cpp +++ b/src/cosim/proxy/remote_slave.cpp @@ -202,6 +202,34 @@ void cosim::proxy::remote_slave::set_string_variables(gsl::span variables, gsl::span values) override; + state_index save_state() override; + + void save_state(state_index stateIndex) override; + + void restore_state(state_index stateIndex) override; + + void release_state(state_index stateIndex) override; + ~remote_slave() override; private: diff --git a/src/cosim/slave_simulator.cpp b/src/cosim/slave_simulator.cpp index f985ec2c..522a99ef 100644 --- a/src/cosim/slave_simulator.cpp +++ b/src/cosim/slave_simulator.cpp @@ -6,6 +6,7 @@ #include "cosim/slave_simulator.hpp" #include "cosim/error.hpp" +#include "cosim/exception.hpp" #include #include @@ -31,86 +32,6 @@ struct var_view_type using type = std::string_view; }; -/** - * Helper class which checks, sets and resets the state variable for - * an `slave_simulator`. - * - * The constructors of this class take a reference to the `slave_state` - * variable to be managed, and immediately set it to `indeterminate`. - * On destruction, the managed variable will be automatically set to - * a specified value, or, if an exception is currently "in flight", to - * the special `error` value. - */ -class state_guard -{ -public: - /** - * Constructs a `state_guard` that sets `stateVariable` to `finalState` - * on destruction. - */ - state_guard(slave_state& stateVariable, slave_state finalState) - : stateVariable_(&stateVariable) - , finalState_(finalState) - { - stateVariable = slave_state::indeterminate; - } - - /** - * Constructs a `state_guard` that resets `stateVariable` to its original - * value on destruction. - */ - state_guard(slave_state& stateVariable) - : state_guard(stateVariable, stateVariable) - { } - - /** - * Manually sets the managed variable to its final value and relinquishes - * control of it. Does not check for exceptions. - */ - void reset() noexcept - { - if (stateVariable_ != nullptr) { - *stateVariable_ = finalState_; - stateVariable_ = nullptr; - } - } - - ~state_guard() noexcept - { - if (stateVariable_ != nullptr) { - if (std::uncaught_exceptions()) { - *stateVariable_ = slave_state::error; - } else { - *stateVariable_ = finalState_; - } - } - } - - // Disallow copying, so that we don't inadvertently end up in a situation - // where multiple `state_guard` objects try to manage the same state. - state_guard(const state_guard&) = delete; - state_guard& operator=(const state_guard&) = delete; - - state_guard(state_guard&& other) noexcept - : stateVariable_(other.stateVariable_) - , finalState_(other.finalState_) - { - other.stateVariable_ = nullptr; - } - state_guard& operator=(state_guard&& other) noexcept - { - stateVariable_ = other.stateVariable_; - finalState_ = other.finalState_; - other.stateVariable_ = nullptr; - return *this; - } - -private: - slave_state* stateVariable_; - slave_state finalState_; -}; - - template struct get_variable_cache { @@ -511,13 +432,38 @@ class slave_simulator::impl time_point currentT, duration deltaT) { - set_variables(deltaT); const auto result = slave_->do_step(currentT, deltaT); get_variables(deltaT); return result; } + simulator::state_index save_state() + { + check_state_saving_allowed(); + set_variables(duration::zero()); + return slave_->save_state(); + } + + void save_state(simulator::state_index stateIndex) + { + check_state_saving_allowed(); + set_variables(duration::zero()); + slave_->save_state(stateIndex); + } + + void restore_state(simulator::state_index stateIndex) + { + check_state_saving_allowed(); + slave_->restore_state(stateIndex); + get_variables(duration::zero()); + } + + void release_state(simulator::state_index stateIndex) + { + slave_->release_state(stateIndex); + } + private: void set_variables(duration deltaT) { @@ -583,6 +529,17 @@ class slave_simulator::impl } } + void check_state_saving_allowed() const + { + if (modifiedRealVariables_.empty() && modifiedIntegerVariables_.empty() && + modifiedBooleanVariables_.empty() && modifiedStringVariables_.empty()) { + return; + } + throw error( + make_error_code(errc::unsupported_feature), + "Cannot save or restore subsimulator state when variable modifiers are active"); + } + private: std::shared_ptr slave_; std::string name_; @@ -611,7 +568,6 @@ slave_simulator::slave_simulator( std::shared_ptr slave, std::string_view name) : pimpl_(std::make_unique(std::move(slave), name)) - , state_(slave_state::created) { } @@ -631,7 +587,6 @@ std::string slave_simulator::name() const cosim::model_description slave_simulator::model_description() const { - COSIM_PRECONDITION(state_ != slave_state::error); return pimpl_->model_description(); } @@ -776,8 +731,6 @@ void slave_simulator::setup( std::optional stopTime, std::optional relativeTolerance) { - COSIM_PRECONDITION(state_ == slave_state::created); - state_guard guard(state_, slave_state::initialisation); return pimpl_->setup(startTime, stopTime, relativeTolerance); } @@ -788,8 +741,6 @@ void slave_simulator::do_iteration() void slave_simulator::start_simulation() { - COSIM_PRECONDITION(state_ == slave_state::initialisation); - state_guard guard(state_, slave_state::simulation); return pimpl_->start_simulation(); } @@ -797,10 +748,28 @@ step_result slave_simulator::do_step( time_point currentT, duration deltaT) { - COSIM_PRECONDITION(state_ == slave_state::simulation); - state_guard guard(state_); return pimpl_->do_step(currentT, deltaT); } +simulator::state_index slave_simulator::save_state() +{ + return pimpl_->save_state(); +} + +void slave_simulator::save_state(state_index stateIndex) +{ + pimpl_->save_state(stateIndex); +} + +void slave_simulator::restore_state(state_index stateIndex) +{ + pimpl_->restore_state(stateIndex); +} + +void slave_simulator::release_state(state_index stateIndex) +{ + pimpl_->release_state(stateIndex); +} + } // namespace cosim diff --git a/src/cosim/slave_simulator.hpp b/src/cosim/slave_simulator.hpp index bdf07ee6..d49d3008 100644 --- a/src/cosim/slave_simulator.hpp +++ b/src/cosim/slave_simulator.hpp @@ -20,50 +20,6 @@ namespace cosim { -/// Symbolic constants that represent the state of a slave. -enum class slave_state -{ - /** - * The slave exists but has not been configured yet. - * - * The slave is in this state from its creation until `setup()` is called. - */ - created, - - /** - * The slave is in initialisation mode. - * - * The slave is in this state from the time `setup()` is called and until - * `start_simulation()` is called. - */ - initialisation, - - /** - * The slave is in simulation mode. - * - * The slave is in this state from the time `start_simulation()` is called - * and until `end_simulation()` is called. - */ - simulation, - - /** - * An irrecoverable error occurred. - * - * The slave is in this state from the time an exception is thrown and - * until its destruction. - */ - error, - - /** - * The slave is in an indeterminate state. - * - * This is the case when a state-changing asynchronous function call is - * currently in progress. - */ - indeterminate -}; - - class slave_simulator : public simulator { public: @@ -81,8 +37,6 @@ class slave_simulator : public simulator std::string name() const override; cosim::model_description model_description() const override; - slave_state state() const noexcept; - void expose_for_getting(variable_type type, value_reference ref) override; double get_real(value_reference reference) const override; int get_integer(value_reference reference) const override; @@ -139,11 +93,14 @@ class slave_simulator : public simulator time_point currentT, duration deltaT) override; + state_index save_state() override; + void save_state(state_index stateIndex) override; + void restore_state(state_index stateIndex) override; + void release_state(state_index stateIndex) override; + private: class impl; std::unique_ptr pimpl_; - - slave_state state_; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7156497f..1849a9ba 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(unittests "fmi_v2_fmu_unittest" "orchestration_unittest" "scenario_parser_unittest" + "slave_simulator_unittest" "ssp_loader_unittest" "system_structure_unittest" "time_unittest" diff --git a/tests/data/fmi2/Dahlquist.fmu b/tests/data/fmi2/Dahlquist.fmu new file mode 100644 index 00000000..c4c385f4 Binary files /dev/null and b/tests/data/fmi2/Dahlquist.fmu differ diff --git a/tests/data/fmi2/README.md b/tests/data/fmi2/README.md index 655479a0..eea75008 100644 --- a/tests/data/fmi2/README.md +++ b/tests/data/fmi2/README.md @@ -1,10 +1,12 @@ # FMI 2.0 test FMUs -| Name | Origin | License | -| ---------------------- | -------------- | ----------------------------- | -| `CraneController.fmu` | OSP | [MPL 2.0](../../../LICENSE) | -| `KnuckleBoomCrane.fmu` | OSP | [MPL 2.0](../../../LICENSE) | -| `vector.fmu` | [OSP cpp-fmus] | [MIT](./osp_cpp-fmus_LICENSE) | +| Name | Origin | License | +| ---------------------- | ----------------------- | ---------------------------------------- | +| `CraneController.fmu` | OSP | [MPL 2.0](../../../LICENSE) | +| `Dahlquist.fmu` | [Reference FMUs] 0.0.31 | [2-clause BSD](./reference-fmus_LICENSE) | +| `KnuckleBoomCrane.fmu` | OSP | [MPL 2.0](../../../LICENSE) | +| `vector.fmu` | [OSP cpp-fmus] | [MIT](./osp_cpp-fmus_LICENSE) | [OSP cpp-fmus]: https://github.com/open-simulation-platform/cpp-fmus +[Reference FMUs]: https://github.com/modelica/Reference-FMUs diff --git a/tests/data/fmi2/reference-fmus_LICENCE b/tests/data/fmi2/reference-fmus_LICENCE new file mode 100644 index 00000000..e69de29b diff --git a/tests/mock_slave.hpp b/tests/mock_slave.hpp index eeb8f1a1..7d89a899 100644 --- a/tests/mock_slave.hpp +++ b/tests/mock_slave.hpp @@ -93,7 +93,7 @@ class mock_slave : public cosim::slave std::optional /*stopTime*/, std::optional /*relativeTolerance*/) override { - currentTime_ = startTime; + state_.currentTime = startTime; } void start_simulation() override @@ -107,7 +107,7 @@ class mock_slave : public cosim::slave cosim::step_result do_step(cosim::time_point currentT, cosim::duration deltaT) override { if (stepAction_) stepAction_(currentT); - currentTime_ = currentT + deltaT; + state_.currentTime = currentT + deltaT; return cosim::step_result::complete; } @@ -117,9 +117,9 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == real_out_reference) { - values[i] = realOp_ ? realOp_(currentTime_, realIn_) : realIn_; + values[i] = realOp_ ? realOp_(state_.currentTime, state_.realIn) : state_.realIn; } else if (variables[i] == real_in_reference) { - values[i] = realIn_; + values[i] = state_.realIn; } else { throw std::out_of_range("bad reference"); } @@ -132,9 +132,9 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == integer_out_reference) { - values[i] = intOp_ ? intOp_(currentTime_, intIn_) : intIn_; + values[i] = intOp_ ? intOp_(state_.currentTime, state_.intIn) : state_.intIn; } else if (variables[i] == integer_in_reference) { - values[i] = intIn_; + values[i] = state_.intIn; } else { throw std::out_of_range("bad reference"); } @@ -147,9 +147,9 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == boolean_out_reference) { - values[i] = boolOp_ ? boolOp_(currentTime_, boolIn_) : boolIn_; + values[i] = boolOp_ ? boolOp_(state_.currentTime, state_.boolIn) : state_.boolIn; } else if (variables[i] == boolean_in_reference) { - values[i] = boolIn_; + values[i] = state_.boolIn; } else { throw std::out_of_range("bad reference"); } @@ -162,9 +162,9 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == string_out_reference) { - values[i] = stringOp_ ? stringOp_(currentTime_, stringIn_) : stringIn_; + values[i] = stringOp_ ? stringOp_(state_.currentTime, state_.stringIn) : state_.stringIn; } else if (variables[i] == string_in_reference) { - values[i] = stringIn_; + values[i] = state_.stringIn; } else { throw std::out_of_range("bad reference"); } @@ -177,7 +177,7 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == real_in_reference) { - realIn_ = values[i]; + state_.realIn = values[i]; } else { throw std::out_of_range("bad reference"); } @@ -190,7 +190,7 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == integer_in_reference) { - intIn_ = values[i]; + state_.intIn = values[i]; } else { throw std::out_of_range("bad reference"); } @@ -203,7 +203,7 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == boolean_in_reference) { - boolIn_ = values[i]; + state_.boolIn = values[i]; } else { throw std::out_of_range("bad reference"); } @@ -216,13 +216,34 @@ class mock_slave : public cosim::slave { for (std::size_t i = 0; i < variables.size(); ++i) { if (variables[i] == string_in_reference) { - stringIn_ = values[i]; + state_.stringIn = values[i]; } else { throw std::out_of_range("bad reference"); } } } + state_index save_state() override + { + savedStates_.push_back(state_); + return static_cast(savedStates_.size() - 1); + } + + void save_state(state_index overwriteState) override + { + savedStates_.at(overwriteState) = state_; + } + + void restore_state(state_index state) override + { + state_ = savedStates_.at(state); + } + + void release_state(state_index /*state*/) override + { + // Let's not worry about this. + } + private: std::function realOp_; std::function intOp_; @@ -230,12 +251,17 @@ class mock_slave : public cosim::slave std::function stringOp_; std::function stepAction_; - cosim::time_point currentTime_; + struct state + { + cosim::time_point currentTime; - double realIn_ = 0.0; - int intIn_ = 0; - bool boolIn_ = false; - std::string stringIn_; + double realIn = 0.0; + int intIn = 0; + bool boolIn = false; + std::string stringIn; + }; + state state_; + std::vector savedStates_; }; diff --git a/tests/slave_simulator_unittest.cpp b/tests/slave_simulator_unittest.cpp new file mode 100644 index 00000000..92183478 --- /dev/null +++ b/tests/slave_simulator_unittest.cpp @@ -0,0 +1,77 @@ +#define BOOST_TEST_MODULE cosim::slave_simulator unittest +#include +#include +#include +#include + +#include + + +BOOST_AUTO_TEST_CASE(slave_simulator_save_state) +{ + const auto testDataDir = std::getenv("TEST_DATA_DIR"); + BOOST_TEST_REQUIRE(!!testDataDir); + auto importer = cosim::fmi::importer::create(); + const std::string modelName = "Dahlquist"; + auto fmu = importer->import( + cosim::filesystem::path(testDataDir) / "fmi2" / (modelName + ".fmu")); + const auto modelDescription = fmu->model_description(); + BOOST_TEST(modelDescription->uuid == "{221063D2-EF4A-45FE-B954-B5BFEEA9A59B}"); + + const auto xVar = cosim::find_variable(*modelDescription, "x")->reference; + + auto t = cosim::time_point(); + const auto dt = cosim::to_duration(1.0); + + auto sim = cosim::slave_simulator( + fmu->instantiate_slave("testSlave"), + "testSlave"); + sim.expose_for_getting(cosim::variable_type::real, xVar); + + sim.setup(t, {}, {}); + const auto value0 = sim.get_real(xVar); + BOOST_TEST(value0 == 1.0); + const auto state0 = sim.save_state(); + + sim.start_simulation(); + sim.do_step(t, dt); + t += dt; + const auto value1 = sim.get_real(xVar); + BOOST_TEST((0.0 < value1 && value1 < value0)); + const auto state1 = sim.save_state(); + + sim.do_step(t, dt); + t += dt; + const auto value2 = sim.get_real(xVar); + BOOST_TEST((0.0 < value2 && value2 < value1)); + const auto state2 = sim.save_state(); + + sim.do_step(t, dt); + t += dt; + const auto value3 = sim.get_real(xVar); + BOOST_TEST((0.0 < value3 && value3 < value2)); + const auto state3 = state2; + sim.save_state(state3); // Overwrite the previously-saved state + + sim.restore_state(state1); + const auto value1Test = sim.get_real(xVar); + BOOST_TEST(value1Test == value1); + + sim.restore_state(state3); + const auto value3Test = sim.get_real(xVar); + BOOST_TEST(value3Test == value3); + + sim.restore_state(state0); + t = cosim::time_point(); + sim.start_simulation(); + sim.do_step(t, dt); + t += dt; + sim.do_step(t, dt); + t += dt; + const auto value0To2Test = sim.get_real(xVar); + BOOST_TEST(value0To2Test == value2); + + sim.release_state(state0); + sim.release_state(state1); + sim.release_state(state3); +}