From 42accae61944863021147bef807ad4aad5bf9298 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:20:57 +0100 Subject: [PATCH 1/8] CHG: Add lazy edge addition function --- cpp/memilio/mobility/graph.h | 56 +++++++++++++++++++++++++++++++ cpp/tests/test_graph.cpp | 35 +++++++++++++++++++ docs/source/cpp/graph_metapop.rst | 15 +++++++++ 3 files changed, 106 insertions(+) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 26a8bb5a6a..7b49954993 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -178,6 +178,62 @@ class Graph }); } + /** + * @brief Add edges to a graph without checking for duplicates and without sorting. + * + * @param start_node_idx Id of start node + * @param end_node_idx Id of end node + * @param args Additional arguments for edge construction + * @return Edge& End of edge vector + */ + template + Edge& lazy_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); + m_edges.emplace_back(start_node_idx, end_node_idx, std::forward(args)...); + return m_edges.back(); + } + + /** + * @brief Sort the edge vector of a graph. + * + * @return Edge& End of edge vector + */ + Edge& sort_edges() + { + std::sort(m_edges.begin(), m_edges.end(), [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx ? e1.end_node_idx < e2.end_node_idx + : e1.start_node_idx < e2.start_node_idx; + }); + return m_edges.back(); + } + + /** + * @brief Make the edges of a graph unique. + * + * Copies all the unique edges to a new vector and replaces the edge vector of the graph with it. Unique means that + * the start and end node indices are unique. Other edge properties are not checked. + * @return Edge& End of edge vector + */ + Edge& make_edges_unique() + { + std::vector> unique_edges; + unique_edges.reserve(m_edges.size()); + std::ranges::unique_copy(m_edges, std::back_inserter(unique_edges), [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx && e1.end_node_idx == e2.end_node_idx; + }); + m_edges = std::move(unique_edges); + return m_edges.back(); + } + + /** + * @brief reserve space for edges. + */ + void reserve_edges(size_t n) + { + m_edges.reserve(n); + } + /** * @brief range of nodes */ diff --git a/cpp/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index 65bdb71c42..319921bec2 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -344,6 +344,41 @@ TEST(TestGraph, ot_edges) EXPECT_THAT(g.out_edges(1), testing::ElementsAreArray(v1)); } +TEST(TestGraph, compare_add_edge_functions) +{ + mio::Graph g; + mio::Graph g_lazy; + int num_nodes = 10; + for (int index = 0; index < num_nodes; ++index) { + g.add_node(index); + g_lazy.add_node(index); + } + for (int first_node = num_nodes; first_node >= 0; --first_node) { + for (int second_node = 0; second_node < num_nodes; ++second_node) { + if (first_node != second_node) { + g.add_edge(first_node, second_node, int(first_node + second_node)); + g_lazy.lazy_add_edge(first_node, second_node, int(first_node + second_node)); + } + } + } + for (int first_node = num_nodes; first_node >= 0; --first_node) { + for (int second_node = 0; second_node < num_nodes; ++second_node) { + if (first_node != second_node) { + g.add_edge(first_node, second_node, int(first_node + second_node)); + g_lazy.lazy_add_edge(first_node, second_node, int(first_node + second_node)); + } + } + } + g_lazy.sort_edges(); + g_lazy.make_edges_unique(); + EXPECT_EQ(g.edges().size(), g_lazy.edges().size()); + + for (size_t index = 0; index < g.edges().size(); index++) { + EXPECT_EQ(g.edges()[index].start_node_idx, g_lazy.edges()[index].start_node_idx); + EXPECT_EQ(g.edges()[index].end_node_idx, g_lazy.edges()[index].end_node_idx); + } +} + namespace { diff --git a/docs/source/cpp/graph_metapop.rst b/docs/source/cpp/graph_metapop.rst index 1662d91246..2c07bd2c3e 100644 --- a/docs/source/cpp/graph_metapop.rst +++ b/docs/source/cpp/graph_metapop.rst @@ -177,6 +177,21 @@ The following steps detail how to configure and execute a graph simulation: graph.add_edge(0, 1, std::move(transition_rates)); graph.add_edge(1, 0, std::move(transition_rates)); +.. dropdown:: :fa:`gears` Working with large graphs + + When working with very large graphs, i.e. starting from a few thousand edges, it will be faster to not use the standard ``add_edge`` function. This function always + keeps the list of edges inside the graph sorted and checks for duplicates. For large graphs, it is faster to first add all the edges to the graph + and then sort them and remove duplicates: + + .. code-block:: cpp + + graph.reserve_edges(2); + graph.lazy_add_edge(0, 1, std::move(transition_rates)); + graph.lazy_add_edge(1, 0, std::move(transition_rates)); + graph.sort_edges(); + graph.make_edges_unique(); + + 5. **Initialize and Advance the Mobility Simulation:** With the graph constructed, initialize the simulation with the starting time and time step. Then, advance the simulation until the final time :math:`t_{max}`. From d14011baae6252649539a120cd35d5c9aea63b22 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:55:10 +0100 Subject: [PATCH 2/8] FIX: start edge generation with correct node index --- cpp/tests/test_graph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index 319921bec2..dba5dacdd9 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -353,7 +353,7 @@ TEST(TestGraph, compare_add_edge_functions) g.add_node(index); g_lazy.add_node(index); } - for (int first_node = num_nodes; first_node >= 0; --first_node) { + for (int first_node = num_nodes - 1; first_node >= 0; --first_node) { for (int second_node = 0; second_node < num_nodes; ++second_node) { if (first_node != second_node) { g.add_edge(first_node, second_node, int(first_node + second_node)); @@ -361,7 +361,7 @@ TEST(TestGraph, compare_add_edge_functions) } } } - for (int first_node = num_nodes; first_node >= 0; --first_node) { + for (int first_node = num_nodes - 1; first_node >= 0; --first_node) { for (int second_node = 0; second_node < num_nodes; ++second_node) { if (first_node != second_node) { g.add_edge(first_node, second_node, int(first_node + second_node)); From 7fa39ae468eb0195aec50ae40d98b09943dca930 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:20:02 +0100 Subject: [PATCH 3/8] CHG: Rename make_edges_unique --- cpp/memilio/mobility/graph.h | 10 +++++++--- cpp/tests/test_graph.cpp | 2 +- docs/source/cpp/graph_metapop.rst | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 7b49954993..382e11a6b7 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -185,6 +185,9 @@ class Graph * @param end_node_idx Id of end node * @param args Additional arguments for edge construction * @return Edge& End of edge vector + * + * This can be used in combination with :ref sort_edges and :ref remove_duplicate_edges instead of :ref add_edge + * for better performance when adding many edges at once. */ template Edge& lazy_add_edge(size_t start_node_idx, size_t end_node_idx, Args&&... args) @@ -209,13 +212,14 @@ class Graph } /** - * @brief Make the edges of a graph unique. + * @brief Remove duplicate edges from a sorted edge vector. * * Copies all the unique edges to a new vector and replaces the edge vector of the graph with it. Unique means that - * the start and end node indices are unique. Other edge properties are not checked. + * the start and end node indices are unique. Other edge properties are not checked and may get lost. Only the first + * edge in the vector is kept. * @return Edge& End of edge vector */ - Edge& make_edges_unique() + Edge& remove_duplicate_edges() { std::vector> unique_edges; unique_edges.reserve(m_edges.size()); diff --git a/cpp/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index dba5dacdd9..dfbffa3e59 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -370,7 +370,7 @@ TEST(TestGraph, compare_add_edge_functions) } } g_lazy.sort_edges(); - g_lazy.make_edges_unique(); + g_lazy.remove_duplicate_edges(); EXPECT_EQ(g.edges().size(), g_lazy.edges().size()); for (size_t index = 0; index < g.edges().size(); index++) { diff --git a/docs/source/cpp/graph_metapop.rst b/docs/source/cpp/graph_metapop.rst index 2c07bd2c3e..d540a9dd62 100644 --- a/docs/source/cpp/graph_metapop.rst +++ b/docs/source/cpp/graph_metapop.rst @@ -189,7 +189,7 @@ The following steps detail how to configure and execute a graph simulation: graph.lazy_add_edge(0, 1, std::move(transition_rates)); graph.lazy_add_edge(1, 0, std::move(transition_rates)); graph.sort_edges(); - graph.make_edges_unique(); + graph.remove_duplicate_edges(); 5. **Initialize and Advance the Mobility Simulation:** From 32b1bec7f95214eee091ef1894fe2ed10879e910 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:12:47 +0100 Subject: [PATCH 4/8] CHG: Make add_edge void --- cpp/memilio/mobility/graph.h | 14 ++++++-------- cpp/memilio/utils/stl_util.h | 9 +++++---- cpp/tests/test_stl_util.cpp | 16 ---------------- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 382e11a6b7..80fe278313 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -166,16 +166,14 @@ class Graph * @brief add an edge to the graph. property of the edge is constructed from arguments. */ template - Edge& add_edge(size_t start_node_idx, size_t end_node_idx, Args&&... 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); - return *insert_sorted_replace(m_edges, - Edge(start_node_idx, end_node_idx, std::forward(args)...), - [](auto&& e1, auto&& e2) { - return e1.start_node_idx == e2.start_node_idx - ? e1.end_node_idx < e2.end_node_idx - : e1.start_node_idx < e2.start_node_idx; - }); + insert_sorted_replace(m_edges, Edge(start_node_idx, end_node_idx, std::forward(args)...), + [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx ? e1.end_node_idx < e2.end_node_idx + : e1.start_node_idx < e2.start_node_idx; + }); } /** diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index f1deb46c04..72a668eac0 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -65,7 +65,7 @@ inline std::ostream& set_ostream_format(std::ostream& out, size_t width, size_t * @return iterator to inserted or replaced item in vec */ template -typename std::vector::iterator insert_sorted_replace(std::vector& vec, T const& item, Pred pred) +void insert_sorted_replace(std::vector& vec, T const& item, Pred pred) { auto bounds = std::equal_range(begin(vec), end(vec), item, pred); auto lb = bounds.first; @@ -73,15 +73,16 @@ typename std::vector::iterator insert_sorted_replace(std::vector& vec, T c assert(ub - lb <= 1); //input vector contains at most one item that is equal to the new item if (ub - lb == 1) { *lb = item; - return lb; + return; } else { - return vec.insert(lb, item); + vec.insert(lb, item); + return; } } template -typename std::vector::iterator insert_sorted_replace(std::vector& vec, T const& item) +void insert_sorted_replace(std::vector& vec, T const& item) { return insert_sorted_replace(vec, item, std::less()); } diff --git a/cpp/tests/test_stl_util.cpp b/cpp/tests/test_stl_util.cpp index 3de196389e..a09498c51f 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -109,22 +109,6 @@ TEST(TestInsertSortedReplace, normal) EXPECT_THAT(v, testing::ElementsAre(1, 2, 5, 6, 7)); } -TEST(TestInsertSortedReplace, returnsValidIterator) -{ - std::vector v; - int x; - - //There is no GTEST_NO_DEATH macro so we just let the test crash. - //If this test crashes, the function does not return a valid iterator. - //Dereferencing an invalid iterator is undefined behavior so the test - //may behave unexpectedly (pass, fail, or something else) if the iterator is invalid. - x = *mio::insert_sorted_replace(v, 5); - x = *mio::insert_sorted_replace(v, 1); - x = *mio::insert_sorted_replace(v, 4); - x = *mio::insert_sorted_replace(v, 7); - ASSERT_EQ(x, 7); -} - TEST(TestInsertSortedReplace, reverse) { std::vector v = {5}; From 9b37c779fab386202280052e9f865a047c9cc187 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:19:50 +0100 Subject: [PATCH 5/8] CHG: Make add_node void --- cpp/memilio/mobility/graph.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 80fe278313..e85f25cbe3 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -156,10 +156,9 @@ class Graph * @brief add a node to the graph. property of the node is constructed from arguments. */ template - Node& add_node(int id, Args&&... args) + void add_node(int id, Args&&... args) { m_nodes.emplace_back(id, std::forward(args)...); - return m_nodes.back(); } /** From d908d71e54ffa9fdd13bb8fd8a73d66aa303390f Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:34:29 +0100 Subject: [PATCH 6/8] CHG: Add GraphBuilder --- cpp/memilio/mobility/graph.h | 177 ++++++++++++++++++++---------- cpp/tests/test_graph.cpp | 63 +++++------ docs/source/cpp/graph_metapop.rst | 31 ++++-- 3 files changed, 171 insertions(+), 100 deletions(-) diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index e85f25cbe3..7b8b24d521 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" @@ -28,7 +27,10 @@ #include "memilio/utils/parameter_distributions.h" #include "memilio/epidemiology/damping.h" #include "memilio/geography/regions.h" +#include +#include #include +#include #include "boost/filesystem.hpp" @@ -152,6 +154,13 @@ class Graph using NodeProperty = NodePropertyT; using EdgeProperty = EdgePropertyT; + Graph() = default; + Graph(std::vector>&& nodes, std::vector>&& edges) + : m_nodes(std::move(nodes)) + , m_edges(std::move(edges)) + { + } + /** * @brief add a node to the graph. property of the node is constructed from arguments. */ @@ -175,66 +184,6 @@ class Graph }); } - /** - * @brief Add edges to a graph without checking for duplicates and without sorting. - * - * @param start_node_idx Id of start node - * @param end_node_idx Id of end node - * @param args Additional arguments for edge construction - * @return Edge& End of edge vector - * - * This can be used in combination with :ref sort_edges and :ref remove_duplicate_edges instead of :ref add_edge - * for better performance when adding many edges at once. - */ - template - Edge& lazy_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); - m_edges.emplace_back(start_node_idx, end_node_idx, std::forward(args)...); - return m_edges.back(); - } - - /** - * @brief Sort the edge vector of a graph. - * - * @return Edge& End of edge vector - */ - Edge& sort_edges() - { - std::sort(m_edges.begin(), m_edges.end(), [](auto&& e1, auto&& e2) { - return e1.start_node_idx == e2.start_node_idx ? e1.end_node_idx < e2.end_node_idx - : e1.start_node_idx < e2.start_node_idx; - }); - return m_edges.back(); - } - - /** - * @brief Remove duplicate edges from a sorted edge vector. - * - * Copies all the unique edges to a new vector and replaces the edge vector of the graph with it. Unique means that - * the start and end node indices are unique. Other edge properties are not checked and may get lost. Only the first - * edge in the vector is kept. - * @return Edge& End of edge vector - */ - Edge& remove_duplicate_edges() - { - std::vector> unique_edges; - unique_edges.reserve(m_edges.size()); - std::ranges::unique_copy(m_edges, std::back_inserter(unique_edges), [](auto&& e1, auto&& e2) { - return e1.start_node_idx == e2.start_node_idx && e1.end_node_idx == e2.end_node_idx; - }); - m_edges = std::move(unique_edges); - return m_edges.back(); - } - - /** - * @brief reserve space for edges. - */ - void reserve_edges(size_t n) - { - m_edges.reserve(n); - } - /** * @brief range of nodes */ @@ -493,6 +442,112 @@ void print_graph(std::ostream& os, const Graph& g) } } +/** + * @brief A builder class for constructing graphs. + * + * This class provides a interface for adding nodes and edges to a graph. It allows for efficient construction of large + * graphs by reserving space for nodes and edges in advance. The build method finalizes the graph by sorting edges and + * optionally removing duplicates. + * The advantage over the :ref add_edge function of the Graph class is that edges are only sorted once during the build + * process, improving performance when adding many edges. + * + * @tparam NodePropertyT Type of the node property. + * @tparam EdgePropertyT Type of the edge property. + */ +template +class GraphBuilder +{ +public: + using NodeProperty = NodePropertyT; + using EdgeProperty = EdgePropertyT; + + GraphBuilder() = default; + GraphBuilder(const size_t num_nodes, const size_t num_edges) + { + m_nodes.reserve(num_nodes); + m_edges.reserve(num_edges); + } + + /** + * @brief Add a node to the GraphBuilder. + * + * The property of the node is constructed from arguments. + * @param id Id for the node. + * @tparam args Additional arguments for node construction. + */ + template + void add_node(int id, Args&&... args) + { + m_nodes.emplace_back(id, std::forward(args)...); + } + + /** + * @brief Add an edge to the GraphBuilder. + * + * @param start_node_idx Id of start node + * @param end_node_idx Id of end node + * @tparam args Additional arguments for edge construction + */ + template + 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); + m_edges.emplace_back(start_node_idx, end_node_idx, std::forward(args)...); + } + + /** + * @brief Build the graph from the added nodes and edges. + * + * Sorts the edges and optionally removes duplicate edges (same start and end node indices). + * @param make_unique If true, duplicate edges are removed. The first added edge is kept! + * @return Graph The constructed graph. + * @tparam NodeProperty The type of the node property. + * @tparam EdgeProperty The type of the edge property. + */ + Graph build(bool make_unique = false) + { + sort_edges(); + if (make_unique) { + remove_duplicate_edges(); + } + Graph graph(std::move(m_nodes), std::move(m_edges)); + return graph; + } + +private: + /** + * @brief Sort the edge vector of a graph. + */ + void sort_edges() + { + std::stable_sort(m_edges.begin(), m_edges.end(), [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx ? e1.end_node_idx < e2.end_node_idx + : e1.start_node_idx < e2.start_node_idx; + }); + } + + /** + * @brief Remove duplicate edges from a sorted edge vector. + * + * Copies all the unique edges to a new vector and replaces the original edge vector with it. Unique means that + * the start and end node indices are unique. Other edge properties are not checked and may get lost. Only the first + * edge in the vector is kept. + */ + void remove_duplicate_edges() + { + std::vector> unique_edges; + unique_edges.reserve(m_edges.size()); + std::ranges::unique_copy(m_edges, std::back_inserter(unique_edges), [](auto&& e1, auto&& e2) { + return e1.start_node_idx == e2.start_node_idx && e1.end_node_idx == e2.end_node_idx; + }); + m_edges = std::move(unique_edges); + } + +private: + std::vector> m_nodes; + std::vector> m_edges; +}; + } // namespace mio #endif //GRAPH_H diff --git a/cpp/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index dfbffa3e59..b084537cb8 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -325,7 +325,7 @@ TEST(TestGraph, set_edges_saving_edges) EXPECT_EQ(indices_edge1, indices_save_edges); } -TEST(TestGraph, ot_edges) +TEST(TestGraph, out_edges) { mio::Graph g; g.add_node(0); @@ -344,38 +344,39 @@ TEST(TestGraph, ot_edges) EXPECT_THAT(g.out_edges(1), testing::ElementsAreArray(v1)); } -TEST(TestGraph, compare_add_edge_functions) +TEST(TestGraphBuilder, Build) { - mio::Graph g; - mio::Graph g_lazy; - int num_nodes = 10; - for (int index = 0; index < num_nodes; ++index) { - g.add_node(index); - g_lazy.add_node(index); - } - for (int first_node = num_nodes - 1; first_node >= 0; --first_node) { - for (int second_node = 0; second_node < num_nodes; ++second_node) { - if (first_node != second_node) { - g.add_edge(first_node, second_node, int(first_node + second_node)); - g_lazy.lazy_add_edge(first_node, second_node, int(first_node + second_node)); - } - } - } - for (int first_node = num_nodes - 1; first_node >= 0; --first_node) { - for (int second_node = 0; second_node < num_nodes; ++second_node) { - if (first_node != second_node) { - g.add_edge(first_node, second_node, int(first_node + second_node)); - g_lazy.lazy_add_edge(first_node, second_node, int(first_node + second_node)); - } - } - } - g_lazy.sort_edges(); - g_lazy.remove_duplicate_edges(); - EXPECT_EQ(g.edges().size(), g_lazy.edges().size()); + mio::GraphBuilder builder(3, 3); + builder.add_node(0, 100); + builder.add_node(1, 100); + builder.add_node(2, 100); + builder.add_edge(0, 1, 100); + builder.add_edge(2, 1, 100); + builder.add_edge(1, 2, 100); + + auto g = builder.build(); + + EXPECT_EQ(g.nodes().size(), 3); + EXPECT_EQ(g.edges().size(), 3); +} - for (size_t index = 0; index < g.edges().size(); index++) { - EXPECT_EQ(g.edges()[index].start_node_idx, g_lazy.edges()[index].start_node_idx); - EXPECT_EQ(g.edges()[index].end_node_idx, g_lazy.edges()[index].end_node_idx); +TEST(TestGraphBuilder, Build_unique) +{ + mio::GraphBuilder builder; + builder.add_node(0, 100); + builder.add_node(1, 100); + builder.add_node(2, 100); + builder.add_edge(1, 2, 100); + builder.add_edge(0, 1, 100); + builder.add_edge(2, 1, 100); + builder.add_edge(1, 2, 200); + + auto g = builder.build(true); + + EXPECT_EQ(g.nodes().size(), 3); + EXPECT_EQ(g.edges().size(), 3); + for (const auto& e : g.edges()) { + EXPECT_EQ(e.property, 100); } } diff --git a/docs/source/cpp/graph_metapop.rst b/docs/source/cpp/graph_metapop.rst index d540a9dd62..122e6bb1a6 100644 --- a/docs/source/cpp/graph_metapop.rst +++ b/docs/source/cpp/graph_metapop.rst @@ -179,17 +179,32 @@ The following steps detail how to configure and execute a graph simulation: .. dropdown:: :fa:`gears` Working with large graphs - When working with very large graphs, i.e. starting from a few thousand edges, it will be faster to not use the standard ``add_edge`` function. This function always - keeps the list of edges inside the graph sorted and checks for duplicates. For large graphs, it is faster to first add all the edges to the graph - and then sort them and remove duplicates: + When working with very large graphs, i.e. starting from a few thousand edges, it will be faster to not use the standard ``add_edge`` function. + For this case, we provide a ``GraphBuilder``. There you can add all edges without any checks and the edges will be sorted when the graph is generated: .. code-block:: cpp - graph.reserve_edges(2); - graph.lazy_add_edge(0, 1, std::move(transition_rates)); - graph.lazy_add_edge(1, 0, std::move(transition_rates)); - graph.sort_edges(); - graph.remove_duplicate_edges(); + mio::GraphBuilder>>, mio::MobilityEdgeStochastic> builder; + builder.add_node(1001, model_group1, t0); + builder.add_node(1002, model_group2, to); + builder.add_edge(0, 1, std::move(transition_rates)); + builder.add_edge(1, 0, std::move(transition_rates)); + auto graph = builder.build(); + + + Usually, there should be no duplicate edges. If this is not certain, the ``GraphBuilder`` can also remove duplicates, based on the start and end node. + The parameters in the edge will not be compared. In this case it will only keep the first edge that was inserted: + + .. code-block:: cpp + + mio::GraphBuilder builder; + builder.add_node(1001, 100); + builder.add_node(1002, 100); + builder.add_edge(0, 1, 100); + builder.add_edge(1, 0, 100); + builder.add_edge(0, 1, 200); + auto graph = builder.build(true); + // graph contains the edges (0, 1, 100) and (1, 0, 100) 5. **Initialize and Advance the Mobility Simulation:** From 447a7ca2c4bb3812e0c84407312441614519e120 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:41:50 +0100 Subject: [PATCH 7/8] CHG: try fix bindigs --- .../bindings/mobility/metapopulation_mobility_instant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h b/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h index 361bfea983..146db880fd 100644 --- a/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h +++ b/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h @@ -38,7 +38,7 @@ void bind_MobilityGraph(pybind11::module_& m, std::string const& name) .def( "add_node", [](G& self, int id, const typename Simulation::Model& p, double t0, double dt) -> auto& { - return self.add_node(id, p, t0, dt); + self.add_node(id, p, t0, dt); }, pybind11::arg("id"), pybind11::arg("model"), pybind11::arg("t0") = 0.0, pybind11::arg("dt") = 0.1, pybind11::return_value_policy::reference_internal) From 5f35220c4743bac84d25d32aebfeb38bb5201ed5 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:59:05 +0100 Subject: [PATCH 8/8] CHG: try fix bindings --- .../bindings/mobility/metapopulation_mobility_instant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h b/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h index 146db880fd..c35b586248 100644 --- a/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h +++ b/pycode/memilio-simulation/memilio/simulation/bindings/mobility/metapopulation_mobility_instant.h @@ -37,7 +37,7 @@ void bind_MobilityGraph(pybind11::module_& m, std::string const& name) .def(pybind11::init<>()) .def( "add_node", - [](G& self, int id, const typename Simulation::Model& p, double t0, double dt) -> auto& { + [](G& self, int id, const typename Simulation::Model& p, double t0, double dt) -> void { self.add_node(id, p, t0, dt); }, pybind11::arg("id"), pybind11::arg("model"), pybind11::arg("t0") = 0.0, pybind11::arg("dt") = 0.1,