Skip to content

Commit

Permalink
Trac #16475: Bug in Gomory-Hu tree algorithm
Browse files Browse the repository at this point in the history
When trying to come up with a doctest to verify ticket #16404, I
stumbled across this error:

{{{
sage: G = graphs.PetersenGraph()
sage: for u, v in [(0, 1), (0, 4), (0, 5), (1, 2), (1, 6), (3, 4), (5,
7), (5, 8)]:
....:     G.set_edge_label(u, v, 2)
....: T = G.gomory_hu_tree(method="FF")
....: f = G.flow(1, 9, use_edge_labels = True)
....: f2 = T.edge_label(1, 9)
....: if f != f2:
....:     print 'Proper Tree Edge Error:', f, f2
....:
Proper Tree Edge Error: 3 6
}}}

This is a violation of the property that defines a Gomory-Hu tree.

Furthermore, the derived property that the flow between two vertices in
a graph is the minimum of the edge_labels in the constructed Gomory-Hu
tree can be violated, for instance in the above graph:
{{{
sage: f = G.flow(5, 1, use_edge_labels = True)
sage: P = T.shortest_path(5, 1)
sage: E = zip(P, P[1::])
sage: f2 = min(map(lambda x: T.edge_label(x[0], x[1]), E))
sage: if f != f2:
....:     print 'Tree Path Error:', f, f2, P
....:
Tree Path Error: 6 3 [5, 9, 1]
}}}

URL: http://trac.sagemath.org/16475
Reported by: foosterhof
Ticket author(s): Nathann Cohen
Reviewer(s): Michele Borassi
  • Loading branch information
Release Manager authored and vbraun committed May 19, 2015
2 parents 1fae0dc + b0bbcd4 commit 3c8cda1
Showing 1 changed file with 71 additions and 84 deletions.
155 changes: 71 additions & 84 deletions src/sage/graphs/graph.py
Expand Up @@ -6312,7 +6312,7 @@ def is_prime(self):

return D[0] == "Prime" and len(D[1]) == self.order()

def _gomory_hu_tree(self, vertices=None, method="FF"):
def _gomory_hu_tree(self, vertices, method="FF"):
r"""
Returns a Gomory-Hu tree associated to self.
Expand Down Expand Up @@ -6345,101 +6345,65 @@ def _gomory_hu_tree(self, vertices=None, method="FF"):
example is only present to have a doctest coverage of 100%.
sage: g = graphs.PetersenGraph()
sage: t = g._gomory_hu_tree()
sage: t = g._gomory_hu_tree(frozenset(g.vertices()))
"""
self._scream_if_not_simple()
from sage.sets.set import Set

# The default capacity of an arc is 1
from sage.rings.real_mpfr import RR
capacity = lambda label: label if label in RR else 1

# Keeping the graph's embedding
pos = False

# Small case, not really a problem ;-)
if self.order() == 1:
return self.copy()

# This is a sign that this is the first call
# to this recursive function
if vertices is None:
# Now is the time to care about positions
pos = self.get_pos()

# if the graph is not connected, returns the union
# of the Gomory-Hu tree of each component
if not self.is_connected():
g = Graph()
for cc in self.connected_components_subgraphs():
g = g.union(cc._gomory_hu_tree(method=method))
g.set_pos(self.get_pos())
return g
# All the vertices is this graph are the "real ones"
vertices = Set(self.vertices())

# There may be many vertices, though only one which is "real"
if len(vertices) == 1:
g = Graph()
g.add_vertex(vertices[0])
g.add_vertices(vertices)
return g

# Take any two vertices
u,v = vertices[0:2]
# Take any two vertices (u,v)
it = vertices.__iter__()
u,v = it.next(),it.next()

# Recovers the following values
# flow is the connectivity between u and v
# edges of a min cut
# sets1, sets2 are the two sides of the edge cut
flow,edges,[set1,set2] = self.edge_cut(u, v, use_edge_labels=True, vertices=True, method=method)
# Compute a uv min-edge-cut.
#
# The graph is split into U,V with u \in U and v\in V.
flow,edges,[U,V] = self.edge_cut(u, v, use_edge_labels=True, vertices=True, method=method)

# One graph for each part of the previous one
g1,g2 = self.subgraph(set1), self.subgraph(set2)

# Adding the fake vertex to each part
g1_v = Set(set2)
g2_v = Set(set1)
g1.add_vertex(g1_v)
g1.add_vertex(g2_v)

# Each part of the graph had many edges going to the other part
# Now that we have a new fake vertex in each part
# we just say that the edges which were in the cut and going
# to the other side are now going to this fake vertex

# We must preserve the labels. They sum.

for e in edges:
x,y = e[0],e[1]
# Assumes x is in g1
if x in g2:
x,y = y,x
# If the edge x-g1_v exists, adds to its label the capacity of arc xy
if g1.has_edge(x, g1_v):
g1.set_edge_label(x, g1_v, g1.edge_label(x, g1_v) + capacity(self.edge_label(x, y)))
else:
# Otherwise, creates it with the good label
g1.add_edge(x, g1_v, capacity(self.edge_label(x, y)))
# Same thing for g2
if g2.has_edge(y, g2_v):
g2.set_edge_label(y, g2_v, g2.edge_label(y, g2_v) + capacity(self.edge_label(x, y)))
else:
g2.add_edge(y, g2_v, capacity(self.edge_label(x, y)))
gU,gV = self.subgraph(U), self.subgraph(V)

# Recursion for the two new graphs... The new "real" vertices are the intersection with
# with the previous set of "real" vertices
g1_tree = g1._gomory_hu_tree(vertices=(vertices & Set(g1.vertices())), method=method)
g2_tree = g2._gomory_hu_tree(vertices=(vertices & Set(g2.vertices())), method=method)
# A fake vertex fU (resp. fV) to represent U (resp. V)
fU = frozenset(U)
fV = frozenset(V)

# Union of the two partial trees ( it is disjoint, but
# disjoint_union does not preserve the name of the vertices )
g = g1_tree.union(g2_tree)
# Each edge (uu,vv) with uu \in U and vv\in V yields:
# - an edge (uu,fV) in gU
# - an edge (vv,fU) in gV
#
# If the same edge is added several times their capacities add up.

# An edge to connect them, with the appropriate label
g.add_edge(next(g1_tree.vertex_iterator()), next(g2_tree.vertex_iterator()), flow)
from sage.rings.real_mpfr import RR
for uu,vv,capacity in edges:
capacity = capacity if capacity in RR else 1

# Assume uu is in gU
if uu in V:
uu,vv = vv,uu

# Create the new edges if necessary
if not gU.has_edge(uu, fV):
gU.add_edge(uu, fV, 0)
if not gV.has_edge(vv, fU):
gV.add_edge(vv, fU, 0)

# update the capacities
gU.set_edge_label(uu, fV, gU.edge_label(uu, fV) + capacity)
gV.set_edge_label(vv, fU, gV.edge_label(vv, fU) + capacity)

# Recursion on each side
gU_tree = gU._gomory_hu_tree(vertices & frozenset(gU), method=method)
gV_tree = gV._gomory_hu_tree(vertices & frozenset(gV), method=method)

if pos:
g.set_pos(pos)
# Union of the two partial trees
g = gU_tree.union(gV_tree)

# An edge to connect them, with the appropriate label
g.add_edge(u, v, flow)

return g

Expand All @@ -6448,15 +6412,17 @@ def gomory_hu_tree(self, method="FF"):
Returns a Gomory-Hu tree of self.
Given a tree `T` with labeled edges representing capacities, it is very
easy to determine the maximal flow between any pair of vertices :
easy to determine the maximum flow between any pair of vertices :
it is the minimal label on the edges of the unique path between them.
Given a graph `G`, a Gomory-Hu tree `T` of `G` is a tree
with the same set of vertices, and such that the maximal flow
with the same set of vertices, and such that the maximum flow
between any two vertices is the same in `G` as in `T`. See the
`Wikipedia article on Gomory-Hu tree <http://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree>`_.
Note that, in general, a graph admits more than one Gomory-Hu tree.
See also 15.4 (Gomory-Hu trees) from [SchrijverCombOpt]_.
INPUT:
- ``method`` -- There are currently two different
Expand Down Expand Up @@ -6511,8 +6477,29 @@ def gomory_hu_tree(self, method="FF"):
sage: g.edge_connectivity() == min(t.edge_labels())
True
TESTS:
:trac:`16475`::
sage: G = graphs.PetersenGraph()
sage: for u, v in [(0, 1), (0, 4), (0, 5), (1, 2), (1, 6), (3, 4), (5, 7), (5, 8)]:
....: G.set_edge_label(u, v, 2)
sage: T = G.gomory_hu_tree()
sage: from itertools import combinations
sage: for u,v in combinations(G,2):
....: assert T.flow(u,v,use_edge_labels=True) == G.flow(u,v,use_edge_labels=True)
"""
return self._gomory_hu_tree(method=method)
if not self.is_connected():
g = Graph()
for cc in self.connected_components_subgraphs():
g = g.union(cc._gomory_hu_tree(frozenset(cc.vertices()),method=method))
else:
g = self._gomory_hu_tree(frozenset(self.vertices()),method=method)

if self.get_pos() is not None:
g.set_pos(dict(self.get_pos()))
return g

def two_factor_petersen(self):
r"""
Expand Down

0 comments on commit 3c8cda1

Please sign in to comment.