1 # Copyright 2014 MINES ParisTech
3 # This file is part of Linpy.
5 # Linpy is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # Linpy is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Linpy. If not, see <http://www.gnu.org/licenses/>.
22 from abc
import ABC
, abstractproperty
, abstractmethod
23 from collections
import OrderedDict
, Mapping
25 from .linexprs
import Symbol
35 class GeometricObject(ABC
):
43 return len(self
.symbols
)
46 def aspolyhedron(self
):
50 return self
.aspolyhedron()
59 def __new__(cls
, coordinates
):
60 if isinstance(coordinates
, Mapping
):
61 coordinates
= coordinates
.items()
62 self
= object().__new
__(cls
)
63 self
._coordinates
= OrderedDict()
64 for symbol
, coordinate
in sorted(coordinates
,
65 key
=lambda item
: item
[0].sortkey()):
66 if not isinstance(symbol
, Symbol
):
67 raise TypeError('symbols must be Symbol instances')
68 if not isinstance(coordinate
, numbers
.Real
):
69 raise TypeError('coordinates must be real numbers')
70 self
._coordinates
[symbol
] = coordinate
75 return tuple(self
._coordinates
)
79 return len(self
.symbols
)
81 def coordinates(self
):
82 yield from self
._coordinates
.items()
84 def coordinate(self
, symbol
):
85 if not isinstance(symbol
, Symbol
):
86 raise TypeError('symbol must be a Symbol instance')
87 return self
._coordinates
[symbol
]
89 __getitem__
= coordinate
92 yield from self
._coordinates
.values()
95 return any(self
._coordinates
.values())
98 return hash(tuple(self
.coordinates()))
101 string
= ', '.join(['{!r}: {!r}'.format(symbol
, coordinate
)
102 for symbol
, coordinate
in self
.coordinates()])
103 return '{}({{{}}})'.format(self
.__class
__.__name
__, string
)
105 def _map(self
, func
):
106 for symbol
, coordinate
in self
.coordinates():
107 yield symbol
, func(coordinate
)
109 def _iter2(self
, other
):
110 if self
.symbols
!= other
.symbols
:
111 raise ValueError('arguments must belong to the same space')
112 coordinates1
= self
._coordinates
.values()
113 coordinates2
= other
._coordinates
.values()
114 yield from zip(self
.symbols
, coordinates1
, coordinates2
)
116 def _map2(self
, other
, func
):
117 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
118 yield symbol
, func(coordinate1
, coordinate2
)
121 class Point(Coordinates
, GeometricObject
):
123 This class represents points in space.
128 Return True if a Point is the origin.
130 return not bool(self
)
133 return super().__hash
__()
135 def __add__(self
, other
):
137 Adds a Point to a Vector and returns the result as a Point.
139 if not isinstance(other
, Vector
):
140 return NotImplemented
141 coordinates
= self
._map
2(other
, operator
.add
)
142 return Point(coordinates
)
144 def __sub__(self
, other
):
146 Returns the difference between two Points as a Vector.
149 if isinstance(other
, Point
):
150 coordinates
= self
._map
2(other
, operator
.sub
)
151 return Vector(coordinates
)
152 elif isinstance(other
, Vector
):
153 coordinates
= self
._map
2(other
, operator
.sub
)
154 return Point(coordinates
)
156 return NotImplemented
158 def __eq__(self
, other
):
160 Compares two Points for equality.
162 return isinstance(other
, Point
) and \
163 self
._coordinates
== other
._coordinates
165 def aspolyhedron(self
):
167 Return a Point as a polyhedron.
169 from .polyhedra
import Polyhedron
171 for symbol
, coordinate
in self
.coordinates():
172 equalities
.append(symbol
- coordinate
)
173 return Polyhedron(equalities
)
176 class Vector(Coordinates
):
178 This class represents displacements in space.
181 def __new__(cls
, initial
, terminal
=None):
182 if not isinstance(initial
, Point
):
183 initial
= Point(initial
)
185 coordinates
= initial
._coordinates
187 if not isinstance(terminal
, Point
):
188 terminal
= Point(terminal
)
189 coordinates
= terminal
._map
2(initial
, operator
.sub
)
190 return super().__new
__(cls
, coordinates
)
194 Returns true if a Vector is null.
196 return not bool(self
)
199 return super().__hash
__()
201 def __add__(self
, other
):
203 Adds either a Point or Vector to a Vector.
205 if isinstance(other
, (Point
, Vector
)):
206 coordinates
= self
._map
2(other
, operator
.add
)
207 return other
.__class
__(coordinates
)
208 return NotImplemented
210 def angle(self
, other
):
212 Retrieve the angle required to rotate the vector into the vector passed
213 in argument. The result is an angle in radians, ranging between -pi and
216 if not isinstance(other
, Vector
):
217 raise TypeError('argument must be a Vector instance')
218 cosinus
= self
.dot(other
) / (self
.norm()*other
.norm())
219 return math
.acos(cosinus
)
221 def cross(self
, other
):
223 Calculate the cross product of two Vector3D structures.
225 if not isinstance(other
, Vector
):
226 raise TypeError('other must be a Vector instance')
227 if self
.dimension
!= 3 or other
.dimension
!= 3:
228 raise ValueError('arguments must be three-dimensional vectors')
229 if self
.symbols
!= other
.symbols
:
230 raise ValueError('arguments must belong to the same space')
231 x
, y
, z
= self
.symbols
233 coordinates
.append((x
, self
[y
]*other
[z
] - self
[z
]*other
[y
]))
234 coordinates
.append((y
, self
[z
]*other
[x
] - self
[x
]*other
[z
]))
235 coordinates
.append((z
, self
[x
]*other
[y
] - self
[y
]*other
[x
]))
236 return Vector(coordinates
)
238 def __truediv__(self
, other
):
240 Divide the vector by the specified scalar and returns the result as a
243 if not isinstance(other
, numbers
.Real
):
244 return NotImplemented
245 coordinates
= self
._map
(lambda coordinate
: coordinate
/ other
)
246 return Vector(coordinates
)
248 def dot(self
, other
):
250 Calculate the dot product of two vectors.
252 if not isinstance(other
, Vector
):
253 raise TypeError('argument must be a Vector instance')
255 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
256 result
+= coordinate1
* coordinate2
259 def __eq__(self
, other
):
261 Compares two Vectors for equality.
263 return isinstance(other
, Vector
) and \
264 self
._coordinates
== other
._coordinates
267 return hash(tuple(self
.coordinates()))
269 def __mul__(self
, other
):
271 Multiplies a Vector by a scalar value.
273 if not isinstance(other
, numbers
.Real
):
274 return NotImplemented
275 coordinates
= self
._map
(lambda coordinate
: other
* coordinate
)
276 return Vector(coordinates
)
282 Returns the negated form of a Vector.
284 coordinates
= self
._map
(operator
.neg
)
285 return Vector(coordinates
)
291 return math
.sqrt(self
.norm2())
295 for coordinate
in self
._coordinates
.values():
296 result
+= coordinate
** 2
300 return self
/ self
.norm()
302 def __sub__(self
, other
):
304 Subtract a Point or Vector from a Vector.
306 if isinstance(other
, (Point
, Vector
)):
307 coordinates
= self
._map
2(other
, operator
.sub
)
308 return other
.__class
__(coordinates
)
309 return NotImplemented