P. Boothe
Computer Science Dept.
University of Oregon
peter@cs.uoregon.edu
http://soy.dyndns.org/~peter/projects/research/reduction/
The ideal algorithm would take a description of the desired property in some formal language, and a graph. Then it would quickly respond with true or false based on whether the graph has the desired property. Unfortunately, since testing for many properties is NP-Hard, this algorithm is currently unknown and possibly nonexistant. One way to get around this is to restrict the language so that it cannot describe an NP-Hard property. One such restriction is monadic second order logic, which is a restriction of second order logic to quantifiers over functions of arity at most one. It was shown in [1] that any property with bounded treewidth described in this language was necessarily testable in linear time with respect to the size of the input graph. Despite this surprising time bound, the language still has quite a bit of expressive power. Many properties, including outerplanarity, 3-connectedness, partial 3-tree, planar partial 3-tree, and planar partial 2-tree can be expressed.
The existence of a time-efficient algorithm was then proven by showing (non-constructively) that any property describable in monadic second order logic necessarily has a corresponding graph reduction system and then showing (non-constructively again) that each reduction system necessarily has a linear time algorithm for testing whether a graph has the property corresponding to the original property. A graph reduction system is a set of reduction rules, which are pairs of labeled graphs, each vertex of which is also tagged as restricted or unrestricted, and a set of base graphs which we refer to as reducts. Whenever an instance of the left graph of any of the reduction rules is found as a subgraph of our tested graph, subject to the constraint that the restricted vertices have the same degree in the original graph as they do in the reduction rule, we apply the corresponding reduction rule by replacing the subgraph with the other graph in the rule. Once we can no longer apply any reduction rules we test whether the reduced graph is isomorphic to any of our reducts. If our graph is isomorphic to a reduct, then the graph has the desired property. Otherwise it does not. A sample graph reduction system is shown in Figure 1, with the restricted nodes colored solid, and the unrestricted ones hollow. The labeling of the graph is implied by the positions of each node. The definition of a graph reduction system does not imply an ordering on reduction rules, thus any graph with the the desired property will reduce to a valid reduct, no matter the order in which the reduction rules are applied.
|
More formally, a graph reduction system
consists of a set of reduction
rules
, and a set of irreducible reducts
. A reduction rule
is two graphs
and
such that the size of
is strictly less than
the size of
for some measure of graph size.1Furthermore, for each
we have a set
that
we label restricted. Whenever we see a subgraph of our input graph
, that is isomorphic to a
in rule
via some mapping
, and
such that
it is true that
where
is the degree function for
, we can
reduce our graph by reduction rule
by setting
| (1) | |||
| (2) |
Several things are surprising about these systems. The first is that the order
in which rules are applied does not matter! In a graph reduction system, it
does not matter what order you apply the rules in - if a graph has the
property under test, it will be reduced to some reduct in
no matter what
the rule order is. The second is that these systems have enough power to test
any MSOL bounded treewidth property. The power of these systems should not be
too surprising, however, as there is a very deep connection between graph
reduction systems and graph grammars.
Graph grammars are tools used to create graphs that have a particular property, and graph reduction systems can be analogized to tools that parse graphs to check for a property. Further extending the analogy, it is sometimes useful to think of the restricted vertices as the terminal symbols of a production grammar, and the reducts as start expressions for the production of graphs. Graph grammars have been written about extensively, and their relation to graph reduction systems can be found many places, including [3].
The goal of Algorithm 1 is to take each vertex with a degree no larger than the largest degree of any restricted vertex, and attempt to place that vertex in each restricted place in each reduction rule. Since it does not matter what order we apply reductions in, we apply them greedily. Every time it is possible to reduce the graph, we do so. Our prof of correctness will then follow from the fact that we never stop trying to reduce the graph until there are no reductions possible.
For the purposes of the algorithm, it will be necessary to talk about the border nodes of a restricted vertex. These are exactly the unrestricted vertices surrounding the subgraph of restricted nodes containing that restricted vertex.
The running time of our algorithm is initially hard to see for two reasons. Partially because it hinges upon the time it takes to perform the operations SET and ACCESS on an associative array, and partially because when we talk about the runtime of our algorithm, we talk about its parameterized complexity[2], which has the expressiveness to deal with the two inputs separately.
Abstracting away issues of SET and ACCESS time, and simply
referring to
as
, we will prove the following
theorem:
Every iteration of that main loop either applies a reduction or it does not. If the vertex under test does not complete a reduction rule, then we simply record any rules that it matched part of, and never look at it again unless its degree changes. If it does complete a reduction rule, then that reduction rule is applied to the graph, making it smaller. At the same time the rule is applied, all vertices whose degree has changed and is sufficiently small are put into the search queue if they were not there already. To get our
bound on the number of while() loop iterations, we count the maximum number of iterations that a problematic graph could force us to go through.
Since every increase in queue size is accompanied by a corresponding decrease
in graph size, we can have at most
queue size increases before the graph
has been reduced to nothing. These increases in queue size never increase the
queue by more than a constant (again, with respect to the input graph) because
the rules are constant size with respect to the graph. Calling the maximum
size increase
, we can see that the greatest number of iterations we could be
forced to do is
. Thus, we will run through the main loop
times, and each iteration takes
time, for a total time
bound of
.
Various implementations of
have different effects on our runtime, with the
more obvious balanced tree or hashtable approaches leading to
and
expected time and
worst case, respectively. Tarjan
describes in [4] a method of implementing an associative array
with
access time, which leads to our best case linear time bound,
but it is unlikely that this would be used in practice due to its
extremely large space requirements.
All this time, however, we have been ignoring the effects of our reduction
system on the runtime. If we hold our input graph fixed, and only look at the
reduction system's affect on the runtime, then simply counting up the maximum
bounds of each internal for() loop we get a runtime of
, where
is the number of reduction rules,
is the
maximum number of subgraphs of restricted components in a rule,
is the
maximum size of these subgraphs,
is the maximum degree of a restricted
vertex, and
is the time it takes to perform graph isomorphism on a
graph of the given size. To get a runtime in terms of only two factors, we
concentrate on the maximum subgraph size and call it
. The best
known graph isomorphism solutions have a running time of
where
is the size of the graph being tested.
On an intuitive level, graph reduction systems have many rules, but it is
only when the rules get big that the problem becomes really hard. Choosing
subgraph size as our parameter allows us to do several things. First of all,
we can ignore
and
, then we can note that the largest
or
can be
is
. Which makes the runtime of the algorithm
. Which,
as described earlier, we can make as low as
, but will probably be
in most implementations.
We have described and proven correct algorithm that will work for any reduction
system. In addition, we also have an implementation that works in
expected time that can be downloaded from
http://soy.dyndns.org/~peter/projects/research/reduction/linear. The
algorithm works and, despite an intimidating looking runtime, is useful for
testing many properties.
Automatic discovery of reduction rules from a monadic second order logic description remains an open problem, as does the automatic generation of heuristics that might help with subgraph isomorphism. Also, figuring out whether or not an MSOL property has bounded treewidth is not always easy, and it would be useful if that was automated as well.
#!/usr/bin/env python2.2
from __future__ import generators
def getAllPermutations(seq):
""" Erlich's Swap Method - from TAOCP Book 4 pre-facile 2
http://www-cs-staff.stanford.edu/~knuth/taocp.html """
seq = seq[:] # Work on a copy of what we are given
n = len(seq)
b = [i for i in range(n)]
c = [0 for i in range(n+1)]
while 1:
yield seq
k = 1
if c[k] == k:
while not c[k] < k:
c[k] = 0
k = k+1
if n == k:
return
else:
c[k] = c[k] + 1
seq[b[k]], seq[0] = seq[0], seq[b[k]]
j = 1
k = k-1
while j < k:
b[j], b[k] = b[k], b[j]
j = j+1
k = k-1
def getAllSubsets(seq, size):
"Generate all subsets of a particular size"
assert size <= len(seq)
seq = seq[:]
# This algorithm comes from Knuth's third pre-facile for volume 4
# It is very dense.
c = [ 0 ] + range(size) + [ len(seq), 0 ]
while 1:
yield map(lambda x: seq[x], c[1:size+1])
j = 1
while c[j]+1 == c[j+1]:
c[j] = j-1
j = j+1
if j > size:
return
c[j] = c[j]+1
def getAllSubsetPermutations(seq, size):
"Generate all permutations of all subsets of the indicated size."
assert size <= len(seq)
for s in getAllSubsets(seq, size):
for c in getAllPermutations(s):
yield c
if __name__ == '__main__':
count = 0
for c in getAllPermutations(range(5)):
count = count + 1
assert count == 5*4*3*2*1
count = 0
for s in getAllSubsets(range(7), 5):
count = count + 1
assert count == (7*6)/(2*1)
count = 0
for sp in getAllSubsetPermutations(range(7), 5):
count = count + 1
assert count == (7*6*5*4*3)
print "All tests passed!"
#!/usr/bin/env python2.2
debug = 0
def p(*s):
"Print stuff if we are in debug mode. Don't otherwise"
if debug:
import sys
sys.stderr.write(reduce(lambda x,y: x+' '+str(y), s, "")[1:] + "\n")
from combo import getAllPermutations
class Edge:
"An edge in a graph"
def __init__(self, v1,v2):
self.v1 = v1
self.v2 = v2
def __str__(self):
return( str(self.v1)+' -- '+str(self.v2))
class Graph:
def __init__(self, vertices, edges):
"""Creates an undirected graph with vertices and edges. All vertex
objects must be hashable (and therefore immutable) objects, but they
don't have to be strings.
Make sure that every vertex in the edge list is also in the vertex
list, otherwise an exception will be thrown.
"""
for e in edges:
assert e.v1 in vertices
assert e.v2 in vertices
self.degrees = {}
self.adjacent = {}
self.e_cache = edges
self.v_cache = vertices
for v in vertices:
self.degrees[v] = 0
self.adjacent[v] = {}
for e in edges:
self.degrees[e.v1] = self.degrees[e.v1] + 1
self.adjacent[e.v1][e.v2] = None
self.degrees[e.v2] = self.degrees[e.v2] + 1
self.adjacent[e.v2][e.v1] = None
def remove_edge(self, v1, v2):
assert self.are_adjacent(v1,v2), \
"Can't remove non-existent edge: " + str(v1) + " -- " + str(v2)
self.degrees[v1] = self.degrees[v1] - 1
self.degrees[v2] = self.degrees[v2] - 1
del self.adjacent[v1][v2]
del self.adjacent[v2][v1]
self.e_cache = []
def remove_vertex(self, vertex):
for v in self.adjacent[vertex]:
del self.adjacent[v][vertex]
del self.adjacent[vertex]
del self.degrees[vertex]
def add_edge(self, v1, v2):
if not self.are_adjacent(v1,v2):
self.e_cache.append(Edge(v1,v2))
self.adjacent[v1][v2] = None
self.adjacent[v2][v1] = None
self.degrees[v1] += 1
self.degrees[v2] += 1
else:
p(v1, "and", v2, "are already adjacent!")
p(self.adjacent)
def are_adjacent(self, v1, v2):
"Return the number of connections between v1 and v2"
assert self.adjacent[v1].has_key(v2) == self.adjacent[v2].has_key(v1)
return self.adjacent[v1].has_key(v2)
def isIsomorphic(self, other):
"""A combinatorial approach to graph isomorphism. This is really slow,
but that's okay because it is only used on graphs of bounded size."""
if len(self.getVertices()) != len(other.getVertices()):
return 0
if len(self.getEdges()) != len(other.getEdges()):
return 0
for ordering in getAllPermutations(self.getVertices().keys()):
mapping = {}
other_vlist = other.getVertices().keys()
for i in range(len(ordering)):
mapping[ordering[i]] = other_vlist[i]
error = 0
for v in self.getVertices():
if self.degrees[v] != other.degrees[mapping[v]]:
error = 1
break
for n in self.adjacent[v]:
if mapping[n] not in other.adjacent[mapping[v]]:
error = 1
break
#If we didn't find anything wrong
if not error:
return 1
return 0
def getEdges(self):
#if not self.e_cache:
if 1:
self.e_cache = []
dealt_with = {}
for fr in self.getVertices():
dealt_with[fr] = None
for to in filter(lambda x: not dealt_with.has_key(x), \
self.adjacent[fr]):
self.e_cache.append(Edge(fr, to))
return self.e_cache
def getVertices(self):
return self.adjacent
def __str__(self):
"""Returns a string with the title of %s and the label %s. Use the %
operator to either further flesh the string out, or just print it out
as-is.
"""
p(self.adjacent)
p(map(str, self.getEdges()))
s = "graph \"%s\" {\n"
for node in self.getVertices():
s = s + "\t" + str(node) + " ;\n"
for edge in self.getEdges():
s = s + "\t" + str(edge) + " ;\n"
s = s + """
label = "%s";
}
"""
return s
def readGraph(file):
"""Read a graph from the passed in open file"""
lines = file.readlines()
vertices = lines[0].split()
edges = []
for edge in lines[1:]:
edge = edge.split()
if len(edge) == 3:
edges.append(Edge(edge[0], edge[2]))
return Graph(vertices, edges)
def printGraph(graph, title):
"""Prints out a graph in a form suitable for input into graphviz stuff"""
print str(graph) % (title, title)
#!/usr/bin/env python2.2
import sys
debug = 0
def p(*s):
"Print stuff if we are in debug mode. Don't otherwise"
if debug:
import sys
sys.stderr.write(reduce(lambda x,y: x+' '+str(y), s, "")[1:] + "\n")
# argument parsing - you must tell us what file to get the reduction from
assert len(sys.argv) > 1 and len(sys.argv[1]) > 0, """
Error - did not supply an argument.
usage:
%s [-P] reductionFileWithoutDotPy < file.graph | neato -Tps > file.ps
""" % (sys.argv[0],)
shouldPrint = 0
if sys.argv[1] == '-P' or (len(sys.argv) == 3 and sys.argv[2] == '-P'):
shouldPrint = 1
if sys.argv[1] == '-P':
del sys.argv[1]
exec "from "+sys.argv[1]+" import reductions, propertyName, representatives"
import reduction
from graphs import readGraph, printGraph
graph = readGraph(sys.stdin)
maxSmallDegree = max(map(lambda x: x.getMaxBlackDegree(), reductions))
for r in reductions:
r.maxdegree = maxSmallDegree
list = filter(lambda x: graph.degrees[x] <= maxSmallDegree, graph.getVertices())
titleString = "Original graph"
count = 0
printGraph(graph, titleString)
while list:
v = list.pop()
if v not in graph.getVertices():
continue
p(v)
for reduction in reductions:
p(reduction.name)
small = reduction.process(v, graph)
if small != None:
for s in small:
if s not in list:
list.append(s)
count = count + 1
if shouldPrint:
printGraph(graph, \
"After applying %s -- %d" % (reduction.name, count))
break
inSet = 0
for rep in representatives:
if rep.isIsomorphic(graph):
#printGraph(rep, "Representative")
inSet = 1
break
if inSet:
sys.stderr.write("It was a " + propertyName + "\n")
else:
sys.stderr.write("It wasn't a " + propertyName + "\n")
#!/usr/bin/env python2.2
from graphs import Graph
from graphs import Edge as E
from reduction import Reduction
# some convenience variables to save typing lots of quotes:
a="a"; b="b"; c="c"; d="d"; e="e"; f="f"; g="g"; h="h"; i="i"; j="j"; k="k";
l="l"; m="m"; n="n"; o="o"; p="p"; q="q"; r="r"; s="s"; t="t"; u="u"; v="v";
w="w"; x="x"; y="y"; z="z";
p_2_1_2 = Reduction(
"p_2(1,2)",
Graph("lrm", [E(l,r), E(l,m), E(m,r)]),
"m",
Graph("lrm",[E(l,m), E(m,r)])
)
s_2_1_2 = Reduction(
"s_2(1,2)",
Graph("labr", [E(l,a), E(a,b), E(b,r)]),
"ab",
Graph("lbr", [E(l,b), E(b,r)])
)
s_2_1_3a = Reduction(
"s_2(1,3)a",
Graph("ltbmar", [E(l,t), E(l,b), E(t,m), E(b,m), E(m,a), E(a,r)]),
"tbma",
Graph("ltbmr", [E(l,t), E(l,b), E(t,m), E(b,m), E(m,r)])
)
s_2_1_3b = Reduction(
"s_2(1,3)b",
Graph("cdlrtb", [E(c,l), E(l,t), E(l,b), E(t,r), E(b,r), E(r,d)]),
"lrtb",
Graph("ctbrd", [E(c,t), E(c,b), E(t,r), E(b,r), E(r,d)])
)
s_2_3_3 = Reduction(
"s_2(3,3)",
Graph("lmrabcd", \
[E(l,a), E(l,b), E(a,m), E(b,m), E(m,c), E(m,d), E(c,r), E(d,r)] ),
"mabcd",
Graph("lrabm", [E(l,a), E(l,b), E(b,m), E(a,m), E(m,r)])
)
pendant = Reduction(
"pendant",
Graph("lr", [E(l,r)]),
"l",
Graph("r", [])
)
singleton = Reduction(
"singleton",
Graph("a", []),
"a",
Graph([], [])
)
propertyName = "Outerplanar Graph"
reductions = [ p_2_1_2 , s_2_1_2, s_2_1_3a, s_2_1_3b, s_2_3_3, pendant, \
singleton ]
representatives = [ Graph([],[]) ]
#!/usr/bin/env python2.2
from graphs import Graph
from graphs import Edge as E
from reduction import Reduction
# some convenience variables to save typing lots of quotes:
a="a"; b="b"; c="c"; d="d"; e="e"; f="f"; g="g"; h="h"; i="i"; j="j"; k="k";
l="l"; m="m"; n="n"; o="o"; p="p"; q="q"; r="r"; s="s"; t="t"; u="u"; v="v";
w="w"; x="x"; y="y"; z="z";
isolated = Reduction(
"isolated",
Graph(["point"], []),
["point"],
Graph([],[])
)
pendant = Reduction(
"pendant",
Graph(["body", "leaf"], [E("body", "leaf")]),
["leaf"],
Graph(["body"], [])
)
series = Reduction(
"series",
Graph("lmr", [E(l, m), E(m, r)]),
"m",
Graph("lr", [E(l, r)])
)
triangle = Reduction(
"triangle",
Graph("tmlr", [E(t,m), E(m,l), E(l,r), E(r,m)]),
"m",
Graph("tlr", [E(t,l), E(t,r), E(l,r)])
)
buddy = Reduction(
"buddy",
Graph("tmlrb", [E(t,m), E(m,l), E(m,r), E(t,b), E(l,b), E(r,b)]),
"mb",
Graph("tlr", [E(t,l), E(t,r), E(l,r)])
)
cube = Reduction(
"cube",
Graph("tlrabcd",
[E(a,t),E(t,b),E(b,r),E(r,c),E(c,l),E(l,a),E(a,d),E(b,d),E(c,d)]
),
"abcd",
Graph("tlr", [E(t,l), E(t,r), E(l,r)])
)
propertyName = "Partial 3-Tree"
reductions = [isolated, pendant, series, triangle, buddy, cube]
representatives = [ Graph([],[]) ]
#!/usr/bin/env python2.2
from graphs import Graph
from graphs import Edge as E
from reduction import Reduction
# some convenience variables to save typing lots of quotes:
a="a"; b="b"; c="c"; d="d"; e="e"; f="f"; g="g"; h="h"; i="i"; j="j"; k="k";
l="l"; m="m"; n="n"; o="o"; p="p"; q="q"; r="r"; s="s"; t="t"; u="u"; v="v";
w="w"; x="x"; y="y"; z="z";
r2 = Reduction(
"r_2",
Graph(["body", "leaf"], [E("body", "leaf")]),
["leaf"],
Graph(["body"], [])
)
s2 = Reduction(
"s_2",
Graph("lmr", [E(l, m), E(m, r)]),
"m",
Graph("lr", [E(l, r)])
)
slash32 = Reduction(
"//_3,2",
Graph("tbrm", [E(t,b), E(t,r), E(t,m), E(b,m), E(r,m)]),
"m",
Graph("tbrm", [E(t,r), E(t,m), E(b,m), E(r,m)])
)
s33 = Reduction(
"s_3,3",
Graph("lrmnb", [E(l,m), E(m,b), E(m,n), E(b,n), E(n,r)]),
"mn",
Graph("lbrm", [E(l,r), E(l,m), E(b,m), E(r,m)])
)
s33alt = Reduction(
"s_3,3 (alternate)",
Graph("lrbmnt",
[E(l,t), E(t,r), E(l,m), E(r,n), E(m,t), E(n,t), E(m,b), E(n,b)]
),
"mnt",
Graph("lbrm", [E(l,r), E(l,m), E(b,m), E(r,m)])
)
s3 = Reduction(
"s_3",
Graph("lrbmnot",
[ E(l,t), E(t,r), E(r,n), E(n,b), E(b,m), E(m,l),
E(m,o), E(n,o), E(o,t) ]
),
"mnot",
Graph("lbrm", [E(l,r), E(l,m), E(b,m), E(r,m)])
)
s3alt = Reduction(
"s_3 (alternate)",
Graph("lrbmnot",
[ E(l,t), E(t,r), E(r,n), E(n,b), E(b,m), E(m,l),
E(m,o), E(n,o), E(o,t), E(o,b) ]
),
"mnot",
Graph("lbrm", [E(l,r), E(l,m), E(b,m), E(r,m)])
)
propertyName = "Planar 3-Tree"
reductionRules = [r2, s2, slash2, slash32, s33, s33alt, s3, s3alt ]
representatives = [ Graph([],[]), Graph([a],[]) ]
#!/usr/bin/env python2.2
from __future__ import generators
from combo import getAllSubsetPermutations
debug = 0
def p(*s):
"Print stuff if we are in debug mode. Don't otherwise"
if debug:
import sys
sys.stderr.write(reduce(lambda x,y: x+' '+str(y), s, "")[1:] + "\n")
from graphs import Graph, Edge
verticesToMatches = {}
class Reduction:
"""A reduction is two graphs, the second one of which is smaller, which has
some vertices whose degree is fully specified, and other vertices whose
degree is only given a lower bound.
"""
def __init__(self, name, fromgraph, restrictedNodes, tograph):
"""Initialize a new reduction. Give the nodes in fromgraph and tograph
the same names. In particular, all names in tograph have to be in
fromgraph. Also, You need at least some restricted nodes.
The big idea here is to break the graph up into contiguous black
regions, rather than single nodes.
"""
self.name = name
self.fr = fromgraph
self.restrict = restrictedNodes
self.to = tograph
self.matches = {}
self.maxBlackDegree = -1
self.maxdegree = self.getMaxBlackDegree()
self.vToM = {}
verticesToMatches[self] = self.vToM
# Find the contiguous black node regions
self.regions = []
for node in self.restrict:
if node in reduce(lambda x,y: x+list(y), self.regions, []):
continue
region = [ node ]
i = 0
while i < len(region):
if region[i] not in self.restrict:
i = i+1
continue
newNodes = filter(lambda x: x not in region, \
self.fr.adjacent[region[i]])
if newNodes:
region = region + newNodes
i = i+1
self.regions.append(tuple(region))
# Make sure of some properties
assert self.restrict, "You need to have some black nodes"
for v in self.restrict:
assert v in self.fr.getVertices(), "Bad restricted node: "+str(v)
for v in self.to.getVertices():
assert v in self.fr.getVertices(), "Bad mapping :"+str(v)
def getMaxBlackDegree(self):
"""Return the maximum degree of a black vertex in this rule"""
if self.maxBlackDegree == -1:
self.maxBlackDegree = \
max(map(lambda x: self.fr.degrees[x], self.restrict) + [0])
return self.maxBlackDegree
def getRegionDepth(self, region):
"""What is the maximum depth of a BFS required to search the entire
region in question?
"""
queue = [(region[0], 0)]
bucket = []
max = 0
while queue:
v, d = queue[0]
del queue[0]
bucket.append(v)
if d > max:
max = d
if self.fr.degrees[v] <= self.getMaxBlackDegree():
for n in filter(lambda x: x not in bucket, \
self.fr.adjacent[v].keys()):
queue.append((n, d+1))
return max
def whiteBorderedSearch(self, start, graph, region):
"""Do a breadth first search (that does not cross any white vertices)
down depth levels.
"""
depth = self.getRegionDepth(region)
bucket=[]
queue = [(start, 0)]
while queue:
v, d = queue[0]
del queue[0]
bucket.append(v)
p("Reading", v)
if d < depth and graph.degrees[v] <= self.getMaxBlackDegree():
for n in filter(lambda x: x not in bucket, \
graph.adjacent[v].keys()):
queue.append((n, d+1))
return bucket
def getAllPossibleMatches(self, start, graph, region):
"""Get all possible matches. Note that this number is bounded, and so
can be thrown away, despite its potential hugeosity.
"""
p(start, graph)
nodes = self.whiteBorderedSearch(start, graph, region)
if len(nodes) < len(region):
return
else:
mapping = {}
for n_permute in getAllSubsetPermutations(nodes, len(region)):
for i in range(len(region)):
mapping[region[i]] = n_permute[i]
yield mapping
def verifyMatch(self, match, region, graph):
"Verify that a particular match does not violate any properties"
region = list(region)
for v in match.keys():
# Make sure it has the right number of connections
if v in self.restrict:
if self.fr.degrees[v] != graph.degrees[match[v]]:
return 0
else:
if self.fr.degrees[v] > graph.degrees[match[v]]:
return 0
# Make sure the connections are to the right place
for n in filter(lambda x: x in region, self.fr.adjacent[v].keys()):
if not graph.are_adjacent(match[v], match[n]):
return 0
return 1
def findIndex(self, region):
"Get the canonical ordering for the external nodes of a region"
index = []
for r in self.regions:
if r == tuple(region):
continue
index.extend(filter(lambda x: x in region and x not in index, r))
index.sort()
return tuple(index)
def addMatch(self, match, region):
"""Add a match into the database, combine it with any matches it can be
combined with, and if it can be applied to the graph at hand, then
return the appropriate mapping"""
region_l = list(region)
index = self.findIndex(region)
m_index = tuple([ match[i] for i in index ])
external_node_map = (tuple(index), tuple(m_index))
if self.matches.has_key(external_node_map):
self.matches[external_node_map].append(match)
else:
self.matches[external_node_map] = [ match ]
for n in match.keys():
if not self.vToM.has_key(n):
self.vToM[n] = []
self.vToM[n].append(match)
for ext_hc, ext_dyn in self.matches.keys():
ext_hc_l = list(ext_hc)
ext_dyn_l = list(ext_dyn)
all_ok = 1
for i in range(len(index)):
if ext_dyn[ext_hc_l.index(range[i])] != m_index[i]:
all_ok = 0
break
if not all_ok:
continue
for partial_match in self.matches[(ext_hc, ext_dyn)]:
intersection = filter( \
lambda x: (x in match) and (x not in ext_dyn_l), \
partial_match.values())
if intersection:
continue
partial_match = partial_match.copy()
for i in match.keys():
partial_match[i] = match[i]
remaining = filter(lambda x: x not in partial_match.keys(),\
self.fr.getVertices())
if not remaining:
return partial_match
else:
remaining = tuple(remaining)
if self.matches.has_key(remaining):
self.matches[remaining].append(partial_match)
else:
self.matches[remaining] = [ partial_match ]
# If we made it to here, nothing matched
return None
def apply(self, mapping, graph):
"Apply this reduction to the graph in a specific place"
for e in self.fr.getEdges():
graph.remove_edge(mapping[e.v1], mapping[e.v2])
# For every black edge that doesn't appear in the final graph
for v in filter(lambda x: x not in self.to.getVertices(), \
self.restrict):
p("Removing", mapping[v])
graph.remove_vertex(mapping[v])
for reduction in verticesToMatches.keys():
if reduction.vToM.has_key(mapping[v]):
for m in reduction.vToM[mapping[v]]:
del reduction.matches[m]
del reduction.vToM[mapping[v]]
for e in self.to.getEdges():
p("Adding an edge from", mapping[e.v1], "to", mapping[e.v2])
graph.add_edge(mapping[e.v1], mapping[e.v2])
return filter(lambda x: graph.degrees[x] <= self.maxdegree, \
map(mapping.__getitem__, self.to.getVertices()))
def process(self, v, graph):
"Take one 'small vertex' from graph and try and place it in each region"
for region in self.regions:
# For every possible match (exponential in the size of region)
for match in self.getAllPossibleMatches(v, graph, region):
if self.verifyMatch(match, region, graph):
mapping = self.addMatch(match, region)
if mapping:
return self.apply(mapping, graph)
#!/usr/bin/env python
from graphs import Graph, Edge
from reduction import Reduction
trimLeaf = Reduction("Leaf Trimmer",
Graph(["tree","leaf"], [Edge("tree","leaf")]),
["leaf"],
Graph(["tree"], []))
propertyName = "Tree"
reductions = [ trimLeaf ]
singlePoint = Graph(["single point"], [])
representatives = [ singlePoint ]
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.70)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -show_section_numbers -html_version 4.0 -numbered_footnotes -no_white -split 0 drp.tex
The translation was initiated by Peter Boothe on 2004-05-25