pytorch-mvgc 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.1
2
+ Name: pytorch-mvgc
3
+ Version: 0.1.0
4
+ Summary: A GPU-accelerated Multivariate Granger Causality implementation using PyTorch.
5
+ Home-page: https://github.com/yourusername/pytorch-mvgc
6
+ Author: nju_sit_hubc
7
+ Author-email: 2515094316@qq.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy
15
+ Requires-Dist: torch>=1.9.0
16
+ Requires-Dist: tqdm
17
+
18
+ # PyTorch-MVGC
19
+
20
+ [![PyPI version](https://badge.fury.io/py/pytorch-mvgc.svg)](https://badge.fury.io/py/pytorch-mvgc)
21
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
+
23
+ **PyTorch-MVGC** is a GPU-accelerated Python implementation of **Multivariate Granger Causality (MVGC)** analysis.
24
+
25
+ It is designed for high-dimensional time-series data (e.g., EEG, fMRI, MEA) where traditional CPU-based implementations are too slow. By leveraging PyTorch tensors and batched matrix operations, this library achieves significant speedups compared to standard NumPy or Matlab implementations.
26
+
27
+ ## Features
28
+
29
+ - **🚀 GPU Acceleration**: Fully implemented in PyTorch, supporting CUDA acceleration out-of-the-box.
30
+ - **⚡ Batch Processing**: Optimized for processing multiple trials or sliding windows simultaneously.
31
+ - **🔧 Numerical Stability**: Implements Ridge regularization and Pseudo-inverse solvers to handle ill-conditioned matrices common in high-channel neural recordings (e.g., 128-channel EEG).
32
+ - **🐍 Python Native**: A seamless replacement for the Matlab MVGC toolbox for Python pipelines.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install pytorch-mvgc
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ import numpy as np
44
+ import torch
45
+ from pytorch_mvgc import compute_mvgc
46
+
47
+ # 1. Generate sample data (3 channels, 1000 timepoints)
48
+ # System: Node 0 drives Node 1
49
+ n_time = 1000
50
+ X = np.zeros((3, n_time))
51
+ noise = 0.5 * np.random.randn(3, n_time)
52
+ for t in range(1, n_time):
53
+ X[0, t] = 0.8 * X[0, t-1] + noise[0, t]
54
+ X[1, t] = 0.5 * X[1, t-1] + 0.5 * X[0, t-1] + noise[1, t] # 0->1 Causal
55
+ X[2, t] = 0.7 * X[2, t-1] + noise[2, t]
56
+
57
+ # 2. Compute MVGC
58
+ # Returns matrix F where F[i, j] denotes causality j -> i
59
+ F_matrix = compute_mvgc(X, p=1, device='cuda')
60
+
61
+ print("MVGC Matrix:\n", np.round(F_matrix, 4))
62
+ # Expected: F[1, 0] should be significant (> 0)
63
+ ```
64
+
@@ -0,0 +1,47 @@
1
+ # PyTorch-MVGC
2
+
3
+ [![PyPI version](https://badge.fury.io/py/pytorch-mvgc.svg)](https://badge.fury.io/py/pytorch-mvgc)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **PyTorch-MVGC** is a GPU-accelerated Python implementation of **Multivariate Granger Causality (MVGC)** analysis.
7
+
8
+ It is designed for high-dimensional time-series data (e.g., EEG, fMRI, MEA) where traditional CPU-based implementations are too slow. By leveraging PyTorch tensors and batched matrix operations, this library achieves significant speedups compared to standard NumPy or Matlab implementations.
9
+
10
+ ## Features
11
+
12
+ - **🚀 GPU Acceleration**: Fully implemented in PyTorch, supporting CUDA acceleration out-of-the-box.
13
+ - **⚡ Batch Processing**: Optimized for processing multiple trials or sliding windows simultaneously.
14
+ - **🔧 Numerical Stability**: Implements Ridge regularization and Pseudo-inverse solvers to handle ill-conditioned matrices common in high-channel neural recordings (e.g., 128-channel EEG).
15
+ - **🐍 Python Native**: A seamless replacement for the Matlab MVGC toolbox for Python pipelines.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install pytorch-mvgc
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ import numpy as np
27
+ import torch
28
+ from pytorch_mvgc import compute_mvgc
29
+
30
+ # 1. Generate sample data (3 channels, 1000 timepoints)
31
+ # System: Node 0 drives Node 1
32
+ n_time = 1000
33
+ X = np.zeros((3, n_time))
34
+ noise = 0.5 * np.random.randn(3, n_time)
35
+ for t in range(1, n_time):
36
+ X[0, t] = 0.8 * X[0, t-1] + noise[0, t]
37
+ X[1, t] = 0.5 * X[1, t-1] + 0.5 * X[0, t-1] + noise[1, t] # 0->1 Causal
38
+ X[2, t] = 0.7 * X[2, t-1] + noise[2, t]
39
+
40
+ # 2. Compute MVGC
41
+ # Returns matrix F where F[i, j] denotes causality j -> i
42
+ F_matrix = compute_mvgc(X, p=1, device='cuda')
43
+
44
+ print("MVGC Matrix:\n", np.round(F_matrix, 4))
45
+ # Expected: F[1, 0] should be significant (> 0)
46
+ ```
47
+
@@ -0,0 +1,4 @@
1
+ from .core import compute_mvgc, compute_mvgc_batch
2
+ from .utils import segment_data
3
+
4
+ __version__ = "0.1.0"
@@ -0,0 +1,169 @@
1
+ import numpy as np
2
+ import torch
3
+ import warnings
4
+
5
+ def check_backend(device: str = 'cuda'):
6
+ if device == 'cuda' and not torch.cuda.is_available():
7
+ warnings.warn("CUDA not available, falling back to CPU.")
8
+ return torch.device('cpu')
9
+ return torch.device(device)
10
+
11
+ def compute_mvgc(X: np.ndarray, p: int, alpha: float = 0.01, device: str = 'cuda') -> np.ndarray:
12
+ """
13
+ 计算 MVGC 矩阵(用户主入口)。
14
+
15
+ Args:
16
+ X: (n_channels, n_time) 时间序列数据
17
+ p: VAR 滞后阶数 (Lag order)
18
+ alpha: 岭回归正则化系数 (Ridge regularization),解决 NaN 的关键!
19
+ 建议值 0.01 ~ 0.1。如果数据很干净,可以用 1e-4。
20
+ device: 'cuda' 或 'cpu'
21
+
22
+ Returns:
23
+ F: (n_channels, n_channels) 格兰杰因果矩阵。F[i, j] 代表 j -> i 的因果强度。
24
+ """
25
+ X = np.asarray(X)
26
+
27
+ # 维度检查与修正
28
+ if X.ndim != 2:
29
+ raise ValueError(f"Input must be 2D (n_channels, n_time), got {X.shape}")
30
+
31
+ n_channels, n_time = X.shape
32
+ if n_channels > n_time:
33
+ warnings.warn(f"Warning: Channels ({n_channels}) > Time points ({n_time}). Did you transpose the input?")
34
+
35
+ # 视作 Batch = 1 进行处理
36
+ X_batch = X[np.newaxis, :, :] # (1, n, T)
37
+
38
+ F_batch = compute_mvgc_batch(X_batch, p, alpha, device)
39
+
40
+ return F_batch[0] # 返回 (n, n)
41
+
42
+ def compute_mvgc_batch(X_batch: np.ndarray, p: int, alpha: float = 0.01, device: str = 'cuda') -> np.ndarray:
43
+ """
44
+ 批量 MVGC 计算函数。
45
+ """
46
+ dev = check_backend(device)
47
+ X_t = torch.from_numpy(X_batch).to(dev, dtype=torch.float64) # 使用 float64 提高精度
48
+
49
+ # 调用内部 Torch 内核
50
+ with torch.no_grad():
51
+ F_res = _mvgc_batch_kernel(X_t, p, alpha, dev)
52
+
53
+ return F_res.cpu().numpy()
54
+
55
+ def _mvgc_batch_kernel(segments: torch.Tensor, p: int, alpha: float, dev: torch.device) -> torch.Tensor:
56
+ """
57
+ PyTorch 核心计算逻辑:向量化 OLS 回归 + 岭回归正则化
58
+ segments: (B, n, T)
59
+ return: (B, n, n)
60
+ """
61
+ B, n, T = segments.shape
62
+ if T <= p:
63
+ raise ValueError(f'Time length {T} must be > order {p}')
64
+
65
+ # 1. 去均值 (Centering)
66
+ seg_t = segments - segments.mean(dim=2, keepdim=True)
67
+
68
+ # 2. 构建 Y (响应变量) 和 Z (设计矩阵)
69
+ # Y: (B, N, n), N = T-p
70
+ # Z: (B, N, n*p)
71
+ N = T - p
72
+ Y = seg_t[:, :, p:].permute(0, 2, 1)
73
+
74
+ Z_list = []
75
+ for k in range(1, p + 1):
76
+ # 滞后 k 个时间点
77
+ lag = seg_t[:, :, p - k : T - k].permute(0, 2, 1)
78
+ Z_list.append(lag)
79
+ Zs = torch.cat(Z_list, dim=2) # (B, N, n*p)
80
+
81
+ D = n * p # 特征维度
82
+
83
+ # 3. 计算 Full Model (全模型) 的残差方差
84
+ # 求解 Y = Z * Beta + E
85
+ # Beta = (Z^T Z + alpha*I)^-1 Z^T Y
86
+
87
+ ZsT = Zs.permute(0, 2, 1) # (B, D, N)
88
+ ZTZ = torch.bmm(ZsT, Zs) # (B, D, D)
89
+ ZTY = torch.bmm(ZsT, Y) # (B, D, n)
90
+
91
+ # === 关键:正则化 (Ridge) 防止奇异矩阵导致的 NaN ===
92
+ # 添加 alpha 到对角线
93
+ eye = torch.eye(D, device=dev, dtype=ZTZ.dtype).unsqueeze(0).expand(B, D, D)
94
+ ZTZ_reg = ZTZ + alpha * eye
95
+
96
+ # 使用 cholesky 或 solve 求解比求逆更稳定
97
+ # beta_full: (B, D, n)
98
+ try:
99
+ # 尝试更快的 Cholesky
100
+ L = torch.linalg.cholesky(ZTZ_reg)
101
+ beta_full = torch.cholesky_solve(ZTY, L)
102
+ except:
103
+ # 回退到标准求逆 (伪逆)
104
+ beta_full = torch.linalg.solve(ZTZ_reg, ZTY)
105
+
106
+ # 计算 Full Model 残差方差 sigma_full
107
+ resid_full = Y - torch.bmm(Zs, beta_full)
108
+ # 只需要对角线部分的方差 (Generalized Variance 近似为变量方差的乘积,在MVGC中简化为逐变量比较)
109
+ sigma_full = resid_full.pow(2).sum(dim=1) # (B, n)
110
+
111
+ # 4. 计算 Reduced Models (逐个剔除源变量 j)
112
+ # 结果矩阵 F (B, target, source)
113
+ F_batch = torch.zeros((B, n, n), dtype=torch.float64, device=dev)
114
+
115
+ # 预先生成所有列的索引
116
+ all_cols = torch.arange(D, device=dev)
117
+
118
+ for j in range(n): # j 是 Source (因)
119
+ # 剔除 j 的所有滞后项 (columns: j*p, j*p+1, ..., (j+1)*p-1)
120
+ # 保留 Target i 和其他变量 Z
121
+
122
+ # 确定要保留的列索引
123
+ # 这是一个简单的掩码操作
124
+ mask = torch.ones(D, dtype=torch.bool, device=dev)
125
+ mask[j*p : (j+1)*p] = False
126
+ keep_idx = all_cols[mask]
127
+
128
+ # 提取 Reduced 设计矩阵 Z_red
129
+ Zs_red = Zs.index_select(2, keep_idx) # (B, N, D_reduced)
130
+ D_red = Zs_red.shape[2]
131
+
132
+ # OLS for Reduced Model
133
+ Zs_red_T = Zs_red.permute(0, 2, 1)
134
+ ZTZ_r = torch.bmm(Zs_red_T, Zs_red)
135
+ ZTY_r = torch.bmm(Zs_red_T, Y)
136
+
137
+ # Regularization again
138
+ eye_r = torch.eye(D_red, device=dev, dtype=ZTZ_r.dtype).unsqueeze(0).expand(B, D_red, D_red)
139
+ ZTZ_r_reg = ZTZ_r + alpha * eye_r
140
+
141
+ try:
142
+ L_r = torch.linalg.cholesky(ZTZ_r_reg)
143
+ beta_r = torch.cholesky_solve(ZTY_r, L_r)
144
+ except:
145
+ beta_r = torch.linalg.solve(ZTZ_r_reg, ZTY_r)
146
+
147
+ resid_r = Y - torch.bmm(Zs_red, beta_r)
148
+ sigma_red = resid_r.pow(2).sum(dim=1) # (B, n) - 每个变量作为 Target 时的 Reduced 方差
149
+
150
+ # 5. 计算 Granger Causality
151
+ # F_{j->i} = ln(var_reduced_i / var_full_i)
152
+ # 注意:这里 sigma_red 和 sigma_full 都是 (B, n),表示 n 个变量作为 Target 时的方差
153
+ # 当前循环 j 是 Source。
154
+ # 所以 ratio[b, i] 代表:Source j 被剔除后,Target i 的方差变化
155
+
156
+ # 加上极小值防止除以0
157
+ ratio = (sigma_red + 1e-12) / (sigma_full + 1e-12)
158
+
159
+ # 结果填入 F 的第 j 列 (j -> all targets)
160
+ F_batch[:, :, j] = torch.log(ratio)
161
+
162
+ # 6. 后处理:对角线置 0 (无自因果)
163
+ idx = torch.arange(n, device=dev)
164
+ F_batch[:, idx, idx] = 0.0
165
+
166
+ # 修正可能的负值 (由于数值误差,完全无关时 log 可能出现微小负值)
167
+ F_batch = torch.relu(F_batch)
168
+
169
+ return F_batch
@@ -0,0 +1,120 @@
1
+ import numpy as np
2
+ import warnings
3
+
4
+ def compute_mvgc_cpu(X: np.ndarray, p: int, alpha: float = 0.01) -> np.ndarray:
5
+ """
6
+ CPU implementation of MVGC using NumPy (OLS with Ridge Regression).
7
+ Acts as a fallback or reference implementation.
8
+
9
+ Args:
10
+ X: (n_channels, n_time)
11
+ p: Lag order
12
+ alpha: Ridge regularization parameter
13
+ """
14
+ X = np.asarray(X)
15
+ n, T = X.shape
16
+ if T <= p:
17
+ raise ValueError(f'Time length {T} must be > order {p}')
18
+
19
+ # 1. Prepare Data
20
+ # Center the data
21
+ X_mean = X - X.mean(axis=1, keepdims=True)
22
+
23
+ # Build Y (Target) and Z (Design Matrix)
24
+ # Y: (n, N) where N = T - p
25
+ N = T - p
26
+ Y = X_mean[:, p:]
27
+
28
+ Z_list = []
29
+ for k in range(1, p + 1):
30
+ lag = X_mean[:, p-k : T-k]
31
+ Z_list.append(lag)
32
+ # Stack lags: Z shape (n*p, N)
33
+ Z = np.vstack(Z_list)
34
+
35
+ # 2. Full Model Regression
36
+ # Beta = (Z Z.T + alpha I)^-1 Z Y.T
37
+ # We solve (Z Z.T + alpha I) Beta = Z Y.T
38
+
39
+ ZZT = Z @ Z.T
40
+ ZYT = Z @ Y.T
41
+
42
+ # Ridge Regularization
43
+ D = n * p
44
+ ZZT_reg = ZZT + alpha * np.eye(D)
45
+
46
+ # Solve linear system
47
+ try:
48
+ # utilize symmetry for speed (equivalent to cholesky solve)
49
+ beta_full = np.linalg.solve(ZZT_reg, ZYT)
50
+ except np.linalg.LinAlgError:
51
+ # Fallback to pseudoinverse if somehow still singular
52
+ beta_full = np.linalg.pinv(ZZT_reg) @ ZYT
53
+
54
+ # Residuals
55
+ # Note: Beta shape is (n*p, n), so Z.T @ Beta gives (N, n), transpose to (n, N)
56
+ preds_full = beta_full.T @ Z
57
+ res_full = Y - preds_full
58
+ # Variance of residuals (diagonal of covariance matrix)
59
+ sig_full = np.var(res_full, axis=1, ddof=0)
60
+
61
+ # 3. Reduced Models (Loop over source j)
62
+ F = np.zeros((n, n))
63
+
64
+ # Cache ZZT parts to avoid recomputing?
65
+ # For CPU simple implementation, we just slice Z.
66
+
67
+ for j in range(n):
68
+ # We want to calculate causality j -> i (for all i)
69
+ # So we remove source j from the predictors Z
70
+
71
+ # Identify rows in Z to keep (remove lag rows corresponding to channel j)
72
+ # Z rows: 0..n-1 (lag 1), n..2n-1 (lag 2)...
73
+ keep_indices = []
74
+ for k in range(p):
75
+ # The row index for channel j at lag k+1 is: j + k*n
76
+ # Wait, construction of Z above:
77
+ # Z_list[0] is shape (n, N). It contains ch0_lag1, ch1_lag1, ...
78
+ # So Z structure is: [ch0_l1, ch1_l1, ..., ch0_l2, ...].T
79
+ # Correct index for channel c at lag k (1-based) is: c + (k-1)*n
80
+
81
+ # Let's verify standard flattening.
82
+ # Z = vstack(Z_list). Z_list[0] has n rows.
83
+ # Row x corresponds to channel (x % n)
84
+ pass
85
+
86
+ # Easier way: mask indices where (idx % n) != j
87
+ all_indices = np.arange(D)
88
+ mask = (all_indices % n) != j
89
+ idx_reduced = all_indices[mask]
90
+
91
+ Z_red = Z[idx_reduced, :]
92
+ D_red = len(idx_reduced)
93
+
94
+ # OLS Reduced
95
+ ZZT_r = Z_red @ Z_red.T
96
+ ZYT_r = Z_red @ Y.T
97
+
98
+ ZZT_r_reg = ZZT_r + alpha * np.eye(D_red)
99
+
100
+ try:
101
+ beta_r = np.linalg.solve(ZZT_r_reg, ZYT_r)
102
+ except np.linalg.LinAlgError:
103
+ beta_r = np.linalg.pinv(ZZT_r_reg) @ ZYT_r
104
+
105
+ preds_r = beta_r.T @ Z_red
106
+ res_r = Y - preds_r
107
+ sig_r = np.var(res_r, axis=1, ddof=0)
108
+
109
+ # 4. Granger Causality
110
+ # F_{j->i} = ln(sig_r_i / sig_full_i)
111
+ ratio = (sig_r + 1e-12) / (sig_full + 1e-12)
112
+ F[:, j] = np.log(ratio)
113
+
114
+ # Zero diagonal
115
+ np.fill_diagonal(F, 0.0)
116
+
117
+ # Clean up small negative values
118
+ F[F < 0] = 0.0
119
+
120
+ return F
@@ -0,0 +1,20 @@
1
+ # fast_mvgc/utils.py
2
+ import numpy as np
3
+
4
+ def segment_data(X: np.ndarray, window_size: int, step_size: int = None) -> np.ndarray:
5
+ """
6
+ Generic segmentation function.
7
+ Args:
8
+ X: (n_channels, total_time)
9
+ window_size: Length of each segment
10
+ step_size: Stride (default = window_size, i.e., no overlap)
11
+ Returns:
12
+ segments: (n_segments, n_channels, window_size)
13
+ """
14
+ if step_size is None:
15
+ step_size = window_size
16
+
17
+ n_channels, n_time = X.shape
18
+ starts = range(0, n_time - window_size + 1, step_size)
19
+ segments = [X[:, s:s+window_size] for s in starts]
20
+ return np.stack(segments, axis=0)
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.1
2
+ Name: pytorch-mvgc
3
+ Version: 0.1.0
4
+ Summary: A GPU-accelerated Multivariate Granger Causality implementation using PyTorch.
5
+ Home-page: https://github.com/yourusername/pytorch-mvgc
6
+ Author: nju_sit_hubc
7
+ Author-email: 2515094316@qq.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy
15
+ Requires-Dist: torch>=1.9.0
16
+ Requires-Dist: tqdm
17
+
18
+ # PyTorch-MVGC
19
+
20
+ [![PyPI version](https://badge.fury.io/py/pytorch-mvgc.svg)](https://badge.fury.io/py/pytorch-mvgc)
21
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
+
23
+ **PyTorch-MVGC** is a GPU-accelerated Python implementation of **Multivariate Granger Causality (MVGC)** analysis.
24
+
25
+ It is designed for high-dimensional time-series data (e.g., EEG, fMRI, MEA) where traditional CPU-based implementations are too slow. By leveraging PyTorch tensors and batched matrix operations, this library achieves significant speedups compared to standard NumPy or Matlab implementations.
26
+
27
+ ## Features
28
+
29
+ - **🚀 GPU Acceleration**: Fully implemented in PyTorch, supporting CUDA acceleration out-of-the-box.
30
+ - **⚡ Batch Processing**: Optimized for processing multiple trials or sliding windows simultaneously.
31
+ - **🔧 Numerical Stability**: Implements Ridge regularization and Pseudo-inverse solvers to handle ill-conditioned matrices common in high-channel neural recordings (e.g., 128-channel EEG).
32
+ - **🐍 Python Native**: A seamless replacement for the Matlab MVGC toolbox for Python pipelines.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install pytorch-mvgc
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ import numpy as np
44
+ import torch
45
+ from pytorch_mvgc import compute_mvgc
46
+
47
+ # 1. Generate sample data (3 channels, 1000 timepoints)
48
+ # System: Node 0 drives Node 1
49
+ n_time = 1000
50
+ X = np.zeros((3, n_time))
51
+ noise = 0.5 * np.random.randn(3, n_time)
52
+ for t in range(1, n_time):
53
+ X[0, t] = 0.8 * X[0, t-1] + noise[0, t]
54
+ X[1, t] = 0.5 * X[1, t-1] + 0.5 * X[0, t-1] + noise[1, t] # 0->1 Causal
55
+ X[2, t] = 0.7 * X[2, t-1] + noise[2, t]
56
+
57
+ # 2. Compute MVGC
58
+ # Returns matrix F where F[i, j] denotes causality j -> i
59
+ F_matrix = compute_mvgc(X, p=1, device='cuda')
60
+
61
+ print("MVGC Matrix:\n", np.round(F_matrix, 4))
62
+ # Expected: F[1, 0] should be significant (> 0)
63
+ ```
64
+
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ fast_mvgc/__init__.py
5
+ fast_mvgc/core.py
6
+ fast_mvgc/cpu_engine.py
7
+ fast_mvgc/utils.py
8
+ pytorch_mvgc.egg-info/PKG-INFO
9
+ pytorch_mvgc.egg-info/SOURCES.txt
10
+ pytorch_mvgc.egg-info/dependency_links.txt
11
+ pytorch_mvgc.egg-info/requires.txt
12
+ pytorch_mvgc.egg-info/top_level.txt
13
+ tests/test.py
@@ -0,0 +1,3 @@
1
+ numpy
2
+ torch>=1.9.0
3
+ tqdm
@@ -0,0 +1 @@
1
+ fast_mvgc
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="pytorch-mvgc",
5
+ version="0.1.0",
6
+ author="nju_sit_hubc",
7
+ author_email="2515094316@qq.com",
8
+ description="A GPU-accelerated Multivariate Granger Causality implementation using PyTorch.",
9
+ long_description=open("README.md").read(),
10
+ long_description_content_type="text/markdown",
11
+ url="https://github.com/yourusername/pytorch-mvgc",
12
+ packages=find_packages(),
13
+ classifiers=[
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ ],
18
+ python_requires='>=3.6',
19
+ install_requires=[
20
+ "numpy",
21
+ "torch>=1.9.0", # 指定最低版本
22
+ "tqdm"
23
+ ],
24
+ )
@@ -0,0 +1,49 @@
1
+ import numpy as np
2
+ import torch
3
+ from fast_mvgc.core import compute_mvgc # 确保引用了上面的新文件
4
+
5
+ def test_causality_fix():
6
+ print("=== 开始 MVGC 正确性测试 ===")
7
+
8
+ # 1. 生成标准的 VAR(1) 3通道模型
9
+ # 0 -> 1 (强因果)
10
+ # 2 独立
11
+ np.random.seed(42)
12
+ n_time = 2000
13
+ X = np.zeros((3, n_time))
14
+ noise = np.random.randn(3, n_time) # 标准正态噪声
15
+
16
+ print("生成数据中: Node 0 -> Node 1 ...")
17
+ for t in range(1, n_time):
18
+ X[0, t] = 0.8 * X[0, t-1] + noise[0, t]
19
+ # X[1] 依赖 X[0] 的过去值
20
+ X[1, t] = 0.5 * X[1, t-1] + 0.5 * X[0, t-1] + noise[1, t]
21
+ X[2, t] = 0.7 * X[2, t-1] + noise[2, t]
22
+
23
+ # 2. 计算 MVGC
24
+ # 注意:alpha (正则化) 对于数值稳定性至关重要
25
+ try:
26
+ F = compute_mvgc(X, p=1, alpha=0.01, device='cuda')
27
+ except Exception as e:
28
+ print(f"GPU计算失败,尝试CPU... 错误: {e}")
29
+ F = compute_mvgc(X, p=1, alpha=0.01, device='cpu')
30
+
31
+ print("\n计算结果 F (F[i, j] 表示 j->i):")
32
+ print(np.round(F, 4))
33
+
34
+ # 3. 验证
35
+ # 我们期望 F[1, 0] (0->1) 是显著的
36
+ val_0_to_1 = F[1, 0]
37
+ val_1_to_0 = F[0, 1]
38
+
39
+ print(f"\n关键路径检查:")
40
+ print(f"0 -> 1 (预期 > 0.05): {val_0_to_1:.4f}", "✅ PASS" if val_0_to_1 > 0.05 else "❌ FAIL")
41
+ print(f"1 -> 0 (预期 < 0.05): {val_1_to_0:.4f}", "✅ PASS" if val_1_to_0 < 0.05 else "❌ FAIL (False Positive)")
42
+
43
+ if np.isnan(F).any():
44
+ print("\n❌ 警告: 结果中包含 NaN!")
45
+ else:
46
+ print("\n✅ 结果数值正常 (无 NaN).")
47
+
48
+ if __name__ == "__main__":
49
+ test_causality_fix()