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
._iter
2(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
):
120 Adds a Point to a Vector and returns the result as a Point.
122 if not isinstance(other
, Vector
):
123 return NotImplemented
124 coordinates
= self
._map
2(other
, operator
.add
)
125 return Point(coordinates
)
127 def __sub__(self
, other
):
129 Returns the difference between two Points as a Vector.
132 if isinstance(other
, Point
):
133 coordinates
= self
._map
2(other
, operator
.sub
)
134 return Vector(coordinates
)
135 elif isinstance(other
, Vector
):
136 coordinates
= self
._map
2(other
, operator
.sub
)
137 return Point(coordinates
)
139 return NotImplemented
141 def __eq__(self
, other
):
143 Compares two Points for equality.
145 return isinstance(other
, Point
) and \
146 self
._coordinates
== other
._coordinates
148 def aspolyhedron(self
):
150 Return a Point as a polyhedron.
152 from .polyhedra
import Polyhedron
154 for symbol
, coordinate
in self
.coordinates():
155 equalities
.append(symbol
- coordinate
)
156 return Polyhedron(equalities
)
159 class Vector(Coordinates
):
161 This class represents displacements in space.
164 def __new__(cls
, initial
, terminal
=None):
165 if not isinstance(initial
, Point
):
166 initial
= Point(initial
)
168 coordinates
= initial
._coordinates
170 if not isinstance(terminal
, Point
):
171 terminal
= Point(terminal
)
172 coordinates
= terminal
._map
2(initial
, operator
.sub
)
173 return super().__new
__(cls
, coordinates
)
177 Returns true if a Vector is null.
179 return not bool(self
)
182 return super().__hash
__()
184 def __add__(self
, other
):
186 Adds either a Point or Vector to a Vector.
188 if isinstance(other
, (Point
, Vector
)):
189 coordinates
= self
._map
2(other
, operator
.add
)
190 return other
.__class
__(coordinates
)
191 return NotImplemented
193 def angle(self
, other
):
195 Retrieve the angle required to rotate the vector into the vector passed
196 in argument. The result is an angle in radians, ranging between -pi and
199 if not isinstance(other
, Vector
):
200 raise TypeError('argument must be a Vector instance')
201 cosinus
= self
.dot(other
) / (self
.norm()*other
.norm())
202 return math
.acos(cosinus
)
204 def cross(self
, other
):
206 Calculate the cross product of two Vector3D structures.
208 if not isinstance(other
, Vector
):
209 raise TypeError('other must be a Vector instance')
210 if self
.dimension
!= 3 or other
.dimension
!= 3:
211 raise ValueError('arguments must be three-dimensional vectors')
212 if self
.symbols
!= other
.symbols
:
213 raise ValueError('arguments must belong to the same space')
214 x
, y
, z
= self
.symbols
216 coordinates
.append((x
, self
[y
]*other
[z
] - self
[z
]*other
[y
]))
217 coordinates
.append((y
, self
[z
]*other
[x
] - self
[x
]*other
[z
]))
218 coordinates
.append((z
, self
[x
]*other
[y
] - self
[y
]*other
[x
]))
219 return Vector(coordinates
)
221 def __truediv__(self
, other
):
223 Divide the vector by the specified scalar and returns the result as a
226 if not isinstance(other
, numbers
.Real
):
227 return NotImplemented
228 coordinates
= self
._map
(lambda coordinate
: coordinate
/ other
)
229 return Vector(coordinates
)
231 def dot(self
, other
):
233 Calculate the dot product of two vectors.
235 if not isinstance(other
, Vector
):
236 raise TypeError('argument must be a Vector instance')
238 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
239 result
+= coordinate1
* coordinate2
242 def __eq__(self
, other
):
244 Compares two Vectors for equality.
246 return isinstance(other
, Vector
) and \
247 self
._coordinates
== other
._coordinates
250 return hash(tuple(self
.coordinates()))
252 def __mul__(self
, other
):
254 Multiplies a Vector by a scalar value.
256 if not isinstance(other
, numbers
.Real
):
257 return NotImplemented
258 coordinates
= self
._map
(lambda coordinate
: other
* coordinate
)
259 return Vector(coordinates
)
265 Returns the negated form of a Vector.
267 coordinates
= self
._map
(operator
.neg
)
268 return Vector(coordinates
)
274 return math
.sqrt(self
.norm2())
278 for coordinate
in self
._coordinates
.values():
279 result
+= coordinate
** 2
283 return self
/ self
.norm()
285 def __sub__(self
, other
):
287 Subtract a Point or Vector from a Vector.
289 if isinstance(other
, (Point
, Vector
)):
290 coordinates
= self
._map
2(other
, operator
.sub
)
291 return other
.__class
__(coordinates
)
292 return NotImplemented