-
Notifications
You must be signed in to change notification settings - Fork 0
/
vector.py
125 lines (90 loc) · 3.7 KB
/
vector.py
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
from math import sqrt, isclose, hypot
from random import gauss
from itertools import starmap, zip_longest, repeat
from operator import add, sub, mul, truediv
class Vector:
"""An infinite dimensional vector class."""
#construction stuff
def __init__(self, coef):
"""Creates a new vector with the given coefficients
or the i-th basis vector if an integer i is given."""
if isinstance(coef, int):
self.coef = Vector.basis_tuple(coef)
else:
self.coef = tuple(coef)
@staticmethod
def basis_tuple(i):
return (0,)*i + (1,)
@staticmethod
def random(n, normed=True):
"""Creates a, by default normed, vector of the given dimensionality
with normal distributed coefficients."""
v = Vector(gauss() for _ in range(n))
return v / abs(v) if normed else v
#sequence stuff
def __len__(self):
return len(self.coef)
def __getitem__(self, key):
if isinstance(key, slice):
return Vector(self[i] for i in range(key.start, key.stop or len(self.coef), key.step or 1))
try:
return self.coef[key]
except IndexError:
return 0
def __iter__(self):
return iter(self.coef)
def __eq__(self, other):
return isinstance(other, type(self)) and self.trim().coef == other.trim().coef
def __lshift__(self, other):
return type(self)(self[other:])
def __rshift__(self, other):
return type(self)(other*(0,) + self.coef)
def trim(self, tol=1e-8):
"""Removes all trailing near zero (<=tol=1e-8) coefficients."""
c = self.coef
while c and abs(c[-1])<=tol:
c = c[:-1]
return type(self)(c)
def round(self, ndigits=None):
"""Rounds all coefficients to the given precision."""
return type(self)(round(c, ndigits) for c in self).trim()
#Hilbert space stuff
def __abs__(self):
return hypot(*self)
def __matmul__(self, other):
#https://docs.python.org/3/library/itertools.html
return sum(map(mul, self, other))
#vector space operations
#The return type of the arithmetic operations is determined by
#the first argument (self) to enable correctly typed results for subclasses.
#E.g. the sum of two HermiteFunctions should again be a HermiteFunction,
#and not a Vector.
@staticmethod
def map_zip(f, v, w):
"""Applies f(v, w) elementwise; the second argument may be iterable."""
try: #second argument iterable
return type(v)(map(f, v, w))
except TypeError: #second argument scalar
return type(v)(map(f, v, repeat(w)))
@staticmethod
def map_zip_longest(f, v, w):
"""Applies f(v, w) elementwise; the second argument may be iterable."""
try: #second argument iterable
return type(v)(starmap(f, zip_longest(v, w, fillvalue=0)))
except TypeError: #second argument scalar
return type(v)(map(f, v, repeat(w)))
#implementing vector space operations like they would be correct on paper:
#v+w, v-w, av, va, v/a
def __add__(self, other):
return Vector.map_zip_longest(add, self, other)
def __sub__(self, other):
return Vector.map_zip_longest(sub, self, other)
def __mul__(self, other):
return Vector.map_zip(mul, self, other)
__rmul__ = __mul__
def __truediv__(self, other):
return Vector.map_zip(truediv, self, other)
#python stuff
def __str__(self):
return 'Vector(' + ', '.join(map(str, self.coef)) + ', ...)'
Vector.ZERO = Vector(tuple())