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.
Files changed (116) hide show
  1. torchspin/__init__.py +363 -0
  2. torchspin/_cardamom_istos.py +327 -0
  3. torchspin/_cardamom_propagatedm.py +413 -0
  4. torchspin/_cardamom_utils.py +113 -0
  5. torchspin/_compile.py +100 -0
  6. torchspin/_linalg.py +140 -0
  7. torchspin/angmom.py +742 -0
  8. torchspin/autograd.py +557 -0
  9. torchspin/autoguess.py +342 -0
  10. torchspin/batch.py +225 -0
  11. torchspin/blochsteady.py +274 -0
  12. torchspin/cardamom.py +552 -0
  13. torchspin/chili.py +932 -0
  14. torchspin/chili_sle.py +1233 -0
  15. torchspin/constants.py +88 -0
  16. torchspin/convspec.py +148 -0
  17. torchspin/ctafft.py +91 -0
  18. torchspin/curry.py +241 -0
  19. torchspin/data/FourierSeriesCoefficients.txt +89 -0
  20. torchspin/data/GaussianCascadeCoefficients.txt +56 -0
  21. torchspin/data/isotopedata.txt +416 -0
  22. torchspin/data/spacegroups.txt +637 -0
  23. torchspin/dataproc.py +361 -0
  24. torchspin/dipbackground.py +69 -0
  25. torchspin/dipkernel.py +102 -0
  26. torchspin/diptensor.py +138 -0
  27. torchspin/endorfrq.py +198 -0
  28. torchspin/endorfrq_perturb.py +302 -0
  29. torchspin/eprload.py +1538 -0
  30. torchspin/eprsave.py +165 -0
  31. torchspin/esfit.py +2427 -0
  32. torchspin/evolve.py +274 -0
  33. torchspin/ewrls.py +124 -0
  34. torchspin/excitation.py +107 -0
  35. torchspin/exciteprofile.py +132 -0
  36. torchspin/experiment.py +323 -0
  37. torchspin/exponfit.py +177 -0
  38. torchspin/fastmotion.py +182 -0
  39. torchspin/fdaxis.py +57 -0
  40. torchspin/fitgui.py +350 -0
  41. torchspin/garlic.py +760 -0
  42. torchspin/ham.py +145 -0
  43. torchspin/ham_cf.py +115 -0
  44. torchspin/ham_ee.py +124 -0
  45. torchspin/ham_ez.py +119 -0
  46. torchspin/ham_ezho.py +403 -0
  47. torchspin/ham_hf.py +122 -0
  48. torchspin/ham_nn.py +108 -0
  49. torchspin/ham_nq.py +100 -0
  50. torchspin/ham_nz.py +136 -0
  51. torchspin/ham_oz.py +115 -0
  52. torchspin/ham_so.py +118 -0
  53. torchspin/ham_zf.py +254 -0
  54. torchspin/hamsymm.py +723 -0
  55. torchspin/initstate.py +158 -0
  56. torchspin/isotopologues.py +296 -0
  57. torchspin/levels.py +189 -0
  58. torchspin/levelsplot.py +363 -0
  59. torchspin/lineshape.py +715 -0
  60. torchspin/lpsvd.py +219 -0
  61. torchspin/makespec.py +87 -0
  62. torchspin/mdhmm.py +573 -0
  63. torchspin/mdload.py +788 -0
  64. torchspin/mdtraj2oripot.py +90 -0
  65. torchspin/ml.py +377 -0
  66. torchspin/mlpsvd.py +297 -0
  67. torchspin/nucdata.py +310 -0
  68. torchspin/nucfrq2d.py +228 -0
  69. torchspin/orca2torchspin.py +1198 -0
  70. torchspin/ordering.py +78 -0
  71. torchspin/oripotentialplot.py +246 -0
  72. torchspin/orisel.py +176 -0
  73. torchspin/pepper.py +2019 -0
  74. torchspin/pepper_autograd.py +613 -0
  75. torchspin/photoselect.py +174 -0
  76. torchspin/plegendre.py +131 -0
  77. torchspin/propint.py +127 -0
  78. torchspin/pulse.py +652 -0
  79. torchspin/py.typed +0 -0
  80. torchspin/rapidscan2spc.py +101 -0
  81. torchspin/resfields.py +337 -0
  82. torchspin/resfields_batch.py +446 -0
  83. torchspin/resfields_eig.py +250 -0
  84. torchspin/resfields_perturb.py +706 -0
  85. torchspin/resfreqs_matrix.py +344 -0
  86. torchspin/resfreqs_perturb.py +299 -0
  87. torchspin/resonator.py +310 -0
  88. torchspin/resonatorprofile.py +85 -0
  89. torchspin/rfmixer.py +147 -0
  90. torchspin/rotations.py +87 -0
  91. torchspin/rotutils.py +880 -0
  92. torchspin/saffron.py +1523 -0
  93. torchspin/saffron_pathways.py +92 -0
  94. torchspin/saffron_peaks.py +580 -0
  95. torchspin/saffron_thyme.py +472 -0
  96. torchspin/salt.py +680 -0
  97. torchspin/sigeq.py +71 -0
  98. torchspin/signalprocessing.py +226 -0
  99. torchspin/sitetransforms.py +254 -0
  100. torchspin/sphgrid.py +470 -0
  101. torchspin/spidyan.py +1508 -0
  102. torchspin/spinladder.py +161 -0
  103. torchspin/spinops.py +265 -0
  104. torchspin/spinsystem.py +1427 -0
  105. torchspin/stackplot.py +167 -0
  106. torchspin/stev.py +283 -0
  107. torchspin/stochtraj_diffusion.py +494 -0
  108. torchspin/stochtraj_jump.py +195 -0
  109. torchspin/strainwidth.py +727 -0
  110. torchspin/transmitter.py +89 -0
  111. torchspin/utils.py +605 -0
  112. torchspin-0.3.0.dist-info/METADATA +267 -0
  113. torchspin-0.3.0.dist-info/RECORD +116 -0
  114. torchspin-0.3.0.dist-info/WHEEL +5 -0
  115. torchspin-0.3.0.dist-info/licenses/LICENSE.md +22 -0
  116. 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)