corticalfields 0.2.2__tar.gz → 0.2.3__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.
- {corticalfields-0.2.2/src/corticalfields.egg-info → corticalfields-0.2.3}/PKG-INFO +1 -1
- {corticalfields-0.2.2 → corticalfields-0.2.3}/pyproject.toml +1 -1
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/__init__.py +7 -7
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/backends.py +181 -190
- {corticalfields-0.2.2 → corticalfields-0.2.3/src/corticalfields.egg-info}/PKG-INFO +1 -1
- {corticalfields-0.2.2 → corticalfields-0.2.3}/LICENSE +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/README.md +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/setup.cfg +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/_pointcloud_legacy.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/analysis/__init__.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/analysis/bayesian.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/analysis/eda_qc.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/analysis/normative.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/analysis/stats.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/asymmetry.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/bayes_viz.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/bayesian.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/brainplots.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/datasets.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/distance_stats.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/eda_qc.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/features.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/functional_maps.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/graphs.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/hippocampus.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/kernels.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/normative.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/__init__.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/deep/__init__.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/deep/diffusion_net.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/deep/egnn.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/functional_maps.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/morphometrics.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/registration.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/spectral.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/transport.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/viz.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/spectral.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/subcortical.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/surface.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/surprise.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/transport.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/utils.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz/__init__.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz/bayes.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz/brainplots.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz/graph_viz.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz/subcortical.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz/viz.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/viz_subcortical.py +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields.egg-info/SOURCES.txt +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields.egg-info/dependency_links.txt +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields.egg-info/requires.txt +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields.egg-info/top_level.txt +0 -0
- {corticalfields-0.2.2 → corticalfields-0.2.3}/tests/test_core.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: corticalfields
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Spectral cortical and subcortical analysis with statistical testing (RSA, CCA, PLS, PERMANOVA, TFCE, NBS, laterality classification), on meshes and point clouds — Laplace-Beltrami decomposition, atlas-free asymmetry, GPU-accelerated optimal transport, hippocampal subfield analysis (HippUnfold), ShapeDNA/BrainPrint spectral fingerprinting, geometric deep learning, Bayesian inference, and normative modeling for structural neuroimaging.
|
|
5
5
|
Author-email: rdneuro <r.debona@ufrj.br>
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "corticalfields"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Spectral cortical and subcortical analysis with statistical testing (RSA, CCA, PLS, PERMANOVA, TFCE, NBS, laterality classification), on meshes and point clouds — Laplace-Beltrami decomposition, atlas-free asymmetry, GPU-accelerated optimal transport, hippocampal subfield analysis (HippUnfold), ShapeDNA/BrainPrint spectral fingerprinting, geometric deep learning, Bayesian inference, and normative modeling for structural neuroimaging."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -6,7 +6,7 @@ functional maps, optimal-transport distances, and information-theoretic
|
|
|
6
6
|
surprise maps on brain surface meshes. Designed for structural MRI (T1w)
|
|
7
7
|
data in clinical neuroimaging, with emphasis on epilepsy (MTLE-HS).
|
|
8
8
|
|
|
9
|
-
Subpackages (v0.2.
|
|
9
|
+
Subpackages (v0.2.3)
|
|
10
10
|
---------------------
|
|
11
11
|
analysis : Statistical analysis & modeling
|
|
12
12
|
analysis.stats — MCC, GLM, PERMANOVA, CCA/PLS, RSA, NBS,
|
|
@@ -29,7 +29,7 @@ surface, subcortical, hippocampus, spectral, kernels, surprise, features,
|
|
|
29
29
|
graphs, distance_stats, asymmetry, transport, functional_maps, datasets, utils
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
__version__ = "0.2.
|
|
32
|
+
__version__ = "0.2.3"
|
|
33
33
|
__author__ = "rdneuro"
|
|
34
34
|
|
|
35
35
|
|
|
@@ -100,7 +100,7 @@ def __getattr__(name: str):
|
|
|
100
100
|
"to_latex_table": ("corticalfields.analysis.bayesian", "to_latex_table"),
|
|
101
101
|
"elicit_prior": ("corticalfields.analysis.bayesian", "elicit_prior"),
|
|
102
102
|
"enigma_informed_prior": ("corticalfields.analysis.bayesian", "enigma_informed_prior"),
|
|
103
|
-
# ── analysis.stats
|
|
103
|
+
# ── analysis.stats ─────────────────────────────────
|
|
104
104
|
"StatResult": ("corticalfields.analysis.stats", "StatResult"),
|
|
105
105
|
"MultipleComparisonResult": ("corticalfields.analysis.stats", "MultipleComparisonResult"),
|
|
106
106
|
"fdr_correction": ("corticalfields.analysis.stats", "fdr_correction"),
|
|
@@ -128,7 +128,7 @@ def __getattr__(name: str):
|
|
|
128
128
|
"conformal_prediction_intervals": ("corticalfields.analysis.stats", "conformal_prediction_intervals"),
|
|
129
129
|
"bootstrap_gpu": ("corticalfields.analysis.stats", "bootstrap_gpu"),
|
|
130
130
|
"permutation_matrix_gpu": ("corticalfields.analysis.stats", "permutation_matrix_gpu"),
|
|
131
|
-
# ── graphs.py
|
|
131
|
+
# ── graphs.py ──────────────────────────────────
|
|
132
132
|
"GraphResult": ("corticalfields.graphs", "GraphResult"),
|
|
133
133
|
"GraphMetrics": ("corticalfields.graphs", "GraphMetrics"),
|
|
134
134
|
"morphometric_similarity_network": ("corticalfields.graphs", "morphometric_similarity_network"),
|
|
@@ -150,7 +150,7 @@ def __getattr__(name: str):
|
|
|
150
150
|
"BrainGraphGCN": ("corticalfields.graphs", "BrainGraphGCN"),
|
|
151
151
|
"spectral_morphometric_pipeline": ("corticalfields.graphs", "spectral_morphometric_pipeline"),
|
|
152
152
|
"YEO7_COLORS": ("corticalfields.graphs", "YEO7_COLORS"),
|
|
153
|
-
# ── viz.graph_viz (NEW v0.2.
|
|
153
|
+
# ── viz.graph_viz (NEW v0.2.3) ───────────────────────────────────
|
|
154
154
|
"plot_glass_brain_connectome": ("corticalfields.viz.graph_viz", "plot_glass_brain_connectome"),
|
|
155
155
|
"plot_adjacency_matrix": ("corticalfields.viz.graph_viz", "plot_adjacency_matrix"),
|
|
156
156
|
"plot_edge_weight_distribution": ("corticalfields.viz.graph_viz", "plot_edge_weight_distribution"),
|
|
@@ -310,7 +310,7 @@ __all__ = [
|
|
|
310
310
|
"estimate_n_eigenpairs", "gc_gpu", "vram_report", "vram_guard",
|
|
311
311
|
"fetch_toy_dataset", "clear_toy_dataset",
|
|
312
312
|
"load_example_surface", "ToyDataset",
|
|
313
|
-
# ── graphs (v0.2.
|
|
313
|
+
# ── graphs (v0.2.3) ──────────────────────────────────────────────
|
|
314
314
|
"GraphResult", "GraphMetrics",
|
|
315
315
|
"morphometric_similarity_network", "spectral_similarity_network",
|
|
316
316
|
"mind_divergence_network", "wasserstein_spectral_network",
|
|
@@ -321,7 +321,7 @@ __all__ = [
|
|
|
321
321
|
"persistent_homology", "nbs_morphometric", "group_metric_comparison",
|
|
322
322
|
"to_pyg_data", "build_population_graph", "BrainGraphGCN",
|
|
323
323
|
"spectral_morphometric_pipeline", "YEO7_COLORS",
|
|
324
|
-
# ── viz.graph_viz (v0.2.
|
|
324
|
+
# ── viz.graph_viz (v0.2.3) ────────────────────────────────────────
|
|
325
325
|
"plot_glass_brain_connectome", "plot_adjacency_matrix",
|
|
326
326
|
"plot_edge_weight_distribution", "plot_laplacian_spectrum",
|
|
327
327
|
"plot_graph_layout", "plot_rich_club_curve", "plot_nbs_result",
|
|
@@ -439,8 +439,6 @@ def _eigsh_torch(
|
|
|
439
439
|
3. **ChFSI outer loop** (typically 15–40 iterations):
|
|
440
440
|
a. Apply degree-``d`` Chebyshev polynomial filter via 3-term
|
|
441
441
|
SpMV recurrence (no matrix assembly — only matvecs).
|
|
442
|
-
The filter amplifies components in ``[0, λ_cutoff]`` and
|
|
443
|
-
damps the rest, concentrating V into the target eigenspace.
|
|
444
442
|
b. Orthogonalise: ``V, _ = QR(filtered_V)``.
|
|
445
443
|
c. Rayleigh–Ritz: ``H = Vᵀ A V`` (m×m dense eigh).
|
|
446
444
|
d. Convergence check: max residual norm < tol.
|
|
@@ -448,11 +446,28 @@ def _eigsh_torch(
|
|
|
448
446
|
|
|
449
447
|
Mixed precision
|
|
450
448
|
---------------
|
|
451
|
-
SpMV and the Chebyshev filter run in **float32** for ~2× throughput
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
449
|
+
SpMV and the Chebyshev filter run in **float32** for ~2× throughput.
|
|
450
|
+
The Rayleigh–Ritz projection (small m×m) is accumulated in **float64**.
|
|
451
|
+
|
|
452
|
+
GPU stability
|
|
453
|
+
-------------
|
|
454
|
+
The NVIDIA driver's GPU watchdog timer can trigger a PCIe bus hang
|
|
455
|
+
if the GPU command queue grows unboundedly without CPU sync points.
|
|
456
|
+
This function inserts ``torch.cuda.synchronize()`` at three levels:
|
|
457
|
+
|
|
458
|
+
- **After each Chebyshev filter pass** (every ~12 SpMV launches)
|
|
459
|
+
- **After every Rayleigh–Ritz step** (before convergence check)
|
|
460
|
+
- **Periodic ``empty_cache()``** every 5 outer iterations
|
|
461
|
+
|
|
462
|
+
These add ~1 ms overhead per sync but prevent the driver from
|
|
463
|
+
interpreting a deep async queue as a hung GPU. Critical on X570
|
|
464
|
+
chipsets (e.g. ASUS Crosshair Dark Hero) with drivers ≥ 560.x
|
|
465
|
+
and CUDA ≥ 12.x, where failed GPU resets cascade to PCIe bus
|
|
466
|
+
hangs and apparent filesystem loss.
|
|
467
|
+
|
|
468
|
+
All operations run inside ``torch.no_grad()`` to prevent autograd
|
|
469
|
+
from tracking the ~500 SpMV operations, which would otherwise
|
|
470
|
+
build a 10+ GB computation graph in RAM.
|
|
456
471
|
|
|
457
472
|
VRAM budget (N = 150k, k = 300, m = 330)
|
|
458
473
|
------------------------------------------
|
|
@@ -461,29 +476,10 @@ def _eigsh_torch(
|
|
|
461
476
|
- Chebyshev temps: 2 × N × m × 4 = ~396 MB (Y_prev, Y_curr)
|
|
462
477
|
- Rayleigh–Ritz H: m × m × 8 = ~0.9 MB (float64)
|
|
463
478
|
- **Peak total: ~609 MB** — fits in 8 GB VRAM with margin.
|
|
464
|
-
- Previous lobpcg: 9 × N × k × 8 = ~3.2 GB — 5× higher.
|
|
465
|
-
|
|
466
|
-
Performance (RTX 3090, N=150k, k=300)
|
|
467
|
-
-------------------------------------
|
|
468
|
-
- ChFSI (this): ~10–25 s (degree=12, 15–30 outer iters)
|
|
469
|
-
- torch.lobpcg (old): ~60–120 s
|
|
470
|
-
- CuPy eigsh: ~10–30 s (Thick-Restart Lanczos)
|
|
471
|
-
- scipy eigsh: ~30–120 s (ARPACK shift-invert)
|
|
472
|
-
|
|
473
|
-
Both individual and batch processing use this function. In batch
|
|
474
|
-
mode, ``gc_gpu()`` is called between subjects by the caller
|
|
475
|
-
(``_process_single_subject`` in ``spectral.py``), which frees
|
|
476
|
-
VRAM for the next subject.
|
|
477
479
|
|
|
478
480
|
Parameters
|
|
479
481
|
----------
|
|
480
|
-
L
|
|
481
|
-
M : scipy.sparse.spmatrix (N, N) — diagonal lumped mass matrix
|
|
482
|
-
k : int — number of smallest eigenpairs to compute
|
|
483
|
-
tol : float — convergence tolerance on max residual norm
|
|
484
|
-
maxiter : int — maximum ChFSI outer iterations
|
|
485
|
-
dtype : str — ``"float32"`` or ``"float64"`` for SpMV precision;
|
|
486
|
-
Rayleigh–Ritz always uses float64 regardless.
|
|
482
|
+
L, M, k, tol, maxiter, dtype : see ``eigsh_solve``
|
|
487
483
|
|
|
488
484
|
Returns
|
|
489
485
|
-------
|
|
@@ -492,28 +488,25 @@ def _eigsh_torch(
|
|
|
492
488
|
|
|
493
489
|
References
|
|
494
490
|
----------
|
|
495
|
-
[1] Y. Zhou, Y. Saad
|
|
496
|
-
|
|
497
|
-
subspace iteration", J. Comput. Phys. 219 (2006) 172–184.
|
|
498
|
-
[2] A.V. Knyazev, "Toward the optimal preconditioned eigensolver:
|
|
499
|
-
LOBPCG", SIAM J. Sci. Comput. 23 (2001) 517–541.
|
|
491
|
+
[1] Y. Zhou, Y. Saad et al., "Chebyshev-filtered subspace iteration",
|
|
492
|
+
J. Comput. Phys. 219 (2006) 172–184.
|
|
500
493
|
"""
|
|
501
494
|
import torch
|
|
502
495
|
|
|
503
496
|
# ── Precision setup ─────────────────────────────────────────────
|
|
504
|
-
|
|
505
|
-
spmv_np_dtype = np.float32 if dtype != "float64" else np.float32
|
|
497
|
+
spmv_np_dtype = np.float32
|
|
506
498
|
spmv_torch_dtype = torch.float32
|
|
507
499
|
ritz_torch_dtype = torch.float64
|
|
508
500
|
|
|
509
501
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
502
|
+
is_cuda = device.type == "cuda"
|
|
510
503
|
N = L.shape[0]
|
|
511
504
|
|
|
512
505
|
# ChFSI hyperparameters — calibrated for LBO meshes
|
|
513
|
-
EXTRA = min(30, max(10, k // 10))
|
|
514
|
-
m = k + EXTRA
|
|
515
|
-
CHEB_DEGREE = 12
|
|
516
|
-
POWER_ITERS = 30
|
|
506
|
+
EXTRA = min(30, max(10, k // 10))
|
|
507
|
+
m = k + EXTRA
|
|
508
|
+
CHEB_DEGREE = 12
|
|
509
|
+
POWER_ITERS = 30
|
|
517
510
|
|
|
518
511
|
logger.info(
|
|
519
512
|
" torch ChFSI eigensolver: N=%d, k=%d, m=%d, degree=%d, "
|
|
@@ -524,11 +517,11 @@ def _eigsh_torch(
|
|
|
524
517
|
# ── Step 1: Generalised → standard via M^{−½} (on CPU) ─────────
|
|
525
518
|
M_diag = np.array(M.diagonal()).ravel().astype(np.float64)
|
|
526
519
|
M_diag = np.maximum(M_diag, 1e-16)
|
|
527
|
-
M_inv_sqrt_np =
|
|
520
|
+
M_inv_sqrt_np = 1.0 / np.sqrt(M_diag) # float64 for precision
|
|
528
521
|
|
|
529
522
|
D_sp = sp.diags(M_inv_sqrt_np.astype(spmv_np_dtype), format="csc")
|
|
530
523
|
A_cpu = (D_sp @ L.tocsc().astype(spmv_np_dtype) @ D_sp).tocsr()
|
|
531
|
-
del D_sp
|
|
524
|
+
del D_sp
|
|
532
525
|
|
|
533
526
|
# ── Helper: scipy CSR → torch sparse CSR on device ──────────────
|
|
534
527
|
def _scipy_to_torch_csr(mat_csr):
|
|
@@ -540,161 +533,159 @@ def _eigsh_torch(
|
|
|
540
533
|
dtype=spmv_torch_dtype,
|
|
541
534
|
)
|
|
542
535
|
|
|
543
|
-
# ── Helper: sparse matvec A @ X on GPU ──────────────────────────
|
|
544
|
-
def _spmm(A_t, X):
|
|
545
|
-
"""Sparse × dense matrix multiply, shape (N, m)."""
|
|
546
|
-
return torch.sparse.mm(A_t, X)
|
|
547
|
-
|
|
548
536
|
try:
|
|
549
|
-
# ── Step 2: Transfer A to GPU ───────────────────────────────
|
|
550
537
|
A_t = _scipy_to_torch_csr(A_cpu)
|
|
551
|
-
del A_cpu
|
|
552
|
-
|
|
553
|
-
#
|
|
554
|
-
#
|
|
555
|
-
#
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
v = _spmm(A_t, v)
|
|
538
|
+
del A_cpu
|
|
539
|
+
|
|
540
|
+
# Everything below is inference — no autograd needed.
|
|
541
|
+
# torch.no_grad() prevents building a massive computation graph
|
|
542
|
+
# from the ~500 SpMV calls (would leak ~10+ GB of RAM otherwise).
|
|
543
|
+
with torch.no_grad():
|
|
544
|
+
|
|
545
|
+
# ── Step 2: Estimate λ_max via power iteration ──────────
|
|
546
|
+
torch.manual_seed(42)
|
|
547
|
+
v = torch.randn(N, 1, dtype=spmv_torch_dtype, device=device)
|
|
562
548
|
v = v / v.norm()
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
549
|
+
for pi in range(POWER_ITERS):
|
|
550
|
+
v = torch.sparse.mm(A_t, v)
|
|
551
|
+
v = v / v.norm()
|
|
552
|
+
# Sync every 10 iters to keep driver watchdog happy
|
|
553
|
+
if is_cuda and pi % 10 == 9:
|
|
554
|
+
torch.cuda.synchronize()
|
|
555
|
+
|
|
556
|
+
# Rayleigh quotient in float64 for a precise λ_max
|
|
557
|
+
v64 = v.to(ritz_torch_dtype)
|
|
558
|
+
Av64 = torch.sparse.mm(A_t, v).to(ritz_torch_dtype)
|
|
559
|
+
lambda_max = float((v64.T @ Av64).item()) * 1.05
|
|
560
|
+
del v, v64, Av64
|
|
561
|
+
if is_cuda:
|
|
562
|
+
torch.cuda.synchronize()
|
|
563
|
+
logger.info(" λ_max ≈ %.4f", lambda_max)
|
|
564
|
+
|
|
565
|
+
# ── Step 3: ChFSI outer loop ────────────────────────────
|
|
566
|
+
torch.manual_seed(42)
|
|
567
|
+
V = torch.randn(N, m, dtype=spmv_torch_dtype, device=device)
|
|
568
|
+
V, _ = torch.linalg.qr(V)
|
|
569
|
+
|
|
570
|
+
lambda_cut = lambda_max * (2.0 * m / N)
|
|
571
|
+
lambda_cut = max(lambda_cut, lambda_max * 0.01)
|
|
572
|
+
|
|
573
|
+
converged = False
|
|
574
|
+
max_res = float("inf")
|
|
575
|
+
|
|
576
|
+
for outer in range(maxiter):
|
|
577
|
+
|
|
578
|
+
# ── Chebyshev filter: T_d(scaled_A) @ V ─────────────
|
|
579
|
+
e = (lambda_max - lambda_cut) / 2.0
|
|
580
|
+
c_coeff = (lambda_max + lambda_cut) / 2.0
|
|
581
|
+
|
|
582
|
+
if e < 1e-10:
|
|
583
|
+
e = lambda_max * 0.5
|
|
584
|
+
c_coeff = lambda_max * 0.5
|
|
585
|
+
|
|
586
|
+
sigma = e / c_coeff if abs(c_coeff) > 1e-12 else 1.0
|
|
587
|
+
sigma1 = sigma
|
|
588
|
+
|
|
589
|
+
# Y₁ = (σ₁/e) · (A·V − c·V)
|
|
590
|
+
AV = torch.sparse.mm(A_t, V)
|
|
591
|
+
Y_prev = V
|
|
592
|
+
Y_curr = (sigma1 / e) * (AV - c_coeff * V)
|
|
593
|
+
del AV
|
|
594
|
+
|
|
595
|
+
for d in range(2, CHEB_DEGREE + 1):
|
|
596
|
+
sigma_new = 1.0 / (2.0 / sigma - sigma1)
|
|
597
|
+
AY = torch.sparse.mm(A_t, Y_curr)
|
|
598
|
+
Y_next = (2.0 * sigma_new / e) * (AY - c_coeff * Y_curr) \
|
|
599
|
+
- (sigma * sigma_new) * Y_prev
|
|
600
|
+
Y_prev = Y_curr
|
|
601
|
+
Y_curr = Y_next
|
|
602
|
+
sigma = sigma_new
|
|
603
|
+
del AY
|
|
604
|
+
# Mid-filter sync every 4 SpMV to prevent driver timeout
|
|
605
|
+
if is_cuda and d % 4 == 0:
|
|
606
|
+
torch.cuda.synchronize()
|
|
607
|
+
|
|
608
|
+
del Y_prev
|
|
609
|
+
|
|
610
|
+
# ── SYNC: end of Chebyshev filter ───────────────────
|
|
611
|
+
if is_cuda:
|
|
612
|
+
torch.cuda.synchronize()
|
|
613
|
+
|
|
614
|
+
# ── Orthogonalise filtered subspace ─────────────────
|
|
615
|
+
V, _ = torch.linalg.qr(Y_curr)
|
|
616
|
+
del Y_curr
|
|
617
|
+
|
|
618
|
+
# ── Rayleigh–Ritz in float64 ────────────────────────
|
|
619
|
+
AV = torch.sparse.mm(A_t, V)
|
|
620
|
+
V64 = V.to(ritz_torch_dtype)
|
|
621
|
+
AV64 = AV.to(ritz_torch_dtype)
|
|
622
|
+
del AV
|
|
623
|
+
|
|
624
|
+
H = V64.T @ AV64
|
|
625
|
+
H = 0.5 * (H + H.T)
|
|
626
|
+
ritz_vals, ritz_vecs = torch.linalg.eigh(H)
|
|
627
|
+
|
|
628
|
+
# ── Convergence check ───────────────────────────────
|
|
629
|
+
eigvecs_m = V64 @ ritz_vecs[:, :k]
|
|
630
|
+
Aeigvecs = AV64 @ ritz_vecs[:, :k]
|
|
631
|
+
residuals = Aeigvecs - eigvecs_m * ritz_vals[:k].unsqueeze(0)
|
|
632
|
+
max_res = float(residuals.norm(dim=0).max().item())
|
|
633
|
+
|
|
634
|
+
del eigvecs_m, Aeigvecs, residuals, V64, AV64
|
|
635
|
+
|
|
636
|
+
# ── SYNC: end of Ritz step ──────────────────────────
|
|
637
|
+
if is_cuda:
|
|
638
|
+
torch.cuda.synchronize()
|
|
639
|
+
|
|
640
|
+
if outer % 5 == 0 or max_res < tol:
|
|
641
|
+
logger.info(
|
|
642
|
+
" ChFSI iter %2d: max_residual=%.2e, λ_cut=%.4f",
|
|
643
|
+
outer, max_res, lambda_cut,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
if max_res < tol:
|
|
647
|
+
converged = True
|
|
648
|
+
break
|
|
649
|
+
|
|
650
|
+
# Update subspace
|
|
651
|
+
V = V @ ritz_vecs[:, :m].to(spmv_torch_dtype)
|
|
652
|
+
|
|
653
|
+
# Refine λ_cut from current Ritz estimates
|
|
654
|
+
if ritz_vals.shape[0] > k:
|
|
655
|
+
lambda_cut = float(ritz_vals[m - 1].item()) * 1.5
|
|
656
|
+
lambda_cut = min(lambda_cut, lambda_max * 0.95)
|
|
657
|
+
|
|
658
|
+
# Periodic VRAM housekeeping — prevents fragmentation
|
|
659
|
+
if is_cuda and outer % 5 == 4:
|
|
660
|
+
torch.cuda.empty_cache()
|
|
661
|
+
|
|
662
|
+
# ── end outer loop ──────────────────────────────────────
|
|
663
|
+
|
|
664
|
+
if not converged:
|
|
665
|
+
logger.warning(
|
|
666
|
+
" ChFSI did not converge in %d iters "
|
|
667
|
+
"(max_residual=%.2e > tol=%.1e). "
|
|
668
|
+
"Results may be approximate.",
|
|
669
|
+
maxiter, max_res, tol,
|
|
659
670
|
)
|
|
660
671
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
maxiter, max_res, tol,
|
|
679
|
-
)
|
|
680
|
-
|
|
681
|
-
# ── Step 5: Extract final eigenpairs ────────────────────────
|
|
682
|
-
evals_t = ritz_vals[:k] # (k,) f64 on GPU
|
|
683
|
-
# Final eigenvectors: V @ ritz_vecs[:, :k] in float64
|
|
684
|
-
evecs_t = V.to(ritz_torch_dtype) @ ritz_vecs[:, :k] # (N, k) f64
|
|
685
|
-
|
|
686
|
-
# ── Step 6: Undo mass-matrix transform: φ = M^{−½} · y ────
|
|
687
|
-
M_inv_sqrt_t = torch.from_numpy(
|
|
688
|
-
M_inv_sqrt_np
|
|
689
|
-
).to(dtype=ritz_torch_dtype, device=device).unsqueeze(1) # (N, 1)
|
|
690
|
-
|
|
691
|
-
evecs_t = evecs_t * M_inv_sqrt_t # (N, k) f64
|
|
692
|
-
del M_inv_sqrt_t, V, ritz_vals, ritz_vecs
|
|
693
|
-
|
|
694
|
-
# Move to CPU
|
|
695
|
-
evals = evals_t.cpu().numpy().astype(np.float64)
|
|
696
|
-
evecs = evecs_t.cpu().numpy().astype(np.float64)
|
|
697
|
-
del evals_t, evecs_t
|
|
672
|
+
# ── Extract final eigenpairs ────────────────────────────
|
|
673
|
+
evals_t = ritz_vals[:k]
|
|
674
|
+
evecs_t = V.to(ritz_torch_dtype) @ ritz_vecs[:, :k]
|
|
675
|
+
|
|
676
|
+
M_inv_sqrt_t = torch.from_numpy(
|
|
677
|
+
M_inv_sqrt_np
|
|
678
|
+
).to(dtype=ritz_torch_dtype, device=device).unsqueeze(1)
|
|
679
|
+
|
|
680
|
+
evecs_t = evecs_t * M_inv_sqrt_t
|
|
681
|
+
del M_inv_sqrt_t, V, ritz_vals, ritz_vecs
|
|
682
|
+
|
|
683
|
+
# Move to CPU
|
|
684
|
+
if is_cuda:
|
|
685
|
+
torch.cuda.synchronize()
|
|
686
|
+
evals = evals_t.cpu().numpy().astype(np.float64)
|
|
687
|
+
evecs = evecs_t.cpu().numpy().astype(np.float64)
|
|
688
|
+
del evals_t, evecs_t
|
|
698
689
|
|
|
699
690
|
finally:
|
|
700
691
|
# Guarantee GPU cleanup even on error — critical for batch mode
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: corticalfields
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Spectral cortical and subcortical analysis with statistical testing (RSA, CCA, PLS, PERMANOVA, TFCE, NBS, laterality classification), on meshes and point clouds — Laplace-Beltrami decomposition, atlas-free asymmetry, GPU-accelerated optimal transport, hippocampal subfield analysis (HippUnfold), ShapeDNA/BrainPrint spectral fingerprinting, geometric deep learning, Bayesian inference, and normative modeling for structural neuroimaging.
|
|
5
5
|
Author-email: rdneuro <r.debona@ufrj.br>
|
|
6
6
|
License: MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/deep/__init__.py
RENAMED
|
File without changes
|
{corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/deep/diffusion_net.py
RENAMED
|
File without changes
|
|
File without changes
|
{corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/functional_maps.py
RENAMED
|
File without changes
|
{corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields/pointcloud/morphometrics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{corticalfields-0.2.2 → corticalfields-0.2.3}/src/corticalfields.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|