From: Vivien Maisonneuve Date: Fri, 11 Jul 2014 17:48:04 +0000 (+0200) Subject: Rename coordinates.py into geometry.py X-Git-Tag: 1.0~119 X-Git-Url: https://scm.cri.ensmp.fr/git/linpy.git/commitdiff_plain/6a8f0d937524e9210b3283f8331ccaf6ce3caaa3 Rename coordinates.py into geometry.py --- diff --git a/pypol/__init__.py b/pypol/__init__.py index be1440b..166ccf4 100644 --- a/pypol/__init__.py +++ b/pypol/__init__.py @@ -2,8 +2,8 @@ A polyhedral library based on ISL. """ +from .geometry import Point, Vector from .linexprs import Expression, Symbol, Dummy, symbols, Rational -from .coordinates import Point, Vector from .polyhedra import Polyhedron, Eq, Ne, Le, Lt, Ge, Gt, Ne, Empty, Universe from .domains import Domain, And, Or, Not diff --git a/pypol/coordinates.py b/pypol/coordinates.py deleted file mode 100644 index ceab418..0000000 --- a/pypol/coordinates.py +++ /dev/null @@ -1,227 +0,0 @@ -import math -import numbers -import operator - -from collections import OrderedDict, Mapping - -from .geometry import GeometricObject -from .linexprs import Symbol - - -__all__ = [ - 'Point', - 'Vector', -] - - -class Coordinates: - - __slots__ = ( - '_coordinates', - ) - - def __new__(cls, coordinates): - if isinstance(coordinates, Mapping): - coordinates = coordinates.items() - self = object().__new__(cls) - self._coordinates = OrderedDict() - for symbol, coordinate in sorted(coordinates, - key=lambda item: item[0].sortkey()): - if not isinstance(symbol, Symbol): - raise TypeError('symbols must be Symbol instances') - if not isinstance(coordinate, numbers.Real): - raise TypeError('coordinates must be real numbers') - self._coordinates[symbol] = coordinate - return self - - @property - def symbols(self): - return tuple(self._coordinates) - - @property - def dimension(self): - return len(self.symbols) - - def coordinates(self): - yield from self._coordinates.items() - - def coordinate(self, symbol): - if not isinstance(symbol, Symbol): - raise TypeError('symbol must be a Symbol instance') - return self._coordinates[symbol] - - __getitem__ = coordinate - - def __bool__(self): - return any(self._coordinates.values()) - - def __hash__(self): - return hash(tuple(self.coordinates())) - - def __repr__(self): - string = ', '.join(['{!r}: {!r}'.format(symbol, coordinate) - for symbol, coordinate in self.coordinates()]) - return '{}({{{}}})'.format(self.__class__.__name__, string) - - def _map(self, func): - for symbol, coordinate in self.coordinates(): - yield symbol, func(coordinate) - - def _iter2(self, other): - if self.symbols != other.symbols: - raise ValueError('arguments must belong to the same space') - coordinates1 = self._coordinates.values() - coordinates2 = other._coordinates.values() - yield from zip(self.symbols, coordinates1, coordinates2) - - def _map2(self, other, func): - for symbol, coordinate1, coordinate2 in self._iter2(other): - yield symbol, func(coordinate1, coordinate2) - - -class Point(Coordinates, GeometricObject): - """ - This class represents points in space. - """ - - def isorigin(self): - return not bool(self) - - def __add__(self, other): - if not isinstance(other, Vector): - return NotImplemented - coordinates = self._map2(other, operator.add) - return Point(coordinates) - - def __sub__(self, other): - coordinates = [] - if isinstance(other, Point): - coordinates = self._map2(other, operator.sub) - return Vector(coordinates) - elif isinstance(other, Vector): - coordinates = self._map2(other, operator.sub) - return Point(coordinates) - else: - return NotImplemented - - def __eq__(self, other): - return isinstance(other, Point) and \ - self._coordinates == other._coordinates - - def aspolyhedron(self): - from .polyhedra import Polyhedron - equalities = [] - for symbol, coordinate in self.coordinates(): - equalities.append(symbol - coordinate) - return Polyhedron(equalities) - - -class Vector(Coordinates): - """ - This class represents displacements in space. - """ - - def __new__(cls, initial, terminal=None): - if not isinstance(initial, Point): - initial = Point(initial) - if terminal is None: - coordinates = initial._coordinates - elif not isinstance(terminal, Point): - terminal = Point(terminal) - coordinates = terminal._map2(initial, operator.sub) - return super().__new__(cls, coordinates) - - def isnull(self): - return not bool(self) - - def __add__(self, other): - if isinstance(other, (Point, Vector)): - coordinates = self._map2(other, operator.add) - return other.__class__(coordinates) - return NotImplemented - - def angle(self, other): - """ - Retrieve the angle required to rotate the vector into the vector passed - in argument. The result is an angle in radians, ranging between -pi and - pi. - """ - if not isinstance(other, Vector): - raise TypeError('argument must be a Vector instance') - cosinus = self.dot(other) / (self.norm()*other.norm()) - return math.acos(cosinus) - - def cross(self, other): - """ - Calculate the cross product of two Vector3D structures. - """ - if not isinstance(other, Vector): - raise TypeError('other must be a Vector instance') - if self.dimension != 3 or other.dimension != 3: - raise ValueError('arguments must be three-dimensional vectors') - if self.symbols != other.symbols: - raise ValueError('arguments must belong to the same space') - x, y, z = self.symbols - coordinates = [] - coordinates.append((x, self[y]*other[z] - self[z]*other[y])) - coordinates.append((y, self[z]*other[x] - self[x]*other[z])) - coordinates.append((z, self[x]*other[y] - self[y]*other[x])) - return Vector(coordinates) - - def __truediv__(self, other): - """ - Divide the vector by the specified scalar and returns the result as a - vector. - """ - if not isinstance(other, numbers.Real): - return NotImplemented - coordinates = self._map(lambda coordinate: coordinate / other) - return Vector(coordinates) - - def dot(self, other): - """ - Calculate the dot product of two vectors. - """ - if not isinstance(other, Vector): - raise TypeError('argument must be a Vector instance') - result = 0 - for symbol, coordinate1, coordinate2 in self._iter2(other): - result += coordinate1 * coordinate2 - return result - - def __eq__(self, other): - return isinstance(other, Vector) and \ - self._coordinates == other._coordinates - - def __hash__(self): - return hash(tuple(self.coordinates())) - - def __mul__(self, other): - if not isinstance(other, numbers.Real): - return NotImplemented - coordinates = self._map(lambda coordinate: other * coordinate) - return Vector(coordinates) - - __rmul__ = __mul__ - - def __neg__(self): - coordinates = self._map(operator.neg) - return Vector(coordinates) - - def norm(self): - return math.sqrt(self.norm2()) - - def norm2(self): - result = 0 - for coordinate in self._coordinates.values(): - result += coordinate ** 2 - return result - - def asunit(self): - return self / self.norm() - - def __sub__(self, other): - if isinstance(other, (Point, Vector)): - coordinates = self._map2(other, operator.sub) - return other.__class__(coordinates) - return NotImplemented diff --git a/pypol/domains.py b/pypol/domains.py index 5d98af8..d80fd91 100644 --- a/pypol/domains.py +++ b/pypol/domains.py @@ -6,8 +6,7 @@ from fractions import Fraction from . import islhelper from .islhelper import mainctx, libisl -from .geometry import GeometricObject -from .coordinates import Point +from .geometry import GeometricObject, Point from .linexprs import Expression, Symbol diff --git a/pypol/geometry.py b/pypol/geometry.py index d0146cf..ce055a4 100644 --- a/pypol/geometry.py +++ b/pypol/geometry.py @@ -1,8 +1,17 @@ -from abc import ABC, abstractmethod, abstractproperty +import math +import numbers +import operator + +from abc import ABC, abstractproperty, abstractmethod +from collections import OrderedDict, Mapping + +from .linexprs import Symbol __all__ = [ 'GeometricObject', + 'Point', + 'Vector', ] @@ -22,3 +31,216 @@ class GeometricObject(ABC): def asdomain(self): return self.aspolyhedron() + + +class Coordinates: + + __slots__ = ( + '_coordinates', + ) + + def __new__(cls, coordinates): + if isinstance(coordinates, Mapping): + coordinates = coordinates.items() + self = object().__new__(cls) + self._coordinates = OrderedDict() + for symbol, coordinate in sorted(coordinates, + key=lambda item: item[0].sortkey()): + if not isinstance(symbol, Symbol): + raise TypeError('symbols must be Symbol instances') + if not isinstance(coordinate, numbers.Real): + raise TypeError('coordinates must be real numbers') + self._coordinates[symbol] = coordinate + return self + + @property + def symbols(self): + return tuple(self._coordinates) + + @property + def dimension(self): + return len(self.symbols) + + def coordinates(self): + yield from self._coordinates.items() + + def coordinate(self, symbol): + if not isinstance(symbol, Symbol): + raise TypeError('symbol must be a Symbol instance') + return self._coordinates[symbol] + + __getitem__ = coordinate + + def __bool__(self): + return any(self._coordinates.values()) + + def __hash__(self): + return hash(tuple(self.coordinates())) + + def __repr__(self): + string = ', '.join(['{!r}: {!r}'.format(symbol, coordinate) + for symbol, coordinate in self.coordinates()]) + return '{}({{{}}})'.format(self.__class__.__name__, string) + + def _map(self, func): + for symbol, coordinate in self.coordinates(): + yield symbol, func(coordinate) + + def _iter2(self, other): + if self.symbols != other.symbols: + raise ValueError('arguments must belong to the same space') + coordinates1 = self._coordinates.values() + coordinates2 = other._coordinates.values() + yield from zip(self.symbols, coordinates1, coordinates2) + + def _map2(self, other, func): + for symbol, coordinate1, coordinate2 in self._iter2(other): + yield symbol, func(coordinate1, coordinate2) + + +class Point(Coordinates, GeometricObject): + """ + This class represents points in space. + """ + + def isorigin(self): + return not bool(self) + + def __add__(self, other): + if not isinstance(other, Vector): + return NotImplemented + coordinates = self._map2(other, operator.add) + return Point(coordinates) + + def __sub__(self, other): + coordinates = [] + if isinstance(other, Point): + coordinates = self._map2(other, operator.sub) + return Vector(coordinates) + elif isinstance(other, Vector): + coordinates = self._map2(other, operator.sub) + return Point(coordinates) + else: + return NotImplemented + + def __eq__(self, other): + return isinstance(other, Point) and \ + self._coordinates == other._coordinates + + def aspolyhedron(self): + from .polyhedra import Polyhedron + equalities = [] + for symbol, coordinate in self.coordinates(): + equalities.append(symbol - coordinate) + return Polyhedron(equalities) + + +class Vector(Coordinates): + """ + This class represents displacements in space. + """ + + def __new__(cls, initial, terminal=None): + if not isinstance(initial, Point): + initial = Point(initial) + if terminal is None: + coordinates = initial._coordinates + elif not isinstance(terminal, Point): + terminal = Point(terminal) + coordinates = terminal._map2(initial, operator.sub) + return super().__new__(cls, coordinates) + + def isnull(self): + return not bool(self) + + def __add__(self, other): + if isinstance(other, (Point, Vector)): + coordinates = self._map2(other, operator.add) + return other.__class__(coordinates) + return NotImplemented + + def angle(self, other): + """ + Retrieve the angle required to rotate the vector into the vector passed + in argument. The result is an angle in radians, ranging between -pi and + pi. + """ + if not isinstance(other, Vector): + raise TypeError('argument must be a Vector instance') + cosinus = self.dot(other) / (self.norm()*other.norm()) + return math.acos(cosinus) + + def cross(self, other): + """ + Calculate the cross product of two Vector3D structures. + """ + if not isinstance(other, Vector): + raise TypeError('other must be a Vector instance') + if self.dimension != 3 or other.dimension != 3: + raise ValueError('arguments must be three-dimensional vectors') + if self.symbols != other.symbols: + raise ValueError('arguments must belong to the same space') + x, y, z = self.symbols + coordinates = [] + coordinates.append((x, self[y]*other[z] - self[z]*other[y])) + coordinates.append((y, self[z]*other[x] - self[x]*other[z])) + coordinates.append((z, self[x]*other[y] - self[y]*other[x])) + return Vector(coordinates) + + def __truediv__(self, other): + """ + Divide the vector by the specified scalar and returns the result as a + vector. + """ + if not isinstance(other, numbers.Real): + return NotImplemented + coordinates = self._map(lambda coordinate: coordinate / other) + return Vector(coordinates) + + def dot(self, other): + """ + Calculate the dot product of two vectors. + """ + if not isinstance(other, Vector): + raise TypeError('argument must be a Vector instance') + result = 0 + for symbol, coordinate1, coordinate2 in self._iter2(other): + result += coordinate1 * coordinate2 + return result + + def __eq__(self, other): + return isinstance(other, Vector) and \ + self._coordinates == other._coordinates + + def __hash__(self): + return hash(tuple(self.coordinates())) + + def __mul__(self, other): + if not isinstance(other, numbers.Real): + return NotImplemented + coordinates = self._map(lambda coordinate: other * coordinate) + return Vector(coordinates) + + __rmul__ = __mul__ + + def __neg__(self): + coordinates = self._map(operator.neg) + return Vector(coordinates) + + def norm(self): + return math.sqrt(self.norm2()) + + def norm2(self): + result = 0 + for coordinate in self._coordinates.values(): + result += coordinate ** 2 + return result + + def asunit(self): + return self / self.norm() + + def __sub__(self, other): + if isinstance(other, (Point, Vector)): + coordinates = self._map2(other, operator.sub) + return other.__class__(coordinates) + return NotImplemented diff --git a/pypol/polyhedra.py b/pypol/polyhedra.py index c30fd13..5d9c287 100644 --- a/pypol/polyhedra.py +++ b/pypol/polyhedra.py @@ -5,8 +5,7 @@ import numbers from . import islhelper from .islhelper import mainctx, libisl -from .geometry import GeometricObject -from .coordinates import Point +from .geometry import GeometricObject, Point from .linexprs import Expression, Symbol, Rational from .domains import Domain diff --git a/pypol/tests/test_coordinates.py b/pypol/tests/test_geometry.py similarity index 99% rename from pypol/tests/test_coordinates.py rename to pypol/tests/test_geometry.py index 044d773..e602616 100644 --- a/pypol/tests/test_coordinates.py +++ b/pypol/tests/test_geometry.py @@ -1,7 +1,7 @@ import math import unittest -from ..coordinates import * +from ..geometry import * from ..linexprs import Symbol from ..polyhedra import Eq