5 from collections
import OrderedDict
, Mapping
7 from .linexprs
import Symbol
22 def __new__(cls
, coordinates
):
23 if isinstance(coordinates
, Mapping
):
24 coordinates
= coordinates
.items()
25 self
= object().__new
__(cls
)
26 self
._coordinates
= OrderedDict()
27 for symbol
, coordinate
in sorted(coordinates
,
28 key
=lambda item
: item
[0].sortkey()):
29 if not isinstance(symbol
, Symbol
):
30 raise TypeError('symbols must be Symbol instances')
31 if not isinstance(coordinate
, numbers
.Real
):
32 raise TypeError('coordinates must be real numbers')
33 self
._coordinates
[symbol
] = coordinate
38 return tuple(self
._coordinates
)
42 return len(self
.symbols
)
44 def coordinates(self
):
45 yield from self
._coordinates
.items()
47 def coordinate(self
, symbol
):
48 if not isinstance(symbol
, Symbol
):
49 raise TypeError('symbol must be a Symbol instance')
50 return self
._coordinates
[symbol
]
52 __getitem__
= coordinate
55 return any(self
._coordinates
.values())
58 return hash(tuple(self
.coordinates()))
61 string
= ', '.join(['{!r}: {!r}'.format(symbol
, coordinate
)
62 for symbol
, coordinate
in self
.coordinates()])
63 return '{}({{{}}})'.format(self
.__class
__.__name
__, string
)
66 for symbol
, coordinate
in self
.coordinates():
67 yield symbol
, func(coordinate
)
69 def _iter2(self
, other
):
70 if self
.symbols
!= other
.symbols
:
71 raise ValueError('arguments must belong to the same space')
72 coordinates1
= self
._coordinates
.values()
73 coordinates2
= other
._coordinates
.values()
74 yield from zip(self
.symbols
, coordinates1
, coordinates2
)
76 def _map2(self
, other
, func
):
77 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
78 yield symbol
, func(coordinate1
, coordinate2
)
81 class Point(Coordinates
):
83 This class represents points in space.
89 def __add__(self
, other
):
90 if not isinstance(other
, Vector
):
92 coordinates
= self
._map
2(other
, operator
.add
)
93 return Point(coordinates
)
95 def __sub__(self
, other
):
97 if isinstance(other
, Point
):
98 coordinates
= self
._map
2(other
, operator
.sub
)
99 return Vector(coordinates
)
100 elif isinstance(other
, Vector
):
101 coordinates
= self
._map
2(other
, operator
.sub
)
102 return Point(coordinates
)
104 return NotImplemented
106 def __eq__(self
, other
):
107 return isinstance(other
, Point
) and \
108 self
._coordinates
== other
._coordinates
110 def aspolyhedron(self
):
111 from .polyhedra
import Polyhedron
113 for symbol
, coordinate
in self
.coordinates():
114 equalities
.append(symbol
- coordinate
)
115 return Polyhedron(equalities
)
118 class Vector(Coordinates
):
120 This class represents displacements in space.
123 def __new__(cls
, initial
, terminal
=None):
124 if not isinstance(initial
, Point
):
125 initial
= Point(initial
)
127 coordinates
= initial
._coordinates
128 elif not isinstance(terminal
, Point
):
129 terminal
= Point(terminal
)
130 coordinates
= terminal
._map
2(initial
, operator
.sub
)
131 return super().__new
__(cls
, coordinates
)
134 return not bool(self
)
136 def __add__(self
, other
):
137 if isinstance(other
, (Point
, Vector
)):
138 coordinates
= self
._map
2(other
, operator
.add
)
139 return other
.__class
__(coordinates
)
140 return NotImplemented
142 def angle(self
, other
):
144 Retrieve the angle required to rotate the vector into the vector passed
145 in argument. The result is an angle in radians, ranging between -pi and
148 if not isinstance(other
, Vector
):
149 raise TypeError('argument must be a Vector instance')
150 cosinus
= self
.dot(other
) / (self
.norm()*other
.norm())
151 return math
.acos(cosinus
)
153 def cross(self
, other
):
155 Calculate the cross product of two Vector3D structures.
157 if not isinstance(other
, Vector
):
158 raise TypeError('other must be a Vector instance')
159 if self
.dimension
!= 3 or other
.dimension
!= 3:
160 raise ValueError('arguments must be three-dimensional vectors')
161 if self
.symbols
!= other
.symbols
:
162 raise ValueError('arguments must belong to the same space')
163 x
, y
, z
= self
.symbols
165 coordinates
.append((x
, self
[y
]*other
[z
] - self
[z
]*other
[y
]))
166 coordinates
.append((y
, self
[z
]*other
[x
] - self
[x
]*other
[z
]))
167 coordinates
.append((z
, self
[x
]*other
[y
] - self
[y
]*other
[x
]))
168 return Vector(coordinates
)
170 def __truediv__(self
, other
):
172 Divide the vector by the specified scalar and returns the result as a
175 if not isinstance(other
, numbers
.Real
):
176 return NotImplemented
177 coordinates
= self
._map
(lambda coordinate
: coordinate
/ other
)
178 return Vector(coordinates
)
180 def dot(self
, other
):
182 Calculate the dot product of two vectors.
184 if not isinstance(other
, Vector
):
185 raise TypeError('argument must be a Vector instance')
187 for symbol
, coordinate1
, coordinate2
in self
._iter
2(other
):
188 result
+= coordinate1
* coordinate2
191 def __eq__(self
, other
):
192 return isinstance(other
, Vector
) and \
193 self
._coordinates
== other
._coordinates
196 return hash(tuple(self
.coordinates()))
198 def __mul__(self
, other
):
199 if not isinstance(other
, numbers
.Real
):
200 return NotImplemented
201 coordinates
= self
._map
(lambda coordinate
: other
* coordinate
)
202 return Vector(coordinates
)
207 coordinates
= self
._map
(operator
.neg
)
208 return Vector(coordinates
)
211 return math
.sqrt(self
.norm2())
215 for coordinate
in self
._coordinates
.values():
216 result
+= coordinate
** 2
220 return self
/ self
.norm()
222 def __sub__(self
, other
):
223 if isinstance(other
, (Point
, Vector
)):
224 coordinates
= self
._map
2(other
, operator
.sub
)
225 return other
.__class
__(coordinates
)
226 return NotImplemented