Improved some class and function documentation
Cleaned up the code. Moved most code from ClusterSolver3D to ClusterSolver Added option to ClusterMethods for hand-coded mathing routines Added CheckAR Method to fix detection of overconstrained situations
This commit is contained in:
parent
fac2609def
commit
673bf117bd
|
@ -25,29 +25,43 @@ from method import OrMethod
|
|||
# -----------------------------------------------------------
|
||||
|
||||
class ClusterMethod(MultiMethod):
|
||||
"""A method that determines a single output cluster from a ser of input clusters.
|
||||
Subclasses should ensure that the output cluster satisfies all the constraints
|
||||
in the input clusters.
|
||||
"""A method that determines a single output cluster from a set of input clusters.
|
||||
|
||||
Subclasses should provide a static class variable 'patterngraph', which is a graph,
|
||||
describing the pattern that is matched by the solver and used to instantiate the Method.
|
||||
(see function pattern2graph)
|
||||
|
||||
Alternatively, subclasses may implement the static class method 'handcoded_match', which should
|
||||
return a list of matches (given a new cluster and all connected clusters).
|
||||
|
||||
Subclasses should implement function _multi_execute such that the output cluster satisfies all
|
||||
the constraints in the input clusters.
|
||||
|
||||
instance vars:
|
||||
overconstrained - True iff the merge locally overconstrained
|
||||
consistent - True iff the merge is generically consistent
|
||||
(these variables are automatically set by the solver for debugging purposes )
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.consistent = None
|
||||
self.overconstrained = None
|
||||
self.consistent = None
|
||||
MultiMethod.__init__(self)
|
||||
|
||||
def prototype_constraints(self):
|
||||
"""Return a list of SelectionConstraint"""
|
||||
return []
|
||||
|
||||
def status_str(self):
|
||||
s = ""
|
||||
if self.consistent == True:
|
||||
s += "consistent "
|
||||
elif self.consistent == False:
|
||||
elif self.consistent == False:
|
||||
s += "inconsistent "
|
||||
if self.overconstrained == True:
|
||||
s += "overconstrained"
|
||||
elif self.overconstrained == False:
|
||||
s += "wellconstrained"
|
||||
s += "well-constrained"
|
||||
return s
|
||||
|
||||
def input_clusters(self):
|
||||
|
@ -155,7 +169,15 @@ class SelectionMethod(MultiMethod):
|
|||
# --------------------------------------
|
||||
|
||||
def pattern2graph(pattern):
|
||||
"""convert pattern to pattern graph"""
|
||||
"""Convert a pattern to a pattern graph, used before graph based matching.
|
||||
The pattern is a list of tuples (pattype, patname, patvars), where
|
||||
pattype is one of "point", "distance", "rigid", "balloon" or "hedgehog"
|
||||
patname is a string, which is the name of a variable which will be associated with a cluster
|
||||
patvars is a list of strings, where each string is a variable to be associated with a point variable
|
||||
If pattype is point or distance, then the length of the cluster is fixed to 1 or 2 points.
|
||||
Otherwise, clusters with any number of variables are matched.
|
||||
If pattype is hedgehog, then the first variable in patvars is the center variable.
|
||||
"""
|
||||
pgraph = Graph()
|
||||
pgraph.add_vertex("point")
|
||||
pgraph.add_vertex("distance")
|
||||
|
@ -174,7 +196,7 @@ def pattern2graph(pattern):
|
|||
return pgraph
|
||||
|
||||
def reference2graph(nlet):
|
||||
"""convert nlet to reference graph"""
|
||||
"""Convert a set of (supposedly connected) clusters to a reference graph, used before graph-based matching."""
|
||||
rgraph = Graph()
|
||||
rgraph.add_vertex("point")
|
||||
rgraph.add_vertex("distance")
|
||||
|
@ -200,6 +222,7 @@ def reference2graph(nlet):
|
|||
return rgraph
|
||||
|
||||
def rootname(cluster):
|
||||
"""returns the name of the root variable associated with the name of a cluster variable"""
|
||||
return "root#"+str(id(cluster))
|
||||
|
||||
|
||||
|
@ -226,13 +249,14 @@ class ClusterSolver(Notifier):
|
|||
|
||||
# ------- PUBLIC METHODS --------
|
||||
|
||||
def __init__(self, dimension, methodclasses):
|
||||
"""Create a new empty solver"""
|
||||
def __init__(self, methodclasses):
|
||||
"""Create a new solver, using the given subclasses of ClusterMethod."""
|
||||
# init superclasses
|
||||
Notifier.__init__(self)
|
||||
# store arguments
|
||||
self.dimension = dimension
|
||||
self.methodclasses = methodclasses
|
||||
self.pattern_methods = filter(lambda m: hasattr(m,"patterngraph"),self.methodclasses)
|
||||
self.handcoded_methods = filter(lambda m: hasattr(m,"handcoded_match"),self.methodclasses)
|
||||
# init instance vars
|
||||
self._graph = Graph()
|
||||
self._graph.add_vertex("_root")
|
||||
|
@ -253,8 +277,69 @@ class ClusterSolver(Notifier):
|
|||
# store map of selection_constraints to SelectionMethod (or None)
|
||||
self._selection_method = {}
|
||||
# store root cluster (will be assigned when first cluster added)
|
||||
self.rootcluster = None
|
||||
self._rootcluster = None
|
||||
|
||||
# ------- methods for setting up constraint problems ------------
|
||||
|
||||
def add(self, cluster):
|
||||
"""Add a cluster"""
|
||||
diag_print("add_cluster "+str(cluster), "clsolver")
|
||||
self._add_cluster(cluster)
|
||||
self._process_new()
|
||||
|
||||
def remove(self, cluster):
|
||||
"""Remove a cluster.
|
||||
All dependend objects are also removed.
|
||||
"""
|
||||
self._remove(cluster)
|
||||
self._process_new()
|
||||
|
||||
def set(self, cluster, configurations):
|
||||
"""Associate a list of configurations with a cluster"""
|
||||
self._mg.set(cluster, configurations)
|
||||
|
||||
def get(self, cluster):
|
||||
"""Return a set of configurations associated with a cluster"""
|
||||
return self._mg.get(cluster)
|
||||
|
||||
def set_root(self, cluster):
|
||||
"""Set root cluster, used for positionig and orienting the solutions"""
|
||||
diag_print("set root "+str(self._rootcluster), "clsolver")
|
||||
if self._rootcluster != None:
|
||||
oldrootvar = rootname(self._rootcluster)
|
||||
self._mg.set(oldrootvar, False)
|
||||
newrootvar = rootname(cluster)
|
||||
self._mg.set(newrootvar, True)
|
||||
self._rootcluster = cluster
|
||||
|
||||
def get_root(self):
|
||||
"""returns current root cluster or None"""
|
||||
return self._rootcluster
|
||||
|
||||
def set_prototype_selection(self, enabled):
|
||||
"""Enable or disable prototype-based solution selection"""
|
||||
self._mg.set(self._prototype_selection_var, enabled)
|
||||
|
||||
def add_selection_constraint(self, con):
|
||||
"""Add a SelectionConstraint to filter solutions"""
|
||||
if con not in self._selection_method:
|
||||
selector = self._find_selection_method(con)
|
||||
if selector != None:
|
||||
selector.add_constraint(con)
|
||||
self._selection_method[con] = selector
|
||||
self._mg.execute(selector)
|
||||
self._selection_method[con] = None
|
||||
|
||||
def rem_selection_constraint(self, con):
|
||||
"""Remove a SelectionConstraint"""
|
||||
if con in self._selection_method:
|
||||
selector = self._selection_method[con]
|
||||
if selector != None:
|
||||
selector.rem_constraint(con)
|
||||
self._mg.execute(selector)
|
||||
del self._selection_method[con]
|
||||
|
||||
# ------- methods for inspecting the state of the solver ------------
|
||||
|
||||
def variables(self):
|
||||
"""get list of variables"""
|
||||
|
@ -285,60 +370,13 @@ class ClusterSolver(Notifier):
|
|||
return self._graph.outgoing_vertices("_methods")
|
||||
|
||||
def top_level(self):
|
||||
"""get top-level objects"""
|
||||
"""get top-level clusters"""
|
||||
return self._graph.outgoing_vertices("_toplevel")
|
||||
|
||||
def is_top_level(self, object):
|
||||
"""Returns True iff given cluster is a top-level cluster"""
|
||||
return self._graph.has_edge("_toplevel",object)
|
||||
|
||||
def add(self, cluster):
|
||||
"""Add a cluster.
|
||||
|
||||
arguments:
|
||||
cluster: A Rigid
|
||||
"""
|
||||
diag_print("add_cluster "+str(cluster), "clsolver")
|
||||
self._add_cluster(cluster)
|
||||
self._process_new()
|
||||
|
||||
def remove(self, cluster):
|
||||
"""Remove a cluster.
|
||||
All dependend objects are also removed.
|
||||
"""
|
||||
self._remove(cluster)
|
||||
self._process_new()
|
||||
|
||||
def set(self, cluster, configurations):
|
||||
"""Associate a list of configurations with a cluster"""
|
||||
self._mg.set(cluster, configurations)
|
||||
|
||||
def get(self, cluster):
|
||||
"""Return a set of configurations associated with a cluster"""
|
||||
return self._mg.get(cluster)
|
||||
|
||||
def set_root(self, cluster):
|
||||
"""Set root cluster, used for positionig and orienting the solutions"""
|
||||
diag_print("set root "+str(self.rootcluster), "clsolver")
|
||||
if self.rootcluster != None:
|
||||
oldrootvar = rootname(self.rootcluster)
|
||||
self._mg.set(oldrootvar, False)
|
||||
newrootvar = rootname(cluster)
|
||||
self._mg.set(newrootvar, True)
|
||||
self.rootcluster = cluster
|
||||
|
||||
def get_root(self):
|
||||
"""returns current root cluster or None"""
|
||||
return self.rootcluster
|
||||
|
||||
##def set_root(self, rigid):
|
||||
## """Make given rigid cluster the root cluster
|
||||
##
|
||||
## arguments:
|
||||
## cluster: A Rigid
|
||||
## """
|
||||
## self._graph.rem_vertex("_root")
|
||||
## self._graph.add_edge("_root", rigid)
|
||||
|
||||
def find_dependend(self, object):
|
||||
"""Return a list of objects that depend on given object directly."""
|
||||
l = self._graph.outgoing_vertices(object)
|
||||
|
@ -351,26 +389,6 @@ class ClusterSolver(Notifier):
|
|||
|
||||
def contains(self, obj):
|
||||
return self._graph.has_vertex(obj)
|
||||
|
||||
def set_prototype_selection(self, enabled):
|
||||
self._mg.set(self._prototype_selection_var, enabled)
|
||||
|
||||
def add_selection_constraint(self, con):
|
||||
if con not in self._selection_method:
|
||||
selector = self._find_selection_method(con)
|
||||
if selector != None:
|
||||
selector.add_constraint(con)
|
||||
self._selection_method[con] = selector
|
||||
self._mg.execute(selector)
|
||||
self._selection_method[con] = None
|
||||
|
||||
def rem_selection_constraint(self, con):
|
||||
if con in self._selection_method:
|
||||
selector = self._selection_method[con]
|
||||
if selector != None:
|
||||
selector.rem_constraint(con)
|
||||
self._mg.execute(selector)
|
||||
del self._selection_method[con]
|
||||
|
||||
# ------------ INTERNALLY USED METHODS --------
|
||||
|
||||
|
@ -508,41 +526,6 @@ class ClusterSolver(Notifier):
|
|||
self.send_notify(("add", newballoon))
|
||||
#end def _add_balloon
|
||||
|
||||
def _add_merge(self, merge):
|
||||
# structural check that method has one output
|
||||
if len(merge.outputs()) != 1:
|
||||
raise StandardError, "merge number of outputs != 1"
|
||||
output = merge.outputs()[0]
|
||||
# remove any derives from clusters to be merged
|
||||
#for cluster in merge.inputs():
|
||||
# outgoing = self.find_dependend(cluster)
|
||||
# derives = filter(lambda x: isinstance(x, Derive), outgoing)
|
||||
# for d in derives:
|
||||
# self._remove(d)
|
||||
# consistent merge?
|
||||
consistent = True
|
||||
for i1 in range(0, len(merge.inputs())):
|
||||
for i2 in range(i1+1, len(merge.inputs())):
|
||||
c1 = merge.inputs()[i1]
|
||||
c2 = merge.inputs()[i2]
|
||||
consistent = consistent and self._is_consistent_pair(c1, c2)
|
||||
merge.consistent = consistent
|
||||
# overconstrained cluster?
|
||||
overconstrained = not consistent
|
||||
for cluster in merge.inputs():
|
||||
overconstrained = overconstrained and cluster.overconstrained
|
||||
output.overconstrained = overconstrained
|
||||
# add to graph
|
||||
self._add_cluster(output)
|
||||
self._add_method(merge)
|
||||
# remove inputs from toplevel
|
||||
for cluster in merge.inputs():
|
||||
self._rem_top_level(cluster)
|
||||
# add selection methods
|
||||
output2 = self._add_prototype_selector(merge)
|
||||
output3 = self._add_solution_selector(output2)
|
||||
return output3
|
||||
|
||||
def _add_method(self, method):
|
||||
diag_print("new "+str(method),"clsolver")
|
||||
self._add_to_group("_methods", method)
|
||||
|
@ -649,40 +632,55 @@ class ClusterSolver(Notifier):
|
|||
#end def
|
||||
|
||||
def _search(self, newcluster):
|
||||
diag_print("search from: "+str(newcluster),"clsolver")
|
||||
# find all toplevel clusters connected to newcluster via one or more variables
|
||||
diag_print("search from:"+str(newcluster),"clsolver3D")
|
||||
# first find all toplevel clusters connected to newcluster
|
||||
# via one or more variables
|
||||
connected = set()
|
||||
for var in newcluster.vars:
|
||||
dependend = self.find_dependend(var)
|
||||
dependend = filter(lambda x: self.is_top_level(x), dependend)
|
||||
connected.update(dependend)
|
||||
diag_print("search: connected clusters="+str(connected),"clsolver")
|
||||
# try applying methods
|
||||
if self._try_method(connected):
|
||||
diag_print("search: connected clusters="+str(connected),"clsolver3D")
|
||||
# first try incremental mathing
|
||||
for methodclass in self.handcoded_methods:
|
||||
diag_print("trying incremental matching for "+str(methodclass), "clsolver3D")
|
||||
matches = methodclass.handcoded_match(self, newcluster,connected)
|
||||
if self._try_matches(methodclass, matches):
|
||||
return True
|
||||
# if incremental matching failed, try full pattern matching
|
||||
if self._try_methods(connected):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _try_method(self, nlet):
|
||||
|
||||
def _try_methods(self, nlet):
|
||||
"""finds a possible rewrite rule applications on given set of clusters, applies it
|
||||
and returns True iff successfull
|
||||
"""
|
||||
refgraph = reference2graph(nlet)
|
||||
for methodclass in self.methodclasses:
|
||||
for methodclass in self.pattern_methods:
|
||||
diag_print("trying generic pattern matching for "+str(methodclass), "clsolver3D")
|
||||
matches = gmatch(methodclass.patterngraph, refgraph)
|
||||
if len(matches) > 0:
|
||||
diag_print("number of matches = "+str(len(matches)), "clsolver")
|
||||
for s in matches:
|
||||
# diag_print("try match: "+str(s),"clsolver")
|
||||
method = apply(methodclass, [s])
|
||||
succes = self._add_method_complete(method)
|
||||
if succes:
|
||||
#raw_input()
|
||||
#print "press key"
|
||||
return True
|
||||
if self._try_matches(methodclass,matches):
|
||||
return True
|
||||
# end for match
|
||||
# end for method
|
||||
return False
|
||||
|
||||
|
||||
def _try_matches(self, methodclass, matches):
|
||||
# print "method="+str(methodclass),"number of matches = "+str(len(matches))
|
||||
for s in matches:
|
||||
diag_print("try match: "+str(s),"clsolver3D")
|
||||
method = apply(methodclass, [s])
|
||||
succes = self._add_method_complete(method)
|
||||
if succes:
|
||||
#raw_input()
|
||||
#print "press key"
|
||||
return True
|
||||
else: # WARING: fast bailout, may be incoplete!
|
||||
return False
|
||||
# end for match
|
||||
return False
|
||||
|
||||
def _add_method_complete(self, merge):
|
||||
# diag_print("add_method_complete "+str(merge), "clsolver")
|
||||
|
@ -983,173 +981,6 @@ class ClusterSolver(Notifier):
|
|||
s += str(x) + "\n"
|
||||
return s
|
||||
|
||||
# ---------- older unused methods, kept for possible future use ---------
|
||||
|
||||
##def _known_distance(self,a,b):
|
||||
## """returns Distance or Rigid that contains a and b"""
|
||||
## # get objects dependend on a and b
|
||||
## dep_a = self._graph.outgoing_vertices(a)
|
||||
## dep_b = self._graph.outgoing_vertices(b)
|
||||
## dependend = []
|
||||
## for obj in dep_a:
|
||||
## if obj in dep_b:
|
||||
## dependend.append(obj)
|
||||
## # find a Distance
|
||||
## # distances = filter(lambda x: isinstance(x,Distance), dependend)
|
||||
## # if len(distances) > 0: return distances[0]
|
||||
## # or find a Rigid
|
||||
## clusters = filter(lambda x: isinstance(x,Rigid), dependend)
|
||||
## clusters = filter(lambda x: self.is_top_level(x), clusters)
|
||||
## if len(clusters) > 0: return clusters[0]
|
||||
## # or return None
|
||||
## return None
|
||||
##
|
||||
|
||||
## def _known_angle(self,a,b,c):
|
||||
## """returns Balloon, Rigid or Hedgehog that contains angle(a, b, c)"""
|
||||
## if a==b or a==c or b==c:
|
||||
## raise StandardError, "all vars in angle must be different"
|
||||
## # get objects dependend on a, b and c
|
||||
## dep_a = self._graph.outgoing_vertices(a)
|
||||
## dep_b = self._graph.outgoing_vertices(b)
|
||||
## dep_c = self._graph.outgoing_vertices(c)
|
||||
## dependend = []
|
||||
## for obj in dep_a:
|
||||
## if obj in dep_b and obj in dep_c:
|
||||
## dependend.append(obj)
|
||||
## # find a hedgehog
|
||||
## hogs = filter(lambda x: isinstance(x,Hedgehog), dependend)
|
||||
## hogs = filter(lambda hog: hog.cvar == b, hogs)
|
||||
## hogs = filter(lambda x: self.is_top_level(x), hogs)
|
||||
## if len(hogs) == 1: return hogs[0]
|
||||
## if len(hogs) > 1: raise "error: angle in more than one hedgehogs"
|
||||
## # or find a cluster
|
||||
## clusters = filter(lambda x: isinstance(x,Rigid), dependend)
|
||||
## clusters = filter(lambda x: self.is_top_level(x), clusters)
|
||||
## if len(clusters) == 1: return clusters[0]
|
||||
## if len(clusters) > 1: raise "error: angle in more than one Rigids"
|
||||
## # or find a balloon
|
||||
## balloons = filter(lambda x: isinstance(x,Balloon), dependend)
|
||||
## balloons = filter(lambda x: self.is_top_level(x), balloons)
|
||||
## if len(balloons) == 1: return balloons[0]
|
||||
## if len(balloons) > 1: raise "error: angle in more than one Balloons"
|
||||
## # or return None
|
||||
## return None
|
||||
|
||||
##def _is_source(self, object, constraint):
|
||||
## if not self._contains_constraint(object, constraint):
|
||||
## return False
|
||||
## elif self._is_atomic(object):
|
||||
## return True
|
||||
## else:
|
||||
## method = self._determining_method(object)
|
||||
## inputs = method.inputs()
|
||||
## for object in inputs:
|
||||
## if self._contains_constraint(object, constraint):
|
||||
## return False
|
||||
## return True
|
||||
|
||||
##def _distance_sources(self, distance):
|
||||
## # find coincident clusters
|
||||
## dep_a = self._graph.outgoing_vertices(distance.vars[0])
|
||||
## dep_b = self._graph.outgoing_vertices(distance.vars[1])
|
||||
## dependend = []
|
||||
## for obj in dep_a:
|
||||
## if obj in dep_b:
|
||||
## dependend.append(obj)
|
||||
## candidates = filter(lambda x: self._contains_distance(x, distance), dependend)
|
||||
## # determine sources, i.e. clusters created from clusters that do not contain the distance
|
||||
## sources = set()
|
||||
## for c1 in candidates:
|
||||
## methods = filter(lambda v: isinstance(v, Method), self._graph.ingoing_vertices(c1))
|
||||
## if len(methods) == 0:
|
||||
## sources.add(c1)
|
||||
## elif len(methods) == 1:
|
||||
## method = methods[0]
|
||||
## newsource = True
|
||||
## for c2 in method.inputs():
|
||||
## if self._contains_distance(c2, distance):
|
||||
## newsource = False
|
||||
## break
|
||||
## if newsource:
|
||||
## sources.add(c1)
|
||||
## else:
|
||||
## raise "cluster determined by more than one method"
|
||||
## diag_print("sources for "+str(distance), "clsolver")
|
||||
## for source in sources:
|
||||
## diag_print(str(source), "clsolver")
|
||||
## # filter sources for dependencies
|
||||
## #unfiltered = set(sources)
|
||||
## #for s1 in unfiltered:
|
||||
## # if s1 not in sources: continue
|
||||
## # descendants = self._find_descendants(s1)
|
||||
## # for s2 in unfiltered:
|
||||
## # if s2 not in sources: continue
|
||||
## # if s2 in descendants:
|
||||
## # sources.remove(s2)
|
||||
## return sources
|
||||
|
||||
##def _angle_sources(self, angle):
|
||||
## # find coincident objects
|
||||
## dep_a = self._graph.outgoing_vertices(angle.vars[0])
|
||||
## dep_b = self._graph.outgoing_vertices(angle.vars[1])
|
||||
## dep_c = self._graph.outgoing_vertices(angle.vars[2])
|
||||
## dependend = []
|
||||
## for obj in dep_a:
|
||||
## if obj in dep_b and obj in dep_c:
|
||||
## dependend.append(obj)
|
||||
## candidates = filter(lambda x: self._contains_angle(x, angle), dependend)
|
||||
## # determine sources, i.e. clusters created from clusters that do not contain the angle
|
||||
## sources = set()
|
||||
## for c1 in candidates:
|
||||
## methods = filter(lambda v: isinstance(v, Method), self._graph.ingoing_vertices(c1))
|
||||
## if len(methods) == 0:
|
||||
## sources.add(c1)
|
||||
## elif len(methods) == 1:
|
||||
## method = methods[0]
|
||||
## newsource = True
|
||||
## for c2 in method.inputs():
|
||||
## if self._contains_angle(c2, angle):
|
||||
## newsource = False
|
||||
## break
|
||||
## if newsource:
|
||||
## sources.add(c1)
|
||||
## else:
|
||||
## raise "cluster determined by more than one method"
|
||||
## diag_print("sources for "+str(angle), "clsolver")
|
||||
## for source in sources:
|
||||
## diag_print(str(source), "clsolver")
|
||||
## return sources
|
||||
|
||||
##def _roots(self,object):
|
||||
## front = [object]
|
||||
## result = set()
|
||||
## done = set()
|
||||
## while len(front) > 0:
|
||||
## x = front.pop()
|
||||
## if x not in done:
|
||||
## done.add(x)
|
||||
## methods = filter(lambda v: isinstance(v, Method), self._graph.ingoing_vertices(x))
|
||||
## if len(methods) == 0:
|
||||
## result.add(x)
|
||||
## elif len(methods) == 1:
|
||||
## front += methods[0].inputs()
|
||||
## else:
|
||||
## raise "cluster determined by more than one method"
|
||||
## return result
|
||||
|
||||
|
||||
##def _all_sources_constraint_in_cluster(self, constraint, cluster):
|
||||
## if not self._contains_constraint(cluster, constraint):
|
||||
## return set()
|
||||
## elif self._is_atomic(cluster):
|
||||
## return set([cluster])
|
||||
## else:
|
||||
## method = self._determining_method(cluster)
|
||||
## sources = set()
|
||||
## for inp in method.input_clusters():
|
||||
## sources.update(self._all_sources_constraint_in_cluster(constraint, inp))
|
||||
## return sources
|
||||
|
||||
|
||||
# class ClusterSolver
|
||||
|
||||
|
|
|
@ -14,16 +14,73 @@ class ClusterSolver3D(ClusterSolver):
|
|||
|
||||
def __init__(self):
|
||||
"""Instantiate a ClusterSolver3D"""
|
||||
ClusterSolver.__init__(self, 3, [MergePR, MergeDR, MergeRR, MergeSR, DeriveTTD, DeriveDDD, DeriveADD, DeriveDAD, DeriveAA])
|
||||
ClusterSolver.__init__(self, [CheckAR, MergePR, MergeDR, MergeRR, MergeSR, DeriveTTD, DeriveDDD, DeriveADD, DeriveDAD, DeriveAA])
|
||||
|
||||
|
||||
# ----------------------------------------------
|
||||
# ---------- Methods for 3D solving -------------
|
||||
# ----------------------------------------------
|
||||
|
||||
# Merge<X> methods take root-cluster in considerations
|
||||
# Merge<X> methods take root cluster in considerations
|
||||
# Derive<X> methods do not take root cluster in consideration
|
||||
|
||||
class CheckAR(ClusterMethod):
|
||||
"""Represents the overconstrained merging a hedgehog and a rigid that completely overlaps it."""
|
||||
def __init__(self, map):
|
||||
# get input clusters
|
||||
self.hog = map["$h"]
|
||||
self.rigid = map["$r"]
|
||||
self.sharedx = self.hog.xvars.intersection(self.rigid.vars)
|
||||
# create ouptut cluster
|
||||
outvars = set(self.rigid.vars)
|
||||
self.out = Rigid(outvars)
|
||||
# set method properties
|
||||
self._inputs = [self.hog, self.rigid]
|
||||
self._outputs = [self.out]
|
||||
ClusterMethod.__init__(self)
|
||||
|
||||
def _handcoded_match(self, newcluster, connected):
|
||||
matches = [];
|
||||
if isinstance(newcluster, Rigid) and len(newcluster.vars)>=3:
|
||||
rigids = [newcluster]
|
||||
hogs = filter(lambda hog: isinstance(hog, Hedgehog) and hog.vars.intersection(newcluster.vars) == hog.vars, connected)
|
||||
elif isinstance(newcluster, Hedgehog):
|
||||
hogs = [newcluster]
|
||||
rigids = filter(lambda rigid: isinstance(rigid, Rigid) and newcluster.vars.intersection(rigid.vars) == newcluster.vars, connected)
|
||||
else:
|
||||
return []
|
||||
for h in hogs:
|
||||
for r in rigids:
|
||||
m = Map({
|
||||
"$h": h,
|
||||
"$r": r,
|
||||
})
|
||||
matches.append(m)
|
||||
return matches;
|
||||
handcoded_match = staticmethod(_handcoded_match)
|
||||
|
||||
def __str__(self):
|
||||
s = "CheckAR("+str(self._inputs[0])+"+"+str(self._inputs[1])+"->"+str(self._outputs[0])+")"
|
||||
s += "[" + self.status_str()+"]"
|
||||
return s
|
||||
|
||||
def multi_execute(self, inmap):
|
||||
diag_print("CheckAR.multi_execute called","clmethods")
|
||||
# get configurations
|
||||
hog = inmap[self.hog]
|
||||
rigid = inmap[self.rigid]
|
||||
xvars = list(self.hog.xvars)
|
||||
# test if all angles match
|
||||
for i in range(len(self.sharedx)-1):
|
||||
hangle = angle_3p(hog.get(xvars[i]), hog.get(self.hog.cvar), hog.get(xvars[i+1]))
|
||||
rangle = angle_3p(rigid.get(xvars[i]), rigid.get(self.hog.cvar), rigid.get(xvars[i+1]))
|
||||
# angle check failed, return no configuration
|
||||
if not tol_eq(hangle,rangle):
|
||||
return []
|
||||
# all checks passed, return rigid configuration
|
||||
return [rigid]
|
||||
|
||||
|
||||
class MergePR(ClusterMethod):
|
||||
"""Represents a merging of a one-point cluster with any other rigid."""
|
||||
def __init__(self, map):
|
||||
|
@ -40,6 +97,28 @@ class MergePR(ClusterMethod):
|
|||
self._outputs = [out]
|
||||
ClusterMethod.__init__(self)
|
||||
|
||||
|
||||
def _handcoded_match(self, newcluster, connected):
|
||||
matches = [];
|
||||
if isinstance(newcluster, Rigid) and len(newcluster.vars)==1:
|
||||
points = [newcluster]
|
||||
distances = filter(lambda x: isinstance(x, Rigid) and len(x.vars)==2, connected)
|
||||
elif isinstance(newcluster, Rigid) and len(newcluster.vars)==2:
|
||||
distances = [newcluster]
|
||||
points = filter(lambda x: isinstance(x, Rigid) and len(x.vars)==1, connected)
|
||||
else:
|
||||
return []
|
||||
for p in points:
|
||||
for d in distances:
|
||||
m = Map({
|
||||
"$p": p,
|
||||
"$r": d,
|
||||
"$a": list(p.vars)[0]
|
||||
})
|
||||
matches.append(m)
|
||||
return matches;
|
||||
handcoded_match = staticmethod(_handcoded_match)
|
||||
|
||||
def _pattern():
|
||||
pattern = [["point","$p",["$a"]], ["rigid", "$r", ["$a"]]]
|
||||
return pattern2graph(pattern)
|
||||
|
|
|
@ -4,7 +4,7 @@ types are Rigids, Hedgehogs and Balloons. """
|
|||
from multimethod import MultiVariable
|
||||
|
||||
class Distance:
|
||||
"""A Distance represents a known distance"""
|
||||
"""A Distance represents an unknown distance between two points"""
|
||||
|
||||
|
||||
def __init__(self, a, b):
|
||||
|
@ -32,7 +32,7 @@ class Distance:
|
|||
|
||||
|
||||
class Angle:
|
||||
"""A Angle represents a known angle"""
|
||||
"""A Angle represents an unknown angle on three points"""
|
||||
|
||||
def __init__(self, a, b, c):
|
||||
"""Create a new Angle
|
||||
|
@ -62,13 +62,24 @@ class Angle:
|
|||
|
||||
|
||||
class Cluster(MultiVariable):
|
||||
"""A set of points, satisfying some constaint"""
|
||||
"""A cluster represents a set of Configurations on the same set point variables.
|
||||
Subtypes of Cluster (e.g. Rigid, Balloon and Hedgehog)
|
||||
define a specific combination of distance and angle constraints on those points.
|
||||
The configurations specify the values of those distances and angles.
|
||||
|
||||
Instance attributes:
|
||||
Cluster.vars is a frozenset of point variables
|
||||
Cluster.creationtime is a uniue integer
|
||||
Cluster.overconstrained is a boolean
|
||||
"""
|
||||
|
||||
staticcounter = 0
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, variables):
|
||||
Cluster.staticcounter += 1
|
||||
self.creationtime = Cluster.staticcounter
|
||||
self.vars = frozenset(variables)
|
||||
self.overconstrained = False
|
||||
|
||||
def intersection(self, other):
|
||||
shared = set(self.vars).intersection(other.vars)
|
||||
|
@ -123,7 +134,7 @@ class Cluster(MultiVariable):
|
|||
|
||||
|
||||
class Rigid(Cluster):
|
||||
"""A Rigid (or RigidCluster) represent a cluster of points variables
|
||||
"""A Rigid (or RigidCluster) represent a cluster of point variables
|
||||
that forms a rigid body."""
|
||||
|
||||
def __init__(self, vars):
|
||||
|
@ -132,9 +143,7 @@ class Rigid(Cluster):
|
|||
keyword args:
|
||||
vars - list of variables
|
||||
"""
|
||||
Cluster.__init__(self)
|
||||
self.vars = frozenset(vars)
|
||||
self.overconstrained = False
|
||||
Cluster.__init__(self, vars)
|
||||
|
||||
def __str__(self):
|
||||
s = "rigid#"+str(id(self))+"("+str(map(str, self.vars))+")"
|
||||
|
@ -152,6 +161,10 @@ class Rigid(Cluster):
|
|||
class Hedgehog(Cluster):
|
||||
"""An Hedgehog (or AngleCluster) represents a set of points (M,X1...XN)
|
||||
where all angles a(Xi,M,Xj) are known.
|
||||
|
||||
Instance attributes:
|
||||
cvar - center point variable
|
||||
xvars - list of other point variables
|
||||
"""
|
||||
def __init__(self, cvar, xvars):
|
||||
"""Create a new hedgehog
|
||||
|
@ -160,13 +173,11 @@ class Hedgehog(Cluster):
|
|||
cvar - center variable
|
||||
xvars - list of variables
|
||||
"""
|
||||
Cluster.__init__(self)
|
||||
self.cvar = cvar
|
||||
if len(xvars) < 2:
|
||||
raise StandardError, "hedgehog must have at least three variables"
|
||||
self.xvars = frozenset(xvars)
|
||||
self.vars = self.xvars.union([self.cvar])
|
||||
self.overconstrained = False
|
||||
Cluster.__init__(self, self.xvars.union([self.cvar]))
|
||||
if len(self.vars) < 3:
|
||||
raise StandardError, "hedgehog must have at least three variables"
|
||||
|
||||
def __str__(self):
|
||||
s = "hedgehog#"+str(id(self))+"("+str(self.cvar)+","+str(map(str, self.xvars))+")"
|
||||
|
@ -188,13 +199,11 @@ class Balloon(Cluster):
|
|||
"""Create a new balloon
|
||||
|
||||
keyword args:
|
||||
vars - collection of PointVar's
|
||||
variables - collection of PointVar's
|
||||
"""
|
||||
Cluster.__init__(self)
|
||||
if len(variables) < 3:
|
||||
raise StandardError, "balloon must have at least three variables"
|
||||
self.vars = frozenset(variables)
|
||||
self.overconstrained = False
|
||||
Cluster.__init__(self,variables)
|
||||
|
||||
def __str__(self):
|
||||
s = "balloon#"+str(id(self))+"("+str(map(str, self.vars))+")"
|
||||
|
@ -211,10 +220,11 @@ class Balloon(Cluster):
|
|||
|
||||
def over_constraints(c1, c2):
|
||||
"""returns the over-constraints (duplicate distances and angles) for
|
||||
a pair of clusters (rigid, angle or scalable)."""
|
||||
a pair of clusters."""
|
||||
return over_distances(c1,c2).union(over_angles(c1,c2))
|
||||
|
||||
def over_angles(c1, c2):
|
||||
"""determine set of angles in c1 and c2"""
|
||||
if isinstance(c1,Rigid) and isinstance(c2,Rigid):
|
||||
return over_angles_bb(c1,c2)
|
||||
if isinstance(c1,Rigid) and isinstance(c2,Hedgehog):
|
||||
|
|
|
@ -364,24 +364,26 @@ class GeometricSolver (Listener):
|
|||
if sub != parent:
|
||||
parent.subs.append(sub)
|
||||
|
||||
# determine top-level result
|
||||
rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level())
|
||||
if len(rigids) == 0:
|
||||
# no variables in problem?
|
||||
result = GeometricCluster(self.problem.cg.variables())
|
||||
result.variables = []
|
||||
result.subs = []
|
||||
result.solutions = []
|
||||
result.flags = GeometricCluster.UNSOLVED
|
||||
elif len(rigids) == 1:
|
||||
# structurally well constrained, or structurally overconstrained
|
||||
result = map[rigids[0]]
|
||||
else:
|
||||
# determine resutl from top-level clusters
|
||||
top = self.dr.top_level()
|
||||
rigids = filter(lambda c: isinstance(c, Rigid), top)
|
||||
if len(top) > 1:
|
||||
# structurally underconstrained cluster
|
||||
result = GeometricCluster(self.problem.cg.variables())
|
||||
result.flag = GeometricCluster.S_UNDER
|
||||
for rigid in rigids:
|
||||
result.subs.append(map[rigid])
|
||||
else:
|
||||
if len(rigids) == 1:
|
||||
# structurally well constrained, or structurally overconstrained
|
||||
result = map[rigids[0]]
|
||||
else:
|
||||
# no variables in problem?
|
||||
result = GeometricCluster(self.problem.cg.variables())
|
||||
result.variables = []
|
||||
result.subs = []
|
||||
result.solutions = []
|
||||
result.flags = GeometricCluster.UNSOLVED
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
@ -245,7 +245,8 @@ def overconstrained_tetra():
|
|||
problem.add_constraint(DistanceConstraint('v2', 'v4', 10.0))
|
||||
problem.add_constraint(DistanceConstraint('v3', 'v4', 10.0))
|
||||
# overconstrain me!
|
||||
problem.add_constraint(AngleConstraint('v1', 'v2', 'v3', math.pi/4.0))
|
||||
problem.add_constraint(AngleConstraint('v1', 'v2', 'v3', math.pi/3))
|
||||
#problem.add_constraint(AngleConstraint('v1', 'v2', 'v3', math.pi/4))
|
||||
return problem
|
||||
|
||||
# -------- 2D problems
|
||||
|
@ -919,9 +920,9 @@ def runtests():
|
|||
#test(fix3_problem_3d())
|
||||
#test(block("BB", 4.0,2.5,5.0))
|
||||
#diag_select("SelectionMethod.*")
|
||||
#test(selection_problem(),False)
|
||||
#selection_test()
|
||||
test(selection_problem(),False)
|
||||
|
||||
test(overconstrained_tetra())
|
||||
|
||||
|
||||
if __name__ == "__main__": runtests()
|
||||
|
|
Loading…
Reference in New Issue
Block a user