Skip to content

Commit

Permalink
Add is_arborescence(), is_branching().
Browse files Browse the repository at this point in the history
Generalize is_tree(), is_forest() to handle directed graphs.
  • Loading branch information
chebee7i committed May 9, 2014
1 parent 1f48a6c commit 2b2d6c0
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 53 deletions.
153 changes: 127 additions & 26 deletions networkx/algorithms/tree/recognition.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,151 @@
#-*- coding: utf-8 -*-
"""
We use the following definitions:
A forest is an (undirected) graph with no cycles.
A tree is a connected forest.
A directed forest is a directed graph whose underlying graph is a forest.
A directed tree is a (weakly) connected, directed forest.
Equivalently: It is a directed graph whose underlying graph is a tree.
Note: Some take the term directed tree to be synonymous with an
arborescence. We do not follow that convention here.
Note: Since the underlying graph is a tree, any orientation defines a DAG
So all directed trees are DAGs. Thus, the definition we use here is,
in fact, equivalent to a polytree.
A DAG is a directed graph with no directed cycles.
Example: A -> B, A -> C, B -> C is a DAG that is not a directed tree.
A polyforest is a DAG that is also a directed forest.
A polytree is a weakly connected polyforest.
Equivalently, a polytree is a DAG whose underlying graph is a tree.
A branching is a polyforest with each edge directed to a different node.
So the maximum in-degree is, at most, one. The maximum number of edges any
branching can have is n-1. In this case, the branching spans the graph, and
we have an arborescence. It is in this sense that the min/max spanning tree
problem is analogous to the min/max arborescence problem.
An arborescence is a (weakly) connected branching. That is, if you look
at the underlying graph, it is a spanning tree. Additionally, all edges
are directed away from a unique root node, for if you had two nodes with
in-degree zero, then weak connectivity would force some other node
to have in-degree of at least 2 (which is not allowed in branchings).
"""

import networkx as nx
from networkx.utils import not_implemented_for
__author__ = """\n""".join(['Ferdinando Papale <[email protected]>'])
__all__ = ['is_tree', 'is_forest']

__author__ = """\n""".join([
'Ferdinando Papale <[email protected]>',
'chebee7i <[email protected]>',
])

@not_implemented_for('directed')
def is_tree(G):
"""Return True if the input graph is a tree

__all__ = ['is_arborescence', 'is_branching', 'is_forest', 'is_tree']

@nx.utils.not_implemented_for('undirected')
def is_arborescence(G):
"""
Returns `True` if `G` is an arborescence.
"""
if not is_tree(G):
return False

if max(G.in_degree().values()) > 1:
return False

return True

@nx.utils.not_implemented_for('undirected')
def is_branching(G):
"""
Returns `True` if `G` is a branching.
A branching is a directed forest with maximum in-degree equal to 1.
Parameters
----------
G : NetworkX Graph
An undirected graph.
G : directed graph
The directed graph to test.
Returns
-------
True if the input graph is a tree
b : bool
A boolean that is `True` if `G` is a branching.
Notes
-----
For undirected graphs only.
"""
n = len(G)
if n == 0:
raise nx.NetworkXPointlessConcept
return nx.number_of_edges(G) == n - 1
if not is_forest(G):
return False

if max(G.in_degree().values()) > 1:
return False

return True

@not_implemented_for('directed')
def is_forest(G):
"""Return True if the input graph is a forest
"""
Returns `True` if G is a forest.
For directed graphs, the direction of edges is ignored, and the graph `G`
is considered to be a directed forest if the underlying graph is a forest.
"""
n = G.number_of_nodes()
if n == 0:
raise nx.exception.NetworkXPointlessConcept('G has no nodes.')

if G.is_directed():
components = nx.weakly_connected_component_subgraphs
else:
components = nx.connected_component_subgraphs

for component in components(G):
# Make sure the component is a tree.
if component.number_of_edges() != component.number_of_nodes() - 1:
return False

return True

def is_tree(G):
"""
Returns `True` if `G` is a tree.
A tree is a simple, connected graph with no cycles.
For directed graphs, the direction of edges is ignored, and the graph `G`
is considered to be a directed tree if the underlying graph is a tree.
Parameters
----------
G : NetworkX Graph
An undirected graph.
G : graph
The graph to test.
Returns
-------
True if the input graph is a forest
b : bool
A boolean that is `True` if `G` is a tree.
Notes
-----
For undirected graphs only.
Directed trees are also known as polytrees. Sometimes, "directed tree"
is defined more restrictively to mean "arboresence" instead.
"""
for graph in nx.connected_component_subgraphs(G):
if not nx.is_tree(graph):
return False
return True
n = G.number_of_nodes()
if n == 0:
raise nx.exception.NetworkXPointlessConcept('G has no nodes.')

if G.is_directed():
is_connected = nx.is_weakly_connected
else:
is_connected = nx.is_connected

# A simple, connected graph with no cycles has n-1 edges.

if G.number_of_edges() != n - 1:
return False

return is_connected(G)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python

from nose.tools import *
import networkx as nx

Expand All @@ -25,13 +25,7 @@ def setUp(self):
self.T6.add_nodes_from([6,7])
self.T6.add_edge(6,7)

self.F1 = nx.compose(self.T6,self.T3)



self.N1 = nx.DiGraph()

self.N3 = nx.MultiDiGraph()
self.F1 = nx.compose(self.T6, self.T3)

self.N4 = nx.Graph()
self.N4.add_node(1)
Expand All @@ -55,23 +49,6 @@ def test_null(self):
def test_null(self):
nx.is_tree(nx.MultiGraph())


@raises(nx.NetworkXNotImplemented)
def test_digraph(self):
assert_false(nx.is_tree(self.N1))

@raises(nx.NetworkXNotImplemented)
def test_multidigraph(self):
assert_false(nx.is_tree(self.N3))

@raises(nx.NetworkXNotImplemented)
def test_digraph_forest(self):
assert_false(nx.is_forest(self.N1))

@raises(nx.NetworkXNotImplemented)
def test_multidigraph_forest(self):
assert_false(nx.is_forest(self.N3))

def test_is_tree(self):
assert_true(nx.is_tree(self.T2))
assert_true(nx.is_tree(self.T3))
Expand All @@ -94,7 +71,7 @@ def test_is_not_forest(self):
assert_false(nx.is_forest(self.N6))
assert_false(nx.is_forest(self.NF1))







0 comments on commit 2b2d6c0

Please sign in to comment.