This repository was archived by the owner on Dec 4, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvector.py
More file actions
144 lines (113 loc) · 4.05 KB
/
vector.py
File metadata and controls
144 lines (113 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# Incremental Version 6
# - add cross product
# - add area of the parallelogram spanned by v, w
# - add area of the triangle spanned by v, w
from math import sqrt, acos, pi
from decimal import Decimal, getcontext #better numerical precision
getcontext().prec = 30
class Vector(object):
CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot normalize the zero vector'
NO_UNIQUE_PARALLEL_COMPONENT_MSG = 'No unique parallel component'
NO_UNIQUE_ORTHOGONAL_COMPONENT_MSG = 'No unique orthogonal component'
ONLY_DEFINED_IN_TWO_THREE_DIMS_MSG = 'Onyl defined in two 3D vectors'
def __init__(self, coordinates):
try:
if not coordinates:
raise ValueError
#ensure floating numbers
self.coordinates = tuple([Decimal(x) for x in coordinates])
self.dimension = len(coordinates)
except ValueError:
raise ValueError('The coordinate must be nonempty')
except TypeError:
raise TypeError('The coordinates must be an iterable')
def __add__(self, v):
new_coordinates = [x + y for x, y in zip(self.coordinates, v.coordinates)]
return Vector(new_coordinates)
def minus(self, v):
new_coordinates = [x - y for x, y in zip(self.coordinates, v.coordinates)]
return Vector(new_coordinates)
def times_scalar(self, c):
new_coordinates = [c * x for x in self.coordinates]
return Vector(new_coordinates)
def magnitude(self):
return sqrt(sum(x ** 2 for x in self.coordinates))
def normalized(self):
try:
magnitude = self.magnitude()
return self.times_scalar(Decimal('1.0')/magnitude)
except ZeroDivisionError:
raise Exception('Cannot normalize the zero vector')
def is_orthogonal_to(self, v, tolerance = 1e-10):
return abs(self.dot(v)) < tolerance
def is_parallel_to(self,v):
return ( self.is_zero() or
v.is_zero() or
self.angle_with(v) == 0 or
self.angle_with(v) == pi)
def is_zero(self, tolerance = 1e-10):
return self.magnitude() < tolerance
def component_orthogonal_to(self, basis):
try:
projection = self.component_parallel_to(basis)
return self.minus(projection)
except Exception as e:
if str(e) == self.NO_UNIQUE_PARALLEL_COMPONENT_MSG:
raise Exception(self.NO_UNIQUE_ORTHOGONAL_COMPONENT_MSG)
else:
raise e
def component_parallel_to(self, basis):
try:
u = basis.normalized()
weight = self.dot(u)
return u.times_scalar(weight)
except Exception as e:
if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:
raise Exception(self.NO_UNIQUE_PARALLEL_COMPONENT_MSG)
else:
raise e
def dot(self,v):
return sum(x * y for x, y in zip(self.coordinates, v.coordinates))
def area_of_triangle_with(self, v):
return self.area_of_parallelogram_with(v) / Decimal('2.0')
def area_of_parallelogram_with(self, v):
cross_product = self.cross(v)
return cross_product.magnitude()
def cross(self, v):
try:
x_1, y_1, z_1 = self.coordinates
x_2, y_2, z_2 = v.coordinates
new_coordinates = [ y_1 * z_2 - y_2 * z_1,
-(x_1 * z_2 - x_2 * z_1),
x_1 * y_2 - x_2 * y_1]
return Vector(new_coordinates)
except ValueError as e:
msg = str(e)
if msg == 'need more than 2 values to unpack':
"""if vectors are in two dimension, add 0 to the 3rd dimention"""
self_embedded_in_R3 = Vector(self.coordinates + ('0', ))
v_embedded_in_R3 = Vector(v.coordinates + ('0', ))
return self_embedded_in_R3.cross(v_embedded_in_R3)
elif (msg == 'too many value to unpack' or msg == 'need more than 1 value to unpack'):
raise Exception(self.ONLY_DEFINED_IN_TWO_THREE_DIMS_MSG)
else:
raise e
def angle_with(self, v, in_degrees=False):
try:
u1 = self.normalized()
u2 = v.normalized()
angle_in_radians = acos(u1.dot(u2))
if in_degrees:
degrees_per_radian = 180. / pi
return angle_in_radians * degrees_per_radian
else:
return angle_in_radians
except Exception as e:
if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:
raise Exception("Cannot compute an angle with zero vector")
else:
raise e
def __str__(self):
return 'Vector: {}'.format(self.coordinates)
def __eq__(self, v):
return self.coordinates == v.coordinates