vectorphile 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. vectorphile/__init__.py +154 -0
  2. vectorphile/amp/__init__.py +52 -0
  3. vectorphile/autograd/__init__.py +13 -0
  4. vectorphile/autograd/engine.py +208 -0
  5. vectorphile/core/__init__.py +21 -0
  6. vectorphile/core/tensor.py +696 -0
  7. vectorphile/data/__init__.py +23 -0
  8. vectorphile/data/frame.py +537 -0
  9. vectorphile/data/groupby.py +98 -0
  10. vectorphile/data/index.py +86 -0
  11. vectorphile/data/series.py +228 -0
  12. vectorphile/fft/__init__.py +34 -0
  13. vectorphile/linalg/__init__.py +63 -0
  14. vectorphile/ml/__init__.py +44 -0
  15. vectorphile/ml/boost/__init__.py +133 -0
  16. vectorphile/ml/cluster.py +218 -0
  17. vectorphile/ml/decomposition.py +161 -0
  18. vectorphile/ml/ensemble.py +209 -0
  19. vectorphile/ml/linear.py +250 -0
  20. vectorphile/ml/metrics.py +148 -0
  21. vectorphile/ml/model_selection.py +171 -0
  22. vectorphile/ml/naive_bayes.py +113 -0
  23. vectorphile/ml/neighbors.py +95 -0
  24. vectorphile/ml/preprocessing.py +202 -0
  25. vectorphile/ml/svm.py +165 -0
  26. vectorphile/ml/tree.py +155 -0
  27. vectorphile/nn/__init__.py +20 -0
  28. vectorphile/nn/activations.py +97 -0
  29. vectorphile/nn/layers.py +231 -0
  30. vectorphile/nn/losses.py +172 -0
  31. vectorphile/nn/module.py +97 -0
  32. vectorphile/nn/sequential.py +47 -0
  33. vectorphile/optim/__init__.py +12 -0
  34. vectorphile/optim/adagrad.py +31 -0
  35. vectorphile/optim/adam.py +71 -0
  36. vectorphile/optim/lr_scheduler.py +110 -0
  37. vectorphile/optim/optimizer.py +30 -0
  38. vectorphile/optim/rmsprop.py +34 -0
  39. vectorphile/optim/sgd.py +38 -0
  40. vectorphile/optimize/__init__.py +97 -0
  41. vectorphile/random/__init__.py +64 -0
  42. vectorphile/signal/__init__.py +67 -0
  43. vectorphile/sparse/__init__.py +129 -0
  44. vectorphile/stats/__init__.py +90 -0
  45. vectorphile/utils/__init__.py +3 -0
  46. vectorphile/utils/data/__init__.py +4 -0
  47. vectorphile/utils/data/dataloader.py +47 -0
  48. vectorphile/utils/data/dataset.py +29 -0
  49. vectorphile-0.1.0.dist-info/METADATA +208 -0
  50. vectorphile-0.1.0.dist-info/RECORD +53 -0
  51. vectorphile-0.1.0.dist-info/WHEEL +5 -0
  52. vectorphile-0.1.0.dist-info/licenses/LICENSE +201 -0
  53. vectorphile-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,696 @@
1
+ """Core Tensor class — the foundation of vectorphile."""
2
+ from __future__ import annotations
3
+ import numpy as np
4
+ from typing import Any, Optional, Tuple, Union, List
5
+
6
+
7
+ _DTYPE_MAP = {
8
+ 'float32': np.float32, 'float': np.float32,
9
+ 'float64': np.float64, 'double': np.float64,
10
+ 'float16': np.float16, 'half': np.float16,
11
+ 'int32': np.int32, 'int': np.int32,
12
+ 'int64': np.int64, 'long': np.int64,
13
+ 'int8': np.int8, 'uint8': np.uint8,
14
+ 'bool': np.bool_,
15
+ }
16
+
17
+
18
+ class Tensor:
19
+ """N-dimensional tensor backed by numpy, with autograd support."""
20
+
21
+ def __init__(self, data, dtype=None, device='cpu', requires_grad=False):
22
+ if isinstance(data, Tensor):
23
+ data = data._data
24
+ if isinstance(data, np.ndarray):
25
+ self._data = data.astype(_DTYPE_MAP.get(dtype, data.dtype)) if dtype else data.copy()
26
+ else:
27
+ np_dtype = _DTYPE_MAP.get(dtype, np.float32) if dtype else None
28
+ self._data = np.array(data, dtype=np_dtype)
29
+ self.device = device
30
+ self.requires_grad = requires_grad
31
+ self.grad: Optional[Tensor] = None
32
+ self.grad_fn = None
33
+ self._is_leaf = True
34
+
35
+ # ── properties ──────────────────────────────────────────────────────────
36
+
37
+ @property
38
+ def shape(self) -> Tuple[int, ...]:
39
+ return self._data.shape
40
+
41
+ @property
42
+ def ndim(self) -> int:
43
+ return self._data.ndim
44
+
45
+ @property
46
+ def dtype(self) -> str:
47
+ return str(self._data.dtype)
48
+
49
+ @property
50
+ def size(self) -> int:
51
+ return self._data.size
52
+
53
+ @property
54
+ def T(self) -> 'Tensor':
55
+ return self.transpose()
56
+
57
+ @property
58
+ def data(self) -> np.ndarray:
59
+ return self._data
60
+
61
+ # ── creation helpers (class methods) ───────────────────────────────────
62
+
63
+ @classmethod
64
+ def zeros(cls, *shape, dtype='float32', device='cpu', requires_grad=False) -> 'Tensor':
65
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
66
+ shape = shape[0]
67
+ return cls(np.zeros(shape, dtype=_DTYPE_MAP[dtype]), device=device, requires_grad=requires_grad)
68
+
69
+ @classmethod
70
+ def ones(cls, *shape, dtype='float32', device='cpu', requires_grad=False) -> 'Tensor':
71
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
72
+ shape = shape[0]
73
+ return cls(np.ones(shape, dtype=_DTYPE_MAP[dtype]), device=device, requires_grad=requires_grad)
74
+
75
+ @classmethod
76
+ def eye(cls, n, m=None, dtype='float32', device='cpu') -> 'Tensor':
77
+ return cls(np.eye(n, m, dtype=_DTYPE_MAP[dtype]), device=device)
78
+
79
+ @classmethod
80
+ def arange(cls, start, stop=None, step=1, dtype='float32', device='cpu') -> 'Tensor':
81
+ if stop is None:
82
+ start, stop = 0, start
83
+ return cls(np.arange(start, stop, step, dtype=_DTYPE_MAP[dtype]), device=device)
84
+
85
+ @classmethod
86
+ def linspace(cls, start, stop, num, dtype='float32', device='cpu') -> 'Tensor':
87
+ return cls(np.linspace(start, stop, num, dtype=_DTYPE_MAP[dtype]), device=device)
88
+
89
+ @classmethod
90
+ def full(cls, shape, fill_value, dtype='float32', device='cpu') -> 'Tensor':
91
+ return cls(np.full(shape, fill_value, dtype=_DTYPE_MAP[dtype]), device=device)
92
+
93
+ @classmethod
94
+ def empty(cls, *shape, dtype='float32', device='cpu') -> 'Tensor':
95
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
96
+ shape = shape[0]
97
+ return cls(np.empty(shape, dtype=_DTYPE_MAP[dtype]), device=device)
98
+
99
+ @classmethod
100
+ def rand(cls, *shape, device='cpu', requires_grad=False) -> 'Tensor':
101
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
102
+ shape = shape[0]
103
+ return cls(np.random.rand(*shape).astype(np.float32), device=device, requires_grad=requires_grad)
104
+
105
+ @classmethod
106
+ def randn(cls, *shape, device='cpu', requires_grad=False) -> 'Tensor':
107
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
108
+ shape = shape[0]
109
+ return cls(np.random.randn(*shape).astype(np.float32), device=device, requires_grad=requires_grad)
110
+
111
+ @classmethod
112
+ def randint(cls, low, high, shape, dtype='int64', device='cpu') -> 'Tensor':
113
+ return cls(np.random.randint(low, high, shape).astype(_DTYPE_MAP[dtype]), device=device)
114
+
115
+ @classmethod
116
+ def from_numpy(cls, arr: np.ndarray) -> 'Tensor':
117
+ return cls(arr)
118
+
119
+ # ── type casting ────────────────────────────────────────────────────────
120
+
121
+ def float(self) -> 'Tensor':
122
+ return Tensor(self._data.astype(np.float32), device=self.device)
123
+
124
+ def double(self) -> 'Tensor':
125
+ return Tensor(self._data.astype(np.float64), device=self.device)
126
+
127
+ def half(self) -> 'Tensor':
128
+ return Tensor(self._data.astype(np.float16), device=self.device)
129
+
130
+ def int(self) -> 'Tensor':
131
+ return Tensor(self._data.astype(np.int32), device=self.device)
132
+
133
+ def long(self) -> 'Tensor':
134
+ return Tensor(self._data.astype(np.int64), device=self.device)
135
+
136
+ def bool(self) -> 'Tensor':
137
+ return Tensor(self._data.astype(np.bool_), device=self.device)
138
+
139
+ def to(self, device=None, dtype=None) -> 'Tensor':
140
+ data = self._data
141
+ if dtype is not None:
142
+ data = data.astype(_DTYPE_MAP.get(dtype, data.dtype))
143
+ return Tensor(data, device=device or self.device, requires_grad=self.requires_grad)
144
+
145
+ def numpy(self) -> np.ndarray:
146
+ return self._data.copy()
147
+
148
+ def item(self):
149
+ return self._data.item()
150
+
151
+ def tolist(self):
152
+ return self._data.tolist()
153
+
154
+ def detach(self) -> 'Tensor':
155
+ t = Tensor(self._data, device=self.device)
156
+ t.requires_grad = False
157
+ return t
158
+
159
+ def clone(self) -> 'Tensor':
160
+ return Tensor(self._data.copy(), device=self.device, requires_grad=self.requires_grad)
161
+
162
+ # ── shape ops ───────────────────────────────────────────────────────────
163
+
164
+ def reshape(self, *shape) -> 'Tensor':
165
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
166
+ shape = shape[0]
167
+ return Tensor(self._data.reshape(shape), device=self.device, requires_grad=self.requires_grad)
168
+
169
+ def view(self, *shape) -> 'Tensor':
170
+ return self.reshape(*shape)
171
+
172
+ def transpose(self, dim0=None, dim1=None) -> 'Tensor':
173
+ if dim0 is None and dim1 is None:
174
+ return Tensor(self._data.T, device=self.device, requires_grad=self.requires_grad)
175
+ axes = list(range(self.ndim))
176
+ axes[dim0], axes[dim1] = axes[dim1], axes[dim0]
177
+ return Tensor(np.transpose(self._data, axes), device=self.device, requires_grad=self.requires_grad)
178
+
179
+ def permute(self, *dims) -> 'Tensor':
180
+ if len(dims) == 1 and isinstance(dims[0], (tuple, list)):
181
+ dims = dims[0]
182
+ return Tensor(np.transpose(self._data, dims), device=self.device, requires_grad=self.requires_grad)
183
+
184
+ def squeeze(self, dim=None) -> 'Tensor':
185
+ if dim is None:
186
+ return Tensor(np.squeeze(self._data), device=self.device, requires_grad=self.requires_grad)
187
+ return Tensor(np.squeeze(self._data, axis=dim), device=self.device, requires_grad=self.requires_grad)
188
+
189
+ def unsqueeze(self, dim) -> 'Tensor':
190
+ return Tensor(np.expand_dims(self._data, axis=dim), device=self.device, requires_grad=self.requires_grad)
191
+
192
+ def flatten(self, start_dim=0, end_dim=-1) -> 'Tensor':
193
+ shape = self._data.shape
194
+ if end_dim < 0:
195
+ end_dim = self.ndim + end_dim
196
+ new_shape = shape[:start_dim] + (-1,) + shape[end_dim+1:]
197
+ return Tensor(self._data.reshape(new_shape), device=self.device, requires_grad=self.requires_grad)
198
+
199
+ def expand(self, *shape) -> 'Tensor':
200
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
201
+ shape = shape[0]
202
+ return Tensor(np.broadcast_to(self._data, shape).copy(), device=self.device, requires_grad=self.requires_grad)
203
+
204
+ def contiguous(self) -> 'Tensor':
205
+ return Tensor(np.ascontiguousarray(self._data), device=self.device, requires_grad=self.requires_grad)
206
+
207
+ def chunk(self, chunks, dim=0) -> List['Tensor']:
208
+ return [Tensor(a, device=self.device) for a in np.array_split(self._data, chunks, axis=dim)]
209
+
210
+ def split(self, split_size, dim=0) -> List['Tensor']:
211
+ indices = list(range(split_size, self.shape[dim], split_size))
212
+ return [Tensor(a, device=self.device) for a in np.split(self._data, indices, axis=dim)]
213
+
214
+ def repeat(self, *repeats) -> 'Tensor':
215
+ if len(repeats) == 1 and isinstance(repeats[0], (tuple, list)):
216
+ repeats = repeats[0]
217
+ return Tensor(np.tile(self._data, repeats), device=self.device)
218
+
219
+ # ── reduction ops ───────────────────────────────────────────────────────
220
+
221
+ def sum(self, dim=None, keepdim=False) -> 'Tensor':
222
+ from vectorphile.autograd.engine import SumBackward
223
+ result = Tensor(self._data.sum(axis=dim, keepdims=keepdim), device=self.device,
224
+ requires_grad=self.requires_grad)
225
+ if self.requires_grad:
226
+ result.grad_fn = SumBackward(self, dim, keepdim)
227
+ result._is_leaf = False
228
+ return result
229
+
230
+ def mean(self, dim=None, keepdim=False) -> 'Tensor':
231
+ from vectorphile.autograd.engine import MeanBackward
232
+ result = Tensor(self._data.mean(axis=dim, keepdims=keepdim), device=self.device,
233
+ requires_grad=self.requires_grad)
234
+ if self.requires_grad:
235
+ result.grad_fn = MeanBackward(self, dim, keepdim)
236
+ result._is_leaf = False
237
+ return result
238
+
239
+ def max(self, dim=None, keepdim=False):
240
+ if dim is None:
241
+ return Tensor(self._data.max(), device=self.device)
242
+ vals = self._data.max(axis=dim, keepdims=keepdim)
243
+ idxs = self._data.argmax(axis=dim)
244
+ return Tensor(vals, device=self.device), Tensor(idxs, device=self.device)
245
+
246
+ def min(self, dim=None, keepdim=False):
247
+ if dim is None:
248
+ return Tensor(self._data.min(), device=self.device)
249
+ vals = self._data.min(axis=dim, keepdims=keepdim)
250
+ idxs = self._data.argmin(axis=dim)
251
+ return Tensor(vals, device=self.device), Tensor(idxs, device=self.device)
252
+
253
+ def argmax(self, dim=None) -> 'Tensor':
254
+ return Tensor(self._data.argmax(axis=dim), device=self.device)
255
+
256
+ def argmin(self, dim=None) -> 'Tensor':
257
+ return Tensor(self._data.argmin(axis=dim), device=self.device)
258
+
259
+ def std(self, dim=None, keepdim=False, unbiased=True) -> 'Tensor':
260
+ ddof = 1 if unbiased else 0
261
+ return Tensor(self._data.std(axis=dim, keepdims=keepdim, ddof=ddof), device=self.device)
262
+
263
+ def var(self, dim=None, keepdim=False, unbiased=True) -> 'Tensor':
264
+ ddof = 1 if unbiased else 0
265
+ return Tensor(self._data.var(axis=dim, keepdims=keepdim, ddof=ddof), device=self.device)
266
+
267
+ def norm(self, p=2, dim=None, keepdim=False) -> 'Tensor':
268
+ return Tensor(np.linalg.norm(self._data, ord=p, axis=dim, keepdims=keepdim), device=self.device)
269
+
270
+ def prod(self, dim=None, keepdim=False) -> 'Tensor':
271
+ return Tensor(self._data.prod(axis=dim, keepdims=keepdim), device=self.device)
272
+
273
+ def cumsum(self, dim) -> 'Tensor':
274
+ return Tensor(self._data.cumsum(axis=dim), device=self.device)
275
+
276
+ def cumprod(self, dim) -> 'Tensor':
277
+ return Tensor(self._data.cumprod(axis=dim), device=self.device)
278
+
279
+ # ── element-wise math ───────────────────────────────────────────────────
280
+
281
+ def exp(self) -> 'Tensor':
282
+ return Tensor(np.exp(self._data), device=self.device, requires_grad=self.requires_grad)
283
+
284
+ def log(self) -> 'Tensor':
285
+ return Tensor(np.log(self._data), device=self.device, requires_grad=self.requires_grad)
286
+
287
+ def log2(self) -> 'Tensor':
288
+ return Tensor(np.log2(self._data), device=self.device)
289
+
290
+ def log10(self) -> 'Tensor':
291
+ return Tensor(np.log10(self._data), device=self.device)
292
+
293
+ def sqrt(self) -> 'Tensor':
294
+ return Tensor(np.sqrt(self._data), device=self.device, requires_grad=self.requires_grad)
295
+
296
+ def abs(self) -> 'Tensor':
297
+ return Tensor(np.abs(self._data), device=self.device, requires_grad=self.requires_grad)
298
+
299
+ def sign(self) -> 'Tensor':
300
+ return Tensor(np.sign(self._data), device=self.device)
301
+
302
+ def sin(self) -> 'Tensor':
303
+ return Tensor(np.sin(self._data), device=self.device)
304
+
305
+ def cos(self) -> 'Tensor':
306
+ return Tensor(np.cos(self._data), device=self.device)
307
+
308
+ def tan(self) -> 'Tensor':
309
+ return Tensor(np.tan(self._data), device=self.device)
310
+
311
+ def tanh(self) -> 'Tensor':
312
+ from vectorphile.autograd.engine import TanhBackward
313
+ out_data = np.tanh(self._data)
314
+ result = Tensor(out_data, device=self.device, requires_grad=self.requires_grad)
315
+ if self.requires_grad:
316
+ result.grad_fn = TanhBackward(self, result)
317
+ result._is_leaf = False
318
+ return result
319
+
320
+ def sigmoid(self) -> 'Tensor':
321
+ from vectorphile.autograd.engine import SigmoidBackward
322
+ out_data = 1.0 / (1.0 + np.exp(-self._data))
323
+ result = Tensor(out_data, device=self.device, requires_grad=self.requires_grad)
324
+ if self.requires_grad:
325
+ result.grad_fn = SigmoidBackward(self, result)
326
+ result._is_leaf = False
327
+ return result
328
+
329
+ def relu(self) -> 'Tensor':
330
+ from vectorphile.autograd.engine import ReLUBackward
331
+ result = Tensor(np.maximum(0, self._data), device=self.device, requires_grad=self.requires_grad)
332
+ if self.requires_grad:
333
+ result.grad_fn = ReLUBackward(self)
334
+ result._is_leaf = False
335
+ return result
336
+
337
+ def clamp(self, min=None, max=None) -> 'Tensor':
338
+ return Tensor(np.clip(self._data, min, max), device=self.device)
339
+
340
+ def clip(self, min=None, max=None) -> 'Tensor':
341
+ return self.clamp(min, max)
342
+
343
+ def floor(self) -> 'Tensor':
344
+ return Tensor(np.floor(self._data), device=self.device)
345
+
346
+ def ceil(self) -> 'Tensor':
347
+ return Tensor(np.ceil(self._data), device=self.device)
348
+
349
+ def round(self, decimals=0) -> 'Tensor':
350
+ return Tensor(np.round(self._data, decimals), device=self.device)
351
+
352
+ def pow(self, exponent) -> 'Tensor':
353
+ exp = exponent._data if isinstance(exponent, Tensor) else exponent
354
+ return Tensor(np.power(self._data, exp), device=self.device, requires_grad=self.requires_grad)
355
+
356
+ # ── matrix ops ──────────────────────────────────────────────────────────
357
+
358
+ def matmul(self, other: 'Tensor') -> 'Tensor':
359
+ return Tensor(np.matmul(self._data, other._data), device=self.device, requires_grad=self.requires_grad or other.requires_grad)
360
+
361
+ def mm(self, other: 'Tensor') -> 'Tensor':
362
+ return self.matmul(other)
363
+
364
+ def bmm(self, other: 'Tensor') -> 'Tensor':
365
+ return Tensor(np.matmul(self._data, other._data), device=self.device)
366
+
367
+ def dot(self, other: 'Tensor') -> 'Tensor':
368
+ return Tensor(np.dot(self._data, other._data), device=self.device)
369
+
370
+ # ── comparison ops ──────────────────────────────────────────────────────
371
+
372
+ def eq(self, other) -> 'Tensor':
373
+ o = other._data if isinstance(other, Tensor) else other
374
+ return Tensor(self._data == o, device=self.device)
375
+
376
+ def ne(self, other) -> 'Tensor':
377
+ o = other._data if isinstance(other, Tensor) else other
378
+ return Tensor(self._data != o, device=self.device)
379
+
380
+ def lt(self, other) -> 'Tensor':
381
+ o = other._data if isinstance(other, Tensor) else other
382
+ return Tensor(self._data < o, device=self.device)
383
+
384
+ def le(self, other) -> 'Tensor':
385
+ o = other._data if isinstance(other, Tensor) else other
386
+ return Tensor(self._data <= o, device=self.device)
387
+
388
+ def gt(self, other) -> 'Tensor':
389
+ o = other._data if isinstance(other, Tensor) else other
390
+ return Tensor(self._data > o, device=self.device)
391
+
392
+ def ge(self, other) -> 'Tensor':
393
+ o = other._data if isinstance(other, Tensor) else other
394
+ return Tensor(self._data >= o, device=self.device)
395
+
396
+ def all(self, dim=None) -> 'Tensor':
397
+ return Tensor(self._data.all(axis=dim), device=self.device)
398
+
399
+ def any(self, dim=None) -> 'Tensor':
400
+ return Tensor(self._data.any(axis=dim), device=self.device)
401
+
402
+ # ── operators ───────────────────────────────────────────────────────────
403
+
404
+ def _coerce(self, other):
405
+ return other._data if isinstance(other, Tensor) else other
406
+
407
+ def __add__(self, other) -> 'Tensor':
408
+ from vectorphile.autograd.engine import AddBackward
409
+ result = Tensor(self._data + self._coerce(other), device=self.device,
410
+ requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
411
+ if result.requires_grad:
412
+ result.grad_fn = AddBackward(self, other)
413
+ result._is_leaf = False
414
+ return result
415
+
416
+ def __radd__(self, other) -> 'Tensor':
417
+ return self.__add__(other)
418
+
419
+ def __sub__(self, other) -> 'Tensor':
420
+ from vectorphile.autograd.engine import SubBackward
421
+ result = Tensor(self._data - self._coerce(other), device=self.device,
422
+ requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
423
+ if result.requires_grad:
424
+ result.grad_fn = SubBackward(self, other)
425
+ result._is_leaf = False
426
+ return result
427
+
428
+ def __rsub__(self, other) -> 'Tensor':
429
+ return Tensor(self._coerce(other) - self._data, device=self.device)
430
+
431
+ def __mul__(self, other) -> 'Tensor':
432
+ from vectorphile.autograd.engine import MulBackward
433
+ result = Tensor(self._data * self._coerce(other), device=self.device,
434
+ requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
435
+ if result.requires_grad:
436
+ result.grad_fn = MulBackward(self, other)
437
+ result._is_leaf = False
438
+ return result
439
+
440
+ def __rmul__(self, other) -> 'Tensor':
441
+ return self.__mul__(other)
442
+
443
+ def __truediv__(self, other) -> 'Tensor':
444
+ from vectorphile.autograd.engine import DivBackward
445
+ result = Tensor(self._data / self._coerce(other), device=self.device,
446
+ requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
447
+ if result.requires_grad:
448
+ result.grad_fn = DivBackward(self, other)
449
+ result._is_leaf = False
450
+ return result
451
+
452
+ def __rtruediv__(self, other) -> 'Tensor':
453
+ return Tensor(self._coerce(other) / self._data, device=self.device)
454
+
455
+ def __pow__(self, exponent) -> 'Tensor':
456
+ exp = self._coerce(exponent)
457
+ return Tensor(np.power(self._data, exp), device=self.device, requires_grad=self.requires_grad)
458
+
459
+ def __neg__(self) -> 'Tensor':
460
+ return Tensor(-self._data, device=self.device, requires_grad=self.requires_grad)
461
+
462
+ def __abs__(self) -> 'Tensor':
463
+ return self.abs()
464
+
465
+ def __matmul__(self, other) -> 'Tensor':
466
+ from vectorphile.autograd.engine import MatMulBackward
467
+ result = Tensor(np.matmul(self._data, other._data), device=self.device,
468
+ requires_grad=self.requires_grad or other.requires_grad)
469
+ if result.requires_grad:
470
+ result.grad_fn = MatMulBackward(self, other)
471
+ result._is_leaf = False
472
+ return result
473
+
474
+ def __eq__(self, other) -> 'Tensor':
475
+ return self.eq(other)
476
+
477
+ def __ne__(self, other) -> 'Tensor':
478
+ return self.ne(other)
479
+
480
+ def __lt__(self, other) -> 'Tensor':
481
+ return self.lt(other)
482
+
483
+ def __le__(self, other) -> 'Tensor':
484
+ return self.le(other)
485
+
486
+ def __gt__(self, other) -> 'Tensor':
487
+ return self.gt(other)
488
+
489
+ def __ge__(self, other) -> 'Tensor':
490
+ return self.ge(other)
491
+
492
+ def __getitem__(self, idx):
493
+ if isinstance(idx, Tensor):
494
+ idx = idx._data
495
+ return Tensor(self._data[idx], device=self.device)
496
+
497
+ def __setitem__(self, idx, value):
498
+ if isinstance(idx, Tensor):
499
+ idx = idx._data
500
+ self._data[idx] = value._data if isinstance(value, Tensor) else value
501
+
502
+ def __len__(self) -> int:
503
+ return len(self._data)
504
+
505
+ def __iter__(self):
506
+ for i in range(len(self)):
507
+ yield self[i]
508
+
509
+ def __repr__(self) -> str:
510
+ return f"tensor({self._data}, dtype={self.dtype}, device='{self.device}')"
511
+
512
+ def __str__(self) -> str:
513
+ return self.__repr__()
514
+
515
+ def __hash__(self):
516
+ return id(self)
517
+
518
+ # ── autograd ────────────────────────────────────────────────────────────
519
+
520
+ def backward(self, gradient=None):
521
+ from vectorphile.autograd.engine import backward
522
+ if gradient is None:
523
+ gradient = Tensor(np.ones_like(self._data), device=self.device)
524
+ backward(self, gradient)
525
+
526
+ def zero_grad(self):
527
+ self.grad = None
528
+
529
+ # ── utility ─────────────────────────────────────────────────────────────
530
+
531
+ def cpu(self) -> 'Tensor':
532
+ return Tensor(self._data, device='cpu', requires_grad=self.requires_grad)
533
+
534
+ def cuda(self) -> 'Tensor':
535
+ # Falls back to CPU gracefully (Phase 2 will add real CUDA)
536
+ return Tensor(self._data, device='cuda', requires_grad=self.requires_grad)
537
+
538
+ def is_cuda(self) -> bool:
539
+ return self.device == 'cuda'
540
+
541
+ def is_leaf(self) -> bool:
542
+ return self._is_leaf
543
+
544
+ def fill_(self, value) -> 'Tensor':
545
+ self._data.fill(value)
546
+ return self
547
+
548
+ def copy_(self, src: 'Tensor') -> 'Tensor':
549
+ self._data[:] = src._data
550
+ return self
551
+
552
+ def masked_fill(self, mask: 'Tensor', value: float) -> 'Tensor':
553
+ result = self._data.copy()
554
+ result[mask._data] = value
555
+ return Tensor(result, device=self.device)
556
+
557
+ def where(self, condition: 'Tensor', other: 'Tensor') -> 'Tensor':
558
+ return Tensor(np.where(condition._data, self._data, other._data), device=self.device)
559
+
560
+
561
+ # ── functional tensor creation ───────────────────────────────────────────────
562
+
563
+ def tensor(data, dtype=None, device='cpu', requires_grad=False) -> Tensor:
564
+ return Tensor(data, dtype=dtype, device=device, requires_grad=requires_grad)
565
+
566
+ def zeros(*shape, dtype='float32', device='cpu', requires_grad=False) -> Tensor:
567
+ return Tensor.zeros(*shape, dtype=dtype, device=device, requires_grad=requires_grad)
568
+
569
+ def ones(*shape, dtype='float32', device='cpu', requires_grad=False) -> Tensor:
570
+ return Tensor.ones(*shape, dtype=dtype, device=device, requires_grad=requires_grad)
571
+
572
+ def eye(n, m=None, dtype='float32', device='cpu') -> Tensor:
573
+ return Tensor.eye(n, m, dtype=dtype, device=device)
574
+
575
+ def arange(start, stop=None, step=1, dtype='float32', device='cpu') -> Tensor:
576
+ return Tensor.arange(start, stop, step, dtype=dtype, device=device)
577
+
578
+ def linspace(start, stop, num, dtype='float32', device='cpu') -> Tensor:
579
+ return Tensor.linspace(start, stop, num, dtype=dtype, device=device)
580
+
581
+ def full(shape, fill_value, dtype='float32', device='cpu') -> Tensor:
582
+ return Tensor.full(shape, fill_value, dtype=dtype, device=device)
583
+
584
+ def empty(*shape, dtype='float32', device='cpu') -> Tensor:
585
+ return Tensor.empty(*shape, dtype=dtype, device=device)
586
+
587
+ def rand(*shape, device='cpu', requires_grad=False) -> Tensor:
588
+ return Tensor.rand(*shape, device=device, requires_grad=requires_grad)
589
+
590
+ def randn(*shape, device='cpu', requires_grad=False) -> Tensor:
591
+ return Tensor.randn(*shape, device=device, requires_grad=requires_grad)
592
+
593
+ def randint(low, high, shape, dtype='int64', device='cpu') -> Tensor:
594
+ return Tensor.randint(low, high, shape, dtype=dtype, device=device)
595
+
596
+ def from_numpy(arr: np.ndarray) -> Tensor:
597
+ return Tensor.from_numpy(arr)
598
+
599
+ def zeros_like(t: Tensor, requires_grad=False) -> Tensor:
600
+ return Tensor(np.zeros_like(t._data), device=t.device, requires_grad=requires_grad)
601
+
602
+ def ones_like(t: Tensor, requires_grad=False) -> Tensor:
603
+ return Tensor(np.ones_like(t._data), device=t.device, requires_grad=requires_grad)
604
+
605
+ def empty_like(t: Tensor) -> Tensor:
606
+ return Tensor(np.empty_like(t._data), device=t.device)
607
+
608
+ def cat(tensors, dim=0) -> Tensor:
609
+ arrays = [t._data for t in tensors]
610
+ return Tensor(np.concatenate(arrays, axis=dim), device=tensors[0].device)
611
+
612
+ def stack(tensors, dim=0) -> Tensor:
613
+ arrays = [t._data for t in tensors]
614
+ return Tensor(np.stack(arrays, axis=dim), device=tensors[0].device)
615
+
616
+ def split(tensor, split_size, dim=0):
617
+ return tensor.split(split_size, dim)
618
+
619
+ def chunk(tensor, chunks, dim=0):
620
+ return tensor.chunk(chunks, dim)
621
+
622
+ def squeeze(tensor, dim=None) -> Tensor:
623
+ return tensor.squeeze(dim)
624
+
625
+ def unsqueeze(tensor, dim) -> Tensor:
626
+ return tensor.unsqueeze(dim)
627
+
628
+ def flatten(tensor, start_dim=0, end_dim=-1) -> Tensor:
629
+ return tensor.flatten(start_dim, end_dim)
630
+
631
+ def transpose(tensor, dim0, dim1) -> Tensor:
632
+ return tensor.transpose(dim0, dim1)
633
+
634
+ def permute(tensor, *dims) -> Tensor:
635
+ return tensor.permute(*dims)
636
+
637
+ def matmul(a: Tensor, b: Tensor) -> Tensor:
638
+ return a.matmul(b)
639
+
640
+ def dot(a: Tensor, b: Tensor) -> Tensor:
641
+ return a.dot(b)
642
+
643
+ def where(condition: Tensor, x: Tensor, y: Tensor) -> Tensor:
644
+ return Tensor(np.where(condition._data, x._data, y._data), device=x.device)
645
+
646
+ def max(input, dim=None, keepdim=False):
647
+ return input.max(dim, keepdim)
648
+
649
+ def min(input, dim=None, keepdim=False):
650
+ return input.min(dim, keepdim)
651
+
652
+ def sum(input, dim=None, keepdim=False) -> Tensor:
653
+ return input.sum(dim, keepdim)
654
+
655
+ def mean(input, dim=None, keepdim=False) -> Tensor:
656
+ return input.mean(dim, keepdim)
657
+
658
+ def exp(input: Tensor) -> Tensor:
659
+ return input.exp()
660
+
661
+ def log(input: Tensor) -> Tensor:
662
+ return input.log()
663
+
664
+ def sqrt(input: Tensor) -> Tensor:
665
+ return input.sqrt()
666
+
667
+ def abs(input: Tensor) -> Tensor:
668
+ return input.abs()
669
+
670
+ def clamp(input: Tensor, min=None, max=None) -> Tensor:
671
+ return input.clamp(min, max)
672
+
673
+ def softmax(input: Tensor, dim=-1) -> Tensor:
674
+ data = input._data
675
+ shifted = data - data.max(axis=dim, keepdims=True)
676
+ e = np.exp(shifted)
677
+ return Tensor(e / e.sum(axis=dim, keepdims=True), device=input.device)
678
+
679
+ def log_softmax(input: Tensor, dim=-1) -> Tensor:
680
+ data = input._data
681
+ shifted = data - data.max(axis=dim, keepdims=True)
682
+ log_sum_exp = np.log(np.exp(shifted).sum(axis=dim, keepdims=True))
683
+ return Tensor(shifted - log_sum_exp, device=input.device)
684
+
685
+ def sigmoid(input: Tensor) -> Tensor:
686
+ return input.sigmoid()
687
+
688
+ def relu(input: Tensor) -> Tensor:
689
+ return input.relu()
690
+
691
+ def tanh(input: Tensor) -> Tensor:
692
+ return input.tanh()
693
+
694
+ def einsum(equation: str, *operands) -> Tensor:
695
+ arrays = [op._data for op in operands]
696
+ return Tensor(np.einsum(equation, *arrays), device=operands[0].device)