torchspin 0.3.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.
Files changed (124) hide show
  1. torchspin-0.3.0/CHANGELOG.md +522 -0
  2. torchspin-0.3.0/CITATION.cff +38 -0
  3. torchspin-0.3.0/LICENSE.md +22 -0
  4. torchspin-0.3.0/MANIFEST.in +22 -0
  5. torchspin-0.3.0/PKG-INFO +267 -0
  6. torchspin-0.3.0/README.md +216 -0
  7. torchspin-0.3.0/pyproject.toml +69 -0
  8. torchspin-0.3.0/setup.cfg +4 -0
  9. torchspin-0.3.0/torchspin/__init__.py +363 -0
  10. torchspin-0.3.0/torchspin/_cardamom_istos.py +327 -0
  11. torchspin-0.3.0/torchspin/_cardamom_propagatedm.py +413 -0
  12. torchspin-0.3.0/torchspin/_cardamom_utils.py +113 -0
  13. torchspin-0.3.0/torchspin/_compile.py +100 -0
  14. torchspin-0.3.0/torchspin/_linalg.py +140 -0
  15. torchspin-0.3.0/torchspin/angmom.py +742 -0
  16. torchspin-0.3.0/torchspin/autograd.py +557 -0
  17. torchspin-0.3.0/torchspin/autoguess.py +342 -0
  18. torchspin-0.3.0/torchspin/batch.py +225 -0
  19. torchspin-0.3.0/torchspin/blochsteady.py +274 -0
  20. torchspin-0.3.0/torchspin/cardamom.py +552 -0
  21. torchspin-0.3.0/torchspin/chili.py +932 -0
  22. torchspin-0.3.0/torchspin/chili_sle.py +1233 -0
  23. torchspin-0.3.0/torchspin/constants.py +88 -0
  24. torchspin-0.3.0/torchspin/convspec.py +148 -0
  25. torchspin-0.3.0/torchspin/ctafft.py +91 -0
  26. torchspin-0.3.0/torchspin/curry.py +241 -0
  27. torchspin-0.3.0/torchspin/data/FourierSeriesCoefficients.txt +89 -0
  28. torchspin-0.3.0/torchspin/data/GaussianCascadeCoefficients.txt +56 -0
  29. torchspin-0.3.0/torchspin/data/isotopedata.txt +416 -0
  30. torchspin-0.3.0/torchspin/data/spacegroups.txt +637 -0
  31. torchspin-0.3.0/torchspin/dataproc.py +361 -0
  32. torchspin-0.3.0/torchspin/dipbackground.py +69 -0
  33. torchspin-0.3.0/torchspin/dipkernel.py +102 -0
  34. torchspin-0.3.0/torchspin/diptensor.py +138 -0
  35. torchspin-0.3.0/torchspin/endorfrq.py +198 -0
  36. torchspin-0.3.0/torchspin/endorfrq_perturb.py +302 -0
  37. torchspin-0.3.0/torchspin/eprload.py +1538 -0
  38. torchspin-0.3.0/torchspin/eprsave.py +165 -0
  39. torchspin-0.3.0/torchspin/esfit.py +2427 -0
  40. torchspin-0.3.0/torchspin/evolve.py +274 -0
  41. torchspin-0.3.0/torchspin/ewrls.py +124 -0
  42. torchspin-0.3.0/torchspin/excitation.py +107 -0
  43. torchspin-0.3.0/torchspin/exciteprofile.py +132 -0
  44. torchspin-0.3.0/torchspin/experiment.py +323 -0
  45. torchspin-0.3.0/torchspin/exponfit.py +177 -0
  46. torchspin-0.3.0/torchspin/fastmotion.py +182 -0
  47. torchspin-0.3.0/torchspin/fdaxis.py +57 -0
  48. torchspin-0.3.0/torchspin/fitgui.py +350 -0
  49. torchspin-0.3.0/torchspin/garlic.py +760 -0
  50. torchspin-0.3.0/torchspin/ham.py +145 -0
  51. torchspin-0.3.0/torchspin/ham_cf.py +115 -0
  52. torchspin-0.3.0/torchspin/ham_ee.py +124 -0
  53. torchspin-0.3.0/torchspin/ham_ez.py +119 -0
  54. torchspin-0.3.0/torchspin/ham_ezho.py +403 -0
  55. torchspin-0.3.0/torchspin/ham_hf.py +122 -0
  56. torchspin-0.3.0/torchspin/ham_nn.py +108 -0
  57. torchspin-0.3.0/torchspin/ham_nq.py +100 -0
  58. torchspin-0.3.0/torchspin/ham_nz.py +136 -0
  59. torchspin-0.3.0/torchspin/ham_oz.py +115 -0
  60. torchspin-0.3.0/torchspin/ham_so.py +118 -0
  61. torchspin-0.3.0/torchspin/ham_zf.py +254 -0
  62. torchspin-0.3.0/torchspin/hamsymm.py +723 -0
  63. torchspin-0.3.0/torchspin/initstate.py +158 -0
  64. torchspin-0.3.0/torchspin/isotopologues.py +296 -0
  65. torchspin-0.3.0/torchspin/levels.py +189 -0
  66. torchspin-0.3.0/torchspin/levelsplot.py +363 -0
  67. torchspin-0.3.0/torchspin/lineshape.py +715 -0
  68. torchspin-0.3.0/torchspin/lpsvd.py +219 -0
  69. torchspin-0.3.0/torchspin/makespec.py +87 -0
  70. torchspin-0.3.0/torchspin/mdhmm.py +573 -0
  71. torchspin-0.3.0/torchspin/mdload.py +788 -0
  72. torchspin-0.3.0/torchspin/mdtraj2oripot.py +90 -0
  73. torchspin-0.3.0/torchspin/ml.py +377 -0
  74. torchspin-0.3.0/torchspin/mlpsvd.py +297 -0
  75. torchspin-0.3.0/torchspin/nucdata.py +310 -0
  76. torchspin-0.3.0/torchspin/nucfrq2d.py +228 -0
  77. torchspin-0.3.0/torchspin/orca2torchspin.py +1198 -0
  78. torchspin-0.3.0/torchspin/ordering.py +78 -0
  79. torchspin-0.3.0/torchspin/oripotentialplot.py +246 -0
  80. torchspin-0.3.0/torchspin/orisel.py +176 -0
  81. torchspin-0.3.0/torchspin/pepper.py +2019 -0
  82. torchspin-0.3.0/torchspin/pepper_autograd.py +613 -0
  83. torchspin-0.3.0/torchspin/photoselect.py +174 -0
  84. torchspin-0.3.0/torchspin/plegendre.py +131 -0
  85. torchspin-0.3.0/torchspin/propint.py +127 -0
  86. torchspin-0.3.0/torchspin/pulse.py +652 -0
  87. torchspin-0.3.0/torchspin/py.typed +0 -0
  88. torchspin-0.3.0/torchspin/rapidscan2spc.py +101 -0
  89. torchspin-0.3.0/torchspin/resfields.py +337 -0
  90. torchspin-0.3.0/torchspin/resfields_batch.py +446 -0
  91. torchspin-0.3.0/torchspin/resfields_eig.py +250 -0
  92. torchspin-0.3.0/torchspin/resfields_perturb.py +706 -0
  93. torchspin-0.3.0/torchspin/resfreqs_matrix.py +344 -0
  94. torchspin-0.3.0/torchspin/resfreqs_perturb.py +299 -0
  95. torchspin-0.3.0/torchspin/resonator.py +310 -0
  96. torchspin-0.3.0/torchspin/resonatorprofile.py +85 -0
  97. torchspin-0.3.0/torchspin/rfmixer.py +147 -0
  98. torchspin-0.3.0/torchspin/rotations.py +87 -0
  99. torchspin-0.3.0/torchspin/rotutils.py +880 -0
  100. torchspin-0.3.0/torchspin/saffron.py +1523 -0
  101. torchspin-0.3.0/torchspin/saffron_pathways.py +92 -0
  102. torchspin-0.3.0/torchspin/saffron_peaks.py +580 -0
  103. torchspin-0.3.0/torchspin/saffron_thyme.py +472 -0
  104. torchspin-0.3.0/torchspin/salt.py +680 -0
  105. torchspin-0.3.0/torchspin/sigeq.py +71 -0
  106. torchspin-0.3.0/torchspin/signalprocessing.py +226 -0
  107. torchspin-0.3.0/torchspin/sitetransforms.py +254 -0
  108. torchspin-0.3.0/torchspin/sphgrid.py +470 -0
  109. torchspin-0.3.0/torchspin/spidyan.py +1508 -0
  110. torchspin-0.3.0/torchspin/spinladder.py +161 -0
  111. torchspin-0.3.0/torchspin/spinops.py +265 -0
  112. torchspin-0.3.0/torchspin/spinsystem.py +1427 -0
  113. torchspin-0.3.0/torchspin/stackplot.py +167 -0
  114. torchspin-0.3.0/torchspin/stev.py +283 -0
  115. torchspin-0.3.0/torchspin/stochtraj_diffusion.py +494 -0
  116. torchspin-0.3.0/torchspin/stochtraj_jump.py +195 -0
  117. torchspin-0.3.0/torchspin/strainwidth.py +727 -0
  118. torchspin-0.3.0/torchspin/transmitter.py +89 -0
  119. torchspin-0.3.0/torchspin/utils.py +605 -0
  120. torchspin-0.3.0/torchspin.egg-info/PKG-INFO +267 -0
  121. torchspin-0.3.0/torchspin.egg-info/SOURCES.txt +122 -0
  122. torchspin-0.3.0/torchspin.egg-info/dependency_links.txt +1 -0
  123. torchspin-0.3.0/torchspin.egg-info/requires.txt +28 -0
  124. torchspin-0.3.0/torchspin.egg-info/top_level.txt +1 -0
@@ -0,0 +1,522 @@
1
+ # Changelog
2
+
3
+ All notable changes to torchspin will be documented in this file.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.3.0] — 2026-10-04
8
+
9
+ First public release.
10
+
11
+ ### Fixed — NumPy 2.0 and case-sensitive filesystems (2026-10-04)
12
+
13
+ Found by installing the release into an empty environment on a machine that
14
+ had never run torchspin (NumPy 2.5, SciPy 1.18, PyTorch 2.14, Linux). All
15
+ three were silent on the development hosts and broke 26 tests on the clean
16
+ one; none changes any numerical result.
17
+
18
+ - `pepper` partial ordering (`Exp.Ordering`) raised
19
+ `AttributeError: module 'numpy' has no attribute 'trapz'` on **every** call
20
+ under NumPy >= 2.0, which removed `np.trapz`. `torchspin/ordering.py` now
21
+ uses `np.trapezoid` through the same fallback shim already used in
22
+ `torchspin/pulse.py` and `torchspin/exponfit.py`. The 10 MATLAB-validation
23
+ cases for ordering (`test_pepper_features2_matlab_validation.py`,
24
+ `test_pepper_round3_matlab_validation.py`) were failing on this alone and
25
+ now pass — the ordering physics was never in question.
26
+ - `eprload` could not open Bruker ESP/WinEPR data on a case-sensitive
27
+ filesystem: a `FOO.SPC` file was paired with a hard-coded `FOO.par`, so the
28
+ real `FOO.PAR` was reported missing. The companion-file probe that the
29
+ BES3T loader already had is now a module-level `_find_companion` used by
30
+ both loaders.
31
+ - Three saffron tests called `float()` on the shape-`(nNuclei,)` array
32
+ returned by `larmorfrq`, which NumPy 2.0 rejects; they now take the element
33
+ explicitly.
34
+
35
+
36
+ ### Added — esfit `method='trf'` (2026-09-09)
37
+ - `esfit`: `FitOptions(method='trf')`, SciPy's bounded trust-region reflective
38
+ least squares on esfit's target-transformed residual vector. Parameter
39
+ mapping (dict/array `p0`, fixed parameters, `lb`/`ub`/`vary`), amplitude and
40
+ baseline fitting (`autoscale`/`baseline`), `target`, progress reporting and
41
+ `max_time`/`stop_when`/interrupt handling are shared with the other methods.
42
+ New options: `trf_diff_step` (relative finite-difference step, SciPy
43
+ `diff_step`), `trf_x_scale` (`'jac'`, a float, or one entry per active
44
+ parameter) and `trf_gradient_tol` (`gtol`); `tol_fun`/`tol_x` are passed as
45
+ `ftol`/`xtol`. For this method `max_iter` is SciPy's `max_nfev`, the number
46
+ of step evaluations; the finite-difference Jacobian probes are not charged
47
+ against it but are counted in `FitResult.n_evaluations` and drive the
48
+ progress/stop checks. An exhausted budget returns `success=False,
49
+ interrupted=False` with SciPy's message; a stop or interrupt returns the
50
+ best point so far with `interrupted=True`; `n_iterations` is the
51
+ Jacobian-evaluation count (one per trust-region iteration). TRF is an
52
+ optimisation method, not a CPU-acceleration feature: it runs sequentially;
53
+ simulation threading and the population-worker `n_workers` parallelism are
54
+ separate. The default method is unchanged (`simplex`). Parity with a direct
55
+ `scipy.optimize.least_squares` call is tested (`tests/test_esfit_trf.py`).
56
+
57
+ ### Fixed — fitting panel (2026-09-09)
58
+ - `fitgui`: `'trf'` is offered in the method selector. Sessions built with
59
+ explicit `lb`/`ub` keep those absolute bounds across restarts and slider
60
+ moves (they were rebuilt as p0 ± vary around each new start). A parameter
61
+ fixed for one run (the "fix" box, or `start(vary=...)` with a zero) can be
62
+ released again on the next run (the zeroed `vary` used to overwrite the
63
+ session's). A start outside the bounds raises before any session state
64
+ changes, and the panel re-enables its controls. The residual trace is now
65
+ data − fit (it was fit − data).
66
+ - `FitResult.residuals` docstring states the actual sign (fit − data).
67
+
68
+ ### Added — ML layer, cardamom seed (2026-09-06)
69
+ - `torchspin.ml`: `ParamSpec` (free spin-system parameters with ranges and
70
+ linear/log/softplus transforms; `pack`/`unpack` between a flat tensor and a
71
+ `SpinSystem` built from a template, on the autograd graph when the vector
72
+ requires grad), `simulate`/`simulate_batch` (any differentiable simulator,
73
+ `(N, nPoints)` output, optional spawned pool for no-grad generation),
74
+ `SpectrumDataset` (seeded on-the-fly samples with resampling, normalisation,
75
+ noise and baseline), and torch losses (`cosine_loss`, `rmsd`, `normalize`,
76
+ `resample`) so network predictions can be refined through the simulators.
77
+ - `convspec` accepts tensor widths: differentiable ones are routed to the
78
+ torch implementation, others are used as floats.
79
+ - `CardamomPar.seed`: reproducible trajectory ensembles (`DiffusionPar`/
80
+ `JumpPar` seeds were not reachable from `cardamom`).
81
+
82
+ ### Changed — differentiable field modulation (2026-09-06)
83
+ - `dataproc.fieldmod_t`: the pseudo-modulation (Jacobi–Anger Bessel kernel,
84
+ Kaelin & Schweiger) in torch (orders 0–2 closed form, higher by recurrence),
85
+ equal to `fieldmod` to 1e-12; `pepper_autograd` and `garlic` use it, so
86
+ `Exp.ModAmp` spectra are differentiable (pepper: forward = `pepper()` to
87
+ 5e-10, gradient vs FD 2e-6).
88
+
89
+ ### Changed — pepper_autograd: perturbation methods (2026-09-06)
90
+ - `Opt.Method='perturb*'` now uses pepper's perturbation resonance route in
91
+ `pepper_autograd` (forward equals `pepper()` to 1e-10 for the dense-grid
92
+ perturb2 workload, nitroxide perturb1 and Cu perturb2). Second-order trace
93
+ and determinant scalars in `resfields_perturb_batch` stay tensors (they were
94
+ `.item()`-detached, which left 1e-2 errors in the A gradient); gradients vs
95
+ finite differences of `pepper()` are 1e-8 for A and g.
96
+
97
+ ### Changed — pepper_autograd: strain broadening (2026-09-06)
98
+ - `pepper_autograd` / `differentiable_spectrum` handle strains (HStrain,
99
+ gStrain, AStrain, DStrain): the per-transition widths from
100
+ `compute_strain_widths_batch` are already torch, and the EasySpin summation
101
+ branch (facet centres, spread-smoothed widths, bin-integrated Gaussians —
102
+ `gaussian_bins_t`) is now torch too. Forward equals `pepper()` to 2e-12 on
103
+ nitroxide HStrain+gStrain (D2h), axial gStrain (Dinfh), AStrain and S=1
104
+ DStrain cases; gradients vs finite differences of `pepper()` 1e-6–1e-8 for
105
+ g, A, gStrain, HStrain. DStrain magnitudes are constants (not differentiable).
106
+
107
+ ### Changed — differentiable saffron (2026-09-06)
108
+ - `saffron` (ESEEM/HYSCORE, predefined and custom ideal-pulse sequences): the
109
+ relaxation decay, apodisation and FFT tail and the S=1/2 orientation-selection
110
+ weights are torch, so with grad tensors in the spin system the time-domain
111
+ signal and `info['td']`/`info['fd']` are tensors on the autograd graph; without
112
+ grad tensors the NumPy outputs are unchanged (forward identical to rounding).
113
+ The default frequency-domain accumulation bins peaks (EasySpin `sf_peaks`) and
114
+ is not differentiable in the peak frequencies, so grad tensors switch the
115
+ call to `SaffronOptions(TimeDomain=True)` (exact evolution; forward differs
116
+ from the binned default by the bin quantisation). Gradients vs finite
117
+ differences of the time-domain path: 1e-5–1e-7 for A, Q, g (2pESEEM,
118
+ HYSCORE; `test_saffron_autograd.py`). Mims-ENDOR raises for grad tensors.
119
+
120
+ ### Changed — differentiable garlic (2026-09-06)
121
+ - `garlic` runs in torch end to end (same algorithm: isotropic g/A, Breit–Rabi
122
+ fixed point and perturbation Newton for the line positions, Kivelson–Freed
123
+ fast-motion widths in `fastmotion_t`, explicit Lorentzian accumulation with
124
+ torch line shapes and convolution, torch resampling), so spectra are on the
125
+ autograd graph for g, A, Q, tcorr/logtcorr and lw/lwpp. Forward unchanged to
126
+ 1e-9 (Fremy's salt, nitroxide fast motion, methyl perturb2, frequency sweep,
127
+ Temperature, Harmonic 2, ModAmp); gradients vs finite differences 1e-8
128
+ (`test_garlic_autograd.py`; the fine accumulation grid is re-discretised with
129
+ the smallest width, so tcorr finite differences need a step within one
130
+ discretisation).
131
+ - `Options.AccumMethod='linear'` (EasySpin `makespec`: a line is split between
132
+ its two neighbouring bins) is the differentiable form of the default
133
+ nearest-bin stick spectrum and is selected automatically when the spin
134
+ system carries grad tensors; it differs from nearest-bin binning by the
135
+ sub-bin quantisation only (≈(Δx/2)/σ relative, 1 % for a 0.3 mT Gaussian at
136
+ Δx = 6 µT; cosine 0.99998). `ModAmp` with grad tensors raises.
137
+ - `fastmotion()` keeps its NumPy signature and wraps `fastmotion_t`.
138
+
139
+ ### Changed — differentiable salt; projection and eigh gradient fixes (2026-09-06)
140
+ - `salt` (fixed-field ENDOR) is differentiable: the transition arrays stay on
141
+ the graph and the powder average uses the torch interpolation / projection /
142
+ convolution shared with `pepper_autograd` (forward unchanged to 1e-12 on the
143
+ 8-case MATLAB suite; Jacobians vs finite differences 1e-6–1e-8 for g, A, Q,
144
+ lw in both the perturbative and the matrix path). The field-swept ENDOR mode
145
+ still bins sticks and raises `NotImplementedError` for grad tensors.
146
+ - `pepper_autograd` projection: a tent ramp of zero width (tied vertices —
147
+ generic for axial systems, where the resonance field does not depend on φ)
148
+ contributes no value but a first-order term; it was masked away, giving
149
+ 9 % errors in d spec/dA_x at A_x = A_y. Now kept on the graph (1e-7).
150
+ - `torchspin._linalg.eigh` uses a degeneracy-safe backward on the graph:
151
+ within-subspace terms 1/(λ_j − λ_i) of coincident eigenvalues are dropped,
152
+ the correct gradient for gauge-invariant outputs such as spectra.
153
+ - `torchspin.pepper_autograd.projecttriangles_t/projectzones_t/convspec_t`
154
+ are reused by `salt`; `salt(..., lw_mhz=tensor)` is allowed.
155
+
156
+ ### Changed — differentiable pepper shares pepper's forward path (2026-09-05)
157
+ - New `torchspin.pepper_autograd.pepper_autograd(sys, exp, opt)`: pepper's
158
+ rigid-limit powder spectrum (matrix method, no strain) on the autograd graph.
159
+ It reuses pepper's resonance-field search and transition tracking
160
+ (`resfields_batch`, whose converged Newton step yields the implicit gradient
161
+ of the resonance fields), and adds torch ports of the transition-slot
162
+ bookkeeping, EasySpin grid interpolation (as linear maps, `L3` in torch),
163
+ SOPHE triangle/zone projection (`projecttriangles_t`, `projectzones_t`) and
164
+ EasySpin's sampled-kernel convolution/harmonic (`convspec_t`). Forward output
165
+ equals `pepper()` to 1e-11 relative; autograd Jacobians match central finite
166
+ differences of `pepper()` for g, A, D, Gaussian/Lorentzian lw and
167
+ Temperature (1e-8 relative for A/D/lw; g to the finite-difference kink limit
168
+ of the piecewise-linear projection).
169
+ - `differentiable_spectrum` now builds a `SpinSystem` from the parameter
170
+ tensors and calls `pepper_autograd` (`method='pepper'`, default); `GridSize`
171
+ accepts pepper's `[N, Ninterp]` form, `GridSymmetry` defaults to `'auto'`,
172
+ a tensor `Temperature` is differentiable. The earlier stand-alone models are
173
+ kept as deprecated `method='broadband'` / `'analytical'`: they sum discrete
174
+ orientations without interpolation or projection and carry orientation-grid
175
+ ripple at GridSizes where pepper is converged (Cu(II) hyperfine, GridSize 31:
176
+ cosine to EasySpin 0.985–0.999 in absorption, 0.73–0.88 in first derivative;
177
+ pepper 0.9998–1.0000 / 0.999). Training a CNN on their output was training on
178
+ a model that differs from pepper/EasySpin by more than the features to learn.
179
+ - `SpinSystem` keeps tensors that `requires_grad` (no copy) and accepts lists of
180
+ tensors (`g=[g_tensor]`, `A=[[ax, ay, az]]`), so parameters can be optimised
181
+ through the regular simulators; `resfields_batch` Boltzmann normalisation is
182
+ out of place (autograd through `exp`); `hamsymm` detaches before comparing.
183
+ - Not yet differentiable: strains, `ModAmp`, `mwPhase`, ordering,
184
+ photoselection, crystals, frequency sweeps (`pepper_autograd` raises
185
+ `NotImplementedError`; `pepper()` is unchanged).
186
+
187
+ ## [0.2.2] — 2026-09-03
188
+
189
+ ### Fixed — salt powder average
190
+ - `salt` (fixed-field ENDOR): the powder spectrum was accumulated as a stick
191
+ spectrum (one nearest-bin hit per orientation), which left orientation-grid
192
+ ripple on top of every powder pattern (EasySpin parity cosine 0.9947 for the
193
+ rhombic-g 1H reference at GridSize 31). It now follows EasySpin `salt.m`:
194
+ transition positions/intensities are tracked per transition across the
195
+ grid, interpolated when `GridSize=[N, Ninterp]` is given (G3 positions,
196
+ linear intensities; off when any orientation is missing), and projected with
197
+ `projecttriangles` / `projectzones`, reusing the pepper ports. Parity on the
198
+ 8-case MATLAB suite rises to 0.9917–0.99999 (rhombic-g 0.99996 at GridSize
199
+ 31, 0.999998 at the reference's `[20 5]`; S=3/2 0.942 → 0.992).
200
+ - `salt` accepted only an integer `GridSize` and crashed with the default
201
+ `Options()` (`GridSize=[19, 4]`); the `[N, Ninterp]` form is now honoured.
202
+
203
+ ## [0.2.1] — 2026-09-03
204
+
205
+ ### Added — interactive fitting panel
206
+ - `torchspin.fitgui`: `FitSession` (esfit in a background thread with live
207
+ progress; Stop returns the best fit so far) and `FitPanel` (ipywidgets
208
+ sliders with bounds and fix boxes, method/budget controls, Start/Stop/Reset,
209
+ live figure with data, best fit, residual and RMSD trace; ipympl canvas or
210
+ PNG refresh). Optional extra `torchspin[gui]`; tutorial notebook
211
+ `examples/notebooks/09_fit_gui.ipynb`.
212
+
213
+ ## [0.2.0] — 2026-09-03
214
+
215
+ EasySpin parity rounds 1–3, performance rounds 1–2, esfit progress/early stop,
216
+ notebooks 01–08, benchmark report against MATLAB/EasySpin. Details below.
217
+
218
+ ### Added — esfit progress, resource summary, early stop (2026-09-02)
219
+ - `FitOptions.progress` (`'text'`, `'bar'` with tqdm, or a callable), `progress_every`,
220
+ `progress_interval`: rate-limited reports of iteration, evaluations, current and
221
+ best RMSD, elapsed time, evaluations/s and ETA for every method (population
222
+ methods report per evaluation chunk when worker processes are used); a
223
+ resource summary (torch threads, worker processes, CPUs, CUDA/MPS, RSS) at
224
+ the start and a totals line at the end.
225
+ - Early stop: `max_time`, `stop_when(info)`, a progress callable returning
226
+ `False`, or a keyboard/kernel interrupt now return the best-so-far
227
+ `FitResult` (`success=False`, `interrupted=True`, message with the reason)
228
+ instead of losing the fit; an interrupted worker pool is terminated cleanly.
229
+ - `FitResult.n_evaluations`, `elapsed_s`, `interrupted`.
230
+
231
+ ### Fixed — chili default basis (2026-09-02)
232
+ - `chili`: for one S = 1/2 electron with ≤ 2 nuclei, no potential and a
233
+ rhombic basis (Mmax > 0, Kmax > 0) — where EasySpin auto-selects its `fast`
234
+ builder and that builder equals general+MpSymm at half amplitude — the
235
+ M–pS–pI symmetry basis is now the default (`MpSymm=None` = automatic) with
236
+ the fast-method amplitude convention (verified in MATLAB on the stored cases). The plain general
237
+ basis was 6 % off EasySpin's default in the slow-motion limit
238
+ (τc = 100 ns nitroxide: cosine 0.937 → 1.000).
239
+ - Examples: `fitting/fit_bruker_cw.py` (load a Bruker file with `eprload`,
240
+ read the acquisition parameters, baseline, fit with `esfit`);
241
+ `slowmotion/nitroxide_tcorr.py` plot fix; `liquids/fremysalt.py` uses the
242
+ reference's A conversion; `fitting/multicomponents.py` built its target from separately weighted
243
+ spectra (minor component weighted twice) — now uses the multi-component call,
244
+ seeded noise and `method='global'`; `examples/tests/compare_with_matlab_refs.py` path fixed.
245
+
246
+ ### Changed — performance round 2 (2026-09-02, after the reference-workstation baseline)
247
+ - `pepper` strain summation: Gaussian accumulation in cache-sized chunks run in
248
+ a thread pool, one spline evaluation per fine grid, cached spherical grids.
249
+ - `pepper` interpolation: the D2h G3 coarse→fine interpolation (row splines +
250
+ bicubic) is applied as one cached matrix per grid size (75× faster per slot;
251
+ agreement 1e-12), which halves a small interpolated `pepper` call.
252
+ - `cardamom`: orientations propagated in groups (trajectories were unseeded per
253
+ orientation, so results are statistically unchanged), torch propagator and
254
+ step loop, torch quaternion→rotation conversion.
255
+ - `esfit`: population methods can evaluate the population in a spawned process
256
+ pool (`FitOptions.workers='auto'|'processes'|'threads'`, one torch thread per
257
+ worker); the objective is a picklable partial.
258
+ - Test: `test_esfit_vs_matlab[field_g3lw_simplex]` accepts the gx↔gy mirror
259
+ solution — the case starts from gx0 == gy0 without hyperfine coupling, so the
260
+ two minima are the same powder spectrum and which one a simplex reaches is
261
+ decided by rounding noise.
262
+
263
+ ### Changed — performance round 1 (2026-09-02)
264
+ Results are unchanged (MATLAB parity suites and the full test suite green after
265
+ every step; resonance positions within 1e-11 mT of the previous search).
266
+ - `resfields_batch`: one exact diagonalisation per resonance candidate at the
267
+ cubic-model root (EasySpin's scheme) with a free Hellmann–Feynman Newton
268
+ correction, instead of three polish evaluations plus a final pass; the
269
+ transition eigenvectors are gathered there and reused for intensities;
270
+ knot and candidate diagonalisations chunked to ~400 MB; per-orientation
271
+ reassembly and strain widths as tensor ops (one host sync per call).
272
+ - `torchspin/_linalg.py`: batched `eigh`/`eigvalsh` split over a thread pool of
273
+ single-threaded LAPACK calls (torch loops serially over CPU batches; 21–23×
274
+ on 32 cores); CUDA batches of matrices larger than cuSOLVER's batched limit
275
+ (32) are routed to the CPU pool. Used by resfields, resfreqs, salt, curry
276
+ and the strain widths.
277
+ - `Options.BatchSize` default is now automatic (all orientations per call,
278
+ memory-capped) instead of 10.
279
+ - `pepper`: vectorised SOPHE projection (`_projecttriangles`/`_projectzones`,
280
+ loop ports kept as `*_loop`), windowed Gaussian bin accumulation, no
281
+ per-resonance Python in the strain path.
282
+ - `cardamom`: batched density-matrix propagation and tensor rotation.
283
+ - `chili`: vectorised Liouvillian assembly (`liouvhamiltonian`; loop port kept
284
+ as `_liouvhamiltonian_loop`).
285
+ - Benchmarks: `benchmarks/cluster/run_workstation.sh` (serial campaign incl. MATLAB),
286
+ `benchmarks/analysis/workstation_report.py`;
287
+ results in `benchmarks/results/workstation_20260902*/` and
288
+ `benchmarks/results/BENCHMARK_REPORT.md`.
289
+
290
+ ### Added — EasySpin parity round 2 (2026-09-01)
291
+ - `chili`: full port of EasySpin's general stochastic-Liouville method
292
+ (`torchspin/chili_sle.py`): arbitrary spin systems, several nuclei, S>1/2,
293
+ orienting potentials with powder integration, LjKKM basis switches,
294
+ Lanczos/direct/eigen solvers, field-sweep methods, post-convolution nuclei,
295
+ multi-component input; 60-case MATLAB suite (59 at cosine ≥0.999).
296
+ - `pepper`: first-principles absolute intensity (dBdE, nuclear-sublevel
297
+ sharing, density normalisation) — the empirical scale factors are gone;
298
+ symmetry-frame fix for tilted axial tensors; level-pair transition
299
+ bookkeeping and EasySpin's strain summation on the interpolated grid;
300
+ Delaunay triangulation, rectified interpolation and projection for the
301
+ open-φ grids (Ci, C2h, C1, …); exact templates for isotropic systems;
302
+ `Options.Method` honoured (matrix default, perturbation opt-in);
303
+ `Experiment.SampleFrame`; 102+12+11-case MATLAB suites.
304
+ - `sphgrid.grid_triangulation` for all grid symmetries.
305
+ - `pepper` features (all MATLAB-validated): single crystals and site
306
+ transforms, parallel mode, isotopologues (`torchspin/isotopologues.py`,
307
+ shared with garlic and chili), non-equilibrium populations
308
+ (`SpinSystem.initState`, `torchspin/initstate.py`), partial ordering
309
+ (`Experiment.Ordering`, `torchspin/ordering.py`), photoselection
310
+ (`lightBeam`/`lightScatter`/`tdm`), field modulation, dispersion,
311
+ `Options.separate`, and frequency sweeps through the unified
312
+ interpolation/projection/summation path (`resfreqs_batch`).
313
+ - `garlic`: component × isotopologue loop with weights,
314
+ `Options.separate='components'`, automatic sweep ranges
315
+ (`Options.Stretch`), frequency sweeps with `Experiment.Field` alone.
316
+ - `SpinSystem.ee2` (biquadratic exchange), `SpinSystem.D_`, `SpinSystem.Abund`,
317
+ `SpinSystem.Q` shorthand forms (`[e2qQ/h eta]`), `Options.IsoCutoff`.
318
+ - `spidyan`: `Dim` sweeps of the pulse length `tp`.
319
+ - `eprload` validated on all 75 EasySpin-readable example files; multi-D data,
320
+ companion-file axes, multi-value BES3T, JEOL and specman fixes.
321
+ - Ports of EasySpin's `isotopologues_*` tests; cardamom MATLAB references.
322
+
323
+ ### Added — EasySpin parity round 3 (2026-09-02)
324
+ - `pepper`: `Experiment.mwMode` excitation modes (`torchspin/excitation.py`),
325
+ photoselection on the perturbation paths, `Options.separate='transitions'`,
326
+ automatic sweep ranges, EasySpin transition pre-/post-selection, paired
327
+ resonances of looping transitions, EasySpin interpolation modes for
328
+ intensities and widths; 32-case MATLAB suite.
329
+ - `esfit`: `levmar` is a port of `esfit_levmar.m`; 6-case EasySpin oracle.
330
+ - `orca2torchspin`: 33-file `orca2easyspin` oracle; binary `.prop` frames and
331
+ coordinates read column-major.
332
+ - `cardamom`: three EasySpin 6 references (diffusion/fast, jump/fast, ISTOs)
333
+ at cosine ≥0.97–0.9998.
334
+
335
+ ### Changed — EasySpin parity round 3
336
+ - `cardamom`: field-swept spectra no longer apply `Experiment.Harmonic`
337
+ (EasySpin returns the imaginary FFT of the time-weighted FID); the output
338
+ scale follows EasySpin's double orientation normalisation; the jump model
339
+ no longer requires a correlation time.
340
+ - `hamsymm`: principal-value equality at 1e-12 relative (was `torch.allclose`).
341
+ - `FitOptions.lm_delta` default 1e-3 and `lm_gradient_tol` 1e-5 (EasySpin).
342
+
343
+ ### Changed — EasySpin parity round 2
344
+ - `eprload` returns abscissae in the file's native units (Bruker: Gauss) and
345
+ one abscissa per dimension for multi-dimensional data, as EasySpin does.
346
+ - `Experiment.Harmonic` defaults to 1 for field sweeps and 0 for frequency
347
+ sweeps; `Experiment.Range` may be omitted for automatic ranging (garlic).
348
+ - `resfields`/`resfreqs` intensity thresholds use absolute values so emissive
349
+ lines of non-equilibrium states survive.
350
+
351
+ ### Fixed — EasySpin parity round 2
352
+ - `SpinSystem` silently defaulted `lw` to 0.5 mT; the default is now no
353
+ broadening (EasySpin). pepper/garlic fall back to Harmonic 0 when nothing
354
+ broadens.
355
+ - `convspec` samples the line-shape kernel on the grid like EasySpin
356
+ (sub-increment widths differed by up to 3.7× from the analytic kernel).
357
+ - pepper: resonances were matched across orientations by list index;
358
+ strain widths were evaluated at one field per orientation; slots with
359
+ missing resonances were filled from neighbours; isotropic systems and
360
+ frequency sweeps had wrong normalisation/units; single components ignored
361
+ `Sys.weight`.
362
+
363
+ ### Added — EasySpin port completion (2026-09-01)
364
+ - `saffron`: S>1/2 systems, MimsENDOR blind-spot fix, `ProductRule` with
365
+ `TimeDomain`, multi-component input with `Sys.weight` and
366
+ `opt.separate='components'`, single-crystal simulations via
367
+ `PulseExperiment.SampleFrame`/`CrystalSymmetry`/`MolFrame` backed by a full
368
+ `sitetransforms` port (230 space groups, vendored `data/spacegroups.txt`);
369
+ `saffron_thyme` crystal orientations. MATLAB validation suite: 25 cases,
370
+ zero skips.
371
+ - `salt`: EasySpin fixed-field ENDOR mode (`Experiment.Field` + RF `Range`,
372
+ `ExciteWidth` orientation selection) and an 8-case MATLAB validation suite.
373
+ - `spidyan`: simulation-frame machinery (ZeemanFreq→g, `SimFreq`, pulse
374
+ carriers through `pulse()`/`rfmixer`, phase cycling, `DetFreq`
375
+ down-conversion, `Dim` pulse-parameter sweeps); 7-case MATLAB validation.
376
+ - `garlic`: EasySpin's line-position engine — exact Breit–Rabi fixed-point
377
+ solver (default) and `perturb1`–`perturb5`, equivalent nuclei
378
+ (`SpinSystem.n`, `equivcouple`), frequency sweeps, `CenterSweep`, field
379
+ modulation (`ModAmp`), Boltzmann polarization, full g/A matrices; 18-case
380
+ MATLAB suite (positions <1e-6 mT).
381
+ - `hamsymm`: eigenvalue-based point-group analysis (`hamsymm_eigs` port) for
382
+ Stevens/`Ham*`/crystal-field/full-tensor systems; sigma and nn tensors in
383
+ the geometric pass; 22-case MATLAB suite plus the 20 EasySpin `hamsymm_*`
384
+ tests.
385
+ - Strain: multi-electron g strain and per-electron D strain with tilted
386
+ `DFrame` and `DStrainCorr`; `SpinSystem.DStrain` is `(nElectrons, 2)`.
387
+ Transition-level MATLAB validation of strain widths.
388
+ - `ham_zf`: `BFrame` rotation of high-order Stevens terms (Wigner D).
389
+ - `curry`/`blochsteady` MATLAB validation suite (5 cases).
390
+ - Hamiltonian MATLAB validation extended from 10 to 92 cases mirroring
391
+ EasySpin's `ham_*` tests.
392
+ - `esfit`: dict-style `lb`/`ub` bounds.
393
+ - `Options.Method` accepts `exact`/`perturb`/`perturbN`; new
394
+ `Options.AccumMethod`, `Accuracy`, `MaxIterations`;
395
+ `Experiment.CenterSweep`, `mwCenterSweep`, `ModAmp`.
396
+
397
+ ### Fixed — EasySpin port completion
398
+ - `ham()` omitted higher-order Zeeman terms (`Sys.Ham*`); now includes
399
+ `ham_ezho` like EasySpin `ham.m`.
400
+ - `SpinSystem`: scalar `D` meant `[D D D]` (isotropic); now `[D, E=0]` as in
401
+ EasySpin. EasySpin input shorthands that crashed (per-electron scalar
402
+ g/D, 1-D `Q`/`sigma`/frames for one nucleus, scalar/per-pair `nn`, scalar
403
+ `gnscale`) are accepted; `ee=[J]` per-pair isotropic input no longer
404
+ raises in `ham_ee`; `g` must be zero with `Ham110`/`Ham112`.
405
+ - `saffron`: double orientation weighting under `ProductRule`; `t2`
406
+ UnboundLocalError for 2D custom sequences.
407
+ - `spidyan`: pulses were propagated in the lab frame with DC envelopes
408
+ (off-resonance) and `Sys.ZeemanFreq` was ignored.
409
+ - `salt`: `Experiment.Range` was treated as an EPR field window instead of
410
+ the RF window at fixed field.
411
+ - `pepper`/`chili` reject sets of equivalent nuclei (`SpinSystem.n > 1`)
412
+ explicitly, as EasySpin does.
413
+
414
+ ### Fixed
415
+ - `esfit` Nelder-Mead simplex: initial simplex edge length increased from
416
+ 0.05 to 0.2 in the transformed [-1, 1] parameter space, matching MATLAB
417
+ EasySpin's `esfit_simplex.m` (`delta = 0.1 * (ub - lb)` in original space).
418
+ The undersized initial simplex caused premature convergence to local
419
+ minima on rugged loss surfaces (alpha-test report: 2-parameter EPR fits
420
+ converging to wrong values even with reasonable bounds). Added
421
+ `TestSimplexExploration` regression tests in `tests/test_esfit.py`.
422
+ - `esfit` autoscale: scale factor is now forced positive (matches EasySpin
423
+ `esfit.m:1049` `coeffs(1) = abs(coeffs(1))`). A negative scale lets the
424
+ optimizer "fit" by sign-flipping the model, which created spurious local
425
+ minima on derivative spectra.
426
+ - `salt` CUDA device wiring: frequency-swept path kept `g_mat`, `A_i`,
427
+ frame rotations, flattened arrays, and spectrum bins on the active
428
+ compute device. `Options(device='cuda')` now flows end-to-end through
429
+ the freq-swept salt path; `freq_axis` and `spec` are returned on CPU
430
+ to preserve the public API contract.
431
+ - `pepper`, `resfields_batch`, `resfields_perturb`, and all
432
+ `ham_{ee,ez,hf,nn,nq,nz,zf}.py`: tightened device propagation so
433
+ `Options(device='cuda')` no longer leaks intermediate CPU tensors.
434
+ - `eprload` Bruker BES3T: case-insensitive companion-file lookup
435
+ (probes both `.DSC/.DTA` and `.dsc/.dta`) so Linux checkouts with
436
+ lowercase fixtures load correctly.
437
+ - MATLAB validation tests (`test_chili_matlab_validation.py`,
438
+ `test_endorfrq_matlab_validation.py`): `float(np.asarray(x).squeeze())`
439
+ replaces `float(x)` on `.mat` fields to work with numpy ≥ 1.25 where
440
+ 0-dim-array→scalar conversion is a hard error rather than a warning.
441
+
442
+ ### Added
443
+ - `esfit` new option `target='auto'` (now the default): inspects the data
444
+ and picks `'int'` for derivative-like spectra (mean ≪ std) and `'fcn'`
445
+ otherwise. Matches EasySpin's behavior of auto-selecting the integral
446
+ target for pepper/garlic with `Harmonic > 0`. Smooths the loss landscape
447
+ for derivative EPR.
448
+ - `esfit` new method `method='global'`: particle-swarm (global) followed by
449
+ Nelder-Mead (local polish). Recommended for realistic EPR fits with
450
+ rugged loss landscapes where plain `simplex` gets trapped. Recovers
451
+ `gx=2.001 (true 2.000)` on the canonical Harmonic=1 powder fit where
452
+ plain `simplex` returned `gx=2.080`. Brings the optimizer count to 9.
453
+ - `esfit` new option `n_workers: int = 1`: enables ThreadPoolExecutor
454
+ evaluation of the population for `swarm`, `genetic`, `montecarlo`,
455
+ `grid`, and `global` methods. Effective for forward models that release
456
+ the GIL during torch tensor ops (pepper/garlic/chili/saffron).
457
+ - `TestAutoTargetAndGlobal` and `TestSimplexExploration` regression tests
458
+ in `tests/test_esfit.py` (6 new tests total).
459
+ - `benchmarks/PERFORMANCE_SUMMARY.md` and `benchmarks/RUNLOG_2026-04-20.md`
460
+ (development records, not distributed; their results are summarised in
461
+ `benchmarks/results/BENCHMARK_REPORT.md` and embedded in
462
+ `benchmarks/analysis/make_manuscript_figures.py`)
463
+ — performance and accuracy summary (MATLAB parity
464
+ table, pepper CPU vs GPU scaling, `differentiable_spectrum` 2.5× GPU
465
+ speedup, esfit method comparison, 5-parameter stress test, MSE vs
466
+ integral-MSE finding for gradient-based fitting).
467
+
468
+ ## [0.1.0] — 2026-04-18
469
+
470
+ ### Initial PyPI release
471
+
472
+ Full EasySpin parity achieved — all scientifically relevant MATLAB functions
473
+ ported to Python/PyTorch with 1820+ passing tests.
474
+
475
+ **CW EPR simulators**
476
+ - `pepper` — powder CW EPR (MATLAB cosine > 0.999)
477
+ - `garlic` — solution / fast-motion EPR (MATLAB cosine 1.0000)
478
+ - `chili` — slow-motion SLE (MATLAB cosine 0.92–0.997)
479
+ - `salt` — ENDOR powder (MATLAB cosine > 0.92)
480
+ - `curry` — magnetometry (susceptibility, magnetization)
481
+ - `blochsteady` — Bloch steady-state
482
+ - `levels` — energy level diagrams
483
+ - `fastmotion` — Kivelson/Freed linewidths
484
+
485
+ **Pulse EPR**
486
+ - `saffron` — predefined (2p/3p/4p ESEEM, HYSCORE, MimsENDOR) + custom sequences
487
+ - `saffron_thyme` — real-pulse orientation-averaged propagation
488
+ - `spidyan` — arbitrary pulse sequences with relaxation and phase cycling
489
+ - `pulse`, `exciteprofile`, `resonator`, `rfmixer`, `transmitter` — pulse primitives
490
+
491
+ **Trajectory-based**
492
+ - `cardamom` — diffusion, jump, MD-direct, ISTOs methods
493
+ - `stochtraj_diffusion`, `stochtraj_jump`
494
+ - `mdload`, `mdhmm`, `mdtraj2oripot` — MD trajectory analysis
495
+
496
+ **Differentiable**
497
+ - `differentiable_spectrum` — S=1/2 analytical + N-spin broadband via batch `eigh`
498
+ - Gradients validated vs finite differences for g, A, D tensors
499
+
500
+ **Fitting**
501
+ - `esfit` — 8 optimizers: Nelder-Mead, L-BFGS-B, Powell, grid, Monte Carlo,
502
+ genetic, particle swarm, Levenberg-Marquardt
503
+ - `autoguess` — starting-parameter estimation
504
+
505
+ **Data I/O**
506
+ - `eprload` — 12 vendor formats (BES3T, ESP, Bruker, Varian, JEOL, Magnettech, …)
507
+ - `eprsave` — BES3T writer
508
+ - `orca2torchspin` — ORCA QC output import (.out, .prop, _property.txt)
509
+
510
+ **GPU + compilation**
511
+ - `Options.device='cuda'` support throughout simulator chain
512
+ - Opt-in `torch.compile` via `TORCHSPIN_COMPILE=1`
513
+ - Batched simulation via `batch_pepper`, `batch_simulate`
514
+
515
+ ### Conventions
516
+ - Energy units: MHz throughout
517
+ - Field units: mT
518
+ - Euler angles: radians, z-y'-z'' passive rotation
519
+ - Default dtype: `torch.complex128`
520
+
521
+ [Unreleased]: https://github.com/follmerlab/torchspin/compare/v0.1.0...HEAD
522
+ [0.1.0]: https://github.com/follmerlab/torchspin/releases/tag/v0.1.0
@@ -0,0 +1,38 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use torchspin in your research, please cite both torchspin and the upstream EasySpin paper."
3
+ title: "torchspin: Differentiable PyTorch port of EasySpin for EPR spectroscopy"
4
+ type: software
5
+ version: "0.3.0"
6
+ date-released: "2026-10-04"
7
+ license: MIT
8
+ authors:
9
+ - family-names: "Follmer"
10
+ given-names: "Alec"
11
+ email: "ahfollmer@ucdavis.edu"
12
+ repository-code: "https://github.com/follmerlab/torchspin"
13
+ url: "https://github.com/follmerlab/torchspin"
14
+ keywords:
15
+ - "EPR"
16
+ - "ESR"
17
+ - "spectroscopy"
18
+ - "spin-Hamiltonian"
19
+ - "pytorch"
20
+ - "autograd"
21
+ - "magnetic resonance"
22
+ - "EasySpin"
23
+
24
+ references:
25
+ - type: article
26
+ title: "EasySpin, a comprehensive software package for spectral simulation and analysis in EPR"
27
+ authors:
28
+ - family-names: "Stoll"
29
+ given-names: "Stefan"
30
+ - family-names: "Schweiger"
31
+ given-names: "Arthur"
32
+ journal: "Journal of Magnetic Resonance"
33
+ volume: 178
34
+ issue: 1
35
+ start: 42
36
+ end: 55
37
+ year: 2006
38
+ doi: "10.1016/j.jmr.2005.08.013"
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2006-2026 Stefan Stoll and other contributors (EasySpin MATLAB toolbox)
4
+ Copyright (c) 2024-2026 Alec Follmer and torchspin contributors (Python/PyTorch port)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # Files to include in the sdist beyond auto-discovered package files.
2
+ include LICENSE.md
3
+ include README.md
4
+ include CHANGELOG.md
5
+ include CITATION.cff
6
+ include pyproject.toml
7
+
8
+ # Package runtime data shipped inside torchspin/.
9
+ recursive-include torchspin/data *.txt
10
+ include torchspin/py.typed
11
+
12
+ # Keep the sdist small: reference data, notebooks and benchmarks are repo-only.
13
+ prune tests
14
+ prune docs
15
+ prune examples
16
+ prune benchmarks
17
+ prune .github
18
+ global-exclude *.m
19
+ global-exclude *.mat
20
+ global-exclude *.png
21
+ global-exclude *.ipynb
22
+ global-exclude .DS_Store