Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions cpp/memilio/epidemiology/populations.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "memilio/math/eigen.h"
#include "memilio/math/math_utils.h"

#include <concepts>
#include <numeric>

namespace mio
Expand Down Expand Up @@ -63,6 +64,25 @@ class Populations : public CustomIndexArray<UncertainValue<FP>, Categories...>
{
}

/// @brief Create populations by taking ownership of a CustomIndexArray.
explicit Populations(Base&& array)
: Base(std::move(array))
{
}

/**
* @brief Convert internally stored data to OtherType and save into new Populations.
* @tparam OtherType The type to convert into.
* @return New Populations of OtherType with copy of internal data.
*/
template <class OtherType>
requires std::convertible_to<typename Base::Type::Type, OtherType>
Populations<OtherType, Categories...> convert() const
{
return Populations<OtherType, Categories...>(
Base::template convert<typename Base::Type::Type>().template convert<UncertainValue<OtherType>>());
}

/**
* @brief get_num_compartments returns the number of compartments
*
Expand Down
4 changes: 2 additions & 2 deletions cpp/memilio/io/result_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ IOResult<void> save_result_with_params(const std::vector<TimeSeries<ScalarType>>
BOOST_OUTCOME_TRY(create_directory(result_dir_run.string()));
BOOST_OUTCOME_TRY(save_result(result, county_ids, (int)(size_t)params[0].parameters.get_num_groups(),
(result_dir_run / "Result.h5").string()));
BOOST_OUTCOME_TRY(write_graph(create_graph_without_edges<Model, MobilityParameters<ScalarType>>(params, county_ids),
BOOST_OUTCOME_TRY(write_graph(Graph<Model, MobilityParameters<ScalarType>>(county_ids, params),
result_dir_run.string(), IOF_OmitDistributions));
return success();
}
Expand Down Expand Up @@ -212,7 +212,7 @@ IOResult<void> save_results(const std::vector<std::vector<TimeSeries<ScalarType>
auto ensemble_params_p95 = ensemble_params_percentile(ensemble_params, 0.95);

auto make_graph = [&county_ids](auto&& params) {
return create_graph_without_edges<Model, MobilityParameters<ScalarType>>(params, county_ids);
return Graph<Model, MobilityParameters<ScalarType>>(county_ids, params);
};
BOOST_OUTCOME_TRY(
write_graph(make_graph(ensemble_params_p05), result_dir_p05.string(), IOF_OmitDistributions));
Expand Down
71 changes: 53 additions & 18 deletions cpp/memilio/mobility/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <algorithm>
#include <functional>
#include <iostream>
#include <concepts>
#include <ranges>

#include "boost/filesystem.hpp"
Expand Down Expand Up @@ -154,7 +155,57 @@ class Graph
using NodeProperty = NodePropertyT;
using EdgeProperty = EdgePropertyT;

/**
* @brief Construct graph without edges from pairs of node_ids and node_properties.
*/
Graph(const std::vector<int>& node_ids, const std::vector<NodePropertyT>& node_properties)
{
assert(node_ids.size() == node_properties.size());

for (auto i = size_t(0); i < node_ids.size(); ++i) {
add_node(node_ids[i], node_properties[i]);
}
}

/**
* @brief Construct graph without edges from node_properties with default node ids [0, 1, ...].
*/
Graph(std::vector<NodePropertyT>& node_properties)
{
for (auto i = size_t(0); i < node_properties.size(); ++i) {
add_node(i, node_properties[i]);
}
}

/**
* @brief Construct graph without edges, creating a node for each id in node_ids from the same node_args.
*/
template <class... Args>
requires std::constructible_from<NodePropertyT, Args...>
Graph(const std::vector<int>& node_ids, Args&&... node_args)
{
for (int id : node_ids) {
add_node(id, std::forward<Args>(node_args)...);
}
}

/**
* @brief Construct graph without edges, creating each node from the same node_args with default node ids [0, 1, ...].
*/
template <class... Args>
requires std::constructible_from<NodePropertyT, Args...>
Graph(const int number_of_nodes, Args&&... args)
{
for (int id = 0; id < number_of_nodes; ++id) {
add_node(id, std::forward<Args>(args)...);
}
}

Graph() = default;

/**
* @brief Construct graph containing the given nodes and edges.
*/
Graph(std::vector<Node<NodePropertyT>>&& nodes, std::vector<Edge<EdgePropertyT>>&& edges)
: m_nodes(std::move(nodes))
, m_edges(std::move(edges))
Expand All @@ -168,6 +219,7 @@ class Graph
* @tparam args additional arguments for node construction
*/
template <class... Args>
requires std::constructible_from<NodePropertyT, Args...>
void add_node(int id, Args&&... args)
{
m_nodes.emplace_back(id, std::forward<Args>(args)...);
Expand All @@ -182,6 +234,7 @@ class Graph
* If an edge with the same start and end node indices already exists, it is replaced by the newly constructed edge.
*/
template <class... Args>
requires std::constructible_from<EdgePropertyT, Args...>
void add_edge(size_t start_node_idx, size_t end_node_idx, Args&&... args)
{
assert(m_nodes.size() > start_node_idx && m_nodes.size() > end_node_idx);
Expand Down Expand Up @@ -397,24 +450,6 @@ IOResult<void> set_edges(const fs::path& mobility_data_file, Graph<Model, Mobili
return success();
}

/**
* Create an unconnected graph.
* Can be used to save space on disk when writing parameters if the edges are not required.
* @param node_properties Vector of node properties of all nodes, e.g., parameters in each model node.
* @param node_ids Indices for the nodes.
* @return Graph with nodes only having no edges.
*/
template <class NodePropertyT, class EdgePropertyT>
auto create_graph_without_edges(const std::vector<NodePropertyT>& node_properties, const std::vector<int>& node_ids)
{
// create a graph without edges for writing to file
auto graph = mio::Graph<NodePropertyT, EdgePropertyT>();
for (auto i = size_t(0); i < node_ids.size(); ++i) {
graph.add_node(node_ids[i], node_properties[i]);
}
return graph;
}

template <class T>
std::enable_if_t<!has_ostream_op<T>::value, void> print_graph_object(std::ostream& os, size_t idx, const T&)
{
Expand Down
17 changes: 17 additions & 0 deletions cpp/memilio/utils/custom_index_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "memilio/utils/index.h"
#include "memilio/utils/stl_util.h"

#include <concepts>

namespace
{

Expand Down Expand Up @@ -342,6 +344,21 @@ class CustomIndexArray
}
}

/**
* @brief Convert internally stored data to OtherType and save into new CustomIndexArray.
* @tparam OtherType The type to convert into.
* @return New CustomIndexArray of OtherType with copy of internal data.
*/
template <class OtherType>
requires std::convertible_to<Type, OtherType>
CustomIndexArray<OtherType, Tags...> convert() const
{
CustomIndexArray<OtherType, Tags...> other;
other.resize(m_dimensions);
other.array() = m_y.template cast<OtherType>();
return other;
}

private:
// Random Access Iterator for CustomIndexArray
// To Do: As of Eigen 3.4, this is not needed anymore,
Expand Down
15 changes: 15 additions & 0 deletions cpp/memilio/utils/stl_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,21 @@ constexpr std::array<T, size_t(T::Count)> enum_members()
return enum_members;
}

/**
* @brief Defines generic Range type for IterPair of a vector.
* When elements should be const, template argument accepts const type.
* As vector is not defined for const values, the const specifier is removed and
* the const_iterator is used. std::conditional tries to compile both cases, thus
* we also need std::remove_const for the case where T is not const.
*/
template <class T>
using VectorRange =
std::conditional_t<std::is_const_v<T>,
typename mio::Range<std::pair<typename std::vector<std::remove_const_t<T>>::const_iterator,
typename std::vector<std::remove_const_t<T>>::const_iterator>>,
typename mio::Range<std::pair<typename std::vector<std::remove_const_t<T>>::iterator,
typename std::vector<std::remove_const_t<T>>::iterator>>>;

} // namespace mio

#endif //STL_UTIL_H
2 changes: 2 additions & 0 deletions cpp/memilio/utils/uncertain_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ template <typename FP = ScalarType>
class UncertainValue
{
public:
using Type = FP;

UncertainValue(FP v, const ParameterDistribution& dist)
: m_value(v)
, m_dist(dist.clone())
Expand Down
28 changes: 28 additions & 0 deletions cpp/models/ode_secirvvs/parameters_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#ifdef MEMILIO_HAS_JSONCPP

#include "ode_secirvvs/model.h"
#include "memilio/mobility/graph.h"
#include "memilio/io/epi_data.h"
#include "memilio/io/parameters_io.h"
#include "memilio/io/io.h"
Expand Down Expand Up @@ -1058,6 +1059,33 @@ IOResult<void> read_input_data(std::vector<Model>& model, Date date, const std::
return success();
}

/**
* @brief Converts input data from one range of models to another with different type.
*
* @tparam FP Floating point type.
* @param[in] model_from VectorRange of Node%s each containing a Model with the input data.
* @param[in,out] model_to VectorRange of Node%s each containing a Model to be initialized with data.
*
* @return An IOResult indicating success or failure.
*/
template <class FP>
IOResult<void> convert_model_data_type(mio::VectorRange<Node<Model<ScalarType>>> model_from,
mio::VectorRange<Node<Model<FP>>> model_to)
{
assert(model_from.size() == model_to.size());
assert((size_t)model_from[0].property.parameters.get_num_groups() ==
(size_t)model_to[0].property.parameters.get_num_groups());
// Todo: Add conversion of ParameterSet and then re-use code from all model parameters io
// OR call set_vacination_data with FP to set correct parameters

for (size_t region_idx = 0; region_idx < model_from.size(); ++region_idx) {
// convert populations to mio::UncertainValue<FP>
// needs 2 converts as mio::UncertainValue<ScalarType> -> mio::UncertainValue<FP> does not work
model_to[region_idx].property.populations = model_from[region_idx].property.populations.template convert<FP>();
}
return mio::success();
}

} // namespace osecirvvs
} // namespace mio

Expand Down
22 changes: 22 additions & 0 deletions cpp/tests/test_custom_index_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,25 @@ TEST(CustomIndexArray, setMultiple_emptyIndices)
}
}
}

TEST(CustomIndexArray, convert_floating_point)
{
// case: float arrays with mixed precision; expect same result after conversion through double precision
float value_float = 0.10005f;
auto size_dim1 = mio::Index<Dim1>(3);
mio::CustomIndexArray<float, Dim1> array_float({size_dim1}, value_float);
mio::CustomIndexArray<float, Dim1> array_float2 = array_float.convert<double>().convert<float>();
for (auto i = mio::Index<Dim1>(0); i < size_dim1; ++i) {
EXPECT_FLOAT_EQ(array_float2[i], value_float);
}

// case: double arrays with mixed precision; expect the value to be truncated during conversion to float
double value_double = 1.0 + 5e-16;
mio::CustomIndexArray<double, Dim1> array_double({size_dim1}, value_double);
array_float = array_double.convert<float>();
for (auto i = mio::Index<Dim1>(0); i < size_dim1; ++i) {
// Check if array_double is unchanged
EXPECT_DOUBLE_EQ(array_double[i], value_double);
EXPECT_FLOAT_EQ(array_float[i], 1.f);
}
}
4 changes: 2 additions & 2 deletions cpp/tests/test_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ TEST(TestGraph, graph_without_edges)
std::vector<MockModel> models = {MockModel(), MockModel()};
std::vector<int> ids = {1, 2};

auto g = mio::create_graph_without_edges<MockModel, MockMobility>(models, ids);

mio::Graph<MockModel, MockMobility> g(ids, models);
EXPECT_EQ(g.edges().size(), 0);
EXPECT_EQ(g.nodes().size(), 2);
EXPECT_EQ(g.nodes()[0].id, 1);
Expand Down
23 changes: 23 additions & 0 deletions cpp/tests/test_populations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,26 @@ TEST(TestPopulations, populations_constraints)
// Reactive log output.
mio::set_log_level(mio::LogLevel::warn);
}

TEST(TestPopulations, convert_floating_point)
{
// similar test to CustomIndexArray.convert_floating_point
// case: float arrays with mixed precision; expect same result after conversion through double precision
float value_float = 0.10005f;
auto size_dim1 = mio::Index<TestAgeGroup>(7);
mio::Populations<float, TestAgeGroup> pop_float({size_dim1}, value_float);
mio::Populations<float, TestAgeGroup> pop_float2 = pop_float.convert<double>().convert<float>();
for (auto i = mio::Index<TestAgeGroup>(0); i < size_dim1; ++i) {
ASSERT_FLOAT_EQ(pop_float2[i], value_float);
}

// case: double arrays with mixed precision; expect the value to be truncated during conversion to float
double value_double = 1.0 + 5e-16;
mio::Populations<float, TestAgeGroup> pop_double({size_dim1}, value_double);
pop_float = pop_double.convert<float>();
for (auto i = mio::Index<TestAgeGroup>(0); i < size_dim1; ++i) {
// Check if pop_double is unchanged
ASSERT_DOUBLE_EQ(pop_double[i], value_double);
ASSERT_FLOAT_EQ(pop_float[i], 1.f);
}
}
2 changes: 1 addition & 1 deletion cpp/tests/test_save_parameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ TEST(TestSaveParameters, read_graph_without_edges)
std::vector<mio::osecir::Model<double>> models = {model, model};
std::vector<int> ids = {0, 1};
auto graph_no_edges =
mio::create_graph_without_edges<mio::osecir::Model<double>, mio::MobilityParameters<double>>(models, ids);
mio::Graph<mio::osecir::Model<double>, mio::MobilityParameters<double>>(ids, models);
auto write_status = mio::write_graph(graph_no_edges, tmp_results_dir);
ASSERT_THAT(print_wrap(write_status), IsSuccess());

Expand Down
Loading