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,71 @@
1
+ """Adam and AdamW optimizers."""
2
+ import numpy as np
3
+ from vectorphile.optim.optimizer import Optimizer
4
+
5
+
6
+ class Adam(Optimizer):
7
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.0, amsgrad=False):
8
+ defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay, amsgrad=amsgrad)
9
+ super().__init__(params, defaults)
10
+ self.state = {}
11
+
12
+ def step(self):
13
+ for group in self.param_groups:
14
+ lr = group['lr']
15
+ b1, b2 = group['betas']
16
+ eps = group['eps']
17
+ wd = group['weight_decay']
18
+ amsgrad = group['amsgrad']
19
+ for p in group['params']:
20
+ if p.grad is None:
21
+ continue
22
+ g = p.grad._data
23
+ pid = id(p)
24
+ if pid not in self.state:
25
+ self.state[pid] = {'step': 0, 'm': np.zeros_like(p._data), 'v': np.zeros_like(p._data)}
26
+ if amsgrad:
27
+ self.state[pid]['v_max'] = np.zeros_like(p._data)
28
+ s = self.state[pid]
29
+ s['step'] += 1
30
+ s['m'] = b1 * s['m'] + (1 - b1) * g
31
+ s['v'] = b2 * s['v'] + (1 - b2) * g**2
32
+ m_hat = s['m'] / (1 - b1**s['step'])
33
+ v_hat = s['v'] / (1 - b2**s['step'])
34
+ if amsgrad:
35
+ s['v_max'] = np.maximum(s['v_max'], v_hat)
36
+ denom = np.sqrt(s['v_max']) + eps
37
+ else:
38
+ denom = np.sqrt(v_hat) + eps
39
+ if wd != 0:
40
+ p._data -= lr * wd * p._data
41
+ p._data -= lr * m_hat / denom
42
+
43
+
44
+ class AdamW(Optimizer):
45
+ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01):
46
+ defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay)
47
+ super().__init__(params, defaults)
48
+ self.state = {}
49
+
50
+ def step(self):
51
+ for group in self.param_groups:
52
+ lr = group['lr']
53
+ b1, b2 = group['betas']
54
+ eps = group['eps']
55
+ wd = group['weight_decay']
56
+ for p in group['params']:
57
+ if p.grad is None:
58
+ continue
59
+ # Decoupled weight decay
60
+ p._data *= (1 - lr * wd)
61
+ g = p.grad._data
62
+ pid = id(p)
63
+ if pid not in self.state:
64
+ self.state[pid] = {'step': 0, 'm': np.zeros_like(p._data), 'v': np.zeros_like(p._data)}
65
+ s = self.state[pid]
66
+ s['step'] += 1
67
+ s['m'] = b1 * s['m'] + (1 - b1) * g
68
+ s['v'] = b2 * s['v'] + (1 - b2) * g**2
69
+ m_hat = s['m'] / (1 - b1**s['step'])
70
+ v_hat = s['v'] / (1 - b2**s['step'])
71
+ p._data -= lr * m_hat / (np.sqrt(v_hat) + eps)
@@ -0,0 +1,110 @@
1
+ """Learning rate schedulers."""
2
+ import math
3
+
4
+
5
+ class _LRScheduler:
6
+ def __init__(self, optimizer, last_epoch=-1):
7
+ self.optimizer = optimizer
8
+ self.last_epoch = last_epoch
9
+ self.base_lrs = [g['lr'] for g in optimizer.param_groups]
10
+ self.step()
11
+
12
+ def get_lr(self):
13
+ raise NotImplementedError
14
+
15
+ def step(self, epoch=None):
16
+ if epoch is None:
17
+ self.last_epoch += 1
18
+ else:
19
+ self.last_epoch = epoch
20
+ lrs = self.get_lr()
21
+ for g, lr in zip(self.optimizer.param_groups, lrs):
22
+ g['lr'] = lr
23
+
24
+
25
+ class StepLR(_LRScheduler):
26
+ def __init__(self, optimizer, step_size, gamma=0.1, last_epoch=-1):
27
+ self.step_size = step_size
28
+ self.gamma = gamma
29
+ super().__init__(optimizer, last_epoch)
30
+
31
+ def get_lr(self):
32
+ return [base * self.gamma ** (self.last_epoch // self.step_size) for base in self.base_lrs]
33
+
34
+
35
+ class ExponentialLR(_LRScheduler):
36
+ def __init__(self, optimizer, gamma, last_epoch=-1):
37
+ self.gamma = gamma
38
+ super().__init__(optimizer, last_epoch)
39
+
40
+ def get_lr(self):
41
+ return [base * self.gamma ** self.last_epoch for base in self.base_lrs]
42
+
43
+
44
+ class CosineAnnealingLR(_LRScheduler):
45
+ def __init__(self, optimizer, T_max, eta_min=0.0, last_epoch=-1):
46
+ self.T_max = T_max
47
+ self.eta_min = eta_min
48
+ super().__init__(optimizer, last_epoch)
49
+
50
+ def get_lr(self):
51
+ t = self.last_epoch
52
+ return [
53
+ self.eta_min + (base - self.eta_min) * (1 + math.cos(math.pi * t / self.T_max)) / 2
54
+ for base in self.base_lrs
55
+ ]
56
+
57
+
58
+ class LinearLR(_LRScheduler):
59
+ def __init__(self, optimizer, start_factor=1.0/3, end_factor=1.0, total_iters=5, last_epoch=-1):
60
+ self.start_factor = start_factor
61
+ self.end_factor = end_factor
62
+ self.total_iters = total_iters
63
+ super().__init__(optimizer, last_epoch)
64
+
65
+ def get_lr(self):
66
+ t = min(self.last_epoch, self.total_iters)
67
+ alpha = t / self.total_iters
68
+ factor = self.start_factor + alpha * (self.end_factor - self.start_factor)
69
+ return [base * factor for base in self.base_lrs]
70
+
71
+
72
+ class ReduceLROnPlateau:
73
+ def __init__(self, optimizer, mode='min', factor=0.1, patience=10, threshold=1e-4,
74
+ threshold_mode='rel', cooldown=0, min_lr=0.0, eps=1e-8):
75
+ self.optimizer = optimizer
76
+ self.mode = mode
77
+ self.factor = factor
78
+ self.patience = patience
79
+ self.threshold = threshold
80
+ self.threshold_mode = threshold_mode
81
+ self.cooldown = cooldown
82
+ self.min_lr = min_lr
83
+ self.eps = eps
84
+ self.best = None
85
+ self.num_bad_epochs = 0
86
+ self.cooldown_counter = 0
87
+
88
+ def step(self, metrics):
89
+ current = float(metrics)
90
+ if self.best is None:
91
+ self.best = current
92
+ return
93
+ if self.mode == 'min':
94
+ improved = current < self.best - self.threshold
95
+ else:
96
+ improved = current > self.best + self.threshold
97
+ if improved:
98
+ self.best = current
99
+ self.num_bad_epochs = 0
100
+ else:
101
+ self.num_bad_epochs += 1
102
+ if self.cooldown_counter > 0:
103
+ self.cooldown_counter -= 1
104
+ return
105
+ if self.num_bad_epochs >= self.patience:
106
+ for g in self.optimizer.param_groups:
107
+ new_lr = max(g['lr'] * self.factor, self.min_lr)
108
+ g['lr'] = new_lr
109
+ self.cooldown_counter = self.cooldown
110
+ self.num_bad_epochs = 0
@@ -0,0 +1,30 @@
1
+ """Base Optimizer class."""
2
+ from typing import List, Dict, Any
3
+
4
+
5
+ class Optimizer:
6
+ def __init__(self, params, defaults: Dict[str, Any]):
7
+ if isinstance(params, dict):
8
+ params = list(params.values())
9
+ self.param_groups: List[Dict] = []
10
+ self.defaults = defaults
11
+ self.add_param_group({'params': list(params)})
12
+
13
+ def add_param_group(self, param_group: Dict):
14
+ group = {**self.defaults, **param_group}
15
+ self.param_groups.append(group)
16
+
17
+ def zero_grad(self):
18
+ for group in self.param_groups:
19
+ for p in group['params']:
20
+ if p.grad is not None:
21
+ p.grad = None
22
+
23
+ def step(self):
24
+ raise NotImplementedError
25
+
26
+ def state_dict(self) -> Dict:
27
+ return {'state': self.state, 'param_groups': self.param_groups}
28
+
29
+ def load_state_dict(self, state_dict: Dict):
30
+ self.state = state_dict['state']
@@ -0,0 +1,34 @@
1
+ """RMSprop and Adagrad optimizers."""
2
+ import numpy as np
3
+ from vectorphile.optim.optimizer import Optimizer
4
+
5
+
6
+ class RMSprop(Optimizer):
7
+ def __init__(self, params, lr=0.01, alpha=0.99, eps=1e-8, weight_decay=0.0, momentum=0.0):
8
+ defaults = dict(lr=lr, alpha=alpha, eps=eps, weight_decay=weight_decay, momentum=momentum)
9
+ super().__init__(params, defaults)
10
+ self.state = {}
11
+
12
+ def step(self):
13
+ for group in self.param_groups:
14
+ lr = group['lr']
15
+ alpha = group['alpha']
16
+ eps = group['eps']
17
+ wd = group['weight_decay']
18
+ mu = group['momentum']
19
+ for p in group['params']:
20
+ if p.grad is None:
21
+ continue
22
+ g = p.grad._data.copy()
23
+ if wd != 0:
24
+ g += wd * p._data
25
+ pid = id(p)
26
+ if pid not in self.state:
27
+ self.state[pid] = {'sq': np.zeros_like(p._data), 'buf': np.zeros_like(p._data)}
28
+ s = self.state[pid]
29
+ s['sq'] = alpha * s['sq'] + (1 - alpha) * g**2
30
+ step = g / (np.sqrt(s['sq']) + eps)
31
+ if mu != 0:
32
+ s['buf'] = mu * s['buf'] + step
33
+ step = s['buf']
34
+ p._data -= lr * step
@@ -0,0 +1,38 @@
1
+ """SGD optimizer with momentum and weight decay."""
2
+ import numpy as np
3
+ from vectorphile.optim.optimizer import Optimizer
4
+ from vectorphile.core.tensor import Tensor
5
+
6
+
7
+ class SGD(Optimizer):
8
+ def __init__(self, params, lr=0.01, momentum=0.0, weight_decay=0.0, nesterov=False, dampening=0.0):
9
+ defaults = dict(lr=lr, momentum=momentum, weight_decay=weight_decay,
10
+ nesterov=nesterov, dampening=dampening)
11
+ super().__init__(params, defaults)
12
+ self.state = {}
13
+
14
+ def step(self):
15
+ for group in self.param_groups:
16
+ lr = group['lr']
17
+ momentum = group['momentum']
18
+ weight_decay = group['weight_decay']
19
+ dampening = group['dampening']
20
+ nesterov = group['nesterov']
21
+ for p in group['params']:
22
+ if p.grad is None:
23
+ continue
24
+ g = p.grad._data.copy()
25
+ if weight_decay != 0:
26
+ g += weight_decay * p._data
27
+ pid = id(p)
28
+ if momentum != 0:
29
+ if pid not in self.state:
30
+ self.state[pid] = {'momentum_buffer': np.zeros_like(p._data)}
31
+ buf = self.state[pid]['momentum_buffer']
32
+ buf *= momentum
33
+ buf += (1 - dampening) * g
34
+ if nesterov:
35
+ g = g + momentum * buf
36
+ else:
37
+ g = buf
38
+ p._data -= lr * g
@@ -0,0 +1,97 @@
1
+ """Numerical optimization routines."""
2
+ import numpy as np
3
+ from typing import Callable, Optional, Tuple
4
+ from vectorphile.core.tensor import Tensor
5
+
6
+
7
+ def minimize(fun: Callable, x0, method='bfgs', jac=None, tol=1e-6, options=None):
8
+ """Minimize a scalar function. Wraps scipy.optimize.minimize when available."""
9
+ try:
10
+ from scipy.optimize import minimize as sp_minimize
11
+ x0_arr = x0._data.flatten() if isinstance(x0, Tensor) else np.array(x0).flatten()
12
+ def fn(x):
13
+ t = Tensor(x.reshape(x0_arr.shape) if isinstance(x0, Tensor) else x, requires_grad=True)
14
+ result = fun(t)
15
+ return float(result._data) if isinstance(result, Tensor) else float(result)
16
+ result = sp_minimize(fn, x0_arr, method=method.upper(), jac=jac, tol=tol, options=options)
17
+ result.x = Tensor(result.x.reshape(x0._data.shape if isinstance(x0, Tensor) else x0.shape))
18
+ return result
19
+ except ImportError:
20
+ return _gradient_descent(fun, x0, tol=tol, options=options or {})
21
+
22
+
23
+ def _gradient_descent(fun: Callable, x0, lr=0.01, tol=1e-6, max_iter=1000, options=None):
24
+ if options:
25
+ lr = options.get('lr', lr)
26
+ max_iter = options.get('maxiter', max_iter)
27
+ if isinstance(x0, Tensor):
28
+ x = Tensor(x0._data.copy(), requires_grad=True)
29
+ else:
30
+ x = Tensor(np.array(x0, dtype=float), requires_grad=True)
31
+ for i in range(max_iter):
32
+ if x.grad is not None:
33
+ x.grad = None
34
+ result = fun(x)
35
+ result.backward()
36
+ if x.grad is None:
37
+ break
38
+ grad_norm = float(np.linalg.norm(x.grad._data))
39
+ if grad_norm < tol:
40
+ break
41
+ x._data -= lr * x.grad._data
42
+
43
+ class Result:
44
+ pass
45
+ r = Result()
46
+ r.x = x
47
+ r.fun = float(fun(x)._data)
48
+ r.success = True
49
+ return r
50
+
51
+
52
+ def root(fun: Callable, x0, method='hybr', tol=1e-6):
53
+ """Find root of a function."""
54
+ try:
55
+ from scipy.optimize import root as sp_root
56
+ x0_arr = x0._data.flatten() if isinstance(x0, Tensor) else np.array(x0).flatten()
57
+ def fn(x):
58
+ t = Tensor(x)
59
+ result = fun(t)
60
+ return result._data.flatten() if isinstance(result, Tensor) else np.array(result).flatten()
61
+ return sp_root(fn, x0_arr, method=method, tol=tol)
62
+ except ImportError:
63
+ raise ImportError("root requires scipy: pip install scipy")
64
+
65
+
66
+ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None, method='highs'):
67
+ """Linear programming."""
68
+ try:
69
+ from scipy.optimize import linprog as sp_linprog
70
+ c_arr = c._data if isinstance(c, Tensor) else np.array(c)
71
+ return sp_linprog(c_arr, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method=method)
72
+ except ImportError:
73
+ raise ImportError("linprog requires scipy: pip install scipy")
74
+
75
+
76
+ def curve_fit(f: Callable, xdata, ydata, p0=None):
77
+ """Curve fitting."""
78
+ try:
79
+ from scipy.optimize import curve_fit as sp_curve_fit
80
+ xd = xdata._data if isinstance(xdata, Tensor) else np.array(xdata)
81
+ yd = ydata._data if isinstance(ydata, Tensor) else np.array(ydata)
82
+ popt, pcov = sp_curve_fit(f, xd, yd, p0=p0)
83
+ return Tensor(popt.astype(np.float32)), Tensor(pcov.astype(np.float32))
84
+ except ImportError:
85
+ raise ImportError("curve_fit requires scipy: pip install scipy")
86
+
87
+
88
+ def differential_evolution(fun: Callable, bounds, maxiter=1000, tol=0.01, seed=None):
89
+ """Global optimization via differential evolution."""
90
+ try:
91
+ from scipy.optimize import differential_evolution as sp_de
92
+ def fn(x):
93
+ result = fun(Tensor(x))
94
+ return float(result._data) if isinstance(result, Tensor) else float(result)
95
+ return sp_de(fn, bounds, maxiter=maxiter, tol=tol, seed=seed)
96
+ except ImportError:
97
+ raise ImportError("differential_evolution requires scipy: pip install scipy")
@@ -0,0 +1,64 @@
1
+ """Random number generation."""
2
+ import numpy as np
3
+ from vectorphile.core.tensor import Tensor
4
+
5
+
6
+ def seed(s: int):
7
+ np.random.seed(s)
8
+
9
+ def rand(*shape, device='cpu') -> Tensor:
10
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
11
+ shape = shape[0]
12
+ return Tensor(np.random.rand(*shape).astype(np.float32), device=device)
13
+
14
+ def randn(*shape, device='cpu') -> Tensor:
15
+ if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
16
+ shape = shape[0]
17
+ return Tensor(np.random.randn(*shape).astype(np.float32), device=device)
18
+
19
+ def randint(low, high, shape, device='cpu') -> Tensor:
20
+ return Tensor(np.random.randint(low, high, shape).astype(np.int64), device=device)
21
+
22
+ def choice(a, size=None, replace=True, p=None) -> Tensor:
23
+ if isinstance(a, Tensor):
24
+ a = a._data
25
+ return Tensor(np.random.choice(a, size=size, replace=replace, p=p))
26
+
27
+ def shuffle(x: Tensor) -> Tensor:
28
+ data = x._data.copy()
29
+ np.random.shuffle(data)
30
+ return Tensor(data)
31
+
32
+ def normal(loc=0.0, scale=1.0, size=None, device='cpu') -> Tensor:
33
+ return Tensor(np.random.normal(loc, scale, size).astype(np.float32), device=device)
34
+
35
+ def uniform(low=0.0, high=1.0, size=None, device='cpu') -> Tensor:
36
+ return Tensor(np.random.uniform(low, high, size).astype(np.float32), device=device)
37
+
38
+ def binomial(n, p, size=None) -> Tensor:
39
+ return Tensor(np.random.binomial(n, p, size).astype(np.float32))
40
+
41
+ def poisson(lam=1.0, size=None) -> Tensor:
42
+ return Tensor(np.random.poisson(lam, size).astype(np.float32))
43
+
44
+ def exponential(scale=1.0, size=None) -> Tensor:
45
+ return Tensor(np.random.exponential(scale, size).astype(np.float32))
46
+
47
+ def multinomial(input: Tensor, num_samples: int, replacement=False) -> Tensor:
48
+ probs = input._data
49
+ probs = probs / probs.sum(axis=-1, keepdims=True)
50
+ if probs.ndim == 1:
51
+ return Tensor(np.random.choice(len(probs), size=num_samples, replace=replacement, p=probs).astype(np.int64))
52
+ result = np.stack([
53
+ np.random.choice(probs.shape[-1], size=num_samples, replace=replacement, p=row)
54
+ for row in probs.reshape(-1, probs.shape[-1])
55
+ ])
56
+ return Tensor(result.reshape(*probs.shape[:-1], num_samples).astype(np.int64))
57
+
58
+ def permutation(x) -> Tensor:
59
+ if isinstance(x, int):
60
+ return Tensor(np.random.permutation(x).astype(np.int64))
61
+ return Tensor(np.random.permutation(x._data if isinstance(x, Tensor) else x))
62
+
63
+ def bernoulli(p: Tensor) -> Tensor:
64
+ return Tensor((np.random.rand(*p._data.shape) < p._data).astype(np.float32))
@@ -0,0 +1,67 @@
1
+ """Digital signal processing."""
2
+ import numpy as np
3
+ from vectorphile.core.tensor import Tensor
4
+
5
+
6
+ def fft(x: Tensor) -> Tensor:
7
+ return Tensor(np.fft.fft(x._data))
8
+
9
+ def ifft(x: Tensor) -> Tensor:
10
+ return Tensor(np.fft.ifft(x._data))
11
+
12
+ def fftfreq(n: int, d: float = 1.0) -> Tensor:
13
+ return Tensor(np.fft.fftfreq(n, d).astype(np.float32))
14
+
15
+ def convolve(x: Tensor, h: Tensor, mode='full') -> Tensor:
16
+ return Tensor(np.convolve(x._data.flatten(), h._data.flatten(), mode=mode).astype(np.float32))
17
+
18
+ def correlate(x: Tensor, y: Tensor, mode='full') -> Tensor:
19
+ return Tensor(np.correlate(x._data.flatten(), y._data.flatten(), mode=mode).astype(np.float32))
20
+
21
+ def stft(x: Tensor, n_fft=512, hop_length=None, win_length=None, window='hann') -> Tensor:
22
+ signal = x._data.flatten()
23
+ if hop_length is None:
24
+ hop_length = n_fft // 4
25
+ if win_length is None:
26
+ win_length = n_fft
27
+ if window == 'hann':
28
+ win = np.hanning(win_length)
29
+ else:
30
+ win = np.ones(win_length)
31
+ n = len(signal)
32
+ frames = []
33
+ for i in range(0, n - win_length + 1, hop_length):
34
+ frame = signal[i:i+win_length] * win
35
+ frames.append(np.fft.rfft(frame, n=n_fft))
36
+ return Tensor(np.array(frames))
37
+
38
+ def spectrogram(x: Tensor, n_fft=512, hop_length=None, win_length=None) -> Tensor:
39
+ s = stft(x, n_fft, hop_length, win_length)
40
+ return Tensor(np.abs(s._data)**2)
41
+
42
+ def butter(N: int, Wn, btype='low', output='ba'):
43
+ try:
44
+ from scipy.signal import butter as sp_butter
45
+ return sp_butter(N, Wn, btype=btype, output=output)
46
+ except ImportError:
47
+ raise ImportError("butter requires scipy: pip install scipy")
48
+
49
+ def filtfilt(b, a, x: Tensor) -> Tensor:
50
+ try:
51
+ from scipy.signal import filtfilt as sp_filtfilt
52
+ return Tensor(sp_filtfilt(b, a, x._data).astype(np.float32))
53
+ except ImportError:
54
+ raise ImportError("filtfilt requires scipy: pip install scipy")
55
+
56
+ def hilbert(x: Tensor) -> Tensor:
57
+ try:
58
+ from scipy.signal import hilbert as sp_hilbert
59
+ return Tensor(np.abs(sp_hilbert(x._data)).astype(np.float32))
60
+ except ImportError:
61
+ return x
62
+
63
+ def resample(x: Tensor, num: int) -> Tensor:
64
+ signal = x._data.flatten()
65
+ n = len(signal)
66
+ indices = np.linspace(0, n - 1, num)
67
+ return Tensor(np.interp(indices, np.arange(n), signal).astype(np.float32))
@@ -0,0 +1,129 @@
1
+ """Sparse matrix support."""
2
+ import numpy as np
3
+ from vectorphile.core.tensor import Tensor
4
+
5
+
6
+ class csr_matrix:
7
+ """Compressed Sparse Row matrix."""
8
+ def __init__(self, data, shape=None):
9
+ if isinstance(data, np.ndarray):
10
+ self._from_dense(data)
11
+ elif isinstance(data, Tensor):
12
+ self._from_dense(data._data)
13
+ elif isinstance(data, tuple) and len(data) == 3:
14
+ (vals, (row, col)), shape = data, shape
15
+ self.data = np.array(vals)
16
+ self.indices = np.array(col, dtype=int)
17
+ self.indptr = np.zeros(shape[0] + 1, dtype=int)
18
+ for r in row:
19
+ self.indptr[r + 1:] += 1
20
+ self.shape = shape
21
+ else:
22
+ raise ValueError("Unsupported input format")
23
+
24
+ def _from_dense(self, arr):
25
+ self.shape = arr.shape
26
+ rows, cols = np.nonzero(arr)
27
+ self.data = arr[rows, cols]
28
+ self.indices = cols
29
+ self.indptr = np.zeros(arr.shape[0] + 1, dtype=int)
30
+ for r in rows:
31
+ self.indptr[r + 1] += 1
32
+ self.indptr = np.cumsum(self.indptr)
33
+
34
+ def toarray(self) -> np.ndarray:
35
+ result = np.zeros(self.shape)
36
+ for i in range(self.shape[0]):
37
+ start, end = self.indptr[i], self.indptr[i+1]
38
+ result[i, self.indices[start:end]] = self.data[start:end]
39
+ return result
40
+
41
+ def to_tensor(self) -> Tensor:
42
+ return Tensor(self.toarray().astype(np.float32))
43
+
44
+ def __matmul__(self, other):
45
+ a = self.toarray()
46
+ if isinstance(other, csr_matrix):
47
+ return csr_matrix(a @ other.toarray())
48
+ if isinstance(other, np.ndarray):
49
+ return a @ other
50
+ if isinstance(other, Tensor):
51
+ return Tensor((a @ other._data).astype(np.float32))
52
+ return NotImplemented
53
+
54
+ @property
55
+ def nnz(self) -> int:
56
+ return len(self.data)
57
+
58
+ def __repr__(self):
59
+ return f'csr_matrix(shape={self.shape}, nnz={self.nnz})'
60
+
61
+
62
+ class csc_matrix:
63
+ """Compressed Sparse Column matrix."""
64
+ def __init__(self, data):
65
+ if isinstance(data, np.ndarray):
66
+ arr = data
67
+ elif isinstance(data, Tensor):
68
+ arr = data._data
69
+ else:
70
+ arr = np.array(data)
71
+ self.shape = arr.shape
72
+ rows, cols = np.nonzero(arr)
73
+ order = np.argsort(cols)
74
+ self.data = arr[rows[order], cols[order]]
75
+ self.indices = rows[order]
76
+ self.indptr = np.zeros(arr.shape[1] + 1, dtype=int)
77
+ for c in cols[order]:
78
+ self.indptr[c + 1] += 1
79
+ self.indptr = np.cumsum(self.indptr)
80
+
81
+ def toarray(self) -> np.ndarray:
82
+ result = np.zeros(self.shape)
83
+ for j in range(self.shape[1]):
84
+ start, end = self.indptr[j], self.indptr[j+1]
85
+ result[self.indices[start:end], j] = self.data[start:end]
86
+ return result
87
+
88
+ def to_tensor(self) -> Tensor:
89
+ return Tensor(self.toarray().astype(np.float32))
90
+
91
+ @property
92
+ def nnz(self) -> int:
93
+ return len(self.data)
94
+
95
+
96
+ class coo_matrix:
97
+ """Coordinate (COO) sparse matrix."""
98
+ def __init__(self, data):
99
+ if isinstance(data, tuple) and len(data) == 2:
100
+ (vals, (rows, cols)), shape = data
101
+ self.data = np.array(vals)
102
+ self.row = np.array(rows, dtype=int)
103
+ self.col = np.array(cols, dtype=int)
104
+ self.shape = shape
105
+ elif isinstance(data, np.ndarray):
106
+ arr = data
107
+ self.shape = arr.shape
108
+ self.row, self.col = np.nonzero(arr)
109
+ self.data = arr[self.row, self.col]
110
+ else:
111
+ arr = np.array(data)
112
+ self.shape = arr.shape
113
+ self.row, self.col = np.nonzero(arr)
114
+ self.data = arr[self.row, self.col]
115
+
116
+ def toarray(self) -> np.ndarray:
117
+ result = np.zeros(self.shape)
118
+ result[self.row, self.col] = self.data
119
+ return result
120
+
121
+ def tocsr(self) -> csr_matrix:
122
+ return csr_matrix(self.toarray())
123
+
124
+ def to_tensor(self) -> Tensor:
125
+ return Tensor(self.toarray().astype(np.float32))
126
+
127
+ @property
128
+ def nnz(self) -> int:
129
+ return len(self.data)