Moved Gaphor canvas (Gaphas in the right location)
This commit is contained in:
commit
d5966e21f1
103
constraint.py
Normal file
103
constraint.py
Normal 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
184
solver.py
Normal 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
169
tree.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user