Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
cf52fa1
manually move branch to new main
reneSchm Jul 19, 2024
f0598bf
CI fixes
reneSchm Jul 19, 2024
5037147
move serialization tests and improve coverage
reneSchm Jul 23, 2024
55ad700
Merge branch 'main' into 652-add-serialization-to-abm
reneSchm Jul 23, 2024
2810cf9
fix and cover testing_strategy serialization
reneSchm Jul 23, 2024
64f566e
remove unwanted includes
reneSchm Jul 23, 2024
2f7bc79
fix and cover testing_strategy serialization v2
reneSchm Jul 23, 2024
a1832ca
add TDPF tests
reneSchm Jul 23, 2024
ed0ec32
fix logging level accross tests
reneSchm Jul 23, 2024
9ee8ae4
improve TDPF implementation
reneSchm Jul 23, 2024
dd88980
debug CI
reneSchm Jul 23, 2024
7310b2e
CI
reneSchm Jul 23, 2024
6e99e08
Revert "CI"
reneSchm Jul 23, 2024
e669dde
Revert "debug CI"
reneSchm Jul 23, 2024
ec78f58
limit TDPF Zero evaluation to avoid potential msvc bug
reneSchm Jul 23, 2024
4d1d924
try out a codecov arg to fix some lines incorrectly marked as uncovered
reneSchm Jul 24, 2024
3dd06e6
remove codecov arg, instead disable some gcc optimisations
reneSchm Jul 24, 2024
07d863b
move NVPs by value, use add/expect_list
reneSchm Aug 1, 2024
9a05c3c
Merge branch 'main' into 652-add-serialization-to-abm
reneSchm Aug 1, 2024
d945bf4
Enable auto_serialize to add lists directly.
reneSchm Aug 7, 2024
8fbe7dc
Simplify auto_(de)serialize_impl by moving tuple unpacking to (de)ser…
reneSchm Aug 7, 2024
736cd1b
Rework TDPF to use TimeSeries and rename it.
reneSchm Aug 7, 2024
b37f653
clean up, comments, documentation
reneSchm Aug 8, 2024
3e44683
Merge branch 'main' into 652-add-serialization-to-abm
reneSchm Aug 8, 2024
57af8ba
combine PickleType specialization for small ints.
reneSchm Aug 8, 2024
49034b9
add serialization test for TestResult
reneSchm Aug 9, 2024
cef8cba
implement most review suggestions
reneSchm Aug 15, 2024
cb2783d
avoid creating object twice
reneSchm Aug 16, 2024
7cb2f69
fix evaluation order of auto_deserialize
reneSchm Aug 16, 2024
36289ce
cover unhandled type asserts in tests
reneSchm Aug 16, 2024
537edbd
[ci skip] update io readme
reneSchm Aug 16, 2024
0b67e1c
rename auto-serialize to default serialize,
reneSchm Aug 27, 2024
f2e158c
rename auto_serialization.h/.cpp
reneSchm Aug 27, 2024
2f5bd39
use uniform_real_distribution directly to not get accidentally mocked
reneSchm Aug 27, 2024
0f3ac78
simplify abm serialization test
reneSchm Aug 27, 2024
3837102
add threadsafe flag to death tests
reneSchm Aug 27, 2024
1bc6ab3
add review suggestions
reneSchm Aug 27, 2024
116daeb
Merge branch 'main' into 652-add-serialization-to-abm
reneSchm Aug 28, 2024
8628b66
rework linear_interpolation to avoid allocation
reneSchm Sep 17, 2024
fe8e205
Merge branch 'main' into 652-add-serialization-to-abm
reneSchm Sep 17, 2024
a69b0f4
undo changes to linear_interpolation, as lazy evaluation was broken
reneSchm Sep 18, 2024
1bc0c1b
enable reusing documentation within groups
reneSchm Sep 27, 2024
5472aff
add a .cpp for time_series_functor to possibly reduce compile time
reneSchm Nov 7, 2024
e8b0a3e
extend documentation of Members and default_deserialize_impl
reneSchm Nov 7, 2024
930a885
review suggestions
reneSchm Nov 7, 2024
8e6cf69
Merge branch 'main' into 652-add-serialization-to-abm
reneSchm Nov 7, 2024
91b6213
update serialization(tests) after main merge
reneSchm Nov 7, 2024
502d6e2
[ci skip] clarify some comments
reneSchm Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "DEBUG")
message(STATUS "Coverage enabled")
include(CodeCoverage)
append_coverage_compiler_flags()
# In addition to standard flags, disable elision and inlining to prevent e.g. closing brackets being marked as uncovered.

# In addition to standard flags, disable elision and inlining to prevent e.g. closing brackets being marked as
# uncovered.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors -fno-default-inline")
setup_target_for_coverage_lcov(
NAME coverage
Expand Down
4 changes: 4 additions & 0 deletions cpp/memilio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ add_library(memilio
compartments/simulation.h
compartments/flow_simulation.h
compartments/parameter_studies.h
io/default_serialize.h
io/default_serialize.cpp
io/io.h
io/io.cpp
io/hdf5_cpp.h
Expand Down Expand Up @@ -57,6 +59,8 @@ add_library(memilio
math/matrix_shape.cpp
math/interpolation.h
math/interpolation.cpp
math/time_series_functor.h
math/time_series_functor.cpp
mobility/metapopulation_mobility_instant.h
mobility/metapopulation_mobility_instant.cpp
mobility/metapopulation_mobility_stochastic.h
Expand Down
57 changes: 53 additions & 4 deletions cpp/memilio/io/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,25 @@ This directory contains utilities for reading and writing data from and to files

## The Serialization framework

## Main functions and types
### Using serialization

In the next sections we will explain how to implement serialization (both for types and formats), here we quickly show
how to use it once it already is implemented for a type. In the following examples, we serialize (write) `Foo` to a
file in Json format, then deserialize (read) the Json again.
```cpp
Foo foo{5};
mio::IOResult<void> io_result = mio::write_json("path/to/foo.json", foo);
```
```cpp
mio::IOResult<Foo> io_result = mio::read_json("path/to/foo.json", mio::Tag<Foo>{});
if (io_result) {
Foo foo = io_result.value();
}
```
There is also support for a binary format. If you want to use a format directly, use the
`serialize_json`/`deserialize_json` and `serialize_binary`/`deserialize_binary` functions.

### Main functions and types

- functions serialize and deserialize:
Main entry points to the framework to write and read values, respectively. The functions expect an IOContext
Expand All @@ -14,7 +32,38 @@ This directory contains utilities for reading and writing data from and to files
- IOStatus and IOResult:
Used for error handling, see section "Error Handling" below.

## Concepts
### Default serialization

Before we get into the details of the framework, this feature provides an easy and convenient alternative to the
serialize and deserialize functions. To give an example:

```cpp
struct Foo {
int i;
auto default_serialize() {
return Members("Foo").add("i", i);
}
};
```
The default serialization is less flexible than the serialize and deserialize functions and has additional
requirements:
- The class must be default constructible.
- If there is a default constructor that is *private*, it can still be used by marking the struct `DefaultFactory` as
a friend. For the example above, the line `friend DefaultFactory<Foo>;` would be added to the class definition.
- Alternatively, you may provide a specialization of the struct `DefaultFactory`. For more details,
view the struct's documentation.
- Every class member must be added to `Members` exactly once, and the provided names must be unique.
- The members must be passed directly, like in the example. No copies, accessors, etc.
- It is recommended, but not required, to add member variables to `Members` in the same order they are declared in
the class, using the variables' names or something very similar.
- Every class member itself must be serializable, deserializable and assignable.

As to the feature set, default-serialization only supports the `add_element` and `expect_element` operations defined in
the Concepts section below, where each operation's arguments are provided through the `add` function. Note that the
value provided to `add` is also used to assign a value during deserialization, hence the class members must be used
directly in the function (i.e. as a non-const lvalue reference).

### Concepts

1. IOContext
Stores data that describes serialized objects of any type in some unspecified format and provides structured
Expand Down Expand Up @@ -66,7 +115,7 @@ for an IOObject `obj`:
value or it may be empty. Otherwise returns an error. Note that for some formats a wrong key is indistinguishable from
an empty optional, so make sure to provide the correct key.

## Error handling
### Error handling

Errors are handled by returning error codes. The type IOStatus contains an error code and an optional string with additional
information. The type IOResult contains either a value or an IOStatus that describes an error. Operations that can fail return
Expand All @@ -78,7 +127,7 @@ inspected, so `expect_...` operations return an IOResult. The `apply` utility fu
of multiple `expect_...` operations and use the values if all are succesful. See the documentation of `IOStatus`, `IOResult`
and `apply` below for more details.

## Adding a new data type to be serialized
### Adding a new data type to be serialized

Serialization of a new type T can be customized by providing _either_ member functions `serialize` and `deserialize` _or_ free functions
`serialize_internal` and `deserialize_internal`.
Expand Down
2 changes: 1 addition & 1 deletion cpp/memilio/io/binary_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class BinarySerializerContext : public SerializerBase
"Unexpected type in stream:" + type_result.value() + ". Expected " + type);
}
}
return BinarySerializerObject(m_stream, m_status, m_flags);
return obj;
}

/**
Expand Down
20 changes: 20 additions & 0 deletions cpp/memilio/io/default_serialize.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2020-2024 MEmilio
*
* Authors: Rene Schmieding
*
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "memilio/io/default_serialize.h"
254 changes: 254 additions & 0 deletions cpp/memilio/io/default_serialize.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
* Copyright (C) 2020-2024 MEmilio
*
* Authors: Rene Schmieding
*
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MIO_IO_DEFAULT_SERIALIZE_H_
#define MIO_IO_DEFAULT_SERIALIZE_H_

#include "memilio/io/io.h"
#include "memilio/utils/metaprogramming.h"

#include <tuple>
#include <type_traits>
#include <utility>

namespace mio
{

/**
* @brief A pair of name and reference.
*
* Used for default (de)serialization.
* This object holds a char pointer to a name and reference to value. Mind their lifetime!
* @tparam ValueType The (non-cv, non-reference) type of the value.
*/
template <class ValueType>
struct NamedRef {
using Reference = ValueType&;

const char* name;
Reference value;

/**
* @brief Create a named reference.
*
* @param n A string literal.
* @param v A non-const lvalue reference to the value.
*/
explicit NamedRef(const char* n, Reference v)
: name(n)
, value(v)
{
}
};

namespace details
{

/**
* @brief Helper type to detect whether T has a default_serialize member function.
* Use has_default_serialize.
* @tparam T Any type.
*/
template <class T>
using default_serialize_expr_t = decltype(std::declval<T>().default_serialize());

/// Add a name-value pair to an io object.
template <class IOObject, class Member>
void add_named_ref(IOObject& obj, const NamedRef<Member> named_ref)
{
obj.add_element(named_ref.name, named_ref.value);
}

/// Unpack all name-value pairs from the tuple and add them to a new io object with the given name.
template <class IOContext, class... Members>
void default_serialize_impl(IOContext& io, const char* name, const NamedRef<Members>... named_refs)
{
auto obj = io.create_object(name);
(add_named_ref(obj, named_refs), ...);
}

/// Retrieve a name-value pair from an io object.
template <class IOObject, class Member>
IOResult<Member> expect_named_ref(IOObject& obj, const NamedRef<Member> named_ref)
{
return obj.expect_element(named_ref.name, Tag<Member>{});
}

/// Read an io object and its members from the io context using the given names and assign the values to a.
template <class IOContext, class DefaultSerializable, class... Members>
IOResult<DefaultSerializable> default_deserialize_impl(IOContext& io, DefaultSerializable& a, const char* name,
NamedRef<Members>... named_refs)
{
auto obj = io.expect_object(name);

// we cannot use expect_named_ref directly in apply, as function arguments have no guarantueed order of evaluation
std::tuple<IOResult<Members>...> results{expect_named_ref(obj, named_refs)...};

return apply(
io,
[&a, &named_refs...](const Members&... result_values) {
// if all results are successfully deserialized, they are unpacked into result_values
// then all class variables are overwritten (via the named_refs) with these values
((named_refs.value = result_values), ...);
return a;
},
results);
}

} // namespace details

/**
* @brief List of a class's members.
*
* Used for default (de)serialization.
* Holds a char pointer to the class name as well as a tuple of NamedRefs with all added class members.
* Initially, the template parameter pack should be left empty. It will be filled by calling Members::add.
* @tparam ValueTypes The (non-cv, non-reference) types of member variables.
*/
template <class... ValueTypes>
struct Members {
// allow other Members access to the private constructor
template <class...>
friend struct Members;

/**
* @brief Initialize Members with a class name. Use the member function `add` to specify the class's variables.
* @param[in] class_name Name of a class.
*/
Members(const char* class_name)
: name(class_name)
, named_refs()
{
}

/**
* @brief Add a class member.
*
* Use this function consecutively for all members, e.g. `Members("class").add("a", a).add("b", b).add...`.
*
* @param[in] member_name The name used for serialization. Should be the same as or similar to the class member.
* For example, a good option a private class member `m_time` is simply `"time"`.
* @param[in] member A class member. Always pass this variable directly, do not use getters or accessors.
* @return A Members object with all previous class members and the newly added one.
*/
template <class T>
[[nodiscard]] Members<ValueTypes..., T> add(const char* member_name, T& member)
{
return Members<ValueTypes..., T>{name, std::tuple_cat(named_refs, std::tuple(NamedRef{member_name, member}))};
}

const char* name; ///< Name of the class.
std::tuple<NamedRef<ValueTypes>...> named_refs; ///< Names and references to members of the class.

private:
/**
* @brief Initialize Members directly. Used by the add function.
* @param[in] class_name Name of a class.
* @param[in] named_references Tuple of added class Members.
*/
Members(const char* class_name, std::tuple<NamedRef<ValueTypes>...> named_references)
: name(class_name)
, named_refs(named_references)
{
}
};

/**
* @brief Creates an instance of T for later initialization.
*
* The default implementation uses the default constructor of T, if available. If there is no default constructor, this
* class can be spezialized to provide the method `static T create()`. If there is a default constructor, but it is
* private, DefaultFactory<T> can be marked as friend instead.
*
* The state of the object retured by `create()` is completely arbitrary, and may be invalid. Make sure to set it to a
* valid state before using it further.
*
* @tparam T The type to create.
*/
template <class T>
struct DefaultFactory {
/// @brief Creates a new instance of T.
static T create()
{
return T{};
}
};

/**
* @brief Detect whether T has a default_serialize member function.
* @tparam T Any type.
*/
template <class T>
using has_default_serialize = is_expression_valid<details::default_serialize_expr_t, T>;

/**
* @brief Serialization implementation for the default serialization feature.
* Disables itself (SFINAE) if there is no default_serialize member or if a serialize member is present.
* Generates the serialize method depending on the NamedRefs given by default_serialize.
* @tparam IOContext A type that models the IOContext concept.
* @tparam DefaultSerializable A type that can be default serialized.
* @param io An IO context.
* @param a An instance of DefaultSerializable to be serialized.
*/
template <class IOContext, class DefaultSerializable,
std::enable_if_t<has_default_serialize<DefaultSerializable>::value &&
!has_serialize<IOContext, DefaultSerializable>::value,
DefaultSerializable*> = nullptr>
void serialize_internal(IOContext& io, const DefaultSerializable& a)
{
// Note that the following cons_cast is only safe if we do not modify members.
const auto members = const_cast<DefaultSerializable&>(a).default_serialize();
// unpack members and serialize
std::apply(
[&io, &members](auto... named_refs) {
details::default_serialize_impl(io, members.name, named_refs...);
},
members.named_refs);
}

/**
* @brief Deserialization implementation for the default serialization feature.
* Disables itself (SFINAE) if there is no default_serialize member or if a deserialize meember is present.
* Generates the deserialize method depending on the NamedRefs given by default_serialize.
* @tparam IOContext A type that models the IOContext concept.
* @tparam DefaultSerializable A type that can be default serialized.
* @param io An IO context.
* @param tag Defines the type of the object that is to be deserialized (i.e. DefaultSerializble).
* @return The restored object if successful, an error otherwise.
*/
template <class IOContext, class DefaultSerializable,
std::enable_if_t<has_default_serialize<DefaultSerializable>::value &&
!has_deserialize<IOContext, DefaultSerializable>::value,
DefaultSerializable*> = nullptr>
IOResult<DefaultSerializable> deserialize_internal(IOContext& io, Tag<DefaultSerializable> tag)
{
mio::unused(tag);
DefaultSerializable a = DefaultFactory<DefaultSerializable>::create();
auto members = a.default_serialize();
// unpack members and deserialize
return std::apply(
[&io, &members, &a](auto... named_refs) {
return details::default_deserialize_impl(io, a, members.name, named_refs...);
},
members.named_refs);
}

} // namespace mio

#endif // MIO_IO_DEFAULT_SERIALIZE_H_
Loading