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:
parent
fe8d04497f
commit
22e159a5ad
|
@ -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 *
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)+")"
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Loading…
Reference in New Issue
Block a user