diff --git a/cpp/memilio/epidemiology/populations.h b/cpp/memilio/epidemiology/populations.h index f2da6092ad..3c300bd79a 100644 --- a/cpp/memilio/epidemiology/populations.h +++ b/cpp/memilio/epidemiology/populations.h @@ -26,6 +26,7 @@ #include "memilio/math/eigen.h" #include "memilio/math/math_utils.h" +#include #include namespace mio @@ -63,6 +64,25 @@ class Populations : public CustomIndexArray, 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 + requires std::convertible_to + Populations convert() const + { + return Populations( + Base::template convert().template convert>()); + } + /** * @brief get_num_compartments returns the number of compartments * diff --git a/cpp/memilio/io/result_io.h b/cpp/memilio/io/result_io.h index e325100e48..56a7ea7070 100644 --- a/cpp/memilio/io/result_io.h +++ b/cpp/memilio/io/result_io.h @@ -117,7 +117,7 @@ IOResult save_result_with_params(const std::vector> 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>(params, county_ids), + BOOST_OUTCOME_TRY(write_graph(Graph>(county_ids, params), result_dir_run.string(), IOF_OmitDistributions)); return success(); } @@ -212,7 +212,7 @@ IOResult save_results(const std::vector auto ensemble_params_p95 = ensemble_params_percentile(ensemble_params, 0.95); auto make_graph = [&county_ids](auto&& params) { - return create_graph_without_edges>(params, county_ids); + return Graph>(county_ids, params); }; BOOST_OUTCOME_TRY( write_graph(make_graph(ensemble_params_p05), result_dir_p05.string(), IOF_OmitDistributions)); diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 1c30749a24..fade2f3a45 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "boost/filesystem.hpp" @@ -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& node_ids, const std::vector& 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& 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 + requires std::constructible_from + Graph(const std::vector& node_ids, Args&&... node_args) + { + for (int id : node_ids) { + add_node(id, std::forward(node_args)...); + } + } + + /** + * @brief Construct graph without edges, creating each node from the same node_args with default node ids [0, 1, ...]. + */ + template + requires std::constructible_from + Graph(const int number_of_nodes, Args&&... args) + { + for (int id = 0; id < number_of_nodes; ++id) { + add_node(id, std::forward(args)...); + } + } + Graph() = default; + + /** + * @brief Construct graph containing the given nodes and edges. + */ Graph(std::vector>&& nodes, std::vector>&& edges) : m_nodes(std::move(nodes)) , m_edges(std::move(edges)) @@ -168,6 +219,7 @@ class Graph * @tparam args additional arguments for node construction */ template + requires std::constructible_from void add_node(int id, Args&&... args) { m_nodes.emplace_back(id, std::forward(args)...); @@ -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 + requires std::constructible_from 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); @@ -397,24 +450,6 @@ IOResult set_edges(const fs::path& mobility_data_file, Graph -auto create_graph_without_edges(const std::vector& node_properties, const std::vector& node_ids) -{ - // create a graph without edges for writing to file - auto graph = mio::Graph(); - for (auto i = size_t(0); i < node_ids.size(); ++i) { - graph.add_node(node_ids[i], node_properties[i]); - } - return graph; -} - template std::enable_if_t::value, void> print_graph_object(std::ostream& os, size_t idx, const T&) { diff --git a/cpp/memilio/utils/custom_index_array.h b/cpp/memilio/utils/custom_index_array.h index d17d496275..c621c7c8b8 100644 --- a/cpp/memilio/utils/custom_index_array.h +++ b/cpp/memilio/utils/custom_index_array.h @@ -24,6 +24,8 @@ #include "memilio/utils/index.h" #include "memilio/utils/stl_util.h" +#include + namespace { @@ -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 + requires std::convertible_to + CustomIndexArray convert() const + { + CustomIndexArray other; + other.resize(m_dimensions); + other.array() = m_y.template cast(); + return other; + } + private: // Random Access Iterator for CustomIndexArray // To Do: As of Eigen 3.4, this is not needed anymore, diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index 5abc2791f3..d663843c09 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -315,6 +315,21 @@ constexpr std::array 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 +using VectorRange = + std::conditional_t, + typename mio::Range>::const_iterator, + typename std::vector>::const_iterator>>, + typename mio::Range>::iterator, + typename std::vector>::iterator>>>; + } // namespace mio #endif //STL_UTIL_H diff --git a/cpp/memilio/utils/uncertain_value.h b/cpp/memilio/utils/uncertain_value.h index e861e2c6b5..38ded9e524 100644 --- a/cpp/memilio/utils/uncertain_value.h +++ b/cpp/memilio/utils/uncertain_value.h @@ -45,6 +45,8 @@ template class UncertainValue { public: + using Type = FP; + UncertainValue(FP v, const ParameterDistribution& dist) : m_value(v) , m_dist(dist.clone()) diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index 3cd0a412c0..815f09febf 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -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" @@ -1058,6 +1059,33 @@ IOResult read_input_data(std::vector& 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 +IOResult convert_model_data_type(mio::VectorRange>> model_from, + mio::VectorRange>> 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 + // needs 2 converts as mio::UncertainValue -> mio::UncertainValue does not work + model_to[region_idx].property.populations = model_from[region_idx].property.populations.template convert(); + } + return mio::success(); +} + } // namespace osecirvvs } // namespace mio diff --git a/cpp/tests/test_custom_index_array.cpp b/cpp/tests/test_custom_index_array.cpp index f1b1f72a0e..1995e800c1 100644 --- a/cpp/tests/test_custom_index_array.cpp +++ b/cpp/tests/test_custom_index_array.cpp @@ -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(3); + mio::CustomIndexArray array_float({size_dim1}, value_float); + mio::CustomIndexArray array_float2 = array_float.convert().convert(); + for (auto i = mio::Index(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 array_double({size_dim1}, value_double); + array_float = array_double.convert(); + for (auto i = mio::Index(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); + } +} diff --git a/cpp/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index 1f14ce65b9..bfffc6c8c2 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -147,8 +147,8 @@ TEST(TestGraph, graph_without_edges) std::vector models = {MockModel(), MockModel()}; std::vector ids = {1, 2}; - auto g = mio::create_graph_without_edges(models, ids); - + mio::Graph g(ids, models); + EXPECT_EQ(g.edges().size(), 0); EXPECT_EQ(g.nodes().size(), 2); EXPECT_EQ(g.nodes()[0].id, 1); diff --git a/cpp/tests/test_populations.cpp b/cpp/tests/test_populations.cpp index a031e294af..3d03002ed7 100644 --- a/cpp/tests/test_populations.cpp +++ b/cpp/tests/test_populations.cpp @@ -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(7); + mio::Populations pop_float({size_dim1}, value_float); + mio::Populations pop_float2 = pop_float.convert().convert(); + for (auto i = mio::Index(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 pop_double({size_dim1}, value_double); + pop_float = pop_double.convert(); + for (auto i = mio::Index(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); + } +} diff --git a/cpp/tests/test_save_parameters.cpp b/cpp/tests/test_save_parameters.cpp index e08324a9ed..acdd16c546 100644 --- a/cpp/tests/test_save_parameters.cpp +++ b/cpp/tests/test_save_parameters.cpp @@ -255,7 +255,7 @@ TEST(TestSaveParameters, read_graph_without_edges) std::vector> models = {model, model}; std::vector ids = {0, 1}; auto graph_no_edges = - mio::create_graph_without_edges, mio::MobilityParameters>(models, ids); + mio::Graph, mio::MobilityParameters>(ids, models); auto write_status = mio::write_graph(graph_no_edges, tmp_results_dir); ASSERT_THAT(print_wrap(write_status), IsSuccess());