Skip to content

Commit 9660aff

Browse files
Add connected components
1 parent f207532 commit 9660aff

4 files changed

Lines changed: 77 additions & 0 deletions

File tree

g2o/core/sparse_optimizer.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <cassert>
3232
#include <cstdlib>
3333
#include <iostream>
34+
#include <limits>
3435
#include <memory>
3536
#include <string_view>
3637
#include <unordered_set>
@@ -40,6 +41,7 @@
4041
#include "estimate_propagator.h"
4142
#include "g2o/config.h" // IWYU pragma: keep
4243
#include "g2o/core/eigen_types.h"
44+
#include "g2o/core/hyper_dijkstra.h"
4345
#include "g2o/core/jacobian_workspace.h" // IWYU pragma: keep
4446
#include "g2o/core/optimizable_graph.h"
4547
#include "g2o/core/sparse_block_matrix.h"
@@ -164,6 +166,50 @@ void SparseOptimizer::printGraphSummary(std::ostream& os) const {
164166
os << " active chi2: " << activeChi2() << "\n";
165167
os << " active robust chi2: " << activeRobustChi2() << "\n";
166168
}
169+
os << " connected_components: " << numConnectedComponents() << "\n";
170+
}
171+
172+
int SparseOptimizer::numConnectedComponents(int level) const {
173+
if (vertices().empty()) return 0;
174+
175+
struct LevelCostFunction : public HyperDijkstra::CostFunction {
176+
explicit LevelCostFunction(int level_) : level(level_) {}
177+
double operator()(HyperGraph::Edge* edge, HyperGraph::Vertex* /*from*/,
178+
HyperGraph::Vertex* /*to*/) override {
179+
auto oe = static_cast<OptimizableGraph::Edge*>(edge);
180+
if (!oe || oe->level() != level) {
181+
return std::numeric_limits<double>::max();
182+
}
183+
return 1.;
184+
}
185+
int level;
186+
};
187+
188+
std::shared_ptr<HyperGraph> graph(const_cast<SparseOptimizer*>(this),
189+
[](HyperGraph*) {});
190+
HyperDijkstra d(graph);
191+
LevelCostFunction cost(level);
192+
193+
HyperGraph::VertexSet remaining;
194+
for (const auto& it : vertices()) {
195+
remaining.insert(it.second);
196+
}
197+
198+
int components = 0;
199+
while (!remaining.empty()) {
200+
auto root = *remaining.begin();
201+
d.shortestPaths(root, cost);
202+
const auto& visited = d.visited();
203+
for (const auto& v : visited) {
204+
remaining.erase(v);
205+
}
206+
components++;
207+
}
208+
return components;
209+
}
210+
211+
bool SparseOptimizer::isConnected(int level) const {
212+
return numConnectedComponents(level) <= 1;
167213
}
168214

169215
std::shared_ptr<OptimizableGraph::Vertex> SparseOptimizer::findGauge() {

g2o/core/sparse_optimizer.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,18 @@ class G2O_CORE_API SparseOptimizer : public OptimizableGraph {
177177
*/
178178
void printGraphSummary(std::ostream& os = std::cout) const;
179179

180+
/**
181+
* Returns the number of connected components in the graph.
182+
* Only edges with the specified level are considered.
183+
*/
184+
int numConnectedComponents(int level = 0) const;
185+
186+
/**
187+
* Returns true if the graph is fully connected.
188+
* Only edges with the specified level are considered.
189+
*/
190+
bool isConnected(int level = 0) const;
191+
180192
//! verbose information during optimization
181193
bool verbose() const { return verbose_; }
182194
void setVerbose(bool verbose);

python/core/py_sparse_optimizer.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ void declareSparseOptimizer(py::module& m) {
8484
.def("gauge_freedom", &CLS::gaugeFreedom) // -> bool
8585
.def("active_chi2", &CLS::activeChi2) // -> double
8686
.def("active_robust_chi2", &CLS::activeRobustChi2) // -> double
87+
.def("num_connected_components", &CLS::numConnectedComponents,
88+
"level"_a = 0)
89+
.def("is_connected", &CLS::isConnected, "level"_a = 0)
8790
.def("print_graph_summary",
8891
[](CLS& optimizer) {
8992
std::ostringstream os;

python/tests/test_core.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ def test_print_graph_summary(self, simple_se2_graph):
5757
assert "vertices:" in summary
5858
assert "edges:" in summary
5959
assert "poses:" in summary
60+
assert "levels:" in summary
61+
assert "connected_components:" in summary
62+
63+
def test_connected_components(self, simple_se2_graph):
64+
"""Test graph connectivity diagnostics."""
65+
assert simple_se2_graph.is_connected()
66+
assert simple_se2_graph.num_connected_components() == 1
67+
68+
# Disconnect the graph by promoting one edge to a different level.
69+
edge = next(iter(simple_se2_graph.edges()))
70+
edge.set_level(1)
71+
72+
assert not simple_se2_graph.is_connected(0)
73+
assert simple_se2_graph.num_connected_components(0) == 2
74+
assert not simple_se2_graph.is_connected(1)
75+
assert simple_se2_graph.num_connected_components(1) == 2
6076

6177

6278
class TestBlockSolvers:

0 commit comments

Comments
 (0)