lockkernel 1.1.0__tar.gz → 1.1.1__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.
- lockkernel-1.1.1/MANIFEST.in +1 -0
- lockkernel-1.1.1/PKG-INFO +679 -0
- lockkernel-1.1.1/README.md +649 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/pyproject.toml +1 -1
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/__init__.py +1 -1
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/parametric.py +22 -11
- lockkernel-1.1.1/src/lockkernel.egg-info/PKG-INFO +679 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel.egg-info/SOURCES.txt +2 -0
- lockkernel-1.1.1/tests/conftest.py +23 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/tests/test_measured.py +1 -1
- {lockkernel-1.1.0 → lockkernel-1.1.1}/tests/test_parametric.py +15 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/tests/test_universality.py +6 -5
- lockkernel-1.1.0/PKG-INFO +0 -207
- lockkernel-1.1.0/README.md +0 -177
- lockkernel-1.1.0/src/lockkernel.egg-info/PKG-INFO +0 -207
- {lockkernel-1.1.0 → lockkernel-1.1.1}/LICENSE +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/NOTICE +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/setup.cfg +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/cumulant.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/ensemble.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/exact.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/kernels.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/lineshapes.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel/measured.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel.egg-info/dependency_links.txt +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel.egg-info/requires.txt +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/src/lockkernel.egg-info/top_level.txt +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/tests/test_cumulant.py +0 -0
- {lockkernel-1.1.0 → lockkernel-1.1.1}/tests/test_meanfield.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include tests/conftest.py
|
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lockkernel
|
|
3
|
+
Version: 1.1.1
|
|
4
|
+
Summary: Locking kernel universality: exact thresholds and exponents of synchronization transitions, and fits of measured branches with error bars and refusals
|
|
5
|
+
Author: Tanvir Mahmud Mahim
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/TaN-MM-Org/lockkernel
|
|
8
|
+
Project-URL: Issues, https://github.com/TaN-MM-Org/lockkernel/issues
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
License-File: NOTICE
|
|
24
|
+
Requires-Dist: numpy>=1.22
|
|
25
|
+
Requires-Dist: scipy>=1.8
|
|
26
|
+
Requires-Dist: mpmath>=1.2
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# lockkernel
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/lockkernel/) [](https://doi.org/10.5281/zenodo.22829483) [](https://github.com/TaN-MM-Org/lockkernel/actions)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
36
|
+
`lockkernel` is a Python package about **synchronization**: many
|
|
37
|
+
oscillators, each with a slightly different natural frequency, start to
|
|
38
|
+
move in step once the coupling between them is strong enough. Below a
|
|
39
|
+
certain coupling (the **threshold**) nothing happens; above it, a
|
|
40
|
+
collective motion appears and grows. The package answers the questions
|
|
41
|
+
one asks about that onset:
|
|
42
|
+
|
|
43
|
+
- At what coupling does synchronization start?
|
|
44
|
+
- How fast does it grow above that point? Near the threshold the growth
|
|
45
|
+
follows a power law, and the power is called the **exponent**
|
|
46
|
+
(`beta`).
|
|
47
|
+
- Is the onset smooth, or does the system jump and show hysteresis
|
|
48
|
+
(a first-order onset)?
|
|
49
|
+
- Given a **measured** onset, from an experiment or a simulation, what
|
|
50
|
+
are the threshold and the exponent, with error bars? What does the
|
|
51
|
+
exponent say about the system? How many measurements does a target
|
|
52
|
+
error bar need?
|
|
53
|
+
|
|
54
|
+
It has two halves. The **theory half** computes the onset exactly from
|
|
55
|
+
two inputs: the spread of natural frequencies, and the **locking
|
|
56
|
+
kernel**, a function that says how strongly an oscillator follows the
|
|
57
|
+
group as a function of how far its own frequency is from the group's.
|
|
58
|
+
The collective motion must be the one the oscillators themselves
|
|
59
|
+
produce (the "self-consistency condition"); one substitution turns
|
|
60
|
+
that condition into an exact formula (a "parametric solution") valid
|
|
61
|
+
for any frequency spread and any kernel. The exponent depends only on how fast the kernel falls off
|
|
62
|
+
far from the centre (its **tail**). The **lab half** (`lockkernel.measured`)
|
|
63
|
+
works the other way round: it fits measured points for the threshold
|
|
64
|
+
and the exponent, reads the kernel's tail back off the exponent, and
|
|
65
|
+
plans how many points a target error bar costs. When the data cannot
|
|
66
|
+
support the number asked for, it stops with an error message that says
|
|
67
|
+
why, rather than returning a number that looks fine but is not.
|
|
68
|
+
|
|
69
|
+
## Contents
|
|
70
|
+
|
|
71
|
+
- [A short guide to the words used here](#a-short-guide-to-the-words-used-here)
|
|
72
|
+
- [Install and conventions](#install-and-conventions)
|
|
73
|
+
- [Examples](#examples) (each with the output it prints)
|
|
74
|
+
- [What is in the package](#what-is-in-the-package)
|
|
75
|
+
- [When it refuses, and why](#when-it-refuses-and-why)
|
|
76
|
+
- [How the results are checked](#how-the-results-are-checked)
|
|
77
|
+
- [Corrections in earlier versions](#corrections-in-earlier-versions)
|
|
78
|
+
- [Limits](#limits)
|
|
79
|
+
- [Where it comes from](#where-it-comes-from)
|
|
80
|
+
- [Citing, support and license](#citing-support-and-license)
|
|
81
|
+
|
|
82
|
+
## A short guide to the words used here
|
|
83
|
+
|
|
84
|
+
- **Detuning** `delta` -- how far one oscillator's natural frequency
|
|
85
|
+
is from the centre of the group.
|
|
86
|
+
- **Line shape** `p(delta)` -- the distribution of detunings across
|
|
87
|
+
the group (the name comes from spectroscopy, where it is the shape
|
|
88
|
+
of a spectral line). The package ships a Lorentzian, a Gaussian, a
|
|
89
|
+
Student-t (heavy tails), a box (flat) and a two-peaked Gaussian, and
|
|
90
|
+
you can build your own `LineShape`.
|
|
91
|
+
- **Coupling** `chiN` -- the coupling per oscillator times the number
|
|
92
|
+
of oscillators. The **threshold** `chiN_c` is the coupling where
|
|
93
|
+
synchronization starts.
|
|
94
|
+
- **Order parameter** `R` -- how synchronized the group is: 0 for no
|
|
95
|
+
common motion, 1 for perfect step. It is zero below the threshold.
|
|
96
|
+
- **Reduced coupling** `eps = chiN/chiN_c - 1` -- the relative
|
|
97
|
+
distance above the threshold. Near the onset `R ~ A eps^beta`, with
|
|
98
|
+
**amplitude** `A` and **exponent** `beta`.
|
|
99
|
+
- **Locking bandwidth** `Omega = chiN * R` -- the range of detunings
|
|
100
|
+
over which an oscillator follows the group.
|
|
101
|
+
- **Locking kernel** `W(u)` -- the time-averaged share of an
|
|
102
|
+
oscillator that lines up with the group, as a function of its
|
|
103
|
+
detuning in units of the locking bandwidth, `u = delta/Omega`.
|
|
104
|
+
`W(0) = 1` (an oscillator at the centre follows perfectly) and `W`
|
|
105
|
+
is even. Its **mass** `m` is its area, `m = integral W(u) du`. Its
|
|
106
|
+
**tail exponent** `s` describes how fast it falls far out:
|
|
107
|
+
`W ~ |u|^-s`. The package's kernels are:
|
|
108
|
+
- *conservative* `W = 1/(1+u^2)`, for spins with no damping
|
|
109
|
+
(`s = 2`, mass `pi`);
|
|
110
|
+
- *Kuramoto* `W = sqrt(1-u^2)` for `|u| < 1` and 0 outside, for
|
|
111
|
+
overdamped phase oscillators, whose motion is dominated by friction
|
|
112
|
+
(the classic Kuramoto model; no tail at all, mass `pi/2`);
|
|
113
|
+
- the test family `W = 1/(1+|u|^s)`, and a Gaussian kernel;
|
|
114
|
+
- a kernel averaged over a broad, power-law spread of coupling
|
|
115
|
+
strengths between oscillators (`heterogeneous`).
|
|
116
|
+
- **The rule that links them** (from the `parametric` module's
|
|
117
|
+
derivation, and tested below): the threshold is
|
|
118
|
+
`chiN_c = 1/(p(0) m)`, and the exponent is
|
|
119
|
+
`beta = 1/(s-1)` for `1 < s < 3` and `beta = 1/2` for `s >= 3` or
|
|
120
|
+
for a kernel with no algebraic tail. So a measured `beta` above 1/2
|
|
121
|
+
names the tail, `s = 1 + 1/beta`, while `beta = 1/2` only says
|
|
122
|
+
"`s >= 3`" and cannot name one value.
|
|
123
|
+
- **Order of the onset** -- for the conservative kernel, the sign of
|
|
124
|
+
one number `c` (an integral over the line shape) decides it:
|
|
125
|
+
`c > 0` gives a smooth (continuous) onset, `c < 0` a jump with
|
|
126
|
+
hysteresis (first order).
|
|
127
|
+
- **Cumulant dynamics**, **Wineland parameter** -- terms from the
|
|
128
|
+
quantum-spin half of the package; see
|
|
129
|
+
[What is in the package](#what-is-in-the-package).
|
|
130
|
+
|
|
131
|
+
## Install and conventions
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
pip install lockkernel
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
It needs Python 3.9 or newer, NumPy 1.22 or newer, SciPy 1.8 or newer
|
|
138
|
+
and mpmath 1.2 or newer, and nothing else.
|
|
139
|
+
|
|
140
|
+
- **Units.** Detunings, line widths, couplings `chiN` and the locking
|
|
141
|
+
bandwidth `Omega` share one frequency unit of your choice (the line
|
|
142
|
+
width is a natural one). In the dynamics modules, time is in the
|
|
143
|
+
inverse of that unit (the Hamiltonian is written with hbar = 1).
|
|
144
|
+
- **Precision.** The theory half (`parametric`, `kernels`,
|
|
145
|
+
`lineshapes`) works in mpmath's arbitrary precision and returns
|
|
146
|
+
mpmath numbers. Set the working precision with `mpmath.mp.dps`
|
|
147
|
+
(decimal digits). The lab half and the dynamics work in ordinary
|
|
148
|
+
floating point with NumPy.
|
|
149
|
+
- **Line shapes** are normalised probability densities; `lorentzian`
|
|
150
|
+
and `gaussian` take a full width at half maximum, `box` a half
|
|
151
|
+
width.
|
|
152
|
+
|
|
153
|
+
## Examples
|
|
154
|
+
|
|
155
|
+
Each example below runs as written, and the output shown is what it
|
|
156
|
+
printed with lockkernel 1.1.1. Line widths, couplings and noise levels
|
|
157
|
+
are illustrative values, not taken from any experiment.
|
|
158
|
+
|
|
159
|
+
### 1. Threshold and exponent from the theory
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import mpmath as mp
|
|
163
|
+
from lockkernel.lineshapes import lorentzian, gaussian
|
|
164
|
+
from lockkernel.kernels import conservative, kuramoto, power_tail, predicted_beta
|
|
165
|
+
from lockkernel.parametric import threshold, extract_beta
|
|
166
|
+
|
|
167
|
+
mp.mp.dps = 25 # working precision of mpmath, in decimal digits
|
|
168
|
+
|
|
169
|
+
line = lorentzian(fwhm=1.0)
|
|
170
|
+
print("threshold, conservative kernel:", mp.nstr(threshold(line, conservative()), 15))
|
|
171
|
+
print("threshold, Kuramoto kernel: ", mp.nstr(threshold(line, kuramoto()), 15))
|
|
172
|
+
|
|
173
|
+
# Local slopes d log R / d log eps at Omega = 1e-3 ... 1e-6 (closest to onset last).
|
|
174
|
+
slopes = extract_beta(gaussian(1.0), power_tail(2.5), [-3, -4, -5, -6])
|
|
175
|
+
print("local exponents:", [mp.nstr(b, 8) for b in slopes])
|
|
176
|
+
print("predicted 1/(s-1):", predicted_beta(2.5))
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
threshold, conservative kernel: 0.5
|
|
181
|
+
threshold, Kuramoto kernel: 1.0
|
|
182
|
+
local exponents: ['0.67152587', '0.6681861', '0.66714445']
|
|
183
|
+
predicted 1/(s-1): 0.6666666666666666
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The threshold is `1/(p(0) m)`: the kernel enters only through its
|
|
187
|
+
mass, so the Kuramoto threshold (mass `pi/2`) is twice the
|
|
188
|
+
conservative one (mass `pi`) on every line. `extract_beta` steps the
|
|
189
|
+
exact branch towards the onset and returns the local slope between
|
|
190
|
+
neighbouring points; the list settling down is the evidence that the
|
|
191
|
+
exponent has been reached. List the exponents of `Omega` so that the
|
|
192
|
+
one closest to the onset comes last, because the last entry is the
|
|
193
|
+
one to quote. Here it is within 0.1 % of `2/3`.
|
|
194
|
+
|
|
195
|
+
### 2. The exact branch against a closed form
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
import mpmath as mp
|
|
199
|
+
from lockkernel.lineshapes import lorentzian
|
|
200
|
+
from lockkernel.kernels import conservative
|
|
201
|
+
from lockkernel.parametric import branch_point
|
|
202
|
+
|
|
203
|
+
mp.mp.dps = 30
|
|
204
|
+
line, kern = lorentzian(fwhm=1.0), conservative()
|
|
205
|
+
a = mp.mpf(1) / 2 # half width of this line
|
|
206
|
+
for Omega in ["1e-2", "1e-4", "1e-6"]:
|
|
207
|
+
chiN, R, eps = branch_point(line, kern, mp.mpf(Omega))
|
|
208
|
+
gap = abs(R - (1 - a / chiN))
|
|
209
|
+
print(f"Omega={Omega}: chiN={mp.nstr(chiN, 10)} R={mp.nstr(R, 10)} "
|
|
210
|
+
f"eps={mp.nstr(eps, 6)} matches 1 - a/chiN to 1e-28: {gap < 1e-28}")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
Omega=1e-2: chiN=0.51 R=0.01960784314 eps=0.02 matches 1 - a/chiN to 1e-28: True
|
|
215
|
+
Omega=1e-4: chiN=0.5001 R=0.000199960008 eps=0.0002 matches 1 - a/chiN to 1e-28: True
|
|
216
|
+
Omega=1e-6: chiN=0.500001 R=1.999996e-6 eps=2.0e-6 matches 1 - a/chiN to 1e-28: True
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The branch is traced by choosing the locking bandwidth `Omega` and
|
|
220
|
+
reading off the coupling and the order parameter. No equation is
|
|
221
|
+
solved, so nothing is lost to cancellation near the threshold. For
|
|
222
|
+
conservative spins on a Lorentzian line the answer is known in closed
|
|
223
|
+
form (`chiN = a + Omega`, `R = 1 - a/chiN`), and the package matches
|
|
224
|
+
it to 1e-28 at 30 digits.
|
|
225
|
+
|
|
226
|
+
### 3. Amplitude, and whether the onset is smooth
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
import mpmath as mp
|
|
230
|
+
from lockkernel.lineshapes import lorentzian, gaussian, bimodal_gaussian
|
|
231
|
+
from lockkernel.parametric import amplitude, c_coefficient
|
|
232
|
+
|
|
233
|
+
for line in (lorentzian(1.0), gaussian(1.0)):
|
|
234
|
+
print(f"{line.name:10s} A = {mp.nstr(amplitude(line), 12)}")
|
|
235
|
+
print("pi/2 =", mp.nstr(mp.pi / 2, 12))
|
|
236
|
+
|
|
237
|
+
# Two Gaussian peaks (width 1) at +-sep/2: the sign of c decides the order.
|
|
238
|
+
for sep in (1.0, 4.0):
|
|
239
|
+
c = c_coefficient(bimodal_gaussian(sep))
|
|
240
|
+
kind = "continuous onset" if c > 0 else "first order (hysteretic) onset"
|
|
241
|
+
print(f"sep = {sep}: c = {mp.nstr(c, 6)} -> {kind}")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
lorentzian A = 1.0
|
|
246
|
+
gaussian A = 1.57079632679
|
|
247
|
+
pi/2 = 1.57079632679
|
|
248
|
+
sep = 1.0: c = 0.245044 -> continuous onset
|
|
249
|
+
sep = 4.0: c = -0.0891192 -> first order (hysteretic) onset
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
For conservative spins the exponent is 1, so near the onset
|
|
253
|
+
`R = A eps`, with `A = pi p(0)^2 / c`. This example runs at mpmath's
|
|
254
|
+
default 15 digits; before version 1.1.1 `amplitude` and
|
|
255
|
+
`c_coefficient` needed about 30 digits to be right (see
|
|
256
|
+
[Corrections](#corrections-in-earlier-versions)). The two-peaked line
|
|
257
|
+
is not covered by the tests; the sign rule it illustrates is the one
|
|
258
|
+
derived in the `parametric` module.
|
|
259
|
+
|
|
260
|
+
### 4. Fit a measured branch
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
import numpy as np
|
|
264
|
+
from lockkernel import fit_branch, kernel_tail_from_beta
|
|
265
|
+
from lockkernel.lineshapes import lorentzian
|
|
266
|
+
from lockkernel.kernels import power_tail
|
|
267
|
+
from lockkernel.parametric import sweep, threshold
|
|
268
|
+
|
|
269
|
+
# Stand-in "measurement": 14 points of the exact branch for a kernel with
|
|
270
|
+
# tail s = 2.5 (so beta = 2/3), with 2 % seeded noise on R.
|
|
271
|
+
line, kern = lorentzian(1.0), power_tail(2.5)
|
|
272
|
+
pts = sweep(line, kern, np.linspace(-5.0, -2.4, 14))
|
|
273
|
+
chi = np.array([float(p[0]) for p in pts])
|
|
274
|
+
r = np.array([float(p[1]) for p in pts])
|
|
275
|
+
rng = np.random.default_rng(1)
|
|
276
|
+
r_meas = r * np.exp(rng.normal(0.0, 0.02, r.size))
|
|
277
|
+
|
|
278
|
+
fit = fit_branch(chi, r_meas, reference="illustrative: exact branch + 2 % noise",
|
|
279
|
+
sigma_r=0.02 * r_meas)
|
|
280
|
+
print(f"threshold chi_c = {fit.chi_c:.5f} +- {fit.sigma_chi_c:.1e}"
|
|
281
|
+
f" (exact {float(threshold(line, kern)):.5f})")
|
|
282
|
+
print(f"exponent beta = {fit.beta:.3f} +- {fit.sigma_beta:.3f} (exact 0.667)")
|
|
283
|
+
print(f"amplitude A = {fit.amplitude:.3f} +- {fit.sigma_amplitude:.3f}")
|
|
284
|
+
print(f"eps range {fit.eps_range[0]:.2e} .. {fit.eps_range[1]:.2e}, "
|
|
285
|
+
f"rms log residual {fit.residual_rms_log:.3f}")
|
|
286
|
+
|
|
287
|
+
s, s_err = kernel_tail_from_beta(fit.beta, fit.sigma_beta)
|
|
288
|
+
print(f"kernel tail s = {s:.2f} +- {s_err:.2f} (true 2.5)")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
threshold chi_c = 0.59441 +- 3.5e-09 (exact 0.59441)
|
|
293
|
+
exponent beta = 0.673 +- 0.003 (exact 0.667)
|
|
294
|
+
amplitude A = 0.652 +- 0.019
|
|
295
|
+
eps range 1.56e-07 .. 1.09e-03, rms log residual 0.011
|
|
296
|
+
kernel tail s = 2.49 +- 0.01 (true 2.5)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`fit_branch` fits `R = A (chi/chi_c - 1)^beta` for the threshold,
|
|
300
|
+
exponent and amplitude together. With your own data, pass your
|
|
301
|
+
couplings and order parameters, and a `reference` that says where
|
|
302
|
+
they come from (it is required). The error bars are the standard
|
|
303
|
+
asymptotic ones of a least-squares fit: they are right when the power
|
|
304
|
+
law holds over the fitted range and `sigma_r` is right. Without
|
|
305
|
+
`sigma_r`, the scatter of the points sets them. Here the fitted
|
|
306
|
+
exponent is 2.4 of its own error bars from the exact 2/3; the tests
|
|
307
|
+
allow four (see [How the results are checked](#how-the-results-are-checked)).
|
|
308
|
+
`kernel_tail_from_beta` turns the exponent into the kernel's tail
|
|
309
|
+
exponent `s = 1 + 1/beta`, with error `sigma_beta / beta^2`.
|
|
310
|
+
|
|
311
|
+
### 5. Plan the measurement, and a refusal
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
from lockkernel import beta_relative_sigma, points_for_beta, kernel_tail_from_beta
|
|
315
|
+
|
|
316
|
+
# How many points, spread evenly in log(eps) over 2 decades, with 5 %
|
|
317
|
+
# scatter in R, for an error bar of 0.01 on beta (threshold known)?
|
|
318
|
+
n, achieved = points_for_beta(0.01, decades=2.0, sigma_log=0.05)
|
|
319
|
+
print(f"points needed: {n} (error bar {achieved:.5f}); "
|
|
320
|
+
f"with {n - 1}: {beta_relative_sigma(n - 1, 2.0, 0.05):.5f}")
|
|
321
|
+
|
|
322
|
+
# A fitted beta = 0.52 +- 0.02 cannot name a kernel tail:
|
|
323
|
+
try:
|
|
324
|
+
kernel_tail_from_beta(0.52, 0.02)
|
|
325
|
+
except ValueError as err:
|
|
326
|
+
print("refused:", err)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
points needed: 12 (error bar 0.00999); with 11: 0.01035
|
|
331
|
+
refused: beta = 0.52 +- 0.02 is consistent with 1/2, which identifies only the CLASS s >= 3 (compact support or decay faster than |u|^-3); no single tail exponent can be named from it
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`beta_relative_sigma` gives the error bar of a straight-line slope on a
|
|
335
|
+
log-log plot. Despite its name, the result is the absolute error of
|
|
336
|
+
`beta`, not a relative one. It assumes the threshold is known, while
|
|
337
|
+
`fit_branch` also fits the threshold, which is harder, so plan with
|
|
338
|
+
some margin. `points_for_beta` finds the smallest number of points
|
|
339
|
+
that meets the target.
|
|
340
|
+
|
|
341
|
+
### 6. A spread of coupling strengths changes the exponent
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
import mpmath as mp
|
|
345
|
+
from lockkernel.lineshapes import gaussian
|
|
346
|
+
from lockkernel.kernels import kuramoto, heterogeneous, predicted_beta
|
|
347
|
+
from lockkernel.parametric import extract_beta
|
|
348
|
+
|
|
349
|
+
mp.mp.dps = 25
|
|
350
|
+
# Kuramoto oscillators whose coupling weights k follow P(k) ~ k^-3.8 (k >= 1).
|
|
351
|
+
ker = heterogeneous(kuramoto(), degree_exponent=3.8)
|
|
352
|
+
print("tail exponent s of the averaged kernel:", round(ker.tail, 12))
|
|
353
|
+
print("predicted beta:", round(predicted_beta(ker.tail), 12))
|
|
354
|
+
beta = extract_beta(gaussian(1.0), ker, [-3, -4, -5, -6])[-1]
|
|
355
|
+
print("beta from the branch:", mp.nstr(beta, 6))
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
tail exponent s of the averaged kernel: 1.8
|
|
360
|
+
predicted beta: 1.25
|
|
361
|
+
beta from the branch: 1.24989
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
When oscillators couple with different strengths `k`, drawn from a
|
|
365
|
+
power law `P(k) ~ k^-gamma`, the kernel that decides the onset is an
|
|
366
|
+
average over them. A strongly coupled oscillator stays locked far out
|
|
367
|
+
in detuning, so the averaged kernel gets a tail even when each
|
|
368
|
+
oscillator's own kernel has none: `s = min(s0, (gamma-2)/eta)`, with
|
|
369
|
+
`s0` the tail of the single-oscillator kernel (infinite for Kuramoto).
|
|
370
|
+
Here `s = 1.8`, so `beta = 1/(s-1) = 1.25`, which is `1/(gamma-3)`.
|
|
371
|
+
|
|
372
|
+
### 7. The quantum spin model against exact diagonalisation
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
import numpy as np
|
|
376
|
+
from lockkernel.cumulant import System, coherent_state_x, evolve, coherence, wineland_xi2
|
|
377
|
+
from lockkernel.exact import full_exact
|
|
378
|
+
|
|
379
|
+
# 8 emitters, two at each of four detunings, all starting along +x.
|
|
380
|
+
deltas = np.array([-1.0, -1.0, -0.3, -0.3, 0.3, 0.3, 1.0, 1.0])
|
|
381
|
+
chiN, times = 1.5, np.array([0.05, 0.2, 1.0])
|
|
382
|
+
|
|
383
|
+
u, count = np.unique(deltas, return_counts=True)
|
|
384
|
+
sysm = System(delta=u, n=count.astype(float), chiN=chiN)
|
|
385
|
+
states = evolve(coherent_state_x(sysm), sysm, times[-1], t_eval=times,
|
|
386
|
+
rtol=1e-11, atol=1e-14)
|
|
387
|
+
R_ex, xi_ex = full_exact(deltas, chiN, times)
|
|
388
|
+
for t, st, r, xi in zip(times, states, R_ex, xi_ex):
|
|
389
|
+
print(f"t={t:4.2f} R: cumulant {coherence(st, sysm):.6f} exact {r:.6f} "
|
|
390
|
+
f"xi^2: cumulant {wineland_xi2(st, sysm):.4f} exact {xi:.4f}")
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
t=0.05 R: cumulant 0.999012 exact 0.999012 xi^2: cumulant 0.9374 exact 0.9374
|
|
395
|
+
t=0.20 R: cumulant 0.984309 exact 0.984316 xi^2: cumulant 0.7818 exact 0.7824
|
|
396
|
+
t=1.00 R: cumulant 0.674467 exact 0.677579 xi^2: cumulant 0.5328 exact 0.5771
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The dynamics modules treat the conservative case as a quantum model:
|
|
400
|
+
two-level emitters (spin one-half) with equal all-to-all exchange
|
|
401
|
+
coupling and spread detunings, with no damping. The **cumulant**
|
|
402
|
+
solver follows the average of each emitter group plus the pair
|
|
403
|
+
correlations between emitters, and drops higher correlations. That
|
|
404
|
+
makes it fast for large numbers of emitters, but it is an
|
|
405
|
+
approximation. For small groups `full_exact` solves the full quantum
|
|
406
|
+
problem by exact diagonalisation (computing the quantum states of the
|
|
407
|
+
whole group directly, which is only affordable for a few emitters).
|
|
408
|
+
The two agree at short times and drift apart later, as a truncation
|
|
409
|
+
should. `xi^2` is the **Wineland spin-squeezing parameter**:
|
|
410
|
+
1 for uncorrelated spins, below 1 when the spins are entangled in a way
|
|
411
|
+
that improves phase measurements.
|
|
412
|
+
|
|
413
|
+
## What is in the package
|
|
414
|
+
|
|
415
|
+
The top level imports the `measured` names below and the submodules
|
|
416
|
+
`lineshapes`, `kernels`, `parametric` and `measured`. Import
|
|
417
|
+
`lockkernel.cumulant`, `lockkernel.ensemble` and `lockkernel.exact`
|
|
418
|
+
directly.
|
|
419
|
+
|
|
420
|
+
**Line shapes and kernels** (`lockkernel.lineshapes`, `lockkernel.kernels`)
|
|
421
|
+
|
|
422
|
+
- `LineShape` -- a symmetric density with its value at the centre
|
|
423
|
+
(`p0()`), and, where available, a floating-point `cdf` and `ppf`.
|
|
424
|
+
Makers: `lorentzian`, `gaussian`, `student_t`, `box`,
|
|
425
|
+
`bimodal_gaussian`; `LINESHAPES` maps names to them.
|
|
426
|
+
- `Kernel` -- an even kernel with `W(0) = 1`, its tail exponent, its
|
|
427
|
+
support, and `mass()`. Makers: `conservative`, `kuramoto`,
|
|
428
|
+
`power_tail(s)`, `gaussian_kernel`; `KERNELS` maps names to them.
|
|
429
|
+
- `heterogeneous(base, degree_exponent, eta=1, k_min=1)` -- the kernel
|
|
430
|
+
averaged over a power-law spread of coupling strengths (example 6).
|
|
431
|
+
- `predicted_beta(s)` -- the exponent the rule gives for tail `s`
|
|
432
|
+
(`None` meaning no algebraic tail).
|
|
433
|
+
|
|
434
|
+
**The exact solution** (`lockkernel.parametric`, mpmath precision)
|
|
435
|
+
|
|
436
|
+
- `G_of_Omega`, `H_of_Omega` -- the two integrals the solution is built
|
|
437
|
+
on: `G(Omega) = integral p(Omega u) W(u) du` and
|
|
438
|
+
`H = Omega G`, so that `chiN = 1/G` and `R = H`.
|
|
439
|
+
- `threshold`, `branch_point`, `sweep`, `extract_beta` -- the
|
|
440
|
+
threshold, one point `(chiN, R, eps)` of the branch, the branch at
|
|
441
|
+
`Omega = 10^e` for a list of `e`, and the local exponents along it.
|
|
442
|
+
- `c_coefficient`, `amplitude` -- for the conservative kernel, the
|
|
443
|
+
number `c` whose sign sets the order of the onset, and the amplitude
|
|
444
|
+
`A = pi p(0)^2 / c`.
|
|
445
|
+
- `tail_integral`, `amplitude_general` -- the amplitude for a kernel
|
|
446
|
+
with tail exponent `1 < s < 3`.
|
|
447
|
+
- `fold_interval` -- samples the branch and, if it folds back, returns
|
|
448
|
+
the coupling range of the hysteresis and the jump in `R`
|
|
449
|
+
(`None` if the branch does not fold). Not covered by the tests.
|
|
450
|
+
|
|
451
|
+
**Fitting measured data** (`lockkernel.measured`, also at the top level)
|
|
452
|
+
|
|
453
|
+
- `fit_branch(chi, r, reference, sigma_r=None, min_decade=1.0)` --
|
|
454
|
+
returns a `BranchFit` with `chi_c`, `beta`, `amplitude`, their
|
|
455
|
+
errors, `n_points`, `eps_range`, `residual_rms_log` (the
|
|
456
|
+
root-mean-square misfit in `log R`) and `reference`.
|
|
457
|
+
- `kernel_tail_from_beta(beta, sigma_beta=0, n_sigma=2)` -- the tail
|
|
458
|
+
exponent `s` and its error.
|
|
459
|
+
- `beta_relative_sigma(n_points, decades, sigma_log)`,
|
|
460
|
+
`points_for_beta(target_sigma_beta, decades, sigma_log)` -- the
|
|
461
|
+
error bar of `beta` for a planned measurement, and the number of
|
|
462
|
+
points for a target error bar.
|
|
463
|
+
|
|
464
|
+
**Quantum spin dynamics** (`lockkernel.cumulant`, `lockkernel.ensemble`,
|
|
465
|
+
`lockkernel.exact`)
|
|
466
|
+
|
|
467
|
+
- `System`, `State`, `coherent_state_x`, `evolve` -- groups ("classes")
|
|
468
|
+
of emitters with equal detuning, the cumulant state, the start with
|
|
469
|
+
all spins along +x, and the time evolution. Emitters detuned far
|
|
470
|
+
outside the locking range can be kept as free spins that precess
|
|
471
|
+
exactly without being integrated, so the line's tails are not cut.
|
|
472
|
+
- `evolve_meanfield` -- the same with all correlations dropped.
|
|
473
|
+
- `coherence` (the order parameter `R`), `wineland_xi2`,
|
|
474
|
+
`collective_moments` -- what is read from a state;
|
|
475
|
+
`physicality` and `valid_window` flag when the truncated state stops
|
|
476
|
+
being physical.
|
|
477
|
+
- `class_table`, `build_system` -- turn a line shape into a finite
|
|
478
|
+
set of detuning classes that keeps the whole population.
|
|
479
|
+
- `symmetric_exact` (all detunings equal, any number of emitters),
|
|
480
|
+
`full_exact` (full quantum problem, up to about twelve emitters) and
|
|
481
|
+
`class_exact` (emitters grouped by detuning, a few tens) -- exact
|
|
482
|
+
references. `class_exact` is not covered by the tests.
|
|
483
|
+
|
|
484
|
+
Each function's docstring (`help(lockkernel.fit_branch)`, for example)
|
|
485
|
+
gives its inputs and conventions.
|
|
486
|
+
|
|
487
|
+
## When it refuses, and why
|
|
488
|
+
|
|
489
|
+
`lockkernel` raises an error instead of guessing when:
|
|
490
|
+
|
|
491
|
+
- measured data come without a `reference` of at least 8 characters
|
|
492
|
+
saying where they come from;
|
|
493
|
+
- fewer than 6 points are given, or `chi` and `r` differ in length
|
|
494
|
+
(three parameters with error bars need more);
|
|
495
|
+
- the couplings are not finite and strictly increasing, or an order
|
|
496
|
+
parameter is not finite and positive (points below the threshold,
|
|
497
|
+
`R = 0`, carry no exponent information; drop them);
|
|
498
|
+
- `sigma_r` is not positive or not on the same grid as `r`;
|
|
499
|
+
- the fit does not converge, or its covariance is singular (the data
|
|
500
|
+
cannot pin down the three parameters separately);
|
|
501
|
+
- the fitted threshold is indistinguishable from the smallest
|
|
502
|
+
coupling: the data do not reach the onset;
|
|
503
|
+
- the fitted points span less than `min_decade` decades of `eps`
|
|
504
|
+
(default one): exponent and amplitude cannot be told apart on so
|
|
505
|
+
short a range;
|
|
506
|
+
- a fitted `beta` is consistent with 1/2 (only the class `s >= 3` is
|
|
507
|
+
identified), clearly below 1/2 (no kernel gives that; the fit has
|
|
508
|
+
probably left the near-threshold range), or not positive;
|
|
509
|
+
- a planned measurement has fewer than 3 points, non-positive range or
|
|
510
|
+
scatter, a non-positive target, or would need more than 10^7 points;
|
|
511
|
+
- `power_tail(s)` is asked for `s <= 1` (the kernel mass would be
|
|
512
|
+
infinite), or `heterogeneous` for `gamma <= 2` or a resulting tail
|
|
513
|
+
`s <= 1`;
|
|
514
|
+
- `tail_integral` is asked for `s` outside `1 < s < 3`, where it
|
|
515
|
+
diverges;
|
|
516
|
+
- a line shape without a floating-point distribution
|
|
517
|
+
(`bimodal_gaussian`) is asked for `cdf`, `ppf` or a class table;
|
|
518
|
+
- the solver of the differential equations fails in `evolve` or
|
|
519
|
+
`evolve_meanfield`.
|
|
520
|
+
|
|
521
|
+
## How the results are checked
|
|
522
|
+
|
|
523
|
+
65 automated tests run on every push and pull request, on Python 3.9
|
|
524
|
+
to 3.14, and once more on Python 3.10 with the oldest NumPy (1.22.0),
|
|
525
|
+
SciPy (1.8.0) and mpmath (1.2.1) the package allows. The numerical
|
|
526
|
+
checks compare the package with something independent of it: a closed
|
|
527
|
+
form, an exact identity, a second calculation done a different way,
|
|
528
|
+
exact diagonalisation, or seeded simulation. The rest check that the
|
|
529
|
+
refusals fire, plus one check of the version number and that importing
|
|
530
|
+
the package does not import matplotlib. The main checks:
|
|
531
|
+
|
|
532
|
+
**The exact solution** (30 digits unless stated)
|
|
533
|
+
|
|
534
|
+
- Kernel masses: `pi` (conservative) and `pi/2` (Kuramoto) to 1e-25;
|
|
535
|
+
`power_tail(s)` for `s` = 1.5, 2, 3, 4 against
|
|
536
|
+
`2 (pi/s) / sin(pi/s)` to 1e-14.
|
|
537
|
+
- The Kuramoto threshold is twice the conservative one to 1e-25, on
|
|
538
|
+
three lines.
|
|
539
|
+
- Conservative kernel, Lorentzian line: the branch matches
|
|
540
|
+
`chiN = a + Omega`, `R = 1 - a/chiN` to 1e-28 for `Omega` from 1e-1
|
|
541
|
+
to 1e-6. Kuramoto kernel, Lorentzian line: `R = sqrt(1 - chiN_c/chiN)`
|
|
542
|
+
to 1e-26 for `Omega` from 1e-1 to 1e-4.
|
|
543
|
+
- The amplitude takes the closed values 1, `pi/2`, 4/3 and `pi^2/4` on
|
|
544
|
+
Lorentzian, Gaussian, Student-t and box lines, to a relative 1e-9,
|
|
545
|
+
and (new in 1.1.1) 1 and `pi/2` to a relative 1e-12 at mpmath's
|
|
546
|
+
default 15 digits (Lorentzian lines of width 1 and 1000, and a
|
|
547
|
+
Gaussian).
|
|
548
|
+
- `c` from its integral equals the slope of `G(Omega)/pi` at
|
|
549
|
+
`Omega` = 1e-6 and 2e-6 to a relative 1e-9, on four lines; `R/eps` at `Omega = 1e-6` is
|
|
550
|
+
within a relative 1e-5 of the amplitude.
|
|
551
|
+
|
|
552
|
+
**The exponent rule** (25 digits, Gaussian line, `Omega` down to 1e-6)
|
|
553
|
+
|
|
554
|
+
- `beta = 1/(s-1)` or 1/2 for the kernel family with `s` = 1.5, 1.8,
|
|
555
|
+
2.0, 2.2, 2.5, 4 and 6, to a relative 5e-3, 1e-3, 1e-4, 1e-3, 2e-3,
|
|
556
|
+
1e-4 and 1e-6 respectively. At the borderline `s = 3` the local
|
|
557
|
+
exponent lies between 0.5 and 0.56.
|
|
558
|
+
- The conservative kernel gives 1, and the Kuramoto and Gaussian
|
|
559
|
+
kernels give 1/2, within 1e-4 on three lines; for `s = 2.5` three
|
|
560
|
+
lines agree within 1e-3.
|
|
561
|
+
- The general amplitude matches the branch at `Omega = 1e-7` for
|
|
562
|
+
`s` = 1.8, 2.0 and 2.2 (relative 1e-4, 1e-5, 1e-4) on three lines,
|
|
563
|
+
and equals the `s = 2` form within a relative 1e-6.
|
|
564
|
+
- The averaged kernel of example 6 has the tail `(gamma-2)/eta` or
|
|
565
|
+
`s0` in five cases (within 1.5e-2 from its measured slope), and
|
|
566
|
+
gives `beta = 1/(gamma-3)` within 5e-3 for `gamma = 3.8`.
|
|
567
|
+
|
|
568
|
+
**Fitting measured data** (the theory half checks the lab half)
|
|
569
|
+
|
|
570
|
+
- On exact branches with no noise, `fit_branch` recovers `beta = 1`
|
|
571
|
+
(conservative) and `beta = 2/3` (`s = 2.5`) within 0.02, and the
|
|
572
|
+
threshold within a relative 1e-3; `s` comes back within 0.1 of 2.5.
|
|
573
|
+
- With 2 % seeded noise, the fitted `beta` is within four of its own
|
|
574
|
+
error bars of 1, between 0.9 and 1.1, with an error bar below 0.05.
|
|
575
|
+
- `kernel_tail_from_beta(1.0, 0.02)` returns `s = 2` and error 0.02 to
|
|
576
|
+
1e-12.
|
|
577
|
+
- The planning formula matches 6000 seeded simulated fits within 5 %,
|
|
578
|
+
and `points_for_beta` returns the smallest `n` that meets the target
|
|
579
|
+
(checked on both sides).
|
|
580
|
+
|
|
581
|
+
**Dynamics and discretisation**
|
|
582
|
+
|
|
583
|
+
- Cumulant solver, all detunings equal, against the exact result:
|
|
584
|
+
`xi^2` within 0.2, 0.015 and 0.0015 for 50, 200 and 1000 emitters
|
|
585
|
+
(and `R` within a quarter of that), with the error falling with `N`
|
|
586
|
+
(more than 50 times smaller at 1000 than at 50).
|
|
587
|
+
- Cumulant solver, 8 and 10 emitters at several detunings, against
|
|
588
|
+
full diagonalisation: `R` within 1e-7 and `xi^2` within 1e-4 at
|
|
589
|
+
`t = 0.05`; at `t = 0.2` the difference is larger but below 1e-2.
|
|
590
|
+
- The equations keep the exact symmetries of the pair correlations to
|
|
591
|
+
a relative 1e-12, and an uncorrelated start gives `xi^2 = 1` and
|
|
592
|
+
`R = 1` to 1e-12.
|
|
593
|
+
- The class table keeps the whole population (sums to 1 within 1e-12)
|
|
594
|
+
and reproduces the locking integral within 0.2 %.
|
|
595
|
+
- Mean-field dynamics on a Lorentzian line settles at `R = 1 - 1/r`
|
|
596
|
+
within 5e-3 for couplings `r` = 1.2, 2 and 3 times the threshold,
|
|
597
|
+
and stays below 0.05 at 0.6 times the threshold.
|
|
598
|
+
|
|
599
|
+
Not covered by tests: `fold_interval`, `bimodal_gaussian`,
|
|
600
|
+
`class_exact`, `physicality` and `valid_window`.
|
|
601
|
+
|
|
602
|
+
## Corrections in earlier versions
|
|
603
|
+
|
|
604
|
+
**1.1.1 fixed the amplitude at ordinary precision.**
|
|
605
|
+
`c_coefficient` (and so `amplitude`) lost about 40 digits to a
|
|
606
|
+
cancellation near the centre of the line. At mpmath's default 15
|
|
607
|
+
digits, `amplitude(lorentzian())` returned about 0.0005 instead of 1;
|
|
608
|
+
at 20 digits it was off by about 0.16 %; a Lorentzian 1000 wide lost
|
|
609
|
+
digits even at 30. The tests ran at 30 digits with a unit-width line
|
|
610
|
+
and did not see it. The integral now runs with 25 extra digits, and a
|
|
611
|
+
test at 15 digits was added. If you computed amplitudes or `c` below
|
|
612
|
+
30 digits with 1.1.0 or earlier, please re-run them.
|
|
613
|
+
|
|
614
|
+
1.1.1 also corrects this README: several tolerances it quoted were not
|
|
615
|
+
the ones the tests use, it described `class_exact` as tested, and the
|
|
616
|
+
CI did not run Python 3.10. The full history is in
|
|
617
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
618
|
+
|
|
619
|
+
## Limits
|
|
620
|
+
|
|
621
|
+
- The power law `R ~ eps^beta` is the leading behaviour near the
|
|
622
|
+
onset. Data taken far above it bend away and bias `beta`. The
|
|
623
|
+
one-decade rule and the residuals guard the fit, not your choice of
|
|
624
|
+
range.
|
|
625
|
+
- The fit's error bars are the standard asymptotic ones; they are
|
|
626
|
+
right when the power law holds and the noise estimate is right.
|
|
627
|
+
- `extract_beta` converges slowly near `s = 3`, where the exponent
|
|
628
|
+
carries logarithmic corrections.
|
|
629
|
+
- In the undamped spin model the truncated cumulant equations become
|
|
630
|
+
unstable at long times; check `physicality` / `valid_window` before
|
|
631
|
+
trusting a long run. The time-averaged kernel is an assumption,
|
|
632
|
+
checked against the full dynamics (the mean-field tests above), not
|
|
633
|
+
a theorem.
|
|
634
|
+
- `c_coefficient` and `amplitude` are good to about 20 significant
|
|
635
|
+
digits at most, however high `mp.dps` is set: the integral leaves
|
|
636
|
+
out the first 1e-20 line widths next to the centre, a piece of
|
|
637
|
+
relative size about 1e-20.
|
|
638
|
+
- `fold_interval` is slow (each branch point is a high-precision
|
|
639
|
+
integral, and it takes 400 by default) and is not covered by the
|
|
640
|
+
tests.
|
|
641
|
+
- `build_system`'s docstring refers to a convergence check in
|
|
642
|
+
`scripts/vlasov_check.py`; that script belongs to the research
|
|
643
|
+
repository and is not part of this package.
|
|
644
|
+
|
|
645
|
+
## Where it comes from
|
|
646
|
+
|
|
647
|
+
This package is the maintained distribution of the reference
|
|
648
|
+
implementation for the locking-kernel universality study
|
|
649
|
+
([Tanvir-Mahmud-Mahim/locking-kernel-universality](https://github.com/Tanvir-Mahmud-Mahim/locking-kernel-universality),
|
|
650
|
+
concept DOI
|
|
651
|
+
[10.5281/zenodo.22696369](https://doi.org/10.5281/zenodo.22696369)),
|
|
652
|
+
whose scripts, archived run records and figures remain with the
|
|
653
|
+
study. The core modules were carried over unchanged in v1.1.0 (1.1.1
|
|
654
|
+
changes only `c_coefficient`); the `measured` module and the
|
|
655
|
+
packaging are new here. Copyright as in [NOTICE](NOTICE).
|
|
656
|
+
|
|
657
|
+
## Citing, support and license
|
|
658
|
+
|
|
659
|
+
If `lockkernel` helps your work, please cite it with the concept DOI
|
|
660
|
+
[10.5281/zenodo.22829483](https://doi.org/10.5281/zenodo.22829483),
|
|
661
|
+
which always resolves to the latest release; every release is archived
|
|
662
|
+
on Zenodo. [CITATION.cff](CITATION.cff) has the details.
|
|
663
|
+
|
|
664
|
+
Written and maintained by Tanvir Mahmud Mahim (Department of
|
|
665
|
+
Electrical and Electronic Engineering, BRAC University), who reviews
|
|
666
|
+
every change and takes the final decision on scope and releases.
|
|
667
|
+
Design questions are discussed in the open in issues and pull
|
|
668
|
+
requests, and the standing rule of
|
|
669
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) binds the maintainer exactly as it
|
|
670
|
+
binds contributors: a change that touches the physics arrives with a
|
|
671
|
+
test, and a claim arrives with its source.
|
|
672
|
+
|
|
673
|
+
Support runs through the
|
|
674
|
+
[issue tracker](https://github.com/TaN-MM-Org/lockkernel/issues).
|
|
675
|
+
Usage questions are welcome alongside bug reports; a docstring that
|
|
676
|
+
left a unit or a convention unclear is treated as a documentation
|
|
677
|
+
bug, not user error.
|
|
678
|
+
|
|
679
|
+
Licensed under Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|