5 from collections
import OrderedDict
, Mapping
7 from .geometry
import GeometricObject
8 from .linexprs
import Symbol
23 def __new__(cls
, coordinates
):
24 if isinstance(coordinates
, Mapping
):
25 coordinates
= coordinates
.items()
26 self
= object().__new
__(cls
)
27 self
._coordinates
= OrderedDict()
28 for symbol
, coordinate
in sorted(coordinates
,
29 key
=lambda item
: item
[0].sortkey()):
30 if not isinstance(symbol
, Symbol
):
31 raise TypeError('symbols must be Symbol instances')
32 if not isinstance(coordinate
, numbers
.Real
):
33 raise TypeError('coordinates must be real numbers')
34 self
._coordinates
[symbol
] = coordinate
39 return tuple(self
._coordinates
)
43 return len(self
.symbols
)
45 def coordinates(self
):
46 yield from self
._coordinates
.items()
48 def coordinate(self
, symbol
):
49 if not isinstance(symbol
, Symbol
):
50 raise TypeError('symbol must be a Symbol instance')
51 return self
._coordinates
[symbol
]
53 __getitem__
= coordinate
56 return any(self
._coordinates
.values())
59 return hash(tuple(self
.coordinates()))
62 string
= ', '.join(['{!r}: {!r}'.format(symbol
, coordinate
)
63 for symbol
, coordinate
in self
.coordinates()])
64 return '{}({{{}}})'.format(self
.__class
__.__name
__, string
)
67 for symbol
, coordinate
in self
.coordinates():
68 yield symbol
, func(coordinate
)
70 def _iter2(self
, other
):
71 if self
.symbols
!= other
.symbols
:
72 raise ValueError('arguments must belong to the same space')
73 coordinates1
= self
._coordinates
.values()
74 coordinates2
= other
._coordinates
.values()
75 yield from zip(self
.symbols
, coordinates1
, coordinates2
)
77 def _map2(self
, other
, func
):
78 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
79 yield symbol
, func(coordinate1
, coordinate2
)
82 class Point(Coordinates
, GeometricObject
):
84 This class represents points in space.
90 def __add__(self
, other
):
91 if not isinstance(other
, Vector
):
93 coordinates
= self
._map
2(other
, operator
.add
)
94 return Point(coordinates
)
96 def __sub__(self
, other
):
98 if isinstance(other
, Point
):
99 coordinates
= self
._map
2(other
, operator
.sub
)
100 return Vector(coordinates
)
101 elif isinstance(other
, Vector
):
102 coordinates
= self
._map
2(other
, operator
.sub
)
103 return Point(coordinates
)
105 return NotImplemented
107 def __eq__(self
, other
):
108 return isinstance(other
, Point
) and \
109 self
._coordinates
== other
._coordinates
111 def aspolyhedron(self
):
112 from .polyhedra
import Polyhedron
114 for symbol
, coordinate
in self
.coordinates():
115 equalities
.append(symbol
- coordinate
)
116 return Polyhedron(equalities
)
119 class Vector(Coordinates
):
121 This class represents displacements in space.
124 def __new__(cls
, initial
, terminal
=None):
125 if not isinstance(initial
, Point
):
126 initial
= Point(initial
)
128 coordinates
= initial
._coordinates
129 elif not isinstance(terminal
, Point
):
130 terminal
= Point(terminal
)
131 coordinates
= terminal
._map
2(initial
, operator
.sub
)
132 return super().__new
__(cls
, coordinates
)
135 return not bool(self
)
137 def __add__(self
, other
):
138 if isinstance(other
, (Point
, Vector
)):
139 coordinates
= self
._map
2(other
, operator
.add
)
140 return other
.__class
__(coordinates
)
141 return NotImplemented
143 def angle(self
, other
):
145 Retrieve the angle required to rotate the vector into the vector passed
146 in argument. The result is an angle in radians, ranging between -pi and
149 if not isinstance(other
, Vector
):
150 raise TypeError('argument must be a Vector instance')
151 cosinus
= self
.dot(other
) / (self
.norm()*other
.norm())
152 return math
.acos(cosinus
)
154 def cross(self
, other
):
156 Calculate the cross product of two Vector3D structures.
158 if not isinstance(other
, Vector
):
159 raise TypeError('other must be a Vector instance')
160 if self
.dimension
!= 3 or other
.dimension
!= 3:
161 raise ValueError('arguments must be three-dimensional vectors')
162 if self
.symbols
!= other
.symbols
:
163 raise ValueError('arguments must belong to the same space')
164 x
, y
, z
= self
.symbols
166 coordinates
.append((x
, self
[y
]*other
[z
] - self
[z
]*other
[y
]))
167 coordinates
.append((y
, self
[z
]*other
[x
] - self
[x
]*other
[z
]))
168 coordinates
.append((z
, self
[x
]*other
[y
] - self
[y
]*other
[x
]))
169 return Vector(coordinates
)
171 def __truediv__(self
, other
):
173 Divide the vector by the specified scalar and returns the result as a
176 if not isinstance(other
, numbers
.Real
):
177 return NotImplemented
178 coordinates
= self
._map
(lambda coordinate
: coordinate
/ other
)
179 return Vector(coordinates
)
181 def dot(self
, other
):
183 Calculate the dot product of two vectors.
185 if not isinstance(other
, Vector
):
186 raise TypeError('argument must be a Vector instance')
188 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
189 result
+= coordinate1
* coordinate2
192 def __eq__(self
, other
):
193 return isinstance(other
, Vector
) and \
194 self
._coordinates
== other
._coordinates
197 return hash(tuple(self
.coordinates()))
199 def __mul__(self
, other
):
200 if not isinstance(other
, numbers
.Real
):
201 return NotImplemented
202 coordinates
= self
._map
(lambda coordinate
: other
* coordinate
)
203 return Vector(coordinates
)
208 coordinates
= self
._map
(operator
.neg
)
209 return Vector(coordinates
)
212 return math
.sqrt(self
.norm2())
216 for coordinate
in self
._coordinates
.values():
217 result
+= coordinate
** 2
221 return self
/ self
.norm()
223 def __sub__(self
, other
):
224 if isinstance(other
, (Point
, Vector
)):
225 coordinates
= self
._map
2(other
, operator
.sub
)
226 return other
.__class
__(coordinates
)
227 return NotImplemented