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.
- torchspin-0.3.0/CHANGELOG.md +522 -0
- torchspin-0.3.0/CITATION.cff +38 -0
- torchspin-0.3.0/LICENSE.md +22 -0
- torchspin-0.3.0/MANIFEST.in +22 -0
- torchspin-0.3.0/PKG-INFO +267 -0
- torchspin-0.3.0/README.md +216 -0
- torchspin-0.3.0/pyproject.toml +69 -0
- torchspin-0.3.0/setup.cfg +4 -0
- torchspin-0.3.0/torchspin/__init__.py +363 -0
- torchspin-0.3.0/torchspin/_cardamom_istos.py +327 -0
- torchspin-0.3.0/torchspin/_cardamom_propagatedm.py +413 -0
- torchspin-0.3.0/torchspin/_cardamom_utils.py +113 -0
- torchspin-0.3.0/torchspin/_compile.py +100 -0
- torchspin-0.3.0/torchspin/_linalg.py +140 -0
- torchspin-0.3.0/torchspin/angmom.py +742 -0
- torchspin-0.3.0/torchspin/autograd.py +557 -0
- torchspin-0.3.0/torchspin/autoguess.py +342 -0
- torchspin-0.3.0/torchspin/batch.py +225 -0
- torchspin-0.3.0/torchspin/blochsteady.py +274 -0
- torchspin-0.3.0/torchspin/cardamom.py +552 -0
- torchspin-0.3.0/torchspin/chili.py +932 -0
- torchspin-0.3.0/torchspin/chili_sle.py +1233 -0
- torchspin-0.3.0/torchspin/constants.py +88 -0
- torchspin-0.3.0/torchspin/convspec.py +148 -0
- torchspin-0.3.0/torchspin/ctafft.py +91 -0
- torchspin-0.3.0/torchspin/curry.py +241 -0
- torchspin-0.3.0/torchspin/data/FourierSeriesCoefficients.txt +89 -0
- torchspin-0.3.0/torchspin/data/GaussianCascadeCoefficients.txt +56 -0
- torchspin-0.3.0/torchspin/data/isotopedata.txt +416 -0
- torchspin-0.3.0/torchspin/data/spacegroups.txt +637 -0
- torchspin-0.3.0/torchspin/dataproc.py +361 -0
- torchspin-0.3.0/torchspin/dipbackground.py +69 -0
- torchspin-0.3.0/torchspin/dipkernel.py +102 -0
- torchspin-0.3.0/torchspin/diptensor.py +138 -0
- torchspin-0.3.0/torchspin/endorfrq.py +198 -0
- torchspin-0.3.0/torchspin/endorfrq_perturb.py +302 -0
- torchspin-0.3.0/torchspin/eprload.py +1538 -0
- torchspin-0.3.0/torchspin/eprsave.py +165 -0
- torchspin-0.3.0/torchspin/esfit.py +2427 -0
- torchspin-0.3.0/torchspin/evolve.py +274 -0
- torchspin-0.3.0/torchspin/ewrls.py +124 -0
- torchspin-0.3.0/torchspin/excitation.py +107 -0
- torchspin-0.3.0/torchspin/exciteprofile.py +132 -0
- torchspin-0.3.0/torchspin/experiment.py +323 -0
- torchspin-0.3.0/torchspin/exponfit.py +177 -0
- torchspin-0.3.0/torchspin/fastmotion.py +182 -0
- torchspin-0.3.0/torchspin/fdaxis.py +57 -0
- torchspin-0.3.0/torchspin/fitgui.py +350 -0
- torchspin-0.3.0/torchspin/garlic.py +760 -0
- torchspin-0.3.0/torchspin/ham.py +145 -0
- torchspin-0.3.0/torchspin/ham_cf.py +115 -0
- torchspin-0.3.0/torchspin/ham_ee.py +124 -0
- torchspin-0.3.0/torchspin/ham_ez.py +119 -0
- torchspin-0.3.0/torchspin/ham_ezho.py +403 -0
- torchspin-0.3.0/torchspin/ham_hf.py +122 -0
- torchspin-0.3.0/torchspin/ham_nn.py +108 -0
- torchspin-0.3.0/torchspin/ham_nq.py +100 -0
- torchspin-0.3.0/torchspin/ham_nz.py +136 -0
- torchspin-0.3.0/torchspin/ham_oz.py +115 -0
- torchspin-0.3.0/torchspin/ham_so.py +118 -0
- torchspin-0.3.0/torchspin/ham_zf.py +254 -0
- torchspin-0.3.0/torchspin/hamsymm.py +723 -0
- torchspin-0.3.0/torchspin/initstate.py +158 -0
- torchspin-0.3.0/torchspin/isotopologues.py +296 -0
- torchspin-0.3.0/torchspin/levels.py +189 -0
- torchspin-0.3.0/torchspin/levelsplot.py +363 -0
- torchspin-0.3.0/torchspin/lineshape.py +715 -0
- torchspin-0.3.0/torchspin/lpsvd.py +219 -0
- torchspin-0.3.0/torchspin/makespec.py +87 -0
- torchspin-0.3.0/torchspin/mdhmm.py +573 -0
- torchspin-0.3.0/torchspin/mdload.py +788 -0
- torchspin-0.3.0/torchspin/mdtraj2oripot.py +90 -0
- torchspin-0.3.0/torchspin/ml.py +377 -0
- torchspin-0.3.0/torchspin/mlpsvd.py +297 -0
- torchspin-0.3.0/torchspin/nucdata.py +310 -0
- torchspin-0.3.0/torchspin/nucfrq2d.py +228 -0
- torchspin-0.3.0/torchspin/orca2torchspin.py +1198 -0
- torchspin-0.3.0/torchspin/ordering.py +78 -0
- torchspin-0.3.0/torchspin/oripotentialplot.py +246 -0
- torchspin-0.3.0/torchspin/orisel.py +176 -0
- torchspin-0.3.0/torchspin/pepper.py +2019 -0
- torchspin-0.3.0/torchspin/pepper_autograd.py +613 -0
- torchspin-0.3.0/torchspin/photoselect.py +174 -0
- torchspin-0.3.0/torchspin/plegendre.py +131 -0
- torchspin-0.3.0/torchspin/propint.py +127 -0
- torchspin-0.3.0/torchspin/pulse.py +652 -0
- torchspin-0.3.0/torchspin/py.typed +0 -0
- torchspin-0.3.0/torchspin/rapidscan2spc.py +101 -0
- torchspin-0.3.0/torchspin/resfields.py +337 -0
- torchspin-0.3.0/torchspin/resfields_batch.py +446 -0
- torchspin-0.3.0/torchspin/resfields_eig.py +250 -0
- torchspin-0.3.0/torchspin/resfields_perturb.py +706 -0
- torchspin-0.3.0/torchspin/resfreqs_matrix.py +344 -0
- torchspin-0.3.0/torchspin/resfreqs_perturb.py +299 -0
- torchspin-0.3.0/torchspin/resonator.py +310 -0
- torchspin-0.3.0/torchspin/resonatorprofile.py +85 -0
- torchspin-0.3.0/torchspin/rfmixer.py +147 -0
- torchspin-0.3.0/torchspin/rotations.py +87 -0
- torchspin-0.3.0/torchspin/rotutils.py +880 -0
- torchspin-0.3.0/torchspin/saffron.py +1523 -0
- torchspin-0.3.0/torchspin/saffron_pathways.py +92 -0
- torchspin-0.3.0/torchspin/saffron_peaks.py +580 -0
- torchspin-0.3.0/torchspin/saffron_thyme.py +472 -0
- torchspin-0.3.0/torchspin/salt.py +680 -0
- torchspin-0.3.0/torchspin/sigeq.py +71 -0
- torchspin-0.3.0/torchspin/signalprocessing.py +226 -0
- torchspin-0.3.0/torchspin/sitetransforms.py +254 -0
- torchspin-0.3.0/torchspin/sphgrid.py +470 -0
- torchspin-0.3.0/torchspin/spidyan.py +1508 -0
- torchspin-0.3.0/torchspin/spinladder.py +161 -0
- torchspin-0.3.0/torchspin/spinops.py +265 -0
- torchspin-0.3.0/torchspin/spinsystem.py +1427 -0
- torchspin-0.3.0/torchspin/stackplot.py +167 -0
- torchspin-0.3.0/torchspin/stev.py +283 -0
- torchspin-0.3.0/torchspin/stochtraj_diffusion.py +494 -0
- torchspin-0.3.0/torchspin/stochtraj_jump.py +195 -0
- torchspin-0.3.0/torchspin/strainwidth.py +727 -0
- torchspin-0.3.0/torchspin/transmitter.py +89 -0
- torchspin-0.3.0/torchspin/utils.py +605 -0
- torchspin-0.3.0/torchspin.egg-info/PKG-INFO +267 -0
- torchspin-0.3.0/torchspin.egg-info/SOURCES.txt +122 -0
- torchspin-0.3.0/torchspin.egg-info/dependency_links.txt +1 -0
- torchspin-0.3.0/torchspin.egg-info/requires.txt +28 -0
- 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
|