diff --git a/README.rst b/README.rst
index ab2eea6..77faca0 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,15 @@
Optimized Boids (2D)
====================
-run with python optboid.py
+There are two GUIs for the simulation.
+One based on PyGlet which must first be installed. Run this with
+
+python glboid.py
+
+The second is a curses (terminal) interface. Run with
+
+python curseboid.py
+
An optimized version of C. Reynolds flocking simulation which uses "boids"
with simple rules to reproduce the behaviour of flocking creatures.
diff --git a/curseboid.py b/curseboid.py
new file mode 100644
index 0000000..6547590
--- /dev/null
+++ b/curseboid.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+from __future__ import (division, print_function,
+ absolute_import)
+
+import curses
+import simulation
+import math
+from time import time
+
+
+class World(object):
+ """
+ world class with draw functions for entities
+ """
+ def __init__(self, swarm, width):
+ self.width = width
+ self.swarm = swarm
+ self.ents = swarm.boids # this will point to a list of boids
+ self.ent_char = '^'
+ self.num_ents = len(swarm.boids)
+ #self.fps = clock.ClockDisplay()
+ self._basescr = curses.initscr()
+ self.scr = curses.newwin(80, 80, 0, 0)
+ self.scr.border(0)
+ self.scr.refresh()
+
+ self._update_time = 0
+ self._fps = 0
+
+ def draw_entity(self, e):
+ """ Draws a boid """
+ x = int(math.floor(e.position.x / self.width))
+ y = int(math.floor(e.position.y / self.width))
+ self.scr.addstr(x, y, self.ent_char)
+
+ def draw_fps(self, dt):
+ self._update_time += dt
+ if self._update_time > 0.5:
+ self._fps = 1 / dt
+ self._update_time = 0
+
+ self.scr.addstr(3, 3, '%.1f fps' % self._fps)
+ self.scr.refresh()
+
+ def draw(self, dt):
+ self.scr.clear()
+ #self.draw_grid()
+ for ent in self.ents:
+ self.draw_entity(ent)
+ self.draw_fps(dt)
+ self.scr.refresh()
+
+
+sim = simulation.FlockSimulation(100, 700)
+world = World(sim.swarm, 10)
+
+
+def run():
+ go = True
+ prev_time = time()
+ print('starting')
+ try:
+ while go:
+ now_time = time()
+ dt = now_time - prev_time
+ prev_time = now_time
+ sim.update(dt)
+ world.draw(dt)
+ #x = world.scr.getkey()
+ #if x == 'e':
+ # go = False
+ except KeyboardInterrupt:
+ curses.endwin()
+
+
+if __name__ == '__main__':
+ run()
diff --git a/euclid.py b/euclid.py
deleted file mode 100644
index 10e962c..0000000
--- a/euclid.py
+++ /dev/null
@@ -1,1901 +0,0 @@
-#!/usr/bin/env python
-#
-# euclid graphics maths module
-#
-# Copyright (c) 2006 Alex Holkner
-# Alex.Holkner@mail.google.com
-#
-# This library is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation; either version 2.1 of the License, or (at your
-# option) any later version.
-#
-# This library is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
-# for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this library; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-'''euclid graphics maths module
-
-Documentation and tests are included in the file "euclid.txt", or online
-at http://code.google.com/p/pyeuclid
-'''
-
-__docformat__ = 'restructuredtext'
-__version__ = '$Id$'
-__revision__ = '$Revision: 1.3 $'
-
-import math
-import operator
-import types
-
-# Some magic here. If _use_slots is True, the classes will derive from
-# object and will define a __slots__ class variable. If _use_slots is
-# False, classes will be old-style and will not define __slots__.
-#
-# _use_slots = True: Memory efficient, probably faster in future versions
-# of Python, "better".
-# _use_slots = False: Ordinary classes, much faster than slots in current
-# versions of Python (2.4 and 2.5).
-_use_slots = True
-
-# If True, allows components of Vector2 and Vector3 to be set via swizzling;
-# e.g. v.xyz = (1, 2, 3). This is much, much slower than the more verbose
-# v.x = 1; v.y = 2; v.z = 3, and slows down ordinary element setting as
-# well. Recommended setting is False.
-_enable_swizzle_set = False
-
-# Requires class to derive from object.
-if _enable_swizzle_set:
- _use_slots = True
-
-# Implement _use_slots magic.
-class _EuclidMetaclass(type):
- def __new__(cls, name, bases, dct):
- if _use_slots:
- return type.__new__(cls, name, bases + (object,), dct)
- else:
- del dct['__slots__']
- return types.ClassType.__new__(types.ClassType, name, bases, dct)
-__metaclass__ = _EuclidMetaclass
-
-class Vector2(object):
- __slots__ = ['x', 'y']
-
- def __init__(self, x, y):
- self.x = x
- self.y = y
-
- def __copy__(self):
- return self.__class__(self.x, self.y)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Vector2(%.4f, %.4f)' % (self.x, self.y)
-
- def __eq__(self, other):
- if isinstance(other, Vector2):
- return self.x == other.x and \
- self.y == other.y
- else:
- assert hasattr(other, '__len__') and len(other) == 2
- return self.x == other[0] and \
- self.y == other[1]
-
- def __neq__(self, other):
- return not self.__eq__(other)
-
- def __nonzero__(self):
- return self.x != 0 or self.y != 0
-
- def __len__(self):
- return 2
-
- def __getitem__(self, key):
- return (self.x, self.y)[key]
-
- def __setitem__(self, key, value):
- l = [self.x, self.y]
- l[key] = value
- self.x, self.y = l
-
- def __iter__(self):
- return iter((self.x, self.y))
-
- def __getattr__(self, name):
- try:
- return tuple([(self.x, self.y)['xy'.index(c)] \
- for c in name])
- except ValueError:
- raise AttributeError, name
-
- if _enable_swizzle_set:
- # This has detrimental performance on ordinary setattr as well
- # if enabled
- def __setattr__(self, name, value):
- if len(name) == 1:
- object.__setattr__(self, name, value)
- else:
- try:
- l = [self.x, self.y]
- for c, v in map(None, name, value):
- l['xy'.index(c)] = v
- self.x, self.y = l
- except ValueError:
- raise AttributeError, name
-
- def __add__(self, other):
- if isinstance(other, Vector2):
- return Vector2(self.x + other.x,
- self.y + other.y)
- else:
- assert hasattr(other, '__len__') and len(other) == 2
- return Vector2(self.x + other[0],
- self.y + other[1])
- __radd__ = __add__
-
- def __iadd__(self, other):
- if isinstance(other, Vector2):
- self.x += other.x
- self.y += other.y
- else:
- self.x += other[0]
- self.y += other[1]
- return self
-
- def __sub__(self, other):
- if isinstance(other, Vector2):
- return Vector2(self.x - other.x,
- self.y - other.y)
- else:
- assert hasattr(other, '__len__') and len(other) == 2
- return Vector2(self.x - other[0],
- self.y - other[1])
-
-
- def __rsub__(self, other):
- if isinstance(other, Vector2):
- return Vector2(other.x - self.x,
- other.y - self.y)
- else:
- assert hasattr(other, '__len__') and len(other) == 2
- return Vector2(other.x - self[0],
- other.y - self[1])
-
- def __mul__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(self.x * other,
- self.y * other)
-
- __rmul__ = __mul__
-
- def __imul__(self, other):
- assert type(other) in (int, long, float)
- self.x *= other
- self.y *= other
- return self
-
- def __div__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(operator.div(self.x, other),
- operator.div(self.y, other))
-
-
- def __rdiv__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(operator.div(other, self.x),
- operator.div(other, self.y))
-
- def __floordiv__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(operator.floordiv(self.x, other),
- operator.floordiv(self.y, other))
-
-
- def __rfloordiv__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(operator.floordiv(other, self.x),
- operator.floordiv(other, self.y))
-
- def __truediv__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(operator.truediv(self.x, other),
- operator.truediv(self.y, other))
-
-
- def __rtruediv__(self, other):
- assert type(other) in (int, long, float)
- return Vector2(operator.truediv(other, self.x),
- operator.truediv(other, self.y))
-
- def __neg__(self):
- return Vector2(-self.x,
- -self.y)
-
- __pos__ = __copy__
-
- def __abs__(self):
- return math.sqrt(self.x ** 2 + \
- self.y ** 2)
-
- magnitude = __abs__
-
- def clear(self):
- self.x = 0
- self.y = 0
-
- def magnitude_squared(self):
- return self.x ** 2 + \
- self.y ** 2
-
- def normalize(self):
- d = self.magnitude()
- if d:
- self.x /= d
- self.y /= d
- return self
-
- def normalized(self):
- d = self.magnitude()
- if d:
- return Vector2(self.x / d,
- self.y / d)
- return self.copy()
-
- def dot(self, other):
- assert isinstance(other, Vector2)
- return self.x * other.x + \
- self.y * other.y
-
- def cross(self):
- return Vector2(self.y, -self.x)
-
- def reflect(self, normal):
- # assume normal is normalized
- assert isinstance(normal, Vector2)
- d = 2 * (self.x * normal.x + self.y * normal.y)
- return Vector2(self.x - d * normal.x,
- self.y - d * normal.y)
-
-class Vector3(object):
- __slots__ = ['x', 'y', 'z']
-
- def __init__(self, x, y, z):
- self.x = x
- self.y = y
- self.z = z
-
- def __copy__(self):
- return self.__class__(self.x, self.y, self.z)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Vector3(%.2f, %.2f, %.2f)' % (self.x,
- self.y,
- self.z)
-
- def __eq__(self, other):
- if isinstance(other, Vector3):
- return self.x == other.x and \
- self.y == other.y and \
- self.z == other.z
- else:
- assert hasattr(other, '__len__') and len(other) == 3
- return self.x == other[0] and \
- self.y == other[1] and \
- self.z == other[2]
-
- def __neq__(self, other):
- return not self.__eq__(other)
-
- def __nonzero__(self):
- return self.x != 0 or self.y != 0 or self.z != 0
-
- def __len__(self):
- return 3
-
- def __getitem__(self, key):
- return (self.x, self.y, self.z)[key]
-
- def __setitem__(self, key, value):
- l = [self.x, self.y, self.z]
- l[key] = value
- self.x, self.y, self.z = l
-
- def __iter__(self):
- return iter((self.x, self.y, self.z))
-
- def __getattr__(self, name):
- try:
- return tuple([(self.x, self.y, self.z)['xyz'.index(c)] \
- for c in name])
- except ValueError:
- raise AttributeError, name
-
- if _enable_swizzle_set:
- # This has detrimental performance on ordinary setattr as well
- # if enabled
- def __setattr__(self, name, value):
- if len(name) == 1:
- object.__setattr__(self, name, value)
- else:
- try:
- l = [self.x, self.y, self.z]
- for c, v in map(None, name, value):
- l['xyz'.index(c)] = v
- self.x, self.y, self.z = l
- except ValueError:
- raise AttributeError, name
-
-
- def __add__(self, other):
- if isinstance(other, Vector3):
- return Vector3(self.x + other.x,
- self.y + other.y,
- self.z + other.z)
- else:
- assert hasattr(other, '__len__') and len(other) == 3
- return Vector3(self.x + other[0],
- self.y + other[1],
- self.z + other[2])
- __radd__ = __add__
-
- def __iadd__(self, other):
- if isinstance(other, Vector3):
- self.x += other.x
- self.y += other.y
- self.z += other.z
- else:
- self.x += other[0]
- self.y += other[1]
- self.z += other[2]
- return self
-
- def __sub__(self, other):
- if isinstance(other, Vector3):
- return Vector3(self.x - other.x,
- self.y - other.y,
- self.z - other.z)
- else:
- assert hasattr(other, '__len__') and len(other) == 3
- return Vector3(self.x - other[0],
- self.y - other[1],
- self.z - other[2])
-
-
- def __rsub__(self, other):
- if isinstance(other, Vector3):
- return Vector3(other.x - self.x,
- other.y - self.y,
- other.z - self.z)
- else:
- assert hasattr(other, '__len__') and len(other) == 3
- return Vector3(other.x - self[0],
- other.y - self[1],
- other.z - self[2])
-
- def __mul__(self, other):
- if isinstance(other, Vector3):
- return Vector3(self.x * other.x,
- self.y * other.y,
- self.z * other.z)
- assert type(other) in (int, long, float)
- return Vector3(self.x * other,
- self.y * other,
- self.z * other)
-
- __rmul__ = __mul__
-
- def __imul__(self, other):
- assert type(other) in (int, long, float)
- self.x *= other
- self.y *= other
- self.z *= other
- return self
-
- def __div__(self, other):
- assert type(other) in (int, long, float)
- return Vector3(operator.div(self.x, other),
- operator.div(self.y, other),
- operator.div(self.z, other))
-
-
- def __rdiv__(self, other):
- assert type(other) in (int, long, float)
- return Vector3(operator.div(other, self.x),
- operator.div(other, self.y),
- operator.div(other, self.z))
-
- def __floordiv__(self, other):
- assert type(other) in (int, long, float)
- return Vector3(operator.floordiv(self.x, other),
- operator.floordiv(self.y, other),
- operator.floordiv(self.z, other))
-
-
- def __rfloordiv__(self, other):
- assert type(other) in (int, long, float)
- return Vector3(operator.floordiv(other, self.x),
- operator.floordiv(other, self.y),
- operator.floordiv(other, self.z))
-
- def __truediv__(self, other):
- assert type(other) in (int, long, float)
- return Vector3(operator.truediv(self.x, other),
- operator.truediv(self.y, other),
- operator.truediv(self.z, other))
-
-
- def __rtruediv__(self, other):
- assert type(other) in (int, long, float)
- return Vector3(operator.truediv(other, self.x),
- operator.truediv(other, self.y),
- operator.truediv(other, self.z))
-
- def __neg__(self):
- return Vector3(-self.x,
- -self.y,
- -self.z)
-
- __pos__ = __copy__
-
- def __abs__(self):
- return math.sqrt(self.x ** 2 + \
- self.y ** 2 + \
- self.z ** 2)
-
- magnitude = __abs__
-
- def magnitude_squared(self):
- return self.x ** 2 + \
- self.y ** 2 + \
- self.z ** 2
-
- def normalize(self):
- d = self.magnitude()
- if d:
- self.x /= d
- self.y /= d
- self.z /= d
- return self
-
- def normalized(self):
- d = self.magnitude()
- if d:
- return Vector3(self.x / d,
- self.y / d,
- self.z / d)
- return self.copy()
-
- def dot(self, other):
- assert isinstance(other, Vector3)
- return self.x * other.x + \
- self.y * other.y + \
- self.z * other.z
-
- def cross(self, other):
- assert isinstance(other, Vector3)
- return Vector3(self.y * other.z - self.z * other.y,
- -self.x * other.z + self.z * other.x,
- self.x * other.y - self.y * other.x)
-
- def reflect(self, normal):
- # assume normal is normalized
- assert isinstance(normal, Vector3)
- d = 2 * (self.x * normal.x + self.y * normal.y + self.z * normal.z)
- return Vector3(self.x - d * normal.x,
- self.y - d * normal.y,
- self.z - d * normal.z)
-
-# a b c
-# e f g
-# i j k
-
-class Matrix3(object):
- __slots__ = list('abcefgijk')
-
- def __init__(self):
- self.identity()
-
- def __copy__(self):
- M = Matrix3()
- M.a = self.a
- M.b = self.b
- M.c = self.c
- M.e = self.e
- M.f = self.f
- M.g = self.g
- M.i = self.i
- M.j = self.j
- M.k = self.k
- return M
-
- copy = __copy__
- def __repr__(self):
- return ('Matrix3([% 8.2f % 8.2f % 8.2f\n' \
- ' % 8.2f % 8.2f % 8.2f\n' \
- ' % 8.2f % 8.2f % 8.2f])') \
- % (self.a, self.b, self.c,
- self.e, self.f, self.g,
- self.i, self.j, self.k)
-
- def __getitem__(self, key):
- return [self.a, self.e, self.i,
- self.b, self.f, self.j,
- self.c, self.g, self.k][key]
-
- def __setitem__(self, key, value):
- L = self[:]
- L[key] = value
- (self.a, self.e, self.i,
- self.b, self.f, self.j,
- self.c, self.g, self.k) = L
-
- def __mul__(self, other):
- A = self
- B = other
- if isinstance(other, Matrix3):
- C = Matrix3()
- C.a = A.a * B.a + A.b * B.e + A.c * B.i
- C.b = A.a * B.b + A.b * B.f + A.c * B.j
- C.c = A.a * B.c + A.b * B.g + A.c * B.k
- C.e = A.e * B.a + A.f * B.e + A.g * B.i
- C.f = A.e * B.b + A.f * B.f + A.g * B.j
- C.g = A.e * B.c + A.f * B.g + A.g * B.k
- C.i = A.i * B.a + A.j * B.e + A.k * B.i
- C.j = A.i * B.b + A.j * B.f + A.k * B.j
- C.k = A.i * B.c + A.j * B.g + A.k * B.k
- return C
- elif isinstance(other, Point2):
- P = Point2(0, 0)
- P.x = A.a * B.x + A.b * B.y + A.c
- P.y = A.e * B.x + A.f * B.y + A.g
- return P
- elif isinstance(other, Vector2):
- V = Vector2(0, 0)
- V.x = A.a * B.x + A.b * B.y
- V.y = A.e * B.x + A.f * B.y
- return V
- else:
- other = other.copy()
- other._apply_transform(self)
- return other
-
- def __imul__(self, other):
- assert isinstance(other, Matrix3)
- a = self.a
- b = self.b
- c = self.c
- e = self.e
- f = self.f
- g = self.g
- i = self.i
- j = self.j
- k = self.k
- self.a = a * other.a + b * other.e + c * other.i
- self.b = a * other.b + b * other.f + c * other.j
- self.c = a * other.c + b * other.g + c * other.k
- self.e = e * other.a + f * other.e + g * other.i
- self.f = e * other.b + f * other.f + g * other.j
- self.g = e * other.c + f * other.g + g * other.k
- self.i = i * other.a + j * other.e + k * other.i
- self.j = i * other.b + j * other.f + k * other.j
- self.k = i * other.c + j * other.g + k * other.k
- return self
-
- def identity(self):
- self.a = self.f = self.k = 1.
- self.b = self.c = self.e = self.g = self.i = self.j = 0
- return self
-
- def scale(self, x, y):
- self *= Matrix3.new_scale(x, y)
- return self
-
- def translate(self, x, y):
- self *= Matrix3.new_translate(x, y)
- return self
-
- def rotate(self, angle):
- self *= Matrix3.new_rotate(angle)
- return self
-
- # Static constructors
- def new_identity(cls):
- self = cls()
- return self
- new_identity = classmethod(new_identity)
-
- def new_scale(cls, x, y):
- self = cls()
- self.a = x
- self.f = y
- return self
- new_scale = classmethod(new_scale)
-
- def new_translate(cls, x, y):
- self = cls()
- self.c = x
- self.g = y
- return self
- new_translate = classmethod(new_translate)
-
- def new_rotate(cls, angle):
- self = cls()
- s = math.sin(angle)
- c = math.cos(angle)
- self.a = self.f = c
- self.b = -s
- self.e = s
- return self
- new_rotate = classmethod(new_rotate)
-
-# a b c d
-# e f g h
-# i j k l
-# m n o p
-
-class Matrix4(object):
- __slots__ = list('abcdefghijklmnop')
-
- def __init__(self):
- self.identity()
-
- def __copy__(self):
- M = Matrix4()
- M.a = self.a
- M.b = self.b
- M.c = self.c
- M.d = self.d
- M.e = self.e
- M.f = self.f
- M.g = self.g
- M.h = self.h
- M.i = self.i
- M.j = self.j
- M.k = self.k
- M.l = self.l
- M.m = self.m
- M.n = self.n
- M.o = self.o
- M.p = self.p
- return M
-
- copy = __copy__
-
- def __repr__(self):
- return ('Matrix4([% 8.2f % 8.2f % 8.2f % 8.2f\n' \
- ' % 8.2f % 8.2f % 8.2f % 8.2f\n' \
- ' % 8.2f % 8.2f % 8.2f % 8.2f\n' \
- ' % 8.2f % 8.2f % 8.2f % 8.2f])') \
- % (self.a, self.b, self.c, self.d,
- self.e, self.f, self.g, self.h,
- self.i, self.j, self.k, self.l,
- self.m, self.n, self.o, self.p)
-
- def __getitem__(self, key):
- return [self.a, self.e, self.i, self.m,
- self.b, self.f, self.j, self.n,
- self.c, self.g, self.k, self.o,
- self.d, self.h, self.l, self.p][key]
-
- def __setitem__(self, key, value):
- assert not isinstance(key, slice) or \
- key.stop - key.start == len(value), 'key length != value length'
- L = self[:]
- L[key] = value
- (self.a, self.e, self.i, self.m,
- self.b, self.f, self.j, self.n,
- self.c, self.g, self.k, self.o,
- self.d, self.h, self.l, self.p) = L
-
- def __mul__(self, other):
- A = self
- B = other
- if isinstance(other, Matrix4):
- C = Matrix4()
- C.a = A.a * B.a + A.b * B.e + A.c * B.i + A.d * B.m
- C.b = A.a * B.b + A.b * B.f + A.c * B.j + A.d * B.n
- C.c = A.a * B.c + A.b * B.g + A.c * B.k + A.d * B.o
- C.d = A.a * B.d + A.b * B.h + A.c * B.l + A.d * B.p
- C.e = A.e * B.a + A.f * B.e + A.g * B.i + A.h * B.m
- C.f = A.e * B.b + A.f * B.f + A.g * B.j + A.h * B.n
- C.g = A.e * B.c + A.f * B.g + A.g * B.k + A.h * B.o
- C.h = A.e * B.d + A.f * B.h + A.g * B.l + A.h * B.p
- C.i = A.i * B.a + A.j * B.e + A.k * B.i + A.l * B.m
- C.j = A.i * B.b + A.j * B.f + A.k * B.j + A.l * B.n
- C.k = A.i * B.c + A.j * B.g + A.k * B.k + A.l * B.o
- C.l = A.i * B.d + A.j * B.h + A.k * B.l + A.l * B.p
- C.m = A.m * B.a + A.n * B.e + A.o * B.i + A.p * B.m
- C.n = A.m * B.b + A.n * B.f + A.o * B.j + A.p * B.n
- C.o = A.m * B.c + A.n * B.g + A.o * B.k + A.p * B.o
- C.p = A.m * B.d + A.n * B.h + A.o * B.l + A.p * B.p
- return C
- elif isinstance(other, Point3):
- P = Point3(0, 0, 0)
- P.x = A.a * B.x + A.b * B.y + A.c * B.z + A.d
- P.y = A.e * B.x + A.f * B.y + A.g * B.z + A.h
- P.z = A.i * B.x + A.j * B.y + A.k * B.z + A.l
- return P
- elif isinstance(other, Vector3):
- V = Vector3(0, 0, 0)
- V.x = A.a * B.x + A.b * B.y + A.c * B.z
- V.y = A.e * B.x + A.f * B.y + A.g * B.z
- V.z = A.i * B.x + A.j * B.y + A.k * B.z
- return V
- else:
- other = other.copy()
- other._apply_transform(self)
- return other
-
- def __imul__(self, other):
- assert isinstance(other, Matrix4)
- a = self.a
- b = self.b
- c = self.c
- d = self.d
- e = self.e
- f = self.f
- g = self.g
- h = self.h
- i = self.i
- j = self.j
- k = self.k
- l = self.l
- m = self.m
- n = self.n
- o = self.o
- p = self.p
- self.a = a * other.a + b * other.e + c * other.i + d * other.m
- self.b = a * other.b + b * other.f + c * other.j + d * other.n
- self.c = a * other.c + b * other.g + c * other.k + d * other.o
- self.d = a * other.d + b * other.h + c * other.l + d * other.p
- self.e = e * other.a + f * other.e + g * other.i + h * other.m
- self.f = e * other.b + f * other.f + g * other.j + h * other.n
- self.g = e * other.c + f * other.g + g * other.k + h * other.o
- self.h = e * other.d + f * other.h + g * other.l + h * other.p
- self.i = i * other.a + j * other.e + k * other.i + l * other.m
- self.j = i * other.b + j * other.f + k * other.j + l * other.n
- self.k = i * other.c + j * other.g + k * other.k + l * other.o
- self.l = i * other.d + j * other.h + k * other.l + l * other.p
- self.m = m * other.a + n * other.e + o * other.i + p * other.m
- self.n = m * other.b + n * other.f + o * other.j + p * other.n
- self.o = m * other.c + n * other.g + o * other.k + p * other.o
- self.p = m * other.d + n * other.h + o * other.l + p * other.p
- return self
-
- def identity(self):
- self.a = self.f = self.k = self.p = 1.
- self.b = self.c = self.d = self.e = self.g = self.h = \
- self.i = self.j = self.l = self.m = self.n = self.o = 0
- return self
-
- def scale(self, x, y, z):
- self *= Matrix4.new_scale(x, y, z)
- return self
-
- def translate(self, x, y, z):
- self *= Matrix4.new_translate(x, y, z)
- return self
-
- def rotatex(self, angle):
- self *= Matrix4.new_rotatex(angle)
- return self
-
- def rotatey(self, angle):
- self *= Matrix4.new_rotatey(angle)
- return self
-
- def rotatez(self, angle):
- self *= Matrix4.new_rotatez(angle)
- return self
-
- def rotate_axis(self, angle, axis):
- self *= Matrix4.new_rotate_axis(angle, axis)
- return self
-
- def rotate_euler(self, heading, attitude, bank):
- self *= Matrix4.new_rotate_euler(heading, attitude, bank)
- return self
-
- # Static constructors
- def new_identity(cls):
- self = cls()
- return self
- new_identity = classmethod(new_identity)
-
- def new_scale(cls, x, y, z):
- self = cls()
- self.a = x
- self.f = y
- self.k = z
- return self
- new_scale = classmethod(new_scale)
-
- def new_translate(cls, x, y, z):
- self = cls()
- self.d = x
- self.h = y
- self.l = z
- return self
- new_translate = classmethod(new_translate)
-
- def new_rotatex(cls, angle):
- self = cls()
- s = math.sin(angle)
- c = math.cos(angle)
- self.f = self.k = c
- self.g = -s
- self.j = s
- return self
- new_rotatex = classmethod(new_rotatex)
-
- def new_rotatey(cls, angle):
- self = cls()
- s = math.sin(angle)
- c = math.cos(angle)
- self.a = self.k = c
- self.c = s
- self.i = -s
- return self
- new_rotatey = classmethod(new_rotatey)
-
- def new_rotatez(cls, angle):
- self = cls()
- s = math.sin(angle)
- c = math.cos(angle)
- self.a = self.f = c
- self.b = -s
- self.e = s
- return self
- new_rotatez = classmethod(new_rotatez)
-
- def new_rotate_axis(cls, angle, axis):
- assert(isinstance(axis, Vector3))
- vector = axis.normalized()
- x = vector.x
- y = vector.y
- z = vector.z
-
- self = cls()
- s = math.sin(angle)
- c = math.cos(angle)
- c1 = 1. - c
-
- # from the glRotate man page
- self.a = x * x * c1 + c
- self.b = x * y * c1 - z * s
- self.c = x * z * c1 + y * s
- self.e = y * x * c1 + z * s
- self.f = y * y * c1 + c
- self.g = y * z * c1 - x * s
- self.i = x * z * c1 - y * s
- self.j = y * z * c1 + x * s
- self.k = z * z * c1 + c
- return self
- new_rotate_axis = classmethod(new_rotate_axis)
-
- def new_rotate_euler(cls, heading, attitude, bank):
- # from http://www.euclideanspace.com/
- ch = math.cos(heading)
- sh = math.sin(heading)
- ca = math.cos(attitude)
- sa = math.sin(attitude)
- cb = math.cos(bank)
- sb = math.sin(bank)
-
- self = cls()
- self.a = ch * ca
- self.b = sh * sb - ch * sa * cb
- self.c = ch * sa * sb + sh * cb
- self.e = sa
- self.f = ca * cb
- self.g = -ca * sb
- self.i = -sh * ca
- self.j = sh * sa * cb + ch * sb
- self.k = -sh * sa * sb + ch * cb
- return self
- new_rotate_euler = classmethod(new_rotate_euler)
-
- def new_perspective(cls, fov_y, aspect, near, far):
- # from the gluPerspective man page
- f = 1 / math.tan(fov_y / 2)
- self = cls()
- assert near != 0.0 and near != far
- self.a = f / aspect
- self.f = f
- self.k = (far + near) / (near - far)
- self.l = 2 * far * near / (near - far)
- self.o = -1
- self.p = 0
- return self
- new_perspective = classmethod(new_perspective)
-
-class Quaternion(object):
- # All methods and naming conventions based off
- # http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions
-
- # w is the real part, (x, y, z) are the imaginary parts
- __slots__ = ['w', 'x', 'y', 'z']
-
- def __init__(self):
- self.identity()
-
- def __copy__(self):
- Q = Quaternion()
- Q.w = self.w
- Q.x = self.x
- Q.y = self.y
- Q.z = self.z
-
- copy = __copy__
-
- def __repr__(self):
- return 'Quaternion(real=%.2f, imag=<%.2f, %.2f, %.2f>)' % \
- (self.w, self.x, self.y, self.z)
-
- def __mul__(self, other):
- if isinstance(other, Quaternion):
- A = self
- B = other
- Q = Quaternion()
- Q.x = A.x * B.w + A.y * B.z - A.z * B.y + A.w * B.x
- Q.y = -A.x * B.z + A.y * B.w + A.z * B.x + A.w * B.y
- Q.z = A.x * B.y - A.y * B.x + A.z * B.w + A.w * B.z
- Q.w = -A.x * B.x - A.y * B.y - A.z * B.z + A.w * B.w
- return Q
- elif isinstance(other, Vector3):
- V = other
- w = self.w
- x = self.x
- y = self.y
- z = self.z
- return other.__class__(\
- w * w * V.x + 2 * y * w * V.z - 2 * z * w * V.y + \
- x * x * V.x + 2 * y * x * V.y + 2 * z * x * V.z - \
- z * z * V.x - y * y * V.x,
- 2 * x * y * V.x + y * y * V.y + 2 * z * y * V.z + \
- 2 * w * z * V.x - z * z * V.y + w * w * V.y - \
- 2 * x * w * V.z - x * x * V.y,
- 2 * x * z * V.x + 2 * y * z * V.y + \
- z * z * V.z - 2 * w * y * V.x - y * y * V.z + \
- 2 * w * x * V.y - x * x * V.z + w * w * V.z)
- else:
- other = other.copy()
- other._apply_transform(self)
- return other
-
- def __imul__(self, other):
- assert isinstance(other, Quaternion)
- x = self.x
- y = self.y
- z = self.z
- w = self.w
- self.x = x * other.w + y * other.z - z * other.y + w * other.x
- self.y = -x * other.z + y * other.w + z * other.x + w * other.y
- self.z = x * other.y - y * other.x + z * other.w + w * other.z
- self.w = -x * other.x - y * other.y - z * other.z + w * other.w
- return self
-
- def __abs__(self):
- return math.sqrt(self.w ** 2 + \
- self.x ** 2 + \
- self.y ** 2 + \
- self.z ** 2)
-
- magnitude = __abs__
-
- def magnitude_squared(self):
- return self.w ** 2 + \
- self.x ** 2 + \
- self.y ** 2 + \
- self.z ** 2
-
- def identity(self):
- self.w = 1
- self.x = 0
- self.y = 0
- self.z = 0
- return self
-
- def rotate_axis(self, angle, axis):
- self *= Quaternion.new_rotate_axis(angle, axis)
- return self
-
- def rotate_euler(self, heading, attitude, bank):
- self *= Quaternion.new_rotate_euler(heading, attitude, bank)
- return self
-
- def conjugated(self):
- Q = Quaternion()
- Q.w = self.w
- Q.x = -self.x
- Q.y = -self.y
- Q.z = -self.z
- return Q
-
- def normalize(self):
- d = self.magnitude()
- if d != 0:
- self.w /= d
- self.x /= d
- self.y /= d
- self.z /= d
- return self
-
- def normalized(self):
- d = self.magnitude()
- if d != 0:
- Q = Quaternion()
- Q.w /= d
- Q.x /= d
- Q.y /= d
- Q.z /= d
- return Q
- else:
- return self.copy()
-
- def get_angle_axis(self):
- if self.w > 1:
- self = self.normalized()
- angle = 2 * math.acos(self.w)
- s = math.sqrt(1 - self.w ** 2)
- if s < 0.001:
- return angle, Vector3(1, 0, 0)
- else:
- return angle, Vector3(self.x / s, self.y / s, self.z / s)
-
- def get_euler(self):
- t = self.x * self.y + self.z * self.w
- if t > 0.4999:
- heading = 2 * math.atan2(self.x, self.w)
- attitude = math.pi / 2
- bank = 0
- elif t < -0.4999:
- heading = -2 * math.atan2(self.x, self.w)
- attitude = -math.pi / 2
- bank = 0
- else:
- sqx = self.x ** 2
- sqy = self.y ** 2
- sqz = self.z ** 2
- heading = math.atan2(2 * self.y * self.w - 2 * self.x * self.z,
- 1 - 2 * sqy - 2 * sqz)
- attitude = math.asin(2 * t)
- bank = math.atan2(2 * self.x * self.w - 2 * self.y * self.z,
- 1 - 2 * sqx - 2 * sqz)
- return heading, attitude, bank
-
- def get_matrix(self):
- xx = self.x ** 2
- xy = self.x * self.y
- xz = self.x * self.z
- xw = self.x * self.w
- yy = self.y ** 2
- yz = self.y * self.z
- yw = self.y * self.w
- zz = self.z ** 2
- zw = self.z * self.w
- M = Matrix4()
- M.a = 1 - 2 * (yy + zz)
- M.b = 2 * (xy - zw)
- M.c = 2 * (xz + yw)
- M.e = 2 * (xy + zw)
- M.f = 1 - 2 * (xx + zz)
- M.g = 2 * (yz - xw)
- M.i = 2 * (xz - yw)
- M.j = 2 * (yz + xw)
- M.k = 1 - 2 * (xx + yy)
- return M
-
- # Static constructors
- def new_identity(cls):
- return cls()
- new_identity = classmethod(new_identity)
-
- def new_rotate_axis(cls, angle, axis):
- assert(isinstance(axis, Vector3))
- axis = axis.normalized()
- s = math.sin(angle / 2)
- Q = cls()
- Q.w = math.cos(angle / 2)
- Q.x = axis.x * s
- Q.y = axis.y * s
- Q.z = axis.z * s
- return Q
- new_rotate_axis = classmethod(new_rotate_axis)
-
- def new_rotate_euler(cls, heading, attitude, bank):
- Q = cls()
- c1 = math.cos(heading / 2)
- s1 = math.sin(heading / 2)
- c2 = math.cos(attitude / 2)
- s2 = math.sin(attitude / 2)
- c3 = math.cos(bank / 2)
- s3 = math.sin(bank / 2)
-
- Q.w = c1 * c2 * c3 - s1 * s2 * s3
- Q.x = s1 * s2 * c3 + c1 * c2 * s3
- Q.y = s1 * c2 * c3 + c1 * s2 * s3
- Q.z = c1 * s2 * c3 - s1 * c2 * s3
- return Q
- new_rotate_euler = classmethod(new_rotate_euler)
-
- def new_interpolate(cls, q1, q2, t):
- assert isinstance(q1, Quaternion) and isinstance(q2, Quaternion)
- Q = cls()
-
- costheta = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z
- theta = math.acos(costheta)
- if abs(theta) < 0.01:
- Q.w = q2.w
- Q.x = q2.x
- Q.y = q2.y
- Q.z = q2.z
- return Q
-
- sintheta = math.sqrt(1.0 - costheta * costheta)
- if abs(sintheta) < 0.01:
- Q.w = (q1.w + q2.w) * 0.5
- Q.x = (q1.x + q2.x) * 0.5
- Q.y = (q1.y + q2.y) * 0.5
- Q.z = (q1.z + q2.z) * 0.5
- return Q
-
- ratio1 = math.sin((1 - t) * theta) / sintheta
- ratio2 = math.sin(t * theta) / sintheta
-
- Q.w = q1.w * ratio1 + q2.w * ratio2
- Q.x = q1.x * ratio1 + q2.x * ratio2
- Q.y = q1.y * ratio1 + q2.y * ratio2
- Q.z = q1.z * ratio1 + q2.z * ratio2
- return Q
- new_interpolate = classmethod(new_interpolate)
-
-# Geometry
-# Much maths thanks to Paul Bourke, http://astronomy.swin.edu.au/~pbourke
-# ---------------------------------------------------------------------------
-
-class Geometry(object):
- def _connect_unimplemented(self, other):
- raise AttributeError, 'Cannot connect %s to %s' % \
- (self.__class__, other.__class__)
-
- def _intersect_unimplemented(self, other):
- raise AttributeError, 'Cannot intersect %s and %s' % \
- (self.__class__, other.__class__)
-
- _intersect_point2 = _intersect_unimplemented
- _intersect_line2 = _intersect_unimplemented
- _intersect_circle = _intersect_unimplemented
- _connect_point2 = _connect_unimplemented
- _connect_line2 = _connect_unimplemented
- _connect_circle = _connect_unimplemented
-
- _intersect_point3 = _intersect_unimplemented
- _intersect_line3 = _intersect_unimplemented
- _intersect_sphere = _intersect_unimplemented
- _intersect_plane = _intersect_unimplemented
- _connect_point3 = _connect_unimplemented
- _connect_line3 = _connect_unimplemented
- _connect_sphere = _connect_unimplemented
- _connect_plane = _connect_unimplemented
-
- def intersect(self, other):
- raise NotImplementedError
-
- def connect(self, other):
- raise NotImplementedError
-
- def distance(self, other):
- c = self.connect(other)
- if c:
- return c.length
- return 0.0
-
-def _intersect_point2_circle(P, C):
- return abs(P - C.c) <= C.r
-
-def _intersect_line2_line2(A, B):
- d = B.v.y * A.v.x - B.v.x * A.v.y
- if d == 0:
- return None
-
- dy = A.p.y - B.p.y
- dx = A.p.x - B.p.x
- ua = (B.v.x * dy - B.v.y * dx) / d
- if not A._u_in(ua):
- return None
- ub = (A.v.x * dy - A.v.y * dx) / d
- if not B._u_in(ub):
- return None
-
- return Point2(A.p.x + ua * A.v.x,
- A.p.y + ua * A.v.y)
-
-def _intersect_line2_circle(L, C):
- a = L.v.magnitude_squared()
- b = 2 * (L.v.x * (L.p.x - C.c.x) + \
- L.v.y * (L.p.y - C.c.y))
- c = C.c.magnitude_squared() + \
- L.p.magnitude_squared() - \
- 2 * C.c.dot(L.p) - \
- C.r ** 2
- det = b ** 2 - 4 * a * c
- if det < 0:
- return None
- sq = math.sqrt(det)
- u1 = (-b + sq) / (2 * a)
- u2 = (-b - sq) / (2 * a)
- if not L._u_in(u1):
- u1 = max(min(u1, 1.0), 0.0)
- if not L._u_in(u2):
- u2 = max(min(u2, 1.0), 0.0)
- return LineSegment2(Point2(L.p.x + u1 * L.v.x,
- L.p.y + u1 * L.v.y),
- Point2(L.p.x + u2 * L.v.x,
- L.p.y + u2 * L.v.y))
-
-def _connect_point2_line2(P, L):
- d = L.v.magnitude_squared()
- assert d != 0
- u = ((P.x - L.p.x) * L.v.x + \
- (P.y - L.p.y) * L.v.y) / d
- if not L._u_in(u):
- u = max(min(u, 1.0), 0.0)
- return LineSegment2(P,
- Point2(L.p.x + u * L.v.x,
- L.p.y + u * L.v.y))
-
-def _connect_point2_circle(P, C):
- v = P - C.c
- v.normalize()
- v *= C.r
- return LineSegment2(P, Point2(C.c.x + v.x, C.c.y + v.y))
-
-def _connect_line2_line2(A, B):
- d = B.v.y * A.v.x - B.v.x * A.v.y
- if d == 0:
- # Parallel, connect an endpoint with a line
- if isinstance(B, Ray2) or isinstance(B, LineSegment2):
- p1, p2 = _connect_point2_line2(B.p, A)
- return p2, p1
- # No endpoint (or endpoint is on A), possibly choose arbitrary point
- # on line.
- return _connect_point2_line2(A.p, B)
-
- dy = A.p.y - B.p.y
- dx = A.p.x - B.p.x
- ua = (B.v.x * dy - B.v.y * dx) / d
- if not A._u_in(ua):
- ua = max(min(ua, 1.0), 0.0)
- ub = (A.v.x * dy - A.v.y * dx) / d
- if not B._u_in(ub):
- ub = max(min(ub, 1.0), 0.0)
-
- return LineSegment2(Point2(A.p.x + ua * A.v.x, A.p.y + ua * A.v.y),
- Point2(B.p.x + ub * B.v.x, B.p.y + ub * B.v.y))
-
-def _connect_circle_line2(C, L):
- d = L.v.magnitude_squared()
- assert d != 0
- u = ((C.c.x - L.p.x) * L.v.x + (C.c.y - L.p.y) * L.v.y) / d
- if not L._u_in(u):
- u = max(min(u, 1.0), 0.0)
- point = Point2(L.p.x + u * L.v.x, L.p.y + u * L.v.y)
- v = (point - C.c)
- v.normalize()
- v *= C.r
- return LineSegment2(Point2(C.c.x + v.x, C.c.y + v.y), point)
-
-def _connect_circle_circle(A, B):
- v = B.c - A.c
- v.normalize()
- return LineSegment2(Point2(A.c.x + v.x * A.r, A.c.y + v.y * A.r),
- Point2(B.c.x - v.x * B.r, B.c.y - v.y * B.r))
-
-
-class Point2(Vector2, Geometry):
- def __repr__(self):
- return 'Point2(%.2f, %.2f)' % (self.x, self.y)
-
- def intersect(self, other):
- return other._intersect_point2(self)
-
- def _intersect_circle(self, other):
- return _intersect_point2_circle(self, other)
-
- def connect(self, other):
- return other._connect_point2(self)
-
- def _connect_point2(self, other):
- return LineSegment2(other, self)
-
- def _connect_line2(self, other):
- c = _connect_point2_line2(self, other)
- if c:
- return c._swap()
-
- def _connect_circle(self, other):
- c = _connect_point2_circle(self, other)
- if c:
- return c._swap()
-
-class Line2(Geometry):
- __slots__ = ['p', 'v']
-
- def __init__(self, *args):
- if len(args) == 3:
- assert isinstance(args[0], Point2) and \
- isinstance(args[1], Vector2) and \
- type(args[2]) == float
- self.p = args[0].copy()
- self.v = args[1] * args[2] / abs(args[1])
- elif len(args) == 2:
- if isinstance(args[0], Point2) and isinstance(args[1], Point2):
- self.p = args[0].copy()
- self.v = args[1] - args[0]
- elif isinstance(args[0], Point2) and isinstance(args[1], Vector2):
- self.p = args[0].copy()
- self.v = args[1].copy()
- else:
- raise AttributeError, '%r' % (args,)
- elif len(args) == 1:
- if isinstance(args[0], Line2):
- self.p = args[0].p.copy()
- self.v = args[0].v.copy()
- else:
- raise AttributeError, '%r' % (args,)
- else:
- raise AttributeError, '%r' % (args,)
-
- if not self.v:
- raise AttributeError, 'Line has zero-length vector'
-
- def __copy__(self):
- return self.__class__(self.p, self.v)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Line2(<%.2f, %.2f> + u<%.2f, %.2f>)' % \
- (self.p.x, self.p.y, self.v.x, self.v.y)
-
- p1 = property(lambda self: self.p)
- p2 = property(lambda self: Point2(self.p.x + self.v.x,
- self.p.y + self.v.y))
-
- def _apply_transform(self, t):
- self.p = t * self.p
- self.v = t * self.v
-
- def _u_in(self, u):
- return True
-
- def intersect(self, other):
- return other._intersect_line2(self)
-
- def _intersect_line2(self, other):
- return _intersect_line2_line2(self, other)
-
- def _intersect_circle(self, other):
- return _intersect_line2_circle(self, other)
-
- def connect(self, other):
- return other._connect_line2(self)
-
- def _connect_point2(self, other):
- return _connect_point2_line2(other, self)
-
- def _connect_line2(self, other):
- return _connect_line2_line2(other, self)
-
- def _connect_circle(self, other):
- return _connect_circle_line2(other, self)
-
-class Ray2(Line2):
- def __repr__(self):
- return 'Ray2(<%.2f, %.2f> + u<%.2f, %.2f>)' % \
- (self.p.x, self.p.y, self.v.x, self.v.y)
-
- def _u_in(self, u):
- return u >= 0.0
-
-class LineSegment2(Line2):
- def __repr__(self):
- return 'LineSegment2(<%.2f, %.2f> to <%.2f, %.2f>)' % \
- (self.p.x, self.p.y, self.p.x + self.v.x, self.p.y + self.v.y)
-
- def _u_in(self, u):
- return u >= 0.0 and u <= 1.0
-
- def __abs__(self):
- return abs(self.v)
-
- def _swap(self):
- # used by connect methods to switch order of points
- self.p = self.p2
- self.v *= -1
- return self
-
- length = property(lambda self: abs(self.v))
-
-class Circle(Geometry):
- __slots__ = ['c', 'r']
-
- def __init__(self, center, radius):
- assert isinstance(center, Vector2) and type(radius) == float
- self.c = center.copy()
- self.r = radius
-
- def __copy__(self):
- return self.__class__(self.c, self.r)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Circle(<%.2f, %.2f>, radius=%.2f)' % \
- (self.c.x, self.c.y, self.r)
-
- def _apply_transform(self, t):
- self.c = t * self.c
-
- def intersect(self, other):
- return other._intersect_circle(self)
-
- def _intersect_point2(self, other):
- return _intersect_point2_circle(other, self)
-
- def _intersect_line2(self, other):
- return _intersect_line2_circle(other, self)
-
- def connect(self, other):
- return other._connect_circle(self)
-
- def _connect_point2(self, other):
- return _connect_point2_circle(other, self)
-
- def _connect_line2(self, other):
- c = _connect_circle_line2(self, other)
- if c:
- return c._swap()
-
- def _connect_circle(self, other):
- return _connect_circle_circle(other, self)
-
-# 3D Geometry
-# -------------------------------------------------------------------------
-
-def _connect_point3_line3(P, L):
- d = L.v.magnitude_squared()
- assert d != 0
- u = ((P.x - L.p.x) * L.v.x + \
- (P.y - L.p.y) * L.v.y + \
- (P.z - L.p.z) * L.v.z) / d
- if not L._u_in(u):
- u = max(min(u, 1.0), 0.0)
- return LineSegment3(P, Point3(L.p.x + u * L.v.x,
- L.p.y + u * L.v.y,
- L.p.z + u * L.v.z))
-
-def _connect_point3_sphere(P, S):
- v = P - S.c
- v.normalize()
- v *= S.r
- return LineSegment3(P, Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z))
-
-def _connect_point3_plane(p, plane):
- n = plane.n.normalized()
- d = p.dot(plane.n) - plane.k
- return LineSegment3(p, Point3(p.x - n.x * d, p.y - n.y * d, p.z - n.z * d))
-
-def _connect_line3_line3(A, B):
- assert A.v and B.v
- p13 = A.p - B.p
- d1343 = p13.dot(B.v)
- d4321 = B.v.dot(A.v)
- d1321 = p13.dot(A.v)
- d4343 = B.v.magnitude_squared()
- denom = A.v.magnitude_squared() * d4343 - d4321 ** 2
- if denom == 0:
- # Parallel, connect an endpoint with a line
- if isinstance(B, Ray3) or isinstance(B, LineSegment3):
- return _connect_point3_line3(B.p, A)._swap()
- # No endpoint (or endpoint is on A), possibly choose arbitrary
- # point on line.
- return _connect_point3_line3(A.p, B)
-
- ua = (d1343 * d4321 - d1321 * d4343) / denom
- if not A._u_in(ua):
- ua = max(min(ua, 1.0), 0.0)
- ub = (d1343 + d4321 * ua) / d4343
- if not B._u_in(ub):
- ub = max(min(ub, 1.0), 0.0)
- return LineSegment3(Point3(A.p.x + ua * A.v.x,
- A.p.y + ua * A.v.y,
- A.p.z + ua * A.v.z),
- Point3(B.p.x + ub * B.v.x,
- B.p.y + ub * B.v.y,
- B.p.z + ub * B.v.z))
-
-def _connect_line3_plane(L, P):
- d = P.n.dot(L.v)
- if not d:
- # Parallel, choose an endpoint
- return _connect_point3_plane(L.p, P)
- u = (P.k - P.n.dot(L.p)) / d
- if not L._u_in(u):
- # intersects out of range, choose nearest endpoint
- u = max(min(u, 1.0, 0.0))
- return _connect_point3_plane(Point3(L.p.x + u * L.v.x,
- L.p.y + u * L.v.y,
- L.p.z + u * L.v.z), P)
- # Intersection
- return None
-
-def _connect_sphere_line3(S, L):
- d = L.v.magnitude_squared()
- assert d != 0
- u = ((S.c.x - L.p.x) * L.v.x + \
- (S.c.y - L.p.y) * L.v.y + \
- (S.c.z - L.p.z) * L.v.z) / d
- if not L._u_in(u):
- u = max(min(u, 1.0), 0.0)
- point = Point3(L.p.x + u * L.v.x, L.p.y + u * L.v.y, L.p.z + u * L.v.z)
- v = (point - S.c)
- v.normalize()
- v *= S.r
- return LineSegment3(Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z),
- point)
-
-def _connect_sphere_sphere(A, B):
- v = B.c - A.c
- v.normalize()
- return LineSegment3(Point3(A.c.x + v.x * A.r,
- A.c.y + v.y * A.r,
- A.c.x + v.z * A.r),
- Point3(B.c.x + v.x * B.r,
- B.c.y + v.y * B.r,
- B.c.x + v.z * B.r))
-
-def _connect_sphere_plane(S, P):
- c = _connect_point3_plane(S.c, P)
- if not c:
- return None
- p2 = c.p2
- v = p2 - S.c
- v.normalize()
- v *= S.r
- return LineSegment3(Point3(S.c.x + v.x, S.c.y + v.y, S.c.z + v.z), p2)
-
-def _connect_plane_plane(A, B):
- if A.n.cross(B.n):
- # Planes intersect
- return None
- else:
- # Planes are parallel, connect to arbitrary point
- return _connect_point3_plane(A._get_point(), B)
-
-def _intersect_point3_sphere(P, S):
- return abs(P - S.c) <= S.r
-
-def _intersect_line3_sphere(L, S):
- a = L.v.magnitude_squared()
- b = 2 * (L.v.x * (L.p.x - S.c.x) + \
- L.v.y * (L.p.y - S.c.y) + \
- L.v.z * (L.p.z - S.c.z))
- c = S.c.magnitude_squared() + \
- L.p.magnitude_squared() - \
- 2 * S.c.dot(L.p) - \
- S.r ** 2
- det = b ** 2 - 4 * a * c
- if det < 0:
- return None
- sq = math.sqrt(det)
- u1 = (-b + sq) / (2 * a)
- u2 = (-b - sq) / (2 * a)
- if not L._u_in(u1):
- u1 = max(min(u1, 1.0), 0.0)
- if not L._u_in(u2):
- u2 = max(min(u2, 1.0), 0.0)
- return LineSegment3(Point3(L.p.x + u1 * L.v.x,
- L.p.y + u1 * L.v.y,
- L.p.z + u1 * L.v.z),
- Point3(L.p.x + u2 * L.v.x,
- L.p.y + u2 * L.v.y,
- L.p.z + u2 * L.v.z))
-
-def _intersect_line3_plane(L, P):
- d = P.n.dot(L.v)
- if not d:
- # Parallel
- return None
- u = (P.k - P.n.dot(L.p)) / d
- if not L._u_in(u):
- return None
- return Point3(L.p.x + u * L.v.x,
- L.p.y + u * L.v.y,
- L.p.z + u * L.v.z)
-
-def _intersect_plane_plane(A, B):
- n1_m = A.n.magnitude_squared()
- n2_m = B.n.magnitude_squared()
- n1d2 = A.n.dot(B.n)
- det = n1_m * n2_m - n1d2 ** 2
- if det == 0:
- # Parallel
- return None
- c1 = (A.k * n2_m - B.k * n1d2) / det
- c2 = (B.k * n1_m - A.k * n1d2) / det
- return Line3(Point3(c1 * A.n.x + c2 * B.n.x,
- c1 * A.n.y + c2 * B.n.y,
- c1 * A.n.z + c2 * B.n.z),
- A.n.cross(B.n))
-
-class Point3(Vector3, Geometry):
- def __repr__(self):
- return 'Point3(%.2f, %.2f, %.2f)' % (self.x, self.y, self.z)
-
- def intersect(self, other):
- return other._intersect_point3(self)
-
- def _intersect_sphere(self, other):
- return _intersect_point3_sphere(self, other)
-
- def connect(self, other):
- other._connect_point3(self)
-
- def _connect_point3(self, other):
- if self != other:
- return LineSegment3(other, self)
- return None
-
- def _connect_line3(self, other):
- c = _connect_point3_line3(self, other)
- if c:
- return c._swap()
-
- def _connect_sphere(self, other):
- c = _connect_point3_sphere(self, other)
- if c:
- return c._swap()
-
- def _connect_plane(self, other):
- c = _connect_point3_plane(self, other)
- if c:
- return c._swap()
-
-class Line3(object):
- __slots__ = ['p', 'v']
-
- def __init__(self, *args):
- if len(args) == 3:
- assert isinstance(args[0], Point3) and \
- isinstance(args[1], Vector3) and \
- type(args[2]) == float
- self.p = args[0].copy()
- self.v = args[1] * args[2] / abs(args[1])
- elif len(args) == 2:
- if isinstance(args[0], Point3) and isinstance(args[1], Point3):
- self.p = args[0].copy()
- self.v = args[1] - args[0]
- elif isinstance(args[0], Point3) and isinstance(args[1], Vector3):
- self.p = args[0].copy()
- self.v = args[1].copy()
- else:
- raise AttributeError, '%r' % (args,)
- elif len(args) == 1:
- if isinstance(args[0], Line3):
- self.p = args[0].p.copy()
- self.v = args[0].v.copy()
- else:
- raise AttributeError, '%r' % (args,)
- else:
- raise AttributeError, '%r' % (args,)
-
- if not self.v:
- raise AttributeError, 'Line has zero-length vector'
-
- def __copy__(self):
- return self.__class__(self.p, self.v)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Line3(<%.2f, %.2f, %.2f> + u<%.2f, %.2f, %.2f>)' % \
- (self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z)
-
- p1 = property(lambda self: self.p)
- p2 = property(lambda self: Point3(self.p.x + self.v.x,
- self.p.y + self.v.y,
- self.p.z + self.v.z))
-
- def _apply_transform(self, t):
- self.p = t * self.p
- self.v = t * self.v
-
- def _u_in(self, u):
- return True
-
- def intersect(self, other):
- return other._intersect_line3(self)
-
- def _intersect_sphere(self, other):
- return _intersect_line3_sphere(self, other)
-
- def _intersect_plane(self, other):
- return _intersect_line3_plane(self, other)
-
- def connect(self, other):
- return other._connect_line3(self)
-
- def _connect_point3(self, other):
- return _connect_point3_line3(other, self)
-
- def _connect_line3(self, other):
- return _connect_line3_line3(other, self)
-
- def _connect_sphere(self, other):
- return _connect_sphere_line3(other, self)
-
- def _connect_plane(self, other):
- c = _connect_line3_plane(self, other)
- if c:
- return c
-
-class Ray3(Line3):
- def __repr__(self):
- return 'Ray3(<%.2f, %.2f, %.2f> + u<%.2f, %.2f, %.2f>)' % \
- (self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z)
-
- def _u_in(self, u):
- return u >= 0.0
-
-class LineSegment3(Line3):
- def __repr__(self):
- return 'LineSegment3(<%.2f, %.2f, %.2f> to <%.2f, %.2f, %.2f>)' % \
- (self.p.x, self.p.y, self.p.z,
- self.p.x + self.v.x, self.p.y + self.v.y, self.p.z + self.v.z)
-
- def _u_in(self, u):
- return u >= 0.0 and u <= 1.0
-
- def __abs__(self):
- return abs(self.v)
-
- def _swap(self):
- # used by connect methods to switch order of points
- self.p = self.p2
- self.v *= -1
- return self
-
- length = property(lambda self: abs(self.v))
-
-class Sphere(object):
- __slots__ = ['c', 'r']
-
- def __init__(self, center, radius):
- assert isinstance(center, Vector3) and type(radius) == float
- self.c = center.copy()
- self.r = radius
-
- def __copy__(self):
- return self.__class__(self.c, self.r)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Sphere(<%.2f, %.2f, %.2f>, radius=%.2f)' % \
- (self.c.x, self.c.y, self.c.z, self.r)
-
- def _apply_transform(self, t):
- self.c = t * self.c
-
- def intersect(self, other):
- return other._intersect_sphere(self)
-
- def _intersect_point3(self, other):
- return _intersect_point3_sphere(other, self)
-
- def _intersect_line3(self, other):
- return _intersect_line3_sphere(other, self)
-
- def connect(self, other):
- return other._connect_sphere(self)
-
- def _connect_point3(self, other):
- return _connect_point3_sphere(other, self)
-
- def _connect_line3(self, other):
- c = _connect_sphere_line3(self, other)
- if c:
- return c._swap()
-
- def _connect_sphere(self, other):
- return _connect_sphere_sphere(other, self)
-
- def _connect_plane(self, other):
- c = _connect_sphere_plane(self, other)
- if c:
- return c
-
-class Plane(object):
- # n.p = k, where n is normal, p is point on plane, k is constant scalar
- __slots__ = ['n', 'k']
-
- def __init__(self, *args):
- if len(args) == 3:
- assert isinstance(args[0], Point3) and \
- isinstance(args[1], Point3) and \
- isinstance(args[2], Point3)
- self.n = (args[1] - args[0]).cross(args[2] - args[0])
- self.k = self.n.dot(args[0])
- elif len(args) == 2:
- if isinstance(args[0], Point3) and isinstance(args[1], Vector3):
- self.n = args[1].copy()
- self.k = self.n.dot(args[0])
- elif isinstance(args[0], Vector3) and type(args[1]) == float:
- self.n = args[0].copy()
- self.k = args[1]
- else:
- raise AttributeError, '%r' % (args,)
-
- else:
- raise AttributeError, '%r' % (args,)
-
- if not self.n:
- raise AttributeError, 'Points on plane are colinear'
-
- def __copy__(self):
- return self.__class__(self.n, self.k)
-
- copy = __copy__
-
- def __repr__(self):
- return 'Plane(<%.2f, %.2f, %.2f>.p = %.2f)' % \
- (self.n.x, self.n.y, self.n.z, self.k)
-
- def _get_point(self):
- # Return an arbitrary point on the plane
- if self.n.z:
- return Point3(0., 0., self.k / self.n.z)
- elif self.n.y:
- return Point3(0., self.k / self.n.y, 0.)
- else:
- return Point3(self.k / self.n.x, 0., 0.)
-
- def _apply_transform(self, t):
- p = t * self._get_point()
- self.n = t * self.n
- self.k = self.n.dot(p)
-
- def intersect(self, other):
- return other._intersect_plane(self)
-
- def _intersect_line3(self, other):
- return _intersect_line3_plane(other, self)
-
- def _intersect_plane(self, other):
- return _intersect_plane_plane(self, other)
-
- def connect(self, other):
- return other._connect_plane(self)
-
- def _connect_point3(self, other):
- return _connect_point3_plane(other, self)
-
- def _connect_line3(self, other):
- return _connect_line3_plane(other, self)
-
- def _connect_sphere(self, other):
- return _connect_sphere_plane(other, self)
-
- def _connect_plane(self, other):
- return _connect_plane_plane(other, self)
-
diff --git a/optboid.py b/glboid.py
similarity index 57%
rename from optboid.py
rename to glboid.py
index b391dcb..8c2f9e2 100644
--- a/optboid.py
+++ b/glboid.py
@@ -1,34 +1,13 @@
#!/usr/bin/env python
-#
-# euclid graphics maths module
-#
-# Copyright (c) 2006 Alex Holkner
-# Alex.Holkner@mail.google.com
-#
-# This library is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation; either version 2.1 of the License, or (at your
-# option) any later version.
-#
-# This library is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
-# for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this library; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
"""
A simple pyglet engine to do some 2D rendering.
Used to display boids at given positions
"""
-from __future__ import division
+from __future__ import division, print_function, absolute_import, unicode_literals
from pyglet import *
from pyglet.gl import *
from math import *
-import random
import simulation
@@ -44,6 +23,7 @@ def __init__(self, swarm, offx, offy):
self.swarm = swarm
self.ents = swarm.boids # this will point to a list of boids
self.ent_size = 15.0
+ self.num_ents = len(swarm.boids)
self.fps = clock.ClockDisplay()
self.o_x = offx
self.o_y = offy
@@ -51,8 +31,8 @@ def __init__(self, swarm, offx, offy):
def draw_entity(self, e):
""" Draws a boid """
glLoadIdentity()
- glTranslatef(e.position.x+self.o_x, e.position.y+self.o_y, 0.0)
- glRotatef(e.rotation*180/pi, 0, 0, 1)
+ glTranslatef(e.position.x + self.o_x, e.position.y + self.o_y, 0.0)
+ glRotatef(e.rotation*180 / pi, 0, 0, 1)
glScalef(self.ent_size, self.ent_size, 1.0)
glColor4f(0, 0, 0, 0)
glEnableClientState(GL_VERTEX_ARRAY)
@@ -60,29 +40,29 @@ def draw_entity(self, e):
glDrawArrays(GL_TRIANGLES, 0, len(self.vertsGl) // 2)
def draw_grid(self):
- cw=self.swarm.cell_width
- w=cw*self.swarm.divisions
+ cw = self.swarm.cell_width
+ w = cw * self.swarm.divisions
for i in range(self.swarm.divisions):
xy = i*cw
glLoadIdentity()
glBegin(GL_LINES)
- glColor4f(1,1,1,0)
- glVertex2f(0,xy)
- glVertex2f(w,xy)
+ glColor4f(0.5, 0.5, 0.5, 0)
+ glVertex2f(0, xy)
+ glVertex2f(w, xy)
glEnd()
glBegin(GL_LINES)
- glColor4f(0.5,0.5,0.5,0)
- glVertex2f(xy,0)
- glVertex2f(xy,w)
+ glColor4f(0.5, 0.5, 0.5, 0)
+ glVertex2f(xy, 0)
+ glVertex2f(xy, w)
glEnd()
-
+
def draw(self):
glClearColor(1.0, 1.0, 1.0, 0.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
self.fps.draw()
-
+
#self.draw_grid()
for ent in self.ents:
self.draw_entity(ent)
@@ -90,7 +70,7 @@ def draw(self):
sim = simulation.FlockSimulation(150, 750)
world = World(sim.swarm, -25, -25)
-window = pyglet.window.Window(700, 700, vsync=True)
+window = pyglet.window.Window(700, 700, vsync=False)
@window.event
@@ -106,7 +86,6 @@ def update(dt):
def idle(dt):
pass
-
clock.schedule(update)
clock.schedule(idle)
diff --git a/gprof2dot.py b/gprof2dot.py
deleted file mode 100644
index 039bc5c..0000000
--- a/gprof2dot.py
+++ /dev/null
@@ -1,2899 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2008-2009 Jose Fonseca
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see .
-#
-
-"""Generate a dot graph from the output of several profilers."""
-
-__author__ = "Jose Fonseca"
-
-__version__ = "1.0"
-
-
-import sys
-import math
-import os.path
-import re
-import textwrap
-import optparse
-import xml.parsers.expat
-
-
-try:
- # Debugging helper module
- import debug
-except ImportError:
- pass
-
-
-def times(x):
- return u"%u\xd7" % (x,)
-
-def percentage(p):
- return "%.02f%%" % (p*100.0,)
-
-def add(a, b):
- return a + b
-
-def equal(a, b):
- if a == b:
- return a
- else:
- return None
-
-def fail(a, b):
- assert False
-
-
-tol = 2 ** -23
-
-def ratio(numerator, denominator):
- try:
- ratio = float(numerator)/float(denominator)
- except ZeroDivisionError:
- # 0/0 is undefined, but 1.0 yields more useful results
- return 1.0
- if ratio < 0.0:
- if ratio < -tol:
- sys.stderr.write('warning: negative ratio (%s/%s)\n' % (numerator, denominator))
- return 0.0
- if ratio > 1.0:
- if ratio > 1.0 + tol:
- sys.stderr.write('warning: ratio greater than one (%s/%s)\n' % (numerator, denominator))
- return 1.0
- return ratio
-
-
-class UndefinedEvent(Exception):
- """Raised when attempting to get an event which is undefined."""
-
- def __init__(self, event):
- Exception.__init__(self)
- self.event = event
-
- def __str__(self):
- return 'unspecified event %s' % self.event.name
-
-
-class Event(object):
- """Describe a kind of event, and its basic operations."""
-
- def __init__(self, name, null, aggregator, formatter = str):
- self.name = name
- self._null = null
- self._aggregator = aggregator
- self._formatter = formatter
-
- def __eq__(self, other):
- return self is other
-
- def __hash__(self):
- return id(self)
-
- def null(self):
- return self._null
-
- def aggregate(self, val1, val2):
- """Aggregate two event values."""
- assert val1 is not None
- assert val2 is not None
- return self._aggregator(val1, val2)
-
- def format(self, val):
- """Format an event value."""
- assert val is not None
- return self._formatter(val)
-
-
-CALLS = Event("Calls", 0, add, times)
-SAMPLES = Event("Samples", 0, add)
-SAMPLES2 = Event("Samples", 0, add)
-
-TIME = Event("Time", 0.0, add, lambda x: '(' + str(x) + ')')
-TIME_RATIO = Event("Time ratio", 0.0, add, lambda x: '(' + percentage(x) + ')')
-TOTAL_TIME = Event("Total time", 0.0, fail)
-TOTAL_TIME_RATIO = Event("Total time ratio", 0.0, fail, percentage)
-
-
-class Object(object):
- """Base class for all objects in profile which can store events."""
-
- def __init__(self, events=None):
- if events is None:
- self.events = {}
- else:
- self.events = events
-
- def __hash__(self):
- return id(self)
-
- def __eq__(self, other):
- return self is other
-
- def __contains__(self, event):
- return event in self.events
-
- def __getitem__(self, event):
- try:
- return self.events[event]
- except KeyError:
- raise UndefinedEvent(event)
-
- def __setitem__(self, event, value):
- if value is None:
- if event in self.events:
- del self.events[event]
- else:
- self.events[event] = value
-
-
-class Call(Object):
- """A call between functions.
-
- There should be at most one call object for every pair of functions.
- """
-
- def __init__(self, callee_id):
- Object.__init__(self)
- self.callee_id = callee_id
- self.ratio = None
- self.weight = None
-
-
-class Function(Object):
- """A function."""
-
- def __init__(self, id, name):
- Object.__init__(self)
- self.id = id
- self.name = name
- self.module = None
- self.process = None
- self.calls = {}
- self.called = None
- self.weight = None
- self.cycle = None
-
- def add_call(self, call):
- if call.callee_id in self.calls:
- sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id)))
- self.calls[call.callee_id] = call
-
- def get_call(self, callee_id):
- if not callee_id in self.calls:
- call = Call(callee_id)
- call[SAMPLES] = 0
- call[SAMPLES2] = 0
- call[CALLS] = 0
- self.calls[callee_id] = call
- return self.calls[callee_id]
-
- _parenthesis_re = re.compile(r'\([^()]*\)')
- _angles_re = re.compile(r'<[^<>]*>')
- _const_re = re.compile(r'\s+const$')
-
- def stripped_name(self):
- """Remove extraneous information from C++ demangled function names."""
-
- name = self.name
-
- # Strip function parameters from name by recursively removing paired parenthesis
- while True:
- name, n = self._parenthesis_re.subn('', name)
- if not n:
- break
-
- # Strip const qualifier
- name = self._const_re.sub('', name)
-
- # Strip template parameters from name by recursively removing paired angles
- while True:
- name, n = self._angles_re.subn('', name)
- if not n:
- break
-
- return name
-
- # TODO: write utility functions
-
- def __repr__(self):
- return self.name
-
-
-class Cycle(Object):
- """A cycle made from recursive function calls."""
-
- def __init__(self):
- Object.__init__(self)
- # XXX: Do cycles need an id?
- self.functions = set()
-
- def add_function(self, function):
- assert function not in self.functions
- self.functions.add(function)
- # XXX: Aggregate events?
- if function.cycle is not None:
- for other in function.cycle.functions:
- if function not in self.functions:
- self.add_function(other)
- function.cycle = self
-
-
-class Profile(Object):
- """The whole profile."""
-
- def __init__(self):
- Object.__init__(self)
- self.functions = {}
- self.cycles = []
-
- def add_function(self, function):
- if function.id in self.functions:
- sys.stderr.write('warning: overwriting function %s (id %s)\n' % (function.name, str(function.id)))
- self.functions[function.id] = function
-
- def add_cycle(self, cycle):
- self.cycles.append(cycle)
-
- def validate(self):
- """Validate the edges."""
-
- for function in self.functions.itervalues():
- for callee_id in function.calls.keys():
- assert function.calls[callee_id].callee_id == callee_id
- if callee_id not in self.functions:
- sys.stderr.write('warning: call to undefined function %s from function %s\n' % (str(callee_id), function.name))
- del function.calls[callee_id]
-
- def find_cycles(self):
- """Find cycles using Tarjan's strongly connected components algorithm."""
-
- # Apply the Tarjan's algorithm successively until all functions are visited
- visited = set()
- for function in self.functions.itervalues():
- if function not in visited:
- self._tarjan(function, 0, [], {}, {}, visited)
- cycles = []
- for function in self.functions.itervalues():
- if function.cycle is not None and function.cycle not in cycles:
- cycles.append(function.cycle)
- self.cycles = cycles
- if 0:
- for cycle in cycles:
- sys.stderr.write("Cycle:\n")
- for member in cycle.functions:
- sys.stderr.write("\tFunction %s\n" % member.name)
-
- def _tarjan(self, function, order, stack, orders, lowlinks, visited):
- """Tarjan's strongly connected components algorithm.
-
- See also:
- - http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
- """
-
- visited.add(function)
- orders[function] = order
- lowlinks[function] = order
- order += 1
- pos = len(stack)
- stack.append(function)
- for call in function.calls.itervalues():
- callee = self.functions[call.callee_id]
- # TODO: use a set to optimize lookup
- if callee not in orders:
- order = self._tarjan(callee, order, stack, orders, lowlinks, visited)
- lowlinks[function] = min(lowlinks[function], lowlinks[callee])
- elif callee in stack:
- lowlinks[function] = min(lowlinks[function], orders[callee])
- if lowlinks[function] == orders[function]:
- # Strongly connected component found
- members = stack[pos:]
- del stack[pos:]
- if len(members) > 1:
- cycle = Cycle()
- for member in members:
- cycle.add_function(member)
- return order
-
- def call_ratios(self, event):
- # Aggregate for incoming calls
- cycle_totals = {}
- for cycle in self.cycles:
- cycle_totals[cycle] = 0.0
- function_totals = {}
- for function in self.functions.itervalues():
- function_totals[function] = 0.0
- for function in self.functions.itervalues():
- for call in function.calls.itervalues():
- if call.callee_id != function.id:
- callee = self.functions[call.callee_id]
- function_totals[callee] += call[event]
- if callee.cycle is not None and callee.cycle is not function.cycle:
- cycle_totals[callee.cycle] += call[event]
-
- # Compute the ratios
- for function in self.functions.itervalues():
- for call in function.calls.itervalues():
- assert call.ratio is None
- if call.callee_id != function.id:
- callee = self.functions[call.callee_id]
- if callee.cycle is not None and callee.cycle is not function.cycle:
- total = cycle_totals[callee.cycle]
- else:
- total = function_totals[callee]
- call.ratio = ratio(call[event], total)
-
- def integrate(self, outevent, inevent):
- """Propagate function time ratio allong the function calls.
-
- Must be called after finding the cycles.
-
- See also:
- - http://citeseer.ist.psu.edu/graham82gprof.html
- """
-
- # Sanity checking
- assert outevent not in self
- for function in self.functions.itervalues():
- assert outevent not in function
- assert inevent in function
- for call in function.calls.itervalues():
- assert outevent not in call
- if call.callee_id != function.id:
- assert call.ratio is not None
-
- # Aggregate the input for each cycle
- for cycle in self.cycles:
- total = inevent.null()
- for function in self.functions.itervalues():
- total = inevent.aggregate(total, function[inevent])
- self[inevent] = total
-
- # Integrate along the edges
- total = inevent.null()
- for function in self.functions.itervalues():
- total = inevent.aggregate(total, function[inevent])
- self._integrate_function(function, outevent, inevent)
- self[outevent] = total
-
- def _integrate_function(self, function, outevent, inevent):
- if function.cycle is not None:
- return self._integrate_cycle(function.cycle, outevent, inevent)
- else:
- if outevent not in function:
- total = function[inevent]
- for call in function.calls.itervalues():
- if call.callee_id != function.id:
- total += self._integrate_call(call, outevent, inevent)
- function[outevent] = total
- return function[outevent]
-
- def _integrate_call(self, call, outevent, inevent):
- assert outevent not in call
- assert call.ratio is not None
- callee = self.functions[call.callee_id]
- subtotal = call.ratio *self._integrate_function(callee, outevent, inevent)
- call[outevent] = subtotal
- return subtotal
-
- def _integrate_cycle(self, cycle, outevent, inevent):
- if outevent not in cycle:
-
- # Compute the outevent for the whole cycle
- total = inevent.null()
- for member in cycle.functions:
- subtotal = member[inevent]
- for call in member.calls.itervalues():
- callee = self.functions[call.callee_id]
- if callee.cycle is not cycle:
- subtotal += self._integrate_call(call, outevent, inevent)
- total += subtotal
- cycle[outevent] = total
-
- # Compute the time propagated to callers of this cycle
- callees = {}
- for function in self.functions.itervalues():
- if function.cycle is not cycle:
- for call in function.calls.itervalues():
- callee = self.functions[call.callee_id]
- if callee.cycle is cycle:
- try:
- callees[callee] += call.ratio
- except KeyError:
- callees[callee] = call.ratio
-
- for member in cycle.functions:
- member[outevent] = outevent.null()
-
- for callee, call_ratio in callees.iteritems():
- ranks = {}
- call_ratios = {}
- partials = {}
- self._rank_cycle_function(cycle, callee, 0, ranks)
- self._call_ratios_cycle(cycle, callee, ranks, call_ratios, set())
- partial = self._integrate_cycle_function(cycle, callee, call_ratio, partials, ranks, call_ratios, outevent, inevent)
- assert partial == max(partials.values())
- assert not total or abs(1.0 - partial/(call_ratio*total)) <= 0.001
-
- return cycle[outevent]
-
- def _rank_cycle_function(self, cycle, function, rank, ranks):
- if function not in ranks or ranks[function] > rank:
- ranks[function] = rank
- for call in function.calls.itervalues():
- if call.callee_id != function.id:
- callee = self.functions[call.callee_id]
- if callee.cycle is cycle:
- self._rank_cycle_function(cycle, callee, rank + 1, ranks)
-
- def _call_ratios_cycle(self, cycle, function, ranks, call_ratios, visited):
- if function not in visited:
- visited.add(function)
- for call in function.calls.itervalues():
- if call.callee_id != function.id:
- callee = self.functions[call.callee_id]
- if callee.cycle is cycle:
- if ranks[callee] > ranks[function]:
- call_ratios[callee] = call_ratios.get(callee, 0.0) + call.ratio
- self._call_ratios_cycle(cycle, callee, ranks, call_ratios, visited)
-
- def _integrate_cycle_function(self, cycle, function, partial_ratio, partials, ranks, call_ratios, outevent, inevent):
- if function not in partials:
- partial = partial_ratio*function[inevent]
- for call in function.calls.itervalues():
- if call.callee_id != function.id:
- callee = self.functions[call.callee_id]
- if callee.cycle is not cycle:
- assert outevent in call
- partial += partial_ratio*call[outevent]
- else:
- if ranks[callee] > ranks[function]:
- callee_partial = self._integrate_cycle_function(cycle, callee, partial_ratio, partials, ranks, call_ratios, outevent, inevent)
- call_ratio = ratio(call.ratio, call_ratios[callee])
- call_partial = call_ratio*callee_partial
- try:
- call[outevent] += call_partial
- except UndefinedEvent:
- call[outevent] = call_partial
- partial += call_partial
- partials[function] = partial
- try:
- function[outevent] += partial
- except UndefinedEvent:
- function[outevent] = partial
- return partials[function]
-
- def aggregate(self, event):
- """Aggregate an event for the whole profile."""
-
- total = event.null()
- for function in self.functions.itervalues():
- try:
- total = event.aggregate(total, function[event])
- except UndefinedEvent:
- return
- self[event] = total
-
- def ratio(self, outevent, inevent):
- assert outevent not in self
- assert inevent in self
- for function in self.functions.itervalues():
- assert outevent not in function
- assert inevent in function
- function[outevent] = ratio(function[inevent], self[inevent])
- for call in function.calls.itervalues():
- assert outevent not in call
- if inevent in call:
- call[outevent] = ratio(call[inevent], self[inevent])
- self[outevent] = 1.0
-
- def prune(self, node_thres, edge_thres):
- """Prune the profile"""
-
- # compute the prune ratios
- for function in self.functions.itervalues():
- try:
- function.weight = function[TOTAL_TIME_RATIO]
- except UndefinedEvent:
- pass
-
- for call in function.calls.itervalues():
- callee = self.functions[call.callee_id]
-
- if TOTAL_TIME_RATIO in call:
- # handle exact cases first
- call.weight = call[TOTAL_TIME_RATIO]
- else:
- try:
- # make a safe estimate
- call.weight = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO])
- except UndefinedEvent:
- pass
-
- # prune the nodes
- for function_id in self.functions.keys():
- function = self.functions[function_id]
- if function.weight is not None:
- if function.weight < node_thres:
- del self.functions[function_id]
-
- # prune the egdes
- for function in self.functions.itervalues():
- for callee_id in function.calls.keys():
- call = function.calls[callee_id]
- if callee_id not in self.functions or call.weight is not None and call.weight < edge_thres:
- del function.calls[callee_id]
-
- def dump(self):
- for function in self.functions.itervalues():
- sys.stderr.write('Function %s:\n' % (function.name,))
- self._dump_events(function.events)
- for call in function.calls.itervalues():
- callee = self.functions[call.callee_id]
- sys.stderr.write(' Call %s:\n' % (callee.name,))
- self._dump_events(call.events)
- for cycle in self.cycles:
- sys.stderr.write('Cycle:\n')
- self._dump_events(cycle.events)
- for function in cycle.functions:
- sys.stderr.write(' Function %s\n' % (function.name,))
-
- def _dump_events(self, events):
- for event, value in events.iteritems():
- sys.stderr.write(' %s: %s\n' % (event.name, event.format(value)))
-
-
-class Struct:
- """Masquerade a dictionary with a structure-like behavior."""
-
- def __init__(self, attrs = None):
- if attrs is None:
- attrs = {}
- self.__dict__['_attrs'] = attrs
-
- def __getattr__(self, name):
- try:
- return self._attrs[name]
- except KeyError:
- raise AttributeError(name)
-
- def __setattr__(self, name, value):
- self._attrs[name] = value
-
- def __str__(self):
- return str(self._attrs)
-
- def __repr__(self):
- return repr(self._attrs)
-
-
-class ParseError(Exception):
- """Raised when parsing to signal mismatches."""
-
- def __init__(self, msg, line):
- self.msg = msg
- # TODO: store more source line information
- self.line = line
-
- def __str__(self):
- return '%s: %r' % (self.msg, self.line)
-
-
-class Parser:
- """Parser interface."""
-
- def __init__(self):
- pass
-
- def parse(self):
- raise NotImplementedError
-
-
-class LineParser(Parser):
- """Base class for parsers that read line-based formats."""
-
- def __init__(self, file):
- Parser.__init__(self)
- self._file = file
- self.__line = None
- self.__eof = False
- self.line_no = 0
-
- def readline(self):
- line = self._file.readline()
- if not line:
- self.__line = ''
- self.__eof = True
- else:
- self.line_no += 1
- self.__line = line.rstrip('\r\n')
-
- def lookahead(self):
- assert self.__line is not None
- return self.__line
-
- def consume(self):
- assert self.__line is not None
- line = self.__line
- self.readline()
- return line
-
- def eof(self):
- assert self.__line is not None
- return self.__eof
-
-
-XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF = range(4)
-
-
-class XmlToken:
-
- def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
- assert type in (XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF)
- self.type = type
- self.name_or_data = name_or_data
- self.attrs = attrs
- self.line = line
- self.column = column
-
- def __str__(self):
- if self.type == XML_ELEMENT_START:
- return '<' + self.name_or_data + ' ...>'
- if self.type == XML_ELEMENT_END:
- return '' + self.name_or_data + '>'
- if self.type == XML_CHARACTER_DATA:
- return self.name_or_data
- if self.type == XML_EOF:
- return 'end of file'
- assert 0
-
-
-class XmlTokenizer:
- """Expat based XML tokenizer."""
-
- def __init__(self, fp, skip_ws = True):
- self.fp = fp
- self.tokens = []
- self.index = 0
- self.final = False
- self.skip_ws = skip_ws
-
- self.character_pos = 0, 0
- self.character_data = ''
-
- self.parser = xml.parsers.expat.ParserCreate()
- self.parser.StartElementHandler = self.handle_element_start
- self.parser.EndElementHandler = self.handle_element_end
- self.parser.CharacterDataHandler = self.handle_character_data
-
- def handle_element_start(self, name, attributes):
- self.finish_character_data()
- line, column = self.pos()
- token = XmlToken(XML_ELEMENT_START, name, attributes, line, column)
- self.tokens.append(token)
-
- def handle_element_end(self, name):
- self.finish_character_data()
- line, column = self.pos()
- token = XmlToken(XML_ELEMENT_END, name, None, line, column)
- self.tokens.append(token)
-
- def handle_character_data(self, data):
- if not self.character_data:
- self.character_pos = self.pos()
- self.character_data += data
-
- def finish_character_data(self):
- if self.character_data:
- if not self.skip_ws or not self.character_data.isspace():
- line, column = self.character_pos
- token = XmlToken(XML_CHARACTER_DATA, self.character_data, None, line, column)
- self.tokens.append(token)
- self.character_data = ''
-
- def next(self):
- size = 16*1024
- while self.index >= len(self.tokens) and not self.final:
- self.tokens = []
- self.index = 0
- data = self.fp.read(size)
- self.final = len(data) < size
- try:
- self.parser.Parse(data, self.final)
- except xml.parsers.expat.ExpatError, e:
- #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
- if e.code == 3:
- pass
- else:
- raise e
- if self.index >= len(self.tokens):
- line, column = self.pos()
- token = XmlToken(XML_EOF, None, None, line, column)
- else:
- token = self.tokens[self.index]
- self.index += 1
- return token
-
- def pos(self):
- return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
-
-
-class XmlTokenMismatch(Exception):
-
- def __init__(self, expected, found):
- self.expected = expected
- self.found = found
-
- def __str__(self):
- return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
-
-
-class XmlParser(Parser):
- """Base XML document parser."""
-
- def __init__(self, fp):
- Parser.__init__(self)
- self.tokenizer = XmlTokenizer(fp)
- self.consume()
-
- def consume(self):
- self.token = self.tokenizer.next()
-
- def match_element_start(self, name):
- return self.token.type == XML_ELEMENT_START and self.token.name_or_data == name
-
- def match_element_end(self, name):
- return self.token.type == XML_ELEMENT_END and self.token.name_or_data == name
-
- def element_start(self, name):
- while self.token.type == XML_CHARACTER_DATA:
- self.consume()
- if self.token.type != XML_ELEMENT_START:
- raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token)
- if self.token.name_or_data != name:
- raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token)
- attrs = self.token.attrs
- self.consume()
- return attrs
-
- def element_end(self, name):
- while self.token.type == XML_CHARACTER_DATA:
- self.consume()
- if self.token.type != XML_ELEMENT_END:
- raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token)
- if self.token.name_or_data != name:
- raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token)
- self.consume()
-
- def character_data(self, strip = True):
- data = ''
- while self.token.type == XML_CHARACTER_DATA:
- data += self.token.name_or_data
- self.consume()
- if strip:
- data = data.strip()
- return data
-
-
-class GprofParser(Parser):
- """Parser for GNU gprof output.
-
- See also:
- - Chapter "Interpreting gprof's Output" from the GNU gprof manual
- http://sourceware.org/binutils/docs-2.18/gprof/Call-Graph.html#Call-Graph
- - File "cg_print.c" from the GNU gprof source code
- http://sourceware.org/cgi-bin/cvsweb.cgi/~checkout~/src/gprof/cg_print.c?rev=1.12&cvsroot=src
- """
-
- def __init__(self, fp):
- Parser.__init__(self)
- self.fp = fp
- self.functions = {}
- self.cycles = {}
-
- def readline(self):
- line = self.fp.readline()
- if not line:
- sys.stderr.write('error: unexpected end of file\n')
- sys.exit(1)
- line = line.rstrip('\r\n')
- return line
-
- _int_re = re.compile(r'^\d+$')
- _float_re = re.compile(r'^\d+\.\d+$')
-
- def translate(self, mo):
- """Extract a structure from a match object, while translating the types in the process."""
- attrs = {}
- groupdict = mo.groupdict()
- for name, value in groupdict.iteritems():
- if value is None:
- value = None
- elif self._int_re.match(value):
- value = int(value)
- elif self._float_re.match(value):
- value = float(value)
- attrs[name] = (value)
- return Struct(attrs)
-
- _cg_header_re = re.compile(
- # original gprof header
- r'^\s+called/total\s+parents\s*$|' +
- r'^index\s+%time\s+self\s+descendents\s+called\+self\s+name\s+index\s*$|' +
- r'^\s+called/total\s+children\s*$|' +
- # GNU gprof header
- r'^index\s+%\s+time\s+self\s+children\s+called\s+name\s*$'
- )
-
- _cg_ignore_re = re.compile(
- # spontaneous
- r'^\s+\s*$|'
- # internal calls (such as "mcount")
- r'^.*\((\d+)\)$'
- )
-
- _cg_primary_re = re.compile(
- r'^\[(?P\d+)\]?' +
- r'\s+(?P\d+\.\d+)' +
- r'\s+(?P\d+\.\d+)' +
- r'\s+(?P\d+\.\d+)' +
- r'\s+(?:(?P\d+)(?:\+(?P\d+))?)?' +
- r'\s+(?P\S.*?)' +
- r'(?:\s+\d+)>)?' +
- r'\s\[(\d+)\]$'
- )
-
- _cg_parent_re = re.compile(
- r'^\s+(?P\d+\.\d+)?' +
- r'\s+(?P\d+\.\d+)?' +
- r'\s+(?P\d+)(?:/(?P\d+))?' +
- r'\s+(?P\S.*?)' +
- r'(?:\s+\d+)>)?' +
- r'\s\[(?P\d+)\]$'
- )
-
- _cg_child_re = _cg_parent_re
-
- _cg_cycle_header_re = re.compile(
- r'^\[(?P\d+)\]?' +
- r'\s+(?P\d+\.\d+)' +
- r'\s+(?P\d+\.\d+)' +
- r'\s+(?P\d+\.\d+)' +
- r'\s+(?:(?P\d+)(?:\+(?P\d+))?)?' +
- r'\s+\d+)\sas\sa\swhole>' +
- r'\s\[(\d+)\]$'
- )
-
- _cg_cycle_member_re = re.compile(
- r'^\s+(?P\d+\.\d+)?' +
- r'\s+(?P\d+\.\d+)?' +
- r'\s+(?P\d+)(?:\+(?P\d+))?' +
- r'\s+(?P\S.*?)' +
- r'(?:\s+\d+)>)?' +
- r'\s\[(?P\d+)\]$'
- )
-
- _cg_sep_re = re.compile(r'^--+$')
-
- def parse_function_entry(self, lines):
- parents = []
- children = []
-
- while True:
- if not lines:
- sys.stderr.write('warning: unexpected end of entry\n')
- line = lines.pop(0)
- if line.startswith('['):
- break
-
- # read function parent line
- mo = self._cg_parent_re.match(line)
- if not mo:
- if self._cg_ignore_re.match(line):
- continue
- sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
- else:
- parent = self.translate(mo)
- parents.append(parent)
-
- # read primary line
- mo = self._cg_primary_re.match(line)
- if not mo:
- sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
- return
- else:
- function = self.translate(mo)
-
- while lines:
- line = lines.pop(0)
-
- # read function subroutine line
- mo = self._cg_child_re.match(line)
- if not mo:
- if self._cg_ignore_re.match(line):
- continue
- sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
- else:
- child = self.translate(mo)
- children.append(child)
-
- function.parents = parents
- function.children = children
-
- self.functions[function.index] = function
-
- def parse_cycle_entry(self, lines):
-
- # read cycle header line
- line = lines[0]
- mo = self._cg_cycle_header_re.match(line)
- if not mo:
- sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
- return
- cycle = self.translate(mo)
-
- # read cycle member lines
- cycle.functions = []
- for line in lines[1:]:
- mo = self._cg_cycle_member_re.match(line)
- if not mo:
- sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
- continue
- call = self.translate(mo)
- cycle.functions.append(call)
-
- self.cycles[cycle.cycle] = cycle
-
- def parse_cg_entry(self, lines):
- if lines[0].startswith("["):
- self.parse_cycle_entry(lines)
- else:
- self.parse_function_entry(lines)
-
- def parse_cg(self):
- """Parse the call graph."""
-
- # skip call graph header
- while not self._cg_header_re.match(self.readline()):
- pass
- line = self.readline()
- while self._cg_header_re.match(line):
- line = self.readline()
-
- # process call graph entries
- entry_lines = []
- while line != '\014': # form feed
- if line and not line.isspace():
- if self._cg_sep_re.match(line):
- self.parse_cg_entry(entry_lines)
- entry_lines = []
- else:
- entry_lines.append(line)
- line = self.readline()
-
- def parse(self):
- self.parse_cg()
- self.fp.close()
-
- profile = Profile()
- profile[TIME] = 0.0
-
- cycles = {}
- for index in self.cycles.iterkeys():
- cycles[index] = Cycle()
-
- for entry in self.functions.itervalues():
- # populate the function
- function = Function(entry.index, entry.name)
- function[TIME] = entry.self
- if entry.called is not None:
- function.called = entry.called
- if entry.called_self is not None:
- call = Call(entry.index)
- call[CALLS] = entry.called_self
- function.called += entry.called_self
-
- # populate the function calls
- for child in entry.children:
- call = Call(child.index)
-
- assert child.called is not None
- call[CALLS] = child.called
-
- if child.index not in self.functions:
- # NOTE: functions that were never called but were discovered by gprof's
- # static call graph analysis dont have a call graph entry so we need
- # to add them here
- missing = Function(child.index, child.name)
- function[TIME] = 0.0
- function.called = 0
- profile.add_function(missing)
-
- function.add_call(call)
-
- profile.add_function(function)
-
- if entry.cycle is not None:
- try:
- cycle = cycles[entry.cycle]
- except KeyError:
- sys.stderr.write('warning: entry missing\n' % entry.cycle)
- cycle = Cycle()
- cycles[entry.cycle] = cycle
- cycle.add_function(function)
-
- profile[TIME] = profile[TIME] + function[TIME]
-
- for cycle in cycles.itervalues():
- profile.add_cycle(cycle)
-
- # Compute derived events
- profile.validate()
- profile.ratio(TIME_RATIO, TIME)
- profile.call_ratios(CALLS)
- profile.integrate(TOTAL_TIME, TIME)
- profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
-
- return profile
-
-
-class CallgrindParser(LineParser):
- """Parser for valgrind's callgrind tool.
-
- See also:
- - http://valgrind.org/docs/manual/cl-format.html
- """
-
- _call_re = re.compile('^calls=\s*(\d+)\s+((\d+|\+\d+|-\d+|\*)\s+)+$')
-
- def __init__(self, infile):
- LineParser.__init__(self, infile)
-
- # Textual positions
- self.position_ids = {}
- self.positions = {}
-
- # Numeric positions
- self.num_positions = 1
- self.cost_positions = ['line']
- self.last_positions = [0]
-
- # Events
- self.num_events = 0
- self.cost_events = []
-
- self.profile = Profile()
- self.profile[SAMPLES] = 0
-
- def parse(self):
- # read lookahead
- self.readline()
-
- self.parse_key('version')
- self.parse_key('creator')
- while self.parse_part():
- pass
- if not self.eof():
- sys.stderr.write('warning: line %u: unexpected line\n' % self.line_no)
- sys.stderr.write('%s\n' % self.lookahead())
-
- # compute derived data
- self.profile.validate()
- self.profile.find_cycles()
- self.profile.ratio(TIME_RATIO, SAMPLES)
- self.profile.call_ratios(CALLS)
- self.profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return self.profile
-
- def parse_part(self):
- if not self.parse_header_line():
- return False
- while self.parse_header_line():
- pass
- if not self.parse_body_line():
- return False
- while self.parse_body_line():
- pass
- return True
-
- def parse_header_line(self):
- return \
- self.parse_empty() or \
- self.parse_comment() or \
- self.parse_part_detail() or \
- self.parse_description() or \
- self.parse_event_specification() or \
- self.parse_cost_line_def() or \
- self.parse_cost_summary()
-
- _detail_keys = set(('cmd', 'pid', 'thread', 'part'))
-
- def parse_part_detail(self):
- return self.parse_keys(self._detail_keys)
-
- def parse_description(self):
- return self.parse_key('desc') is not None
-
- def parse_event_specification(self):
- event = self.parse_key('event')
- if event is None:
- return False
- return True
-
- def parse_cost_line_def(self):
- pair = self.parse_keys(('events', 'positions'))
- if pair is None:
- return False
- key, value = pair
- items = value.split()
- if key == 'events':
- self.num_events = len(items)
- self.cost_events = items
- if key == 'positions':
- self.num_positions = len(items)
- self.cost_positions = items
- self.last_positions = [0]*self.num_positions
- return True
-
- def parse_cost_summary(self):
- pair = self.parse_keys(('summary', 'totals'))
- if pair is None:
- return False
- return True
-
- def parse_body_line(self):
- return \
- self.parse_empty() or \
- self.parse_comment() or \
- self.parse_cost_line() or \
- self.parse_position_spec() or \
- self.parse_association_spec()
-
- __subpos_re = r'(0x[0-9a-fA-F]+|\d+|\+\d+|-\d+|\*)'
- _cost_re = re.compile(r'^' +
- __subpos_re + r'( +' + __subpos_re + r')*' +
- r'( +\d+)*' +
- '$')
-
- def parse_cost_line(self, calls=None):
- line = self.lookahead().rstrip()
- mo = self._cost_re.match(line)
- if not mo:
- return False
-
- function = self.get_function()
-
- if calls is None:
- # Unlike other aspects, call object (cob) is relative not to the
- # last call object, but to the caller's object (ob), so try to
- # update it when processing a functions cost line
- try:
- self.positions['cob'] = self.positions['ob']
- except KeyError:
- pass
-
- values = line.split()
- assert len(values) <= self.num_positions + self.num_events
-
- positions = values[0 : self.num_positions]
- events = values[self.num_positions : ]
- events += ['0']*(self.num_events - len(events))
-
- for i in range(self.num_positions):
- position = positions[i]
- if position == '*':
- position = self.last_positions[i]
- elif position[0] in '-+':
- position = self.last_positions[i] + int(position)
- elif position.startswith('0x'):
- position = int(position, 16)
- else:
- position = int(position)
- self.last_positions[i] = position
-
- events = map(float, events)
-
- if calls is None:
- function[SAMPLES] += events[0]
- self.profile[SAMPLES] += events[0]
- else:
- callee = self.get_callee()
- callee.called += calls
-
- try:
- call = function.calls[callee.id]
- except KeyError:
- call = Call(callee.id)
- call[CALLS] = calls
- call[SAMPLES] = events[0]
- function.add_call(call)
- else:
- call[CALLS] += calls
- call[SAMPLES] += events[0]
-
- self.consume()
- return True
-
- def parse_association_spec(self):
- line = self.lookahead()
- if not line.startswith('calls='):
- return False
-
- _, values = line.split('=', 1)
- values = values.strip().split()
- calls = int(values[0])
- call_position = values[1:]
- self.consume()
-
- self.parse_cost_line(calls)
-
- return True
-
- _position_re = re.compile('^(?P[cj]?(?:ob|fl|fi|fe|fn))=\s*(?:\((?P\d+)\))?(?:\s*(?P.+))?')
-
- _position_table_map = {
- 'ob': 'ob',
- 'fl': 'fl',
- 'fi': 'fl',
- 'fe': 'fl',
- 'fn': 'fn',
- 'cob': 'ob',
- 'cfl': 'fl',
- 'cfi': 'fl',
- 'cfe': 'fl',
- 'cfn': 'fn',
- 'jfi': 'fl',
- }
-
- _position_map = {
- 'ob': 'ob',
- 'fl': 'fl',
- 'fi': 'fl',
- 'fe': 'fl',
- 'fn': 'fn',
- 'cob': 'cob',
- 'cfl': 'cfl',
- 'cfi': 'cfl',
- 'cfe': 'cfl',
- 'cfn': 'cfn',
- 'jfi': 'jfi',
- }
-
- def parse_position_spec(self):
- line = self.lookahead()
-
- if line.startswith('jump=') or line.startswith('jcnd='):
- self.consume()
- return True
-
- mo = self._position_re.match(line)
- if not mo:
- return False
-
- position, id, name = mo.groups()
- if id:
- table = self._position_table_map[position]
- if name:
- self.position_ids[(table, id)] = name
- else:
- name = self.position_ids.get((table, id), '')
- self.positions[self._position_map[position]] = name
-
- self.consume()
- return True
-
- def parse_empty(self):
- if self.eof():
- return False
- line = self.lookahead()
- if line.strip():
- return False
- self.consume()
- return True
-
- def parse_comment(self):
- line = self.lookahead()
- if not line.startswith('#'):
- return False
- self.consume()
- return True
-
- _key_re = re.compile(r'^(\w+):')
-
- def parse_key(self, key):
- pair = self.parse_keys((key,))
- if not pair:
- return None
- key, value = pair
- return value
- line = self.lookahead()
- mo = self._key_re.match(line)
- if not mo:
- return None
- key, value = line.split(':', 1)
- if key not in keys:
- return None
- value = value.strip()
- self.consume()
- return key, value
-
- def parse_keys(self, keys):
- line = self.lookahead()
- mo = self._key_re.match(line)
- if not mo:
- return None
- key, value = line.split(':', 1)
- if key not in keys:
- return None
- value = value.strip()
- self.consume()
- return key, value
-
- def make_function(self, module, filename, name):
- # FIXME: module and filename are not being tracked reliably
- #id = '|'.join((module, filename, name))
- id = name
- try:
- function = self.profile.functions[id]
- except KeyError:
- function = Function(id, name)
- if module:
- function.module = os.path.basename(module)
- function[SAMPLES] = 0
- function.called = 0
- self.profile.add_function(function)
- return function
-
- def get_function(self):
- module = self.positions.get('ob', '')
- filename = self.positions.get('fl', '')
- function = self.positions.get('fn', '')
- return self.make_function(module, filename, function)
-
- def get_callee(self):
- module = self.positions.get('cob', '')
- filename = self.positions.get('cfi', '')
- function = self.positions.get('cfn', '')
- return self.make_function(module, filename, function)
-
-
-class PerfParser(LineParser):
- """Parser for linux perf callgraph output.
-
- It expects output generated with
-
- perf record -g
- perf script | gprof2dot.py --format=perf
- """
-
- def __init__(self, infile):
- LineParser.__init__(self, infile)
- self.profile = Profile()
-
- def readline(self):
- # Override LineParser.readline to ignore comment lines
- while True:
- LineParser.readline(self)
- if self.eof() or not self.lookahead().startswith('#'):
- break
-
- def parse(self):
- # read lookahead
- self.readline()
-
- profile = self.profile
- profile[SAMPLES] = 0
- while not self.eof():
- self.parse_event()
-
- # compute derived data
- profile.validate()
- profile.find_cycles()
- profile.ratio(TIME_RATIO, SAMPLES)
- profile.call_ratios(SAMPLES2)
- profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return profile
-
- def parse_event(self):
- if self.eof():
- return
-
- line = self.consume()
- assert line
-
- callchain = self.parse_callchain()
- if not callchain:
- return
-
- callee = callchain[0]
- callee[SAMPLES] += 1
- self.profile[SAMPLES] += 1
-
- for caller in callchain[1:]:
- try:
- call = caller.calls[callee.id]
- except KeyError:
- call = Call(callee.id)
- call[SAMPLES2] = 1
- caller.add_call(call)
- else:
- call[SAMPLES2] += 1
-
- callee = caller
-
- def parse_callchain(self):
- callchain = []
- while self.lookahead():
- function = self.parse_call()
- if function is None:
- break
- callchain.append(function)
- if self.lookahead() == '':
- self.consume()
- return callchain
-
- call_re = re.compile(r'^\s+(?P[0-9a-fA-F]+)\s+(?P.*)\s+\((?P[^)]*)\)$')
-
- def parse_call(self):
- line = self.consume()
- mo = self.call_re.match(line)
- assert mo
- if not mo:
- return None
-
- function_name = mo.group('symbol')
- if not function_name:
- function_name = mo.group('address')
-
- module = mo.group('module')
-
- function_id = function_name + ':' + module
-
- try:
- function = self.profile.functions[function_id]
- except KeyError:
- function = Function(function_id, function_name)
- function.module = os.path.basename(module)
- function[SAMPLES] = 0
- self.profile.add_function(function)
-
- return function
-
-
-class OprofileParser(LineParser):
- """Parser for oprofile callgraph output.
-
- See also:
- - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph
- """
-
- _fields_re = {
- 'samples': r'(\d+)',
- '%': r'(\S+)',
- 'linenr info': r'(?P\(no location information\)|\S+:\d+)',
- 'image name': r'(?P\S+(?:\s\(tgid:[^)]*\))?)',
- 'app name': r'(?P\S+)',
- 'symbol name': r'(?P\(no symbols\)|.+?)',
- }
-
- def __init__(self, infile):
- LineParser.__init__(self, infile)
- self.entries = {}
- self.entry_re = None
-
- def add_entry(self, callers, function, callees):
- try:
- entry = self.entries[function.id]
- except KeyError:
- self.entries[function.id] = (callers, function, callees)
- else:
- callers_total, function_total, callees_total = entry
- self.update_subentries_dict(callers_total, callers)
- function_total.samples += function.samples
- self.update_subentries_dict(callees_total, callees)
-
- def update_subentries_dict(self, totals, partials):
- for partial in partials.itervalues():
- try:
- total = totals[partial.id]
- except KeyError:
- totals[partial.id] = partial
- else:
- total.samples += partial.samples
-
- def parse(self):
- # read lookahead
- self.readline()
-
- self.parse_header()
- while self.lookahead():
- self.parse_entry()
-
- profile = Profile()
-
- reverse_call_samples = {}
-
- # populate the profile
- profile[SAMPLES] = 0
- for _callers, _function, _callees in self.entries.itervalues():
- function = Function(_function.id, _function.name)
- function[SAMPLES] = _function.samples
- profile.add_function(function)
- profile[SAMPLES] += _function.samples
-
- if _function.application:
- function.process = os.path.basename(_function.application)
- if _function.image:
- function.module = os.path.basename(_function.image)
-
- total_callee_samples = 0
- for _callee in _callees.itervalues():
- total_callee_samples += _callee.samples
-
- for _callee in _callees.itervalues():
- if not _callee.self:
- call = Call(_callee.id)
- call[SAMPLES2] = _callee.samples
- function.add_call(call)
-
- # compute derived data
- profile.validate()
- profile.find_cycles()
- profile.ratio(TIME_RATIO, SAMPLES)
- profile.call_ratios(SAMPLES2)
- profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return profile
-
- def parse_header(self):
- while not self.match_header():
- self.consume()
- line = self.lookahead()
- fields = re.split(r'\s\s+', line)
- entry_re = r'^\s*' + r'\s+'.join([self._fields_re[field] for field in fields]) + r'(?P\s+\[self\])?$'
- self.entry_re = re.compile(entry_re)
- self.skip_separator()
-
- def parse_entry(self):
- callers = self.parse_subentries()
- if self.match_primary():
- function = self.parse_subentry()
- if function is not None:
- callees = self.parse_subentries()
- self.add_entry(callers, function, callees)
- self.skip_separator()
-
- def parse_subentries(self):
- subentries = {}
- while self.match_secondary():
- subentry = self.parse_subentry()
- subentries[subentry.id] = subentry
- return subentries
-
- def parse_subentry(self):
- entry = Struct()
- line = self.consume()
- mo = self.entry_re.match(line)
- if not mo:
- raise ParseError('failed to parse', line)
- fields = mo.groupdict()
- entry.samples = int(mo.group(1))
- if 'source' in fields and fields['source'] != '(no location information)':
- source = fields['source']
- filename, lineno = source.split(':')
- entry.filename = filename
- entry.lineno = int(lineno)
- else:
- source = ''
- entry.filename = None
- entry.lineno = None
- entry.image = fields.get('image', '')
- entry.application = fields.get('application', '')
- if 'symbol' in fields and fields['symbol'] != '(no symbols)':
- entry.symbol = fields['symbol']
- else:
- entry.symbol = ''
- if entry.symbol.startswith('"') and entry.symbol.endswith('"'):
- entry.symbol = entry.symbol[1:-1]
- entry.id = ':'.join((entry.application, entry.image, source, entry.symbol))
- entry.self = fields.get('self', None) != None
- if entry.self:
- entry.id += ':self'
- if entry.symbol:
- entry.name = entry.symbol
- else:
- entry.name = entry.image
- return entry
-
- def skip_separator(self):
- while not self.match_separator():
- self.consume()
- self.consume()
-
- def match_header(self):
- line = self.lookahead()
- return line.startswith('samples')
-
- def match_separator(self):
- line = self.lookahead()
- return line == '-'*len(line)
-
- def match_primary(self):
- line = self.lookahead()
- return not line[:1].isspace()
-
- def match_secondary(self):
- line = self.lookahead()
- return line[:1].isspace()
-
-
-class HProfParser(LineParser):
- """Parser for java hprof output
-
- See also:
- - http://java.sun.com/developer/technicalArticles/Programming/HPROF.html
- """
-
- trace_re = re.compile(r'\t(.*)\((.*):(.*)\)')
- trace_id_re = re.compile(r'^TRACE (\d+):$')
-
- def __init__(self, infile):
- LineParser.__init__(self, infile)
- self.traces = {}
- self.samples = {}
-
- def parse(self):
- # read lookahead
- self.readline()
-
- while not self.lookahead().startswith('------'): self.consume()
- while not self.lookahead().startswith('TRACE '): self.consume()
-
- self.parse_traces()
-
- while not self.lookahead().startswith('CPU'):
- self.consume()
-
- self.parse_samples()
-
- # populate the profile
- profile = Profile()
- profile[SAMPLES] = 0
-
- functions = {}
-
- # build up callgraph
- for id, trace in self.traces.iteritems():
- if not id in self.samples: continue
- mtime = self.samples[id][0]
- last = None
-
- for func, file, line in trace:
- if not func in functions:
- function = Function(func, func)
- function[SAMPLES] = 0
- profile.add_function(function)
- functions[func] = function
-
- function = functions[func]
- # allocate time to the deepest method in the trace
- if not last:
- function[SAMPLES] += mtime
- profile[SAMPLES] += mtime
- else:
- c = function.get_call(last)
- c[SAMPLES2] += mtime
-
- last = func
-
- # compute derived data
- profile.validate()
- profile.find_cycles()
- profile.ratio(TIME_RATIO, SAMPLES)
- profile.call_ratios(SAMPLES2)
- profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return profile
-
- def parse_traces(self):
- while self.lookahead().startswith('TRACE '):
- self.parse_trace()
-
- def parse_trace(self):
- l = self.consume()
- mo = self.trace_id_re.match(l)
- tid = mo.group(1)
- last = None
- trace = []
-
- while self.lookahead().startswith('\t'):
- l = self.consume()
- match = self.trace_re.search(l)
- if not match:
- #sys.stderr.write('Invalid line: %s\n' % l)
- break
- else:
- function_name, file, line = match.groups()
- trace += [(function_name, file, line)]
-
- self.traces[int(tid)] = trace
-
- def parse_samples(self):
- self.consume()
- self.consume()
-
- while not self.lookahead().startswith('CPU'):
- rank, percent_self, percent_accum, count, traceid, method = self.lookahead().split()
- self.samples[int(traceid)] = (int(count), method)
- self.consume()
-
-
-class SysprofParser(XmlParser):
-
- def __init__(self, stream):
- XmlParser.__init__(self, stream)
-
- def parse(self):
- objects = {}
- nodes = {}
-
- self.element_start('profile')
- while self.token.type == XML_ELEMENT_START:
- if self.token.name_or_data == 'objects':
- assert not objects
- objects = self.parse_items('objects')
- elif self.token.name_or_data == 'nodes':
- assert not nodes
- nodes = self.parse_items('nodes')
- else:
- self.parse_value(self.token.name_or_data)
- self.element_end('profile')
-
- return self.build_profile(objects, nodes)
-
- def parse_items(self, name):
- assert name[-1] == 's'
- items = {}
- self.element_start(name)
- while self.token.type == XML_ELEMENT_START:
- id, values = self.parse_item(name[:-1])
- assert id not in items
- items[id] = values
- self.element_end(name)
- return items
-
- def parse_item(self, name):
- attrs = self.element_start(name)
- id = int(attrs['id'])
- values = self.parse_values()
- self.element_end(name)
- return id, values
-
- def parse_values(self):
- values = {}
- while self.token.type == XML_ELEMENT_START:
- name = self.token.name_or_data
- value = self.parse_value(name)
- assert name not in values
- values[name] = value
- return values
-
- def parse_value(self, tag):
- self.element_start(tag)
- value = self.character_data()
- self.element_end(tag)
- if value.isdigit():
- return int(value)
- if value.startswith('"') and value.endswith('"'):
- return value[1:-1]
- return value
-
- def build_profile(self, objects, nodes):
- profile = Profile()
-
- profile[SAMPLES] = 0
- for id, object in objects.iteritems():
- # Ignore fake objects (process names, modules, "Everything", "kernel", etc.)
- if object['self'] == 0:
- continue
-
- function = Function(id, object['name'])
- function[SAMPLES] = object['self']
- profile.add_function(function)
- profile[SAMPLES] += function[SAMPLES]
-
- for id, node in nodes.iteritems():
- # Ignore fake calls
- if node['self'] == 0:
- continue
-
- # Find a non-ignored parent
- parent_id = node['parent']
- while parent_id != 0:
- parent = nodes[parent_id]
- caller_id = parent['object']
- if objects[caller_id]['self'] != 0:
- break
- parent_id = parent['parent']
- if parent_id == 0:
- continue
-
- callee_id = node['object']
-
- assert objects[caller_id]['self']
- assert objects[callee_id]['self']
-
- function = profile.functions[caller_id]
-
- samples = node['self']
- try:
- call = function.calls[callee_id]
- except KeyError:
- call = Call(callee_id)
- call[SAMPLES2] = samples
- function.add_call(call)
- else:
- call[SAMPLES2] += samples
-
- # Compute derived events
- profile.validate()
- profile.find_cycles()
- profile.ratio(TIME_RATIO, SAMPLES)
- profile.call_ratios(SAMPLES2)
- profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return profile
-
-
-class SharkParser(LineParser):
- """Parser for MacOSX Shark output.
-
- Author: tom@dbservice.com
- """
-
- def __init__(self, infile):
- LineParser.__init__(self, infile)
- self.stack = []
- self.entries = {}
-
- def add_entry(self, function):
- try:
- entry = self.entries[function.id]
- except KeyError:
- self.entries[function.id] = (function, { })
- else:
- function_total, callees_total = entry
- function_total.samples += function.samples
-
- def add_callee(self, function, callee):
- func, callees = self.entries[function.id]
- try:
- entry = callees[callee.id]
- except KeyError:
- callees[callee.id] = callee
- else:
- entry.samples += callee.samples
-
- def parse(self):
- self.readline()
- self.readline()
- self.readline()
- self.readline()
-
- match = re.compile(r'(?P[|+ ]*)(?P\d+), (?P[^,]+), (?P.*)')
-
- while self.lookahead():
- line = self.consume()
- mo = match.match(line)
- if not mo:
- raise ParseError('failed to parse', line)
-
- fields = mo.groupdict()
- prefix = len(fields.get('prefix', 0)) / 2 - 1
-
- symbol = str(fields.get('symbol', 0))
- image = str(fields.get('image', 0))
-
- entry = Struct()
- entry.id = ':'.join([symbol, image])
- entry.samples = int(fields.get('samples', 0))
-
- entry.name = symbol
- entry.image = image
-
- # adjust the callstack
- if prefix < len(self.stack):
- del self.stack[prefix:]
-
- if prefix == len(self.stack):
- self.stack.append(entry)
-
- # if the callstack has had an entry, it's this functions caller
- if prefix > 0:
- self.add_callee(self.stack[prefix - 1], entry)
-
- self.add_entry(entry)
-
- profile = Profile()
- profile[SAMPLES] = 0
- for _function, _callees in self.entries.itervalues():
- function = Function(_function.id, _function.name)
- function[SAMPLES] = _function.samples
- profile.add_function(function)
- profile[SAMPLES] += _function.samples
-
- if _function.image:
- function.module = os.path.basename(_function.image)
-
- for _callee in _callees.itervalues():
- call = Call(_callee.id)
- call[SAMPLES] = _callee.samples
- function.add_call(call)
-
- # compute derived data
- profile.validate()
- profile.find_cycles()
- profile.ratio(TIME_RATIO, SAMPLES)
- profile.call_ratios(SAMPLES)
- profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return profile
-
-
-class XPerfParser(Parser):
- """Parser for CSVs generted by XPerf, from Microsoft Windows Performance Tools.
- """
-
- def __init__(self, stream):
- Parser.__init__(self)
- self.stream = stream
- self.profile = Profile()
- self.profile[SAMPLES] = 0
- self.column = {}
-
- def parse(self):
- import csv
- reader = csv.reader(
- self.stream,
- delimiter = ',',
- quotechar = None,
- escapechar = None,
- doublequote = False,
- skipinitialspace = True,
- lineterminator = '\r\n',
- quoting = csv.QUOTE_NONE)
- it = iter(reader)
- row = reader.next()
- self.parse_header(row)
- for row in it:
- self.parse_row(row)
-
- # compute derived data
- self.profile.validate()
- self.profile.find_cycles()
- self.profile.ratio(TIME_RATIO, SAMPLES)
- self.profile.call_ratios(SAMPLES2)
- self.profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return self.profile
-
- def parse_header(self, row):
- for column in range(len(row)):
- name = row[column]
- assert name not in self.column
- self.column[name] = column
-
- def parse_row(self, row):
- fields = {}
- for name, column in self.column.iteritems():
- value = row[column]
- for factory in int, float:
- try:
- value = factory(value)
- except ValueError:
- pass
- else:
- break
- fields[name] = value
-
- process = fields['Process Name']
- symbol = fields['Module'] + '!' + fields['Function']
- weight = fields['Weight']
- count = fields['Count']
-
- function = self.get_function(process, symbol)
- function[SAMPLES] += weight * count
- self.profile[SAMPLES] += weight * count
-
- stack = fields['Stack']
- if stack != '?':
- stack = stack.split('/')
- assert stack[0] == '[Root]'
- if stack[-1] != symbol:
- # XXX: some cases the sampled function does not appear in the stack
- stack.append(symbol)
- caller = None
- for symbol in stack[1:]:
- callee = self.get_function(process, symbol)
- if caller is not None:
- try:
- call = caller.calls[callee.id]
- except KeyError:
- call = Call(callee.id)
- call[SAMPLES2] = count
- caller.add_call(call)
- else:
- call[SAMPLES2] += count
- caller = callee
-
- def get_function(self, process, symbol):
- function_id = process + '!' + symbol
-
- try:
- function = self.profile.functions[function_id]
- except KeyError:
- module, name = symbol.split('!', 1)
- function = Function(function_id, name)
- function.process = process
- function.module = module
- function[SAMPLES] = 0
- self.profile.add_function(function)
-
- return function
-
-
-class SleepyParser(Parser):
- """Parser for GNU gprof output.
-
- See also:
- - http://www.codersnotes.com/sleepy/
- - http://sleepygraph.sourceforge.net/
- """
-
- def __init__(self, filename):
- Parser.__init__(self)
-
- from zipfile import ZipFile
-
- self.database = ZipFile(filename)
-
- self.version_0_7 = 'Version 0.7 required' in self.database.namelist()
-
- self.symbols = {}
- self.calls = {}
-
- self.profile = Profile()
-
- _symbol_re = re.compile(
- r'^(?P\w+)' +
- r'\s+"(?P[^"]*)"' +
- r'\s+"(?P[^"]*)"' +
- r'\s+"(?P[^"]*)"' +
- r'\s+(?P\d+)$'
- )
-
- def parse_symbols(self):
- if self.version_0_7:
- symbols_txt = 'Symbols.txt'
- else:
- symbols_txt = 'symbols.txt'
- lines = self.database.read(symbols_txt).splitlines()
- for line in lines:
- mo = self._symbol_re.match(line)
- if mo:
- symbol_id, module, procname, sourcefile, sourceline = mo.groups()
-
- function_id = ':'.join([module, procname])
-
- try:
- function = self.profile.functions[function_id]
- except KeyError:
- function = Function(function_id, procname)
- function.module = module
- function[SAMPLES] = 0
- self.profile.add_function(function)
-
- self.symbols[symbol_id] = function
-
- def parse_callstacks(self):
- if self.version_0_7:
- callstacks_txt = 'Callstacks.txt'
- else:
- callstacks_txt = 'callstacks.txt'
- lines = self.database.read(callstacks_txt).splitlines()
- for line in lines:
- fields = line.split()
- samples = float(fields[0])
- callstack = fields[1:]
-
- callstack = [self.symbols[symbol_id] for symbol_id in callstack]
-
- callee = callstack[0]
-
- callee[SAMPLES] += samples
- self.profile[SAMPLES] += samples
-
- for caller in callstack[1:]:
- try:
- call = caller.calls[callee.id]
- except KeyError:
- call = Call(callee.id)
- call[SAMPLES2] = samples
- caller.add_call(call)
- else:
- call[SAMPLES2] += samples
-
- callee = caller
-
- def parse(self):
- profile = self.profile
- profile[SAMPLES] = 0
-
- self.parse_symbols()
- self.parse_callstacks()
-
- # Compute derived events
- profile.validate()
- profile.find_cycles()
- profile.ratio(TIME_RATIO, SAMPLES)
- profile.call_ratios(SAMPLES2)
- profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
-
- return profile
-
-
-class AQtimeTable:
-
- def __init__(self, name, fields):
- self.name = name
-
- self.fields = fields
- self.field_column = {}
- for column in range(len(fields)):
- self.field_column[fields[column]] = column
- self.rows = []
-
- def __len__(self):
- return len(self.rows)
-
- def __iter__(self):
- for values, children in self.rows:
- fields = {}
- for name, value in zip(self.fields, values):
- fields[name] = value
- children = dict([(child.name, child) for child in children])
- yield fields, children
- raise StopIteration
-
- def add_row(self, values, children=()):
- self.rows.append((values, children))
-
-
-class AQtimeParser(XmlParser):
-
- def __init__(self, stream):
- XmlParser.__init__(self, stream)
- self.tables = {}
-
- def parse(self):
- self.element_start('AQtime_Results')
- self.parse_headers()
- results = self.parse_results()
- self.element_end('AQtime_Results')
- return self.build_profile(results)
-
- def parse_headers(self):
- self.element_start('HEADERS')
- while self.token.type == XML_ELEMENT_START:
- self.parse_table_header()
- self.element_end('HEADERS')
-
- def parse_table_header(self):
- attrs = self.element_start('TABLE_HEADER')
- name = attrs['NAME']
- id = int(attrs['ID'])
- field_types = []
- field_names = []
- while self.token.type == XML_ELEMENT_START:
- field_type, field_name = self.parse_table_field()
- field_types.append(field_type)
- field_names.append(field_name)
- self.element_end('TABLE_HEADER')
- self.tables[id] = name, field_types, field_names
-
- def parse_table_field(self):
- attrs = self.element_start('TABLE_FIELD')
- type = attrs['TYPE']
- name = self.character_data()
- self.element_end('TABLE_FIELD')
- return type, name
-
- def parse_results(self):
- self.element_start('RESULTS')
- table = self.parse_data()
- self.element_end('RESULTS')
- return table
-
- def parse_data(self):
- rows = []
- attrs = self.element_start('DATA')
- table_id = int(attrs['TABLE_ID'])
- table_name, field_types, field_names = self.tables[table_id]
- table = AQtimeTable(table_name, field_names)
- while self.token.type == XML_ELEMENT_START:
- row, children = self.parse_row(field_types)
- table.add_row(row, children)
- self.element_end('DATA')
- return table
-
- def parse_row(self, field_types):
- row = [None]*len(field_types)
- children = []
- self.element_start('ROW')
- while self.token.type == XML_ELEMENT_START:
- if self.token.name_or_data == 'FIELD':
- field_id, field_value = self.parse_field(field_types)
- row[field_id] = field_value
- elif self.token.name_or_data == 'CHILDREN':
- children = self.parse_children()
- else:
- raise XmlTokenMismatch(" or ", self.token)
- self.element_end('ROW')
- return row, children
-
- def parse_field(self, field_types):
- attrs = self.element_start('FIELD')
- id = int(attrs['ID'])
- type = field_types[id]
- value = self.character_data()
- if type == 'Integer':
- value = int(value)
- elif type == 'Float':
- value = float(value)
- elif type == 'Address':
- value = int(value)
- elif type == 'String':
- pass
- else:
- assert False
- self.element_end('FIELD')
- return id, value
-
- def parse_children(self):
- children = []
- self.element_start('CHILDREN')
- while self.token.type == XML_ELEMENT_START:
- table = self.parse_data()
- assert table.name not in children
- children.append(table)
- self.element_end('CHILDREN')
- return children
-
- def build_profile(self, results):
- assert results.name == 'Routines'
- profile = Profile()
- profile[TIME] = 0.0
- for fields, tables in results:
- function = self.build_function(fields)
- children = tables['Children']
- for fields, _ in children:
- call = self.build_call(fields)
- function.add_call(call)
- profile.add_function(function)
- profile[TIME] = profile[TIME] + function[TIME]
- profile[TOTAL_TIME] = profile[TIME]
- profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
- return profile
-
- def build_function(self, fields):
- function = Function(self.build_id(fields), self.build_name(fields))
- function[TIME] = fields['Time']
- function[TOTAL_TIME] = fields['Time with Children']
- #function[TIME_RATIO] = fields['% Time']/100.0
- #function[TOTAL_TIME_RATIO] = fields['% with Children']/100.0
- return function
-
- def build_call(self, fields):
- call = Call(self.build_id(fields))
- call[TIME] = fields['Time']
- call[TOTAL_TIME] = fields['Time with Children']
- #call[TIME_RATIO] = fields['% Time']/100.0
- #call[TOTAL_TIME_RATIO] = fields['% with Children']/100.0
- return call
-
- def build_id(self, fields):
- return ':'.join([fields['Module Name'], fields['Unit Name'], fields['Routine Name']])
-
- def build_name(self, fields):
- # TODO: use more fields
- return fields['Routine Name']
-
-
-class PstatsParser:
- """Parser python profiling statistics saved with te pstats module."""
-
- def __init__(self, *filename):
- import pstats
- try:
- self.stats = pstats.Stats(*filename)
- except ValueError:
- import hotshot.stats
- self.stats = hotshot.stats.load(filename[0])
- self.profile = Profile()
- self.function_ids = {}
-
- def get_function_name(self, (filename, line, name)):
- module = os.path.splitext(filename)[0]
- module = os.path.basename(module)
- return "%s:%d:%s" % (module, line, name)
-
- def get_function(self, key):
- try:
- id = self.function_ids[key]
- except KeyError:
- id = len(self.function_ids)
- name = self.get_function_name(key)
- function = Function(id, name)
- self.profile.functions[id] = function
- self.function_ids[key] = id
- else:
- function = self.profile.functions[id]
- return function
-
- def parse(self):
- self.profile[TIME] = 0.0
- self.profile[TOTAL_TIME] = self.stats.total_tt
- for fn, (cc, nc, tt, ct, callers) in self.stats.stats.iteritems():
- callee = self.get_function(fn)
- callee.called = nc
- callee[TOTAL_TIME] = ct
- callee[TIME] = tt
- self.profile[TIME] += tt
- self.profile[TOTAL_TIME] = max(self.profile[TOTAL_TIME], ct)
- for fn, value in callers.iteritems():
- caller = self.get_function(fn)
- call = Call(callee.id)
- if isinstance(value, tuple):
- for i in xrange(0, len(value), 4):
- nc, cc, tt, ct = value[i:i+4]
- if CALLS in call:
- call[CALLS] += cc
- else:
- call[CALLS] = cc
-
- if TOTAL_TIME in call:
- call[TOTAL_TIME] += ct
- else:
- call[TOTAL_TIME] = ct
-
- else:
- call[CALLS] = value
- call[TOTAL_TIME] = ratio(value, nc)*ct
-
- caller.add_call(call)
- #self.stats.print_stats()
- #self.stats.print_callees()
-
- # Compute derived events
- self.profile.validate()
- self.profile.ratio(TIME_RATIO, TIME)
- self.profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
-
- return self.profile
-
-
-class Theme:
-
- def __init__(self,
- bgcolor = (0.0, 0.0, 1.0),
- mincolor = (0.0, 0.0, 0.0),
- maxcolor = (0.0, 0.0, 1.0),
- fontname = "Arial",
- minfontsize = 10.0,
- maxfontsize = 10.0,
- minpenwidth = 0.5,
- maxpenwidth = 4.0,
- gamma = 2.2,
- skew = 1.0):
- self.bgcolor = bgcolor
- self.mincolor = mincolor
- self.maxcolor = maxcolor
- self.fontname = fontname
- self.minfontsize = minfontsize
- self.maxfontsize = maxfontsize
- self.minpenwidth = minpenwidth
- self.maxpenwidth = maxpenwidth
- self.gamma = gamma
- self.skew = skew
-
- def graph_bgcolor(self):
- return self.hsl_to_rgb(*self.bgcolor)
-
- def graph_fontname(self):
- return self.fontname
-
- def graph_fontsize(self):
- return self.minfontsize
-
- def node_bgcolor(self, weight):
- return self.color(weight)
-
- def node_fgcolor(self, weight):
- return self.graph_bgcolor()
-
- def node_fontsize(self, weight):
- return self.fontsize(weight)
-
- def edge_color(self, weight):
- return self.color(weight)
-
- def edge_fontsize(self, weight):
- return self.fontsize(weight)
-
- def edge_penwidth(self, weight):
- return max(weight*self.maxpenwidth, self.minpenwidth)
-
- def edge_arrowsize(self, weight):
- return 0.5 * math.sqrt(self.edge_penwidth(weight))
-
- def fontsize(self, weight):
- return max(weight**2 * self.maxfontsize, self.minfontsize)
-
- def color(self, weight):
- weight = min(max(weight, 0.0), 1.0)
-
- hmin, smin, lmin = self.mincolor
- hmax, smax, lmax = self.maxcolor
-
- if self.skew < 0:
- raise ValueError("Skew must be greater than 0")
- elif self.skew == 1.0:
- h = hmin + weight*(hmax - hmin)
- s = smin + weight*(smax - smin)
- l = lmin + weight*(lmax - lmin)
- else:
- base = self.skew
- h = hmin + ((hmax-hmin)*(-1.0 + (base ** weight)) / (base - 1.0))
- s = smin + ((smax-smin)*(-1.0 + (base ** weight)) / (base - 1.0))
- l = lmin + ((lmax-lmin)*(-1.0 + (base ** weight)) / (base - 1.0))
-
- return self.hsl_to_rgb(h, s, l)
-
- def hsl_to_rgb(self, h, s, l):
- """Convert a color from HSL color-model to RGB.
-
- See also:
- - http://www.w3.org/TR/css3-color/#hsl-color
- """
-
- h = h % 1.0
- s = min(max(s, 0.0), 1.0)
- l = min(max(l, 0.0), 1.0)
-
- if l <= 0.5:
- m2 = l*(s + 1.0)
- else:
- m2 = l + s - l*s
- m1 = l*2.0 - m2
- r = self._hue_to_rgb(m1, m2, h + 1.0/3.0)
- g = self._hue_to_rgb(m1, m2, h)
- b = self._hue_to_rgb(m1, m2, h - 1.0/3.0)
-
- # Apply gamma correction
- r **= self.gamma
- g **= self.gamma
- b **= self.gamma
-
- return (r, g, b)
-
- def _hue_to_rgb(self, m1, m2, h):
- if h < 0.0:
- h += 1.0
- elif h > 1.0:
- h -= 1.0
- if h*6 < 1.0:
- return m1 + (m2 - m1)*h*6.0
- elif h*2 < 1.0:
- return m2
- elif h*3 < 2.0:
- return m1 + (m2 - m1)*(2.0/3.0 - h)*6.0
- else:
- return m1
-
-
-TEMPERATURE_COLORMAP = Theme(
- mincolor = (2.0/3.0, 0.80, 0.25), # dark blue
- maxcolor = (0.0, 1.0, 0.5), # satured red
- gamma = 1.0
-)
-
-PINK_COLORMAP = Theme(
- mincolor = (0.0, 1.0, 0.90), # pink
- maxcolor = (0.0, 1.0, 0.5), # satured red
-)
-
-GRAY_COLORMAP = Theme(
- mincolor = (0.0, 0.0, 0.85), # light gray
- maxcolor = (0.0, 0.0, 0.0), # black
-)
-
-BW_COLORMAP = Theme(
- minfontsize = 8.0,
- maxfontsize = 24.0,
- mincolor = (0.0, 0.0, 0.0), # black
- maxcolor = (0.0, 0.0, 0.0), # black
- minpenwidth = 0.1,
- maxpenwidth = 8.0,
-)
-
-
-class DotWriter:
- """Writer for the DOT language.
-
- See also:
- - "The DOT Language" specification
- http://www.graphviz.org/doc/info/lang.html
- """
-
- strip = False
- wrap = False
-
- def __init__(self, fp):
- self.fp = fp
-
- def wrap_function_name(self, name):
- """Split the function name on multiple lines."""
-
- if len(name) > 32:
- ratio = 2.0/3.0
- height = max(int(len(name)/(1.0 - ratio) + 0.5), 1)
- width = max(len(name)/height, 32)
- # TODO: break lines in symbols
- name = textwrap.fill(name, width, break_long_words=False)
-
- # Take away spaces
- name = name.replace(", ", ",")
- name = name.replace("> >", ">>")
- name = name.replace("> >", ">>") # catch consecutive
-
- return name
-
- def graph(self, profile, theme):
- self.begin_graph()
-
- fontname = theme.graph_fontname()
-
- self.attr('graph', fontname=fontname, ranksep=0.25, nodesep=0.125)
- self.attr('node', fontname=fontname, shape="box", style="filled", fontcolor="white", width=0, height=0)
- self.attr('edge', fontname=fontname)
-
- for function in profile.functions.itervalues():
- labels = []
- if function.process is not None:
- labels.append(function.process)
- if function.module is not None:
- labels.append(function.module)
-
- if self.strip:
- function_name = function.stripped_name()
- else:
- function_name = function.name
- if self.wrap:
- function_name = self.wrap_function_name(function_name)
- labels.append(function_name)
-
- for event in TOTAL_TIME_RATIO, TIME_RATIO:
- if event in function.events:
- label = event.format(function[event])
- labels.append(label)
- if function.called is not None:
- labels.append(u"%u\xd7" % (function.called,))
-
- if function.weight is not None:
- weight = function.weight
- else:
- weight = 0.0
-
- label = '\n'.join(labels)
- self.node(function.id,
- label = label,
- color = self.color(theme.node_bgcolor(weight)),
- fontcolor = self.color(theme.node_fgcolor(weight)),
- fontsize = "%.2f" % theme.node_fontsize(weight),
- )
-
- for call in function.calls.itervalues():
- callee = profile.functions[call.callee_id]
-
- labels = []
- for event in TOTAL_TIME_RATIO, CALLS:
- if event in call.events:
- label = event.format(call[event])
- labels.append(label)
-
- if call.weight is not None:
- weight = call.weight
- elif callee.weight is not None:
- weight = callee.weight
- else:
- weight = 0.0
-
- label = '\n'.join(labels)
-
- self.edge(function.id, call.callee_id,
- label = label,
- color = self.color(theme.edge_color(weight)),
- fontcolor = self.color(theme.edge_color(weight)),
- fontsize = "%.2f" % theme.edge_fontsize(weight),
- penwidth = "%.2f" % theme.edge_penwidth(weight),
- labeldistance = "%.2f" % theme.edge_penwidth(weight),
- arrowsize = "%.2f" % theme.edge_arrowsize(weight),
- )
-
- self.end_graph()
-
- def begin_graph(self):
- self.write('digraph {\n')
-
- def end_graph(self):
- self.write('}\n')
-
- def attr(self, what, **attrs):
- self.write("\t")
- self.write(what)
- self.attr_list(attrs)
- self.write(";\n")
-
- def node(self, node, **attrs):
- self.write("\t")
- self.id(node)
- self.attr_list(attrs)
- self.write(";\n")
-
- def edge(self, src, dst, **attrs):
- self.write("\t")
- self.id(src)
- self.write(" -> ")
- self.id(dst)
- self.attr_list(attrs)
- self.write(";\n")
-
- def attr_list(self, attrs):
- if not attrs:
- return
- self.write(' [')
- first = True
- for name, value in attrs.iteritems():
- if first:
- first = False
- else:
- self.write(", ")
- self.id(name)
- self.write('=')
- self.id(value)
- self.write(']')
-
- def id(self, id):
- if isinstance(id, (int, float)):
- s = str(id)
- elif isinstance(id, basestring):
- if id.isalnum() and not id.startswith('0x'):
- s = id
- else:
- s = self.escape(id)
- else:
- raise TypeError
- self.write(s)
-
- def color(self, (r, g, b)):
-
- def float2int(f):
- if f <= 0.0:
- return 0
- if f >= 1.0:
- return 255
- return int(255.0*f + 0.5)
-
- return "#" + "".join(["%02x" % float2int(c) for c in (r, g, b)])
-
- def escape(self, s):
- s = s.encode('utf-8')
- s = s.replace('\\', r'\\')
- s = s.replace('\n', r'\n')
- s = s.replace('\t', r'\t')
- s = s.replace('"', r'\"')
- return '"' + s + '"'
-
- def write(self, s):
- self.fp.write(s)
-
-
-class Main:
- """Main program."""
-
- themes = {
- "color": TEMPERATURE_COLORMAP,
- "pink": PINK_COLORMAP,
- "gray": GRAY_COLORMAP,
- "bw": BW_COLORMAP,
- }
-
- def main(self):
- """Main program."""
-
- parser = optparse.OptionParser(
- usage="\n\t%prog [options] [file] ...",
- version="%%prog %s" % __version__)
- parser.add_option(
- '-o', '--output', metavar='FILE',
- type="string", dest="output",
- help="output filename [stdout]")
- parser.add_option(
- '-n', '--node-thres', metavar='PERCENTAGE',
- type="float", dest="node_thres", default=0.5,
- help="eliminate nodes below this threshold [default: %default]")
- parser.add_option(
- '-e', '--edge-thres', metavar='PERCENTAGE',
- type="float", dest="edge_thres", default=0.1,
- help="eliminate edges below this threshold [default: %default]")
- parser.add_option(
- '-f', '--format',
- type="choice", choices=('prof', 'callgrind', 'perf', 'oprofile', 'hprof', 'sysprof', 'pstats', 'shark', 'sleepy', 'aqtime', 'xperf'),
- dest="format", default="prof",
- help="profile format: prof, callgrind, oprofile, hprof, sysprof, shark, sleepy, aqtime, pstats, or xperf [default: %default]")
- parser.add_option(
- '-c', '--colormap',
- type="choice", choices=('color', 'pink', 'gray', 'bw'),
- dest="theme", default="color",
- help="color map: color, pink, gray, or bw [default: %default]")
- parser.add_option(
- '-s', '--strip',
- action="store_true",
- dest="strip", default=False,
- help="strip function parameters, template parameters, and const modifiers from demangled C++ function names")
- parser.add_option(
- '-w', '--wrap',
- action="store_true",
- dest="wrap", default=False,
- help="wrap function names")
- # add a new option to control skew of the colorization curve
- parser.add_option(
- '--skew',
- type="float", dest="theme_skew", default=1.0,
- help="skew the colorization curve. Values < 1.0 give more variety to lower percentages. Value > 1.0 give less variety to lower percentages")
- (self.options, self.args) = parser.parse_args(sys.argv[1:])
-
- if len(self.args) > 1 and self.options.format != 'pstats':
- parser.error('incorrect number of arguments')
-
- try:
- self.theme = self.themes[self.options.theme]
- except KeyError:
- parser.error('invalid colormap \'%s\'' % self.options.theme)
-
- # set skew on the theme now that it has been picked.
- if self.options.theme_skew:
- self.theme.skew = self.options.theme_skew
-
- if self.options.format == 'prof':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = GprofParser(fp)
- elif self.options.format == 'callgrind':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = CallgrindParser(fp)
- elif self.options.format == 'perf':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = PerfParser(fp)
- elif self.options.format == 'oprofile':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = OprofileParser(fp)
- elif self.options.format == 'sysprof':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = SysprofParser(fp)
- elif self.options.format == 'hprof':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = HProfParser(fp)
- elif self.options.format == 'pstats':
- if not self.args:
- parser.error('at least a file must be specified for pstats input')
- parser = PstatsParser(*self.args)
- elif self.options.format == 'xperf':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = XPerfParser(fp)
- elif self.options.format == 'shark':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = SharkParser(fp)
- elif self.options.format == 'sleepy':
- if len(self.args) != 1:
- parser.error('exactly one file must be specified for sleepy input')
- parser = SleepyParser(self.args[0])
- elif self.options.format == 'aqtime':
- if not self.args:
- fp = sys.stdin
- else:
- fp = open(self.args[0], 'rt')
- parser = AQtimeParser(fp)
- else:
- parser.error('invalid format \'%s\'' % self.options.format)
-
- self.profile = parser.parse()
-
- if self.options.output is None:
- self.output = sys.stdout
- else:
- self.output = open(self.options.output, 'wt')
-
- self.write_graph()
-
- def write_graph(self):
- dot = DotWriter(self.output)
- dot.strip = self.options.strip
- dot.wrap = self.options.wrap
-
- profile = self.profile
- profile.prune(self.options.node_thres/100.0, self.options.edge_thres/100.0)
-
- dot.graph(profile, self.theme)
-
-
-if __name__ == '__main__':
- Main().main()
diff --git a/lgpl-2.1.txt b/lgpl-2.1.txt
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/lgpl-2.1.txt
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ , 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/life.py b/life.py
new file mode 100644
index 0000000..cd45c1d
--- /dev/null
+++ b/life.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python3
+# life.py -- A curses-based version of Conway's Game of Life.
+# Contributed by AMK
+# Mouse support and color by Dafydd Crosby
+#
+# An empty board will be displayed, and the following commands are available:
+# E : Erase the board
+# R : Fill the board randomly
+# S : Step for a single generation
+# C : Update continuously until a key is struck
+# Q : Quit
+# Cursor keys : Move the cursor around the board
+# Space or Enter : Toggle the contents of the cursor's position
+#
+# TODO :
+# Make board updates faster
+#
+
+import random
+import string
+import traceback
+import curses
+
+
+class LifeBoard:
+
+ """Encapsulates a Life board
+
+ Attributes:
+ X,Y : horizontal and vertical size of the board
+ state : dictionary mapping (x,y) to 0 or 1
+
+ Methods:
+ display(update_board) -- If update_board is true, compute the
+ next generation. Then display the state
+ of the board and refresh the screen.
+ erase() -- clear the entire board
+ makeRandom() -- fill the board randomly
+ set(y,x) -- set the given cell to Live; doesn't refresh the screen
+ toggle(y,x) -- change the given cell from live to dead, or vice
+ versa, and refresh the screen display
+
+ """
+ def __init__(self, scr, char=ord('*')):
+ """Create a new LifeBoard instance.
+
+ scr -- curses screen object to use for display
+ char -- character used to render live cells (default: '*')
+ """
+ self.state = {}
+ self.scr = scr
+ Y, X = self.scr.getmaxyx()
+ self.X, self.Y = X-2, Y-2-1
+ self.char = char
+ self.scr.clear()
+
+ # Draw a border around the board
+ border_line = '+'+(self.X*'-')+'+'
+ self.scr.addstr(0, 0, border_line)
+ self.scr.addstr(self.Y+1, 0, border_line)
+ for y in range(0, self.Y):
+ self.scr.addstr(1+y, 0, '|')
+ self.scr.addstr(1+y, self.X+1, '|')
+ self.scr.refresh()
+
+ def set(self, y, x):
+ """Set a cell to the live state"""
+ if x < 0 or self.X <= x or y < 0 or self.Y <= y:
+ raise ValueError("Coordinates out of range %i,%i" % (y, x))
+ self.state[x, y] = 1
+
+ def toggle(self, y, x):
+ """Toggle a cell's state between live and dead"""
+ if x < 0 or self.X <= x or y < 0 or self.Y <= y:
+ raise ValueError("Coordinates out of range %i,%i" % (y, x))
+ if (x, y) in self.state:
+ del self.state[x, y]
+ self.scr.addch(y+1, x+1, ' ')
+ else:
+ self.state[x, y] = 1
+ if curses.has_colors():
+ # Let's pick a random color!
+ self.scr.attrset(curses.color_pair(random.randrange(1, 7)))
+ self.scr.addch(y+1, x+1, self.char)
+ self.scr.attrset(0)
+ self.scr.refresh()
+
+ def erase(self):
+ """Clear the entire board and update the board display"""
+ self.state = {}
+ self.display(update_board=False)
+
+ def display(self, update_board=True):
+ """Display the whole board, optionally computing one generation"""
+ M, N = self.X, self.Y
+ if not update_board:
+ for i in range(0, M):
+ for j in range(0, N):
+ if (i, j) in self.state:
+ self.scr.addch(j+1, i+1, self.char)
+ else:
+ self.scr.addch(j+1, i+1, ' ')
+ self.scr.refresh()
+ return
+
+ d = {}
+ self.boring = 1
+ for i in range(0, M):
+ L = range(max(0, i-1), min(M, i+2))
+ for j in range(0, N):
+ s = 0
+ live = (i, j) in self.state
+ for k in range(max(0, j-1), min(N, j+2)):
+ for l in L:
+ if (l, k) in self.state:
+ s += 1
+ s -= live
+ if s == 3:
+ # Birth
+ d[i, j] = 1
+ if curses.has_colors():
+ # Let's pick a random color!
+ self.scr.attrset(curses.color_pair(random.randrange(1, 7)))
+ self.scr.addch(j+1, i+1, self.char)
+ self.scr.attrset(0)
+ if not live:
+ self.boring = 0
+ elif s == 2 and live:
+ d[i, j] = 1 # Survival
+ elif live:
+ # Death
+ self.scr.addch(j+1, i+1, ' ')
+ self.boring = 0
+ self.state = d
+ self.scr.refresh()
+
+ def makeRandom(self):
+ "Fill the board with a random pattern"
+ self.state = {}
+ for i in range(0, self.X):
+ for j in range(0, self.Y):
+ if random.random() > 0.5:
+ self.set(j, i)
+
+
+def erase_menu(stdscr, menu_y):
+ "Clear the space where the menu resides"
+ stdscr.move(menu_y, 0)
+ stdscr.clrtoeol()
+ stdscr.move(menu_y+1, 0)
+ stdscr.clrtoeol()
+
+
+def display_menu(stdscr, menu_y):
+ "Display the menu of possible keystroke commands"
+ erase_menu(stdscr, menu_y)
+
+ # If color, then light the menu up :-)
+ if curses.has_colors():
+ stdscr.attrset(curses.color_pair(1))
+ stdscr.addstr(menu_y, 4,
+ 'Use the cursor keys to move, and space or Enter to toggle a cell.')
+ stdscr.addstr(menu_y+1, 4,
+ 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
+ stdscr.attrset(0)
+
+
+def keyloop(stdscr):
+ # Clear the screen and display the menu of keys
+ stdscr.clear()
+ stdscr_y, stdscr_x = stdscr.getmaxyx()
+ menu_y = (stdscr_y-3)-1
+ display_menu(stdscr, menu_y)
+
+ # If color, then initialize the color pairs
+ if curses.has_colors():
+ curses.init_pair(1, curses.COLOR_BLUE, 0)
+ curses.init_pair(2, curses.COLOR_CYAN, 0)
+ curses.init_pair(3, curses.COLOR_GREEN, 0)
+ curses.init_pair(4, curses.COLOR_MAGENTA, 0)
+ curses.init_pair(5, curses.COLOR_RED, 0)
+ curses.init_pair(6, curses.COLOR_YELLOW, 0)
+ curses.init_pair(7, curses.COLOR_WHITE, 0)
+
+ # Set up the mask to listen for mouse events
+ curses.mousemask(curses.BUTTON1_CLICKED)
+
+ # Allocate a subwindow for the Life board and create the board object
+ subwin = stdscr.subwin(stdscr_y-3, stdscr_x, 0, 0)
+ board = LifeBoard(subwin, char=ord('*'))
+ board.display(update_board=False)
+
+ # xpos, ypos are the cursor's position
+ xpos, ypos = board.X//2, board.Y//2
+
+ # Main loop:
+ while (1):
+ stdscr.move(1+ypos, 1+xpos) # Move the cursor
+ c = stdscr.getch() # Get a keystroke
+ if 0 < c < 256:
+ c = chr(c)
+ if c in ' \n':
+ board.toggle(ypos, xpos)
+ elif c in 'Cc':
+ erase_menu(stdscr, menu_y)
+ stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
+ 'updating the screen.')
+ stdscr.refresh()
+ # Activate nodelay mode; getch() will return -1
+ # if no keystroke is available, instead of waiting.
+ stdscr.nodelay(1)
+ while (1):
+ c = stdscr.getch()
+ if c != -1:
+ break
+ stdscr.addstr(0, 0, '/')
+ stdscr.refresh()
+ board.display()
+ stdscr.addstr(0, 0, '+')
+ stdscr.refresh()
+
+ stdscr.nodelay(0) # Disable nodelay mode
+ display_menu(stdscr, menu_y)
+
+ elif c in 'Ee':
+ board.erase()
+ elif c in 'Qq':
+ break
+ elif c in 'Rr':
+ board.makeRandom()
+ board.display(update_board=False)
+ elif c in 'Ss':
+ board.display()
+ else:
+ pass # Ignore incorrect keys
+ elif c == curses.KEY_UP and ypos > 0:
+ ypos -= 1
+ elif c == curses.KEY_DOWN and ypos < board.Y-1:
+ ypos += 1
+ elif c == curses.KEY_LEFT and xpos > 0:
+ xpos -= 1
+ elif c == curses.KEY_RIGHT and xpos < board.X-1:
+ xpos += 1
+ elif c == curses.KEY_MOUSE:
+ (mouse_id, mouse_x, mouse_y, mouse_z, button_state) = curses.getmouse()
+ if (mouse_x > 0 and mouse_x < board.X+1) and (mouse_y > 0 and mouse_y < board.Y+1):
+ xpos = mouse_x - 1
+ ypos = mouse_y - 1
+ board.toggle(ypos, xpos)
+ else:
+ # They've clicked outside the board
+ curses.flash()
+ else:
+ # Ignore incorrect keys
+ pass
+
+
+def main(stdscr):
+ keyloop(stdscr) # Enter the main loop
+
+
+if __name__ == '__main__':
+ curses.wrapper(main)
diff --git a/simulation.py b/simulation.py
index 5bc5e3e..e1f4c13 100644
--- a/simulation.py
+++ b/simulation.py
@@ -1,11 +1,33 @@
-from __future__ import division
+#
+# This is where the fun stuff happens - simulating the boids
+#
+# This file includes Vector classes borrowed from Cocos2D
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 2.1 of the License, or (at your
+# option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+from __future__ import division, print_function, absolute_import, unicode_literals
+
+import math
+import operator
+import types
from math import atan2, sin, cos, floor, ceil
import random
import collections
-from euclid import Vector2
-def limit(vector,lim):
+
+def limit(vector, lim):
"""
limit a vector to a given magnitude
this is an 'in place' function, modifies the vector supplied.
@@ -13,13 +35,234 @@ def limit(vector,lim):
if abs(vector) > lim:
vector.normalize()
vector *= lim
-
+
+
+# Some magic here. If _use_slots is True, the classes will derive from
+# object and will define a __slots__ class variable. If _use_slots is
+# False, classes will be old-style and will not define __slots__.
+#
+# _use_slots = True: Memory efficient, probably faster in future versions
+# of Python, "better".
+# _use_slots = False: Ordinary classes, much faster than slots in current
+# versions of Python (2.4 and 2.5).
+_use_slots = True
+
+# If True, allows components of Vector2 and Vector3 to be set via swizzling;
+# e.g. v.xyz = (1, 2, 3). This is much, much slower than the more verbose
+# v.x = 1; v.y = 2; v.z = 3, and slows down ordinary element setting as
+# well. Recommended setting is False.
+_enable_swizzle_set = False
+
+# Requires class to derive from object.
+if _enable_swizzle_set:
+ _use_slots = True
+
+
+# Implement _use_slots magic.
+class _EuclidMetaclass(type):
+ def __new__(cls, name, bases, dct):
+ if _use_slots:
+ return type.__new__(cls, name, bases + (object,), dct)
+ else:
+ del dct['__slots__']
+ return types.ClassType.__new__(types.ClassType, name, bases, dct)
+__metaclass__ = _EuclidMetaclass
+
+
+class Vector2(object):
+ __slots__ = ['x', 'y']
+
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ def __copy__(self):
+ return self.__class__(self.x, self.y)
+
+ copy = __copy__
+
+ def __repr__(self):
+ return 'Vector2(%.4f, %.4f)' % (self.x, self.y)
+
+ def __eq__(self, other):
+ if isinstance(other, Vector2):
+ return self.x == other.x and self.y == other.y
+ else:
+ assert hasattr(other, '__len__') and len(other) == 2
+ return self.x == other[0] and self.y == other[1]
+
+ def __neq__(self, other):
+ return not self.__eq__(other)
+
+ def __nonzero__(self):
+ return self.x != 0 or self.y != 0
+
+ def __len__(self):
+ return 2
+
+ def __getitem__(self, key):
+ return (self.x, self.y)[key]
+
+ def __setitem__(self, key, value):
+ l = [self.x, self.y]
+ l[key] = value
+ self.x, self.y = l
+
+ def __iter__(self):
+ return iter((self.x, self.y))
+
+ def __getattr__(self, name):
+ try:
+ return tuple([(self.x, self.y)['xy'.index(c)] for c in name])
+ except ValueError:
+ raise AttributeError(name)
+
+ if _enable_swizzle_set:
+ # This has detrimental performance on ordinary setattr as well
+ # if enabled
+ def __setattr__(self, name, value):
+ if len(name) == 1:
+ object.__setattr__(self, name, value)
+ else:
+ try:
+ l = [self.x, self.y]
+ for c, v in map(None, name, value):
+ l['xy'.index(c)] = v
+ self.x, self.y = l
+ except ValueError:
+ raise AttributeError(name)
+
+ def __add__(self, other):
+ if isinstance(other, Vector2):
+ return Vector2(self.x + other.x,
+ self.y + other.y)
+ else:
+ assert hasattr(other, '__len__') and len(other) == 2
+ return Vector2(self.x + other[0],
+ self.y + other[1])
+ __radd__ = __add__
+
+ def __iadd__(self, other):
+ if isinstance(other, Vector2):
+ self.x += other.x
+ self.y += other.y
+ else:
+ self.x += other[0]
+ self.y += other[1]
+ return self
+
+ def __sub__(self, other):
+ if isinstance(other, Vector2):
+ return Vector2(self.x - other.x,
+ self.y - other.y)
+ else:
+ assert hasattr(other, '__len__') and len(other) == 2
+ return Vector2(self.x - other[0],
+ self.y - other[1])
+
+ def __rsub__(self, other):
+ if isinstance(other, Vector2):
+ return Vector2(other.x - self.x,
+ other.y - self.y)
+ else:
+ assert hasattr(other, '__len__') and len(other) == 2
+ return Vector2(other.x - self[0],
+ other.y - self[1])
+
+ def __mul__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(self.x * other,
+ self.y * other)
+
+ __rmul__ = __mul__
+
+ def __imul__(self, other):
+ assert type(other) in (int, long, float)
+ self.x *= other
+ self.y *= other
+ return self
+
+ def __div__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(operator.div(self.x, other),
+ operator.div(self.y, other))
+
+ def __rdiv__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(operator.div(other, self.x),
+ operator.div(other, self.y))
+
+ def __floordiv__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(operator.floordiv(self.x, other),
+ operator.floordiv(self.y, other))
+
+ def __rfloordiv__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(operator.floordiv(other, self.x),
+ operator.floordiv(other, self.y))
+
+ def __truediv__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(operator.truediv(self.x, other),
+ operator.truediv(self.y, other))
+
+ def __rtruediv__(self, other):
+ assert type(other) in (int, long, float)
+ return Vector2(operator.truediv(other, self.x),
+ operator.truediv(other, self.y))
+
+ def __neg__(self):
+ return Vector2(-self.x, -self.y)
+
+ __pos__ = __copy__
+
+ def __abs__(self):
+ return math.sqrt(self.x ** 2 + self.y ** 2)
+
+ magnitude = __abs__
+
+ def clear(self):
+ self.x = 0
+ self.y = 0
+
+ def magnitude_squared(self):
+ return self.x ** 2 + self.y ** 2
+
+ def normalize(self):
+ d = self.magnitude()
+ if d:
+ self.x /= d
+ self.y /= d
+ return self
+
+ def normalized(self):
+ d = self.magnitude()
+ if d:
+ return Vector2(self.x / d, self.y / d)
+ return self.copy()
+
+ def dot(self, other):
+ assert isinstance(other, Vector2)
+ return self.x * other.x + self.y * other.y
+
+ def cross(self):
+ return Vector2(self.y, -self.x)
+
+ def reflect(self, normal):
+ # assume normal is normalized
+ assert isinstance(normal, Vector2)
+ d = 2 * (self.x * normal.x + self.y * normal.y)
+ return Vector2(self.x - d * normal.x, self.y - d * normal.y)
+
+
class BoidSwarm(object):
+
"""
The grid to hold the boids
"""
-
- def __init__(self,width, cell_w):
+
+ def __init__(self, width, cell_w):
"""
Create data structure to hold the things. At the base is a table of cell nodes
each containing an arbitrary number of units. Need whole number of cells, so cell width
@@ -27,72 +270,77 @@ def __init__(self,width, cell_w):
The structure is always square. It can provide boundaries though good level design
should mean you don't ever hit them.
"""
-
- self.boids = [] #list of all the boids
-
+
+ self.boids = [] # list of all the boids
+
divs = int(floor(width/cell_w))
self.divisions = divs
self.cell_width = cell_w
-
+
self.cell_table = {}
self.num_cells = divs*divs
for i in range(divs):
for j in range(divs):
- #use deque for fast appends of several deque
- self.cell_table[(i,j)] = collections.deque()
+ # use deque for fast appends of several deque
+ self.cell_table[(i, j)] = collections.deque()
-
- def cell_num(self,x, y):
+ def cell_num(self, x, y):
"""Forces units into border cells if they hit an edge"""
i = int(floor(x / self.cell_width))
j = int(floor(y / self.cell_width))
- #deal with boundary conditions
- if i < 0: i=0
- if j < 0: j=0
- if i >= self.divisions: i = self.divisions-1 #zero indexing
- if j >= self.divisions: j = self.divisions-1 #zero indexing
- return (i,j)
-
+ # deal with boundary conditions
+ if i < 0:
+ i = 0
+ if j < 0:
+ j = 0
+ if i >= self.divisions:
+ i = self.divisions-1 # zero indexing
+ if j >= self.divisions:
+ j = self.divisions-1 # zero indexing
+ return (i, j)
+
def find_cell_containing(self, x, y):
"""returns the cell containing a position x,y"""
- return self.cell_table[self.cell_num(x,y)]
+ return self.cell_table[self.cell_num(x, y)]
def find_near(self, x, y, influence_range):
"""return objects within radius influence_range of point x,y"""
- if influence_range == 0:
- _nearObjects = self.find_cell_containing(x,y)
+ if influence_range == 0:
+ _nearObjects = self.find_cell_containing(x, y)
elif influence_range <= self.cell_width:
- _nearObjects = self.find_neighbour_cells(x,y)
- else:
+ _nearObjects = self.find_neighbour_cells(x, y)
+ else:
ext = ceil(influence_range/self.cell_width)
- _nearObjects = self.find_extended(x,y,ext)
-
+ _nearObjects = self.find_extended(x, y, ext)
+
return _nearObjects
def find_neighbour_cells(self, x, y):
- return self.cell_table[self.cell_num(x,y)]
-
+ return self.cell_table[self.cell_num(x, y)]
+
def find_extended(self, x, y, d):
"""
use to find set of cells surrounding the cell containing position x,y
"""
- I,J = self.cell_num(x,y)
- d=int(d)
+ I, J = self.cell_num(x, y)
+ d = int(d)
group = collections.deque()
- for i in range(I-d,I+d):
- for j in range(J-d,J+d):
- if (i,j) in self.cell_table:
- group.extend( self.cell_table[(i,j)] ) #merge deque
+ for i in range(I-d, I+d):
+ for j in range(J-d, J+d):
+ if (i, j) in self.cell_table:
+ group.extend(self.cell_table[(i, j)]) # merge deque
return group
- def rebuild(self):
+ def rebuild(self):
for cell in self.cell_table.values():
cell.clear()
for b in self.boids:
c = self.find_cell_containing(b.position.x, b.position.y)
c.append(b)
+
class Boid(object):
+
"""
Boids class
"""
@@ -101,50 +349,52 @@ class Boid(object):
max_force = 20.0
max_speed = 80.0
drag = 0.9
- cohesion_strength = 1.0
+ cohesion_strength = 1.0
align_strength = 1.0
sep_strength = 1.5
-
- #cohesion_strength *= max_force
- #align_strength *= max_force
- #sep_strength *= max_force
-
+
+ # cohesion_strength *= max_force
+ # align_strength *= max_force
+ # sep_strength *= max_force
+
# Get and set the speed as a scalar
def _get_speed(self):
return abs(self.velocity)
-
- def _set_speed(self,s):
+
+ def _set_speed(self, s):
if abs(self.velocity) == 0:
- velocity = Vector2(1,0)
-
+ self.velocity = Vector2(1, 0)
+
self.velocity.normalize()
self.velocity *= s
-
- speed = property(_get_speed,_set_speed)
-
- #get and set the rotation as an angle
+
+ speed = property(_get_speed, _set_speed)
+
+ # get and set the rotation as an angle
def _get_rotation(self):
- return atan2(self.velocity.y,self.velocity.x)
-
- def _set_rotation(self,r):
+ return atan2(self.velocity.y, self.velocity.x)
+
+ def _set_rotation(self, r):
old_speed = self.speed
- #set the direction as a unit vector
- velocity.x = cos(r)
- velocity.y = sin(r)
-
- self.speed=old_speed
-
- rotation = property(_get_rotation,_set_rotation)
-
- def __init__(self,x, y):
+ # set the direction as a unit vector
+ self.velocity.x = cos(r)
+ self.velocity.y = sin(r)
+
+ self.speed = old_speed
+
+ rotation = property(_get_rotation, _set_rotation)
+
+ def __init__(self, x, y):
""" create a new boid at x,y """
- self.position = Vector2(x,y,)
- self.acceleration = Vector2(0,0)
+ self.neighbors = 0
+
+ self.position = Vector2(x, y)
+ self.acceleration = Vector2(0, 0)
self.velocity = Vector2(random.uniform(-self.max_speed, self.max_speed),
random.uniform(-self.max_speed, self.max_speed))
-
+
def __repr__(self):
- return 'id %d'%self.id
+ return 'id %d' % self.id
def borders(self, top, bottom, left, right):
"""
@@ -160,32 +410,31 @@ def borders(self, top, bottom, left, right):
"""
if self.position.x < left:
self.position.x = right
- if self.position.x > right :
+ if self.position.x > right:
self.position.x = left
if self.position.y < top:
self.position.y = bottom
if self.position.y > bottom:
self.position.y = top
-
- def update(self,t):
+ def update(self, t):
"""
Method to update position by computing displacement from velocity and acceleration
"""
self.velocity += self.acceleration * t
- limit(self.velocity,self.max_speed)
+ limit(self.velocity, self.max_speed)
self.position += self.velocity * t
- #Calculation variables for interact method - init once instead of on each call
- _sep_f = Vector2(0,0)
- _align_f = Vector2(0,0)
- _cohes_sum = Vector2(0,0)
+ # Calculation variables for interact method - init once instead of on each call
+ _sep_f = Vector2(0, 0)
+ _align_f = Vector2(0, 0)
+ _cohes_sum = Vector2(0, 0)
def interact(self, actors):
"""
Unit-unit interaction method, combining a separation force, and velocity
alignment force, and a cohesion force.
-
+
Many examples separate these into different functions for clarity
but combining them means we need fewer loops over the neibor list
"""
@@ -195,57 +444,58 @@ def interact(self, actors):
self._cohes_sum.clear()
count = 0
+ self.neighbors = len(actors)
for other in actors:
- #vector pointing from neighbors to self
+ # vector pointing from neighbors to self
diff = self.position - other.position
d = abs(diff)
- #Only perform on "neighbor" actors, i.e. ones closer than arbitrary
- #dist or if the distance is not 0 (you are yourself)
+ # Only perform on "neighbor" actors, i.e. ones closer than arbitrary
+ # dist or if the distance is not 0 (you are yourself)
if 0 < d < self.influence_range:
count += 1
-
+
diff.normalize()
if d < self.minsep:
# diff *= self.max_force
# else:
- diff /= d # Weight by distance
+ diff /= d # Weight by distance
self._sep_f += diff
-
- self._cohes_sum += other.position # Add position
-
- #Align - add the velocity of the neighbouring actors, then average
- self._align_f += other.velocity
+
+ self._cohes_sum += other.position # Add position
+
+ # Align - add the velocity of the neighbouring actors, then average
+ self._align_f += other.velocity
if count > 0:
- #calc the average of the separation vector
- #self._sep_f /=count don't div by count if normalizing anyway!
+ # calc the average of the separation vector
+ # self._sep_f /=count don't div by count if normalizing anyway!
self._sep_f.normalize()
self._sep_f *= self.max_speed
self._sep_f -= self.velocity
limit(self._sep_f, self.max_force)
-
- #calc the average direction (normed avg velocity)
- #self._align_f /= count
+
+ # calc the average direction (normed avg velocity)
+ # self._align_f /= count
self._align_f.normalize()
self._align_f *= self.max_speed
self._align_f -= self.velocity
limit(self._align_f, self.max_force)
-
- #calc the average position and calc steering vector towards it
+
+ # calc the average position and calc steering vector towards it
self._cohes_sum /= count
- cohesion_f = self.steer(self._cohes_sum,True)
-
+ cohesion_f = self.steer(self._cohes_sum, True)
+
self._sep_f *= self.sep_strength
self._align_f *= self.align_strength
cohesion_f *= self.cohesion_strength
-
- #finally add the velocities
- sum = self._sep_f + cohesion_f + self._align_f
+
+ # finally add the velocities
+ sum = self._sep_f + cohesion_f + self._align_f
self.acceleration = sum
-
+
def steer(self, desired, slowdown=False):
"""
A helper method that calculates a steering vector towards a target
@@ -253,51 +503,52 @@ def steer(self, desired, slowdown=False):
"""
desired -= self.position
d = abs(desired)
- #If the distance is greater than 0, calc steering (otherwise return zero vector)
+ # If the distance is greater than 0, calc steering (otherwise return zero vector)
if d > 0:
desired.normalize()
if slowdown and (d < self.minsep):
- desired *= self.max_speed*d/ self.minsep
+ desired *= self.max_speed*d / self.minsep
else:
desired *= self.max_speed
-
+
steer = desired - self.velocity
limit(steer, self.max_force)
else:
- steer = Vector2(0,0)
+ steer = Vector2(0, 0)
return steer
-
-
+
+
class FlockSimulation(object):
+
"""
Ties the BoidSwarm with the boids.
boidswarm just holds the data, boids know how to interact with each
other. This class keeps the two separated
"""
-
- def __init__(self,starting_units = 100, field_size = 800):
+
+ def __init__(self, starting_units=100, field_size=800):
"""
"""
- self.swarm = BoidSwarm(field_size+2*40,Boid.influence_range+5) #/2
+ self.swarm = BoidSwarm(field_size+2*40, Boid.influence_range+5) # /2
self.field_size = field_size
- self.pad = 40 #use to keep boids inside the play field
-
+ self.pad = 40 # use to keep boids inside the play field
+
for i in range(starting_units):
- b = Boid(random.uniform(100, 200),
- random.uniform(100, 200))
+ b = Boid(random.uniform(100, 400),
+ random.uniform(100, 400))
self.swarm.boids.append(b)
self.swarm.rebuild()
- self._cumltime = 0 #calculation var
-
- def update(self,dt):
+ self._cumltime = 0 # calculation var
+
+ def update(self, dt):
"""dt is in seconds"""
for b in self.swarm.boids:
- close_boids = self.swarm.find_near(b.position.x,b.position.y,b.influence_range)
+ close_boids = self.swarm.find_near(b.position.x, b.position.y, b.influence_range)
b.interact(close_boids)
- b.update(dt)
- w=self.field_size
- p=self.pad
- b.borders(p,w-p,p,w-p) #keep the boids inside the borders
-
- #rebuild the swarm once we've updated all the positions
- self.swarm.rebuild()
+ b.update(dt)
+ w = self.field_size
+ p = self.pad
+ b.borders(p, w-p, p, w-p) # keep the boids inside the borders
+
+ # rebuild the swarm once we've updated all the positions
+ self.swarm.rebuild()