Skip to content

Commit

Permalink
Enable saving and restoring full simulation state
Browse files Browse the repository at this point in the history
Closes #768
  • Loading branch information
kyllingstad committed Nov 6, 2024
1 parent efdbc87 commit 7506092
Show file tree
Hide file tree
Showing 16 changed files with 546 additions and 96 deletions.
35 changes: 34 additions & 1 deletion include/cosim/algorithm/algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cosim/function/function.hpp>
#include <cosim/model_description.hpp>
#include <cosim/observer/observer.hpp>
#include <cosim/serialization.hpp>
#include <cosim/time.hpp>

#include <functional>
Expand Down Expand Up @@ -157,7 +158,9 @@ class algorithm
* values for some of them.
*
* This function is guaranteed to be called after `setup()` and before
* the first `do_step()` call.
* the first `do_step()` call. Furthermore, no more subsimulators and
* functions will be added or removed after `initialize()` has been called;
* that is, `{add,remove}_{simulator,function}()` will not be called again.
*/
virtual void initialize() = 0;

Expand All @@ -180,6 +183,36 @@ class algorithm
*/
virtual std::pair<duration, std::unordered_set<simulator_index>> do_step(time_point currentT) = 0;

/**
* Exports the current state of the algorithm.
*
* Note that system-structural information should not be included in the
* data exported by this function, only internal, algorithm-specific data.
* This is because it will be assumed that the system structure is
* unchanged or has already been restored when the state is imported
* again, as explained in the `import_state()` function documentation.
*/
virtual serialization::node export_current_state() const = 0;

/**
* Imports a previously-exported algorithm state.
*
* When this function is called, it should be assumed that the system
* structure is the same as when the state was exported. That is, either
*
* 1. this is the algorithm instance from which the state was exported,
* and the system structure actually hasn't changed
* 2. this is a new instance, but the original system structure has been
* restored prior to calling this function
*
* By "system structure", we here mean the subsimulator indexes, the
* function indexes, and the variable connections.
*
* It is guaranteed that this function is never called before
* `initialize()`.
*/
virtual void import_state(const serialization::node& exportedState) = 0;

virtual ~algorithm() noexcept = default;
};

Expand Down
2 changes: 2 additions & 0 deletions include/cosim/algorithm/fixed_step_algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class fixed_step_algorithm : public algorithm
void setup(time_point startTime, std::optional<time_point> stopTime) override;
void initialize() override;
std::pair<duration, std::unordered_set<simulator_index>> do_step(time_point currentT) override;
serialization::node export_current_state() const override;
void import_state(const serialization::node& exportedState) override;

/**
* Sets step size decimation factor for a simulator.
Expand Down
66 changes: 64 additions & 2 deletions include/cosim/execution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <boost/functional/hash.hpp>

#include <cstdint>
#include <future>
#include <memory>
#include <optional>
Expand All @@ -36,7 +37,7 @@ using simulator_index = int;
using function_index = int;

/// An number which identifies a specific time step in an execution.
using step_number = long long;
using step_number = std::int64_t;

/// An object which uniquely identifies a simulator variable in a simulation.
struct variable_id
Expand Down Expand Up @@ -145,6 +146,13 @@ class simulator;
* The `execution` class manages all the entities involved in an execution
* and provides a high-level API for driving the co-simulation algorithm
* forward.
*
* \warning
* In general, the member functions of this class are not exception safe.
* This means that if any of them throw an exception, one must assume that
* the `execution` object is in an invalid state and can no longer be used.
* The same holds for its associated algorithm and any simulators or
* functions that are part of the execution.
*/
class execution
{
Expand Down Expand Up @@ -179,13 +187,19 @@ class execution
* The recommended co-simulation step size for this slave.
* Whether and how this is taken into account is algorithm dependent.
* If zero, the algorithm will attempt to choose a sensible default.
*
* \pre `initialize()` has not been called.
*/
simulator_index add_slave(
std::shared_ptr<slave> slave,
std::string_view name,
duration stepSizeHint = duration::zero());

/// Adds a function to the execution.
/**
* Adds a function to the execution.
*
* \pre `initialize()` has not been called.
*/
function_index add_function(std::shared_ptr<function> fun);

/// Adds an observer to the execution.
Expand Down Expand Up @@ -242,6 +256,14 @@ class execution
/// Returns the current logical time.
time_point current_time() const noexcept;

/**
* Initialize the co-simulation (in an algorithm-dependent manner).
*
* After this function is called, it is no longer possible to add more
* subsimulators or functions.
*/
void initialize();

/**
* Advance the co-simulation forward to the given logical time (blocks the current thread).
*
Expand All @@ -255,6 +277,12 @@ class execution
* `true` if the co-simulation was advanced to the given time,
* or `false` if it was stopped before this. In the latter case,
* `current_time()` may be called to determine the actual end time.
*
* \note
* For backwards compatibility, this function automatically calls
* `initialize()` if this hasn't already been done. However, new code
* should always call `initialize()` before any of the
* simulation/stepping functions.
*/
bool simulate_until(std::optional<time_point> targetTime);

Expand All @@ -271,6 +299,12 @@ class execution
* `true` if the co-simulation was advanced to the given time,
* or `false` if it was stopped before this. In the latter case,
* `current_time()` may be called to determine the actual end time.
*
* \note
* For backwards compatibility, this function automatically calls
* `initialize()` if this hasn't already been done. However, new code
* should always call `initialize()` before any of the
* simulation/stepping functions.
*/
std::future<bool> simulate_until_async(std::optional<time_point> targetTime);

Expand All @@ -281,6 +315,12 @@ class execution
* The actual duration of the step.
* `current_time()` may be called to determine the actual time after
* the step completed.
*
* \note
* For backwards compatibility, this function automatically calls
* `initialize()` if this hasn't already been done. However, new code
* should always call `initialize()` before any of the
* simulation/stepping functions.
*/
duration step();

Expand Down Expand Up @@ -314,6 +354,28 @@ class execution
/// Set initial value for a variable of type string. Must be called before simulation is started.
void set_string_initial_value(simulator_index sim, value_reference var, const std::string& value);

/**
* Exports the current state of the co-simulation.
*
* \pre `initialize()` has been called.
* \pre `!is_running()`
*/
serialization::node export_current_state() const;

/**
* Imports a previously-exported co-simulation state.
*
* Note that the data returned by `export_current_state()` only describe
* the *state* of the system, not its structure. This means that it's the
* caller's responsibility to either
*
* 1. not modify the system structure between state export and state import
* 2. restore the pre-export system structure prior to state import
*
* \pre `initialize()` has been called.
* \pre `!is_running()`
*/
void import_state(const serialization::node& exportedState);

private:
class impl;
Expand Down
2 changes: 2 additions & 0 deletions include/cosim/observer/file_observer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ class file_observer : public observer
duration lastStepSize,
time_point currentTime) override;

void state_restored(step_number currentStep, time_point currentTime) override;

cosim::filesystem::path get_log_path();

~file_observer() override;
Expand Down
2 changes: 2 additions & 0 deletions include/cosim/observer/last_value_observer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class last_value_observer : public last_value_provider
duration lastStepSize,
time_point currentTime) override;

void state_restored(step_number currentStep, time_point currentTime) override;

void get_real(
simulator_index sim,
gsl::span<const value_reference> variables,
Expand Down
3 changes: 3 additions & 0 deletions include/cosim/observer/observer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ class observer
duration lastStepSize,
time_point currentTime) = 0;

/// The simulation was restored to a previously saved state.
virtual void state_restored(step_number currentStep, time_point currentTime) = 0;

virtual ~observer() noexcept { }
};

Expand Down
2 changes: 2 additions & 0 deletions include/cosim/observer/time_series_observer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class time_series_observer : public time_series_provider
duration lastStepSize,
time_point currentTime) override;

void state_restored(step_number currentStep, time_point currentTime) override;

/**
* Start observing a variable.
*
Expand Down
48 changes: 43 additions & 5 deletions src/cosim/algorithm/fixed_step_algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ class fixed_step_algorithm::impl
if (stepCounter_ % info.decimationFactor == 0) {
pool_.submit([&] {
try {
info.stepResult = info.sim->do_step(currentT, baseStepSize_ * info.decimationFactor);
const auto stepResult = info.sim->do_step(currentT, baseStepSize_ * info.decimationFactor);

if (info.stepResult != step_result::complete) {
if (stepResult != step_result::complete) {
std::lock_guard<std::mutex> lck(m);
errMessages
<< info.sim->name() << ": "
Expand Down Expand Up @@ -231,6 +231,28 @@ class fixed_step_algorithm::impl
return {baseStepSize_, std::move(finished)};
}

serialization::node export_current_state() const
{
auto exportedState = serialization::node();
exportedState.put("type", std::string("fixed_step_algorithm"));
exportedState.put("step_counter", stepCounter_);
return exportedState;
}

void import_state(const serialization::node& exportedState)
{
try {
if (exportedState.get<std::string>("type") != "fixed_step_algorithm") {
throw std::exception();
}
stepCounter_ = exportedState.get<std::int64_t>("step_counter");
} catch (...) {
throw error(
make_error_code(errc::bad_file),
"The serialized algorithm state is invalid or corrupt");
}
}

void set_stepsize_decimation_factor(cosim::simulator_index i, int factor)
{
COSIM_INPUT_CHECK(factor > 0);
Expand Down Expand Up @@ -261,7 +283,6 @@ class fixed_step_algorithm::impl
{
simulator* sim;
int decimationFactor = 1;
step_result stepResult;
std::vector<connection_ss> outgoingSimConnections;
std::vector<connection_sf> outgoingFunConnections;
};
Expand Down Expand Up @@ -421,13 +442,20 @@ class fixed_step_algorithm::impl
}
}

// Algorithm parameters
const duration baseStepSize_;
time_point startTime_;
std::optional<time_point> stopTime_;
unsigned int max_threads_ = std::thread::hardware_concurrency() - 1;

// System structure
std::unordered_map<simulator_index, simulator_info> simulators_;
std::unordered_map<function_index, function_info> functions_;
int64_t stepCounter_ = 0;
unsigned int max_threads_ = std::thread::hardware_concurrency() - 1;

// Simulation state
std::int64_t stepCounter_ = 0;

// Other
utility::thread_pool pool_;
};

Expand Down Expand Up @@ -521,6 +549,16 @@ std::pair<duration, std::unordered_set<simulator_index>> fixed_step_algorithm::d
return pimpl_->do_step(currentT);
}

serialization::node fixed_step_algorithm::export_current_state() const
{
return pimpl_->export_current_state();
}

void fixed_step_algorithm::import_state(const serialization::node& exportedState)
{
pimpl_->import_state(exportedState);
}

void fixed_step_algorithm::set_stepsize_decimation_factor(cosim::simulator_index simulator, int factor)
{
pimpl_->set_stepsize_decimation_factor(simulator, factor);
Expand Down
Loading

0 comments on commit 7506092

Please sign in to comment.