X-Git-Url: https://scm.cri.mines-paristech.fr/git/linpy.git/blobdiff_plain/7b93cea1daf2889e9ee10ca9c22a1b5124404937..ba15f3f33f837b1291f74bc94081e99b860d3228:/linpy/geometry.py diff --git a/linpy/geometry.py b/linpy/geometry.py index 335a826..1a56269 100644 --- a/linpy/geometry.py +++ b/linpy/geometry.py @@ -33,30 +33,55 @@ __all__ = [ class GeometricObject(ABC): + """ + GeometricObject is an abstract class to represent objects with a + geometric representation in space. Subclasses of GeometricObject are + Polyhedron, Domain and Point. + """ @abstractproperty def symbols(self): + """ + The tuple of symbols present in the object expression, sorted according + to Symbol.sortkey(). + """ pass @property def dimension(self): + """ + The dimension of the object, i.e. the number of symbols present in it. + """ return len(self.symbols) @abstractmethod def aspolyhedron(self): + """ + Return a Polyhedron object that approximates the geometric object. + """ pass def asdomain(self): + """ + Return a Domain object that approximates the geometric object. + """ return self.aspolyhedron() class Coordinates: + """ + This class represents coordinate systems. + """ __slots__ = ( '_coordinates', ) def __new__(cls, coordinates): + """ + Create a coordinate system from a dictionary or a sequence that maps the + symbols to their coordinates. Coordinates must be rational numbers. + """ if isinstance(coordinates, Mapping): coordinates = coordinates.items() self = object().__new__(cls) @@ -72,26 +97,48 @@ class Coordinates: @property def symbols(self): + """ + The tuple of symbols present in the coordinate system, sorted according + to Symbol.sortkey(). + """ return tuple(self._coordinates) @property def dimension(self): + """ + The dimension of the coordinate system, i.e. the number of symbols + present in it. + """ return len(self.symbols) - def coordinates(self): - yield from self._coordinates.items() - def coordinate(self, symbol): + """ + Return the coordinate value of the given symbol. Raise KeyError if the + symbol is not involved in the coordinate system. + """ if not isinstance(symbol, Symbol): raise TypeError('symbol must be a Symbol instance') return self._coordinates[symbol] __getitem__ = coordinate + def coordinates(self): + """ + Iterate over the pairs (symbol, value) of coordinates in the coordinate + system. + """ + yield from self._coordinates.items() + def values(self): + """ + Iterate over the coordinate values in the coordinate system. + """ yield from self._coordinates.values() def __bool__(self): + """ + Return True if not all coordinates are 0. + """ return any(self._coordinates.values()) def __hash__(self): @@ -121,11 +168,13 @@ class Coordinates: class Point(Coordinates, GeometricObject): """ This class represents points in space. + + Point instances are hashable and should be treated as immutable. """ def isorigin(self): """ - Return True if a Point is the origin. + Return True if all coordinates are 0. """ return not bool(self) @@ -134,7 +183,7 @@ class Point(Coordinates, GeometricObject): def __add__(self, other): """ - Adds a Point to a Vector and returns the result as a Point. + Translate the point by a Vector object and return the resulting point. """ if not isinstance(other, Vector): return NotImplemented @@ -143,7 +192,9 @@ class Point(Coordinates, GeometricObject): def __sub__(self, other): """ - Returns the difference between two Points as a Vector. + If other is a point, substract a point from another and returns the + resulting vector. If other is a vector, translate the point by the + opposite vector and returns the resulting point. """ coordinates = [] if isinstance(other, Point): @@ -157,15 +208,12 @@ class Point(Coordinates, GeometricObject): def __eq__(self, other): """ - Compares two Points for equality. + Test whether two points are equal. """ return isinstance(other, Point) and \ self._coordinates == other._coordinates def aspolyhedron(self): - """ - Return a Point as a polyhedron. - """ from .polyhedra import Polyhedron equalities = [] for symbol, coordinate in self.coordinates(): @@ -175,10 +223,16 @@ class Point(Coordinates, GeometricObject): class Vector(Coordinates): """ - This class represents displacements in space. + This class represents vectors in space. + + Vector instances are hashable and should be treated as immutable. """ def __new__(cls, initial, terminal=None): + """ + Create a vector from a dictionary or a sequence that maps the symbols to + their coordinates, or as the difference between two points. + """ if not isinstance(initial, Point): initial = Point(initial) if terminal is None: @@ -191,7 +245,7 @@ class Vector(Coordinates): def isnull(self): """ - Returns true if a Vector is null. + Return True if all coordinates are 0. """ return not bool(self) @@ -200,13 +254,59 @@ class Vector(Coordinates): def __add__(self, other): """ - Adds either a Point or Vector to a Vector. + If other is a point, translate it with the vector self and return the + resulting point. If other is a vector, return the vector self + other. """ if isinstance(other, (Point, Vector)): coordinates = self._map2(other, operator.add) return other.__class__(coordinates) return NotImplemented + def __sub__(self, other): + """ + If other is a point, substract it from the vector self and return the + resulting point. If other is a vector, return the vector self - other. + """ + if isinstance(other, (Point, Vector)): + coordinates = self._map2(other, operator.sub) + return other.__class__(coordinates) + return NotImplemented + + def __neg__(self): + """ + Return the vector -self. + """ + coordinates = self._map(operator.neg) + return Vector(coordinates) + + def __mul__(self, other): + """ + Multiplies a Vector by a scalar value. + """ + if not isinstance(other, numbers.Real): + return NotImplemented + coordinates = self._map(lambda coordinate: other * coordinate) + return Vector(coordinates) + + __rmul__ = __mul__ + + 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 __eq__(self, other): + """ + Test whether two vectors are equal. + """ + return isinstance(other, Vector) and \ + self._coordinates == other._coordinates + def angle(self, other): """ Retrieve the angle required to rotate the vector into the vector passed @@ -220,7 +320,8 @@ class Vector(Coordinates): def cross(self, other): """ - Calculate the cross product of two Vector3D structures. + Compute the cross product of two 3D vectors. If either one of the + vectors is not tridimensional, a ValueError exception is raised. """ if not isinstance(other, Vector): raise TypeError('other must be a Vector instance') @@ -235,19 +336,9 @@ class Vector(Coordinates): 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. + Compute the dot product of two vectors. """ if not isinstance(other, Vector): raise TypeError('argument must be a Vector instance') @@ -256,54 +347,27 @@ class Vector(Coordinates): result += coordinate1 * coordinate2 return result - def __eq__(self, other): - """ - Compares two Vectors for equality. - """ - return isinstance(other, Vector) and \ - self._coordinates == other._coordinates - def __hash__(self): return hash(tuple(self.coordinates())) - def __mul__(self, other): - """ - Multiplies a Vector by a scalar value. - """ - if not isinstance(other, numbers.Real): - return NotImplemented - coordinates = self._map(lambda coordinate: other * coordinate) - return Vector(coordinates) - - __rmul__ = __mul__ - - def __neg__(self): - """ - Returns the negated form of a Vector. - """ - coordinates = self._map(operator.neg) - return Vector(coordinates) - def norm(self): """ - Normalizes a Vector. + Return the norm of the vector. """ return math.sqrt(self.norm2()) def norm2(self): + """ + Return the squared norm of the vector. + """ result = 0 for coordinate in self._coordinates.values(): result += coordinate ** 2 return result def asunit(self): - return self / self.norm() - - def __sub__(self, other): """ - Subtract a Point or Vector from a Vector. + Return the normalized vector, i.e. the vector of same direction but with + norm 1. """ - if isinstance(other, (Point, Vector)): - coordinates = self._map2(other, operator.sub) - return other.__class__(coordinates) - return NotImplemented + return self / self.norm()