Changes to GeometricSolver:

- Added RigidConstraint.
- Depricated some methods (get_result, get_constrainedness) and 
added some new methods (get_solution, get_cluster, get_status).

Removed old SelectionConstraint and renamed FunctionConstraint to SelectionConstraint

Depricated ClusterSolver2D
Small bugfixes in cluster ClusterSolver3D

Renamed solvergui directory to workbench

Renamed solvertest directory to test
This commit is contained in:
kwikrick 2009-10-09 12:23:02 +00:00
parent fe8d04497f
commit 22e159a5ad
95 changed files with 189 additions and 127 deletions

View File

@ -4,6 +4,10 @@ The solver finds a generic solution
for problems formulated by Clusters. The generic solution for problems formulated by Clusters. The generic solution
is a directed acyclic graph of Clusters and Methods. Particilar problems is a directed acyclic graph of Clusters and Methods. Particilar problems
and solutions are represented by a Configuration for each cluster. and solutions are represented by a Configuration for each cluster.
Note: this module is now depricated, and will be removed or replaced
in the future.
""" """
from clsolver import * from clsolver import *

View File

@ -301,8 +301,8 @@ class MergePR(ClusterMethod):
res = conf1.merge(conf2) res = conf1.merge(conf2)
elif isroot2: elif isroot2:
res = conf2.merge(conf1) res = conf2.merge(conf1)
else: # cheapest else: # cheapest - just copy reference
res = conf2.merge(conf1) res = conf2
return [res] return [res]
class MergeDR(ClusterMethod): class MergeDR(ClusterMethod):
@ -344,8 +344,8 @@ class MergeDR(ClusterMethod):
res = conf1.merge(conf2) res = conf1.merge(conf2)
elif isroot2: elif isroot2:
res = conf2.merge(conf1) res = conf2.merge(conf1)
else: # cheapest else: # cheapest - just copy reference
res = conf2.merge(conf1) res = conf2
return [res] return [res]
class MergeRR(ClusterMethod): class MergeRR(ClusterMethod):
@ -386,7 +386,7 @@ class MergeRR(ClusterMethod):
res = conf1.merge(conf2) res = conf1.merge(conf2)
elif isroot2 and not isroot1: elif isroot2 and not isroot1:
res = conf2.merge(conf1) res = conf2.merge(conf1)
elif len(c1.vars) < len(c2.vars): # cheapest elif len(c1.vars) < len(c2.vars): # cheapest - transform smallest config
res = conf2.merge(conf1) res = conf2.merge(conf1)
else: else:
res = conf1.merge(conf2) res = conf1.merge(conf2)
@ -495,8 +495,8 @@ class DeriveTTD(ClusterMethod):
def prototype_constraints(self): def prototype_constraints(self):
constraints = [] constraints = []
constraints.append(FunctionConstraint(fnot(is_left_handed),[self.a,self.b,self.c,self.d])) constraints.append(SelectionConstraint(fnot(is_left_handed),[self.a,self.b,self.c,self.d]))
constraints.append(FunctionConstraint(fnot(is_right_handed),[self.a,self.b,self.c,self.d])) constraints.append(SelectionConstraint(fnot(is_right_handed),[self.a,self.b,self.c,self.d]))
return constraints return constraints
class DeriveDAD(ClusterMethod): class DeriveDAD(ClusterMethod):

View File

@ -3,7 +3,7 @@ problems incrementally."""
import vector import vector
from clsolver import PrototypeMethod from clsolver import PrototypeMethod
from clsolver2D import ClusterSolver2D # depricated! from clsolver2D import ClusterSolver2D
from clsolver3D import ClusterSolver3D from clsolver3D import ClusterSolver3D
from cluster import Rigid, Hedgehog from cluster import Rigid, Hedgehog
from configuration import Configuration from configuration import Configuration
@ -95,6 +95,16 @@ class GeometricProblem (Notifier, Listener):
else: else:
con.add_listener(self) con.add_listener(self)
self.cg.add_constraint(con) self.cg.add_constraint(con)
elif isinstance(con, RigidConstraint):
for var in con.variables():
if var not in self.prototype:
raise StandardError, "point variable not in problem"
#if self.get_rigid(con.variables())
# raise StandardError, "rigid already in problem"
#else:
if True:
con.add_listener(self)
self.cg.add_constraint(con)
elif isinstance(con, SelectionConstraint): elif isinstance(con, SelectionConstraint):
for var in con.variables(): for var in con.variables():
if var not in self.prototype: if var not in self.prototype:
@ -212,7 +222,7 @@ class GeometricProblem (Notifier, Listener):
class GeometricSolver (Listener): class GeometricSolver (Listener):
"""The GeometricSolver monitors changes in a GeometricProblem and """The GeometricSolver monitors changes in a GeometricProblem and
mappes any changes to corresponding changes in a GeometricCluster maps any changes to corresponding changes in a GeometricCluster
""" """
# public methods # public methods
@ -261,26 +271,33 @@ class GeometricSolver (Listener):
self._add_constraint(con) self._add_constraint(con)
def get_constrainedness(self): def get_constrainedness(self):
toplevel = self.dr.top_level() """Depricated. Use get_statis instead"""
if len(toplevel) > 1: return self.get_status()
return "under-constrained" # toplevel = self.dr.top_level()
elif len(toplevel) == 1: # if len(toplevel) > 1:
cluster = toplevel[0] # return "under-constrained"
if isinstance(cluster,Rigid): # elif len(toplevel) == 1:
configurations = self.dr.get(cluster) # cluster = toplevel[0]
if configurations == None: # if isinstance(cluster,Rigid):
return "unsolved" # configurations = self.dr.get(cluster)
elif len(configurations) > 0: # if configurations == None:
return "well-constrained" # return "unsolved"
else: # elif len(configurations) > 0:
return "over-constrained" # return "well-constrained"
else: # else:
return "under-constrained" # return "over-constrained"
elif len(toplevel) == 0: # else:
return "error" # return "under-constrained"
# elif len(toplevel) == 0:
# return "error"
def get_result(self): def get_result(self):
"""returns the result as a GeometricCluster""" """Depricated. Use get_cluster instead."""
return self.get_cluster()
def get_cluster(self):
"""Returns a GeometricCluster (the root of a tree of clusters),
describing the solutions and the decomposition of the problem."""
map = {} map = {}
# map dr clusters # map dr clusters
for drcluster in self.dr.rigids(): for drcluster in self.dr.rigids():
@ -327,7 +344,6 @@ class GeometricSolver (Listener):
geoout = map[outcluster] geoout = map[outcluster]
geoout.subs = list(geoin.subs) geoout.subs = list(geoin.subs)
# determine top-level result # determine top-level result
rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level()) rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level())
if len(rigids) == 0: if len(rigids) == 0:
@ -338,7 +354,7 @@ class GeometricSolver (Listener):
result.solutions = [] result.solutions = []
result.flags = GeometricCluster.UNSOLVED result.flags = GeometricCluster.UNSOLVED
elif len(rigids) == 1: elif len(rigids) == 1:
# structurally well constrained # structurally well constrained, or structurally overconstrained
result = map[rigids[0]] result = map[rigids[0]]
else: else:
# structurally underconstrained cluster # structurally underconstrained cluster
@ -348,6 +364,53 @@ class GeometricSolver (Listener):
result.subs.append(map[rigid]) result.subs.append(map[rigid])
return result return result
def get_solutions(self):
"""Returns a list of Configurations, which will be empty if the
problem is not structurally well-constrained. Note: this method is
cheaper but less informative than get_cluster. The
list and the configurations should not be changed (since they are
references to objects in the solver)."""
rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level())
if len(rigids) == 0:
return self.dr.get(rigids[0])
else:
return []
def get_status(self):
"""Returns a symbolic flag, one of:
GeometricCluster.S_UNDER,
GeometricCluster.S_OVER,
GeometricCluster.OK,
GeometricCluster.UNSOLVED,
GeometricCluster.EMPTY,
GeometricCluster.I_OVER,
GeometricCluster.I_UNDER.
Note: this method is cheaper but less informative than get_cluster.
"""
rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level())
if len(rigids) == 0:
return GeometricCluster.EMPTY
elif len(rigids) == 1:
drcluster = rigids[0]
solutions = self.dr.get(drcluster)
underconstrained = False
if solutions == None:
return GeometricCluster.UNSOLVED
else:
for solution in solutions:
if solution.underconstrained:
underconstrained = True
if drcluster.overconstrained:
return GeometricCluster.S_OVER
elif len(solutions) == 0:
return GeometricCluster.I_OVER
elif underconstrained:
return GeometricCluster.I_UNDER
else:
return GeometricCluster.OK
else:
return GeometricCluster.S_UNDER
def receive_notify(self, object, message): def receive_notify(self, object, message):
"""Take notice of changes in constraint graph""" """Take notice of changes in constraint graph"""
if object == self.cg: if object == self.cg:
@ -412,6 +475,15 @@ class GeometricSolver (Listener):
self.dr.add(rig) self.dr.add(rig)
# set configuration # set configuration
self._update_constraint(con) self._update_constraint(con)
elif isinstance(con, RigidConstraint):
# map to rigid
vars = list(con.variables());
rig = Rigid(vars)
self._map[con] = rig
self._map[rig] = con
self.dr.add(rig)
# set configuration
self._update_constraint(con)
elif isinstance(con, FixConstraint): elif isinstance(con, FixConstraint):
if self.fixcluster != None: if self.fixcluster != None:
self.dr.remove(self.fixcluster) self.dr.remove(self.fixcluster)
@ -422,6 +494,9 @@ class GeometricSolver (Listener):
self.dr.add(self.fixcluster) self.dr.add(self.fixcluster)
self.dr.set_root(self.fixcluster) self.dr.set_root(self.fixcluster)
self._update_fix() self._update_fix()
elif isinstance(con, SelectionConstraint):
# add directly to clustersolver
self.dr.add(con)
else: else:
## raise StandardError, "unknown constraint type" ## raise StandardError, "unknown constraint type"
pass pass
@ -482,6 +557,13 @@ class GeometricSolver (Listener):
conf = Configuration({v0:p0,v1:p1}) conf = Configuration({v0:p0,v1:p1})
self.dr.set(rig, [conf]) self.dr.set(rig, [conf])
assert con.satisfied(conf.map) assert con.satisfied(conf.map)
elif isinstance(con, RigidConstraint):
# set configuration
rig = self._map[con]
vars = list(con.variables())
conf = con.get_parameter()
self.dr.set(rig, [conf])
assert con.satisfied(conf.map)
elif isinstance(con, FixConstraint): elif isinstance(con, FixConstraint):
self._update_fix() self._update_fix()
else: else:
@ -529,15 +611,17 @@ class GeometricCluster:
I_UNDER incidental under-constrained I_UNDER incidental under-constrained
S_OVER structural overconstrained S_OVER structural overconstrained
S_UNDER structural underconstrained S_UNDER structural underconstrained
UNSOLVED unsolved UNSOLVED unsolved (no input values)
EMPTY empty (no variables)
""" """
OK = "well constrained" OK = "well-constrained"
I_OVER = "incidental over-constrained" I_OVER = "incidental over-constrained"
I_UNDER = "incidental under-constrained" I_UNDER = "incidental under-constrained"
S_OVER = "structral over-constrained" S_OVER = "structral over-constrained"
S_UNDER = "structural under-constrained" S_UNDER = "structural under-constrained"
UNSOLVED = "unsolved" UNSOLVED = "unsolved"
EMPTY = "empty"
def __init__(self): def __init__(self):
"""initialise an empty new cluster""" """initialise an empty new cluster"""
@ -602,7 +686,7 @@ class FixConstraint(ParametricConstraint):
"""A constraint to fix a point relative to the coordinate system""" """A constraint to fix a point relative to the coordinate system"""
def __init__(self, var, pos): def __init__(self, var, pos):
"""Create a new DistanceConstraint instance """Create a new FixConstraint instance
keyword args: keyword args:
var - a point variable name var - a point variable name
@ -698,4 +782,36 @@ class AngleConstraint(ParametricConstraint):
+str(self._variables[2])+","\ +str(self._variables[2])+","\
+str(self._value)+")" +str(self._value)+")"
class RigidConstraint(ParametricConstraint):
"""A constraint to set the relative position of a set of points"""
def __init__(self, conf):
"""Create a new DistanceConstraint instance
keyword args:
conf - a Configuration
"""
ParametricConstraint.__init__(self)
self._variables = list(conf.vars())
self.set_parameter(conf.copy())
def satisfied(self, mapping):
"""return True iff mapping from variable names to points satisfies constraint"""
result = True
conf = self._value
for index in range(1,len(self._variables)-1):
p1 = mapping[self._variables[index-1]]
p2 = mapping[self._variables[index]]
p3 = mapping[self._variables[index+1]]
c1 = conf.map[self._variables[index-1]]
c2 = conf.map[self._variables[index]]
c3 = conf.map[self._variables[index+1]]
result = tol_eq(distance_2p(p1,p2), distance_2p(c1,c2))
result = tol_eq(distance_2p(p1,p3), distance_2p(c1,c3))
result = tol_eq(distance_2p(p2,p3), distance_2p(c2,c3))
return result
def __str__(self):
return "RigidConstraint("+str(self._variables)+")"

View File

@ -2,92 +2,9 @@ from constraint import Constraint
from tolerance import tol_eq from tolerance import tol_eq
from intersections import * from intersections import *
class SelectionConstraint(Constraint): class SelectionConstraint(Constraint):
"""constraints for solution selection""" """select solutions where function returns true when applied to given variables."""
class NotCounterClockwiseConstraint(SelectionConstraint):
"""select triplets that are not counter clockwise (clockwise or degenerate)"""
def __init__(self,v1,v2,v3):
"""init constraint with names of point variables"""
self._variables = [v1,v2,v3]
def satisfied(self, map):
"""return True iff mapping from variable names to points satisfies constraint"""
p1 = map[self._variables[0]]
p2 = map[self._variables[1]]
p3 = map[self._variables[2]]
return not is_counterclockwise(p1,p2,p3)
def __str__(self):
return "NotCounterClockwiseConstraint("\
+str(self._variables[0])+","\
+str(self._variables[1])+","\
+str(self._variables[2])+")"
class NotClockwiseConstraint(SelectionConstraint):
"""select triplets that are not clockwise (counterclockwise or degenerate)"""
def __init__(self,v1,v2,v3):
"""init constraint with names of point variables"""
self._variables = [v1,v2,v3]
def satisfied(self, map):
"""return True iff mapping from variable names to points satisfies constraint"""
p1 = map[self._variables[0]]
p2 = map[self._variables[1]]
p3 = map[self._variables[2]]
return not is_clockwise(p1,p2,p3)
def __str__(self):
return "NotClockwiseConstraint("\
+str(self._variables[0])+","\
+str(self._variables[1])+","\
+str(self._variables[2])+")"
class NotObtuseConstraint(SelectionConstraint):
"""select triplets that are not obtuse (acute or degenerate)"""
def __init__(self,v1,v2,v3):
"""init constraint with names of point variables"""
self._variables = [v1,v2,v3]
def satisfied(self, map):
"""return True iff mapping from variable names to points satisfies constraint"""
p1 = map[self._variables[0]]
p2 = map[self._variables[1]]
p3 = map[self._variables[2]]
return not is_obtuse(p1,p2,p3)
def __str__(self):
return "NotObtuseConstraint("\
+str(self._variables[0])+","\
+str(self._variables[1])+","\
+str(self._variables[2])+")"
class NotAcuteConstraint(SelectionConstraint):
"""select triplets that are not acute (obtuse or degenerate)"""
def __init__(self,v1,v2,v3):
"""init constraint with names of point variables"""
self._variables = [v1,v2,v3]
def satisfied(self, map):
"""return True iff mapping from variable names to points satisfies constraint"""
p1 = map[self._variables[0]]
p2 = map[self._variables[1]]
p3 = map[self._variables[2]]
return not is_acute(p1,p2,p3)
def __str__(self):
return "NotAcuteConstraint("\
+str(self._variables[0])+","\
+str(self._variables[1])+","\
+str(self._variables[2])+")"
class FunctionConstraint(SelectionConstraint):
"""select solutions where function returns true when applied to given variables"""
def __init__(self,function, vars): def __init__(self,function, vars):
"""init constraint with function and a sequence of variables""" """init constraint with function and a sequence of variables"""
@ -102,7 +19,7 @@ class FunctionConstraint(SelectionConstraint):
return apply(self._function, values)==True return apply(self._function, values)==True
def __str__(self): def __str__(self):
return "FunctionConstraint("+self._function.__name__+","+str(map(str, self._variables))+")" return "SelectionConstraint("+self._function.__name__+","+str(map(str, self._variables))+")"
def fnot(function): def fnot(function):
notf = lambda *args: not apply(function,args) notf = lambda *args: not apply(function,args)
@ -110,7 +27,7 @@ def fnot(function):
return notf return notf
def test(): def test():
print FunctionConstraint(is_right_handed, ['a','b','c','d']) print SelectionConstraint(is_right_handed, ['a','b','c','d'])
print FunctionConstraint(fnot(is_right_handed), ['a','b','c','d']) print SelectionConstraint(fnot(is_right_handed), ['a','b','c','d'])
if __name__ == "__main__": test() if __name__ == "__main__": test()

View File

@ -1,5 +1,5 @@
# Geometric constraint solver test """This module provides some tests for the GeoSolver.
# See below for simple use of the GeometricSolver API The tests are also simple examples of how to use of the GeomSolver API"""
from geosolver.geometric import * from geosolver.geometric import *
from geosolver.vector import vector from geosolver.vector import vector
@ -10,6 +10,30 @@ from time import time
# ---------- 3D problems ----- # ---------- 3D problems -----
def block(name,x,y,z):
"""A block with variables name+#1...8 and dimensions x,y,z"""
problem = GeometricProblem(dimension=3)
problem.add_point(name+'#1', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#2', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#3', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#4', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#5', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#6', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#7', vector([0.0, 0.0, 0.0]))
problem.add_point(name+'#8', vector([0.0, 0.0, 0.0]))
conf = Configuration({
name+'#1':vector([-x/2, -y/2, -z/2]),
name+'#2':vector([-x/2, -y/2, +z/2]),
name+'#3':vector([-x/2, +y/2, -z/2]),
name+'#4':vector([-x/2, +y/2, +z/2]),
name+'#5':vector([+x/2, -y/2, -z/2]),
name+'#6':vector([+x/2, -y/2, +z/2]),
name+'#7':vector([+x/2, +y/2, -z/2]),
name+'#8':vector([+x/2, +y/2, +z/2])
})
problem.add_constraint(RigidConstraint(conf))
return problem
def fix3_problem_3d(): def fix3_problem_3d():
"""A problem with a fix constraint""" """A problem with a fix constraint"""
problem = GeometricProblem(dimension=3) problem = GeometricProblem(dimension=3)
@ -737,7 +761,7 @@ def stats_solving():
problem = random_triangular_problem_3D(size,10.0,0.0,0.0) problem = random_triangular_problem_3D(size,10.0,0.0,0.0)
t1 = time() t1 = time()
solver = GeometricSolver(problem) solver = GeometricSolver(problem)
result = solver.get_constrainedness() result = solver.get_status()
t2 = time() t2 = time()
t = t2-t1 t = t2-t1
print size,"\t",i,"\t",t,"\t",result print size,"\t",i,"\t",t,"\t",result
@ -754,7 +778,7 @@ def stats_incremental():
constraint = random.choice(problem.cg.constraints()) constraint = random.choice(problem.cg.constraints())
problem.rem_constraint(constraint) problem.rem_constraint(constraint)
problem.add_constraint(constraint) problem.add_constraint(constraint)
result = solver.get_constrainedness() result = solver.get_status()
t2 = time() t2 = time()
t = t2-t1 t = t2-t1
print size,"\t",i,"\t",t,"\t",result print size,"\t",i,"\t",t,"\t",result
@ -776,7 +800,7 @@ def stats_parametric_incremental():
#problem.rem_constraint(constraint) #problem.rem_constraint(constraint)
#problem.add_constraint(constraint) #problem.add_constraint(constraint)
#constraint.set_parameter(constraint.get_parameter()) #constraint.set_parameter(constraint.get_parameter())
result = solver.get_constrainedness() result = solver.get_status()
t2 = time() t2 = time()
t = t2-t1 t = t2-t1
print size,"\t",i,"\t",t,"\t",result print size,"\t",i,"\t",t,"\t",result
@ -792,7 +816,7 @@ def stats_parametric():
constraint = random.choice(problem.cg.constraints()) constraint = random.choice(problem.cg.constraints())
t1 = time() t1 = time()
constraint.set_parameter(constraint.get_parameter()) constraint.set_parameter(constraint.get_parameter())
result = solver.get_constrainedness() result = solver.get_status()
t2 = time() t2 = time()
t = t2-t1 t = t2-t1
print size,"\t",i,"\t",t,"\t",result print size,"\t",i,"\t",t,"\t",result
@ -813,6 +837,7 @@ def runtests():
#test(random_distance_problem_3D(10,1.0,0.0)) #test(random_distance_problem_3D(10,1.0,0.0))
#test(fix1_problem_3d()) #test(fix1_problem_3d())
#test(fix2_problem_3d()) #test(fix2_problem_3d())
test(fix3_problem_3d()) #test(fix3_problem_3d())
test(block("BB", 4.0,2.5,5.0))
if __name__ == "__main__": runtests() if __name__ == "__main__": runtests()

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB