From ed93eda5404ce8421c246ab7ef4adef67fd4cc76 Mon Sep 17 00:00:00 2001 From: David Einstein Date: Fri, 30 Jun 2023 09:46:39 -0400 Subject: [PATCH 1/2] Return subgraphs with exactly k vertices In response to issue #35857 modified connected_subgraph_iterator to just return the subgraphs of order k. - Added a parameter 'exactly_k --- src/sage/graphs/base/static_dense_graph.pyx | 99 +++++++++++---------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/src/sage/graphs/base/static_dense_graph.pyx b/src/sage/graphs/base/static_dense_graph.pyx index 99bf7a8a117..59b456762a9 100644 --- a/src/sage/graphs/base/static_dense_graph.pyx +++ b/src/sage/graphs/base/static_dense_graph.pyx @@ -745,7 +745,8 @@ def connected_full_subgraphs(G, edges_only=False, labels=False, def connected_subgraph_iterator(G, k=None, bint vertices_only=False, - edges_only=False, labels=False, induced=True): + edges_only=False, labels=False, induced=True, + exactly_k=False): r""" Return an terator over the induced connected subgraphs of order at most `k`. @@ -783,6 +784,10 @@ def connected_subgraph_iterator(G, k=None, bint vertices_only=False, connected sub(di)graph only or also non-induced sub(di)graphs. This parameter can be set to ``False`` for simple (di)graphs only. + - ``exactly_k`` -- boolean (default: ``False``); ``True`` if we only + return graphs of order ``k``, ``False`` if we return graphs of order + at most ``k``. + EXAMPLES:: sage: G = DiGraph([(1, 2), (2, 3), (3, 4), (4, 2)]) @@ -811,6 +816,8 @@ def connected_subgraph_iterator(G, k=None, bint vertices_only=False, Subgraph of (): Digraph on 1 vertex, Subgraph of (): Digraph on 2 vertices, Subgraph of (): Digraph on 1 vertex] + sage: list(G.connected_subgraph_iterator(k=3, vertices_only=True, exactly_k=True)) + [[1, 2, 3], [1, 2, 4], [2, 3, 4]] sage: list(G.connected_subgraph_iterator(k=2, vertices_only=True)) [[1], [1, 2], [2], [2, 3], [2, 4], [3], [3, 4], [4]] @@ -921,14 +928,15 @@ def connected_subgraph_iterator(G, k=None, bint vertices_only=False, sig_check() vertices = [int_to_vertex[u]] - if vertices_only: - yield vertices - else: - H = G.subgraph(vertices) - if edges_only: - yield H.edges(sort=False, labels=labels) + if not exactly_k or mk == 1: + if vertices_only: + yield vertices else: - yield H + H = G.subgraph(vertices) + if edges_only: + yield H.edges(sort=False, labels=labels) + else: + yield H # We initialize the loop with vertices u in current, {u+1, ..., n-1} # in left, and N(u) in boundary @@ -970,45 +978,46 @@ def connected_subgraph_iterator(G, k=None, bint vertices_only=False, # We yield that new subset vertices = [int_to_vertex[a] for a in range(u, n) if bitset_in(stack.rows[level], a)] - if vertices_only: - yield vertices - else: - H = G.subgraph(vertices) - if induced: - if edges_only: - yield H.edges(sort=False, labels=labels) - else: - yield H + if not exactly_k or bitset_len(current) == mk - 1: + if vertices_only: + yield vertices else: - # We use a decomposition into biconnected components to - # work on smaller graphs. - if H.is_directed(): - blocks = H.to_undirected().blocks_and_cut_vertices()[0] - else: - blocks = H.blocks_and_cut_vertices()[0] - if len(blocks) == 1: - # H is strongly connected or biconnected - yield from connected_full_subgraphs(H, edges_only=edges_only, - labels=labels) + H = G.subgraph(vertices) + if induced: + if edges_only: + yield H.edges(sort=False, labels=labels) + else: + yield H else: - L = [] - for bloc in blocks: - if len(bloc) == 2: - bb = [[e] for e in H.edge_boundary(bloc, bloc, labels=labels)] - if len(bb) == 2: - # H is directed with edges (u, v) and (v, u) - bb.append(H.edge_boundary(bloc, bloc, labels=labels)) - L.append(bb) - else: - L.append(connected_full_subgraphs(H.subgraph(vertices=bloc), - edges_only=True, labels=labels)) - - for edges in product(*L): - good_edges = flatten(edges, ltypes=list) - if edges_only: - yield list(good_edges) - else: - yield H.subgraph(vertices=H, edges=good_edges) + # We use a decomposition into biconnected components to + # work on smaller graphs. + if H.is_directed(): + blocks = H.to_undirected().blocks_and_cut_vertices()[0] + else: + blocks = H.blocks_and_cut_vertices()[0] + if len(blocks) == 1: + # H is strongly connected or biconnected + yield from connected_full_subgraphs(H, edges_only=edges_only, + labels=labels) + else: + L = [] + for bloc in blocks: + if len(bloc) == 2: + bb = [[e] for e in H.edge_boundary(bloc, bloc, labels=labels)] + if len(bb) == 2: + # H is directed with edges (u, v) and (v, u) + bb.append(H.edge_boundary(bloc, bloc, labels=labels)) + L.append(bb) + else: + L.append(connected_full_subgraphs(H.subgraph(vertices=bloc), + edges_only=True, labels=labels)) + + for edges in product(*L): + good_edges = flatten(edges, ltypes=list) + if edges_only: + yield list(good_edges) + else: + yield H.subgraph(vertices=H, edges=good_edges) else: # We cannot extend the current subset, either due to a lack of From 0dcd557a464e6a600b55c3ca2de1d91a93aeac56 Mon Sep 17 00:00:00 2001 From: David Einstein Date: Fri, 30 Jun 2023 13:17:58 -0400 Subject: [PATCH 2/2] Fix formatting in documentation of connected_subgraph_iterator --- src/sage/graphs/base/static_dense_graph.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/base/static_dense_graph.pyx b/src/sage/graphs/base/static_dense_graph.pyx index 59b456762a9..2014289457d 100644 --- a/src/sage/graphs/base/static_dense_graph.pyx +++ b/src/sage/graphs/base/static_dense_graph.pyx @@ -785,8 +785,8 @@ def connected_subgraph_iterator(G, k=None, bint vertices_only=False, This parameter can be set to ``False`` for simple (di)graphs only. - ``exactly_k`` -- boolean (default: ``False``); ``True`` if we only - return graphs of order ``k``, ``False`` if we return graphs of order - at most ``k``. + return graphs of order `k`, ``False`` if we return graphs of order + at most `k`. EXAMPLES::