5 from abc import ABC, abstractproperty, abstractmethod
6 from collections import OrderedDict, Mapping
8 from .linexprs import Symbol
18 class GeometricObject(ABC):
26 return len(self.symbols)
29 def aspolyhedron(self):
33 return self.aspolyhedron()
42 def __new__(cls, coordinates):
43 if isinstance(coordinates, Mapping):
44 coordinates = coordinates.items()
45 self = object().__new__(cls)
46 self._coordinates = OrderedDict()
47 for symbol, coordinate in sorted(coordinates,
48 key=lambda item: item[0].sortkey()):
49 if not isinstance(symbol, Symbol):
50 raise TypeError('symbols must be Symbol instances')
51 if not isinstance(coordinate, numbers.Real):
52 raise TypeError('coordinates must be real numbers')
53 self._coordinates[symbol] = coordinate
58 return tuple(self._coordinates)
62 return len(self.symbols)
64 def coordinates(self):
65 yield from self._coordinates.items()
67 def coordinate(self, symbol):
68 if not isinstance(symbol, Symbol):
69 raise TypeError('symbol must be a Symbol instance')
70 return self._coordinates[symbol]
72 __getitem__ = coordinate
75 yield from self._coordinates.values()
78 return any(self._coordinates.values())
81 return hash(tuple(self.coordinates()))
84 string = ', '.join(['{!r}: {!r}'.format(symbol, coordinate)
85 for symbol, coordinate in self.coordinates()])
86 return '{}({{{}}})'.format(self.__class__.__name__, string)
89 for symbol, coordinate in self.coordinates():
90 yield symbol, func(coordinate)
92 def _iter2(self, other):
93 if self.symbols != other.symbols:
94 raise ValueError('arguments must belong to the same space')
95 coordinates1 = self._coordinates.values()
96 coordinates2 = other._coordinates.values()
97 yield from zip(self.symbols, coordinates1, coordinates2)
99 def _map2(self, other, func):
100 for symbol, coordinate1, coordinate2 in self._iter2(other):
101 yield symbol, func(coordinate1, coordinate2)
104 class Point(Coordinates, GeometricObject):
106 This class represents points in space.
111 Return True if a Point is the origin.
113 return not bool(self)
116 return super().__hash__()
118 def __add__(self, other):
119 if not isinstance(other, Vector):
120 return NotImplemented
121 coordinates = self._map2(other, operator.add)
122 return Point(coordinates)
124 def __sub__(self, other):
126 if isinstance(other, Point):
127 coordinates = self._map2(other, operator.sub)
128 return Vector(coordinates)
129 elif isinstance(other, Vector):
130 coordinates = self._map2(other, operator.sub)
131 return Point(coordinates)
133 return NotImplemented
135 def __eq__(self, other):
137 Compares two Points for equality.
139 return isinstance(other, Point) and \
140 self._coordinates == other._coordinates
142 def aspolyhedron(self):
144 Return a Point as a polyhedron.
146 from .polyhedra import Polyhedron
148 for symbol, coordinate in self.coordinates():
149 equalities.append(symbol - coordinate)
150 return Polyhedron(equalities)
153 class Vector(Coordinates):
155 This class represents displacements in space.
158 def __new__(cls, initial, terminal=None):
159 if not isinstance(initial, Point):
160 initial = Point(initial)
162 coordinates = initial._coordinates
164 if not isinstance(terminal, Point):
165 terminal = Point(terminal)
166 coordinates = terminal._map2(initial, operator.sub)
167 return super().__new__(cls, coordinates)
171 Returns true if a Vector is null.
173 return not bool(self)
176 return super().__hash__()
178 def __add__(self, other):
180 Adds either a Point or Vector to a Vector.
182 if isinstance(other, (Point, Vector)):
183 coordinates = self._map2(other, operator.add)
184 return other.__class__(coordinates)
185 return NotImplemented
187 def angle(self, other):
189 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.
191 if not isinstance(other, Vector):
192 raise TypeError('argument must be a Vector instance')
193 cosinus = self.dot(other) / (self.norm()*other.norm())
194 return math.acos(cosinus)
196 def cross(self, other):
198 Calculate the cross product of two Vector3D structures.
200 if not isinstance(other, Vector):
201 raise TypeError('other must be a Vector instance')
202 if self.dimension != 3 or other.dimension != 3:
203 raise ValueError('arguments must be three-dimensional vectors')
204 if self.symbols != other.symbols:
205 raise ValueError('arguments must belong to the same space')
206 x, y, z = self.symbols
208 coordinates.append((x, self[y]*other[z] - self[z]*other[y]))
209 coordinates.append((y, self[z]*other[x] - self[x]*other[z]))
210 coordinates.append((z, self[x]*other[y] - self[y]*other[x]))
211 return Vector(coordinates)
213 def __truediv__(self, other):
215 Divide the vector by the specified scalar and returns the result as a
218 if not isinstance(other, numbers.Real):
219 return NotImplemented
220 coordinates = self._map(lambda coordinate: coordinate / other)
221 return Vector(coordinates)
223 def dot(self, other):
225 Calculate the dot product of two vectors.
227 if not isinstance(other, Vector):
228 raise TypeError('argument must be a Vector instance')
230 for symbol, coordinate1, coordinate2 in self._iter2(other):
231 result += coordinate1 * coordinate2
234 def __eq__(self, other):
236 Compares two Vectors for equality.
238 return isinstance(other, Vector) and \
239 self._coordinates == other._coordinates
242 return hash(tuple(self.coordinates()))
244 def __mul__(self, other):
246 Multiplies a Vector by a scalar value.
248 if not isinstance(other, numbers.Real):
249 return NotImplemented
250 coordinates = self._map(lambda coordinate: other * coordinate)
251 return Vector(coordinates)
257 Returns the negated form of a Vector.
259 coordinates = self._map(operator.neg)
260 return Vector(coordinates)
266 return math.sqrt(self.norm2())
270 for coordinate in self._coordinates.values():
271 result += coordinate ** 2
275 return self / self.norm()
277 def __sub__(self, other):
279 Subtract a Point or Vector from a Vector.
281 if isinstance(other, (Point, Vector)):
282 coordinates = self._map2(other, operator.sub)
283 return other.__class__(coordinates)
284 return NotImplemented