Moved Gaphor canvas (Gaphas in the right location)

This commit is contained in:
Arjan Molenaar 2006-03-19 19:57:19 +00:00
commit d5966e21f1
3 changed files with 456 additions and 0 deletions

103
constraint.py Normal file
View File

@ -0,0 +1,103 @@
'''equation solver using attributes and introspection.
Class Constraint from
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303396
'''
from __future__ import division
class Constraint(object):
'''takes a function, named arg value (opt.) and returns a Constraint object'''
def __init__(self,f,**args):
self._f=f
self._args={}
# see important note on order of operations in __setattr__ below.
for arg in f.func_code.co_varnames[0:f.func_code.co_argcount]:
self._args[arg]=None
self.set(**args)
def __repr__(self):
argstring=','.join(['%s=%s' % (arg,str(value)) for (arg,value) in
self._args.items()])
if argstring:
return 'Constraint(%s,%s)' % (self._f.func_code.co_name, argstring)
else:
return 'Constraint(%s)' % self._f.func_code.co_name
def __getattr__(self,name):
'''used to extract function argument values'''
self._args[name]
return self.solve_for(name)
def __setattr__(self,name,value):
'''sets function argument values'''
# Note - once self._args is created, no new attributes can
# be added to self.__dict__. This is a good thing as it throws
# an exception if you try to assign to an arg which is inappropriate
# for the function in the solver.
if self.__dict__.has_key('_args'):
if name in self._args:
self._args[name]=value
else:
raise KeyError, name
else:
object.__setattr__(self,name,value)
def set(self, **args):
'''sets values of function arguments'''
for arg in args:
self._args[arg] # raise exception if arg not in _args
setattr(self,arg,args[arg])
def solve_for(self,arg):
'''Newton's method solver'''
TOL=0.0000001 # tolerance
ITERLIMIT=1000 # iteration limit
CLOSE_RUNS=10 # after getting close, do more passes
args=self._args
if self._args[arg]:
x0=self._args[arg]
else:
x0=1
if x0==0:
x1=1
else:
x1=x0*1.1
def f(x):
'''function to solve'''
args[arg]=x
return self._f(**args)
fx0=f(x0)
n=0
while 1: # Newton's method loop here
fx1 = f(x1)
if fx1==0 or x1==x0: # managed to nail it exactly
break
if abs(fx1-fx0)<TOL: # very close
close_flag=True
if CLOSE_RUNS==0: # been close several times
break
else:
CLOSE_RUNS-=1 # try some more
else:
close_flag=False
if n>ITERLIMIT:
print "Failed to converge; exceeded iteration limit"
break
slope=(fx1-fx0)/(x1-x0)
if slope==0:
if close_flag: # we're close but have zero slope, finish
break
else:
print 'Zero slope and not close enough to solution'
break
x2=x0-fx0/slope # New 'x1'
fx0 = fx1
x0=x1
x1=x2
n+=1
self._args[arg]=x1
return x1

184
solver.py Normal file
View File

@ -0,0 +1,184 @@
from operator import isCallable
from constraint import Constraint
# Variable Strengths:
VERY_WEAK = 0
WEAK = 10
NORMAL = 20
STRONG = 30
VERY_STRONG = 40
REQUIRED = 100
class Variable(object):
"""Representation of a variable in the constraint solver.
Each Variable has a @value and a @strength. Ina constraint the
weakest variables are changed.
"""
def __init__(self, value=0.0, strength=NORMAL):
self._value = value
self._strength = strength
# These variables are set by the Solver:
self._solver = None
self._constraints = set()
strength = property(lambda s: s._strength)
def set_value(self, value):
self._value = value
if self._solver:
self._solver.mark_dirty(self)
value = property(lambda s: s._value, set_value)
def __str__(self):
return 'Variable(%f, %d)' % (self._value, self._strength)
__repr__ = __str__
class Solver(object):
"""Solve constraints. A constraint should have accompanying
variables.
"""
def __init__(self):
# a dict of constraint -> name/variable mappings
self._constraints = {}
self._marked_vars = []
self._marked_cons = []
def mark_dirty(self, variable):
"""Mark a variable as "dirty". This means it it solved the next time
the constraints are resolved.
Example:
>>> a,b,c = Variable(1.0), Variable(2.0), Variable(3.0)
>>> s=Solver()
>>> s.add_constraint(lambda a,b: a+b, a=a, b=b)
Constraint(<lambda>,a=None,b=None)
>>> s._marked_vars
[]
>>> s._marked_cons
[Constraint(<lambda>,a=None,b=None)]
>>> a.value=5.0
>>> s._marked_vars
[Variable(5.000000, 20)]
>>> b.value=2.0
>>> s._marked_vars
[Variable(5.000000, 20), Variable(2.000000, 20)]
>>> a.value=5.0
>>> s._marked_vars
[Variable(2.000000, 20), Variable(5.000000, 20)]
"""
if variable in self._marked_vars:
self._marked_vars.remove(variable)
self._marked_vars.append(variable)
for c in variable._constraints:
if c in self._marked_cons:
self._marked_cons.remove(c)
self._marked_cons.append(c)
def add_constraint(self, constraint, **variables):
"""Add a constraint.
The actual constraint is returned, so the constraint can be removed
later on.
Example:
>>> s = Solver()
>>> a, b = Variable(), Variable(2.0)
>>> s.add_constraint(lambda a, b: a -b, a=a, b=b)
Constraint(<lambda>,a=None,b=None)
>>> a.value
0.0
>>> b.value
2.0
>>> len(s._constraints)
1
"""
if isCallable(constraint):
constraint = Constraint(constraint)
self._constraints[constraint] = dict(variables)
self._marked_cons.append(constraint)
for v in variables.values():
v._constraints.add(constraint)
v._solver = self
return constraint
def remove_constraint(self, constraint):
""" Remove a constraint from the solver
"""
for v in self._constraints[constraint]: v._constraints.remove(constraint)
del self._constraints[constraint]
if self._marked_cons.get(constraint):
del self._marked_cons[constraint]
def weakest_variable(self, variables):
"""Returns the name(!) of the weakest variable.
Example:
>>> s = Solver()
>>> s.weakest_variable({'a': Variable(2.0, 30), 'b': Variable(2.0, 20)})
('b', Variable(2.000000, 20))
>>> a,b,c = Variable(), Variable(), Variable()
>>> s._marked_vars = [a, b, c]
"""
marked_vars = self._marked_vars
wname, wvar = None, None
for n, v in variables.items():
if not wvar or \
(v.strength < wvar.strength) or \
(v.strength == wvar.strength and \
(v not in marked_vars
or wvar in marked_vars and \
marked_vars.index(v) < marked_vars.index(wvar))):
wname, wvar = n, v
return wname, wvar
def solve(self):
"""
Example:
>>> a,b,c = Variable(1.0), Variable(2.0), Variable(3.0)
>>> s=Solver()
>>> s.add_constraint(lambda a,b: a+b, a=a, b=b)
Constraint(<lambda>,a=None,b=None)
>>> a.value=5.0
>>> s.solve()
>>> len(s._marked_cons)
0
>>> b._value
-5.0
>>> s.add_constraint(lambda a,b: a+b, a=b, b=c)
Constraint(<lambda>,a=None,b=None)
>>> len(s._constraints)
2
>>> len(s._marked_cons)
1
>>> s.solve()
>>> b._value
-5.0
"""
constraints = self._constraints
marked_cons = self._marked_cons
# Solve each constraint. Using a counter makes it
# possible to also solve constraints that are marked as
# a result of other variabled being solved.
n = 0
while n < len(marked_cons):
c = marked_cons[n]
wname, wvar = self.weakest_variable(constraints[c])
xx = {}
for nm, v in constraints[c].items():
xx[nm] = v.value
c.set(**xx)
wvar.value = c.solve_for(wname)
n += 1
self._marked_cons = []
self._marked_vars = []
if __name__ == '__main__':
import doctest
doctest.testmod()

169
tree.py Normal file
View File

@ -0,0 +1,169 @@
"""
The canvas base class.
"""
class Tree(object):
"""A Tree structure.
None is the root node.
@invariant: len(self._children) == len(self._nodes) + 1
"""
def __init__(self):
# List of nodes in the tree, sorted in the order they ought to be
# rendered
self._nodes = []
# Per entry a list of children is maintained.
self._children = { None: [] }
def get_parent(self, node):
"""Return the parent item of @node.
"""
for item, children in self._children.items():
if node in children:
return item
def get_siblings(self, node):
parent = self.get_parent(node)
return self._children[parent]
def get_next_sibling(self, node):
parent = self.get_parent(node)
siblings = self._children[parent]
return siblings[siblings.index(node) + 1]
def get_previous_sibling(self, node):
parent = self.get_parent(node)
siblings = self._children[parent]
return siblings[siblings.index(node) - 1]
def _add_to_nodes(self, node, parent):
"""Called only from add()
"""
nodes = self._nodes
if parent:
try:
next_uncle = self.get_next_sibling(parent)
except IndexError:
# parent has no younger brothers..
# place it before the next uncle of grant_parent:
self._add_to_nodes(node, self.get_parent(parent))
else:
nodes.insert(nodes.index(next_uncle), node)
else:
# append to root node:
nodes.append(node)
def add(self, node, parent=None):
"""Add @node to the tree. @parent is the parent node, which may
be None if the item should be added to the root item.
"""
assert not self._children.get(node)
nodes = self._nodes
siblings = self._children[parent]
self._add_to_nodes(node, parent)
siblings.append(node)
# Create new entry for it's own children:
self._children[node] = []
def remove(self, node):
"""Remove @node from the tree.
"""
# First remove children:
children = self._children[node]
for c in children:
self.remove(c)
# Remove from parent item
self.get_siblings(node).remove(node)
# Remove data entries:
del self._children[node]
self._nodes.remove(node)
def test_add():
print 'test_add'
tree = Tree()
n1 = 'n1'
n2 = 'n2'
n3 = 'n3'
tree.add(n1)
assert len(tree._nodes) == 1
assert len(tree._children) == 2
assert len(tree._children[None]) == 1
assert len(tree._children[n1]) == 0
tree.add(n2)
tree.add(n3, parent=n1)
assert len(tree._nodes) == 3
assert len(tree._children) == 4
assert len(tree._children[None]) == 2
assert len(tree._children[n1]) == 1
assert len(tree._children[n2]) == 0
assert len(tree._children[n2]) == 0
print tree._nodes
assert tree._nodes == [n1, n3, n2]
n4 = 'n4'
tree.add(n4, parent=n3)
print tree._nodes
assert tree._nodes == [n1, n3, n4, n2]
n5 = 'n5'
tree.add(n5, parent=n3)
print tree._nodes
assert tree._nodes == [n1, n3, n4, n5, n2]
n6 = 'n6'
tree.add(n6, parent=n2)
print tree._nodes
assert tree._nodes == [n1, n3, n4, n5, n2, n6]
n7 = 'n7'
tree.add(n7, parent=n1)
print tree._nodes
assert len(tree._children) == 8
assert tree._nodes == [n1, n3, n4, n5, n7, n2, n6]
assert tree.get_parent(n7) is n1
assert tree.get_parent(n6) is n2
assert tree.get_parent(n5) is n3
assert tree.get_parent(n4) is n3
assert tree.get_parent(n3) is n1
assert tree.get_parent(n2) is None
assert tree.get_parent(n1) is None
def test_remove():
print 'test_remove'
tree = Tree()
n1 = 'n1'
n2 = 'n2'
n3 = 'n3'
n4 = 'n4'
n5 = 'n5'
tree.add(n1)
tree.add(n2)
tree.add(n3, parent=n1)
tree.add(n4, parent=n3)
tree.add(n5, parent=n4)
assert tree._nodes == [n1, n3, n4, n5, n2]
tree.remove(n4)
assert tree._nodes == [n1, n3, n2]
tree.remove(n1)
assert len(tree._children) == 2
assert tree._children[None] == [n2]
assert tree._children[n2] == []
assert tree._nodes == [n2]
if __name__ == '__main__':
test_add()
test_remove()