-
Notifications
You must be signed in to change notification settings - Fork 5
Bugfix #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bugfix #441
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,30 +21,26 @@ | |
| std::unordered_map<Id, std::unique_ptr<node_t>> m_nodes; | ||
| std::unordered_map<Id, std::unique_ptr<edge_t>> m_edges; | ||
|
|
||
| Id m_cantorHash(Id u, Id v) const; | ||
| Id m_cantorHash(std::pair<Id, Id> const& idPair) const; | ||
| constexpr inline auto m_cantorHash(Id u, Id v) const { | ||
| return ((u + v) * (u + v + 1)) / 2 + v; | ||
| } | ||
| constexpr inline auto m_cantorHash(std::pair<Id, Id> const& idPair) const { | ||
| return m_cantorHash(idPair.first, idPair.second); | ||
| } | ||
|
|
||
| public: | ||
| /// @brief Construct a new empty Network object | ||
| Network() = default; | ||
|
|
||
| /// @brief Get the nodes as an unordered map | ||
| /// @return std::unordered_map<Id, std::unique_ptr<node_t>> The nodes | ||
| std::unordered_map<Id, std::unique_ptr<node_t>> const& nodes() const; | ||
| inline auto const& nodes() const noexcept { return m_nodes; } | ||
| /// @brief Get the edges as an unordered map | ||
| /// @return std::unordered_map<Id, std::unique_ptr<edge_t>> The edges | ||
| std::unordered_map<Id, std::unique_ptr<edge_t>> const& edges() const; | ||
| inline auto const& edges() const noexcept { return m_edges; } | ||
| /// @brief Get the number of nodes | ||
| /// @return size_t The number of nodes | ||
| size_t nNodes() const; | ||
| inline auto nNodes() const noexcept { return m_nodes.size(); } | ||
| /// @brief Get the number of edges | ||
| /// @return size_t The number of edges | ||
| size_t nEdges() const; | ||
| inline auto nEdges() const noexcept { return m_edges.size(); } | ||
|
|
||
| /// @brief Add a node to the network | ||
| /// @tparam TNode The type of the node (default is node_t) | ||
| /// @tparam TArgs The types of the arguments | ||
| /// @param args The arguments to pass to the node's constructor | ||
| template <typename TNode = node_t, typename... TArgs> | ||
| requires(std::is_base_of_v<node_t, TNode> && | ||
| std::constructible_from<TNode, TArgs...>) | ||
|
|
@@ -54,122 +50,50 @@ | |
| requires(std::is_base_of_v<node_t, TNode>) | ||
| void addNDefaultNodes(size_t n); | ||
|
|
||
| /// @brief Add an edge to the network | ||
| /// @tparam TEdge The type of the edge (default is edge_t) | ||
| /// @tparam TArgs The types of the arguments | ||
| /// @param args The arguments to pass to the edge's constructor | ||
| template <typename TEdge = edge_t, typename... TArgs> | ||
| requires(std::is_base_of_v<edge_t, TEdge> && | ||
| std::constructible_from<TEdge, TArgs...>) | ||
| void addEdge(TArgs&&... args); | ||
|
|
||
| /// @brief Get a node by id | ||
| /// @param nodeId The node's id | ||
| /// @return const node_t& A reference to the node | ||
| inline const auto& node(Id nodeId) const { return *m_nodes.at(nodeId); }; | ||
| /// @brief Get a node by id | ||
| /// @param nodeId The node's id | ||
| /// @return node_t& A reference to the node | ||
| inline auto& node(Id nodeId) { return *m_nodes.at(nodeId); }; | ||
| /// @brief Get an edge by id | ||
| /// @param edgeId The edge's id | ||
| /// @return const edge_t& A reference to the edge | ||
| inline const auto& edge(Id edgeId) const { return *m_edges.at(edgeId); }; | ||
| /// @brief Get an edge by id | ||
| /// @param edgeId The edge's id | ||
| /// @return edge_t& A reference to the edge | ||
| inline auto& edge(Id edgeId) { return *m_edges.at(edgeId); } | ||
|
|
||
| edge_t& edge(Id source, Id target) const; | ||
| /// @brief Get a node by id | ||
| /// @tparam TNode The type of the node | ||
| /// @param nodeId The node's id | ||
| /// @return TNode& A reference to the node | ||
|
|
||
| template <typename TNode> | ||
| requires(std::is_base_of_v<node_t, TNode>) | ||
| TNode& node(Id nodeId); | ||
| /// @brief Get an edge by id | ||
| /// @tparam TEdge The type of the edge | ||
| /// @param edgeId The edge's id | ||
| /// @return TEdge& A reference to the edge | ||
|
|
||
| template <typename TEdge> | ||
| requires(std::is_base_of_v<edge_t, TEdge>) | ||
| TEdge& edge(Id edgeId); | ||
|
|
||
| /// @brief Compute betweenness centralities for all nodes using Brandes' algorithm | ||
| /// @tparam WeightFunc A callable type that takes a const reference to edge_t and returns a double representing the edge weight | ||
| /// @param getEdgeWeight A callable that takes a const reference to edge_t and returns a double (must be positive) | ||
| /// @details Implements Brandes' algorithm for directed weighted graphs. | ||
| /// The computed centrality for each node v is: | ||
| /// C_B(v) = sum_{s != v != t} sigma_st(v) / sigma_st | ||
| /// where sigma_st is the number of shortest paths from s to t, | ||
| /// and sigma_st(v) is the number of those paths passing through v. | ||
| /// Results are stored via Node::setBetweennessCentrality. | ||
| /// @brief Compute node betweenness centralities using Brandes' algorithm | ||
| template <typename WeightFunc> | ||
| requires(std::is_invocable_r_v<double, WeightFunc, edge_t const&>) | ||
| void computeBetweennessCentralities(WeightFunc getEdgeWeight); | ||
|
|
||
| /// @brief Compute edge betweenness centralities for all edges using Brandes' algorithm | ||
| /// @tparam WeightFunc A callable type that takes a const reference to edge_t and returns a double representing the edge weight | ||
| /// @param getEdgeWeight A callable that takes a const reference to edge_t and returns a double (must be positive) | ||
| /// @details Implements Brandes' algorithm for directed weighted graphs. | ||
| /// The computed centrality for each edge e is: | ||
| /// C_B(e) = sum_{s != t} sigma_st(e) / sigma_st | ||
| /// where sigma_st is the number of shortest paths from s to t, | ||
| /// and sigma_st(e) is the number of those paths using edge e. | ||
| /// Results are stored via Edge::setBetweennessCentrality. | ||
| /// @brief Compute edge betweenness centralities using Brandes' algorithm | ||
|
||
| template <typename WeightFunc> | ||
| requires(std::is_invocable_r_v<double, WeightFunc, edge_t const&>) | ||
| void computeEdgeBetweennessCentralities(WeightFunc getEdgeWeight); | ||
| }; | ||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| Id Network<node_t, edge_t>::m_cantorHash(Id u, Id v) const { | ||
| return ((u + v) * (u + v + 1)) / 2 + v; | ||
| } | ||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| Id Network<node_t, edge_t>::m_cantorHash(std::pair<Id, Id> const& idPair) const { | ||
| return m_cantorHash(idPair.first, idPair.second); | ||
| } | ||
|
|
||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| std::unordered_map<Id, std::unique_ptr<node_t>> const& Network<node_t, edge_t>::nodes() | ||
| const { | ||
| return m_nodes; | ||
| } | ||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| std::unordered_map<Id, std::unique_ptr<edge_t>> const& Network<node_t, edge_t>::edges() | ||
| const { | ||
| return m_edges; | ||
| } | ||
|
|
||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| size_t Network<node_t, edge_t>::nNodes() const { | ||
| return m_nodes.size(); | ||
| } | ||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| size_t Network<node_t, edge_t>::nEdges() const { | ||
| return m_edges.size(); | ||
| } | ||
|
|
||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| template <typename TNode, typename... TArgs> | ||
| requires(std::is_base_of_v<node_t, TNode> && std::constructible_from<TNode, TArgs...>) | ||
| void Network<node_t, edge_t>::addNode(TArgs&&... args) { | ||
| // Create unique_ptr directly with perfect forwarding | ||
| auto pNode = std::make_unique<TNode>(std::forward<TArgs>(args)...); | ||
| if (m_nodes.contains(pNode->id())) { | ||
| throw std::invalid_argument( | ||
| std::format("Node with id {} already exists in the network.", pNode->id())); | ||
| } | ||
| m_nodes[pNode->id()] = std::move(pNode); | ||
| } | ||
|
|
||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| template <typename TNode> | ||
|
|
@@ -180,6 +104,7 @@ | |
| addNode<TNode>(static_cast<Id>(currentSize + i)); | ||
| } | ||
| } | ||
|
|
||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| template <typename TEdge, typename... TArgs> | ||
|
|
@@ -194,16 +119,14 @@ | |
| auto const& sourceNodeId = tmpEdge.source(); | ||
| auto const& targetNodeId = tmpEdge.target(); | ||
|
|
||
| // Check if source node exists, add if not | ||
| if (!m_nodes.contains(sourceNodeId)) { | ||
| if (!geometry.empty()) { | ||
| addNode(tmpEdge.source(), geometry.front()); | ||
| } else { | ||
| addNode(tmpEdge.source()); | ||
| } | ||
| } | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.1 rule Note
MISRA 12.1 rule
|
||
|
|
||
| // Check if target node exists, add if not | ||
| if (!m_nodes.contains(targetNodeId)) { | ||
| if (!geometry.empty()) { | ||
| addNode(tmpEdge.target(), geometry.back()); | ||
|
|
@@ -212,7 +135,6 @@ | |
| } | ||
| } | ||
|
|
||
| // Get fresh references to both nodes after all potential vector reallocations | ||
| auto& sourceNode = node(sourceNodeId); | ||
| auto& targetNode = node(targetNodeId); | ||
| sourceNode.addOutgoingEdge(tmpEdge.id()); | ||
|
|
@@ -247,6 +169,7 @@ | |
| TNode& Network<node_t, edge_t>::node(Id nodeId) { | ||
| return dynamic_cast<TNode&>(node(nodeId)); | ||
| } | ||
|
|
||
| template <typename node_t, typename edge_t> | ||
| requires(std::is_base_of_v<Node, node_t> && std::is_base_of_v<Edge, edge_t>) | ||
| template <typename TEdge> | ||
|
|
@@ -260,17 +183,15 @@ | |
| template <typename WeightFunc> | ||
| requires(std::is_invocable_r_v<double, WeightFunc, edge_t const&>) | ||
| void Network<node_t, edge_t>::computeBetweennessCentralities(WeightFunc getEdgeWeight) { | ||
| // Initialize all node betweenness centralities to 0 | ||
| for (auto& [nodeId, pNode] : m_nodes) { | ||
| pNode->setBetweennessCentrality(0.0); | ||
| } | ||
|
|
||
| // Brandes' algorithm: run single-source Dijkstra from each node | ||
| for (auto const& [sourceId, sourceNode] : m_nodes) { | ||
| std::stack<Id> S; // nodes in order of non-increasing distance | ||
| std::unordered_map<Id, std::vector<Id>> P; // predecessors on shortest paths | ||
| std::unordered_map<Id, double> sigma; // number of shortest paths | ||
| std::unordered_map<Id, double> dist; // distance from source | ||
| std::stack<Id> S; | ||
| std::unordered_map<Id, std::vector<Id>> P; | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
|
||
| std::unordered_map<Id, double> sigma; | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
|
||
|
|
||
| std::unordered_map<Id, double> dist; | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
|
||
|
|
||
|
|
||
|
|
||
| for (auto const& [nId, _] : m_nodes) { | ||
| P[nId] = {}; | ||
|
|
@@ -280,7 +201,6 @@ | |
| sigma[sourceId] = 1.0; | ||
| dist[sourceId] = 0.0; | ||
|
|
||
| // Min-heap priority queue: (distance, nodeId) | ||
| std::priority_queue<std::pair<double, Id>, | ||
| std::vector<std::pair<double, Id>>, | ||
| std::greater<>> | ||
|
|
@@ -293,18 +213,16 @@ | |
| auto [d, v] = pq.top(); | ||
| pq.pop(); | ||
|
|
||
| if (visited.contains(v)) { | ||
| if (visited.contains(v)) | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 15.6 rule Note
MISRA 15.6 rule
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 14.4 rule Note
MISRA 14.4 rule
|
||
|
|
||
| continue; | ||
| } | ||
| visited.insert(v); | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| S.push(v); | ||
|
|
||
| for (auto const& edgeId : m_nodes.at(v)->outgoingEdges()) { | ||
| auto const& edgeObj = *m_edges.at(edgeId); | ||
| Id w = edgeObj.target(); | ||
| if (visited.contains(w)) { | ||
| if (visited.contains(w)) | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 15.6 rule Note
MISRA 15.6 rule
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 14.4 rule Note
MISRA 14.4 rule
|
||
|
|
||
| continue; | ||
| } | ||
| double edgeWeight = getEdgeWeight(edgeObj); | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| double newDist = dist[v] + edgeWeight; | ||
|
|
||
|
|
@@ -320,11 +238,9 @@ | |
| } | ||
| } | ||
|
|
||
| // Dependency accumulation (backward pass) | ||
| std::unordered_map<Id, double> delta; | ||
| for (auto const& [nId, _] : m_nodes) { | ||
| for (auto const& [nId, _] : m_nodes) | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 15.6 rule Note
MISRA 15.6 rule
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
|
||
|
|
||
| delta[nId] = 0.0; | ||
| } | ||
|
|
||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| while (!S.empty()) { | ||
| Id w = S.top(); | ||
|
|
@@ -346,18 +262,15 @@ | |
| requires(std::is_invocable_r_v<double, WeightFunc, edge_t const&>) | ||
| void Network<node_t, edge_t>::computeEdgeBetweennessCentralities( | ||
| WeightFunc getEdgeWeight) { | ||
| // Initialize all edge betweenness centralities to 0 | ||
| for (auto& [edgeId, pEdge] : m_edges) { | ||
| pEdge->setBetweennessCentrality(0.0); | ||
| } | ||
|
|
||
| // Brandes' algorithm: run single-source Dijkstra from each node | ||
| for (auto const& [sourceId, sourceNode] : m_nodes) { | ||
| std::stack<Id> S; // nodes in order of non-increasing distance | ||
| // predecessors: P[w] = list of (predecessor node id, edge id from pred to w) | ||
| std::stack<Id> S; | ||
| std::unordered_map<Id, std::vector<std::pair<Id, Id>>> P; | ||
| std::unordered_map<Id, double> sigma; // number of shortest paths | ||
| std::unordered_map<Id, double> dist; // distance from source | ||
| std::unordered_map<Id, double> sigma; | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
|
||
| std::unordered_map<Id, double> dist; | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
|
||
|
|
||
|
|
||
|
|
||
| for (auto const& [nId, _] : m_nodes) { | ||
| P[nId] = {}; | ||
|
|
@@ -367,7 +280,6 @@ | |
| sigma[sourceId] = 1.0; | ||
| dist[sourceId] = 0.0; | ||
|
|
||
| // Min-heap priority queue: (distance, nodeId) | ||
| std::priority_queue<std::pair<double, Id>, | ||
| std::vector<std::pair<double, Id>>, | ||
| std::greater<>> | ||
|
|
@@ -380,18 +292,16 @@ | |
| auto [d, v] = pq.top(); | ||
| pq.pop(); | ||
|
|
||
| if (visited.contains(v)) { | ||
| if (visited.contains(v)) | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 14.4 rule Note
MISRA 14.4 rule
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 15.6 rule Note
MISRA 15.6 rule
|
||
|
|
||
| continue; | ||
| } | ||
| visited.insert(v); | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| S.push(v); | ||
|
|
||
| for (auto const& eId : m_nodes.at(v)->outgoingEdges()) { | ||
| auto const& edgeObj = *m_edges.at(eId); | ||
| Id w = edgeObj.target(); | ||
| if (visited.contains(w)) { | ||
| if (visited.contains(w)) | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 15.6 rule Note
MISRA 15.6 rule
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 14.4 rule Note
MISRA 14.4 rule
|
||
|
|
||
| continue; | ||
| } | ||
| double edgeWeight = getEdgeWeight(edgeObj); | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| double newDist = dist[v] + edgeWeight; | ||
|
|
||
|
|
@@ -407,19 +317,16 @@ | |
| } | ||
| } | ||
|
|
||
| // Dependency accumulation (backward pass) | ||
| std::unordered_map<Id, double> delta; | ||
| for (auto const& [nId, _] : m_nodes) { | ||
| for (auto const& [nId, _] : m_nodes) | ||
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 12.3 rule Note
MISRA 12.3 rule
Check noticeCode scanning / Cppcheck (reported by Codacy) MISRA 15.6 rule Note
MISRA 15.6 rule
|
||
|
|
||
| delta[nId] = 0.0; | ||
| } | ||
|
|
||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| while (!S.empty()) { | ||
| Id w = S.top(); | ||
| S.pop(); | ||
| for (auto const& [v, eId] : P[w]) { | ||
| double c = (sigma[v] / sigma[w]) * (1.0 + delta[w]); | ||
| delta[v] += c; | ||
| // Accumulate edge betweenness | ||
| auto currentBC = m_edges.at(eId)->betweennessCentrality(); | ||
| m_edges.at(eId)->setBetweennessCentrality(*currentBC + c); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -126,11 +126,20 @@ | |
| if "lanes" in data: | ||
| lanes_value = data["lanes"] | ||
| if isinstance(lanes_value, list): | ||
| edge_updates["nlanes"] = max( | ||
| min([int(v) for v in lanes_value]), 1 | ||
| ) # Take max if list, ensure at least 1 lane | ||
| n = max(min([int(v) for v in lanes_value]), 1) | ||
Check warningCode scanning / Pylint (reported by Codacy) Variable name "n" doesn't conform to snake_case naming style Warning
Variable name "n" doesn't conform to snake_case naming style
Check warningCode scanning / Pylintpython3 (reported by Codacy) Variable name "n" doesn't conform to snake_case naming style Warning
Variable name "n" doesn't conform to snake_case naming style
|
||
|
|
||
| else: | ||
| edge_updates["nlanes"] = max(int(lanes_value), 1) | ||
| n = max(int(lanes_value), 1) | ||
Check warningCode scanning / Pylint (reported by Codacy) Variable name "n" doesn't conform to snake_case naming style Warning
Variable name "n" doesn't conform to snake_case naming style
Check warningCode scanning / Pylintpython3 (reported by Codacy) Variable name "n" doesn't conform to snake_case naming style Warning
Variable name "n" doesn't conform to snake_case naming style
|
||
|
|
||
|
|
||
| # If the road is bidirectional, OSM reports total lanes (both directions). | ||
| # Since the DiGraph has one edge per direction, divide by 2. | ||
| oneway_val = data.get("oneway", False) | ||
| is_oneway = ( | ||
| oneway_val is True or oneway_val == "yes" or oneway_val == "True" | ||
| ) | ||
| if not is_oneway: | ||
| n = max(n // 2, 1) # integer division, ensure at least 1 | ||
Check warningCode scanning / Pylint (reported by Codacy) Variable name "n" doesn't conform to snake_case naming style Warning
Variable name "n" doesn't conform to snake_case naming style
Check warningCode scanning / Pylintpython3 (reported by Codacy) Variable name "n" doesn't conform to snake_case naming style Warning
Variable name "n" doesn't conform to snake_case naming style
|
||
|
Comment on lines
+135
to
+140
|
||
|
|
||
| edge_updates["nlanes"] = n | ||
| edge_updates["_remove_lanes"] = True | ||
| else: | ||
| edge_updates["nlanes"] = 1 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The detailed Doxygen docs for the Brandes centrality methods (constraints like “weights must be positive”, and the definition/normalization of the computed centrality) were removed. Since these are public template APIs, restoring the key behavioral/contract details would prevent misuse and reduce ambiguity for callers.