Skip to content
Merged
40 changes: 35 additions & 5 deletions src/sage/graphs/base/boost_graph.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ Functions
# http://www.gnu.org/licenses/
# ****************************************************************************

cimport cython
from cysignals.signals cimport sig_check, sig_on, sig_off
from libcpp.set cimport set as cset
from libcpp.pair cimport pair
Expand Down Expand Up @@ -744,7 +743,7 @@ cpdef min_spanning_tree(g,
return [(u, v, g.edge_label(u, v)) for u, v in edges]


cpdef blocks_and_cut_vertices(g):
cpdef blocks_and_cut_vertices(g, forbidden_vertices=None):
r"""
Compute the blocks and cut vertices of the graph.

Expand All @@ -755,6 +754,9 @@ cpdef blocks_and_cut_vertices(g):

- ``g`` -- the input Sage graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search
Comment thread
dcoudert marked this conversation as resolved.

OUTPUT:

A 2-dimensional vector with m+1 rows (m is the number of biconnected
Expand All @@ -772,10 +774,29 @@ cpdef blocks_and_cut_vertices(g):
sage: blocks_and_cut_vertices(g)
([[8, 9], [7, 8], [0, 1, 2, 3, 5, 4, 6, 7]], [8, 7])

sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})])
sage: G = Graph([(0,1,{'name':'a','weight':1}),
....: (0,2,{'name':'b','weight':3}),
....: (1,2,{'name':'b','weight':1})])
sage: blocks_and_cut_vertices(G)
([[0, 1, 2]], [])

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.WindmillGraph(4, 3)
sage: blocks_and_cut_vertices(G)
([[0, 1, 2, 3], [0, 4, 5, 6], [0, 7, 8, 9]], [0])
sage: blocks_and_cut_vertices(G, forbidden_vertices=[0])
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [])
sage: blocks_and_cut_vertices(G, forbidden_vertices=[1])
([[0, 2, 3], [0, 4, 5, 6], [0, 7, 8, 9]], [0])
sage: G = graphs.PathGraph(3)
sage: blocks_and_cut_vertices(G)
([[1, 2], [0, 1]], [1])
sage: blocks_and_cut_vertices(G, forbidden_vertices=[0])
([[1, 2]], [])
sage: blocks_and_cut_vertices(G, forbidden_vertices=[1])
([[0], [2]], [])

TESTS:

Given an input which is not a graph::
Expand All @@ -790,8 +811,17 @@ cpdef blocks_and_cut_vertices(g):
if not isinstance(g, GenericGraph):
raise TypeError("the input must be a Sage graph")

if g.allows_loops() or g.allows_multiple_edges() or g.is_directed():
g = g.to_simple()
cdef set forbidden = set() if forbidden_vertices is None else set(forbidden_vertices)

if (g.allows_loops() or g.allows_multiple_edges()
or g.is_directed() or forbidden):
# Build the underlying undirected graph without loops or multiple edges,
# and without the forbidden vertices
V = [v for v in g if v not in forbidden]
E = [(u, v) for u, v in g.edge_iterator(vertices=V, labels=False, sort_vertices=False)
if u != v and u not in forbidden and v not in forbidden]
from sage.graphs.graph import Graph
g = Graph([V, E], format='vertices_and_edges')

cdef BoostVecGraph g_boost
cdef vector[vector[v_index]] result
Expand Down
144 changes: 129 additions & 15 deletions src/sage/graphs/connectivity.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,8 @@ def connected_components_sizes(G, forbidden_vertices=None):
forbidden_vertices=forbidden_vertices)]


def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):
def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None,
forbidden_vertices=None):
"""
Return the blocks and cut vertices of the graph.

Expand Down Expand Up @@ -497,6 +498,9 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):
vertex as its one argument and returns a value that can be used for
comparisons in the sorting algorithm (we must have ``sort=True``)

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search

OUTPUT: ``(B, C)``, where ``B`` is a list of blocks - each is a list of
vertices and the blocks are the corresponding induced subgraphs - and
``C`` is a list of cut vertices.
Expand All @@ -510,7 +514,7 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):

- :meth:`blocks_and_cuts_tree`
- :func:`sage.graphs.base.boost_graph.blocks_and_cut_vertices`
- :meth:`~sage.graphs.generic_graph.GenericGraph.is_biconnected`
- :meth:`~Graph.is_biconnected`
- :meth:`~Graph.bridges`

EXAMPLES:
Expand Down Expand Up @@ -556,6 +560,29 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):
sage: blocks_and_cut_vertices(rings, algorithm='Tarjan_Boost')
([[0, 1, 4, 2, 3], [0, 6, 9, 7, 8]], [0])

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.WindmillGraph(4, 3)
sage: G.blocks_and_cut_vertices(sort=True)
([[0, 1, 2, 3], [0, 4, 5, 6], [0, 7, 8, 9]], [0])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Boost', forbidden_vertices=[0])
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Sage', forbidden_vertices=[0])
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Boost', forbidden_vertices=[1])
([[0, 2, 3], [0, 4, 5, 6], [0, 7, 8, 9]], [0])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Sage', forbidden_vertices=[1])
([[0, 2, 3], [0, 4, 5, 6], [0, 7, 8, 9]], [0])
sage: G = graphs.PathGraph(3)
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Sage')
([[1, 2], [0, 1]], [1])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Sage', forbidden_vertices=[0])
([[1, 2]], [])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Sage', forbidden_vertices=[1])
([[0], [2]], [])
sage: G.blocks_and_cut_vertices(sort=True, algorithm='Tarjan_Boost', forbidden_vertices=[1])
([[0], [2]], [])

TESTS::

sage: blocks_and_cut_vertices(Graph(0))
Expand All @@ -579,7 +606,7 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):

if algorithm == "Tarjan_Boost":
from sage.graphs.base.boost_graph import blocks_and_cut_vertices
return blocks_and_cut_vertices(G)
return blocks_and_cut_vertices(G, forbidden_vertices=forbidden_vertices)

if algorithm != "Tarjan_Sage":
raise NotImplementedError("blocks and cut vertices algorithm '%s' is not implemented" % algorithm)
Expand All @@ -593,13 +620,16 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):

# We iterate over all vertices to ensure that we visit each connected
# component of the graph
seen = set()
cdef set seen = set() if forbidden_vertices is None else set(forbidden_vertices)
cdef frozenset forbidden = frozenset(seen)
for start in G.vertex_iterator():
if start in seen:
continue

# Special case of an isolated vertex
if not G.degree(start):
if (not G.degree(start) or
(forbidden and
all(v in forbidden for v in G.neighbor_iterator(start)))):
blocks.append([start])
seen.add(start)
continue
Expand Down Expand Up @@ -639,6 +669,10 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):
# We consider the next of its neighbors
w = next(neighbors[v])

if w in forbidden:
# We skip that neighbor
continue

# If we never met w before, we remember the direction of
# edge vw, and add w to the stack.
if w not in number:
Expand Down Expand Up @@ -699,7 +733,7 @@ def blocks_and_cut_vertices(G, algorithm='Tarjan_Boost', sort=False, key=None):
return blocks, list(cut_vertices)


def blocks_and_cuts_tree(G):
def blocks_and_cuts_tree(G, forbidden_vertices=None):
"""
Return the blocks-and-cuts tree of ``self``.

Expand All @@ -718,6 +752,14 @@ def blocks_and_cuts_tree(G):

We referred to [HarPri]_ and [Gallai]_ for blocks and cuts tree.

INPUT:

- ``G`` -- a Sage graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search. This is equivalent to getting the blocks and cut
tree of a graph in which the forbidden vertices have been removed.

.. SEEALSO::

- :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
Expand Down Expand Up @@ -749,6 +791,18 @@ def blocks_and_cuts_tree(G):
sage: T.vertices(sort=True)
[('B', (0, 1, 4, 5, 2, 6, 3, 7, 8, 9))]

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.CycleGraph(5)
sage: G.blocks_and_cut_vertices()
([[0, 1, 4, 2, 3]], [])
sage: G.blocks_and_cuts_tree()
Graph on 1 vertex
sage: G.blocks_and_cut_vertices(forbidden_vertices=[0])
([[3, 4], [2, 3], [1, 2]], [2, 3])
sage: G.blocks_and_cuts_tree(forbidden_vertices=[0])
Graph on 5 vertices

TESTS:

When ``self`` is not connected, the resulting graph is a forest
Expand All @@ -771,7 +825,7 @@ def blocks_and_cuts_tree(G):
raise TypeError("the input must be a Sage graph")

from sage.graphs.graph import Graph
B, C = G.blocks_and_cut_vertices()
B, C = G.blocks_and_cut_vertices(forbidden_vertices=forbidden_vertices)
B = map(tuple, B)
set_C = set(C)
g = Graph()
Expand All @@ -783,13 +837,21 @@ def blocks_and_cuts_tree(G):
return g


def is_biconnected(G):
def is_biconnected(G, forbidden_vertices=None):
r"""
Check whether the graph is biconnected.

A biconnected graph is a connected graph on two or more vertices that is not
broken into disconnected pieces by deleting any single vertex.

INPUT:

- ``G`` -- a Sage graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search. This is equivalent to checking whether the graph
in which the forbidden vertices have been removed is biconnected.

.. SEEALSO::

- :meth:`~sage.graphs.generic_graph.GenericGraph.is_connected`
Expand All @@ -809,6 +871,14 @@ def is_biconnected(G):
sage: G.is_biconnected()
True

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.CycleGraph(5)
sage: G.is_biconnected()
True
sage: G.is_biconnected(forbidden_vertices=[0])
False

TESTS::

sage: Graph().is_biconnected()
Expand All @@ -820,10 +890,10 @@ def is_biconnected(G):
"""
if G.order() < 2 or not G.is_connected():
return False
return not G.blocks_and_cut_vertices()[1]
return not G.blocks_and_cut_vertices(forbidden_vertices=forbidden_vertices)[1]


def biconnected_components(G):
def biconnected_components(G, forbidden_vertices=None):
r"""
Return the list of biconnected components.

Expand All @@ -834,6 +904,10 @@ def biconnected_components(G):

- ``G`` -- the input graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search. This is equivalent to getting the biconnected
components of the graph after the removal of the forbidden vertices.

EXAMPLES::

sage: from sage.graphs.connectivity import biconnected_components
Expand All @@ -843,6 +917,14 @@ def biconnected_components(G):
sage: sorted(len(b) for b in biconnected_components(2 * G))
[2, 2, 3, 3]

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.CycleGraph(5)
sage: len(G.biconnected_components())
1
sage: len(G.biconnected_components(forbidden_vertices=[0]))
3

TESTS:

If ``G`` is not a Sage graph, an error is raised::
Expand All @@ -857,10 +939,11 @@ def biconnected_components(G):
if not isinstance(G, GenericGraph):
raise TypeError("the input must be a Sage graph")

return [b for b in blocks_and_cut_vertices(G)[0] if len(b) > 1]
B = blocks_and_cut_vertices(G, forbidden_vertices=forbidden_vertices)[0]
return [b for b in B if len(b) > 1]


def biconnected_components_subgraphs(G):
def biconnected_components_subgraphs(G, forbidden_vertices=None):
r"""
Return a list of biconnected components as graph objects.

Expand All @@ -871,6 +954,11 @@ def biconnected_components_subgraphs(G):

- ``G`` -- the input graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search. This is equivalent to getting the biconnected
components subgraphs of the graph after the removal of the forbidden
vertices.

EXAMPLES::

sage: from sage.graphs.connectivity import biconnected_components_subgraphs
Expand All @@ -883,6 +971,14 @@ def biconnected_components_subgraphs(G):
sage: L[1].edges()
[(0, 1, None), (0, 2, None), (1, 2, None)]

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.CycleGraph(5)
sage: len(G.biconnected_components_subgraphs())
1
sage: len(G.biconnected_components_subgraphs(forbidden_vertices=[0]))
3

TESTS:

If ``G`` is not a Sage graph, an error is raised::
Expand All @@ -897,16 +993,26 @@ def biconnected_components_subgraphs(G):
if not isinstance(G, GenericGraph):
raise TypeError("the input must be a Sage graph")

return [G.subgraph(c) for c in G.biconnected_components()]
B = G.biconnected_components(forbidden_vertices=forbidden_vertices)
return [G.subgraph(c) for c in B]


def number_of_biconnected_components(G):
def number_of_biconnected_components(G, forbidden_vertices=None):
r"""
Return the number of biconnected components.

A biconnected component is a maximal subgraph on two or more vertices that
is biconnected, i.e., removing any vertex does not disconnect it.

INPUT:

- ``G`` -- the input graph

- ``forbidden_vertices`` -- list (default: ``None``); set of vertices to
avoid during the search. This is equivalent to getting the number of
biconnected components of the graph after the removal of the forbidden
vertices.

.. SEEALSO::

- :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
Expand Down Expand Up @@ -947,6 +1053,14 @@ def number_of_biconnected_components(G):
sage: G.number_of_biconnected_components()
0

Check the behavior of parameter ``forbidden_vertices``::

sage: G = graphs.CycleGraph(5)
sage: G.number_of_biconnected_components()
1
sage: G.number_of_biconnected_components(forbidden_vertices=[0])
3

TESTS:

An error is raised if the input is not a Sage graph::
Expand All @@ -961,7 +1075,7 @@ def number_of_biconnected_components(G):
if not isinstance(G, GenericGraph):
raise TypeError("the input must be a Sage graph")

return len(G.biconnected_components())
return len(G.biconnected_components(forbidden_vertices=forbidden_vertices))


def is_edge_cut(G, edges):
Expand Down
Loading