View on GitHub

Graphitti

A project to facilitate construction of high-performance neural simulations

Serialization and Deserialization using Cereal

This guide explains how to implement serialization using the Cereal library within Graphitti. If you’re looking to add serialization to your class, follow this guide. For more comprehensive information on Cereal, refer to their official documentation.

What is Serialization and Deserialization?

Serialization involves converting an object or data structure into a format that can be stored or transmitted, while deserialization is the reverse process— reconstructing an object from the serialized format.

What is Cereal?

Cereal is a lightweight C++11 library designed for object serialization and deserialization. It provides a straightforward interface for serializing objects and supports a wide range of data types. Cereal is efficient and well-suited for handling large data sets, making it a preferred choice for serialization tasks in Graphitti.

Why Graphitti Uses Serialization?

Graphitti utilizes Cereal to enable efficient serialization and deserialization of its simulation state and network structure. Serialized data can serve as a checkpoint for large simulations or as input for subsequent simulations with varying conditions. This flexibility enhances Graphitti’s efficiency and adaptability in modeling scenarios.

Understand Cereal at a High Level

C++ Features Supported by Cereal

Serialization Archive Types

Serialization Functions in Cereal

Incorporate Cereal in your class

STEP 01: ADD CEREAL HEADERS

Before implementing serialization in your class, you need to include the appropriate Cereal headers for the types of data members you want to serialize. Cereal provides headers for various standard types, such as vectors, strings, and other containers.

Commonly used C++ data types and their corresponding Cereal headers | Type | Header to include | |:--------------------|:----------------------------------------------| | `std::array` | `#include <cereal/types/array.hpp>` | | `std::atomic` | `#include <cereal/types/atomic.hpp>` | | `std::bitset` | `#include <cereal/types/bitset.hpp>` | | `std::chrono` | `#include <cereal/types/chrono.hpp>` | | `std::complex` | `#include <cereal/types/complex.hpp>` | | `std::deque` | `#include <cereal/types/deque.hpp>` | | `std::forward_list` | `#include <cereal/types/forward_list.hpp>` | | `std::functional` | `#include <cereal/types/functional.hpp>` | | `std::list` | `#include <cereal/types/list.hpp>` | | `std::map` | `#include <cereal/types/map.hpp>` | | `std::memory` | `#include <cereal/types/memory.hpp>` | | `std::optional` | `#include <cereal/types/optional.hpp>` | | `std::queue` | `#include <cereal/types/queue.hpp>` | | `std::set` | `#include <cereal/types/set.hpp>` | | `std::stack` | `#include <cereal/types/stack.hpp>` | | `std::string` | `#include <cereal/types/string.hpp>` | | `std::tuple` | `#include <cereal/types/tuple.hpp>` | | `std::unordered_map`| `#include <cereal/types/unordered_map.hpp>` | | `std::unordered_set`| `#include <cereal/types/unordered_set.hpp>` | | `std::utility` | `#include <cereal/types/utility.hpp>` | | `std::valarray` | `#include <cereal/types/valarray.hpp>` | | `std::variant` | `#include <cereal/types/variant.hpp>` | | `std::vector` | `#include <cereal/types/vector.hpp>` |


STEP 02: ADD SERIALIZE FUNCTION

Within your class header file


// STEP 01: Add Necessary Header
#include <cereal/vector.hpp>    

class MyCoolClass
{

  public:
    // STEP 02 (a): Declare the serialize function in the public section
    template <class Archive> 
    void serialize( Archive & archive );    

  private:
    std::vector<int> myVector_;
    int x_;
};

//STEP 02 (b): Define the serialize function outside the class

template <class Archive> 
void MyCoolClass::serialize(Archive &archive)
{
   archive(cereal::make_nvp("myVector", myVector_), cereal::make_nvp("myInt", x_));
}


Adjust the function names and data member names as per your specific requirements.

NOTE:

STEP 03: SPECIAL CASES

Cereal requires additional steps for certain special cases such as inheritance, polymorphism, and templates. In this section, we outline specific steps based on whether your class matches one of these conditions. If you answer “yes” to any of the following questions, follow the corresponding steps. Some classes may fall under multiple categories, so be sure to review all the details carefully.

1. DERIVED CLASS?

This step explains how to serialize base classes in a derived class. Cereal requires a path from the derived to the base type(s), typically done with cereal::base_class or cereal::virtual_base_class.

Virtual Inheritance ? </summary> If your derived class uses virtual inheritance (`class Derived : virtual Base`), use `cereal::virtual_base_class(this)` to cast the derived class to its base class. Ensure this is placed at the start of the `archive` in the `serialize` function before member variables in the derived class. ```cpp class MyDerived : virtual MyBase { int x_; template void serialize( Archive & ar ); }; template void MyDerived::serialize( Archive & archive ) { // We pass this cast to the base type for each base type we need to serialize. archive(cereal::virtual_base_class(this), cereal::make_nvp("myInt", x_)); // For multiple inheritance, link all the base classes one after the other //archive(cereal::virtual_base_class(this), cereal::virtual_base_class(this), cereal::make_nvp("myInt", X_)); } ``` </details>
Normal Inheritance ?</summary> For non-virtual inheritance (`class Derived : public Base`), use `cereal::base_class(this) to serialize the base class. Ensure this is placed at the start of the `archive` in the `serialize` function before member variables in the derived class. ```cpp class MyDerived : public MyBase { int x_; template void serialize( Archive & ar ); }; template void MyDerived::serialize( Archive & archive ) { // We pass this cast to the base type for each base type we need to serialize. archive(cereal::base_class(this), cereal::make_nvp("myInt", x_)); // For multiple inheritance, link all the base classes one after the other //archive(cereal::base_class(this), cereal::base_class(this), cereal::make_nvp("myInt", X_)); } ``` For more details, refer to the official Cereal documentation on [inheritance](https://uscilab.github.io/cereal/inheritance.html) </details> ### **2. EXHIBIT POLYMORPHISM?** If you answered "yes" to the previous question about your class being a derived class, this is likely "yes" as well.
Follow these steps if your class exhibits polymorphic behavior: </summary> 1. Include Necessary Headers: Make sure to include the polymorphic header to enable support for polymorphism in Cereal in the derived class. ``` #include <cereal/types/polymorphic.hpp> ``` 2. Register Your Derived Types: Register each derived class above the definition of the `serialize` function using `CEREAL_REGISTER_TYPE(DerivedClassName)` in the respective derived class. ```cpp // be sure to include support for polymorphism #include <cereal/types/polymorphic.hpp> class MyDerived : public MyBase { int x_; template void serialize( Archive & ar ); }; //Registering the Derived class CEREAL_REGISTER_TYPE(MyDerived); template void MyDerived::serialize( Archive & archive ) { archive(cereal::base_class(this), cereal::make_nvp("myInt", x_)); } ``` 3. Register Your Base Class (if not registered automatically): Normally, registering base classes is handled automatically if you serialize a derived type with either `cereal::base_class` or `cereal::virtual_base_class`. However, in situations where neither of these is used, explicit registration is required using the `CEREAL_REGISTER_POLYMORPHIC_RELATION` macro in the derived class. ```cpp struct MyEmptyBase { virtual void foo() = 0; }; struct MyDerived: MyEmptyBase { void foo() {} double y_; template void serialize( Archive & archive ); }; CEREAL_REGISTER_TYPE(MyDerived) //Registering the Base Class CEREAL_REGISTER_POLYMORPHIC_RELATION(MyEmptyBase, MyDerived) template void MyDerived::serialize( Archive & archive ) { archive( cereal::make_nvp("myDouble", y_) ); } ``` For more detailed information and examples on polymorphism in Cereal, refer to the official documentation on [Polymorphism](https://uscilab.github.io/cereal/polymorphism.html). </details> ### **3. TEMPLATE?**
Template involves inheritance? </summary> Follow all the steps from STEP 01 as if your class is a regular class. However, if the template involves inheritance, you might need to register all potential instantiations of the template during polymorphism handling. ```cpp // Include necessary Cereal headers #include <cereal/types/polymorphic.hpp> #include <cereal/types/vector.hpp> // A pure virtual base class struct BaseClass { virtual void sayType() = 0; }; // A templated class derived from BaseClass template struct DerivedClassTemplate : public BaseClass { T value_; void sayType(); template void serialize(Archive & archive) { archive(cereal::virtual_base_class(this), cereal::make_nvp("myValue", value_)); } }; // Register template instantiations CEREAL_REGISTER_TYPE(DerivedClassTemplate); CEREAL_REGISTER_TYPE(DerivedClassTemplate); // If using Register polymorphic relationships // CEREAL_REGISTER_POLYMORPHIC_RELATION(BaseClass, DerivedClassTemplate); // CEREAL_REGISTER_POLYMORPHIC_RELATION(BaseClass, DerivedClassTemplate); ``` </details> ### **4. NO DEFAULT CONSTRUCTOR?** Cereal provides a special overload method to handle this situation. Refer to the [Cereal documentation](https://uscilab.github.io/cereal/pointers) for detailed information on this technique. ## Common Cereal Errors Encountering a Cereal error during compiling or running Graphitti? Here's a checklist to troubleshoot: 1. **Include Correct Cereal Headers**: Ensure you've included the necessary Cereal headers for the types you're serializing. If using polymorphic serialization, include `#include <cereal/types/polymorphic.hpp>`. 2. **Default Constructor**: Verify that your class has a default constructor. If not possible, utilize Cereal's special overload methods for handling this scenario. 3. **Polymorphic Type Registration**: If serialization of polymorphic types fails or results in incorrect type information, double-check your type registration. Use `CEREAL_REGISTER_TYPE` and `CEREAL_REGISTER_POLYMORPHIC_RELATION` to register polymorphic types correctly. 4. **Runtime Exceptions**: If encountering a runtime exception like ``` what(): Trying to save an unregistered polymorphic type (AllDSSynapses). Make sure your type is registered with CEREAL_REGISTER_TYPE and that the archive you are using was included (and registered with CEREAL_REGISTER_ARCHIVE) prior to calling CEREAL_REGISTER_TYPE. ``` include the following two archive headers for the respective class: ``` #include <cereal/archives/portable_binary.hpp> #include <cereal/archives/xml.hpp> ``` Reasoning: Polymorphic type registration requires mapping your registered type to archives included prior to CEREAL_REGISTER_TYPE being called. Missing archive headers in certain classes could lead to this error. With these checks, you should be able to diagnose and resolve common Cereal errors in Graphitti. --------- [<< Go back to the Developer Documentation page](/Graphitti/Developer/) --------- [<< Go back to the Graphitti home page](/Graphitti/)