pm3-rs-python 0.2.3__tar.gz → 0.2.5__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 (126) hide show
  1. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/CHANGELOG.md +283 -4
  2. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/Cargo.lock +1 -1
  3. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/Cargo.toml +1 -1
  4. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/PKG-INFO +10 -1
  5. pm3_rs_python-0.2.5/THIRD_PARTY_NOTICES.md +115 -0
  6. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/docs/pbc.md +74 -4
  7. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/docs/python-api.md +78 -5
  8. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/docs/rust-api.md +63 -0
  9. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/docs/scope.md +84 -1
  10. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/pyproject.toml +27 -3
  11. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/python/pm3_rs/__init__.py +22 -1
  12. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/python/pm3_rs/_native.pyi +33 -0
  13. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/python/pm3_rs/ase.py +102 -4
  14. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/python/pm3_rs/native.py +238 -14
  15. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/cli.rs +848 -29
  16. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/constants.rs +43 -0
  17. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/corrections/h4.rs +2 -2
  18. pm3_rs_python-0.2.5/src/corrections/mmok.rs +224 -0
  19. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/corrections/mod.rs +33 -0
  20. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dc/derivatives.rs +6 -1
  21. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dc/mod.rs +1 -1
  22. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dc/scf.rs +1 -0
  23. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/densitydiis.rs +5 -0
  24. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/error.rs +22 -4
  25. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/gradient.rs +8 -5
  26. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/hessian.rs +108 -28
  27. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/integrals_d.rs +1 -1
  28. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/ir.rs +22 -76
  29. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/lib.rs +5 -2
  30. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/molden.rs +356 -17
  31. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/optimizer.rs +147 -31
  32. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/overlap.rs +1 -1
  33. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/born.rs +2 -2
  34. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/dfpt.rs +343 -43
  35. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/dielectric.rs +38 -13
  36. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/ewald.rs +257 -56
  37. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/ewald_hessian.rs +95 -28
  38. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/finite_field.rs +1 -0
  39. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/gamma.rs +111 -4
  40. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/hessian.rs +232 -58
  41. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/kpoints.rs +1 -1
  42. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/kscf.rs +558 -9
  43. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/mod.rs +15 -15
  44. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/multipole.rs +2 -2
  45. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/phased.rs +5 -5
  46. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/phonon.rs +27 -11
  47. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/python.rs +431 -126
  48. pm3_rs_python-0.2.5/src/rigid.rs +543 -0
  49. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/scf.rs +428 -16
  50. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/api_surface.rs +73 -6
  51. pm3_rs_python-0.2.5/tests/attribution.rs +426 -0
  52. pm3_rs_python-0.2.5/tests/data/mopac_oracle.tsv +204 -0
  53. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/molecules.rs +104 -0
  54. pm3_rs_python-0.2.5/tests/mopac_oracle.rs +443 -0
  55. pm3_rs_python-0.2.5/tests/pbc_cubic_identities.rs +280 -0
  56. pm3_rs_python-0.2.5/tests/pbc_scf_diagnosis.rs +379 -0
  57. pm3_rs_python-0.2.5/tests/test_api_key_contract.py +478 -0
  58. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/test_periodic_python_api.py +7 -1
  59. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/test_python_api.py +256 -5
  60. pm3_rs_python-0.2.5/third_party/README.md +60 -0
  61. pm3_rs_python-0.2.5/third_party/mopac/NOTICE +78 -0
  62. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/third_party/pyseqm/NOTICE +12 -3
  63. pm3_rs_python-0.2.5/third_party/rust-crates/LICENSES.txt +10114 -0
  64. pm3_rs_python-0.2.5/third_party/rust-crates/NOTICE +119 -0
  65. pm3_rs_python-0.2.3/THIRD_PARTY_NOTICES.md +0 -63
  66. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/LICENSE +0 -0
  67. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/README.md +0 -0
  68. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/docs/divide-and-conquer.md +0 -0
  69. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/python/pm3_rs/cli.py +0 -0
  70. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/python/pm3_rs/py.typed +0 -0
  71. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/basis.rs +0 -0
  72. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/bin/pm3_rs.rs +0 -0
  73. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/cell.rs +0 -0
  74. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/cmatrix.rs +0 -0
  75. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/corrections/d3.rs +0 -0
  76. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/corrections/hx.rs +0 -0
  77. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/corrections/periodic.rs +0 -0
  78. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/d3_c6_reference.csv +0 -0
  79. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/d3_r0ab.csv +0 -0
  80. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/d3_radii.csv +0 -0
  81. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/element_data.csv +0 -0
  82. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/pm3_global.csv +0 -0
  83. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/pm3_pair_parameters.csv +0 -0
  84. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/pm3_parameters.csv +0 -0
  85. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data/pm3_sparkles.csv +0 -0
  86. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/data_tables.rs +0 -0
  87. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dc/partition.rs +0 -0
  88. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dc/pattern.rs +0 -0
  89. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dipole.rs +0 -0
  90. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dual.rs +0 -0
  91. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/dual2.rs +0 -0
  92. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/fock.rs +0 -0
  93. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/frame.rs +0 -0
  94. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/hamiltonian.rs +0 -0
  95. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/integrals.rs +0 -0
  96. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/linalg.rs +0 -0
  97. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/math.rs +0 -0
  98. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/neighbor.rs +0 -0
  99. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/onecenter.rs +0 -0
  100. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/overlap_numeric.rs +0 -0
  101. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/params.rs +0 -0
  102. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/berry.rs +0 -0
  103. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/ewald_reference.rs +0 -0
  104. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/gradient.rs +0 -0
  105. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/kernel.rs +0 -0
  106. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/lo_to.rs +0 -0
  107. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/optimize.rs +0 -0
  108. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/pbc/screen.rs +0 -0
  109. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/repulsion.rs +0 -0
  110. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/rotations.rs +0 -0
  111. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/special.rs +0 -0
  112. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/src/system.rs +0 -0
  113. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/crystals.rs +0 -0
  114. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_berry.rs +0 -0
  115. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_born_charges.rs +0 -0
  116. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_dfpt_options.rs +0 -0
  117. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_dielectric.rs +0 -0
  118. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_finite_field.rs +0 -0
  119. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_lo_to.rs +0 -0
  120. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_phonon.rs +0 -0
  121. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/pbc_uhf_response.rs +0 -0
  122. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/test_ase_npt.py +0 -0
  123. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/tests/test_python.py +0 -0
  124. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/third_party/dftd3/NOTICE +0 -0
  125. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/third_party/h_bonds4/NOTICE +0 -0
  126. {pm3_rs_python-0.2.3 → pm3_rs_python-0.2.5}/third_party/mopac/LICENSE +0 -0
@@ -1,6 +1,285 @@
1
1
  # Changelog
2
2
 
3
- ## 0.2.3 — 2026-09-03
3
+ ## 0.2.5
4
+
5
+ A broad oracle, and the three defects it found.
6
+
7
+ 0.2.4 was validated against a dozen hand-picked molecules. This release compares
8
+ against **189, spanning every element PM3 is parameterized for**, and the answer
9
+ to "does pm3-rs reproduce MOPAC" is now a measurement rather than a belief: all
10
+ 189 agree on heat of formation, dipole, net atomic charges and ionization
11
+ potential. Getting there took fixing two real bugs and correcting one assumption
12
+ about what the oracle was.
13
+
14
+ ### Added
15
+
16
+ - **`tests/data/mopac_oracle.tsv`** — MOPAC v23.2.5's own answers for 189
17
+ molecules, generated by `tools/oracle/build_oracle_set.py` and read by
18
+ `tests/mopac_oracle.rs`. Geometries are MOPAC-optimized (so each fixture is a
19
+ PM3 stationary point) except where there is nothing to optimize — a noble-gas
20
+ pair has no bound minimum, a Sparkle no valence orbitals — and those rows say so
21
+ in a `relaxed` column. Coverage is the whole parameter set: main group, the
22
+ noble gases, the heavy s-block, Zn/Cd/Hg, the fifteen La–Lu Sparkles and MOPAC's
23
+ `Cb`/`+`/`-`. Francium is the one absentee, because neither code has parameters
24
+ for it and both refuse it.
25
+
26
+ **Agreement: 189 of 189**, worst heat of formation `7.2e-4 kcal/mol`, worst
27
+ dipole `1.1e-3 D`, worst charge `1.6e-4 e`.
28
+
29
+ - **`Pm3Options::stability`** ([`ScfStability`]) — an SCF stability search. A
30
+ converged SCF is one fixed point of an operator that has several, and nothing in
31
+ a convergence test says it is the ground state. When the frontier gap is small
32
+ enough that the orbital ordering was in doubt, the molecule is re-solved from
33
+ deliberately different starting points and the lowest solution kept.
34
+
35
+ - **`Pm3Options::guess`** ([`ScfGuess`]) — the starting density is now selectable
36
+ (`Sad`, `Core`, `SymmetryBroken`), because it decides *which* solution is
37
+ reached and not merely how fast.
38
+
39
+ - **`Pm3Options::mmok`** — MOPAC's `MMOK` molecular-mechanics amide correction,
40
+ `K·sin²(O=C–N–H)` per amide hydrogen with `K = 7.1853 kcal/mol`. **Off by
41
+ default**, unlike MOPAC. Available from Python and the CLI as a `+mmok` suffix
42
+ on the method (`method="pm3+mmok"`, `--method pm3-d3h4+mmok`). Written over the
43
+ crate's dual numbers, so its gradient and Hessian contributions come from the
44
+ same expression and an optimization with it on minimizes what it reports.
45
+
46
+ ### Fixed
47
+
48
+ - **The SCF converged to excited solutions on seven molecules**, by up to 279
49
+ kcal/mol, and reported them as converged. `2-butyne` (+279), `ketene` (+204),
50
+ `C₂F₄` (+166), `C₂Cl₄` (+157), the `O₂` triplet (+135), `NH₂•` (+37) and the
51
+ methyl radical (+111) each reached a self-consistent state that obeyed the
52
+ aufbau principle among its own eigenvalues and was not the ground state. Every
53
+ one of them now agrees with MOPAC.
54
+
55
+ Three separate causes, which is why no single change fixed them: DIIS stepping
56
+ over the fixed point (`2-butyne`, `ketene`), the SAD guess being in the wrong
57
+ basin (`C₂Cl₄`), and the restricted iteration having no path to the solution at
58
+ all (`C₂F₄` — reachable only through the unrestricted loop, which converges
59
+ spin-pure there and so returns a restricted state). The search covers all three
60
+ by trying them and keeping the lowest.
61
+
62
+ It fires on 6 of the 189 and costs nothing on the rest: the frontier gap
63
+ separates the two populations cleanly (every wrong solution below 4 eV, every
64
+ first-try-correct one above 5.7), so the trigger is a gap threshold sitting in
65
+ the empty band between. The whole 189-molecule set solves in under a second.
66
+
67
+ - **The unrestricted path started from core-Hamiltonian orbitals**, which is the
68
+ wrong guess. From SAD, the `O₂` triplet and the amino radical land on MOPAC's
69
+ answer instead of 135 and 37 kcal/mol above it, and the methoxy radical takes a
70
+ few dozen iterations instead of about 1600.
71
+
72
+ - **`Pm3Result` gained `scf_paths_tried` and `scf_improvement_ev`**, so a caller
73
+ can see when the search changed the answer rather than having to infer it.
74
+
75
+ ### Changed
76
+
77
+ - **MOPAC's default is not PM3, and the oracle now says so.** MOPAC applies `MMOK`
78
+ unless given `NOMM`, adding a classical amide torsion term after the SCF. It
79
+ changes no orbital and no total energy, which is what makes it easy to mistake
80
+ for a discrepancy: acetamide's density, dipole and every eigenvalue agreed with
81
+ ours to six figures while its heat of formation was 0.66 kcal/mol away. Every
82
+ row of the oracle is now generated with `NOMM`, so the file is plain PM3.
83
+
84
+ Both the form and the constant of the correction were measured against MOPAC
85
+ rather than copied, by scanning formamide's torsion with `MMOK` and `NOMM` and
86
+ subtracting; the fit is exact to the digits MOPAC prints.
87
+
88
+ ## 0.2.4
89
+
90
+ Most of what was broken in 0.2.3 was not the physics but the **surface**: things
91
+ the library could do that no caller could ask for, and one assumption that was
92
+ true at Γ and quietly false everywhere else.
93
+
94
+ ### Fixed
95
+
96
+ - **A meshed `D(q)` was wrong at `q = 0`** — by 300%, anisotropic on a cubic
97
+ crystal, and with the *sign* reversed, so a stable structure reported an
98
+ imaginary optical mode. One substitution made in two places: `P(T) = P(0)`,
99
+ which **is** Γ sampling (one k-point cannot tell images apart) and is false on a
100
+ mesh, where `P(0)` is the Brillouin-zone average while `P(T)` decays with `T`.
101
+ `phased_skeleton` asserted it for the skeleton — the dominant half — and
102
+ `bare_blocks` asserted it again for the response's exchange term. NaCl `3×3×3`
103
+ now gives an isotropic `D(0)` agreeing with a central difference of the analytic
104
+ forces to `1e-4`. Γ sampling is unchanged bit for bit: there the two expressions
105
+ are the same number.
106
+
107
+ The clue had been recorded a release earlier and not used — the error converged
108
+ smoothly with mesh size, **0% at `1³`** rising to 77% at `7³`. Zero at `1³` is
109
+ the tell: a `1×1×1` mesh *is* Γ.
110
+
111
+ - **`native.optimize()` returned no `energy_ev`, and `relax()` no `iterations`
112
+ or heat of formation.** Reading the docs for one and applying them to the other
113
+ ended a structure optimization with a `KeyError` from the call that had done the
114
+ most work. All three optimizers now answer to the same names, held by a
115
+ table-driven contract test over every native function.
116
+
117
+ - **`pm3-rs optimize big.xyz --dc 4.8` optimized nothing** and exited 0: the
118
+ divide-and-conquer branch returned before the command was read. The same held
119
+ for `frequencies`, `hessian`, `gradient`, `molden`, `ir` and `charges`.
120
+
121
+ - **Translations and rotations are removed by projection**, not by being small.
122
+ The rigid-body subspace is discovered from the geometry — 2 rotations for a
123
+ linear molecule, 0 for an atom — and those modes are **exactly** `0.00 cm⁻¹`.
124
+ Three different thresholds (`< 50`, `< 100`, `< 300 cm⁻¹`) for the same quantity
125
+ are gone, as is `SOFT_MODE_FLOOR`.
126
+
127
+ - **The Γ acoustic sum rule was applied row-wise**, which is not symmetric, and
128
+ the eigensolver reads one triangle — so the rule that had just been imposed was
129
+ partly undone and `PeriodicPhonons::hessian` came back non-symmetric. It is a
130
+ symmetric projection now, and `acoustic_residual_cm` reports the **pre**-projection
131
+ residual, which is the number worth seeing rather than the one the correction
132
+ just flattened.
133
+
134
+ - **`force_tol` was converted the wrong way** in the new CLI and partitioned
135
+ paths — a gradient scales the opposite way to a length, so the factor is
136
+ `BOHR_TO_ANGSTROM`. The tolerance was 3.57× looser than asked for, the optimizer
137
+ stopped early, and it still reported `converged: true`. Now
138
+ `constants::force_tol_to_au` and `stress_tol_to_au`, with a test that pins the
139
+ direction.
140
+
141
+ - **A stalled periodic SCF had no recourse on the Γ path and one rung on the
142
+ k-point path.** Damping now goes ahead of smearing on both, because its guarantee
143
+ is stronger: it changes the path and not the equations, and both loops measure
144
+ convergence against the *undamped* step, so a converged damped run solves what was
145
+ asked. Silicon on a 3×3×3 mesh is rescued (−145.536995 eV); diamond still fails,
146
+ because its residual alternates rather than decays and no mixing fraction reaches
147
+ a symmetry-breaking limit cycle. There is deliberately no third rung: a level
148
+ shift converges diamond to a *different solution*, which is worse than not
149
+ converging, so it stays in the diagnosis for the caller to choose.
150
+
151
+ - **A periodic response returned an incomplete answer for a partially occupied
152
+ cell, silently.** The occupation factors are the metallic
153
+ `(f_n − f_m)/(ε_n − ε_m)` form, which makes it look handled; what is missing is
154
+ the Fermi-level shift, and the sum skips the near-degenerate pairs that carry the
155
+ Fermi-surface term. It is refused now — keyed on whether the occupations came out
156
+ **integral**, not on whether smearing was used, so a gapped cell under a small
157
+ smearing still runs. Same `T·S` certificate the SCF rescue uses.
158
+
159
+ - Seventeen lines across three files had been committed after a PowerShell cp932
160
+ round-trip. In `pbc/mod.rs` the casualties were the electrostatics derivation:
161
+ `√(r² + a)` had lost its root and bracket and `[ W_KO(r) − W_point(r) ]` its
162
+ minus sign, so a formula read as noise rather than as wrong.
163
+
164
+ ### Added
165
+
166
+ - **Orbital energies, coefficients and occupations** (`native.orbitals`,
167
+ `pm3-rs orbitals`, `--coefficients`). Coefficients come with `ao_labels`, and
168
+ the frontier is taken across both spin channels — a radical's β LUMO sits below
169
+ its α one.
170
+ - **Phonon eigenvectors.** They were computed and discarded. `modes` (real) at Γ,
171
+ `modes_real`/`modes_imag` at a wavevector, through `native.phonons` and
172
+ `PM3.get_phonons`.
173
+ - **`Pm3Options::cphf_max_iter`** and a `cphf_max_iter=` argument on the six
174
+ Python entry points that run a coupled-perturbed solve. It was two hard-coded
175
+ `400`s, so a stiff response could only be rescued by editing the crate.
176
+ - **Divide-and-conquer geometry optimization** from the CLI (`--dc` with
177
+ `optimize`), `native.divide_and_conquer_optimize` and `PM3`.
178
+ - **Molden `[STO]`** (`--sto`, `basis="sto"`) beside the default `[GTO]`, and
179
+ `--output` to say where the file goes.
180
+ - **`--pbc` in the spelling the reader has in mind**: `1,0,1`, `101`, `xz`,
181
+ `x,z`, `true,false,true`, `none`.
182
+ - **The periodic optimizer's controls**: `--relax-cell`, `--fixed-cell`,
183
+ `--max-steps`, `--force-tol`, `--stress-tol`, `--pressure`. `write_xyz` now
184
+ carries the cell as extended-XYZ `Lattice="..."`, which was silent data loss the
185
+ moment variable-cell relaxation existed.
186
+ - `--kpts` and `--q` on the CLI; `native.divide_and_conquer_forces`; periodic
187
+ `hessian` from Python.
188
+
189
+ ### Performance
190
+
191
+ - **Periodic runs are about 10× faster.** A 48-atom Γ single point went from
192
+ 17.7 s to 1.82 s; the Python test suite from 396 s to 72 s. The numbers are
193
+ unchanged.
194
+
195
+ None of the three optimizations that had been planned were the problem. Measured
196
+ first, as `examples/born_profile.rs` established: the serial k-point loop was
197
+ 31–45% of a *meshed* run and nothing of a Γ one, the serial Fock build was
198
+ **0.4%** of an iteration at every size from 6 to 72 atoms, and an optimization
199
+ step cost 1.6 gradients rather than the up-to-30 the line search was feared to be
200
+ paying. It was the Ewald sum, at 89–98% of every iteration — and inside that,
201
+ 90.5 s of real space against 2.5 s of reciprocal, so the guess *within* the guess
202
+ was wrong too.
203
+
204
+ The real-space sum evaluated `erfc` and `exp` for every pair inside the cutoff,
205
+ **twice per iteration**, at distances that do not move while the charges do. The
206
+ kernel is geometry and now sits in `EwaldContext` beside the phase table. Real
207
+ space fell 32×. `examples/periodic_profile.rs` and `PM3_GAMMA_PROFILE=1` are the
208
+ measurement.
209
+
210
+ - **A Γ-point Hessian is 2.3× faster, and its Ewald phase 210×.** 48 atoms: 51.3 s
211
+ to 22.8 s, with the Ewald second derivative going from 31.60 s to 0.15 s. Numbers
212
+ unchanged.
213
+
214
+ Profiled first, again, and again it was not the assumption: the coupled-perturbed
215
+ response — the phase everyone expects to dominate — was 38%, the skeleton rounded
216
+ to zero, and the Ewald term was 62%. Its inner sum turned out to be a **structure
217
+ factor written out longhand**: what the site-pair loop accumulates for an atom
218
+ pair is `Re[S_A S_B*]`, so `O(N_G · N_sites²)` becomes `N_sites` to build the
219
+ factors plus `N_atoms²` to combine them — and PM3 puts four to five multipole
220
+ sites on every heavy atom. `examples/hessian_profile.rs` and
221
+ `PM3_HESSIAN_PROFILE=1` are the measurement. The Hessian is now 96% response,
222
+ which is where the next look starts.
223
+
224
+ ### Licensing
225
+
226
+ - **The wheel shipped `LICENSE` alone.** `_native.pyd` has the MOPAC-derived PM3
227
+ parameter tables compiled into it, so `pip install` distributed Apache-2.0
228
+ material without the licence text or the attribution. It now carries
229
+ `THIRD_PARTY_NOTICES.md`, the Apache text and every `NOTICE`.
230
+ - **Nothing recorded the ~60 Rust crates statically linked into the binaries.**
231
+ `faer`, `rayon`, `pyo3` and their transitive closure are compiled into
232
+ everything shipped, and MIT asks for the copyright notice to travel with a
233
+ substantial portion. `third_party/rust-crates/` now carries the index and 524 KB
234
+ of verbatim texts, generated by `tools/collect_rust_notices.py`.
235
+
236
+ Two things the graph turned up: `faer` declares MIT and ships **no MIT text**
237
+ (what it ships is its own upstream Eigen/LAPACK/SuiteSparse attributions), and
238
+ `unicode-ident` is `(MIT OR Apache-2.0) **AND** Unicode-3.0` — the only entry
239
+ where choosing a licence does not settle the question.
240
+ - `tests/attribution.rs` holds all of it to the tree: every linked crate is in the
241
+ notice, the two distributions agree, no upstream source is tracked, and no file
242
+ has been through a codepage round-trip.
243
+
244
+ ### Testing
245
+
246
+ - **Every CLI command against every flag**, mechanically — the matrix that found
247
+ `--dc` swallowing seven commands. Plus a second layer running each command
248
+ through the installed console script, where only `energy` had ever been
249
+ exercised across the Python↔Rust boundary.
250
+ - **All fifteen La–Lu Sparkles against MOPAC** at `1e-5 kcal/mol`, re-run through
251
+ the executable rather than compared to a stored JSON.
252
+ - `tests/pbc_cubic_identities.rs` has no `#[ignore]`s left, and neither does the
253
+ suite.
254
+
255
+ ### Known limits
256
+
257
+ - PM3's transition metals are **Zn, Cd and Hg** and nothing else. `TiO₂`, `SrTiO₃`,
258
+ `LiCoO₂` and ferrocene are outside the model, not unimplemented. Those three are
259
+ enough for real organometallic chemistry: `examples/phonon_structures.rs` and
260
+ `examples/organometallic_bands.rs` cover `Zn(CH₃)₂` and `Hg(CH₃)₂` — linear
261
+ dialkyls with genuine `M–C` σ bonds — and the `Cd(CN)₂` framework.
262
+
263
+ - **Phonons must be evaluated at PM3's own geometry, not at the experimental one**,
264
+ and for ionic solids the two are far apart. A Hessian off a stationary point is
265
+ not a phonon spectrum, so the experimental lattice constant is the wrong place to
266
+ ask. At the lattice constant PM3 actually prefers, MgO's spectrum is clean — but
267
+ that equilibrium sits 12.7% *above* the measured value, and the highest mode comes
268
+ out at 745 cm⁻¹ against 401 measured.
269
+
270
+ For NaCl the energy is still falling at the compressive edge of the range Γ
271
+ sampling can hold, so PM3 has no bound rocksalt minimum there at all. Neither is
272
+ a defect in the second-derivative code; PM3 was fitted to molecular heats of
273
+ formation and never saw a Madelung lattice.
274
+
275
+ - Where PM3 *is* on firm ground is the molecular chemistry it was fitted to.
276
+ Dimethylzinc relaxes to a Zn–C bond of 1.924 Å against 1.930 Å measured, and its
277
+ symmetric Zn–C stretch comes out at 599 cm⁻¹ against ≈615.
278
+
279
+ - A periodic SCF on some cubic cells with an even Monkhorst–Pack mesh still
280
+ converges to a mesh-dependent solution; see `docs/pbc.md`.
281
+
282
+ ## 0.2.3
4
283
 
5
284
  One performance fix, found by running real crystals rather than by reading code,
6
285
  and two reporting defects it turned up on the way.
@@ -45,7 +324,7 @@ and two reporting defects it turned up on the way.
45
324
  - `cargo fmt` across the tree: 0.2.2 shipped 26 files that rustfmt disagreed
46
325
  with, all of them new or edited in that release.
47
326
 
48
- ## 0.2.2 — 2026-09-03
327
+ ## 0.2.2
49
328
 
50
329
  The response properties a dynamical matrix was already most of the way to, and a
51
330
  Berry phase to check them from outside. The largest fix is none of those: a
@@ -189,7 +468,7 @@ removing the intra-atomic `dd` moment the phase omits (`PM3_BORN_NO_DD=1`)
189
468
  collapses that to `1.9e-4`. The two formalisms agree on everything except one
190
469
  identified physical term.
191
470
 
192
- ## 0.2.1 — 2026-09-01
471
+ ## 0.2.1
193
472
 
194
473
  Phonons everywhere the Γ point already worked, an external electric field, and
195
474
  the wavefunction outputs that go with it. Three defects that had shipped in
@@ -377,7 +656,7 @@ test that could have caught them ran.
377
656
  existed. A test now compares every stub signature against the extension, and
378
657
  another asserts `pm3_rs.native` can pass every argument the extension accepts.
379
658
 
380
- ## 0.2.0 — 2026-08-27
659
+ ## 0.2.0
381
660
 
382
661
  Periodic boundary conditions. Molecular results are unchanged except for one
383
662
  bug fix, noted below.
@@ -653,7 +653,7 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
653
653
 
654
654
  [[package]]
655
655
  name = "pm3-rs"
656
- version = "0.2.3"
656
+ version = "0.2.5"
657
657
  dependencies = [
658
658
  "faer",
659
659
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pm3-rs"
3
- version = "0.2.3"
3
+ version = "0.2.5"
4
4
  edition = "2021"
5
5
  license = "GPL-3.0-or-later"
6
6
  authors = ["ss0832"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pm3-rs-python
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Programming Language :: Rust
@@ -19,6 +19,15 @@ Requires-Dist: numpy>=1.21
19
19
  Requires-Dist: ase>=3.22 ; extra == 'ase'
20
20
  Provides-Extra: ase
21
21
  License-File: LICENSE
22
+ License-File: THIRD_PARTY_NOTICES.md
23
+ License-File: third_party/README.md
24
+ License-File: third_party/mopac/LICENSE
25
+ License-File: third_party/dftd3/NOTICE
26
+ License-File: third_party/h_bonds4/NOTICE
27
+ License-File: third_party/mopac/NOTICE
28
+ License-File: third_party/pyseqm/NOTICE
29
+ License-File: third_party/rust-crates/NOTICE
30
+ License-File: third_party/rust-crates/LICENSES.txt
22
31
  Summary: PM3 semiempirical method with periodic boundary conditions, divide-and-conquer, a Python-native API and an ASE calculator
23
32
  Keywords: chemistry,computational-chemistry,quantum-chemistry,semiempirical,pm3,nddo,mopac,ase,periodic-boundary-conditions,dft
24
33
  Author: ss0832
@@ -0,0 +1,115 @@
1
+ # Third-party notices
2
+
3
+ `pm3-rs` is GPL-3.0-or-later. This document records the provenance of its
4
+ scientific parameters and reference algorithms, with the citations.
5
+
6
+ **[`third_party/README.md`](third_party/README.md) is the index**: one table
7
+ saying, for each upstream project, what `pm3-rs` takes from it, under which
8
+ license, and which files here hold the terms. Each `third_party/<name>/NOTICE`
9
+ records the same for that project in detail. Start there if the question is
10
+ "what is owed to whom"; this file is the bibliography.
11
+
12
+ No third-party source code is vendored in this repository. What crosses the
13
+ boundary is published numeric parameters and published working equations.
14
+
15
+ That is true of the *repository*. It is not true of the **binaries**: the Rust
16
+ dependency graph is statically linked into `_native.pyd` and the `pm3-rs`
17
+ command, so around sixty crates are compiled into everything this project ships.
18
+ They are recorded separately in
19
+ [`third_party/rust-crates/NOTICE`](third_party/rust-crates/NOTICE), with the
20
+ verbatim texts in `LICENSES.txt` beside it, because the obligation they carry is
21
+ a different one: MIT asks for the copyright notice to travel with the binary,
22
+ not for a citation in a bibliography. See "Rust dependencies" below.
23
+
24
+ ## PM3
25
+
26
+ The PM3 model is described by:
27
+
28
+ - J. J. P. Stewart, "Optimization of parameters for semiempirical methods I.
29
+ Method", *J. Comput. Chem.* **10**, 209-220 (1989),
30
+ DOI 10.1002/jcc.540100208.
31
+ - J. J. P. Stewart, "Optimization of parameters for semiempirical methods II.
32
+ Applications", *J. Comput. Chem.* **10**, 221-264 (1989),
33
+ DOI 10.1002/jcc.540100209.
34
+
35
+ The machine-readable PM3 tables under `src/data/` are generated by
36
+ `tools/extract_pm3_params.py` from open-source MOPAC v23.2.5:
37
+
38
+ - repository: https://github.com/openmopac/mopac, tag `v23.2.5`
39
+ - license: Apache-2.0, retained at `third_party/mopac/LICENSE`
40
+ - input files: `parameters_for_PM3_C.F90`,
41
+ `parameters_for_PM3_Sparkles_C.F90`, `alpb_and_xfac_pm3.F90`, and
42
+ `parameters_C.F90`
43
+
44
+ The extracted data include element parameters, per-element Gaussian terms,
45
+ PM3 pair factors, shell/reference-atom data, La-Lu Sparkles, and MOPAC special
46
+ atom codes. MOPAC is also used as the executable oracle for plain PM3.
47
+
48
+ The Rust implementation follows the MOPAC reference equations for derived
49
+ multipoles, one- and two-center NDDO integrals, PM3 core-core repulsion, SCF
50
+ bookkeeping, and special zero-orbital cores. The implementation is original
51
+ Rust code; no MOPAC Fortran source is redistributed.
52
+
53
+ MOPAC contributes in three distinct roles — redistributed parameter data,
54
+ followed-but-not-copied equations, and a build-time oracle that never ships —
55
+ which carry different obligations. [`third_party/mopac/NOTICE`](third_party/mopac/NOTICE)
56
+ separates them and names the upstream file behind each.
57
+
58
+ ## D3, H4, and X corrections
59
+
60
+ - D3: S. Grimme, J. Antony, S. Ehrlich, and H. Krieg, *J. Chem. Phys.*
61
+ **132**, 154104 (2010). Embedded C6/radius tables were extracted from the
62
+ Apache-2.0 MOPAC implementation; see `third_party/dftd3/NOTICE`.
63
+ - PM3-D3: the zero-damping constants `s6=1`, `s8=0.612`, `rs6=1.345`,
64
+ `rs8=1`, `alpha6=14`, and `alpha8=16` are verified against the public MOPAC
65
+ 5.022mn `anad3.f` implementation distributed by the University of Minnesota.
66
+ - D3H4: J. Rezac and P. Hobza, *J. Chem. Theory Comput.* **8**, 141-151
67
+ (2012), DOI 10.1021/ct200751e. PM3-specific D3/H4/H-H coefficients follow
68
+ the authors' public implementation and Cuby interface parameter table; see
69
+ `third_party/h_bonds4/NOTICE`.
70
+ - X: J. Rezac and P. Hobza, *Chem. Phys. Lett.* **506**, 286-289 (2011),
71
+ DOI 10.1016/j.cplett.2011.03.009. Parameters follow MOPAC
72
+ `corrections/disp_DnX.F90`.
73
+
74
+ ## PySEQM
75
+
76
+ The closed-form s/p two-center integral and rotation kernels in
77
+ `src/integrals.rs` and `src/overlap.rs` follow the reference equations
78
+ implemented by PySEQM:
79
+
80
+ - https://github.com/lanl/PYSEQM
81
+ - G. Zhou et al., *J. Chem. Theory Comput.* **16**, 4951 (2020)
82
+ - BSD-3-Clause; see `third_party/pyseqm/NOTICE`
83
+
84
+ ## Rust dependencies
85
+
86
+ Everything above concerns *scientific* provenance — parameters and equations —
87
+ and none of it is code that ships. The Rust crates are the opposite case: no
88
+ science crosses from them, and all of their code does.
89
+
90
+ `faer` (linear algebra), `rayon` (parallelism) and `pyo3` (the Python bindings)
91
+ are the three direct dependencies; with their transitive closure that is around
92
+ sixty crates, statically linked into `_native.pyd` and the `pm3-rs` executable.
93
+ Most are MIT, which requires the copyright notice and the permission notice to
94
+ be included in "all copies or substantial portions of the Software" — which a
95
+ static binary is. A handful carry other terms: `atomic-wait` is BSD-2-Clause,
96
+ `bytemuck` offers Zlib, `target-lexicon` is Apache-2.0 with the LLVM exception,
97
+ and `unicode-ident` is `(MIT OR Apache-2.0) AND Unicode-3.0`, where the `AND`
98
+ means the Unicode terms apply on top rather than as an alternative.
99
+
100
+ - index, with the SPDX expression and files found per crate:
101
+ [`third_party/rust-crates/NOTICE`](third_party/rust-crates/NOTICE)
102
+ - the verbatim texts, with their copyright lines:
103
+ `third_party/rust-crates/LICENSES.txt`
104
+ - regenerate with `python tools/collect_rust_notices.py` after changing a
105
+ dependency; `tests/attribution.rs` fails if the graph and the index disagree
106
+
107
+ `faer` additionally carries upstream attributions of its own — Eigen (MPL-2.0),
108
+ LAPACK and SuiteSparse (BSD) — which are reproduced in `LICENSES.txt` along with
109
+ everything else, rather than being left one level up the chain.
110
+
111
+ Apache-2.0 and BSD-3-Clause are compatible with GPL-3.0-or-later for this use,
112
+ as are MIT, BSD-2-Clause, Zlib, MPL-2.0 and Apache-2.0-WITH-LLVM-exception; the
113
+ compatibility is one-way, and this project is the GPL end of it. Numeric
114
+ scientific parameter values are retained with provenance regardless of their
115
+ copyright status.
@@ -461,6 +461,46 @@ each image's displacement scaled by its own `e^{iq·T}`, validated by folding a
461
461
  hydrogen-bonded chain onto its own doubled cell. The Γ-point analytic Hessian
462
462
  reaches 1D and 2D the same way, its Ewald block being the phased sum at `q = 0`.
463
463
 
464
+ ## A meshed `D(q)` was wrong at `q = 0`, and is fixed in 0.2.4
465
+
466
+ Through 0.2.3, `dynamical_matrix_on_mesh` / `phonons(..., kpts=...)` / `--kpts`
467
+ with `--q` returned a badly wrong answer at `q = 0`. Measured on rocksalt NaCl at
468
+ its experimental lattice constant, `3×3×3` mesh, in eV/Ų:
469
+
470
+ | | `D_xx` | `D_yy` | `D_zz` |
471
+ |---|---:|---:|---:|
472
+ | central difference of the analytic forces | +2.16 | +2.16 | +2.16 |
473
+ | `dynamical_matrix_on_mesh`, 0.2.3 | **−4.61** | **−1.28** | **−1.28** |
474
+ | `dynamical_matrix_on_mesh`, 0.2.4 | +2.16 | +2.16 | +2.16 |
475
+
476
+ Three things had been wrong at once — the magnitude, the isotropy a cubic crystal
477
+ must have, and the *sign*, which reported an imaginary optical mode for a
478
+ structure that is a minimum.
479
+
480
+ **The cause was one substitution made in two places.** `P(T) = P(0)` — one density
481
+ matrix serving every lattice image — *is* Γ sampling: a single k-point cannot tell
482
+ images apart, so it holds there by construction. On a mesh it is false, because
483
+ `P(0)` becomes the Brillouin-zone average while `P(T)` decays with `T`.
484
+
485
+ - `phased_skeleton` passed one density matrix to `pair_block` for every image. The
486
+ skeleton is the larger half of `D(q)`, so a meshed run built its dominant term
487
+ from the wrong density.
488
+ - `bare_blocks` read `exchange_scale · P^σ(0)` for the exchange part of each bare
489
+ perturbation, where the response needs `P^σ(T)`. `SpinChannel::p_images` already
490
+ carried the right thing under both samplings; nothing was reading it.
491
+
492
+ Both now read the per-image density. Γ sampling is unchanged, bit for bit: at Γ
493
+ the two expressions are the same number.
494
+
495
+ The clue that identified it had been recorded for a release and not used — the
496
+ error converged smoothly with mesh size, 0% at `1³` rising to 77% at `7³`. Zero at
497
+ `1³` is the tell: a `1×1×1` mesh *is* Γ, where the substitution is exactly true.
498
+
499
+ `tests/pbc_cubic_identities.rs` holds both identities on a mesh, and
500
+ `pbc::dfpt::tests::the_meshed_skeleton_is_where_the_cubic_symmetry_breaks` runs
501
+ the skeleton without the response under both samplings — a separation no public
502
+ entry point offers, and what made the second occurrence findable.
503
+
464
504
  ## Response properties
465
505
 
466
506
  Everything in this section is a contraction of the same first-order density the
@@ -597,10 +637,40 @@ so it is known rather than discovered:
597
637
  level shifts to 5 eV, and at a thousand iterations. Two eV of smearing does converge — to a
598
638
  *different* solution 200 eV away.
599
639
 
600
- The chemical potential is what gives it away: it swings by an electronvolt every iteration as
601
- occupations flip. An even Γ-centred mesh on a cubic cell puts every one of its points on a
602
- zone-boundary symmetry point, where bands meet; a Fermi level inside a degenerate manifold has
603
- no stable filling to find, and the bisection reassigns it each pass.
640
+ The chemical potential is what gives it away: it swings by an electronvolt every iteration. An
641
+ even Γ-centred mesh on a cubic cell puts every one of its points on a zone-boundary symmetry
642
+ point, where bands meet.
643
+
644
+ > **The mechanism above is now measured, and the "occupations flip" half of it is wrong.**
645
+ > `PM3_KSCF_TRACE=1` reports the occupation-flip count and the per-atom populations alongside
646
+ > the residual. On the **primitive** two-atom cell at 5.64 Å with a `2×2×2` mesh, the flip count
647
+ > is **zero at every iteration** — the occupations never change sides — while a full **1.1
648
+ > electrons** move between Na and Cl each pass and `μ` follows across twelve electronvolts. It
649
+ > is charge sloshing, not a Fermi level trapped in a degenerate manifold; the charge moves first
650
+ > and `μ` follows it. That cell also *recovers*, converging unaided to `−341.786 eV`.
651
+ >
652
+ > **And converging is not the point.** The primitive cell on a `2×2×2` mesh converges, cleanly,
653
+ > to a state with **−2.10 electrons of charge on the sodium**, where every mesh from `3³` to
654
+ > `7³` puts `+0.16` to `+0.24` there and agrees on the energy to 0.06 eV. A sodium that has
655
+ > gained two electrons is not a chemical result, and nothing in the energy, the residual or the
656
+ > `converged` flag says so. `KpointResult::charge_swing` does: the even mesh swings **1.44
657
+ > electrons** on its way there against the odd mesh's 0.29. **Check it, and check the charges.**
658
+ >
659
+ > **It is not a mixing problem, and three standard remedies say so.** A deeper Pulay history
660
+ > (8 → 24) moves diamond's residual from `3.72e-5` to `3.41e-5`; Kerker preconditioning of the
661
+ > atomic-charge channel makes NaCl's sloshing *worse* (1.44 → 2.56 electrons of swing); handing
662
+ > damping over to the accelerator, as the Γ path does, converges diamond and silicon and breaks
663
+ > four k-point identities. What the Kerker experiment showed instead is the actual shape of the
664
+ > problem: **three different mixers reach three different converged solutions on the same cell
665
+ > and mesh** — `−341.79`, `−366.34` and `−379.22 eV`, with `−2.10`, `−4.35` and `−4.94`
666
+ > electrons on the sodium — while every odd mesh agrees on `−327.45` and `+0.17`. The
667
+ > fixed-point map has several attractors at that sampling and the mixer picks among them. No
668
+ > preconditioner fixes that. Use an odd mesh, and check `charge_swing`.
669
+ >
670
+ > The conventional-cell failure `tests/crystals.rs` pins is a different system and has not been
671
+ > re-measured with the trace. Run it before repeating the explanation above.
672
+ > `examples/scf_hardening.rs` has the ladder, and a failed SCF now carries its own diagnosis in
673
+ > the error rather than only a residual.
604
674
 
605
675
  Odd meshes do not sit on those points. `3×3×3` converges in eighteen iterations, and the answer
606
676
  is right: energies agree to 0.02 eV and charges to 0.02 electrons across `3×3×3`, `4×4×4` and