Skip to content

Commit

Permalink
Enable exporting and importing subsimulator state
Browse files Browse the repository at this point in the history
This is a follow-up to #765 and the second and final step to close #756.

Here, I've implemented functionality to export the internal state of
individual subsimulators in a generic, structured form, and to import
them again later.

This exported form is intended as an intermediate step before
serialisation and disk storage.  The idea was to create a type that can
be inspected and serialised to almost any file format we'd like.

The type is defined by `cosim::serialization::node` in
`cosim/serialization.hpp`.  It is a hierarchical, dynamic data type with
support for a variety of primitive scalar types and a few aggregate
types: strings, arrays of nodes, dictionaries of nodes, and binary
blobs. (Think JSON, only with more types.)
  • Loading branch information
kyllingstad committed Jul 4, 2024
1 parent 981236a commit f5967bb
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 15 deletions.
18 changes: 18 additions & 0 deletions include/cosim/algorithm/simulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <cosim/manipulator/manipulator.hpp>
#include <cosim/model_description.hpp>
#include <cosim/serialization.hpp>
#include <cosim/time.hpp>

#include <functional>
Expand Down Expand Up @@ -182,6 +183,23 @@ class simulator : public manipulable
* implementation is free to reuse the same `state_index` at a later point.
*/
virtual void release_state(state_index stateIndex) = 0;

/**
* Exports a saved state.
*
* This returns a previously-saved state in a generic format so it can be
* serialized, e.g. to write it to disk and use it in a later simulation.
*/
virtual serialization::node export_state(state_index stateIndex) const = 0;

/**
* Imports an exported state.
*
* The imported state is added to the simulator's internal list of saved
* states. Use `restore_state()` to restore it again. The state must have
* been saved by a simulator of the same or a compatible type.
*/
virtual state_index import_state(const serialization::node& exportedState) = 0;
};

} // namespace cosim
Expand Down
2 changes: 2 additions & 0 deletions include/cosim/fmi/v1/fmu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ class slave_instance : public fmi::slave_instance
void save_state(state_index overwriteState) override;
void restore_state(state_index state) override;
void release_state(state_index state) override;
serialization::node export_state(state_index stateIndex) const override;
state_index import_state(const serialization::node& exportedState) override;

// fmi::slave_instance methods
std::shared_ptr<fmi::fmu> fmu() const override
Expand Down
3 changes: 3 additions & 0 deletions include/cosim/fmi/v2/fmu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ class slave_instance : public fmi::slave_instance
void save_state(state_index stateIndex) override;
void restore_state(state_index stateIndex) override;
void release_state(state_index stateIndex) override;
serialization::node export_state(state_index stateIndex) const override;
state_index import_state(const serialization::node& exportedState) override;

// fmi::slave_instance methods
std::shared_ptr<fmi::fmu> fmu() const override
Expand All @@ -192,6 +194,7 @@ class slave_instance : public fmi::slave_instance
bool simStarted = false;
};
void copy_current_state(saved_state& state);
state_index store_new_state(saved_state state);

std::shared_ptr<v2::fmu> fmu_;
fmi2_import_t* handle_;
Expand Down
121 changes: 121 additions & 0 deletions include/cosim/serialization.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* \file
* Supporting functionality for serialization and persistence of simulation state.
*
* \copyright
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef COSIM_SERIALIZATION_HPP
#define COSIM_SERIALIZATION_HPP

#include <cstddef>
#include <cstdint>
#include <ostream>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>


namespace cosim
{
/// Supporting functionality for serialization and persistence of simulation state.
namespace serialization
{

// This is a small trick to enable nodes to contain other nodes. We
// forward-declare the `node` struct here, which allows us to use it in the
// declarations of the container types below. Thus, it can (indirectly) be
// used in the `node_base` type, which is the "actual" node type. Finally,
// we'll define `node` so that it inherits from `node_base`.
struct node;

/**
* An array of `cosim::serialization::node` objects.
*
* This is used to enable a node to contain a sequence of other nodes.
*/
using array = std::vector<node>;

/**
* An associative array which maps strings to `cosim::serialization::node`
* objects.
*
* This is used to enable a node to contain a dictionary of other nodes.
*/
using associative_array = std::unordered_map<std::string, node>;

/**
* An array of bytes.
*
* This is used to enable a `cosim::serialization::node` to contain arbitrary
* binary data.
*/
using binary_blob = std::vector<std::byte>;


namespace detail
{
// This is step 2 of the trick mentioned earlier, the type that actually
// holds the node contents.
using node_base = std::variant<
bool,
std::byte,
std::uint8_t,
std::int8_t,
std::uint16_t,
std::int16_t,
std::uint32_t,
std::int32_t,
std::uint64_t,
std::int64_t,
float,
double,
std::string,
array,
associative_array,
binary_blob
>;
}


/**
* A recursive, dynamic data type that can be used to store structured data in
* a type-safe manner.
*
* A `node` is essentially an `std::variant` which can hold the following
* types:
*
* - `bool`
* - `std::byte`
* - `std::[u]int{8,16,32,64}_t`
* - `float` and `double`
* - `std::string`
* - `cosim::serialization::array`
* - `cosim::serialization::associative_array`
* - `cosim::serialization::binary_blob`
*
* Its purpose is to be a generic representation of virtually any data
* structure, so that serialization to a variety of formats can be supported.
*/
struct node : public detail::node_base
{
using detail::node_base::node_base;
};


/**
* Writes the contents of `data` to the output stream `out` in a human-readable
* format.
*
* This is meant for debugging purposes, not for serialization. There is no
* corresponding "read" function, nor is the output format designed to support
* round-trip information or type preservation.
*/
std::ostream& operator<<(std::ostream& out, const node& data);


}} // namespace cosim::serialization
#endif // COSIM_SERIALIZATION_HPP
22 changes: 18 additions & 4 deletions include/cosim/slave.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define COSIM_SLAVE_HPP

#include <cosim/model_description.hpp>
#include <cosim/serialization.hpp>
#include <cosim/time.hpp>

#include <boost/container/vector.hpp>
Expand Down Expand Up @@ -290,8 +291,6 @@ class slave
* `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;

Expand All @@ -301,8 +300,6 @@ class slave
* 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;

Expand Down Expand Up @@ -330,6 +327,23 @@ class slave
* implementation is free to reuse the same `state_index` at a later point.
*/
virtual void release_state(state_index stateIndex) = 0;

/**
* Exports a saved state.
*
* This returns a previously-saved state in a generic format so it can be
* serialized, e.g. to write it to disk and use it in a later simulation.
*/
virtual serialization::node export_state(state_index stateIndex) const = 0;

/**
* Imports an exported state.
*
* The imported state is added to the slave's internal list of saved
* states. Use `restore_state()` to restore it again. The state must have
* been saved by a slave of the same or a compatible type.
*/
virtual state_index import_state(const serialization::node& exportedState) = 0;
};


Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ set(publicHeaders
"cosim/osp_config_parser.hpp"
"cosim/scenario.hpp"
"cosim/scenario_parser.hpp"
"cosim/serialization.hpp"
"cosim/slave.hpp"
"cosim/ssp/ssp_loader.hpp"
"cosim/system_structure.hpp"
Expand Down Expand Up @@ -88,6 +89,7 @@ set(sources
"cosim/orchestration.cpp"
"cosim/osp_config_parser.cpp"
"cosim/scenario_parser.cpp"
"cosim/serialization.cpp"
"cosim/slave_simulator.cpp"
"cosim/ssp/ssp_loader.cpp"
"cosim/ssp/ssp_parser.cpp"
Expand Down
16 changes: 16 additions & 0 deletions src/cosim/fmi/v1/fmu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,22 @@ void slave_instance::release_state(state_index)
}


serialization::node slave_instance::export_state(state_index) const
{
throw error(
make_error_code(errc::unsupported_feature),
"Serializing and deserializing state not supported in FMI 1.0");
}


slave::state_index slave_instance::import_state(const serialization::node&)
{
throw error(
make_error_code(errc::unsupported_feature),
"Serializing and deserializing state not supported in FMI 1.0");
}


std::shared_ptr<v1::fmu> slave_instance::v1_fmu() const
{
return fmu_;
Expand Down
Loading

0 comments on commit f5967bb

Please sign in to comment.