highdimensionsmap 0.1.0__tar.gz → 0.2.2__tar.gz
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.
- highdimensionsmap-0.2.2/PKG-INFO +103 -0
- highdimensionsmap-0.2.2/README.md +84 -0
- highdimensionsmap-0.2.2/highdimensionsmap/__init__.py +17 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/highdimensionsmap/api.py +13 -4
- highdimensionsmap-0.2.2/highdimensionsmap/compressor.py +202 -0
- highdimensionsmap-0.2.2/highdimensionsmap/core.py +19 -0
- highdimensionsmap-0.2.2/highdimensionsmap/scanner.py +135 -0
- highdimensionsmap-0.2.2/highdimensionsmap/tensor.py +48 -0
- highdimensionsmap-0.2.2/highdimensionsmap/tracker.py +61 -0
- highdimensionsmap-0.2.2/highdimensionsmap.egg-info/PKG-INFO +103 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/highdimensionsmap.egg-info/SOURCES.txt +5 -1
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/highdimensionsmap.egg-info/requires.txt +5 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/pyproject.toml +7 -3
- highdimensionsmap-0.2.2/tests/test_compressor.py +135 -0
- highdimensionsmap-0.2.2/tests/test_scanner_regressions.py +41 -0
- highdimensionsmap-0.1.0/PKG-INFO +0 -95
- highdimensionsmap-0.1.0/README.md +0 -80
- highdimensionsmap-0.1.0/highdimensionsmap/__init__.py +0 -8
- highdimensionsmap-0.1.0/highdimensionsmap/core.py +0 -74
- highdimensionsmap-0.1.0/highdimensionsmap/scanner.py +0 -80
- highdimensionsmap-0.1.0/highdimensionsmap/tracker.py +0 -31
- highdimensionsmap-0.1.0/highdimensionsmap.egg-info/PKG-INFO +0 -95
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/LICENSE +0 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/highdimensionsmap/kinematics.py +0 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/highdimensionsmap.egg-info/dependency_links.txt +0 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/highdimensionsmap.egg-info/top_level.txt +0 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/setup.cfg +0 -0
- {highdimensionsmap-0.1.0 → highdimensionsmap-0.2.2}/tests/test_api.py +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: highdimensionsmap
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: High Dimensions Map: lightweight non-linear geometric scanning and phase-orbit indexing
|
|
5
|
+
Author: Marta Reinhardt
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: geometry,high-dimensions,embeddings,manifold-learning,kinematics,spectral
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy>=1.20.0
|
|
12
|
+
Requires-Dist: fastapi>=0.110.0
|
|
13
|
+
Requires-Dist: uvicorn>=0.30.0
|
|
14
|
+
Requires-Dist: scipy>=1.7.0
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
17
|
+
Requires-Dist: httpx>=0.24; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# High Dimensions Map (HDM) 📐
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<b>PT:</b> Biblioteca Python para projeção ortogonal analítica e compressão adaptativa de vetores via DCT-II.<br>
|
|
25
|
+
<b>EN:</b> Python library for analytical orthogonal projection and adaptive vector compression using DCT-II.
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img src="https://img.shields.io/badge/Version-0.2.2-blue?style=flat-square" alt="Version">
|
|
30
|
+
<img src="https://img.shields.io/badge/Python-3.9+-blue?style=flat-square" alt="Python">
|
|
31
|
+
<img src="https://img.shields.io/badge/Status-Active%20Development-orange?style=flat-square" alt="Status">
|
|
32
|
+
<img src="https://img.shields.io/badge/License-Apache-green?style=flat-square" alt="License">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🇧🇷 Português
|
|
38
|
+
|
|
39
|
+
### Visão Geral
|
|
40
|
+
O **HDM (High Dimensions Map)** é uma biblioteca focada na otimização, redução dimensional e compressão adaptativa de vetores em espaços de alta dimensão.
|
|
41
|
+
|
|
42
|
+
Ao contrário de métodos baseados em aprendizado iterativo ou matrizes de covariância (como PCA), o HDM utiliza a **Transformada Discreta de Cosseno Tipo II (DCT-II)** de forma analítica e determinística (*training-free*), permitindo processamento em lote em tempo real com preservação garantida de energia.
|
|
43
|
+
|
|
44
|
+
### Principais Recursos
|
|
45
|
+
- **`HDMCompressor` (Compressão Adaptativa):** Reduz a dimensão de vetores ordenando os coeficientes por magnitude decrescente e cortando no limiar exato de energia acumulada (`target_energy`).
|
|
46
|
+
- **Sem Necessidade de Treino (*Training-Free*):** Funciona vetor por vetor ou em lotes sem requerer dados de ajuste prévio.
|
|
47
|
+
- **Processamento Vetorizado:** Algoritmo implementado em C/NumPy nativo, capaz de processar 10.000 vetores de 1.000 dimensões em ~1.7 segundos.
|
|
48
|
+
|
|
49
|
+
### Exemplo de Uso Prático
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import numpy as np
|
|
53
|
+
from highdimensionmap import HDMCompressor
|
|
54
|
+
|
|
55
|
+
# Matriz de 1.000 vetores de 100 dimensões
|
|
56
|
+
X = np.random.randn(1000, 100)
|
|
57
|
+
|
|
58
|
+
# Instancia o compressor para reter 99.9% da energia do sinal
|
|
59
|
+
compressor = HDMCompressor(target_energy=0.999)
|
|
60
|
+
|
|
61
|
+
# Executa a transformação adaptativa
|
|
62
|
+
X_rec, k_componentes, tempo_ms = compressor.fit_transform(X)
|
|
63
|
+
|
|
64
|
+
print(f"Tempo de execução: {tempo_ms:.2f} ms")
|
|
65
|
+
print(f"Média de dimensões retidas: {np.mean(k_componentes):.1f} / {X.shape[1]}")
|
|
66
|
+
```
|
|
67
|
+
# 🇬🇧 English
|
|
68
|
+
## Overview
|
|
69
|
+
**HDM (High Dimensions Map) is a Python library focused on optimization**, dimensional reduction, and adaptive compression of high-dimensional vectors.
|
|
70
|
+
Unlike methods relying on iterative learning or covariance matrices (such as PCA), HDM uses the Discrete Cosine Transform Type II (DCT-II) analytically and deterministically (training-free), enabling real-time batch processing with guaranteed energy preservation.
|
|
71
|
+
Key Features
|
|
72
|
+
***HDMCompressor (Adaptive Compression): Reduces vector dimensionality by sorting coefficients by descending magnitude and truncating at the exact threshold of cumulative energy (target_energy)."**
|
|
73
|
+
**Training-Free: Operates vector-by-vector or in batches without requiring prior calibration data.**
|
|
74
|
+
**Vectorized Performance: Core algorithm implemented natively using NumPy/SciPy, capable of processing 10,000 1,000-dimensional vectors in ~1.7 seconds.**
|
|
75
|
+
Quickstart Example
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
import numpy as np
|
|
79
|
+
from highdimensionmap import HDMCompressor
|
|
80
|
+
|
|
81
|
+
# Matrix with 1,000 vectors of 100 dimensions
|
|
82
|
+
X = np.random.randn(1000, 100)
|
|
83
|
+
|
|
84
|
+
# Initialize compressor with 99.9% target energy threshold
|
|
85
|
+
compressor = HDMCompressor(target_energy=0.999)
|
|
86
|
+
|
|
87
|
+
# Execute adaptive transformation
|
|
88
|
+
X_rec, k_components, time_ms = compressor.fit_transform(X)
|
|
89
|
+
|
|
90
|
+
print(f"Execution time: {time_ms:.2f} ms")
|
|
91
|
+
print(f"Average dimensions retained: {np.mean(k_components):.1f} / {X.shape[1]}")
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
⚙️ Instalação / Installation
|
|
96
|
+
# Clone o repositório / Clone the repository
|
|
97
|
+
git clone [https://github.com/reinhardtmarta/hdm.git](https://github.com/reinhardtmarta/hdm.git)
|
|
98
|
+
cd hdm
|
|
99
|
+
|
|
100
|
+
# Instalação local em modo editável / Local editable installation
|
|
101
|
+
pip install -e .
|
|
102
|
+
|
|
103
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
# High Dimensions Map (HDM) 📐
|
|
3
|
+
|
|
4
|
+
<p align="center">
|
|
5
|
+
<b>PT:</b> Biblioteca Python para projeção ortogonal analítica e compressão adaptativa de vetores via DCT-II.<br>
|
|
6
|
+
<b>EN:</b> Python library for analytical orthogonal projection and adaptive vector compression using DCT-II.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="https://img.shields.io/badge/Version-0.2.2-blue?style=flat-square" alt="Version">
|
|
11
|
+
<img src="https://img.shields.io/badge/Python-3.9+-blue?style=flat-square" alt="Python">
|
|
12
|
+
<img src="https://img.shields.io/badge/Status-Active%20Development-orange?style=flat-square" alt="Status">
|
|
13
|
+
<img src="https://img.shields.io/badge/License-Apache-green?style=flat-square" alt="License">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🇧🇷 Português
|
|
19
|
+
|
|
20
|
+
### Visão Geral
|
|
21
|
+
O **HDM (High Dimensions Map)** é uma biblioteca focada na otimização, redução dimensional e compressão adaptativa de vetores em espaços de alta dimensão.
|
|
22
|
+
|
|
23
|
+
Ao contrário de métodos baseados em aprendizado iterativo ou matrizes de covariância (como PCA), o HDM utiliza a **Transformada Discreta de Cosseno Tipo II (DCT-II)** de forma analítica e determinística (*training-free*), permitindo processamento em lote em tempo real com preservação garantida de energia.
|
|
24
|
+
|
|
25
|
+
### Principais Recursos
|
|
26
|
+
- **`HDMCompressor` (Compressão Adaptativa):** Reduz a dimensão de vetores ordenando os coeficientes por magnitude decrescente e cortando no limiar exato de energia acumulada (`target_energy`).
|
|
27
|
+
- **Sem Necessidade de Treino (*Training-Free*):** Funciona vetor por vetor ou em lotes sem requerer dados de ajuste prévio.
|
|
28
|
+
- **Processamento Vetorizado:** Algoritmo implementado em C/NumPy nativo, capaz de processar 10.000 vetores de 1.000 dimensões em ~1.7 segundos.
|
|
29
|
+
|
|
30
|
+
### Exemplo de Uso Prático
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import numpy as np
|
|
34
|
+
from highdimensionmap import HDMCompressor
|
|
35
|
+
|
|
36
|
+
# Matriz de 1.000 vetores de 100 dimensões
|
|
37
|
+
X = np.random.randn(1000, 100)
|
|
38
|
+
|
|
39
|
+
# Instancia o compressor para reter 99.9% da energia do sinal
|
|
40
|
+
compressor = HDMCompressor(target_energy=0.999)
|
|
41
|
+
|
|
42
|
+
# Executa a transformação adaptativa
|
|
43
|
+
X_rec, k_componentes, tempo_ms = compressor.fit_transform(X)
|
|
44
|
+
|
|
45
|
+
print(f"Tempo de execução: {tempo_ms:.2f} ms")
|
|
46
|
+
print(f"Média de dimensões retidas: {np.mean(k_componentes):.1f} / {X.shape[1]}")
|
|
47
|
+
```
|
|
48
|
+
# 🇬🇧 English
|
|
49
|
+
## Overview
|
|
50
|
+
**HDM (High Dimensions Map) is a Python library focused on optimization**, dimensional reduction, and adaptive compression of high-dimensional vectors.
|
|
51
|
+
Unlike methods relying on iterative learning or covariance matrices (such as PCA), HDM uses the Discrete Cosine Transform Type II (DCT-II) analytically and deterministically (training-free), enabling real-time batch processing with guaranteed energy preservation.
|
|
52
|
+
Key Features
|
|
53
|
+
***HDMCompressor (Adaptive Compression): Reduces vector dimensionality by sorting coefficients by descending magnitude and truncating at the exact threshold of cumulative energy (target_energy)."**
|
|
54
|
+
**Training-Free: Operates vector-by-vector or in batches without requiring prior calibration data.**
|
|
55
|
+
**Vectorized Performance: Core algorithm implemented natively using NumPy/SciPy, capable of processing 10,000 1,000-dimensional vectors in ~1.7 seconds.**
|
|
56
|
+
Quickstart Example
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
import numpy as np
|
|
60
|
+
from highdimensionmap import HDMCompressor
|
|
61
|
+
|
|
62
|
+
# Matrix with 1,000 vectors of 100 dimensions
|
|
63
|
+
X = np.random.randn(1000, 100)
|
|
64
|
+
|
|
65
|
+
# Initialize compressor with 99.9% target energy threshold
|
|
66
|
+
compressor = HDMCompressor(target_energy=0.999)
|
|
67
|
+
|
|
68
|
+
# Execute adaptive transformation
|
|
69
|
+
X_rec, k_components, time_ms = compressor.fit_transform(X)
|
|
70
|
+
|
|
71
|
+
print(f"Execution time: {time_ms:.2f} ms")
|
|
72
|
+
print(f"Average dimensions retained: {np.mean(k_components):.1f} / {X.shape[1]}")
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
⚙️ Instalação / Installation
|
|
77
|
+
# Clone o repositório / Clone the repository
|
|
78
|
+
git clone [https://github.com/reinhardtmarta/hdm.git](https://github.com/reinhardtmarta/hdm.git)
|
|
79
|
+
cd hdm
|
|
80
|
+
|
|
81
|
+
# Instalação local em modo editável / Local editable installation
|
|
82
|
+
pip install -e .
|
|
83
|
+
|
|
84
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .scanner import HDMScanner
|
|
2
|
+
from .kinematics import HDMKinematics
|
|
3
|
+
from .tracker import RobustMotionTracker, RobustMotionTracker as MotionNoiseTracker
|
|
4
|
+
from .api import app
|
|
5
|
+
from .tensor import TrajectoryMotionTensor
|
|
6
|
+
from .compressor import HDMCompressor
|
|
7
|
+
|
|
8
|
+
__version__ = "0.2.2"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"HDMScanner",
|
|
11
|
+
"HDMKinematics",
|
|
12
|
+
"MotionNoiseTracker",
|
|
13
|
+
"RobustMotionTracker",
|
|
14
|
+
"app",
|
|
15
|
+
"TrajectoryMotionTensor",
|
|
16
|
+
"HDMCompressor"
|
|
17
|
+
]
|
|
@@ -28,22 +28,31 @@ def healthcheck():
|
|
|
28
28
|
@app.post("/transform")
|
|
29
29
|
def transform_endpoint(payload: TransformRequest):
|
|
30
30
|
try:
|
|
31
|
-
values = np.asarray(payload.input, dtype=
|
|
31
|
+
values = np.asarray(payload.input, dtype=np.float64)
|
|
32
32
|
except ValueError as exc:
|
|
33
33
|
raise HTTPException(status_code=400, detail="'input' precisa ser numérico.") from exc
|
|
34
34
|
|
|
35
35
|
if values.ndim not in (1, 2):
|
|
36
36
|
raise HTTPException(status_code=400, detail="'input' deve ser um vetor ou uma matriz 2D.")
|
|
37
37
|
|
|
38
|
+
was_1d = values.ndim == 1
|
|
38
39
|
signature = scanner.transform(values)
|
|
39
|
-
|
|
40
|
+
sig_array = np.asarray(signature, dtype=np.float64)
|
|
41
|
+
|
|
42
|
+
# Garante que a estrutura dimensional da resposta corresponda à entrada
|
|
43
|
+
if was_1d and sig_array.ndim > 1:
|
|
44
|
+
sig_array = sig_array[0]
|
|
45
|
+
elif not was_1d and sig_array.ndim == 1:
|
|
46
|
+
sig_array = sig_array.reshape(1, -1)
|
|
47
|
+
|
|
48
|
+
return {"signature": sig_array.tolist()}
|
|
40
49
|
|
|
41
50
|
|
|
42
51
|
@app.post("/query")
|
|
43
52
|
def query_endpoint(payload: QueryRequest):
|
|
44
53
|
try:
|
|
45
|
-
dataset = np.asarray(payload.dataset, dtype=
|
|
46
|
-
query = np.asarray(payload.query, dtype=
|
|
54
|
+
dataset = np.asarray(payload.dataset, dtype=np.float64)
|
|
55
|
+
query = np.asarray(payload.query, dtype=np.float64)
|
|
47
56
|
except ValueError as exc:
|
|
48
57
|
raise HTTPException(status_code=400, detail="'dataset' e 'query' precisam ser numéricos.") from exc
|
|
49
58
|
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Tuple, Union
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.fftpack import dct, idct
|
|
5
|
+
|
|
6
|
+
# Importações condicionais para frameworks de terceiros (não força dependência rígida)
|
|
7
|
+
try:
|
|
8
|
+
import pandas as pd
|
|
9
|
+
HAS_PANDAS = True
|
|
10
|
+
except ImportError:
|
|
11
|
+
HAS_PANDAS = False
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import torch
|
|
15
|
+
HAS_TORCH = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
HAS_TORCH = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HDMCompressor:
|
|
21
|
+
"""Compressor e redutor de dimensionalidade adaptativo baseado na DCT-II.
|
|
22
|
+
|
|
23
|
+
Seleciona coeficientes por ordem decrescente de magnitude até atingir
|
|
24
|
+
o limiar de energia acumulada especificado (target_energy).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, target_energy: float = 0.999):
|
|
28
|
+
if not (isinstance(target_energy, (float, int)) and 0.0 < target_energy <= 1.0):
|
|
29
|
+
raise ValueError("target_energy deve ser um numero entre 0.0 (exclusivo) e 1.0 (inclusivo).")
|
|
30
|
+
self.target_energy = float(target_energy)
|
|
31
|
+
|
|
32
|
+
def _convert_input(self, X: Union[np.ndarray, "pd.DataFrame", "pd.Series", "torch.Tensor"]) -> Tuple[np.ndarray, str]:
|
|
33
|
+
"""Converte entradas diversas (Pandas, PyTorch) para numpy.ndarray com seguranca."""
|
|
34
|
+
original_type = "numpy"
|
|
35
|
+
|
|
36
|
+
if HAS_TORCH and isinstance(X, torch.Tensor):
|
|
37
|
+
original_type = "torch"
|
|
38
|
+
X = X.detach().cpu().numpy()
|
|
39
|
+
elif HAS_PANDAS and isinstance(X, (pd.DataFrame, pd.Series)):
|
|
40
|
+
original_type = "pandas"
|
|
41
|
+
X = X.to_numpy()
|
|
42
|
+
|
|
43
|
+
if not isinstance(X, np.ndarray):
|
|
44
|
+
X = np.asarray(X)
|
|
45
|
+
|
|
46
|
+
return X, original_type
|
|
47
|
+
|
|
48
|
+
def fit_transform(
|
|
49
|
+
self, X: Union[np.ndarray, "pd.DataFrame", "pd.Series", "torch.Tensor"]
|
|
50
|
+
) -> Tuple[np.ndarray, np.ndarray, float]:
|
|
51
|
+
"""Aplica a DCT-II e projeta os vetores com base na energia acumulada.
|
|
52
|
+
|
|
53
|
+
Trata casos de borda como NaN, Inf e vetores nulos.
|
|
54
|
+
|
|
55
|
+
Parâmetros:
|
|
56
|
+
X: Matriz 2D (n_amostras, n_dimensoes) ou vetor 1D. Aceita NumPy, Pandas e PyTorch.
|
|
57
|
+
|
|
58
|
+
Retorna:
|
|
59
|
+
tuple: (X_reconstruido, k_por_linha, tempo_execucao_ms)
|
|
60
|
+
"""
|
|
61
|
+
t0 = time.perf_counter()
|
|
62
|
+
|
|
63
|
+
# 1. Normalização de tipo de entrada
|
|
64
|
+
X_np, original_type = self._convert_input(X)
|
|
65
|
+
|
|
66
|
+
# 2. Validação de formato (dimensionalidade)
|
|
67
|
+
if X_np.ndim not in (1, 2):
|
|
68
|
+
raise ValueError(f"Entrada invalida. Esperado array 1D ou 2D, mas recebido ndim={X_np.ndim}.")
|
|
69
|
+
|
|
70
|
+
is_1d = X_np.ndim == 1
|
|
71
|
+
if is_1d:
|
|
72
|
+
X_np = X_np.reshape(1, -1)
|
|
73
|
+
|
|
74
|
+
# 3. Tratamento de NaN e Inf
|
|
75
|
+
if not np.isfinite(X_np).all():
|
|
76
|
+
raise ValueError("A matriz de entrada contem valores invalidos (NaN ou Inf). Limpe os dados antes de processar.")
|
|
77
|
+
|
|
78
|
+
# 4. Processamento da DCT-II nas linhas (axis=1)
|
|
79
|
+
coefs = dct(X_np, type=2, norm="ortho", axis=1)
|
|
80
|
+
|
|
81
|
+
# 5. Ordenação por magnitude decrescente por linha
|
|
82
|
+
abs_coefs = np.abs(coefs)
|
|
83
|
+
sorted_indices = np.argsort(-abs_coefs, axis=1)
|
|
84
|
+
|
|
85
|
+
rows = np.arange(X_np.shape[0])[:, None]
|
|
86
|
+
sorted_sq_coefs = coefs[rows, sorted_indices] ** 2
|
|
87
|
+
|
|
88
|
+
# 6. Cálculo da Energia Total por linha (Tratamento para Vetores Nulos)
|
|
89
|
+
total_energy = np.sum(coefs**2, axis=1, keepdims=True)
|
|
90
|
+
null_vectors_mask = total_energy.squeeze(axis=1) == 0.0
|
|
91
|
+
|
|
92
|
+
# Evita divisão por zero em vetores nulos
|
|
93
|
+
safe_total_energy = np.where(total_energy == 0.0, 1.0, total_energy)
|
|
94
|
+
cum_energy = np.cumsum(sorted_sq_coefs, axis=1) / safe_total_energy
|
|
95
|
+
|
|
96
|
+
# 7. Determinação adaptativa do parâmetro K por linha
|
|
97
|
+
mask_energy = cum_energy >= self.target_energy
|
|
98
|
+
k_per_row = np.argmax(mask_energy, axis=1) + 1
|
|
99
|
+
|
|
100
|
+
# Trata linhas que não atingem o limiar exacto ou são nulas
|
|
101
|
+
no_reach = ~np.any(mask_energy, axis=1)
|
|
102
|
+
k_per_row[no_reach] = X_np.shape[1]
|
|
103
|
+
k_per_row[null_vectors_mask] = 1 # Vetores nulos exigem apenas 1 componente (zerado)
|
|
104
|
+
|
|
105
|
+
# 8. Filtro dos coeficientes fora do Top-K
|
|
106
|
+
col_indices = np.arange(X_np.shape[1])[None, :]
|
|
107
|
+
mask_top_k = col_indices < k_per_row[:, None]
|
|
108
|
+
|
|
109
|
+
filtered_coefs_sorted = np.zeros_like(coefs)
|
|
110
|
+
filtered_coefs_sorted[mask_top_k] = coefs[rows, sorted_indices][mask_top_k]
|
|
111
|
+
|
|
112
|
+
# 9. Reordenação para o espaço de frequência original
|
|
113
|
+
filtered_coefs = np.zeros_like(coefs)
|
|
114
|
+
filtered_coefs[rows, sorted_indices] = filtered_coefs_sorted
|
|
115
|
+
|
|
116
|
+
# 10. Reconstrução vetorial via IDCT-II
|
|
117
|
+
X_reconstructed = idct(filtered_coefs, type=2, norm="ortho", axis=1)
|
|
118
|
+
|
|
119
|
+
t1 = time.perf_counter()
|
|
120
|
+
exec_time_ms = (t1 - t0) * 1000.0
|
|
121
|
+
|
|
122
|
+
if is_1d:
|
|
123
|
+
return X_reconstructed.squeeze(0), k_per_row[0], exec_time_ms
|
|
124
|
+
|
|
125
|
+
return X_reconstructed, k_per_row, exec_time_ms
|
|
126
|
+
|
|
127
|
+
def compress(
|
|
128
|
+
self, X: Union[np.ndarray, "pd.DataFrame", "pd.Series", "torch.Tensor"]
|
|
129
|
+
) -> dict:
|
|
130
|
+
"""Comprime a matriz/vetor retornando apenas os K coeficientes relevantes e seus indices.
|
|
131
|
+
|
|
132
|
+
Retorna:
|
|
133
|
+
dict: {
|
|
134
|
+
'values': list de np.ndarray (coeficientes mantidos por linha),
|
|
135
|
+
'indices': list de np.ndarray (indices originais dos coeficientes),
|
|
136
|
+
'shape': tuple (formato original da matriz/vetor),
|
|
137
|
+
'is_1d': bool
|
|
138
|
+
}
|
|
139
|
+
"""
|
|
140
|
+
X_np, _ = self._convert_input(X)
|
|
141
|
+
is_1d = X_np.ndim == 1
|
|
142
|
+
if is_1d:
|
|
143
|
+
X_np = X_np.reshape(1, -1)
|
|
144
|
+
|
|
145
|
+
if not np.isfinite(X_np).all():
|
|
146
|
+
raise ValueError("A matriz de entrada contem valores invalidos (NaN ou Inf).")
|
|
147
|
+
|
|
148
|
+
# 1. DCT-II e ordenacao por magnitude
|
|
149
|
+
coefs = dct(X_np, type=2, norm="ortho", axis=1)
|
|
150
|
+
abs_coefs = np.abs(coefs)
|
|
151
|
+
sorted_indices = np.argsort(-abs_coefs, axis=1)
|
|
152
|
+
|
|
153
|
+
rows = np.arange(X_np.shape[0])[:, None]
|
|
154
|
+
sorted_sq_coefs = coefs[rows, sorted_indices] ** 2
|
|
155
|
+
|
|
156
|
+
# 2. Determinar K por linha com protecao para vetores nulos
|
|
157
|
+
total_energy = np.sum(coefs**2, axis=1, keepdims=True)
|
|
158
|
+
null_mask = total_energy.squeeze(axis=1) == 0.0
|
|
159
|
+
safe_total = np.where(total_energy == 0.0, 1.0, total_energy)
|
|
160
|
+
cum_energy = np.cumsum(sorted_sq_coefs, axis=1) / safe_total
|
|
161
|
+
|
|
162
|
+
mask_energy = cum_energy >= self.target_energy
|
|
163
|
+
k_per_row = np.argmax(mask_energy, axis=1) + 1
|
|
164
|
+
k_per_row[~np.any(mask_energy, axis=1)] = X_np.shape[1]
|
|
165
|
+
k_per_row[null_mask] = 1
|
|
166
|
+
|
|
167
|
+
# 3. Extrair apenas os K coeficientes e indices por linha
|
|
168
|
+
compressed_values = []
|
|
169
|
+
compressed_indices = []
|
|
170
|
+
|
|
171
|
+
for i in range(X_np.shape[0]):
|
|
172
|
+
k = k_per_row[i]
|
|
173
|
+
idx = sorted_indices[i, :k]
|
|
174
|
+
val = coefs[i, idx]
|
|
175
|
+
compressed_indices.append(idx.astype(np.int32))
|
|
176
|
+
compressed_values.append(val.astype(np.float32))
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"values": compressed_values,
|
|
180
|
+
"indices": compressed_indices,
|
|
181
|
+
"shape": X_np.shape,
|
|
182
|
+
"is_1d": is_1d,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
def decompress(self, compressed_data: dict) -> np.ndarray:
|
|
186
|
+
"""Reconstrui a matriz/vetor original a partir do dicionario comprimido."""
|
|
187
|
+
values = compressed_data["values"]
|
|
188
|
+
indices = compressed_data["indices"]
|
|
189
|
+
shape = compressed_data["shape"]
|
|
190
|
+
is_1d = compressed_data["is_1d"]
|
|
191
|
+
|
|
192
|
+
coefs = np.zeros(shape, dtype=np.float32)
|
|
193
|
+
|
|
194
|
+
for i in range(shape[0]):
|
|
195
|
+
coefs[i, indices[i]] = values[i]
|
|
196
|
+
|
|
197
|
+
X_reconstructed = idct(coefs, type=2, norm="ortho", axis=1)
|
|
198
|
+
|
|
199
|
+
if is_1d:
|
|
200
|
+
return X_reconstructed.squeeze(0)
|
|
201
|
+
return X_reconstructed
|
|
202
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .scanner import HDMScanner
|
|
2
|
+
from .kinematics import HDMKinematics
|
|
3
|
+
from .tracker import RobustMotionTracker, RobustMotionTracker as MotionNoiseTracker
|
|
4
|
+
from .core import hdm
|
|
5
|
+
from .api import app
|
|
6
|
+
from .tensor import TrajectoryMotionTensor
|
|
7
|
+
from .compressor import HDMCompressor
|
|
8
|
+
|
|
9
|
+
__version__ = "0.2.2"
|
|
10
|
+
__all__ = [
|
|
11
|
+
"HDMScanner",
|
|
12
|
+
"HDMKinematics",
|
|
13
|
+
"MotionNoiseTracker",
|
|
14
|
+
"RobustMotionTracker",
|
|
15
|
+
"hdm",
|
|
16
|
+
"app",
|
|
17
|
+
"TrajectoryMotionTensor",
|
|
18
|
+
"HDMCompressor"
|
|
19
|
+
]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
class HDMScanner:
|
|
4
|
+
"""
|
|
5
|
+
Scanner dimensional determinístico com suporte a projeções ortogonais
|
|
6
|
+
adaptativas (SVD/PCA), padronização de feições e modos espectrais.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, input_dim=512, latent_modes=32, method="adaptive", scale=True, seed=42, steps=None):
|
|
10
|
+
if int(input_dim) < 1:
|
|
11
|
+
raise ValueError("input_dim precisa ser >= 1.")
|
|
12
|
+
if int(latent_modes) < 1:
|
|
13
|
+
raise ValueError("latent_modes precisa ser >= 1.")
|
|
14
|
+
if method not in ("adaptive", "spectral", "random"):
|
|
15
|
+
raise ValueError("method deve ser 'adaptive', 'spectral' ou 'random'.")
|
|
16
|
+
self.input_dim = int(input_dim)
|
|
17
|
+
self.latent_modes = int(latent_modes)
|
|
18
|
+
self.method = method
|
|
19
|
+
self.scale = scale
|
|
20
|
+
self.seed = seed
|
|
21
|
+
self.steps = steps # mantido por compatibilidade com exemplos antigos
|
|
22
|
+
self.projection_matrix = None
|
|
23
|
+
self.mean_ = None
|
|
24
|
+
self.std_ = None
|
|
25
|
+
|
|
26
|
+
if self.method == "spectral":
|
|
27
|
+
self.projection_matrix = self._build_dct_basis(self.latent_modes, self.input_dim)
|
|
28
|
+
elif self.method == "random":
|
|
29
|
+
self.projection_matrix = self._build_random_basis(self.latent_modes, self.input_dim)
|
|
30
|
+
|
|
31
|
+
def _build_dct_basis(self, n_modes, d):
|
|
32
|
+
n = np.arange(d, dtype=np.float64)
|
|
33
|
+
basis = np.zeros((n_modes, d), dtype=np.float64)
|
|
34
|
+
for k in range(n_modes):
|
|
35
|
+
if k == 0:
|
|
36
|
+
basis[k, :] = 1.0 / np.sqrt(d)
|
|
37
|
+
else:
|
|
38
|
+
basis[k, :] = np.sqrt(2.0 / d) * np.cos((np.pi * (2.0 * n + 1.0) * k) / (2.0 * d))
|
|
39
|
+
return basis
|
|
40
|
+
|
|
41
|
+
def _build_random_basis(self, n_modes, d):
|
|
42
|
+
rng = np.random.default_rng(self.seed)
|
|
43
|
+
gaussian_matrix = rng.standard_normal((d, n_modes))
|
|
44
|
+
q, _ = np.linalg.qr(gaussian_matrix)
|
|
45
|
+
return q.T
|
|
46
|
+
|
|
47
|
+
def generate_dataset(self, n_samples=1, noise=0.05, drift=0.1):
|
|
48
|
+
"""Gera dados sintéticos reproduzíveis para testes e exemplos.
|
|
49
|
+
|
|
50
|
+
Cada linha é uma amostra. ``noise`` controla o ruído gaussiano;
|
|
51
|
+
``drift`` adiciona uma tendência gradual ao longo das amostras.
|
|
52
|
+
"""
|
|
53
|
+
if int(n_samples) < 1:
|
|
54
|
+
raise ValueError("n_samples precisa ser >= 1.")
|
|
55
|
+
if noise < 0 or drift < 0:
|
|
56
|
+
raise ValueError("noise e drift precisam ser >= 0.")
|
|
57
|
+
rng = np.random.default_rng(self.seed)
|
|
58
|
+
data = rng.standard_normal((int(n_samples), self.input_dim))
|
|
59
|
+
if drift:
|
|
60
|
+
trend = np.linspace(0.0, float(drift), int(n_samples))[:, None]
|
|
61
|
+
direction = np.ones((1, self.input_dim), dtype=np.float64) / np.sqrt(self.input_dim)
|
|
62
|
+
data = data + trend * direction
|
|
63
|
+
if noise:
|
|
64
|
+
data = data + rng.normal(0.0, float(noise), size=data.shape)
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
def fit(self, X):
|
|
68
|
+
X = np.asarray(X, dtype=np.float64)
|
|
69
|
+
X = np.atleast_2d(X)
|
|
70
|
+
if X.ndim != 2 or X.shape[0] == 0 or X.shape[1] == 0:
|
|
71
|
+
raise ValueError("X precisa ser uma matriz 2D não vazia.")
|
|
72
|
+
if not np.isfinite(X).all():
|
|
73
|
+
raise ValueError("X deve conter apenas valores finitos.")
|
|
74
|
+
self.input_dim = X.shape[1]
|
|
75
|
+
|
|
76
|
+
# Uma única amostra não permite estimar componentes principais.
|
|
77
|
+
# Use uma base ortogonal determinística sem centralizar pela amostra,
|
|
78
|
+
# pois isso zeraria completamente a assinatura.
|
|
79
|
+
if X.shape[0] == 1:
|
|
80
|
+
self.mean_ = np.zeros(self.input_dim, dtype=np.float64)
|
|
81
|
+
self.std_ = np.ones(self.input_dim, dtype=np.float64)
|
|
82
|
+
self.projection_matrix = self._build_random_basis(
|
|
83
|
+
min(self.latent_modes, self.input_dim), self.input_dim
|
|
84
|
+
)
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
self.mean_ = np.mean(X, axis=0)
|
|
88
|
+
X_proc = X - self.mean_
|
|
89
|
+
|
|
90
|
+
if self.scale:
|
|
91
|
+
self.std_ = np.std(X_proc, axis=0)
|
|
92
|
+
self.std_[self.std_ == 0.0] = 1.0
|
|
93
|
+
X_proc = X_proc / self.std_
|
|
94
|
+
else:
|
|
95
|
+
self.std_ = np.ones(self.input_dim, dtype=np.float64)
|
|
96
|
+
|
|
97
|
+
_, _, Vh = np.linalg.svd(X_proc, full_matrices=False)
|
|
98
|
+
self.projection_matrix = Vh[: min(self.latent_modes, Vh.shape[0]), :]
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def transform(self, X, normalize=False):
|
|
102
|
+
X = np.asarray(X, dtype=np.float64)
|
|
103
|
+
X = np.atleast_2d(X)
|
|
104
|
+
|
|
105
|
+
if X.shape[1] != self.input_dim or self.projection_matrix is None:
|
|
106
|
+
self.fit(X)
|
|
107
|
+
|
|
108
|
+
X_proc = X - self.mean_ if self.mean_ is not None else X
|
|
109
|
+
if self.scale and self.std_ is not None:
|
|
110
|
+
X_proc = X_proc / self.std_
|
|
111
|
+
|
|
112
|
+
signatures = np.dot(X_proc, self.projection_matrix.T)
|
|
113
|
+
|
|
114
|
+
if normalize:
|
|
115
|
+
norms = np.linalg.norm(signatures, axis=1, keepdims=True)
|
|
116
|
+
norms[norms == 0] = 1.0
|
|
117
|
+
signatures = signatures / norms
|
|
118
|
+
|
|
119
|
+
return signatures if signatures.shape[0] > 1 else signatures[0]
|
|
120
|
+
|
|
121
|
+
def query(self, dataset, query_vector, k=5):
|
|
122
|
+
dataset_array = np.asarray(dataset, dtype=np.float64)
|
|
123
|
+
if dataset_array.ndim == 1:
|
|
124
|
+
dataset_array = dataset_array.reshape(1, -1)
|
|
125
|
+
|
|
126
|
+
query_sig = self.transform(query_vector)
|
|
127
|
+
|
|
128
|
+
if dataset_array.shape[1] != query_sig.shape[0]:
|
|
129
|
+
dataset_signatures = self.transform(dataset_array)
|
|
130
|
+
else:
|
|
131
|
+
dataset_signatures = dataset_array
|
|
132
|
+
|
|
133
|
+
distances = np.linalg.norm(dataset_signatures - query_sig, axis=1)
|
|
134
|
+
nearest = np.argsort(distances)[:k]
|
|
135
|
+
return nearest, distances[nearest]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
class TrajectoryMotionTensor:
|
|
4
|
+
"""
|
|
5
|
+
Tensor de Trajetória no Espaço de Estados para Alta Dimensão.
|
|
6
|
+
Mede a coerência angular contínua e detecta anomalias dinâmicas.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, scanner=None):
|
|
9
|
+
self.scanner = scanner
|
|
10
|
+
|
|
11
|
+
def compute_coherence(self, trajectory_matrix, step_lag=1):
|
|
12
|
+
matriz = np.asarray(trajectory_matrix, dtype=np.float64)
|
|
13
|
+
if matriz.ndim != 2:
|
|
14
|
+
raise ValueError("A matriz de trajetória precisa ter 2 dimensões (N_passos, Dimensão).")
|
|
15
|
+
|
|
16
|
+
if matriz.shape[0] <= step_lag:
|
|
17
|
+
return {
|
|
18
|
+
"global_coherence": 0.0,
|
|
19
|
+
"instantaneous_profile": np.array([]),
|
|
20
|
+
"drift_instability": np.array([]),
|
|
21
|
+
"is_structured": False
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
x0 = matriz[:-step_lag]
|
|
25
|
+
x1 = matriz[step_lag:]
|
|
26
|
+
|
|
27
|
+
# Produto escalar normalizado
|
|
28
|
+
dot_products = np.sum(x0 * x1, axis=1)
|
|
29
|
+
normas = np.linalg.norm(x0, axis=1) * np.linalg.norm(x1, axis=1)
|
|
30
|
+
|
|
31
|
+
# Evita divisão por zero/valores insignificantes
|
|
32
|
+
valid_mask = normas > 1e-8
|
|
33
|
+
instantaneous_coherence = np.ones(len(dot_products))
|
|
34
|
+
instantaneous_coherence[valid_mask] = dot_products[valid_mask] / normas[valid_mask]
|
|
35
|
+
|
|
36
|
+
# Garante intervalo [-1.0, 1.0]
|
|
37
|
+
instantaneous_coherence = np.clip(instantaneous_coherence, -1.0, 1.0)
|
|
38
|
+
drift_instability = 1.0 - instantaneous_coherence
|
|
39
|
+
|
|
40
|
+
global_coherence = float(np.median(instantaneous_coherence)) # Uso de mediana para robustez a outliers
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
"global_coherence": global_coherence,
|
|
44
|
+
"instantaneous_profile": instantaneous_coherence,
|
|
45
|
+
"drift_instability": drift_instability,
|
|
46
|
+
"is_structured": bool(global_coherence > 0.3) # Limiar adaptativo
|
|
47
|
+
}
|
|
48
|
+
|