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 '' - 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()