lockkernel 1.1.1__tar.gz → 1.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 (30) hide show
  1. {lockkernel-1.1.1/src/lockkernel.egg-info → lockkernel-1.3.0}/PKG-INFO +483 -46
  2. {lockkernel-1.1.1 → lockkernel-1.3.0}/README.md +482 -45
  3. {lockkernel-1.1.1 → lockkernel-1.3.0}/pyproject.toml +1 -1
  4. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel/__init__.py +5 -4
  5. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel/exact.py +27 -6
  6. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel/kernels.py +81 -6
  7. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel/lineshapes.py +14 -0
  8. lockkernel-1.3.0/src/lockkernel/measured.py +628 -0
  9. lockkernel-1.3.0/src/lockkernel/parametric.py +513 -0
  10. {lockkernel-1.1.1 → lockkernel-1.3.0/src/lockkernel.egg-info}/PKG-INFO +483 -46
  11. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel.egg-info/SOURCES.txt +1 -0
  12. {lockkernel-1.1.1 → lockkernel-1.3.0}/tests/conftest.py +1 -0
  13. {lockkernel-1.1.1 → lockkernel-1.3.0}/tests/test_cumulant.py +47 -0
  14. lockkernel-1.3.0/tests/test_measured.py +415 -0
  15. lockkernel-1.3.0/tests/test_onset.py +458 -0
  16. lockkernel-1.1.1/src/lockkernel/measured.py +0 -271
  17. lockkernel-1.1.1/src/lockkernel/parametric.py +0 -277
  18. lockkernel-1.1.1/tests/test_measured.py +0 -132
  19. {lockkernel-1.1.1 → lockkernel-1.3.0}/LICENSE +0 -0
  20. {lockkernel-1.1.1 → lockkernel-1.3.0}/MANIFEST.in +0 -0
  21. {lockkernel-1.1.1 → lockkernel-1.3.0}/NOTICE +0 -0
  22. {lockkernel-1.1.1 → lockkernel-1.3.0}/setup.cfg +0 -0
  23. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel/cumulant.py +0 -0
  24. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel/ensemble.py +0 -0
  25. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel.egg-info/dependency_links.txt +0 -0
  26. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel.egg-info/requires.txt +0 -0
  27. {lockkernel-1.1.1 → lockkernel-1.3.0}/src/lockkernel.egg-info/top_level.txt +0 -0
  28. {lockkernel-1.1.1 → lockkernel-1.3.0}/tests/test_meanfield.py +0 -0
  29. {lockkernel-1.1.1 → lockkernel-1.3.0}/tests/test_parametric.py +0 -0
  30. {lockkernel-1.1.1 → lockkernel-1.3.0}/tests/test_universality.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockkernel
3
- Version: 1.1.1
3
+ Version: 1.3.0
4
4
  Summary: Locking kernel universality: exact thresholds and exponents of synchronization transitions, and fits of measured branches with error bars and refusals
5
5
  Author: Tanvir Mahmud Mahim
6
6
  License: Apache-2.0
@@ -59,10 +59,13 @@ The collective motion must be the one the oscillators themselves
59
59
  produce (the "self-consistency condition"); one substitution turns
60
60
  that condition into an exact formula (a "parametric solution") valid
61
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`)
62
+ far from the center (its **tail**). The **lab half** (`lockkernel.measured`)
63
63
  works the other way round: it fits measured points for the threshold
64
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
65
+ plans how many points a target error bar costs. It also tests whether
66
+ the data are consistent with the power law at their stated noise, and
67
+ carries the uncertainties of the measured couplings and of calibration
68
+ scales into the error bars. When the data cannot
66
69
  support the number asked for, it stops with an error message that says
67
70
  why, rather than returning a number that looks fine but is not.
68
71
 
@@ -119,7 +122,12 @@ why, rather than returning a number that looks fine but is not.
119
122
  `beta = 1/(s-1)` for `1 < s < 3` and `beta = 1/2` for `s >= 3` or
120
123
  for a kernel with no algebraic tail. So a measured `beta` above 1/2
121
124
  names the tail, `s = 1 + 1/beta`, while `beta = 1/2` only says
122
- "`s >= 3`" and cannot name one value.
125
+ "`s >= 3`" and cannot name one value. The value 1/2 needs a line
126
+ with a rounded top at its centre (`p''(0) < 0`, true for every
127
+ shipped line except `box`). On a line that is flat at the centre,
128
+ such as `box`, a tail gives `beta = 1/(s-1)` for every `s > 1`
129
+ (so below 1/2 once `s > 3`), and a kernel with no tail gives a jump
130
+ instead of a power law (see [Limits](#limits)).
123
131
  - **Order of the onset** -- for the conservative kernel, the sign of
124
132
  one number `c` (an integral over the line shape) decides it:
125
133
  `c > 0` gives a smooth (continuous) onset, `c < 0` a jump with
@@ -144,7 +152,10 @@ and mpmath 1.2 or newer, and nothing else.
144
152
  - **Precision.** The theory half (`parametric`, `kernels`,
145
153
  `lineshapes`) works in mpmath's arbitrary precision and returns
146
154
  mpmath numbers. Set the working precision with `mpmath.mp.dps`
147
- (decimal digits). The lab half and the dynamics work in ordinary
155
+ (decimal digits). The reduced coupling `eps` keeps that precision
156
+ however close to the threshold it is (since 1.2.0), so the working
157
+ precision does not need to exceed the number of decades you step
158
+ towards the onset. The lab half and the dynamics work in ordinary
148
159
  floating point with NumPy.
149
160
  - **Line shapes** are normalised probability densities; `lorentzian`
150
161
  and `gaussian` take a full width at half maximum, `box` a half
@@ -153,8 +164,9 @@ and mpmath 1.2 or newer, and nothing else.
153
164
  ## Examples
154
165
 
155
166
  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.
167
+ printed with lockkernel 1.3.0 (examples 1 to 9 print the same as with
168
+ 1.2.0). Line widths, couplings and noise levels are illustrative
169
+ values, not taken from any experiment.
158
170
 
159
171
  ### 1. Threshold and exponent from the theory
160
172
 
@@ -253,9 +265,12 @@ For conservative spins the exponent is 1, so near the onset
253
265
  `R = A eps`, with `A = pi p(0)^2 / c`. This example runs at mpmath's
254
266
  default 15 digits; before version 1.1.1 `amplitude` and
255
267
  `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.
268
+ [Corrections](#corrections-in-earlier-versions)). For two unit-width
269
+ Gaussian peaks at `+-a`, `c = (1 - 2x D(x))/pi` with `x = a/sqrt(2)`
270
+ and `D` Dawson's function, and the tests hold `c_coefficient` to this
271
+ formula. So the onset turns first order when the peaks are more than
272
+ 2.61386 widths apart, where Dawson's function has its maximum.
273
+ Example 8 follows the first-order case through its hysteresis loop.
259
274
 
260
275
  ### 4. Fit a measured branch
261
276
 
@@ -289,7 +304,7 @@ print(f"kernel tail s = {s:.2f} +- {s_err:.2f} (true 2.5)")
289
304
  ```
290
305
 
291
306
  ```
292
- threshold chi_c = 0.59441 +- 3.5e-09 (exact 0.59441)
307
+ threshold chi_c = 0.59441 +- 3.8e-09 (exact 0.59441)
293
308
  exponent beta = 0.673 +- 0.003 (exact 0.667)
294
309
  amplitude A = 0.652 +- 0.019
295
310
  eps range 1.56e-07 .. 1.09e-03, rms log residual 0.011
@@ -302,7 +317,9 @@ couplings and order parameters, and a `reference` that says where
302
317
  they come from (it is required). The error bars are the standard
303
318
  asymptotic ones of a least-squares fit: they are right when the power
304
319
  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
320
+ `sigma_r`, the scatter of the points sets them. Since 1.2.0 they are
321
+ built from exact derivatives; 1.1.1 printed `3.5e-09` for the
322
+ threshold here (see [Corrections](#corrections-in-earlier-versions)). Here the fitted
306
323
  exponent is 2.4 of its own error bars from the exact 2/3; the tests
307
324
  allow four (see [How the results are checked](#how-the-results-are-checked)).
308
325
  `kernel_tail_from_beta` turns the exponent into the kernel's tail
@@ -311,7 +328,8 @@ exponent `s = 1 + 1/beta`, with error `sigma_beta / beta^2`.
311
328
  ### 5. Plan the measurement, and a refusal
312
329
 
313
330
  ```python
314
- from lockkernel import beta_relative_sigma, points_for_beta, kernel_tail_from_beta
331
+ from lockkernel import (beta_relative_sigma, points_for_beta, kernel_tail_from_beta,
332
+ plan_fit, points_for_fit)
315
333
 
316
334
  # How many points, spread evenly in log(eps) over 2 decades, with 5 %
317
335
  # scatter in R, for an error bar of 0.01 on beta (threshold known)?
@@ -319,6 +337,15 @@ n, achieved = points_for_beta(0.01, decades=2.0, sigma_log=0.05)
319
337
  print(f"points needed: {n} (error bar {achieved:.5f}); "
320
338
  f"with {n - 1}: {beta_relative_sigma(n - 1, 2.0, 0.05):.5f}")
321
339
 
340
+ # The same range (eps = 1e-4 .. 1e-2), but with the threshold fitted too,
341
+ # as fit_branch does it:
342
+ plan = plan_fit(n, 1e-4, 1e-2, beta=2/3, sigma_log=0.05)
343
+ print(f"{n} points, threshold fitted: beta +- {plan.sigma_beta:.4f}, "
344
+ f"chi_c +- {plan.sigma_chi_c_rel:.1e} (relative)")
345
+ n_fit, plan = points_for_fit(0.01, 1e-4, 1e-2, beta=2/3, sigma_log=0.05)
346
+ print(f"points needed with the threshold fitted: {n_fit} "
347
+ f"(error bar {plan.sigma_beta:.5f})")
348
+
322
349
  # A fitted beta = 0.52 +- 0.02 cannot name a kernel tail:
323
350
  try:
324
351
  kernel_tail_from_beta(0.52, 0.02)
@@ -328,15 +355,28 @@ except ValueError as err:
328
355
 
329
356
  ```
330
357
  points needed: 12 (error bar 0.00999); with 11: 0.01035
358
+ 12 points, threshold fitted: beta +- 0.0197, chi_c +- 1.4e-05 (relative)
359
+ points needed with the threshold fitted: 57 (error bar 0.00995)
331
360
  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
361
  ```
333
362
 
334
363
  `beta_relative_sigma` gives the error bar of a straight-line slope on a
335
364
  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.
365
+ `beta`, not a relative one. It assumes the threshold is known, and
366
+ `points_for_beta` finds the smallest number of points that meets the
367
+ target on that assumption.
368
+
369
+ `fit_branch` fits the threshold as well, and that costs precision:
370
+ the same 12 points give an error bar about twice as large. `plan_fit`
371
+ gives the error bars `fit_branch` will report (for `beta`, and
372
+ relative ones for `chi_c` and `A`), before any data are taken, from
373
+ the planned range of `eps` and the expected `beta`. `points_for_fit`
374
+ turns that into a number of points: 57 instead of 12 here. How much
375
+ fitting the threshold costs depends on how many decades the points
376
+ span: for 12 points the error bar grows about 2.0 times over two
377
+ decades, 1.6 over three, and 1.3 over five. These are the standard
378
+ asymptotic error bars, held in the tests against 300 seeded simulated
379
+ fits (within 12 %).
340
380
 
341
381
  ### 6. A spread of coupling strengths changes the exponent
342
382
 
@@ -369,7 +409,86 @@ oscillator's own kernel has none: `s = min(s0, (gamma-2)/eta)`, with
369
409
  `s0` the tail of the single-oscillator kernel (infinite for Kuramoto).
370
410
  Here `s = 1.8`, so `beta = 1/(s-1) = 1.25`, which is `1/(gamma-3)`.
371
411
 
372
- ### 7. The quantum spin model against exact diagonalisation
412
+ ### 7. The amplitude when beta = 1/2
413
+
414
+ ```python
415
+ import mpmath as mp
416
+ from lockkernel.lineshapes import lorentzian, gaussian
417
+ from lockkernel.kernels import kuramoto, gaussian_kernel
418
+ from lockkernel.parametric import amplitude_curvature, branch_point
419
+
420
+ mp.mp.dps = 20
421
+ for line, kern in [(lorentzian(1.0), kuramoto()), (gaussian(1.0), kuramoto()),
422
+ (gaussian(1.0), gaussian_kernel())]:
423
+ A = amplitude_curvature(line, kern)
424
+ _, R, eps = branch_point(line, kern, mp.mpf("1e-6"))
425
+ print(f"{kern.name:9s} kernel, {line.name:10s} line: A = {mp.nstr(A, 12)}, "
426
+ f"R/sqrt(eps) at Omega = 1e-6: {mp.nstr(R / mp.sqrt(eps), 12)}")
427
+ print("sqrt(pi) =", mp.nstr(mp.sqrt(mp.pi), 12))
428
+ ```
429
+
430
+ ```
431
+ kuramoto kernel, lorentzian line: A = 1.0, R/sqrt(eps) at Omega = 1e-6: 0.999999999999
432
+ kuramoto kernel, gaussian line: A = 1.77245385091, R/sqrt(eps) at Omega = 1e-6: 1.7724538509
433
+ gaussian kernel, gaussian line: A = 1.41421356237, R/sqrt(eps) at Omega = 1e-6: 1.41421356237
434
+ sqrt(pi) = 1.77245385091
435
+ ```
436
+
437
+ For a kernel with no tail, or a tail `s > 3`, the onset is
438
+ `R = A eps^(1/2)`. The first correction to the self-consistency then
439
+ comes from the curvature of the line at its centre, `p''(0)`, and
440
+ `amplitude_curvature` returns
441
+ `A = G0^(3/2) sqrt(2 / (-p''(0) M2))`, where `G0 = p(0) m` and
442
+ `M2 = integral u^2 W(u) du` is the kernel's second moment
443
+ (`Kernel.second_moment()`). For the Kuramoto kernel on a Lorentzian
444
+ line this is 1, the closed form `R = sqrt(1 - chiN_c/chiN)`. On a
445
+ Gaussian line the width cancels, which leaves `sqrt(pi)` (Kuramoto
446
+ kernel) and `sqrt(2)` (Gaussian kernel) at any width. The branch
447
+ approaches these values with corrections of relative size
448
+ `Omega^2`, or `Omega^(s-3)` for a tail `3 < s < 5`.
449
+
450
+ ### 8. A first-order onset: the hysteresis loop
451
+
452
+ ```python
453
+ import mpmath as mp
454
+ from lockkernel.lineshapes import bimodal_gaussian
455
+ from lockkernel.kernels import conservative
456
+ from lockkernel.parametric import fold_interval, threshold, extract_beta
457
+
458
+ line = bimodal_gaussian(4.0) # two Gaussian peaks (width 1) at -2 and +2
459
+ f = fold_interval(line, n=30, lo=-2, hi=1)
460
+ print("threshold :", mp.nstr(threshold(line, conservative()), 10))
461
+ print("hysteresis from chiN =", mp.nstr(f["chiN_lo"], 10), "to", mp.nstr(f["chiN_hi"], 10))
462
+ print("R jumps from 0 to :", mp.nstr(f["R_jump"], 10))
463
+ print("R where the high branch ends:", mp.nstr(f["R_high_at_lo"], 10))
464
+ try:
465
+ extract_beta(line, conservative(), [-3, -4, -5])
466
+ except ValueError as err:
467
+ print("refused:", str(err)[:72], "...")
468
+ ```
469
+
470
+ ```
471
+ threshold : 5.89561378
472
+ hysteresis from chiN = 3.493404803 to 5.89561378
473
+ R jumps from 0 to : 0.8480505911
474
+ R where the high branch ends: 0.3342759883
475
+ refused: eps = -0.0016464 <= 0 at Omega = 10^-3: the branch is at or below the th ...
476
+ ```
477
+
478
+ With the peaks 4 widths apart `c < 0` (example 3). The branch leaves
479
+ the threshold backwards, then turns round at `chiN = 3.4934`. In the
480
+ usual reading of such a fold (the package computes where the
481
+ solutions are, not whether they are stable), raising the coupling
482
+ keeps the unsynchronized state up to the threshold, where `R` jumps to
483
+ 0.848; lowering it again keeps the synchronized state down to
484
+ `chiN = 3.4934` (where `R = 0.334`) before it collapses. `fold_interval` samples the branch on `n` points
485
+ between `Omega = 10^lo` and `10^hi` and then finds the turning points
486
+ and the jump exactly. The tests check every number it returns against
487
+ an independent closed form (the Voigt profile). There is no exponent
488
+ on such a branch, and `extract_beta` says so instead of returning
489
+ one.
490
+
491
+ ### 9. The quantum spin model against exact diagonalisation
373
492
 
374
493
  ```python
375
494
  import numpy as np
@@ -410,6 +529,98 @@ should. `xi^2` is the **Wineland spin-squeezing parameter**:
410
529
  1 for uncorrelated spins, below 1 when the spins are entangled in a way
411
530
  that improves phase measurements.
412
531
 
532
+ ### 10. Are the data consistent with the power law?
533
+
534
+ ```python
535
+ import numpy as np
536
+ from lockkernel import fit_branch
537
+
538
+ # Conservative spins on a Lorentzian line of FWHM 1: chi_c = 1/2 and,
539
+ # exactly, R = eps/(1+eps). 14 points from eps = 1e-4, 1 % seeded noise.
540
+ rng = np.random.default_rng(4)
541
+ for eps_max in (0.01, 0.1, 0.5):
542
+ eps = np.geomspace(1e-4, eps_max, 14)
543
+ r = eps / (1 + eps) * np.exp(rng.normal(0.0, 0.01, eps.size))
544
+ fit = fit_branch(0.5 * (1 + eps), r, sigma_r=0.01 * r,
545
+ reference="illustrative: closed form + 1 % noise")
546
+ print(f"eps up to {eps_max:4}: beta = {fit.beta:.4f} +- {fit.sigma_beta:.4f}, "
547
+ f"chi2 = {fit.chi2:5.1f} for {fit.dof} dof, p = {fit.p_value:.2g}")
548
+ ```
549
+
550
+ ```
551
+ eps up to 0.01: beta = 1.0001 +- 0.0037, chi2 = 11.1 for 11 dof, p = 0.44
552
+ eps up to 0.1: beta = 0.9833 +- 0.0019, chi2 = 48.3 for 11 dof, p = 1.3e-06
553
+ eps up to 0.5: beta = 0.9546 +- 0.0014, chi2 = 584.2 for 11 dof, p = 3.4e-118
554
+ ```
555
+
556
+ The true exponent is 1. When the points reach far above the threshold,
557
+ the branch bends away from the power law, and the fit returns a biased
558
+ exponent with a small error bar: 0.9833 +- 0.0019 is 9 error bars from
559
+ 1. The error bar alone does not show this. The chi-square test does.
560
+ When `sigma_r` is given, `fit_branch` also returns `chi2` (the weighted
561
+ sum of squared misfits in `log R`), its degrees of freedom `dof`
562
+ (points minus 3) and `p_value`, the chance of a misfit at least this
563
+ large if the law and the stated errors were right. A very small
564
+ `p_value` means one of the two is wrong: drop the points farthest from
565
+ the threshold, or check `sigma_r`. Without `sigma_r` the scatter of the
566
+ points sets the error scale, so there is nothing to test, and `chi2`
567
+ and `p_value` are `None`.
568
+
569
+ ### 11. Errors in the couplings, and calibration
570
+
571
+ ```python
572
+ import numpy as np
573
+ from lockkernel import fit_branch, with_calibration
574
+
575
+ # Stand-in measurement: R = 0.65 eps^(2/3) above chi_c = 0.5, at 14
576
+ # recorded couplings. Each true coupling differs from the recorded one by
577
+ # a seeded error of 5e-6 (a tenth of the distance to the threshold at the
578
+ # first point), and R carries 1 % seeded noise.
579
+ rng = np.random.default_rng(2)
580
+ chi = 0.5 * (1 + np.geomspace(1e-4, 1e-1, 14))
581
+ sigma_chi = np.full(chi.size, 5e-6)
582
+ chi_true = chi + rng.normal(0.0, 1.0, chi.size) * sigma_chi
583
+ r = 0.65 * (chi_true / 0.5 - 1) ** (2 / 3) * np.exp(rng.normal(0.0, 0.01, chi.size))
584
+
585
+ ref = "illustrative: power law + seeded noise"
586
+ plain = fit_branch(chi, r, ref, sigma_r=0.01 * r)
587
+ full = fit_branch(chi, r, ref, sigma_r=0.01 * r, sigma_chi=sigma_chi)
588
+ for name, f in (("errors of R only:", plain), ("errors of R and chi:", full)):
589
+ print(f"{name:20s} beta = {f.beta:.4f} +- {f.sigma_beta:.4f}, "
590
+ f"chi_c = {f.chi_c:.7f} +- {f.sigma_chi_c:.1e}, p = {f.p_value:.2g}")
591
+
592
+ # The couplings share a 2 % calibration uncertainty, R a 5 % one:
593
+ cal = with_calibration(full, chi_scale_rel=0.02, r_scale_rel=0.05)
594
+ print(f"with calibration: chi_c +- {cal.sigma_chi_c:.1e}, "
595
+ f"A = {cal.amplitude:.3f} +- {cal.sigma_amplitude:.3f}, "
596
+ f"beta +- {cal.sigma_beta:.4f} (unchanged)")
597
+ ```
598
+
599
+ ```
600
+ errors of R only: beta = 0.6698 +- 0.0020, chi_c = 0.4999987 +- 1.2e-06, p = 0.00051
601
+ errors of R and chi: beta = 0.6658 +- 0.0026, chi_c = 0.5000034 +- 3.7e-06, p = 0.15
602
+ with calibration: chi_c +- 1.0e-02, A = 0.649 +- 0.033, beta +- 0.0026 (unchanged)
603
+ ```
604
+
605
+ Near the threshold `R` rises steeply with the coupling, so a small
606
+ error in the coupling moves `R` a lot: by `beta sigma_chi / (chi -
607
+ chi_c)` in `log R`. Pass the coupling errors as `sigma_chi` and
608
+ `fit_branch` adds this term to each point's error (to first order, and
609
+ repeats the fit until the weights settle). Here, without it, the error
610
+ bars are too small and the chi-square test says so (`p = 0.00051`).
611
+ With it the fit is consistent (`p = 0.15`) and the exponent lands within
612
+ its error bar of 2/3. `fit_branch` refuses `sigma_chi` larger than a
613
+ quarter of `chi - chi_c` at any point, where first order is not enough.
614
+
615
+ `with_calibration` adds the uncertainty of an overall scale factor: one
616
+ common to all couplings (for example the conversion of a laser power
617
+ into `chiN`), and one common to all `R` (for example a detection
618
+ efficiency). Scaling every coupling scales the fitted threshold by the
619
+ same factor and leaves `beta` and `A` exactly as they were; scaling
620
+ `R` scales only `A`. So the two calibrations add to the error bars of
621
+ `chi_c` and `A` in quadrature, and the exponent is immune to both. The
622
+ tests check both statements by refitting scaled data.
623
+
413
624
  ## What is in the package
414
625
 
415
626
  The top level imports the `measured` names below and the submodules
@@ -424,12 +635,14 @@ directly.
424
635
  Makers: `lorentzian`, `gaussian`, `student_t`, `box`,
425
636
  `bimodal_gaussian`; `LINESHAPES` maps names to them.
426
637
  - `Kernel` -- an even kernel with `W(0) = 1`, its tail exponent, its
427
- support, and `mass()`. Makers: `conservative`, `kuramoto`,
638
+ support, `mass()` and `second_moment()` (the integral of
639
+ `u^2 W(u)`, finite only for compact support, fast decay or `s > 3`).
640
+ Makers: `conservative`, `kuramoto`,
428
641
  `power_tail(s)`, `gaussian_kernel`; `KERNELS` maps names to them.
429
642
  - `heterogeneous(base, degree_exponent, eta=1, k_min=1)` -- the kernel
430
643
  averaged over a power-law spread of coupling strengths (example 6).
431
644
  - `predicted_beta(s)` -- the exponent the rule gives for tail `s`
432
- (`None` meaning no algebraic tail).
645
+ (`None` meaning no algebraic tail), for a line with a rounded top.
433
646
 
434
647
  **The exact solution** (`lockkernel.parametric`, mpmath precision)
435
648
 
@@ -439,27 +652,45 @@ directly.
439
652
  - `threshold`, `branch_point`, `sweep`, `extract_beta` -- the
440
653
  threshold, one point `(chiN, R, eps)` of the branch, the branch at
441
654
  `Omega = 10^e` for a list of `e`, and the local exponents along it.
655
+ `eps` is computed from `G(0) - G(Omega)` directly, so it keeps the
656
+ working precision however small it is.
442
657
  - `c_coefficient`, `amplitude` -- for the conservative kernel, the
443
658
  number `c` whose sign sets the order of the onset, and the amplitude
444
659
  `A = pi p(0)^2 / c`.
445
660
  - `tail_integral`, `amplitude_general` -- the amplitude for a kernel
446
661
  with tail exponent `1 < s < 3`.
662
+ - `amplitude_curvature` -- the amplitude when `beta = 1/2` (no tail,
663
+ or a tail `s > 3`, on a line with a rounded top; example 7).
447
664
  - `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.
665
+ the coupling range of the hysteresis, the turning points and the
666
+ jump in `R` (`None` if the branch does not fold; example 8).
450
667
 
451
668
  **Fitting measured data** (`lockkernel.measured`, also at the top level)
452
669
 
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`.
670
+ - `fit_branch(chi, r, reference, sigma_r=None, min_decade=1.0,
671
+ sigma_chi=None)` -- returns a `BranchFit` with `chi_c`, `beta`,
672
+ `amplitude`, their errors, `n_points`, `eps_range`,
673
+ `residual_rms_log` (the root-mean-square misfit in `log R`) and
674
+ `reference`. Since 1.3.0 it also has `dof`, `chi2` and `p_value`
675
+ (the last two `None` without `sigma_r`; example 10),
676
+ `sigma_chi_used` and `calibration_rel`. `sigma_chi` gives the errors
677
+ of the couplings (example 11).
678
+ - `with_calibration(fit, chi_scale_rel=0, r_scale_rel=0)` -- the same
679
+ fit with the relative uncertainties of an overall coupling scale and
680
+ an overall `R` scale added to the error bars of `chi_c` and `A`
681
+ (example 11).
457
682
  - `kernel_tail_from_beta(beta, sigma_beta=0, n_sigma=2)` -- the tail
458
683
  exponent `s` and its error.
459
684
  - `beta_relative_sigma(n_points, decades, sigma_log)`,
460
685
  `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.
686
+ error bar of `beta` for a planned measurement with the threshold
687
+ known, and the number of points for a target error bar.
688
+ - `plan_fit(n_points, eps_min, eps_max, beta, sigma_log,
689
+ fit_threshold=True)`, `points_for_fit(target_sigma_beta, eps_min,
690
+ eps_max, beta, sigma_log)` -- the same for the fit `fit_branch`
691
+ actually does, with the threshold fitted: `plan_fit` returns a
692
+ `FitPlan` with `sigma_beta`, `sigma_chi_c_rel` and
693
+ `sigma_amplitude_rel` (example 5).
463
694
 
464
695
  **Quantum spin dynamics** (`lockkernel.cumulant`, `lockkernel.ensemble`,
465
696
  `lockkernel.exact`)
@@ -479,7 +710,7 @@ directly.
479
710
  - `symmetric_exact` (all detunings equal, any number of emitters),
480
711
  `full_exact` (full quantum problem, up to about twelve emitters) and
481
712
  `class_exact` (emitters grouped by detuning, a few tens) -- exact
482
- references. `class_exact` is not covered by the tests.
713
+ references. `class_exact` is tested against `full_exact` since 1.3.0.
483
714
 
484
715
  Each function's docstring (`help(lockkernel.fit_branch)`, for example)
485
716
  gives its inputs and conventions.
@@ -492,12 +723,19 @@ gives its inputs and conventions.
492
723
  saying where they come from;
493
724
  - fewer than 6 points are given, or `chi` and `r` differ in length
494
725
  (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`;
726
+ - the couplings are not finite, positive and strictly increasing, or
727
+ an order parameter is not finite and positive (points below the
728
+ threshold, `R = 0`, carry no exponent information; drop them);
729
+ - `sigma_r` is not finite and positive, or not on the same grid as `r`;
499
730
  - the fit does not converge, or its covariance is singular (the data
500
731
  cannot pin down the three parameters separately);
732
+ - the fitted exponent runs into the limits of the search, 0.05 or 20
733
+ (new in 1.3.0; the data are not a power law of this kind, for
734
+ example because `R` hardly changes, as at a jump);
735
+ - `sigma_chi` is given without `sigma_r`, is negative, or is larger
736
+ than a quarter of `chi - chi_c` at some point (new in 1.3.0);
737
+ - `with_calibration` gets a negative uncertainty, or a fit that
738
+ already includes one;
501
739
  - the fitted threshold is indistinguishable from the smallest
502
740
  coupling: the data do not reach the onset;
503
741
  - the fitted points span less than `min_decade` decades of `eps`
@@ -506,13 +744,32 @@ gives its inputs and conventions.
506
744
  - a fitted `beta` is consistent with 1/2 (only the class `s >= 3` is
507
745
  identified), clearly below 1/2 (no kernel gives that; the fit has
508
746
  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`;
747
+ - a planned measurement has fewer than 3 points (6 with the threshold
748
+ fitted, as `fit_branch` needs), a number of points that is not a
749
+ whole number, a non-positive or reversed range, a non-positive
750
+ scatter or `beta`, a non-positive target, or would need more than
751
+ 10^7 points;
752
+ - `extract_beta` meets a point with `eps <= 0`: the branch is at or
753
+ below the threshold there, because it bends back (a first-order
754
+ onset, example 8) or is flat (`R` jumps at the threshold), and there
755
+ is no exponent to measure;
756
+ - `amplitude_curvature` is asked for a line with `p''(0) >= 0` (a flat
757
+ top or a dip at the centre) or a kernel with tail `s <= 3`, and
758
+ `second_moment` for a kernel with tail `s <= 3` (it diverges);
759
+ - `fold_interval` finds a fold on a line with compact support (`box`),
760
+ which it cannot refine, or cannot bracket a turning point on its
761
+ grid;
762
+ - `power_tail(s)` or `predicted_beta(s)` is asked for `s <= 1` (the
763
+ kernel mass would be infinite), or `heterogeneous` for `gamma <= 2`,
764
+ a non-positive `eta` or `k_min`, or a resulting tail `s <= 1`;
765
+ - a line shape is given a width that is not finite and positive;
514
766
  - `tail_integral` is asked for `s` outside `1 < s < 3`, where it
515
767
  diverges;
768
+ - `amplitude_general` is asked for a kernel without a tail in
769
+ `1 < s < 3`, or (new in 1.3.0) its default tail amplitude `C`, read
770
+ off the kernel at `|u| = 10^6`, differs by more than a relative 1e-6
771
+ from the value at `10^9` (pass `C` yourself then);
772
+ - `class_exact` gets class populations that are not whole numbers;
516
773
  - a line shape without a floating-point distribution
517
774
  (`bimodal_gaussian`) is asked for `cdf`, `ppf` or a class table;
518
775
  - the solver of the differential equations fails in `evolve` or
@@ -520,7 +777,7 @@ gives its inputs and conventions.
520
777
 
521
778
  ## How the results are checked
522
779
 
523
- 65 automated tests run on every push and pull request, on Python 3.9
780
+ 128 automated tests run on every push and pull request, on Python 3.9
524
781
  to 3.14, and once more on Python 3.10 with the oldest NumPy (1.22.0),
525
782
  SciPy (1.8.0) and mpmath (1.2.1) the package allows. The numerical
526
783
  checks compare the package with something independent of it: a closed
@@ -549,6 +806,79 @@ the package does not import matplotlib. The main checks:
549
806
  `Omega` = 1e-6 and 2e-6 to a relative 1e-9, on four lines; `R/eps` at `Omega = 1e-6` is
550
807
  within a relative 1e-5 of the amplitude.
551
808
 
809
+ **New in 1.2.0** (15 digits unless stated; the references are closed
810
+ forms evaluated separately from the package's integrals)
811
+
812
+ - `eps`, `chiN` and `R` on the Lorentzian line match `eps = Omega/a`,
813
+ `chiN = a + Omega`, `R = Omega/(a + Omega)` to a relative 1e-13 for
814
+ `Omega` from 1e-6 to 1e-14, and to 1e-28 at 30 digits down to
815
+ `Omega` = 1e-20.
816
+ - Box line, kernels `1/(1+|u|^s)` with `s` = 1.5, 2.5, 4 and 6: `G`
817
+ and `eps` match the closed form `G = X 2F1(1, 1/s; 1+1/s; -X^s)`,
818
+ `X = 1/Omega`, to a relative 1e-13 at `Omega` = 1e-1, 1e-3 and 1e-5
819
+ (down to `eps` of about 1e-26). On this flat-topped line
820
+ `extract_beta` gives `1/(s-1)` = 1/3 and 1/5 for `s` = 4 and 6, within
821
+ 1e-6.
822
+ - Two-peaked line: `c_coefficient` matches `(1 - 2x D(x))/pi` to 1e-13
823
+ for separations 1, 2 and 4; the sign of `c` flips across the
824
+ separation 2.61386 (the maximum of Dawson's function, located by root
825
+ finding); `G` matches the Voigt closed form to a relative 1e-13.
826
+ - `fold_interval`: every number it returns (couplings, order
827
+ parameters, `Omega` of the turning points and of the jump) matches
828
+ the Voigt closed form, with turning points found by root finding on
829
+ its derivative, to a relative 1e-12, for a branch that leaves the
830
+ threshold backwards (two peaks 4 widths apart) and for an S-shaped
831
+ branch (a three-peak line); it returns `None` for a monotonic branch.
832
+ - `extract_beta` refuses the backward branch and the flat one (box
833
+ line, Kuramoto kernel, where `eps` is exactly 0).
834
+ - `second_moment` matches `pi/8` (Kuramoto), `sqrt(pi)/2` (Gaussian
835
+ kernel) and `2 (pi/s)/sin(3 pi/s)` (`s` = 4, 6) to 1e-12.
836
+ `amplitude_curvature` is 1 for the Kuramoto kernel on a Lorentzian
837
+ line and `sqrt(pi)`, `sqrt(2)` on Gaussian lines of two widths, to
838
+ 1e-12; at 20 digits it matches `R/sqrt(eps)` at `Omega = 1e-6` to a
839
+ relative 1e-10 for three kernel-line pairs, and to 2e-6 for `s = 4`.
840
+
841
+ **New in 1.3.0** (15 digits unless stated; every reference is a closed
842
+ form or a calculation done another way)
843
+
844
+ - Kernel masses for slow tails, `power_tail(s)` with `s` = 1.05, 1.1,
845
+ 1.25 and 1.5, against `2 (pi/s)/sin(pi/s)` to a relative 1e-14, and
846
+ 1e-28 at 30 digits. The averaged Kuramoto kernel of example 6 with
847
+ `gamma` = 3.4, 3.8 and 4.4 against its mass
848
+ `(pi/2)(gamma-2)/(gamma-3)` to 1e-13. `second_moment` for `s` = 3.1,
849
+ 3.25 and 3.5 against `2 (pi/s)/sin(3 pi/s)` to 1e-13.
850
+ - For `s = 1.1` on a Lorentzian line the threshold equals
851
+ `s sin(pi/s)/4` to 1e-13, and `chiN/chiN_c - 1` on the branch equals
852
+ `eps` from its separate integral to 1e-10.
853
+ - `tail_integral` against the closed forms on Lorentzian and Gaussian
854
+ lines of widths 0.001, 1 and 1000, for `s` from 1.1 to 2.99, to a
855
+ relative 1e-13 (and 1e-27 at 30 digits). `amplitude_general` against
856
+ its closed form on the Lorentzian line for `s` = 1.1, 1.5, 2.5 and
857
+ 2.9, to 1e-12.
858
+ - `amplitude_general` refuses the conservative kernel averaged over
859
+ coupling strengths, whose tail amplitude (a closed form, also checked
860
+ by quadrature) is not reached at `|u| = 10^6`, and accepts the
861
+ averaged Kuramoto kernel, whose tail is an exact power law (its
862
+ default `C` matches the closed form `(gamma-2) B(s/2, 3/2)/2` to give
863
+ the same amplitude to 1e-12).
864
+ - `class_exact` matches `full_exact` (8 emitters, two sets of classes)
865
+ to 1e-10 in `R` and 1e-9 in `xi^2`, with the times in any order.
866
+ - `fit_branch`: `chi2` matches the sum recomputed from the returned
867
+ parameters; over 300 seeded fits under the model the p-values pass a
868
+ Kolmogorov-Smirnov test for uniformity and `chi2` averages to `dof`
869
+ within four standard errors; on the exact conservative branch with
870
+ 1 % noise, points up to `eps = 0.01` give `p > 0.05` and `beta`
871
+ within 3 error bars of 1, points up to `eps = 0.5` give
872
+ `p < 1e-10` and `beta` more than 10 error bars off.
873
+ - With coupling noise of a tenth of `chi - chi_c` at the first point,
874
+ the error bars of `chi_c`, `beta` and `A` with `sigma_chi` match the
875
+ scatter of 300 seeded fits within 12 %, while without it `sigma_beta`
876
+ is more than 15 % too small. `sigma_chi = 0` gives exactly the plain
877
+ fit. A fit refuses data generated with `beta` = 0.02, 0.04 and 25.
878
+ - Refitting data with all couplings scaled by 1.07 scales `chi_c` by
879
+ 1.07 to 1e-9 and leaves `beta` and `A` unchanged to 1e-7; scaling `R`
880
+ scales only `A`. `with_calibration` adds in quadrature to 1e-12.
881
+
552
882
  **The exponent rule** (25 digits, Gaussian line, `Omega` down to 1e-6)
553
883
 
554
884
  - `beta = 1/(s-1)` or 1/2 for the kernel family with `s` = 1.5, 1.8,
@@ -577,6 +907,17 @@ the package does not import matplotlib. The main checks:
577
907
  - The planning formula matches 6000 seeded simulated fits within 5 %,
578
908
  and `points_for_beta` returns the smallest `n` that meets the target
579
909
  (checked on both sides).
910
+ - (New in 1.2.0) On noiseless data reaching `eps = 1e-7`, the error
911
+ bars `fit_branch` reports, and those `plan_fit` predicts, equal the
912
+ ones built from mpmath's numerical derivatives at 30 digits to a
913
+ relative 1e-6. With the threshold known, `plan_fit` equals
914
+ `beta_relative_sigma` to 1e-12. Over 300 seeded noisy fits (1 %
915
+ scatter), the scatter of `chi_c`, `beta` and `A` matches `plan_fit`,
916
+ and the median reported error bar matches the scatter, within 12 %.
917
+ `points_for_fit` is checked on both sides, and the error bar is
918
+ checked to fall with every added point from 6 to 400. `sigma_beta`
919
+ and `sigma_A` from `plan_fit` do not depend on `beta` and
920
+ `sigma_chi_c` scales as `1/beta` (to 1e-12).
580
921
 
581
922
  **Dynamics and discretisation**
582
923
 
@@ -596,11 +937,80 @@ the package does not import matplotlib. The main checks:
596
937
  within 5e-3 for couplings `r` = 1.2, 2 and 3 times the threshold,
597
938
  and stays below 0.05 at 0.6 times the threshold.
598
939
 
599
- Not covered by tests: `fold_interval`, `bimodal_gaussian`,
600
- `class_exact`, `physicality` and `valid_window`.
940
+ Not covered by tests: `physicality` and `valid_window`.
601
941
 
602
942
  ## Corrections in earlier versions
603
943
 
944
+ **1.3.0 fixed five silent errors.** Numbers are at mpmath's default 15
945
+ digits unless stated.
946
+
947
+ - `Kernel.mass()` integrated a slow tail directly out to infinity. For
948
+ `power_tail(1.1)` the mass was 20.0312 instead of 20.2745 (1.2 % low;
949
+ 3.7e-4 at 30 digits), so the threshold on a Lorentzian line of FWHM 1
950
+ was 0.0784174 instead of 0.0774765, and did not match the branch's own
951
+ `eps`. `second_moment()` had the same problem just above `s = 3`
952
+ (19.7910 instead of 20.0343 for `s = 3.1`). The tail is now mapped
953
+ onto a finite interval, as `G(Omega)` already was, and both match
954
+ their closed forms to the working precision.
955
+ - `tail_integral` integrated a singularity `delta^(2-s)` at the center
956
+ directly. On a Lorentzian line of FWHM 1, `I_s` was 47.063 instead
957
+ of 47.715 at `s = 2.9` and 86.97 instead of 98.49 at `s = 2.95`. A
958
+ change of variables now removes the singularity.
959
+ - Through these two, `amplitude_general` was wrong at the edges of
960
+ `1 < s < 3`, where the power `1/(s-1)` amplifies errors: on a
961
+ Gaussian line 8.4219 instead of 9.6179 for `s = 1.1`, and 0.45574
962
+ instead of 0.45258 for `s = 2.9`. Its default tail amplitude was also
963
+ used without a check: for the conservative kernel averaged over
964
+ coupling strengths (`gamma = 3.8`) it was 6.2 % low, and the
965
+ amplitude 8.3 % high. It now refuses such a kernel unless `C` is
966
+ given.
967
+ - `fit_branch` returned the limit of its search as the exponent when
968
+ the best fit lay beyond it: for `R = 0.8 eps^0.04` it reported
969
+ `beta = 0.0500 +- 0.0040`, and for `eps^25`, `beta = 20 +- 1.5`. It
970
+ now refuses.
971
+ - `class_exact` skipped a time earlier than the one before it and
972
+ returned the later state: for times `[1.0, 0.2]` (the system of
973
+ example 9) both entries gave `R = 0.677579`; the right value at
974
+ `t = 0.2` is 0.984316. It now accepts times in any order.
975
+
976
+ All earlier tests pass unchanged, and examples 1 to 9 print the same
977
+ as with 1.2.0.
978
+
979
+ **1.2.0 fixed four silent inaccuracies.** Numbers below are at
980
+ mpmath's default 15 digits unless stated.
981
+
982
+ - `branch_point` formed `eps` as `chiN/chiN_c - 1` and lost about
983
+ `log10(1/eps)` digits: on the Lorentzian line `eps` was 2e-4
984
+ (relative) off at `Omega = 1e-9` and wrong by a factor of about 220 at
985
+ `1e-12`. `G_of_Omega` itself was 4e-10 off at `Omega = 1e-12`,
986
+ because a segment spanning many decades was integrated on a linear
987
+ scale. On the box line with `s = 4`, `extract_beta` quoted 0.3194
988
+ instead of 1/3, and with `s = 6` it raised `ZeroDivisionError`. Both
989
+ quantities now keep the working precision (see the new checks above).
990
+ The earlier tests and examples ran at 20 to 30 digits with `eps` no
991
+ smaller than about 1e-12, where the loss did not show: all of them
992
+ still pass unchanged, and README examples 1, 2, 3, 6 and 9 print the
993
+ same as before.
994
+ - `extract_beta` returned slopes close to 1 (1.0016, 1.00016, 1.000016
995
+ for `Omega` = 1e-3 down to 1e-6, at 20 digits) for the backward
996
+ branch of the two-peaked line (a first-order onset, `eps < 0`), and
997
+ raised `ZeroDivisionError` on a flat branch. It now refuses both.
998
+ - `fit_branch` took its derivatives by finite differences, which near
999
+ the threshold are not small steps: with points down to `eps = 1e-7`
1000
+ it reported `sigma_chi_c` about 17 % and `sigma_beta` about 2 % too
1001
+ small, and on example 4's data the fit stopped marginally short of
1002
+ the least-squares minimum. It now uses exact derivatives. In example 4 the threshold
1003
+ error bar goes from 3.5e-9 to 3.8e-9 (beta 0.67322 -> 0.67325, its
1004
+ error bar 0.00275 -> 0.00279).
1005
+ - `fold_interval`, for a branch that leaves the threshold backwards,
1006
+ returned the first grid point instead of the threshold as the end of
1007
+ the low branch (5.89464 instead of 5.89561 in example 8, and so a
1008
+ jump to 0.84800 instead of 0.84805). A turning point it could not
1009
+ refine was silently replaced by a grid point; it now raises. It is
1010
+ also much faster: on the line of example 8 with `n = 60` and the
1011
+ default range it took 79 s in 1.1.1 and takes 3.5 s now (17 s at the
1012
+ default `n = 400`).
1013
+
604
1014
  **1.1.1 fixed the amplitude at ordinary precision.**
605
1015
  `c_coefficient` (and so `amplitude`) lost about 40 digits to a
606
1016
  cancellation near the centre of the line. At mpmath's default 15
@@ -626,6 +1036,16 @@ CI did not run Python 3.10. The full history is in
626
1036
  right when the power law holds and the noise estimate is right.
627
1037
  - `extract_beta` converges slowly near `s = 3`, where the exponent
628
1038
  carries logarithmic corrections.
1039
+ - The rule `beta = 1/2` for `s >= 3` (and `predicted_beta`,
1040
+ `kernel_tail_from_beta`, `amplitude_curvature`) assumes a line with a
1041
+ rounded top, `p''(0) < 0`. On a line that is flat at the centre, as
1042
+ `box` is, a tail gives `beta = 1/(s-1)` for every `s > 1`, and a
1043
+ kernel without a tail gives a jump. A measured `beta` below 1/2 is
1044
+ refused by `kernel_tail_from_beta`, although a flat-topped line can
1045
+ produce one.
1046
+ - `plan_fit` gives the asymptotic error bars, exact to first order in
1047
+ the noise. They were checked against simulated fits at 1 % scatter;
1048
+ at much larger scatter the real fit can do worse.
629
1049
  - In the undamped spin model the truncated cumulant equations become
630
1050
  unstable at long times; check `physicality` / `valid_window` before
631
1051
  trusting a long run. The time-averaged kernel is an assumption,
@@ -635,9 +1055,23 @@ CI did not run Python 3.10. The full history is in
635
1055
  digits at most, however high `mp.dps` is set: the integral leaves
636
1056
  out the first 1e-20 line widths next to the centre, a piece of
637
1057
  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.
1058
+ - `fold_interval` finds a fold only if its grid shows it: structure
1059
+ below `Omega = 10^lo` or narrower than the grid spacing is missed, and
1060
+ with several folds only the first and last turning points are used.
1061
+ It needs a differentiable line. It costs `n` plus about 40
1062
+ high-precision integrals (about 17 s at the default `n = 400` on the
1063
+ two-peaked line).
1064
+ - `fit_branch` propagates coupling errors `sigma_chi` to first order.
1065
+ The tests check this at a tenth of `chi - chi_c`, and hand checks
1066
+ with 1000 seeded fits held to within 6 % up to three tenths. It is
1067
+ refused above a quarter.
1068
+ - `with_calibration` treats calibration as one scale factor for all
1069
+ couplings and one for all `R`. An offset (a zero error) is not a
1070
+ scale: it changes `eps` and so the exponent, and is not covered.
1071
+ - The default tail amplitude of `amplitude_general` is read off the
1072
+ kernel at `|u| = 10^6`; for `power_tail(s)` it is low by a relative
1073
+ `10^(-6 s)`, which the power `1/(s-1)` multiplies (2.5e-6 in `A` for
1074
+ `s = 1.1`). Pass `C` for more digits.
641
1075
  - `build_system`'s docstring refers to a convergence check in
642
1076
  `scripts/vlasov_check.py`; that script belongs to the research
643
1077
  repository and is not part of this package.
@@ -651,8 +1085,11 @@ concept DOI
651
1085
  [10.5281/zenodo.22696369](https://doi.org/10.5281/zenodo.22696369)),
652
1086
  whose scripts, archived run records and figures remain with the
653
1087
  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).
1088
+ changes only `c_coefficient`; 1.2.0 changes how `G`, `eps` and the
1089
+ fold are computed, and adds `amplitude_curvature` and
1090
+ `Kernel.second_moment`; 1.3.0 changes how the kernel mass, the second
1091
+ moment and `tail_integral` are integrated, and how `class_exact` steps
1092
+ through time); the `measured` module and the packaging are new here. Copyright as in [NOTICE](NOTICE).
656
1093
 
657
1094
  ## Citing, support and license
658
1095