From c53ece8a6c7cf4b40378f6a16a4dcbfbec390cf1 Mon Sep 17 00:00:00 2001 From: Arnaud Becheler <8360330+Becheler@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:03:06 +0100 Subject: [PATCH 1/2] bug: parallel edges left unlabeled by biconnected_components --- test/biconnected_components_test.cpp | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/biconnected_components_test.cpp b/test/biconnected_components_test.cpp index 270f5ec55..2d95ed388 100644 --- a/test/biconnected_components_test.cpp +++ b/test/biconnected_components_test.cpp @@ -1,4 +1,5 @@ // Copyright 2004 The Trustees of Indiana University. +// Copyright (c) 2026 Arnaud Becheler // Use, modification and distribution is subject to the Boost Software // License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -17,10 +18,13 @@ #include #include #include +#include #include #include #include +#include +#include using namespace boost; @@ -168,6 +172,51 @@ void test_stateful_visitor_with_ref() BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); } +// endpoints of an undirected edge, smallest first +std::pair< Vertex, Vertex > sorted_endpoints(boost::graph_traits< Graph >::edge_descriptor e, const Graph& g) +{ + const auto u = boost::source(e, g); + const auto v = boost::target(e, g); + return std::make_pair(std::min(u, v), std::max(u, v)); +} + +// every parallel edge gets the label of its block +void test_parallel_edges() +{ + Graph g(6); + boost::add_edge(0, 1, g); + boost::add_edge(0, 1, g); + boost::add_edge(0, 1, g); + boost::add_edge(1, 2, g); + boost::add_edge(2, 3, g); + boost::add_edge(2, 3, g); + boost::add_edge(2, 3, g); + boost::add_edge(3, 4, g); + boost::add_edge(4, 5, g); + + constexpr std::size_t unlabeled = std::numeric_limits< std::size_t >::max(); + auto component_map = boost::get(&EdgeProperty::component, g); + for (const auto e : boost::make_iterator_range(boost::edges(g))) + { + boost::put(component_map, e, unlabeled); + } + + const auto num_comps = boost::biconnected_components(g, component_map); + BOOST_TEST_EQ(num_comps, static_cast< std::size_t >(5)); + + // each block of this graph is exactly one bundle of parallel edges + for (const auto e : boost::make_iterator_range(boost::edges(g))) + { + BOOST_TEST_NE(component_map[e], unlabeled); + for (const auto f : boost::make_iterator_range(boost::edges(g))) + { + const bool parallel = sorted_endpoints(e, g) == sorted_endpoints(f, g); + const bool same_component = component_map[e] == component_map[f]; + BOOST_TEST_EQ(parallel, same_component); + } + } +} + int main(int argc, char* argv[]) { std::size_t n = 100; @@ -198,6 +247,7 @@ int main(int argc, char* argv[]) } test_stateful_visitor_with_ref(); + test_parallel_edges(); return boost::report_errors(); } From d975f51ff60b495bf33e4efe963dc6e94ba4c080 Mon Sep 17 00:00:00 2001 From: Arnaud Becheler <8360330+Becheler@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:18:01 +0100 Subject: [PATCH 2/2] fix: label parallel edges in biconnected_components --- .../connected_components/biconnected_components.adoc | 2 ++ include/boost/graph/biconnected_components.hpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/algorithms/connected_components/biconnected_components.adoc b/doc/modules/ROOT/pages/algorithms/connected_components/biconnected_components.adoc index 26be80388..6eb0f00c8 100644 --- a/doc/modules/ROOT/pages/algorithms/connected_components/biconnected_components.adoc +++ b/doc/modules/ROOT/pages/algorithms/connected_components/biconnected_components.adoc @@ -268,6 +268,8 @@ image::figs/biconnected.png[] Vertices can be present in multiple biconnected components, but each edge can only be contained in a single biconnected component. The output of the `biconnected_components` algorithm records the biconnected component number of each edge in the property map `comp`. Articulation points will be emitted to the (optional) output iterator argument to `biconnected_components`, or can be computed without the use of a biconnected component number map via `articulation_points`. These functions return the number of biconnected components, the articulation point output iterator, or a pair of these quantities, depending on what information was recorded. +Parallel edges are supported: each parallel edge is assigned the component number of its block. Self-loops are not supported: the component number recorded for a self-loop is unspecified and may not be written at all. + The algorithm implemented is due to Tarjan [xref:about/bibliography.adoc#tarjan72:dfs_and_linear_algo[36]]. == Returns diff --git a/include/boost/graph/biconnected_components.hpp b/include/boost/graph/biconnected_components.hpp index cff7c8f12..081b57e53 100644 --- a/include/boost/graph/biconnected_components.hpp +++ b/include/boost/graph/biconnected_components.hpp @@ -1,5 +1,6 @@ // Copyright (c) Jeremy Siek 2001 // Copyright (c) Douglas Gregor 2004 +// Copyright (c) 2026 Arnaud Becheler // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at @@ -109,9 +110,10 @@ namespace detail = source(e, g); typename boost::graph_traits< Graph >::vertex_descriptor tgt = target(e, g); + // Parallel edges to the parent also reach here and need a label. + S.push(e); if (tgt != get(pred, src)) { - S.push(e); put(lowpt, src, min BOOST_PREVENT_MACRO_SUBSTITUTION( get(lowpt, src), get(dtm, tgt)));