From 2fb8ae1db3abc6ae479583e170ee1c83a6e5bcf8 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:30:27 +0000 Subject: [PATCH 01/16] Add new functions --- cpp/memilio/epidemiology/populations.h | 18 +++++++++++ cpp/memilio/mobility/graph.h | 41 +++++++++++++++++++++++-- cpp/memilio/utils/custom_index_array.h | 16 ++++++++++ cpp/memilio/utils/stl_util.h | 6 ++++ cpp/models/ode_secir/parameters_io.h | 23 ++++++++++++++ cpp/models/ode_secirts/parameters_io.h | 28 +++++++++++++++++ cpp/models/ode_secirvvs/parameters_io.h | 29 +++++++++++++++++ 7 files changed, 158 insertions(+), 3 deletions(-) diff --git a/cpp/memilio/epidemiology/populations.h b/cpp/memilio/epidemiology/populations.h index f2da6092ad..c29111532a 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 @@ -54,6 +55,7 @@ class Populations : public CustomIndexArray, Categories...> { public: using Base = CustomIndexArray, Categories...>; + using BaseFP = FP; using Index = typename Base::Index; template , Categories...> { } + 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/mobility/graph.h b/cpp/memilio/mobility/graph.h index 26a8bb5a6a..67bc817bd8 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -20,7 +20,6 @@ #ifndef GRAPH_H #define GRAPH_H -#include #include "memilio/utils/stl_util.h" #include "memilio/epidemiology/age_group.h" #include "memilio/utils/date.h" @@ -29,6 +28,8 @@ #include "memilio/epidemiology/damping.h" #include "memilio/geography/regions.h" #include +#include +#include #include "boost/filesystem.hpp" @@ -152,10 +153,44 @@ class Graph using NodeProperty = NodePropertyT; using EdgeProperty = EdgePropertyT; + 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]); + } + } + + Graph(std::vector& node_properties) + { + for (auto i = size_t(0); i < node_properties.size(); ++i) { + add_node(i, node_properties[i]); + } + } + + 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)...); + } + } + + 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 add a node to the graph. property of the node is constructed from arguments. */ - template + template requires std::constructible_from Node& add_node(int id, Args&&... args) { m_nodes.emplace_back(id, std::forward(args)...); @@ -165,7 +200,7 @@ class Graph /** * @brief add an edge to the graph. property of the edge is constructed from arguments. */ - template + template requires std::constructible_from Edge& 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); diff --git a/cpp/memilio/utils/custom_index_array.h b/cpp/memilio/utils/custom_index_array.h index d17d496275..14ac136a88 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,20 @@ 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 f1deb46c04..67efd672cd 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -315,6 +315,12 @@ constexpr std::array enum_members() return enum_members; } +template +using VectorRange = mio::Range::iterator, typename std::vector::iterator>>; + +template +using ConstVectorRange = mio::Range::const_iterator, typename std::vector::const_iterator>>; + } // namespace mio #endif //STL_UTIL_H diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 0505d45563..3970a7125b 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -499,6 +499,29 @@ 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 (default: double). + * @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 +void convert_input_data_type(const mio::VectorRange>>& model_from, const 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 other model parameters io + + 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(); + } +} + } // namespace osecir } // namespace mio diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index c86263e717..c1b2aa95b8 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -1099,6 +1099,34 @@ 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 (default: double). + * @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_input_data_type(const mio::VectorRange>>& model_from, + const 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 success(); +} + } // namespace osecirts } // namespace mio diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index 3cd0a412c0..f37b445745 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1058,6 +1058,35 @@ 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 (default: double). + * @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_input_data_type(const mio::VectorRange>>& model_from, + const 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 success(); +} + } // namespace osecirvvs } // namespace mio From 948e1fbe8bc737b94ed9cad768d286da907af0b3 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:32:49 +0000 Subject: [PATCH 02/16] Remove unnecessary helper function --- cpp/memilio/io/result_io.h | 4 ++-- cpp/tests/test_graph.cpp | 4 ++-- cpp/tests/test_save_parameters.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index 65bdb71c42..1ea4a7fa29 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -146,8 +146,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_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()); From ad2aec073b7ab4ca260f7fb28beed21dfbcb1171 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:04:25 +0000 Subject: [PATCH 03/16] Bug fix --- cpp/memilio/mobility/graph.h | 18 ------------------ cpp/models/ode_secir/parameters_io.h | 1 + cpp/models/ode_secirvvs/parameters_io.h | 1 + 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 67bc817bd8..17703550ea 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -418,24 +418,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/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 3970a7125b..219860265c 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -26,6 +26,7 @@ #ifdef MEMILIO_HAS_JSONCPP #include "ode_secir/model.h" +#include "memilio/mobility/graph.h" #include "memilio/io/epi_data.h" #include "memilio/io/parameters_io.h" #include "memilio/io/result_io.h" diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index f37b445745..e3fcc80787 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" From 5c2118d4c1508edbf24ce2e2ae80a6f830532a8e Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:43:23 +0000 Subject: [PATCH 04/16] requested changes --- cpp/memilio/mobility/graph.h | 12 ++++++++++++ cpp/models/ode_secir/parameters_io.h | 6 ++---- cpp/models/ode_secirts/parameters_io.h | 7 ++----- cpp/models/ode_secirvvs/parameters_io.h | 7 ++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 17703550ea..7315beff72 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -153,6 +153,9 @@ class Graph using NodeProperty = NodePropertyT; using EdgeProperty = EdgePropertyT; + /** + * @brief construct graph without edges from pairs of node ids and properties. + */ Graph(const std::vector& node_ids, const std::vector& node_properties) { assert(node_ids.size() == node_properties.size()); @@ -162,6 +165,9 @@ class Graph } } + /** + * @brief construct graph without edges from properties with default node ids. + */ Graph(std::vector& node_properties) { for (auto i = size_t(0); i < node_properties.size(); ++i) { @@ -169,6 +175,9 @@ class Graph } } + /** + * @brief construct graph without edges creating each node with same arguments and a corresponding node id. + */ template requires std::constructible_from Graph(const std::vector& node_ids, Args&&... node_args) { @@ -177,6 +186,9 @@ class Graph } } + /** + * @brief construct graph without edges creating each node with same arguments and default node ids. + */ template requires std::constructible_from Graph(const int number_of_nodes, Args&&... args) { diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 219860265c..6f68a3345c 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -503,14 +503,12 @@ IOResult read_input_data(std::vector& model, Date date, const std:: /** * @brief Converts input data from one range of models to another with different type. * - * @tparam FP Floating point type (default: double). + * @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 -void convert_input_data_type(const mio::VectorRange>>& model_from, const mio::VectorRange>>& model_to) +void convert_model_data_type(const mio::VectorRange>>& model_from, const 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()); diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index c1b2aa95b8..4eb3f3f56b 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -1102,14 +1102,12 @@ IOResult read_input_data(std::vector& model, Date date, const std:: /** * @brief Converts input data from one range of models to another with different type. * - * @tparam FP Floating point type (default: double). + * @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_input_data_type(const mio::VectorRange>>& model_from, +void convert_model_data_type(const mio::VectorRange>>& model_from, const mio::VectorRange>>& model_to) { assert(model_from.size() == model_to.size()); @@ -1124,7 +1122,6 @@ IOResult convert_input_data_type(const mio::VectorRange(); } - return success(); } } // namespace osecirts diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index e3fcc80787..8cfd7172c9 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1063,14 +1063,12 @@ IOResult read_input_data(std::vector& model, Date date, const std:: /** * @brief Converts input data from one range of models to another with different type. * - * @tparam FP Floating point type (default: double). + * @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_input_data_type(const mio::VectorRange>>& model_from, +void convert_model_data_type(const mio::VectorRange>>& model_from, const mio::VectorRange>>& model_to) { assert(model_from.size() == model_to.size()); @@ -1085,7 +1083,6 @@ IOResult convert_input_data_type(const mio::VectorRange(); } - return success(); } } // namespace osecirvvs From d206c1698f8efea681a41544b8a010be2ee24929 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:44:40 +0000 Subject: [PATCH 05/16] Change VectorRange and pass them by value --- cpp/memilio/utils/stl_util.h | 10 ++++++---- cpp/models/ode_secir/parameters_io.h | 3 ++- cpp/models/ode_secirts/parameters_io.h | 4 ++-- cpp/models/ode_secirvvs/parameters_io.h | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index 67efd672cd..fd61c0964a 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -315,11 +315,13 @@ constexpr std::array enum_members() return enum_members; } -template -using VectorRange = mio::Range::iterator, typename std::vector::iterator>>; +template +using VectorRange = std::conditional_t, + typename mio::Range>::const_iterator, + typename std::vector>::const_iterator>>, + typename mio::Range::iterator, + typename std::vector::iterator>>>; -template -using ConstVectorRange = mio::Range::const_iterator, typename std::vector::const_iterator>>; } // namespace mio diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 6f68a3345c..8786449268 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -508,7 +508,8 @@ IOResult read_input_data(std::vector& model, Date date, const std:: * @param[in,out] model_to VectorRange of Node%s each containing a Model to be initialized with data. */ template -void convert_model_data_type(const mio::VectorRange>>& model_from, const mio::VectorRange>>& model_to) +void 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()); diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index 4eb3f3f56b..0ffd2fa5bc 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -1107,8 +1107,8 @@ IOResult read_input_data(std::vector& model, Date date, const std:: * @param[in,out] model_to VectorRange of Node%s each containing a Model to be initialized with data. */ template -void convert_model_data_type(const mio::VectorRange>>& model_from, - const mio::VectorRange>>& model_to) +void 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() == diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index 8cfd7172c9..b1512d4f21 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1068,8 +1068,8 @@ IOResult read_input_data(std::vector& model, Date date, const std:: * @param[in,out] model_to VectorRange of Node%s each containing a Model to be initialized with data. */ template -void convert_model_data_type(const mio::VectorRange>>& model_from, - const mio::VectorRange>>& model_to) +void 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() == From a78547714ea4f7521b346da85f4639a19b22c75a Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:26:52 +0000 Subject: [PATCH 06/16] VectorRange update --- cpp/memilio/utils/stl_util.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index fd61c0964a..30df83ce4c 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -315,12 +315,19 @@ 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>>>; + typename mio::Range>::iterator, + typename std::vector>::iterator>>>; } // namespace mio From ed75d521d6cf9319d0a611baa2ad91905b40f481 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:48:12 +0000 Subject: [PATCH 07/16] Requested Changes --- cpp/memilio/mobility/graph.h | 8 ++++---- cpp/models/ode_secir/parameters_io.h | 22 ---------------------- cpp/models/ode_secirts/parameters_io.h | 25 ------------------------- cpp/models/ode_secirvvs/parameters_io.h | 7 +++++-- 4 files changed, 9 insertions(+), 53 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 7315beff72..17e1463e4e 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -154,7 +154,7 @@ class Graph using EdgeProperty = EdgePropertyT; /** - * @brief construct graph without edges from pairs of node ids and properties. + * @brief Construct graph without edges from pairs of node_ids and node_properties. */ Graph(const std::vector& node_ids, const std::vector& node_properties) { @@ -166,7 +166,7 @@ class Graph } /** - * @brief construct graph without edges from properties with default node ids. + * @brief Construct graph without edges from node_properties with default node ids [0, 1, ...]. */ Graph(std::vector& node_properties) { @@ -176,7 +176,7 @@ class Graph } /** - * @brief construct graph without edges creating each node with same arguments and a corresponding node id. + * @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) @@ -187,7 +187,7 @@ class Graph } /** - * @brief construct graph without edges creating each node with same arguments and default node ids. + * @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) diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 8786449268..0da6044dfd 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -500,28 +500,6 @@ 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. - */ -template -void 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 other model parameters io - - 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(); - } -} - } // namespace osecir } // namespace mio diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index 0ffd2fa5bc..c86263e717 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -1099,31 +1099,6 @@ 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. - */ -template -void 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(); - } -} - } // namespace osecirts } // namespace mio diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index b1512d4f21..0695d172fe 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1066,10 +1066,12 @@ IOResult read_input_data(std::vector& model, Date date, const std:: * @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 -void convert_model_data_type(mio::VectorRange>> model_from, - mio::VectorRange>> model_to) +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() == @@ -1083,6 +1085,7 @@ void convert_model_data_type(mio::VectorRange>> model_fro model_to[region_idx].property.populations = model_from[region_idx] .property.populations.template convert(); } + return mio::sucess(); } } // namespace osecirvvs From 335736599825c5e2222fa4fb89d080096806fe47 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:28:34 +0000 Subject: [PATCH 08/16] Add FP type alias to UnvertainValue --- cpp/memilio/epidemiology/populations.h | 5 ++--- cpp/memilio/utils/uncertain_value.h | 2 ++ cpp/models/ode_secirvvs/parameters_io.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cpp/memilio/epidemiology/populations.h b/cpp/memilio/epidemiology/populations.h index c29111532a..ab085dfb75 100644 --- a/cpp/memilio/epidemiology/populations.h +++ b/cpp/memilio/epidemiology/populations.h @@ -55,7 +55,6 @@ class Populations : public CustomIndexArray, Categories...> { public: using Base = CustomIndexArray, Categories...>; - using BaseFP = FP; using Index = typename Base::Index; template , Categories...> * @tparam OtherType The type to convert into. * @return New Populations of OtherType with copy of internal data. */ - template requires std::convertible_to + template requires std::convertible_to Populations convert() const { - return Populations(Base::template convert().template convert>()); + return Populations(Base::template convert().template convert>()); } /** 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 0695d172fe..2adb969195 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1085,7 +1085,7 @@ IOResult convert_model_data_type(mio::VectorRange>> model_to[region_idx].property.populations = model_from[region_idx] .property.populations.template convert(); } - return mio::sucess(); + return mio::success(); } } // namespace osecirvvs From 2cbc36db250720548c8a6b325a3d5c1a9d6df90d Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:54:20 +0100 Subject: [PATCH 09/16] Update cpp/memilio/epidemiology/populations.h Co-authored-by: reneSchm <49305466+reneSchm@users.noreply.github.com> --- cpp/memilio/epidemiology/populations.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/memilio/epidemiology/populations.h b/cpp/memilio/epidemiology/populations.h index ab085dfb75..3737b8d4cc 100644 --- a/cpp/memilio/epidemiology/populations.h +++ b/cpp/memilio/epidemiology/populations.h @@ -64,7 +64,8 @@ class Populations : public CustomIndexArray, Categories...> { } - explicit Populations(Base&& array) + /// @brief Create populations by taking ownership of a CustomIndexArray. + explicit Populations(Base&& array) : Base(std::move(array)) { } From d4dfc04d9247c0c59b7e879c0856a9a747345e69 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:43:40 +0000 Subject: [PATCH 10/16] format --- cpp/memilio/epidemiology/populations.h | 6 ++++-- cpp/memilio/mobility/graph.h | 12 ++++++++---- cpp/memilio/utils/custom_index_array.h | 3 ++- cpp/memilio/utils/stl_util.h | 14 +++++++------- cpp/models/ode_secirvvs/parameters_io.h | 4 +--- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/cpp/memilio/epidemiology/populations.h b/cpp/memilio/epidemiology/populations.h index 3737b8d4cc..3c300bd79a 100644 --- a/cpp/memilio/epidemiology/populations.h +++ b/cpp/memilio/epidemiology/populations.h @@ -75,10 +75,12 @@ class Populations : public CustomIndexArray, Categories...> * @tparam OtherType The type to convert into. * @return New Populations of OtherType with copy of internal data. */ - template requires std::convertible_to + template + requires std::convertible_to Populations convert() const { - return Populations(Base::template convert().template convert>()); + return Populations( + Base::template convert().template convert>()); } /** diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 3e6b9f8772..605613d724 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -180,7 +180,8 @@ class Graph /** * @brief Construct graph without edges, creating a node for each id in node_ids from the same node_args. */ - template requires std::constructible_from + template + requires std::constructible_from Graph(const std::vector& node_ids, Args&&... node_args) { for (int id : node_ids) { @@ -191,7 +192,8 @@ class Graph /** * @brief Construct graph without edges, creating each node from the same node_args with default node ids [0, 1, ...]. */ - template requires std::constructible_from + template + requires std::constructible_from Graph(const int number_of_nodes, Args&&... args) { for (int id = 0; id < number_of_nodes; ++id) { @@ -213,7 +215,8 @@ class Graph * @param id id for the node * @tparam args additional arguments for node construction */ - template requires std::constructible_from + template + requires std::constructible_from void add_node(int id, Args&&... args) { m_nodes.emplace_back(id, std::forward(args)...); @@ -227,7 +230,8 @@ 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 + 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); diff --git a/cpp/memilio/utils/custom_index_array.h b/cpp/memilio/utils/custom_index_array.h index 14ac136a88..c621c7c8b8 100644 --- a/cpp/memilio/utils/custom_index_array.h +++ b/cpp/memilio/utils/custom_index_array.h @@ -349,7 +349,8 @@ class CustomIndexArray * @tparam OtherType The type to convert into. * @return New CustomIndexArray of OtherType with copy of internal data. */ - template requires std::convertible_to + template + requires std::convertible_to CustomIndexArray convert() const { CustomIndexArray other; diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index 1926177ee6..d663843c09 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -322,13 +322,13 @@ constexpr std::array enum_members() * 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>>>; - +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 diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index 2adb969195..815f09febf 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -1059,7 +1059,6 @@ 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. * @@ -1082,8 +1081,7 @@ IOResult convert_model_data_type(mio::VectorRange>> 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(); + model_to[region_idx].property.populations = model_from[region_idx].property.populations.template convert(); } return mio::success(); } From ce1b8a2fd480ecc5b2562c739d1d14e67c89cac2 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:47:39 +0000 Subject: [PATCH 11/16] fix --- cpp/memilio/mobility/graph.h | 3 +++ cpp/models/ode_secir/parameters_io.h | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 605613d724..fade2f3a45 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -203,6 +203,9 @@ class Graph 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)) diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 0da6044dfd..0505d45563 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -26,7 +26,6 @@ #ifdef MEMILIO_HAS_JSONCPP #include "ode_secir/model.h" -#include "memilio/mobility/graph.h" #include "memilio/io/epi_data.h" #include "memilio/io/parameters_io.h" #include "memilio/io/result_io.h" From 58c25dfc1fbfb6809f87e2bb386f98a252efea91 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:00:36 +0000 Subject: [PATCH 12/16] tests --- cpp/tests/test_custom_index_array.cpp | 22 ++++++++++++++++++++++ cpp/tests/test_populations.cpp | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/cpp/tests/test_custom_index_array.cpp b/cpp/tests/test_custom_index_array.cpp index f1b1f72a0e..ad183484c4 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.10005; + 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); + } +} \ No newline at end of file diff --git a/cpp/tests/test_populations.cpp b/cpp/tests/test_populations.cpp index a031e294af..6220609858 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.10005; + 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); + } +} From df19d46bc9f879df8878dc92d20554ddfaa5e87f Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:01:45 +0000 Subject: [PATCH 13/16] test fix --- cpp/tests/test_populations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/tests/test_populations.cpp b/cpp/tests/test_populations.cpp index 6220609858..7f8d650e3f 100644 --- a/cpp/tests/test_populations.cpp +++ b/cpp/tests/test_populations.cpp @@ -269,7 +269,7 @@ TEST(TestPopulations, convert_floating_point) 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); + ASSERT_FLOAT_EQ(pop_float2[i], value_float); } // case: double arrays with mixed precision; expect the value to be truncated during conversion to float From 2e5cff40b9baeb903ecf1af7cd1be8dcc9a03bbd Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:03:16 +0000 Subject: [PATCH 14/16] fix test --- cpp/tests/test_custom_index_array.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/tests/test_custom_index_array.cpp b/cpp/tests/test_custom_index_array.cpp index ad183484c4..13ce825e23 100644 --- a/cpp/tests/test_custom_index_array.cpp +++ b/cpp/tests/test_custom_index_array.cpp @@ -433,7 +433,7 @@ TEST(CustomIndexArray, convert_floating_point) 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); + EXPECT_FLOAT_EQ(array_float2[i], value_float); } // case: double arrays with mixed precision; expect the value to be truncated during conversion to float From 5ca9e2bb957a606f19daae699d7e36f75856c029 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:31:49 +0000 Subject: [PATCH 15/16] build for msvc fails --- cpp/tests/test_custom_index_array.cpp | 2 +- cpp/tests/test_populations.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/tests/test_custom_index_array.cpp b/cpp/tests/test_custom_index_array.cpp index 13ce825e23..22697e2a71 100644 --- a/cpp/tests/test_custom_index_array.cpp +++ b/cpp/tests/test_custom_index_array.cpp @@ -428,7 +428,7 @@ 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.10005; + 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(); diff --git a/cpp/tests/test_populations.cpp b/cpp/tests/test_populations.cpp index 7f8d650e3f..3d03002ed7 100644 --- a/cpp/tests/test_populations.cpp +++ b/cpp/tests/test_populations.cpp @@ -264,7 +264,7 @@ 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.10005; + 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(); From bcf14e220170ca44fd823df54f06aab70b5cb1b9 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:32:42 +0000 Subject: [PATCH 16/16] eof new line --- cpp/tests/test_custom_index_array.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/tests/test_custom_index_array.cpp b/cpp/tests/test_custom_index_array.cpp index 22697e2a71..1995e800c1 100644 --- a/cpp/tests/test_custom_index_array.cpp +++ b/cpp/tests/test_custom_index_array.cpp @@ -445,4 +445,4 @@ TEST(CustomIndexArray, convert_floating_point) EXPECT_DOUBLE_EQ(array_double[i], value_double); EXPECT_FLOAT_EQ(array_float[i], 1.f); } -} \ No newline at end of file +}