torchspin 0.3.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.
- torchspin/__init__.py +363 -0
- torchspin/_cardamom_istos.py +327 -0
- torchspin/_cardamom_propagatedm.py +413 -0
- torchspin/_cardamom_utils.py +113 -0
- torchspin/_compile.py +100 -0
- torchspin/_linalg.py +140 -0
- torchspin/angmom.py +742 -0
- torchspin/autograd.py +557 -0
- torchspin/autoguess.py +342 -0
- torchspin/batch.py +225 -0
- torchspin/blochsteady.py +274 -0
- torchspin/cardamom.py +552 -0
- torchspin/chili.py +932 -0
- torchspin/chili_sle.py +1233 -0
- torchspin/constants.py +88 -0
- torchspin/convspec.py +148 -0
- torchspin/ctafft.py +91 -0
- torchspin/curry.py +241 -0
- torchspin/data/FourierSeriesCoefficients.txt +89 -0
- torchspin/data/GaussianCascadeCoefficients.txt +56 -0
- torchspin/data/isotopedata.txt +416 -0
- torchspin/data/spacegroups.txt +637 -0
- torchspin/dataproc.py +361 -0
- torchspin/dipbackground.py +69 -0
- torchspin/dipkernel.py +102 -0
- torchspin/diptensor.py +138 -0
- torchspin/endorfrq.py +198 -0
- torchspin/endorfrq_perturb.py +302 -0
- torchspin/eprload.py +1538 -0
- torchspin/eprsave.py +165 -0
- torchspin/esfit.py +2427 -0
- torchspin/evolve.py +274 -0
- torchspin/ewrls.py +124 -0
- torchspin/excitation.py +107 -0
- torchspin/exciteprofile.py +132 -0
- torchspin/experiment.py +323 -0
- torchspin/exponfit.py +177 -0
- torchspin/fastmotion.py +182 -0
- torchspin/fdaxis.py +57 -0
- torchspin/fitgui.py +350 -0
- torchspin/garlic.py +760 -0
- torchspin/ham.py +145 -0
- torchspin/ham_cf.py +115 -0
- torchspin/ham_ee.py +124 -0
- torchspin/ham_ez.py +119 -0
- torchspin/ham_ezho.py +403 -0
- torchspin/ham_hf.py +122 -0
- torchspin/ham_nn.py +108 -0
- torchspin/ham_nq.py +100 -0
- torchspin/ham_nz.py +136 -0
- torchspin/ham_oz.py +115 -0
- torchspin/ham_so.py +118 -0
- torchspin/ham_zf.py +254 -0
- torchspin/hamsymm.py +723 -0
- torchspin/initstate.py +158 -0
- torchspin/isotopologues.py +296 -0
- torchspin/levels.py +189 -0
- torchspin/levelsplot.py +363 -0
- torchspin/lineshape.py +715 -0
- torchspin/lpsvd.py +219 -0
- torchspin/makespec.py +87 -0
- torchspin/mdhmm.py +573 -0
- torchspin/mdload.py +788 -0
- torchspin/mdtraj2oripot.py +90 -0
- torchspin/ml.py +377 -0
- torchspin/mlpsvd.py +297 -0
- torchspin/nucdata.py +310 -0
- torchspin/nucfrq2d.py +228 -0
- torchspin/orca2torchspin.py +1198 -0
- torchspin/ordering.py +78 -0
- torchspin/oripotentialplot.py +246 -0
- torchspin/orisel.py +176 -0
- torchspin/pepper.py +2019 -0
- torchspin/pepper_autograd.py +613 -0
- torchspin/photoselect.py +174 -0
- torchspin/plegendre.py +131 -0
- torchspin/propint.py +127 -0
- torchspin/pulse.py +652 -0
- torchspin/py.typed +0 -0
- torchspin/rapidscan2spc.py +101 -0
- torchspin/resfields.py +337 -0
- torchspin/resfields_batch.py +446 -0
- torchspin/resfields_eig.py +250 -0
- torchspin/resfields_perturb.py +706 -0
- torchspin/resfreqs_matrix.py +344 -0
- torchspin/resfreqs_perturb.py +299 -0
- torchspin/resonator.py +310 -0
- torchspin/resonatorprofile.py +85 -0
- torchspin/rfmixer.py +147 -0
- torchspin/rotations.py +87 -0
- torchspin/rotutils.py +880 -0
- torchspin/saffron.py +1523 -0
- torchspin/saffron_pathways.py +92 -0
- torchspin/saffron_peaks.py +580 -0
- torchspin/saffron_thyme.py +472 -0
- torchspin/salt.py +680 -0
- torchspin/sigeq.py +71 -0
- torchspin/signalprocessing.py +226 -0
- torchspin/sitetransforms.py +254 -0
- torchspin/sphgrid.py +470 -0
- torchspin/spidyan.py +1508 -0
- torchspin/spinladder.py +161 -0
- torchspin/spinops.py +265 -0
- torchspin/spinsystem.py +1427 -0
- torchspin/stackplot.py +167 -0
- torchspin/stev.py +283 -0
- torchspin/stochtraj_diffusion.py +494 -0
- torchspin/stochtraj_jump.py +195 -0
- torchspin/strainwidth.py +727 -0
- torchspin/transmitter.py +89 -0
- torchspin/utils.py +605 -0
- torchspin-0.3.0.dist-info/METADATA +267 -0
- torchspin-0.3.0.dist-info/RECORD +116 -0
- torchspin-0.3.0.dist-info/WHEEL +5 -0
- torchspin-0.3.0.dist-info/licenses/LICENSE.md +22 -0
- torchspin-0.3.0.dist-info/top_level.txt +1 -0
torchspin/_linalg.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Multi-core helpers: batched Hermitian eigendecompositions and chunked map-reduce.
|
|
2
|
+
|
|
3
|
+
``torch.linalg.eigh`` on a batch of small matrices loops serially over the batch on
|
|
4
|
+
the CPU (one LAPACK call per matrix), so a 32-core node is no faster than one core.
|
|
5
|
+
Splitting the batch over a thread pool of single-threaded LAPACK calls scales almost
|
|
6
|
+
linearly (64-core workstation, 2400 × 72×72 complex: 1.5 s → 0.066 s with 32 workers). On CUDA,
|
|
7
|
+
cuSOLVER's batched Jacobi solver only covers matrices up to 32×32; larger batches are
|
|
8
|
+
diagonalised one launch at a time and are 5–10× slower than the CPU pool, so they are
|
|
9
|
+
routed to the CPU and copied back. Results are identical to the serial path (each
|
|
10
|
+
matrix is still handled by the same LAPACK routine).
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
15
|
+
|
|
16
|
+
import torch
|
|
17
|
+
|
|
18
|
+
_MIN_BATCH = 32 # below this the pool overhead is not worth it
|
|
19
|
+
_CUSOLVER_BATCHED_MAX = 32 # syevjBatched limit
|
|
20
|
+
_executors: dict = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _executor(w: int) -> ThreadPoolExecutor:
|
|
24
|
+
ex = _executors.get(w)
|
|
25
|
+
if ex is None:
|
|
26
|
+
ex = _executors[w] = ThreadPoolExecutor(w, thread_name_prefix='torchspin-eigh')
|
|
27
|
+
return ex
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _pool_size(nb: int) -> int:
|
|
31
|
+
return max(1, min(torch.get_num_threads(), nb // 8))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _pooled(fn, H: torch.Tensor):
|
|
35
|
+
nb = H.shape[:-2].numel()
|
|
36
|
+
n = H.shape[-1]
|
|
37
|
+
w = _pool_size(nb)
|
|
38
|
+
if w <= 1 or nb < _MIN_BATCH:
|
|
39
|
+
return fn(H)
|
|
40
|
+
chunks = torch.chunk(H.reshape(nb, n, n), w)
|
|
41
|
+
prev = torch.get_num_threads()
|
|
42
|
+
torch.set_num_threads(1)
|
|
43
|
+
try:
|
|
44
|
+
res = list(_executor(w).map(fn, chunks))
|
|
45
|
+
finally:
|
|
46
|
+
torch.set_num_threads(prev)
|
|
47
|
+
return res
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _EighDegenerateSafe(torch.autograd.Function):
|
|
51
|
+
"""``torch.linalg.eigh`` with a degeneracy-safe backward.
|
|
52
|
+
|
|
53
|
+
The eigenvector gradient of a Hermitian eigendecomposition contains the
|
|
54
|
+
factors 1/(λ_j − λ_i). At exactly degenerate levels (axial or isotropic
|
|
55
|
+
tensors, Kramers pairs at symmetric orientations) they are undefined and
|
|
56
|
+
torch returns finite but wrong parameter gradients for directions that lift
|
|
57
|
+
the degeneracy (checked: 9 % error in d spec/dA_x for an axial Cu(II) system
|
|
58
|
+
at A_x = A_y, 1e-8 once A_x ≠ A_y). Spectra are invariant under unitary
|
|
59
|
+
rotations inside a degenerate subspace (the transitions share a position and
|
|
60
|
+
only the summed intensity enters), so the within-subspace terms carry no
|
|
61
|
+
information: they are set to zero, which is the correct gradient of any
|
|
62
|
+
gauge-invariant function of (λ, V).
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def forward(ctx, H):
|
|
67
|
+
L, V = torch.linalg.eigh(H)
|
|
68
|
+
ctx.save_for_backward(L, V)
|
|
69
|
+
return L, V
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def backward(ctx, gL, gV):
|
|
73
|
+
L, V = ctx.saved_tensors
|
|
74
|
+
Vh = V.conj().transpose(-2, -1)
|
|
75
|
+
scale = L.abs().amax(dim=-1, keepdim=True).unsqueeze(-1)
|
|
76
|
+
E = L.unsqueeze(-2) - L.unsqueeze(-1) # E_ij = λ_j − λ_i
|
|
77
|
+
F = torch.where(E.abs() > 1e-9 * (scale + 1e-300), 1.0 / torch.where(E == 0, torch.ones_like(E), E),
|
|
78
|
+
torch.zeros_like(E))
|
|
79
|
+
inner = torch.zeros_like(V)
|
|
80
|
+
if gL is not None:
|
|
81
|
+
inner = inner + torch.diag_embed(gL.to(V.dtype))
|
|
82
|
+
if gV is not None:
|
|
83
|
+
inner = inner + F.to(V.dtype) * (Vh @ gV)
|
|
84
|
+
gH = V @ inner @ Vh
|
|
85
|
+
return 0.5 * (gH + gH.conj().transpose(-2, -1))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def eigh(H: torch.Tensor):
|
|
89
|
+
"""Batched ``torch.linalg.eigh`` (see module docstring); on the autograd graph
|
|
90
|
+
the backward is the degeneracy-safe variant (:class:`_EighDegenerateSafe`)."""
|
|
91
|
+
if H.requires_grad:
|
|
92
|
+
return _EighDegenerateSafe.apply(H)
|
|
93
|
+
if H.ndim < 3:
|
|
94
|
+
return torch.linalg.eigh(H)
|
|
95
|
+
nb = H.shape[:-2].numel()
|
|
96
|
+
if H.is_cuda:
|
|
97
|
+
if H.shape[-1] <= _CUSOLVER_BATCHED_MAX or nb < 4:
|
|
98
|
+
return torch.linalg.eigh(H)
|
|
99
|
+
E, V = eigh(H.cpu())
|
|
100
|
+
return E.to(H.device), V.to(H.device)
|
|
101
|
+
res = _pooled(torch.linalg.eigh, H)
|
|
102
|
+
if isinstance(res, tuple):
|
|
103
|
+
return res
|
|
104
|
+
E = torch.cat([r[0] for r in res]).reshape(*H.shape[:-1])
|
|
105
|
+
V = torch.cat([r[1] for r in res]).reshape(H.shape)
|
|
106
|
+
return E, V
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def eigvalsh(H: torch.Tensor):
|
|
110
|
+
"""Batched ``torch.linalg.eigvalsh`` (see module docstring)."""
|
|
111
|
+
if H.ndim < 3 or H.requires_grad:
|
|
112
|
+
return torch.linalg.eigvalsh(H)
|
|
113
|
+
nb = H.shape[:-2].numel()
|
|
114
|
+
if H.is_cuda:
|
|
115
|
+
if H.shape[-1] <= _CUSOLVER_BATCHED_MAX or nb < 4:
|
|
116
|
+
return torch.linalg.eigvalsh(H)
|
|
117
|
+
return eigvalsh(H.cpu()).to(H.device)
|
|
118
|
+
res = _pooled(torch.linalg.eigvalsh, H)
|
|
119
|
+
if isinstance(res, torch.Tensor):
|
|
120
|
+
return res
|
|
121
|
+
return torch.cat(res).reshape(*H.shape[:-1])
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def pool_map(fn, items: list):
|
|
125
|
+
"""Apply ``fn`` to every item in a thread pool of single-threaded torch calls.
|
|
126
|
+
|
|
127
|
+
For chunked CPU work whose kernels do not parallelise well (scatter/bincount
|
|
128
|
+
accumulations): each worker runs with one intra-op thread so the chunks run
|
|
129
|
+
concurrently instead of contending. Falls back to a plain map for one item
|
|
130
|
+
or one thread.
|
|
131
|
+
"""
|
|
132
|
+
w = min(torch.get_num_threads(), len(items))
|
|
133
|
+
if w <= 1:
|
|
134
|
+
return [fn(it) for it in items]
|
|
135
|
+
prev = torch.get_num_threads()
|
|
136
|
+
torch.set_num_threads(1)
|
|
137
|
+
try:
|
|
138
|
+
return list(_executor(w).map(fn, items))
|
|
139
|
+
finally:
|
|
140
|
+
torch.set_num_threads(prev)
|