added mapping for lines

This commit is contained in:
kwikrick 2012-09-12 14:59:35 +00:00
parent a4bee26e4e
commit 1e88c4d65b
8 changed files with 449 additions and 242 deletions

View File

@ -3,7 +3,7 @@ TODO/WISH LIST
Code: Some bits of the code are ugly:
- vector module, matpy module and Numpy package, all offer similar
functionality. Use only one, get rid of two.
functionality. Use only one (numpy), get rid of two.
- more documentation!
Speed: The solver is currently much too slow. The problem is the pattern

View File

@ -590,7 +590,7 @@ class ClusterSolver(Notifier):
if isinstance(item, Cluster):
for var in item.vars:
if len(self.find_dependend(var)) == 0:
self._graph.remove(var)
self._graph.rem_vertex(var)
# notify listeners
self.send_notify(("remove", item))
# restore toplevel (also added to _new)

View File

@ -333,6 +333,11 @@ class Configuration:
else:
return "Configuration("+str(self.map)+")"
def __contains__(self,var):
return var in self.map
def __getitem__(self,var):
return self.map[var]
def testeq():
p1 = vector.vector([0.0,0.0,0.0])

View File

@ -7,13 +7,14 @@ from clsolver import PrototypeMethod, SelectionMethod
from clsolver3D import ClusterSolver3D
from clsolver2D import ClusterSolver2D
from cluster import *
from selconstr import SelectionConstraint
from configuration import Configuration
from diagnostic import diag_print
from constraint import Constraint, ConstraintGraph
from notify import Notifier, Listener
from tolerance import tol_eq
from intersections import angle_3p, distance_2p
from selconstr import SelectionConstraint
from intersections import distance_point_line
from intersections import is_left_handed, is_right_handed
from intersections import is_clockwise, is_counterclockwise
from intersections import transform_point, make_hcs_3d
@ -107,7 +108,7 @@ class GeometricProblem (Notifier, Listener):
if variable in self.prototype:
return self.prototype[variable]
else:
raise StandardError, "unknown variable variable"
raise StandardError, "unknown variable "+str(variable)
# --------------- points - depricated -------------
def add_point(self, variable, prototype):
@ -201,18 +202,39 @@ class GeometricProblem (Notifier, Listener):
else:
return None
def get_constraints_with_type_on_variables(self, constrainttype, variables):
candidates = None
for var in variables:
if candidates == None:
candidates = set(filter(lambda c:isinstance(c,constrainttype),self.cg.get_constraints_on(var)))
else:
candidates.intersection_update(filter(lambda c:isinstance(c,constrainttype),self.cg.get_constraints_on(var)))
return candidates
def get_unique_constraint(self, constrainttype, variables):
candidates = self.get_constraints_with_type_on_variables(constrainttype, variables)
if len(candidates) == 0:
return None
elif len(candidates) == 1:
return candidates[0]
else: # >= 1
raise StandardError, "multiple constraints found"
def get_coincidence(self, p, g):
print "GeometricProblem.get_coincidence NOT IMPLEMENTED"
return None
return self.get_unique_constraint(CoincidenceConstraint, [p,g])
def get_rigid(self, vars):
print "GeometricProblem.get_rigid NOT IMPLEMENTED"
return None
def get_rigid(self, variables):
return self.get_unique_constraint(RigidConstraint, variables)
def get_mate(self, vars):
print "GeometricProblem.get_mate NOT IMPLEMENTED"
return None
def get_mate(self, variables):
return self.get_unique_constraint(MateConstraint, variables)
def get_coincident_points(self, geometry):
coincidences = self.get_constraints_with_type_on_variables(CoincidenceConstraint, [geometry])
points = set()
for constraint in coincidences:
points.update(filter(lambda var: isinstance(var, Point) and var != geometry, constraint.variables()))
return points
def verify(self, solution):
"""returns true iff all constraints satisfied by given solution.
@ -384,12 +406,13 @@ class GeometricSolver (Listener):
drclusters = map[geocluster]
drcluster = max(drclusters, key=lambda c: c.creationtime)
# determine solutions
solutions = self.dr.get(drcluster)
geocluster.solutions = self._map_cluster_solutions(drcluster)
# determine incidental underconstrainedness
underconstrained = False
if solutions != None:
for solution in solutions:
geocluster.solutions.append(solution.map)
if solution.underconstrained:
configurations = self.dr.get(drcluster)
if configurations != None:
for config in configurations:
if config.underconstrained:
underconstrained = True
# determine flag
if drcluster.overconstrained:
@ -442,14 +465,51 @@ class GeometricSolver (Listener):
def get_solutions(self):
"""Returns a list of Configurations, which will be empty if the
problem has no solutions. Note: this method is
cheaper but less informative than get_decomposition. The
list and the configurations should not be changed (since they are
references to objects in the solver)."""
cheaper but less informative than get_decomposition.
"""
#"""The list and the configurations should not be changed (since they are
#references to objects in the solver)."""
# find top level rigid and all its configurations
rigids = filter(lambda c: isinstance(c, Rigid), self.dr.top_level())
if len(rigids) != 0:
return self.dr.get(rigids[0])
solutions = self._map_cluster_solutions(drcluster)
else:
return []
solutions = []
return solutions
def _map_cluster_solutions(self, drcluster):
# map dr-cluster configurations to solutions, i.e. a map from problem variables to values
configurations = self.dr.get(drcluster)
solutions = []
diag_print("mapping cluster "+str(drcluster)+" #configurations="+str(len(configurations)),"GeometricSolver")
for configuration in configurations:
solution = {}
for var in self.problem.cg.variables():
if isinstance(var, Point):
assert len(self._map[var].vars) == 1
point = iter(self._map[var].vars).next()
if point in configuration:
solution[var] = configuration[point]
elif isinstance(var, Line):
if var in self._map:
vertices = list(self._map[var].vars)
else:
# when line coincident with 2 points, then not mapped to cluster... use any two coincident points
points = self.problem.get_coincident_points(var)
assert len(points) >= 2
print "points",points
vertices = map(lambda v: iter(self._map[v].vars).next(), list(points)[0:2])
print "vertices",vertices
assert len(vertices) == 2
if vertices[0] in configuration and vertices[1] in configuration:
solution[var] = vector.vector(configuration[vertices[0]]).concatonated( vector.vector(configuration[vertices[1]]) )
else:
raise StandardError, "unknown variable type"
#for
solutions.append(solution)
#for
return solutions
def get_status(self):
"""Returns a symbolic flag, one of:
@ -542,7 +602,50 @@ class GeometricSolver (Listener):
self._map[rigid] = var
self.dr.add(rigid)
self._update_variable(var)
def _add_line(self, var):
diag_print("add line "+str(var),"GeometricSolver")
# find coincident points
points = list(self.problem.get_coincident_points(var))
diag_print("on "+str(points),"GeometricSolver")
if len(points) == 0:
self._map_line_distance(var)
elif len(points) == 1:
self._map_line_point_distance(var, points[0])
elif len(points) == 2:
self._map_line_point_point(var, points[0], points[1])
else: # >=3
self._map_line_points_radial(var, points)
def _map_line_distance(self,line):
# map a line (coincident with no points) to a distance cluster (on two new point variables)
v1 = str(line)+"_v1"
v2 = str(line)+"_v2"
dist = Rigid([v1,v2])
self._map[line] = dist
self._map[dist] = line
self.dr.add(dist)
self._update_variable(line)
def _map_line_point_distance(self,line, point):
# map a line coincident with one point to a distance clusters (and one new point variable)
v1 = list(self._map[point].vars)[0]
v2 = str(line)+"_v2"
dist = Rigid([v1,v2])
self._map[line] = dist
self._map[dist] = line
self.dr.add(dist)
self._update_variable(line)
def _map_line_point_point(self, line, point1, point2):
# map a line coincident with two existing point variables; no new clusters created
self._update_variable(line)
def _map_line_points_radial(self, line, points):
# map a line coincient with three or more points to a radial clusters
raise NotImplementedError
def _rem_variable(self, var):
diag_print("GeometricSolver._rem_variable","gcs")
if var in self._map:
@ -600,6 +703,14 @@ class GeometricSolver (Listener):
elif isinstance(con, SelectionConstraint):
# add directly to clustersolver
self.dr.add_selection_constraint(con)
elif isinstance(con, CoincidenceConstraint):
# re-map lines, planes, etc
lines = filter(lambda var: isinstance(var,Line),con.variables())
if len(lines)==1:
line = iter(lines).next()
self._rem_variable(line)
self._add_line(line)
#endif
else:
raise StandardError, "unknown constraint type"
pass
@ -710,12 +821,41 @@ class GeometricSolver (Listener):
else:
raise StandardError, "unknown constraint type"
def _update_variable(self, variable):
def _update_variable(self, var):
if isinstance(var, Point):
self._update_point(var)
elif isinstance(var, Line):
self._update_line(var)
elif isinstance(var, Plane):
self._update_plane(var)
else:
# assume point - depricated
self._update_point(var)
def _update_point(self, variable):
cluster = self._map[variable]
proto = self.problem.get_point(variable)
proto = self.problem.get_prototype(variable)
conf = Configuration({variable:proto})
self.dr.set(cluster, [conf])
def _update_line(self, variable):
# note: line may not be mapped to a cluster at all!
if variable in self._map:
cluster = self._map[variable]
proto = self.problem.get_prototype(variable)
vertices = list(cluster.vars);
v1 = vertices[0]
v2 = vertices[1]
assert self.problem.dimension==2 or self.problem.dimension==3
if self.problem.dimension == 2:
p1 = proto[0:2]
p2 = proto[2:4]
elif self.problem.dimension == 3:
p1 = proto[0:3]
p2 = proto[3:6]
conf = Configuration({v1:p1, v2:p2})
self.dr.set(cluster, [conf])
def _update_fix(self):
if self.fixcluster:
vars = self.fixcluster.vars
@ -826,7 +966,7 @@ class GeometricVariable:
return hash(self.name)
def __repr__(self):
return repr(self.__class__)+"("+repr(self.name)+")"
return self.__class__.__name__+"("+repr(self.name)+")"
class Point(GeometricVariable):
@ -1093,17 +1233,17 @@ class CoincidenceConstraint(Constraint):
def satisfied(self, mapping):
"""return True iff mapping from variable names to points satisfies constraint"""
if isinstance(geometry, Point):
if isinstance(self._geometry, Point):
p1 = mapping[self._point]
p2 = mapping[self._geometry]
return tol_eq(distance_2p(p1,p2),0)
elif isinstance(geometry, Line):
elif isinstance(self._geometry, Line):
p = mapping[self._point]
l = mapping[self._geometry]
p1 = l[0:3]
p2 = l[3:6]
return tol_eq(distance_point_line(p, p1, p2),0)
elif isinstance(geometry, Plane):
elif isinstance(self._geometry, Plane):
p = mapping[self._point]
l = mapping[self._geometry]
p1 = l[0:3]

View File

@ -231,6 +231,20 @@ def distance_2p(p1, p2):
"""
return vector.norm(p2 - p1)
def distance_point_line(p,l1,l2):
"""distance from point p to line l1-l2"""
# v,w is p, l2 relative to l1
v = p-l1
w = l2-l1
# x = projection v on w
l = (vector.norm(v) * vector.norm(w))
if tol_eq(l,0):
x = 0*v
else:
x = v * vector.dot(v,w) / l
# result is distance x,v
return vector.norm(x-v)
# ------ 2D
def is_clockwise(p1,p2,p3):

View File

@ -12,91 +12,95 @@ requires Python 2.2 or later.
"""
class vector(list):
"""
"""
A list based vector class
"""
# no c'tor
"""
# no c'tor
def __getslice__(self, i, j):
try:
# use the list __getslice__ method and convert
# result to vector
return vector(super(vector, self).__getslice__(i,j))
except:
raise TypeError, 'vector::FAILURE in __getslice__'
def __add__(self, other):
return vector(map(lambda x,y: x+y, self, other))
def __getslice__(self, i, j):
try:
# use the list __getslice__ method and convert
# result to vector
return vector(super(vector, self).__getslice__(i,j))
except:
raise TypeError, 'vector::FAILURE in __getslice__'
def __add__(self, other):
return vector(map(lambda x,y: x+y, self, other))
def __neg__(self):
return vector(map(lambda x: -x, self))
def __sub__(self, other):
return vector(map(lambda x,y: x-y, self, other))
def __neg__(self):
return vector(map(lambda x: -x, self))
def __sub__(self, other):
return vector(map(lambda x,y: x-y, self, other))
def __mul__(self, other):
"""
Element by element multiplication
"""
try:
return vector(map(lambda x,y: x*y, self,other))
except:
# other is a const
return vector(map(lambda x: x*other, self))
def __mul__(self, other):
"""
Element by element multiplication
"""
try:
return vector(map(lambda x,y: x*y, self,other))
except:
# other is a const
return vector(map(lambda x: x*other, self))
def __rmul__(self, other):
return (self*other)
def __rmul__(self, other):
return (self*other)
def __div__(self, other):
"""
Element by element division.
"""
try:
return vector(map(lambda x,y: x/y, self, other))
except:
return vector(map(lambda x: x/other, self))
def __div__(self, other):
"""
Element by element division.
"""
try:
return vector(map(lambda x,y: x/y, self, other))
except:
return vector(map(lambda x: x/other, self))
def __rdiv__(self, other):
"""
The same as __div__
"""
try:
return vector(map(lambda x,y: x/y, other, self))
except:
# other is a const
return vector(map(lambda x: other/x, self))
def __rdiv__(self, other):
"""
The same as __div__
"""
try:
return vector(map(lambda x,y: x/y, other, self))
except:
# other is a const
return vector(map(lambda x: other/x, self))
def size(self): return len(self)
def conjugate(self):
return vector(map(lambda x: x.conjugate(), self))
def conjugate(self):
return vector(map(lambda x: x.conjugate(), self))
def ReIm(self):
"""
Return the real and imaginary parts
"""
return [
vector(map(lambda x: x.real, self)),
vector(map(lambda x: x.imag, self)),
]
"""
Return the real and imaginary parts
"""
return [
vector(map(lambda x: x.real, self)),
vector(map(lambda x: x.imag, self)),
]
def AbsArg(self):
"""
Return modulus and phase parts
"""
return [
vector(map(lambda x: abs(x), self)),
vector(map(lambda x: math.atan2(x.imag,x.real), self)),
]
"""
Return modulus and phase parts
"""
return [
vector(map(lambda x: abs(x), self)),
vector(map(lambda x: math.atan2(x.imag,x.real), self)),
]
def out(self):
"""
Prints out the vector.
"""
print self
def out(self):
"""
Prints out the vector.
"""
print self
def concatonated(self,other):
"""this vector concatonated with another"""
return vector(list(self)+list(other))
###############################################################################
@ -121,173 +125,173 @@ def ones(n):
def randvec(n, lmin=0.0, lmax=1.0, roundoff=0.0):
"""
Returns a random vector of length n.
"""
def _round(val,roundoff):
if roundoff > 0:
return val - (val % roundoff)
else:
return val
return vector(map(lambda x: _round(random.uniform(lmin, lmax),roundoff),
range(n)))
"""
Returns a random vector of length n.
"""
def _round(val,roundoff):
if roundoff > 0:
return val - (val % roundoff)
else:
return val
return vector(map(lambda x: _round(random.uniform(lmin, lmax),roundoff),
range(n)))
def dot(a, b):
"""
dot product of two vectors.
"""
try:
return reduce(lambda x, y: x+y, a*b, 0.)
return reduce(lambda x, y: x+y, a*b, 0.)
except:
raise TypeError, 'vector::FAILURE in dot'
raise TypeError, 'vector::FAILURE in dot'
def cross(a, b):
"""
cross product of two 3-vectors.
"""
if len(a) == len(b) == 3:
return vector([a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0]])
return vector([a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0]])
else:
raise TypeError, 'vector.cross - args be 3-vectors'
raise TypeError, 'vector.cross - args be 3-vectors'
def norm(a):
"""
Computes the norm of vector a.
"""
try:
return math.sqrt(abs(dot(a,a)))
return math.sqrt(abs(dot(a,a)))
except:
raise TypeError, 'vector::FAILURE in norm'
raise TypeError, 'vector::FAILURE in norm'
def sum(a):
"""
Returns the sum of the elements of a.
"""
try:
return reduce(lambda x, y: x+y, a, 0)
return reduce(lambda x, y: x+y, a, 0)
except:
raise TypeError, 'vector::FAILURE in sum'
raise TypeError, 'vector::FAILURE in sum'
# elementwise operations
def log10(a):
"""
log10 of each element of a.
"""
try:
return vector(map(math.log10, a))
return vector(map(math.log10, a))
except:
raise TypeError, 'vector::FAILURE in log10'
raise TypeError, 'vector::FAILURE in log10'
def log(a):
"""
log of each element of a.
"""
try:
return vector(map(math.log, a))
return vector(map(math.log, a))
except:
raise TypeError, 'vector::FAILURE in log'
raise TypeError, 'vector::FAILURE in log'
def exp(a):
"""
Elementwise exponential.
"""
try:
return vector(map(math.exp, a))
return vector(map(math.exp, a))
except:
raise TypeError, 'vector::FAILURE in exp'
raise TypeError, 'vector::FAILURE in exp'
def sin(a):
"""
Elementwise sine.
"""
try:
return vector(map(math.sin, a))
return vector(map(math.sin, a))
except:
raise TypeError, 'vector::FAILURE in sin'
raise TypeError, 'vector::FAILURE in sin'
def tan(a):
"""
Elementwise tangent.
"""
try:
return vector(map(math.tan, a))
return vector(map(math.tan, a))
except:
raise TypeError, 'vector::FAILURE in tan'
raise TypeError, 'vector::FAILURE in tan'
def cos(a):
"""
Elementwise cosine.
"""
try:
return vector(map(math.cos, a))
return vector(map(math.cos, a))
except:
raise TypeError, 'vector::FAILURE in cos'
raise TypeError, 'vector::FAILURE in cos'
def asin(a):
"""
Elementwise inverse sine.
"""
try:
return vector(map(math.asin, a))
return vector(map(math.asin, a))
except:
raise TypeError, 'vector::FAILURE in asin'
raise TypeError, 'vector::FAILURE in asin'
def atan(a):
"""
Elementwise inverse tangent.
"""
"""
try:
return vector(map(math.atan, a))
return vector(map(math.atan, a))
except:
raise TypeError, 'vector::FAILURE in atan'
raise TypeError, 'vector::FAILURE in atan'
def acos(a):
"""
Elementwise inverse cosine.
"""
try:
return vector(map(math.acos, a))
return vector(map(math.acos, a))
except:
raise TypeError, 'vector::FAILURE in acos'
raise TypeError, 'vector::FAILURE in acos'
def sqrt(a):
"""
Elementwise sqrt.
"""
try:
return vector(map(math.sqrt, a))
return vector(map(math.sqrt, a))
except:
raise TypeError, 'vector::FAILURE in sqrt'
raise TypeError, 'vector::FAILURE in sqrt'
def sinh(a):
"""
Elementwise hyperbolic sine.
"""
try:
return vector(map(math.sinh, a))
return vector(map(math.sinh, a))
except:
raise TypeError, 'vector::FAILURE in sinh'
raise TypeError, 'vector::FAILURE in sinh'
def tanh(a):
"""
Elementwise hyperbolic tangent.
"""
try:
return vector(map(math.tanh, a))
return vector(map(math.tanh, a))
except:
raise TypeError, 'vector::FAILURE in tanh'
raise TypeError, 'vector::FAILURE in tanh'
def cosh(a):
"""
Elementwise hyperbolic cosine.
"""
try:
return vector(map(math.cosh, a))
return vector(map(math.cosh, a))
except:
raise TypeError, 'vector::FAILURE in cosh'
raise TypeError, 'vector::FAILURE in cosh'
def pow(a,b):
@ -298,105 +302,105 @@ def pow(a,b):
return vector(map(lambda x: x**b, a))
except:
try:
return vector(map(lambda x,y: x**y, a, b))
except:
raise TypeError, 'vector::FAILURE in pow'
return vector(map(lambda x,y: x**y, a, b))
except:
raise TypeError, 'vector::FAILURE in pow'
def atan2(a,b):
"""
Arc tangent
"""
try:
return vector(map(math.atan2, a, b))
return vector(map(math.atan2, a, b))
except:
raise TypeError, 'vector::FAILURE in atan2'
raise TypeError, 'vector::FAILURE in atan2'
###############################################################################
if __name__ == "__main__":
print 'a = zeros(4)'
a = zeros(4)
print 'a = zeros(4)'
a = zeros(4)
print 'a.__doc__=',a.__doc__
print 'a.__doc__=',a.__doc__
print 'a[0] = 1.0'
a[0] = 1.0
print 'a[0] = 1.0'
a[0] = 1.0
print 'a[3] = 3.0'
a[3] = 3.0
print 'a[3] = 3.0'
a[3] = 3.0
print 'a[0]=', a[0]
print 'a[1]=', a[1]
print 'a[0]=', a[0]
print 'a[1]=', a[1]
print 'len(a)=',len(a)
print 'a.size()=', a.size()
b = vector([1, 2, 3, 4])
print 'a=', a
print 'b=', b
print 'len(a)=',len(a)
print 'a.size()=', a.size()
b = vector([1, 2, 3, 4])
print 'a=', a
print 'b=', b
print 'a+b'
c = a + b
c.out()
print 'a+b'
c = a + b
c.out()
print '-a'
c = -a
c.out()
a.out()
print '-a'
c = -a
c.out()
a.out()
print 'a-b'
c = a - b
c.out()
print 'a-b'
c = a - b
c.out()
print 'a*1.2'
c = a*1.2
c.out()
print 'a*1.2'
c = a*1.2
c.out()
print '1.2*a'
c = 1.2*a
c.out()
print 'a=', a
print '1.2*a'
c = 1.2*a
c.out()
print 'a=', a
print 'dot(a,b) = ', dot(a,b)
print 'dot(b,a) = ', dot(b,a)
print 'dot(a,b) = ', dot(a,b)
print 'dot(b,a) = ', dot(b,a)
print 'a*b'
c = a*b
c.out()
print 'a/1.2'
c = a/1.2
c.out()
print 'a*b'
c = a*b
c.out()
print 'a/1.2'
c = a/1.2
c.out()
print 'a[0:2]'
c = a[0:2]
c.out()
print 'a[0:2]'
c = a[0:2]
c.out()
print 'a[2:5] = [9.0, 4.0, 5.0]'
a[2:5] = [9.0, 4.0, 5.0]
a.out()
print 'a[2:5] = [9.0, 4.0, 5.0]'
a[2:5] = [9.0, 4.0, 5.0]
a.out()
print 'sqrt(a)=',sqrt(a)
print 'pow(a, 2*ones(len(a)))=',pow(a, 2*ones(len(a)))
print 'pow(a, 2)=',pow(a, 2*ones(len(a)))
print 'sqrt(a)=',sqrt(a)
print 'pow(a, 2*ones(len(a)))=',pow(a, 2*ones(len(a)))
print 'pow(a, 2)=',pow(a, 2*ones(len(a)))
print 'ones(10)'
c = ones(10)
c.out()
print 'ones(10)'
c = ones(10)
c.out()
print 'zeros(10)'
c = zeros(10)
c.out()
print 'zeros(10)'
c = zeros(10)
c.out()
print 'del a'
del a
print 'del a'
del a
try:
a = randvec(11, 0., 2.)
a.out()
try:
a = randvec(11, 0., 2.)
a.out()
except: pass
except: pass

View File

@ -10,16 +10,35 @@ from geosolver.geometric import Point, Line, CoincidenceConstraint
from geosolver.vector import vector
from geosolver.diagnostic import diag_select, diag_print
def line_problem():
def line_problem1():
"""A problem with a Point, a Line and a CoincicentConstraint"""
problem = GeometricProblem(dimension=3)
problem.add_variable(Point('p1'),vector([0.0, 0.0, 0.0]))
problem.add_variable(Line('l1'),vector([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]))
return problem
def line_problem2():
"""A problem with a Point, a Line and a CoincicentConstraint"""
problem = GeometricProblem(dimension=3)
problem.add_variable(Point('p1'),vector([3.0, 2.0, 1.0]))
problem.add_variable(Line('l1'),vector([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]))
problem.add_constraint(CoincidenceConstraint(Point('p1'), Line('l1')))
return problem
def line_problem3():
"""A problem with a Point, a Line and a CoincicentConstraint"""
problem = GeometricProblem(dimension=3)
problem.add_variable(Point('p1'),vector([3.0, 2.0, 1.0]))
problem.add_variable(Point('p2'),vector([1.0, 1.0, 1.0]))
problem.add_variable(Line('l1'),vector([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]))
problem.add_constraint(CoincidenceConstraint(Point('p1'), Line('l1')))
problem.add_constraint(CoincidenceConstraint(Point('p2'), Line('l1')))
problem.add_constraint(DistanceConstraint(Point('p1'), Point('p2'), 5.0))
return problem
def test_line():
test(line_problem())
test(line_problem3())
if __name__ == "__main__":
test_line()

View File

@ -138,6 +138,13 @@ class DecompositionView(QtGui.QDialog):
def optimiseGraphLayout(self):
print "optimising graph layout..."
# force due to overlapping overlaps
force_cluster = 0.2
# force due to connection length
force_connection = 0.025
# force due to clusters overlapping connections
force_cluster_connection = 0.05
# create a graph of clusters and connections
graph = geosolver.graph.Graph()
if self.ui.graphicsScene != None:
@ -150,7 +157,7 @@ class DecompositionView(QtGui.QDialog):
l = list(graph.vertices())
# iteratively implove layout
# iteratively improve layout
for i in range(100):
# clear forces
for c in l:
@ -178,9 +185,11 @@ class DecompositionView(QtGui.QDialog):
norm = numpy.linalg.norm(direction)
if norm != 0:
direction = direction / numpy.linalg.norm(direction)
else:
direction = numpy.array([0,0])
#direction[1] = 0.0
c1.force += -force*direction / 10.0;
c2.force += force*direction / 10.0;
c1.force += -force*direction * force_cluster;
c2.force += force*direction * force_cluster;
#print "force 1", c1.force
#print "force 2", c2.force
@ -190,30 +199,43 @@ class DecompositionView(QtGui.QDialog):
c2 = e[1]
box1 = c1.boundingRect().translated(c1.position)
box2 = c2.boundingRect().translated(c2.position)
# force 1: pull together on x
centerdiff = box2.center()-box1.center()
direction = numpy.array([centerdiff.x(),centerdiff.y()])
direction = numpy.array([centerdiff.x(),0])
norm = numpy.linalg.norm(direction)
if norm != 0:
direction = direction / numpy.linalg.norm(direction)
#goal = max(box1.width(),box2.width())
goal = 5 * box1.height()
force = (norm - goal) / 50.0
#direction[1] = 0.0
else:
direction = numpy.array([0,0])
goal = 0
force = (norm - goal) * force_connection;
c1.force += +force*direction;
c2.force += -force*direction;
# force 2: keep y at distance and in layer order
direction = numpy.array([0, centerdiff.y()])
norm = numpy.linalg.norm(direction)
if norm != 0:
direction = direction / numpy.linalg.norm(direction)
else:
direction = numpy.array([0,0])
goal = box1.height() + box2.height()
force = (norm - goal) * force_connection;
c1.force += +force*direction;
c2.force += -force*direction;
#print "force ", force
# determine forces due to clusters overlapping connections
n = len(l)
for c in graph.vertices():
for e in graph.edges():
box1 = c.boundingRect().translated(c.position)
box1.setWidth(2*box1.width())
box1.setHeight(2*box1.height())
box1.setWidth(0.5*box1.width())
box1.setHeight(0.5*box1.height())
con = graph.get(e[0],e[1])
box2 = con.boundingRect()
box2.setWidth(2*box2.width())
box2.setHeight(2*box2.height())
box2.setWidth(0.5*box2.width())
box2.setHeight(0.5*box2.height())
#print "box 1", box1
#print "box 2", box2
if box1.intersects(box2):
@ -224,23 +246,26 @@ class DecompositionView(QtGui.QDialog):
norm = numpy.linalg.norm(direction)
if norm != 0:
direction = direction / numpy.linalg.norm(direction)
#direction[1] = 0.0
c.force += -force*direction / 50.0;
else:
direction = numpy.array([0,0])
c.force += -force*direction * force_cluster_connection;
e[0].force += force*direction * force_cluster_connection;
e[1].force += force*direction * force_cluster_connection;
#print "force 1", c1.force
#print "force 2", c2.force
# apply forces
for c in l:
move = QtCore.QPointF(c.force[0],c.force[1])
c.position += move
c.translate(move.x(), move.y())
# done iterating
# uppate connectors
for e in graph.edges():
connector = graph.get(e[0],e[1])
connector.determinePath()
# uppate connectors
for e in graph.edges():
connector = graph.get(e[0],e[1])
connector.determinePath()
# done iterating
print "done"
def updateViewports(self):