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
is a directed acyclic graph of Clusters and Methods. Particilar problems
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 *

View File

@ -301,8 +301,8 @@ class MergePR(ClusterMethod):
res = conf1.merge(conf2)
elif isroot2:
res = conf2.merge(conf1)
else: # cheapest
res = conf2.merge(conf1)
else: # cheapest - just copy reference
res = conf2
return [res]
class MergeDR(ClusterMethod):
@ -344,8 +344,8 @@ class MergeDR(ClusterMethod):
res = conf1.merge(conf2)
elif isroot2:
res = conf2.merge(conf1)
else: # cheapest
res = conf2.merge(conf1)
else: # cheapest - just copy reference
res = conf2
return [res]
class MergeRR(ClusterMethod):
@ -386,7 +386,7 @@ class MergeRR(ClusterMethod):
res = conf1.merge(conf2)
elif isroot2 and not isroot1:
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)
else:
res = conf1.merge(conf2)
@ -495,8 +495,8 @@ class DeriveTTD(ClusterMethod):
def prototype_constraints(self):
constraints = []
constraints.append(FunctionConstraint(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_left_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
class DeriveDAD(ClusterMethod):

View File

@ -3,7 +3,7 @@ problems incrementally."""
import vector
from clsolver import PrototypeMethod
from clsolver2D import ClusterSolver2D
# depricated! from clsolver2D import ClusterSolver2D
from clsolver3D import ClusterSolver3D
from cluster import Rigid, Hedgehog
from configuration import Configuration
@ -95,6 +95,16 @@ class GeometricProblem (Notifier, Listener):
else:
con.add_listener(self)
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):
for var in con.variables():
if var not in self.prototype:
@ -212,7 +222,7 @@ class GeometricProblem (Notifier, Listener):
class GeometricSolver (Listener):
"""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
@ -261,26 +271,33 @@ class GeometricSolver (Listener):
self._add_constraint(con)
def get_constrainedness(self):
toplevel = self.dr.top_level()
if len(toplevel) > 1:
return "under-constrained"
elif len(toplevel) == 1:
cluster = toplevel[0]
if isinstance(cluster,Rigid):
configurations = self.dr.get(cluster)
if configurations == None:
return "unsolved"
elif len(configurations) > 0:
return "well-constrained"
else:
return "over-constrained"
else:
return "under-constrained"
elif len(toplevel) == 0:
return "error"
"""Depricated. Use get_statis instead"""
return self.get_status()
# toplevel = self.dr.top_level()
# if len(toplevel) > 1:
# return "under-constrained"
# elif len(toplevel) == 1:
# cluster = toplevel[0]
# if isinstance(cluster,Rigid):
# configurations = self.dr.get(cluster)
# if configurations == None:
# return "unsolved"
# elif len(configurations) > 0:
# return "well-constrained"
# else:
# return "over-constrained"
# else:
# return "under-constrained"
# elif len(toplevel) == 0:
# return "error"
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 dr clusters
for drcluster in self.dr.rigids():
@ -326,7 +343,6 @@ class GeometricSolver (Listener):
geoin = map[incluster]
geoout = map[outcluster]
geoout.subs = list(geoin.subs)
# determine top-level result
rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level())
@ -338,7 +354,7 @@ class GeometricSolver (Listener):
result.solutions = []
result.flags = GeometricCluster.UNSOLVED
elif len(rigids) == 1:
# structurally well constrained
# structurally well constrained, or structurally overconstrained
result = map[rigids[0]]
else:
# structurally underconstrained cluster
@ -348,6 +364,53 @@ class GeometricSolver (Listener):
result.subs.append(map[rigid])
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):
"""Take notice of changes in constraint graph"""
if object == self.cg:
@ -412,6 +475,15 @@ class GeometricSolver (Listener):
self.dr.add(rig)
# set configuration
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):
if self.fixcluster != None:
self.dr.remove(self.fixcluster)
@ -422,6 +494,9 @@ class GeometricSolver (Listener):
self.dr.add(self.fixcluster)
self.dr.set_root(self.fixcluster)
self._update_fix()
elif isinstance(con, SelectionConstraint):
# add directly to clustersolver
self.dr.add(con)
else:
## raise StandardError, "unknown constraint type"
pass
@ -482,6 +557,13 @@ class GeometricSolver (Listener):
conf = Configuration({v0:p0,v1:p1})
self.dr.set(rig, [conf])
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):
self._update_fix()
else:
@ -529,15 +611,17 @@ class GeometricCluster:
I_UNDER incidental under-constrained
S_OVER structural overconstrained
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_UNDER = "incidental under-constrained"
S_OVER = "structral over-constrained"
S_UNDER = "structural under-constrained"
UNSOLVED = "unsolved"
EMPTY = "empty"
def __init__(self):
"""initialise an empty new cluster"""
@ -602,7 +686,7 @@ class FixConstraint(ParametricConstraint):
"""A constraint to fix a point relative to the coordinate system"""
def __init__(self, var, pos):
"""Create a new DistanceConstraint instance
"""Create a new FixConstraint instance
keyword args:
var - a point variable name
@ -698,4 +782,36 @@ class AngleConstraint(ParametricConstraint):
+str(self._variables[2])+","\
+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 intersections import *
class SelectionConstraint(Constraint):
"""constraints for solution selection"""
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"""
"""select solutions where function returns true when applied to given variables."""
def __init__(self,function, vars):
"""init constraint with function and a sequence of variables"""
@ -102,7 +19,7 @@ class FunctionConstraint(SelectionConstraint):
return apply(self._function, values)==True
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):
notf = lambda *args: not apply(function,args)
@ -110,7 +27,7 @@ def fnot(function):
return notf
def test():
print FunctionConstraint(is_right_handed, ['a','b','c','d'])
print FunctionConstraint(fnot(is_right_handed), ['a','b','c','d'])
print SelectionConstraint(is_right_handed, ['a','b','c','d'])
print SelectionConstraint(fnot(is_right_handed), ['a','b','c','d'])
if __name__ == "__main__": test()

View File

@ -1,5 +1,5 @@
# Geometric constraint solver test
# See below for simple use of the GeometricSolver API
"""This module provides some tests for the GeoSolver.
The tests are also simple examples of how to use of the GeomSolver API"""
from geosolver.geometric import *
from geosolver.vector import vector
@ -10,6 +10,30 @@ from time import time
# ---------- 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():
"""A problem with a fix constraint"""
problem = GeometricProblem(dimension=3)
@ -737,7 +761,7 @@ def stats_solving():
problem = random_triangular_problem_3D(size,10.0,0.0,0.0)
t1 = time()
solver = GeometricSolver(problem)
result = solver.get_constrainedness()
result = solver.get_status()
t2 = time()
t = t2-t1
print size,"\t",i,"\t",t,"\t",result
@ -754,7 +778,7 @@ def stats_incremental():
constraint = random.choice(problem.cg.constraints())
problem.rem_constraint(constraint)
problem.add_constraint(constraint)
result = solver.get_constrainedness()
result = solver.get_status()
t2 = time()
t = t2-t1
print size,"\t",i,"\t",t,"\t",result
@ -776,7 +800,7 @@ def stats_parametric_incremental():
#problem.rem_constraint(constraint)
#problem.add_constraint(constraint)
#constraint.set_parameter(constraint.get_parameter())
result = solver.get_constrainedness()
result = solver.get_status()
t2 = time()
t = t2-t1
print size,"\t",i,"\t",t,"\t",result
@ -792,7 +816,7 @@ def stats_parametric():
constraint = random.choice(problem.cg.constraints())
t1 = time()
constraint.set_parameter(constraint.get_parameter())
result = solver.get_constrainedness()
result = solver.get_status()
t2 = time()
t = t2-t1
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(fix1_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()

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