diff --git a/src/robin.cpp b/src/robin.cpp index da6cbcf..cad6c5d 100644 --- a/src/robin.cpp +++ b/src/robin.cpp @@ -6,23 +6,82 @@ #include -std::vector robin::FindInlierStructure(const IGraph* g, - InlierGraphStructure graph_structure) { - // identify inlier structures +namespace { +std::vector solve_on_graph(const robin::IGraph* g, + robin::InlierGraphStructure graph_structure) { switch (graph_structure) { - case InlierGraphStructure::MAX_CORE: { - KCoreDecompositionSolver k_core_decomposition_solver( + case robin::InlierGraphStructure::MAX_CORE: { + robin::KCoreDecompositionSolver k_core_decomposition_solver( robin::KCoreDecompositionSolver::KCORE_SOLVER_MODE::BZ_SERIAL); k_core_decomposition_solver.Solve(*g); return k_core_decomposition_solver.GetMaxKCore(); } - case InlierGraphStructure::MAX_CLIQUE: { + case robin::InlierGraphStructure::MAX_CLIQUE: { robin::MaxCliqueSolver::Params clique_params; clique_params.solver_mode = robin::MaxCliqueSolver::CLIQUE_SOLVER_MODE::PMC_EXACT; robin::MaxCliqueSolver clique_solver(clique_params); return clique_solver.FindMaxClique(*g); } } + return {}; +} +} // anonymous namespace + +std::vector robin::FindInlierStructure(const IGraph* g, + InlierGraphStructure graph_structure) { + size_t N = g->VertexCount(); + + // Identify non-isolated vertices + std::vector new_to_old; + std::vector old_to_new(N, SIZE_MAX); + + for (size_t i = 0; i < N; ++i) { + if (g->GetVertexDegree(i) > 0) { + old_to_new[i] = new_to_old.size(); + new_to_old.push_back(i); + } + } + + size_t N_compact = new_to_old.size(); + + // If no vertices were filtered, run solver on original graph + if (N_compact == N) { + return solve_on_graph(g, graph_structure); + } + + // All vertices isolated — no inliers + if (N_compact == 0) { + return {}; + } + + // Build compacted adjacency list with remapped vertex IDs + std::vector> compact_adj(N_compact); + size_t compact_edges = 0; + for (size_t new_i = 0; new_i < N_compact; ++new_i) { + size_t old_i = new_to_old[new_i]; + size_t deg = g->GetVertexDegree(old_i); + compact_adj[new_i].reserve(deg); + for (size_t e = 0; e < deg; ++e) { + size_t old_j = g->GetVertexEdge(old_i, e); + size_t new_j = old_to_new[old_j]; + compact_adj[new_i].push_back(new_j); + } + compact_edges += deg; + } + compact_edges /= 2; // undirected: each edge counted twice + + AdjListGraph compact_graph(std::move(compact_adj), compact_edges); + + // Run solver on compact graph + auto compact_result = solve_on_graph(&compact_graph, graph_structure); + + // Remap back to original vertex IDs + std::vector result; + result.reserve(compact_result.size()); + for (size_t new_id : compact_result) { + result.push_back(new_to_old[new_id]); + } + return result; } robin::IGraph* robin::MakeVecAvgInvGraph(const Eigen::MatrixXd& measurements, double noise_bound, diff --git a/tests/robin_test.cpp b/tests/robin_test.cpp index 6a8fa57..dc3a63e 100644 --- a/tests/robin_test.cpp +++ b/tests/robin_test.cpp @@ -17,6 +17,50 @@ #include +TEST_CASE("zero-degree vertex filtering") { + SECTION("graph with isolated vertices") { + // Vertices 0,1,2 form a clique; vertices 3-9 are isolated + robin::AdjListGraph g; + g.PopulateVertices(10); + g.AddEdge(0, 1); + g.AddEdge(1, 2); + g.AddEdge(0, 2); + + auto result_core = robin::FindInlierStructure(&g, robin::InlierGraphStructure::MAX_CORE); + REQUIRE(result_core.size() == 3); + std::sort(result_core.begin(), result_core.end()); + REQUIRE(result_core == std::vector{0, 1, 2}); + + auto result_clique = robin::FindInlierStructure(&g, robin::InlierGraphStructure::MAX_CLIQUE); + REQUIRE(result_clique.size() == 3); + std::sort(result_clique.begin(), result_clique.end()); + REQUIRE(result_clique == std::vector{0, 1, 2}); + } + + SECTION("all vertices isolated") { + robin::AdjListGraph g; + g.PopulateVertices(5); + // No edges + auto result = robin::FindInlierStructure(&g, robin::InlierGraphStructure::MAX_CORE); + REQUIRE(result.empty()); + } + + SECTION("no isolated vertices") { + // Complete graph on 4 vertices — all have degree 3 + robin::AdjListGraph g; + g.PopulateVertices(4); + g.AddEdge(0, 1); + g.AddEdge(0, 2); + g.AddEdge(0, 3); + g.AddEdge(1, 2); + g.AddEdge(1, 3); + g.AddEdge(2, 3); + + auto result = robin::FindInlierStructure(&g, robin::InlierGraphStructure::MAX_CLIQUE); + REQUIRE(result.size() == 4); + } +} + TEST_CASE("vector averaging") { // // Prepare test data